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

feat: better image defer and cache control (#593)

* fix: scalar nil value protection

* feat: better image defer and cache control

* v2.0.0-beta.19
上级 f7765359
......@@ -7,16 +7,16 @@ import GridLoader from 'react-spinners/GridLoader';
type ImageProps = {
src?: string;
cache?: number;
};
const Image: FunctionComponent<ImageProps> = ({src}) => {
const Image: FunctionComponent<ImageProps> = ({src, cache}) => {
const {t} = useTranslation('common');
const [url, setUrl] = useState('');
const {data, error, loading} = useRequest<Blob>(src ?? null, blobFetcher, {
// cache image for 5 minutes
dedupingInterval: 5 * 60 * 1000
dedupingInterval: cache ?? 2000
});
// use useLayoutEffect hook to prevent image render after url revoked
......
import React, {FunctionComponent, useState, useMemo} from 'react';
import React, {FunctionComponent, useState, useMemo, useRef, useEffect, useCallback} from 'react';
import styled from 'styled-components';
import queryString from 'query-string';
import isEmpty from 'lodash/isEmpty';
......@@ -82,6 +82,8 @@ type SampleChartProps = {
const getImageUrl = (index: number, run: string, tag: string, wallTime: number): string =>
`/images/image?${queryString.stringify({index, ts: wallTime, run, tag})}`;
const cacheValidity = 5 * 60 * 1000;
const SampleChart: FunctionComponent<SampleChartProps> = ({run, tag, fit, running}) => {
const {t} = useTranslation('common');
......@@ -91,9 +93,49 @@ const SampleChart: FunctionComponent<SampleChartProps> = ({run, tag, fit, runnin
);
const [step, setStep] = useState(0);
const [src, setSrc] = useState<string>();
const cached = useRef<Record<number, {src: string; timer: NodeJS.Timeout}>>({});
const timer = useRef<NodeJS.Timeout | null>(null);
// clear cache if tag or run changed
useEffect(() => {
Object.values(cached.current).forEach(({timer}) => clearTimeout(timer));
cached.current = {};
}, [tag, run]);
const cacheImageSrc = useCallback(() => {
if (!data) {
return;
}
const imageUrl = getImageUrl(step, run, tag, data[step].wallTime);
cached.current[step] = {
src: imageUrl,
timer: setTimeout(() => {
((s: number) => delete cached.current[s])(step);
}, cacheValidity)
};
setSrc(imageUrl);
}, [step, run, tag, data]);
useEffect(() => {
if (cached.current[step]) {
// cached, return immediately
setSrc(cached.current[step].src);
} else if (isEmpty(cached.current)) {
// first load, return immediately
cacheImageSrc();
} else {
timer.current = setTimeout(cacheImageSrc, 500);
return () => {
timer.current && clearTimeout(timer.current);
};
}
}, [step, cacheImageSrc]);
const Content = useMemo(() => {
if (loading) {
// show loading when deferring
if (loading || !cached.current[step]) {
return <GridLoader color={primaryColor} size="10px" />;
}
if (!data && error) {
......@@ -102,8 +144,8 @@ const SampleChart: FunctionComponent<SampleChartProps> = ({run, tag, fit, runnin
if (isEmpty(data)) {
return <span>{t('empty')}</span>;
}
return <Image src={getImageUrl(step, run, tag, (data as ImageData[])[step].wallTime)} />;
}, [loading, error, data, step, run, tag, t]);
return <Image src={src} cache={cacheValidity} />;
}, [loading, error, data, step, src, t]);
return (
<Wrapper>
......@@ -111,7 +153,12 @@ const SampleChart: FunctionComponent<SampleChartProps> = ({run, tag, fit, runnin
<h4>{tag}</h4>
<span>{run}</span>
</Title>
<StepSlider value={step} steps={data?.map(item => item.step) ?? []} onChange={setStep} />
<StepSlider
value={step}
steps={data?.map(item => item.step) ?? []}
onChange={setStep}
onChangeComplete={cacheImageSrc}
/>
<Container fit={fit}>{Content}</Container>
</Wrapper>
);
......
import React, {FunctionComponent, useState, useEffect, useCallback, useRef} from 'react';
import React, {FunctionComponent, useState, useEffect, useCallback} from 'react';
import styled from 'styled-components';
import {em, textLightColor} from '~/utils/style';
import {useTranslation} from '~/utils/i18n';
......@@ -18,35 +18,22 @@ type StepSliderProps = {
value: number;
steps: number[];
onChange?: (value: number) => unknown;
onChangeComplete?: () => unknown;
};
const StepSlider: FunctionComponent<StepSliderProps> = ({onChange, value, steps}) => {
const StepSlider: FunctionComponent<StepSliderProps> = ({onChange, onChangeComplete, value, steps}) => {
const {t} = useTranslation('samples');
const [step, setStep] = useState(value);
const timer = useRef<NodeJS.Timeout | null>(null);
useEffect(() => setStep(value), [value]);
const clearTimer = useCallback(() => {
if (timer.current) {
clearTimeout(timer.current);
timer.current = null;
}
}, []);
// debounce for backend performance consideration
useEffect(() => {
timer.current = setTimeout(() => {
onChange?.(step);
}, 500);
return clearTimer;
}, [step, onChange, clearTimer]);
// trigger immediately when mouse up
const changeComplate = useCallback(() => {
clearTimer();
onChange?.(step);
}, [onChange, clearTimer, step]);
const changeStep = useCallback(
(num: number) => {
setStep(num);
onChange?.(num);
},
[onChange]
);
return (
<>
......@@ -56,8 +43,8 @@ const StepSlider: FunctionComponent<StepSliderProps> = ({onChange, value, steps}
max={steps.length ? steps.length - 1 : 0}
step={1}
value={step}
onChange={setStep}
onChangeComplete={changeComplate}
onChange={changeStep}
onChangeComplete={onChangeComplete}
/>
</>
);
......
{
"name": "visualdl",
"version": "2.0.0-beta.18",
"version": "2.0.0-beta.19",
"title": "VisualDL",
"description": "A platform to visualize the deep learning process and result.",
"keywords": [
......
......@@ -155,8 +155,8 @@ export const tooltip = (data: TooltipData[], i18n: I18n) => {
return {
Run: item.run,
// Keep six number for easy-read.
Smoothed: data[indexPropMap.Smoothed].toString().slice(0, 6),
Value: data[indexPropMap.Value].toString().slice(0, 6),
Smoothed: data[indexPropMap.Smoothed]?.toString().slice(0, 6),
Value: data[indexPropMap.Value]?.toString().slice(0, 6),
Step: data[indexPropMap.Step],
Time: formatTime(data[indexPropMap.Time], i18n.language),
// Relative display value should take easy-read into consideration.
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册