From a749faef4025b29974be693f0cc2996986d28ee7 Mon Sep 17 00:00:00 2001 From: Peter Pan Date: Wed, 11 Mar 2020 01:06:37 +0800 Subject: [PATCH] frontend code polishment & build progress update (#585) * v2.0.0-beta.12 * v2.0.0-beta.13 * fix: use new development server * v2.0.0-beta.14 * build: build frontend from npm tarball * fix: update python setup * build: fix build on windows * add loading & empty style --- frontend/README.md | 28 ++++- frontend/cli.js | 14 ++- frontend/components/ChartPage.tsx | 26 ++++- frontend/components/Checkbox.tsx | 19 +-- .../HighDimensionalChart.tsx | 5 +- frontend/components/Image.tsx | 17 ++- frontend/components/LineChart.tsx | 34 +++++- frontend/components/Pagination.tsx | 68 ++++++----- frontend/components/RadioButton.tsx | 6 +- .../components/SamplesPage/SampleChart.tsx | 38 ++++-- .../components/ScalarsPage/ScalarChart.tsx | 10 +- frontend/components/ScatterChart.tsx | 34 +++++- frontend/components/Select.tsx | 73 +++++++----- frontend/components/TagFilter.tsx | 24 ++-- frontend/hooks/useECharts.ts | 3 +- frontend/hooks/useRequest.ts | 26 +++++ frontend/hooks/useTagFilter.ts | 10 +- frontend/mock/images/image.ts | 2 +- frontend/package.json | 2 +- frontend/pages/graphs.tsx | 43 +++++-- frontend/pages/high-dimensional.tsx | 26 +++-- frontend/public/locales/en/common.json | 3 +- frontend/public/locales/zh/common.json | 3 +- frontend/resource/scalars/index.ts | 9 +- frontend/server/index.ts | 6 +- frontend/utils/chart.ts | 42 ++++++- scripts/build.ps1 | 30 +++-- scripts/build.sh | 10 +- scripts/start_dev_server.sh | 2 +- tests.sh | 109 ------------------ 30 files changed, 454 insertions(+), 268 deletions(-) create mode 100644 frontend/hooks/useRequest.ts delete mode 100644 tests.sh diff --git a/frontend/README.md b/frontend/README.md index 297067ad..ee1af455 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -24,10 +24,36 @@ English | [简体中文](https://github.com/PaddlePaddle/VisualDL/blob/develop/f **🚧PULL REQUESTS WELCOMED🚧** -## Development +## Usage > nodejs ≥ 10 and npm ≥ 6 are required. +```bash +npm install -g visualdl +# or +yarn global add visualdl +``` + +Then you can start visualdl server by + +```bash +visualdl start --backend="http://127.0.0.1:8040" +``` + +To stop visualdl server, just type + +```bash +visualdl stop +``` + +For more usage infomation, please type + +```bash +visualdl -h +``` + +## Development + First, install all dependencies: ```bash diff --git a/frontend/cli.js b/frontend/cli.js index a4e79912..dc77ba14 100755 --- a/frontend/cli.js +++ b/frontend/cli.js @@ -6,17 +6,23 @@ const argv = require('yargs') .usage('Usage: $0 [options]') .command('start', 'Start VisualDL server') .command('stop', 'Stop VisualDL server') - .example('$0 start --host 192.168.0.2 --port 3000', 'Start VisualDL server at http://192.168.0.2:3000') + .example( + '$0 start --backend="http://172.17.0.82:8040"', + 'Start VisualDL server with backend address http://172.17.0.82:8040' + ) .alias('p', 'port') .nargs('p', 1) .nargs('port', 1) .describe('p', 'Port of server') .nargs('host', 1) .describe('host', 'Host of server') - .nargs('proxy', 1) - .describe('proxy', 'Backend proxy address') + .alias('b', 'backend') + .nargs('b', 1) + .nargs('backend', 1) + .describe('b', 'Backend API address') .boolean('open') .describe('open', 'Open browser when server is ready') + .demandOption(['b']) .help('h') .alias('h', 'help') .epilog('Visit https://github.com/PaddlePaddle/VisualDL for more infomation.').argv; @@ -86,7 +92,7 @@ pm2.connect(err => { NODE_ENV: 'production', HOST: host, PORT: port, - PROXY: argv.proxy + BACKEND: argv.backend } }, err => { diff --git a/frontend/components/ChartPage.tsx b/frontend/components/ChartPage.tsx index c41ac446..f2fed6d3 100644 --- a/frontend/components/ChartPage.tsx +++ b/frontend/components/ChartPage.tsx @@ -1,7 +1,8 @@ -import React, {FunctionComponent, useState} from 'react'; +import React, {FunctionComponent, useState, useMemo} from 'react'; import styled from 'styled-components'; -import {WithStyled, rem, primaryColor} from '~/utils/style'; import BarLoader from 'react-spinners/BarLoader'; +import {WithStyled, rem, primaryColor} from '~/utils/style'; +import {useTranslation} from '~/utils/i18n'; import Chart from '~/components/Chart'; import Pagination from '~/components/Pagination'; @@ -27,6 +28,15 @@ const Loading = styled.div` padding: ${rem(40)} 0; `; +const Empty = styled.div` + display: flex; + justify-content: center; + align-items: center; + font-size: ${rem(20)}; + height: ${rem(150)}; + flex-grow: 1; +`; + // TODO: add types // eslint-disable-next-line type ChartPageProps = { @@ -36,12 +46,14 @@ type ChartPageProps = { }; const ChartPage: FunctionComponent = ({items, loading, withChart, className}) => { + const {t} = useTranslation('common'); + const pageSize = 12; const total = Math.ceil((items?.length ?? 0) / pageSize); const [page, setPage] = useState(1); - const pageItems = items?.slice((page - 1) * pageSize, page * pageSize) ?? []; + const pageItems = useMemo(() => items?.slice((page - 1) * pageSize, page * pageSize) ?? [], [items, page]); return (
@@ -51,9 +63,11 @@ const ChartPage: FunctionComponent = ({items, loadi ) : ( - {pageItems.map((item, index) => ( - {withChart?.(item)} - ))} + {pageItems.length ? ( + pageItems.map((item, index) => {withChart?.(item)}) + ) : ( + {t('empty')} + )} )} diff --git a/frontend/components/Checkbox.tsx b/frontend/components/Checkbox.tsx index 65986fb6..22f263a8 100644 --- a/frontend/components/Checkbox.tsx +++ b/frontend/components/Checkbox.tsx @@ -1,4 +1,4 @@ -import React, {FunctionComponent, useState, useEffect} from 'react'; +import React, {FunctionComponent, useState, useEffect, useCallback} from 'react'; import styled from 'styled-components'; import { WithStyled, @@ -96,13 +96,16 @@ const Checkbox: FunctionComponent = ({ }) => { const [checked, setChecked] = useState(!!value); useEffect(() => setChecked(!!value), [setChecked, value]); - const onChangeInput = (e: React.ChangeEvent) => { - if (disabled) { - return; - } - setChecked(e.target.checked); - onChange?.(e.target.checked); - }; + const onChangeInput = useCallback( + (e: React.ChangeEvent) => { + if (disabled) { + return; + } + setChecked(e.target.checked); + onChange?.(e.target.checked); + }, + [disabled, onChange] + ); return ( diff --git a/frontend/components/HighDimensionalPage/HighDimensionalChart.tsx b/frontend/components/HighDimensionalPage/HighDimensionalChart.tsx index 22505837..e7dfe4b1 100644 --- a/frontend/components/HighDimensionalPage/HighDimensionalChart.tsx +++ b/frontend/components/HighDimensionalPage/HighDimensionalChart.tsx @@ -1,8 +1,8 @@ import React, {FunctionComponent, useMemo} from 'react'; import styled from 'styled-components'; -import useSWR from 'swr'; import queryString from 'query-string'; import {rem, primaryColor} from '~/utils/style'; +import useRequest from '~/hooks/useRequest'; import useHeavyWork from '~/hooks/useHeavyWork'; import {divide, Dimension, Reduction, DivideParams, Point} from '~/resource/high-dimensional'; import ScatterChart from '~/components/ScatterChart'; @@ -52,12 +52,13 @@ const HighDimensionalChart: FunctionComponent = ({ reduction, dimension }) => { - const {data, error} = useSWR( + const {data, error} = useRequest( `/embeddings/embedding?${queryString.stringify({ run: run ?? '', dimension: Number.parseInt(dimension), reduction })}`, + undefined, { refreshInterval: running ? 15 * 1000 : 0 } diff --git a/frontend/components/Image.tsx b/frontend/components/Image.tsx index 48a300cd..a74cb83d 100644 --- a/frontend/components/Image.tsx +++ b/frontend/components/Image.tsx @@ -1,6 +1,7 @@ import React, {FunctionComponent, useLayoutEffect, useState} from 'react'; -import useSWR from 'swr'; +import useRequest from '~/hooks/useRequest'; import {primaryColor} from '~/utils/style'; +import {useTranslation} from '~/utils/i18n'; import {blobFetcher} from '~/utils/fetch'; import GridLoader from 'react-spinners/GridLoader'; @@ -9,9 +10,11 @@ type ImageProps = { }; const Image: FunctionComponent = ({src}) => { + const {t} = useTranslation('common'); + const [url, setUrl] = useState(''); - const {data} = useSWR(src ?? null, blobFetcher); + const {data, error, loading} = useRequest(src ?? null, blobFetcher); // use useLayoutEffect hook to prevent image render after url revoked useLayoutEffect(() => { @@ -25,7 +28,15 @@ const Image: FunctionComponent = ({src}) => { } }, [data]); - return !data ? : ; + if (loading) { + return ; + } + + if (error) { + return
{t('error')}
; + } + + return ; }; export default Image; diff --git a/frontend/components/LineChart.tsx b/frontend/components/LineChart.tsx index 046a6ffc..dcc33fac 100644 --- a/frontend/components/LineChart.tsx +++ b/frontend/components/LineChart.tsx @@ -1,11 +1,32 @@ import React, {FunctionComponent, useEffect, useCallback} from 'react'; +import styled from 'styled-components'; import {EChartOption} from 'echarts'; -import {WithStyled} from '~/utils/style'; +import GridLoader from 'react-spinners/GridLoader'; +import {WithStyled, primaryColor} from '~/utils/style'; import {useTranslation} from '~/utils/i18n'; import useECharts from '~/hooks/useECharts'; import {formatTime} from '~/utils'; import * as chart from '~/utils/chart'; +const Wrapper = styled.div` + position: relative; + + > .echarts { + height: 100%; + } + + > .loading { + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + } +`; + type LineChartProps = { title?: string; legend?: string[]; @@ -89,7 +110,16 @@ const LineChart: FunctionComponent = ({ } }, [data, title, legend, xAxis, type, xAxisFormatter, yRange, tooltip, echart]); - return
; + return ( + + {!echart && ( +
+ +
+ )} +
+
+ ); }; export default LineChart; diff --git a/frontend/components/Pagination.tsx b/frontend/components/Pagination.tsx index 6d7c516a..8e23fbb3 100644 --- a/frontend/components/Pagination.tsx +++ b/frontend/components/Pagination.tsx @@ -1,4 +1,4 @@ -import React, {FunctionComponent} from 'react'; +import React, {FunctionComponent, useMemo, useCallback} from 'react'; import styled from 'styled-components'; import { WithStyled, @@ -75,32 +75,48 @@ const Pagination: FunctionComponent = ({page, tota const padding = 2; const around = 2; - const startEllipsis = page - padding - around - 1 > 0; - const endEllipsis = page + padding + around < total; - const start = - page - around - 1 <= 0 ? [] : Array.from(new Array(Math.min(padding, page - around - 1)), (_v, i) => i + 1); - const end = - page + around >= total - ? [] - : Array.from( - new Array(Math.min(padding, total - page - around)), - (_v, i) => total - padding + i + 1 + Math.max(padding - total + page + around, 0) - ); - const before = - page - 1 <= 0 - ? [] - : Array.from( - new Array(Math.min(around, page - 1)), - (_v, i) => page - around + i + Math.max(around - page + 1, 0) - ); - const after = page >= total ? [] : Array.from(new Array(Math.min(around, total - page)), (_v, i) => page + i + 1); + const startEllipsis = useMemo(() => page - padding - around - 1 > 0, [page]); + const endEllipsis = useMemo(() => page + padding + around < total, [page, total]); + const start = useMemo( + () => + page - around - 1 <= 0 ? [] : Array.from(new Array(Math.min(padding, page - around - 1)), (_v, i) => i + 1), + [page] + ); + const end = useMemo( + () => + page + around >= total + ? [] + : Array.from( + new Array(Math.min(padding, total - page - around)), + (_v, i) => total - padding + i + 1 + Math.max(padding - total + page + around, 0) + ), + [page, total] + ); + const before = useMemo( + () => + page - 1 <= 0 + ? [] + : Array.from( + new Array(Math.min(around, page - 1)), + (_v, i) => page - around + i + Math.max(around - page + 1, 0) + ), + [page] + ); + const after = useMemo( + () => (page >= total ? [] : Array.from(new Array(Math.min(around, total - page)), (_v, i) => page + i + 1)), + [page, total] + ); + + const genLink = useCallback( + (arr: number[]) => + arr.map(i => ( +
  • + onChange?.(i)}>{i} +
  • + )), + [onChange] + ); - const genLink = (arr: number[]) => - arr.map(i => ( -
  • - onChange?.(i)}>{i} -
  • - )); const hellip = (
  • diff --git a/frontend/components/RadioButton.tsx b/frontend/components/RadioButton.tsx index 19338f3d..54cf7ee6 100644 --- a/frontend/components/RadioButton.tsx +++ b/frontend/components/RadioButton.tsx @@ -1,4 +1,4 @@ -import React, {FunctionComponent, useContext} from 'react'; +import React, {FunctionComponent, useContext, useCallback} from 'react'; import styled from 'styled-components'; import { WithStyled, @@ -70,11 +70,11 @@ const RadioButton: FunctionComponent = ({ const groupValue = useContext(ValueContext); const onChange = useContext(EventContext); - const onClick = () => { + const onClick = useCallback(() => { if (value && onChange && groupValue !== value) { onChange(value); } - }; + }, [value, onChange, groupValue]); return (