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

refactor: get rid of webpack & nextjs while introducing es module (#786)

* fix: incorrect locale redirect

* chore: use millisecond

* refactor: get rid of nextjs

* feat: add telemetry

* feat: add disable telemetry option

* fix #739

* fix: compatibility of nodejs 12

* mute webpack

* feat: show min & max value in scalar page

* fix: tooltip sorting chinese translation update

* style: fix lint
上级 e529448f
......@@ -8,7 +8,7 @@ RUN ["pip", "install", "--disable-pip-version-check", "-r", "requirements.txt"]
RUN ["python", "setup.py", "bdist_wheel"]
RUN ["pip", "install", "--disable-pip-version-check", "--find-links=dist", "visualdl"]
WORKDIR frontend
WORKDIR /home/visualdl/frontend
ENV SCOPE server
ENV PUBLIC_PATH /paddle/visualdl/demo
ENV API_URL /paddle/visualdl/demo/api
......
......@@ -9,20 +9,14 @@ module.exports = {
ecmaVersion: 2018,
sourceType: 'module'
},
ignorePatterns: ['node_modules/', 'dist/', 'output/', '_next'],
ignorePatterns: ['node_modules/', 'dist/', 'output/'],
rules: {
'no-console': 'warn',
'sort-imports': 'error'
},
overrides: [
{
files: [
'packages/cli/**/*',
'packages/mock/**/*',
'packages/demo/**/*',
'packages/server/**/*',
'packages/serverless/**/*'
],
files: ['packages/cli/**/*', 'packages/mock/**/*', 'packages/demo/**/*', 'packages/server/**/*'],
extends: [
'plugin:@typescript-eslint/recommended',
'prettier/@typescript-eslint',
......@@ -36,7 +30,7 @@ module.exports = {
}
},
{
files: ['packages/core/**/*', 'packages/i18n/**/*'],
files: ['packages/core/**/*'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
......
{
"hooks": {
"pre-commit": "lint-staged"
}
}
......@@ -59,9 +59,8 @@ yarn
[core](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/core/README.md)
[server](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/server/README.md)
[serverless](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/serverless/README.md)
[netron](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/netron/README.md)
[cli](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/cli/README.md)
[i18n](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/i18n/README.md)
[wasm](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/wasm/README.md)
[mock](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/mock/README.md)
......
......@@ -59,9 +59,8 @@ yarn
[core](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/core/README.md)
[server](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/server/README.md)
[serverless](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/serverless/README.md)
[netron](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/netron/README.md)
[cli](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/cli/README.md)
[i18n](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/i18n/README.md)
[wasm](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/wasm/README.md)
[mock](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/mock/README.md)
......
const path = require('path');
const fs = require('fs');
const getPackages = filenames => [
...new Set(filenames.map(filename => path.relative(path.join(__dirname, 'packages'), filename).split(path.sep)[0]))
];
module.exports = {
// lint all files when global package.json or eslint config changes.
'./(package.json|.eslintrc.js)': () =>
......@@ -8,11 +12,7 @@ module.exports = {
// check types when ts file or package.json changes.
'./(packages/*/package.json|packages/*/**/*.ts?(x))': filenames =>
[
...new Set(
filenames.map(filename => path.relative(path.join(__dirname, 'packages'), filename).split(path.sep)[0])
)
]
getPackages(filenames)
.map(p => path.join(__dirname, 'packages', p, 'tsconfig.json'))
.filter(p => {
try {
......@@ -26,6 +26,15 @@ module.exports = {
// lint changed files
'**/*.(j|t)s?(x)': filenames => [
`eslint ${filenames.join(' ')}`,
`yarn test --silent --bail --findRelatedTests ${filenames.join(' ')}`
...getPackages(filenames).map(p => {
const filename = path.join(__dirname, 'packages', p, 'package.json');
const packageFile = JSON.parse(fs.readFileSync(filename, 'utf-8'));
if (packageFile.scripts.test === 'jest') {
return `yarn workspace @visualdl/${p} run test --silent --bail --findRelatedTests ${filenames.join(
' '
)}`;
}
return `yarn workspace @visualdl/${p} run test`;
})
]
};
......@@ -29,29 +29,29 @@
"scripts": {
"bootstrap": "lerna bootstrap",
"build": "./scripts/build.sh",
"clean": "rimraf output *.log packages/*/dist packages/core/out packages/wasm/target packages/*/*.log",
"clean": "rimraf output packages/*/dist packages/wasm/target",
"lint": "eslint --ext .tsx,.jsx.ts,.js --ignore-path .gitignore .",
"format": "prettier --write \"**/*.ts\" \"**/*.tsx\" \"**/*.js\"",
"format": "prettier --write \"**/*.{ts,tsx,js,jsx}\"",
"test": "yarn workspaces run test",
"prepublishOnly": "yarn lint && yarn test && yarn build",
"preversion": "yarn lint",
"version": "yarn format && git add -A"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "3.8.0",
"@typescript-eslint/parser": "3.8.0",
"eslint": "7.6.0",
"@typescript-eslint/eslint-plugin": "4.0.1",
"@typescript-eslint/parser": "4.0.1",
"eslint": "7.8.1",
"eslint-config-prettier": "6.11.0",
"eslint-plugin-prettier": "3.1.4",
"eslint-plugin-react": "7.20.5",
"eslint-plugin-react-hooks": "4.0.8",
"eslint-plugin-react": "7.20.6",
"eslint-plugin-react-hooks": "4.1.0",
"husky": "4.2.5",
"lerna": "3.22.1",
"lint-staged": "10.2.11",
"prettier": "2.0.5",
"lint-staged": "10.2.13",
"prettier": "2.1.1",
"rimraf": "3.0.2",
"typescript": "3.9.7",
"yarn": "1.22.4"
"typescript": "4.0.2",
"yarn": "1.22.5"
},
"engines": {
"node": ">=10",
......
......@@ -23,10 +23,10 @@
"directory": "frontend/packages/cli"
},
"scripts": {
"dev": "cross-env NODE_ENV=development ts-node index.ts",
"build": "tsc",
"dev": "cross-env NODE_ENV=development ts-node index.ts",
"start": "node index.js",
"test": "echo \"Error: no test specified\" && exit 0; #"
"test": "echo \"Error: no test specified\" && exit 0"
},
"bin": "dist/index.js",
"types": "dist/index.d.ts",
......@@ -35,28 +35,23 @@
],
"dependencies": {
"@visualdl/server": "2.0.0",
"open": "7.1.0",
"ora": "4.0.5",
"pm2": "4.4.0",
"open": "7.2.1",
"ora": "5.0.0",
"pm2": "4.4.1",
"yargs": "15.4.1"
},
"devDependencies": {
"@types/node": "14.0.27",
"@types/node": "14.6.3",
"@types/yargs": "15.0.5",
"cross-env": "7.0.2",
"ts-node": "8.10.2",
"typescript": "3.9.7"
"ts-node": "9.0.0",
"typescript": "4.0.2"
},
"engines": {
"node": ">=10",
"node": ">=12",
"npm": ">=6"
},
"publishConfig": {
"access": "public"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
}
}
# VisualDL FrontEnd Core
This is core contents of VisualDL pages.
DO NOT use it directly.
## Development
......@@ -12,9 +11,3 @@ yarn dev
```
Now open [http://localhost:3000](http://localhost:3000) with your browser.
You can change the port with `PORT` environment variable:
```bash
PORT=8999 yarn dev
```
import Page404 from '../pages/404';
import React from 'react';
import {shallow} from 'enzyme';
describe('Page 404', () => {
test('page with content `404 - errors:page-not-found`', () => {
const page = shallow(<Page404 namespacesRequired={['errors']} />);
expect(page.text()).toEqual('404 - errors:page-not-found');
});
});
import Icon from '../components/Icon';
import React from 'react';
import {shallow} from 'enzyme';
describe('Icon component', () => {
const click = jest.fn();
const icon = shallow(<Icon type="close" onClick={click} />);
test('icon has one empty `i` element', () => {
expect(icon.is('i')).toBe(true);
expect(icon.children().length).toBe(0);
});
test('icon has class name `vdl-icon`', () => {
expect(icon.hasClass('vdl-icon')).toBe(true);
});
test('icon with type has class name `icon-${type}`', () => {
expect(icon.hasClass('icon-close')).toBe(true);
});
test('icon click', () => {
icon.simulate('click');
expect(click.mock.calls.length).toBe(1);
});
});
module.exports = {
presets: ['next/babel'],
plugins: [
[
'styled-components',
{
ssr: true,
displayName: true,
preprocess: false
}
],
['emotion'],
...(process.env.NODE_ENV !== 'production' ? ['typescript-to-proptypes'] : [])
]
extends: '@snowpack/app-scripts-react/babel.config.json',
plugins: ['styled-components']
};
/* eslint-disable no-console */
import ora from 'ora';
import path from 'path';
import {spawn} from 'child_process';
import {writeFileSync} from 'fs';
const next = require.resolve('next/dist/bin/next');
export const projectRoot = path.dirname(require.resolve('@visualdl/core'));
export default function (action: 'build' | 'export', ...args: string[]): Promise<number> {
return new Promise((resolve, reject) => {
const capitalizedAction = action.replace(/^./, w => w.toUpperCase());
const spinner = ora(`${capitalizedAction} in process...`).start();
const log = path.join(process.cwd(), `${action}.log`);
writeFileSync(log, '', {flag: 'w'});
const p = spawn(next, [action, ...args], {
cwd: projectRoot,
env: {
...process.env,
NODE_ENV: 'production'
}
});
p.stdout.on('data', data => writeFileSync(log, data, {flag: 'a'}));
p.stderr.on('data', data => writeFileSync(log, data, {flag: 'a'}));
p.on('close', code => {
if (code) {
spinner.fail(`${capitalizedAction} failed!`);
console.error(`Please refer to ${log} for more detail.`);
reject(code);
} else {
spinner.succeed(`${capitalizedAction} complete!`);
resolve(code);
}
});
});
}
/* eslint-disable @typescript-eslint/no-var-requires */
const express = require('express');
const mock = require('./mock');
const icons = require('./icons');
const netron = require('./netron');
const wasm = require('./wasm');
const argv = require('yargs').nargs('port', 1).number('port').nargs('host', 1).argv;
const app = express();
app.use(mock.pathname, mock.middleware());
app.use(icons.pathname, icons.middleware());
app.use(netron.pathname, express.static(netron.root, {index: false}));
app.get(`${wasm.pathname}/${wasm.out}`, (_req, res) => {
res.type('application/wasm');
res.sendFile(wasm.source);
});
app.listen(argv.port, argv.host);
process.env.SNOWPACK_PUBLIC_PATH =
process.env.PUBLIC_PATH === '/' || !process.env.PUBLIC_PATH ? '' : process.env.PUBLIC_PATH;
process.env.SNOWPACK_PUBLIC_API_URL = process.env.API_URL || `${process.env.SNOWPACK_PUBLIC_PATH}/api`;
process.env.SNOWPACK_PUBLIC_TELEMETRY_ID = process.env.TELEMETRY_ID || '';
process.env.SNOWPACK_PUBLIC_API_TOKEN_KEY = process.env.API_TOKEN_KEY || '';
process.env.SNOWPACK_PUBLIC_LANGUAGES = process.env.LANGUAGES || 'en,zh';
process.env.SNOWPACK_PUBLIC_DEFAULT_LANGUAGE = process.env.DEFAULT_LANGUAGE || 'en';
/* eslint-disable no-console */
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require('path');
const {promises: fs} = require('fs');
const {default: svgr} = require('@svgr/core');
const babel = require('@babel/core');
const {camelCase} = require('lodash');
const {ensureDir} = require('fs-extra');
const root = path.resolve(__dirname, '../public/icons');
const pathname = '/icons';
const dist = path.resolve(__dirname, '../dist');
const dest = path.join(dist, pathname);
async function transform(file, minified) {
const basename = path.basename(file, '.svg');
let jsx = await svgr(
await fs.readFile(file, 'utf-8'),
{
icon: true,
svgProps: {
fill: 'currentColor',
className: 'vdl-icon'
}
},
{componentName: camelCase(basename).replace(/./, w => w.toUpperCase())}
);
jsx = jsx.replace('import * as React from "react";', 'import React from "../web_modules/react.js";');
const result = await babel.transformAsync(jsx, {
filename: basename + '.jsx',
presets: ['@babel/preset-react'],
minified
});
return result.code;
}
async function build() {
await ensureDir(dest);
const files = await fs.readdir(root);
for (const file of files) {
if (path.extname(file) === '.svg') {
const js = await transform(path.join(root, file), false);
await fs.writeFile(path.join(dest, path.basename(file, '.svg') + '.js'), js, 'utf-8');
}
}
console.log('Icons copied!');
}
if (require.main === module) {
build();
}
const middleware = () => {
return async (req, res) => {
const file = path.join(root, req.path.replace(/\.js$/, '.svg'));
if ((await fs.stat(file)).isFile()) {
res.type('js');
res.send(await transform(file, false));
}
};
};
module.exports = {
middleware,
root,
pathname
};
/* eslint-disable @typescript-eslint/no-var-requires */
const {middleware} = require('@visualdl/mock');
module.exports = {
middleware,
pathname: '/api'
};
/* eslint-disable no-console */
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require('path');
const fs = require('fs-extra');
const root = path.dirname(require('enhanced-resolve').sync(__dirname, '@visualdl/netron'));
const pathname = '/netron';
const dist = path.resolve(__dirname, '../dist');
const dest = path.join(dist, pathname);
async function build() {
await fs.ensureDir(dest);
await fs.copy(root, dest, {preserveTimestamps: true});
console.log('Netron copied!');
}
if (require.main === module) {
build();
}
module.exports = {
root,
dest,
pathname
};
/* eslint-disable no-console */
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require('path');
const fs = require('fs-extra');
const root = path.dirname(require('enhanced-resolve').sync(__dirname, '@visualdl/wasm'));
const source = path.join(root, 'index_bg.wasm');
const dist = path.resolve(__dirname, '../dist');
const pathname = '/wasm';
const out = 'visualdl.wasm';
const dest = path.join(dist, pathname, out);
async function build() {
await fs.ensureDir(path.join(dist, pathname));
await fs.copy(source, dest, {preserveTimestamps: true});
console.log('WebAssembly copied!');
}
if (require.main === module) {
build();
}
module.exports = {
dest,
source,
pathname,
out
};
import React, {FunctionComponent} from 'react';
import {WithStyled} from '~/utils/style';
type IconProps = {
type: string;
onClick?: () => unknown;
};
const Icon: FunctionComponent<IconProps & WithStyled> = ({type, onClick, className}) => {
return <i className={`vdl-icon icon-${type} ${className ?? ''}`} onClick={() => onClick?.()}></i>;
};
export default Icon;
import React, {FunctionComponent} from 'react';
import {headerHeight, position, size} from '~/utils/style';
import Navbar from '~/components/Navbar';
import styled from 'styled-components';
const Main = styled.main`
padding-top: ${headerHeight};
`;
const Header = styled.header`
z-index: 10000;
${size(headerHeight, '100%')}
${position('fixed', 0, 0, null, 0)}
`;
const Layout: FunctionComponent = ({children}) => (
<Main>
<Header>
<Navbar />
</Header>
{children}
</Main>
);
export default Layout;
import React, {FunctionComponent} from 'react';
import Head from 'next/head';
type PreloaderProps = {
url: string;
as?:
| 'audio'
| 'document'
| 'embed'
| 'fetch'
| 'font'
| 'image'
| 'object'
| 'script'
| 'style'
| 'track'
| 'worker'
| 'video';
};
const Preloader: FunctionComponent<PreloaderProps> = ({url, as}) =>
process.env.API_TOKEN_KEY ? null : (
<Head>
<link rel="preload" href={process.env.API_URL + url} crossOrigin="anonymous" as={as || 'fetch'} />
</Head>
);
export default Preloader;
import React, {FunctionComponent} from 'react';
import Head from 'next/head';
const Title: FunctionComponent = ({children: title}) => (
<Head>
<title>
{'string' === typeof title ? `${title} - ` : ''}
{process.env.title}
</title>
</Head>
);
export default Title;
module.exports = require('next');
/* eslint-disable @typescript-eslint/no-var-requires */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
moduleFileExtensions: ['ts', 'tsx', 'js'],
transform: {
'^.+\\.(ts|tsx)$': 'ts-jest'
},
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.[jt]sx?$',
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
coveragePathIgnorePatterns: ['<rootDir>/next.config.js', '<rootDir>/node_modules/'],
testPathIgnorePatterns: ['<rootDir>/dist/', '<rootDir>/out/', '<rootDir>/node_modules/'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
moduleNameMapper: {
'\\.css$': '<rootDir>/__mocks__/styleMock.js',
'~/(.*)': '<rootDir>/$1'
},
snapshotSerializers: ['enzyme-to-json/serializer'],
globals: {
'ts-jest': {
tsConfig: '<rootDir>/tsconfig.test.json'
}
}
...require('@snowpack/app-scripts-react/jest.config.js')()
};
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect';
import ReactSixteenAdapter from 'enzyme-adapter-react-16';
import {configure} from 'enzyme';
configure({adapter: new ReactSixteenAdapter()});
/// <reference types="next" />
/// <reference types="next/types/global" />
declare type NextEnv = {
PUBLIC_PATH: string;
API_URL: string;
DEFAULT_LANGUAGE: string;
LANGUAGES: string[];
LOCALE_PATH: string;
};
declare interface NextConfig {
env: NextEnv;
}
const nextConfig: NextConfig;
export default nextConfig;
export const env: NextEnv;
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require('path');
const pkg = require('./package.json');
const publicPath = process.env.PUBLIC_PATH || '';
const apiUrl = process.env.API_URL || '/api';
const distDir = 'dist';
const APP = {
name: pkg.name,
version: pkg.version,
title: pkg.title,
description: pkg.description,
author: pkg.author,
keywords: pkg.keywords.join(',')
};
const DEFAULT_LANGUAGE = 'en';
const LOCALE_PATH = 'public/locales';
const LANGUAGES = ['en', 'zh'];
const otherLanguages = LANGUAGES.filter(lang => lang !== DEFAULT_LANGUAGE);
module.exports = {
assetPrefix: publicPath,
distDir,
poweredByHeader: false,
env: {
...APP,
DEFAULT_LANGUAGE,
LOCALE_PATH,
LANGUAGES,
API_URL: apiUrl,
PUBLIC_PATH: publicPath,
API_TOKEN_KEY: process.env.API_TOKEN_KEY || ''
},
exportPathMap: defaultPathMap => ({
...defaultPathMap,
...Object.entries(defaultPathMap).reduce((prev, [path, router]) => {
otherLanguages.forEach(lang => (prev[`/${lang}${path}`] = router));
return prev;
}, {})
}),
experimental: {
basePath: publicPath
},
webpack: config => {
const WorkerPlugin = require('worker-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
config.resolve = config.resolve || {};
config.resolve.alias = config.resolve.alias || {};
config.resolve.alias['~'] = path.resolve(__dirname);
config.node = Object.assign({}, config.node, {
// eslint-disable-next-line @typescript-eslint/naming-convention
child_process: 'empty',
fs: 'empty'
});
config.plugins = [
...(config.plugins || []),
new WorkerPlugin({
globalObject: 'self'
}),
new CopyWebpackPlugin({
patterns: [
{
context: path.dirname(require('enhanced-resolve').sync(__dirname, '@visualdl/netron')),
from: '**/*',
to: 'static/netron',
toType: 'dir'
}
]
})
];
return config;
}
};
{
"name": "@visualdl/core",
"version": "2.0.0",
"title": "VisualDL",
"description": "A platform to visualize the deep learning process and result.",
"keywords": [
"visualdl",
......@@ -24,90 +23,87 @@
"directory": "frontend/packages/core"
},
"scripts": {
"dev": "next",
"build": "echo 'This package does not need to build. Please use @visualdl/server or @visualdl/serverless to build from source.'; echo 'If you want to test build function, please run `build:next` instead.'",
"build:next": "next build",
"export": "next export",
"start": "next start",
"test": "NODE_OPTIONS=\"--max-old-space-size=4096\" jest",
"test:coverage": "NODE_OPTIONS=\"--max-old-space-size=4096\" jest --coverage"
"dev": "snowpack dev",
"build": "snowpack build",
"start": "yarn dev --reload",
"test": "jest"
},
"main": "dist/index.html",
"files": [
"dist"
],
"dependencies": {
"@tippyjs/react": "4.1.0",
"@visualdl/i18n": "2.0.0",
"@visualdl/netron": "2.0.0",
"@visualdl/wasm": "2.0.0",
"bignumber.js": "9.0.0",
"d3-format": "1.4.4",
"echarts": "4.8.0",
"d3-format": "2.0.0",
"echarts": "4.9.0",
"echarts-gl": "1.1.1",
"eventemitter3": "4.0.4",
"eventemitter3": "4.0.7",
"file-saver": "2.0.2",
"isomorphic-unfetch": "3.0.0",
"lodash": "4.17.19",
"i18next": "19.7.0",
"i18next-browser-languagedetector": "6.0.1",
"i18next-fetch-backend": "3.0.0",
"lodash": "4.17.20",
"mime-types": "2.1.27",
"moment": "2.27.0",
"next": "9.4.4",
"nprogress": "0.2.0",
"polished": "3.6.5",
"prop-types": "15.7.2",
"polished": "3.6.6",
"query-string": "6.13.1",
"react": "16.13.1",
"react-dom": "16.13.1",
"react-helmet": "6.1.0",
"react-i18next": "11.7.2",
"react-input-range": "1.3.0",
"react-is": "16.13.1",
"react-rangeslider": "2.2.0",
"react-router-dom": "5.2.0",
"react-spinners": "0.9.0",
"react-toastify": "6.0.8",
"save-svg-as-png": "1.4.17",
"styled-components": "5.1.1",
"swr": "0.3.0",
"tippy.js": "6.2.6"
},
"devDependencies": {
"@babel/core": "7.11.1",
"@babel/core": "7.11.5",
"@babel/preset-react": "7.10.4",
"@snowpack/app-scripts-react": "1.10.0",
"@snowpack/plugin-dotenv": "2.0.1",
"@snowpack/plugin-run-script": "2.1.1",
"@svgr/core": "5.4.0",
"@testing-library/jest-dom": "5.11.4",
"@testing-library/react": "10.4.9",
"@types/d3-format": "1.3.1",
"@types/echarts": "4.6.4",
"@types/enzyme": "3.10.5",
"@types/enzyme-adapter-react-16": "1.0.6",
"@types/echarts": "4.6.5",
"@types/file-saver": "2.0.1",
"@types/jest": "26.0.8",
"@types/lodash": "4.14.158",
"@types/jest": "26.0.13",
"@types/loadable__component": "5.13.0",
"@types/lodash": "4.14.161",
"@types/mime-types": "2.1.0",
"@types/node": "14.0.27",
"@types/nprogress": "0.2.0",
"@types/react": "16.9.44",
"@types/react": "16.9.49",
"@types/react-dom": "16.9.8",
"@types/react-helmet": "6.1.0",
"@types/react-rangeslider": "2.2.3",
"@types/styled-components": "5.1.2",
"@types/react-router-dom": "5.1.5",
"@types/snowpack-env": "2.3.0",
"@types/styled-components": "5.1.3",
"@visualdl/mock": "2.0.0",
"babel-plugin-emotion": "10.0.33",
"babel-plugin-styled-components": "1.11.1",
"babel-plugin-typescript-to-proptypes": "1.4.0",
"copy-webpack-plugin": "6.0.3",
"core-js": "3.6.5",
"cross-env": "7.0.2",
"css-loader": "4.2.0",
"enhanced-resolve": "4.3.0",
"enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.2",
"enzyme-to-json": "3.5.0",
"jest": "26.2.2",
"ora": "4.0.5",
"ts-jest": "26.1.4",
"typescript": "3.9.7",
"worker-plugin": "4.0.3"
"express": "4.17.1",
"fs-extra": "9.0.1",
"jest": "26.4.2",
"snowpack": "2.10.1",
"typescript": "4.0.2",
"yargs": "15.4.1"
},
"engines": {
"node": ">=10",
"node": ">=12",
"npm": ">=6"
},
"publishConfig": {
"access": "public"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
}
}
import {NextI18NextPage, useTranslation} from '~/utils/i18n';
import {headerHeight, rem} from '~/utils/style';
import React from 'react';
import styled from 'styled-components';
const Error = styled.div`
height: calc(100vh - ${headerHeight});
display: flex;
justify-content: center;
align-items: center;
font-size: ${rem(20)};
`;
const Error404: NextI18NextPage = () => {
const {t} = useTranslation('errors');
return <Error>404 - {t('errors:page-not-found')}</Error>;
};
// 404 page cannot have getInitialProps or getServerSideProps
// we need next-i18next support getStaticProps
// https://github.com/zeit/next.js/blob/master/errors/404-get-initial-props.md
// https://github.com/isaachinman/next-i18next/issues/652
// Error404.getInitialProps = () => {
// return {
// namespacesRequired: ['errors']
// };
// };
export default Error404;
import {Router, appWithTranslation} from '~/utils/i18n';
import {fetcher, getApiToken} from '~/utils/fetch';
import App from 'next/app';
import {GlobalStyle} from '~/utils/style';
import Head from 'next/head';
import Layout from '~/components/Layout';
import NProgress from 'nprogress';
import React from 'react';
import {SWRConfig} from 'swr';
import {ToastContainer} from 'react-toastify';
import queryString from 'query-string';
import {withRouter} from 'next/router';
const API_TOKEN_KEY = process.env.API_TOKEN_KEY;
const PUBLIC_PATH = process.env.PUBLIC_PATH;
class VDLApp extends App {
componentDidMount() {
Router.events.on('routeChangeStart', () => NProgress.start());
Router.events.on('routeChangeComplete', (url: string) => {
NProgress.done();
if (API_TOKEN_KEY) {
const id = getApiToken();
const parsed = queryString.parseUrl(url);
if (id && !parsed.query[API_TOKEN_KEY]) {
this.props.router.replace(
queryString.stringifyUrl({
url: parsed.url,
query: {
...parsed.query,
[API_TOKEN_KEY]: id
}
}),
undefined,
{shallow: true}
);
}
}
});
Router.events.on('routeChangeError', () => NProgress.done());
}
render() {
const {Component, pageProps} = this.props;
return (
<>
<Head>
<title>{process.env.title}</title>
<link rel="shortcut icon" href={`${PUBLIC_PATH}/favicon.ico`} />
<meta
name="viewport"
content="width=device-width,minimum-scale=1,maximum-scale=1,initial-scale=1,user-scalable=no,shrink-to-fit=no"
/>
<meta name="description" content={process.env.description} />
<meta name="keywords" content={process.env.keywords} />
<meta name="author" content={process.env.author} />
</Head>
<GlobalStyle />
<SWRConfig
value={{
fetcher,
revalidateOnFocus: false,
revalidateOnReconnect: false
}}
>
<Layout>
<Component {...pageProps} />
</Layout>
<ToastContainer position="top-center" hideProgressBar closeOnClick={false} />
</SWRConfig>
</>
);
}
}
export default appWithTranslation(withRouter(VDLApp));
import Document, {
DocumentContext,
DocumentInitialProps,
DocumentProps,
Head,
Html,
Main,
NextScript
} from 'next/document';
import {ServerStyleSheet} from '~/utils/style';
interface VDLDocumentProps extends DocumentProps {
language: string;
languageDir: string;
}
export default class VDLDocument extends Document<VDLDocumentProps> {
static async getInitialProps(ctx: DocumentContext): Promise<DocumentInitialProps> {
// https://github.com/zeit/next.js/blob/canary/examples/with-typescript-styled-components/pages/_document.tsx
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: App => props => sheet.collectStyles(<App {...props} />)
});
const initialProps = await Document.getInitialProps(ctx);
// steal 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
const additionalProps = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...((ctx.res as any)?.locals || {})
};
return {
...initialProps,
...additionalProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
)
};
} finally {
sheet.seal();
}
}
render(): JSX.Element {
const {language, languageDir} = this.props;
return (
<Html lang={language} dir={languageDir}>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
import {NextI18NextPage, useTranslation} from '~/utils/i18n';
import {headerHeight, rem} from '~/utils/style';
import styled from 'styled-components';
const ErrorDiv = styled.div`
height: calc(100vh - ${headerHeight});
display: flex;
justify-content: center;
align-items: center;
font-size: ${rem(20)};
`;
interface ErrorProps {
statusCode?: number | null;
}
const Error: NextI18NextPage<ErrorProps> = ({statusCode}) => {
const {t} = useTranslation('errors');
return (
<ErrorDiv>
{statusCode ? t('errors:error-with-status', {statusCode}) : t('errors:error-without-status')}
</ErrorDiv>
);
};
Error.getInitialProps = ({res, err}) => {
let statusCode = null;
if (res) {
({statusCode} = res);
} else if (err) {
({statusCode} = err);
}
return {
namespacesRequired: ['errors'],
statusCode
};
};
export default Error;
import mock from '@visualdl/mock';
const delay = Number.parseInt(process.env.DELAY || '', 10);
export default mock({delay: delay ? () => Math.random() * delay : 0});
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>audio</title>
<path d="M8.628 10.97c-0.333 0-0.653 0.14-0.889 0.391s-0.368 0.589-0.368 0.943v10.541c0 0.737 0.563 1.334 1.258 1.334s1.258-0.597 1.258-1.334v-10.539c0.001-0.354-0.132-0.694-0.368-0.945s-0.557-0.391-0.89-0.391zM16 0c-0.333 0-0.653 0.141-0.889 0.391s-0.368 0.589-0.368 0.943v29.333c0 0.736 0.563 1.333 1.257 1.333s1.257-0.597 1.257-1.333v-29.333c0-0.354-0.132-0.693-0.368-0.943s-0.555-0.391-0.889-0.391zM30.743 13.781c-0.334-0.001-0.654 0.14-0.89 0.391s-0.369 0.591-0.368 0.945v5.773c0 0.737 0.563 1.334 1.258 1.334s1.258-0.597 1.258-1.334v-5.775c0.001-0.354-0.132-0.694-0.367-0.944s-0.556-0.391-0.889-0.391v0.002zM1.257 13.781c-0.334 0-0.654 0.141-0.889 0.391s-0.368 0.59-0.367 0.944v5.773c0 0.737 0.563 1.334 1.258 1.334s1.258-0.597 1.258-1.334v-5.775c0.001-0.354-0.132-0.694-0.368-0.945s-0.557-0.391-0.89-0.391v0.002zM23.372 5.307c-0.334-0.001-0.654 0.14-0.89 0.391s-0.369 0.591-0.368 0.945v20.113c0 0.738 0.563 1.335 1.259 1.335s1.259-0.598 1.259-1.335v-20.113c0.001-0.354-0.132-0.694-0.368-0.945s-0.557-0.391-0.89-0.391z"></path>
</svg>
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="44" height="32" viewBox="0 0 44 32">
<title>check-mark</title>
<path d="M17.849 30.793l-14.849-14.849 3.712-3.712 11.136 11.136 22.275-22.272 3.712 3.712z"></path>
</svg>
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>chevron-down</title>
<path d="M16 20.8l-13.714-14.4-2.286 2.4 16 16.8 16-16.8-2.286-2.4z"></path>
</svg>
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>close</title>
<path d="M32 0.99l-1-0.99-15 15.010-15.010-15.010-0.99 0.99 15.010 15.010-15.010 15 0.99 1 15.010-15 15 15 1-1-15-15 15-15.010z"></path>
</svg>
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>dimension</title>
<path d="M1.067 0v0c0.64 0 1.067 0.427 1.067 1.067v29.867c0 0.64-0.427 1.067-1.067 1.067v0c-0.64 0-1.067-0.427-1.067-1.067v-29.867c0-0.64 0.427-1.067 1.067-1.067z"></path>
<path d="M32 30.933v0c0 0.64-0.427 1.067-1.067 1.067h-29.867c-0.64 0-1.067-0.427-1.067-1.067v0c0-0.64 0.427-1.067 1.067-1.067h29.867c0.64 0 1.067 0.427 1.067 1.067z"></path>
<path d="M6.4 20.267c-0.213 0-0.427 0-0.64-0.213-0.427-0.427-0.64-1.067-0.213-1.493l7.040-8.533c0.213-0.213 0.64-0.427 0.853-0.427s0.64 0.213 0.853 0.427l6.4 7.467 6.187-7.467c0.427-0.427 1.067-0.427 1.493-0.213 0.427 0.427 0.427 1.067 0.213 1.493l-7.040 8.533c-0.213 0.213-0.427 0.427-0.853 0.427-0.213 0-0.64-0.213-0.853-0.427l-6.4-7.467-6.187 7.467c-0.213 0.213-0.64 0.427-0.853 0.427z"></path>
</svg>
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>download</title>
<path d="M30.844 29.689h-29.69c-0.637 0-1.154 0.517-1.154 1.156 0 0.638 0.517 1.155 1.154 1.155h29.692c0.637 0 1.154-0.517 1.154-1.155 0-0.639-0.516-1.156-1.154-1.156h-0.002zM15.976 27.572c0.329 0 0.626-0.135 0.839-0.354l12.054-12.070c0.216-0.214 0.351-0.511 0.352-0.839 0-0.654-0.533-1.184-1.183-1.184-0.329-0.001-0.624 0.134-0.837 0.348l-10.043 10.056v-22.343c0-0.656-0.529-1.187-1.181-1.187-0.654 0-1.182 0.531-1.182 1.185l0.001 22.344-10.042-10.056c-0.213-0.214-0.511-0.348-0.837-0.347-0.653-0.001-1.182 0.53-1.184 1.181 0 0.329 0.134 0.628 0.35 0.84l12.052 12.072c0.215 0.217 0.512 0.353 0.841 0.353z"></path>
</svg>
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>histogram</title>
<path d="M0.8 28.2h30.2c0.4 0 0.8 0.4 0.8 1s-0.4 1-0.8 1h-30.2c-0.4 0-0.8-0.4-0.8-1s0.4-1 0.8-1z"></path>
<path d="M5 23.8c-1.6 0-3-1.4-3-3v-6.6c0-1.6 1.4-3 3-3s3 1.4 3 3v6.6c0 1.8-1.4 3-3 3zM5 13.2c-0.6 0-1 0.6-1 1v6.6c0 0.6 0.4 1 1 1s1-0.4 1-1v-6.6c0-0.4-0.4-1-1-1z"></path>
<path d="M27 23.8c-1.6 0-3-1.4-3-3v-12.8c0-1.6 1.4-3 3-3s3 1.4 3 3v12.8c0 1.8-1.4 3-3 3zM27 7c-0.6 0-1 0.4-1 1v12.8c0 0.6 0.4 1 1 1s1-0.4 1-1v-12.8c0-0.6-0.4-1-1-1z"></path>
<path d="M16 23.8c-1.6 0-3-1.4-3-3v-17c0-1.6 1.4-3 3-3s3 1.4 3 3v17c0 1.8-1.4 3-3 3zM16 2.8c-0.6 0-1 0.4-1 1v17c0 0.6 0.4 1 1 1s1-0.4 1-1v-17c0-0.6-0.4-1-1-1z"></path>
</svg>
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="34" height="32" viewBox="0 0 34 32">
<title>image</title>
<path d="M30.933 32h-27.733c-1.707 0-3.2-1.493-3.2-3.2v-25.6c0-1.707 1.493-3.2 3.2-3.2h27.733c1.707 0 3.2 1.493 3.2 3.2v25.6c0 1.707-1.493 3.2-3.2 3.2zM3.2 2.133c-0.64 0-1.067 0.427-1.067 1.067v25.6c0 0.64 0.427 1.067 1.067 1.067h27.733c0.64 0 1.067-0.427 1.067-1.067v-25.6c0-0.64-0.427-1.067-1.067-1.067h-27.733z"></path>
<path d="M24.533 13.867c-2.347 0-4.267-1.92-4.267-4.267s1.92-4.267 4.267-4.267 4.267 1.92 4.267 4.267-1.92 4.267-4.267 4.267zM24.533 7.467c-1.28 0-2.133 0.853-2.133 2.133s0.853 2.133 2.133 2.133 2.133-0.853 2.133-2.133-0.853-2.133-2.133-2.133z"></path>
<path d="M31.36 31.36l-8.533-10.667-5.973 6.187-6.4-9.813-7.467 13.653-1.92-1.067 9.173-16.64 7.040 10.453 5.547-6.187 10.24 12.8z"></path>
</svg>
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="34" height="32" viewBox="0 0 34 32">
<title>log-axis</title>
<path d="M32.36 4.435h-22.058c-0.316-2.522-2.448-4.454-5.030-4.454-2.799 0-5.068 2.269-5.068 5.068s2.269 5.068 5.068 5.068c2.11 0 3.919-1.29 4.682-3.124l0.012-0.034h22.394c0.697 0 1.262-0.565 1.262-1.262s-0.565-1.262-1.262-1.262v0zM5.193 7.589c-1.366-0.035-2.46-1.151-2.46-2.522 0-1.394 1.13-2.523 2.523-2.523s2.523 1.129 2.523 2.522v0c-0.024 1.399-1.163 2.523-2.565 2.523-0.008 0-0.015-0-0.022-0h0.001z"></path>
<path d="M7.506 21.047c-0.697 0-1.262 0.565-1.262 1.262s0.565 1.262 1.262 1.262h18.63c0.697 0 1.262-0.565 1.262-1.262s-0.565-1.262-1.262-1.262v0z"></path>
<path d="M5.424 12.636c-0.697 0-1.262 0.565-1.262 1.262s0.565 1.262 1.262 1.262h22.815c0.697 0 1.262-0.565 1.262-1.262s-0.565-1.262-1.262-1.262v0z"></path>
<path d="M11.669 29.457c-0.697 0-1.262 0.565-1.262 1.262s0.565 1.262 1.262 1.262h10.324c0.697 0 1.262-0.565 1.262-1.262s-0.565-1.262-1.262-1.262v0z"></path>
</svg>
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>maximize</title>
<path d="M19.174 31.97c-0.662 0-1.198-0.536-1.198-1.198s0.536-1.198 1.198-1.198h10.241l0.2-10.38c0.039-0.638 0.542-1.148 1.173-1.197l0.004-0c0.636 0.050 1.139 0.559 1.178 1.194l0 0.004v11.139c0 0.012 0 0.026 0 0.040 0 0.86-0.697 1.557-1.557 1.557-0.007 0-0.014-0-0.022-0h0.001zM0.030 12.806v-11.179c-0-0.006-0-0.013-0-0.020 0-0.871 0.706-1.577 1.577-1.577 0.007 0 0.014 0 0.021 0h11.178c0.662 0 1.198 0.536 1.198 1.198s-0.536 1.198-1.198 1.198h-10.38v10.38c0 0.662-0.536 1.198-1.198 1.198s-1.198-0.536-1.198-1.198v0zM1.627 31.97c-0.006 0-0.013 0-0.020 0-0.871 0-1.577-0.706-1.577-1.577 0-0.007 0-0.014 0-0.021v0.001-9.183c0-0.662 0.536-1.198 1.198-1.198s1.198 0.536 1.198 1.198v6.787l8.185-8.185c0.205-0.177 0.474-0.285 0.769-0.285s0.564 0.108 0.77 0.287l-0.001-0.001c0.174 0.206 0.279 0.475 0.279 0.769s-0.105 0.562-0.281 0.77l0.002-0.002-8.185 8.185h6.787c0.662 0 1.198 0.536 1.198 1.198s-0.536 1.198-1.198 1.198v0zM29.574 10.81v-6.787l-8.185 8.185c-0.189 0.111-0.416 0.176-0.658 0.176-0.338 0-0.647-0.127-0.88-0.337l0.001 0.001c-0.174-0.188-0.282-0.441-0.282-0.719s0.107-0.531 0.282-0.719l-0.001 0.001 8.185-8.185h-6.787c-0.662 0-1.198-0.536-1.198-1.198s0.536-1.198 1.198-1.198h9.183c0.006-0 0.013-0 0.020-0 0.871 0 1.577 0.706 1.577 1.577 0 0.007-0 0.014-0 0.021v-0.001 9.183c0 0.662-0.536 1.198-1.198 1.198s-1.198-0.536-1.198-1.198v0z"></path>
</svg>
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>minimize</title>
<path d="M19.176 31.979c-0.662 0-1.198-0.537-1.198-1.198s0.537-1.198 1.198-1.198h10.246l0.2-10.386c0.039-0.639 0.542-1.148 1.174-1.198l0.004-0c0.636 0.050 1.139 0.56 1.178 1.195l0 0.004v11.185c0 0.012 0.001 0.026 0.001 0.040 0 0.86-0.698 1.558-1.558 1.558-0.007 0-0.014-0-0.022-0h0.001z"></path>
<path d="M0.021 12.804v-11.185c-0-0.006-0-0.013-0-0.020 0-0.871 0.706-1.578 1.578-1.578 0.007 0 0.014 0 0.021 0h11.184c0.641 0.049 1.149 0.557 1.198 1.194l0 0.004c-0.049 0.641-0.557 1.149-1.194 1.198l-0.004 0h-10.386v10.386c-0.049 0.641-0.557 1.149-1.194 1.198l-0.004 0c-0.641-0.049-1.149-0.557-1.198-1.194l-0-0.004z"></path>
<path d="M10.867 19.535c0.006-0 0.013-0 0.020-0 0.871 0 1.578 0.706 1.578 1.578 0 0.007-0 0.014-0 0.021v-0.001 9.188c0 0.662-0.537 1.198-1.198 1.198s-1.198-0.537-1.198-1.198v0-6.791l-8.189 8.189c-0.208 0.179-0.481 0.288-0.779 0.288s-0.571-0.109-0.781-0.289l0.002 0.001c-0.17-0.208-0.273-0.476-0.273-0.769s0.103-0.561 0.275-0.771l-0.002 0.002 8.189-8.189h-6.831c-0.662 0-1.198-0.537-1.198-1.198s0.537-1.198 1.198-1.198v0z"></path>
<path d="M21.992 1.599v6.791l8.189-8.189c0.192-0.115 0.423-0.183 0.671-0.183 0.342 0 0.654 0.13 0.888 0.344l-0.001-0.001c0.175 0.188 0.282 0.441 0.282 0.719s-0.107 0.531-0.282 0.72l0.001-0.001-8.209 8.209h6.791c0.662 0 1.198 0.537 1.198 1.198s-0.537 1.198-1.198 1.198h-9.188c-0.006 0-0.013 0-0.020 0-0.871 0-1.578-0.706-1.578-1.578 0-0.007 0-0.014 0-0.021v0.001-9.208c0-0.662 0.537-1.198 1.198-1.198s1.198 0.537 1.198 1.198v0z"></path>
</svg>
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>minus</title>
<path d="M1.45 14.55h29.1c0.801 0 1.45 0.649 1.45 1.45s-0.649 1.45-1.45 1.45h-29.1c-0.801 0-1.45-0.649-1.45-1.45s0.649-1.45 1.45-1.45v0z"></path>
</svg>
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>mute</title>
<path d="M7.009 10.023l7.296-5.856c0.343-0.275 0.813-0.329 1.21-0.139s0.649 0.591 0.649 1.031v21.801c-0 0.439-0.252 0.839-0.648 1.029s-0.865 0.137-1.208-0.137l-7.497-5.984h-5.667c-0.631 0-1.143-0.512-1.143-1.143v-9.459c0-0.631 0.512-1.143 1.143-1.143h5.866z"></path>
</svg>
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>pause</title>
<path d="M7 0c2.209 0 4 1.791 4 4v24c0 2.209-1.791 4-4 4s-4-1.791-4-4v-24c0-2.209 1.791-4 4-4z"></path>
<path d="M25 0c2.209 0 4 1.791 4 4v24c0 2.209-1.791 4-4 4s-4-1.791-4-4v-24c0-2.209 1.791-4 4-4z"></path>
</svg>
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>play</title>
<path d="M27.184 19.287l-17.96 12.042c-1.816 1.217-4.275 0.732-5.492-1.083-0.437-0.652-0.671-1.419-0.671-2.204v-24.083c0-2.186 1.772-3.958 3.958-3.958 0.785 0 1.552 0.233 2.204 0.671l17.96 12.042c1.816 1.217 2.301 3.676 1.084 5.492-0.287 0.428-0.655 0.796-1.083 1.083z"></path>
</svg>
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>plus</title>
<path d="M16 0c0.801 0 1.45 0.649 1.45 1.45v13.1h13.1c0.801 0 1.45 0.649 1.45 1.45s-0.649 1.45-1.45 1.45h-13.1v13.1c0 0.801-0.649 1.45-1.45 1.45s-1.45-0.649-1.45-1.45v0-13.1h-13.1c-0.801 0-1.45-0.649-1.45-1.45s0.649-1.45 1.45-1.45h13.1v-13.1c0-0.801 0.649-1.45 1.45-1.45v0z"></path>
</svg>
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>question-circle</title>
<path d="M14.45 24v0c-0.001 0.015-0.001 0.032-0.001 0.050 0 0.746 0.605 1.351 1.351 1.351 0.729 0 1.322-0.577 1.35-1.298l0-0.003v-0.1c0-0.746-0.604-1.35-1.35-1.35s-1.35 0.604-1.35 1.35v0zM15.84 20.71h-0.11c-0.568-0.060-1.006-0.536-1.006-1.114 0-0.041 0.002-0.081 0.006-0.121l-0 0.005c0.15-1.56 1.29-2.82 2.55-4.080 2-2 2.050-2.65 2.090-3.33 0.002-0.035 0.002-0.077 0.002-0.118 0-0.795-0.318-1.516-0.833-2.042l0 0.001c-0.639-0.648-1.527-1.050-2.508-1.050-0.011 0-0.022 0-0.034 0h0.002c-1.853 0.006-3.354 1.507-3.36 3.359v0.001c-0.056 0.577-0.538 1.024-1.125 1.024s-1.069-0.447-1.125-1.019l-0-0.005c-0-0.021-0-0.045-0-0.069 0-1.537 0.627-2.928 1.64-3.93l0-0c1.008-1.019 2.407-1.65 3.953-1.65 0.017 0 0.033 0 0.049 0l-0.003-0c0.002 0 0.005 0 0.008 0 1.639 0 3.119 0.682 4.17 1.778l0.002 0.002c0.895 0.917 1.447 2.171 1.447 3.555 0 0.097-0.003 0.193-0.008 0.288l0.001-0.013c-0.070 1.44-0.52 2.59-2.74 4.8-1.17 1.17-1.91 2.020-1.91 2.75-0.072 0.563-0.545 0.994-1.119 1h-0.001zM25.72 25.71v0c-2.476 2.472-5.895 4-9.671 4-0.017 0-0.035-0-0.052-0h0.003c-0.017 0-0.037 0-0.058 0-3.773 0-7.189-1.529-9.662-4l0 0c-2.472-2.482-4-5.906-4-9.687 0-0.015 0-0.030 0-0.045v0.002c0.031-7.57 6.175-13.695 13.75-13.695 3.781 0 7.205 1.526 9.691 3.996l-0.001-0.001c2.472 2.476 4 5.895 4 9.671 0 0.017-0 0.035-0 0.052v-0.003c0 0.021 0 0.045 0 0.070 0 3.769-1.529 7.181-4 9.65l-0 0zM16 0v0c-8.837 0-16 7.163-16 16s7.163 16 16 16c8.837 0 16-7.163 16-16v0c0-8.837-7.163-16-16-16v0z"></path>
</svg>
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>reduction</title>
<path d="M1.067 0v0c0.64 0 1.067 0.427 1.067 1.067v29.867c0 0.64-0.427 1.067-1.067 1.067v0c-0.64 0-1.067-0.427-1.067-1.067v-29.867c0-0.64 0.427-1.067 1.067-1.067z"></path>
<path d="M32 30.933v0c0 0.64-0.427 1.067-1.067 1.067h-29.867c-0.64 0-1.067-0.427-1.067-1.067v0c0-0.64 0.427-1.067 1.067-1.067h29.867c0.64 0 1.067 0.427 1.067 1.067z"></path>
<path d="M26.24 23.68c-0.427 0-0.64-0.213-0.853-0.427l-8.747-14.933h-8.533c-0.64 0-1.067-0.427-1.067-1.067s0.427-1.067 1.067-1.067h8.96c0.427 0 0.64 0.213 0.853 0.427l9.173 15.573c0.213 0.427 0.213 1.067-0.427 1.493 0 0-0.213 0-0.427 0z"></path>
<path d="M27.733 17.28v0c0.64 0.213 0.853 0.64 0.853 1.28l-1.067 4.053c-0.213 0.64-0.64 0.853-1.28 0.853v0c-0.64-0.213-0.853-0.64-0.853-1.28l1.067-4.053c0-0.64 0.64-1.067 1.28-0.853z"></path>
<path d="M20.693 21.547v0c0 0.64 0.213 1.067 0.853 1.28l4.267 0.64c0.64 0 1.067-0.213 1.28-0.853v0c0-0.64-0.213-1.067-0.853-1.28l-4.267-0.64c-0.64 0-1.067 0.213-1.28 0.853z"></path>
</svg>
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>refresh</title>
<path d="M32 12.571v-8l-2.606 2.606c-2.907-4.348-7.796-7.172-13.344-7.172-8.837 0-16 7.163-16 16s7.163 16 16 16c6.672 0 12.39-4.084 14.791-9.888l0.039-0.106c0.063-0.152 0.099-0.328 0.099-0.513 0-0.571-0.349-1.060-0.845-1.267l-0.009-0.003c-0.152-0.063-0.328-0.099-0.513-0.099-0.571 0-1.060 0.349-1.267 0.845l-0.003 0.009c-2.030 4.895-6.771 8.275-12.301 8.275-7.334 0-13.28-5.946-13.28-13.28s5.946-13.28 13.28-13.28c4.81 0 9.023 2.557 11.353 6.387l0.033 0.059-3.429 3.406z"></path>
</svg>
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>restore-size</title>
<path d="M29.8 0h-27.6c-1.215 0-2.2 0.985-2.2 2.2v0 27.6c0 1.215 0.985 2.2 2.2 2.2h27.6c1.215 0 2.2-0.985 2.2-2.2v0-27.6c0-1.215-0.985-2.2-2.2-2.2v0zM2.2 29.8v-27.6h27.6v27.6z"></path>
<path d="M10.78 4c-0.687 0.011-1.24 0.571-1.24 1.26 0 0 0 0 0 0v0 4.2h-4.32c-0.679 0.022-1.22 0.578-1.22 1.259 0 0.007 0 0.014 0 0.022v-0.001c-0 0.006-0 0.013-0 0.020 0 0.678 0.544 1.229 1.219 1.24h5.561c0.676-0.011 1.22-0.562 1.22-1.24 0-0.007-0-0.014-0-0.021v0.001-5.48c0-0.006 0-0.013 0-0.020 0-0.678-0.544-1.229-1.219-1.24h-0.001zM26.78 9.46h-4.32v-4.2c0-0 0-0 0-0 0-0.689-0.553-1.249-1.239-1.26h-0.001c-0.676 0.011-1.22 0.562-1.22 1.24 0 0.007 0 0.014 0 0.021v-0.001 5.48c-0 0.006-0 0.013-0 0.020 0 0.678 0.544 1.229 1.219 1.24h5.561c0.676-0.011 1.22-0.562 1.22-1.24 0-0.007-0-0.014-0-0.021v0.001c0-0.006 0-0.013 0-0.021 0-0.682-0.542-1.237-1.218-1.259l-0.002-0zM10.78 20h-5.56c-0.676 0.011-1.22 0.562-1.22 1.24 0 0.007 0 0.014 0 0.021v-0.001c-0 0.006-0 0.013-0 0.021 0 0.682 0.542 1.237 1.218 1.259l0.002 0h4.32v4.2c0 0 0 0 0 0 0 0.689 0.553 1.249 1.239 1.26h0.001c0.676-0.011 1.22-0.562 1.22-1.24 0-0.007-0-0.014-0-0.021v0.001-5.48c0-0.006 0-0.013 0-0.020 0-0.678-0.544-1.229-1.219-1.24h-0.001zM26.78 20h-5.56c-0.676 0.011-1.22 0.562-1.22 1.24 0 0.007 0 0.014 0 0.021v-0.001 5.48c-0 0.006-0 0.013-0 0.020 0 0.678 0.544 1.229 1.219 1.24h0.001c0.687-0.011 1.24-0.571 1.24-1.26 0-0 0-0 0-0v0-4.2h4.32c0.679-0.022 1.22-0.578 1.22-1.259 0-0.007-0-0.015-0-0.022v0.001c0-0.006 0-0.013 0-0.020 0-0.678-0.544-1.229-1.219-1.24h-0.001z"></path>
</svg>
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="28" height="32" viewBox="0 0 28 32">
<title>revert</title>
<path d="M26.846 14.886c-0.373-0.141-0.791-0.071-1.099 0.184s-0.457 0.656-0.392 1.053c0.099 0.595 0.149 1.244 0.149 1.884 0.009 3.117-1.218 6.108-3.407 8.307-2.178 2.21-5.142 3.448-8.231 3.437-3.088 0.009-6.052-1.23-8.231-3.44-2.189-2.199-3.416-5.191-3.407-8.309 0-3.137 1.21-6.091 3.407-8.309 2.179-2.21 5.142-3.448 8.231-3.439 0.129 0 0.26 0.003 0.39 0.007l-2.532 2.553c-0.209 0.211-0.327 0.497-0.327 0.795s0.118 0.585 0.327 0.795c0.436 0.44 1.142 0.44 1.576 0l4.204-4.244c0.209-0.211 0.327-0.497 0.327-0.795s-0.118-0.585-0.327-0.795l-4.205-4.239c-0.209-0.211-0.492-0.33-0.788-0.33s-0.579 0.119-0.788 0.33c-0.209 0.211-0.327 0.497-0.327 0.795s0.118 0.585 0.327 0.795l2.064 2.079c-3.65 0.018-7.144 1.492-9.722 4.1-2.607 2.623-4.070 6.186-4.066 9.901-0.005 3.714 1.457 7.276 4.061 9.899 2.598 2.629 6.127 4.105 9.806 4.1 1.873 0 3.686-0.369 5.398-1.1 1.648-0.7 3.145-1.718 4.407-2.996 1.266-1.275 2.275-2.785 2.972-4.447 0.725-1.725 1.090-3.56 1.090-5.451 0-0.771-0.062-1.531-0.18-2.253-0.065-0.395-0.335-0.726-0.707-0.867z"></path>
</svg>
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>scalar</title>
<path d="M29 32h-26c-1.6 0-3-1.4-3-3v-26c0-1.6 1.4-3 3-3h26c1.6 0 3 1.4 3 3v26c0 1.6-1.4 3-3 3zM3 2c-0.6 0-1 0.4-1 1v26c0 0.6 0.4 1 1 1h26c0.6 0 1-0.4 1-1v-26c0-0.6-0.4-1-1-1h-26z"></path>
<path d="M6 21c-0.2 0-0.4 0-0.6-0.2-0.4-0.4-0.4-1-0.2-1.4l6.6-8c0.2-0.2 0.6-0.4 0.8-0.4s0.6 0.2 0.8 0.4l6 7 5.8-7c0.4-0.4 1-0.4 1.4-0.2 0.4 0.4 0.4 1 0.2 1.4l-6.6 8c-0.2 0.2-0.4 0.4-0.8 0.4-0.2 0-0.6-0.2-0.8-0.4l-6-7-5.8 7c-0.2 0.2-0.6 0.4-0.8 0.4z"></path>
</svg>
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>search</title>
<path d="M30.293 31.787l-5.547-5.547c-2.773 2.347-6.187 3.627-9.813 3.627-8.32 0-14.933-6.613-14.933-14.933s6.613-14.933 14.933-14.933c8.32 0 14.933 6.613 14.933 14.933 0 3.627-1.28 7.040-3.627 9.813l5.547 5.547-1.493 1.493zM14.933 2.133c-7.040 0-12.8 5.76-12.8 12.8s5.76 12.8 12.8 12.8c2.987 0 5.973-1.067 8.32-3.2l1.28-1.28c2.133-2.347 3.2-5.333 3.2-8.32 0-7.040-5.76-12.8-12.8-12.8z"></path>
</svg>
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>text</title>
<path d="M10 8h12v2h-12v-2z"></path>
<path d="M8 24h16v2h-16v-2z"></path>
<path d="M8 6h2v6h-2v-6z"></path>
<path d="M22 6h2v6h-2v-6z"></path>
<path d="M15 9h2v12h-2v-12z"></path>
<path d="M30 32h-28c-1.2 0-2-0.8-2-2v-28c0-1.2 0.8-2 2-2h28c1.2 0 2 0.8 2 2v28c0 1.2-0.8 2-2 2zM2 2v28h28v-28h-28zM2 1v1c0 0 0 0 0 0v-1z"></path>
</svg>
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="39" height="32" viewBox="0 0 39 32">
<title>upload</title>
<path d="M31.702 10.073c-1.054-5.763-6.037-10.073-12.026-10.073s-10.972 4.31-12.015 9.998l-0.011 0.075c-4.341 0.982-7.534 4.806-7.534 9.376 0 0.372 0.021 0.739 0.062 1.1l-0.004-0.044c0.628 4.846 4.729 8.551 9.696 8.551 0.027 0 0.054-0 0.081-0l-0.004 0h2.993c0.675 0 1.222-0.547 1.222-1.222s-0.547-1.222-1.222-1.222v0 0h-3.054c-3.977 0-7.201-3.224-7.201-7.201s3.224-7.201 7.201-7.201v0c0-5.397 4.375-9.772 9.772-9.772s9.772 4.375 9.772 9.772v0c0.088-0.004 0.192-0.006 0.296-0.006 3.974 0 7.195 3.221 7.195 7.195s-3.221 7.195-7.195 7.195c-0.104 0-0.208-0.002-0.311-0.007l0.015 0h-3.665c-0.675 0-1.222 0.547-1.222 1.222s0.547 1.222 1.222 1.222v0h3.665c0.024 0 0.052 0 0.080 0 4.958 0 9.054-3.692 9.687-8.477l0.005-0.050c0.036-0.312 0.057-0.674 0.057-1.041 0-4.581-3.204-8.413-7.493-9.379l-0.064-0.012z"></path>
<path d="M20.208 14.972c-0.219-0.209-0.516-0.337-0.843-0.337s-0.624 0.129-0.843 0.338l0-0-3.396 3.445c-0.218 0.22-0.353 0.521-0.354 0.855v0c0.005 0.329 0.139 0.626 0.354 0.843l-0-0c0.219 0.209 0.516 0.337 0.843 0.337s0.624-0.129 0.843-0.338l-0 0 1.356-1.368v12.032c0 0.675 0.547 1.222 1.222 1.222s1.222-0.547 1.222-1.222v0-12.020l1.344 1.368c0.209 0.218 0.504 0.354 0.83 0.354 0.005 0 0.009-0 0.014-0h-0.001c0.331-0.008 0.628-0.147 0.843-0.366l0-0c0.212-0.22 0.343-0.519 0.343-0.849s-0.131-0.63-0.344-0.849l0 0z"></path>
</svg>
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>volumn-low</title>
<path d="M7.009 10.023l7.296-5.856c0.343-0.275 0.813-0.329 1.21-0.139s0.649 0.591 0.649 1.031v21.801c-0 0.439-0.252 0.839-0.648 1.029s-0.865 0.137-1.208-0.137l-7.497-5.984h-5.667c-0.631 0-1.143-0.512-1.143-1.143v-9.459c0-0.631 0.512-1.143 1.143-1.143h5.866z"></path>
<path d="M21.671 23.007c-0.452 0.431-1.166 0.419-1.604-0.027s-0.436-1.16 0.003-1.604c1.472-1.441 2.3-3.415 2.297-5.475 0.003-1.908-0.707-3.748-1.992-5.159-0.28-0.301-0.377-0.73-0.253-1.123s0.449-0.688 0.852-0.774c0.402-0.086 0.82 0.051 1.093 0.359 1.667 1.831 2.59 4.22 2.586 6.696 0.003 2.674-1.072 5.235-2.982 7.106v0z"></path>
</svg>
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>volumn</title>
<path d="M7.009 10.023l7.296-5.856c0.343-0.275 0.813-0.329 1.21-0.139s0.649 0.591 0.649 1.031v21.801c-0 0.439-0.252 0.839-0.648 1.029s-0.865 0.137-1.208-0.137l-7.497-5.984h-5.667c-0.631 0-1.143-0.512-1.143-1.143v-9.459c0-0.631 0.512-1.143 1.143-1.143h5.866z"></path>
<path d="M21.671 23.007c-0.452 0.431-1.166 0.419-1.604-0.027s-0.436-1.16 0.003-1.604c1.472-1.441 2.3-3.415 2.297-5.475 0.003-1.908-0.707-3.748-1.992-5.159-0.28-0.301-0.377-0.73-0.253-1.123s0.449-0.688 0.852-0.774c0.402-0.086 0.82 0.051 1.093 0.359 1.667 1.831 2.59 4.22 2.586 6.696 0.003 2.674-1.072 5.235-2.982 7.106v0z"></path>
<path d="M27.104 27.962c-0.284 0.298-0.705 0.42-1.104 0.321s-0.714-0.405-0.824-0.801c-0.111-0.396-0-0.821 0.289-1.113 2.73-2.8 4.255-6.558 4.249-10.469 0-3.882-1.478-7.53-4.087-10.299-0.425-0.461-0.4-1.177 0.057-1.607s1.173-0.412 1.607 0.040c3.006 3.189 4.709 7.394 4.709 11.866 0 4.566-1.777 8.854-4.896 12.062z"></path>
</svg>
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>zoom-in</title>
<path d="M22.062 14.97h-5.073v-5.073c0-0.554-0.449-1.003-1.003-1.003s-1.003 0.449-1.003 1.003v0 5.073h-5.073c-0.554 0-1.003 0.449-1.003 1.003s0.449 1.003 1.003 1.003v0h5.073v5.013c0 0.554 0.449 1.003 1.003 1.003s1.003-0.449 1.003-1.003v0-5.013h5.013c0.554 0 1.003-0.449 1.003-1.003s-0.449-1.003-1.003-1.003v0z"></path>
<path d="M26.574 25.999c2.824-2.755 4.576-6.597 4.576-10.849 0-8.367-6.783-15.15-15.15-15.15s-15.15 6.783-15.15 15.15c0 8.367 6.783 15.15 15.15 15.15 3.258 0 6.276-1.028 8.746-2.778l-0.047 0.032 3.369 4.011c0.224 0.268 0.559 0.437 0.932 0.437 0.67 0 1.214-0.543 1.214-1.214 0-0.296-0.106-0.568-0.283-0.779l0.002 0.002zM24.268 25.126l-0.842 0.602c-2.102 1.497-4.723 2.393-7.553 2.393-7.243 0-13.114-5.872-13.114-13.114s5.871-13.114 13.114-13.114c7.243 0 13.114 5.872 13.114 13.114 0 3.689-1.523 7.022-3.975 9.405l-0.003 0.003z"></path>
</svg>
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="30" height="32" viewBox="0 0 30 32">
<title>zoom-out</title>
<path d="M21.218 14.97h-12.152c-0.554 0-1.003 0.449-1.003 1.003s0.449 1.003 1.003 1.003v0h12.152c0.554 0 1.003-0.449 1.003-1.003s-0.449-1.003-1.003-1.003v0z"></path>
<path d="M25.73 25.999c2.824-2.755 4.576-6.597 4.576-10.849 0-8.367-6.783-15.15-15.15-15.15s-15.15 6.783-15.15 15.15c0 8.367 6.783 15.15 15.15 15.15 3.258 0 6.276-1.028 8.746-2.778l-0.047 0.032 3.369 4.011c0.224 0.268 0.559 0.437 0.932 0.437 0.67 0 1.214-0.543 1.214-1.214 0-0.296-0.106-0.568-0.283-0.779l0.002 0.002zM23.424 25.126l-0.842 0.602c-2.102 1.497-4.723 2.393-7.553 2.393-7.243 0-13.114-5.872-13.114-13.114s5.872-13.114 13.114-13.114c7.243 0 13.114 5.872 13.114 13.114 0 3.689-1.523 7.022-3.975 9.405l-0.003 0.003z"></path>
</svg>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>VisualDL</title>
</head>
<body>
<div id="root"></div>
<noscript>You need to enable JavaScript to run this app.</noscript>
<script type="module" src="%PUBLIC_URL%/_dist_/index.js"></script>
</body>
</html>
{
"download-image": "Download image",
"ignore-outliers": "Ignore outliers in chart scaling",
"max": "Max",
"maximize": "Maximize",
"min": "Min",
"minimize": "Minimize",
"restore": "Selection restore",
"smoothed": "Smoothed",
......
{
"download-image": "下载图片",
"ignore-outliers": "图表缩放时忽略极端值",
"max": "最大值",
"maximize": "最大化",
"min": "最小值",
"minimize": "最小化",
"restore": "还原图表框选",
"smoothed": "Smoothed",
"smoothing": "平滑度",
"toggle-log-axis": "切换对数坐标轴",
"tooltip-sorting": "标签排序方法",
"tooltip-sorting": "详情数据排序",
"tooltip-sorting-value": {
"ascending": "升序",
"default": "默认",
......
.vdl-icon {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'vdl-icon' !important;
speak: never;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-mute:before {
content: '\e918';
}
.icon-volumn-low:before {
content: '\e91b';
}
.icon-volumn:before {
content: '\e91c';
}
.icon-pause:before {
content: '\e919';
}
.icon-play:before {
content: '\e91a';
}
.icon-question-circle:before {
content: '\e913';
}
.icon-minus:before {
content: '\e914';
}
.icon-plus:before {
content: '\e915';
}
.icon-close:before {
content: '\e916';
}
.icon-upload:before {
content: '\e917';
}
.icon-zoom-out:before {
content: '\e911';
}
.icon-zoom-in:before {
content: '\e912';
}
.icon-refresh:before {
content: '\e90c';
}
.icon-restore-size:before {
content: '\e90d';
}
.icon-minimize:before {
content: '\e90e';
}
.icon-log-axis:before {
content: '\e90f';
}
.icon-maximize:before {
content: '\e910';
}
.icon-chevron-down:before {
content: '\e90a';
}
.icon-reduction:before {
content: '\e900';
}
.icon-dimension:before {
content: '\e901';
}
.icon-revert:before {
content: '\e902';
}
.icon-download:before {
content: '\e903';
}
.icon-text:before {
content: '\e904';
}
.icon-audio:before {
content: '\e905';
}
.icon-scalar:before {
content: '\e906';
}
.icon-histogram:before {
content: '\e907';
}
.icon-search:before {
content: '\e908';
}
.icon-image:before {
content: '\e909';
}
.icon-check-mark:before {
content: '\e90b';
}
/* eslint-disable @typescript-eslint/no-var-requires */
require('./builder/environment');
const mock = require('./builder/mock');
const icons = require('./builder/icons');
const netron = require('./builder/netron');
const wasm = require('./builder/wasm');
const port = Number.parseInt(process.env.PORT || 3000, 10);
const devServer = {
port: port + 1,
host: '127.0.0.1'
};
module.exports = {
extends: '@snowpack/app-scripts-react',
plugins: [
'@snowpack/plugin-dotenv',
[
'@snowpack/plugin-run-script',
{
cmd: 'node builder/icons.js && node builder/netron.js && node builder/wasm.js',
watch: `node builder/dev-server.js --port ${devServer.port} --host ${devServer.host}`,
output: 'dashboard'
}
]
],
install: ['@visualdl/wasm'],
alias: {
'~': './src'
},
proxy: {
...[mock.pathname, icons.pathname, netron.pathname, wasm.pathname].reduce((m, pathname) => {
m[(process.env.PUBLIC_PATH || '') + pathname] = `http://${devServer.host}:${devServer.port}${pathname}`;
return m;
}, {})
},
devOptions: {
out: 'dist',
hostname: process.env.HOST || 'localhost',
port
},
buildOptions: {
baseUrl: process.env.PUBLIC_PATH || '/',
clean: true
},
installOptions: {
polyfillNode: true,
namedExports: ['file-saver']
}
};
import React, {FunctionComponent, Suspense, useEffect, useMemo, useState} from 'react';
import {Redirect, Route, BrowserRouter as Router, Switch, useLocation} from 'react-router-dom';
import {headerHeight, position, size} from '~/utils/style';
import BodyLoading from '~/components/BodyLoading';
import {Helmet} from 'react-helmet';
import NProgress from 'nprogress';
import Navbar from '~/components/Navbar';
import {SWRConfig} from 'swr';
import {fetcher} from '~/utils/fetch';
import init from '@visualdl/wasm';
import routes from '~/routes';
import styled from 'styled-components';
import {useTranslation} from 'react-i18next';
const PUBLIC_PATH: string = import.meta.env.SNOWPACK_PUBLIC_PATH;
const Main = styled.main`
padding-top: ${headerHeight};
`;
const Header = styled.header`
z-index: 10000;
${size(headerHeight, '100%')}
${position('fixed', 0, 0, null, 0)}
`;
const defaultRoute = routes.find(route => route.default);
const routers = routes.reduce<Omit<typeof routes[number], 'children'>[]>((m, route) => {
if (route.children) {
m.push(...route.children);
} else {
m.push(route);
}
return m;
}, []);
const Progress: FunctionComponent = () => {
useEffect(() => {
NProgress.start();
return () => {
NProgress.done();
};
}, []);
return null;
};
const Telemetry: FunctionComponent = () => {
const location = useLocation();
useEffect(() => {
globalThis._hmt.push(['_trackPageview', PUBLIC_PATH + location.pathname]);
}, [location.pathname]);
return null;
};
const App: FunctionComponent = () => {
const {i18n} = useTranslation();
const dir = useMemo(() => (i18n.language ? i18n.dir(i18n.language) : ''), [i18n]);
const [inited, setInited] = useState(false);
useEffect(() => {
(async () => {
if (!inited) {
await init(`${PUBLIC_PATH}/wasm/visualdl.wasm`);
setInited(true);
}
})();
}, [inited]);
return (
<div className="app">
<Helmet defaultTitle="VisualDL" titleTemplate="%s - VisualDL">
<html lang={i18n.language} dir={dir} />
</Helmet>
<SWRConfig
value={{
fetcher,
revalidateOnFocus: false,
revalidateOnReconnect: false
}}
>
{!inited ? (
<BodyLoading />
) : (
<Main>
<Router basename={PUBLIC_PATH || '/'}>
<Telemetry />
<Header>
<Navbar />
</Header>
<Suspense fallback={<Progress />}>
<Switch>
<Redirect exact from="/" to={defaultRoute?.path ?? '/index'} />
{routers.map(route => (
<Route key={route.id} path={route.path} component={route.component} />
))}
</Switch>
</Suspense>
</Router>
</Main>
)}
</SWRConfig>
</div>
);
};
export default App;
......@@ -24,7 +24,7 @@ import moment from 'moment';
import {saveAs} from 'file-saver';
import styled from 'styled-components';
import useRequest from '~/hooks/useRequest';
import {useTranslation} from '~/utils/i18n';
import {useTranslation} from 'react-i18next';
const Container = styled.div`
background-color: ${primaryBackgroundColor};
......@@ -230,42 +230,40 @@ const Audio = React.forwardRef<AudioRef, AudioProps & WithStyled>(
}, [volumn]);
useEffect(() => {
if (process.browser) {
let p: AudioPlayer | null = null;
if (data) {
(async () => {
setDecoding(true);
onLoading?.();
setOffset(0);
setSliderValue(0);
setDuration('00:00');
p = new AudioPlayer({
context: audioContext,
onplay: () => {
setPlaying(true);
startTimer();
},
onstop: () => {
setPlaying(false);
stopTimer();
}
});
const buffer = await data.data.arrayBuffer();
await p.load(buffer, data.type != null ? mime.extension(data.type) || undefined : undefined);
setDecoding(false);
setDuration(formatDuration(p.duration));
onLoad?.({sampleRate: p.sampleRate, duration: p.duration});
player.current = p;
})();
}
return () => {
if (p) {
setPlaying(false);
p.dispose();
player.current = null;
}
};
let p: AudioPlayer | null = null;
if (data) {
(async () => {
setDecoding(true);
onLoading?.();
setOffset(0);
setSliderValue(0);
setDuration('00:00');
p = new AudioPlayer({
context: audioContext,
onplay: () => {
setPlaying(true);
startTimer();
},
onstop: () => {
setPlaying(false);
stopTimer();
}
});
const buffer = await data.data.arrayBuffer();
await p.load(buffer, data.type != null ? mime.extension(data.type) || undefined : undefined);
setDecoding(false);
setDuration(formatDuration(p.duration));
onLoad?.({sampleRate: p.sampleRate, duration: p.duration});
player.current = p;
})();
}
return () => {
if (p) {
setPlaying(false);
p.dispose();
player.current = null;
}
};
}, [data, startTimer, stopTimer, onLoading, onLoad, audioContext]);
const volumnIcon = useMemo(() => {
......
import React, {FunctionComponent} from 'react';
import {position, primaryColor, size} from '~/utils/style';
import HashLoader from 'react-spinners/HashLoader';
import styled from 'styled-components';
const Wrapper = styled.div`
${size('100vh', '100vw')}
${position('fixed', 0, 0, 0, 0)}
background-color: rgba(255, 255, 255, 0.8);
display: flex;
justify-content: center;
align-items: center;
overscroll-behavior: none;
cursor: progress;
`;
const BodyLoading: FunctionComponent = () => {
return (
<Wrapper>
<HashLoader size="60px" color={primaryColor} />
</Wrapper>
);
};
export default BodyLoading;
......@@ -22,6 +22,7 @@ import {
transitionProps
} from '~/utils/style';
import type {Icons} from '~/components/Icon';
import RawIcon from '~/components/Icon';
import styled from 'styled-components';
......@@ -85,7 +86,7 @@ const Icon = styled(RawIcon)`
type ButtonProps = {
rounded?: boolean;
icon?: string;
icon?: Icons;
type?: colorTypes;
disabled?: boolean;
onClick?: () => unknown;
......
......@@ -15,11 +15,11 @@ import {
import ee from '~/utils/event';
import styled from 'styled-components';
const Div = styled.div<{maximized?: boolean; width?: string; height?: string}>`
const Div = styled.div<{maximized?: boolean; divWidth?: string; divHeight?: string}>`
${props =>
size(
props.maximized ? `calc(100vh - ${headerHeight} - ${rem(40)})` : props.height || 'auto',
props.maximized ? '100%' : props.width || '100%'
props.maximized ? `calc(100vh - ${headerHeight} - ${rem(40)})` : props.divHeight || 'auto',
props.maximized ? '100%' : props.divWidth || '100%'
)}
background-color: ${backgroundColor};
${sameBorder({radius: math(`${borderRadius} * 2`)})}
......@@ -58,8 +58,8 @@ const Chart: FunctionComponent<ChartProps & WithStyled> = ({cid, width, height,
return (
<Div
maximized={maximized}
width={width}
height={height}
divWidth={width}
divHeight={height}
className={`${maximized ? 'maximized' : ''} ${className ?? ''}`}
>
{children}
......
import React, {FunctionComponent, PropsWithChildren, useCallback, useEffect, useMemo, useState} from 'react';
import {Trans, useTranslation} from '~/utils/i18n';
import {Trans, useTranslation} from 'react-i18next';
import {WithStyled, backgroundColor, headerHeight, link, primaryColor, rem, textLighterColor} from '~/utils/style';
import BarLoader from 'react-spinners/BarLoader';
......@@ -11,7 +11,7 @@ import groupBy from 'lodash/groupBy';
import styled from 'styled-components';
import useSearchValue from '~/hooks/useSearchValue';
const PUBLIC_PATH = process.env.PUBLIC_PATH;
const PUBLIC_PATH: string = import.meta.env.SNOWPACK_PUBLIC_PATH;
const StyledPagination = styled(Pagination)`
margin-top: ${rem(20)};
......@@ -131,7 +131,7 @@ const ChartPage = <T extends Item>({
}
return 0;
}),
[items]
[items] // eslint-disable-line react-hooks/exhaustive-deps
);
const total = useMemo(() => Math.ceil(matchedTags.length / pageSize), [matchedTags]);
......@@ -173,7 +173,7 @@ const ChartPage = <T extends Item>({
)}
</Wrapper>
),
[withChart, loading, chartSize, t]
[withChart, loading, chartSize, t] // eslint-disable-line react-hooks/exhaustive-deps
);
return (
......
......@@ -13,12 +13,14 @@ import {
} from '~/utils/style';
import Icon from '~/components/Icon';
import type {Icons} from '~/components/Icon';
import Tippy from '@tippyjs/react';
import styled from 'styled-components';
const Toolbox = styled.div<{reversed?: boolean}>`
font-size: ${em(16)};
line-height: 1;
height: 1em;
display: flex;
flex-direction: ${props => (props.reversed ? 'row-reverse' : 'row')};
align-items: center;
......@@ -43,7 +45,7 @@ const ToolboxItem = styled.a<{active?: boolean; reversed?: boolean}>`
`;
type BaseChartToolboxItem = {
icon: string;
icon: Icons;
tooltip?: string;
};
......@@ -54,7 +56,7 @@ type NormalChartToolboxItem = {
type ToggleChartToolboxItem = {
toggle: true;
activeIcon?: string;
activeIcon?: Icons;
activeTooltip?: string;
onClick?: (value: boolean) => unknown;
} & BaseChartToolboxItem;
......
import React, {FunctionComponent} from 'react';
import {backgroundColor, contentHeight, contentMargin, headerHeight, position, primaryColor, size} from '~/utils/style';
import {backgroundColor, contentHeight, contentMargin, headerHeight, position} from '~/utils/style';
import HashLoader from 'react-spinners/HashLoader';
import BodyLoading from '~/components/BodyLoading';
import styled from 'styled-components';
const Section = styled.section`
......@@ -23,17 +23,6 @@ const Aside = styled.aside`
overflow-y: auto;
`;
const Loading = styled.div`
${size('100vh', '100vw')}
${position('fixed', 0, 0, 0, 0)}
background-color: rgba(255, 255, 255, 0.8);
display: flex;
justify-content: center;
align-items: center;
overscroll-behavior: none;
cursor: progress;
`;
type ContentProps = {
aside?: React.ReactNode;
loading?: boolean;
......@@ -43,11 +32,7 @@ const Content: FunctionComponent<ContentProps> = ({children, aside, loading}) =>
<Section>
<Article>{children}</Article>
{aside && <Aside>{aside}</Aside>}
{loading && (
<Loading>
<HashLoader size="60px" color={primaryColor} />
</Loading>
)}
{loading && <BodyLoading />}
</Section>
);
......
import React, {FunctionComponent} from 'react';
import {Trans, useTranslation} from '~/utils/i18n';
import {Trans, useTranslation} from 'react-i18next';
import {WithStyled, backgroundColor, em, link, rem, size, textColor, textLightColor} from '~/utils/style';
import styled from 'styled-components';
const PUBLIC_PATH = process.env.PUBLIC_PATH;
const PUBLIC_PATH: string = import.meta.env.SNOWPACK_PUBLIC_PATH;
const Wrapper = styled.div`
display: flex;
......
import {Argument as ArgumentType, Property as PropertyType} from '~/resource/graph/types';
import type {Argument as ArgumentType, Property as PropertyType} from '~/resource/graph/types';
import React, {FunctionComponent, useMemo, useState} from 'react';
import {borderColor, em, sameBorder, textLightColor, textLighterColor} from '~/utils/style';
......
import {Documentation, Properties, SearchItem, SearchResult} from '~/resource/graph/types';
import type {Documentation, Properties, SearchItem, SearchResult} from '~/resource/graph/types';
import React, {useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
import {backgroundColor, borderColor, contentHeight, position, primaryColor, rem, size} from '~/utils/style';
import ChartToolbox from '~/components/ChartToolbox';
import HashLoader from 'react-spinners/HashLoader';
import logo from '~/assets/images/netron.png';
import styled from 'styled-components';
import {toast} from 'react-toastify';
import {useTranslation} from '~/utils/i18n';
import {useTranslation} from 'react-i18next';
const PUBLIC_PATH = process.env.PUBLIC_PATH;
const PUBLIC_PATH: string = import.meta.env.SNOWPACK_PUBLIC_PATH;
const toolboxHeight = rem(40);
......@@ -158,24 +159,20 @@ const Graph = React.forwardRef<GraphRef, GraphProps>(
[onRendered, onSearch, onShowModelProperties, onShowNodeProperties, onShowNodeDocumentation]
);
const dispatch = useCallback((type: string, data?: unknown) => {
if (process.browser) {
iframe.current?.contentWindow?.postMessage(
{
type,
data
},
`${window.location.protocol}//${window.location.host}`
);
}
iframe.current?.contentWindow?.postMessage(
{
type,
data
},
`${window.location.protocol}//${window.location.host}`
);
}, []);
useEffect(() => {
if (process.browser) {
window.addEventListener('message', handler);
dispatch('ready');
return () => {
window.removeEventListener('message', handler);
};
}
window.addEventListener('message', handler);
dispatch('ready');
return () => {
window.removeEventListener('message', handler);
};
}, [handler, dispatch]);
useEffect(() => (ready && dispatch('change-files', files)) || undefined, [dispatch, files, ready]);
......@@ -256,7 +253,7 @@ const Graph = React.forwardRef<GraphRef, GraphProps>(
<Content>
<iframe
ref={iframe}
src={`${PUBLIC_PATH ?? ''}/_next/static/netron/index.html`}
src={`${PUBLIC_PATH}/netron/index.html`}
frameBorder={0}
scrolling="no"
marginWidth={0}
......@@ -268,7 +265,7 @@ const Graph = React.forwardRef<GraphRef, GraphProps>(
target="_blank"
rel="noreferrer"
>
Powered by <img src={`${PUBLIC_PATH ?? ''}/images/netron.png`} alt="netron" />
Powered by <img src={PUBLIC_PATH + logo} alt="netron" />
</a>
</Content>
</RenderContent>
......
......@@ -2,7 +2,7 @@ import React, {FunctionComponent} from 'react';
import {backgroundColor, borderColor, rem, textLightColor} from '~/utils/style';
import styled from 'styled-components';
import {useTranslation} from '~/utils/i18n';
import {useTranslation} from 'react-i18next';
const Sidebar = styled.div`
height: 100%;
......
......@@ -3,9 +3,9 @@ import {backgroundColor, em, size} from '~/utils/style';
import Icon from '~/components/Icon';
import Properties from '~/components/GraphPage/Properties';
import {Properties as PropertiesType} from '~/resource/graph/types';
import type {Properties as PropertiesType} from '~/resource/graph/types';
import styled from 'styled-components';
import {useTranslation} from '~/utils/i18n';
import {useTranslation} from 'react-i18next';
const Dialog = styled.div`
position: fixed;
......
import React, {FunctionComponent, useCallback} from 'react';
import {Trans, useTranslation} from '~/utils/i18n';
import {Trans, useTranslation} from 'react-i18next';
import {borderRadius, em, textLightColor} from '~/utils/style';
import {Documentation as DocumentationType} from '~/resource/graph/types';
import type {Documentation as DocumentationType} from '~/resource/graph/types';
import GraphSidebar from '~/components/GraphPage/GraphSidebar';
import styled from 'styled-components';
......
......@@ -2,8 +2,8 @@ import React, {FunctionComponent} from 'react';
import GraphSidebar from '~/components/GraphPage/GraphSidebar';
import Properties from '~/components/GraphPage/Properties';
import {Properties as PropertiesType} from '~/resource/graph/types';
import {useTranslation} from '~/utils/i18n';
import type {Properties as PropertiesType} from '~/resource/graph/types';
import {useTranslation} from 'react-i18next';
type NodePropertiesSidebarProps = {
data?: PropertiesType | null;
......
import React, {FunctionComponent} from 'react';
import {Properties as PropertiesType} from '~/resource/graph/types';
import type {Properties as PropertiesType} from '~/resource/graph/types';
import Property from '~/components/GraphPage/Property';
import {em} from '~/utils/style';
import styled from 'styled-components';
import {useTranslation} from '~/utils/i18n';
import {useTranslation} from 'react-i18next';
const Header = styled.div`
font-size: ${em(16)};
......
import {Argument as ArgumentType, NameValues, Property as PropertyType} from '~/resource/graph/types';
import type {Argument as ArgumentType, NameValues, Property as PropertyType} from '~/resource/graph/types';
import React, {FunctionComponent} from 'react';
import {ellipsis, em, sameBorder} from '~/utils/style';
......
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册