未验证 提交 b7726fde 编写于 作者: P Peter Pan 提交者: GitHub

fix bugs from Kristy and improve interaction (#809)

* feat: split page runs in global states

* fix: bring loading back when switching to a visited page

* fix: scroll tooltips in scalar & pr-curve to prevent content overflow
上级 507627e1
...@@ -8,8 +8,24 @@ interface GlobalDispatch { ...@@ -8,8 +8,24 @@ interface GlobalDispatch {
(state: GlobalStateType, newState: Partial<GlobalStateType>): GlobalStateType; (state: GlobalStateType, newState: Partial<GlobalStateType>): GlobalStateType;
} }
// TODO: use redux
const GlobalState: FunctionComponent = ({children}) => { const GlobalState: FunctionComponent = ({children}) => {
const [state, dispatch] = useReducer<GlobalDispatch>((state, newState) => ({...state, ...newState}), globalState); const [state, dispatch] = useReducer<GlobalDispatch>(
(state, newState) =>
Object.entries(newState).reduce(
(m, [key, value]) => {
if (m.hasOwnProperty(key)) {
m[key] = {...m[key], ...value};
} else {
m[key] = value;
}
return m;
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
{...state} as any
),
globalState
);
return ( return (
<GlobalStateContext.Provider value={state}> <GlobalStateContext.Provider value={state}>
......
...@@ -171,7 +171,9 @@ const PRCurveChart: FunctionComponent<PRCurveChartProps> = ({cid, runs, tag, run ...@@ -171,7 +171,9 @@ const PRCurveChart: FunctionComponent<PRCurveChartProps> = ({cid, runs, tag, run
...chartOptions, ...chartOptions,
tooltip: { tooltip: {
...chartOptions.tooltip, ...chartOptions.tooltip,
formatter formatter,
hideDelay: 300,
enterable: true
} }
}), }),
[formatter] [formatter]
......
...@@ -204,7 +204,9 @@ const ScalarChart: FunctionComponent<ScalarChartProps> = ({ ...@@ -204,7 +204,9 @@ const ScalarChart: FunctionComponent<ScalarChartProps> = ({
...chartOptions, ...chartOptions,
tooltip: { tooltip: {
...chartOptions.tooltip, ...chartOptions.tooltip,
formatter formatter,
hideDelay: 300,
enterable: true
}, },
xAxis: { xAxis: {
type: xAxisType, type: xAxisType,
......
...@@ -5,6 +5,10 @@ import type {Run} from '~/types'; ...@@ -5,6 +5,10 @@ import type {Run} from '~/types';
import styled from 'styled-components'; import styled from 'styled-components';
const Wrapper = styled.div` const Wrapper = styled.div`
max-height: ${rem(160)};
overflow: hidden auto;
overscroll-behavior: auto contain;
table { table {
border-spacing: none; border-spacing: none;
text-align: left; text-align: left;
......
...@@ -30,6 +30,8 @@ const useECharts = <T extends HTMLElement, W extends HTMLElement = HTMLDivElemen ...@@ -30,6 +30,8 @@ const useECharts = <T extends HTMLElement, W extends HTMLElement = HTMLDivElemen
const onInit = useRef(options.onInit); const onInit = useRef(options.onInit);
const onDispose = useRef(options.onDispose); const onDispose = useRef(options.onDispose);
const hideTip = useCallback(() => echartInstance.current?.dispatchAction({type: 'hideTip'}), []);
const createChart = useCallback(() => { const createChart = useCallback(() => {
(async () => { (async () => {
const {default: echarts} = await import('echarts'); const {default: echarts} = await import('echarts');
...@@ -41,6 +43,8 @@ const useECharts = <T extends HTMLElement, W extends HTMLElement = HTMLDivElemen ...@@ -41,6 +43,8 @@ const useECharts = <T extends HTMLElement, W extends HTMLElement = HTMLDivElemen
} }
echartInstance.current = echarts.init((ref.current as unknown) as HTMLDivElement); echartInstance.current = echarts.init((ref.current as unknown) as HTMLDivElement);
ref.current.addEventListener('mouseleave', hideTip);
setTimeout(() => { setTimeout(() => {
if (options.zoom) { if (options.zoom) {
echartInstance.current?.dispatchAction({ echartInstance.current?.dispatchAction({
...@@ -57,15 +61,16 @@ const useECharts = <T extends HTMLElement, W extends HTMLElement = HTMLDivElemen ...@@ -57,15 +61,16 @@ const useECharts = <T extends HTMLElement, W extends HTMLElement = HTMLDivElemen
setEchart(echartInstance.current); setEchart(echartInstance.current);
})(); })();
}, [options.gl, options.zoom]); }, [options.gl, options.zoom, hideTip]);
const destroyChart = useCallback(() => { const destroyChart = useCallback(() => {
if (echartInstance.current) { if (echartInstance.current) {
onDispose.current?.(echartInstance.current); onDispose.current?.(echartInstance.current);
} }
echartInstance.current?.dispose(); echartInstance.current?.dispose();
ref.current?.removeEventListener('mouseleave', hideTip);
setEchart(null); setEchart(null);
}, []); }, [hideTip]);
useEffect(() => { useEffect(() => {
createChart(); createChart();
......
...@@ -3,13 +3,45 @@ import {createContext, useContext} from 'react'; ...@@ -3,13 +3,45 @@ import {createContext, useContext} from 'react';
import type {Dispatch} from 'react'; import type {Dispatch} from 'react';
export interface GlobalState { export interface GlobalState {
runs: string[]; scalar: {
model: FileList | File[] | null; runs: string[];
};
histogram: {
runs: string[];
};
image: {
runs: string[];
};
audio: {
runs: string[];
};
prCurve: {
runs: string[];
};
graph: {
model: FileList | File[] | null;
};
} }
export const globalState: GlobalState = { export const globalState: GlobalState = {
runs: [], scalar: {
model: null runs: []
},
histogram: {
runs: []
},
image: {
runs: []
},
audio: {
runs: []
},
prCurve: {
runs: []
},
graph: {
model: null
}
}; };
export const GlobalStateContext = createContext<GlobalState>(globalState); export const GlobalStateContext = createContext<GlobalState>(globalState);
......
...@@ -2,6 +2,8 @@ import type {Run, Tag, TagWithSingleRun, TagsData} from '~/types'; ...@@ -2,6 +2,8 @@ import type {Run, Tag, TagWithSingleRun, TagsData} from '~/types';
import {color, colorAlt} from '~/utils/chart'; import {color, colorAlt} from '~/utils/chart';
import {useCallback, useEffect, useMemo, useReducer} from 'react'; import {useCallback, useEffect, useMemo, useReducer} from 'react';
import {cache} from 'swr';
import camelCase from 'lodash/camelCase';
import groupBy from 'lodash/groupBy'; import groupBy from 'lodash/groupBy';
import intersection from 'lodash/intersection'; import intersection from 'lodash/intersection';
import intersectionBy from 'lodash/intersectionBy'; import intersectionBy from 'lodash/intersectionBy';
...@@ -163,7 +165,16 @@ const useTagFilter = (type: string, running: boolean) => { ...@@ -163,7 +165,16 @@ const useTagFilter = (type: string, running: boolean) => {
const {data, loading, error} = useRunningRequest<TagsData>(`/${type}/tags`, running); const {data, loading, error} = useRunningRequest<TagsData>(`/${type}/tags`, running);
// clear cache in order to fully reload data when switching page
useEffect(() => () => cache.delete(`/${type}/tags`), [type]);
const pageName = useMemo(() => camelCase(type), [type]);
const [globalState, globalDispatch] = useGlobalState(); const [globalState, globalDispatch] = useGlobalState();
const storedRuns = useMemo(
() => ((globalState as unknown) as Record<string, {runs: string[]}>)[camelCase(pageName)]?.runs ?? [],
[pageName, globalState]
);
const runs: string[] = useMemo(() => data?.runs ?? [], [data]); const runs: string[] = useMemo(() => data?.runs ?? [], [data]);
const tags: Tags = useMemo( const tags: Tags = useMemo(
...@@ -183,7 +194,7 @@ const useTagFilter = (type: string, running: boolean) => { ...@@ -183,7 +194,7 @@ const useTagFilter = (type: string, running: boolean) => {
const [state, dispatch] = useReducer(reducer, { const [state, dispatch] = useReducer(reducer, {
initRuns: [], initRuns: [],
globalRuns: globalState.runs, globalRuns: storedRuns,
runs: [], runs: [],
selectedRuns: [], selectedRuns: [],
initTags: {}, initTags: {},
...@@ -211,7 +222,15 @@ const useTagFilter = (type: string, running: boolean) => { ...@@ -211,7 +222,15 @@ const useTagFilter = (type: string, running: boolean) => {
}); });
} }
}, [queryRuns, state.runs]); }, [queryRuns, state.runs]);
useEffect(() => globalDispatch({runs: state.globalRuns}), [state.globalRuns, globalDispatch]); useEffect(
() =>
globalDispatch({
[pageName]: {
runs: state.globalRuns
}
}),
[pageName, state.globalRuns, globalDispatch]
);
const tagsWithSingleRun = useMemo( const tagsWithSingleRun = useMemo(
() => () =>
......
...@@ -75,7 +75,7 @@ const Graph: FunctionComponent = () => { ...@@ -75,7 +75,7 @@ const Graph: FunctionComponent = () => {
const graph = useRef<GraphRef>(null); const graph = useRef<GraphRef>(null);
const file = useRef<HTMLInputElement>(null); const file = useRef<HTMLInputElement>(null);
const [files, setFiles] = useState<FileList | File[] | null>(globalState.model); const [files, setFiles] = useState<FileList | File[] | null>(globalState.graph.model);
const onClickFile = useCallback(() => { const onClickFile = useCallback(() => {
if (file.current) { if (file.current) {
file.current.value = ''; file.current.value = '';
...@@ -86,7 +86,7 @@ const Graph: FunctionComponent = () => { ...@@ -86,7 +86,7 @@ const Graph: FunctionComponent = () => {
(e: React.ChangeEvent<HTMLInputElement>) => { (e: React.ChangeEvent<HTMLInputElement>) => {
const target = e.target; const target = e.target;
if (target && target.files && target.files.length) { if (target && target.files && target.files.length) {
globalDispatch({model: target.files}); globalDispatch({graph: {model: target.files}});
setFiles(target.files); setFiles(target.files);
} }
}, },
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册