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
# and typescript-eslint runs against JS files too
'@typescript-eslint/explicit-module-boundary-types': off
'@typescript-eslint/ban-ts-ignore': 'off'
react-hooks/rules-of-hooks: error
react-hooks/exhaustive-deps:
- warn

View file

@ -33,7 +33,7 @@
"@hsjs/react-cache": "0.0.0-alpha.aa94237",
"@reach/tooltip": "0.17.0",
"@reach/visually-hidden": "0.17.0",
"chart.js": "2.9.4",
"chart.js": "3.7.1",
"clsx": "^1.1.0",
"core-js": "3.22.4",
"date-fns": "2.28.0",
@ -79,11 +79,10 @@
"@types/react": "18.0.9",
"@types/react-dom": "18.0.3",
"@types/react-modal": "3.13.1",
"@types/react-tabs": "5.0.5",
"@types/react-window": "1.8.5",
"@typescript-eslint/eslint-plugin": "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",
"cssnano": "5.1.7",
"eslint": "8.15.0",

View file

@ -13,13 +13,12 @@ specifiers:
'@types/react': 18.0.9
'@types/react-dom': 18.0.3
'@types/react-modal': 3.13.1
'@types/react-tabs': 5.0.5
'@types/react-window': 1.8.5
'@typescript-eslint/eslint-plugin': 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
chart.js: 2.9.4
chart.js: 3.7.1
clsx: ^1.1.0
core-js: 3.22.4
cssnano: 5.1.7
@ -87,7 +86,7 @@ dependencies:
'@hsjs/react-cache': 0.0.0-alpha.aa94237_react@18.1.0
'@reach/tooltip': 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
core-js: 3.22.4
date-fns: 2.28.0
@ -133,11 +132,10 @@ devDependencies:
'@types/react': 18.0.9
'@types/react-dom': 18.0.3
'@types/react-modal': 3.13.1
'@types/react-tabs': 5.0.5_react@18.1.0
'@types/react-window': 1.8.5
'@typescript-eslint/eslint-plugin': 5.22.0_tal4xlmvnofklupd3hwjtzfb4q
'@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
cssnano: 5.1.7_postcss@8.4.13
eslint: 8.15.0
@ -1849,15 +1847,6 @@ packages:
'@types/react': 18.0.9
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:
resolution: {integrity: sha512-V9q3CvhC9Jk9bWBOysPGaWy/Z0lxYcTXLtLipkt2cnRj1JOSFNF7wqGpkScSXMgBwC+fnVRg/7shwgddBG5ICw==}
dependencies:
@ -2025,16 +2014,18 @@ packages:
eslint-visitor-keys: 3.3.0
dev: true
/@vitejs/plugin-react-refresh/1.3.6:
resolution: {integrity: sha512-iNR/UqhUOmFFxiezt0em9CgmiJBdWR+5jGxB2FihaoJfqGt76kiwaKoVOJVU5NYcDWMdN06LbyN2VIGIoYdsEA==}
/@vitejs/plugin-react/1.3.2:
resolution: {integrity: sha512-aurBNmMo0kz1O4qRoY+FM4epSA39y3ShWGuqfLRA/3z0oEJAdtoSfgA3aO98/PCCHAqMaduLxIxErWrVKIFzXA==}
engines: {node: '>=12.0.0'}
deprecated: This package has been deprecated in favor of @vitejs/plugin-react
dependencies:
'@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-source': 7.16.7_@babel+core@7.17.10
'@rollup/pluginutils': 4.2.1
react-refresh: 0.10.0
react-refresh: 0.13.0
resolve: 1.22.0
transitivePeerDependencies:
- supports-color
dev: true
@ -2384,24 +2375,8 @@ packages:
engines: {node: '>=6'}
dev: true
/chart.js/2.9.4:
resolution: {integrity: sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A==}
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
/chart.js/3.7.1:
resolution: {integrity: sha512-8knRegQLFnPQAheZV8MjxIXc5gQEfDFD897BJgv/klO/vtIyFFmgMXrNfgrXpbTr/XbTturxRgxIXx/Y+ASJBA==}
dev: false
/chokidar/3.5.3:
@ -2422,11 +2397,13 @@ packages:
/clsx/1.1.1:
resolution: {integrity: sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==}
engines: {node: '>=6'}
dev: false
/color-convert/1.9.3:
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
dependencies:
color-name: 1.1.3
dev: true
/color-convert/2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
@ -2437,9 +2414,11 @@ packages:
/color-name/1.1.3:
resolution: {integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=}
dev: true
/color-name/1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
dev: true
/colord/2.9.2:
resolution: {integrity: sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ==}
@ -4131,10 +4110,6 @@ packages:
engines: {node: '>=6'}
dev: false
/moment/2.29.3:
resolution: {integrity: sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==}
dev: false
/ms/2.0.0:
resolution: {integrity: sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=}
dev: true
@ -4837,8 +4812,8 @@ packages:
react-dom: 18.1.0_react@18.1.0
dev: false
/react-refresh/0.10.0:
resolution: {integrity: sha512-PgidR3wST3dDYKr6b4pJoqQFpPGNKDSCDx4cZoshjXipw3LzO7mG1My2pwEzz2JVkF+inx3xRpDeQLFQGH/hsQ==}
/react-refresh/0.13.0:
resolution: {integrity: sha512-XP8A9BT0CpRBD+NYLLeIhld/RqG9+gktUjW1FkE+Vm7OCinbG1SshcK5tb9ls4kzvjZr9mOQc7HYgBngEyPAXg==}
engines: {node: '>=0.10.0'}
dev: true
@ -4890,6 +4865,7 @@ packages:
clsx: 1.1.1
prop-types: 15.8.1
react: 18.1.0
dev: false
/react-tiny-fab/4.0.4_react@18.1.0:
resolution: {integrity: sha512-PxT6gEnIQR2vFfeIaa1Oq4PRX+cIEDbEfbS6PyevWCQngrKfqjMKPEcZOaaURaUclB9u3RilgjkaBUQFVlbWcg==}

