diff --git a/.eslintrc.yml b/.eslintrc.yml index f2e264e..2a9ebf3 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -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 diff --git a/package.json b/package.json index a610896..96d6141 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 95c81fd..74f960d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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==} diff --git a/src/api/connections.ts b/src/api/connections.ts index d4ed58e..9c94d31 100644 --- a/src/api/connections.ts +++ b/src/api/connections.ts @@ -29,6 +29,7 @@ export type ConnectionItem = { chains: string[]; // e.g. 'Match', 'DomainKeyword' rule: string; + rulePayload?: string; }; type ConnectionsData = { downloadTotal: number; diff --git a/src/api/traffic.ts b/src/api/traffic.ts index e50ec5e..cd18aac 100644 --- a/src/api/traffic.ts +++ b/src/api/traffic.ts @@ -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); diff --git a/src/app.tsx b/src/app.tsx index 94ee328..64e19ee 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -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( - , - rootEl + ); swRegistration.register(); diff --git a/src/components/Connections.tsx b/src/components/Connections.tsx index 2c82fa6..435345b 100644 --- a/src/components/Connections.tsx +++ b/src/components/Connections.tsx @@ -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})`, diff --git a/src/components/Root.tsx b/src/components/Root.tsx index f94d2a2..d4a8aa0 100644 --- a/src/components/Root.tsx +++ b/src/components/Root.tsx @@ -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: }, { path: '/rules', element: }, { path: '/about', element: }, - process.env.NODE_ENV === 'development' - ? { path: '/style', element: } - : false, -].filter(Boolean) as PartialRouteObject[]; + process.env.NODE_ENV === 'development' ? { path: '/style', element: } : false, +].filter(Boolean) as RouteObject[]; function RouteInnerApp() { return useRoutes(routes); diff --git a/src/components/Rules.tsx b/src/components/Rules.tsx index 9019fab..47644e7 100644 --- a/src/components/Rules.tsx +++ b/src/components/Rules.tsx @@ -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) { - {/* @ts-expect-error ts-migrate(2322) FIXME: Type 'number | MutableRefObject' is not assig... Remove this comment to see the full error message */}
{ + (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 ( - - {children} - + {children} ); } -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]; diff --git a/src/components/StyleGuide.tsx b/src/components/StyleGuide.tsx index ec0c29b..ee38697 100644 --- a/src/components/StyleGuide.tsx +++ b/src/components/StyleGuide.tsx @@ -21,9 +21,7 @@ const optionsRule = [ { label: 'Direct', value: 'Direct' }, ]; -const Pane = ({ children, style }) => ( -
{children}
-); +const Pane = ({ children, style }) =>
{children}
; function useToggle(initialState = false) { const [onoff, setonoff] = React.useState(initialState); @@ -52,12 +50,7 @@ class StyleGuide extends PureComponent { {/* @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 */} diff --git a/src/components/SwitchThemed.tsx b/src/components/SwitchThemed.tsx index 7121acb..7289bd2 100644 --- a/src/components/SwitchThemed.tsx +++ b/src/components/SwitchThemed.tsx @@ -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); diff --git a/src/components/ToggleSwitch.tsx b/src/components/ToggleSwitch.tsx index 0c84059..9eb1019 100644 --- a/src/components/ToggleSwitch.tsx +++ b/src/components/ToggleSwitch.tsx @@ -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; diff --git a/src/components/TrafficChart.tsx b/src/components/TrafficChart.tsx index 056cac6..367166a 100644 --- a/src/components/TrafficChart.tsx +++ b/src/components/TrafficChart.tsx @@ -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 diff --git a/src/components/TrafficChartSample.tsx b/src/components/TrafficChartSample.tsx index fb9b2ee..3e0bba3 100644 --- a/src/components/TrafficChartSample.tsx +++ b/src/components/TrafficChartSample.tsx @@ -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 (
diff --git a/src/hooks/useLineChart.ts b/src/hooks/useLineChart.ts index 2757ee1..a3205aa 100644 --- a/src/hooks/useLineChart.ts +++ b/src/hooks/useLineChart.ts @@ -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]); } diff --git a/src/misc/chart-lib.ts b/src/misc/chart-lib.ts new file mode 100644 index 0000000..9a2bf35 --- /dev/null +++ b/src/misc/chart-lib.ts @@ -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 }; diff --git a/src/misc/chart.ts b/src/misc/chart.ts index 9e2c459..a6ee82e 100644 --- a/src/misc/chart.ts +++ b/src/misc/chart.ts @@ -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, + grid: { display: true, - gridLines: { - display: true, - color: '#555', - borderDash: [3, 6], - drawBorder: false, - }, - ticks: { - callback(value) { - return prettyBytes(value) + '/s '; - }, + color: '#555', + drawTicks: false, + borderDash: [3, 6], + drawBorder: false, + }, + ticks: { + callback(value: number) { + return prettyBytes(value) + '/s '; }, }, - ], + }, }, }; diff --git a/src/misc/constants.ts b/src/misc/constants.ts deleted file mode 100644 index 5c66350..0000000 --- a/src/misc/constants.ts +++ /dev/null @@ -1 +0,0 @@ -// const ProxySortingOptions = diff --git a/src/misc/sentry.ts b/src/misc/sentry.ts deleted file mode 100644 index efedcb3..0000000 --- a/src/misc/sentry.ts +++ /dev/null @@ -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; -} diff --git a/src/store/app.ts b/src/store/app.ts index 6680e6e..7262b32 100644 --- a/src/store/app.ts +++ b/src/store/app.ts @@ -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++) { diff --git a/src/store/types.ts b/src/store/types.ts index b9141ac..d12adaa 100644 --- a/src/store/types.ts +++ b/src/store/types.ts @@ -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 - ): ReturnType; - (action: (dispatch: DispatchFn, getState: GetStateFn) => void): ReturnType< + (action: (dispatch: DispatchFn, getState: GetStateFn) => Promise): ReturnType< typeof action >; + (action: (dispatch: DispatchFn, getState: GetStateFn) => void): ReturnType; } diff --git a/tsconfig.json b/tsconfig.json index c5a74e9..9ecdbea 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -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"] } diff --git a/vite.config.ts b/vite.config.ts index c3b00d6..e0f16f8 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -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'), - }, - }, }));