mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 04:46:40 +07:00
removed lab reducer
This commit is contained in:
parent
e24ea4afeb
commit
2b7b756212
26 changed files with 242 additions and 369 deletions
|
@ -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 };
|
||||
|
|
85
src/hooks/lab/useGetLabNodes.ts
Normal file
85
src/hooks/lab/useGetLabNodes.ts
Normal file
|
@ -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<ILabNode[]> = 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<INode>) => {
|
||||
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 };
|
||||
};
|
72
src/hooks/lab/useGetLabStats.ts
Normal file
72
src/hooks/lab/useGetLabStats.ts
Normal file
|
@ -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 };
|
||||
};
|
|
@ -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 };
|
||||
};
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
|
||||
export const useLabPagination = (
|
||||
isLoading: boolean,
|
||||
columns: Element[],
|
||||
onLoadMore: () => void
|
||||
) => {
|
||||
const loadOnIntersection = useCallback<IntersectionObserverCallback>(
|
||||
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]);
|
||||
};
|
|
@ -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]
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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]);
|
||||
};
|
||||
|
|
|
@ -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]
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue