diff --git a/.env.development b/.env.development index cf60c666..fbddcd27 100644 --- a/.env.development +++ b/.env.development @@ -1,3 +1,4 @@ #REACT_APP_API_HOST=http://localhost:3334/ REACT_APP_API_HOST=https://pig.staging.vault48.org/ +#REACT_APP_API_HOST=https://pig.vault48.org/ REACT_APP_REMOTE_CURRENT=https://pig.staging.vault48.org/static/ diff --git a/src/redux/lab/api.ts b/src/api/lab/index.ts similarity index 95% rename from src/redux/lab/api.ts rename to src/api/lab/index.ts index 87e6a7e8..6c9934d9 100644 --- a/src/redux/lab/api.ts +++ b/src/api/lab/index.ts @@ -5,7 +5,7 @@ import { GetLabNodesResult, GetLabStatsResult, GetLabUpdatesResult, -} from '~/redux/lab/types'; +} from '~/types/lab'; export const getLabNodes = ({ after }: GetLabNodesRequest) => api diff --git a/src/components/main/Header/index.tsx b/src/components/main/Header/index.tsx index 489c2657..91a917c1 100644 --- a/src/components/main/Header/index.tsx +++ b/src/components/main/Header/index.tsx @@ -17,13 +17,12 @@ import * as AUTH_ACTIONS from '~/redux/auth/actions'; import { IState } from '~/redux/store'; import isBefore from 'date-fns/isBefore'; import { Authorized } from '~/components/containers/Authorized'; -import { useShallowSelect } from '~/hooks/data/useShallowSelect'; -import { selectLabUpdatesNodes } from '~/redux/lab/selectors'; import { Button } from '~/components/input/Button'; import { useFlowStore } from '~/store/flow/useFlowStore'; import { observer } from 'mobx-react'; import { useShowModal } from '~/hooks/modal/useShowModal'; import { Dialog } from '~/constants/modal'; +import { useGetLabStats } from '~/hooks/lab/useGetLabStats'; const mapStateToProps = (state: IState) => ({ user: pick(['username', 'is_user', 'photo', 'last_seen_boris'])(selectUser(state)), @@ -49,9 +48,9 @@ const HeaderUnconnected: FC = observer( authOpenProfile, }) => { const [is_scrolled, setIsScrolled] = useState(false); - const labUpdates = useShallowSelect(selectLabUpdatesNodes); const { updated: flowUpdates } = useFlowStore(); const onLogin = useShowModal(Dialog.Login); + const labStats = useGetLabStats(); const onScroll = useCallback(() => { const active = window.scrollY > 32; @@ -76,7 +75,7 @@ const HeaderUnconnected: FC = observer( [boris_commented_at, is_user, last_seen_boris] ); - const hasLabUpdates = useMemo(() => labUpdates.length > 0, [labUpdates]); + const hasLabUpdates = useMemo(() => labStats.updates.length > 0, [labStats.updates]); const hasFlowUpdates = useMemo(() => flowUpdates.length > 0, [flowUpdates]); return ( diff --git a/src/containers/lab/LabGrid/index.tsx b/src/containers/lab/LabGrid/index.tsx index 620aeef8..7feeb26f 100644 --- a/src/containers/lab/LabGrid/index.tsx +++ b/src/containers/lab/LabGrid/index.tsx @@ -1,11 +1,11 @@ -import React, { FC, useMemo } from 'react'; +import React, { FC } from 'react'; import Masonry from 'react-masonry-css'; import styles from './styles.module.scss'; import { LabNode } from '~/components/lab/LabNode'; import { EMPTY_NODE, NODE_TYPES } from '~/constants/node'; import { values } from 'ramda'; -import { useLabPagination } from '~/hooks/lab/useLabPagination'; import { useLabContext } from '~/utils/context/LabContextProvider'; +import { InfiniteScroll } from '~/components/containers/InfiniteScroll'; interface IProps {} @@ -27,11 +27,7 @@ const LoadingNode = () => ( ); const LabGrid: FC = () => { - const { isLoading, nodes, onLoadMore } = useLabContext(); - - const columns = useMemo(() => Array.from(document.querySelectorAll(`.${styles.column}`)), []); - - useLabPagination(isLoading, columns, onLoadMore); + const { isLoading, nodes, hasMore, loadMore } = useLabContext(); if (isLoading) { return ( @@ -54,20 +50,22 @@ const LabGrid: FC = () => { } return ( - - {nodes.map(node => ( - - ))} - + + + {nodes.map(node => ( + + ))} + + ); }; diff --git a/src/hooks/flow/useFlow.ts b/src/hooks/flow/useFlow.ts index 4d7c73f8..1514f0a4 100644 --- a/src/hooks/flow/useFlow.ts +++ b/src/hooks/flow/useFlow.ts @@ -1,22 +1,21 @@ -import { useShallowSelect } from '~/hooks/data/useShallowSelect'; import { useFlowLayout } from '~/hooks/flow/useFlowLayout'; -import { selectLabUpdatesNodes } from '~/redux/lab/selectors'; import { useMemo } from 'react'; import { useFlowLoader } from '~/hooks/flow/useFlowLoader'; import { useFlowStore } from '~/store/flow/useFlowStore'; import { useInfiniteLoader } from '~/hooks/dom/useInfiniteLoader'; import { useFlowSetCellView } from '~/hooks/flow/useFlowSetCellView'; +import { useGetLabStats } from '~/hooks/lab/useGetLabStats'; export const useFlow = () => { const { loadMore, isSyncing } = useFlowLoader(); const { nodes, heroes, recent, updated } = useFlowStore(); const { isFluid, toggleLayout } = useFlowLayout(); - const labUpdates = useShallowSelect(selectLabUpdatesNodes); + const lab = useGetLabStats(); useInfiniteLoader(loadMore, isSyncing); - const updates = useMemo(() => [...updated, ...labUpdates].slice(0, 10), [updated, labUpdates]); + const updates = useMemo(() => [...updated, ...lab.updates].slice(0, 10), [lab.updates, updated]); const onChangeCellView = useFlowSetCellView(); return { nodes, heroes, recent, updates, isFluid, toggleLayout, onChangeCellView }; diff --git a/src/hooks/lab/useGetLabNodes.ts b/src/hooks/lab/useGetLabNodes.ts new file mode 100644 index 00000000..3cc44f46 --- /dev/null +++ b/src/hooks/lab/useGetLabNodes.ts @@ -0,0 +1,85 @@ +import useSWRInfinite from 'swr/infinite'; +import { KeyLoader } from 'swr'; +import { GetLabNodesRequest, ILabNode } from '~/types/lab'; +import { getLabNodes } from '~/api/lab'; +import { flatten, last, uniqBy } from 'ramda'; +import { useLabStore } from '~/store/lab/useLabStore'; +import { useCallback, useEffect } from 'react'; +import { INode } from '~/redux/types'; +import { useUser } from '~/hooks/user/userUser'; + +const getKey: (isUser: boolean) => KeyLoader = isUser => (index, prev) => { + if (!isUser) return null; + if (index > 0 && !prev?.length) return null; + + const lastNode = last(prev || []); + if (!lastNode && index > 0) { + return null; + } + + const props: GetLabNodesRequest = { + after: lastNode?.node.commented_at || lastNode?.node.created_at, + }; + + return JSON.stringify(props); +}; + +const parseKey = (key: string): GetLabNodesRequest => { + try { + return JSON.parse(key); + } catch (error) { + return {}; + } +}; + +export const useGetLabNodes = () => { + const labStore = useLabStore(); + const { is_user } = useUser(); + + const { data, isValidating, size, setSize, mutate } = useSWRInfinite( + getKey(is_user), + async (key: string) => { + const result = await getLabNodes(parseKey(key)); + return result.nodes; + }, + { + fallbackData: [labStore.nodes], + onSuccess: data => labStore.setNodes(flatten(data)), + } + ); + + const nodes = uniqBy(n => n.node.id, flatten(data || [])); + const hasMore = (data?.[size - 1]?.length || 0) >= 1; + const loadMore = useCallback(() => setSize(size + 1), [setSize, size]); + + /** prepends list with a node */ + const unshift = useCallback( + async (node: ILabNode) => { + await mutate([[node], ...(data || [])]); + }, + [data, mutate] + ); + + /** updates node on cache */ + const updateNode = useCallback( + async (nodeId: number, node: Partial) => { + await mutate( + data?.map(page => + page.map(it => (it.node.id === nodeId ? { ...it, node: { ...it.node, node } } : it)) + ) + ); + }, + [data, mutate] + ); + + /** purge cache on exit */ + useEffect(() => { + if (is_user) { + return; + } + + labStore.setNodes([]); + }, [is_user, labStore]); + + return { nodes, isLoading: !data && isValidating, hasMore, loadMore, unshift, updateNode }; +}; diff --git a/src/hooks/lab/useGetLabStats.ts b/src/hooks/lab/useGetLabStats.ts new file mode 100644 index 00000000..f684955d --- /dev/null +++ b/src/hooks/lab/useGetLabStats.ts @@ -0,0 +1,72 @@ +import useSWR from 'swr'; +import { API } from '~/constants/api'; +import { getLabStats, getLabUpdates } from '~/api/lab'; +import { useLabStore } from '~/store/lab/useLabStore'; +import { useCallback, useEffect } from 'react'; +import { useUser } from '~/hooks/user/userUser'; + +const refreshInterval = 1000 * 60 * 5; // 5 minutes + +export const useGetLabStats = () => { + const lab = useLabStore(); + const { is_user } = useUser(); + + const { data: stats, isValidating: isValidatingStats } = useSWR( + is_user ? API.LAB.STATS : null, + async () => getLabStats(), + { + fallbackData: { + heroes: lab.heroes, + tags: lab.tags, + }, + onSuccess: data => { + lab.setHeroes(data.heroes); + lab.setTags(data.tags); + }, + refreshInterval, + } + ); + + const { data: updatesData, isValidating: isValidatingUpdates, mutate: mutateUpdates } = useSWR( + is_user ? API.LAB.UPDATES : null, + async () => { + const result = await getLabUpdates(); + return result.nodes; + }, + { + fallbackData: lab.updates, + onSuccess: data => { + lab.setUpdates(data); + }, + refreshInterval, + } + ); + + const heroes = stats?.heroes || []; + const tags = stats?.tags || []; + const updates = updatesData || []; + + const isLoading = (!stats || !updates) && (isValidatingStats || isValidatingUpdates); + const seenNode = useCallback( + async (nodeId: number) => { + await mutateUpdates( + updates.filter(it => it.id !== nodeId), + false + ); + }, + [mutateUpdates, updates] + ); + + /** purge cache on exit */ + useEffect(() => { + if (is_user) { + return; + } + + lab.setHeroes([]); + lab.setTags([]); + lab.setUpdates([]); + }, [is_user, lab]); + + return { heroes, tags, updates, isLoading, seenNode }; +}; diff --git a/src/hooks/lab/useLab.ts b/src/hooks/lab/useLab.ts index e3005ed9..31680ccc 100644 --- a/src/hooks/lab/useLab.ts +++ b/src/hooks/lab/useLab.ts @@ -1,35 +1,9 @@ -import { useShallowSelect } from '~/hooks/data/useShallowSelect'; -import { - selectLabList, - selectLabStatsHeroes, - selectLabStatsLoading, - selectLabStatsTags, - selectLabUpdatesNodes, -} from '~/redux/lab/selectors'; -import { useDispatch } from 'react-redux'; -import { useCallback, useEffect } from 'react'; -import { labGetList, labGetMore, labGetStats } from '~/redux/lab/actions'; +import { useGetLabNodes } from '~/hooks/lab/useGetLabNodes'; +import { useGetLabStats } from '~/hooks/lab/useGetLabStats'; export const useLab = () => { - const { is_loading: isLoading, nodes, count } = useShallowSelect(selectLabList); - const dispatch = useDispatch(); - const tags = useShallowSelect(selectLabStatsTags); - const heroes = useShallowSelect(selectLabStatsHeroes); - const isLoadingStats = useShallowSelect(selectLabStatsLoading); - const updates = useShallowSelect(selectLabUpdatesNodes); + const { nodes, isLoading, loadMore, hasMore } = useGetLabNodes(); + const { tags, heroes, updates, isLoading: isLoadingStats } = useGetLabStats(); - useEffect(() => { - dispatch(labGetList()); - dispatch(labGetStats()); - }, [dispatch]); - - const onLoadMore = useCallback(() => { - if (nodes.length >= count) { - return; - } - - dispatch(labGetMore()); - }, [nodes, count, dispatch]); - - return { isLoading, nodes, count, onLoadMore, tags, heroes, isLoadingStats, updates }; + return { isLoading, nodes, hasMore, loadMore, tags, heroes, isLoadingStats, updates }; }; diff --git a/src/hooks/lab/useLabPagination.ts b/src/hooks/lab/useLabPagination.ts deleted file mode 100644 index af71b7b0..00000000 --- a/src/hooks/lab/useLabPagination.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { useCallback, useEffect, useMemo } from 'react'; - -export const useLabPagination = ( - isLoading: boolean, - columns: Element[], - onLoadMore: () => void -) => { - const loadOnIntersection = useCallback( - entries => { - const isVisible = entries.some(entry => entry.intersectionRatio > 0); - - if (!isVisible) { - return; - } - - onLoadMore(); - }, - [onLoadMore] - ); - - const observer = useMemo( - () => - new IntersectionObserver(loadOnIntersection, { - threshold: [0], - }), - [loadOnIntersection] - ); - - useEffect(() => { - if (isLoading) { - return; - } - - const lastItems = Array.from(columns) - .map(col => col.children.item(col.childNodes.length - 1)) - .filter(el => el) as Element[]; - - lastItems.forEach(item => observer.observe(item)); - - return () => { - lastItems.forEach(item => observer.unobserve(item)); - }; - }, [observer, columns, isLoading]); -}; diff --git a/src/hooks/node/useCreateNode.ts b/src/hooks/node/useCreateNode.ts index b35456c5..f6d555a9 100644 --- a/src/hooks/node/useCreateNode.ts +++ b/src/hooks/node/useCreateNode.ts @@ -1,16 +1,12 @@ import { useCallback } from 'react'; import { INode } from '~/redux/types'; import { apiPostNode } from '~/api/node'; -import { selectLabListNodes } from '~/redux/lab/selectors'; -import { labSetList } from '~/redux/lab/actions'; -import { useShallowSelect } from '~/hooks/data/useShallowSelect'; -import { useDispatch } from 'react-redux'; import { useFlowStore } from '~/store/flow/useFlowStore'; +import { useGetLabNodes } from '~/hooks/lab/useGetLabNodes'; export const useCreateNode = () => { - const dispatch = useDispatch(); const flow = useFlowStore(); - const labNodes = useShallowSelect(selectLabListNodes); + const lab = useGetLabNodes(); return useCallback( async (node: INode) => { @@ -19,13 +15,9 @@ export const useCreateNode = () => { if (node.is_promoted) { flow.setNodes([result.node, ...flow.nodes]); } else { - const updatedNodes = [ - { node: result.node, comment_count: 0, last_seen: node.created_at }, - ...labNodes, - ]; - dispatch(labSetList({ nodes: updatedNodes })); + await lab.unshift({ node: result.node, comment_count: 0, last_seen: node.created_at }); } }, - [flow, labNodes, dispatch] + [flow, lab] ); }; diff --git a/src/hooks/node/useOnNodeSeen.ts b/src/hooks/node/useOnNodeSeen.ts index e1508bf3..65f94b5c 100644 --- a/src/hooks/node/useOnNodeSeen.ts +++ b/src/hooks/node/useOnNodeSeen.ts @@ -1,13 +1,12 @@ import { INode } from '~/redux/types'; -import { useDispatch } from 'react-redux'; -import { labSeenNode } from '~/redux/lab/actions'; import { useEffect } from 'react'; import { useFlowStore } from '~/store/flow/useFlowStore'; +import { useGetLabStats } from '~/hooks/lab/useGetLabStats'; // useOnNodeSeen updates node seen status across all needed places export const useOnNodeSeen = (node?: INode) => { + const labStats = useGetLabStats(); const flow = useFlowStore(); - const dispatch = useDispatch(); useEffect(() => { if (!node?.id) { @@ -18,7 +17,8 @@ export const useOnNodeSeen = (node?: INode) => { if (node.is_promoted) { flow.seenNode(node.id); } else { - dispatch(labSeenNode(node.id)); + void labStats.seenNode(node.id); } - }, [dispatch, flow, node]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [node?.id]); }; diff --git a/src/hooks/node/useUpdateNode.ts b/src/hooks/node/useUpdateNode.ts index cd702b19..907a0891 100644 --- a/src/hooks/node/useUpdateNode.ts +++ b/src/hooks/node/useUpdateNode.ts @@ -2,17 +2,15 @@ import { useLoadNode } from '~/hooks/node/useLoadNode'; import { useCallback } from 'react'; import { INode } from '~/redux/types'; import { apiPostNode } from '~/api/node'; -import { selectLabListNodes } from '~/redux/lab/selectors'; -import { labSetList } from '~/redux/lab/actions'; -import { useShallowSelect } from '~/hooks/data/useShallowSelect'; import { useDispatch } from 'react-redux'; import { useFlowStore } from '~/store/flow/useFlowStore'; +import { useGetLabNodes } from '~/hooks/lab/useGetLabNodes'; export const useUpdateNode = (id: number) => { const dispatch = useDispatch(); const { update } = useLoadNode(id); const flow = useFlowStore(); - const labNodes = useShallowSelect(selectLabListNodes); + const lab = useGetLabNodes(); return useCallback( async (node: INode) => { @@ -27,12 +25,9 @@ export const useUpdateNode = (id: number) => { if (node.is_promoted) { flow.updateNode(result.node.id!, result.node); } else { - const updatedNodes = labNodes.map(item => - item.node.id === result.node.id ? { ...item, node: result.node } : item - ); - dispatch(labSetList({ nodes: updatedNodes })); + await lab.updateNode(result.node.id!, result.node); } }, - [update, flow, labNodes, dispatch] + [update, flow, lab] ); }; diff --git a/src/redux/auth/sagas.ts b/src/redux/auth/sagas.ts index df18f05b..4919339f 100644 --- a/src/redux/auth/sagas.ts +++ b/src/redux/auth/sagas.ts @@ -55,16 +55,15 @@ import { messagesSet } from '~/redux/messages/actions'; import { SagaIterator } from 'redux-saga'; import { isEmpty } from 'ramda'; import { AxiosError } from 'axios'; -import { labGetUpdates } from '~/redux/lab/actions'; import { getMOBXStore } from '~/store'; import { Dialog } from '~/constants/modal'; import { showErrorToast } from '~/utils/errors/showToast'; -import { showToastSuccess, showToastInfo } from '~/utils/toast'; +import { showToastInfo } from '~/utils/toast'; import { getRandomPhrase } from '~/constants/phrases'; const modalStore = getMOBXStore().modal; -function* setTokenSaga({ token }: ReturnType) { +function setTokenSaga({ token }: ReturnType) { localStorage.setItem('token', token); } @@ -205,7 +204,6 @@ function* getUpdates() { function* startPollingSaga() { while (true) { yield call(getUpdates); - yield put(labGetUpdates()); yield delay(60000); } } diff --git a/src/redux/lab/actions.ts b/src/redux/lab/actions.ts deleted file mode 100644 index 00dcc529..00000000 --- a/src/redux/lab/actions.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { LAB_ACTIONS } from '~/redux/lab/constants'; -import { ILabState } from '~/redux/lab/types'; -import { INode } from '~/redux/types'; - -export const labGetList = (after?: string) => ({ - type: LAB_ACTIONS.GET_LIST, - after, -}); - -export const labSetList = (list: Partial) => ({ - type: LAB_ACTIONS.SET_LIST, - list, -}); - -export const labGetStats = () => ({ - type: LAB_ACTIONS.GET_STATS, -}); - -export const labSetStats = (stats: Partial) => ({ - type: LAB_ACTIONS.SET_STATS, - stats, -}); - -export const labSetUpdates = (updates: Partial) => ({ - type: LAB_ACTIONS.SET_UPDATES, - updates, -}); - -export const labGetUpdates = () => ({ - type: LAB_ACTIONS.GET_UPDATES, -}); - -export const labSeenNode = (nodeId: INode['id']) => ({ - type: LAB_ACTIONS.SEEN_NODE, - nodeId, -}); - -export const labGetMore = () => ({ - type: LAB_ACTIONS.GET_MORE, -}); diff --git a/src/redux/lab/constants.ts b/src/redux/lab/constants.ts deleted file mode 100644 index bc2e150e..00000000 --- a/src/redux/lab/constants.ts +++ /dev/null @@ -1,14 +0,0 @@ -const prefix = 'LAB.'; - -export const LAB_ACTIONS = { - GET_LIST: `${prefix}GET_LIST`, - SET_LIST: `${prefix}SET_LIST`, - - GET_STATS: `${prefix}GET_STATS`, - SET_STATS: `${prefix}SET_STATS`, - - SET_UPDATES: `${prefix}SET_UPDATES`, - GET_UPDATES: `${prefix}GET_UPDATES`, - SEEN_NODE: `${prefix}SET_UPDATE_SEEN`, - GET_MORE: `${prefix}GET_MORE`, -}; diff --git a/src/redux/lab/handlers.ts b/src/redux/lab/handlers.ts deleted file mode 100644 index 15c0b923..00000000 --- a/src/redux/lab/handlers.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { LAB_ACTIONS } from '~/redux/lab/constants'; -import { labSetList, labSetStats, labSetUpdates } from '~/redux/lab/actions'; -import { ILabState } from '~/redux/lab/types'; - -type LabHandler any> = ( - state: Readonly, - payload: ReturnType -) => Readonly; - -const setList: LabHandler = (state, { list }) => ({ - ...state, - list: { - ...state.list, - ...list, - }, -}); - -const setStats: LabHandler = (state, { stats }) => ({ - ...state, - stats: { - ...state.stats, - ...stats, - }, -}); - -const setUpdates: LabHandler = (state, { updates }) => ({ - ...state, - updates: { - ...state.updates, - ...updates, - }, -}); - -export const LAB_HANDLERS = { - [LAB_ACTIONS.SET_LIST]: setList, - [LAB_ACTIONS.SET_STATS]: setStats, - [LAB_ACTIONS.SET_UPDATES]: setUpdates, -}; diff --git a/src/redux/lab/index.ts b/src/redux/lab/index.ts deleted file mode 100644 index ba277a55..00000000 --- a/src/redux/lab/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { createReducer } from '~/utils/reducer'; -import { LAB_HANDLERS } from '~/redux/lab/handlers'; -import { ILabState } from '~/redux/lab/types'; - -const INITIAL_STATE: ILabState = { - list: { - is_loading: false, - nodes: [], - count: 0, - error: '', - }, - stats: { - is_loading: false, - heroes: [], - tags: [], - error: undefined, - }, - updates: { - nodes: [], - isLoading: false, - }, -}; - -export default createReducer(INITIAL_STATE, LAB_HANDLERS); diff --git a/src/redux/lab/sagas.ts b/src/redux/lab/sagas.ts deleted file mode 100644 index fc01d490..00000000 --- a/src/redux/lab/sagas.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { call, put, select, takeLeading } from 'redux-saga/effects'; -import { - labGetList, - labSeenNode, - labSetList, - labSetStats, - labSetUpdates, -} from '~/redux/lab/actions'; -import { LAB_ACTIONS } from '~/redux/lab/constants'; -import { Unwrap } from '~/redux/types'; -import { getLabNodes, getLabStats, getLabUpdates } from '~/redux/lab/api'; -import { selectLabList, selectLabUpdatesNodes } from '~/redux/lab/selectors'; - -function* getList({ after = '' }: ReturnType) { - try { - yield put(labSetList({ is_loading: true })); - const { nodes, count }: Unwrap = yield call(getLabNodes, { after }); - yield put(labSetList({ nodes, count })); - } catch (error) { - yield put(labSetList({ error: error.message })); - } finally { - yield put(labSetList({ is_loading: false })); - } -} - -function* getStats() { - try { - yield put(labSetStats({ is_loading: true })); - const { heroes, tags }: Unwrap = yield call(getLabStats); - yield put(labSetStats({ heroes, tags })); - } catch (error) { - yield put(labSetStats({ error: error.message })); - } finally { - yield put(labSetStats({ is_loading: false })); - } -} - -function* getUpdates() { - try { - yield put(labSetUpdates({ isLoading: true })); - const { nodes }: Unwrap = yield call(getLabUpdates); - yield put(labSetUpdates({ nodes })); - } catch (error) { - console.log(error.message); - } finally { - yield put(labSetUpdates({ isLoading: false })); - } -} - -function* seenNode({ nodeId }: ReturnType) { - const nodes: ReturnType = yield select(selectLabUpdatesNodes); - const newNodes = nodes.filter(node => node.id != nodeId); - yield put(labSetUpdates({ nodes: newNodes })); -} - -function* getMore() { - try { - yield put(labSetList({ is_loading: true })); - - const list: ReturnType = yield select(selectLabList); - if (list.nodes.length === list.count) { - return; - } - - const last = list.nodes[list.nodes.length - 1]; - - if (!last) { - return; - } - const after = last.node.commented_at || last.node.created_at; - const { nodes, count }: Unwrap = yield call(getLabNodes, { after }); - const newNodes = [...list.nodes, ...nodes]; - - yield put(labSetList({ nodes: newNodes, count })); - } catch (error) { - yield put(labSetList({ error: error.message })); - } finally { - yield put(labSetList({ is_loading: false })); - } -} - -export default function* labSaga() { - yield takeLeading(LAB_ACTIONS.GET_LIST, getList); - yield takeLeading(LAB_ACTIONS.GET_STATS, getStats); - - yield takeLeading(LAB_ACTIONS.GET_UPDATES, getUpdates); - yield takeLeading(LAB_ACTIONS.SEEN_NODE, seenNode); - - yield takeLeading(LAB_ACTIONS.GET_MORE, getMore); -} diff --git a/src/redux/lab/selectors.ts b/src/redux/lab/selectors.ts deleted file mode 100644 index 6b9e52ca..00000000 --- a/src/redux/lab/selectors.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { IState } from '~/redux/store'; - -export const selectLab = (state: IState) => state.lab; -export const selectLabListNodes = (state: IState) => state.lab.list.nodes; -export const selectLabList = (state: IState) => state.lab.list; -export const selectLabStatsHeroes = (state: IState) => state.lab.stats.heroes; -export const selectLabStatsTags = (state: IState) => state.lab.stats.tags; -export const selectLabStatsLoading = (state: IState) => state.lab.stats.is_loading; -export const selectLabUpdates = (state: IState) => state.lab.updates; -export const selectLabUpdatesNodes = (state: IState) => state.lab.updates.nodes; diff --git a/src/redux/store.ts b/src/redux/store.ts index 6ffd230d..c4402e9d 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -11,10 +11,6 @@ import auth from '~/redux/auth'; import authSaga from '~/redux/auth/sagas'; import { IAuthState } from '~/redux/auth/types'; -import lab from '~/redux/lab'; -import labSaga from '~/redux/lab/sagas'; -import { ILabState } from '~/redux/lab/types'; - import uploads, { IUploadState } from '~/redux/uploads/reducer'; import uploadSaga from '~/redux/uploads/sagas'; @@ -48,7 +44,6 @@ export interface IState { uploads: IUploadState; player: IPlayerState; messages: IMessagesState; - lab: ILabState; } export const sagaMiddleware = createSagaMiddleware(); @@ -68,7 +63,6 @@ export const store = createStore( uploads, player: persistReducer(playerPersistConfig, player), messages, - lab: lab, }), composeEnhancers(applyMiddleware(routerMiddleware(history), sagaMiddleware)) ); @@ -81,7 +75,6 @@ export function configureStore(): { sagaMiddleware.run(uploadSaga); sagaMiddleware.run(playerSaga); sagaMiddleware.run(messagesSaga); - sagaMiddleware.run(labSaga); window.addEventListener('message', message => { if (message && message.data && message.data.type === 'oauth_login' && message.data.token) diff --git a/src/store/index.ts b/src/store/index.ts index 85009ca3..78924d92 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -2,11 +2,13 @@ import { makeAutoObservable } from 'mobx'; import { FlowStore } from '~/store/flow/FlowStore'; import { ModalStore } from '~/store/modal/ModalStore'; import { PhotoSwipeStore } from '~/store/photoSwipe/PhotoSwipeStore'; +import { LabStore } from '~/store/lab/LabStore'; export class Store { flow = new FlowStore(); modal = new ModalStore(); photoSwipe = new PhotoSwipeStore(); + lab = new LabStore(); constructor() { makeAutoObservable(this); diff --git a/src/store/lab/LabStore.ts b/src/store/lab/LabStore.ts new file mode 100644 index 00000000..498c3dce --- /dev/null +++ b/src/store/lab/LabStore.ts @@ -0,0 +1,22 @@ +import { makeAutoObservable } from 'mobx'; +import { IFlowNode, ITag } from '~/redux/types'; +import { ILabNode } from '~/types/lab'; + +export class LabStore { + // actual nodes + nodes: ILabNode[] = []; + + // stats + heroes: IFlowNode[] = []; + tags: ITag[] = []; + updates: IFlowNode[] = []; + + constructor() { + makeAutoObservable(this); + } + + setNodes = (nodes: ILabNode[]) => (this.nodes = nodes); + setHeroes = (heroes: IFlowNode[]) => (this.heroes = heroes); + setTags = (tags: ITag[]) => (this.tags = tags); + setUpdates = (updates: IFlowNode[]) => (this.updates = updates); +} diff --git a/src/store/lab/useLabStore.ts b/src/store/lab/useLabStore.ts new file mode 100644 index 00000000..a7698cdf --- /dev/null +++ b/src/store/lab/useLabStore.ts @@ -0,0 +1,3 @@ +import { useStore } from '~/utils/context/StoreContextProvider'; + +export const useLabStore = () => useStore().lab; diff --git a/src/redux/lab/types.ts b/src/types/lab/index.ts similarity index 100% rename from src/redux/lab/types.ts rename to src/types/lab/index.ts diff --git a/src/utils/context/LabContextProvider.tsx b/src/utils/context/LabContextProvider.tsx index 0c347f5d..9f1d31a3 100644 --- a/src/utils/context/LabContextProvider.tsx +++ b/src/utils/context/LabContextProvider.tsx @@ -1,24 +1,24 @@ -import { ILabNode } from '~/redux/lab/types'; +import { ILabNode } from '~/types/lab'; import React, { createContext, FC, useContext } from 'react'; -import { INode, ITag } from '~/redux/types'; +import { IFlowNode, ITag } from '~/redux/types'; export interface LabContextProps { isLoading: boolean; nodes: ILabNode[]; - count: number; - onLoadMore: () => void; + hasMore: boolean; + loadMore: () => void; tags: ITag[]; - heroes: Partial[]; + heroes: IFlowNode[]; isLoadingStats: boolean; - updates: Partial[]; + updates: IFlowNode[]; } const defaultValues: LabContextProps = { isLoading: false, nodes: [], - count: 0, - onLoadMore: () => {}, + hasMore: false, + loadMore: () => {}, tags: [], heroes: [], isLoadingStats: false, diff --git a/src/utils/providers/LabProvider.tsx b/src/utils/providers/LabProvider.tsx index 087f19aa..8531893d 100644 --- a/src/utils/providers/LabProvider.tsx +++ b/src/utils/providers/LabProvider.tsx @@ -5,14 +5,14 @@ import { useLab } from '~/hooks/lab/useLab'; interface LabProviderProps {} const LabProvider: FC = ({ children }) => { - const { isLoading, nodes, count, onLoadMore, tags, heroes, isLoadingStats, updates } = useLab(); + const { isLoading, nodes, loadMore, hasMore, tags, heroes, isLoadingStats, updates } = useLab(); return (