refactor(chart): lazy load Chart.js with suspense
- chore: add ico favicon - chore: lint - chore: add react-hooks lint rules
This commit is contained in:
parent
d247e37890
commit
7f75345c03
12 changed files with 81 additions and 67 deletions
|
@ -10,9 +10,9 @@ parser: babel-eslint
|
||||||
plugins:
|
plugins:
|
||||||
- import
|
- import
|
||||||
- react
|
- react
|
||||||
|
- react-hooks
|
||||||
- jest
|
- jest
|
||||||
|
|
||||||
|
|
||||||
extends:
|
extends:
|
||||||
- eslint:recommended
|
- eslint:recommended
|
||||||
- plugin:import/errors
|
- plugin:import/errors
|
||||||
|
@ -31,3 +31,4 @@ rules:
|
||||||
react/jsx-uses-react: "error"
|
react/jsx-uses-react: "error"
|
||||||
react/jsx-uses-vars: "error"
|
react/jsx-uses-vars: "error"
|
||||||
react/react-in-jsx-scope: "error"
|
react/react-in-jsx-scope: "error"
|
||||||
|
react-hooks/rules-of-hooks: error
|
||||||
|
|
BIN
assets/yacd.ico
Normal file
BIN
assets/yacd.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
|
@ -40,6 +40,7 @@
|
||||||
"modern-normalize": "^0.5.0",
|
"modern-normalize": "^0.5.0",
|
||||||
"prop-types": "^15.5.10",
|
"prop-types": "^15.5.10",
|
||||||
"react": "^16.7.0-alpha.0",
|
"react": "^16.7.0-alpha.0",
|
||||||
|
"react-cache": "^2.0.0-alpha.0",
|
||||||
"react-dom": "^16.7.0-alpha.0",
|
"react-dom": "^16.7.0-alpha.0",
|
||||||
"react-modal": "^3.6.1",
|
"react-modal": "^3.6.1",
|
||||||
"react-redux": "^6.0.0-alpha.2a2f108",
|
"react-redux": "^6.0.0-alpha.2a2f108",
|
||||||
|
@ -71,6 +72,7 @@
|
||||||
"eslint-plugin-import": "2.14.0",
|
"eslint-plugin-import": "2.14.0",
|
||||||
"eslint-plugin-jest": "^21.26.1",
|
"eslint-plugin-jest": "^21.26.1",
|
||||||
"eslint-plugin-react": "7.11.1",
|
"eslint-plugin-react": "7.11.1",
|
||||||
|
"eslint-plugin-react-hooks": "^0.0.0",
|
||||||
"file-loader": "^2.0.0",
|
"file-loader": "^2.0.0",
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"husky": "^1.0.1",
|
"husky": "^1.0.1",
|
||||||
|
|
|
@ -26,6 +26,7 @@ let even = false;
|
||||||
const store = {
|
const store = {
|
||||||
logs: [],
|
logs: [],
|
||||||
size: Size,
|
size: Size,
|
||||||
|
fetched: false,
|
||||||
subscribers: [],
|
subscribers: [],
|
||||||
appendData(o) {
|
appendData(o) {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
@ -68,15 +69,19 @@ function pump(reader) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let fetched = false;
|
|
||||||
function fetchLogs() {
|
function fetchLogs() {
|
||||||
if (fetched) return store;
|
if (store.fetched) return store;
|
||||||
|
store.fetched = true;
|
||||||
const { url, init } = getURLAndInit();
|
const { url, init } = getURLAndInit();
|
||||||
fetch(url, init).then(response => {
|
fetch(url, init)
|
||||||
fetched = true;
|
.then(response => {
|
||||||
const reader = response.body.getReader();
|
const reader = response.body.getReader();
|
||||||
pump(reader);
|
pump(reader);
|
||||||
});
|
})
|
||||||
|
.catch(err => {
|
||||||
|
store.fetched = false;
|
||||||
|
console.log('Error', err);
|
||||||
|
});
|
||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import React from 'react';
|
import React, { Suspense } from 'react';
|
||||||
|
|
||||||
import ContentHeader from 'c/ContentHeader';
|
import ContentHeader from 'c/ContentHeader';
|
||||||
import TrafficChart from 'c/TrafficChart';
|
import TrafficChart from 'c/TrafficChart';
|
||||||
import TrafficNow from 'c/TrafficNow';
|
import TrafficNow from 'c/TrafficNow';
|
||||||
|
import Loading from 'c/Loading';
|
||||||
import s0 from 'c/Home.module.scss';
|
import s0 from 'c/Home.module.scss';
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
|
@ -14,7 +15,9 @@ export default function Home() {
|
||||||
<TrafficNow />
|
<TrafficNow />
|
||||||
</div>
|
</div>
|
||||||
<div className={s0.chart}>
|
<div className={s0.chart}>
|
||||||
<TrafficChart />
|
<Suspense fallback={<Loading height="200px" />} maxDuration={10}>
|
||||||
|
<TrafficChart />
|
||||||
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,14 +1,19 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import style from './Loading.module.scss';
|
import s0 from './Loading.module.scss';
|
||||||
|
|
||||||
const Loading = () => {
|
const Loading = ({ height }) => {
|
||||||
|
const style = height ? { height } : {};
|
||||||
return (
|
return (
|
||||||
<div className={style.loading}>
|
<div className={s0.loading} style={style}>
|
||||||
<div className={style.left + ' ' + style.circle} />
|
<div className={s0.pulse} />
|
||||||
<div className={style.right + ' ' + style.circle} />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading.propTypes = {
|
||||||
|
height: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
export default Loading;
|
export default Loading;
|
||||||
|
|
|
@ -1,50 +1,31 @@
|
||||||
$color1: #2a477a;
|
// $color1: #2a477a;
|
||||||
$color2: #dddddd;
|
$color1: #dddddd;
|
||||||
|
|
||||||
@keyframes moveRight {
|
$size: 40px;
|
||||||
0% {
|
|
||||||
transform: translate(-50px);
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
transform: translate(10px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes moveLeft {
|
|
||||||
0% {
|
|
||||||
transform: translate(50px);
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
transform: translate(-10px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading {
|
.loading {
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 300px;
|
height: 100%;
|
||||||
margin: 0 auto;
|
display: flex;
|
||||||
height: 30vh;
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.circle {
|
.pulse {
|
||||||
width: 40px;
|
width: $size;
|
||||||
height: 40px;
|
height: $size;
|
||||||
border-radius: 50%;
|
margin: 10px;
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left {
|
|
||||||
background-color: $color1;
|
background-color: $color1;
|
||||||
left: 50%;
|
border-radius: 100%;
|
||||||
animation: moveRight 1s ease-in-out 0s infinite alternate;
|
animation: pulseScaleOut 1s infinite ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.right {
|
@keyframes pulseScaleOut {
|
||||||
background-color: $color2;
|
0% {
|
||||||
right: 50%;
|
transform: scale(0);
|
||||||
animation: moveLeft 1s ease-in-out 0s infinite alternate;
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ export default function Logs() {
|
||||||
const x = fetchLogs();
|
const x = fetchLogs();
|
||||||
setLogs(x.logs);
|
setLogs(x.logs);
|
||||||
return x.subscribe(() => setLogs(x.logs));
|
return x.subscribe(() => setLogs(x.logs));
|
||||||
});
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import Chart from 'chart.js/dist/Chart.min.js';
|
|
||||||
import prettyBytes from 'm/pretty-bytes';
|
import prettyBytes from 'm/pretty-bytes';
|
||||||
|
|
||||||
import { fetchData } from '../api/traffic';
|
import { fetchData } from '../api/traffic';
|
||||||
|
import { unstable_createResource as createResource } from 'react-cache';
|
||||||
|
|
||||||
|
// const delay = ms => new Promise(r => setTimeout(r, ms));
|
||||||
|
const chartJSResource = createResource(() => {
|
||||||
|
return import('chart.js/dist/Chart.min.js').then(c => c.default);
|
||||||
|
});
|
||||||
|
|
||||||
const colorCombo = {
|
const colorCombo = {
|
||||||
0: {
|
0: {
|
||||||
|
@ -108,13 +112,15 @@ const options = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const chartWrapperStyle = {
|
const chartWrapperStyle = {
|
||||||
|
// make chartjs chart responsive
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
width: '90%'
|
width: '90%'
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function TrafficChart() {
|
export default function TrafficChart() {
|
||||||
|
const Chart = chartJSResource.read();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const ctx = document.getElementById('myChart').getContext('2d');
|
const ctx = document.getElementById('trafficChart').getContext('2d');
|
||||||
const traffic = fetchData();
|
const traffic = fetchData();
|
||||||
const data = {
|
const data = {
|
||||||
labels: traffic.labels,
|
labels: traffic.labels,
|
||||||
|
@ -139,7 +145,7 @@ export default function TrafficChart() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={chartWrapperStyle}>
|
<div style={chartWrapperStyle}>
|
||||||
<canvas id="myChart" />
|
<canvas id="trafficChart" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,14 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
<link rel="shortcut icon" href="yacd.ico">
|
||||||
|
<link rel="icon" type="image/png" sizes="64x64" href="yacd-64.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="128x128" href="yacd-128.png">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name=viewport content="width=device-width, initial-scale=1">
|
<meta name=viewport content="width=device-width, initial-scale=1">
|
||||||
<meta name="application-name" content="yacd">
|
<meta name="application-name" content="yacd">
|
||||||
<meta name="description" content="Yet Another Clash Dashboard">
|
<meta name="description" content="Yet Another Clash Dashboard">
|
||||||
<meta name="theme-color" content="#202020">
|
<meta name="theme-color" content="#202020">
|
||||||
<link id="favicon" rel="icon" type="image/png" sizes="64x64" href="yacd-64.png">
|
|
||||||
<link id="favicon" rel="icon" type="image/png" sizes="128x128" href="yacd-128.png">
|
|
||||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||||
<link rel="prefetch" href="https://cdn.jsdelivr.net/npm/@hsjs/fonts@0.0.1/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhLq3-cXbKD.woff2">
|
<link rel="prefetch" href="https://cdn.jsdelivr.net/npm/@hsjs/fonts@0.0.1/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhLq3-cXbKD.woff2">
|
||||||
<meta property="og:image" content="https://user-images.githubusercontent.com/1166872/47304841-536f3d80-d65a-11e8-8908-1917127dafc5.png">
|
<meta property="og:image" content="https://user-images.githubusercontent.com/1166872/47304841-536f3d80-d65a-11e8-8908-1917127dafc5.png">
|
||||||
|
|
|
@ -106,11 +106,11 @@ module.exports = {
|
||||||
// test: /[\\/]node_modules[\\/](core-js)[\\/]/,
|
// test: /[\\/]node_modules[\\/](core-js)[\\/]/,
|
||||||
// chunks: 'all'
|
// chunks: 'all'
|
||||||
// },
|
// },
|
||||||
chartjs: {
|
// chartjs: {
|
||||||
test: /[\\/]node_modules[\\/]chart\.js[\\/]/,
|
// test: /[\\/]node_modules[\\/]chart\.js[\\/]/,
|
||||||
// name: 'chartjs',
|
// // name: 'chartjs',
|
||||||
chunks: 'all'
|
// chunks: 'all'
|
||||||
},
|
// },
|
||||||
react: {
|
react: {
|
||||||
test: /[\\/]node_modules[\\/](react-dom|react|redux|react-router|react-router-dom|schedule|react-redux|react-modal)[\\/]/,
|
test: /[\\/]node_modules[\\/](react-dom|react|redux|react-router|react-router-dom|schedule|react-redux|react-modal)[\\/]/,
|
||||||
chunks: 'all'
|
chunks: 'all'
|
||||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -2919,6 +2919,11 @@ eslint-plugin-jest@^21.26.1:
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-21.26.2.tgz#5b24413970e83e2c5b87c5c047a08a4881783605"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-21.26.2.tgz#5b24413970e83e2c5b87c5c047a08a4881783605"
|
||||||
integrity sha512-SCTBC6q182D4qQlQAN81D351jdte/YwTMo4f+l19Gvh1VemaNZP7ak3MLLvw6xkL9dO2FxVjCLk5DCdl1KfdLw==
|
integrity sha512-SCTBC6q182D4qQlQAN81D351jdte/YwTMo4f+l19Gvh1VemaNZP7ak3MLLvw6xkL9dO2FxVjCLk5DCdl1KfdLw==
|
||||||
|
|
||||||
|
eslint-plugin-react-hooks@^0.0.0:
|
||||||
|
version "0.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-0.0.0.tgz#9988f14082a159931c3dfa9ba699130457da927a"
|
||||||
|
integrity sha512-SXyU7C3E8AJbXKMdb10P/zHazcxzfuWR5OFwAVZKXVU7P/H56NLszVG6WdQBo9Pt80FfnPXtUGGbWhs3/98N4w==
|
||||||
|
|
||||||
eslint-plugin-react@7.11.1:
|
eslint-plugin-react@7.11.1:
|
||||||
version "7.11.1"
|
version "7.11.1"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.11.1.tgz#c01a7af6f17519457d6116aa94fc6d2ccad5443c"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.11.1.tgz#c01a7af6f17519457d6116aa94fc6d2ccad5443c"
|
||||||
|
@ -6603,6 +6608,11 @@ rc@^1.2.7:
|
||||||
minimist "^1.2.0"
|
minimist "^1.2.0"
|
||||||
strip-json-comments "~2.0.1"
|
strip-json-comments "~2.0.1"
|
||||||
|
|
||||||
|
react-cache@^2.0.0-alpha.0:
|
||||||
|
version "2.0.0-alpha.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-cache/-/react-cache-2.0.0-alpha.0.tgz#d02d16a4565fd9f12478a2a41e980ba4fcbab4d5"
|
||||||
|
integrity sha512-o7nA1dIbi6wOoIoQPQBL58CgIQ4tCA3itIR3WlE2VHMIFkIXbvsMyolg+Q9wDVUlCvU6b2n40RTTv8vOmjXCOQ==
|
||||||
|
|
||||||
react-dom@^16.7.0-alpha.0:
|
react-dom@^16.7.0-alpha.0:
|
||||||
version "16.7.0-alpha.0"
|
version "16.7.0-alpha.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.7.0-alpha.0.tgz#8379158d4c76d63c989f325f45dfa5762582584f"
|
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.7.0-alpha.0.tgz#8379158d4c76d63c989f325f45dfa5762582584f"
|
||||||
|
|
Loading…
Reference in a new issue