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

opt-in for SSG, remove server-side data fetching & server-side i18n (#574)

* refactor: initialize VisualDL 2.0

* refactor: fix dev server problem

* refactor: add i18n

* refactor: fix i18n

* infra i18n支持

* styled-components for debug

* infra. essential hack for styled comopnent

* use yaml for translation

* fix: navbar url problem

* feat: add nuxt module to build locales

* fix: index route redirect error

* feat: add page title

* refactor: move i18n to module

* feat: add html lang attribute

* R.I.P

* Hello React

* refactor: initialize VisualDL 2.0

* fix: layout rerender

* add favicon

* add page title

* add meta tags

* feat: finish tag filter

* refactor: hook tastes good

* add single select

* finish components

* add api server

* scalars segregate metrics

* json-server sucks

* echarts

* add eslint

* bug fix

* finish scalars page

* change layout, fix aside

* add commit hook

* use tag filter hook

* add chart loading

* encapsulate run select

* samples page under construction

* finish images

* feat: graph page, still need some polishment

* finish high-dimensional

* fix mock data problem

* update readme

* fix build

* fix: use Buffer.from instead of constractor

* update Readme

* add simplified chinese readme

* build: travis

* build: travis

* style: fix type errors

* build: travis-ci

* fix: remove unused page

* add docker build

* opt-in for SSG, remove server-side data fetching & server-side i18n
Co-authored-by: NNiandalu <Niandalu@users.noreply.github.com>
上级 73c30cdb
......@@ -18,7 +18,7 @@ module.exports = {
ecmaVersion: 2018,
sourceType: 'module'
},
plugins: ['react', 'react-hooks', '@typescript-eslint'],
plugins: ['react-hooks'],
settings: {
react: {
version: 'detect'
......@@ -26,11 +26,11 @@ module.exports = {
},
rules: {
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'error',
'react/prop-types': 'off',
'react/react-in-jsx-scope': 'off',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
'@typescript-eslint/no-explicit-any': 'error',
'no-console': 'warn'
}
};
from node:12
# Create app directory
WORKDIR /usr/src/app
ENV NODE_ENV production
COPY package.json .
COPY yarn.lock .
RUN npm i -g pm2
RUN yarn
COPY dist dist
COPY public public
EXPOSE 8999
CMD ["pm2-runtime", "dist/server/index.js"]
<p align="center">
<img align="center" style="width:480px" width="480" src="https://raw.githubusercontent.com/PaddlePaddle/VisualDL/develop/frontend/public/images/logo-visualdl.svg?sanitize=true" />
<a href="https://github.com/PaddlePaddle/VisualDL"><img align="center" style="width:480px" width="480" src="https://raw.githubusercontent.com/PaddlePaddle/VisualDL/develop/frontend/public/images/logo-visualdl.svg?sanitize=true" alt="VisualDL" /></a>
</p>
<br />
......
<p align="center">
<img align="center" style="width:480px" width="480" src="https://raw.githubusercontent.com/PaddlePaddle/VisualDL/develop/frontend/public/images/logo-visualdl.svg?sanitize=true" />
<a href="https://github.com/PaddlePaddle/VisualDL"><img align="center" style="width:480px" width="480" src="https://raw.githubusercontent.com/PaddlePaddle/VisualDL/develop/frontend/public/images/logo-visualdl.svg?sanitize=true" alt="VisualDL" /></a>
</p>
<br />
......
import React, {FunctionComponent, useState} from 'react';
import React, {FunctionComponent, useState, useEffect} from 'react';
import styled from 'styled-components';
import {
WithStyled,
......@@ -93,6 +93,7 @@ const Checkbox: FunctionComponent<CheckboxProps & WithStyled> = ({
onChange
}) => {
const [checked, setChecked] = useState(!!value);
useEffect(() => setChecked(!!value), [setChecked, value]);
const onChangeInput = (e: React.ChangeEvent<HTMLInputElement>) => {
if (disabled) {
return;
......
import React, {FunctionComponent} from 'react';
import {useTranslation} from '~/utils/i18n';
import {useTranslation} from 'react-i18next';
import {NodeType, TypedNode} from '~/resource/graph';
import styled from 'styled-components';
import {WithStyled} from '~/utils/style';
......
import React, {FunctionComponent, useEffect, useState, useRef} from 'react';
import {useTranslation} from '~/utils/i18n';
import {useTranslation} from 'react-i18next';
import fetch from 'isomorphic-unfetch';
type ImageProps = {
......
import React, {FunctionComponent} from 'react';
import styled from 'styled-components';
import {useRouter} from 'next/router';
import {useTranslation, Link} from '~/utils/i18n';
import Link from 'next/link';
import {useTranslation} from 'react-i18next';
import {rem, headerColor, duration, easing, lighten, transitions} from '~/utils/style';
const navItems = ['scalars', 'samples', 'graphs', 'high-dimensional'];
......
import React, {FunctionComponent} from 'react';
import React, {FunctionComponent, useCallback} from 'react';
import styled from 'styled-components';
import {WithStyled, em, size, half, math, primaryColor, textLighterColor, backgroundColor} from '~/utils/style';
import InputRange, {Range} from 'react-input-range';
......@@ -75,7 +75,7 @@ const RangeSlider: FunctionComponent<RangeSliderProps & WithStyled> = ({
value,
disabled
}) => {
const onChangeRange = (range: number | Range) => onChange?.(range as number);
const onChangeRange = useCallback((range: number | Range) => onChange?.(range as number), [onChange]);
return (
<Wrapper className={className} disabled={disabled}>
......
import React, {FunctionComponent} from 'react';
import styled from 'styled-components';
import {rem} from '~/utils/style';
import {useTranslation} from '~/utils/i18n';
import {useTranslation} from 'react-i18next';
import Select, {SelectValueType} from '~/components/Select';
const Title = styled.div`
......
import React, {FunctionComponent, useState, useCallback} from 'react';
import styled from 'styled-components';
import {rem} from '~/utils/style';
import {useTranslation} from '~/utils/i18n';
import {useTranslation} from 'react-i18next';
import Button from '~/components/Button';
const StyledButton = styled(Button)`
......
import React, {FunctionComponent, useState} from 'react';
import styled from 'styled-components';
import useSWR from 'swr';
import {useTranslation} from '~/utils/i18n';
import {useTranslation} from 'react-i18next';
import {em, size, ellipsis, textLightColor} from '~/utils/style';
import StepSlider from '~/components/StepSlider';
import Image from '~/components/Image';
......
......@@ -7,7 +7,7 @@ import maxBy from 'lodash/maxBy';
import sortBy from 'lodash/sortBy';
import {EChartOption} from 'echarts';
import {em, size} from '~/utils/style';
import {useTranslation} from '~/utils/i18n';
import {useTranslation} from 'react-i18next';
import {cycleFetcher} from '~/utils/fetch';
import {transform, range, tooltip, TooltipData} from '~/utils/scalars';
import * as chart from '~/utils/chart';
......
import React, {FunctionComponent, useState, useCallback} from 'react';
import React, {FunctionComponent, useState, useCallback, useEffect} from 'react';
import styled from 'styled-components';
import without from 'lodash/without';
import {useTranslation} from 'react-i18next';
import useClickOutside from '~/hooks/useClickOutside';
import {useTranslation} from '~/utils/i18n';
import {
WithStyled,
em,
......@@ -147,6 +147,12 @@ const Select: FunctionComponent<SelectProps<SelectValueType> & WithStyled> = ({
const setIsOpenedFalse = useCallback(() => setIsOpened(false), []);
const [value, setValue] = useState(multiple ? (Array.isArray(propValue) ? propValue : []) : propValue);
useEffect(() => setValue(multiple ? (Array.isArray(propValue) ? propValue : []) : propValue), [
multiple,
propValue,
setValue
]);
const isSelected = !!(multiple ? value && (value as SelectValueType[]).length !== 0 : (value as SelectValueType));
const changeValue = (mutateValue: SelectValueType, checked?: boolean) => {
let newValue;
......
import React, {FunctionComponent, useState} from 'react';
import styled from 'styled-components';
import {useTranslation} from '~/utils/i18n';
import {useTranslation} from 'react-i18next';
import Field from '~/components/Field';
import RangeSlider from '~/components/RangeSlider';
......
import React, {FunctionComponent, useState} from 'react';
import React, {FunctionComponent, useState, useEffect} from 'react';
import styled from 'styled-components';
import {em, textLightColor} from '~/utils/style';
import {useTranslation} from '~/utils/i18n';
import {useTranslation} from 'react-i18next';
import RangeSlider from '~/components/RangeSlider';
const Label = styled.div`
......@@ -23,6 +23,7 @@ type StepSliderProps = {
const StepSlider: FunctionComponent<StepSliderProps> = ({onChange, value, steps}) => {
const {t} = useTranslation('samples');
const [step, setStep] = useState(value);
useEffect(() => setStep(value), [setStep, value]);
return (
<>
......
import React, {FunctionComponent, useState, useCallback} from 'react';
import React, {FunctionComponent, useState, useCallback, useEffect} from 'react';
import styled from 'styled-components';
import groupBy from 'lodash/groupBy';
import sortBy from 'lodash/sortBy';
import {useTranslation} from '~/utils/i18n';
import {useTranslation} from 'react-i18next';
import {rem, math, ellipsis} from '~/utils/style';
import SearchInput from '~/components/SearchInput';
import Tag from '~/components/Tag';
......@@ -52,7 +52,11 @@ const TagFilter: FunctionComponent<TagFilterProps> = ({value, tags: propTags, on
);
const [matchedCount, setMatchedCount] = useState(propTags?.length ?? 0);
useEffect(() => setMatchedCount(propTags?.length ?? 0), [propTags, setMatchedCount]);
const [inputValue, setInputValue] = useState(value || '');
useEffect(() => setInputValue(value || ''), [value, setInputValue]);
const [selectedValue, setSelectedValue] = useState('');
const hasSelectedValue = selectedValue !== '';
const allText = inputValue || t('all');
......
import {useRef, useEffect, useCallback, MutableRefObject} from 'react';
import echarts, {ECharts} from 'echarts';
import {useTranslation} from '~/utils/i18n';
import {useTranslation} from 'react-i18next';
const useECharts = <T extends HTMLElement>(
loading: boolean
......
import {useReducer} from 'react';
import {useReducer, useEffect, useCallback, useMemo} from 'react';
import useSWR from 'swr';
import groupBy from 'lodash/groupBy';
import uniq from 'lodash/uniq';
import intersection from 'lodash/intersection';
import {NextPageContext} from 'next';
import {fetcher} from '~/utils/fetch';
import {Tag} from '~/types';
import {useRouter} from 'next/router';
type Runs = string[];
type Tags = Record<string, string[]>;
const groupTags = (runs: Runs, tags?: Tags): Tag[] =>
Object.entries(
groupBy<{label: Tag['label']; run: Tag['runs'][number]}>(
runs
// get tags of selected runs
.filter(run => runs.includes(run))
// group by runs
.reduce((prev, run) => {
if (tags && tags[run]) {
Array.prototype.push.apply(
prev,
tags[run].map(label => ({label, run}))
);
}
return prev;
}, []),
tag => tag.label
)
).map(([label, tags]) => ({label, runs: tags.map(tag => tag.run)}));
type State = {
runs: Runs;
initTags: Tags;
tags: Tag[];
filteredTags: Tag[];
};
enum ActionType {
setRuns,
initTags,
setTags,
setFilteredTags
}
......@@ -47,6 +28,11 @@ type ActionSetRuns = {
payload: Runs;
};
type ActionInitTags = {
type: ActionType.initTags;
payload: Tags;
};
type ActionSetTags = {
type: ActionType.setTags;
payload: Tag[];
......@@ -57,78 +43,99 @@ type ActionSetFilteredTags = {
payload: Tag[];
};
type Action = ActionSetRuns | ActionSetTags | ActionSetFilteredTags;
type InitData = {
runs: Runs;
tags: Tags;
};
type Action = ActionSetRuns | ActionInitTags | ActionSetTags | ActionSetFilteredTags;
export type Props = {
tags: Tags;
runs: Runs;
selectedRuns: Runs;
};
const groupTags = (runs: Runs, tags?: Tags): Tag[] =>
Object.entries(
groupBy<{label: Tag['label']; run: Tag['runs'][number]}>(
runs
// get tags of selected runs
.filter(run => runs.includes(run))
// group by runs
.reduce((prev, run) => {
if (tags && tags[run]) {
Array.prototype.push.apply(
prev,
tags[run].map(label => ({label, run}))
);
}
return prev;
}, []),
tag => tag.label
)
).map(([label, tags]) => ({label, runs: tags.map(tag => tag.run)}));
export const defaultProps = {
tags: {},
runs: []
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case ActionType.setRuns:
const runTags = groupTags(action.payload, state.initTags);
return {
...state,
runs: action.payload,
tags: runTags,
filteredTags: runTags
};
case ActionType.initTags:
const newTags = groupTags(state.runs, action.payload);
return {
...state,
initTags: action.payload,
tags: newTags,
filteredTags: newTags
};
case ActionType.setTags:
return {
...state,
tags: action.payload,
filteredTags: action.payload
};
case ActionType.setFilteredTags:
return {
...state,
filteredTags: action.payload
};
default:
throw new Error();
}
};
type GetInitialProps = (type: string, context: NextPageContext, f: typeof fetcher) => Promise<Props>;
const useTagFilters = (type: string) => {
const router = useRouter();
export const getInitialProps: GetInitialProps = async (type, {query}, fetcher) => {
const [runs, tags] = await Promise.all([fetcher('/runs').then(uniq), fetcher(`/${type}/tags`)]);
return {
runs,
selectedRuns: query.runs
? intersection(uniq(Array.isArray(query.runs) ? query.runs : query.runs.split(',')), runs)
: runs,
tags
};
};
const {data: runs} = useSWR<Runs>('/runs');
const {data: tags} = useSWR<Tags>(`/${type}/tags`);
const useTagFilters = (type: string, selectedRuns: Runs, initData: InitData) => {
const {data: runs} = useSWR('/runs', {initialData: initData.runs});
const {data: tags} = useSWR(`/${type}/tags`, {initialData: initData.tags});
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case ActionType.setRuns:
const newTags = groupTags(action.payload, tags);
return {
...state,
runs: action.payload,
tags: newTags,
filteredTags: newTags
};
case ActionType.setTags:
return {
...state,
tags: action.payload,
filteredTags: action.payload
};
case ActionType.setFilteredTags:
return {
...state,
filteredTags: action.payload
};
default:
throw Error();
}
};
const selectedRuns = useMemo(
() =>
runs
? router.query.runs
? intersection(
uniq(Array.isArray(router.query.runs) ? router.query.runs : router.query.runs.split(',')),
runs
)
: runs
: [],
[router, runs]
);
const [state, dispatch] = useReducer(
reducer,
{
runs: selectedRuns,
initTags: {},
tags: groupTags(selectedRuns, tags)
},
initArgs => ({...initArgs, filteredTags: initArgs.tags})
);
const onChangeRuns = (runs: Runs) => dispatch({type: ActionType.setRuns, payload: runs});
const onFilterTags = (tags: Tag[]) => dispatch({type: ActionType.setFilteredTags, payload: tags});
const onChangeRuns = useCallback((runs: Runs) => dispatch({type: ActionType.setRuns, payload: runs}), [dispatch]);
const onInitTags = useCallback((tags: Tags) => dispatch({type: ActionType.initTags, payload: tags}), [dispatch]);
const onFilterTags = useCallback((tags: Tag[]) => dispatch({type: ActionType.setFilteredTags, payload: tags}), [
dispatch
]);
useEffect(() => onInitTags(tags || {}), [onInitTags, tags]);
useEffect(() => onChangeRuns(selectedRuns), [onChangeRuns, selectedRuns]);
return {
runs,
......
......@@ -27,6 +27,8 @@
"dev": "cross-env NODE_ENV=development nodemon --watch server --ext ts --exec \"ts-node --project=tsconfig.server.json\" server/index.ts",
"build:next": "next build",
"build:server": "tsc --project tsconfig.server.json",
"build": "./scripts/build.sh",
"export": "yarn build:next && next export",
"start": "NODE_ENV=production node dist/server/index.js",
"lint": "tsc --noEmit && eslint --ext .tsx,.jsx.ts,.js --ignore-path .gitignore .",
"format": "prettier --write \"**/*.ts\" \"**/*.tsx\" \"**/*.js\" \"**/*.jsx\"",
......@@ -37,15 +39,20 @@
"echarts": "4.6.0",
"echarts-gl": "1.1.1",
"express": "4.17.1",
"i18next": "19.3.2",
"i18next-browser-languagedetector": "4.0.2",
"i18next-chained-backend": "2.0.1",
"i18next-localstorage-backend": "3.1.1",
"i18next-xhr-backend": "3.2.2",
"isomorphic-unfetch": "3.0.0",
"lodash": "4.17.15",
"moment": "2.24.0",
"next": "9.2.2",
"next-i18next": "4.2.0",
"nprogress": "0.2.0",
"polished": "3.4.4",
"react": "16.13.0",
"react-dom": "16.13.0",
"react-i18next": "11.3.3",
"react-input-range": "1.3.0",
"react-is": "16.13.0",
"save-svg-as-png": "1.4.17",
......
import '~/public/style/vdl-icon.css';
import '~/utils/i18n';
import React from 'react';
import {NextComponentType, NextPageContext} from 'next';
import Router from 'next/router';
import App from 'next/app';
import Head from 'next/head';
import NProgress from 'nprogress';
import {SWRConfig} from 'swr';
import {fetcher} from '~/utils/fetch';
import {Router, appWithTranslation} from '~/utils/i18n';
import {GlobalStyle} from '~/utils/style';
import Title from '~/components/Title';
import Layout from '~/components/Layout';
Router.events.on('routeChangeStart', () => NProgress.start());
Router.events.on('routeChangeComplete', () => NProgress.done());
Router.events.on('routeChangeError', () => NProgress.done());
type AppProps<P = {}> = {
// eslint-disable-next-line
Component: {title?: string} & NextComponentType<NextPageContext, any, P>;
};
class VDLApp extends App<AppProps> {
class VDLApp extends App {
render() {
const {Component, pageProps} = this.props;
......@@ -37,7 +31,6 @@ class VDLApp extends App<AppProps> {
<meta name="keywords" content={process.env.keywords} />
<meta name="author" content={process.env.author} />
</Head>
<Title>{Component.title}</Title>
<GlobalStyle />
<SWRConfig
value={{
......@@ -55,4 +48,4 @@ class VDLApp extends App<AppProps> {
}
}
export default appWithTranslation(VDLApp);
export default VDLApp;
import Document, {Head, Main, NextScript, DocumentContext, DocumentProps} from 'next/document';
import Document, {Head, Main, NextScript, DocumentContext} from 'next/document';
import {ServerStyleSheet} from '~/utils/style';
interface VDLDocumentProps extends DocumentProps {
languageDirection: string;
language: string;
}
export default class VDLDocument extends Document<VDLDocumentProps> {
export default class VDLDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
// https://github.com/zeit/next.js/blob/canary/examples/with-typescript-styled-components/pages/_document.tsx
const sheet = new ServerStyleSheet();
......@@ -20,17 +15,8 @@ export default class VDLDocument extends Document<VDLDocumentProps> {
const initialProps = await Document.getInitialProps(ctx);
// stealed from https://github.com/isaachinman/next-i18next/issues/20#issuecomment-558799264
// FIXME: https://github.com/i18next/i18next-express-middleware/blob/master/src/index.js#L23-L26
// eslint-disable-next-line
const {locals} = ctx.res as any;
const additionalProps = {
languageDirection: locals.languageDirection as string,
language: locals.language as string
};
return {
...initialProps,
...additionalProps,
styles: (
<>
{initialProps.styles}
......@@ -44,9 +30,8 @@ export default class VDLDocument extends Document<VDLDocumentProps> {
}
render() {
const {languageDirection, language} = this.props;
return (
<html lang={language} dir={languageDirection}>
<html>
<Head />
<body>
<Main />
......
import React from 'react';
import {useTranslation, NextI18NextPage} from '~/utils/i18n';
import {useTranslation} from 'react-i18next';
import {NextPage} from 'next';
interface ErrorProps {
statusCode?: number | null;
}
const Error: NextI18NextPage<ErrorProps> = ({statusCode}) => {
const Error: NextPage<ErrorProps> = ({statusCode}) => {
const {t} = useTranslation('errors');
return <p>{statusCode ? t('error-with-status', {statusCode}) : t('error-without-status')}</p>;
......@@ -19,7 +20,6 @@ Error.getInitialProps = ({res, err}) => {
({statusCode} = err);
}
return {
namespacesRequired: ['errors'],
statusCode
};
};
......
import React, {useState, useEffect, useMemo} from 'react';
import useSWR from 'swr';
import styled from 'styled-components';
import {NextPage} from 'next';
import RawButton from '~/components/Button';
import RawRangeSlider from '~/components/RangeSlider';
import Content from '~/components/Content';
import Title from '~/components/Title';
import Field from '~/components/Field';
import {useTranslation, NextI18NextPage} from '~/utils/i18n';
import {useTranslation} from 'react-i18next';
import {rem} from '~/utils/style';
import {fetcher} from '~/utils/fetch';
import NodeInfo, {NodeInfoProps} from '~/components/GraphPage/NodeInfo';
......@@ -239,9 +240,7 @@ const useDagreD3 = (graph: Graph | undefined) => {
return {currentNode, displaySwitch, setDisplaySwitch, downloadImage, fitScreen, scale, setScale};
};
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface GraphsProps {}
const Graphs: NextI18NextPage<GraphsProps> = () => {
const Graphs: NextPage = () => {
const {t} = useTranslation(['graphs', 'common']);
const {data: graph} = useSWR<{data: Graph}>('/graphs/graph', fetcher);
const {currentNode, downloadImage, fitScreen, scale, setScale} = useDagreD3(graph ? graph.data : undefined);
......@@ -283,10 +282,4 @@ const Graphs: NextI18NextPage<GraphsProps> = () => {
);
};
Graphs.getInitialProps = () => {
return {
namespacesRequired: ['graphs', 'common']
};
};
export default Graphs;
import React, {useState} from 'react';
import React, {useState, useEffect} from 'react';
import styled from 'styled-components';
import uniq from 'lodash/uniq';
import useSWR from 'swr';
import {withFetcher} from '~/utils/fetch';
import {NextPage} from 'next';
import {useRouter} from 'next/router';
import {rem, em} from '~/utils/style';
import {useTranslation, NextI18NextPage} from '~/utils/i18n';
import {useTranslation} from 'react-i18next';
import Title from '~/components/Title';
import Content from '~/components/Content';
import SearchInput from '~/components/SearchInput';
......@@ -43,22 +43,24 @@ type Data = {
labels: string[];
};
type HighDimensionalProps = {
runs: string[];
selectedRun: string;
};
const HighDimensional: NextI18NextPage<HighDimensionalProps> = ({runs, selectedRun}) => {
const HighDimensional: NextPage = () => {
const {t} = useTranslation(['high-dimensional', 'common']);
const {query} = useRouter();
const queryRun = Array.isArray(query.run) ? query.run[0] : query.run;
const {data: runs} = useSWR<string[]>('/runs');
const selectedRun = runs?.includes(queryRun) ? queryRun : runs?.[0];
const [run, setRun] = useState(selectedRun);
useEffect(() => setRun(selectedRun), [setRun, selectedRun]);
const [search, setSearch] = useState('');
const [dimension, setDimension] = useState(dimensions[0] as Dimension);
const [reduction, setReduction] = useState(reductions[0]);
const [running, setRunning] = useState(true);
const {data, error} = useSWR<Data>(
`/embeddings/embeddings?run=${encodeURIComponent(run)}&dimension=${Number.parseInt(
`/embeddings/embeddings?run=${encodeURIComponent(run ?? '')}&dimension=${Number.parseInt(
dimension
)}&reduction=${reduction}`,
{
......@@ -128,17 +130,4 @@ const HighDimensional: NextI18NextPage<HighDimensionalProps> = ({runs, selectedR
);
};
HighDimensional.defaultProps = {
runs: []
};
HighDimensional.getInitialProps = withFetcher(async ({query}, fetcher) => {
const runs = await fetcher('/runs').then(uniq);
return {
runs,
selectedRun: runs.includes(query.run) ? query.run : runs[0],
namespacesRequired: ['high-dimensional', 'common']
};
});
export default HighDimensional;
import {useEffect} from 'react';
import {NextI18NextPage, Router} from '~/utils/i18n';
import {NextPage} from 'next';
import Router from 'next/router';
const Index: NextI18NextPage = () => {
const Index: NextPage = () => {
useEffect(() => {
Router.replace('/scalars');
}, []);
......@@ -9,10 +10,4 @@ const Index: NextI18NextPage = () => {
return null;
};
Index.getInitialProps = () => {
return {
namespacesRequired: []
};
};
export default Index;
import React, {useState, useCallback, useMemo} from 'react';
import {useTranslation} from 'react-i18next';
import styled from 'styled-components';
import {NextPage} from 'next';
import {rem, em} from '~/utils/style';
import {useTranslation, NextI18NextPage} from '~/utils/i18n';
import useTagFilter, {
getInitialProps as getTagFilterInitialProps,
defaultProps as defaultTagFilterProps,
Props as TagFilterProps
} from '~/hooks/useTagFilter';
import {withFetcher} from '~/utils/fetch';
import useTagFilter from '~/hooks/useTagFilter';
import Title from '~/components/Title';
import Content from '~/components/Content';
import RunSelect from '~/components/RunSelect';
......@@ -42,19 +38,10 @@ type Item = {
label: string;
};
type SamplesProps = TagFilterProps & {};
const Samples: NextI18NextPage<SamplesProps> = ({tags: propTags, runs: propRuns, selectedRuns: propSelectedRuns}) => {
const Samples: NextPage = () => {
const {t} = useTranslation(['samples', 'common']);
const {runs, tags, selectedRuns, selectedTags, onChangeRuns, onFilterTags} = useTagFilter(
'images',
propSelectedRuns,
{
runs: propRuns,
tags: propTags
}
);
const {runs, tags, selectedRuns, selectedTags, onChangeRuns, onFilterTags} = useTagFilter('images');
const ungroupedSelectedTags = useMemo(
() =>
selectedTags.reduce((prev, {runs, ...item}) => {
......@@ -126,15 +113,4 @@ const Samples: NextI18NextPage<SamplesProps> = ({tags: propTags, runs: propRuns,
);
};
Samples.defaultProps = {
...defaultTagFilterProps
};
Samples.getInitialProps = withFetcher(async (context, fetcher) => {
return {
...(await getTagFilterInitialProps('images', context, fetcher)),
namespacesRequired: ['samples', 'common']
};
});
export default Samples;
import React, {useState, useCallback} from 'react';
import {useTranslation, NextI18NextPage} from '~/utils/i18n';
import useTagFilter, {
getInitialProps as getTagFilterInitialProps,
defaultProps as defaultTagFilterProps,
Props as TagFilterProps
} from '~/hooks/useTagFilter';
import {withFetcher} from '~/utils/fetch';
import {useTranslation} from 'react-i18next';
import {NextPage} from 'next';
import useTagFilter from '~/hooks/useTagFilter';
import Title from '~/components/Title';
import Content from '~/components/Content';
import RunSelect from '~/components/RunSelect';
......@@ -25,19 +21,10 @@ const xAxisValues = ['step', 'relative', 'wall'];
type TooltiopSorting = keyof typeof sortingMethodMap;
const toolTipSortingValues = ['default', 'descending', 'ascending', 'nearest'];
type ScalarsProps = TagFilterProps & {};
const Scalars: NextI18NextPage<ScalarsProps> = ({tags: propTags, runs: propRuns, selectedRuns: propSelectedRuns}) => {
const Scalars: NextPage = () => {
const {t} = useTranslation(['scalars', 'common']);
const {runs, tags, selectedRuns, selectedTags, onChangeRuns, onFilterTags} = useTagFilter(
'scalars',
propSelectedRuns,
{
runs: propRuns,
tags: propTags
}
);
const {runs, tags, selectedRuns, selectedTags, onChangeRuns, onFilterTags} = useTagFilter('scalars');
const [smoothing, setSmoothing] = useState(0.6);
......@@ -106,15 +93,4 @@ const Scalars: NextI18NextPage<ScalarsProps> = ({tags: propTags, runs: propRuns,
);
};
Scalars.defaultProps = {
...defaultTagFilterProps
};
Scalars.getInitialProps = withFetcher(async (context, fetcher) => {
return {
...(await getTagFilterInitialProps('scalars', context, fetcher)),
namespacesRequired: ['scalars', 'common']
};
});
export default Scalars;
#!/bin/bash
set -e
if [ ! -d dist ]; then
echo "Please build first!"
exit 1
fi
docker build -t paddlepaddle/visualdl .
......@@ -2,10 +2,7 @@ import path from 'path';
import express from 'express';
import next from 'next';
import {setConfig} from 'next/config';
import nextI18NextMiddleware from 'next-i18next/middleware';
import config from '../next.config';
import nextI18next from '../utils/i18n';
import mock from '../utils/mock';
const isDev = process.env.NODE_ENV !== 'production';
......@@ -20,12 +17,10 @@ const handle = app.getRequestHandler();
const server = express();
if (isDev) {
const {default: mock} = await import('../utils/mock');
server.use(config.env.API_URL, mock({path: path.resolve(__dirname, '../mock')}));
}
await nextI18next.initPromise;
server.use(nextI18NextMiddleware(nextI18next));
server.get('*', (req, res) => handle(req, res));
server.listen(port);
......
......@@ -2,6 +2,7 @@
"extend": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"target": "ES2018",
"esModuleInterop": true,
"typeRoots": ["types"],
"outDir": "dist"
......
import next, {NextPageContext} from 'next';
import i18NextExpressMiddleware from 'i18next-express-middleware';
declare module 'next' {
// FIXME: ~/node_modules/i18next-express-middleware/index.d.ts
interface NextI18NextPageContext extends NextPageContext {
req?: Express.Request;
res?: Express.Response;
}
}
// TODO: use this instead
// https://github.com/zeit/swr/blob/master/examples/axios-typescript/libs/useRequest.ts
import fetch from 'isomorphic-unfetch';
import {NextPageContext} from 'next';
import {Request} from 'express';
/* eslint-disable @typescript-eslint/no-explicit-any */
export const fetcher = async (url: string, options?: any, baseUrl = ''): Promise<any> => {
......@@ -15,12 +13,3 @@ export const fetcher = async (url: string, options?: any, baseUrl = ''): Promise
export const cycleFetcher = async (urls: string[], options?: any, baseUrl = ''): Promise<any> => {
return await Promise.all(urls.map(url => fetcher(url, options, baseUrl)));
};
type GetInitialProps<T = any> = (context: NextPageContext, f: typeof fetcher) => T | Promise<T>;
export const withFetcher = (getInitialProps: GetInitialProps) => (context: NextPageContext) => {
const {req} = context;
// FIXME
const baseUrl = req ? `${((req as unknown) as Request).protocol}://${req.headers.host}` : '';
return getInitialProps(context, (url: string, options: unknown) => fetcher(url, options, baseUrl));
};
import {NextComponentType, NextPageContext} from 'next';
import NextI18Next from 'next-i18next';
import i18n, {InitOptions} from 'i18next';
import {initReactI18next} from 'react-i18next';
import Backend from 'i18next-chained-backend';
import LocalStorageBackend from 'i18next-localstorage-backend';
import XHR from 'i18next-xhr-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
const isProduction = process.env.NODE_ENV === 'production';
const options: InitOptions = {
react: {
useSuspense: false
},
const nextI18Next = new NextI18Next({
browserLanguageDetection: isProduction,
serverLanguageDetection: isProduction,
load: 'languageOnly',
fallbackLng: 'en',
defaultNS: 'common',
defaultLanguage: 'en',
otherLanguages: ['zh'],
localeSubpaths: {
zh: 'zh'
ns: ['common'],
interpolation: {
escapeValue: false // not needed for react as it escapes by default
}
});
};
if (process.browser) {
i18n
// load translation using xhr -> see /public/locales (i.e. https://github.com/i18next/react-i18next/tree/master/example/react/public/locales)
// learn more: https://github.com/i18next/i18next-xhr-backend
.use(Backend)
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
// pass the i18n instance to react-i18next.
.use(initReactI18next)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
backend: {
backends: [LocalStorageBackend, XHR],
backendOptions: [
{
defaultVersion: '1' // TODO: use build id
},
{
loadPath: `${process.env.PUBLIC_PATH}/locales/{{lng}}/{{ns}}.json`,
allowMultiLoading: false,
crossDomain: true,
overrideMimeType: true
}
]
},
// from ~/node_modules/next/types/index.d.ts
// https://gitlab.com/kachkaev/website-frontend/-/blob/master/src/i18n.ts#L64-68
export type NextI18NextPage<P = {}, IP = P> = NextComponentType<
NextPageContext,
IP & {namespacesRequired: string[]},
P & {namespacesRequired: string[]}
>;
detection: {},
export default nextI18Next;
...options
});
} else {
i18n.use(initReactI18next).init(options);
}
export const {i18n, appWithTranslation, withTranslation, useTranslation, Router, Link, Trans} = nextI18Next;
export default i18n;
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册