View file

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

View file

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

View file

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

View file

@ -88,7 +88,7 @@ function formatConnectionDataItem(
download,
start: now - new Date(start).valueOf(),
chains: chains.reverse().join(' / '),
rule: (rulePayload == null | rulePayload === '') ? rule : (`${rule}(${rulePayload})`),
rule: !rulePayload ? rule : `${rule}(${rulePayload})`,
...metadata,
host: `${host2}:${destinationPort}`,
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-700.css';
import React, { lazy, Suspense } from 'react';
import * as React from 'react';
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 { RecoilRoot } from 'recoil';
import { About } from 'src/components/about/About';
@ -24,6 +24,8 @@ import SideBar from './SideBar';
import StateProvider from './StateProvider';
import StyleGuide from './StyleGuide';
const { lazy, Suspense } = React;
const Connections = lazy(() => import('./Connections'));
const Config = lazy(() => import('./Config'));
const Logs = lazy(() => import('./Logs'));
@ -38,10 +40,8 @@ const routes = [
{ path: '/proxies', element: <Proxies /> },
{ path: '/rules', element: <Rules /> },
{ path: '/about', element: <About /> },
process.env.NODE_ENV === 'development'
? { path: '/style', element: <StyleGuide /> }
: false,
].filter(Boolean) as PartialRouteObject[];
process.env.NODE_ENV === 'development' ? { path: '/style', element: <StyleGuide /> } : false,
].filter(Boolean) as RouteObject[];
function RouteInnerApp() {
return useRoutes(routes);

View file

@ -20,7 +20,13 @@ const { memo } = React;
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;
if (index < providerQty) {
@ -88,10 +94,8 @@ function Rules({ apiConfig }: RulesProps) {
<ContentHeader title={t('Rules')} />
<TextFilter placeholder="Filter" textAtom={ruleFilterText} />
</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 }}>
<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}
width="100%"
itemCount={rules.length + provider.names.length}

View file

