Upgrade chart.js

This commit is contained in:
Haishan 2022-05-08 18:37:08 +08:00
parent 3458ef250d
commit e8f927bfd3
24 changed files with 164 additions and 239 deletions

View file

@ -37,6 +37,7 @@ rules:
# disable this temporarily since we have a lot of JS files # disable this temporarily since we have a lot of JS files
# and typescript-eslint runs against JS files too # and typescript-eslint runs against JS files too
'@typescript-eslint/explicit-module-boundary-types': off '@typescript-eslint/explicit-module-boundary-types': off
'@typescript-eslint/ban-ts-ignore': 'off'
react-hooks/rules-of-hooks: error react-hooks/rules-of-hooks: error
react-hooks/exhaustive-deps: react-hooks/exhaustive-deps:
- warn - warn

View file

@ -33,7 +33,7 @@
"@hsjs/react-cache": "0.0.0-alpha.aa94237", "@hsjs/react-cache": "0.0.0-alpha.aa94237",
"@reach/tooltip": "0.17.0", "@reach/tooltip": "0.17.0",
"@reach/visually-hidden": "0.17.0", "@reach/visually-hidden": "0.17.0",
"chart.js": "2.9.4", "chart.js": "3.7.1",
"clsx": "^1.1.0", "clsx": "^1.1.0",
"core-js": "3.22.4", "core-js": "3.22.4",
"date-fns": "2.28.0", "date-fns": "2.28.0",
@ -79,11 +79,10 @@
"@types/react": "18.0.9", "@types/react": "18.0.9",
"@types/react-dom": "18.0.3", "@types/react-dom": "18.0.3",
"@types/react-modal": "3.13.1", "@types/react-modal": "3.13.1",
"@types/react-tabs": "5.0.5",
"@types/react-window": "1.8.5", "@types/react-window": "1.8.5",
"@typescript-eslint/eslint-plugin": "5.22.0", "@typescript-eslint/eslint-plugin": "5.22.0",
"@typescript-eslint/parser": "5.22.0", "@typescript-eslint/parser": "5.22.0",
"@vitejs/plugin-react-refresh": "1.3.6", "@vitejs/plugin-react": "1.3.2",
"autoprefixer": "10.4.7", "autoprefixer": "10.4.7",
"cssnano": "5.1.7", "cssnano": "5.1.7",
"eslint": "8.15.0", "eslint": "8.15.0",

View file

