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"