@ -6,16 +6,8 @@ import React from 'react';
// this is just workaround
immer.setAutoFreeze(false);
const {
createContext,
memo,
useMemo,
useRef,
useEffect,
useCallback,
useContext,
useState,
} = React;
const { createContext, memo, useMemo, useRef, useEffect, useCallback, useContext, useState } =
React;
export { immer };
@ -46,7 +38,7 @@ export default function Provider({ initialState, actions = {}, children }) {
}
}, [getState]);
const dispatch = useCallback(
(actionId, fn) => {
(actionId: string | ((a: any, b: any) => any), fn: (s: any) => void) => {
if (typeof actionId === 'function') return actionId(dispatch, getState);
const stateNext = produce(getState(), fn);
@ -61,26 +53,21 @@ export default function Provider({ initialState, actions = {}, children }) {
},
[getState]
);
const boundActions = useMemo(() => bindActions(actions, dispatch), [
actions,
dispatch,
]);
const boundActions = useMemo(() => bindActions(actions, dispatch), [actions, dispatch]);
return (
<StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}>
<ActionsContext.Provider value={boundActions}>
{children}
</ActionsContext.Provider>
<ActionsContext.Provider value={boundActions}>{children}</ActionsContext.Provider>
</DispatchContext.Provider>
</StateContext.Provider>
);
}
export function connect(mapStateToProps) {
return (Component) => {
export function connect(mapStateToProps: any) {
return (Component: any) => {
const MemoComponent = memo(Component);
function Connected(props) {
function Connected(props: any) {
const state = useContext(StateContext);
const dispatch = useContext(DispatchContext);
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
function bindAction(action, dispatch) {
return function (...args) {
// @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
function bindAction(action: any, dispatch: any) {
return function (...args: any[]) {
return dispatch(action.apply(this, args));
};
}
function bindActions(actions, dispatch) {
function bindActions(actions: any, dispatch: any) {
const boundActions = {};
for (const key in actions) {
const action = actions[key];

View file

@ -21,9 +21,7 @@ const optionsRule = [
{ label: 'Direct', value: 'Direct' },
];
const Pane = ({ children, style }) => (
<div style={{ ...paneStyle, ...style }}>{children}</div>
);
const Pane = ({ children, style }) => <div style={{ ...paneStyle, ...style }}>{children}</div>;
function useToggle(initialState = false) {
const [onoff, setonoff] = React.useState(initialState);
@ -52,12 +50,7 @@ class StyleGuide extends PureComponent {
</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 */}
<Pane>
<ToggleSwitch
name="test"
options={optionsRule}
value="Rule"
onChange={noop}
/>
<ToggleSwitch name="test" options={optionsRule} value="Rule" onChange={noop} />
</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 */}
<Pane>

View file

@ -1,11 +1,14 @@
import React from 'react';
import S from 'react-switch';
import * as React from 'react';
import ReactSwitch from 'react-switch';
import { State } from '$src/store/types';
import { getTheme } from '../store/app';
import { connect } from './StateProvider';
// 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 }) {
const offColor = theme === 'dark' ? '#393939' : '#e9e9e9';
@ -29,6 +32,4 @@ function SwitchThemed({ checked = false, onChange, theme, name }) {
);
}
export default connect((s) => ({
theme: getTheme(s),
}))(SwitchThemed);
export default connect((s: State) => ({ theme: getTheme(s) }))(SwitchThemed);

View file

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

View file

@ -1,6 +1,8 @@
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { State } from '$src/store/types';
import { fetchData } from '../api/traffic';
import useLineChart from '../hooks/useLineChart';
import {
@ -19,7 +21,7 @@ const chartWrapperStyle = {
maxWidth: 1000,
};
const mapState = (s) => ({
const mapState = (s: State) => ({
apiConfig: getClashAPIConfig(s),
selectedChartStyleIndex: getSelectedChartStyleIndex(s),
});
@ -27,7 +29,7 @@ const mapState = (s) => ({
export default connect(mapState)(TrafficChart);
function TrafficChart({ apiConfig, selectedChartStyleIndex }) {
const Chart = chartJSResource.read();
const ChartMod = chartJSResource.read();
const traffic = fetchData(apiConfig);
const { t } = useTranslation();
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 (
// @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 {
chartJSResource,
chartStyles,
commonDataSetProps,
} from '../misc/chart';
import { chartJSResource, chartStyles, commonDataSetProps } from '../misc/chart';
const extraChartOptions = {
legend: {
display: false,
const { useMemo } = React;
const extraChartOptions: import('chart.js').ChartOptions<'line'> = {
plugins: {
legend: { display: false },
},
scales: {
xAxes: [{ display: false }],
yAxes: [{ display: false }],
x: { display: false, type: 'category' },
y: { display: false, type: 'linear' },
},
};
@ -22,7 +20,7 @@ const data2 = [184e3, 183e3, 196e3, 182e3, 190e3, 186e3, 182e3, 189e3];
const labels = data1;
export default function TrafficChart({ id }) {
const Chart = chartJSResource.read();
const ChartMod = chartJSResource.read();
const data = useMemo(
() => ({
@ -44,7 +42,7 @@ export default function TrafficChart({ id }) {
);
const eleId = 'chart-' + id;
useLineChart(Chart, eleId, data, null, extraChartOptions);
useLineChart(ChartMod.Chart, eleId, data, null, extraChartOptions);
return (
<div style={{ width: 100, padding: 5 }}>

View file

@ -1,28 +1,24 @@
import type { ChartConfiguration } from 'chart.js';
import React from 'react';
import { commonChartOptions } from 'src/misc/chart';
const { useEffect } = React;
const options = commonChartOptions;
export default function useLineChart(
Chart,
elementId,
data,
subscription,
chart: typeof import('chart.js').Chart,
elementId: string,
data: ChartConfiguration['data'],
subscription: any,
extraChartOptions = {}
) {
useEffect(() => {
const ctx = document.getElementById(elementId).getContext('2d');
const c = new Chart(ctx, {
type: 'line',
data,
options: { ...options, ...extraChartOptions },
});
const unsubscribe =
subscription && subscription.subscribe(() => c.update());
const ctx = (document.getElementById(elementId) as HTMLCanvasElement).getContext('2d');
const options = { ...commonChartOptions, ...extraChartOptions };
const c = new chart(ctx, { type: 'line', data, options });
const unsubscribe = subscription && subscription.subscribe(() => c.update());
return () => {
unsubscribe && unsubscribe();
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 prettyBytes from './pretty-bytes';
export const chartJSResource = createResource(() => {
return import(
/* webpackChunkName: "chartjs" */
/* webpackPrefetch: true */
/* webpackPreload: true */
'chart.js/dist/Chart.min.js'
).then((c) => c.default);
return import('$src/misc/chart-lib');
});
export const commonDataSetProps = {
borderWidth: 1,
lineTension: 0,
pointRadius: 0,
};
export const commonDataSetProps = { borderWidth: 1, pointRadius: 0, tension: 0.2, fill: true };
export const commonChartOptions = {
export const commonChartOptions: import('chart.js').ChartOptions<'line'> = {
responsive: true,
maintainAspectRatio: true,
title: {
display: false,
},
legend: {
display: true,
position: 'top',
labels: {
fontColor: '#ccc',
boxWidth: 20,
},
},
tooltips: {
enabled: false,
mode: 'index',
intersect: false,
animationDuration: 100,
},
hover: {
mode: 'nearest',
intersect: true,
plugins: {
legend: { labels: { boxWidth: 20 } }
},
scales: {
xAxes: [
{
display: false,
gridLines: {
display: false,
},
},
],
yAxes: [
{
x: { display: false, type: 'category' },
y: {
type: 'linear',
display: true,
gridLines: {
grid: {
display: true,
color: '#555',
drawTicks: false,
borderDash: [3, 6],
drawBorder: false,
},
ticks: {
callback(value) {
callback(value: number) {
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);
// @ts-expect-error ts-migrate(7030) FIXME: Not all code paths return a value.
function findClashAPIConfigIndex(getState: GetStateFn, { baseURL, secret }) {
const arr = getClashAPIConfigs(getState());
for (let i = 0; i < arr.length; i++) {

View file

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

View file

@ -2,6 +2,10 @@
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"$src": ["src"],
"$src/*": ["src/*"]
},
"target": "ESNext",
"lib": ["DOM", "DOM.Iterable", "ESNext", "WebWorker"],
"skipLibCheck": true,
@ -14,20 +18,7 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"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"]
"jsx": "react"
},
"include": ["./src"]
}

View file

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