From 767c6d30d6672e50c397f443667f3233a867235d Mon Sep 17 00:00:00 2001 From: Peter Pan Date: Fri, 4 Sep 2020 21:19:38 +0800 Subject: [PATCH] feat: remember selected runs between pages (close #774) (#788) --- .../core/src/components/GlobalState.tsx | 21 ++++++++++++++ .../core/src/hooks/useGlobalState.tsx | 18 ++++++++++++ .../packages/core/src/hooks/useTagFilter.ts | 29 ++++++++++++++----- frontend/packages/core/src/index.tsx | 5 +++- 4 files changed, 65 insertions(+), 8 deletions(-) create mode 100644 frontend/packages/core/src/components/GlobalState.tsx create mode 100644 frontend/packages/core/src/hooks/useGlobalState.tsx diff --git a/frontend/packages/core/src/components/GlobalState.tsx b/frontend/packages/core/src/components/GlobalState.tsx new file mode 100644 index 00000000..3b4c9c86 --- /dev/null +++ b/frontend/packages/core/src/components/GlobalState.tsx @@ -0,0 +1,21 @@ +import {GlobalDispatchContext, GlobalStateContext, globalState} from '~/hooks/useGlobalState'; +import React, {useReducer} from 'react'; + +import type {FunctionComponent} from 'react'; +import type {GlobalState as GlobalStateType} from '~/hooks/useGlobalState'; + +interface GlobalDispatch { + (state: GlobalStateType, newState: Partial): GlobalStateType; +} + +const GlobalState: FunctionComponent = ({children}) => { + const [state, dispatch] = useReducer((state, newState) => ({...state, ...newState}), globalState); + + return ( + + {children} + + ); +}; + +export default GlobalState; diff --git a/frontend/packages/core/src/hooks/useGlobalState.tsx b/frontend/packages/core/src/hooks/useGlobalState.tsx new file mode 100644 index 00000000..d3923613 --- /dev/null +++ b/frontend/packages/core/src/hooks/useGlobalState.tsx @@ -0,0 +1,18 @@ +import {createContext, useContext} from 'react'; + +import type {Dispatch} from 'react'; + +export interface GlobalState { + runs: string[]; +} + +export const globalState: GlobalState = { + runs: [] +}; + +export const GlobalStateContext = createContext(globalState); +export const GlobalDispatchContext = createContext>>(() => void 0); + +const useGlobalState = () => [useContext(GlobalStateContext), useContext(GlobalDispatchContext)] as const; + +export default useGlobalState; diff --git a/frontend/packages/core/src/hooks/useTagFilter.ts b/frontend/packages/core/src/hooks/useTagFilter.ts index 99ed91bf..90f4f3f1 100644 --- a/frontend/packages/core/src/hooks/useTagFilter.ts +++ b/frontend/packages/core/src/hooks/useTagFilter.ts @@ -6,6 +6,7 @@ import groupBy from 'lodash/groupBy'; import intersectionBy from 'lodash/intersectionBy'; import queryString from 'query-string'; import uniq from 'lodash/uniq'; +import useGlobalState from '~/hooks/useGlobalState'; import {useLocation} from 'react-router-dom'; import {useRunningRequest} from '~/hooks/useRequest'; @@ -13,6 +14,7 @@ type Tags = Record; type State = { initRuns: string[]; + globalRuns: string[]; runs: Run[]; selectedRuns: Run[]; initTags: Tags; @@ -86,12 +88,16 @@ const reducer = (state: State, action: Action): State => { switch (action.type) { case ActionType.initRuns: const initRuns = action.payload; + const initRunsGlobalRuns = state.globalRuns.length ? state.globalRuns : initRuns; const initRunsRuns = attachRunColor(initRuns); - const initRunsSelectedRuns = state.selectedRuns.filter(run => initRuns.includes(run.label)); + const initRunsSelectedRuns = state.globalRuns.length + ? initRunsRuns.filter(run => initRunsGlobalRuns.includes(run.label)) + : initRunsRuns; const initRunsTags = groupTags(initRunsSelectedRuns, state.initTags); return { ...state, initRuns, + globalRuns: initRunsGlobalRuns, runs: initRunsRuns, selectedRuns: initRunsSelectedRuns, tags: initRunsTags, @@ -111,6 +117,7 @@ const reducer = (state: State, action: Action): State => { const setSelectedRunsTags = groupTags(action.payload, state.initTags); return { ...state, + globalRuns: action.payload.map(run => run.label), selectedRuns: action.payload, tags: setSelectedRunsTags, selectedTags: setSelectedRunsTags @@ -146,6 +153,8 @@ const useTagFilter = (type: string, running: boolean) => { const {data, loading, error} = useRunningRequest(`/${type}/tags`, running); + const [globalState, globalDispatch] = useGlobalState(); + const runs: string[] = useMemo(() => data?.runs ?? [], [data]); const tags: Tags = useMemo( () => @@ -164,6 +173,7 @@ const useTagFilter = (type: string, running: boolean) => { const [state, dispatch] = useReducer(reducer, { initRuns: [], + globalRuns: globalState.runs, runs: [], selectedRuns: [], initTags: {}, @@ -176,18 +186,23 @@ const useTagFilter = (type: string, running: boolean) => { [query] ); - const runsFromQuery = useMemo( - () => (queryRuns.length ? state.runs.filter(run => queryRuns.includes(run.label)) : state.runs), - [state.runs, queryRuns] - ); - const onChangeRuns = useCallback((runs: Run[]) => dispatch({type: ActionType.setSelectedRuns, payload: runs}), []); const onChangeTags = useCallback((tags: Tag[]) => dispatch({type: ActionType.setSelectedTags, payload: tags}), []); useEffect(() => dispatch({type: ActionType.initRuns, payload: runs || []}), [runs]); - useEffect(() => dispatch({type: ActionType.setSelectedRuns, payload: runsFromQuery}), [runsFromQuery]); useEffect(() => dispatch({type: ActionType.initTags, payload: tags || {}}), [tags]); + useEffect(() => { + if (queryRuns.length) { + const runs = state.runs.filter(run => queryRuns.includes(run.label)); + dispatch({ + type: ActionType.setSelectedRuns, + payload: runs.length ? runs : state.runs + }); + } + }, [queryRuns, state.runs]); + useEffect(() => globalDispatch({runs: state.globalRuns}), [state.globalRuns, globalDispatch]); + const tagsWithSingleRun = useMemo( () => state.tags.reduce((prev, {runs, ...item}) => { diff --git a/frontend/packages/core/src/index.tsx b/frontend/packages/core/src/index.tsx index a5c507a8..e644f293 100644 --- a/frontend/packages/core/src/index.tsx +++ b/frontend/packages/core/src/index.tsx @@ -2,6 +2,7 @@ import '~/utils/i18n'; import App from './App'; import BodyLoading from '~/components/BodyLoading'; +import GlobalState from '~/components/GlobalState'; import {GlobalStyle} from '~/utils/style'; import React from 'react'; import ReactDOM from 'react-dom'; @@ -22,7 +23,9 @@ ReactDOM.render( }> - + + + , document.getElementById('root') -- GitLab