@ -13,13 +13,12 @@ specifiers:
'@types/react': 18.0.9 '@types/react': 18.0.9
'@types/react-dom': 18.0.3 '@types/react-dom': 18.0.3
'@types/react-modal': 3.13.1 '@types/react-modal': 3.13.1
'@types/react-tabs': 5.0.5
'@types/react-window': 1.8.5 '@types/react-window': 1.8.5
'@typescript-eslint/eslint-plugin': 5.22.0 '@typescript-eslint/eslint-plugin': 5.22.0
'@typescript-eslint/parser': 5.22.0 '@typescript-eslint/parser': 5.22.0
'@vitejs/plugin-react-refresh': 1.3.6 '@vitejs/plugin-react': 1.3.2
autoprefixer: 10.4.7 autoprefixer: 10.4.7
chart.js: 2.9.4 chart.js: 3.7.1
clsx: ^1.1.0 clsx: ^1.1.0
core-js: 3.22.4 core-js: 3.22.4
cssnano: 5.1.7 cssnano: 5.1.7
@ -87,7 +86,7 @@ dependencies:
'@hsjs/react-cache': 0.0.0-alpha.aa94237_react@18.1.0 '@hsjs/react-cache': 0.0.0-alpha.aa94237_react@18.1.0
'@reach/tooltip': 0.17.0_ef5jwxihqo6n7gxfmzogljlgcm '@reach/tooltip': 0.17.0_ef5jwxihqo6n7gxfmzogljlgcm
'@reach/visually-hidden': 0.17.0_ef5jwxihqo6n7gxfmzogljlgcm '@reach/visually-hidden': 0.17.0_ef5jwxihqo6n7gxfmzogljlgcm
chart.js: 2.9.4 chart.js: 3.7.1
clsx: 1.1.1 clsx: 1.1.1
core-js: 3.22.4 core-js: 3.22.4
date-fns: 2.28.0 date-fns: 2.28.0
@ -133,11 +132,10 @@ devDependencies:
'@types/react': 18.0.9 '@types/react': 18.0.9
'@types/react-dom': 18.0.3 '@types/react-dom': 18.0.3
'@types/react-modal': 3.13.1 '@types/react-modal': 3.13.1
'@types/react-tabs': 5.0.5_react@18.1.0
'@types/react-window': 1.8.5 '@types/react-window': 1.8.5
'@typescript-eslint/eslint-plugin': 5.22.0_tal4xlmvnofklupd3hwjtzfb4q '@typescript-eslint/eslint-plugin': 5.22.0_tal4xlmvnofklupd3hwjtzfb4q
'@typescript-eslint/parser': 5.22.0_hcfsmds2fshutdssjqluwm76uu '@typescript-eslint/parser': 5.22.0_hcfsmds2fshutdssjqluwm76uu
'@vitejs/plugin-react-refresh': 1.3.6 '@vitejs/plugin-react': 1.3.2
autoprefixer: 10.4.7_postcss@8.4.13 autoprefixer: 10.4.7_postcss@8.4.13
cssnano: 5.1.7_postcss@8.4.13 cssnano: 5.1.7_postcss@8.4.13
eslint: 8.15.0 eslint: 8.15.0
@ -1849,15 +1847,6 @@ packages:
'@types/react': 18.0.9 '@types/react': 18.0.9
dev: true dev: true
/@types/react-tabs/5.0.5_react@18.1.0:
resolution: {integrity: sha512-3CZTmjR7nNrZnYbnxp/DtK5e82mhM22dN47aYObmYLcp9fC1XjIEF0mLzGKFl1fR6/R8X7DyGh9hO6lON6LVkQ==}
deprecated: This is a stub types definition. react-tabs provides its own type definitions, so you do not need this installed.
dependencies:
react-tabs: 5.1.0_react@18.1.0
transitivePeerDependencies:
- react
dev: true
/@types/react-window/1.8.5: /@types/react-window/1.8.5:
resolution: {integrity: sha512-V9q3CvhC9Jk9bWBOysPGaWy/Z0lxYcTXLtLipkt2cnRj1JOSFNF7wqGpkScSXMgBwC+fnVRg/7shwgddBG5ICw==} resolution: {integrity: sha512-V9q3CvhC9Jk9bWBOysPGaWy/Z0lxYcTXLtLipkt2cnRj1JOSFNF7wqGpkScSXMgBwC+fnVRg/7shwgddBG5ICw==}
dependencies: dependencies:
@ -2025,16 +2014,18 @@ packages:
eslint-visitor-keys: 3.3.0 eslint-visitor-keys: 3.3.0
dev: true dev: true
/@vitejs/plugin-react-refresh/1.3.6: /@vitejs/plugin-react/1.3.2:
resolution: {integrity: sha512-iNR/UqhUOmFFxiezt0em9CgmiJBdWR+5jGxB2FihaoJfqGt76kiwaKoVOJVU5NYcDWMdN06LbyN2VIGIoYdsEA==} resolution: {integrity: sha512-aurBNmMo0kz1O4qRoY+FM4epSA39y3ShWGuqfLRA/3z0oEJAdtoSfgA3aO98/PCCHAqMaduLxIxErWrVKIFzXA==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
deprecated: This package has been deprecated in favor of @vitejs/plugin-react
dependencies: dependencies:
'@babel/core': 7.17.10 '@babel/core': 7.17.10
'@babel/plugin-transform-react-jsx': 7.17.3_@babel+core@7.17.10
'@babel/plugin-transform-react-jsx-development': 7.16.7_@babel+core@7.17.10
'@babel/plugin-transform-react-jsx-self': 7.16.7_@babel+core@7.17.10 '@babel/plugin-transform-react-jsx-self': 7.16.7_@babel+core@7.17.10
'@babel/plugin-transform-react-jsx-source': 7.16.7_@babel+core@7.17.10 '@babel/plugin-transform-react-jsx-source': 7.16.7_@babel+core@7.17.10
'@rollup/pluginutils': 4.2.1 '@rollup/pluginutils': 4.2.1
react-refresh: 0.10.0 react-refresh: 0.13.0
resolve: 1.22.0
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
@ -2384,24 +2375,8 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
dev: true dev: true
/chart.js/2.9.4: /chart.js/3.7.1:
resolution: {integrity: sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A==} resolution: {integrity: sha512-8knRegQLFnPQAheZV8MjxIXc5gQEfDFD897BJgv/klO/vtIyFFmgMXrNfgrXpbTr/XbTturxRgxIXx/Y+ASJBA==}
dependencies:
chartjs-color: 2.4.1
moment: 2.29.3
dev: false
/chartjs-color-string/0.6.0:
resolution: {integrity: sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==}
dependencies:
color-name: 1.1.4
dev: false
/chartjs-color/2.4.1:
resolution: {integrity: sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==}
dependencies:
chartjs-color-string: 0.6.0
color-convert: 1.9.3
dev: false dev: false
/chokidar/3.5.3: /chokidar/3.5.3:
@ -2422,11 +2397,13 @@ packages:
/clsx/1.1.1: /clsx/1.1.1:
resolution: {integrity: sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==} resolution: {integrity: sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==}
engines: {node: '>=6'} engines: {node: '>=6'}
dev: false
/color-convert/1.9.3: /color-convert/1.9.3:
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
dependencies: dependencies:
color-name: 1.1.3 color-name: 1.1.3
dev: true
/color-convert/2.0.1: /color-convert/2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
@ -2437,9 +2414,11 @@ packages:
/color-name/1.1.3: /color-name/1.1.3:
resolution: {integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=} resolution: {integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=}
dev: true
/color-name/1.1.4: /color-name/1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
dev: true
/colord/2.9.2: /colord/2.9.2:
resolution: {integrity: sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ==} resolution: {integrity: sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ==}
@ -4131,10 +4110,6 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
dev: false dev: false
/moment/2.29.3:
resolution: {integrity: sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==}
dev: false
/ms/2.0.0: /ms/2.0.0:
resolution: {integrity: sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=} resolution: {integrity: sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=}
dev: true dev: true
@ -4837,8 +4812,8 @@ packages:
react-dom: 18.1.0_react@18.1.0 react-dom: 18.1.0_react@18.1.0
dev: false dev: false
/react-refresh/0.10.0: /react-refresh/0.13.0:
resolution: {integrity: sha512-PgidR3wST3dDYKr6b4pJoqQFpPGNKDSCDx4cZoshjXipw3LzO7mG1My2pwEzz2JVkF+inx3xRpDeQLFQGH/hsQ==} resolution: {integrity: sha512-XP8A9BT0CpRBD+NYLLeIhld/RqG9+gktUjW1FkE+Vm7OCinbG1SshcK5tb9ls4kzvjZr9mOQc7HYgBngEyPAXg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true dev: true
@ -4890,6 +4865,7 @@ packages:
clsx: 1.1.1 clsx: 1.1.1
prop-types: 15.8.1 prop-types: 15.8.1
react: 18.1.0 react: 18.1.0
dev: false
/react-tiny-fab/4.0.4_react@18.1.0: /react-tiny-fab/4.0.4_react@18.1.0:
resolution: {integrity: sha512-PxT6gEnIQR2vFfeIaa1Oq4PRX+cIEDbEfbS6PyevWCQngrKfqjMKPEcZOaaURaUclB9u3RilgjkaBUQFVlbWcg==} resolution: {integrity: sha512-PxT6gEnIQR2vFfeIaa1Oq4PRX+cIEDbEfbS6PyevWCQngrKfqjMKPEcZOaaURaUclB9u3RilgjkaBUQFVlbWcg==}

View file

@ -29,6 +29,7 @@ export type ConnectionItem = {
chains: string[]; chains: string[];
// e.g. 'Match', 'DomainKeyword' // e.g. 'Match', 'DomainKeyword'
rule: string; rule: string;
rulePayload?: string;
}; };
type ConnectionsData = { type ConnectionsData = {
downloadTotal: number; downloadTotal: number;

View file

@ -1,31 +1,33 @@
import { ClashAPIConfig } from '$src/types';
import { buildWebSocketURL, getURLAndInit } from '../misc/request-helper'; import { buildWebSocketURL, getURLAndInit } from '../misc/request-helper';
const endpoint = '/traffic'; const endpoint = '/traffic';
const textDecoder = new TextDecoder('utf-8'); const textDecoder = new TextDecoder('utf-8');
const Size = 150; const Size = 150;
const traffic = { const traffic = {
labels: Array(Size), labels: Array(Size).fill(0),
// labels: [],
up: Array(Size), up: Array(Size),
down: Array(Size), down: Array(Size),
size: Size, size: Size,
subscribers: [], subscribers: [],
appendData(o) { appendData(o: { up: number; down: number }) {
this.up.shift();
this.down.shift();
this.labels.shift();
const l = Date.now();
this.up.push(o.up); this.up.push(o.up);
this.down.push(o.down); this.down.push(o.down);
const t = new Date();
const l = '' + t.getMinutes() + t.getSeconds();
this.labels.push(l); this.labels.push(l);
if (this.up.length > this.size) this.up.shift();
if (this.down.length > this.size) this.down.shift();
if (this.labels.length > this.size) this.labels.shift();
this.subscribers.forEach((f) => f(o)); this.subscribers.forEach((f) => f(o));
}, },
subscribe(listener) { subscribe(listener: (x:any) => void) {
this.subscribers.push(listener); this.subscribers.push(listener);
return () => { return () => {
const idx = this.subscribers.indexOf(listener); const idx = this.subscribers.indexOf(listener);
@ -37,11 +39,11 @@ const traffic = {
let fetched = false; let fetched = false;
let decoded = ''; let decoded = '';
function parseAndAppend(x) { function parseAndAppend(x: string) {
traffic.appendData(JSON.parse(x)); traffic.appendData(JSON.parse(x));
} }
function pump(reader) { function pump(reader: ReadableStreamDefaultReader) {
return reader.read().then(({ done, value }) => { return reader.read().then(({ done, value }) => {
const str = textDecoder.decode(value, { stream: !done }); const str = textDecoder.decode(value, { stream: !done });
decoded += str; decoded += str;
@ -73,8 +75,8 @@ function pump(reader) {
// other value CLOSED // other value CLOSED
// similar to ws readyState but not the same // similar to ws readyState but not the same
// https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState // https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState
let wsState; let wsState: number;
function fetchData(apiConfig) { function fetchData(apiConfig: ClashAPIConfig) {
if (fetched || wsState === 1) return traffic; if (fetched || wsState === 1) return traffic;
wsState = 1; wsState = 1;
const url = buildWebSocketURL(apiConfig, endpoint); const url = buildWebSocketURL(apiConfig, endpoint);
@ -92,7 +94,7 @@ function fetchData(apiConfig) {
return traffic; return traffic;
} }
function fetchDataWithFetch(apiConfig) { function fetchDataWithFetch(apiConfig: ClashAPIConfig) {
if (fetched) return traffic; if (fetched) return traffic;
fetched = true; fetched = true;
const { url, init } = getURLAndInit(apiConfig); const { url, init } = getURLAndInit(apiConfig);

View file

@ -1,22 +1,22 @@
import 'modern-normalize/modern-normalize.css'; import 'modern-normalize/modern-normalize.css';
import './misc/i18n'; import './misc/i18n';
import React from 'react'; import * as React from 'react';
import ReactDOM from 'react-dom'; import { createRoot } from 'react-dom/client';
import Modal from 'react-modal'; import Modal from 'react-modal';
import Root from './components/Root'; import Root from './components/Root';
import * as swRegistration from './swRegistration'; import * as swRegistration from './swRegistration';
const rootEl = document.getElementById('app'); const rootEl = document.getElementById('app');
const root = createRoot(rootEl);
Modal.setAppElement(rootEl); Modal.setAppElement(rootEl);
ReactDOM.render( root.render(
<React.StrictMode> <React.StrictMode>
<Root /> <Root />
</React.StrictMode>, </React.StrictMode>
rootEl
); );
swRegistration.register(); swRegistration.register();

View file

@ -88,7 +88,7 @@ function formatConnectionDataItem(
download, download,
start: now - new Date(start).valueOf(), start: now - new Date(start).valueOf(),
chains: chains.reverse().join(' / '), chains: chains.reverse().join(' / '),
rule: (rulePayload == null | rulePayload === '') ? rule : (`${rule}(${rulePayload})`), rule: !rulePayload ? rule : `${rule}(${rulePayload})`,
...metadata, ...metadata,
host: `${host2}:${destinationPort}`, host: `${host2}:${destinationPort}`,
type: `${type}(${network})`, type: `${type}(${network})`,

View file

@ -3,9 +3,9 @@ import '@fontsource/roboto-mono/latin-400.css';
import '@fontsource/open-sans/latin-400.css'; import '@fontsource/open-sans/latin-400.css';
import '@fontsource/open-sans/latin-700.css'; import '@fontsource/open-sans/latin-700.css';
import React, { lazy, Suspense } from 'react'; import * as React from 'react';
import { QueryClientProvider } from 'react-query'; import { QueryClientProvider } from 'react-query';
import { PartialRouteObject } from 'react-router'; import { RouteObject } from 'react-router';
import { HashRouter as Router, useRoutes } from 'react-router-dom'; import { HashRouter as Router, useRoutes } from 'react-router-dom';
import { RecoilRoot } from 'recoil'; import { RecoilRoot } from 'recoil';
import { About } from 'src/components/about/About'; import { About } from 'src/components/about/About';
@ -24,6 +24,8 @@ import SideBar from './SideBar';
import StateProvider from './StateProvider'; import StateProvider from './StateProvider';
import StyleGuide from './StyleGuide'; import StyleGuide from './StyleGuide';
const { lazy, Suspense } = React;
const Connections = lazy(() => import('./Connections')); const Connections = lazy(() => import('./Connections'));
const Config = lazy(() => import('./Config')); const Config = lazy(() => import('./Config'));
const Logs = lazy(() => import('./Logs')); const Logs = lazy(() => import('./Logs'));
@ -38,10 +40,8 @@ const routes = [
{ path: '/proxies', element: <Proxies /> }, { path: '/proxies', element: <Proxies /> },
{ path: '/rules', element: <Rules /> }, { path: '/rules', element: <Rules /> },
{ path: '/about', element: <About /> }, { path: '/about', element: <About /> },
process.env.NODE_ENV === 'development' process.env.NODE_ENV === 'development' ? { path: '/style', element: <StyleGuide /> } : false,
? { path: '/style', element: <StyleGuide /> } ].filter(Boolean) as RouteObject[];
: false,
].filter(Boolean) as PartialRouteObject[];
function RouteInnerApp() { function RouteInnerApp() {
return useRoutes(routes); return useRoutes(routes);

View file

@ -20,7 +20,13 @@ const { memo } = React;
const paddingBottom = 30; const paddingBottom = 30;
function itemKey(index: number, { rules, provider }) { type ItemData = {
rules: any[];
provider: any;
apiConfig: ClashAPIConfig;
};
function itemKey(index: number, { rules, provider }: ItemData) {
const providerQty = provider.names.length; const providerQty = provider.names.length;
if (index < providerQty) { if (index < providerQty) {
@ -88,10 +94,8 @@ function Rules({ apiConfig }: RulesProps) {
<ContentHeader title={t('Rules')} /> <ContentHeader title={t('Rules')} />
<TextFilter placeholder="Filter" textAtom={ruleFilterText} /> <TextFilter placeholder="Filter" textAtom={ruleFilterText} />
</div> </div>
{/* @ts-expect-error ts-migrate(2322) FIXME: Type 'number | MutableRefObject<any>' is not assig... Remove this comment to see the full error message */}
<div ref={refRulesContainer} style={{ paddingBottom }}> <div ref={refRulesContainer} style={{ paddingBottom }}>
<VariableSizeList <VariableSizeList
// @ts-expect-error ts-migrate(2362) FIXME: The left-hand side of an arithmetic operation must... Remove this comment to see the full error message
height={containerHeight - paddingBottom} height={containerHeight - paddingBottom}
width="100%" width="100%"
itemCount={rules.length + provider.names.length} itemCount={rules.length + provider.names.length}

View file

@ -6,16 +6,8 @@ import React from 'react';
// this is just workaround // this is just workaround
immer.setAutoFreeze(false); immer.setAutoFreeze(false);
const { const { createContext, memo, useMemo, useRef, useEffect, useCallback, useContext, useState } =
createContext, React;
memo,
useMemo,
useRef,
useEffect,
useCallback,
useContext,
useState,
} = React;
export { immer }; export { immer };
@ -46,7 +38,7 @@ export default function Provider({ initialState, actions = {}, children }) {
} }
}, [getState]); }, [getState]);
const dispatch = useCallback( const dispatch = useCallback(
(actionId, fn) => { (actionId: string | ((a: any, b: any) => any), fn: (s: any) => void) => {
if (typeof actionId === 'function') return actionId(dispatch, getState); if (typeof actionId === 'function') return actionId(dispatch, getState);
const stateNext = produce(getState(), fn); const stateNext = produce(getState(), fn);
@ -61,26 +53,21 @@ export default function Provider({ initialState, actions = {}, children }) {
}, },
[getState] [getState]
); );
const boundActions = useMemo(() => bindActions(actions, dispatch), [ const boundActions = useMemo(() => bindActions(actions, dispatch), [actions, dispatch]);
actions,
dispatch,
]);
return ( return (
<StateContext.Provider value={state}> <StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}> <DispatchContext.Provider value={dispatch}>
<ActionsContext.Provider value={boundActions}> <ActionsContext.Provider value={boundActions}>{children}</ActionsContext.Provider>
{children}
</ActionsContext.Provider>
</DispatchContext.Provider> </DispatchContext.Provider>
</StateContext.Provider> </StateContext.Provider>
); );
} }
export function connect(mapStateToProps) { export function connect(mapStateToProps: any) {
return (Component) => { return (Component: any) => {
const MemoComponent = memo(Component); const MemoComponent = memo(Component);
function Connected(props) { function Connected(props: any) {
const state = useContext(StateContext); const state = useContext(StateContext);
const dispatch = useContext(DispatchContext); const dispatch = useContext(DispatchContext);
const mapped = mapStateToProps(state, props); const mapped = mapStateToProps(state, props);
@ -92,14 +79,13 @@ export function connect(mapStateToProps) {
} }
// steal from https://github.com/reduxjs/redux/blob/master/src/bindActionCreators.ts // steal from https://github.com/reduxjs/redux/blob/master/src/bindActionCreators.ts
function bindAction(action, dispatch) { function bindAction(action: any, dispatch: any) {
return function (...args) { return function (...args: any[]) {
// @ts-expect-error ts-migrate(2683) FIXME: 'this' implicitly has type 'any' because it does n... Remove this comment to see the full error message
return dispatch(action.apply(this, args)); return dispatch(action.apply(this, args));
}; };
} }
function bindActions(actions, dispatch) { function bindActions(actions: any, dispatch: any) {
const boundActions = {}; const boundActions = {};
for (const key in actions) { for (const key in actions) {
const action = actions[key]; const action = actions[key];

View file

@ -21,9 +21,7 @@ const optionsRule = [
{ label: 'Direct', value: 'Direct' }, { label: 'Direct', value: 'Direct' },
]; ];
const Pane = ({ children, style }) => ( const Pane = ({ children, style }) => <div style={{ ...paneStyle, ...style }}>{children}</div>;
<div style={{ ...paneStyle, ...style }}>{children}</div>
);
function useToggle(initialState = false) { function useToggle(initialState = false) {
const [onoff, setonoff] = React.useState(initialState); const [onoff, setonoff] = React.useState(initialState);
@ -52,12 +50,7 @@ class StyleGuide extends PureComponent {
</Pane> </Pane>
{/* @ts-expect-error ts-migrate(2741) FIXME: Property 'style' is missing in type '{ children: E... Remove this comment to see the full error message */} {/* @ts-expect-error ts-migrate(2741) FIXME: Property 'style' is missing in type '{ children: E... Remove this comment to see the full error message */}
<Pane> <Pane>
<ToggleSwitch <ToggleSwitch name="test" options={optionsRule} value="Rule" onChange={noop} />
name="test"
options={optionsRule}
value="Rule"
onChange={noop}
/>
</Pane> </Pane>
{/* @ts-expect-error ts-migrate(2741) FIXME: Property 'style' is missing in type '{ children: E... Remove this comment to see the full error message */} {/* @ts-expect-error ts-migrate(2741) FIXME: Property 'style' is missing in type '{ children: E... Remove this comment to see the full error message */}
<Pane> <Pane>

View file

@ -1,11 +1,14 @@
import React from 'react'; import * as React from 'react';
import S from 'react-switch'; import ReactSwitch from 'react-switch';
import { State } from '$src/store/types';
import { getTheme } from '../store/app'; import { getTheme } from '../store/app';
import { connect } from './StateProvider'; import { connect } from './StateProvider';
// workaround https://github.com/vitejs/vite/issues/2139#issuecomment-802981228 // workaround https://github.com/vitejs/vite/issues/2139#issuecomment-802981228
const Switch = S.default ? S.default : S; // @ts-ignore
const Switch = ReactSwitch.default ? ReactSwitch.default : ReactSwitch;
function SwitchThemed({ checked = false, onChange, theme, name }) { function SwitchThemed({ checked = false, onChange, theme, name }) {
const offColor = theme === 'dark' ? '#393939' : '#e9e9e9'; const offColor = theme === 'dark' ? '#393939' : '#e9e9e9';
@ -29,6 +32,4 @@ function SwitchThemed({ checked = false, onChange, theme, name }) {
); );
} }
export default connect((s) => ({ export default connect((s: State) => ({ theme: getTheme(s) }))(SwitchThemed);
theme: getTheme(s),
}))(SwitchThemed);

View file

@ -16,8 +16,7 @@ function ToggleSwitch({ options, value, name, onChange }: Props) {
); );
const getPortionPercentage = useCallback( const getPortionPercentage = useCallback(
// @ts-expect-error ts-migrate(7030) FIXME: Not all code paths return a value. (idx: number) => {
(idx) => {
const w = Math.floor(100 / options.length); const w = Math.floor(100 / options.length);
if (idx === options.length - 1) { if (idx === options.length - 1) {
return 100 - options.length * w + w; return 100 - options.length * w + w;

View file

@ -1,6 +1,8 @@
import * as React from 'react'; import * as React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { State } from '$src/store/types';
import { fetchData } from '../api/traffic'; import { fetchData } from '../api/traffic';
import useLineChart from '../hooks/useLineChart'; import useLineChart from '../hooks/useLineChart';
import { import {
@ -19,7 +21,7 @@ const chartWrapperStyle = {
maxWidth: 1000, maxWidth: 1000,
}; };
const mapState = (s) => ({ const mapState = (s: State) => ({
apiConfig: getClashAPIConfig(s), apiConfig: getClashAPIConfig(s),
selectedChartStyleIndex: getSelectedChartStyleIndex(s), selectedChartStyleIndex: getSelectedChartStyleIndex(s),
}); });
@ -27,7 +29,7 @@ const mapState = (s) => ({
export default connect(mapState)(TrafficChart); export default connect(mapState)(TrafficChart);
function TrafficChart({ apiConfig, selectedChartStyleIndex }) { function TrafficChart({ apiConfig, selectedChartStyleIndex }) {
const Chart = chartJSResource.read(); const ChartMod = chartJSResource.read();
const traffic = fetchData(apiConfig); const traffic = fetchData(apiConfig);
const { t } = useTranslation(); const { t } = useTranslation();
const data = useMemo( const data = useMemo(
@ -48,10 +50,10 @@ function TrafficChart({ apiConfig, selectedChartStyleIndex }) {
}, },
], ],
}), }),
[traffic, selectedChartStyleIndex, t] [ traffic, selectedChartStyleIndex, t]
); );
useLineChart(Chart, 'trafficChart', data, traffic); useLineChart(ChartMod.Chart, 'trafficChart', data, traffic);
return ( return (
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ position: string; maxWidth: number; }' is ... Remove this comment to see the full error message // @ts-expect-error ts-migrate(2322) FIXME: Type '{ position: string; maxWidth: number; }' is ... Remove this comment to see the full error message

View file

@ -1,19 +1,17 @@
import React, { useMemo } from 'react'; import * as React from 'react';
import useLineChart from '../hooks/useLineChart'; import useLineChart from '../hooks/useLineChart';
import { import { chartJSResource, chartStyles, commonDataSetProps } from '../misc/chart';
chartJSResource,
chartStyles,
commonDataSetProps,
} from '../misc/chart';
const extraChartOptions = { const { useMemo } = React;
legend: {
display: false, const extraChartOptions: import('chart.js').ChartOptions<'line'> = {
plugins: {
legend: { display: false },
}, },
scales: { scales: {
xAxes: [{ display: false }], x: { display: false, type: 'category' },
yAxes: [{ display: false }], y: { display: false, type: 'linear' },
}, },
}; };
@ -22,7 +20,7 @@ const data2 = [184e3, 183e3, 196e3, 182e3, 190e3, 186e3, 182e3, 189e3];
const labels = data1; const labels = data1;
export default function TrafficChart({ id }) { export default function TrafficChart({ id }) {
const Chart = chartJSResource.read(); const ChartMod = chartJSResource.read();
const data = useMemo( const data = useMemo(
() => ({ () => ({
@ -44,7 +42,7 @@ export default function TrafficChart({ id }) {
); );
const eleId = 'chart-' + id; const eleId = 'chart-' + id;
useLineChart(Chart, eleId, data, null, extraChartOptions); useLineChart(ChartMod.Chart, eleId, data, null, extraChartOptions);
return ( return (
<div style={{ width: 100, padding: 5 }}> <div style={{ width: 100, padding: 5 }}>

View file

@ -1,28 +1,24 @@
import type { ChartConfiguration } from 'chart.js';
import React from 'react'; import React from 'react';
import { commonChartOptions } from 'src/misc/chart'; import { commonChartOptions } from 'src/misc/chart';
const { useEffect } = React; const { useEffect } = React;
const options = commonChartOptions;
export default function useLineChart( export default function useLineChart(
Chart, chart: typeof import('chart.js').Chart,
elementId, elementId: string,
data, data: ChartConfiguration['data'],
subscription, subscription: any,
extraChartOptions = {} extraChartOptions = {}
) { ) {
useEffect(() => { useEffect(() => {
const ctx = document.getElementById(elementId).getContext('2d'); const ctx = (document.getElementById(elementId) as HTMLCanvasElement).getContext('2d');
const c = new Chart(ctx, { const options = { ...commonChartOptions, ...extraChartOptions };
type: 'line', const c = new chart(ctx, { type: 'line', data, options });
data, const unsubscribe = subscription && subscription.subscribe(() => c.update());
options: { ...options, ...extraChartOptions },
});
const unsubscribe =
subscription && subscription.subscribe(() => c.update());
return () => { return () => {
unsubscribe && unsubscribe(); unsubscribe && unsubscribe();
c.destroy(); c.destroy();
}; };
}, [Chart, elementId, data, subscription, extraChartOptions]); }, [chart, elementId, data, subscription, extraChartOptions]);
} }

23
src/misc/chart-lib.ts Normal file
View file

@ -0,0 +1,23 @@
import {
CategoryScale,
Chart,
Filler,
Legend,
LinearScale,
LineController,
LineElement,
PointElement,
} from 'chart.js';
// see https://www.chartjs.org/docs/latest/getting-started/integration.html#bundlers-webpack-rollup-etc
Chart.register(
LineElement,
PointElement,
LineController,
CategoryScale,
LinearScale,
Filler,
Legend
);
export { Chart };

View file

@ -1,71 +1,36 @@
import { unstable_createResource as createResource } from '@hsjs/react-cache'; import { unstable_createResource as createResource } from '@hsjs/react-cache';
import prettyBytes from './pretty-bytes'; import prettyBytes from './pretty-bytes';
export const chartJSResource = createResource(() => { export const chartJSResource = createResource(() => {
return import( return import('$src/misc/chart-lib');
/* webpackChunkName: "chartjs" */
/* webpackPrefetch: true */
/* webpackPreload: true */
'chart.js/dist/Chart.min.js'
).then((c) => c.default);
}); });
export const commonDataSetProps = { export const commonDataSetProps = { borderWidth: 1, pointRadius: 0, tension: 0.2, fill: true };
borderWidth: 1,
lineTension: 0,
pointRadius: 0,
};
export const commonChartOptions = { export const commonChartOptions: import('chart.js').ChartOptions<'line'> = {
responsive: true, responsive: true,
maintainAspectRatio: true, maintainAspectRatio: true,
title: { plugins: {
display: false, legend: { labels: { boxWidth: 20 } }
},
legend: {
display: true,
position: 'top',
labels: {
fontColor: '#ccc',
boxWidth: 20,
},
},
tooltips: {
enabled: false,
mode: 'index',
intersect: false,
animationDuration: 100,
},
hover: {
mode: 'nearest',
intersect: true,
}, },
scales: { scales: {
xAxes: [ x: { display: false, type: 'category' },
{ y: {
display: false, type: 'linear',
gridLines: { display: true,
display: false, grid: {
},
},
],
yAxes: [
{
display: true, display: true,
gridLines: { color: '#555',
display: true, drawTicks: false,
color: '#555', borderDash: [3, 6],
borderDash: [3, 6], drawBorder: false,
drawBorder: false, },
}, ticks: {
ticks: { callback(value: number) {
callback(value) { return prettyBytes(value) + '/s ';
return prettyBytes(value) + '/s ';
},
}, },
}, },
], },
}, },
}; };

View file

@ -1 +0,0 @@
// const ProxySortingOptions =

View file

@ -1,10 +0,0 @@
const dsn = 'https://7068a15928ae45cf884dd8398fe8649c@sentry.io/1359284';
let Sentry;
export async function getSentry() {
if (Sentry) return Sentry;
const s = await import('@sentry/browser');
s.init({ dsn });
// eslint-disable-next-line require-atomic-updates
Sentry = s;
return Sentry;
}

View file

@ -22,7 +22,6 @@ export const getLogStreamingPaused = (s: State) => s.app.logStreamingPaused;
const saveStateDebounced = debounce(saveState, 600); const saveStateDebounced = debounce(saveState, 600);
// @ts-expect-error ts-migrate(7030) FIXME: Not all code paths return a value.
function findClashAPIConfigIndex(getState: GetStateFn, { baseURL, secret }) { function findClashAPIConfigIndex(getState: GetStateFn, { baseURL, secret }) {
const arr = getClashAPIConfigs(getState()); const arr = getClashAPIConfigs(getState());
for (let i = 0; i < arr.length; i++) { for (let i = 0; i < arr.length; i++) {

View file

@ -108,10 +108,8 @@ export type State = {
export type GetStateFn = () => State; export type GetStateFn = () => State;
export interface DispatchFn { export interface DispatchFn {
(msg: string, change: (s: State) => void): void; (msg: string, change: (s: State) => void): void;
( (action: (dispatch: DispatchFn, getState: GetStateFn) => Promise<void>): ReturnType<
action: (dispatch: DispatchFn, getState: GetStateFn) => Promise<void>
): ReturnType<typeof action>;
(action: (dispatch: DispatchFn, getState: GetStateFn) => void): ReturnType<
typeof action typeof action
>; >;
(action: (dispatch: DispatchFn, getState: GetStateFn) => void): ReturnType<typeof action>;
} }

View file

@ -2,6 +2,10 @@
"$schema": "https://json.schemastore.org/tsconfig", "$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "baseUrl": ".",
"paths": {
"$src": ["src"],
"$src/*": ["src/*"]
},
"target": "ESNext", "target": "ESNext",
"lib": ["DOM", "DOM.Iterable", "ESNext", "WebWorker"], "lib": ["DOM", "DOM.Iterable", "ESNext", "WebWorker"],
"skipLibCheck": true, "skipLibCheck": true,
@ -14,20 +18,7 @@
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"noEmit": true, "noEmit": true,
"jsx": "react", "jsx": "react"
"downlevelIteration": true,
"importHelpers": true,
"sourceMap": true,
"checkJs": false,
"experimentalDecorators": true,
"noImplicitAny": false,
"noUnusedParameters": false,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"strictNullChecks": false,
"suppressImplicitAnyIndexErrors": true,
"types": ["jest"]
}, },
"include": ["./src"] "include": ["./src"]
} }

View file

@ -1,6 +1,6 @@
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
import { VitePWA } from 'vite-plugin-pwa'; import { VitePWA } from 'vite-plugin-pwa';
import reactRefresh from '@vitejs/plugin-react-refresh'; import react from '@vitejs/plugin-react'
import * as path from 'path'; import * as path from 'path';
import * as pkg from './package.json'; import * as pkg from './package.json';
@ -12,15 +12,22 @@ export default defineConfig(({ mode }) => ({
'process.env.PUBLIC_URL': JSON.stringify('./'), 'process.env.PUBLIC_URL': JSON.stringify('./'),
}, },
base: './', base: './',
resolve: {
alias: {
$src: path.resolve(__dirname, './src'),
src: path.resolve(__dirname, './src'),
},
},
publicDir: 'assets', publicDir: 'assets',
build: { build: {
// sourcemap: true,
// the default value is 'dist' // the default value is 'dist'
// which make more sense // which make more sense
// but change this may break other people's tools // but change this may break other people's tools
outDir: 'public', outDir: 'public',
}, },
plugins: [ plugins: [
reactRefresh(), react(),
VitePWA({ VitePWA({
srcDir: 'src', srcDir: 'src',
outDir: 'public', outDir: 'public',
@ -29,9 +36,4 @@ export default defineConfig(({ mode }) => ({
base: './', base: './',
}), }),
], ],
resolve: {
alias: {
src: path.resolve(__dirname, './src'),
},
},
})); }));