From 8488a92026a15aa0bab0e9afb6ac4f01efac0af4 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sun, 28 Sep 2025 19:51:10 +0800
Subject: [PATCH 01/10] chore(deps): update npm dependencies (#4843)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
package.json | 18 ++--
pnpm-lock.yaml | 278 ++++++++++++++++++++++++-------------------------
2 files changed, 148 insertions(+), 148 deletions(-)
diff --git a/package.json b/package.json
index ce79d0b3..d77d2fac 100644
--- a/package.json
+++ b/package.json
@@ -39,7 +39,7 @@
"@mui/icons-material": "^7.3.2",
"@mui/lab": "7.0.0-beta.17",
"@mui/material": "^7.3.2",
- "@mui/x-data-grid": "^8.11.3",
+ "@mui/x-data-grid": "^8.12.1",
"@tauri-apps/api": "2.8.0",
"@tauri-apps/plugin-clipboard-manager": "^2.3.0",
"@tauri-apps/plugin-dialog": "^2.4.0",
@@ -64,11 +64,11 @@
"react-dom": "19.1.1",
"react-error-boundary": "6.0.0",
"react-hook-form": "^7.63.0",
- "react-i18next": "15.7.3",
+ "react-i18next": "15.7.4",
"react-markdown": "10.1.0",
"react-monaco-editor": "0.59.0",
- "react-router-dom": "7.9.1",
- "react-virtuoso": "^4.14.0",
+ "react-router-dom": "7.9.3",
+ "react-virtuoso": "^4.14.1",
"swr": "^2.3.6",
"types-pac": "^1.0.3",
"zustand": "^5.0.8"
@@ -80,10 +80,10 @@
"@tauri-apps/cli": "2.8.4",
"@types/js-yaml": "^4.0.9",
"@types/lodash-es": "^4.17.12",
- "@types/react": "19.1.13",
+ "@types/react": "19.1.14",
"@types/react-dom": "19.1.9",
"@vitejs/plugin-legacy": "^7.2.1",
- "@vitejs/plugin-react": "5.0.3",
+ "@vitejs/plugin-react": "5.0.4",
"adm-zip": "^0.5.16",
"cli-color": "^2.0.4",
"commander": "^14.0.1",
@@ -94,7 +94,7 @@
"eslint-plugin-import-x": "^4.16.1",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-react-hooks": "^5.2.0",
- "eslint-plugin-react-refresh": "^0.4.21",
+ "eslint-plugin-react-refresh": "^0.4.22",
"eslint-plugin-unused-imports": "^4.2.0",
"glob": "^11.0.3",
"globals": "^16.4.0",
@@ -103,8 +103,8 @@
"meta-json-schema": "^1.19.13",
"node-fetch": "^3.3.2",
"prettier": "^3.6.2",
- "sass": "^1.93.1",
- "tar": "^7.4.4",
+ "sass": "^1.93.2",
+ "tar": "^7.5.1",
"terser": "^5.44.0",
"typescript": "^5.9.2",
"typescript-eslint": "^8.44.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2f6ffad9..0fc09395 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -19,25 +19,25 @@ importers:
version: 3.2.2(react@19.1.1)
'@emotion/react':
specifier: ^11.14.0
- version: 11.14.0(@types/react@19.1.13)(react@19.1.1)
+ version: 11.14.0(@types/react@19.1.14)(react@19.1.1)
'@emotion/styled':
specifier: ^11.14.1
- version: 11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1)
+ version: 11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
'@juggle/resize-observer':
specifier: ^3.4.0
version: 3.4.0
'@mui/icons-material':
specifier: ^7.3.2
- version: 7.3.2(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.13)(react@19.1.1)
+ version: 7.3.2(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
'@mui/lab':
specifier: 7.0.0-beta.17
- version: 7.0.0-beta.17(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ version: 7.0.0-beta.17(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@mui/material':
specifier: ^7.3.2
- version: 7.3.2(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ version: 7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@mui/x-data-grid':
- specifier: ^8.11.3
- version: 8.11.3(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.2(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ specifier: ^8.12.1
+ version: 8.12.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@tauri-apps/api':
specifier: 2.8.0
version: 2.8.0
@@ -111,20 +111,20 @@ importers:
specifier: ^7.63.0
version: 7.63.0(react@19.1.1)
react-i18next:
- specifier: 15.7.3
- version: 15.7.3(i18next@25.5.2(typescript@5.9.2))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.2)
+ specifier: 15.7.4
+ version: 15.7.4(i18next@25.5.2(typescript@5.9.2))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.2)
react-markdown:
specifier: 10.1.0
- version: 10.1.0(@types/react@19.1.13)(react@19.1.1)
+ version: 10.1.0(@types/react@19.1.14)(react@19.1.1)
react-monaco-editor:
specifier: 0.59.0
version: 0.59.0(monaco-editor@0.53.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
react-router-dom:
- specifier: 7.9.1
- version: 7.9.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ specifier: 7.9.3
+ version: 7.9.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
react-virtuoso:
- specifier: ^4.14.0
- version: 4.14.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ specifier: ^4.14.1
+ version: 4.14.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
swr:
specifier: ^2.3.6
version: 2.3.6(react@19.1.1)
@@ -133,7 +133,7 @@ importers:
version: 1.0.3
zustand:
specifier: ^5.0.8
- version: 5.0.8(@types/react@19.1.13)(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1))
+ version: 5.0.8(@types/react@19.1.14)(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1))
devDependencies:
'@actions/github':
specifier: ^6.0.1
@@ -154,17 +154,17 @@ importers:
specifier: ^4.17.12
version: 4.17.12
'@types/react':
- specifier: 19.1.13
- version: 19.1.13
+ specifier: 19.1.14
+ version: 19.1.14
'@types/react-dom':
specifier: 19.1.9
- version: 19.1.9(@types/react@19.1.13)
+ version: 19.1.9(@types/react@19.1.14)
'@vitejs/plugin-legacy':
specifier: ^7.2.1
- version: 7.2.1(terser@5.44.0)(vite@7.1.7(jiti@2.6.0)(sass@1.93.1)(terser@5.44.0)(yaml@2.7.1))
+ version: 7.2.1(terser@5.44.0)(vite@7.1.7(jiti@2.6.0)(sass@1.93.2)(terser@5.44.0)(yaml@2.7.1))
'@vitejs/plugin-react':
- specifier: 5.0.3
- version: 5.0.3(vite@7.1.7(jiti@2.6.0)(sass@1.93.1)(terser@5.44.0)(yaml@2.7.1))
+ specifier: 5.0.4
+ version: 5.0.4(vite@7.1.7(jiti@2.6.0)(sass@1.93.2)(terser@5.44.0)(yaml@2.7.1))
adm-zip:
specifier: ^0.5.16
version: 0.5.16
@@ -196,8 +196,8 @@ importers:
specifier: ^5.2.0
version: 5.2.0(eslint@9.36.0(jiti@2.6.0))
eslint-plugin-react-refresh:
- specifier: ^0.4.21
- version: 0.4.21(eslint@9.36.0(jiti@2.6.0))
+ specifier: ^0.4.22
+ version: 0.4.22(eslint@9.36.0(jiti@2.6.0))
eslint-plugin-unused-imports:
specifier: ^4.2.0
version: 4.2.0(@typescript-eslint/eslint-plugin@8.44.1(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.0))
@@ -223,11 +223,11 @@ importers:
specifier: ^3.6.2
version: 3.6.2
sass:
- specifier: ^1.93.1
- version: 1.93.1
+ specifier: ^1.93.2
+ version: 1.93.2
tar:
- specifier: ^7.4.4
- version: 7.4.4
+ specifier: ^7.5.1
+ version: 7.5.1
terser:
specifier: ^5.44.0
version: 5.44.0
@@ -239,13 +239,13 @@ importers:
version: 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
vite:
specifier: ^7.1.7
- version: 7.1.7(jiti@2.6.0)(sass@1.93.1)(terser@5.44.0)(yaml@2.7.1)
+ version: 7.1.7(jiti@2.6.0)(sass@1.93.2)(terser@5.44.0)(yaml@2.7.1)
vite-plugin-monaco-editor:
specifier: ^1.1.0
version: 1.1.0(monaco-editor@0.53.0)
vite-plugin-svgr:
specifier: ^4.5.0
- version: 4.5.0(rollup@4.46.2)(typescript@5.9.2)(vite@7.1.7(jiti@2.6.0)(sass@1.93.1)(terser@5.44.0)(yaml@2.7.1))
+ version: 4.5.0(rollup@4.46.2)(typescript@5.9.2)(vite@7.1.7(jiti@2.6.0)(sass@1.93.2)(terser@5.44.0)(yaml@2.7.1))
packages:
@@ -1246,8 +1246,8 @@ packages:
'@types/react':
optional: true
- '@mui/x-data-grid@8.11.3':
- resolution: {integrity: sha512-zd71bRYrm4uFh44/p/kQEtYHdslDy6uzC4NdF0qWYtf2Q0CkmC0ZZHkS4jnqf0iAawFVX2LgJtS7A6L6/ik9aQ==}
+ '@mui/x-data-grid@8.12.1':
+ resolution: {integrity: sha512-IfyV2jhPX6YQwpqxUD5jiy7fNbGIi7D2nCRIwK+lwY5m+I3lH6MFZyeDvZKSYOYT46/A127TmhCEEca/uPlh7Q==}
engines: {node: '>=14.0.0'}
peerDependencies:
'@emotion/react': ^11.9.0
@@ -1262,14 +1262,14 @@ packages:
'@emotion/styled':
optional: true
- '@mui/x-internals@8.11.3':
- resolution: {integrity: sha512-Fmp4Op+nNSqsWn2Jwv9yA8WXi3Wem9jmgdUplvMK6JZAt7iA0ZdzGltCcHrdxOcK1Nu/2F7H8KOZuBzpy1lspw==}
+ '@mui/x-internals@8.12.0':
+ resolution: {integrity: sha512-KCZgFHwuPg0v8I2gpjeC6k3eDRXPPX8RIGSNDXe8zSZ8dAw+p6Q2pzT9kKvctqCXSFK8ct/5YQwqx8Quhs8Ndg==}
engines: {node: '>=14.0.0'}
peerDependencies:
react: ^17.0.0 || ^18.0.0 || ^19.0.0
- '@mui/x-virtualizer@0.1.7':
- resolution: {integrity: sha512-PtAxlDTpmVkOWfaBEwlGGbRCA137C369OjmdxdPYrx5twhvukdhT/2b/KfSVbz6MzTctOGmkw5ye+IkjaFco/g==}
+ '@mui/x-virtualizer@0.2.0':
+ resolution: {integrity: sha512-Gy0MJXXKN9F8zWG89UlIiWEMYqwVyMt0YDWHXBW3ysekmunVFvPwYlFvmwSc3EM2gU0g9IGJWSgfhWS9uLNdcQ==}
engines: {node: '>=14.0.0'}
peerDependencies:
react: ^17.0.0 || ^18.0.0 || ^19.0.0
@@ -1427,8 +1427,8 @@ packages:
'@popperjs/core@2.11.8':
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
- '@rolldown/pluginutils@1.0.0-beta.35':
- resolution: {integrity: sha512-slYrCpoxJUqzFDDNlvrOYRazQUNRvWPjXA17dAOISY3rDMxX6k8K4cj2H+hEYMHF81HO3uNd5rHVigAWRM5dSg==}
+ '@rolldown/pluginutils@1.0.0-beta.38':
+ resolution: {integrity: sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw==}
'@rollup/pluginutils@5.2.0':
resolution: {integrity: sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==}
@@ -1772,8 +1772,8 @@ packages:
peerDependencies:
'@types/react': '*'
- '@types/react@19.1.13':
- resolution: {integrity: sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==}
+ '@types/react@19.1.14':
+ resolution: {integrity: sha512-ukd93VGzaNPMAUPy0gRDSC57UuQbnH9Kussp7HBjM06YFi9uZTFhOvMSO2OKqXm1rSgzOE+pVx1k1PYHGwlc8Q==}
'@types/trusted-types@1.0.6':
resolution: {integrity: sha512-230RC8sFeHoT6sSUlRO6a8cAnclO06eeiq1QDfiv2FGCLWFvvERWgwIQD4FWqD9A69BN7Lzee4OXwoMVnnsWDw==}
@@ -1948,8 +1948,8 @@ packages:
terser: ^5.16.0
vite: ^7.0.0
- '@vitejs/plugin-react@5.0.3':
- resolution: {integrity: sha512-PFVHhosKkofGH0Yzrw1BipSedTH68BFF8ZWy1kfUpCtJcouXXY0+racG8sExw7hw0HoX36813ga5o3LTWZ4FUg==}
+ '@vitejs/plugin-react@5.0.4':
+ resolution: {integrity: sha512-La0KD0vGkVkSk6K+piWDKRUyg8Rl5iAIKRMH0vMJI0Eg47bq1eOxmoObAaQG37WMW9MSyk7Cs8EIWwJC1PtzKA==}
engines: {node: ^20.19.0 || >=22.12.0}
peerDependencies:
vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
@@ -2524,8 +2524,8 @@ packages:
typescript:
optional: true
- eslint-plugin-react-refresh@0.4.21:
- resolution: {integrity: sha512-MWDWTtNC4voTcWDxXbdmBNe8b/TxfxRFUL6hXgKWJjN9c1AagYEmpiFWBWzDw+5H3SulWUe1pJKTnoSdmk88UA==}
+ eslint-plugin-react-refresh@0.4.22:
+ resolution: {integrity: sha512-atkAG6QaJMGoTLc4MDAP+rqZcfwQuTIh2IqHWFLy2TEjxr0MOK+5BSG4RzL2564AAPpZkDRsZXAUz68kjnU6Ug==}
peerDependencies:
eslint: '>=8.40'
@@ -3459,10 +3459,10 @@ packages:
peerDependencies:
react: ^16.8.0 || ^17 || ^18 || ^19
- react-i18next@15.7.3:
- resolution: {integrity: sha512-AANws4tOE+QSq/IeMF/ncoHlMNZaVLxpa5uUGW1wjike68elVYr0018L9xYoqBr1OFO7G7boDPrbn0HpMCJxTw==}
+ react-i18next@15.7.4:
+ resolution: {integrity: sha512-nyU8iKNrI5uDJch0z9+Y5XEr34b0wkyYj3Rp+tfbahxtlswxSCjcUL9H0nqXo9IR3/t5Y5PKIA3fx3MfUyR9Xw==}
peerDependencies:
- i18next: '>= 25.4.1'
+ i18next: '>= 23.4.0'
react: '>= 16.8.0'
react-dom: '*'
react-native: '*'
@@ -3498,15 +3498,15 @@ packages:
resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
engines: {node: '>=0.10.0'}
- react-router-dom@7.9.1:
- resolution: {integrity: sha512-U9WBQssBE9B1vmRjo9qTM7YRzfZ3lUxESIZnsf4VjR/lXYz9MHjvOxHzr/aUm4efpktbVOrF09rL/y4VHa8RMw==}
+ react-router-dom@7.9.3:
+ resolution: {integrity: sha512-1QSbA0TGGFKTAc/aWjpfW/zoEukYfU4dc1dLkT/vvf54JoGMkW+fNA+3oyo2gWVW1GM7BxjJVHz5GnPJv40rvg==}
engines: {node: '>=20.0.0'}
peerDependencies:
react: '>=18'
react-dom: '>=18'
- react-router@7.9.1:
- resolution: {integrity: sha512-pfAByjcTpX55mqSDGwGnY9vDCpxqBLASg0BMNAuMmpSGESo/TaOUG6BllhAtAkCGx8Rnohik/XtaqiYUJtgW2g==}
+ react-router@7.9.3:
+ resolution: {integrity: sha512-4o2iWCFIwhI/eYAIL43+cjORXYn/aRQPgtFRRZb3VzoyQ5Uej0Bmqj7437L97N9NJW4wnicSwLOLS+yCXfAPgg==}
engines: {node: '>=20.0.0'}
peerDependencies:
react: '>=18'
@@ -3521,8 +3521,8 @@ packages:
react: '>=16.6.0'
react-dom: '>=16.6.0'
- react-virtuoso@4.14.0:
- resolution: {integrity: sha512-fR+eiCvirSNIRvvCD7ueJPRsacGQvUbjkwgWzBZXVq+yWypoH7mRUvWJzGHIdoRaCZCT+6mMMMwIG2S1BW3uwA==}
+ react-virtuoso@4.14.1:
+ resolution: {integrity: sha512-NRUF1ak8lY+Tvc6WN9cce59gU+lilzVtOozP+pm9J7iHshLGGjsiAB4rB2qlBPHjFbcXOQpT+7womNHGDUql8w==}
peerDependencies:
react: '>=16 || >=17 || >= 18 || >= 19'
react-dom: '>=16 || >=17 || >= 18 || >=19'
@@ -3612,8 +3612,8 @@ packages:
resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
engines: {node: '>= 0.4'}
- sass@1.93.1:
- resolution: {integrity: sha512-wLAeLB7IksO2u+cCfhHqcy7/2ZUMPp/X2oV6+LjmweTqgjhOKrkaE/Q1wljxtco5EcOcupZ4c981X0gpk5Tiag==}
+ sass@1.93.2:
+ resolution: {integrity: sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==}
engines: {node: '>=14.0.0'}
hasBin: true
@@ -3782,8 +3782,8 @@ packages:
systemjs@6.15.1:
resolution: {integrity: sha512-Nk8c4lXvMB98MtbmjX7JwJRgJOL8fluecYCfCeYBznwmpOs8Bf15hLM6z4z71EDAhQVrQrI+wt1aLWSXZq+hXA==}
- tar@7.4.4:
- resolution: {integrity: sha512-O1z7ajPkjTgEgmTGz0v9X4eqeEXTDREPTO77pVC1Nbs86feBU1Zhdg+edzavPmYW1olxkwsqA2v4uOw6E8LeDg==}
+ tar@7.5.1:
+ resolution: {integrity: sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==}
engines: {node: '>=18'}
terser@5.44.0:
@@ -4847,7 +4847,7 @@ snapshots:
'@emotion/memoize@0.9.0': {}
- '@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1)':
+ '@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.4
'@emotion/babel-plugin': 11.13.5
@@ -4859,7 +4859,7 @@ snapshots:
hoist-non-react-statics: 3.3.2
react: 19.1.1
optionalDependencies:
- '@types/react': 19.1.13
+ '@types/react': 19.1.14
transitivePeerDependencies:
- supports-color
@@ -4873,18 +4873,18 @@ snapshots:
'@emotion/sheet@1.4.0': {}
- '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1)':
+ '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.4
'@emotion/babel-plugin': 11.13.5
'@emotion/is-prop-valid': 1.3.1
- '@emotion/react': 11.14.0(@types/react@19.1.13)(react@19.1.1)
+ '@emotion/react': 11.14.0(@types/react@19.1.14)(react@19.1.1)
'@emotion/serialize': 1.3.3
'@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.1.1)
'@emotion/utils': 1.4.2
react: 19.1.1
optionalDependencies:
- '@types/react': 19.1.13
+ '@types/react': 19.1.14
transitivePeerDependencies:
- supports-color
@@ -5173,39 +5173,39 @@ snapshots:
'@mui/core-downloads-tracker@7.3.2': {}
- '@mui/icons-material@7.3.2(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.13)(react@19.1.1)':
+ '@mui/icons-material@7.3.2(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.4
- '@mui/material': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ '@mui/material': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
react: 19.1.1
optionalDependencies:
- '@types/react': 19.1.13
+ '@types/react': 19.1.14
- '@mui/lab@7.0.0-beta.17(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
+ '@mui/lab@7.0.0-beta.17(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.4
- '@mui/material': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
- '@mui/system': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1)
- '@mui/types': 7.4.6(@types/react@19.1.13)
- '@mui/utils': 7.3.2(@types/react@19.1.13)(react@19.1.1)
+ '@mui/material': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ '@mui/system': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
+ '@mui/types': 7.4.6(@types/react@19.1.14)
+ '@mui/utils': 7.3.2(@types/react@19.1.14)(react@19.1.1)
clsx: 2.1.1
prop-types: 15.8.1
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
optionalDependencies:
- '@emotion/react': 11.14.0(@types/react@19.1.13)(react@19.1.1)
- '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1)
- '@types/react': 19.1.13
+ '@emotion/react': 11.14.0(@types/react@19.1.14)(react@19.1.1)
+ '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
+ '@types/react': 19.1.14
- '@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
+ '@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.4
'@mui/core-downloads-tracker': 7.3.2
- '@mui/system': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1)
- '@mui/types': 7.4.6(@types/react@19.1.13)
- '@mui/utils': 7.3.2(@types/react@19.1.13)(react@19.1.1)
+ '@mui/system': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
+ '@mui/types': 7.4.6(@types/react@19.1.14)
+ '@mui/utils': 7.3.2(@types/react@19.1.14)(react@19.1.1)
'@popperjs/core': 2.11.8
- '@types/react-transition-group': 4.4.12(@types/react@19.1.13)
+ '@types/react-transition-group': 4.4.12(@types/react@19.1.14)
clsx: 2.1.1
csstype: 3.1.3
prop-types: 15.8.1
@@ -5214,20 +5214,20 @@ snapshots:
react-is: 19.1.1
react-transition-group: 4.4.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
optionalDependencies:
- '@emotion/react': 11.14.0(@types/react@19.1.13)(react@19.1.1)
- '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1)
- '@types/react': 19.1.13
+ '@emotion/react': 11.14.0(@types/react@19.1.14)(react@19.1.1)
+ '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
+ '@types/react': 19.1.14
- '@mui/private-theming@7.3.2(@types/react@19.1.13)(react@19.1.1)':
+ '@mui/private-theming@7.3.2(@types/react@19.1.14)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.4
- '@mui/utils': 7.3.2(@types/react@19.1.13)(react@19.1.1)
+ '@mui/utils': 7.3.2(@types/react@19.1.14)(react@19.1.1)
prop-types: 15.8.1
react: 19.1.1
optionalDependencies:
- '@types/react': 19.1.13
+ '@types/react': 19.1.14
- '@mui/styled-engine@7.3.2(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1))(react@19.1.1)':
+ '@mui/styled-engine@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.4
'@emotion/cache': 11.14.0
@@ -5237,77 +5237,77 @@ snapshots:
prop-types: 15.8.1
react: 19.1.1
optionalDependencies:
- '@emotion/react': 11.14.0(@types/react@19.1.13)(react@19.1.1)
- '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1)
+ '@emotion/react': 11.14.0(@types/react@19.1.14)(react@19.1.1)
+ '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
- '@mui/system@7.3.2(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1)':
+ '@mui/system@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.4
- '@mui/private-theming': 7.3.2(@types/react@19.1.13)(react@19.1.1)
- '@mui/styled-engine': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1))(react@19.1.1)
- '@mui/types': 7.4.6(@types/react@19.1.13)
- '@mui/utils': 7.3.2(@types/react@19.1.13)(react@19.1.1)
+ '@mui/private-theming': 7.3.2(@types/react@19.1.14)(react@19.1.1)
+ '@mui/styled-engine': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(react@19.1.1)
+ '@mui/types': 7.4.6(@types/react@19.1.14)
+ '@mui/utils': 7.3.2(@types/react@19.1.14)(react@19.1.1)
clsx: 2.1.1
csstype: 3.1.3
prop-types: 15.8.1
react: 19.1.1
optionalDependencies:
- '@emotion/react': 11.14.0(@types/react@19.1.13)(react@19.1.1)
- '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1)
- '@types/react': 19.1.13
+ '@emotion/react': 11.14.0(@types/react@19.1.14)(react@19.1.1)
+ '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
+ '@types/react': 19.1.14
- '@mui/types@7.4.6(@types/react@19.1.13)':
+ '@mui/types@7.4.6(@types/react@19.1.14)':
dependencies:
'@babel/runtime': 7.28.4
optionalDependencies:
- '@types/react': 19.1.13
+ '@types/react': 19.1.14
- '@mui/utils@7.3.2(@types/react@19.1.13)(react@19.1.1)':
+ '@mui/utils@7.3.2(@types/react@19.1.14)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.4
- '@mui/types': 7.4.6(@types/react@19.1.13)
+ '@mui/types': 7.4.6(@types/react@19.1.14)
'@types/prop-types': 15.7.15
clsx: 2.1.1
prop-types: 15.8.1
react: 19.1.1
react-is: 19.1.1
optionalDependencies:
- '@types/react': 19.1.13
+ '@types/react': 19.1.14
- '@mui/x-data-grid@8.11.3(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.2(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
+ '@mui/x-data-grid@8.12.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.4
- '@mui/material': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
- '@mui/system': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1)
- '@mui/utils': 7.3.2(@types/react@19.1.13)(react@19.1.1)
- '@mui/x-internals': 8.11.3(@types/react@19.1.13)(react@19.1.1)
- '@mui/x-virtualizer': 0.1.7(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ '@mui/material': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ '@mui/system': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
+ '@mui/utils': 7.3.2(@types/react@19.1.14)(react@19.1.1)
+ '@mui/x-internals': 8.12.0(@types/react@19.1.14)(react@19.1.1)
+ '@mui/x-virtualizer': 0.2.0(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
clsx: 2.1.1
prop-types: 15.8.1
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
use-sync-external-store: 1.5.0(react@19.1.1)
optionalDependencies:
- '@emotion/react': 11.14.0(@types/react@19.1.13)(react@19.1.1)
- '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1)
+ '@emotion/react': 11.14.0(@types/react@19.1.14)(react@19.1.1)
+ '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
transitivePeerDependencies:
- '@types/react'
- '@mui/x-internals@8.11.3(@types/react@19.1.13)(react@19.1.1)':
+ '@mui/x-internals@8.12.0(@types/react@19.1.14)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.4
- '@mui/utils': 7.3.2(@types/react@19.1.13)(react@19.1.1)
+ '@mui/utils': 7.3.2(@types/react@19.1.14)(react@19.1.1)
react: 19.1.1
reselect: 5.1.1
use-sync-external-store: 1.5.0(react@19.1.1)
transitivePeerDependencies:
- '@types/react'
- '@mui/x-virtualizer@0.1.7(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
+ '@mui/x-virtualizer@0.2.0(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.4
- '@mui/utils': 7.3.2(@types/react@19.1.13)(react@19.1.1)
- '@mui/x-internals': 8.11.3(@types/react@19.1.13)(react@19.1.1)
+ '@mui/utils': 7.3.2(@types/react@19.1.14)(react@19.1.1)
+ '@mui/x-internals': 8.12.0(@types/react@19.1.14)(react@19.1.1)
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
transitivePeerDependencies:
@@ -5455,7 +5455,7 @@ snapshots:
'@popperjs/core@2.11.8': {}
- '@rolldown/pluginutils@1.0.0-beta.35': {}
+ '@rolldown/pluginutils@1.0.0-beta.38': {}
'@rollup/pluginutils@5.2.0(rollup@4.46.2)':
dependencies:
@@ -5740,15 +5740,15 @@ snapshots:
'@types/prop-types@15.7.15': {}
- '@types/react-dom@19.1.9(@types/react@19.1.13)':
+ '@types/react-dom@19.1.9(@types/react@19.1.14)':
dependencies:
- '@types/react': 19.1.13
+ '@types/react': 19.1.14
- '@types/react-transition-group@4.4.12(@types/react@19.1.13)':
+ '@types/react-transition-group@4.4.12(@types/react@19.1.14)':
dependencies:
- '@types/react': 19.1.13
+ '@types/react': 19.1.14
- '@types/react@19.1.13':
+ '@types/react@19.1.14':
dependencies:
csstype: 3.1.3
@@ -5912,7 +5912,7 @@ snapshots:
'@unrs/resolver-binding-win32-x64-msvc@1.11.1':
optional: true
- '@vitejs/plugin-legacy@7.2.1(terser@5.44.0)(vite@7.1.7(jiti@2.6.0)(sass@1.93.1)(terser@5.44.0)(yaml@2.7.1))':
+ '@vitejs/plugin-legacy@7.2.1(terser@5.44.0)(vite@7.1.7(jiti@2.6.0)(sass@1.93.2)(terser@5.44.0)(yaml@2.7.1))':
dependencies:
'@babel/core': 7.28.4
'@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.28.4)
@@ -5927,19 +5927,19 @@ snapshots:
regenerator-runtime: 0.14.1
systemjs: 6.15.1
terser: 5.44.0
- vite: 7.1.7(jiti@2.6.0)(sass@1.93.1)(terser@5.44.0)(yaml@2.7.1)
+ vite: 7.1.7(jiti@2.6.0)(sass@1.93.2)(terser@5.44.0)(yaml@2.7.1)
transitivePeerDependencies:
- supports-color
- '@vitejs/plugin-react@5.0.3(vite@7.1.7(jiti@2.6.0)(sass@1.93.1)(terser@5.44.0)(yaml@2.7.1))':
+ '@vitejs/plugin-react@5.0.4(vite@7.1.7(jiti@2.6.0)(sass@1.93.2)(terser@5.44.0)(yaml@2.7.1))':
dependencies:
'@babel/core': 7.28.4
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.4)
'@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.4)
- '@rolldown/pluginutils': 1.0.0-beta.35
+ '@rolldown/pluginutils': 1.0.0-beta.38
'@types/babel__core': 7.20.5
react-refresh: 0.17.0
- vite: 7.1.7(jiti@2.6.0)(sass@1.93.1)(terser@5.44.0)(yaml@2.7.1)
+ vite: 7.1.7(jiti@2.6.0)(sass@1.93.2)(terser@5.44.0)(yaml@2.7.1)
transitivePeerDependencies:
- supports-color
@@ -6686,7 +6686,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-plugin-react-refresh@0.4.21(eslint@9.36.0(jiti@2.6.0)):
+ eslint-plugin-react-refresh@0.4.22(eslint@9.36.0(jiti@2.6.0)):
dependencies:
eslint: 9.36.0(jiti@2.6.0)
@@ -7835,7 +7835,7 @@ snapshots:
dependencies:
react: 19.1.1
- react-i18next@15.7.3(i18next@25.5.2(typescript@5.9.2))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.2):
+ react-i18next@15.7.4(i18next@25.5.2(typescript@5.9.2))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.2):
dependencies:
'@babel/runtime': 7.28.4
html-parse-stringify: 3.0.1
@@ -7849,11 +7849,11 @@ snapshots:
react-is@19.1.1: {}
- react-markdown@10.1.0(@types/react@19.1.13)(react@19.1.1):
+ react-markdown@10.1.0(@types/react@19.1.14)(react@19.1.1):
dependencies:
'@types/hast': 3.0.4
'@types/mdast': 4.0.4
- '@types/react': 19.1.13
+ '@types/react': 19.1.14
devlop: 1.1.0
hast-util-to-jsx-runtime: 2.3.6
html-url-attributes: 3.0.1
@@ -7875,13 +7875,13 @@ snapshots:
react-refresh@0.17.0: {}
- react-router-dom@7.9.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
+ react-router-dom@7.9.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
dependencies:
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
- react-router: 7.9.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ react-router: 7.9.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
- react-router@7.9.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
+ react-router@7.9.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
dependencies:
cookie: 1.0.2
react: 19.1.1
@@ -7898,7 +7898,7 @@ snapshots:
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
- react-virtuoso@4.14.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
+ react-virtuoso@4.14.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
dependencies:
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
@@ -8037,7 +8037,7 @@ snapshots:
is-regex: 1.2.1
optional: true
- sass@1.93.1:
+ sass@1.93.2:
dependencies:
chokidar: 4.0.3
immutable: 5.1.2
@@ -8236,7 +8236,7 @@ snapshots:
systemjs@6.15.1: {}
- tar@7.4.4:
+ tar@7.5.1:
dependencies:
'@isaacs/fs-minipass': 4.0.1
chownr: 3.0.0
@@ -8460,18 +8460,18 @@ snapshots:
dependencies:
monaco-editor: 0.53.0
- vite-plugin-svgr@4.5.0(rollup@4.46.2)(typescript@5.9.2)(vite@7.1.7(jiti@2.6.0)(sass@1.93.1)(terser@5.44.0)(yaml@2.7.1)):
+ vite-plugin-svgr@4.5.0(rollup@4.46.2)(typescript@5.9.2)(vite@7.1.7(jiti@2.6.0)(sass@1.93.2)(terser@5.44.0)(yaml@2.7.1)):
dependencies:
'@rollup/pluginutils': 5.2.0(rollup@4.46.2)
'@svgr/core': 8.1.0(typescript@5.9.2)
'@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.9.2))
- vite: 7.1.7(jiti@2.6.0)(sass@1.93.1)(terser@5.44.0)(yaml@2.7.1)
+ vite: 7.1.7(jiti@2.6.0)(sass@1.93.2)(terser@5.44.0)(yaml@2.7.1)
transitivePeerDependencies:
- rollup
- supports-color
- typescript
- vite@7.1.7(jiti@2.6.0)(sass@1.93.1)(terser@5.44.0)(yaml@2.7.1):
+ vite@7.1.7(jiti@2.6.0)(sass@1.93.2)(terser@5.44.0)(yaml@2.7.1):
dependencies:
esbuild: 0.25.4
fdir: 6.5.0(picomatch@4.0.3)
@@ -8482,7 +8482,7 @@ snapshots:
optionalDependencies:
fsevents: 2.3.3
jiti: 2.6.0
- sass: 1.93.1
+ sass: 1.93.2
terser: 5.44.0
yaml: 2.7.1
@@ -8580,9 +8580,9 @@ snapshots:
zod@4.1.11: {}
- zustand@5.0.8(@types/react@19.1.13)(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1)):
+ zustand@5.0.8(@types/react@19.1.14)(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1)):
optionalDependencies:
- '@types/react': 19.1.13
+ '@types/react': 19.1.14
react: 19.1.1
use-sync-external-store: 1.5.0(react@19.1.1)
From 3e23609b68a464d41c07f9b5e0d71ae8b7327148 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sun, 28 Sep 2025 19:51:25 +0800
Subject: [PATCH 02/10] chore(deps): update cargo dependencies (#4842)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
src-tauri/Cargo.toml | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index 18237f94..0dfd1929 100755
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -24,7 +24,7 @@ log = "0.4.28"
dunce = "1.0.5"
nanoid = "0.4"
chrono = "0.4.42"
-sysinfo = { version = "0.37.0", features = ["network", "system"] }
+sysinfo = { version = "0.37.1", features = ["network", "system"] }
boa_engine = "0.20.0"
serde_json = "1.0.145"
serde_yaml_ng = "0.10.0"
@@ -39,9 +39,9 @@ tokio = { version = "1.47.1", features = [
"time",
"sync",
] }
-serde = { version = "1.0.226", features = ["derive"] }
+serde = { version = "1.0.228", features = ["derive"] }
reqwest = { version = "0.12.23", features = ["json", "cookies"] }
-regex = "1.11.2"
+regex = "1.11.3"
sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs" }
tauri = { version = "2.8.5", features = [
"protocol-asset",
@@ -65,13 +65,13 @@ base64 = "0.22.1"
getrandom = "0.3.3"
futures = "0.3.31"
sys-locale = "0.3.2"
-libc = "0.2.175"
+libc = "0.2.176"
gethostname = "1.0.2"
hmac = "0.12.1"
sha2 = "0.10.9"
hex = "0.4.3"
scopeguard = "1.2.0"
-kode-bridge = "0.2.1"
+kode-bridge = "0.3.0"
dashmap = "6.1.0"
tauri-plugin-notification = "2.3.1"
tokio-stream = "0.1.17"
@@ -81,7 +81,7 @@ isahc = { version = "1.7.2", default-features = false, features = [
] }
backoff = { version = "0.4.0", features = ["tokio"] }
tauri-plugin-http = "2.5.2"
-flexi_logger = "0.31.3"
+flexi_logger = "0.31.4"
cfg-if = "1.0.3"
nu-ansi-term = { version = "0.50.1", optional = true }
console-subscriber = { version = "0.4.1", optional = true }
From 78496312ec6919c6ac84728b60a453c5b0356e48 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sun, 28 Sep 2025 19:54:58 +0800
Subject: [PATCH 03/10] chore(deps): update npm dependencies (#4857)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
package.json | 4 +-
pnpm-lock.yaml | 295 ++++++++++++++++++++++---------------------------
2 files changed, 135 insertions(+), 164 deletions(-)
diff --git a/package.json b/package.json
index d77d2fac..eaf08a82 100644
--- a/package.json
+++ b/package.json
@@ -64,7 +64,7 @@
"react-dom": "19.1.1",
"react-error-boundary": "6.0.0",
"react-hook-form": "^7.63.0",
- "react-i18next": "15.7.4",
+ "react-i18next": "16.0.0",
"react-markdown": "10.1.0",
"react-monaco-editor": "0.59.0",
"react-router-dom": "7.9.3",
@@ -75,7 +75,7 @@
},
"devDependencies": {
"@actions/github": "^6.0.1",
- "@eslint-react/eslint-plugin": "^1.53.1",
+ "@eslint-react/eslint-plugin": "^2.0.1",
"@eslint/js": "^9.36.0",
"@tauri-apps/cli": "2.8.4",
"@types/js-yaml": "^4.0.9",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0fc09395..ed391998 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -111,8 +111,8 @@ importers:
specifier: ^7.63.0
version: 7.63.0(react@19.1.1)
react-i18next:
- specifier: 15.7.4
- version: 15.7.4(i18next@25.5.2(typescript@5.9.2))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.2)
+ specifier: 16.0.0
+ version: 16.0.0(i18next@25.5.2(typescript@5.9.2))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.2)
react-markdown:
specifier: 10.1.0
version: 10.1.0(@types/react@19.1.14)(react@19.1.1)
@@ -139,8 +139,8 @@ importers:
specifier: ^6.0.1
version: 6.0.1
'@eslint-react/eslint-plugin':
- specifier: ^1.53.1
- version: 1.53.1(eslint@9.36.0(jiti@2.6.0))(ts-api-utils@2.1.0(typescript@5.9.2))(typescript@5.9.2)
+ specifier: ^2.0.1
+ version: 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
'@eslint/js':
specifier: ^9.36.0
version: 9.36.0
@@ -1010,39 +1010,36 @@ packages:
resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
- '@eslint-react/ast@1.53.1':
- resolution: {integrity: sha512-qvUC99ewtriJp9quVEOvZ6+RHcsMLfVQ0OhZ4/LupZUDhjW7GiX1dxJsFaxHdJ9rLNLhQyLSPmbAToeqUrSruQ==}
- engines: {node: '>=18.18.0'}
+ '@eslint-react/ast@2.0.1':
+ resolution: {integrity: sha512-YUY1QsaDAOOc4fOGHIT5uIQUg14yAbYLXPhcP1cufbbhdf3VU7eGtbw/VeFIkJIPRyIPJYV0cSHW+e8jZUyPGQ==}
+ engines: {node: '>=20.19.0'}
- '@eslint-react/core@1.53.1':
- resolution: {integrity: sha512-8prroos5/Uvvh8Tjl1HHCpq4HWD3hV9tYkm7uXgKA6kqj0jHlgRcQzuO6ZPP7feBcK3uOeug7xrq03BuG8QKCA==}
- engines: {node: '>=18.18.0'}
+ '@eslint-react/core@2.0.1':
+ resolution: {integrity: sha512-KzLiClAChDiw2O+sCiDsi/I1hIfJwxnJwNXp1/EzWyZq1Qgn+M1iuesZve2j2RoJv2dz18ItpkT/Tc36hGIJwA==}
+ engines: {node: '>=20.19.0'}
- '@eslint-react/eff@1.53.1':
- resolution: {integrity: sha512-uq20lPRAmsWRjIZm+mAV/2kZsU2nDqn5IJslxGWe3Vfdw23hoyhEw3S1KKlxbftwbTvsZjKvVP0iw3bZo/NUpg==}
- engines: {node: '>=18.18.0'}
+ '@eslint-react/eff@2.0.1':
+ resolution: {integrity: sha512-VnC5F/8coRS2XuI82cxREw8HeEdxnNl9Ri1flkjZIl6q2geidTb3CVmbep+1NujwEOGe+z4B+8lA/rCeyAGhoQ==}
+ engines: {node: '>=20.19.0'}
- '@eslint-react/eslint-plugin@1.53.1':
- resolution: {integrity: sha512-JZ2ciXNCC9CtBBAqYtwWH+Jy/7ZzLw+whei8atP4Fxsbh+Scs30MfEwBzuiEbNw6uF9eZFfPidchpr5RaEhqxg==}
- engines: {node: '>=18.18.0'}
+ '@eslint-react/eslint-plugin@2.0.1':
+ resolution: {integrity: sha512-RP8S7bTcT6DWyCUWHYrps4wAlOk0hCYvVL1M3nr9cdxBuBRbEx0HqhrIhZr8jl0pafKvABAsiNJNmyqLLEFPqw==}
+ engines: {node: '>=20.19.0'}
peerDependencies:
- eslint: ^8.57.0 || ^9.0.0
- typescript: ^4.9.5 || ^5.3.3
- peerDependenciesMeta:
- typescript:
- optional: true
+ eslint: ^9.36.0
+ typescript: ^5.9.2
- '@eslint-react/kit@1.53.1':
- resolution: {integrity: sha512-zOi2le9V4rMrJvQV4OeedGvMGvDT46OyFPOwXKs7m0tQu5vXVJ8qwIPaVQT1n/WIuvOg49OfmAVaHpGxK++xLQ==}
- engines: {node: '>=18.18.0'}
+ '@eslint-react/kit@2.0.1':
+ resolution: {integrity: sha512-LEtDYjYhI2A3oG0BesJlU7z3bgMV86kaGuMBIZByuYQmeCVkV0tkvPwMmOJf2kLeJeG9d58Cn691DGl7XXz54g==}
+ engines: {node: '>=20.19.0'}
- '@eslint-react/shared@1.53.1':
- resolution: {integrity: sha512-gomJQmFqQgQVI3Ra4vTMG/s6a4bx3JqeNiTBjxBJt4C9iGaBj458GkP4LJHX7TM6xUzX+fMSKOPX7eV3C/+UCw==}
- engines: {node: '>=18.18.0'}
+ '@eslint-react/shared@2.0.1':
+ resolution: {integrity: sha512-/E4mHZKCWh+hJ4cbLWqqDx5IMFloTBMEoxiecpAvC1zJQpx0xdAYOZPOPiUPLbyD+v86ho2UUICgbvvCErULyg==}
+ engines: {node: '>=20.19.0'}
- '@eslint-react/var@1.53.1':
- resolution: {integrity: sha512-yzwopvPntcHU7mmDvWzRo1fb8QhjD8eDRRohD11rTV1u7nWO4QbJi0pOyugQakvte1/W11Y0Vr8Of0Ojk/A6zg==}
- engines: {node: '>=18.18.0'}
+ '@eslint-react/var@2.0.1':
+ resolution: {integrity: sha512-Qc8dbg21Bg6SyN5EKeZYmwJPPfxXh8PbRRvleXeIzC7AbAsyjX+MsZ7W04AUkoE9/46o/+CaFPjN+gCUlQY15Q==}
+ engines: {node: '>=20.19.0'}
'@eslint/config-array@0.21.0':
resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==}
@@ -2478,35 +2475,26 @@ packages:
eslint-config-prettier:
optional: true
- eslint-plugin-react-debug@1.53.1:
- resolution: {integrity: sha512-WNOiQ6jhodJE88VjBU/IVDM+2Zr9gKHlBFDUSA3fQ0dMB5RiBVj5wMtxbxRuipK/GqNJbteqHcZoYEod7nfddg==}
- engines: {node: '>=18.18.0'}
+ eslint-plugin-react-debug@2.0.1:
+ resolution: {integrity: sha512-/yzCn4syAfCddIhao6mOuJ5d4+RzgqPSSCGvYfChiUdrsTt4EKhwgWd0hKes65bmRAjp40IX4QenSleu6yG8PA==}
+ engines: {node: '>=20.19.0'}
peerDependencies:
- eslint: ^8.57.0 || ^9.0.0
- typescript: ^4.9.5 || ^5.3.3
- peerDependenciesMeta:
- typescript:
- optional: true
+ eslint: ^9.36.0
+ typescript: ^5.9.2
- eslint-plugin-react-dom@1.53.1:
- resolution: {integrity: sha512-UYrWJ2cS4HpJ1A5XBuf1HfMpPoLdfGil+27g/ldXfGemb4IXqlxHt4ANLyC8l2CWcE3SXGJW7mTslL34MG0qTQ==}
- engines: {node: '>=18.18.0'}
+ eslint-plugin-react-dom@2.0.1:
+ resolution: {integrity: sha512-2SxLy5v5lvnj14U7WWKTApZwEC9qxLWqtU+LaGb8B9pnlmbunFpIy1FTcRUfHBDcHFD23MqbgXE88HZpOnZ1oA==}
+ engines: {node: '>=20.19.0'}
peerDependencies:
- eslint: ^8.57.0 || ^9.0.0
- typescript: ^4.9.5 || ^5.3.3
- peerDependenciesMeta:
- typescript:
- optional: true
+ eslint: ^9.36.0
+ typescript: ^5.9.2
- eslint-plugin-react-hooks-extra@1.53.1:
- resolution: {integrity: sha512-fshTnMWNn9NjFLIuy7HzkRgGK29vKv4ZBO9UMr+kltVAfKLMeXXP6021qVKk66i/XhQjbktiS+vQsu1Rd3ZKvg==}
- engines: {node: '>=18.18.0'}
+ eslint-plugin-react-hooks-extra@2.0.1:
+ resolution: {integrity: sha512-mLn6TiL3ZLNRWCKZO5CBBK2er72PTFoltnp8izC0RTfR8u6mlM+J1KfCWZmJKsyrFP0TS44KAJwwKOUZSWNOdQ==}
+ engines: {node: '>=20.0.0'}
peerDependencies:
- eslint: ^8.57.0 || ^9.0.0
- typescript: ^4.9.5 || ^5.3.3
- peerDependenciesMeta:
- typescript:
- optional: true
+ eslint: ^9.36.0
+ typescript: ^5.9.2
eslint-plugin-react-hooks@5.2.0:
resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==}
@@ -2514,43 +2502,32 @@ packages:
peerDependencies:
eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
- eslint-plugin-react-naming-convention@1.53.1:
- resolution: {integrity: sha512-rvZ/B/CSVF8d34HQ4qIt90LRuxotVx+KUf3i1OMXAyhsagEFMRe4gAlPJiRufZ+h9lnuu279bEdd+NINsXOteA==}
- engines: {node: '>=18.18.0'}
+ eslint-plugin-react-naming-convention@2.0.1:
+ resolution: {integrity: sha512-7jvTEXKqQzYWXOPKdBOuG2J5L1ie9yqW+bflTGO8nJhCuX52JXdOnIzJdB2Lm6Ws/n+KIc31keWbnl9d9NpfFw==}
+ engines: {node: '>=20.19.0'}
peerDependencies:
- eslint: ^8.57.0 || ^9.0.0
- typescript: ^4.9.5 || ^5.3.3
- peerDependenciesMeta:
- typescript:
- optional: true
+ eslint: ^9.36.0
+ typescript: ^5.9.2
eslint-plugin-react-refresh@0.4.22:
resolution: {integrity: sha512-atkAG6QaJMGoTLc4MDAP+rqZcfwQuTIh2IqHWFLy2TEjxr0MOK+5BSG4RzL2564AAPpZkDRsZXAUz68kjnU6Ug==}
peerDependencies:
eslint: '>=8.40'
- eslint-plugin-react-web-api@1.53.1:
- resolution: {integrity: sha512-INVZ3Cbl9/b+sizyb43ChzEPXXYuDsBGU9BIg7OVTNPyDPloCXdI+dQFAcSlDocZhPrLxhPV3eT6+gXbygzYXg==}
- engines: {node: '>=18.18.0'}
+ eslint-plugin-react-web-api@2.0.1:
+ resolution: {integrity: sha512-KhaSd5k5eTeB0KirRsLmnWTb+fQvJLNhjfRGAOpsrHsV+uDoG8KsnpogoDUHw4RcvvmCOGfNbyURarpkUsopuQ==}
+ engines: {node: '>=20.19.0'}
peerDependencies:
- eslint: ^8.57.0 || ^9.0.0
- typescript: ^4.9.5 || ^5.3.3
- peerDependenciesMeta:
- typescript:
- optional: true
+ eslint: ^9.36.0
+ typescript: ^5.9.2
- eslint-plugin-react-x@1.53.1:
- resolution: {integrity: sha512-MwMNnVwiPem0U6SlejDF/ddA4h/lmP6imL1RDZ2m3pUBrcdcOwOx0gyiRVTA3ENnhRlWfHljHf5y7m8qDSxMEg==}
- engines: {node: '>=18.18.0'}
+ eslint-plugin-react-x@2.0.1:
+ resolution: {integrity: sha512-wpGcR8SUYcYnTvjyLTTMkXdyjt72vqaHXh0aOpx8nYHW12koQQem/LJEazolpc1fXm+lkwPxOs8tKnG9i1g+EQ==}
+ engines: {node: '>=20.19.0'}
peerDependencies:
- eslint: ^8.57.0 || ^9.0.0
+ eslint: ^9.36.0
ts-api-utils: ^2.1.0
- typescript: ^4.9.5 || ^5.3.3
- peerDependenciesMeta:
- ts-api-utils:
- optional: true
- typescript:
- optional: true
+ typescript: ^5.9.2
eslint-plugin-unused-imports@4.2.0:
resolution: {integrity: sha512-hLbJ2/wnjKq4kGA9AUaExVFIbNzyxYdVo49QZmKCnhk5pc9wcYRbfgLHvWJ8tnsdcseGhoUAddm9gn/lt+d74w==}
@@ -3459,10 +3436,10 @@ packages:
peerDependencies:
react: ^16.8.0 || ^17 || ^18 || ^19
- react-i18next@15.7.4:
- resolution: {integrity: sha512-nyU8iKNrI5uDJch0z9+Y5XEr34b0wkyYj3Rp+tfbahxtlswxSCjcUL9H0nqXo9IR3/t5Y5PKIA3fx3MfUyR9Xw==}
+ react-i18next@16.0.0:
+ resolution: {integrity: sha512-JQ+dFfLnFSKJQt7W01lJHWRC0SX7eDPobI+MSTJ3/gP39xH2g33AuTE7iddAfXYHamJdAeMGM0VFboPaD3G68Q==}
peerDependencies:
- i18next: '>= 23.4.0'
+ i18next: '>= 25.5.2'
react: '>= 16.8.0'
react-dom: '*'
react-native: '*'
@@ -4982,9 +4959,9 @@ snapshots:
'@eslint-community/regexpp@4.12.1': {}
- '@eslint-react/ast@1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)':
+ '@eslint-react/ast@2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)':
dependencies:
- '@eslint-react/eff': 1.53.1
+ '@eslint-react/eff': 2.0.1
'@typescript-eslint/types': 8.44.1
'@typescript-eslint/typescript-estree': 8.44.1(typescript@5.9.2)
'@typescript-eslint/utils': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
@@ -4995,51 +4972,51 @@ snapshots:
- supports-color
- typescript
- '@eslint-react/core@1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)':
+ '@eslint-react/core@2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)':
dependencies:
- '@eslint-react/ast': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- '@eslint-react/eff': 1.53.1
- '@eslint-react/kit': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- '@eslint-react/shared': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- '@eslint-react/var': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/ast': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/eff': 2.0.1
+ '@eslint-react/kit': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/shared': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/var': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
'@typescript-eslint/scope-manager': 8.44.1
'@typescript-eslint/type-utils': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
'@typescript-eslint/types': 8.44.1
'@typescript-eslint/utils': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
birecord: 0.1.1
+ ts-api-utils: 2.1.0(typescript@5.9.2)
ts-pattern: 5.8.0
transitivePeerDependencies:
- eslint
- supports-color
- typescript
- '@eslint-react/eff@1.53.1': {}
+ '@eslint-react/eff@2.0.1': {}
- '@eslint-react/eslint-plugin@1.53.1(eslint@9.36.0(jiti@2.6.0))(ts-api-utils@2.1.0(typescript@5.9.2))(typescript@5.9.2)':
+ '@eslint-react/eslint-plugin@2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)':
dependencies:
- '@eslint-react/eff': 1.53.1
- '@eslint-react/kit': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- '@eslint-react/shared': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/eff': 2.0.1
+ '@eslint-react/kit': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/shared': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
'@typescript-eslint/scope-manager': 8.44.1
'@typescript-eslint/type-utils': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
'@typescript-eslint/types': 8.44.1
'@typescript-eslint/utils': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
eslint: 9.36.0(jiti@2.6.0)
- eslint-plugin-react-debug: 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- eslint-plugin-react-dom: 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- eslint-plugin-react-hooks-extra: 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- eslint-plugin-react-naming-convention: 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- eslint-plugin-react-web-api: 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- eslint-plugin-react-x: 1.53.1(eslint@9.36.0(jiti@2.6.0))(ts-api-utils@2.1.0(typescript@5.9.2))(typescript@5.9.2)
- optionalDependencies:
+ eslint-plugin-react-debug: 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ eslint-plugin-react-dom: 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ eslint-plugin-react-hooks-extra: 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ eslint-plugin-react-naming-convention: 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ eslint-plugin-react-web-api: 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ eslint-plugin-react-x: 2.0.1(eslint@9.36.0(jiti@2.6.0))(ts-api-utils@2.1.0(typescript@5.9.2))(typescript@5.9.2)
+ ts-api-utils: 2.1.0(typescript@5.9.2)
typescript: 5.9.2
transitivePeerDependencies:
- supports-color
- - ts-api-utils
- '@eslint-react/kit@1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)':
+ '@eslint-react/kit@2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)':
dependencies:
- '@eslint-react/eff': 1.53.1
+ '@eslint-react/eff': 2.0.1
'@typescript-eslint/utils': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
ts-pattern: 5.8.0
zod: 4.1.11
@@ -5048,10 +5025,10 @@ snapshots:
- supports-color
- typescript
- '@eslint-react/shared@1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)':
+ '@eslint-react/shared@2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)':
dependencies:
- '@eslint-react/eff': 1.53.1
- '@eslint-react/kit': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/eff': 2.0.1
+ '@eslint-react/kit': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
'@typescript-eslint/utils': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
ts-pattern: 5.8.0
zod: 4.1.11
@@ -5060,10 +5037,10 @@ snapshots:
- supports-color
- typescript
- '@eslint-react/var@1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)':
+ '@eslint-react/var@2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)':
dependencies:
- '@eslint-react/ast': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- '@eslint-react/eff': 1.53.1
+ '@eslint-react/ast': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/eff': 2.0.1
'@typescript-eslint/scope-manager': 8.44.1
'@typescript-eslint/types': 8.44.1
'@typescript-eslint/utils': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
@@ -6602,14 +6579,14 @@ snapshots:
optionalDependencies:
eslint-config-prettier: 10.1.8(eslint@9.36.0(jiti@2.6.0))
- eslint-plugin-react-debug@1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2):
+ eslint-plugin-react-debug@2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2):
dependencies:
- '@eslint-react/ast': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- '@eslint-react/core': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- '@eslint-react/eff': 1.53.1
- '@eslint-react/kit': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- '@eslint-react/shared': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- '@eslint-react/var': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/ast': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/core': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/eff': 2.0.1
+ '@eslint-react/kit': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/shared': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/var': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
'@typescript-eslint/scope-manager': 8.44.1
'@typescript-eslint/type-utils': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
'@typescript-eslint/types': 8.44.1
@@ -6617,19 +6594,18 @@ snapshots:
eslint: 9.36.0(jiti@2.6.0)
string-ts: 2.2.1
ts-pattern: 5.8.0
- optionalDependencies:
typescript: 5.9.2
transitivePeerDependencies:
- supports-color
- eslint-plugin-react-dom@1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2):
+ eslint-plugin-react-dom@2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2):
dependencies:
- '@eslint-react/ast': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- '@eslint-react/core': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- '@eslint-react/eff': 1.53.1
- '@eslint-react/kit': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- '@eslint-react/shared': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- '@eslint-react/var': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/ast': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/core': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/eff': 2.0.1
+ '@eslint-react/kit': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/shared': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/var': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
'@typescript-eslint/scope-manager': 8.44.1
'@typescript-eslint/types': 8.44.1
'@typescript-eslint/utils': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
@@ -6637,19 +6613,18 @@ snapshots:
eslint: 9.36.0(jiti@2.6.0)
string-ts: 2.2.1
ts-pattern: 5.8.0
- optionalDependencies:
typescript: 5.9.2
transitivePeerDependencies:
- supports-color
- eslint-plugin-react-hooks-extra@1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2):
+ eslint-plugin-react-hooks-extra@2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2):
dependencies:
- '@eslint-react/ast': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- '@eslint-react/core': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- '@eslint-react/eff': 1.53.1
- '@eslint-react/kit': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- '@eslint-react/shared': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- '@eslint-react/var': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/ast': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/core': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/eff': 2.0.1
+ '@eslint-react/kit': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/shared': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/var': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
'@typescript-eslint/scope-manager': 8.44.1
'@typescript-eslint/type-utils': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
'@typescript-eslint/types': 8.44.1
@@ -6657,7 +6632,6 @@ snapshots:
eslint: 9.36.0(jiti@2.6.0)
string-ts: 2.2.1
ts-pattern: 5.8.0
- optionalDependencies:
typescript: 5.9.2
transitivePeerDependencies:
- supports-color
@@ -6666,14 +6640,14 @@ snapshots:
dependencies:
eslint: 9.36.0(jiti@2.6.0)
- eslint-plugin-react-naming-convention@1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2):
+ eslint-plugin-react-naming-convention@2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2):
dependencies:
- '@eslint-react/ast': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- '@eslint-react/core': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- '@eslint-react/eff': 1.53.1
- '@eslint-react/kit': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- '@eslint-react/shared': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- '@eslint-react/var': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/ast': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/core': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/eff': 2.0.1
+ '@eslint-react/kit': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/shared': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/var': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
'@typescript-eslint/scope-manager': 8.44.1
'@typescript-eslint/type-utils': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
'@typescript-eslint/types': 8.44.1
@@ -6681,7 +6655,6 @@ snapshots:
eslint: 9.36.0(jiti@2.6.0)
string-ts: 2.2.1
ts-pattern: 5.8.0
- optionalDependencies:
typescript: 5.9.2
transitivePeerDependencies:
- supports-color
@@ -6690,33 +6663,32 @@ snapshots:
dependencies:
eslint: 9.36.0(jiti@2.6.0)
- eslint-plugin-react-web-api@1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2):
+ eslint-plugin-react-web-api@2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2):
dependencies:
- '@eslint-react/ast': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- '@eslint-react/core': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- '@eslint-react/eff': 1.53.1
- '@eslint-react/kit': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- '@eslint-react/shared': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- '@eslint-react/var': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/ast': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/core': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/eff': 2.0.1
+ '@eslint-react/kit': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/shared': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/var': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
'@typescript-eslint/scope-manager': 8.44.1
'@typescript-eslint/types': 8.44.1
'@typescript-eslint/utils': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
eslint: 9.36.0(jiti@2.6.0)
string-ts: 2.2.1
ts-pattern: 5.8.0
- optionalDependencies:
typescript: 5.9.2
transitivePeerDependencies:
- supports-color
- eslint-plugin-react-x@1.53.1(eslint@9.36.0(jiti@2.6.0))(ts-api-utils@2.1.0(typescript@5.9.2))(typescript@5.9.2):
+ eslint-plugin-react-x@2.0.1(eslint@9.36.0(jiti@2.6.0))(ts-api-utils@2.1.0(typescript@5.9.2))(typescript@5.9.2):
dependencies:
- '@eslint-react/ast': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- '@eslint-react/core': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- '@eslint-react/eff': 1.53.1
- '@eslint-react/kit': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- '@eslint-react/shared': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
- '@eslint-react/var': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/ast': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/core': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/eff': 2.0.1
+ '@eslint-react/kit': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/shared': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
+ '@eslint-react/var': 2.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
'@typescript-eslint/scope-manager': 8.44.1
'@typescript-eslint/type-utils': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
'@typescript-eslint/types': 8.44.1
@@ -6725,9 +6697,8 @@ snapshots:
eslint: 9.36.0(jiti@2.6.0)
is-immutable-type: 5.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)
string-ts: 2.2.1
- ts-pattern: 5.8.0
- optionalDependencies:
ts-api-utils: 2.1.0(typescript@5.9.2)
+ ts-pattern: 5.8.0
typescript: 5.9.2
transitivePeerDependencies:
- supports-color
@@ -7835,7 +7806,7 @@ snapshots:
dependencies:
react: 19.1.1
- react-i18next@15.7.4(i18next@25.5.2(typescript@5.9.2))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.2):
+ react-i18next@16.0.0(i18next@25.5.2(typescript@5.9.2))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.2):
dependencies:
'@babel/runtime': 7.28.4
html-parse-stringify: 3.0.1
From 40f0e1bb198ec2fd3830d2e6ea76a8503ce38a39 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sun, 28 Sep 2025 19:55:08 +0800
Subject: [PATCH 04/10] chore(deps): update dependency @types/react to v19.1.15
(#4888)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
package.json | 2 +-
pnpm-lock.yaml | 156 ++++++++++++++++++++++++-------------------------
2 files changed, 79 insertions(+), 79 deletions(-)
diff --git a/package.json b/package.json
index eaf08a82..0c23591d 100644
--- a/package.json
+++ b/package.json
@@ -80,7 +80,7 @@
"@tauri-apps/cli": "2.8.4",
"@types/js-yaml": "^4.0.9",
"@types/lodash-es": "^4.17.12",
- "@types/react": "19.1.14",
+ "@types/react": "19.1.15",
"@types/react-dom": "19.1.9",
"@vitejs/plugin-legacy": "^7.2.1",
"@vitejs/plugin-react": "5.0.4",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ed391998..a7ba7cfb 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -19,25 +19,25 @@ importers:
version: 3.2.2(react@19.1.1)
'@emotion/react':
specifier: ^11.14.0
- version: 11.14.0(@types/react@19.1.14)(react@19.1.1)
+ version: 11.14.0(@types/react@19.1.15)(react@19.1.1)
'@emotion/styled':
specifier: ^11.14.1
- version: 11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
+ version: 11.14.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1)
'@juggle/resize-observer':
specifier: ^3.4.0
version: 3.4.0
'@mui/icons-material':
specifier: ^7.3.2
- version: 7.3.2(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
+ version: 7.3.2(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.15)(react@19.1.1)
'@mui/lab':
specifier: 7.0.0-beta.17
- version: 7.0.0-beta.17(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ version: 7.0.0-beta.17(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.15)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@mui/material':
specifier: ^7.3.2
- version: 7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ version: 7.3.2(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@mui/x-data-grid':
specifier: ^8.12.1
- version: 8.12.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ version: 8.12.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.2(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@tauri-apps/api':
specifier: 2.8.0
version: 2.8.0
@@ -115,7 +115,7 @@ importers:
version: 16.0.0(i18next@25.5.2(typescript@5.9.2))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.2)
react-markdown:
specifier: 10.1.0
- version: 10.1.0(@types/react@19.1.14)(react@19.1.1)
+ version: 10.1.0(@types/react@19.1.15)(react@19.1.1)
react-monaco-editor:
specifier: 0.59.0
version: 0.59.0(monaco-editor@0.53.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
@@ -133,7 +133,7 @@ importers:
version: 1.0.3
zustand:
specifier: ^5.0.8
- version: 5.0.8(@types/react@19.1.14)(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1))
+ version: 5.0.8(@types/react@19.1.15)(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1))
devDependencies:
'@actions/github':
specifier: ^6.0.1
@@ -154,11 +154,11 @@ importers:
specifier: ^4.17.12
version: 4.17.12
'@types/react':
- specifier: 19.1.14
- version: 19.1.14
+ specifier: 19.1.15
+ version: 19.1.15
'@types/react-dom':
specifier: 19.1.9
- version: 19.1.9(@types/react@19.1.14)
+ version: 19.1.9(@types/react@19.1.15)
'@vitejs/plugin-legacy':
specifier: ^7.2.1
version: 7.2.1(terser@5.44.0)(vite@7.1.7(jiti@2.6.0)(sass@1.93.2)(terser@5.44.0)(yaml@2.7.1))
@@ -1769,8 +1769,8 @@ packages:
peerDependencies:
'@types/react': '*'
- '@types/react@19.1.14':
- resolution: {integrity: sha512-ukd93VGzaNPMAUPy0gRDSC57UuQbnH9Kussp7HBjM06YFi9uZTFhOvMSO2OKqXm1rSgzOE+pVx1k1PYHGwlc8Q==}
+ '@types/react@19.1.15':
+ resolution: {integrity: sha512-+kLxJpaJzXybyDyFXYADyP1cznTO8HSuBpenGlnKOAkH4hyNINiywvXS/tGJhsrGGP/gM185RA3xpjY0Yg4erA==}
'@types/trusted-types@1.0.6':
resolution: {integrity: sha512-230RC8sFeHoT6sSUlRO6a8cAnclO06eeiq1QDfiv2FGCLWFvvERWgwIQD4FWqD9A69BN7Lzee4OXwoMVnnsWDw==}
@@ -4824,7 +4824,7 @@ snapshots:
'@emotion/memoize@0.9.0': {}
- '@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1)':
+ '@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.4
'@emotion/babel-plugin': 11.13.5
@@ -4836,7 +4836,7 @@ snapshots:
hoist-non-react-statics: 3.3.2
react: 19.1.1
optionalDependencies:
- '@types/react': 19.1.14
+ '@types/react': 19.1.15
transitivePeerDependencies:
- supports-color
@@ -4850,18 +4850,18 @@ snapshots:
'@emotion/sheet@1.4.0': {}
- '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)':
+ '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.4
'@emotion/babel-plugin': 11.13.5
'@emotion/is-prop-valid': 1.3.1
- '@emotion/react': 11.14.0(@types/react@19.1.14)(react@19.1.1)
+ '@emotion/react': 11.14.0(@types/react@19.1.15)(react@19.1.1)
'@emotion/serialize': 1.3.3
'@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.1.1)
'@emotion/utils': 1.4.2
react: 19.1.1
optionalDependencies:
- '@types/react': 19.1.14
+ '@types/react': 19.1.15
transitivePeerDependencies:
- supports-color
@@ -5150,39 +5150,39 @@ snapshots:
'@mui/core-downloads-tracker@7.3.2': {}
- '@mui/icons-material@7.3.2(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)':
+ '@mui/icons-material@7.3.2(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.15)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.4
- '@mui/material': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ '@mui/material': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
react: 19.1.1
optionalDependencies:
- '@types/react': 19.1.14
+ '@types/react': 19.1.15
- '@mui/lab@7.0.0-beta.17(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
+ '@mui/lab@7.0.0-beta.17(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.15)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.4
- '@mui/material': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
- '@mui/system': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
- '@mui/types': 7.4.6(@types/react@19.1.14)
- '@mui/utils': 7.3.2(@types/react@19.1.14)(react@19.1.1)
+ '@mui/material': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ '@mui/system': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1)
+ '@mui/types': 7.4.6(@types/react@19.1.15)
+ '@mui/utils': 7.3.2(@types/react@19.1.15)(react@19.1.1)
clsx: 2.1.1
prop-types: 15.8.1
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
optionalDependencies:
- '@emotion/react': 11.14.0(@types/react@19.1.14)(react@19.1.1)
- '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
- '@types/react': 19.1.14
+ '@emotion/react': 11.14.0(@types/react@19.1.15)(react@19.1.1)
+ '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1)
+ '@types/react': 19.1.15
- '@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
+ '@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.4
'@mui/core-downloads-tracker': 7.3.2
- '@mui/system': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
- '@mui/types': 7.4.6(@types/react@19.1.14)
- '@mui/utils': 7.3.2(@types/react@19.1.14)(react@19.1.1)
+ '@mui/system': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1)
+ '@mui/types': 7.4.6(@types/react@19.1.15)
+ '@mui/utils': 7.3.2(@types/react@19.1.15)(react@19.1.1)
'@popperjs/core': 2.11.8
- '@types/react-transition-group': 4.4.12(@types/react@19.1.14)
+ '@types/react-transition-group': 4.4.12(@types/react@19.1.15)
clsx: 2.1.1
csstype: 3.1.3
prop-types: 15.8.1
@@ -5191,20 +5191,20 @@ snapshots:
react-is: 19.1.1
react-transition-group: 4.4.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
optionalDependencies:
- '@emotion/react': 11.14.0(@types/react@19.1.14)(react@19.1.1)
- '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
- '@types/react': 19.1.14
+ '@emotion/react': 11.14.0(@types/react@19.1.15)(react@19.1.1)
+ '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1)
+ '@types/react': 19.1.15
- '@mui/private-theming@7.3.2(@types/react@19.1.14)(react@19.1.1)':
+ '@mui/private-theming@7.3.2(@types/react@19.1.15)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.4
- '@mui/utils': 7.3.2(@types/react@19.1.14)(react@19.1.1)
+ '@mui/utils': 7.3.2(@types/react@19.1.15)(react@19.1.1)
prop-types: 15.8.1
react: 19.1.1
optionalDependencies:
- '@types/react': 19.1.14
+ '@types/react': 19.1.15
- '@mui/styled-engine@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(react@19.1.1)':
+ '@mui/styled-engine@7.3.2(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1))(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.4
'@emotion/cache': 11.14.0
@@ -5214,77 +5214,77 @@ snapshots:
prop-types: 15.8.1
react: 19.1.1
optionalDependencies:
- '@emotion/react': 11.14.0(@types/react@19.1.14)(react@19.1.1)
- '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
+ '@emotion/react': 11.14.0(@types/react@19.1.15)(react@19.1.1)
+ '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1)
- '@mui/system@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)':
+ '@mui/system@7.3.2(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.4
- '@mui/private-theming': 7.3.2(@types/react@19.1.14)(react@19.1.1)
- '@mui/styled-engine': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(react@19.1.1)
- '@mui/types': 7.4.6(@types/react@19.1.14)
- '@mui/utils': 7.3.2(@types/react@19.1.14)(react@19.1.1)
+ '@mui/private-theming': 7.3.2(@types/react@19.1.15)(react@19.1.1)
+ '@mui/styled-engine': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1))(react@19.1.1)
+ '@mui/types': 7.4.6(@types/react@19.1.15)
+ '@mui/utils': 7.3.2(@types/react@19.1.15)(react@19.1.1)
clsx: 2.1.1
csstype: 3.1.3
prop-types: 15.8.1
react: 19.1.1
optionalDependencies:
- '@emotion/react': 11.14.0(@types/react@19.1.14)(react@19.1.1)
- '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
- '@types/react': 19.1.14
+ '@emotion/react': 11.14.0(@types/react@19.1.15)(react@19.1.1)
+ '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1)
+ '@types/react': 19.1.15
- '@mui/types@7.4.6(@types/react@19.1.14)':
+ '@mui/types@7.4.6(@types/react@19.1.15)':
dependencies:
'@babel/runtime': 7.28.4
optionalDependencies:
- '@types/react': 19.1.14
+ '@types/react': 19.1.15
- '@mui/utils@7.3.2(@types/react@19.1.14)(react@19.1.1)':
+ '@mui/utils@7.3.2(@types/react@19.1.15)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.4
- '@mui/types': 7.4.6(@types/react@19.1.14)
+ '@mui/types': 7.4.6(@types/react@19.1.15)
'@types/prop-types': 15.7.15
clsx: 2.1.1
prop-types: 15.8.1
react: 19.1.1
react-is: 19.1.1
optionalDependencies:
- '@types/react': 19.1.14
+ '@types/react': 19.1.15
- '@mui/x-data-grid@8.12.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
+ '@mui/x-data-grid@8.12.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.2(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.4
- '@mui/material': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
- '@mui/system': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
- '@mui/utils': 7.3.2(@types/react@19.1.14)(react@19.1.1)
- '@mui/x-internals': 8.12.0(@types/react@19.1.14)(react@19.1.1)
- '@mui/x-virtualizer': 0.2.0(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ '@mui/material': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ '@mui/system': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1)
+ '@mui/utils': 7.3.2(@types/react@19.1.15)(react@19.1.1)
+ '@mui/x-internals': 8.12.0(@types/react@19.1.15)(react@19.1.1)
+ '@mui/x-virtualizer': 0.2.0(@types/react@19.1.15)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
clsx: 2.1.1
prop-types: 15.8.1
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
use-sync-external-store: 1.5.0(react@19.1.1)
optionalDependencies:
- '@emotion/react': 11.14.0(@types/react@19.1.14)(react@19.1.1)
- '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)
+ '@emotion/react': 11.14.0(@types/react@19.1.15)(react@19.1.1)
+ '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.15)(react@19.1.1))(@types/react@19.1.15)(react@19.1.1)
transitivePeerDependencies:
- '@types/react'
- '@mui/x-internals@8.12.0(@types/react@19.1.14)(react@19.1.1)':
+ '@mui/x-internals@8.12.0(@types/react@19.1.15)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.4
- '@mui/utils': 7.3.2(@types/react@19.1.14)(react@19.1.1)
+ '@mui/utils': 7.3.2(@types/react@19.1.15)(react@19.1.1)
react: 19.1.1
reselect: 5.1.1
use-sync-external-store: 1.5.0(react@19.1.1)
transitivePeerDependencies:
- '@types/react'
- '@mui/x-virtualizer@0.2.0(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
+ '@mui/x-virtualizer@0.2.0(@types/react@19.1.15)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.4
- '@mui/utils': 7.3.2(@types/react@19.1.14)(react@19.1.1)
- '@mui/x-internals': 8.12.0(@types/react@19.1.14)(react@19.1.1)
+ '@mui/utils': 7.3.2(@types/react@19.1.15)(react@19.1.1)
+ '@mui/x-internals': 8.12.0(@types/react@19.1.15)(react@19.1.1)
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
transitivePeerDependencies:
@@ -5717,15 +5717,15 @@ snapshots:
'@types/prop-types@15.7.15': {}
- '@types/react-dom@19.1.9(@types/react@19.1.14)':
+ '@types/react-dom@19.1.9(@types/react@19.1.15)':
dependencies:
- '@types/react': 19.1.14
+ '@types/react': 19.1.15
- '@types/react-transition-group@4.4.12(@types/react@19.1.14)':
+ '@types/react-transition-group@4.4.12(@types/react@19.1.15)':
dependencies:
- '@types/react': 19.1.14
+ '@types/react': 19.1.15
- '@types/react@19.1.14':
+ '@types/react@19.1.15':
dependencies:
csstype: 3.1.3
@@ -7820,11 +7820,11 @@ snapshots:
react-is@19.1.1: {}
- react-markdown@10.1.0(@types/react@19.1.14)(react@19.1.1):
+ react-markdown@10.1.0(@types/react@19.1.15)(react@19.1.1):
dependencies:
'@types/hast': 3.0.4
'@types/mdast': 4.0.4
- '@types/react': 19.1.14
+ '@types/react': 19.1.15
devlop: 1.1.0
hast-util-to-jsx-runtime: 2.3.6
html-url-attributes: 3.0.1
@@ -8551,9 +8551,9 @@ snapshots:
zod@4.1.11: {}
- zustand@5.0.8(@types/react@19.1.14)(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1)):
+ zustand@5.0.8(@types/react@19.1.15)(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1)):
optionalDependencies:
- '@types/react': 19.1.14
+ '@types/react': 19.1.15
react: 19.1.1
use-sync-external-store: 1.5.0(react@19.1.1)
From d86bdea127394fe47321dff9400b17e7d64dfbb4 Mon Sep 17 00:00:00 2001
From: "Junkai W." <129588175+Be-Forever223@users.noreply.github.com>
Date: Mon, 29 Sep 2025 11:51:53 +0800
Subject: [PATCH 05/10] =?UTF-8?q?feat=EF=BC=9A=20add=20Quick=20navigation?=
=?UTF-8?q?=20bar=20in=20the=20rule=20mode=20agent=20group=20(#4889)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../proxy/proxy-group-navigator.tsx | 94 +++++++++++++++++++
src/components/proxy/proxy-groups.tsx | 52 +++++++++-
2 files changed, 145 insertions(+), 1 deletion(-)
create mode 100644 src/components/proxy/proxy-group-navigator.tsx
diff --git a/src/components/proxy/proxy-group-navigator.tsx b/src/components/proxy/proxy-group-navigator.tsx
new file mode 100644
index 00000000..4999da49
--- /dev/null
+++ b/src/components/proxy/proxy-group-navigator.tsx
@@ -0,0 +1,94 @@
+import { Box, Button, Tooltip } from "@mui/material";
+import { useCallback, useMemo } from "react";
+
+interface ProxyGroupNavigatorProps {
+ proxyGroupNames: string[];
+ onGroupLocation: (groupName: string) => void;
+}
+
+// 提取代理组名的第一个字符
+const getGroupDisplayChar = (groupName: string): string => {
+ if (!groupName) return "?";
+
+ // 直接返回第一个字符,支持表情符号
+ const firstChar = Array.from(groupName)[0];
+ return firstChar || "?";
+};
+
+export const ProxyGroupNavigator = ({
+ proxyGroupNames,
+ onGroupLocation,
+}: ProxyGroupNavigatorProps) => {
+ const handleGroupClick = useCallback(
+ (groupName: string) => {
+ onGroupLocation(groupName);
+ },
+ [onGroupLocation],
+ );
+
+ // 处理代理组数据,去重和排序
+ const processedGroups = useMemo(() => {
+ return proxyGroupNames
+ .filter((name) => name && name.trim())
+ .map((name) => ({
+ name,
+ displayChar: getGroupDisplayChar(name),
+ }));
+ }, [proxyGroupNames]);
+
+ if (processedGroups.length === 0) {
+ return null;
+ }
+
+ return (
+
+ {processedGroups.map(({ name, displayChar }) => (
+
+
+
+ ))}
+
+ );
+};
diff --git a/src/components/proxy/proxy-groups.tsx b/src/components/proxy/proxy-groups.tsx
index 889e66b0..476f5f90 100644
--- a/src/components/proxy/proxy-groups.tsx
+++ b/src/components/proxy/proxy-groups.tsx
@@ -10,10 +10,11 @@ import {
Menu,
MenuItem,
Divider,
+ Button,
} from "@mui/material";
import { ArchiveOutlined, ExpandMoreRounded } from "@mui/icons-material";
import { useLockFn } from "ahooks";
-import { useRef, useState, useEffect, useCallback } from "react";
+import { useRef, useState, useEffect, useCallback, useMemo } from "react";
import useSWR from "swr";
import { useTranslation } from "react-i18next";
import { Virtuoso, type VirtuosoHandle } from "react-virtuoso";
@@ -25,6 +26,7 @@ import {
providerHealthCheck,
getGroupProxyDelays,
updateProxyChainConfigInRuntime,
+ getRuntimeConfig,
} from "@/services/cmds";
import delayManager from "@/services/delay";
@@ -33,6 +35,7 @@ import { ScrollTopButton } from "../layout/scroll-top-button";
import { ProxyChain } from "./proxy-chain";
import { ProxyRender } from "./proxy-render";
+import { ProxyGroupNavigator } from "./proxy-group-navigator";
import { useRenderList } from "./use-render-list";
interface Props {
@@ -326,6 +329,45 @@ export const ProxyGroups = (props: Props) => {
}
};
+ // 获取运行时配置
+ const { data: runtimeConfig } = useSWR("getRuntimeConfig", getRuntimeConfig, {
+ revalidateOnFocus: false,
+ revalidateIfStale: true,
+ });
+
+ // 获取所有代理组名称
+ const getProxyGroupNames = useCallback(() => {
+ const config = runtimeConfig as any;
+ if (!config?.["proxy-groups"]) return [];
+
+ return config["proxy-groups"]
+ .map((group: any) => group.name)
+ .filter((name: string) => name && name.trim() !== "");
+ }, [runtimeConfig]);
+
+ // 定位到指定的代理组
+ const handleGroupLocationByName = useCallback(
+ (groupName: string) => {
+ const index = renderList.findIndex(
+ (item) => item.type === 0 && item.group?.name === groupName,
+ );
+
+ if (index >= 0) {
+ virtuosoRef.current?.scrollToIndex?.({
+ index,
+ align: "start",
+ behavior: "smooth",
+ });
+ }
+ },
+ [renderList],
+ );
+
+ const proxyGroupNames = useMemo(
+ () => getProxyGroupNames(),
+ [getProxyGroupNames],
+ );
+
if (mode === "direct") {
return ;
}
@@ -522,6 +564,14 @@ export const ProxyGroups = (props: Props) => {
+ {/* 代理组导航栏 */}
+ {mode === "rule" && (
+
+ )}
+
Date: Mon, 29 Sep 2025 20:11:55 +0800
Subject: [PATCH 06/10] feat: enhance CI workflows with paths filtering for
Rust and web changes
---
.github/workflows/fmt.yml | 41 ++++++++++++++++++++++++++++++-
.github/workflows/lint-clippy.yml | 16 ++++++++++++
src-tauri/Cargo.lock | 38 ++++++++++++++--------------
3 files changed, 76 insertions(+), 19 deletions(-)
diff --git a/.github/workflows/fmt.yml b/.github/workflows/fmt.yml
index 3320cba1..5ec2ca9a 100644
--- a/.github/workflows/fmt.yml
+++ b/.github/workflows/fmt.yml
@@ -10,28 +10,67 @@ on:
jobs:
rustfmt:
runs-on: ubuntu-latest
-
steps:
- uses: actions/checkout@v4
+ - name: Check Rust changes
+ id: check_rust
+ uses: dorny/paths-filter@v3
+ with:
+ filters: |
+ rust:
+ - 'src-tauri/**'
+ - '**/*.rs'
+
+ - name: Skip if no Rust changes
+ if: steps.check_rust.outputs.rust != 'true'
+ run: echo "No Rust changes, skipping rustfmt."
+
- name: install Rust stable and rustfmt
+ if: steps.check_rust.outputs.rust == 'true'
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- name: run cargo fmt
+ if: steps.check_rust.outputs.rust == 'true'
run: cargo fmt --manifest-path ./src-tauri/Cargo.toml --all -- --check
prettier:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
+
+ - name: Check Web changes
+ id: check_web
+ uses: dorny/paths-filter@v3
+ with:
+ filters: |
+ web:
+ - 'src/**'
+ - '**/*.js'
+ - '**/*.ts'
+ - '**/*.tsx'
+ - '**/*.css'
+ - '**/*.scss'
+ - '**/*.json'
+ - '**/*.md'
+ - '**/*.json'
+
+ - name: Skip if no Web changes
+ if: steps.check_web.outputs.web != 'true'
+ run: echo "No web changes, skipping prettier."
+
- uses: actions/setup-node@v4
+ if: steps.check_web.outputs.web == 'true'
with:
node-version: "lts/*"
- run: corepack enable
+ if: steps.check_web.outputs.web == 'true'
- run: pnpm install --frozen-lockfile
+ if: steps.check_web.outputs.web == 'true'
- run: pnpm format:check
+ if: steps.check_web.outputs.web == 'true'
# taplo:
# name: taplo (.toml files)
diff --git a/.github/workflows/lint-clippy.yml b/.github/workflows/lint-clippy.yml
index 1161faa5..6bc0bf33 100644
--- a/.github/workflows/lint-clippy.yml
+++ b/.github/workflows/lint-clippy.yml
@@ -19,6 +19,22 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
+ - name: Check src-tauri changes
+ id: check_changes
+ uses: dorny/paths-filter@v3
+ with:
+ filters: |
+ rust:
+ - 'src-tauri/**'
+
+ - name: Skip if src-tauri not changed
+ if: steps.check_changes.outputs.rust != 'true'
+ run: echo "No src-tauri changes, skipping clippy lint."
+
+ - name: Continue if src-tauri changed
+ if: steps.check_changes.outputs.rust == 'true'
+ run: echo "src-tauri changed, running clippy lint."
+
- name: Checkout Repository
uses: actions/checkout@v4
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index 4e143907..ca49513d 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -2223,9 +2223,9 @@ dependencies = [
[[package]]
name = "flexi_logger"
-version = "0.31.3"
+version = "0.31.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce93582299e89591bcb298db76e75498a9372eaef42c9cbedfc57d670176a3cd"
+checksum = "ff38b61724dd492b5171d5dbb0921dfc8e859022c5993b22f80f74e9afe6d573"
dependencies = [
"chrono",
"log",
@@ -3775,15 +3775,16 @@ dependencies = [
[[package]]
name = "kode-bridge"
-version = "0.2.1"
+version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e80a61472dcdfbd60624634f257c592d513b8df2991bd0946e4d54ccb24ea568"
+checksum = "368479099245d8ecd5b74e6b2b6279a69b38556a442aefbbaadd3ecf8246ffc3"
dependencies = [
"bytes",
"futures",
"http 1.3.1",
"httparse",
"interprocess",
+ "libc",
"parking_lot 0.12.4",
"pin-project-lite",
"rand 0.9.2",
@@ -3795,6 +3796,7 @@ dependencies = [
"tokio-util",
"toml 0.9.7",
"tracing",
+ "widestring",
]
[[package]]
@@ -3847,9 +3849,9 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7"
[[package]]
name = "libc"
-version = "0.2.175"
+version = "0.2.176"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
+checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174"
[[package]]
name = "libloading"
@@ -5837,9 +5839,9 @@ dependencies = [
[[package]]
name = "regex"
-version = "1.11.2"
+version = "1.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
+checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c"
dependencies = [
"aho-corasick",
"memchr",
@@ -5849,9 +5851,9 @@ dependencies = [
[[package]]
name = "regex-automata"
-version = "0.4.10"
+version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
+checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad"
dependencies = [
"aho-corasick",
"memchr",
@@ -6311,9 +6313,9 @@ dependencies = [
[[package]]
name = "serde"
-version = "1.0.226"
+version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
@@ -6345,18 +6347,18 @@ dependencies = [
[[package]]
name = "serde_core"
-version = "1.0.226"
+version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.226"
+version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
@@ -6877,9 +6879,9 @@ dependencies = [
[[package]]
name = "sysinfo"
-version = "0.37.0"
+version = "0.37.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07cec4dc2d2e357ca1e610cfb07de2fa7a10fc3e9fe89f72545f3d244ea87753"
+checksum = "3bddd368fda2f82ead69c03d46d351987cfa0c2a57abfa37a017f3aa3e9bf69a"
dependencies = [
"libc",
"memchr",
From 0c88568cd7c7645097dcfe630e06e7bddb6c4a5c Mon Sep 17 00:00:00 2001
From: Sukka
Date: Tue, 30 Sep 2025 14:19:49 +0800
Subject: [PATCH 07/10] chore: make eslint happy (part 1) (#4890)
---
src/components/profile/rule-item.tsx | 4 +++-
src/components/proxy/proxy-head.tsx | 24 +++++++++++--------
src/components/proxy/proxy-item.tsx | 4 ++--
src/components/proxy/use-filter-sort.ts | 4 ++--
src/components/setting/mods/hotkey-input.tsx | 6 ++---
.../setting/setting-verge-advanced.tsx | 4 ++--
.../setting/setting-verge-basic.tsx | 2 +-
src/services/cmds.ts | 2 +-
8 files changed, 27 insertions(+), 23 deletions(-)
diff --git a/src/components/profile/rule-item.tsx b/src/components/profile/rule-item.tsx
index 38f05833..03e5fb54 100644
--- a/src/components/profile/rule-item.tsx
+++ b/src/components/profile/rule-item.tsx
@@ -24,6 +24,8 @@ export const RuleItem = (props: Props) => {
const proxyPolicy = rule.match(/[^,]+$/)?.[0] ?? "";
const ruleContent = rule.slice(ruleType.length + 1, -proxyPolicy.length - 1);
+ const $sortable = useSortable({ id: ruleRaw });
+
const {
attributes,
listeners,
@@ -32,7 +34,7 @@ export const RuleItem = (props: Props) => {
transition,
isDragging,
} = sortable
- ? useSortable({ id: ruleRaw })
+ ? $sortable
: {
attributes: {},
listeners: {},
diff --git a/src/components/proxy/proxy-head.tsx b/src/components/proxy/proxy-head.tsx
index f8eeffea..fd031545 100644
--- a/src/components/proxy/proxy-head.tsx
+++ b/src/components/proxy/proxy-head.tsx
@@ -31,9 +31,15 @@ interface Props {
onHeadState: (val: Partial) => void;
}
-export const ProxyHead = (props: Props) => {
- const { sx = {}, url, groupName, headState, onHeadState } = props;
-
+export const ProxyHead = ({
+ sx = {},
+ url,
+ groupName,
+ headState,
+ onHeadState,
+ onLocation,
+ onCheckDelay,
+}: Props) => {
const { showType, sortType, filterText, textState, testUrl } = headState;
const { t } = useTranslation();
@@ -46,13 +52,11 @@ export const ProxyHead = (props: Props) => {
}, []);
const { verge } = useVerge();
+ const default_latency_test = verge!.default_latency_test!;
useEffect(() => {
- delayManager.setUrl(
- groupName,
- testUrl || url || verge?.default_latency_test!,
- );
- }, [groupName, testUrl, verge?.default_latency_test]);
+ delayManager.setUrl(groupName, testUrl || url || default_latency_test);
+ }, [groupName, testUrl, default_latency_test, url]);
return (
@@ -60,7 +64,7 @@ export const ProxyHead = (props: Props) => {
size="small"
color="inherit"
title={t("locate")}
- onClick={props.onLocation}
+ onClick={onLocation}
>
@@ -76,7 +80,7 @@ export const ProxyHead = (props: Props) => {
console.log(`[ProxyHead] 使用自定义测试URL: ${testUrl}`);
onHeadState({ textState: "url" });
}
- props.onCheckDelay();
+ onCheckDelay();
}}
>
diff --git a/src/components/proxy/proxy-item.tsx b/src/components/proxy/proxy-item.tsx
index dff589a9..82308b0e 100644
--- a/src/components/proxy/proxy-item.tsx
+++ b/src/components/proxy/proxy-item.tsx
@@ -61,12 +61,12 @@ export const ProxyItem = (props: Props) => {
return () => {
delayManager.removeListener(proxy.name, group.name);
};
- }, [proxy.name, group.name]);
+ }, [proxy.name, group.name, isPreset]);
useEffect(() => {
if (!proxy) return;
setDelay(delayManager.getDelayFix(proxy, group.name));
- }, [proxy]);
+ }, [group.name, proxy]);
const onDelay = useLockFn(async () => {
setDelay(-2);
diff --git a/src/components/proxy/use-filter-sort.ts b/src/components/proxy/use-filter-sort.ts
index 10880da9..e09f682f 100644
--- a/src/components/proxy/use-filter-sort.ts
+++ b/src/components/proxy/use-filter-sort.ts
@@ -11,7 +11,7 @@ export default function useFilterSort(
filterText: string,
sortType: ProxySortType,
) {
- const [refresh, setRefresh] = useState({});
+ const [, setRefresh] = useState({});
useEffect(() => {
let last = 0;
@@ -34,7 +34,7 @@ export default function useFilterSort(
const fp = filterProxies(proxies, groupName, filterText);
const sp = sortProxies(fp, groupName, sortType);
return sp;
- }, [proxies, groupName, filterText, sortType, refresh]);
+ }, [proxies, groupName, filterText, sortType]);
}
export function filterSort(
diff --git a/src/components/setting/mods/hotkey-input.tsx b/src/components/setting/mods/hotkey-input.tsx
index 3e746936..c4fdd716 100644
--- a/src/components/setting/mods/hotkey-input.tsx
+++ b/src/components/setting/mods/hotkey-input.tsx
@@ -90,13 +90,11 @@ export const HotkeyInput = (props: Props) => {
{keys.map((key, index) => (
-
+
+
-
- {key}
-
+ {key}
))}
diff --git a/src/components/setting/setting-verge-advanced.tsx b/src/components/setting/setting-verge-advanced.tsx
index 35ec6d53..197984c3 100644
--- a/src/components/setting/setting-verge-advanced.tsx
+++ b/src/components/setting/setting-verge-advanced.tsx
@@ -59,13 +59,13 @@ const SettingVergeAdvanced = ({ onError: _ }: Props) => {
const onExportDiagnosticInfo = useCallback(async () => {
await exportDiagnosticInfo();
showNotice("success", t("Copy Success"), 1000);
- }, []);
+ }, [t]);
const copyVersion = useCallback(() => {
navigator.clipboard.writeText(`v${version}`).then(() => {
showNotice("success", t("Version copied to clipboard"), 1000);
});
- }, [version, t]);
+ }, [t]);
return (
diff --git a/src/components/setting/setting-verge-basic.tsx b/src/components/setting/setting-verge-basic.tsx
index 59975aac..915fb5a9 100644
--- a/src/components/setting/setting-verge-basic.tsx
+++ b/src/components/setting/setting-verge-basic.tsx
@@ -77,7 +77,7 @@ const SettingVergeBasic = ({ onError }: Props) => {
const onCopyClashEnv = useCallback(async () => {
await copyClashEnv();
showNotice("success", t("Copy Success"), 1000);
- }, []);
+ }, [t]);
return (
diff --git a/src/services/cmds.ts b/src/services/cmds.ts
index 0d689770..d12f7998 100644
--- a/src/services/cmds.ts
+++ b/src/services/cmds.ts
@@ -87,7 +87,7 @@ export async function getRuntimeLogs() {
return invoke>("get_runtime_logs");
}
-export async function getRuntimeProxyChainConfig(proxyChainExitNode: String) {
+export async function getRuntimeProxyChainConfig(proxyChainExitNode: string) {
return invoke("get_runtime_proxy_chain_config", {
proxyChainExitNode,
});
From 1cd013fb94b4e0be37bc966e5401c2c9b9bb0bb4 Mon Sep 17 00:00:00 2001
From: Tunglies <77394545+Tunglies@users.noreply.github.com>
Date: Tue, 30 Sep 2025 14:23:29 +0800
Subject: [PATCH 08/10] Refactor components to remove forwardRef and simplify
props handling
- Updated multiple components to remove the use of forwardRef, simplifying the props structure.
- Adjusted imports and component definitions accordingly.
- Ensured consistent handling of refs and props across various viewer components.
- Improved readability and maintainability of the codebase.
---
.../connection/connection-detail.tsx | 72 +-
.../home/enhanced-canvas-traffic-graph.tsx | 1600 ++++++++---------
src/components/layout/traffic-graph.tsx | 6 +-
src/components/profile/profile-viewer.tsx | 604 +++----
src/components/proxy/proxy-groups.tsx | 10 +-
src/components/setting/mods/backup-viewer.tsx | 14 +-
.../setting/mods/clash-core-viewer.tsx | 8 +-
.../setting/mods/clash-port-viewer.tsx | 14 +-
src/components/setting/mods/config-viewer.tsx | 7 +-
.../setting/mods/controller-viewer.tsx | 8 +-
src/components/setting/mods/dns-viewer.tsx | 8 +-
.../setting/mods/external-controller-cors.tsx | 359 ++--
src/components/setting/mods/hotkey-viewer.tsx | 8 +-
src/components/setting/mods/layout-viewer.tsx | 8 +-
.../setting/mods/lite-mode-viewer.tsx | 8 +-
src/components/setting/mods/misc-viewer.tsx | 8 +-
.../setting/mods/network-interface-viewer.tsx | 8 +-
.../setting/mods/sysproxy-viewer.tsx | 16 +-
src/components/setting/mods/theme-viewer.tsx | 8 +-
src/components/setting/mods/tun-viewer.tsx | 8 +-
src/components/setting/mods/update-viewer.tsx | 14 +-
src/components/setting/mods/web-ui-viewer.tsx | 8 +-
src/components/test/test-viewer.tsx | 9 +-
src/providers/app-data-provider.tsx | 14 +-
src/providers/chain-proxy-provider.tsx | 8 +-
25 files changed, 1380 insertions(+), 1455 deletions(-)
diff --git a/src/components/connection/connection-detail.tsx b/src/components/connection/connection-detail.tsx
index 9eb0f563..fe563c0e 100644
--- a/src/components/connection/connection-detail.tsx
+++ b/src/components/connection/connection-detail.tsx
@@ -2,7 +2,7 @@ import { Box, Button, Snackbar, useTheme } from "@mui/material";
import { useLockFn } from "ahooks";
import dayjs from "dayjs";
import { t } from "i18next";
-import { forwardRef, useImperativeHandle, useState } from "react";
+import { useImperativeHandle, useState } from "react";
import { deleteConnection } from "@/services/cmds";
import parseTraffic from "@/utils/parse-traffic";
@@ -11,45 +11,43 @@ export interface ConnectionDetailRef {
open: (detail: IConnectionsItem) => void;
}
-export const ConnectionDetail = forwardRef(
- (props, ref) => {
- const [open, setOpen] = useState(false);
- const [detail, setDetail] = useState(null!);
- const theme = useTheme();
+export const ConnectionDetail = ({ ref, ...props }) => {
+ const [open, setOpen] = useState(false);
+ const [detail, setDetail] = useState(null!);
+ const theme = useTheme();
- useImperativeHandle(ref, () => ({
- open: (detail: IConnectionsItem) => {
- if (open) return;
- setOpen(true);
- setDetail(detail);
- },
- }));
+ useImperativeHandle(ref, () => ({
+ open: (detail: IConnectionsItem) => {
+ if (open) return;
+ setOpen(true);
+ setDetail(detail);
+ },
+ }));
- const onClose = () => setOpen(false);
+ const onClose = () => setOpen(false);
- return (
-
- ) : null
- }
- />
- );
- },
-);
+ return (
+
+ ) : null
+ }
+ />
+ );
+};
interface InnerProps {
data: IConnectionsItem;
diff --git a/src/components/home/enhanced-canvas-traffic-graph.tsx b/src/components/home/enhanced-canvas-traffic-graph.tsx
index 2f8a4719..9ccd5cc9 100644
--- a/src/components/home/enhanced-canvas-traffic-graph.tsx
+++ b/src/components/home/enhanced-canvas-traffic-graph.tsx
@@ -1,6 +1,5 @@
import { Box, useTheme } from "@mui/material";
import {
- forwardRef,
useImperativeHandle,
useState,
useEffect,
@@ -81,578 +80,525 @@ const GRAPH_CONFIG = {
* 稳定版Canvas流量图表组件
* 修复闪烁问题,添加时间轴显示
*/
-export const EnhancedCanvasTrafficGraph = memo(
- forwardRef((props, ref) => {
- const theme = useTheme();
- const { t } = useTranslation();
+export const EnhancedCanvasTrafficGraph = memo(({ ref, ...props }) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
- // 使用增强版全局流量数据管理
- const { dataPoints, getDataForTimeRange, isDataFresh, samplerStats } =
- useTrafficGraphDataEnhanced();
+ // 使用增强版全局流量数据管理
+ const { dataPoints, getDataForTimeRange, isDataFresh, samplerStats } =
+ useTrafficGraphDataEnhanced();
- // 基础状态
- const [timeRange, setTimeRange] = useState(10);
- const [chartStyle, setChartStyle] = useState<"bezier" | "line">("bezier");
+ // 基础状态
+ const [timeRange, setTimeRange] = useState(10);
+ const [chartStyle, setChartStyle] = useState<"bezier" | "line">("bezier");
- // 悬浮提示状态
- const [tooltipData, setTooltipData] = useState({
- x: 0,
- y: 0,
- upSpeed: "",
- downSpeed: "",
- timestamp: "",
- visible: false,
- dataIndex: -1,
- highlightY: 0,
- });
+ // 悬浮提示状态
+ const [tooltipData, setTooltipData] = useState({
+ x: 0,
+ y: 0,
+ upSpeed: "",
+ downSpeed: "",
+ timestamp: "",
+ visible: false,
+ dataIndex: -1,
+ highlightY: 0,
+ });
- // Canvas引用和渲染状态
- const canvasRef = useRef(null);
- const animationFrameRef = useRef(undefined);
- const lastRenderTimeRef = useRef(0);
- const isInitializedRef = useRef(false);
+ // Canvas引用和渲染状态
+ const canvasRef = useRef(null);
+ const animationFrameRef = useRef(undefined);
+ const lastRenderTimeRef = useRef(0);
+ const isInitializedRef = useRef(false);
- // 当前显示的数据缓存
- const [displayData, setDisplayData] = useState([]);
+ // 当前显示的数据缓存
+ const [displayData, setDisplayData] = useState([]);
- // 主题颜色配置
- const colors = useMemo(
- () => ({
- up: theme.palette.secondary.main,
- down: theme.palette.primary.main,
- grid: theme.palette.divider,
- text: theme.palette.text.secondary,
- background: theme.palette.background.paper,
- }),
- [theme],
- );
+ // 主题颜色配置
+ const colors = useMemo(
+ () => ({
+ up: theme.palette.secondary.main,
+ down: theme.palette.primary.main,
+ grid: theme.palette.divider,
+ text: theme.palette.text.secondary,
+ background: theme.palette.background.paper,
+ }),
+ [theme],
+ );
- // 更新显示数据(防抖处理)
- const updateDisplayDataDebounced = useMemo(() => {
- let timeoutId: number;
- return (newData: ITrafficDataPoint[]) => {
- clearTimeout(timeoutId);
- timeoutId = window.setTimeout(() => {
- setDisplayData(newData);
- }, 50); // 50ms防抖
- };
- }, []);
+ // 更新显示数据(防抖处理)
+ const updateDisplayDataDebounced = useMemo(() => {
+ let timeoutId: number;
+ return (newData: ITrafficDataPoint[]) => {
+ clearTimeout(timeoutId);
+ timeoutId = window.setTimeout(() => {
+ setDisplayData(newData);
+ }, 50); // 50ms防抖
+ };
+ }, []);
- // 监听数据变化
- useEffect(() => {
- const timeRangeData = getDataForTimeRange(timeRange);
- updateDisplayDataDebounced(timeRangeData);
- }, [
- dataPoints,
- timeRange,
- getDataForTimeRange,
- updateDisplayDataDebounced,
- ]);
+ // 监听数据变化
+ useEffect(() => {
+ const timeRangeData = getDataForTimeRange(timeRange);
+ updateDisplayDataDebounced(timeRangeData);
+ }, [dataPoints, timeRange, getDataForTimeRange, updateDisplayDataDebounced]);
- // Y轴坐标计算 - 基于刻度范围的线性映射
- const calculateY = useCallback(
- (value: number, height: number, data: ITrafficDataPoint[]): number => {
- const padding = GRAPH_CONFIG.padding;
- const topY = padding.top + 10; // 与刻度系统保持一致
- const bottomY = height - padding.bottom - 5;
+ // Y轴坐标计算 - 基于刻度范围的线性映射
+ const calculateY = useCallback(
+ (value: number, height: number, data: ITrafficDataPoint[]): number => {
+ const padding = GRAPH_CONFIG.padding;
+ const topY = padding.top + 10; // 与刻度系统保持一致
+ const bottomY = height - padding.bottom - 5;
- if (data.length === 0) return bottomY;
+ if (data.length === 0) return bottomY;
- // 获取当前的刻度范围
- const allValues = [
- ...data.map((d) => d.up),
- ...data.map((d) => d.down),
- ];
- const maxValue = Math.max(...allValues);
- const minValue = Math.min(...allValues);
+ // 获取当前的刻度范围
+ const allValues = [...data.map((d) => d.up), ...data.map((d) => d.down)];
+ const maxValue = Math.max(...allValues);
+ const minValue = Math.min(...allValues);
- let topValue, bottomValue;
+ let topValue, bottomValue;
- if (maxValue === 0) {
- topValue = 1024;
+ if (maxValue === 0) {
+ topValue = 1024;
+ bottomValue = 0;
+ } else {
+ const range = maxValue - minValue;
+ const padding_percent = range > 0 ? 0.1 : 0.5;
+
+ if (range === 0) {
bottomValue = 0;
+ topValue = maxValue * 1.2;
} else {
- const range = maxValue - minValue;
- const padding_percent = range > 0 ? 0.1 : 0.5;
-
- if (range === 0) {
- bottomValue = 0;
- topValue = maxValue * 1.2;
- } else {
- bottomValue = Math.max(0, minValue - range * padding_percent);
- topValue = maxValue + range * padding_percent;
- }
+ bottomValue = Math.max(0, minValue - range * padding_percent);
+ topValue = maxValue + range * padding_percent;
}
+ }
- // 线性映射到Y坐标
- if (topValue === bottomValue) return bottomY;
+ // 线性映射到Y坐标
+ if (topValue === bottomValue) return bottomY;
- const ratio = (value - bottomValue) / (topValue - bottomValue);
- return bottomY - ratio * (bottomY - topY);
- },
- [],
- );
+ const ratio = (value - bottomValue) / (topValue - bottomValue);
+ return bottomY - ratio * (bottomY - topY);
+ },
+ [],
+ );
- // 鼠标悬浮处理 - 计算最近的数据点
- const handleMouseMove = useCallback(
- (event: React.MouseEvent) => {
- const canvas = canvasRef.current;
- if (!canvas || displayData.length === 0) return;
+ // 鼠标悬浮处理 - 计算最近的数据点
+ const handleMouseMove = useCallback(
+ (event: React.MouseEvent) => {
+ const canvas = canvasRef.current;
+ if (!canvas || displayData.length === 0) return;
- const rect = canvas.getBoundingClientRect();
- const mouseX = event.clientX - rect.left;
- const mouseY = event.clientY - rect.top;
+ const rect = canvas.getBoundingClientRect();
+ const mouseX = event.clientX - rect.left;
+ const mouseY = event.clientY - rect.top;
- const padding = GRAPH_CONFIG.padding;
- const effectiveWidth = rect.width - padding.left - padding.right;
+ const padding = GRAPH_CONFIG.padding;
+ const effectiveWidth = rect.width - padding.left - padding.right;
- // 计算最接近的数据点索引
- const relativeMouseX = mouseX - padding.left;
- const ratio = Math.max(0, Math.min(1, relativeMouseX / effectiveWidth));
- const dataIndex = Math.round(ratio * (displayData.length - 1));
+ // 计算最接近的数据点索引
+ const relativeMouseX = mouseX - padding.left;
+ const ratio = Math.max(0, Math.min(1, relativeMouseX / effectiveWidth));
+ const dataIndex = Math.round(ratio * (displayData.length - 1));
- if (dataIndex >= 0 && dataIndex < displayData.length) {
- const dataPoint = displayData[dataIndex];
+ if (dataIndex >= 0 && dataIndex < displayData.length) {
+ const dataPoint = displayData[dataIndex];
- // 格式化流量数据
- const [upValue, upUnit] = parseTraffic(dataPoint.up);
- const [downValue, downUnit] = parseTraffic(dataPoint.down);
+ // 格式化流量数据
+ const [upValue, upUnit] = parseTraffic(dataPoint.up);
+ const [downValue, downUnit] = parseTraffic(dataPoint.down);
- // 格式化时间戳
- const timeStr = dataPoint.timestamp
- ? new Date(dataPoint.timestamp).toLocaleTimeString("zh-CN", {
- hour: "2-digit",
- minute: "2-digit",
- second: "2-digit",
- })
- : "未知时间";
+ // 格式化时间戳
+ const timeStr = dataPoint.timestamp
+ ? new Date(dataPoint.timestamp).toLocaleTimeString("zh-CN", {
+ hour: "2-digit",
+ minute: "2-digit",
+ second: "2-digit",
+ })
+ : "未知时间";
- // 计算数据点对应的Y坐标位置(用于高亮)
- const upY = calculateY(dataPoint.up, rect.height, displayData);
- const downY = calculateY(dataPoint.down, rect.height, displayData);
- const highlightY =
- Math.max(dataPoint.up, dataPoint.down) === dataPoint.up
- ? upY
- : downY;
+ // 计算数据点对应的Y坐标位置(用于高亮)
+ const upY = calculateY(dataPoint.up, rect.height, displayData);
+ const downY = calculateY(dataPoint.down, rect.height, displayData);
+ const highlightY =
+ Math.max(dataPoint.up, dataPoint.down) === dataPoint.up ? upY : downY;
- setTooltipData({
- x: mouseX,
- y: mouseY,
- upSpeed: `${upValue}${upUnit}/s`,
- downSpeed: `${downValue}${downUnit}/s`,
- timestamp: timeStr,
- visible: true,
- dataIndex,
- highlightY,
- });
- }
- },
- [displayData, calculateY],
- );
-
- // 鼠标离开处理
- const handleMouseLeave = useCallback(() => {
- setTooltipData((prev) => ({ ...prev, visible: false }));
- }, []);
-
- // 获取智能Y轴刻度(三刻度系统:最小值、中间值、最大值)
- const getYAxisTicks = useCallback(
- (data: ITrafficDataPoint[], height: number) => {
- if (data.length === 0) return [];
-
- // 找到数据的最大值和最小值
- const allValues = [
- ...data.map((d) => d.up),
- ...data.map((d) => d.down),
- ];
- const maxValue = Math.max(...allValues);
- const minValue = Math.min(...allValues);
-
- // 格式化流量数值
- const formatTrafficValue = (bytes: number): string => {
- if (bytes === 0) return "0";
- if (bytes < 1024) return `${Math.round(bytes)}B`;
- if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)}KB`;
- return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
- };
-
- const padding = GRAPH_CONFIG.padding;
-
- // 强制显示三个刻度:底部、中间、顶部
- const topY = padding.top + 10; // 避免与顶部时间范围按钮重叠
- const bottomY = height - padding.bottom - 5; // 避免与底部时间轴重叠
- const middleY = (topY + bottomY) / 2;
-
- // 计算对应的值
- let topValue, middleValue, bottomValue;
-
- if (maxValue === 0) {
- // 如果没有流量,显示0到一个小值的范围
- topValue = 1024; // 1KB
- middleValue = 512; // 512B
- bottomValue = 0;
- } else {
- // 根据数据范围计算合适的刻度值
- const range = maxValue - minValue;
- const padding_percent = range > 0 ? 0.1 : 0.5; // 如果范围为0,使用更大的边距
-
- if (range === 0) {
- // 所有值相同的情况
- bottomValue = 0;
- middleValue = maxValue * 0.5;
- topValue = maxValue * 1.2;
- } else {
- // 正常情况
- bottomValue = Math.max(0, minValue - range * padding_percent);
- topValue = maxValue + range * padding_percent;
- middleValue = (bottomValue + topValue) / 2;
- }
- }
-
- // 创建三个固定位置的刻度
- const ticks = [
- {
- value: bottomValue,
- label: formatTrafficValue(bottomValue),
- y: bottomY,
- },
- {
- value: middleValue,
- label: formatTrafficValue(middleValue),
- y: middleY,
- },
- {
- value: topValue,
- label: formatTrafficValue(topValue),
- y: topY,
- },
- ];
-
- return ticks;
- },
- [],
- );
-
- // 绘制Y轴刻度线和标签
- const drawYAxis = useCallback(
- (
- ctx: CanvasRenderingContext2D,
- width: number,
- height: number,
- data: ITrafficDataPoint[],
- ) => {
- const padding = GRAPH_CONFIG.padding;
- const ticks = getYAxisTicks(data, height);
-
- if (ticks.length === 0) return;
-
- ctx.save();
-
- ticks.forEach((tick, index) => {
- const isBottomTick = index === 0; // 最底部的刻度
- const isTopTick = index === ticks.length - 1; // 最顶部的刻度
-
- // 绘制水平刻度线,只绘制关键刻度线
- if (isBottomTick || isTopTick) {
- ctx.strokeStyle = colors.grid;
- ctx.lineWidth = isBottomTick ? 0.8 : 0.4; // 底部刻度线稍粗
- ctx.globalAlpha = isBottomTick ? 0.25 : 0.15;
-
- ctx.beginPath();
- ctx.moveTo(padding.left, tick.y);
- ctx.lineTo(width - padding.right, tick.y);
- ctx.stroke();
- }
-
- // 绘制Y轴标签
- ctx.fillStyle = colors.text;
- ctx.font =
- "8px -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif";
- ctx.globalAlpha = 0.9;
- ctx.textAlign = "right";
- ctx.textBaseline = "middle";
-
- // 为标签添加更清晰的背景(仅在必要时)
- if (tick.label !== "0") {
- const labelWidth = ctx.measureText(tick.label).width;
- ctx.globalAlpha = 0.15;
- ctx.fillStyle = colors.background;
- ctx.fillRect(
- padding.left - labelWidth - 8,
- tick.y - 5,
- labelWidth + 4,
- 10,
- );
- }
-
- // 绘制标签文字
- ctx.globalAlpha = 0.9;
- ctx.fillStyle = colors.text;
- ctx.fillText(tick.label, padding.left - 4, tick.y);
+ setTooltipData({
+ x: mouseX,
+ y: mouseY,
+ upSpeed: `${upValue}${upUnit}/s`,
+ downSpeed: `${downValue}${downUnit}/s`,
+ timestamp: timeStr,
+ visible: true,
+ dataIndex,
+ highlightY,
});
+ }
+ },
+ [displayData, calculateY],
+ );
- ctx.restore();
- },
- [colors.grid, colors.text, colors.background, getYAxisTicks],
- );
+ // 鼠标离开处理
+ const handleMouseLeave = useCallback(() => {
+ setTooltipData((prev) => ({ ...prev, visible: false }));
+ }, []);
- // 获取时间范围对应的最佳时间显示策略
- const getTimeDisplayStrategy = useCallback(
- (timeRangeMinutes: TimeRange) => {
- switch (timeRangeMinutes) {
- case 1: // 1分钟:更密集的时间标签,显示 MM:SS
- return {
- maxLabels: 6, // 减少到6个,更适合短时间
- formatTime: (timestamp: number) => {
- const date = new Date(timestamp);
- const minutes = date.getMinutes().toString().padStart(2, "0");
- const seconds = date.getSeconds().toString().padStart(2, "0");
- return `${minutes}:${seconds}`; // 显示 MM:SS
- },
- intervalSeconds: 10, // 每10秒一个标签,更合理
- minPixelDistance: 35, // 减少间距,允许更多标签
- };
- case 5: // 5分钟:中等密度,显示 HH:MM
- return {
- maxLabels: 6, // 6个标签比较合适
- formatTime: (timestamp: number) => {
- const date = new Date(timestamp);
- return date.toLocaleTimeString("en-US", {
- hour12: false,
- hour: "2-digit",
- minute: "2-digit",
- }); // 显示 HH:MM
- },
- intervalSeconds: 30, // 约30秒间隔
- minPixelDistance: 38, // 减少间距,允许更多标签
- };
- case 10: // 10分钟:标准密度,显示 HH:MM
- default:
- return {
- maxLabels: 8, // 保持8个
- formatTime: (timestamp: number) => {
- const date = new Date(timestamp);
- return date.toLocaleTimeString("en-US", {
- hour12: false,
- hour: "2-digit",
- minute: "2-digit",
- }); // 显示 HH:MM
- },
- intervalSeconds: 60, // 1分钟间隔
- minPixelDistance: 40, // 减少间距,允许更多标签
- };
+ // 获取智能Y轴刻度(三刻度系统:最小值、中间值、最大值)
+ const getYAxisTicks = useCallback(
+ (data: ITrafficDataPoint[], height: number) => {
+ if (data.length === 0) return [];
+
+ // 找到数据的最大值和最小值
+ const allValues = [...data.map((d) => d.up), ...data.map((d) => d.down)];
+ const maxValue = Math.max(...allValues);
+ const minValue = Math.min(...allValues);
+
+ // 格式化流量数值
+ const formatTrafficValue = (bytes: number): string => {
+ if (bytes === 0) return "0";
+ if (bytes < 1024) return `${Math.round(bytes)}B`;
+ if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)}KB`;
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
+ };
+
+ const padding = GRAPH_CONFIG.padding;
+
+ // 强制显示三个刻度:底部、中间、顶部
+ const topY = padding.top + 10; // 避免与顶部时间范围按钮重叠
+ const bottomY = height - padding.bottom - 5; // 避免与底部时间轴重叠
+ const middleY = (topY + bottomY) / 2;
+
+ // 计算对应的值
+ let topValue, middleValue, bottomValue;
+
+ if (maxValue === 0) {
+ // 如果没有流量,显示0到一个小值的范围
+ topValue = 1024; // 1KB
+ middleValue = 512; // 512B
+ bottomValue = 0;
+ } else {
+ // 根据数据范围计算合适的刻度值
+ const range = maxValue - minValue;
+ const padding_percent = range > 0 ? 0.1 : 0.5; // 如果范围为0,使用更大的边距
+
+ if (range === 0) {
+ // 所有值相同的情况
+ bottomValue = 0;
+ middleValue = maxValue * 0.5;
+ topValue = maxValue * 1.2;
+ } else {
+ // 正常情况
+ bottomValue = Math.max(0, minValue - range * padding_percent);
+ topValue = maxValue + range * padding_percent;
+ middleValue = (bottomValue + topValue) / 2;
}
- },
- [],
- );
+ }
- // 绘制时间轴
- const drawTimeAxis = useCallback(
- (
- ctx: CanvasRenderingContext2D,
- width: number,
- height: number,
- data: ITrafficDataPoint[],
- ) => {
- if (data.length === 0) return;
+ // 创建三个固定位置的刻度
+ const ticks = [
+ {
+ value: bottomValue,
+ label: formatTrafficValue(bottomValue),
+ y: bottomY,
+ },
+ {
+ value: middleValue,
+ label: formatTrafficValue(middleValue),
+ y: middleY,
+ },
+ {
+ value: topValue,
+ label: formatTrafficValue(topValue),
+ y: topY,
+ },
+ ];
- const padding = GRAPH_CONFIG.padding;
- const effectiveWidth = width - padding.left - padding.right;
- const timeAxisY = height - padding.bottom + 14;
+ return ticks;
+ },
+ [],
+ );
- const strategy = getTimeDisplayStrategy(timeRange);
+ // 绘制Y轴刻度线和标签
+ const drawYAxis = useCallback(
+ (
+ ctx: CanvasRenderingContext2D,
+ width: number,
+ height: number,
+ data: ITrafficDataPoint[],
+ ) => {
+ const padding = GRAPH_CONFIG.padding;
+ const ticks = getYAxisTicks(data, height);
- ctx.save();
+ if (ticks.length === 0) return;
+
+ ctx.save();
+
+ ticks.forEach((tick, index) => {
+ const isBottomTick = index === 0; // 最底部的刻度
+ const isTopTick = index === ticks.length - 1; // 最顶部的刻度
+
+ // 绘制水平刻度线,只绘制关键刻度线
+ if (isBottomTick || isTopTick) {
+ ctx.strokeStyle = colors.grid;
+ ctx.lineWidth = isBottomTick ? 0.8 : 0.4; // 底部刻度线稍粗
+ ctx.globalAlpha = isBottomTick ? 0.25 : 0.15;
+
+ ctx.beginPath();
+ ctx.moveTo(padding.left, tick.y);
+ ctx.lineTo(width - padding.right, tick.y);
+ ctx.stroke();
+ }
+
+ // 绘制Y轴标签
ctx.fillStyle = colors.text;
ctx.font =
- "10px -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif";
- ctx.globalAlpha = 0.7;
+ "8px -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif";
+ ctx.globalAlpha = 0.9;
+ ctx.textAlign = "right";
+ ctx.textBaseline = "middle";
- // 根据数据长度和时间范围智能选择显示间隔
- const targetLabels = Math.min(strategy.maxLabels, data.length);
- const step = Math.max(1, Math.floor(data.length / (targetLabels - 1)));
-
- // 使用策略中定义的最小像素间距
- const minPixelDistance = strategy.minPixelDistance || 45;
- const actualStep = Math.max(
- step,
- Math.ceil((data.length * minPixelDistance) / effectiveWidth),
- );
-
- // 收集要显示的时间点
- const timePoints: Array<{ index: number; x: number; label: string }> =
- [];
-
- // 添加第一个时间点
- if (data.length > 0 && data[0].timestamp) {
- timePoints.push({
- index: 0,
- x: padding.left,
- label: strategy.formatTime(data[0].timestamp),
- });
+ // 为标签添加更清晰的背景(仅在必要时)
+ if (tick.label !== "0") {
+ const labelWidth = ctx.measureText(tick.label).width;
+ ctx.globalAlpha = 0.15;
+ ctx.fillStyle = colors.background;
+ ctx.fillRect(
+ padding.left - labelWidth - 8,
+ tick.y - 5,
+ labelWidth + 4,
+ 10,
+ );
}
- // 添加中间的时间点
- for (
- let i = actualStep;
- i < data.length - actualStep;
- i += actualStep
- ) {
- const point = data[i];
- if (!point.timestamp) continue;
+ // 绘制标签文字
+ ctx.globalAlpha = 0.9;
+ ctx.fillStyle = colors.text;
+ ctx.fillText(tick.label, padding.left - 4, tick.y);
+ });
- const x = padding.left + (i / (data.length - 1)) * effectiveWidth;
- timePoints.push({
- index: i,
- x,
- label: strategy.formatTime(point.timestamp),
- });
- }
+ ctx.restore();
+ },
+ [colors.grid, colors.text, colors.background, getYAxisTicks],
+ );
- // 添加最后一个时间点(如果不会与前面的重叠)
- if (data.length > 1 && data[data.length - 1].timestamp) {
- const lastX = width - padding.right;
- const lastPoint = timePoints[timePoints.length - 1];
+ // 获取时间范围对应的最佳时间显示策略
+ const getTimeDisplayStrategy = useCallback((timeRangeMinutes: TimeRange) => {
+ switch (timeRangeMinutes) {
+ case 1: // 1分钟:更密集的时间标签,显示 MM:SS
+ return {
+ maxLabels: 6, // 减少到6个,更适合短时间
+ formatTime: (timestamp: number) => {
+ const date = new Date(timestamp);
+ const minutes = date.getMinutes().toString().padStart(2, "0");
+ const seconds = date.getSeconds().toString().padStart(2, "0");
+ return `${minutes}:${seconds}`; // 显示 MM:SS
+ },
+ intervalSeconds: 10, // 每10秒一个标签,更合理
+ minPixelDistance: 35, // 减少间距,允许更多标签
+ };
+ case 5: // 5分钟:中等密度,显示 HH:MM
+ return {
+ maxLabels: 6, // 6个标签比较合适
+ formatTime: (timestamp: number) => {
+ const date = new Date(timestamp);
+ return date.toLocaleTimeString("en-US", {
+ hour12: false,
+ hour: "2-digit",
+ minute: "2-digit",
+ }); // 显示 HH:MM
+ },
+ intervalSeconds: 30, // 约30秒间隔
+ minPixelDistance: 38, // 减少间距,允许更多标签
+ };
+ case 10: // 10分钟:标准密度,显示 HH:MM
+ default:
+ return {
+ maxLabels: 8, // 保持8个
+ formatTime: (timestamp: number) => {
+ const date = new Date(timestamp);
+ return date.toLocaleTimeString("en-US", {
+ hour12: false,
+ hour: "2-digit",
+ minute: "2-digit",
+ }); // 显示 HH:MM
+ },
+ intervalSeconds: 60, // 1分钟间隔
+ minPixelDistance: 40, // 减少间距,允许更多标签
+ };
+ }
+ }, []);
- // 确保最后一个标签与前一个标签有足够间距
- if (!lastPoint || lastX - lastPoint.x >= minPixelDistance) {
- timePoints.push({
- index: data.length - 1,
- x: lastX,
- label: strategy.formatTime(data[data.length - 1].timestamp),
- });
- }
- }
+ // 绘制时间轴
+ const drawTimeAxis = useCallback(
+ (
+ ctx: CanvasRenderingContext2D,
+ width: number,
+ height: number,
+ data: ITrafficDataPoint[],
+ ) => {
+ if (data.length === 0) return;
- // 绘制时间标签
- timePoints.forEach((point, index) => {
- if (index === 0) {
- // 第一个标签左对齐
- ctx.textAlign = "left";
- } else if (index === timePoints.length - 1) {
- // 最后一个标签右对齐
- ctx.textAlign = "right";
- } else {
- // 中间标签居中对齐
- ctx.textAlign = "center";
- }
+ const padding = GRAPH_CONFIG.padding;
+ const effectiveWidth = width - padding.left - padding.right;
+ const timeAxisY = height - padding.bottom + 14;
- ctx.fillText(point.label, point.x, timeAxisY);
+ const strategy = getTimeDisplayStrategy(timeRange);
+
+ ctx.save();
+ ctx.fillStyle = colors.text;
+ ctx.font =
+ "10px -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif";
+ ctx.globalAlpha = 0.7;
+
+ // 根据数据长度和时间范围智能选择显示间隔
+ const targetLabels = Math.min(strategy.maxLabels, data.length);
+ const step = Math.max(1, Math.floor(data.length / (targetLabels - 1)));
+
+ // 使用策略中定义的最小像素间距
+ const minPixelDistance = strategy.minPixelDistance || 45;
+ const actualStep = Math.max(
+ step,
+ Math.ceil((data.length * minPixelDistance) / effectiveWidth),
+ );
+
+ // 收集要显示的时间点
+ const timePoints: Array<{ index: number; x: number; label: string }> = [];
+
+ // 添加第一个时间点
+ if (data.length > 0 && data[0].timestamp) {
+ timePoints.push({
+ index: 0,
+ x: padding.left,
+ label: strategy.formatTime(data[0].timestamp),
});
+ }
- ctx.restore();
- },
- [colors.text, timeRange, getTimeDisplayStrategy],
- );
+ // 添加中间的时间点
+ for (let i = actualStep; i < data.length - actualStep; i += actualStep) {
+ const point = data[i];
+ if (!point.timestamp) continue;
- // 绘制网格线
- const drawGrid = useCallback(
- (ctx: CanvasRenderingContext2D, width: number, height: number) => {
- const padding = GRAPH_CONFIG.padding;
- const effectiveWidth = width - padding.left - padding.right;
- const effectiveHeight = height - padding.top - padding.bottom;
+ const x = padding.left + (i / (data.length - 1)) * effectiveWidth;
+ timePoints.push({
+ index: i,
+ x,
+ label: strategy.formatTime(point.timestamp),
+ });
+ }
- ctx.save();
- ctx.strokeStyle = colors.grid;
- ctx.lineWidth = GRAPH_CONFIG.lineWidth.grid;
- ctx.globalAlpha = 0.7;
+ // 添加最后一个时间点(如果不会与前面的重叠)
+ if (data.length > 1 && data[data.length - 1].timestamp) {
+ const lastX = width - padding.right;
+ const lastPoint = timePoints[timePoints.length - 1];
- // 水平网格线
- const horizontalLines = 4;
- for (let i = 1; i <= horizontalLines; i++) {
- const y = padding.top + (effectiveHeight / (horizontalLines + 1)) * i;
- ctx.beginPath();
- ctx.moveTo(padding.left, y);
- ctx.lineTo(width - padding.right, y);
- ctx.stroke();
+ // 确保最后一个标签与前一个标签有足够间距
+ if (!lastPoint || lastX - lastPoint.x >= minPixelDistance) {
+ timePoints.push({
+ index: data.length - 1,
+ x: lastX,
+ label: strategy.formatTime(data[data.length - 1].timestamp),
+ });
+ }
+ }
+
+ // 绘制时间标签
+ timePoints.forEach((point, index) => {
+ if (index === 0) {
+ // 第一个标签左对齐
+ ctx.textAlign = "left";
+ } else if (index === timePoints.length - 1) {
+ // 最后一个标签右对齐
+ ctx.textAlign = "right";
+ } else {
+ // 中间标签居中对齐
+ ctx.textAlign = "center";
}
- // 垂直网格线
- const verticalLines = 6;
- for (let i = 1; i <= verticalLines; i++) {
- const x = padding.left + (effectiveWidth / (verticalLines + 1)) * i;
- ctx.beginPath();
- ctx.moveTo(x, padding.top);
- ctx.lineTo(x, height - padding.bottom);
- ctx.stroke();
- }
+ ctx.fillText(point.label, point.x, timeAxisY);
+ });
- ctx.restore();
- },
- [colors.grid],
- );
+ ctx.restore();
+ },
+ [colors.text, timeRange, getTimeDisplayStrategy],
+ );
- // 绘制流量线条
- const drawTrafficLine = useCallback(
- (
- ctx: CanvasRenderingContext2D,
- values: number[],
- width: number,
- height: number,
- color: string,
- withGradient = false,
- data: ITrafficDataPoint[],
- ) => {
- if (values.length < 2) return;
+ // 绘制网格线
+ const drawGrid = useCallback(
+ (ctx: CanvasRenderingContext2D, width: number, height: number) => {
+ const padding = GRAPH_CONFIG.padding;
+ const effectiveWidth = width - padding.left - padding.right;
+ const effectiveHeight = height - padding.top - padding.bottom;
- const padding = GRAPH_CONFIG.padding;
- const effectiveWidth = width - padding.left - padding.right;
+ ctx.save();
+ ctx.strokeStyle = colors.grid;
+ ctx.lineWidth = GRAPH_CONFIG.lineWidth.grid;
+ ctx.globalAlpha = 0.7;
- const points = values.map((value, index) => [
- padding.left + (index / (values.length - 1)) * effectiveWidth,
- calculateY(value, height, data),
- ]);
-
- ctx.save();
-
- // 绘制渐变填充
- if (withGradient && chartStyle === "bezier") {
- const gradient = ctx.createLinearGradient(
- 0,
- padding.top,
- 0,
- height - padding.bottom,
- );
- gradient.addColorStop(
- 0,
- `${color}${Math.round(GRAPH_CONFIG.alpha.gradient * 255)
- .toString(16)
- .padStart(2, "0")}`,
- );
- gradient.addColorStop(1, `${color}00`);
-
- ctx.beginPath();
- ctx.moveTo(points[0][0], points[0][1]);
-
- if (chartStyle === "bezier") {
- for (let i = 1; i < points.length; i++) {
- const current = points[i];
- const next = points[i + 1] || current;
- const controlX = (current[0] + next[0]) / 2;
- const controlY = (current[1] + next[1]) / 2;
- ctx.quadraticCurveTo(current[0], current[1], controlX, controlY);
- }
- } else {
- for (let i = 1; i < points.length; i++) {
- ctx.lineTo(points[i][0], points[i][1]);
- }
- }
-
- ctx.lineTo(points[points.length - 1][0], height - padding.bottom);
- ctx.lineTo(points[0][0], height - padding.bottom);
- ctx.closePath();
- ctx.fillStyle = gradient;
- ctx.fill();
- }
-
- // 绘制主线条
+ // 水平网格线
+ const horizontalLines = 4;
+ for (let i = 1; i <= horizontalLines; i++) {
+ const y = padding.top + (effectiveHeight / (horizontalLines + 1)) * i;
ctx.beginPath();
- ctx.strokeStyle = color;
- ctx.lineWidth = GRAPH_CONFIG.lineWidth.up;
- ctx.lineCap = "round";
- ctx.lineJoin = "round";
- ctx.globalAlpha = GRAPH_CONFIG.alpha.line;
+ ctx.moveTo(padding.left, y);
+ ctx.lineTo(width - padding.right, y);
+ ctx.stroke();
+ }
+ // 垂直网格线
+ const verticalLines = 6;
+ for (let i = 1; i <= verticalLines; i++) {
+ const x = padding.left + (effectiveWidth / (verticalLines + 1)) * i;
+ ctx.beginPath();
+ ctx.moveTo(x, padding.top);
+ ctx.lineTo(x, height - padding.bottom);
+ ctx.stroke();
+ }
+
+ ctx.restore();
+ },
+ [colors.grid],
+ );
+
+ // 绘制流量线条
+ const drawTrafficLine = useCallback(
+ (
+ ctx: CanvasRenderingContext2D,
+ values: number[],
+ width: number,
+ height: number,
+ color: string,
+ withGradient = false,
+ data: ITrafficDataPoint[],
+ ) => {
+ if (values.length < 2) return;
+
+ const padding = GRAPH_CONFIG.padding;
+ const effectiveWidth = width - padding.left - padding.right;
+
+ const points = values.map((value, index) => [
+ padding.left + (index / (values.length - 1)) * effectiveWidth,
+ calculateY(value, height, data),
+ ]);
+
+ ctx.save();
+
+ // 绘制渐变填充
+ if (withGradient && chartStyle === "bezier") {
+ const gradient = ctx.createLinearGradient(
+ 0,
+ padding.top,
+ 0,
+ height - padding.bottom,
+ );
+ gradient.addColorStop(
+ 0,
+ `${color}${Math.round(GRAPH_CONFIG.alpha.gradient * 255)
+ .toString(16)
+ .padStart(2, "0")}`,
+ );
+ gradient.addColorStop(1, `${color}00`);
+
+ ctx.beginPath();
ctx.moveTo(points[0][0], points[0][1]);
if (chartStyle === "bezier") {
@@ -669,337 +615,359 @@ export const EnhancedCanvasTrafficGraph = memo(
}
}
- ctx.stroke();
- ctx.restore();
- },
- [calculateY, chartStyle],
- );
-
- // 主绘制函数
- const drawGraph = useCallback(() => {
- const canvas = canvasRef.current;
- if (!canvas || displayData.length === 0) return;
-
- const ctx = canvas.getContext("2d");
- if (!ctx) return;
-
- // Canvas尺寸设置
- const rect = canvas.getBoundingClientRect();
- const dpr = window.devicePixelRatio || 1;
- const width = rect.width;
- const height = rect.height;
-
- // 只在尺寸变化时重新设置Canvas
- if (canvas.width !== width * dpr || canvas.height !== height * dpr) {
- canvas.width = width * dpr;
- canvas.height = height * dpr;
- ctx.scale(dpr, dpr);
- canvas.style.width = width + "px";
- canvas.style.height = height + "px";
+ ctx.lineTo(points[points.length - 1][0], height - padding.bottom);
+ ctx.lineTo(points[0][0], height - padding.bottom);
+ ctx.closePath();
+ ctx.fillStyle = gradient;
+ ctx.fill();
}
- // 清空画布
- ctx.clearRect(0, 0, width, height);
+ // 绘制主线条
+ ctx.beginPath();
+ ctx.strokeStyle = color;
+ ctx.lineWidth = GRAPH_CONFIG.lineWidth.up;
+ ctx.lineCap = "round";
+ ctx.lineJoin = "round";
+ ctx.globalAlpha = GRAPH_CONFIG.alpha.line;
- // 绘制Y轴刻度线(背景层)
- drawYAxis(ctx, width, height, displayData);
+ ctx.moveTo(points[0][0], points[0][1]);
- // 绘制网格
- drawGrid(ctx, width, height);
-
- // 绘制时间轴
- drawTimeAxis(ctx, width, height, displayData);
-
- // 提取流量数据
- const upValues = displayData.map((d) => d.up);
- const downValues = displayData.map((d) => d.down);
-
- // 绘制下载线(背景层)
- drawTrafficLine(
- ctx,
- downValues,
- width,
- height,
- colors.down,
- true,
- displayData,
- );
-
- // 绘制上传线(前景层)
- drawTrafficLine(
- ctx,
- upValues,
- width,
- height,
- colors.up,
- true,
- displayData,
- );
-
- // 绘制悬浮高亮线
- if (tooltipData.visible && tooltipData.dataIndex >= 0) {
- const padding = GRAPH_CONFIG.padding;
- const effectiveWidth = width - padding.left - padding.right;
- const dataX =
- padding.left +
- (tooltipData.dataIndex / (displayData.length - 1)) * effectiveWidth;
-
- ctx.save();
- ctx.strokeStyle = colors.text;
- ctx.lineWidth = 1;
- ctx.globalAlpha = 0.6;
- ctx.setLineDash([4, 4]); // 虚线效果
-
- // 绘制垂直指示线
- ctx.beginPath();
- ctx.moveTo(dataX, padding.top);
- ctx.lineTo(dataX, height - padding.bottom);
- ctx.stroke();
-
- // 绘制水平指示线(高亮Y轴位置)
- ctx.beginPath();
- ctx.moveTo(padding.left, tooltipData.highlightY);
- ctx.lineTo(width - padding.right, tooltipData.highlightY);
- ctx.stroke();
-
- ctx.restore();
+ if (chartStyle === "bezier") {
+ for (let i = 1; i < points.length; i++) {
+ const current = points[i];
+ const next = points[i + 1] || current;
+ const controlX = (current[0] + next[0]) / 2;
+ const controlY = (current[1] + next[1]) / 2;
+ ctx.quadraticCurveTo(current[0], current[1], controlX, controlY);
+ }
+ } else {
+ for (let i = 1; i < points.length; i++) {
+ ctx.lineTo(points[i][0], points[i][1]);
+ }
}
- isInitializedRef.current = true;
- }, [
+ ctx.stroke();
+ ctx.restore();
+ },
+ [calculateY, chartStyle],
+ );
+
+ // 主绘制函数
+ const drawGraph = useCallback(() => {
+ const canvas = canvasRef.current;
+ if (!canvas || displayData.length === 0) return;
+
+ const ctx = canvas.getContext("2d");
+ if (!ctx) return;
+
+ // Canvas尺寸设置
+ const rect = canvas.getBoundingClientRect();
+ const dpr = window.devicePixelRatio || 1;
+ const width = rect.width;
+ const height = rect.height;
+
+ // 只在尺寸变化时重新设置Canvas
+ if (canvas.width !== width * dpr || canvas.height !== height * dpr) {
+ canvas.width = width * dpr;
+ canvas.height = height * dpr;
+ ctx.scale(dpr, dpr);
+ canvas.style.width = width + "px";
+ canvas.style.height = height + "px";
+ }
+
+ // 清空画布
+ ctx.clearRect(0, 0, width, height);
+
+ // 绘制Y轴刻度线(背景层)
+ drawYAxis(ctx, width, height, displayData);
+
+ // 绘制网格
+ drawGrid(ctx, width, height);
+
+ // 绘制时间轴
+ drawTimeAxis(ctx, width, height, displayData);
+
+ // 提取流量数据
+ const upValues = displayData.map((d) => d.up);
+ const downValues = displayData.map((d) => d.down);
+
+ // 绘制下载线(背景层)
+ drawTrafficLine(
+ ctx,
+ downValues,
+ width,
+ height,
+ colors.down,
+ true,
displayData,
- colors,
- drawYAxis,
- drawGrid,
- drawTimeAxis,
- drawTrafficLine,
- tooltipData,
- ]);
-
- // 受控的动画循环
- useEffect(() => {
- const animate = (currentTime: number) => {
- // 控制帧率,减少不必要的重绘
- if (
- currentTime - lastRenderTimeRef.current >=
- 1000 / GRAPH_CONFIG.targetFPS
- ) {
- drawGraph();
- lastRenderTimeRef.current = currentTime;
- }
- animationFrameRef.current = requestAnimationFrame(animate);
- };
-
- // 只有在有数据时才开始动画
- if (displayData.length > 0) {
- animationFrameRef.current = requestAnimationFrame(animate);
- }
-
- return () => {
- if (animationFrameRef.current) {
- cancelAnimationFrame(animationFrameRef.current);
- }
- };
- }, [drawGraph, displayData.length]);
-
- // 切换时间范围
- const handleTimeRangeClick = useCallback((event: React.MouseEvent) => {
- event.stopPropagation();
- setTimeRange((prev) => {
- return prev === 1 ? 5 : prev === 5 ? 10 : 1;
- });
- }, []);
-
- // 切换图表样式
- const toggleStyle = useCallback(() => {
- setChartStyle((prev) => (prev === "bezier" ? "line" : "bezier"));
- }, []);
-
- // 兼容性方法
- const appendData = useCallback((data: ITrafficItem) => {
- console.log(
- "[EnhancedCanvasTrafficGraphV2] appendData called (using global data):",
- data,
- );
- }, []);
-
- // 暴露方法给父组件
- useImperativeHandle(
- ref,
- () => ({
- appendData,
- toggleStyle,
- }),
- [appendData, toggleStyle],
);
- // 获取时间范围文本
- const getTimeRangeText = useCallback(() => {
- return t("{{time}} Minutes", { time: timeRange });
- }, [timeRange, t]);
+ // 绘制上传线(前景层)
+ drawTrafficLine(ctx, upValues, width, height, colors.up, true, displayData);
- return (
- = 0) {
+ const padding = GRAPH_CONFIG.padding;
+ const effectiveWidth = width - padding.left - padding.right;
+ const dataX =
+ padding.left +
+ (tooltipData.dataIndex / (displayData.length - 1)) * effectiveWidth;
+
+ ctx.save();
+ ctx.strokeStyle = colors.text;
+ ctx.lineWidth = 1;
+ ctx.globalAlpha = 0.6;
+ ctx.setLineDash([4, 4]); // 虚线效果
+
+ // 绘制垂直指示线
+ ctx.beginPath();
+ ctx.moveTo(dataX, padding.top);
+ ctx.lineTo(dataX, height - padding.bottom);
+ ctx.stroke();
+
+ // 绘制水平指示线(高亮Y轴位置)
+ ctx.beginPath();
+ ctx.moveTo(padding.left, tooltipData.highlightY);
+ ctx.lineTo(width - padding.right, tooltipData.highlightY);
+ ctx.stroke();
+
+ ctx.restore();
+ }
+
+ isInitializedRef.current = true;
+ }, [
+ displayData,
+ colors,
+ drawYAxis,
+ drawGrid,
+ drawTimeAxis,
+ drawTrafficLine,
+ tooltipData,
+ ]);
+
+ // 受控的动画循环
+ useEffect(() => {
+ const animate = (currentTime: number) => {
+ // 控制帧率,减少不必要的重绘
+ if (
+ currentTime - lastRenderTimeRef.current >=
+ 1000 / GRAPH_CONFIG.targetFPS
+ ) {
+ drawGraph();
+ lastRenderTimeRef.current = currentTime;
+ }
+ animationFrameRef.current = requestAnimationFrame(animate);
+ };
+
+ // 只有在有数据时才开始动画
+ if (displayData.length > 0) {
+ animationFrameRef.current = requestAnimationFrame(animate);
+ }
+
+ return () => {
+ if (animationFrameRef.current) {
+ cancelAnimationFrame(animationFrameRef.current);
+ }
+ };
+ }, [drawGraph, displayData.length]);
+
+ // 切换时间范围
+ const handleTimeRangeClick = useCallback((event: React.MouseEvent) => {
+ event.stopPropagation();
+ setTimeRange((prev) => {
+ return prev === 1 ? 5 : prev === 5 ? 10 : 1;
+ });
+ }, []);
+
+ // 切换图表样式
+ const toggleStyle = useCallback(() => {
+ setChartStyle((prev) => (prev === "bezier" ? "line" : "bezier"));
+ }, []);
+
+ // 兼容性方法
+ const appendData = useCallback((data: ITrafficItem) => {
+ console.log(
+ "[EnhancedCanvasTrafficGraphV2] appendData called (using global data):",
+ data,
+ );
+ }, []);
+
+ // 暴露方法给父组件
+ useImperativeHandle(
+ ref,
+ () => ({
+ appendData,
+ toggleStyle,
+ }),
+ [appendData, toggleStyle],
+ );
+
+ // 获取时间范围文本
+ const getTimeRangeText = useCallback(() => {
+ return t("{{time}} Minutes", { time: timeRange });
+ }, [timeRange, t]);
+
+ return (
+
+
- );
- }),
-);
+
+ );
+});
EnhancedCanvasTrafficGraph.displayName = "EnhancedCanvasTrafficGraph";
diff --git a/src/components/layout/traffic-graph.tsx b/src/components/layout/traffic-graph.tsx
index 9fd6e2a7..82d011b5 100644
--- a/src/components/layout/traffic-graph.tsx
+++ b/src/components/layout/traffic-graph.tsx
@@ -1,5 +1,5 @@
import { useTheme } from "@mui/material";
-import { forwardRef, useEffect, useImperativeHandle, useRef } from "react";
+import { useEffect, useImperativeHandle, useRef } from "react";
const maxPoint = 30;
@@ -24,7 +24,7 @@ export interface TrafficRef {
/**
* draw the traffic graph
*/
-export const TrafficGraph = forwardRef((props, ref) => {
+export const TrafficGraph = ({ ref, ...props }) => {
const countRef = useRef(0);
const styleRef = useRef(true);
const listRef = useRef(defaultList);
@@ -196,4 +196,4 @@ export const TrafficGraph = forwardRef((props, ref) => {
}, [palette]);
return ;
-});
+};
diff --git a/src/components/profile/profile-viewer.tsx b/src/components/profile/profile-viewer.tsx
index d6c4bbc7..0bafb44b 100644
--- a/src/components/profile/profile-viewer.tsx
+++ b/src/components/profile/profile-viewer.tsx
@@ -9,13 +9,7 @@ import {
TextField,
} from "@mui/material";
import { useLockFn } from "ahooks";
-import {
- forwardRef,
- useEffect,
- useImperativeHandle,
- useRef,
- useState,
-} from "react";
+import { useEffect, useImperativeHandle, useRef, useState } from "react";
import { useForm, Controller } from "react-hook-form";
import { useTranslation } from "react-i18next";
@@ -38,304 +32,280 @@ export interface ProfileViewerRef {
// create or edit the profile
// remote / local
-export const ProfileViewer = forwardRef(
- (props, ref) => {
- const { t } = useTranslation();
- const [open, setOpen] = useState(false);
- const [openType, setOpenType] = useState<"new" | "edit">("new");
- const [loading, setLoading] = useState(false);
- const { profiles } = useProfiles();
+export const ProfileViewer = ({
+ ref,
+ ...props
+}: Props & { ref?: React.RefObject }) => {
+ const { t } = useTranslation();
+ const [open, setOpen] = useState(false);
+ const [openType, setOpenType] = useState<"new" | "edit">("new");
+ const [loading, setLoading] = useState(false);
+ const { profiles } = useProfiles();
- // file input
- const fileDataRef = useRef(null);
+ // file input
+ const fileDataRef = useRef(null);
- const {
- control,
- watch,
- register: _register,
- ...formIns
- } = useForm({
- defaultValues: {
- type: "remote",
- name: "",
- desc: "",
- url: "",
- option: {
- with_proxy: false,
- self_proxy: false,
- },
+ const {
+ control,
+ watch,
+ register: _register,
+ ...formIns
+ } = useForm({
+ defaultValues: {
+ type: "remote",
+ name: "",
+ desc: "",
+ url: "",
+ option: {
+ with_proxy: false,
+ self_proxy: false,
},
- });
+ },
+ });
- useImperativeHandle(ref, () => ({
- create: () => {
- setOpenType("new");
- setOpen(true);
- },
- edit: (item) => {
- if (item) {
- Object.entries(item).forEach(([key, value]) => {
- formIns.setValue(key as any, value);
- });
- }
- setOpenType("edit");
- setOpen(true);
- },
- }));
+ useImperativeHandle(ref, () => ({
+ create: () => {
+ setOpenType("new");
+ setOpen(true);
+ },
+ edit: (item) => {
+ if (item) {
+ Object.entries(item).forEach(([key, value]) => {
+ formIns.setValue(key as any, value);
+ });
+ }
+ setOpenType("edit");
+ setOpen(true);
+ },
+ }));
- const selfProxy = watch("option.self_proxy");
- const withProxy = watch("option.with_proxy");
+ const selfProxy = watch("option.self_proxy");
+ const withProxy = watch("option.with_proxy");
- useEffect(() => {
- if (selfProxy) formIns.setValue("option.with_proxy", false);
- }, [selfProxy]);
+ useEffect(() => {
+ if (selfProxy) formIns.setValue("option.with_proxy", false);
+ }, [selfProxy]);
- useEffect(() => {
- if (withProxy) formIns.setValue("option.self_proxy", false);
- }, [withProxy]);
+ useEffect(() => {
+ if (withProxy) formIns.setValue("option.self_proxy", false);
+ }, [withProxy]);
- const handleOk = useLockFn(
- formIns.handleSubmit(async (form) => {
- if (form.option?.timeout_seconds) {
- form.option.timeout_seconds = +form.option.timeout_seconds;
+ const handleOk = useLockFn(
+ formIns.handleSubmit(async (form) => {
+ if (form.option?.timeout_seconds) {
+ form.option.timeout_seconds = +form.option.timeout_seconds;
+ }
+
+ setLoading(true);
+ try {
+ // 基本验证
+ if (!form.type) throw new Error("`Type` should not be null");
+ if (form.type === "remote" && !form.url) {
+ throw new Error("The URL should not be null");
}
- setLoading(true);
- try {
- // 基本验证
- if (!form.type) throw new Error("`Type` should not be null");
- if (form.type === "remote" && !form.url) {
- throw new Error("The URL should not be null");
- }
+ // 处理表单数据
+ if (form.option?.update_interval) {
+ form.option.update_interval = +form.option.update_interval;
+ } else {
+ delete form.option?.update_interval;
+ }
+ if (form.option?.user_agent === "") {
+ delete form.option.user_agent;
+ }
- // 处理表单数据
- if (form.option?.update_interval) {
- form.option.update_interval = +form.option.update_interval;
+ const name = form.name || `${form.type} file`;
+ const item = { ...form, name };
+ const isRemote = form.type === "remote";
+ const isUpdate = openType === "edit";
+
+ // 判断是否是当前激活的配置
+ const isActivating = isUpdate && form.uid === (profiles?.current ?? "");
+
+ // 保存原始代理设置以便回退成功后恢复
+ const originalOptions = {
+ with_proxy: form.option?.with_proxy,
+ self_proxy: form.option?.self_proxy,
+ };
+
+ // 执行创建或更新操作,本地配置不需要回退机制
+ if (!isRemote) {
+ if (openType === "new") {
+ await createProfile(item, fileDataRef.current);
} else {
- delete form.option?.update_interval;
+ if (!form.uid) throw new Error("UID not found");
+ await patchProfile(form.uid, item);
}
- if (form.option?.user_agent === "") {
- delete form.option.user_agent;
- }
-
- const name = form.name || `${form.type} file`;
- const item = { ...form, name };
- const isRemote = form.type === "remote";
- const isUpdate = openType === "edit";
-
- // 判断是否是当前激活的配置
- const isActivating =
- isUpdate && form.uid === (profiles?.current ?? "");
-
- // 保存原始代理设置以便回退成功后恢复
- const originalOptions = {
- with_proxy: form.option?.with_proxy,
- self_proxy: form.option?.self_proxy,
- };
-
- // 执行创建或更新操作,本地配置不需要回退机制
- if (!isRemote) {
+ } else {
+ // 远程配置使用回退机制
+ try {
+ // 尝试正常操作
if (openType === "new") {
await createProfile(item, fileDataRef.current);
} else {
if (!form.uid) throw new Error("UID not found");
await patchProfile(form.uid, item);
}
- } else {
- // 远程配置使用回退机制
- try {
- // 尝试正常操作
- if (openType === "new") {
- await createProfile(item, fileDataRef.current);
- } else {
- if (!form.uid) throw new Error("UID not found");
- await patchProfile(form.uid, item);
- }
- } catch {
- // 首次创建/更新失败,尝试使用自身代理
- showNotice(
- "info",
- t("Profile creation failed, retrying with Clash proxy..."),
- );
+ } catch {
+ // 首次创建/更新失败,尝试使用自身代理
+ showNotice(
+ "info",
+ t("Profile creation failed, retrying with Clash proxy..."),
+ );
- // 使用自身代理的配置
- const retryItem = {
- ...item,
- option: {
- ...item.option,
- with_proxy: false,
- self_proxy: true,
- },
- };
+ // 使用自身代理的配置
+ const retryItem = {
+ ...item,
+ option: {
+ ...item.option,
+ with_proxy: false,
+ self_proxy: true,
+ },
+ };
- // 使用自身代理再次尝试
- if (openType === "new") {
- await createProfile(retryItem, fileDataRef.current);
- } else {
- if (!form.uid) throw new Error("UID not found");
- await patchProfile(form.uid, retryItem);
+ // 使用自身代理再次尝试
+ if (openType === "new") {
+ await createProfile(retryItem, fileDataRef.current);
+ } else {
+ if (!form.uid) throw new Error("UID not found");
+ await patchProfile(form.uid, retryItem);
- // 编辑模式下恢复原始代理设置
- await patchProfile(form.uid, { option: originalOptions });
- }
-
- showNotice(
- "success",
- t("Profile creation succeeded with Clash proxy"),
- );
+ // 编辑模式下恢复原始代理设置
+ await patchProfile(form.uid, { option: originalOptions });
}
+
+ showNotice(
+ "success",
+ t("Profile creation succeeded with Clash proxy"),
+ );
}
-
- // 成功后的操作
- setOpen(false);
- setTimeout(() => formIns.reset(), 500);
- fileDataRef.current = null;
-
- // 优化:UI先关闭,异步通知父组件
- setTimeout(() => {
- props.onChange(isActivating);
- }, 0);
- } catch (err: any) {
- showNotice("error", err.message || err.toString());
- } finally {
- setLoading(false);
}
- }),
- );
- const handleClose = () => {
- try {
+ // 成功后的操作
setOpen(false);
- fileDataRef.current = null;
setTimeout(() => formIns.reset(), 500);
- } catch (e) {
- console.warn("[ProfileViewer] handleClose error:", e);
+ fileDataRef.current = null;
+
+ // 优化:UI先关闭,异步通知父组件
+ setTimeout(() => {
+ props.onChange(isActivating);
+ }, 0);
+ } catch (err: any) {
+ showNotice("error", err.message || err.toString());
+ } finally {
+ setLoading(false);
}
- };
+ }),
+ );
- const text = {
- fullWidth: true,
- size: "small",
- margin: "normal",
- variant: "outlined",
- autoComplete: "off",
- autoCorrect: "off",
- } as const;
+ const handleClose = () => {
+ try {
+ setOpen(false);
+ fileDataRef.current = null;
+ setTimeout(() => formIns.reset(), 500);
+ } catch (e) {
+ console.warn("[ProfileViewer] handleClose error:", e);
+ }
+ };
- const formType = watch("type");
- const isRemote = formType === "remote";
- const isLocal = formType === "local";
+ const text = {
+ fullWidth: true,
+ size: "small",
+ margin: "normal",
+ variant: "outlined",
+ autoComplete: "off",
+ autoCorrect: "off",
+ } as const;
- return (
-
- (
-
- {t("Type")}
-
-
- )}
- />
+ const formType = watch("type");
+ const isRemote = formType === "remote";
+ const isLocal = formType === "local";
- (
-
- )}
- />
-
- (
-
- )}
- />
-
- {isRemote && (
- <>
- (
-
- )}
- />
-
- (
-
- )}
- />
-
- (
-
- {t("seconds")}
-
- ),
- },
- }}
- />
- )}
- />
- >
+ return (
+
+ (
+
+ {t("Type")}
+
+
)}
+ />
- {(isRemote || isLocal) && (
+ (
+
+ )}
+ />
+
+ (
+
+ )}
+ />
+
+ {isRemote && (
+ <>
(
+
+ )}
+ />
+
+ (
+
+ )}
+ />
+
+ (
- {t("mins")}
+ {t("seconds")}
),
},
@@ -343,57 +313,79 @@ export const ProfileViewer = forwardRef(
/>
)}
/>
- )}
+ >
+ )}
- {isLocal && openType === "new" && (
- {
- formIns.setValue("name", formIns.getValues("name") || file.name);
- fileDataRef.current = val;
- }}
+ {(isRemote || isLocal) && (
+ (
+ {t("mins")}
+ ),
+ },
+ }}
+ />
+ )}
+ />
+ )}
+
+ {isLocal && openType === "new" && (
+ {
+ formIns.setValue("name", formIns.getValues("name") || file.name);
+ fileDataRef.current = val;
+ }}
+ />
+ )}
+
+ {isRemote && (
+ <>
+ (
+
+ {t("Use System Proxy")}
+
+
+ )}
/>
- )}
- {isRemote && (
- <>
- (
-
- {t("Use System Proxy")}
-
-
- )}
- />
+ (
+
+ {t("Use Clash Proxy")}
+
+
+ )}
+ />
- (
-
- {t("Use Clash Proxy")}
-
-
- )}
- />
-
- (
-
- {t("Accept Invalid Certs (Danger)")}
-
-
- )}
- />
- >
- )}
-
- );
- },
-);
+ (
+
+ {t("Accept Invalid Certs (Danger)")}
+
+
+ )}
+ />
+ >
+ )}
+
+ );
+};
const StyledBox = styled(Box)(() => ({
margin: "8px 0 8px 8px",
diff --git a/src/components/proxy/proxy-groups.tsx b/src/components/proxy/proxy-groups.tsx
index 476f5f90..33e6b553 100644
--- a/src/components/proxy/proxy-groups.tsx
+++ b/src/components/proxy/proxy-groups.tsx
@@ -1,23 +1,19 @@
+import { ExpandMoreRounded } from "@mui/icons-material";
import {
Box,
Snackbar,
Alert,
Chip,
- Stack,
Typography,
IconButton,
- Collapse,
Menu,
MenuItem,
- Divider,
- Button,
} from "@mui/material";
-import { ArchiveOutlined, ExpandMoreRounded } from "@mui/icons-material";
import { useLockFn } from "ahooks";
import { useRef, useState, useEffect, useCallback, useMemo } from "react";
-import useSWR from "swr";
import { useTranslation } from "react-i18next";
import { Virtuoso, type VirtuosoHandle } from "react-virtuoso";
+import useSWR from "swr";
import { useProxySelection } from "@/hooks/use-proxy-selection";
import { useVerge } from "@/hooks/use-verge";
@@ -34,8 +30,8 @@ import { BaseEmpty } from "../base";
import { ScrollTopButton } from "../layout/scroll-top-button";
import { ProxyChain } from "./proxy-chain";
-import { ProxyRender } from "./proxy-render";
import { ProxyGroupNavigator } from "./proxy-group-navigator";
+import { ProxyRender } from "./proxy-render";
import { useRenderList } from "./use-render-list";
interface Props {
diff --git a/src/components/setting/mods/backup-viewer.tsx b/src/components/setting/mods/backup-viewer.tsx
index 804f3541..8c03e4c7 100644
--- a/src/components/setting/mods/backup-viewer.tsx
+++ b/src/components/setting/mods/backup-viewer.tsx
@@ -1,16 +1,10 @@
import { Box, Paper, Divider } from "@mui/material";
import dayjs from "dayjs";
import customParseFormat from "dayjs/plugin/customParseFormat";
-import {
- forwardRef,
- useImperativeHandle,
- useState,
- useCallback,
- useMemo,
-} from "react";
+import { useImperativeHandle, useState, useCallback, useMemo } from "react";
import { useTranslation } from "react-i18next";
-import { BaseDialog, DialogRef } from "@/components/base";
+import { BaseDialog } from "@/components/base";
import { BaseLoadingOverlay } from "@/components/base";
import { listWebDavBackup } from "@/services/cmds";
@@ -25,7 +19,7 @@ dayjs.extend(customParseFormat);
const DATE_FORMAT = "YYYY-MM-DD_HH-mm-ss";
const FILENAME_PATTERN = /\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}/;
-export const BackupViewer = forwardRef((props, ref) => {
+export const BackupViewer = ({ ref, ...props }) => {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
@@ -131,4 +125,4 @@ export const BackupViewer = forwardRef((props, ref) => {
);
-});
+};
diff --git a/src/components/setting/mods/clash-core-viewer.tsx b/src/components/setting/mods/clash-core-viewer.tsx
index 869b5784..f41626de 100644
--- a/src/components/setting/mods/clash-core-viewer.tsx
+++ b/src/components/setting/mods/clash-core-viewer.tsx
@@ -12,11 +12,11 @@ import {
ListItemText,
} from "@mui/material";
import { useLockFn } from "ahooks";
-import { forwardRef, useImperativeHandle, useState } from "react";
+import { useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
import { mutate } from "swr";
-import { BaseDialog, DialogRef } from "@/components/base";
+import { BaseDialog } from "@/components/base";
import { useVerge } from "@/hooks/use-verge";
import { changeClashCore, restartCore } from "@/services/cmds";
import {
@@ -31,7 +31,7 @@ const VALID_CORE = [
{ name: "Mihomo Alpha", core: "verge-mihomo-alpha", chip: "Alpha Version" },
];
-export const ClashCoreViewer = forwardRef((props, ref) => {
+export const ClashCoreViewer = ({ ref, ...props }) => {
const { t } = useTranslation();
const { verge, mutateVerge } = useVerge();
@@ -169,4 +169,4 @@ export const ClashCoreViewer = forwardRef((props, ref) => {
);
-});
+};
diff --git a/src/components/setting/mods/clash-port-viewer.tsx b/src/components/setting/mods/clash-port-viewer.tsx
index ec495804..e0db3184 100644
--- a/src/components/setting/mods/clash-port-viewer.tsx
+++ b/src/components/setting/mods/clash-port-viewer.tsx
@@ -9,7 +9,7 @@ import {
TextField,
} from "@mui/material";
import { useLockFn, useRequest } from "ahooks";
-import { forwardRef, useImperativeHandle, useState } from "react";
+import { useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
import { BaseDialog, Switch } from "@/components/base";
@@ -30,10 +30,12 @@ interface ClashPortViewerRef {
const generateRandomPort = () =>
Math.floor(Math.random() * (65535 - 1025 + 1)) + 1025;
-export const ClashPortViewer = forwardRef<
- ClashPortViewerRef,
- ClashPortViewerProps
->((props, ref) => {
+export const ClashPortViewer = ({
+ ref,
+ ...props
+}: ClashPortViewerProps & {
+ ref?: React.RefObject;
+}) => {
const { t } = useTranslation();
const { clashInfo, patchInfo } = useClashInfo();
const { verge, patchVerge } = useVerge();
@@ -348,4 +350,4 @@ export const ClashPortViewer = forwardRef<
);
-});
+};
diff --git a/src/components/setting/mods/config-viewer.tsx b/src/components/setting/mods/config-viewer.tsx
index 26609caa..e68030f9 100644
--- a/src/components/setting/mods/config-viewer.tsx
+++ b/src/components/setting/mods/config-viewer.tsx
@@ -1,12 +1,11 @@
import { Box, Chip } from "@mui/material";
-import { forwardRef, useImperativeHandle, useState } from "react";
+import { useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
-import { DialogRef } from "@/components/base";
import { EditorViewer } from "@/components/profile/editor-viewer";
import { getRuntimeYaml } from "@/services/cmds";
-export const ConfigViewer = forwardRef((_, ref) => {
+export const ConfigViewer = ({ ref, ..._ }) => {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const [runtimeConfig, setRuntimeConfig] = useState("");
@@ -38,4 +37,4 @@ export const ConfigViewer = forwardRef((_, ref) => {
onClose={() => setOpen(false)}
/>
);
-});
+};
diff --git a/src/components/setting/mods/controller-viewer.tsx b/src/components/setting/mods/controller-viewer.tsx
index f39b928b..154ff523 100644
--- a/src/components/setting/mods/controller-viewer.tsx
+++ b/src/components/setting/mods/controller-viewer.tsx
@@ -12,15 +12,15 @@ import {
Tooltip,
} from "@mui/material";
import { useLockFn } from "ahooks";
-import { forwardRef, useImperativeHandle, useState } from "react";
+import { useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
-import { BaseDialog, DialogRef, Switch } from "@/components/base";
+import { BaseDialog, Switch } from "@/components/base";
import { useClashInfo } from "@/hooks/use-clash";
import { useVerge } from "@/hooks/use-verge";
import { showNotice } from "@/services/noticeService";
-export const ControllerViewer = forwardRef((props, ref) => {
+export const ControllerViewer = ({ ref, ...props }) => {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const [copySuccess, setCopySuccess] = useState(null);
@@ -217,4 +217,4 @@ export const ControllerViewer = forwardRef((props, ref) => {
);
-});
+};
diff --git a/src/components/setting/mods/dns-viewer.tsx b/src/components/setting/mods/dns-viewer.tsx
index f076dbc0..6f3cf071 100644
--- a/src/components/setting/mods/dns-viewer.tsx
+++ b/src/components/setting/mods/dns-viewer.tsx
@@ -15,11 +15,11 @@ import {
import { invoke } from "@tauri-apps/api/core";
import { useLockFn } from "ahooks";
import yaml from "js-yaml";
-import { forwardRef, useImperativeHandle, useState, useEffect } from "react";
+import { useImperativeHandle, useState, useEffect } from "react";
import { useTranslation } from "react-i18next";
import MonacoEditor from "react-monaco-editor";
-import { BaseDialog, DialogRef, Switch } from "@/components/base";
+import { BaseDialog, Switch } from "@/components/base";
import { useClash } from "@/hooks/use-clash";
import { showNotice } from "@/services/noticeService";
import { useThemeMode } from "@/services/states";
@@ -87,7 +87,7 @@ const DEFAULT_DNS_CONFIG = {
},
};
-export const DnsViewer = forwardRef((props, ref) => {
+export const DnsViewer = ({ ref, ...props }) => {
const { t } = useTranslation();
const { clash, mutateClash } = useClash();
const themeMode = useThemeMode();
@@ -1034,4 +1034,4 @@ export const DnsViewer = forwardRef((props, ref) => {
)}
);
-});
+};
diff --git a/src/components/setting/mods/external-controller-cors.tsx b/src/components/setting/mods/external-controller-cors.tsx
index d2522f35..355e316e 100644
--- a/src/components/setting/mods/external-controller-cors.tsx
+++ b/src/components/setting/mods/external-controller-cors.tsx
@@ -1,7 +1,7 @@
import { Delete as DeleteIcon } from "@mui/icons-material";
import { Box, Button, Divider, List, ListItem, TextField } from "@mui/material";
import { useLockFn, useRequest } from "ahooks";
-import { forwardRef, useImperativeHandle, useState } from "react";
+import { useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
import { BaseDialog, Switch } from "@/components/base";
@@ -71,201 +71,194 @@ interface ClashHeaderConfigingRef {
close: () => void;
}
-export const HeaderConfiguration = forwardRef(
- (props, ref) => {
- const { t } = useTranslation();
- const { clash, mutateClash, patchClash } = useClash();
- const [open, setOpen] = useState(false);
+export const HeaderConfiguration = ({ ref, ...props }) => {
+ const { t } = useTranslation();
+ const { clash, mutateClash, patchClash } = useClash();
+ const [open, setOpen] = useState(false);
- // CORS配置状态管理
- const [corsConfig, setCorsConfig] = useState<{
- allowPrivateNetwork: boolean;
- allowOrigins: string[];
- }>(() => {
+ // CORS配置状态管理
+ const [corsConfig, setCorsConfig] = useState<{
+ allowPrivateNetwork: boolean;
+ allowOrigins: string[];
+ }>(() => {
+ const cors = clash?.["external-controller-cors"];
+ const origins = cors?.["allow-origins"] ?? [];
+ return {
+ allowPrivateNetwork: cors?.["allow-private-network"] ?? true,
+ allowOrigins: filterBaseOriginsForUI(origins),
+ };
+ });
+
+ // 处理CORS配置变更
+ const handleCorsConfigChange = (
+ key: "allowPrivateNetwork" | "allowOrigins",
+ value: boolean | string[],
+ ) => {
+ setCorsConfig((prev) => ({
+ ...prev,
+ [key]: value,
+ }));
+ };
+
+ // 添加新的允许来源
+ const handleAddOrigin = () => {
+ handleCorsConfigChange("allowOrigins", [...corsConfig.allowOrigins, ""]);
+ };
+
+ // 更新允许来源列表中的某一项
+ const handleUpdateOrigin = (index: number, value: string) => {
+ const newOrigins = [...corsConfig.allowOrigins];
+ newOrigins[index] = value;
+ handleCorsConfigChange("allowOrigins", newOrigins);
+ };
+
+ // 删除允许来源列表中的某一项
+ const handleDeleteOrigin = (index: number) => {
+ const newOrigins = [...corsConfig.allowOrigins];
+ newOrigins.splice(index, 1);
+ handleCorsConfigChange("allowOrigins", newOrigins);
+ };
+
+ // 保存配置请求
+ const { loading, run: saveConfig } = useRequest(
+ async () => {
+ // 保存时使用完整的源列表(包括开发URL)
+ const fullOrigins = getFullOrigins(corsConfig.allowOrigins);
+
+ await patchClash({
+ "external-controller-cors": {
+ "allow-private-network": corsConfig.allowPrivateNetwork,
+ "allow-origins": fullOrigins.filter(
+ (origin: string) => origin.trim() !== "",
+ ),
+ },
+ });
+ await mutateClash();
+ },
+ {
+ manual: true,
+ onSuccess: () => {
+ setOpen(false);
+ showNotice("success", t("Configuration saved successfully"));
+ },
+ onError: () => {
+ showNotice("error", t("Failed to save configuration"));
+ },
+ },
+ );
+
+ useImperativeHandle(ref, () => ({
+ open: () => {
const cors = clash?.["external-controller-cors"];
const origins = cors?.["allow-origins"] ?? [];
- return {
+ setCorsConfig({
allowPrivateNetwork: cors?.["allow-private-network"] ?? true,
allowOrigins: filterBaseOriginsForUI(origins),
- };
- });
+ });
+ setOpen(true);
+ },
+ close: () => setOpen(false),
+ }));
- // 处理CORS配置变更
- const handleCorsConfigChange = (
- key: "allowPrivateNetwork" | "allowOrigins",
- value: boolean | string[],
- ) => {
- setCorsConfig((prev) => ({
- ...prev,
- [key]: value,
- }));
- };
+ const handleSave = useLockFn(async () => {
+ await saveConfig();
+ });
- // 添加新的允许来源
- const handleAddOrigin = () => {
- handleCorsConfigChange("allowOrigins", [...corsConfig.allowOrigins, ""]);
- };
+ return (
+ setOpen(false)}
+ onCancel={() => setOpen(false)}
+ onOk={handleSave}
+ >
+
+
+
+
+ {t("Allow private network access")}
+
+
+ handleCorsConfigChange("allowPrivateNetwork", e.target.checked)
+ }
+ />
+
+
- // 更新允许来源列表中的某一项
- const handleUpdateOrigin = (index: number, value: string) => {
- const newOrigins = [...corsConfig.allowOrigins];
- newOrigins[index] = value;
- handleCorsConfigChange("allowOrigins", newOrigins);
- };
-
- // 删除允许来源列表中的某一项
- const handleDeleteOrigin = (index: number) => {
- const newOrigins = [...corsConfig.allowOrigins];
- newOrigins.splice(index, 1);
- handleCorsConfigChange("allowOrigins", newOrigins);
- };
-
- // 保存配置请求
- const { loading, run: saveConfig } = useRequest(
- async () => {
- // 保存时使用完整的源列表(包括开发URL)
- const fullOrigins = getFullOrigins(corsConfig.allowOrigins);
-
- await patchClash({
- "external-controller-cors": {
- "allow-private-network": corsConfig.allowPrivateNetwork,
- "allow-origins": fullOrigins.filter(
- (origin: string) => origin.trim() !== "",
- ),
- },
- });
- await mutateClash();
- },
- {
- manual: true,
- onSuccess: () => {
- setOpen(false);
- showNotice("success", t("Configuration saved successfully"));
- },
- onError: () => {
- showNotice("error", t("Failed to save configuration"));
- },
- },
- );
-
- useImperativeHandle(ref, () => ({
- open: () => {
- const cors = clash?.["external-controller-cors"];
- const origins = cors?.["allow-origins"] ?? [];
- setCorsConfig({
- allowPrivateNetwork: cors?.["allow-private-network"] ?? true,
- allowOrigins: filterBaseOriginsForUI(origins),
- });
- setOpen(true);
- },
- close: () => setOpen(false),
- }));
-
- const handleSave = useLockFn(async () => {
- await saveConfig();
- });
-
- return (
- setOpen(false)}
- onCancel={() => setOpen(false)}
- onOk={handleSave}
- >
-
-
-
-
- {t("Allow private network access")}
-
-
- handleCorsConfigChange(
- "allowPrivateNetwork",
- e.target.checked,
- )
- }
- />
-
-
-
-
-
-
-
-
- {t("Allowed Origins")}
-
- {corsConfig.allowOrigins.map((origin, index) => (
-
- handleUpdateOrigin(index, e.target.value)}
- placeholder={t("Please enter a valid url")}
- inputProps={{ style: { fontSize: 14 } }}
- />
-
-
- ))}
-
+
+
+
+
+ {t("Allowed Origins")}
+
+ {corsConfig.allowOrigins.map((origin, index) => (
-
handleUpdateOrigin(index, e.target.value)}
+ placeholder={t("Please enter a valid url")}
+ inputProps={{ style: { fontSize: 14 } }}
+ />
+
+
+
+
+ ))}
+
+
+
+
+ {t("Always included origins: {{urls}}", {
+ urls: DEV_URLS.join(", "),
+ })}
-
-
-
- );
- },
-);
+
+
+
+
+ );
+};
diff --git a/src/components/setting/mods/hotkey-viewer.tsx b/src/components/setting/mods/hotkey-viewer.tsx
index cd09e244..8ea690fa 100644
--- a/src/components/setting/mods/hotkey-viewer.tsx
+++ b/src/components/setting/mods/hotkey-viewer.tsx
@@ -1,9 +1,9 @@
import { styled, Typography } from "@mui/material";
import { useLockFn } from "ahooks";
-import { forwardRef, useImperativeHandle, useState } from "react";
+import { useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
-import { BaseDialog, DialogRef, Switch } from "@/components/base";
+import { BaseDialog, Switch } from "@/components/base";
import { useVerge } from "@/hooks/use-verge";
import { showNotice } from "@/services/noticeService";
@@ -26,7 +26,7 @@ const HOTKEY_FUNC = [
"entry_lightweight_mode",
];
-export const HotkeyViewer = forwardRef
((props, ref) => {
+export const HotkeyViewer = ({ ref, ...props }) => {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
@@ -117,4 +117,4 @@ export const HotkeyViewer = forwardRef((props, ref) => {
))}
);
-});
+};
diff --git a/src/components/setting/mods/layout-viewer.tsx b/src/components/setting/mods/layout-viewer.tsx
index 5e0f1e25..80951552 100644
--- a/src/components/setting/mods/layout-viewer.tsx
+++ b/src/components/setting/mods/layout-viewer.tsx
@@ -12,10 +12,10 @@ import { convertFileSrc } from "@tauri-apps/api/core";
import { join } from "@tauri-apps/api/path";
import { open as openDialog } from "@tauri-apps/plugin-dialog";
import { exists } from "@tauri-apps/plugin-fs";
-import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
+import { useEffect, useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
-import { BaseDialog, DialogRef, Switch } from "@/components/base";
+import { BaseDialog, Switch } from "@/components/base";
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import { useVerge } from "@/hooks/use-verge";
import { copyIconFile, getAppDir } from "@/services/cmds";
@@ -38,7 +38,7 @@ const getIcons = async (icon_dir: string, name: string) => {
};
};
-export const LayoutViewer = forwardRef((props, ref) => {
+export const LayoutViewer = ({ ref, ...props }) => {
const { t } = useTranslation();
const { verge, patchVerge, mutateVerge } = useVerge();
@@ -387,7 +387,7 @@ export const LayoutViewer = forwardRef((props, ref) => {
);
-});
+};
const Item = styled(ListItem)(() => ({
padding: "5px 2px",
diff --git a/src/components/setting/mods/lite-mode-viewer.tsx b/src/components/setting/mods/lite-mode-viewer.tsx
index 6c243667..30f534a2 100644
--- a/src/components/setting/mods/lite-mode-viewer.tsx
+++ b/src/components/setting/mods/lite-mode-viewer.tsx
@@ -7,16 +7,16 @@ import {
InputAdornment,
} from "@mui/material";
import { useLockFn } from "ahooks";
-import { forwardRef, useImperativeHandle, useState } from "react";
+import { useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
-import { BaseDialog, DialogRef, Switch } from "@/components/base";
+import { BaseDialog, Switch } from "@/components/base";
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import { useVerge } from "@/hooks/use-verge";
import { entry_lightweight_mode } from "@/services/cmds";
import { showNotice } from "@/services/noticeService";
-export const LiteModeViewer = forwardRef((props, ref) => {
+export const LiteModeViewer = ({ ref, ...props }) => {
const { t } = useTranslation();
const { verge, patchVerge } = useVerge();
@@ -143,4 +143,4 @@ export const LiteModeViewer = forwardRef((props, ref) => {
);
-});
+};
diff --git a/src/components/setting/mods/misc-viewer.tsx b/src/components/setting/mods/misc-viewer.tsx
index b0048fd8..cee02882 100644
--- a/src/components/setting/mods/misc-viewer.tsx
+++ b/src/components/setting/mods/misc-viewer.tsx
@@ -8,15 +8,15 @@ import {
TextField,
} from "@mui/material";
import { useLockFn } from "ahooks";
-import { forwardRef, useImperativeHandle, useState } from "react";
+import { useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
-import { BaseDialog, DialogRef, Switch } from "@/components/base";
+import { BaseDialog, Switch } from "@/components/base";
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import { useVerge } from "@/hooks/use-verge";
import { showNotice } from "@/services/noticeService";
-export const MiscViewer = forwardRef((props, ref) => {
+export const MiscViewer = ({ ref, ...props }) => {
const { t } = useTranslation();
const { verge, patchVerge } = useVerge();
@@ -319,4 +319,4 @@ export const MiscViewer = forwardRef((props, ref) => {
);
-});
+};
diff --git a/src/components/setting/mods/network-interface-viewer.tsx b/src/components/setting/mods/network-interface-viewer.tsx
index 912529eb..5b7d0422 100644
--- a/src/components/setting/mods/network-interface-viewer.tsx
+++ b/src/components/setting/mods/network-interface-viewer.tsx
@@ -1,15 +1,15 @@
import { ContentCopyRounded } from "@mui/icons-material";
import { alpha, Box, Button, IconButton } from "@mui/material";
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
-import { forwardRef, useImperativeHandle, useState } from "react";
+import { useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
import useSWR from "swr";
-import { BaseDialog, DialogRef } from "@/components/base";
+import { BaseDialog } from "@/components/base";
import { getNetworkInterfacesInfo } from "@/services/cmds";
import { showNotice } from "@/services/noticeService";
-export const NetworkInterfaceViewer = forwardRef((props, ref) => {
+export const NetworkInterfaceViewer = ({ ref, ...props }) => {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const [isV4, setIsV4] = useState(true);
@@ -99,7 +99,7 @@ export const NetworkInterfaceViewer = forwardRef((props, ref) => {
))}
);
-});
+};
const AddressDisplay = (props: { label: string; content: string }) => {
const { t } = useTranslation();
diff --git a/src/components/setting/mods/sysproxy-viewer.tsx b/src/components/setting/mods/sysproxy-viewer.tsx
index 329d857e..9649a3f6 100644
--- a/src/components/setting/mods/sysproxy-viewer.tsx
+++ b/src/components/setting/mods/sysproxy-viewer.tsx
@@ -11,25 +11,19 @@ import {
Typography,
} from "@mui/material";
import { useLockFn } from "ahooks";
-import {
- forwardRef,
- useEffect,
- useImperativeHandle,
- useMemo,
- useState,
-} from "react";
+import { useEffect, useImperativeHandle, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import useSWR, { mutate } from "swr";
-import { BaseDialog, DialogRef, Switch } from "@/components/base";
+import { BaseDialog, Switch } from "@/components/base";
import { BaseFieldset } from "@/components/base/base-fieldset";
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import { EditorViewer } from "@/components/profile/editor-viewer";
import { useVerge } from "@/hooks/use-verge";
import { useAppData } from "@/providers/app-data-provider";
-import { getClashConfig } from "@/services/cmds";
import {
getAutotemProxy,
+ getClashConfig,
getNetworkInterfacesInfo,
getSystemHostname,
getSystemProxy,
@@ -75,7 +69,7 @@ const getValidReg = (isWindows: boolean) => {
return new RegExp(rValid);
};
-export const SysproxyViewer = forwardRef((props, ref) => {
+export const SysproxyViewer = ({ ref, ...props }) => {
const { t } = useTranslation();
const isWindows = getSystem() === "windows";
const validReg = useMemo(() => getValidReg(isWindows), [isWindows]);
@@ -619,7 +613,7 @@ export const SysproxyViewer = forwardRef((props, ref) => {
);
-});
+};
const FlexBox = styled("div")`
display: flex;
diff --git a/src/components/setting/mods/theme-viewer.tsx b/src/components/setting/mods/theme-viewer.tsx
index e3f764c5..f59fae53 100644
--- a/src/components/setting/mods/theme-viewer.tsx
+++ b/src/components/setting/mods/theme-viewer.tsx
@@ -9,16 +9,16 @@ import {
useTheme,
} from "@mui/material";
import { useLockFn } from "ahooks";
-import { forwardRef, useImperativeHandle, useState } from "react";
+import { useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
-import { BaseDialog, DialogRef } from "@/components/base";
+import { BaseDialog } from "@/components/base";
import { EditorViewer } from "@/components/profile/editor-viewer";
import { useVerge } from "@/hooks/use-verge";
import { defaultTheme, defaultDarkTheme } from "@/pages/_theme";
import { showNotice } from "@/services/noticeService";
-export const ThemeViewer = forwardRef((props, ref) => {
+export const ThemeViewer = ({ ref, ...props }) => {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
@@ -144,7 +144,7 @@ export const ThemeViewer = forwardRef((props, ref) => {
);
-});
+};
const Item = styled(ListItem)(() => ({
padding: "5px 2px",
diff --git a/src/components/setting/mods/tun-viewer.tsx b/src/components/setting/mods/tun-viewer.tsx
index cd361cc9..d3667573 100644
--- a/src/components/setting/mods/tun-viewer.tsx
+++ b/src/components/setting/mods/tun-viewer.tsx
@@ -8,10 +8,10 @@ import {
TextField,
} from "@mui/material";
import { useLockFn } from "ahooks";
-import { forwardRef, useImperativeHandle, useState } from "react";
+import { useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
-import { BaseDialog, DialogRef, Switch } from "@/components/base";
+import { BaseDialog, Switch } from "@/components/base";
import { useClash } from "@/hooks/use-clash";
import { enhanceProfiles } from "@/services/cmds";
import { showNotice } from "@/services/noticeService";
@@ -21,7 +21,7 @@ import { StackModeSwitch } from "./stack-mode-switch";
const OS = getSystem();
-export const TunViewer = forwardRef((props, ref) => {
+export const TunViewer = ({ ref, ...props }) => {
const { t } = useTranslation();
const { clash, mutateClash, patchClash } = useClash();
@@ -238,4 +238,4 @@ export const TunViewer = forwardRef((props, ref) => {
);
-});
+};
diff --git a/src/components/setting/mods/update-viewer.tsx b/src/components/setting/mods/update-viewer.tsx
index 3b67e175..a2fa367e 100644
--- a/src/components/setting/mods/update-viewer.tsx
+++ b/src/components/setting/mods/update-viewer.tsx
@@ -4,24 +4,18 @@ import { relaunch } from "@tauri-apps/plugin-process";
import { open as openUrl } from "@tauri-apps/plugin-shell";
import { check as checkUpdate } from "@tauri-apps/plugin-updater";
import { useLockFn } from "ahooks";
-import {
- forwardRef,
- useImperativeHandle,
- useState,
- useMemo,
- useEffect,
-} from "react";
+import { useImperativeHandle, useState, useMemo, useEffect } from "react";
import { useTranslation } from "react-i18next";
import ReactMarkdown from "react-markdown";
import useSWR from "swr";
-import { BaseDialog, DialogRef } from "@/components/base";
+import { BaseDialog } from "@/components/base";
import { useListen } from "@/hooks/use-listen";
import { portableFlag } from "@/pages/_layout";
import { showNotice } from "@/services/noticeService";
import { useUpdateState, useSetUpdateState } from "@/services/states";
-export const UpdateViewer = forwardRef((props, ref) => {
+export const UpdateViewer = ({ ref, ...props }) => {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
@@ -167,4 +161,4 @@ export const UpdateViewer = forwardRef((props, ref) => {
)}
);
-});
+};
diff --git a/src/components/setting/mods/web-ui-viewer.tsx b/src/components/setting/mods/web-ui-viewer.tsx
index e82035fc..655be1e9 100644
--- a/src/components/setting/mods/web-ui-viewer.tsx
+++ b/src/components/setting/mods/web-ui-viewer.tsx
@@ -1,9 +1,9 @@
import { Button, Box, Typography } from "@mui/material";
import { useLockFn } from "ahooks";
-import { forwardRef, useImperativeHandle, useState } from "react";
+import { useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
-import { BaseDialog, BaseEmpty, DialogRef } from "@/components/base";
+import { BaseDialog, BaseEmpty } from "@/components/base";
import { useClashInfo } from "@/hooks/use-clash";
import { useVerge } from "@/hooks/use-verge";
import { openWebUrl } from "@/services/cmds";
@@ -11,7 +11,7 @@ import { showNotice } from "@/services/noticeService";
import { WebUIItem } from "./web-ui-item";
-export const WebUIViewer = forwardRef((props, ref) => {
+export const WebUIViewer = ({ ref, ...props }) => {
const { t } = useTranslation();
const { clashInfo } = useClashInfo();
@@ -139,4 +139,4 @@ export const WebUIViewer = forwardRef((props, ref) => {
)}
);
-});
+};
diff --git a/src/components/test/test-viewer.tsx b/src/components/test/test-viewer.tsx
index 0962c424..56453bb9 100644
--- a/src/components/test/test-viewer.tsx
+++ b/src/components/test/test-viewer.tsx
@@ -1,7 +1,7 @@
import { TextField } from "@mui/material";
import { useLockFn } from "ahooks";
import { nanoid } from "nanoid";
-import { forwardRef, useImperativeHandle, useState } from "react";
+import { useImperativeHandle, useState } from "react";
import { useForm, Controller } from "react-hook-form";
import { useTranslation } from "react-i18next";
@@ -19,7 +19,10 @@ export interface TestViewerRef {
}
// create or edit the test item
-export const TestViewer = forwardRef((props, ref) => {
+export const TestViewer = ({
+ ref,
+ ...props
+}: Props & { ref?: React.RefObject }) => {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const [openType, setOpenType] = useState<"new" | "edit">("new");
@@ -173,4 +176,4 @@ export const TestViewer = forwardRef((props, ref) => {
/>
);
-});
+};
diff --git a/src/providers/app-data-provider.tsx b/src/providers/app-data-provider.tsx
index 16ad3962..8abd1380 100644
--- a/src/providers/app-data-provider.tsx
+++ b/src/providers/app-data-provider.tsx
@@ -1,11 +1,5 @@
import { listen } from "@tauri-apps/api/event";
-import React, {
- createContext,
- useContext,
- useEffect,
- useMemo,
- useRef,
-} from "react";
+import React, { createContext, use, useEffect, useMemo, useRef } from "react";
import useSWR from "swr";
import { useClashInfo } from "@/hooks/use-clash";
@@ -589,14 +583,12 @@ export const AppDataProvider = ({
refreshAll,
]);
- return (
- {children}
- );
+ return {children};
};
// 自定义Hook访问全局数据
export const useAppData = () => {
- const context = useContext(AppDataContext);
+ const context = use(AppDataContext);
if (!context) {
throw new Error("useAppData必须在AppDataProvider内使用");
diff --git a/src/providers/chain-proxy-provider.tsx b/src/providers/chain-proxy-provider.tsx
index 2b834f89..c8ccce1d 100644
--- a/src/providers/chain-proxy-provider.tsx
+++ b/src/providers/chain-proxy-provider.tsx
@@ -1,4 +1,4 @@
-import React, { createContext, useCallback, useContext, useState } from "react";
+import React, { createContext, useCallback, use, useState } from "react";
interface ChainProxyContextType {
isChainMode: boolean;
@@ -26,7 +26,7 @@ export const ChainProxyProvider = ({
}, []);
return (
-
{children}
-
+
);
};
export const useChainProxy = () => {
- const context = useContext(ChainProxyContext);
+ const context = use(ChainProxyContext);
if (!context) {
throw new Error("useChainProxy must be used within a ChainProxyProvider");
}
From 14288568bffd55a86044491fcb37ddbd50137acf Mon Sep 17 00:00:00 2001
From: Tunglies <77394545+Tunglies@users.noreply.github.com>
Date: Tue, 30 Sep 2025 15:22:08 +0800
Subject: [PATCH 09/10] feat: optimize backend i18n resource usage and improve
language loading
---
UPDATELOG.md | 1 +
src-tauri/src/utils/i18n.rs | 65 +++++++++++++++++++++----------------
2 files changed, 38 insertions(+), 28 deletions(-)
diff --git a/UPDATELOG.md b/UPDATELOG.md
index 204e1195..c8688679 100644
--- a/UPDATELOG.md
+++ b/UPDATELOG.md
@@ -16,6 +16,7 @@
- 改进 macos 下系统代理设置的方法
- 优化 TUN 模式可用性的判断
- 移除流媒体检测的系统级提示(使用软件内通知)
+- 优化后端 i18n 资源占用
### 🐞 修复问题
diff --git a/src-tauri/src/utils/i18n.rs b/src-tauri/src/utils/i18n.rs
index dc4c8859..9f80c563 100644
--- a/src-tauri/src/utils/i18n.rs
+++ b/src-tauri/src/utils/i18n.rs
@@ -1,7 +1,7 @@
use crate::{config::Config, utils::dirs};
use once_cell::sync::Lazy;
use serde_json::Value;
-use std::{collections::HashMap, fs, path::PathBuf};
+use std::{fs, path::PathBuf, sync::RwLock};
use sys_locale;
const DEFAULT_LANGUAGE: &str = "zh";
@@ -33,22 +33,20 @@ pub fn get_supported_languages() -> Vec {
languages
}
-static TRANSLATIONS: Lazy> = Lazy::new(|| {
- let mut translations = HashMap::new();
-
- if let Some(locales_dir) = get_locales_dir() {
- for lang in get_supported_languages() {
- let file_path = locales_dir.join(format!("{lang}.json"));
- if let Ok(content) = fs::read_to_string(file_path)
- && let Ok(json) = serde_json::from_str(&content)
- {
- translations.insert(lang.to_string(), json);
- }
- }
- }
- translations
+static TRANSLATIONS: Lazy> = Lazy::new(|| {
+ let lang = get_system_language();
+ let json = load_lang_file(&lang).unwrap_or_else(|| Value::Object(Default::default()));
+ RwLock::new((lang, json))
});
+fn load_lang_file(lang: &str) -> Option {
+ let locales_dir = get_locales_dir()?;
+ let file_path = locales_dir.join(format!("{lang}.json"));
+ fs::read_to_string(file_path)
+ .ok()
+ .and_then(|content| serde_json::from_str(&content).ok())
+}
+
fn get_system_language() -> String {
sys_locale::get_locale()
.map(|locale| locale.to_lowercase())
@@ -58,8 +56,6 @@ fn get_system_language() -> String {
}
pub async fn t(key: &str) -> String {
- let key = key.to_string(); // own the string
-
let current_lang = Config::verge()
.await
.latest_ref()
@@ -68,22 +64,35 @@ pub async fn t(key: &str) -> String {
.map(String::from)
.unwrap_or_else(get_system_language);
- if let Some(text) = TRANSLATIONS
- .get(¤t_lang)
- .and_then(|trans| trans.get(&key))
- .and_then(|val| val.as_str())
{
- return text.to_string();
+ if let Ok(cache) = TRANSLATIONS.read()
+ && cache.0 == current_lang
+ && let Some(text) = cache.1.get(key).and_then(|val| val.as_str())
+ {
+ return text.to_string();
+ }
+ }
+
+ if let Some(new_json) = load_lang_file(¤t_lang)
+ && let Ok(mut cache) = TRANSLATIONS.write()
+ {
+ *cache = (current_lang.clone(), new_json);
+
+ if let Some(text) = cache.1.get(key).and_then(|val| val.as_str()) {
+ return text.to_string();
+ }
}
if current_lang != DEFAULT_LANGUAGE
- && let Some(text) = TRANSLATIONS
- .get(DEFAULT_LANGUAGE)
- .and_then(|trans| trans.get(&key))
- .and_then(|val| val.as_str())
+ && let Some(default_json) = load_lang_file(DEFAULT_LANGUAGE)
+ && let Ok(mut cache) = TRANSLATIONS.write()
{
- return text.to_string();
+ *cache = (DEFAULT_LANGUAGE.to_string(), default_json);
+
+ if let Some(text) = cache.1.get(key).and_then(|val| val.as_str()) {
+ return text.to_string();
+ }
}
- key
+ key.to_string()
}
From 8a4f2de88775bb3833628f64e820b666c74d9b65 Mon Sep 17 00:00:00 2001
From: Tunglies <77394545+Tunglies@users.noreply.github.com>
Date: Tue, 30 Sep 2025 18:13:02 +0800
Subject: [PATCH 10/10] Revert "Refactor components to remove forwardRef and
simplify props handling"
This reverts commit 1cd013fb94b4e0be37bc966e5401c2c9b9bb0bb4.
---
.../connection/connection-detail.tsx | 72 +-
.../home/enhanced-canvas-traffic-graph.tsx | 1634 +++++++++--------
src/components/layout/traffic-graph.tsx | 6 +-
src/components/profile/profile-viewer.tsx | 608 +++---
src/components/proxy/proxy-groups.tsx | 10 +-
src/components/setting/mods/backup-viewer.tsx | 14 +-
.../setting/mods/clash-core-viewer.tsx | 8 +-
.../setting/mods/clash-port-viewer.tsx | 14 +-
src/components/setting/mods/config-viewer.tsx | 7 +-
.../setting/mods/controller-viewer.tsx | 8 +-
src/components/setting/mods/dns-viewer.tsx | 8 +-
.../setting/mods/external-controller-cors.tsx | 359 ++--
src/components/setting/mods/hotkey-viewer.tsx | 8 +-
src/components/setting/mods/layout-viewer.tsx | 8 +-
.../setting/mods/lite-mode-viewer.tsx | 8 +-
src/components/setting/mods/misc-viewer.tsx | 8 +-
.../setting/mods/network-interface-viewer.tsx | 8 +-
.../setting/mods/sysproxy-viewer.tsx | 16 +-
src/components/setting/mods/theme-viewer.tsx | 8 +-
src/components/setting/mods/tun-viewer.tsx | 8 +-
src/components/setting/mods/update-viewer.tsx | 14 +-
src/components/setting/mods/web-ui-viewer.tsx | 8 +-
src/components/test/test-viewer.tsx | 9 +-
src/providers/app-data-provider.tsx | 14 +-
src/providers/chain-proxy-provider.tsx | 8 +-
25 files changed, 1474 insertions(+), 1399 deletions(-)
diff --git a/src/components/connection/connection-detail.tsx b/src/components/connection/connection-detail.tsx
index fe563c0e..9eb0f563 100644
--- a/src/components/connection/connection-detail.tsx
+++ b/src/components/connection/connection-detail.tsx
@@ -2,7 +2,7 @@ import { Box, Button, Snackbar, useTheme } from "@mui/material";
import { useLockFn } from "ahooks";
import dayjs from "dayjs";
import { t } from "i18next";
-import { useImperativeHandle, useState } from "react";
+import { forwardRef, useImperativeHandle, useState } from "react";
import { deleteConnection } from "@/services/cmds";
import parseTraffic from "@/utils/parse-traffic";
@@ -11,43 +11,45 @@ export interface ConnectionDetailRef {
open: (detail: IConnectionsItem) => void;
}
-export const ConnectionDetail = ({ ref, ...props }) => {
- const [open, setOpen] = useState(false);
- const [detail, setDetail] = useState(null!);
- const theme = useTheme();
+export const ConnectionDetail = forwardRef(
+ (props, ref) => {
+ const [open, setOpen] = useState(false);
+ const [detail, setDetail] = useState(null!);
+ const theme = useTheme();
- useImperativeHandle(ref, () => ({
- open: (detail: IConnectionsItem) => {
- if (open) return;
- setOpen(true);
- setDetail(detail);
- },
- }));
+ useImperativeHandle(ref, () => ({
+ open: (detail: IConnectionsItem) => {
+ if (open) return;
+ setOpen(true);
+ setDetail(detail);
+ },
+ }));
- const onClose = () => setOpen(false);
+ const onClose = () => setOpen(false);
- return (
-
- ) : null
- }
- />
- );
-};
+ return (
+
+ ) : null
+ }
+ />
+ );
+ },
+);
interface InnerProps {
data: IConnectionsItem;
diff --git a/src/components/home/enhanced-canvas-traffic-graph.tsx b/src/components/home/enhanced-canvas-traffic-graph.tsx
index 9ccd5cc9..2f8a4719 100644
--- a/src/components/home/enhanced-canvas-traffic-graph.tsx
+++ b/src/components/home/enhanced-canvas-traffic-graph.tsx
@@ -1,5 +1,6 @@
import { Box, useTheme } from "@mui/material";
import {
+ forwardRef,
useImperativeHandle,
useState,
useEffect,
@@ -80,525 +81,578 @@ const GRAPH_CONFIG = {
* 稳定版Canvas流量图表组件
* 修复闪烁问题,添加时间轴显示
*/
-export const EnhancedCanvasTrafficGraph = memo(({ ref, ...props }) => {
- const theme = useTheme();
- const { t } = useTranslation();
+export const EnhancedCanvasTrafficGraph = memo(
+ forwardRef((props, ref) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
- // 使用增强版全局流量数据管理
- const { dataPoints, getDataForTimeRange, isDataFresh, samplerStats } =
- useTrafficGraphDataEnhanced();
+ // 使用增强版全局流量数据管理
+ const { dataPoints, getDataForTimeRange, isDataFresh, samplerStats } =
+ useTrafficGraphDataEnhanced();
- // 基础状态
- const [timeRange, setTimeRange] = useState(10);
- const [chartStyle, setChartStyle] = useState<"bezier" | "line">("bezier");
+ // 基础状态
+ const [timeRange, setTimeRange] = useState(10);
+ const [chartStyle, setChartStyle] = useState<"bezier" | "line">("bezier");
- // 悬浮提示状态
- const [tooltipData, setTooltipData] = useState({
- x: 0,
- y: 0,
- upSpeed: "",
- downSpeed: "",
- timestamp: "",
- visible: false,
- dataIndex: -1,
- highlightY: 0,
- });
+ // 悬浮提示状态
+ const [tooltipData, setTooltipData] = useState({
+ x: 0,
+ y: 0,
+ upSpeed: "",
+ downSpeed: "",
+ timestamp: "",
+ visible: false,
+ dataIndex: -1,
+ highlightY: 0,
+ });
- // Canvas引用和渲染状态
- const canvasRef = useRef(null);
- const animationFrameRef = useRef(undefined);
- const lastRenderTimeRef = useRef(0);
- const isInitializedRef = useRef(false);
+ // Canvas引用和渲染状态
+ const canvasRef = useRef(null);
+ const animationFrameRef = useRef(undefined);
+ const lastRenderTimeRef = useRef(0);
+ const isInitializedRef = useRef(false);
- // 当前显示的数据缓存
- const [displayData, setDisplayData] = useState([]);
+ // 当前显示的数据缓存
+ const [displayData, setDisplayData] = useState([]);
- // 主题颜色配置
- const colors = useMemo(
- () => ({
- up: theme.palette.secondary.main,
- down: theme.palette.primary.main,
- grid: theme.palette.divider,
- text: theme.palette.text.secondary,
- background: theme.palette.background.paper,
- }),
- [theme],
- );
+ // 主题颜色配置
+ const colors = useMemo(
+ () => ({
+ up: theme.palette.secondary.main,
+ down: theme.palette.primary.main,
+ grid: theme.palette.divider,
+ text: theme.palette.text.secondary,
+ background: theme.palette.background.paper,
+ }),
+ [theme],
+ );
- // 更新显示数据(防抖处理)
- const updateDisplayDataDebounced = useMemo(() => {
- let timeoutId: number;
- return (newData: ITrafficDataPoint[]) => {
- clearTimeout(timeoutId);
- timeoutId = window.setTimeout(() => {
- setDisplayData(newData);
- }, 50); // 50ms防抖
- };
- }, []);
-
- // 监听数据变化
- useEffect(() => {
- const timeRangeData = getDataForTimeRange(timeRange);
- updateDisplayDataDebounced(timeRangeData);
- }, [dataPoints, timeRange, getDataForTimeRange, updateDisplayDataDebounced]);
-
- // Y轴坐标计算 - 基于刻度范围的线性映射
- const calculateY = useCallback(
- (value: number, height: number, data: ITrafficDataPoint[]): number => {
- const padding = GRAPH_CONFIG.padding;
- const topY = padding.top + 10; // 与刻度系统保持一致
- const bottomY = height - padding.bottom - 5;
-
- if (data.length === 0) return bottomY;
-
- // 获取当前的刻度范围
- const allValues = [...data.map((d) => d.up), ...data.map((d) => d.down)];
- const maxValue = Math.max(...allValues);
- const minValue = Math.min(...allValues);
-
- let topValue, bottomValue;
-
- if (maxValue === 0) {
- topValue = 1024;
- bottomValue = 0;
- } else {
- const range = maxValue - minValue;
- const padding_percent = range > 0 ? 0.1 : 0.5;
-
- if (range === 0) {
- bottomValue = 0;
- topValue = maxValue * 1.2;
- } else {
- bottomValue = Math.max(0, minValue - range * padding_percent);
- topValue = maxValue + range * padding_percent;
- }
- }
-
- // 线性映射到Y坐标
- if (topValue === bottomValue) return bottomY;
-
- const ratio = (value - bottomValue) / (topValue - bottomValue);
- return bottomY - ratio * (bottomY - topY);
- },
- [],
- );
-
- // 鼠标悬浮处理 - 计算最近的数据点
- const handleMouseMove = useCallback(
- (event: React.MouseEvent) => {
- const canvas = canvasRef.current;
- if (!canvas || displayData.length === 0) return;
-
- const rect = canvas.getBoundingClientRect();
- const mouseX = event.clientX - rect.left;
- const mouseY = event.clientY - rect.top;
-
- const padding = GRAPH_CONFIG.padding;
- const effectiveWidth = rect.width - padding.left - padding.right;
-
- // 计算最接近的数据点索引
- const relativeMouseX = mouseX - padding.left;
- const ratio = Math.max(0, Math.min(1, relativeMouseX / effectiveWidth));
- const dataIndex = Math.round(ratio * (displayData.length - 1));
-
- if (dataIndex >= 0 && dataIndex < displayData.length) {
- const dataPoint = displayData[dataIndex];
-
- // 格式化流量数据
- const [upValue, upUnit] = parseTraffic(dataPoint.up);
- const [downValue, downUnit] = parseTraffic(dataPoint.down);
-
- // 格式化时间戳
- const timeStr = dataPoint.timestamp
- ? new Date(dataPoint.timestamp).toLocaleTimeString("zh-CN", {
- hour: "2-digit",
- minute: "2-digit",
- second: "2-digit",
- })
- : "未知时间";
-
- // 计算数据点对应的Y坐标位置(用于高亮)
- const upY = calculateY(dataPoint.up, rect.height, displayData);
- const downY = calculateY(dataPoint.down, rect.height, displayData);
- const highlightY =
- Math.max(dataPoint.up, dataPoint.down) === dataPoint.up ? upY : downY;
-
- setTooltipData({
- x: mouseX,
- y: mouseY,
- upSpeed: `${upValue}${upUnit}/s`,
- downSpeed: `${downValue}${downUnit}/s`,
- timestamp: timeStr,
- visible: true,
- dataIndex,
- highlightY,
- });
- }
- },
- [displayData, calculateY],
- );
-
- // 鼠标离开处理
- const handleMouseLeave = useCallback(() => {
- setTooltipData((prev) => ({ ...prev, visible: false }));
- }, []);
-
- // 获取智能Y轴刻度(三刻度系统:最小值、中间值、最大值)
- const getYAxisTicks = useCallback(
- (data: ITrafficDataPoint[], height: number) => {
- if (data.length === 0) return [];
-
- // 找到数据的最大值和最小值
- const allValues = [...data.map((d) => d.up), ...data.map((d) => d.down)];
- const maxValue = Math.max(...allValues);
- const minValue = Math.min(...allValues);
-
- // 格式化流量数值
- const formatTrafficValue = (bytes: number): string => {
- if (bytes === 0) return "0";
- if (bytes < 1024) return `${Math.round(bytes)}B`;
- if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)}KB`;
- return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
+ // 更新显示数据(防抖处理)
+ const updateDisplayDataDebounced = useMemo(() => {
+ let timeoutId: number;
+ return (newData: ITrafficDataPoint[]) => {
+ clearTimeout(timeoutId);
+ timeoutId = window.setTimeout(() => {
+ setDisplayData(newData);
+ }, 50); // 50ms防抖
};
+ }, []);
- const padding = GRAPH_CONFIG.padding;
+ // 监听数据变化
+ useEffect(() => {
+ const timeRangeData = getDataForTimeRange(timeRange);
+ updateDisplayDataDebounced(timeRangeData);
+ }, [
+ dataPoints,
+ timeRange,
+ getDataForTimeRange,
+ updateDisplayDataDebounced,
+ ]);
- // 强制显示三个刻度:底部、中间、顶部
- const topY = padding.top + 10; // 避免与顶部时间范围按钮重叠
- const bottomY = height - padding.bottom - 5; // 避免与底部时间轴重叠
- const middleY = (topY + bottomY) / 2;
+ // Y轴坐标计算 - 基于刻度范围的线性映射
+ const calculateY = useCallback(
+ (value: number, height: number, data: ITrafficDataPoint[]): number => {
+ const padding = GRAPH_CONFIG.padding;
+ const topY = padding.top + 10; // 与刻度系统保持一致
+ const bottomY = height - padding.bottom - 5;
- // 计算对应的值
- let topValue, middleValue, bottomValue;
+ if (data.length === 0) return bottomY;
- if (maxValue === 0) {
- // 如果没有流量,显示0到一个小值的范围
- topValue = 1024; // 1KB
- middleValue = 512; // 512B
- bottomValue = 0;
- } else {
- // 根据数据范围计算合适的刻度值
- const range = maxValue - minValue;
- const padding_percent = range > 0 ? 0.1 : 0.5; // 如果范围为0,使用更大的边距
+ // 获取当前的刻度范围
+ const allValues = [
+ ...data.map((d) => d.up),
+ ...data.map((d) => d.down),
+ ];
+ const maxValue = Math.max(...allValues);
+ const minValue = Math.min(...allValues);
- if (range === 0) {
- // 所有值相同的情况
+ let topValue, bottomValue;
+
+ if (maxValue === 0) {
+ topValue = 1024;
bottomValue = 0;
- middleValue = maxValue * 0.5;
- topValue = maxValue * 1.2;
} else {
- // 正常情况
- bottomValue = Math.max(0, minValue - range * padding_percent);
- topValue = maxValue + range * padding_percent;
- middleValue = (bottomValue + topValue) / 2;
+ const range = maxValue - minValue;
+ const padding_percent = range > 0 ? 0.1 : 0.5;
+
+ if (range === 0) {
+ bottomValue = 0;
+ topValue = maxValue * 1.2;
+ } else {
+ bottomValue = Math.max(0, minValue - range * padding_percent);
+ topValue = maxValue + range * padding_percent;
+ }
}
- }
- // 创建三个固定位置的刻度
- const ticks = [
- {
- value: bottomValue,
- label: formatTrafficValue(bottomValue),
- y: bottomY,
- },
- {
- value: middleValue,
- label: formatTrafficValue(middleValue),
- y: middleY,
- },
- {
- value: topValue,
- label: formatTrafficValue(topValue),
- y: topY,
- },
- ];
+ // 线性映射到Y坐标
+ if (topValue === bottomValue) return bottomY;
- return ticks;
- },
- [],
- );
+ const ratio = (value - bottomValue) / (topValue - bottomValue);
+ return bottomY - ratio * (bottomY - topY);
+ },
+ [],
+ );
- // 绘制Y轴刻度线和标签
- const drawYAxis = useCallback(
- (
- ctx: CanvasRenderingContext2D,
- width: number,
- height: number,
- data: ITrafficDataPoint[],
- ) => {
- const padding = GRAPH_CONFIG.padding;
- const ticks = getYAxisTicks(data, height);
+ // 鼠标悬浮处理 - 计算最近的数据点
+ const handleMouseMove = useCallback(
+ (event: React.MouseEvent) => {
+ const canvas = canvasRef.current;
+ if (!canvas || displayData.length === 0) return;
- if (ticks.length === 0) return;
+ const rect = canvas.getBoundingClientRect();
+ const mouseX = event.clientX - rect.left;
+ const mouseY = event.clientY - rect.top;
- ctx.save();
+ const padding = GRAPH_CONFIG.padding;
+ const effectiveWidth = rect.width - padding.left - padding.right;
- ticks.forEach((tick, index) => {
- const isBottomTick = index === 0; // 最底部的刻度
- const isTopTick = index === ticks.length - 1; // 最顶部的刻度
+ // 计算最接近的数据点索引
+ const relativeMouseX = mouseX - padding.left;
+ const ratio = Math.max(0, Math.min(1, relativeMouseX / effectiveWidth));
+ const dataIndex = Math.round(ratio * (displayData.length - 1));
- // 绘制水平刻度线,只绘制关键刻度线
- if (isBottomTick || isTopTick) {
- ctx.strokeStyle = colors.grid;
- ctx.lineWidth = isBottomTick ? 0.8 : 0.4; // 底部刻度线稍粗
- ctx.globalAlpha = isBottomTick ? 0.25 : 0.15;
+ if (dataIndex >= 0 && dataIndex < displayData.length) {
+ const dataPoint = displayData[dataIndex];
+ // 格式化流量数据
+ const [upValue, upUnit] = parseTraffic(dataPoint.up);
+ const [downValue, downUnit] = parseTraffic(dataPoint.down);
+
+ // 格式化时间戳
+ const timeStr = dataPoint.timestamp
+ ? new Date(dataPoint.timestamp).toLocaleTimeString("zh-CN", {
+ hour: "2-digit",
+ minute: "2-digit",
+ second: "2-digit",
+ })
+ : "未知时间";
+
+ // 计算数据点对应的Y坐标位置(用于高亮)
+ const upY = calculateY(dataPoint.up, rect.height, displayData);
+ const downY = calculateY(dataPoint.down, rect.height, displayData);
+ const highlightY =
+ Math.max(dataPoint.up, dataPoint.down) === dataPoint.up
+ ? upY
+ : downY;
+
+ setTooltipData({
+ x: mouseX,
+ y: mouseY,
+ upSpeed: `${upValue}${upUnit}/s`,
+ downSpeed: `${downValue}${downUnit}/s`,
+ timestamp: timeStr,
+ visible: true,
+ dataIndex,
+ highlightY,
+ });
+ }
+ },
+ [displayData, calculateY],
+ );
+
+ // 鼠标离开处理
+ const handleMouseLeave = useCallback(() => {
+ setTooltipData((prev) => ({ ...prev, visible: false }));
+ }, []);
+
+ // 获取智能Y轴刻度(三刻度系统:最小值、中间值、最大值)
+ const getYAxisTicks = useCallback(
+ (data: ITrafficDataPoint[], height: number) => {
+ if (data.length === 0) return [];
+
+ // 找到数据的最大值和最小值
+ const allValues = [
+ ...data.map((d) => d.up),
+ ...data.map((d) => d.down),
+ ];
+ const maxValue = Math.max(...allValues);
+ const minValue = Math.min(...allValues);
+
+ // 格式化流量数值
+ const formatTrafficValue = (bytes: number): string => {
+ if (bytes === 0) return "0";
+ if (bytes < 1024) return `${Math.round(bytes)}B`;
+ if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)}KB`;
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
+ };
+
+ const padding = GRAPH_CONFIG.padding;
+
+ // 强制显示三个刻度:底部、中间、顶部
+ const topY = padding.top + 10; // 避免与顶部时间范围按钮重叠
+ const bottomY = height - padding.bottom - 5; // 避免与底部时间轴重叠
+ const middleY = (topY + bottomY) / 2;
+
+ // 计算对应的值
+ let topValue, middleValue, bottomValue;
+
+ if (maxValue === 0) {
+ // 如果没有流量,显示0到一个小值的范围
+ topValue = 1024; // 1KB
+ middleValue = 512; // 512B
+ bottomValue = 0;
+ } else {
+ // 根据数据范围计算合适的刻度值
+ const range = maxValue - minValue;
+ const padding_percent = range > 0 ? 0.1 : 0.5; // 如果范围为0,使用更大的边距
+
+ if (range === 0) {
+ // 所有值相同的情况
+ bottomValue = 0;
+ middleValue = maxValue * 0.5;
+ topValue = maxValue * 1.2;
+ } else {
+ // 正常情况
+ bottomValue = Math.max(0, minValue - range * padding_percent);
+ topValue = maxValue + range * padding_percent;
+ middleValue = (bottomValue + topValue) / 2;
+ }
+ }
+
+ // 创建三个固定位置的刻度
+ const ticks = [
+ {
+ value: bottomValue,
+ label: formatTrafficValue(bottomValue),
+ y: bottomY,
+ },
+ {
+ value: middleValue,
+ label: formatTrafficValue(middleValue),
+ y: middleY,
+ },
+ {
+ value: topValue,
+ label: formatTrafficValue(topValue),
+ y: topY,
+ },
+ ];
+
+ return ticks;
+ },
+ [],
+ );
+
+ // 绘制Y轴刻度线和标签
+ const drawYAxis = useCallback(
+ (
+ ctx: CanvasRenderingContext2D,
+ width: number,
+ height: number,
+ data: ITrafficDataPoint[],
+ ) => {
+ const padding = GRAPH_CONFIG.padding;
+ const ticks = getYAxisTicks(data, height);
+
+ if (ticks.length === 0) return;
+
+ ctx.save();
+
+ ticks.forEach((tick, index) => {
+ const isBottomTick = index === 0; // 最底部的刻度
+ const isTopTick = index === ticks.length - 1; // 最顶部的刻度
+
+ // 绘制水平刻度线,只绘制关键刻度线
+ if (isBottomTick || isTopTick) {
+ ctx.strokeStyle = colors.grid;
+ ctx.lineWidth = isBottomTick ? 0.8 : 0.4; // 底部刻度线稍粗
+ ctx.globalAlpha = isBottomTick ? 0.25 : 0.15;
+
+ ctx.beginPath();
+ ctx.moveTo(padding.left, tick.y);
+ ctx.lineTo(width - padding.right, tick.y);
+ ctx.stroke();
+ }
+
+ // 绘制Y轴标签
+ ctx.fillStyle = colors.text;
+ ctx.font =
+ "8px -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif";
+ ctx.globalAlpha = 0.9;
+ ctx.textAlign = "right";
+ ctx.textBaseline = "middle";
+
+ // 为标签添加更清晰的背景(仅在必要时)
+ if (tick.label !== "0") {
+ const labelWidth = ctx.measureText(tick.label).width;
+ ctx.globalAlpha = 0.15;
+ ctx.fillStyle = colors.background;
+ ctx.fillRect(
+ padding.left - labelWidth - 8,
+ tick.y - 5,
+ labelWidth + 4,
+ 10,
+ );
+ }
+
+ // 绘制标签文字
+ ctx.globalAlpha = 0.9;
+ ctx.fillStyle = colors.text;
+ ctx.fillText(tick.label, padding.left - 4, tick.y);
+ });
+
+ ctx.restore();
+ },
+ [colors.grid, colors.text, colors.background, getYAxisTicks],
+ );
+
+ // 获取时间范围对应的最佳时间显示策略
+ const getTimeDisplayStrategy = useCallback(
+ (timeRangeMinutes: TimeRange) => {
+ switch (timeRangeMinutes) {
+ case 1: // 1分钟:更密集的时间标签,显示 MM:SS
+ return {
+ maxLabels: 6, // 减少到6个,更适合短时间
+ formatTime: (timestamp: number) => {
+ const date = new Date(timestamp);
+ const minutes = date.getMinutes().toString().padStart(2, "0");
+ const seconds = date.getSeconds().toString().padStart(2, "0");
+ return `${minutes}:${seconds}`; // 显示 MM:SS
+ },
+ intervalSeconds: 10, // 每10秒一个标签,更合理
+ minPixelDistance: 35, // 减少间距,允许更多标签
+ };
+ case 5: // 5分钟:中等密度,显示 HH:MM
+ return {
+ maxLabels: 6, // 6个标签比较合适
+ formatTime: (timestamp: number) => {
+ const date = new Date(timestamp);
+ return date.toLocaleTimeString("en-US", {
+ hour12: false,
+ hour: "2-digit",
+ minute: "2-digit",
+ }); // 显示 HH:MM
+ },
+ intervalSeconds: 30, // 约30秒间隔
+ minPixelDistance: 38, // 减少间距,允许更多标签
+ };
+ case 10: // 10分钟:标准密度,显示 HH:MM
+ default:
+ return {
+ maxLabels: 8, // 保持8个
+ formatTime: (timestamp: number) => {
+ const date = new Date(timestamp);
+ return date.toLocaleTimeString("en-US", {
+ hour12: false,
+ hour: "2-digit",
+ minute: "2-digit",
+ }); // 显示 HH:MM
+ },
+ intervalSeconds: 60, // 1分钟间隔
+ minPixelDistance: 40, // 减少间距,允许更多标签
+ };
+ }
+ },
+ [],
+ );
+
+ // 绘制时间轴
+ const drawTimeAxis = useCallback(
+ (
+ ctx: CanvasRenderingContext2D,
+ width: number,
+ height: number,
+ data: ITrafficDataPoint[],
+ ) => {
+ if (data.length === 0) return;
+
+ const padding = GRAPH_CONFIG.padding;
+ const effectiveWidth = width - padding.left - padding.right;
+ const timeAxisY = height - padding.bottom + 14;
+
+ const strategy = getTimeDisplayStrategy(timeRange);
+
+ ctx.save();
+ ctx.fillStyle = colors.text;
+ ctx.font =
+ "10px -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif";
+ ctx.globalAlpha = 0.7;
+
+ // 根据数据长度和时间范围智能选择显示间隔
+ const targetLabels = Math.min(strategy.maxLabels, data.length);
+ const step = Math.max(1, Math.floor(data.length / (targetLabels - 1)));
+
+ // 使用策略中定义的最小像素间距
+ const minPixelDistance = strategy.minPixelDistance || 45;
+ const actualStep = Math.max(
+ step,
+ Math.ceil((data.length * minPixelDistance) / effectiveWidth),
+ );
+
+ // 收集要显示的时间点
+ const timePoints: Array<{ index: number; x: number; label: string }> =
+ [];
+
+ // 添加第一个时间点
+ if (data.length > 0 && data[0].timestamp) {
+ timePoints.push({
+ index: 0,
+ x: padding.left,
+ label: strategy.formatTime(data[0].timestamp),
+ });
+ }
+
+ // 添加中间的时间点
+ for (
+ let i = actualStep;
+ i < data.length - actualStep;
+ i += actualStep
+ ) {
+ const point = data[i];
+ if (!point.timestamp) continue;
+
+ const x = padding.left + (i / (data.length - 1)) * effectiveWidth;
+ timePoints.push({
+ index: i,
+ x,
+ label: strategy.formatTime(point.timestamp),
+ });
+ }
+
+ // 添加最后一个时间点(如果不会与前面的重叠)
+ if (data.length > 1 && data[data.length - 1].timestamp) {
+ const lastX = width - padding.right;
+ const lastPoint = timePoints[timePoints.length - 1];
+
+ // 确保最后一个标签与前一个标签有足够间距
+ if (!lastPoint || lastX - lastPoint.x >= minPixelDistance) {
+ timePoints.push({
+ index: data.length - 1,
+ x: lastX,
+ label: strategy.formatTime(data[data.length - 1].timestamp),
+ });
+ }
+ }
+
+ // 绘制时间标签
+ timePoints.forEach((point, index) => {
+ if (index === 0) {
+ // 第一个标签左对齐
+ ctx.textAlign = "left";
+ } else if (index === timePoints.length - 1) {
+ // 最后一个标签右对齐
+ ctx.textAlign = "right";
+ } else {
+ // 中间标签居中对齐
+ ctx.textAlign = "center";
+ }
+
+ ctx.fillText(point.label, point.x, timeAxisY);
+ });
+
+ ctx.restore();
+ },
+ [colors.text, timeRange, getTimeDisplayStrategy],
+ );
+
+ // 绘制网格线
+ const drawGrid = useCallback(
+ (ctx: CanvasRenderingContext2D, width: number, height: number) => {
+ const padding = GRAPH_CONFIG.padding;
+ const effectiveWidth = width - padding.left - padding.right;
+ const effectiveHeight = height - padding.top - padding.bottom;
+
+ ctx.save();
+ ctx.strokeStyle = colors.grid;
+ ctx.lineWidth = GRAPH_CONFIG.lineWidth.grid;
+ ctx.globalAlpha = 0.7;
+
+ // 水平网格线
+ const horizontalLines = 4;
+ for (let i = 1; i <= horizontalLines; i++) {
+ const y = padding.top + (effectiveHeight / (horizontalLines + 1)) * i;
ctx.beginPath();
- ctx.moveTo(padding.left, tick.y);
- ctx.lineTo(width - padding.right, tick.y);
+ ctx.moveTo(padding.left, y);
+ ctx.lineTo(width - padding.right, y);
ctx.stroke();
}
- // 绘制Y轴标签
- ctx.fillStyle = colors.text;
- ctx.font =
- "8px -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif";
- ctx.globalAlpha = 0.9;
- ctx.textAlign = "right";
- ctx.textBaseline = "middle";
+ // 垂直网格线
+ const verticalLines = 6;
+ for (let i = 1; i <= verticalLines; i++) {
+ const x = padding.left + (effectiveWidth / (verticalLines + 1)) * i;
+ ctx.beginPath();
+ ctx.moveTo(x, padding.top);
+ ctx.lineTo(x, height - padding.bottom);
+ ctx.stroke();
+ }
- // 为标签添加更清晰的背景(仅在必要时)
- if (tick.label !== "0") {
- const labelWidth = ctx.measureText(tick.label).width;
- ctx.globalAlpha = 0.15;
- ctx.fillStyle = colors.background;
- ctx.fillRect(
- padding.left - labelWidth - 8,
- tick.y - 5,
- labelWidth + 4,
- 10,
+ ctx.restore();
+ },
+ [colors.grid],
+ );
+
+ // 绘制流量线条
+ const drawTrafficLine = useCallback(
+ (
+ ctx: CanvasRenderingContext2D,
+ values: number[],
+ width: number,
+ height: number,
+ color: string,
+ withGradient = false,
+ data: ITrafficDataPoint[],
+ ) => {
+ if (values.length < 2) return;
+
+ const padding = GRAPH_CONFIG.padding;
+ const effectiveWidth = width - padding.left - padding.right;
+
+ const points = values.map((value, index) => [
+ padding.left + (index / (values.length - 1)) * effectiveWidth,
+ calculateY(value, height, data),
+ ]);
+
+ ctx.save();
+
+ // 绘制渐变填充
+ if (withGradient && chartStyle === "bezier") {
+ const gradient = ctx.createLinearGradient(
+ 0,
+ padding.top,
+ 0,
+ height - padding.bottom,
);
+ gradient.addColorStop(
+ 0,
+ `${color}${Math.round(GRAPH_CONFIG.alpha.gradient * 255)
+ .toString(16)
+ .padStart(2, "0")}`,
+ );
+ gradient.addColorStop(1, `${color}00`);
+
+ ctx.beginPath();
+ ctx.moveTo(points[0][0], points[0][1]);
+
+ if (chartStyle === "bezier") {
+ for (let i = 1; i < points.length; i++) {
+ const current = points[i];
+ const next = points[i + 1] || current;
+ const controlX = (current[0] + next[0]) / 2;
+ const controlY = (current[1] + next[1]) / 2;
+ ctx.quadraticCurveTo(current[0], current[1], controlX, controlY);
+ }
+ } else {
+ for (let i = 1; i < points.length; i++) {
+ ctx.lineTo(points[i][0], points[i][1]);
+ }
+ }
+
+ ctx.lineTo(points[points.length - 1][0], height - padding.bottom);
+ ctx.lineTo(points[0][0], height - padding.bottom);
+ ctx.closePath();
+ ctx.fillStyle = gradient;
+ ctx.fill();
}
- // 绘制标签文字
- ctx.globalAlpha = 0.9;
- ctx.fillStyle = colors.text;
- ctx.fillText(tick.label, padding.left - 4, tick.y);
- });
-
- ctx.restore();
- },
- [colors.grid, colors.text, colors.background, getYAxisTicks],
- );
-
- // 获取时间范围对应的最佳时间显示策略
- const getTimeDisplayStrategy = useCallback((timeRangeMinutes: TimeRange) => {
- switch (timeRangeMinutes) {
- case 1: // 1分钟:更密集的时间标签,显示 MM:SS
- return {
- maxLabels: 6, // 减少到6个,更适合短时间
- formatTime: (timestamp: number) => {
- const date = new Date(timestamp);
- const minutes = date.getMinutes().toString().padStart(2, "0");
- const seconds = date.getSeconds().toString().padStart(2, "0");
- return `${minutes}:${seconds}`; // 显示 MM:SS
- },
- intervalSeconds: 10, // 每10秒一个标签,更合理
- minPixelDistance: 35, // 减少间距,允许更多标签
- };
- case 5: // 5分钟:中等密度,显示 HH:MM
- return {
- maxLabels: 6, // 6个标签比较合适
- formatTime: (timestamp: number) => {
- const date = new Date(timestamp);
- return date.toLocaleTimeString("en-US", {
- hour12: false,
- hour: "2-digit",
- minute: "2-digit",
- }); // 显示 HH:MM
- },
- intervalSeconds: 30, // 约30秒间隔
- minPixelDistance: 38, // 减少间距,允许更多标签
- };
- case 10: // 10分钟:标准密度,显示 HH:MM
- default:
- return {
- maxLabels: 8, // 保持8个
- formatTime: (timestamp: number) => {
- const date = new Date(timestamp);
- return date.toLocaleTimeString("en-US", {
- hour12: false,
- hour: "2-digit",
- minute: "2-digit",
- }); // 显示 HH:MM
- },
- intervalSeconds: 60, // 1分钟间隔
- minPixelDistance: 40, // 减少间距,允许更多标签
- };
- }
- }, []);
-
- // 绘制时间轴
- const drawTimeAxis = useCallback(
- (
- ctx: CanvasRenderingContext2D,
- width: number,
- height: number,
- data: ITrafficDataPoint[],
- ) => {
- if (data.length === 0) return;
-
- const padding = GRAPH_CONFIG.padding;
- const effectiveWidth = width - padding.left - padding.right;
- const timeAxisY = height - padding.bottom + 14;
-
- const strategy = getTimeDisplayStrategy(timeRange);
-
- ctx.save();
- ctx.fillStyle = colors.text;
- ctx.font =
- "10px -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif";
- ctx.globalAlpha = 0.7;
-
- // 根据数据长度和时间范围智能选择显示间隔
- const targetLabels = Math.min(strategy.maxLabels, data.length);
- const step = Math.max(1, Math.floor(data.length / (targetLabels - 1)));
-
- // 使用策略中定义的最小像素间距
- const minPixelDistance = strategy.minPixelDistance || 45;
- const actualStep = Math.max(
- step,
- Math.ceil((data.length * minPixelDistance) / effectiveWidth),
- );
-
- // 收集要显示的时间点
- const timePoints: Array<{ index: number; x: number; label: string }> = [];
-
- // 添加第一个时间点
- if (data.length > 0 && data[0].timestamp) {
- timePoints.push({
- index: 0,
- x: padding.left,
- label: strategy.formatTime(data[0].timestamp),
- });
- }
-
- // 添加中间的时间点
- for (let i = actualStep; i < data.length - actualStep; i += actualStep) {
- const point = data[i];
- if (!point.timestamp) continue;
-
- const x = padding.left + (i / (data.length - 1)) * effectiveWidth;
- timePoints.push({
- index: i,
- x,
- label: strategy.formatTime(point.timestamp),
- });
- }
-
- // 添加最后一个时间点(如果不会与前面的重叠)
- if (data.length > 1 && data[data.length - 1].timestamp) {
- const lastX = width - padding.right;
- const lastPoint = timePoints[timePoints.length - 1];
-
- // 确保最后一个标签与前一个标签有足够间距
- if (!lastPoint || lastX - lastPoint.x >= minPixelDistance) {
- timePoints.push({
- index: data.length - 1,
- x: lastX,
- label: strategy.formatTime(data[data.length - 1].timestamp),
- });
- }
- }
-
- // 绘制时间标签
- timePoints.forEach((point, index) => {
- if (index === 0) {
- // 第一个标签左对齐
- ctx.textAlign = "left";
- } else if (index === timePoints.length - 1) {
- // 最后一个标签右对齐
- ctx.textAlign = "right";
- } else {
- // 中间标签居中对齐
- ctx.textAlign = "center";
- }
-
- ctx.fillText(point.label, point.x, timeAxisY);
- });
-
- ctx.restore();
- },
- [colors.text, timeRange, getTimeDisplayStrategy],
- );
-
- // 绘制网格线
- const drawGrid = useCallback(
- (ctx: CanvasRenderingContext2D, width: number, height: number) => {
- const padding = GRAPH_CONFIG.padding;
- const effectiveWidth = width - padding.left - padding.right;
- const effectiveHeight = height - padding.top - padding.bottom;
-
- ctx.save();
- ctx.strokeStyle = colors.grid;
- ctx.lineWidth = GRAPH_CONFIG.lineWidth.grid;
- ctx.globalAlpha = 0.7;
-
- // 水平网格线
- const horizontalLines = 4;
- for (let i = 1; i <= horizontalLines; i++) {
- const y = padding.top + (effectiveHeight / (horizontalLines + 1)) * i;
+ // 绘制主线条
ctx.beginPath();
- ctx.moveTo(padding.left, y);
- ctx.lineTo(width - padding.right, y);
- ctx.stroke();
- }
+ ctx.strokeStyle = color;
+ ctx.lineWidth = GRAPH_CONFIG.lineWidth.up;
+ ctx.lineCap = "round";
+ ctx.lineJoin = "round";
+ ctx.globalAlpha = GRAPH_CONFIG.alpha.line;
- // 垂直网格线
- const verticalLines = 6;
- for (let i = 1; i <= verticalLines; i++) {
- const x = padding.left + (effectiveWidth / (verticalLines + 1)) * i;
- ctx.beginPath();
- ctx.moveTo(x, padding.top);
- ctx.lineTo(x, height - padding.bottom);
- ctx.stroke();
- }
-
- ctx.restore();
- },
- [colors.grid],
- );
-
- // 绘制流量线条
- const drawTrafficLine = useCallback(
- (
- ctx: CanvasRenderingContext2D,
- values: number[],
- width: number,
- height: number,
- color: string,
- withGradient = false,
- data: ITrafficDataPoint[],
- ) => {
- if (values.length < 2) return;
-
- const padding = GRAPH_CONFIG.padding;
- const effectiveWidth = width - padding.left - padding.right;
-
- const points = values.map((value, index) => [
- padding.left + (index / (values.length - 1)) * effectiveWidth,
- calculateY(value, height, data),
- ]);
-
- ctx.save();
-
- // 绘制渐变填充
- if (withGradient && chartStyle === "bezier") {
- const gradient = ctx.createLinearGradient(
- 0,
- padding.top,
- 0,
- height - padding.bottom,
- );
- gradient.addColorStop(
- 0,
- `${color}${Math.round(GRAPH_CONFIG.alpha.gradient * 255)
- .toString(16)
- .padStart(2, "0")}`,
- );
- gradient.addColorStop(1, `${color}00`);
-
- ctx.beginPath();
ctx.moveTo(points[0][0], points[0][1]);
if (chartStyle === "bezier") {
@@ -615,359 +669,337 @@ export const EnhancedCanvasTrafficGraph = memo(({ ref, ...props }) => {
}
}
- ctx.lineTo(points[points.length - 1][0], height - padding.bottom);
- ctx.lineTo(points[0][0], height - padding.bottom);
- ctx.closePath();
- ctx.fillStyle = gradient;
- ctx.fill();
+ ctx.stroke();
+ ctx.restore();
+ },
+ [calculateY, chartStyle],
+ );
+
+ // 主绘制函数
+ const drawGraph = useCallback(() => {
+ const canvas = canvasRef.current;
+ if (!canvas || displayData.length === 0) return;
+
+ const ctx = canvas.getContext("2d");
+ if (!ctx) return;
+
+ // Canvas尺寸设置
+ const rect = canvas.getBoundingClientRect();
+ const dpr = window.devicePixelRatio || 1;
+ const width = rect.width;
+ const height = rect.height;
+
+ // 只在尺寸变化时重新设置Canvas
+ if (canvas.width !== width * dpr || canvas.height !== height * dpr) {
+ canvas.width = width * dpr;
+ canvas.height = height * dpr;
+ ctx.scale(dpr, dpr);
+ canvas.style.width = width + "px";
+ canvas.style.height = height + "px";
}
- // 绘制主线条
- ctx.beginPath();
- ctx.strokeStyle = color;
- ctx.lineWidth = GRAPH_CONFIG.lineWidth.up;
- ctx.lineCap = "round";
- ctx.lineJoin = "round";
- ctx.globalAlpha = GRAPH_CONFIG.alpha.line;
+ // 清空画布
+ ctx.clearRect(0, 0, width, height);
- ctx.moveTo(points[0][0], points[0][1]);
+ // 绘制Y轴刻度线(背景层)
+ drawYAxis(ctx, width, height, displayData);
- if (chartStyle === "bezier") {
- for (let i = 1; i < points.length; i++) {
- const current = points[i];
- const next = points[i + 1] || current;
- const controlX = (current[0] + next[0]) / 2;
- const controlY = (current[1] + next[1]) / 2;
- ctx.quadraticCurveTo(current[0], current[1], controlX, controlY);
- }
- } else {
- for (let i = 1; i < points.length; i++) {
- ctx.lineTo(points[i][0], points[i][1]);
- }
+ // 绘制网格
+ drawGrid(ctx, width, height);
+
+ // 绘制时间轴
+ drawTimeAxis(ctx, width, height, displayData);
+
+ // 提取流量数据
+ const upValues = displayData.map((d) => d.up);
+ const downValues = displayData.map((d) => d.down);
+
+ // 绘制下载线(背景层)
+ drawTrafficLine(
+ ctx,
+ downValues,
+ width,
+ height,
+ colors.down,
+ true,
+ displayData,
+ );
+
+ // 绘制上传线(前景层)
+ drawTrafficLine(
+ ctx,
+ upValues,
+ width,
+ height,
+ colors.up,
+ true,
+ displayData,
+ );
+
+ // 绘制悬浮高亮线
+ if (tooltipData.visible && tooltipData.dataIndex >= 0) {
+ const padding = GRAPH_CONFIG.padding;
+ const effectiveWidth = width - padding.left - padding.right;
+ const dataX =
+ padding.left +
+ (tooltipData.dataIndex / (displayData.length - 1)) * effectiveWidth;
+
+ ctx.save();
+ ctx.strokeStyle = colors.text;
+ ctx.lineWidth = 1;
+ ctx.globalAlpha = 0.6;
+ ctx.setLineDash([4, 4]); // 虚线效果
+
+ // 绘制垂直指示线
+ ctx.beginPath();
+ ctx.moveTo(dataX, padding.top);
+ ctx.lineTo(dataX, height - padding.bottom);
+ ctx.stroke();
+
+ // 绘制水平指示线(高亮Y轴位置)
+ ctx.beginPath();
+ ctx.moveTo(padding.left, tooltipData.highlightY);
+ ctx.lineTo(width - padding.right, tooltipData.highlightY);
+ ctx.stroke();
+
+ ctx.restore();
}
- ctx.stroke();
- ctx.restore();
- },
- [calculateY, chartStyle],
- );
-
- // 主绘制函数
- const drawGraph = useCallback(() => {
- const canvas = canvasRef.current;
- if (!canvas || displayData.length === 0) return;
-
- const ctx = canvas.getContext("2d");
- if (!ctx) return;
-
- // Canvas尺寸设置
- const rect = canvas.getBoundingClientRect();
- const dpr = window.devicePixelRatio || 1;
- const width = rect.width;
- const height = rect.height;
-
- // 只在尺寸变化时重新设置Canvas
- if (canvas.width !== width * dpr || canvas.height !== height * dpr) {
- canvas.width = width * dpr;
- canvas.height = height * dpr;
- ctx.scale(dpr, dpr);
- canvas.style.width = width + "px";
- canvas.style.height = height + "px";
- }
-
- // 清空画布
- ctx.clearRect(0, 0, width, height);
-
- // 绘制Y轴刻度线(背景层)
- drawYAxis(ctx, width, height, displayData);
-
- // 绘制网格
- drawGrid(ctx, width, height);
-
- // 绘制时间轴
- drawTimeAxis(ctx, width, height, displayData);
-
- // 提取流量数据
- const upValues = displayData.map((d) => d.up);
- const downValues = displayData.map((d) => d.down);
-
- // 绘制下载线(背景层)
- drawTrafficLine(
- ctx,
- downValues,
- width,
- height,
- colors.down,
- true,
+ isInitializedRef.current = true;
+ }, [
displayData,
+ colors,
+ drawYAxis,
+ drawGrid,
+ drawTimeAxis,
+ drawTrafficLine,
+ tooltipData,
+ ]);
+
+ // 受控的动画循环
+ useEffect(() => {
+ const animate = (currentTime: number) => {
+ // 控制帧率,减少不必要的重绘
+ if (
+ currentTime - lastRenderTimeRef.current >=
+ 1000 / GRAPH_CONFIG.targetFPS
+ ) {
+ drawGraph();
+ lastRenderTimeRef.current = currentTime;
+ }
+ animationFrameRef.current = requestAnimationFrame(animate);
+ };
+
+ // 只有在有数据时才开始动画
+ if (displayData.length > 0) {
+ animationFrameRef.current = requestAnimationFrame(animate);
+ }
+
+ return () => {
+ if (animationFrameRef.current) {
+ cancelAnimationFrame(animationFrameRef.current);
+ }
+ };
+ }, [drawGraph, displayData.length]);
+
+ // 切换时间范围
+ const handleTimeRangeClick = useCallback((event: React.MouseEvent) => {
+ event.stopPropagation();
+ setTimeRange((prev) => {
+ return prev === 1 ? 5 : prev === 5 ? 10 : 1;
+ });
+ }, []);
+
+ // 切换图表样式
+ const toggleStyle = useCallback(() => {
+ setChartStyle((prev) => (prev === "bezier" ? "line" : "bezier"));
+ }, []);
+
+ // 兼容性方法
+ const appendData = useCallback((data: ITrafficItem) => {
+ console.log(
+ "[EnhancedCanvasTrafficGraphV2] appendData called (using global data):",
+ data,
+ );
+ }, []);
+
+ // 暴露方法给父组件
+ useImperativeHandle(
+ ref,
+ () => ({
+ appendData,
+ toggleStyle,
+ }),
+ [appendData, toggleStyle],
);
- // 绘制上传线(前景层)
- drawTrafficLine(ctx, upValues, width, height, colors.up, true, displayData);
+ // 获取时间范围文本
+ const getTimeRangeText = useCallback(() => {
+ return t("{{time}} Minutes", { time: timeRange });
+ }, [timeRange, t]);
- // 绘制悬浮高亮线
- if (tooltipData.visible && tooltipData.dataIndex >= 0) {
- const padding = GRAPH_CONFIG.padding;
- const effectiveWidth = width - padding.left - padding.right;
- const dataX =
- padding.left +
- (tooltipData.dataIndex / (displayData.length - 1)) * effectiveWidth;
-
- ctx.save();
- ctx.strokeStyle = colors.text;
- ctx.lineWidth = 1;
- ctx.globalAlpha = 0.6;
- ctx.setLineDash([4, 4]); // 虚线效果
-
- // 绘制垂直指示线
- ctx.beginPath();
- ctx.moveTo(dataX, padding.top);
- ctx.lineTo(dataX, height - padding.bottom);
- ctx.stroke();
-
- // 绘制水平指示线(高亮Y轴位置)
- ctx.beginPath();
- ctx.moveTo(padding.left, tooltipData.highlightY);
- ctx.lineTo(width - padding.right, tooltipData.highlightY);
- ctx.stroke();
-
- ctx.restore();
- }
-
- isInitializedRef.current = true;
- }, [
- displayData,
- colors,
- drawYAxis,
- drawGrid,
- drawTimeAxis,
- drawTrafficLine,
- tooltipData,
- ]);
-
- // 受控的动画循环
- useEffect(() => {
- const animate = (currentTime: number) => {
- // 控制帧率,减少不必要的重绘
- if (
- currentTime - lastRenderTimeRef.current >=
- 1000 / GRAPH_CONFIG.targetFPS
- ) {
- drawGraph();
- lastRenderTimeRef.current = currentTime;
- }
- animationFrameRef.current = requestAnimationFrame(animate);
- };
-
- // 只有在有数据时才开始动画
- if (displayData.length > 0) {
- animationFrameRef.current = requestAnimationFrame(animate);
- }
-
- return () => {
- if (animationFrameRef.current) {
- cancelAnimationFrame(animationFrameRef.current);
- }
- };
- }, [drawGraph, displayData.length]);
-
- // 切换时间范围
- const handleTimeRangeClick = useCallback((event: React.MouseEvent) => {
- event.stopPropagation();
- setTimeRange((prev) => {
- return prev === 1 ? 5 : prev === 5 ? 10 : 1;
- });
- }, []);
-
- // 切换图表样式
- const toggleStyle = useCallback(() => {
- setChartStyle((prev) => (prev === "bezier" ? "line" : "bezier"));
- }, []);
-
- // 兼容性方法
- const appendData = useCallback((data: ITrafficItem) => {
- console.log(
- "[EnhancedCanvasTrafficGraphV2] appendData called (using global data):",
- data,
- );
- }, []);
-
- // 暴露方法给父组件
- useImperativeHandle(
- ref,
- () => ({
- appendData,
- toggleStyle,
- }),
- [appendData, toggleStyle],
- );
-
- // 获取时间范围文本
- const getTimeRangeText = useCallback(() => {
- return t("{{time}} Minutes", { time: timeRange });
- }, [timeRange, t]);
-
- return (
-
-
-
- {/* 控制层覆盖 */}
+ return (
- {/* 时间范围按钮 */}
-
- {getTimeRangeText()}
-
+ onMouseMove={handleMouseMove}
+ onMouseLeave={handleMouseLeave}
+ />
- {/* 图例 */}
+ {/* 控制层覆盖 */}
+ {/* 时间范围按钮 */}
- {t("Upload")}
+ {getTimeRangeText()}
-
- {t("Download")}
-
-
- {/* 样式指示器 */}
-
- {chartStyle === "bezier" ? "Smooth" : "Linear"}
-
-
- {/* 数据统计指示器(左下角) */}
-
- Points: {displayData.length} | Fresh: {isDataFresh ? "✓" : "✗"} |
- Compressed: {samplerStats.compressedBufferSize}
-
-
- {/* 悬浮提示框 */}
- {tooltipData.visible && (
+ {/* 图例 */}
200 ? "translateX(-100%)" : "translateX(0)",
- boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
- backdropFilter: "none",
- opacity: 1,
+ top: 6,
+ right: 8,
+ display: "flex",
+ flexDirection: "column",
+ gap: 0.5,
}}
>
-
- {tooltipData.timestamp}
+
+ {t("Upload")}
-
- ↑ {tooltipData.upSpeed}
-
-
- ↓ {tooltipData.downSpeed}
+
+ {t("Download")}
- )}
+
+ {/* 样式指示器 */}
+
+ {chartStyle === "bezier" ? "Smooth" : "Linear"}
+
+
+ {/* 数据统计指示器(左下角) */}
+
+ Points: {displayData.length} | Fresh: {isDataFresh ? "✓" : "✗"} |
+ Compressed: {samplerStats.compressedBufferSize}
+
+
+ {/* 悬浮提示框 */}
+ {tooltipData.visible && (
+ 200 ? "translateX(-100%)" : "translateX(0)",
+ boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
+ backdropFilter: "none",
+ opacity: 1,
+ }}
+ >
+
+ {tooltipData.timestamp}
+
+
+ ↑ {tooltipData.upSpeed}
+
+
+ ↓ {tooltipData.downSpeed}
+
+
+ )}
+
-
- );
-});
+ );
+ }),
+);
EnhancedCanvasTrafficGraph.displayName = "EnhancedCanvasTrafficGraph";
diff --git a/src/components/layout/traffic-graph.tsx b/src/components/layout/traffic-graph.tsx
index 82d011b5..9fd6e2a7 100644
--- a/src/components/layout/traffic-graph.tsx
+++ b/src/components/layout/traffic-graph.tsx
@@ -1,5 +1,5 @@
import { useTheme } from "@mui/material";
-import { useEffect, useImperativeHandle, useRef } from "react";
+import { forwardRef, useEffect, useImperativeHandle, useRef } from "react";
const maxPoint = 30;
@@ -24,7 +24,7 @@ export interface TrafficRef {
/**
* draw the traffic graph
*/
-export const TrafficGraph = ({ ref, ...props }) => {
+export const TrafficGraph = forwardRef((props, ref) => {
const countRef = useRef(0);
const styleRef = useRef(true);
const listRef = useRef(defaultList);
@@ -196,4 +196,4 @@ export const TrafficGraph = ({ ref, ...props }) => {
}, [palette]);
return ;
-};
+});
diff --git a/src/components/profile/profile-viewer.tsx b/src/components/profile/profile-viewer.tsx
index 0bafb44b..d6c4bbc7 100644
--- a/src/components/profile/profile-viewer.tsx
+++ b/src/components/profile/profile-viewer.tsx
@@ -9,7 +9,13 @@ import {
TextField,
} from "@mui/material";
import { useLockFn } from "ahooks";
-import { useEffect, useImperativeHandle, useRef, useState } from "react";
+import {
+ forwardRef,
+ useEffect,
+ useImperativeHandle,
+ useRef,
+ useState,
+} from "react";
import { useForm, Controller } from "react-hook-form";
import { useTranslation } from "react-i18next";
@@ -32,280 +38,304 @@ export interface ProfileViewerRef {
// create or edit the profile
// remote / local
-export const ProfileViewer = ({
- ref,
- ...props
-}: Props & { ref?: React.RefObject }) => {
- const { t } = useTranslation();
- const [open, setOpen] = useState(false);
- const [openType, setOpenType] = useState<"new" | "edit">("new");
- const [loading, setLoading] = useState(false);
- const { profiles } = useProfiles();
+export const ProfileViewer = forwardRef(
+ (props, ref) => {
+ const { t } = useTranslation();
+ const [open, setOpen] = useState(false);
+ const [openType, setOpenType] = useState<"new" | "edit">("new");
+ const [loading, setLoading] = useState(false);
+ const { profiles } = useProfiles();
- // file input
- const fileDataRef = useRef(null);
+ // file input
+ const fileDataRef = useRef(null);
- const {
- control,
- watch,
- register: _register,
- ...formIns
- } = useForm({
- defaultValues: {
- type: "remote",
- name: "",
- desc: "",
- url: "",
- option: {
- with_proxy: false,
- self_proxy: false,
+ const {
+ control,
+ watch,
+ register: _register,
+ ...formIns
+ } = useForm({
+ defaultValues: {
+ type: "remote",
+ name: "",
+ desc: "",
+ url: "",
+ option: {
+ with_proxy: false,
+ self_proxy: false,
+ },
},
- },
- });
+ });
- useImperativeHandle(ref, () => ({
- create: () => {
- setOpenType("new");
- setOpen(true);
- },
- edit: (item) => {
- if (item) {
- Object.entries(item).forEach(([key, value]) => {
- formIns.setValue(key as any, value);
- });
- }
- setOpenType("edit");
- setOpen(true);
- },
- }));
+ useImperativeHandle(ref, () => ({
+ create: () => {
+ setOpenType("new");
+ setOpen(true);
+ },
+ edit: (item) => {
+ if (item) {
+ Object.entries(item).forEach(([key, value]) => {
+ formIns.setValue(key as any, value);
+ });
+ }
+ setOpenType("edit");
+ setOpen(true);
+ },
+ }));
- const selfProxy = watch("option.self_proxy");
- const withProxy = watch("option.with_proxy");
+ const selfProxy = watch("option.self_proxy");
+ const withProxy = watch("option.with_proxy");
- useEffect(() => {
- if (selfProxy) formIns.setValue("option.with_proxy", false);
- }, [selfProxy]);
+ useEffect(() => {
+ if (selfProxy) formIns.setValue("option.with_proxy", false);
+ }, [selfProxy]);
- useEffect(() => {
- if (withProxy) formIns.setValue("option.self_proxy", false);
- }, [withProxy]);
+ useEffect(() => {
+ if (withProxy) formIns.setValue("option.self_proxy", false);
+ }, [withProxy]);
- const handleOk = useLockFn(
- formIns.handleSubmit(async (form) => {
- if (form.option?.timeout_seconds) {
- form.option.timeout_seconds = +form.option.timeout_seconds;
- }
-
- setLoading(true);
- try {
- // 基本验证
- if (!form.type) throw new Error("`Type` should not be null");
- if (form.type === "remote" && !form.url) {
- throw new Error("The URL should not be null");
+ const handleOk = useLockFn(
+ formIns.handleSubmit(async (form) => {
+ if (form.option?.timeout_seconds) {
+ form.option.timeout_seconds = +form.option.timeout_seconds;
}
- // 处理表单数据
- if (form.option?.update_interval) {
- form.option.update_interval = +form.option.update_interval;
- } else {
- delete form.option?.update_interval;
- }
- if (form.option?.user_agent === "") {
- delete form.option.user_agent;
- }
-
- const name = form.name || `${form.type} file`;
- const item = { ...form, name };
- const isRemote = form.type === "remote";
- const isUpdate = openType === "edit";
-
- // 判断是否是当前激活的配置
- const isActivating = isUpdate && form.uid === (profiles?.current ?? "");
-
- // 保存原始代理设置以便回退成功后恢复
- const originalOptions = {
- with_proxy: form.option?.with_proxy,
- self_proxy: form.option?.self_proxy,
- };
-
- // 执行创建或更新操作,本地配置不需要回退机制
- if (!isRemote) {
- if (openType === "new") {
- await createProfile(item, fileDataRef.current);
- } else {
- if (!form.uid) throw new Error("UID not found");
- await patchProfile(form.uid, item);
+ setLoading(true);
+ try {
+ // 基本验证
+ if (!form.type) throw new Error("`Type` should not be null");
+ if (form.type === "remote" && !form.url) {
+ throw new Error("The URL should not be null");
}
- } else {
- // 远程配置使用回退机制
- try {
- // 尝试正常操作
+
+ // 处理表单数据
+ if (form.option?.update_interval) {
+ form.option.update_interval = +form.option.update_interval;
+ } else {
+ delete form.option?.update_interval;
+ }
+ if (form.option?.user_agent === "") {
+ delete form.option.user_agent;
+ }
+
+ const name = form.name || `${form.type} file`;
+ const item = { ...form, name };
+ const isRemote = form.type === "remote";
+ const isUpdate = openType === "edit";
+
+ // 判断是否是当前激活的配置
+ const isActivating =
+ isUpdate && form.uid === (profiles?.current ?? "");
+
+ // 保存原始代理设置以便回退成功后恢复
+ const originalOptions = {
+ with_proxy: form.option?.with_proxy,
+ self_proxy: form.option?.self_proxy,
+ };
+
+ // 执行创建或更新操作,本地配置不需要回退机制
+ if (!isRemote) {
if (openType === "new") {
await createProfile(item, fileDataRef.current);
} else {
if (!form.uid) throw new Error("UID not found");
await patchProfile(form.uid, item);
}
- } catch {
- // 首次创建/更新失败,尝试使用自身代理
- showNotice(
- "info",
- t("Profile creation failed, retrying with Clash proxy..."),
- );
+ } else {
+ // 远程配置使用回退机制
+ try {
+ // 尝试正常操作
+ if (openType === "new") {
+ await createProfile(item, fileDataRef.current);
+ } else {
+ if (!form.uid) throw new Error("UID not found");
+ await patchProfile(form.uid, item);
+ }
+ } catch {
+ // 首次创建/更新失败,尝试使用自身代理
+ showNotice(
+ "info",
+ t("Profile creation failed, retrying with Clash proxy..."),
+ );
- // 使用自身代理的配置
- const retryItem = {
- ...item,
- option: {
- ...item.option,
- with_proxy: false,
- self_proxy: true,
- },
- };
+ // 使用自身代理的配置
+ const retryItem = {
+ ...item,
+ option: {
+ ...item.option,
+ with_proxy: false,
+ self_proxy: true,
+ },
+ };
- // 使用自身代理再次尝试
- if (openType === "new") {
- await createProfile(retryItem, fileDataRef.current);
- } else {
- if (!form.uid) throw new Error("UID not found");
- await patchProfile(form.uid, retryItem);
+ // 使用自身代理再次尝试
+ if (openType === "new") {
+ await createProfile(retryItem, fileDataRef.current);
+ } else {
+ if (!form.uid) throw new Error("UID not found");
+ await patchProfile(form.uid, retryItem);
- // 编辑模式下恢复原始代理设置
- await patchProfile(form.uid, { option: originalOptions });
+ // 编辑模式下恢复原始代理设置
+ await patchProfile(form.uid, { option: originalOptions });
+ }
+
+ showNotice(
+ "success",
+ t("Profile creation succeeded with Clash proxy"),
+ );
}
-
- showNotice(
- "success",
- t("Profile creation succeeded with Clash proxy"),
- );
}
+
+ // 成功后的操作
+ setOpen(false);
+ setTimeout(() => formIns.reset(), 500);
+ fileDataRef.current = null;
+
+ // 优化:UI先关闭,异步通知父组件
+ setTimeout(() => {
+ props.onChange(isActivating);
+ }, 0);
+ } catch (err: any) {
+ showNotice("error", err.message || err.toString());
+ } finally {
+ setLoading(false);
}
+ }),
+ );
- // 成功后的操作
+ const handleClose = () => {
+ try {
setOpen(false);
- setTimeout(() => formIns.reset(), 500);
fileDataRef.current = null;
-
- // 优化:UI先关闭,异步通知父组件
- setTimeout(() => {
- props.onChange(isActivating);
- }, 0);
- } catch (err: any) {
- showNotice("error", err.message || err.toString());
- } finally {
- setLoading(false);
+ setTimeout(() => formIns.reset(), 500);
+ } catch (e) {
+ console.warn("[ProfileViewer] handleClose error:", e);
}
- }),
- );
+ };
- const handleClose = () => {
- try {
- setOpen(false);
- fileDataRef.current = null;
- setTimeout(() => formIns.reset(), 500);
- } catch (e) {
- console.warn("[ProfileViewer] handleClose error:", e);
- }
- };
+ const text = {
+ fullWidth: true,
+ size: "small",
+ margin: "normal",
+ variant: "outlined",
+ autoComplete: "off",
+ autoCorrect: "off",
+ } as const;
- const text = {
- fullWidth: true,
- size: "small",
- margin: "normal",
- variant: "outlined",
- autoComplete: "off",
- autoCorrect: "off",
- } as const;
+ const formType = watch("type");
+ const isRemote = formType === "remote";
+ const isLocal = formType === "local";
- const formType = watch("type");
- const isRemote = formType === "remote";
- const isLocal = formType === "local";
+ return (
+
+ (
+
+ {t("Type")}
+
+
+ )}
+ />
- return (
-
- (
-
- {t("Type")}
-
-
+ (
+
+ )}
+ />
+
+ (
+
+ )}
+ />
+
+ {isRemote && (
+ <>
+ (
+
+ )}
+ />
+
+ (
+
+ )}
+ />
+
+ (
+
+ {t("seconds")}
+
+ ),
+ },
+ }}
+ />
+ )}
+ />
+ >
)}
- />
- (
-
- )}
- />
-
- (
-
- )}
- />
-
- {isRemote && (
- <>
+ {(isRemote || isLocal) && (
(
-
- )}
- />
-
- (
-
- )}
- />
-
- (
- {t("seconds")}
+ {t("mins")}
),
},
@@ -313,79 +343,57 @@ export const ProfileViewer = ({
/>
)}
/>
- >
- )}
+ )}
- {(isRemote || isLocal) && (
- (
- {t("mins")}
- ),
- },
- }}
+ {isLocal && openType === "new" && (
+ {
+ formIns.setValue("name", formIns.getValues("name") || file.name);
+ fileDataRef.current = val;
+ }}
+ />
+ )}
+
+ {isRemote && (
+ <>
+ (
+
+ {t("Use System Proxy")}
+
+
+ )}
/>
- )}
- />
- )}
- {isLocal && openType === "new" && (
- {
- formIns.setValue("name", formIns.getValues("name") || file.name);
- fileDataRef.current = val;
- }}
- />
- )}
+ (
+
+ {t("Use Clash Proxy")}
+
+
+ )}
+ />
- {isRemote && (
- <>
- (
-
- {t("Use System Proxy")}
-
-
- )}
- />
-
- (
-
- {t("Use Clash Proxy")}
-
-
- )}
- />
-
- (
-
- {t("Accept Invalid Certs (Danger)")}
-
-
- )}
- />
- >
- )}
-
- );
-};
+ (
+
+ {t("Accept Invalid Certs (Danger)")}
+
+
+ )}
+ />
+ >
+ )}
+
+ );
+ },
+);
const StyledBox = styled(Box)(() => ({
margin: "8px 0 8px 8px",
diff --git a/src/components/proxy/proxy-groups.tsx b/src/components/proxy/proxy-groups.tsx
index 33e6b553..476f5f90 100644
--- a/src/components/proxy/proxy-groups.tsx
+++ b/src/components/proxy/proxy-groups.tsx
@@ -1,19 +1,23 @@
-import { ExpandMoreRounded } from "@mui/icons-material";
import {
Box,
Snackbar,
Alert,
Chip,
+ Stack,
Typography,
IconButton,
+ Collapse,
Menu,
MenuItem,
+ Divider,
+ Button,
} from "@mui/material";
+import { ArchiveOutlined, ExpandMoreRounded } from "@mui/icons-material";
import { useLockFn } from "ahooks";
import { useRef, useState, useEffect, useCallback, useMemo } from "react";
+import useSWR from "swr";
import { useTranslation } from "react-i18next";
import { Virtuoso, type VirtuosoHandle } from "react-virtuoso";
-import useSWR from "swr";
import { useProxySelection } from "@/hooks/use-proxy-selection";
import { useVerge } from "@/hooks/use-verge";
@@ -30,8 +34,8 @@ import { BaseEmpty } from "../base";
import { ScrollTopButton } from "../layout/scroll-top-button";
import { ProxyChain } from "./proxy-chain";
-import { ProxyGroupNavigator } from "./proxy-group-navigator";
import { ProxyRender } from "./proxy-render";
+import { ProxyGroupNavigator } from "./proxy-group-navigator";
import { useRenderList } from "./use-render-list";
interface Props {
diff --git a/src/components/setting/mods/backup-viewer.tsx b/src/components/setting/mods/backup-viewer.tsx
index 8c03e4c7..804f3541 100644
--- a/src/components/setting/mods/backup-viewer.tsx
+++ b/src/components/setting/mods/backup-viewer.tsx
@@ -1,10 +1,16 @@
import { Box, Paper, Divider } from "@mui/material";
import dayjs from "dayjs";
import customParseFormat from "dayjs/plugin/customParseFormat";
-import { useImperativeHandle, useState, useCallback, useMemo } from "react";
+import {
+ forwardRef,
+ useImperativeHandle,
+ useState,
+ useCallback,
+ useMemo,
+} from "react";
import { useTranslation } from "react-i18next";
-import { BaseDialog } from "@/components/base";
+import { BaseDialog, DialogRef } from "@/components/base";
import { BaseLoadingOverlay } from "@/components/base";
import { listWebDavBackup } from "@/services/cmds";
@@ -19,7 +25,7 @@ dayjs.extend(customParseFormat);
const DATE_FORMAT = "YYYY-MM-DD_HH-mm-ss";
const FILENAME_PATTERN = /\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}/;
-export const BackupViewer = ({ ref, ...props }) => {
+export const BackupViewer = forwardRef((props, ref) => {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
@@ -125,4 +131,4 @@ export const BackupViewer = ({ ref, ...props }) => {
);
-};
+});
diff --git a/src/components/setting/mods/clash-core-viewer.tsx b/src/components/setting/mods/clash-core-viewer.tsx
index f41626de..869b5784 100644
--- a/src/components/setting/mods/clash-core-viewer.tsx
+++ b/src/components/setting/mods/clash-core-viewer.tsx
@@ -12,11 +12,11 @@ import {
ListItemText,
} from "@mui/material";
import { useLockFn } from "ahooks";
-import { useImperativeHandle, useState } from "react";
+import { forwardRef, useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
import { mutate } from "swr";
-import { BaseDialog } from "@/components/base";
+import { BaseDialog, DialogRef } from "@/components/base";
import { useVerge } from "@/hooks/use-verge";
import { changeClashCore, restartCore } from "@/services/cmds";
import {
@@ -31,7 +31,7 @@ const VALID_CORE = [
{ name: "Mihomo Alpha", core: "verge-mihomo-alpha", chip: "Alpha Version" },
];
-export const ClashCoreViewer = ({ ref, ...props }) => {
+export const ClashCoreViewer = forwardRef((props, ref) => {
const { t } = useTranslation();
const { verge, mutateVerge } = useVerge();
@@ -169,4 +169,4 @@ export const ClashCoreViewer = ({ ref, ...props }) => {
);
-};
+});
diff --git a/src/components/setting/mods/clash-port-viewer.tsx b/src/components/setting/mods/clash-port-viewer.tsx
index e0db3184..ec495804 100644
--- a/src/components/setting/mods/clash-port-viewer.tsx
+++ b/src/components/setting/mods/clash-port-viewer.tsx
@@ -9,7 +9,7 @@ import {
TextField,
} from "@mui/material";
import { useLockFn, useRequest } from "ahooks";
-import { useImperativeHandle, useState } from "react";
+import { forwardRef, useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
import { BaseDialog, Switch } from "@/components/base";
@@ -30,12 +30,10 @@ interface ClashPortViewerRef {
const generateRandomPort = () =>
Math.floor(Math.random() * (65535 - 1025 + 1)) + 1025;
-export const ClashPortViewer = ({
- ref,
- ...props
-}: ClashPortViewerProps & {
- ref?: React.RefObject;
-}) => {
+export const ClashPortViewer = forwardRef<
+ ClashPortViewerRef,
+ ClashPortViewerProps
+>((props, ref) => {
const { t } = useTranslation();
const { clashInfo, patchInfo } = useClashInfo();
const { verge, patchVerge } = useVerge();
@@ -350,4 +348,4 @@ export const ClashPortViewer = ({
);
-};
+});
diff --git a/src/components/setting/mods/config-viewer.tsx b/src/components/setting/mods/config-viewer.tsx
index e68030f9..26609caa 100644
--- a/src/components/setting/mods/config-viewer.tsx
+++ b/src/components/setting/mods/config-viewer.tsx
@@ -1,11 +1,12 @@
import { Box, Chip } from "@mui/material";
-import { useImperativeHandle, useState } from "react";
+import { forwardRef, useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
+import { DialogRef } from "@/components/base";
import { EditorViewer } from "@/components/profile/editor-viewer";
import { getRuntimeYaml } from "@/services/cmds";
-export const ConfigViewer = ({ ref, ..._ }) => {
+export const ConfigViewer = forwardRef((_, ref) => {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const [runtimeConfig, setRuntimeConfig] = useState("");
@@ -37,4 +38,4 @@ export const ConfigViewer = ({ ref, ..._ }) => {
onClose={() => setOpen(false)}
/>
);
-};
+});
diff --git a/src/components/setting/mods/controller-viewer.tsx b/src/components/setting/mods/controller-viewer.tsx
index 154ff523..f39b928b 100644
--- a/src/components/setting/mods/controller-viewer.tsx
+++ b/src/components/setting/mods/controller-viewer.tsx
@@ -12,15 +12,15 @@ import {
Tooltip,
} from "@mui/material";
import { useLockFn } from "ahooks";
-import { useImperativeHandle, useState } from "react";
+import { forwardRef, useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
-import { BaseDialog, Switch } from "@/components/base";
+import { BaseDialog, DialogRef, Switch } from "@/components/base";
import { useClashInfo } from "@/hooks/use-clash";
import { useVerge } from "@/hooks/use-verge";
import { showNotice } from "@/services/noticeService";
-export const ControllerViewer = ({ ref, ...props }) => {
+export const ControllerViewer = forwardRef((props, ref) => {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const [copySuccess, setCopySuccess] = useState(null);
@@ -217,4 +217,4 @@ export const ControllerViewer = ({ ref, ...props }) => {
);
-};
+});
diff --git a/src/components/setting/mods/dns-viewer.tsx b/src/components/setting/mods/dns-viewer.tsx
index 6f3cf071..f076dbc0 100644
--- a/src/components/setting/mods/dns-viewer.tsx
+++ b/src/components/setting/mods/dns-viewer.tsx
@@ -15,11 +15,11 @@ import {
import { invoke } from "@tauri-apps/api/core";
import { useLockFn } from "ahooks";
import yaml from "js-yaml";
-import { useImperativeHandle, useState, useEffect } from "react";
+import { forwardRef, useImperativeHandle, useState, useEffect } from "react";
import { useTranslation } from "react-i18next";
import MonacoEditor from "react-monaco-editor";
-import { BaseDialog, Switch } from "@/components/base";
+import { BaseDialog, DialogRef, Switch } from "@/components/base";
import { useClash } from "@/hooks/use-clash";
import { showNotice } from "@/services/noticeService";
import { useThemeMode } from "@/services/states";
@@ -87,7 +87,7 @@ const DEFAULT_DNS_CONFIG = {
},
};
-export const DnsViewer = ({ ref, ...props }) => {
+export const DnsViewer = forwardRef((props, ref) => {
const { t } = useTranslation();
const { clash, mutateClash } = useClash();
const themeMode = useThemeMode();
@@ -1034,4 +1034,4 @@ export const DnsViewer = ({ ref, ...props }) => {
)}
);
-};
+});
diff --git a/src/components/setting/mods/external-controller-cors.tsx b/src/components/setting/mods/external-controller-cors.tsx
index 355e316e..d2522f35 100644
--- a/src/components/setting/mods/external-controller-cors.tsx
+++ b/src/components/setting/mods/external-controller-cors.tsx
@@ -1,7 +1,7 @@
import { Delete as DeleteIcon } from "@mui/icons-material";
import { Box, Button, Divider, List, ListItem, TextField } from "@mui/material";
import { useLockFn, useRequest } from "ahooks";
-import { useImperativeHandle, useState } from "react";
+import { forwardRef, useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
import { BaseDialog, Switch } from "@/components/base";
@@ -71,194 +71,201 @@ interface ClashHeaderConfigingRef {
close: () => void;
}
-export const HeaderConfiguration = ({ ref, ...props }) => {
- const { t } = useTranslation();
- const { clash, mutateClash, patchClash } = useClash();
- const [open, setOpen] = useState(false);
+export const HeaderConfiguration = forwardRef(
+ (props, ref) => {
+ const { t } = useTranslation();
+ const { clash, mutateClash, patchClash } = useClash();
+ const [open, setOpen] = useState(false);
- // CORS配置状态管理
- const [corsConfig, setCorsConfig] = useState<{
- allowPrivateNetwork: boolean;
- allowOrigins: string[];
- }>(() => {
- const cors = clash?.["external-controller-cors"];
- const origins = cors?.["allow-origins"] ?? [];
- return {
- allowPrivateNetwork: cors?.["allow-private-network"] ?? true,
- allowOrigins: filterBaseOriginsForUI(origins),
- };
- });
-
- // 处理CORS配置变更
- const handleCorsConfigChange = (
- key: "allowPrivateNetwork" | "allowOrigins",
- value: boolean | string[],
- ) => {
- setCorsConfig((prev) => ({
- ...prev,
- [key]: value,
- }));
- };
-
- // 添加新的允许来源
- const handleAddOrigin = () => {
- handleCorsConfigChange("allowOrigins", [...corsConfig.allowOrigins, ""]);
- };
-
- // 更新允许来源列表中的某一项
- const handleUpdateOrigin = (index: number, value: string) => {
- const newOrigins = [...corsConfig.allowOrigins];
- newOrigins[index] = value;
- handleCorsConfigChange("allowOrigins", newOrigins);
- };
-
- // 删除允许来源列表中的某一项
- const handleDeleteOrigin = (index: number) => {
- const newOrigins = [...corsConfig.allowOrigins];
- newOrigins.splice(index, 1);
- handleCorsConfigChange("allowOrigins", newOrigins);
- };
-
- // 保存配置请求
- const { loading, run: saveConfig } = useRequest(
- async () => {
- // 保存时使用完整的源列表(包括开发URL)
- const fullOrigins = getFullOrigins(corsConfig.allowOrigins);
-
- await patchClash({
- "external-controller-cors": {
- "allow-private-network": corsConfig.allowPrivateNetwork,
- "allow-origins": fullOrigins.filter(
- (origin: string) => origin.trim() !== "",
- ),
- },
- });
- await mutateClash();
- },
- {
- manual: true,
- onSuccess: () => {
- setOpen(false);
- showNotice("success", t("Configuration saved successfully"));
- },
- onError: () => {
- showNotice("error", t("Failed to save configuration"));
- },
- },
- );
-
- useImperativeHandle(ref, () => ({
- open: () => {
+ // CORS配置状态管理
+ const [corsConfig, setCorsConfig] = useState<{
+ allowPrivateNetwork: boolean;
+ allowOrigins: string[];
+ }>(() => {
const cors = clash?.["external-controller-cors"];
const origins = cors?.["allow-origins"] ?? [];
- setCorsConfig({
+ return {
allowPrivateNetwork: cors?.["allow-private-network"] ?? true,
allowOrigins: filterBaseOriginsForUI(origins),
- });
- setOpen(true);
- },
- close: () => setOpen(false),
- }));
+ };
+ });
- const handleSave = useLockFn(async () => {
- await saveConfig();
- });
+ // 处理CORS配置变更
+ const handleCorsConfigChange = (
+ key: "allowPrivateNetwork" | "allowOrigins",
+ value: boolean | string[],
+ ) => {
+ setCorsConfig((prev) => ({
+ ...prev,
+ [key]: value,
+ }));
+ };
- return (
- setOpen(false)}
- onCancel={() => setOpen(false)}
- onOk={handleSave}
- >
-
-
-
-
- {t("Allow private network access")}
-
-
- handleCorsConfigChange("allowPrivateNetwork", e.target.checked)
- }
- />
-
-
+ // 添加新的允许来源
+ const handleAddOrigin = () => {
+ handleCorsConfigChange("allowOrigins", [...corsConfig.allowOrigins, ""]);
+ };
-
+ // 更新允许来源列表中的某一项
+ const handleUpdateOrigin = (index: number, value: string) => {
+ const newOrigins = [...corsConfig.allowOrigins];
+ newOrigins[index] = value;
+ handleCorsConfigChange("allowOrigins", newOrigins);
+ };
+
+ // 删除允许来源列表中的某一项
+ const handleDeleteOrigin = (index: number) => {
+ const newOrigins = [...corsConfig.allowOrigins];
+ newOrigins.splice(index, 1);
+ handleCorsConfigChange("allowOrigins", newOrigins);
+ };
+
+ // 保存配置请求
+ const { loading, run: saveConfig } = useRequest(
+ async () => {
+ // 保存时使用完整的源列表(包括开发URL)
+ const fullOrigins = getFullOrigins(corsConfig.allowOrigins);
+
+ await patchClash({
+ "external-controller-cors": {
+ "allow-private-network": corsConfig.allowPrivateNetwork,
+ "allow-origins": fullOrigins.filter(
+ (origin: string) => origin.trim() !== "",
+ ),
+ },
+ });
+ await mutateClash();
+ },
+ {
+ manual: true,
+ onSuccess: () => {
+ setOpen(false);
+ showNotice("success", t("Configuration saved successfully"));
+ },
+ onError: () => {
+ showNotice("error", t("Failed to save configuration"));
+ },
+ },
+ );
+
+ useImperativeHandle(ref, () => ({
+ open: () => {
+ const cors = clash?.["external-controller-cors"];
+ const origins = cors?.["allow-origins"] ?? [];
+ setCorsConfig({
+ allowPrivateNetwork: cors?.["allow-private-network"] ?? true,
+ allowOrigins: filterBaseOriginsForUI(origins),
+ });
+ setOpen(true);
+ },
+ close: () => setOpen(false),
+ }));
+
+ const handleSave = useLockFn(async () => {
+ await saveConfig();
+ });
+
+ return (
+ setOpen(false)}
+ onCancel={() => setOpen(false)}
+ onOk={handleSave}
+ >
+
+
+
+
+ {t("Allow private network access")}
+
+
+ handleCorsConfigChange(
+ "allowPrivateNetwork",
+ e.target.checked,
+ )
+ }
+ />
+
+
+
+
+
+
+
+
+ {t("Allowed Origins")}
+
+ {corsConfig.allowOrigins.map((origin, index) => (
+
+ handleUpdateOrigin(index, e.target.value)}
+ placeholder={t("Please enter a valid url")}
+ inputProps={{ style: { fontSize: 14 } }}
+ />
+
+
+ ))}
+
-
-
-
- {t("Allowed Origins")}
-
- {corsConfig.allowOrigins.map((origin, index) => (
-
handleUpdateOrigin(index, e.target.value)}
- placeholder={t("Please enter a valid url")}
- inputProps={{ style: { fontSize: 14 } }}
- />
-
-
-
-
-
- );
-};
+
+
+
+ );
+ },
+);
diff --git a/src/components/setting/mods/hotkey-viewer.tsx b/src/components/setting/mods/hotkey-viewer.tsx
index 8ea690fa..cd09e244 100644
--- a/src/components/setting/mods/hotkey-viewer.tsx
+++ b/src/components/setting/mods/hotkey-viewer.tsx
@@ -1,9 +1,9 @@
import { styled, Typography } from "@mui/material";
import { useLockFn } from "ahooks";
-import { useImperativeHandle, useState } from "react";
+import { forwardRef, useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
-import { BaseDialog, Switch } from "@/components/base";
+import { BaseDialog, DialogRef, Switch } from "@/components/base";
import { useVerge } from "@/hooks/use-verge";
import { showNotice } from "@/services/noticeService";
@@ -26,7 +26,7 @@ const HOTKEY_FUNC = [
"entry_lightweight_mode",
];
-export const HotkeyViewer = ({ ref, ...props }) => {
+export const HotkeyViewer = forwardRef
((props, ref) => {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
@@ -117,4 +117,4 @@ export const HotkeyViewer = ({ ref, ...props }) => {
))}
);
-};
+});
diff --git a/src/components/setting/mods/layout-viewer.tsx b/src/components/setting/mods/layout-viewer.tsx
index 80951552..5e0f1e25 100644
--- a/src/components/setting/mods/layout-viewer.tsx
+++ b/src/components/setting/mods/layout-viewer.tsx
@@ -12,10 +12,10 @@ import { convertFileSrc } from "@tauri-apps/api/core";
import { join } from "@tauri-apps/api/path";
import { open as openDialog } from "@tauri-apps/plugin-dialog";
import { exists } from "@tauri-apps/plugin-fs";
-import { useEffect, useImperativeHandle, useState } from "react";
+import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
-import { BaseDialog, Switch } from "@/components/base";
+import { BaseDialog, DialogRef, Switch } from "@/components/base";
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import { useVerge } from "@/hooks/use-verge";
import { copyIconFile, getAppDir } from "@/services/cmds";
@@ -38,7 +38,7 @@ const getIcons = async (icon_dir: string, name: string) => {
};
};
-export const LayoutViewer = ({ ref, ...props }) => {
+export const LayoutViewer = forwardRef((props, ref) => {
const { t } = useTranslation();
const { verge, patchVerge, mutateVerge } = useVerge();
@@ -387,7 +387,7 @@ export const LayoutViewer = ({ ref, ...props }) => {
);
-};
+});
const Item = styled(ListItem)(() => ({
padding: "5px 2px",
diff --git a/src/components/setting/mods/lite-mode-viewer.tsx b/src/components/setting/mods/lite-mode-viewer.tsx
index 30f534a2..6c243667 100644
--- a/src/components/setting/mods/lite-mode-viewer.tsx
+++ b/src/components/setting/mods/lite-mode-viewer.tsx
@@ -7,16 +7,16 @@ import {
InputAdornment,
} from "@mui/material";
import { useLockFn } from "ahooks";
-import { useImperativeHandle, useState } from "react";
+import { forwardRef, useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
-import { BaseDialog, Switch } from "@/components/base";
+import { BaseDialog, DialogRef, Switch } from "@/components/base";
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import { useVerge } from "@/hooks/use-verge";
import { entry_lightweight_mode } from "@/services/cmds";
import { showNotice } from "@/services/noticeService";
-export const LiteModeViewer = ({ ref, ...props }) => {
+export const LiteModeViewer = forwardRef((props, ref) => {
const { t } = useTranslation();
const { verge, patchVerge } = useVerge();
@@ -143,4 +143,4 @@ export const LiteModeViewer = ({ ref, ...props }) => {
);
-};
+});
diff --git a/src/components/setting/mods/misc-viewer.tsx b/src/components/setting/mods/misc-viewer.tsx
index cee02882..b0048fd8 100644
--- a/src/components/setting/mods/misc-viewer.tsx
+++ b/src/components/setting/mods/misc-viewer.tsx
@@ -8,15 +8,15 @@ import {
TextField,
} from "@mui/material";
import { useLockFn } from "ahooks";
-import { useImperativeHandle, useState } from "react";
+import { forwardRef, useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
-import { BaseDialog, Switch } from "@/components/base";
+import { BaseDialog, DialogRef, Switch } from "@/components/base";
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import { useVerge } from "@/hooks/use-verge";
import { showNotice } from "@/services/noticeService";
-export const MiscViewer = ({ ref, ...props }) => {
+export const MiscViewer = forwardRef((props, ref) => {
const { t } = useTranslation();
const { verge, patchVerge } = useVerge();
@@ -319,4 +319,4 @@ export const MiscViewer = ({ ref, ...props }) => {
);
-};
+});
diff --git a/src/components/setting/mods/network-interface-viewer.tsx b/src/components/setting/mods/network-interface-viewer.tsx
index 5b7d0422..912529eb 100644
--- a/src/components/setting/mods/network-interface-viewer.tsx
+++ b/src/components/setting/mods/network-interface-viewer.tsx
@@ -1,15 +1,15 @@
import { ContentCopyRounded } from "@mui/icons-material";
import { alpha, Box, Button, IconButton } from "@mui/material";
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
-import { useImperativeHandle, useState } from "react";
+import { forwardRef, useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
import useSWR from "swr";
-import { BaseDialog } from "@/components/base";
+import { BaseDialog, DialogRef } from "@/components/base";
import { getNetworkInterfacesInfo } from "@/services/cmds";
import { showNotice } from "@/services/noticeService";
-export const NetworkInterfaceViewer = ({ ref, ...props }) => {
+export const NetworkInterfaceViewer = forwardRef((props, ref) => {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const [isV4, setIsV4] = useState(true);
@@ -99,7 +99,7 @@ export const NetworkInterfaceViewer = ({ ref, ...props }) => {
))}
);
-};
+});
const AddressDisplay = (props: { label: string; content: string }) => {
const { t } = useTranslation();
diff --git a/src/components/setting/mods/sysproxy-viewer.tsx b/src/components/setting/mods/sysproxy-viewer.tsx
index 9649a3f6..329d857e 100644
--- a/src/components/setting/mods/sysproxy-viewer.tsx
+++ b/src/components/setting/mods/sysproxy-viewer.tsx
@@ -11,19 +11,25 @@ import {
Typography,
} from "@mui/material";
import { useLockFn } from "ahooks";
-import { useEffect, useImperativeHandle, useMemo, useState } from "react";
+import {
+ forwardRef,
+ useEffect,
+ useImperativeHandle,
+ useMemo,
+ useState,
+} from "react";
import { useTranslation } from "react-i18next";
import useSWR, { mutate } from "swr";
-import { BaseDialog, Switch } from "@/components/base";
+import { BaseDialog, DialogRef, Switch } from "@/components/base";
import { BaseFieldset } from "@/components/base/base-fieldset";
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import { EditorViewer } from "@/components/profile/editor-viewer";
import { useVerge } from "@/hooks/use-verge";
import { useAppData } from "@/providers/app-data-provider";
+import { getClashConfig } from "@/services/cmds";
import {
getAutotemProxy,
- getClashConfig,
getNetworkInterfacesInfo,
getSystemHostname,
getSystemProxy,
@@ -69,7 +75,7 @@ const getValidReg = (isWindows: boolean) => {
return new RegExp(rValid);
};
-export const SysproxyViewer = ({ ref, ...props }) => {
+export const SysproxyViewer = forwardRef((props, ref) => {
const { t } = useTranslation();
const isWindows = getSystem() === "windows";
const validReg = useMemo(() => getValidReg(isWindows), [isWindows]);
@@ -613,7 +619,7 @@ export const SysproxyViewer = ({ ref, ...props }) => {
);
-};
+});
const FlexBox = styled("div")`
display: flex;
diff --git a/src/components/setting/mods/theme-viewer.tsx b/src/components/setting/mods/theme-viewer.tsx
index f59fae53..e3f764c5 100644
--- a/src/components/setting/mods/theme-viewer.tsx
+++ b/src/components/setting/mods/theme-viewer.tsx
@@ -9,16 +9,16 @@ import {
useTheme,
} from "@mui/material";
import { useLockFn } from "ahooks";
-import { useImperativeHandle, useState } from "react";
+import { forwardRef, useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
-import { BaseDialog } from "@/components/base";
+import { BaseDialog, DialogRef } from "@/components/base";
import { EditorViewer } from "@/components/profile/editor-viewer";
import { useVerge } from "@/hooks/use-verge";
import { defaultTheme, defaultDarkTheme } from "@/pages/_theme";
import { showNotice } from "@/services/noticeService";
-export const ThemeViewer = ({ ref, ...props }) => {
+export const ThemeViewer = forwardRef((props, ref) => {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
@@ -144,7 +144,7 @@ export const ThemeViewer = ({ ref, ...props }) => {
);
-};
+});
const Item = styled(ListItem)(() => ({
padding: "5px 2px",
diff --git a/src/components/setting/mods/tun-viewer.tsx b/src/components/setting/mods/tun-viewer.tsx
index d3667573..cd361cc9 100644
--- a/src/components/setting/mods/tun-viewer.tsx
+++ b/src/components/setting/mods/tun-viewer.tsx
@@ -8,10 +8,10 @@ import {
TextField,
} from "@mui/material";
import { useLockFn } from "ahooks";
-import { useImperativeHandle, useState } from "react";
+import { forwardRef, useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
-import { BaseDialog, Switch } from "@/components/base";
+import { BaseDialog, DialogRef, Switch } from "@/components/base";
import { useClash } from "@/hooks/use-clash";
import { enhanceProfiles } from "@/services/cmds";
import { showNotice } from "@/services/noticeService";
@@ -21,7 +21,7 @@ import { StackModeSwitch } from "./stack-mode-switch";
const OS = getSystem();
-export const TunViewer = ({ ref, ...props }) => {
+export const TunViewer = forwardRef((props, ref) => {
const { t } = useTranslation();
const { clash, mutateClash, patchClash } = useClash();
@@ -238,4 +238,4 @@ export const TunViewer = ({ ref, ...props }) => {
);
-};
+});
diff --git a/src/components/setting/mods/update-viewer.tsx b/src/components/setting/mods/update-viewer.tsx
index a2fa367e..3b67e175 100644
--- a/src/components/setting/mods/update-viewer.tsx
+++ b/src/components/setting/mods/update-viewer.tsx
@@ -4,18 +4,24 @@ import { relaunch } from "@tauri-apps/plugin-process";
import { open as openUrl } from "@tauri-apps/plugin-shell";
import { check as checkUpdate } from "@tauri-apps/plugin-updater";
import { useLockFn } from "ahooks";
-import { useImperativeHandle, useState, useMemo, useEffect } from "react";
+import {
+ forwardRef,
+ useImperativeHandle,
+ useState,
+ useMemo,
+ useEffect,
+} from "react";
import { useTranslation } from "react-i18next";
import ReactMarkdown from "react-markdown";
import useSWR from "swr";
-import { BaseDialog } from "@/components/base";
+import { BaseDialog, DialogRef } from "@/components/base";
import { useListen } from "@/hooks/use-listen";
import { portableFlag } from "@/pages/_layout";
import { showNotice } from "@/services/noticeService";
import { useUpdateState, useSetUpdateState } from "@/services/states";
-export const UpdateViewer = ({ ref, ...props }) => {
+export const UpdateViewer = forwardRef((props, ref) => {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
@@ -161,4 +167,4 @@ export const UpdateViewer = ({ ref, ...props }) => {
)}
);
-};
+});
diff --git a/src/components/setting/mods/web-ui-viewer.tsx b/src/components/setting/mods/web-ui-viewer.tsx
index 655be1e9..e82035fc 100644
--- a/src/components/setting/mods/web-ui-viewer.tsx
+++ b/src/components/setting/mods/web-ui-viewer.tsx
@@ -1,9 +1,9 @@
import { Button, Box, Typography } from "@mui/material";
import { useLockFn } from "ahooks";
-import { useImperativeHandle, useState } from "react";
+import { forwardRef, useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
-import { BaseDialog, BaseEmpty } from "@/components/base";
+import { BaseDialog, BaseEmpty, DialogRef } from "@/components/base";
import { useClashInfo } from "@/hooks/use-clash";
import { useVerge } from "@/hooks/use-verge";
import { openWebUrl } from "@/services/cmds";
@@ -11,7 +11,7 @@ import { showNotice } from "@/services/noticeService";
import { WebUIItem } from "./web-ui-item";
-export const WebUIViewer = ({ ref, ...props }) => {
+export const WebUIViewer = forwardRef((props, ref) => {
const { t } = useTranslation();
const { clashInfo } = useClashInfo();
@@ -139,4 +139,4 @@ export const WebUIViewer = ({ ref, ...props }) => {
)}
);
-};
+});
diff --git a/src/components/test/test-viewer.tsx b/src/components/test/test-viewer.tsx
index 56453bb9..0962c424 100644
--- a/src/components/test/test-viewer.tsx
+++ b/src/components/test/test-viewer.tsx
@@ -1,7 +1,7 @@
import { TextField } from "@mui/material";
import { useLockFn } from "ahooks";
import { nanoid } from "nanoid";
-import { useImperativeHandle, useState } from "react";
+import { forwardRef, useImperativeHandle, useState } from "react";
import { useForm, Controller } from "react-hook-form";
import { useTranslation } from "react-i18next";
@@ -19,10 +19,7 @@ export interface TestViewerRef {
}
// create or edit the test item
-export const TestViewer = ({
- ref,
- ...props
-}: Props & { ref?: React.RefObject }) => {
+export const TestViewer = forwardRef((props, ref) => {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const [openType, setOpenType] = useState<"new" | "edit">("new");
@@ -176,4 +173,4 @@ export const TestViewer = ({
/>
);
-};
+});
diff --git a/src/providers/app-data-provider.tsx b/src/providers/app-data-provider.tsx
index 8abd1380..16ad3962 100644
--- a/src/providers/app-data-provider.tsx
+++ b/src/providers/app-data-provider.tsx
@@ -1,5 +1,11 @@
import { listen } from "@tauri-apps/api/event";
-import React, { createContext, use, useEffect, useMemo, useRef } from "react";
+import React, {
+ createContext,
+ useContext,
+ useEffect,
+ useMemo,
+ useRef,
+} from "react";
import useSWR from "swr";
import { useClashInfo } from "@/hooks/use-clash";
@@ -583,12 +589,14 @@ export const AppDataProvider = ({
refreshAll,
]);
- return {children};
+ return (
+ {children}
+ );
};
// 自定义Hook访问全局数据
export const useAppData = () => {
- const context = use(AppDataContext);
+ const context = useContext(AppDataContext);
if (!context) {
throw new Error("useAppData必须在AppDataProvider内使用");
diff --git a/src/providers/chain-proxy-provider.tsx b/src/providers/chain-proxy-provider.tsx
index c8ccce1d..2b834f89 100644
--- a/src/providers/chain-proxy-provider.tsx
+++ b/src/providers/chain-proxy-provider.tsx
@@ -1,4 +1,4 @@
-import React, { createContext, useCallback, use, useState } from "react";
+import React, { createContext, useCallback, useContext, useState } from "react";
interface ChainProxyContextType {
isChainMode: boolean;
@@ -26,7 +26,7 @@ export const ChainProxyProvider = ({
}, []);
return (
-
{children}
-
+
);
};
export const useChainProxy = () => {
- const context = use(ChainProxyContext);
+ const context = useContext(ChainProxyContext);
if (!context) {
throw new Error("useChainProxy must be used within a ChainProxyProvider");
}