From 38eedab3c2f914c6e0575100c7022ee85998f63a Mon Sep 17 00:00:00 2001 From: Fedor Katurov Date: Tue, 4 Jan 2022 15:08:20 +0700 Subject: [PATCH] completely removed flow-related sagas --- package.json | 2 + src/containers/App.tsx | 11 +- .../dialogs/EditorEditDialog/index.tsx | 5 +- src/hooks/dom/useGlobalLoader.ts | 16 +++ src/hooks/flow/useFlow.ts | 11 +- src/hooks/flow/useFlowLoader.ts | 94 +++++++++++++ src/hooks/flow/useFlowPagination.ts | 10 -- src/hooks/flow/useFlowSetCellView.ts | 21 +++ src/hooks/node/useCreateNode.ts | 11 +- src/hooks/node/useOnNodeSeen.ts | 7 +- src/hooks/node/useUpdateNode.ts | 12 +- src/index.tsx | 15 +- src/pages/boris.tsx | 5 +- src/pages/index.tsx | 5 +- src/pages/node/[id].tsx | 109 ++++++++------- src/redux/flow/actions.ts | 38 ----- src/redux/flow/constants.ts | 10 -- src/redux/flow/handlers.ts | 32 +---- src/redux/flow/sagas.ts | 131 +----------------- src/store/flow/FlowStore.ts | 33 +++++ src/store/flow/useFlowStore.ts | 3 + src/store/index.ts | 10 ++ src/utils/context/StoreContextProvider.tsx | 10 ++ src/utils/context/UserContextProvider.tsx | 9 +- src/utils/dom/hideLoader.ts | 9 ++ yarn.lock | 17 +++ 26 files changed, 326 insertions(+), 310 deletions(-) create mode 100644 src/hooks/dom/useGlobalLoader.ts create mode 100644 src/hooks/flow/useFlowLoader.ts delete mode 100644 src/hooks/flow/useFlowPagination.ts create mode 100644 src/hooks/flow/useFlowSetCellView.ts create mode 100644 src/store/flow/FlowStore.ts create mode 100644 src/store/flow/useFlowStore.ts create mode 100644 src/store/index.ts create mode 100644 src/utils/context/StoreContextProvider.tsx create mode 100644 src/utils/dom/hideLoader.ts diff --git a/package.json b/package.json index 9a8a595c..1a7a2f22 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,8 @@ "formik": "^2.2.6", "insane": "^2.6.2", "marked": "^2.0.0", + "mobx": "^6.3.10", + "mobx-react": "^7.2.1", "node-sass": "^4.14.1", "photoswipe": "^4.1.3", "raleway-cyrillic": "^4.0.2", diff --git a/src/containers/App.tsx b/src/containers/App.tsx index 01e6405b..ee9f2be1 100644 --- a/src/containers/App.tsx +++ b/src/containers/App.tsx @@ -8,17 +8,18 @@ import { PageCoverProvider } from '~/components/containers/PageCoverProvider'; import { BottomContainer } from '~/containers/main/BottomContainer'; import { MainRouter } from '~/containers/main/MainRouter'; import { DragDetectorProvider } from '~/hooks/dom/useDragDetector'; -import { useUser } from '~/hooks/user/userUser'; import { UserContextProvider } from '~/utils/context/UserContextProvider'; import { SWRConfigProvider } from '~/utils/providers/SWRConfigProvider'; +import { observer } from 'mobx-react'; +import { useGlobalLoader } from '~/hooks/dom/useGlobalLoader'; -const App: VFC = () => { - const user = useUser(); +const App: VFC = observer(() => { + useGlobalLoader(); return ( - + @@ -34,6 +35,6 @@ const App: VFC = () => { ); -}; +}); export { App }; diff --git a/src/containers/dialogs/EditorEditDialog/index.tsx b/src/containers/dialogs/EditorEditDialog/index.tsx index 130953c2..885a10e5 100644 --- a/src/containers/dialogs/EditorEditDialog/index.tsx +++ b/src/containers/dialogs/EditorEditDialog/index.tsx @@ -7,8 +7,9 @@ import styles from './styles.module.scss'; import { useLoadNode } from '~/hooks/node/useLoadNode'; import { useUpdateNode } from '~/hooks/node/useUpdateNode'; import { INode } from '~/redux/types'; +import { observer } from 'mobx-react'; -const EditorEditDialog: FC = () => { +const EditorEditDialog: FC = observer(() => { const history = useHistory(); const { @@ -46,6 +47,6 @@ const EditorEditDialog: FC = () => { } return ; -}; +}); export { EditorEditDialog }; diff --git a/src/hooks/dom/useGlobalLoader.ts b/src/hooks/dom/useGlobalLoader.ts new file mode 100644 index 00000000..5e47c8ea --- /dev/null +++ b/src/hooks/dom/useGlobalLoader.ts @@ -0,0 +1,16 @@ +import { useEffect } from 'react'; +import { useFlowStore } from '~/store/flow/useFlowStore'; +import { hideLoader } from '~/utils/dom/hideLoader'; + +/** simply waits for all data to settle and then show the app */ +export const useGlobalLoader = () => { + const flow = useFlowStore(); + + useEffect(() => { + if (!flow.isRefreshed) { + return; + } + + hideLoader(); + }, [flow.isRefreshed]); +}; diff --git a/src/hooks/flow/useFlow.ts b/src/hooks/flow/useFlow.ts index 6f7ab46e..90bc634e 100644 --- a/src/hooks/flow/useFlow.ts +++ b/src/hooks/flow/useFlow.ts @@ -1,20 +1,23 @@ import { useShallowSelect } from '~/hooks/data/useShallowSelect'; -import { selectFlow } from '~/redux/flow/selectors'; import { useFlowLayout } from '~/hooks/flow/useFlowLayout'; import { selectLabUpdatesNodes } from '~/redux/lab/selectors'; import { useDispatch } from 'react-redux'; -import { useFlowPagination } from '~/hooks/flow/useFlowPagination'; import { useCallback, useMemo } from 'react'; import { FlowDisplay, INode } from '~/redux/types'; import { flowSetCellView } from '~/redux/flow/actions'; +import { useFlowLoader } from '~/hooks/flow/useFlowLoader'; +import { useFlowStore } from '~/store/flow/useFlowStore'; +import { useInfiniteLoader } from '~/hooks/dom/useInfiniteLoader'; export const useFlow = () => { - const { nodes, heroes, recent, updated, isLoading } = useShallowSelect(selectFlow); + const { loadMore, isSyncing } = useFlowLoader(); + + const { nodes, heroes, recent, updated } = useFlowStore(); const { isFluid, toggleLayout } = useFlowLayout(); const labUpdates = useShallowSelect(selectLabUpdatesNodes); const dispatch = useDispatch(); - useFlowPagination({ isLoading }); + useInfiniteLoader(loadMore, isSyncing); const updates = useMemo(() => [...updated, ...labUpdates].slice(0, 10), [updated, labUpdates]); diff --git a/src/hooks/flow/useFlowLoader.ts b/src/hooks/flow/useFlowLoader.ts new file mode 100644 index 00000000..1fc1e0ff --- /dev/null +++ b/src/hooks/flow/useFlowLoader.ts @@ -0,0 +1,94 @@ +import { useCallback, useEffect, useState } from 'react'; +import { getNodeDiff } from '~/api/node'; +import { uniq } from 'ramda'; +import { useFlowStore } from '~/store/flow/useFlowStore'; +import { runInAction } from 'mobx'; +import { showErrorToast } from '~/utils/errors/showToast'; +import { delay } from 'redux-saga/effects'; + +export const useFlowLoader = () => { + const [isSyncing, setIsSyncing] = useState(false); + const flow = useFlowStore(); + + /** Loads initial nodes and puts to store */ + const getInitialNodes = useCallback(async () => { + try { + setIsSyncing(true); + + const { before, after, heroes, recent, updated } = await getNodeDiff({ + start: new Date().toISOString(), + end: new Date().toISOString(), + with_heroes: true, + with_updated: true, + with_recent: true, + with_valid: false, + }); + + runInAction(() => { + flow.setNodes(uniq([...(before || []), ...(after || [])])); + flow.setHeroes(heroes || []); + flow.setUpdated(updated || []); + flow.setRecent(recent || []); + flow.setIsRefreshed(true); + }); + } catch (error) { + showErrorToast(error); + } finally { + setIsSyncing(false); + } + }, [flow]); + + /** Loads next nodes */ + const loadMore = useCallback(async () => { + try { + setIsSyncing(true); + + const start = flow.nodes[0].created_at; + const end = flow.nodes[flow.nodes.length - 1] && flow.nodes[flow.nodes.length - 1].created_at; + + const data = await getNodeDiff({ + start, + end, + with_heroes: false, + with_updated: true, + with_recent: true, + with_valid: true, + }); + + const nodes = uniq([ + ...(data.before || []), + ...(data.valid ? flow.nodes.filter(node => data.valid.includes(node.id)) : flow.nodes), + ...(data.after || []), + ]); + + runInAction(() => { + flow.setNodes(nodes); + + if (data.recent?.length) { + flow.setRecent(data.recent); + } + + if (data.updated?.length) { + flow.setUpdated(data.updated); + } + }); + + // wait a little to debounce + await delay(1000); + } catch (error) { + showErrorToast(error); + } finally { + setIsSyncing(false); + } + }, [flow]); + + useEffect(() => { + if (flow.isRefreshed) { + return; + } + + void getInitialNodes(); + }, [flow, getInitialNodes]); + + return { getInitialNodes, isSyncing, loadMore }; +}; diff --git a/src/hooks/flow/useFlowPagination.ts b/src/hooks/flow/useFlowPagination.ts deleted file mode 100644 index 31dd5596..00000000 --- a/src/hooks/flow/useFlowPagination.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { useCallback } from 'react'; -import { flowGetMore } from '~/redux/flow/actions'; -import { useDispatch } from 'react-redux'; -import { useInfiniteLoader } from '~/hooks/dom/useInfiniteLoader'; - -export const useFlowPagination = ({ isLoading }) => { - const dispatch = useDispatch(); - const loadMore = useCallback(() => dispatch(flowGetMore()), [dispatch]); - useInfiniteLoader(loadMore, isLoading); -}; diff --git a/src/hooks/flow/useFlowSetCellView.ts b/src/hooks/flow/useFlowSetCellView.ts new file mode 100644 index 00000000..dab04cda --- /dev/null +++ b/src/hooks/flow/useFlowSetCellView.ts @@ -0,0 +1,21 @@ +import { useFlowStore } from '~/store/flow/useFlowStore'; +import { useCallback } from 'react'; +import { FlowDisplay } from '~/redux/types'; +import { showErrorToast } from '~/utils/errors/showToast'; +import { postCellView } from '~/redux/flow/api'; + +export const useFlowSetCellView = () => { + const { updateNode } = useFlowStore(); + + return useCallback( + async (id, flow: FlowDisplay) => { + try { + updateNode(id, { flow }); + await postCellView({ id, flow }); + } catch (error) { + showErrorToast(error); + } + }, + [updateNode] + ); +}; diff --git a/src/hooks/node/useCreateNode.ts b/src/hooks/node/useCreateNode.ts index 11456369..b35456c5 100644 --- a/src/hooks/node/useCreateNode.ts +++ b/src/hooks/node/useCreateNode.ts @@ -1,26 +1,23 @@ import { useCallback } from 'react'; import { INode } from '~/redux/types'; import { apiPostNode } from '~/api/node'; -import { selectFlowNodes } from '~/redux/flow/selectors'; -import { flowSetNodes } from '~/redux/flow/actions'; 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'; export const useCreateNode = () => { const dispatch = useDispatch(); - const flowNodes = useShallowSelect(selectFlowNodes); + const flow = useFlowStore(); const labNodes = useShallowSelect(selectLabListNodes); return useCallback( async (node: INode) => { const result = await apiPostNode({ node }); - // TODO: use another store here someday if (node.is_promoted) { - const updatedNodes = [result.node, ...flowNodes]; - dispatch(flowSetNodes(updatedNodes)); + flow.setNodes([result.node, ...flow.nodes]); } else { const updatedNodes = [ { node: result.node, comment_count: 0, last_seen: node.created_at }, @@ -29,6 +26,6 @@ export const useCreateNode = () => { dispatch(labSetList({ nodes: updatedNodes })); } }, - [flowNodes, labNodes, dispatch] + [flow, labNodes, dispatch] ); }; diff --git a/src/hooks/node/useOnNodeSeen.ts b/src/hooks/node/useOnNodeSeen.ts index bf91647a..e1508bf3 100644 --- a/src/hooks/node/useOnNodeSeen.ts +++ b/src/hooks/node/useOnNodeSeen.ts @@ -1,11 +1,12 @@ import { INode } from '~/redux/types'; import { useDispatch } from 'react-redux'; import { labSeenNode } from '~/redux/lab/actions'; -import { flowSeenNode } from '~/redux/flow/actions'; import { useEffect } from 'react'; +import { useFlowStore } from '~/store/flow/useFlowStore'; // useOnNodeSeen updates node seen status across all needed places export const useOnNodeSeen = (node?: INode) => { + const flow = useFlowStore(); const dispatch = useDispatch(); useEffect(() => { @@ -15,9 +16,9 @@ export const useOnNodeSeen = (node?: INode) => { // Remove node from updated if (node.is_promoted) { - dispatch(flowSeenNode(node.id)); + flow.seenNode(node.id); } else { dispatch(labSeenNode(node.id)); } - }, [dispatch, node]); + }, [dispatch, flow, node]); }; diff --git a/src/hooks/node/useUpdateNode.ts b/src/hooks/node/useUpdateNode.ts index ec58c78d..f294211c 100644 --- a/src/hooks/node/useUpdateNode.ts +++ b/src/hooks/node/useUpdateNode.ts @@ -3,16 +3,16 @@ import { useCallback } from 'react'; import { INode } from '~/redux/types'; import { apiPostNode } from '~/api/node'; import { selectFlowNodes } from '~/redux/flow/selectors'; -import { flowSetNodes } from '~/redux/flow/actions'; 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'; export const useUpdateNode = (id: number) => { const dispatch = useDispatch(); const { update } = useLoadNode(id); - const flowNodes = useShallowSelect(selectFlowNodes); + const flow = useFlowStore(); const labNodes = useShallowSelect(selectLabListNodes); return useCallback( @@ -25,12 +25,8 @@ export const useUpdateNode = (id: number) => { await update(result.node); - // TODO: use another store here someday if (node.is_promoted) { - const updatedNodes = flowNodes.map(item => - item.id === result.node.id ? result.node : item - ); - dispatch(flowSetNodes(updatedNodes)); + flow.updateNode(result.node.id!, result.node); } else { const updatedNodes = labNodes.map(item => item.node.id === result.node.id ? { ...item, node: result.node } : item @@ -38,6 +34,6 @@ export const useUpdateNode = (id: number) => { dispatch(labSetList({ nodes: updatedNodes })); } }, - [update, flowNodes, dispatch, labNodes] + [update, flow, labNodes, dispatch] ); }; diff --git a/src/index.tsx b/src/index.tsx index 69782e29..8ef55236 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -5,14 +5,19 @@ import { PersistGate } from 'redux-persist/integration/react'; import { configureStore } from '~/redux/store'; import { App } from '~/containers/App'; import '~/styles/main.scss'; +import { Store } from '~/store'; +import { StoreContextProvider } from '~/utils/context/StoreContextProvider'; const { store, persistor } = configureStore(); +const mobxStore = new Store(); render( - - - - - , + + + + + + + , document.getElementById('app') ); diff --git a/src/pages/boris.tsx b/src/pages/boris.tsx index 2e291cdd..ba9167fb 100644 --- a/src/pages/boris.tsx +++ b/src/pages/boris.tsx @@ -6,8 +6,9 @@ import { useNodeComments } from '~/hooks/comments/useNodeComments'; import { useBoris } from '~/hooks/boris/useBoris'; import { NodeContextProvider } from '~/utils/context/NodeContextProvider'; import { useLoadNode } from '~/hooks/node/useLoadNode'; +import { observer } from 'mobx-react'; -const BorisPage: VFC = () => { +const BorisPage: VFC = observer(() => { const { node, isLoading, update } = useLoadNode(696); const onShowImageModal = useImageModal(); @@ -42,6 +43,6 @@ const BorisPage: VFC = () => { ); -}; +}); export default BorisPage; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 57820f4a..f2ce6bde 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -3,10 +3,11 @@ import { FlowLayout } from '~/layouts/FlowLayout'; import { useFlow } from '~/hooks/flow/useFlow'; import { FlowContextProvider } from '~/utils/context/FlowContextProvider'; import { SearchContextProvider } from '~/utils/context/SearchContextProvider'; +import { observer } from 'mobx-react'; interface Props {} -const FlowPage: FC = () => { +const FlowPage: FC = observer(() => { const { updates, nodes, heroes, recent, isFluid, toggleLayout, onChangeCellView } = useFlow(); return ( @@ -22,6 +23,6 @@ const FlowPage: FC = () => { ); -}; +}); export default FlowPage; diff --git a/src/pages/node/[id].tsx b/src/pages/node/[id].tsx index e14310cd..80007661 100644 --- a/src/pages/node/[id].tsx +++ b/src/pages/node/[id].tsx @@ -12,66 +12,69 @@ import { TagsContextProvider } from '~/utils/context/TagsContextProvider'; import { useNodePermissions } from '~/hooks/node/useNodePermissions'; import { NodeRelatedProvider } from '~/utils/providers/NodeRelatedProvider'; import { useLoadNode } from '~/hooks/node/useLoadNode'; +import { observer } from 'mobx-react'; type Props = RouteComponentProps<{ id: string }> & {}; -const NodePage: FC = ({ - match: { - params: { id }, - }, -}) => { - const { node, isLoading, update, lastSeen } = useLoadNode(parseInt(id, 10)); +const NodePage: FC = observer( + ({ + match: { + params: { id }, + }, + }) => { + const { node, isLoading, update, lastSeen } = useLoadNode(parseInt(id, 10)); - const onShowImageModal = useImageModal(); - const { - onLoadMoreComments, - onDelete: onDeleteComment, - onEdit: onSaveComment, - comments, - hasMore, - isLoading: isLoadingComments, - } = useNodeComments(parseInt(id, 10)); - const { onDelete: onTagDelete, onChange: onTagsChange, onClick: onTagClick } = useNodeTags( - parseInt(id, 10) - ); - const user = useUser(); - const [canEdit] = useNodePermissions(node); + const onShowImageModal = useImageModal(); + const { + onLoadMoreComments, + onDelete: onDeleteComment, + onEdit: onSaveComment, + comments, + hasMore, + isLoading: isLoadingComments, + } = useNodeComments(parseInt(id, 10)); + const { onDelete: onTagDelete, onChange: onTagsChange, onClick: onTagClick } = useNodeTags( + parseInt(id, 10) + ); + const user = useUser(); + const [canEdit] = useNodePermissions(node); - useScrollToTop([id, isLoadingComments]); + useScrollToTop([id, isLoadingComments]); - if (!node) { - // TODO: do something here - return null; - } + if (!node) { + // TODO: do something here + return null; + } - return ( - - - - + + - - - - - - ); -}; + + + + + + + ); + } +); export default NodePage; diff --git a/src/redux/flow/actions.ts b/src/redux/flow/actions.ts index fdd68dae..33f99580 100644 --- a/src/redux/flow/actions.ts +++ b/src/redux/flow/actions.ts @@ -2,45 +2,12 @@ import { FLOW_ACTIONS } from './constants'; import { IFlowState } from './reducer'; import { INode } from '../types'; -export const flowGetFlow = () => ({ - type: FLOW_ACTIONS.GET_FLOW, -}); - -export const flowSetNodes = (nodes: IFlowState['nodes']) => ({ - nodes, - type: FLOW_ACTIONS.SET_NODES, -}); - -export const flowSetHeroes = (heroes: IFlowState['heroes']) => ({ - heroes, - type: FLOW_ACTIONS.SET_HEROES, -}); - -export const flowSetRecent = (recent: IFlowState['recent']) => ({ - recent, - type: FLOW_ACTIONS.SET_RECENT, -}); - -export const flowSetUpdated = (updated: IFlowState['updated']) => ({ - updated, - type: FLOW_ACTIONS.SET_UPDATED, -}); - export const flowSetCellView = (id: INode['id'], flow: INode['flow']) => ({ type: FLOW_ACTIONS.SET_CELL_VIEW, id, flow, }); -export const flowGetMore = () => ({ - type: FLOW_ACTIONS.GET_MORE, -}); - -export const flowSetFlow = (data: Partial) => ({ - type: FLOW_ACTIONS.SET_FLOW, - data, -}); - export const flowSetSearch = (search: Partial) => ({ type: FLOW_ACTIONS.SET_SEARCH, search, @@ -54,8 +21,3 @@ export const flowChangeSearch = (search: Partial) => ({ export const flowLoadMoreSearch = () => ({ type: FLOW_ACTIONS.LOAD_MORE_SEARCH, }); - -export const flowSeenNode = (nodeId: INode['id']) => ({ - type: FLOW_ACTIONS.SEEN_NODE, - nodeId, -}); diff --git a/src/redux/flow/constants.ts b/src/redux/flow/constants.ts index e4e9ede5..73946bf3 100644 --- a/src/redux/flow/constants.ts +++ b/src/redux/flow/constants.ts @@ -1,19 +1,9 @@ const prefix = 'FLOW.'; export const FLOW_ACTIONS = { - GET_FLOW: `${prefix}GET_FLOW`, - SET_FLOW: `${prefix}SET_FLOW`, - SET_NODES: `${prefix}SET_NODES`, - SET_HEROES: `${prefix}SET_HEROES`, - SET_RECENT: `${prefix}SET_RECENT`, - SET_UPDATED: `${prefix}SET_UPDATED`, - SET_RANGE: `${prefix}SET_RANGE`, SET_CELL_VIEW: `${prefix}SET_CELL_VIEW`, - GET_MORE: `${prefix}GET_MORE`, SET_SEARCH: `${prefix}SET_SEARCH`, CHANGE_SEARCH: `${prefix}CHANGE_SEARCH`, LOAD_MORE_SEARCH: `${prefix}LOAD_MORE_SEARCH`, - - SEEN_NODE: `${prefix}SEEN_NODE`, }; diff --git a/src/redux/flow/handlers.ts b/src/redux/flow/handlers.ts index 8376540e..64f8557c 100644 --- a/src/redux/flow/handlers.ts +++ b/src/redux/flow/handlers.ts @@ -1,32 +1,7 @@ -import { assocPath } from 'ramda'; import { FLOW_ACTIONS } from './constants'; -import { - flowSetFlow, - flowSetHeroes, - flowSetNodes, - flowSetRecent, - flowSetSearch, - flowSetUpdated, -} from './actions'; +import { flowSetSearch } from './actions'; import { IFlowState } from './reducer'; -const setNodes = (state: IFlowState, { nodes }: ReturnType) => - assocPath(['nodes'], nodes, state); - -const setHeroes = (state: IFlowState, { heroes }: ReturnType) => - assocPath(['heroes'], heroes, state); - -const setRecent = (state: IFlowState, { recent }: ReturnType) => - assocPath(['recent'], recent, state); - -const setUpdated = (state: IFlowState, { updated }: ReturnType) => - assocPath(['updated'], updated, state); - -const setFlow = (state: IFlowState, { data }: ReturnType): IFlowState => ({ - ...state, - ...data, -}); - const setSearch = ( state: IFlowState, { search }: ReturnType @@ -39,10 +14,5 @@ const setSearch = ( }); export const FLOW_HANDLERS = { - [FLOW_ACTIONS.SET_NODES]: setNodes, - [FLOW_ACTIONS.SET_HEROES]: setHeroes, - [FLOW_ACTIONS.SET_RECENT]: setRecent, - [FLOW_ACTIONS.SET_UPDATED]: setUpdated, - [FLOW_ACTIONS.SET_FLOW]: setFlow, [FLOW_ACTIONS.SET_SEARCH]: setSearch, }; diff --git a/src/redux/flow/sagas.ts b/src/redux/flow/sagas.ts index 69a98cec..4a480d7d 100644 --- a/src/redux/flow/sagas.ts +++ b/src/redux/flow/sagas.ts @@ -1,123 +1,9 @@ -import { call, delay, put, race, select, take, takeLatest, takeLeading } from 'redux-saga/effects'; -import { REHYDRATE } from 'redux-persist'; +import { call, delay, put, race, select, take, takeLatest } from 'redux-saga/effects'; import { FLOW_ACTIONS } from './constants'; -import { getNodeDiff } from '~/api/node'; -import { - flowChangeSearch, - flowSetCellView, - flowSetFlow, - flowSetHeroes, - flowSetNodes, - flowSetRecent, - flowSetSearch, - flowSetUpdated, -} from './actions'; +import { flowChangeSearch, flowSetSearch } from './actions'; import { Unwrap } from '../types'; -import { selectFlow, selectFlowNodes } from './selectors'; -import { getSearchResults, postCellView } from './api'; -import { uniq } from 'ramda'; -import { labSeenNode } from '~/redux/lab/actions'; - -function hideLoader() { - const loader = document.getElementById('main_loader'); - - if (!loader) { - return; - } - - loader.style.display = 'none'; -} - -function* onGetFlow() { - try { - const { - flow: { _persist }, - } = yield select(); - - if (!_persist.rehydrated) return; - - const stored: ReturnType = yield select(selectFlowNodes); - - if (stored.length) { - hideLoader(); - } - - yield put(flowSetFlow({ isLoading: true })); - - const { - before = [], - after = [], - heroes = [], - recent = [], - updated = [], - }: Unwrap = yield call(getNodeDiff, { - start: new Date().toISOString(), - end: new Date().toISOString(), - with_heroes: true, - with_updated: true, - with_recent: true, - with_valid: false, - }); - - const result = uniq([...(before || []), ...(after || [])]); - - yield put(flowSetFlow({ isLoading: false, nodes: result })); - - if (heroes.length) yield put(flowSetHeroes(heroes)); - if (recent.length) yield put(flowSetRecent(recent)); - if (updated.length) yield put(flowSetUpdated(updated)); - - if (!stored.length) hideLoader(); - } catch (error) { - console.log(error); - } -} - -function* onSetCellView({ id, flow }: ReturnType) { - try { - const nodes: ReturnType = yield select(selectFlowNodes); - yield put(flowSetNodes(nodes.map(node => (node.id === id ? { ...node, flow } : node)))); - yield call(postCellView, { id, flow }); - } catch (error) { - console.log(error); - } -} - -function* getMore() { - try { - yield put(flowSetFlow({ isLoading: true })); - const nodes: ReturnType = yield select(selectFlowNodes); - - const start = nodes && nodes[0] && nodes[0].created_at; - const end = nodes && nodes[nodes.length - 1] && nodes[nodes.length - 1].created_at; - - const data: Unwrap = yield call(getNodeDiff, { - start, - end, - with_heroes: false, - with_updated: true, - with_recent: true, - with_valid: true, - }); - - const result = uniq([ - ...(data.before || []), - ...(data.valid ? nodes.filter(node => data.valid.includes(node.id)) : nodes), - ...(data.after || []), - ]); - - yield put( - flowSetFlow({ - isLoading: false, - nodes: result, - ...(data.recent ? { recent: data.recent } : {}), - ...(data.updated ? { updated: data.updated } : {}), - }) - ); - - yield delay(1000); - } catch (error) {} -} +import { selectFlow } from './selectors'; +import { getSearchResults } from './api'; function* changeSearch({ search }: ReturnType) { try { @@ -186,16 +72,7 @@ function* loadMoreSearch() { } } -function* seenNode({ nodeId }: ReturnType) { - const { updated }: ReturnType = yield select(selectFlow); - yield put(flowSetUpdated(updated.filter(node => node.id != nodeId))); -} - export default function* nodeSaga() { - yield takeLatest([FLOW_ACTIONS.GET_FLOW, REHYDRATE], onGetFlow); - yield takeLatest(FLOW_ACTIONS.SET_CELL_VIEW, onSetCellView); - yield takeLeading(FLOW_ACTIONS.GET_MORE, getMore); yield takeLatest(FLOW_ACTIONS.CHANGE_SEARCH, changeSearch); yield takeLatest(FLOW_ACTIONS.LOAD_MORE_SEARCH, loadMoreSearch); - yield takeLatest(FLOW_ACTIONS.SEEN_NODE, seenNode); } diff --git a/src/store/flow/FlowStore.ts b/src/store/flow/FlowStore.ts new file mode 100644 index 00000000..34e045bf --- /dev/null +++ b/src/store/flow/FlowStore.ts @@ -0,0 +1,33 @@ +import { makeAutoObservable } from 'mobx'; +import { IFlowNode } from '~/redux/types'; + +export class FlowStore { + nodes: IFlowNode[] = []; + heroes: IFlowNode[] = []; + recent: IFlowNode[] = []; + updated: IFlowNode[] = []; + + /** if store was updated after rehydration */ + isRefreshed = false; + + constructor() { + makeAutoObservable(this); + } + + setNodes = (nodes: IFlowNode[]) => (this.nodes = nodes); + setHeroes = (heroes: IFlowNode[]) => (this.heroes = heroes); + setRecent = (recent: IFlowNode[]) => (this.recent = recent); + setUpdated = (updated: IFlowNode[]) => (this.updated = updated); + + setIsRefreshed = (refreshed: boolean) => (this.isRefreshed = refreshed); + + /** removes node from updated after user seen it */ + seenNode = (nodeId: number) => { + this.setUpdated(this.updated.filter(node => node.id !== nodeId)); + }; + + /** replaces node with value */ + updateNode = (id: number, node: Partial) => { + this.setNodes(this.nodes.map(it => (it.id === id ? { ...it, ...node } : it))); + }; +} diff --git a/src/store/flow/useFlowStore.ts b/src/store/flow/useFlowStore.ts new file mode 100644 index 00000000..34a13269 --- /dev/null +++ b/src/store/flow/useFlowStore.ts @@ -0,0 +1,3 @@ +import { useStore } from '~/utils/context/StoreContextProvider'; + +export const useFlowStore = () => useStore().flow; diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 00000000..c828c1bd --- /dev/null +++ b/src/store/index.ts @@ -0,0 +1,10 @@ +import { makeAutoObservable } from 'mobx'; +import { FlowStore } from '~/store/flow/FlowStore'; + +export class Store { + flow = new FlowStore(); + + constructor() { + makeAutoObservable(this); + } +} diff --git a/src/utils/context/StoreContextProvider.tsx b/src/utils/context/StoreContextProvider.tsx new file mode 100644 index 00000000..c3a858ee --- /dev/null +++ b/src/utils/context/StoreContextProvider.tsx @@ -0,0 +1,10 @@ +import React, { createContext, FC, useContext } from 'react'; +import { Store } from '~/store'; + +export const StoreContext = createContext(new Store()); + +export const StoreContextProvider: FC<{ store: Store }> = ({ children, store }) => { + return {children}; +}; + +export const useStore = () => useContext(StoreContext); diff --git a/src/utils/context/UserContextProvider.tsx b/src/utils/context/UserContextProvider.tsx index 5d77ec75..c7c7c0ee 100644 --- a/src/utils/context/UserContextProvider.tsx +++ b/src/utils/context/UserContextProvider.tsx @@ -1,11 +1,14 @@ import React, { createContext, FC, useContext } from 'react'; import { IUser } from '~/redux/auth/types'; import { EMPTY_USER } from '~/redux/auth/constants'; +import { useUser } from '~/hooks/user/userUser'; const UserContext = createContext(EMPTY_USER); -export const UserContextProvider: FC<{ user: IUser }> = ({ children, user }) => ( - {children} -); +export const UserContextProvider: FC = ({ children }) => { + const user = useUser(); + + return {children}; +}; export const useUserContext = () => useContext(UserContext); diff --git a/src/utils/dom/hideLoader.ts b/src/utils/dom/hideLoader.ts new file mode 100644 index 00000000..8c58183b --- /dev/null +++ b/src/utils/dom/hideLoader.ts @@ -0,0 +1,9 @@ +export const hideLoader = () => { + const loader = document.getElementById('main_loader'); + + if (!loader) { + return; + } + + loader.style.display = 'none'; +}; diff --git a/yarn.lock b/yarn.lock index b6f5bd04..af27d920 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7526,6 +7526,23 @@ mixin-object@^2.0.1: dependencies: minimist "^1.2.5" +mobx-react-lite@^3.2.0: + version "3.2.3" + resolved "https://registry.yarnpkg.com/mobx-react-lite/-/mobx-react-lite-3.2.3.tgz#83d2b32ebf4383cd0dc0d397acbf53a8e9c66765" + integrity sha512-7exWp1FV0M9dP08H9PIeHlJqDw4IdkQVRMfLYaZFMmlbzSS6ZU6p/kx392KN+rVf81hH3IQYewvRGQ70oiwmbw== + +mobx-react@^7.2.1: + version "7.2.1" + resolved "https://registry.yarnpkg.com/mobx-react/-/mobx-react-7.2.1.tgz#e9d4c04dc63d05e1139ce773f5fee7a5b4cb7c78" + integrity sha512-LZS99KFLn75VWDXPdRJhILzVQ7qLcRjQbzkK+wVs0Qg4kWw5hOI2USp7tmu+9zP9KYsVBmKyx2k/8cTTBfsymw== + dependencies: + mobx-react-lite "^3.2.0" + +mobx@^6.3.10: + version "6.3.10" + resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.3.10.tgz#c3bc715c8f03717b9a2329f9697d42b7998d42e0" + integrity sha512-lfuIN5TGXBNy/5s3ggr1L+IbD+LvfZVlj5q1ZuqyV9AfMtunYQvE8G0WfewS9tgIR3I1q8HJEEbcAOsxEgLwRw== + move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"