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 ( + + - + onMouseMove={handleMouseMove} + onMouseLeave={handleMouseLeave} + /> - {/* 控制层覆盖 */} + {/* 控制层覆盖 */} + + {/* 时间范围按钮 */} + + {getTimeRangeText()} + + + {/* 图例 */} - {/* 时间范围按钮 */} + {t("Upload")} + + + {t("Download")} + + + + {/* 样式指示器 */} + + {chartStyle === "bezier" ? "Smooth" : "Linear"} + + + {/* 数据统计指示器(左下角) */} + + Points: {displayData.length} | Fresh: {isDataFresh ? "✓" : "✗"} | + Compressed: {samplerStats.compressedBufferSize} + + + {/* 悬浮提示框 */} + {tooltipData.visible && ( + - {getTimeRangeText()} - - - {/* 图例 */} - - - {t("Upload")} - - - {t("Download")} - - - - {/* 样式指示器 */} - - {chartStyle === "bezier" ? "Smooth" : "Linear"} - - - {/* 数据统计指示器(左下角) */} - 200 ? "translateX(-100%)" : "translateX(0)", + boxShadow: "0 4px 12px rgba(0,0,0,0.15)", + backdropFilter: "none", + opacity: 1, }} > - 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} - + + {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 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 } }} - /> - -
- ))} - - -
-
- {t("Always included origins: {{urls}}", { - urls: DEV_URLS.join(", "), - })} + {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 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"); }