Compare commits
26 Commits
v2.3.0
...
updater-al
14
.github/workflows/alpha.yml
vendored
14
.github/workflows/alpha.yml
vendored
@@ -286,8 +286,7 @@ jobs:
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: src-tauri
|
||||
cache-all-crates: true
|
||||
cache-on-failure: true
|
||||
save-if: false
|
||||
|
||||
- name: Install dependencies (ubuntu only)
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
@@ -308,7 +307,7 @@ jobs:
|
||||
- name: Pnpm install and check
|
||||
run: |
|
||||
pnpm i
|
||||
pnpm prepare ${{ matrix.target }}
|
||||
pnpm run prebuild ${{ matrix.target }}
|
||||
|
||||
# - name: Release ${{ env.TAG_CHANNEL }} Version
|
||||
# run: pnpm release-version ${{ env.TAG_NAME }}
|
||||
@@ -363,7 +362,7 @@ jobs:
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: src-tauri
|
||||
cache-all-crates: true
|
||||
save-if: false
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
@@ -378,7 +377,7 @@ jobs:
|
||||
- name: Pnpm install and check
|
||||
run: |
|
||||
pnpm i
|
||||
pnpm prepare ${{ matrix.target }}
|
||||
pnpm run prebuild ${{ matrix.target }}
|
||||
|
||||
# - name: Release ${{ env.TAG_CHANNEL }} Version
|
||||
# run: pnpm release-version ${{ env.TAG_NAME }}
|
||||
@@ -491,8 +490,7 @@ jobs:
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: src-tauri
|
||||
cache-all-crates: true
|
||||
cache-on-failure: true
|
||||
save-if: false
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
@@ -507,7 +505,7 @@ jobs:
|
||||
- name: Pnpm install and check
|
||||
run: |
|
||||
pnpm i
|
||||
pnpm prepare ${{ matrix.target }}
|
||||
pnpm run prebuild ${{ matrix.target }}
|
||||
|
||||
# - name: Release ${{ env.TAG_CHANNEL }} Version
|
||||
# run: pnpm release-version ${{ env.TAG_NAME }}
|
||||
|
||||
44
.github/workflows/autobuild.yml
vendored
44
.github/workflows/autobuild.yml
vendored
@@ -183,7 +183,7 @@ jobs:
|
||||
with:
|
||||
workspaces: src-tauri
|
||||
cache-all-crates: true
|
||||
cache-on-failure: true
|
||||
save-if: ${{ github.ref == 'refs/heads/dev' }}
|
||||
|
||||
- name: Install dependencies (ubuntu only)
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
@@ -191,20 +191,21 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Pnpm install and check
|
||||
run: |
|
||||
pnpm i
|
||||
pnpm prepare ${{ matrix.target }}
|
||||
pnpm run prebuild ${{ matrix.target }}
|
||||
|
||||
- name: Release ${{ env.TAG_CHANNEL }} Version
|
||||
run: pnpm release-version ${{ env.TAG_NAME }}
|
||||
@@ -260,21 +261,23 @@ jobs:
|
||||
with:
|
||||
workspaces: src-tauri
|
||||
cache-all-crates: true
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22"
|
||||
save-if: ${{ github.ref == 'refs/heads/dev' }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Pnpm install and check
|
||||
run: |
|
||||
pnpm i
|
||||
pnpm prepare ${{ matrix.target }}
|
||||
pnpm run prebuild ${{ matrix.target }}
|
||||
|
||||
- name: Release ${{ env.TAG_CHANNEL }} Version
|
||||
run: pnpm release-version ${{ env.TAG_NAME }}
|
||||
@@ -388,22 +391,23 @@ jobs:
|
||||
with:
|
||||
workspaces: src-tauri
|
||||
cache-all-crates: true
|
||||
cache-on-failure: true
|
||||
save-if: ${{ github.ref == 'refs/heads/dev' }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
run_install: false
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Pnpm install and check
|
||||
run: |
|
||||
pnpm i
|
||||
pnpm prepare ${{ matrix.target }}
|
||||
pnpm run prebuild ${{ matrix.target }}
|
||||
|
||||
- name: Release ${{ env.TAG_CHANNEL }} Version
|
||||
run: pnpm release-version ${{ env.TAG_NAME }}
|
||||
|
||||
13
.github/workflows/clippy.yml
vendored
13
.github/workflows/clippy.yml
vendored
@@ -27,12 +27,11 @@ jobs:
|
||||
- name: Add Rust Target
|
||||
run: rustup target add ${{ matrix.target }}
|
||||
|
||||
# - name: Rust Cache
|
||||
# uses: Swatinem/rust-cache@v2
|
||||
# with:
|
||||
# workspaces: src-tauri
|
||||
# cache-all-crates: true
|
||||
# cache-on-failure: true
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: src-tauri
|
||||
save-if: false
|
||||
|
||||
- name: Install dependencies (ubuntu only)
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
@@ -53,7 +52,7 @@ jobs:
|
||||
- name: Pnpm install and check
|
||||
run: |
|
||||
pnpm i
|
||||
pnpm prepare ${{ matrix.target }}
|
||||
pnpm run prebuild ${{ matrix.target }}
|
||||
|
||||
- name: Build Web Assets
|
||||
run: pnpm run web:build
|
||||
|
||||
5
.github/workflows/cross_check.yaml
vendored
5
.github/workflows/cross_check.yaml
vendored
@@ -50,14 +50,13 @@ jobs:
|
||||
- name: Pnpm install and check
|
||||
run: |
|
||||
pnpm i
|
||||
pnpm prepare ${{ matrix.target }}
|
||||
pnpm run prebuild ${{ matrix.target }}
|
||||
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: src-tauri
|
||||
cache-all-crates: true
|
||||
cache-on-failure: true
|
||||
save-if: false
|
||||
|
||||
- name: Cargo Check (deny warnings)
|
||||
working-directory: src-tauri
|
||||
|
||||
5
.github/workflows/dev.yml
vendored
5
.github/workflows/dev.yml
vendored
@@ -42,8 +42,7 @@ jobs:
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: src-tauri
|
||||
cache-all-crates: true
|
||||
cache-on-failure: true
|
||||
save-if: false
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
@@ -58,7 +57,7 @@ jobs:
|
||||
- name: Pnpm install and check
|
||||
run: |
|
||||
pnpm i
|
||||
pnpm prepare ${{ matrix.target }}
|
||||
pnpm run prebuild ${{ matrix.target }}
|
||||
|
||||
- name: Tauri build
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
|
||||
1
.github/workflows/fmt.yml
vendored
1
.github/workflows/fmt.yml
vendored
@@ -30,7 +30,6 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
cache: "pnpm"
|
||||
- run: pnpm i --frozen-lockfile
|
||||
- run: pnpm format:check
|
||||
|
||||
|
||||
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
@@ -73,8 +73,7 @@ jobs:
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: src-tauri
|
||||
cache-all-crates: true
|
||||
cache-on-failure: true
|
||||
save-if: false
|
||||
|
||||
- name: Install dependencies (ubuntu only)
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
@@ -95,7 +94,7 @@ jobs:
|
||||
- name: Pnpm install and check
|
||||
run: |
|
||||
pnpm i
|
||||
pnpm prepare ${{ matrix.target }}
|
||||
pnpm run prebuild ${{ matrix.target }}
|
||||
|
||||
- name: Tauri build
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
@@ -144,7 +143,7 @@ jobs:
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: src-tauri
|
||||
cache-all-crates: true
|
||||
save-if: false
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
@@ -159,7 +158,7 @@ jobs:
|
||||
- name: Pnpm install and check
|
||||
run: |
|
||||
pnpm i
|
||||
pnpm prepare ${{ matrix.target }}
|
||||
pnpm run prebuild ${{ matrix.target }}
|
||||
|
||||
- name: "Setup for linux"
|
||||
run: |-
|
||||
@@ -263,8 +262,7 @@ jobs:
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: src-tauri
|
||||
cache-all-crates: true
|
||||
cache-on-failure: true
|
||||
save-if: false
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
@@ -279,7 +277,7 @@ jobs:
|
||||
- name: Pnpm install and check
|
||||
run: |
|
||||
pnpm i
|
||||
pnpm prepare ${{ matrix.target }}
|
||||
pnpm run prebuild ${{ matrix.target }}
|
||||
|
||||
- name: Download WebView2 Runtime
|
||||
run: |
|
||||
|
||||
@@ -8,4 +8,20 @@ if git diff --cached --name-only | grep -q '^src-tauri/'; then
|
||||
fi
|
||||
fi
|
||||
|
||||
remote_name="$1"
|
||||
remote_url=$(git remote get-url "$remote_name")
|
||||
|
||||
if [[ "$remote_url" =~ github\.com[:/]+clash-verge-rev/clash-verge-rev(\.git)?$ ]]; then
|
||||
echo "[pre-push] Detected push to clash-verge-rev/clash-verge-rev ($remote_url)"
|
||||
echo "[pre-push] Running pnpm format:check..."
|
||||
|
||||
pnpm format:check
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "❌ Code format check failed. Please fix formatting before pushing."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "[pre-push] Not pushing to target repo. Skipping format check."
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -52,9 +52,9 @@ You have two options for downloading the clash binary:
|
||||
|
||||
- Automatically download it via the provided script:
|
||||
```shell
|
||||
pnpm run prepare
|
||||
pnpm run prebuild
|
||||
# Use '--force' to force update to the latest version
|
||||
# pnpm run prepare --force
|
||||
# pnpm run prebuild --force
|
||||
```
|
||||
- Manually download it from the [Mihomo release](https://github.com/MetaCubeX/mihomo/releases). After downloading, rename the binary according to the [Tauri configuration](https://tauri.app/v1/api/config#bundleconfig.externalbin).
|
||||
|
||||
|
||||
35
UPDATELOG.md
35
UPDATELOG.md
@@ -1,3 +1,25 @@
|
||||
## v2.3.1
|
||||
|
||||
### 🐞 修复问题
|
||||
|
||||
- 增加配置文件校验,修复从古老版本升级上来的"No such file or directory (os error 2)"错误
|
||||
- 修复扩展脚本转义错误
|
||||
- 修复 macOS Intel X86 架构构建错误导致无法运行
|
||||
- 修复 Linux 下界面边框白边问题
|
||||
- 修复 托盘 无响应问题
|
||||
- 修复 托盘 无法从轻量模式退出并恢复窗口
|
||||
- 修复 快速切换订阅可能导致的卡死问题
|
||||
|
||||
### ✨ 新增功能
|
||||
|
||||
- 新增 window-state 窗口状态管理和恢复
|
||||
|
||||
### 🚀 优化改进
|
||||
|
||||
- 优化 托盘 统一响应
|
||||
- 优化 静默启动+自启动轻量模式 运行方式
|
||||
- 升级依赖
|
||||
|
||||
## v2.3.0
|
||||
|
||||
**发行代号:御**
|
||||
@@ -5,20 +27,17 @@
|
||||
|
||||
尽管 `external-controller` 密钥现已自动补全默认值且不允许为空,**仍建议手动修改密钥以提高安全性**。
|
||||
|
||||
---
|
||||
|
||||
### ⚠️ 已知问题
|
||||
|
||||
- 仅在 Ubuntu 22.04/24.04、Fedora 41 的 **GNOME 桌面环境** 做过简单测试,不保证其他 Linux 发行版兼容,后续将逐步适配和优化。
|
||||
- macOS:
|
||||
|
||||
- MacOS 下自动升级成功后请关闭程序等待 30 秒重启,因为 MacOS 的端口释放特性,卸载服务后需重启应用等 30 秒才能恢复内核通信。立即启动可能无法正常启动内核。
|
||||
- 墙贴主要为浅色,深色 Tray 图标存在闪烁问题;
|
||||
- 彩色 Tray 图标颜色偏淡;
|
||||
|
||||
- 已确认窗口状态管理器存在上游缺陷,已暂时移除窗口大小与位置记忆功能。
|
||||
|
||||
---
|
||||
|
||||
### 🐞 修复问题
|
||||
|
||||
- 修复首页“代理模式”快速切换导致的卡死问题
|
||||
@@ -42,8 +61,6 @@
|
||||
- 修复 JS 脚本转义特殊字符报错
|
||||
- 修复 macOS 静默启动时异常启动 Dock 栏图标
|
||||
|
||||
---
|
||||
|
||||
### ✨ 新增功能
|
||||
|
||||
- **Mihomo(Meta) 内核升级至 v1.19.10**
|
||||
@@ -72,8 +89,6 @@
|
||||
- 新增 Zashboard 一键跳转入口
|
||||
- 使用系统默认窗口管理器
|
||||
|
||||
---
|
||||
|
||||
### 🚀 优化改进
|
||||
|
||||
- **系统相关:**
|
||||
@@ -112,13 +127,9 @@
|
||||
- 网络延迟测试替换为 HTTPS 协议:`https://cp.cloudflare.com/generate_204`
|
||||
- 优化 IP 信息获取流程,添加去重机制与轮询检测算法
|
||||
|
||||
---
|
||||
|
||||
- 同步修复翻译错误与不一致项,优化整体语言体验
|
||||
- 加强语言切换后的页面稳定性,避免加载异常
|
||||
|
||||
---
|
||||
|
||||
### 🗑️ 移除内容
|
||||
|
||||
- 窗口状态管理器(上游存在缺陷)
|
||||
|
||||
32
package.json
32
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "clash-verge",
|
||||
"version": "2.3.0",
|
||||
"version": "2.3.1",
|
||||
"license": "GPL-3.0-only",
|
||||
"scripts": {
|
||||
"dev": "cross-env RUST_BACKTRACE=1 tauri dev -f verge-dev",
|
||||
@@ -11,7 +11,7 @@
|
||||
"web:dev": "vite",
|
||||
"web:build": "tsc --noEmit && vite build",
|
||||
"web:serve": "vite preview",
|
||||
"prepare": "node scripts/prepare.mjs",
|
||||
"prebuild": "node scripts/prebuild.mjs",
|
||||
"updater": "node scripts/updater.mjs",
|
||||
"updater-fixed-webview2": "node scripts/updater-fixed-webview2.mjs",
|
||||
"portable": "node scripts/portable.mjs",
|
||||
@@ -34,27 +34,27 @@
|
||||
"@mui/icons-material": "^7.1.1",
|
||||
"@mui/lab": "7.0.0-beta.13",
|
||||
"@mui/material": "^7.1.1",
|
||||
"@mui/x-data-grid": "^8.5.1",
|
||||
"@mui/x-data-grid": "^8.5.2",
|
||||
"@tauri-apps/api": "2.5.0",
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.2.2",
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.2.3",
|
||||
"@tauri-apps/plugin-dialog": "^2.2.2",
|
||||
"@tauri-apps/plugin-fs": "^2.3.0",
|
||||
"@tauri-apps/plugin-global-shortcut": "^2.2.1",
|
||||
"@tauri-apps/plugin-notification": "^2.2.2",
|
||||
"@tauri-apps/plugin-process": "^2.2.1",
|
||||
"@tauri-apps/plugin-shell": "2.2.1",
|
||||
"@tauri-apps/plugin-updater": "2.7.1",
|
||||
"@tauri-apps/plugin-window-state": "^2.2.2",
|
||||
"@tauri-apps/plugin-notification": "^2.2.3",
|
||||
"@tauri-apps/plugin-process": "^2.2.2",
|
||||
"@tauri-apps/plugin-shell": "2.2.2",
|
||||
"@tauri-apps/plugin-updater": "2.8.1",
|
||||
"@tauri-apps/plugin-window-state": "^2.2.3",
|
||||
"@types/d3-shape": "^3.1.7",
|
||||
"@types/json-schema": "^7.0.15",
|
||||
"ahooks": "^3.8.5",
|
||||
"axios": "^1.9.0",
|
||||
"chart.js": "^4.4.9",
|
||||
"axios": "^1.10.0",
|
||||
"chart.js": "^4.5.0",
|
||||
"cli-color": "^2.0.4",
|
||||
"d3-shape": "^3.2.0",
|
||||
"dayjs": "1.11.13",
|
||||
"foxact": "^0.2.49",
|
||||
"glob": "^11.0.2",
|
||||
"glob": "^11.0.3",
|
||||
"i18next": "^25.2.1",
|
||||
"js-base64": "^3.7.7",
|
||||
"js-yaml": "^4.1.0",
|
||||
@@ -67,12 +67,12 @@
|
||||
"react-chartjs-2": "^5.3.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-error-boundary": "6.0.0",
|
||||
"react-hook-form": "^7.57.0",
|
||||
"react-i18next": "15.5.2",
|
||||
"react-hook-form": "^7.58.1",
|
||||
"react-i18next": "15.5.3",
|
||||
"react-markdown": "10.1.0",
|
||||
"react-monaco-editor": "0.58.0",
|
||||
"react-router-dom": "7.6.2",
|
||||
"react-virtuoso": "^4.12.8",
|
||||
"react-virtuoso": "^4.13.0",
|
||||
"sockette": "^2.0.6",
|
||||
"swr": "^2.3.3",
|
||||
"tar": "^7.4.3",
|
||||
@@ -99,7 +99,7 @@
|
||||
"prettier": "^3.5.3",
|
||||
"pretty-quick": "^4.2.2",
|
||||
"sass": "^1.89.2",
|
||||
"terser": "^5.42.0",
|
||||
"terser": "^5.43.0",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^6.3.5",
|
||||
"vite-plugin-monaco-editor": "^1.1.0",
|
||||
|
||||
228
pnpm-lock.yaml
generated
228
pnpm-lock.yaml
generated
@@ -36,14 +36,14 @@ importers:
|
||||
specifier: ^7.1.1
|
||||
version: 7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@mui/x-data-grid':
|
||||
specifier: ^8.5.1
|
||||
version: 8.5.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@mui/material@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mui/system@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
specifier: ^8.5.2
|
||||
version: 8.5.2(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@mui/material@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mui/system@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@tauri-apps/api':
|
||||
specifier: 2.5.0
|
||||
version: 2.5.0
|
||||
'@tauri-apps/plugin-clipboard-manager':
|
||||
specifier: ^2.2.2
|
||||
version: 2.2.2
|
||||
specifier: ^2.2.3
|
||||
version: 2.2.3
|
||||
'@tauri-apps/plugin-dialog':
|
||||
specifier: ^2.2.2
|
||||
version: 2.2.2
|
||||
@@ -54,20 +54,20 @@ importers:
|
||||
specifier: ^2.2.1
|
||||
version: 2.2.1
|
||||
'@tauri-apps/plugin-notification':
|
||||
specifier: ^2.2.2
|
||||
version: 2.2.2
|
||||
specifier: ^2.2.3
|
||||
version: 2.2.3
|
||||
'@tauri-apps/plugin-process':
|
||||
specifier: ^2.2.1
|
||||
version: 2.2.1
|
||||
'@tauri-apps/plugin-shell':
|
||||
specifier: 2.2.1
|
||||
version: 2.2.1
|
||||
'@tauri-apps/plugin-updater':
|
||||
specifier: 2.7.1
|
||||
version: 2.7.1
|
||||
'@tauri-apps/plugin-window-state':
|
||||
specifier: ^2.2.2
|
||||
version: 2.2.2
|
||||
'@tauri-apps/plugin-shell':
|
||||
specifier: 2.2.2
|
||||
version: 2.2.2
|
||||
'@tauri-apps/plugin-updater':
|
||||
specifier: 2.8.1
|
||||
version: 2.8.1
|
||||
'@tauri-apps/plugin-window-state':
|
||||
specifier: ^2.2.3
|
||||
version: 2.2.3
|
||||
'@types/d3-shape':
|
||||
specifier: ^3.1.7
|
||||
version: 3.1.7
|
||||
@@ -78,11 +78,11 @@ importers:
|
||||
specifier: ^3.8.5
|
||||
version: 3.8.5(react@19.1.0)
|
||||
axios:
|
||||
specifier: ^1.9.0
|
||||
version: 1.9.0
|
||||
specifier: ^1.10.0
|
||||
version: 1.10.0
|
||||
chart.js:
|
||||
specifier: ^4.4.9
|
||||
version: 4.4.9
|
||||
specifier: ^4.5.0
|
||||
version: 4.5.0
|
||||
cli-color:
|
||||
specifier: ^2.0.4
|
||||
version: 2.0.4
|
||||
@@ -96,8 +96,8 @@ importers:
|
||||
specifier: ^0.2.49
|
||||
version: 0.2.49(react@19.1.0)
|
||||
glob:
|
||||
specifier: ^11.0.2
|
||||
version: 11.0.2
|
||||
specifier: ^11.0.3
|
||||
version: 11.0.3
|
||||
i18next:
|
||||
specifier: ^25.2.1
|
||||
version: 25.2.1(typescript@5.8.3)
|
||||
@@ -127,7 +127,7 @@ importers:
|
||||
version: 19.1.0
|
||||
react-chartjs-2:
|
||||
specifier: ^5.3.0
|
||||
version: 5.3.0(chart.js@4.4.9)(react@19.1.0)
|
||||
version: 5.3.0(chart.js@4.5.0)(react@19.1.0)
|
||||
react-dom:
|
||||
specifier: 19.1.0
|
||||
version: 19.1.0(react@19.1.0)
|
||||
@@ -135,11 +135,11 @@ importers:
|
||||
specifier: 6.0.0
|
||||
version: 6.0.0(react@19.1.0)
|
||||
react-hook-form:
|
||||
specifier: ^7.57.0
|
||||
version: 7.57.0(react@19.1.0)
|
||||
specifier: ^7.58.1
|
||||
version: 7.58.1(react@19.1.0)
|
||||
react-i18next:
|
||||
specifier: 15.5.2
|
||||
version: 15.5.2(i18next@25.2.1(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3)
|
||||
specifier: 15.5.3
|
||||
version: 15.5.3(i18next@25.2.1(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3)
|
||||
react-markdown:
|
||||
specifier: 10.1.0
|
||||
version: 10.1.0(@types/react@19.1.8)(react@19.1.0)
|
||||
@@ -150,8 +150,8 @@ importers:
|
||||
specifier: 7.6.2
|
||||
version: 7.6.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
react-virtuoso:
|
||||
specifier: ^4.12.8
|
||||
version: 4.12.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
specifier: ^4.13.0
|
||||
version: 4.13.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
sockette:
|
||||
specifier: ^2.0.6
|
||||
version: 2.0.6
|
||||
@@ -191,10 +191,10 @@ importers:
|
||||
version: 19.1.6(@types/react@19.1.8)
|
||||
'@vitejs/plugin-legacy':
|
||||
specifier: ^6.1.1
|
||||
version: 6.1.1(terser@5.42.0)(vite@6.3.5(sass@1.89.2)(terser@5.42.0)(yaml@2.7.1))
|
||||
version: 6.1.1(terser@5.43.0)(vite@6.3.5(sass@1.89.2)(terser@5.43.0)(yaml@2.7.1))
|
||||
'@vitejs/plugin-react':
|
||||
specifier: 4.5.2
|
||||
version: 4.5.2(vite@6.3.5(sass@1.89.2)(terser@5.42.0)(yaml@2.7.1))
|
||||
version: 4.5.2(vite@6.3.5(sass@1.89.2)(terser@5.43.0)(yaml@2.7.1))
|
||||
adm-zip:
|
||||
specifier: ^0.5.16
|
||||
version: 0.5.16
|
||||
@@ -226,20 +226,20 @@ importers:
|
||||
specifier: ^1.89.2
|
||||
version: 1.89.2
|
||||
terser:
|
||||
specifier: ^5.42.0
|
||||
version: 5.42.0
|
||||
specifier: ^5.43.0
|
||||
version: 5.43.0
|
||||
typescript:
|
||||
specifier: ^5.8.3
|
||||
version: 5.8.3
|
||||
vite:
|
||||
specifier: ^6.3.5
|
||||
version: 6.3.5(sass@1.89.2)(terser@5.42.0)(yaml@2.7.1)
|
||||
version: 6.3.5(sass@1.89.2)(terser@5.43.0)(yaml@2.7.1)
|
||||
vite-plugin-monaco-editor:
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0(monaco-editor@0.52.2)
|
||||
vite-plugin-svgr:
|
||||
specifier: ^4.3.0
|
||||
version: 4.3.0(rollup@4.40.2)(typescript@5.8.3)(vite@6.3.5(sass@1.89.2)(terser@5.42.0)(yaml@2.7.1))
|
||||
version: 4.3.0(rollup@4.40.2)(typescript@5.8.3)(vite@6.3.5(sass@1.89.2)(terser@5.43.0)(yaml@2.7.1))
|
||||
|
||||
packages:
|
||||
|
||||
@@ -980,6 +980,14 @@ packages:
|
||||
resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
'@isaacs/balanced-match@4.0.1':
|
||||
resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
'@isaacs/brace-expansion@5.0.0':
|
||||
resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
'@isaacs/cliui@8.0.2':
|
||||
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -1127,8 +1135,8 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@mui/x-data-grid@8.5.1':
|
||||
resolution: {integrity: sha512-Ukodx8cOc/GR4+2zr4DRNBJJOlyeJNaxK4hggWv7VchDL7Jf70dLZO7oLXPhEFG05Yna81RatL/UFsRYIdWI1Q==}
|
||||
'@mui/x-data-grid@8.5.2':
|
||||
resolution: {integrity: sha512-4KzawLZqRKp3KcGKsTDVz7zkEjACllQD5Zb8ds1QKlA6C3/oIoSU7PsemFLj+RL3rT5aORsLMBl97/egQ5tUhA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
'@emotion/react': ^11.9.0
|
||||
@@ -1143,8 +1151,8 @@ packages:
|
||||
'@emotion/styled':
|
||||
optional: true
|
||||
|
||||
'@mui/x-internals@8.5.1':
|
||||
resolution: {integrity: sha512-7rAWK7SB6FxEIXKgsHsJjIzeeKOLxFJ16gePgZVWlvyew+xDb4P0fgjwW3ThcJjgvkUm0UhGGfLh/JP8l514IA==}
|
||||
'@mui/x-internals@8.5.2':
|
||||
resolution: {integrity: sha512-5YhB2AekK7G8d0YrAjg3WNf0uy3V73JD98WNxJhbIlCraQgl8QOQzr2zNO7MAf/X7mZQtjpjuAsiG3+gI2NVyg==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
'@mui/system': ^5.15.14 || ^6.0.0 || ^7.0.0
|
||||
@@ -1545,8 +1553,8 @@ packages:
|
||||
engines: {node: '>= 10'}
|
||||
hasBin: true
|
||||
|
||||
'@tauri-apps/plugin-clipboard-manager@2.2.2':
|
||||
resolution: {integrity: sha512-bZvDLMqfcNmsw7Ag8I49jlaCjdpDvvlJHnpp6P+Gg/3xtpSERdwlDxm7cKGbs2mj46dsw4AuG3RoAgcpwgioUA==}
|
||||
'@tauri-apps/plugin-clipboard-manager@2.2.3':
|
||||
resolution: {integrity: sha512-myZTLyBpJ9gnDsywtdgRpAYLxEtSVaJa11s1xoiB6w8cjFtG2/znas4Cz3vqYigJkY0A57tyZUE6tjxavIAzgw==}
|
||||
|
||||
'@tauri-apps/plugin-dialog@2.2.2':
|
||||
resolution: {integrity: sha512-Pm9qnXQq8ZVhAMFSEPwxvh+nWb2mk7LASVlNEHYaksHvcz8P6+ElR5U5dNL9Ofrm+uwhh1/gYKWswK8JJJAh6A==}
|
||||
@@ -1557,20 +1565,20 @@ packages:
|
||||
'@tauri-apps/plugin-global-shortcut@2.2.1':
|
||||
resolution: {integrity: sha512-b64/TI1t5LIi2JY4OWlYjZpPRq60T5GVVL/no27sUuxaNUZY8dVtwsMtDUgxUpln2yR+P2PJsYlqY5V8sLSxEw==}
|
||||
|
||||
'@tauri-apps/plugin-notification@2.2.2':
|
||||
resolution: {integrity: sha512-d71rJdtkFUTcG4dqydnv6d7ZwlNZVcdjrVOPwc9GsF6y9DgVN1WCZ9T/vbfD2qrJslf7ai+rnNJc62TLLC2IdA==}
|
||||
'@tauri-apps/plugin-notification@2.2.3':
|
||||
resolution: {integrity: sha512-IlMdSVFsrKg0eIHBloFFosnWbbz6JdwBywfZrYZnE1+acgXvNS3T1YB5w9R6UXw+KKQ94ODBu7JF7a1YUiAK6A==}
|
||||
|
||||
'@tauri-apps/plugin-process@2.2.1':
|
||||
resolution: {integrity: sha512-cF/k8J+YjjuowhNG1AboHNTlrGiOwgX5j6NzsX6WFf9FMzyZUchkCgZMxCdSE5NIgFX0vvOgLQhODFJgbMenLg==}
|
||||
'@tauri-apps/plugin-process@2.2.2':
|
||||
resolution: {integrity: sha512-1HuR+uGcokQxlgbS0DheFyMpJSuVGuy3Yh3Eq5o3Jm/sEW+44JaVVgYWM0efpDPA8oT5wpabTFEOZHvKfp8dCg==}
|
||||
|
||||
'@tauri-apps/plugin-shell@2.2.1':
|
||||
resolution: {integrity: sha512-G1GFYyWe/KlCsymuLiNImUgC8zGY0tI0Y3p8JgBCWduR5IEXlIJS+JuG1qtveitwYXlfJrsExt3enhv5l2/yhA==}
|
||||
'@tauri-apps/plugin-shell@2.2.2':
|
||||
resolution: {integrity: sha512-fg9XKWfzRQsN8p+Zrk82WeHvXFvGVnG0/mTlujQdLWNnO5cM6WD9qCrHbFytScVS+WhmRAkuypQPcxeKKl3VBg==}
|
||||
|
||||
'@tauri-apps/plugin-updater@2.7.1':
|
||||
resolution: {integrity: sha512-1OPqEY/z7NDVSeTEMIhD2ss/vXWdpfZ5Th2Mk0KtPR/RA6FKuOTDGZQhxoyYBk0pcZJ+nNZUbl/IujDCLBApjA==}
|
||||
'@tauri-apps/plugin-updater@2.8.1':
|
||||
resolution: {integrity: sha512-VVQ3wCfM+zok/e0QNBT0oBaFu3gBbzzMsRHzS2yxl7VOCh9dWuZo4yyl29OfaExxSvcixWJ9BZ6pWmKdUt8fCg==}
|
||||
|
||||
'@tauri-apps/plugin-window-state@2.2.2':
|
||||
resolution: {integrity: sha512-7pFwmMtGhhhE/WgmM7PUrj0BSSWVAQMfDdYbRalphIqqF1tWBvxtlxclx8bTutpXHLJTQoCpIeWtBEIXsoAlGw==}
|
||||
'@tauri-apps/plugin-window-state@2.2.3':
|
||||
resolution: {integrity: sha512-Iqqzugs6lxpa9JPOe4O33lkCUyMGvh9dqnXof1tK4dP2wU7jKa7W3MLwVyo6c3oVl3dUCm73wkB3RJ0exR0SPg==}
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
|
||||
@@ -1705,8 +1713,8 @@ packages:
|
||||
asynckit@0.4.0:
|
||||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||
|
||||
axios@1.9.0:
|
||||
resolution: {integrity: sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==}
|
||||
axios@1.10.0:
|
||||
resolution: {integrity: sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==}
|
||||
|
||||
babel-plugin-macros@3.1.0:
|
||||
resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==}
|
||||
@@ -1730,15 +1738,9 @@ packages:
|
||||
bail@2.0.2:
|
||||
resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
|
||||
|
||||
balanced-match@1.0.2:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
|
||||
before-after-hook@2.2.3:
|
||||
resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==}
|
||||
|
||||
brace-expansion@2.0.1:
|
||||
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
|
||||
|
||||
braces@3.0.3:
|
||||
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -1788,8 +1790,8 @@ packages:
|
||||
character-reference-invalid@2.0.1:
|
||||
resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==}
|
||||
|
||||
chart.js@4.4.9:
|
||||
resolution: {integrity: sha512-EyZ9wWKgpAU0fLJ43YAEIF8sr5F2W3LqbS40ZJyHIner2lY14ufqv2VMp69MAiZ2rpwxEUxEhIH/0U3xyRynxg==}
|
||||
chart.js@4.5.0:
|
||||
resolution: {integrity: sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==}
|
||||
engines: {pnpm: '>=8'}
|
||||
|
||||
chokidar@4.0.3:
|
||||
@@ -2086,8 +2088,8 @@ packages:
|
||||
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
glob@11.0.2:
|
||||
resolution: {integrity: sha512-YT7U7Vye+t5fZ/QMkBFrTJ7ZQxInIUjwyAjVj84CYXqgBdv30MFUPGnBR6sQaVq6Is15wYJUsnzTuWaGRBhBAQ==}
|
||||
glob@11.0.3:
|
||||
resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==}
|
||||
engines: {node: 20 || >=22}
|
||||
hasBin: true
|
||||
|
||||
@@ -2205,8 +2207,8 @@ packages:
|
||||
isexe@2.0.0:
|
||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||
|
||||
jackspeak@4.1.0:
|
||||
resolution: {integrity: sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==}
|
||||
jackspeak@4.1.1:
|
||||
resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
js-base64@3.7.7:
|
||||
@@ -2394,8 +2396,8 @@ packages:
|
||||
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
minimatch@10.0.1:
|
||||
resolution: {integrity: sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==}
|
||||
minimatch@10.0.3:
|
||||
resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
minipass@7.1.2:
|
||||
@@ -2570,14 +2572,14 @@ packages:
|
||||
react-fast-compare@3.2.2:
|
||||
resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==}
|
||||
|
||||
react-hook-form@7.57.0:
|
||||
resolution: {integrity: sha512-RbEks3+cbvTP84l/VXGUZ+JMrKOS8ykQCRYdm5aYsxnDquL0vspsyNhGRO7pcH6hsZqWlPOjLye7rJqdtdAmlg==}
|
||||
react-hook-form@7.58.1:
|
||||
resolution: {integrity: sha512-Lml/KZYEEFfPhUVgE0RdCVpnC4yhW+PndRhbiTtdvSlQTL8IfVR+iQkBjLIvmmc6+GGoVeM11z37ktKFPAb0FA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17 || ^18 || ^19
|
||||
|
||||
react-i18next@15.5.2:
|
||||
resolution: {integrity: sha512-ePODyXgmZQAOYTbZXQn5rRsSBu3Gszo69jxW6aKmlSgxKAI1fOhDwSu6bT4EKHciWPKQ7v7lPrjeiadR6Gi+1A==}
|
||||
react-i18next@15.5.3:
|
||||
resolution: {integrity: sha512-ypYmOKOnjqPEJZO4m1BI0kS8kWqkBNsKYyhVUfij0gvjy9xJNoG/VcGkxq5dRlVwzmrmY1BQMAmpbbUBLwC4Kw==}
|
||||
peerDependencies:
|
||||
i18next: '>= 23.2.3'
|
||||
react: '>= 16.8.0'
|
||||
@@ -2638,8 +2640,8 @@ packages:
|
||||
react: '>=16.6.0'
|
||||
react-dom: '>=16.6.0'
|
||||
|
||||
react-virtuoso@4.12.8:
|
||||
resolution: {integrity: sha512-NMMKfDBr/+xZZqCQF3tN1SZsh6FwOJkYgThlfnsPLkaEhdyQo0EuWUzu3ix6qjnI7rYwJhMwRGoJBi+aiDfGsA==}
|
||||
react-virtuoso@4.13.0:
|
||||
resolution: {integrity: sha512-XHv2Fglpx80yFPdjZkV9d1baACKghg/ucpDFEXwaix7z0AfVQj+mF6lM+YQR6UC/TwzXG2rJKydRMb3+7iV3PA==}
|
||||
peerDependencies:
|
||||
react: '>=16 || >=17 || >= 18 || >= 19'
|
||||
react-dom: '>=16 || >=17 || >= 18 || >=19'
|
||||
@@ -2813,8 +2815,8 @@ packages:
|
||||
resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
terser@5.42.0:
|
||||
resolution: {integrity: sha512-UYCvU9YQW2f/Vwl+P0GfhxJxbUGLwd+5QrrGgLajzWAtC/23AX0vcise32kkP7Eu0Wu9VlzzHAXkLObgjQfFlQ==}
|
||||
terser@5.43.0:
|
||||
resolution: {integrity: sha512-CqNNxKSGKSZCunSvwKLTs8u8sGGlp27sxNZ4quGh0QeNuyHM0JSEM/clM9Mf4zUp6J+tO2gUXhgXT2YMMkwfKQ==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
@@ -3896,6 +3898,12 @@ snapshots:
|
||||
|
||||
'@fastify/busboy@2.1.1': {}
|
||||
|
||||
'@isaacs/balanced-match@4.0.1': {}
|
||||
|
||||
'@isaacs/brace-expansion@5.0.0':
|
||||
dependencies:
|
||||
'@isaacs/balanced-match': 4.0.1
|
||||
|
||||
'@isaacs/cliui@8.0.2':
|
||||
dependencies:
|
||||
string-width: 5.1.2
|
||||
@@ -4038,18 +4046,17 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.1.8
|
||||
|
||||
'@mui/x-data-grid@8.5.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@mui/material@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mui/system@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||
'@mui/x-data-grid@8.5.2(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@mui/material@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mui/system@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.27.6
|
||||
'@mui/material': 7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@mui/system': 7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)
|
||||
'@mui/utils': 7.1.1(@types/react@19.1.8)(react@19.1.0)
|
||||
'@mui/x-internals': 8.5.1(@mui/system@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)
|
||||
'@mui/x-internals': 8.5.2(@mui/system@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)
|
||||
clsx: 2.1.1
|
||||
prop-types: 15.8.1
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
reselect: 5.1.1
|
||||
use-sync-external-store: 1.5.0(react@19.1.0)
|
||||
optionalDependencies:
|
||||
'@emotion/react': 11.14.0(@types/react@19.1.8)(react@19.1.0)
|
||||
@@ -4057,12 +4064,13 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
|
||||
'@mui/x-internals@8.5.1(@mui/system@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)':
|
||||
'@mui/x-internals@8.5.2(@mui/system@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.27.6
|
||||
'@mui/system': 7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)
|
||||
'@mui/utils': 7.1.1(@types/react@19.1.8)(react@19.1.0)
|
||||
react: 19.1.0
|
||||
reselect: 5.1.1
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
|
||||
@@ -4382,7 +4390,7 @@ snapshots:
|
||||
'@tauri-apps/cli-win32-ia32-msvc': 2.5.0
|
||||
'@tauri-apps/cli-win32-x64-msvc': 2.5.0
|
||||
|
||||
'@tauri-apps/plugin-clipboard-manager@2.2.2':
|
||||
'@tauri-apps/plugin-clipboard-manager@2.2.3':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.5.0
|
||||
|
||||
@@ -4398,23 +4406,23 @@ snapshots:
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.5.0
|
||||
|
||||
'@tauri-apps/plugin-notification@2.2.2':
|
||||
'@tauri-apps/plugin-notification@2.2.3':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.5.0
|
||||
|
||||
'@tauri-apps/plugin-process@2.2.1':
|
||||
'@tauri-apps/plugin-process@2.2.2':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.5.0
|
||||
|
||||
'@tauri-apps/plugin-shell@2.2.1':
|
||||
'@tauri-apps/plugin-shell@2.2.2':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.5.0
|
||||
|
||||
'@tauri-apps/plugin-updater@2.7.1':
|
||||
'@tauri-apps/plugin-updater@2.8.1':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.5.0
|
||||
|
||||
'@tauri-apps/plugin-window-state@2.2.2':
|
||||
'@tauri-apps/plugin-window-state@2.2.3':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.5.0
|
||||
|
||||
@@ -4499,7 +4507,7 @@ snapshots:
|
||||
|
||||
'@ungap/structured-clone@1.3.0': {}
|
||||
|
||||
'@vitejs/plugin-legacy@6.1.1(terser@5.42.0)(vite@6.3.5(sass@1.89.2)(terser@5.42.0)(yaml@2.7.1))':
|
||||
'@vitejs/plugin-legacy@6.1.1(terser@5.43.0)(vite@6.3.5(sass@1.89.2)(terser@5.43.0)(yaml@2.7.1))':
|
||||
dependencies:
|
||||
'@babel/core': 7.27.4
|
||||
'@babel/preset-env': 7.27.2(@babel/core@7.27.4)
|
||||
@@ -4509,12 +4517,12 @@ snapshots:
|
||||
magic-string: 0.30.17
|
||||
regenerator-runtime: 0.14.1
|
||||
systemjs: 6.15.1
|
||||
terser: 5.42.0
|
||||
vite: 6.3.5(sass@1.89.2)(terser@5.42.0)(yaml@2.7.1)
|
||||
terser: 5.43.0
|
||||
vite: 6.3.5(sass@1.89.2)(terser@5.43.0)(yaml@2.7.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@vitejs/plugin-react@4.5.2(vite@6.3.5(sass@1.89.2)(terser@5.42.0)(yaml@2.7.1))':
|
||||
'@vitejs/plugin-react@4.5.2(vite@6.3.5(sass@1.89.2)(terser@5.43.0)(yaml@2.7.1))':
|
||||
dependencies:
|
||||
'@babel/core': 7.27.4
|
||||
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.27.4)
|
||||
@@ -4522,7 +4530,7 @@ snapshots:
|
||||
'@rolldown/pluginutils': 1.0.0-beta.11
|
||||
'@types/babel__core': 7.20.5
|
||||
react-refresh: 0.17.0
|
||||
vite: 6.3.5(sass@1.89.2)(terser@5.42.0)(yaml@2.7.1)
|
||||
vite: 6.3.5(sass@1.89.2)(terser@5.43.0)(yaml@2.7.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -4559,7 +4567,7 @@ snapshots:
|
||||
|
||||
asynckit@0.4.0: {}
|
||||
|
||||
axios@1.9.0:
|
||||
axios@1.10.0:
|
||||
dependencies:
|
||||
follow-redirects: 1.15.9
|
||||
form-data: 4.0.2
|
||||
@@ -4599,14 +4607,8 @@ snapshots:
|
||||
|
||||
bail@2.0.2: {}
|
||||
|
||||
balanced-match@1.0.2: {}
|
||||
|
||||
before-after-hook@2.2.3: {}
|
||||
|
||||
brace-expansion@2.0.1:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
|
||||
braces@3.0.3:
|
||||
dependencies:
|
||||
fill-range: 7.1.1
|
||||
@@ -4647,7 +4649,7 @@ snapshots:
|
||||
|
||||
character-reference-invalid@2.0.1: {}
|
||||
|
||||
chart.js@4.4.9:
|
||||
chart.js@4.5.0:
|
||||
dependencies:
|
||||
'@kurkle/color': 0.3.4
|
||||
|
||||
@@ -4954,11 +4956,11 @@ snapshots:
|
||||
dunder-proto: 1.0.1
|
||||
es-object-atoms: 1.1.1
|
||||
|
||||
glob@11.0.2:
|
||||
glob@11.0.3:
|
||||
dependencies:
|
||||
foreground-child: 3.3.1
|
||||
jackspeak: 4.1.0
|
||||
minimatch: 10.0.1
|
||||
jackspeak: 4.1.1
|
||||
minimatch: 10.0.3
|
||||
minipass: 7.1.2
|
||||
package-json-from-dist: 1.0.1
|
||||
path-scurry: 2.0.0
|
||||
@@ -5075,7 +5077,7 @@ snapshots:
|
||||
|
||||
isexe@2.0.0: {}
|
||||
|
||||
jackspeak@4.1.0:
|
||||
jackspeak@4.1.1:
|
||||
dependencies:
|
||||
'@isaacs/cliui': 8.0.2
|
||||
|
||||
@@ -5382,9 +5384,9 @@ snapshots:
|
||||
dependencies:
|
||||
mime-db: 1.52.0
|
||||
|
||||
minimatch@10.0.1:
|
||||
minimatch@10.0.3:
|
||||
dependencies:
|
||||
brace-expansion: 2.0.1
|
||||
'@isaacs/brace-expansion': 5.0.0
|
||||
|
||||
minipass@7.1.2: {}
|
||||
|
||||
@@ -5539,9 +5541,9 @@ snapshots:
|
||||
|
||||
proxy-from-env@1.1.0: {}
|
||||
|
||||
react-chartjs-2@5.3.0(chart.js@4.4.9)(react@19.1.0):
|
||||
react-chartjs-2@5.3.0(chart.js@4.5.0)(react@19.1.0):
|
||||
dependencies:
|
||||
chart.js: 4.4.9
|
||||
chart.js: 4.5.0
|
||||
react: 19.1.0
|
||||
|
||||
react-dom@19.1.0(react@19.1.0):
|
||||
@@ -5556,11 +5558,11 @@ snapshots:
|
||||
|
||||
react-fast-compare@3.2.2: {}
|
||||
|
||||
react-hook-form@7.57.0(react@19.1.0):
|
||||
react-hook-form@7.58.1(react@19.1.0):
|
||||
dependencies:
|
||||
react: 19.1.0
|
||||
|
||||
react-i18next@15.5.2(i18next@25.2.1(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3):
|
||||
react-i18next@15.5.3(i18next@25.2.1(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.27.6
|
||||
html-parse-stringify: 3.0.1
|
||||
@@ -5623,7 +5625,7 @@ snapshots:
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
|
||||
react-virtuoso@4.12.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
||||
react-virtuoso@4.13.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
||||
dependencies:
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
@@ -5816,7 +5818,7 @@ snapshots:
|
||||
mkdirp: 3.0.1
|
||||
yallist: 5.0.0
|
||||
|
||||
terser@5.42.0:
|
||||
terser@5.43.0:
|
||||
dependencies:
|
||||
'@jridgewell/source-map': 0.3.6
|
||||
acorn: 8.14.1
|
||||
@@ -5928,18 +5930,18 @@ snapshots:
|
||||
dependencies:
|
||||
monaco-editor: 0.52.2
|
||||
|
||||
vite-plugin-svgr@4.3.0(rollup@4.40.2)(typescript@5.8.3)(vite@6.3.5(sass@1.89.2)(terser@5.42.0)(yaml@2.7.1)):
|
||||
vite-plugin-svgr@4.3.0(rollup@4.40.2)(typescript@5.8.3)(vite@6.3.5(sass@1.89.2)(terser@5.43.0)(yaml@2.7.1)):
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.1.4(rollup@4.40.2)
|
||||
'@svgr/core': 8.1.0(typescript@5.8.3)
|
||||
'@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.8.3))
|
||||
vite: 6.3.5(sass@1.89.2)(terser@5.42.0)(yaml@2.7.1)
|
||||
vite: 6.3.5(sass@1.89.2)(terser@5.43.0)(yaml@2.7.1)
|
||||
transitivePeerDependencies:
|
||||
- rollup
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
vite@6.3.5(sass@1.89.2)(terser@5.42.0)(yaml@2.7.1):
|
||||
vite@6.3.5(sass@1.89.2)(terser@5.43.0)(yaml@2.7.1):
|
||||
dependencies:
|
||||
esbuild: 0.25.4
|
||||
fdir: 6.4.4(picomatch@4.0.2)
|
||||
@@ -5950,7 +5952,7 @@ snapshots:
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
sass: 1.89.2
|
||||
terser: 5.42.0
|
||||
terser: 5.43.0
|
||||
yaml: 2.7.1
|
||||
|
||||
void-elements@3.1.0: {}
|
||||
|
||||
143
src-tauri/Cargo.lock
generated
143
src-tauri/Cargo.lock
generated
@@ -1042,7 +1042,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clash-verge"
|
||||
version = "2.3.0"
|
||||
version = "2.3.1"
|
||||
dependencies = [
|
||||
"ab_glyph",
|
||||
"aes-gcm",
|
||||
@@ -1100,13 +1100,13 @@ dependencies = [
|
||||
"tauri-plugin-window-state",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tokio-tungstenite 0.26.2",
|
||||
"tungstenite 0.26.2",
|
||||
"tokio-tungstenite 0.27.0",
|
||||
"tungstenite 0.27.0",
|
||||
"users",
|
||||
"warp",
|
||||
"winapi",
|
||||
"winreg 0.55.0",
|
||||
"zip 4.0.0",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3587,9 +3587,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.172"
|
||||
version = "0.2.173"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||
checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb"
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
@@ -6977,8 +6977,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-autostart"
|
||||
version = "2.3.0"
|
||||
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#9bc4b2230ebb32bd30a4c0c2a21077829a729193"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9641831518c56775a364a8750e0eed8852adee87e0f11006d043b9ebba0bf5"
|
||||
dependencies = [
|
||||
"auto-launch",
|
||||
"serde",
|
||||
@@ -6990,9 +6991,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-clipboard-manager"
|
||||
version = "2.2.2"
|
||||
version = "2.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ab4cb42fdf745229b768802e9180920a4be63122cf87ed1c879103f7609d98e"
|
||||
checksum = "11fa4f17a6d380490597f7632aca40b65d379cb374cb92bd9d80f333309b7fd7"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"log",
|
||||
@@ -7107,9 +7108,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-process"
|
||||
version = "2.2.1"
|
||||
version = "2.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57da5888533e802b6206b9685091f8714aa1f5266dc80051a82388449558b773"
|
||||
checksum = "4d870adae9408be585abd56eade2b5def2660339512b7c8de5ddf21238b67a34"
|
||||
dependencies = [
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
@@ -7117,9 +7118,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-shell"
|
||||
version = "2.2.1"
|
||||
version = "2.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69d5eb3368b959937ad2aeaf6ef9a8f5d11e01ffe03629d3530707bbcb27ff5d"
|
||||
checksum = "d34e525a448b80ad5d906fcbd93838ac3ba37985b29ac699a045b5da9b0a1a22"
|
||||
dependencies = [
|
||||
"encoding_rs",
|
||||
"log",
|
||||
@@ -7138,9 +7139,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-updater"
|
||||
version = "2.7.1"
|
||||
version = "2.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73f05c38afd77a4b8fd98e8fb6f1cdbb5fbb8a46ba181eb2758b05321e3c6209"
|
||||
checksum = "b068673e9037376ca9906f99b00ae5f9e6eb62f456f900b4435c38d57cfa73e4"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"dirs 6.0.0",
|
||||
@@ -7164,15 +7165,15 @@ dependencies = [
|
||||
"time",
|
||||
"tokio",
|
||||
"url",
|
||||
"windows-sys 0.59.0",
|
||||
"zip 2.6.1",
|
||||
"windows-sys 0.60.2",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-window-state"
|
||||
version = "2.2.2"
|
||||
version = "2.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a27a3fe49de72adbe0d84aee33c89a0b059722cd0b42aaeab29eaaee7f7535cd"
|
||||
checksum = "136e5ce5e61edc8eeeaca70080811bbdcdd890cac9c4070cb4db9cc3de1da449"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"log",
|
||||
@@ -7595,14 +7596,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-tungstenite"
|
||||
version = "0.26.2"
|
||||
version = "0.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084"
|
||||
checksum = "489a59b6730eda1b0171fcfda8b121f4bee2b35cba8645ca35c5f7ba3eb736c1"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"tokio",
|
||||
"tungstenite 0.26.2",
|
||||
"tungstenite 0.27.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7953,9 +7954,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.26.2"
|
||||
version = "0.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13"
|
||||
checksum = "eadc29d668c91fcc564941132e17b28a7ceb2f3ebf0b9dae3e03fd7a6748eb0d"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"data-encoding",
|
||||
@@ -8870,6 +8871,15 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||
dependencies = [
|
||||
"windows-targets 0.53.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.42.2"
|
||||
@@ -8909,13 +8919,29 @@ dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_gnullvm 0.52.6",
|
||||
"windows_i686_msvc 0.52.6",
|
||||
"windows_x86_64_gnu 0.52.6",
|
||||
"windows_x86_64_gnullvm 0.52.6",
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.53.0",
|
||||
"windows_aarch64_msvc 0.53.0",
|
||||
"windows_i686_gnu 0.53.0",
|
||||
"windows_i686_gnullvm 0.53.0",
|
||||
"windows_i686_msvc 0.53.0",
|
||||
"windows_x86_64_gnu 0.53.0",
|
||||
"windows_x86_64_gnullvm 0.53.0",
|
||||
"windows_x86_64_msvc 0.53.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-version"
|
||||
version = "0.1.4"
|
||||
@@ -8943,6 +8969,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.2"
|
||||
@@ -8961,6 +8993,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.2"
|
||||
@@ -8979,12 +9017,24 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.2"
|
||||
@@ -9003,6 +9053,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.2"
|
||||
@@ -9021,6 +9077,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.2"
|
||||
@@ -9039,6 +9101,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.2"
|
||||
@@ -9057,6 +9125,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.5.40"
|
||||
@@ -9452,22 +9526,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "2.6.1"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dcb24d0152526ae49b9b96c1dcf71850ca1e0b882e4e28ed898a93c41334744"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"crc32fast",
|
||||
"crossbeam-utils",
|
||||
"indexmap 2.8.0",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "4.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "153a6fff49d264c4babdcfa6b4d534747f520e56e8f0f384f3b808c4b64cc1fd"
|
||||
checksum = "af7dcdb4229c0e79c2531a24de7726a0e980417a74fb4d030a35f535665439a0"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"arbitrary",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "clash-verge"
|
||||
version = "2.3.0"
|
||||
version = "2.3.1"
|
||||
description = "clash verge"
|
||||
authors = ["zzzgydi", "wonfen", "MystiPanda"]
|
||||
license = "GPL-3.0-only"
|
||||
@@ -55,27 +55,27 @@ tauri = { version = "2.5.1", features = [
|
||||
"image-png",
|
||||
] }
|
||||
network-interface = { version = "2.0.1", features = ["serde"] }
|
||||
tauri-plugin-shell = "2.2.1"
|
||||
tauri-plugin-shell = "2.2.2"
|
||||
tauri-plugin-dialog = "2.2.2"
|
||||
tauri-plugin-fs = "2.3.0"
|
||||
tauri-plugin-process = "2.2.1"
|
||||
tauri-plugin-clipboard-manager = "2.2.2"
|
||||
tauri-plugin-process = "2.2.2"
|
||||
tauri-plugin-clipboard-manager = "2.2.3"
|
||||
tauri-plugin-deep-link = "2.3.0"
|
||||
tauri-plugin-devtools = "2.0.0"
|
||||
tauri-plugin-window-state = "2.2.2"
|
||||
zip = "4.0.0"
|
||||
tauri-plugin-window-state = "2.2.3"
|
||||
zip = "4.1.0"
|
||||
reqwest_dav = "0.2.1"
|
||||
aes-gcm = { version = "0.10.3", features = ["std"] }
|
||||
base64 = "0.22.1"
|
||||
getrandom = "0.3.3"
|
||||
tokio-tungstenite = "0.26.2"
|
||||
tokio-tungstenite = "0.27.0"
|
||||
futures = "0.3.31"
|
||||
sys-locale = "0.3.2"
|
||||
async-trait = "0.1.88"
|
||||
mihomo_api = { path = "src_crates/crate_mihomo_api" }
|
||||
ab_glyph = "0.2.29"
|
||||
tungstenite = "0.26.2"
|
||||
libc = "0.2.172"
|
||||
tungstenite = "0.27.0"
|
||||
libc = "0.2.173"
|
||||
gethostname = "1.0.2"
|
||||
hmac = "0.12.1"
|
||||
sha2 = "0.10.9"
|
||||
@@ -99,9 +99,9 @@ winapi = { version = "0.3.9", features = [
|
||||
users = "0.11.0"
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||
tauri-plugin-autostart = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
|
||||
tauri-plugin-autostart = "2.4.0"
|
||||
tauri-plugin-global-shortcut = "2.2.1"
|
||||
tauri-plugin-updater = "2.7.1"
|
||||
tauri-plugin-updater = "2.8.1"
|
||||
|
||||
[features]
|
||||
default = ["custom-protocol"]
|
||||
|
||||
@@ -916,9 +916,9 @@ FunctionEnd
|
||||
!macroend
|
||||
|
||||
Section Uninstall
|
||||
;删除 .window-state.json 文件
|
||||
;删除 window-state.json 文件
|
||||
SetShellVarContext current
|
||||
Delete "$APPDATA\io.github.clash-verge-rev.clash-verge-rev\.window-state.json"
|
||||
Delete "$APPDATA\io.github.clash-verge-rev.clash-verge-rev\window-state.json"
|
||||
|
||||
!insertmacro CheckIfAppIsRunning
|
||||
!insertmacro CheckAllVergeProcesses
|
||||
@@ -1015,9 +1015,9 @@ Section Uninstall
|
||||
RmDir /r "$LOCALAPPDATA\${BUNDLEID}"
|
||||
${EndIf}
|
||||
|
||||
;删除 .window-state.json 文件
|
||||
;删除 window-state.json 文件
|
||||
SetShellVarContext current
|
||||
Delete "$APPDATA\io.github.clash-verge-rev.clash-verge-rev\.window-state.json"
|
||||
Delete "$APPDATA\io.github.clash-verge-rev.clash-verge-rev\window-state.json"
|
||||
|
||||
${GetOptions} $CMDLINE "/P" $R0
|
||||
IfErrors +2 0
|
||||
|
||||
@@ -12,45 +12,89 @@ use tokio::sync::Mutex;
|
||||
// 添加全局互斥锁防止并发配置更新
|
||||
static PROFILE_UPDATE_MUTEX: Mutex<()> = Mutex::const_new(());
|
||||
|
||||
/// 获取配置文件列表
|
||||
/// 获取配置文件避免锁竞争
|
||||
#[tauri::command]
|
||||
pub async fn get_profiles() -> CmdResult<IProfiles> {
|
||||
let profiles_result = tokio::time::timeout(
|
||||
Duration::from_secs(3), // 3秒超时
|
||||
tokio::task::spawn_blocking(move || Config::profiles().data().clone()),
|
||||
// 策略1: 尝试快速获取latest数据
|
||||
let latest_result = tokio::time::timeout(
|
||||
Duration::from_millis(500),
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let profiles = Config::profiles();
|
||||
let latest = profiles.latest();
|
||||
IProfiles {
|
||||
current: latest.current.clone(),
|
||||
items: latest.items.clone(),
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
match profiles_result {
|
||||
Ok(Ok(profiles)) => Ok(*profiles),
|
||||
match latest_result {
|
||||
Ok(Ok(profiles)) => {
|
||||
logging!(info, Type::Cmd, false, "快速获取配置列表成功");
|
||||
return Ok(profiles);
|
||||
}
|
||||
Ok(Err(join_err)) => {
|
||||
logging!(error, Type::Cmd, true, "获取配置列表任务失败: {}", join_err);
|
||||
Ok(IProfiles {
|
||||
current: None,
|
||||
items: Some(vec![]),
|
||||
})
|
||||
logging!(warn, Type::Cmd, true, "快速获取配置任务失败: {}", join_err);
|
||||
}
|
||||
Err(_) => {
|
||||
// 超时情况
|
||||
logging!(warn, Type::Cmd, true, "快速获取配置超时(500ms)");
|
||||
}
|
||||
}
|
||||
|
||||
// 策略2: 如果快速获取失败,尝试获取data()
|
||||
let data_result = tokio::time::timeout(
|
||||
Duration::from_secs(2),
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let profiles = Config::profiles();
|
||||
let data = profiles.data();
|
||||
IProfiles {
|
||||
current: data.current.clone(),
|
||||
items: data.items.clone(),
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
match data_result {
|
||||
Ok(Ok(profiles)) => {
|
||||
logging!(info, Type::Cmd, false, "获取draft配置列表成功");
|
||||
return Ok(profiles);
|
||||
}
|
||||
Ok(Err(join_err)) => {
|
||||
logging!(
|
||||
error,
|
||||
Type::Cmd,
|
||||
true,
|
||||
"获取配置列表超时(3秒),可能存在锁竞争"
|
||||
"获取draft配置任务失败: {}",
|
||||
join_err
|
||||
);
|
||||
match tokio::task::spawn_blocking(move || Config::profiles().latest().clone()).await {
|
||||
Ok(profiles) => {
|
||||
logging!(info, Type::Cmd, true, "使用latest()成功获取配置");
|
||||
Ok(*profiles)
|
||||
}
|
||||
Err(_) => {
|
||||
logging!(error, Type::Cmd, true, "fallback获取配置也失败,返回空配置");
|
||||
Ok(IProfiles {
|
||||
current: None,
|
||||
items: Some(vec![]),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
logging!(error, Type::Cmd, true, "获取draft配置超时(2秒)");
|
||||
}
|
||||
}
|
||||
|
||||
// 策略3: fallback,尝试重新创建配置
|
||||
logging!(
|
||||
warn,
|
||||
Type::Cmd,
|
||||
true,
|
||||
"所有获取配置策略都失败,尝试fallback"
|
||||
);
|
||||
|
||||
match tokio::task::spawn_blocking(IProfiles::new).await {
|
||||
Ok(profiles) => {
|
||||
logging!(info, Type::Cmd, true, "使用fallback配置成功");
|
||||
Ok(profiles)
|
||||
}
|
||||
Err(err) => {
|
||||
logging!(error, Type::Cmd, true, "fallback配置也失败: {}", err);
|
||||
// 返回空配置避免崩溃
|
||||
Ok(IProfiles {
|
||||
current: None,
|
||||
items: Some(vec![]),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -246,6 +290,13 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<bool> {
|
||||
Config::profiles().apply();
|
||||
handle::Handle::refresh_clash();
|
||||
|
||||
// 强制刷新代理缓存,确保profile切换后立即获取最新节点数据
|
||||
crate::process::AsyncHandler::spawn(|| async move {
|
||||
if let Err(e) = super::proxy::force_refresh_proxies().await {
|
||||
log::warn!(target: "app", "强制刷新代理缓存失败: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
crate::process::AsyncHandler::spawn(|| async move {
|
||||
if let Err(e) = Tray::global().update_tooltip() {
|
||||
log::warn!(target: "app", "异步更新托盘提示失败: {}", e);
|
||||
|
||||
@@ -43,6 +43,28 @@ pub async fn get_proxies() -> CmdResult<serde_json::Value> {
|
||||
Ok(*proxies)
|
||||
}
|
||||
|
||||
/// 强制刷新代理缓存用于profile切换
|
||||
#[tauri::command]
|
||||
pub async fn force_refresh_proxies() -> CmdResult<serde_json::Value> {
|
||||
let manager = MihomoManager::global();
|
||||
let app_handle = handle::Handle::global().app_handle().unwrap();
|
||||
let cmd_proxy_state = app_handle.state::<Mutex<CmdProxyState>>();
|
||||
|
||||
log::debug!(target: "app", "强制刷新代理缓存");
|
||||
|
||||
let proxies = manager.get_refresh_proxies().await?;
|
||||
|
||||
{
|
||||
let mut state = cmd_proxy_state.lock().unwrap();
|
||||
state.proxies = Box::new(proxies.clone());
|
||||
state.need_refresh = false;
|
||||
state.last_refresh_time = Instant::now();
|
||||
}
|
||||
|
||||
log::debug!(target: "app", "强制刷新代理缓存完成");
|
||||
Ok(proxies)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_providers_proxies() -> CmdResult<serde_json::Value> {
|
||||
let app_handle = handle::Handle::global().app_handle().unwrap();
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
use super::CmdResult;
|
||||
use crate::{config::*, core::*, utils::dirs, wrap_err};
|
||||
use crate::{
|
||||
config::*,
|
||||
core::*,
|
||||
logging,
|
||||
utils::{dirs, logging::Type},
|
||||
wrap_err,
|
||||
};
|
||||
use std::fs;
|
||||
|
||||
/// 保存profiles的配置
|
||||
@@ -26,29 +32,54 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
|
||||
wrap_err!(fs::write(&file_path, file_data.clone().unwrap()))?;
|
||||
|
||||
let file_path_str = file_path.to_string_lossy().to_string();
|
||||
println!(
|
||||
logging!(
|
||||
info,
|
||||
Type::Config,
|
||||
true,
|
||||
"[cmd配置save] 开始验证配置文件: {}, 是否为merge文件: {}",
|
||||
file_path_str, is_merge_file
|
||||
file_path_str,
|
||||
is_merge_file
|
||||
);
|
||||
|
||||
// 对于 merge 文件,只进行语法验证,不进行后续内核验证
|
||||
if is_merge_file {
|
||||
println!("[cmd配置save] 检测到merge文件,只进行语法验证");
|
||||
logging!(
|
||||
info,
|
||||
Type::Config,
|
||||
true,
|
||||
"[cmd配置save] 检测到merge文件,只进行语法验证"
|
||||
);
|
||||
match CoreManager::global()
|
||||
.validate_config_file(&file_path_str, Some(true))
|
||||
.await
|
||||
{
|
||||
Ok((true, _)) => {
|
||||
println!("[cmd配置save] merge文件语法验证通过");
|
||||
logging!(
|
||||
info,
|
||||
Type::Config,
|
||||
true,
|
||||
"[cmd配置save] merge文件语法验证通过"
|
||||
);
|
||||
// 成功后尝试更新整体配置
|
||||
if let Err(e) = CoreManager::global().update_config().await {
|
||||
println!("[cmd配置save] 更新整体配置时发生错误: {}", e);
|
||||
log::warn!(target: "app", "更新整体配置时发生错误: {}", e);
|
||||
logging!(
|
||||
warn,
|
||||
Type::Config,
|
||||
true,
|
||||
"[cmd配置save] 更新整体配置时发生错误: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
Ok((false, error_msg)) => {
|
||||
println!("[cmd配置save] merge文件语法验证失败: {}", error_msg);
|
||||
logging!(
|
||||
warn,
|
||||
Type::Config,
|
||||
true,
|
||||
"[cmd配置save] merge文件语法验证失败: {}",
|
||||
error_msg
|
||||
);
|
||||
// 恢复原始配置文件
|
||||
wrap_err!(fs::write(&file_path, original_content))?;
|
||||
// 发送合并文件专用错误通知
|
||||
@@ -57,7 +88,13 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
|
||||
return Ok(());
|
||||
}
|
||||
Err(e) => {
|
||||
println!("[cmd配置save] 验证过程发生错误: {}", e);
|
||||
logging!(
|
||||
error,
|
||||
Type::Config,
|
||||
true,
|
||||
"[cmd配置save] 验证过程发生错误: {}",
|
||||
e
|
||||
);
|
||||
// 恢复原始配置文件
|
||||
wrap_err!(fs::write(&file_path, original_content))?;
|
||||
return Err(e.to_string());
|
||||
@@ -71,11 +108,17 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
|
||||
.await
|
||||
{
|
||||
Ok((true, _)) => {
|
||||
println!("[cmd配置save] 验证成功");
|
||||
logging!(info, Type::Config, true, "[cmd配置save] 验证成功");
|
||||
Ok(())
|
||||
}
|
||||
Ok((false, error_msg)) => {
|
||||
println!("[cmd配置save] 验证失败: {}", error_msg);
|
||||
logging!(
|
||||
warn,
|
||||
Type::Config,
|
||||
true,
|
||||
"[cmd配置save] 验证失败: {}",
|
||||
error_msg
|
||||
);
|
||||
// 恢复原始配置文件
|
||||
wrap_err!(fs::write(&file_path, original_content))?;
|
||||
|
||||
@@ -90,24 +133,30 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
|
||||
|| (!file_path_str.ends_with(".js") && !is_script_error)
|
||||
{
|
||||
// 普通YAML错误使用YAML通知处理
|
||||
println!("[cmd配置save] YAML配置文件验证失败,发送通知");
|
||||
log::info!(target: "app", "[cmd配置save] YAML配置文件验证失败,发送通知");
|
||||
let result = (false, error_msg.clone());
|
||||
crate::cmd::validate::handle_yaml_validation_notice(&result, "YAML配置文件");
|
||||
} else if is_script_error {
|
||||
// 脚本错误使用专门的通知处理
|
||||
println!("[cmd配置save] 脚本文件验证失败,发送通知");
|
||||
log::info!(target: "app", "[cmd配置save] 脚本文件验证失败,发送通知");
|
||||
let result = (false, error_msg.clone());
|
||||
crate::cmd::validate::handle_script_validation_notice(&result, "脚本文件");
|
||||
} else {
|
||||
// 普通配置错误使用一般通知
|
||||
println!("[cmd配置save] 其他类型验证失败,发送一般通知");
|
||||
log::info!(target: "app", "[cmd配置save] 其他类型验证失败,发送一般通知");
|
||||
handle::Handle::notice_message("config_validate::error", &error_msg);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
println!("[cmd配置save] 验证过程发生错误: {}", e);
|
||||
logging!(
|
||||
error,
|
||||
Type::Config,
|
||||
true,
|
||||
"[cmd配置save] 验证过程发生错误: {}",
|
||||
e
|
||||
);
|
||||
// 恢复原始配置文件
|
||||
wrap_err!(fs::write(&file_path, original_content))?;
|
||||
Err(e.to_string())
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::CmdResult;
|
||||
use crate::core::*;
|
||||
use crate::{core::*, logging, utils::logging::Type};
|
||||
|
||||
/// 发送脚本验证通知消息
|
||||
#[tauri::command]
|
||||
@@ -28,7 +28,14 @@ pub fn handle_script_validation_notice(result: &(bool, String), file_type: &str)
|
||||
"config_validate::script_error"
|
||||
};
|
||||
|
||||
log::warn!(target: "app", "{} 验证失败: {}", file_type, error_msg);
|
||||
logging!(
|
||||
warn,
|
||||
Type::Config,
|
||||
true,
|
||||
"{} 验证失败: {}",
|
||||
file_type,
|
||||
error_msg
|
||||
);
|
||||
handle::Handle::notice_message(status, error_msg);
|
||||
}
|
||||
}
|
||||
@@ -36,7 +43,7 @@ pub fn handle_script_validation_notice(result: &(bool, String), file_type: &str)
|
||||
/// 验证指定脚本文件
|
||||
#[tauri::command]
|
||||
pub async fn validate_script_file(file_path: String) -> CmdResult<bool> {
|
||||
log::info!(target: "app", "验证脚本文件: {}", file_path);
|
||||
logging!(info, Type::Config, true, "验证脚本文件: {}", file_path);
|
||||
|
||||
match CoreManager::global()
|
||||
.validate_config_file(&file_path, None)
|
||||
@@ -48,7 +55,13 @@ pub async fn validate_script_file(file_path: String) -> CmdResult<bool> {
|
||||
}
|
||||
Err(e) => {
|
||||
let error_msg = e.to_string();
|
||||
log::error!(target: "app", "验证脚本文件过程发生错误: {}", error_msg);
|
||||
logging!(
|
||||
error,
|
||||
Type::Config,
|
||||
true,
|
||||
"验证脚本文件过程发生错误: {}",
|
||||
error_msg
|
||||
);
|
||||
handle::Handle::notice_message("config_validate::process_terminated", &error_msg);
|
||||
Ok(false)
|
||||
}
|
||||
@@ -60,7 +73,14 @@ pub async fn validate_script_file(file_path: String) -> CmdResult<bool> {
|
||||
pub fn handle_yaml_validation_notice(result: &(bool, String), file_type: &str) {
|
||||
if !result.0 {
|
||||
let error_msg = &result.1;
|
||||
println!("[通知] 处理{}验证错误: {}", file_type, error_msg);
|
||||
logging!(
|
||||
info,
|
||||
Type::Config,
|
||||
true,
|
||||
"[通知] 处理{}验证错误: {}",
|
||||
file_type,
|
||||
error_msg
|
||||
);
|
||||
|
||||
// 检查是否为merge文件
|
||||
let is_merge_file = file_type.contains("合并");
|
||||
@@ -97,8 +117,22 @@ pub fn handle_yaml_validation_notice(result: &(bool, String), file_type: &str) {
|
||||
}
|
||||
};
|
||||
|
||||
log::warn!(target: "app", "{} 验证失败: {}", file_type, error_msg);
|
||||
println!("[通知] 发送通知: status={}, msg={}", status, error_msg);
|
||||
logging!(
|
||||
warn,
|
||||
Type::Config,
|
||||
true,
|
||||
"{} 验证失败: {}",
|
||||
file_type,
|
||||
error_msg
|
||||
);
|
||||
logging!(
|
||||
info,
|
||||
Type::Config,
|
||||
true,
|
||||
"[通知] 发送通知: status={}, msg={}",
|
||||
status,
|
||||
error_msg
|
||||
);
|
||||
handle::Handle::notice_message(status, error_msg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::{
|
||||
config::{deserialize_encrypted, serialize_encrypted, DEFAULT_PAC},
|
||||
utils::{dirs, help, i18n},
|
||||
logging,
|
||||
utils::{dirs, help, i18n, logging::Type},
|
||||
};
|
||||
use anyhow::Result;
|
||||
use log::LevelFilter;
|
||||
@@ -232,6 +233,93 @@ pub struct IVergeTheme {
|
||||
}
|
||||
|
||||
impl IVerge {
|
||||
/// 有效的clash核心名称
|
||||
pub const VALID_CLASH_CORES: &'static [&'static str] = &["verge-mihomo", "verge-mihomo-alpha"];
|
||||
|
||||
/// 验证并修正配置文件中的clash_core值
|
||||
pub fn validate_and_fix_config() -> Result<()> {
|
||||
let config_path = dirs::verge_path()?;
|
||||
let mut config = match help::read_yaml::<IVerge>(&config_path) {
|
||||
Ok(config) => config,
|
||||
Err(_) => Self::template(),
|
||||
};
|
||||
|
||||
let mut needs_fix = false;
|
||||
|
||||
if let Some(ref core) = config.clash_core {
|
||||
let core_str = core.trim();
|
||||
if core_str.is_empty() || !Self::VALID_CLASH_CORES.contains(&core_str) {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Config,
|
||||
true,
|
||||
"启动时发现无效的clash_core配置: '{}', 将自动修正为 'verge-mihomo'",
|
||||
core
|
||||
);
|
||||
config.clash_core = Some("verge-mihomo".to_string());
|
||||
needs_fix = true;
|
||||
}
|
||||
} else {
|
||||
logging!(
|
||||
info,
|
||||
Type::Config,
|
||||
true,
|
||||
"启动时发现未配置clash_core, 将设置为默认值 'verge-mihomo'"
|
||||
);
|
||||
config.clash_core = Some("verge-mihomo".to_string());
|
||||
needs_fix = true;
|
||||
}
|
||||
|
||||
// 修正后保存配置
|
||||
if needs_fix {
|
||||
logging!(info, Type::Config, true, "正在保存修正后的配置文件...");
|
||||
help::save_yaml(&config_path, &config, Some("# Clash Verge Config"))?;
|
||||
logging!(
|
||||
info,
|
||||
Type::Config,
|
||||
true,
|
||||
"配置文件修正完成,需要重新加载配置"
|
||||
);
|
||||
|
||||
Self::reload_config_after_fix(config)?;
|
||||
} else {
|
||||
logging!(
|
||||
info,
|
||||
Type::Config,
|
||||
true,
|
||||
"clash_core配置验证通过: {:?}",
|
||||
config.clash_core
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 配置修正后重新加载配置
|
||||
fn reload_config_after_fix(updated_config: IVerge) -> Result<()> {
|
||||
use crate::config::Config;
|
||||
|
||||
let config_draft = Config::verge();
|
||||
*config_draft.draft() = Box::new(updated_config.clone());
|
||||
config_draft.apply();
|
||||
|
||||
logging!(
|
||||
info,
|
||||
Type::Config,
|
||||
true,
|
||||
"内存配置已强制更新,新的clash_core: {:?}",
|
||||
updated_config.clash_core
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_valid_clash_core(&self) -> String {
|
||||
self.clash_core
|
||||
.clone()
|
||||
.unwrap_or_else(|| "verge-mihomo".to_string())
|
||||
}
|
||||
|
||||
fn get_system_language() -> String {
|
||||
let sys_lang = sys_locale::get_locale()
|
||||
.unwrap_or_else(|| String::from("en"))
|
||||
@@ -503,6 +591,8 @@ pub struct IVergeResponse {
|
||||
|
||||
impl From<IVerge> for IVergeResponse {
|
||||
fn from(verge: IVerge) -> Self {
|
||||
// 先获取验证后的clash_core值,避免后续借用冲突
|
||||
let valid_clash_core = verge.get_valid_clash_core();
|
||||
Self {
|
||||
app_log_level: verge.app_log_level,
|
||||
language: verge.language,
|
||||
@@ -534,7 +624,7 @@ impl From<IVerge> for IVergeResponse {
|
||||
proxy_host: verge.proxy_host,
|
||||
theme_setting: verge.theme_setting,
|
||||
web_ui_list: verge.web_ui_list,
|
||||
clash_core: verge.clash_core,
|
||||
clash_core: Some(valid_clash_core),
|
||||
hotkeys: verge.hotkeys,
|
||||
auto_close_connection: verge.auto_close_connection,
|
||||
auto_check_update: verge.auto_check_update,
|
||||
|
||||
@@ -54,7 +54,7 @@ impl fmt::Display for RunningMode {
|
||||
}
|
||||
}
|
||||
|
||||
const CLASH_CORES: [&str; 2] = ["verge-mihomo", "verge-mihomo-alpha"];
|
||||
use crate::config::IVerge;
|
||||
|
||||
impl CoreManager {
|
||||
/// 检查文件是否为脚本文件
|
||||
@@ -249,8 +249,7 @@ impl CoreManager {
|
||||
config_path
|
||||
);
|
||||
|
||||
let clash_core = { Config::verge().latest().clash_core.clone() };
|
||||
let clash_core = clash_core.unwrap_or("verge-mihomo".into());
|
||||
let clash_core = Config::verge().latest().get_valid_clash_core();
|
||||
logging!(info, Type::Config, true, "使用内核: {}", clash_core);
|
||||
|
||||
let app_handle = handle::Handle::global().app_handle().unwrap();
|
||||
@@ -442,11 +441,7 @@ impl CoreManager {
|
||||
let app_handle = handle::Handle::global()
|
||||
.app_handle()
|
||||
.ok_or(anyhow::anyhow!("failed to get app handle"))?;
|
||||
let clash_core = Config::verge()
|
||||
.latest()
|
||||
.clash_core
|
||||
.clone()
|
||||
.unwrap_or("verge-mihomo".to_string());
|
||||
let clash_core = Config::verge().latest().get_valid_clash_core();
|
||||
let config_dir = dirs::app_home_dir()?;
|
||||
|
||||
let service_log_dir = dirs::app_home_dir()?.join("logs").join("service");
|
||||
@@ -804,7 +799,7 @@ impl CoreManager {
|
||||
return Err(error_message.to_string());
|
||||
}
|
||||
let core: &str = &clash_core.clone().unwrap();
|
||||
if !CLASH_CORES.contains(&core) {
|
||||
if !IVerge::VALID_CLASH_CORES.contains(&core) {
|
||||
let error_message = format!("Clash core invalid name: {}", core);
|
||||
logging!(error, Type::Core, true, "{}", error_message);
|
||||
return Err(error_message);
|
||||
|
||||
@@ -151,6 +151,10 @@ impl NotificationSystem {
|
||||
match window.emit(event_name_str, payload) {
|
||||
Ok(_) => {
|
||||
system.stats.total_sent.fetch_add(1, Ordering::SeqCst);
|
||||
// 记录成功发送的事件
|
||||
if log::log_enabled!(log::Level::Debug) {
|
||||
log::debug!("Successfully emitted event: {}", event_name_str);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("Failed to emit event {}: {}", event_name_str, e);
|
||||
@@ -224,12 +228,27 @@ impl NotificationSystem {
|
||||
}
|
||||
|
||||
fn shutdown(&mut self) {
|
||||
log::info!("NotificationSystem shutdown initiated");
|
||||
self.is_running = false;
|
||||
self.sender = None;
|
||||
|
||||
if let Some(handle) = self.worker_handle.take() {
|
||||
let _ = handle.join();
|
||||
// 先关闭发送端,让接收端知道不会再有新消息
|
||||
if let Some(sender) = self.sender.take() {
|
||||
drop(sender);
|
||||
}
|
||||
|
||||
// 设置超时避免无限等待
|
||||
if let Some(handle) = self.worker_handle.take() {
|
||||
match handle.join() {
|
||||
Ok(_) => {
|
||||
log::info!("NotificationSystem worker thread joined successfully");
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("NotificationSystem worker thread join failed: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("NotificationSystem shutdown completed");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -272,10 +272,11 @@ impl Hotkey {
|
||||
|
||||
if is_enable_global_hotkey {
|
||||
f();
|
||||
} else if let Some(window) = app_handle.get_webview_window("main") {
|
||||
} else {
|
||||
use crate::utils::window_manager::WindowManager;
|
||||
// 非轻量模式且未启用全局热键时,只在窗口可见且有焦点的情况下响应热键
|
||||
let is_visible = window.is_visible().unwrap_or(false);
|
||||
let is_focused = window.is_focused().unwrap_or(false);
|
||||
let is_visible = WindowManager::is_main_window_visible();
|
||||
let is_focused = WindowManager::is_main_window_focused();
|
||||
|
||||
if is_focused && is_visible {
|
||||
f();
|
||||
@@ -330,9 +331,9 @@ impl Hotkey {
|
||||
let func = iter.next();
|
||||
let key = iter.next();
|
||||
|
||||
if func.is_some() && key.is_some() {
|
||||
let func = func.unwrap().trim();
|
||||
let key = key.unwrap().trim();
|
||||
if let (Some(func), Some(key)) = (func, key) {
|
||||
let func = func.trim();
|
||||
let key = key.trim();
|
||||
map.insert(key, func);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -742,8 +742,7 @@ pub(super) async fn start_with_existing_service(config_file: &PathBuf) -> Result
|
||||
log::info!(target:"app", "尝试使用现有服务启动核心 (IPC)");
|
||||
// logging!(info, Type::Service, true, "尝试使用现有服务启动核心");
|
||||
|
||||
let clash_core = { Config::verge().latest().clash_core.clone() };
|
||||
let clash_core = clash_core.unwrap_or("verge-mihomo".into());
|
||||
let clash_core = Config::verge().latest().get_valid_clash_core();
|
||||
|
||||
let bin_ext = if cfg!(windows) { ".exe" } else { "" };
|
||||
let clash_bin = format!("{clash_core}{bin_ext}");
|
||||
|
||||
@@ -6,11 +6,7 @@ use crate::{
|
||||
cmd,
|
||||
config::Config,
|
||||
feat, logging,
|
||||
module::{
|
||||
lightweight::{entry_lightweight_mode, is_in_lightweight_mode},
|
||||
mihomo::Rate,
|
||||
},
|
||||
resolve,
|
||||
module::{lightweight::is_in_lightweight_mode, mihomo::Rate},
|
||||
utils::{dirs::find_target_icons, i18n::t, resolve::VERSION},
|
||||
Type,
|
||||
};
|
||||
@@ -205,33 +201,38 @@ impl Tray {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 更新托盘菜单(带频率限制)
|
||||
/// 更新托盘菜单
|
||||
pub fn update_menu(&self) -> Result<()> {
|
||||
// 检查是否正在更新或距离上次更新太近
|
||||
const MIN_UPDATE_INTERVAL: Duration = Duration::from_millis(500);
|
||||
// 调整最小更新间隔,确保状态及时刷新
|
||||
const MIN_UPDATE_INTERVAL: Duration = Duration::from_millis(100);
|
||||
|
||||
// 检查是否已有更新任务在执行
|
||||
// 检查是否正在更新
|
||||
if self.menu_updating.load(Ordering::Acquire) {
|
||||
log::debug!(target: "app", "托盘菜单正在更新中,跳过本次更新");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 检查更新频率
|
||||
{
|
||||
let last_update = self.last_menu_update.lock();
|
||||
if let Some(last_time) = *last_update {
|
||||
if last_time.elapsed() < MIN_UPDATE_INTERVAL {
|
||||
log::debug!(target: "app", "托盘菜单更新频率过高,跳过本次更新");
|
||||
return Ok(());
|
||||
// 检查更新频率,但允许重要事件跳过频率限制
|
||||
let should_force_update = match std::thread::current().name() {
|
||||
Some("main") => true,
|
||||
_ => {
|
||||
let last_update = self.last_menu_update.lock();
|
||||
if let Some(last_time) = *last_update {
|
||||
last_time.elapsed() >= MIN_UPDATE_INTERVAL
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if !should_force_update {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let app_handle = match handle::Handle::global().app_handle() {
|
||||
Some(handle) => handle,
|
||||
None => {
|
||||
log::warn!(target: "app", "更新托盘菜单失败: app_handle不存在");
|
||||
return Ok(()); // 早期返回,避免panic
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -248,6 +249,7 @@ impl Tray {
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn update_menu_internal(&self, app_handle: &AppHandle) -> Result<()> {
|
||||
let verge = Config::verge().latest().clone();
|
||||
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false);
|
||||
@@ -395,6 +397,17 @@ impl Tray {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 更新托盘显示状态的函数
|
||||
pub fn update_tray_display(&self) -> Result<()> {
|
||||
let app_handle = handle::Handle::global().app_handle().unwrap();
|
||||
let _tray = app_handle.tray_by_id("main").unwrap();
|
||||
|
||||
// 更新菜单
|
||||
self.update_menu()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 更新托盘提示
|
||||
pub fn update_tooltip(&self) -> Result<()> {
|
||||
let app_handle = match handle::Handle::global().app_handle() {
|
||||
@@ -457,6 +470,8 @@ impl Tray {
|
||||
self.update_menu()?;
|
||||
self.update_icon(None)?;
|
||||
self.update_tooltip()?;
|
||||
// 更新轻量模式显示状态
|
||||
self.update_tray_display()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -653,10 +668,14 @@ impl Tray {
|
||||
"system_proxy" => feat::toggle_system_proxy(),
|
||||
"tun_mode" => feat::toggle_tun_mode(None),
|
||||
"main_window" => {
|
||||
use crate::utils::window_manager::WindowManager;
|
||||
log::info!(target: "app", "Tray点击事件: 显示主窗口");
|
||||
if crate::module::lightweight::is_in_lightweight_mode() {
|
||||
log::info!(target: "app", "当前在轻量模式,正在退出轻量模式");
|
||||
crate::module::lightweight::exit_lightweight_mode();
|
||||
}
|
||||
let _ = resolve::create_window(true);
|
||||
let result = WindowManager::show_main_window();
|
||||
log::info!(target: "app", "窗口显示结果: {:?}", result);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -666,6 +685,17 @@ impl Tray {
|
||||
log::info!(target: "app", "系统托盘创建成功");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// 托盘统一的状态更新函数
|
||||
pub fn update_all_states(&self) -> Result<()> {
|
||||
// 确保所有状态更新完成
|
||||
self.update_menu()?;
|
||||
self.update_icon(None)?;
|
||||
self.update_tooltip()?;
|
||||
self.update_tray_display()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn create_tray_menu(
|
||||
@@ -917,15 +947,23 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
|
||||
feat::change_clash_mode(mode.into());
|
||||
}
|
||||
"open_window" => {
|
||||
use crate::utils::window_manager::WindowManager;
|
||||
log::info!(target: "app", "托盘菜单点击: 打开窗口");
|
||||
// 如果在轻量模式中,先退出轻量模式
|
||||
if crate::module::lightweight::is_in_lightweight_mode() {
|
||||
log::info!(target: "app", "当前在轻量模式,正在退出");
|
||||
crate::module::lightweight::exit_lightweight_mode();
|
||||
}
|
||||
// 然后创建窗口
|
||||
let _ = resolve::create_window(true);
|
||||
// 使用统一的窗口管理器显示窗口
|
||||
let result = WindowManager::show_main_window();
|
||||
log::info!(target: "app", "窗口显示结果: {:?}", result);
|
||||
}
|
||||
"system_proxy" => {
|
||||
feat::toggle_system_proxy();
|
||||
}
|
||||
"tun_mode" => {
|
||||
feat::toggle_tun_mode(None);
|
||||
}
|
||||
"system_proxy" => feat::toggle_system_proxy(),
|
||||
"tun_mode" => feat::toggle_tun_mode(None),
|
||||
"copy_env" => feat::copy_clash_env(),
|
||||
"open_app_dir" => {
|
||||
let _ = cmd::open_app_dir();
|
||||
@@ -938,7 +976,22 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
|
||||
}
|
||||
"restart_clash" => feat::restart_clash_core(),
|
||||
"restart_app" => feat::restart_app(),
|
||||
"entry_lightweight_mode" => entry_lightweight_mode(),
|
||||
"entry_lightweight_mode" => {
|
||||
// 处理轻量模式的切换
|
||||
let was_lightweight = crate::module::lightweight::is_in_lightweight_mode();
|
||||
if was_lightweight {
|
||||
crate::module::lightweight::exit_lightweight_mode();
|
||||
} else {
|
||||
crate::module::lightweight::entry_lightweight_mode();
|
||||
}
|
||||
|
||||
// 退出轻量模式后显示主窗口
|
||||
if was_lightweight {
|
||||
use crate::utils::window_manager::WindowManager;
|
||||
let result = WindowManager::show_main_window();
|
||||
log::info!(target: "app", "退出轻量模式后显示主窗口: {:?}", result);
|
||||
}
|
||||
}
|
||||
"quit" => {
|
||||
feat::quit();
|
||||
}
|
||||
@@ -948,4 +1001,9 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// 统一调用状态更新
|
||||
if let Err(e) = Tray::global().update_all_states() {
|
||||
log::warn!(target: "app", "更新托盘状态失败: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
||||
let verge = Config::verge();
|
||||
let verge = verge.latest();
|
||||
(
|
||||
verge.clash_core.clone(),
|
||||
Some(verge.get_valid_clash_core()),
|
||||
verge.enable_tun_mode.unwrap_or(false),
|
||||
verge.enable_builtin_enhanced.unwrap_or(true),
|
||||
verge.verge_socks_enabled.unwrap_or(false),
|
||||
|
||||
@@ -45,8 +45,8 @@ pub fn use_script(
|
||||
let config = use_lowercase(config.clone());
|
||||
let config_str = serde_json::to_string(&config)?;
|
||||
|
||||
// 处理 name 参数中的特殊字符
|
||||
let safe_name = escape_js_string(&name);
|
||||
// 仅处理 name 参数中的特殊字符
|
||||
let safe_name = escape_js_string_for_single_quote(&name);
|
||||
|
||||
let code = format!(
|
||||
r#"try{{
|
||||
@@ -64,18 +64,8 @@ pub fn use_script(
|
||||
let result = result.to_string(&mut context).unwrap();
|
||||
let result = result.to_std_string().unwrap();
|
||||
|
||||
// 处理 JS 执行结果中的特殊字符
|
||||
let unescaped_result = unescape_js_string(&result);
|
||||
|
||||
if unescaped_result.starts_with("__error_flag__") {
|
||||
anyhow::bail!(unescaped_result[15..].to_owned());
|
||||
}
|
||||
if unescaped_result == "\"\"" {
|
||||
anyhow::bail!("main function should return object");
|
||||
}
|
||||
|
||||
// 安全地解析 JSON 结果
|
||||
let res: Result<Mapping, Error> = parse_json_safely(&unescaped_result);
|
||||
// 直接解析JSON结果,不做其他解析
|
||||
let res: Result<Mapping, Error> = parse_json_safely(&result);
|
||||
|
||||
let mut out = outputs.lock().unwrap();
|
||||
match res {
|
||||
@@ -90,72 +80,25 @@ pub fn use_script(
|
||||
}
|
||||
}
|
||||
|
||||
// 解析 JSON 字符串,处理可能的转义字符
|
||||
fn parse_json_safely(json_str: &str) -> Result<Mapping, Error> {
|
||||
// 移除可能的引号包裹
|
||||
let json_str = if json_str.starts_with('"') && json_str.ends_with('"') {
|
||||
&json_str[1..json_str.len() - 1]
|
||||
let json_str = strip_outer_quotes(json_str);
|
||||
|
||||
Ok(serde_json::from_str::<Mapping>(json_str)?)
|
||||
}
|
||||
|
||||
// 移除字符串外层的引号
|
||||
fn strip_outer_quotes(s: &str) -> &str {
|
||||
let s = s.trim();
|
||||
if (s.starts_with('"') && s.ends_with('"')) || (s.starts_with('\'') && s.ends_with('\'')) {
|
||||
&s[1..s.len() - 1]
|
||||
} else {
|
||||
json_str
|
||||
};
|
||||
|
||||
// 处理可能的 JSON 字符串中的转义字符
|
||||
let json_str = json_str.replace("\\\"", "\"");
|
||||
|
||||
Ok(serde_json::from_str::<Mapping>(&json_str)?)
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
// 转义 JS 字符串中的特殊字符
|
||||
fn escape_js_string(s: &str) -> String {
|
||||
let mut result = String::with_capacity(s.len());
|
||||
for c in s.chars() {
|
||||
match c {
|
||||
'\'' => result.push_str("\\'"),
|
||||
'"' => result.push_str("\\\""),
|
||||
'\\' => result.push_str("\\\\"),
|
||||
'\n' => result.push_str("\\n"),
|
||||
'\r' => result.push_str("\\r"),
|
||||
'\t' => result.push_str("\\t"),
|
||||
'\0' => result.push_str("\\0"),
|
||||
_ => result.push(c),
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
// 反转义 JS 字符串中的特殊字符
|
||||
fn unescape_js_string(s: &str) -> String {
|
||||
let mut result = String::with_capacity(s.len());
|
||||
let mut chars = s.chars();
|
||||
|
||||
while let Some(c) = chars.next() {
|
||||
if c == '\\' {
|
||||
match chars.next() {
|
||||
Some('n') => result.push('\n'),
|
||||
Some('r') => result.push('\r'),
|
||||
Some('t') => result.push('\t'),
|
||||
Some('0') => result.push('\0'),
|
||||
Some('\\') => result.push('\\'),
|
||||
Some('\'') => result.push('\''),
|
||||
Some('"') => result.push('"'),
|
||||
Some('u') => {
|
||||
// 处理转义序列
|
||||
let hex = chars.by_ref().take(4).collect::<String>();
|
||||
if let Ok(codepoint) = u32::from_str_radix(&hex, 16) {
|
||||
if let Some(ch) = char::from_u32(codepoint) {
|
||||
result.push(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(other) => result.push(other),
|
||||
None => break,
|
||||
}
|
||||
} else {
|
||||
result.push(c);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
// 转义单引号和反斜杠,用于单引号包裹的JavaScript字符串
|
||||
fn escape_js_string_for_single_quote(s: &str) -> String {
|
||||
s.replace('\\', "\\\\").replace('\'', "\\'")
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -197,10 +140,9 @@ fn test_script() {
|
||||
#[test]
|
||||
fn test_escape_unescape() {
|
||||
let test_string = r#"Hello "World"!\nThis is a test with \u00A9 copyright symbol."#;
|
||||
let escaped = escape_js_string(test_string);
|
||||
let unescaped = unescape_js_string(&escaped);
|
||||
|
||||
assert_eq!(test_string, unescaped);
|
||||
let escaped = escape_js_string_for_single_quote(test_string);
|
||||
println!("Original: {}", test_string);
|
||||
println!("Escaped: {}", escaped);
|
||||
|
||||
let json_str = r#"{"key":"value","nested":{"key":"value"}}"#;
|
||||
let parsed = parse_json_safely(json_str).unwrap();
|
||||
|
||||
@@ -84,7 +84,7 @@ pub fn change_clash_mode(mode: String) {
|
||||
after_change_clash_mode();
|
||||
}
|
||||
}
|
||||
Err(err) => println!("{err}"),
|
||||
Err(err) => log::error!(target: "app", "{err}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ use crate::{
|
||||
cmd,
|
||||
config::{Config, PrfItem, PrfOption},
|
||||
core::{handle, CoreManager, *},
|
||||
logging,
|
||||
process::AsyncHandler,
|
||||
utils::logging::Type,
|
||||
};
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
@@ -29,7 +31,7 @@ pub async fn update_profile(
|
||||
option: Option<PrfOption>,
|
||||
auto_refresh: Option<bool>,
|
||||
) -> Result<()> {
|
||||
println!("[订阅更新] 开始更新订阅 {}", uid);
|
||||
logging!(info, Type::Config, true, "[订阅更新] 开始更新订阅 {}", uid);
|
||||
let auto_refresh = auto_refresh.unwrap_or(true); // 默认为true,保持兼容性
|
||||
|
||||
let url_opt = {
|
||||
@@ -39,13 +41,13 @@ pub async fn update_profile(
|
||||
let is_remote = item.itype.as_ref().is_some_and(|s| s == "remote");
|
||||
|
||||
if !is_remote {
|
||||
println!("[订阅更新] {} 不是远程订阅,跳过更新", uid);
|
||||
log::info!(target: "app", "[订阅更新] {} 不是远程订阅,跳过更新", uid);
|
||||
None // 非远程订阅直接更新
|
||||
} else if item.url.is_none() {
|
||||
println!("[订阅更新] {} 缺少URL,无法更新", uid);
|
||||
log::warn!(target: "app", "[订阅更新] {} 缺少URL,无法更新", uid);
|
||||
bail!("failed to get the profile item url");
|
||||
} else {
|
||||
println!(
|
||||
log::info!(target: "app",
|
||||
"[订阅更新] {} 是远程订阅,URL: {}",
|
||||
uid,
|
||||
item.url.clone().unwrap()
|
||||
@@ -56,24 +58,24 @@ pub async fn update_profile(
|
||||
|
||||
let should_update = match url_opt {
|
||||
Some((url, opt)) => {
|
||||
println!("[订阅更新] 开始下载新的订阅内容");
|
||||
log::info!(target: "app", "[订阅更新] 开始下载新的订阅内容");
|
||||
let merged_opt = PrfOption::merge(opt.clone(), option.clone());
|
||||
|
||||
// 尝试使用正常设置更新
|
||||
match PrfItem::from_url(&url, None, None, merged_opt.clone()).await {
|
||||
Ok(item) => {
|
||||
println!("[订阅更新] 更新订阅配置成功");
|
||||
log::info!(target: "app", "[订阅更新] 更新订阅配置成功");
|
||||
let profiles = Config::profiles();
|
||||
let mut profiles = profiles.latest();
|
||||
profiles.update_item(uid.clone(), item)?;
|
||||
|
||||
let is_current = Some(uid.clone()) == profiles.get_current();
|
||||
println!("[订阅更新] 是否为当前使用的订阅: {}", is_current);
|
||||
log::info!(target: "app", "[订阅更新] 是否为当前使用的订阅: {}", is_current);
|
||||
is_current && auto_refresh
|
||||
}
|
||||
Err(err) => {
|
||||
// 首次更新失败,尝试使用Clash代理
|
||||
println!("[订阅更新] 正常更新失败: {},尝试使用Clash代理更新", err);
|
||||
log::warn!(target: "app", "[订阅更新] 正常更新失败: {},尝试使用Clash代理更新", err);
|
||||
|
||||
// 发送通知
|
||||
handle::Handle::notice_message("update_retry_with_clash", uid.clone());
|
||||
@@ -90,7 +92,7 @@ pub async fn update_profile(
|
||||
// 使用Clash代理重试
|
||||
match PrfItem::from_url(&url, None, None, Some(fallback_opt)).await {
|
||||
Ok(mut item) => {
|
||||
println!("[订阅更新] 使用Clash代理更新成功");
|
||||
log::info!(target: "app", "[订阅更新] 使用Clash代理更新成功");
|
||||
|
||||
// 恢复原始代理设置到item
|
||||
if let Some(option) = item.option.as_mut() {
|
||||
@@ -110,11 +112,11 @@ pub async fn update_profile(
|
||||
handle::Handle::notice_message("update_with_clash_proxy", profile_name);
|
||||
|
||||
let is_current = Some(uid.clone()) == profiles.get_current();
|
||||
println!("[订阅更新] 是否为当前使用的订阅: {}", is_current);
|
||||
log::info!(target: "app", "[订阅更新] 是否为当前使用的订阅: {}", is_current);
|
||||
is_current && auto_refresh
|
||||
}
|
||||
Err(retry_err) => {
|
||||
println!("[订阅更新] 使用Clash代理更新仍然失败: {}", retry_err);
|
||||
log::error!(target: "app", "[订阅更新] 使用Clash代理更新仍然失败: {}", retry_err);
|
||||
handle::Handle::notice_message(
|
||||
"update_failed_even_with_clash",
|
||||
format!("{}", retry_err),
|
||||
@@ -129,14 +131,14 @@ pub async fn update_profile(
|
||||
};
|
||||
|
||||
if should_update {
|
||||
println!("[订阅更新] 更新内核配置");
|
||||
logging!(info, Type::Config, true, "[订阅更新] 更新内核配置");
|
||||
match CoreManager::global().update_config().await {
|
||||
Ok(_) => {
|
||||
println!("[订阅更新] 更新成功");
|
||||
logging!(info, Type::Config, true, "[订阅更新] 更新成功");
|
||||
handle::Handle::refresh_clash();
|
||||
}
|
||||
Err(err) => {
|
||||
println!("[订阅更新] 更新失败: {}", err);
|
||||
logging!(error, Type::Config, true, "[订阅更新] 更新失败: {}", err);
|
||||
handle::Handle::notice_message("update_failed", format!("{err}"));
|
||||
log::error!(target: "app", "{err}");
|
||||
}
|
||||
|
||||
@@ -3,69 +3,37 @@ use crate::AppHandleManager;
|
||||
use crate::{
|
||||
config::Config,
|
||||
core::{handle, sysopt, CoreManager},
|
||||
logging,
|
||||
module::mihomo::MihomoManager,
|
||||
utils::resolve,
|
||||
utils::logging::Type,
|
||||
};
|
||||
|
||||
/// Open or close the dashboard window
|
||||
#[allow(dead_code)]
|
||||
pub fn open_or_close_dashboard() {
|
||||
println!("Attempting to open/close dashboard");
|
||||
use crate::utils::window_manager::WindowManager;
|
||||
|
||||
log::info!(target: "app", "Attempting to open/close dashboard");
|
||||
|
||||
// 检查是否在轻量模式下
|
||||
if crate::module::lightweight::is_in_lightweight_mode() {
|
||||
println!("Currently in lightweight mode, exiting lightweight mode");
|
||||
log::info!(target: "app", "Currently in lightweight mode, exiting lightweight mode");
|
||||
|
||||
crate::module::lightweight::exit_lightweight_mode();
|
||||
|
||||
println!("Creating new window after exiting lightweight mode");
|
||||
log::info!(target: "app", "Creating new window after exiting lightweight mode");
|
||||
resolve::create_window(true);
|
||||
let result = WindowManager::show_main_window();
|
||||
log::info!(target: "app", "Window operation result: {:?}", result);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(window) = handle::Handle::global().get_window() {
|
||||
println!("Found existing window");
|
||||
log::info!(target: "app", "Found existing window");
|
||||
|
||||
// 如果窗口存在,则切换其显示状态
|
||||
match window.is_visible() {
|
||||
Ok(visible) => {
|
||||
println!("Window visibility status: {}", visible);
|
||||
log::info!(target: "app", "Window visibility status: {}", visible);
|
||||
|
||||
if visible {
|
||||
println!("Attempting to hide window");
|
||||
log::info!(target: "app", "Attempting to hide window");
|
||||
let _ = window.hide();
|
||||
} else {
|
||||
println!("Attempting to show and focus window");
|
||||
log::info!(target: "app", "Attempting to show and focus window");
|
||||
if window.is_minimized().unwrap_or(false) {
|
||||
let _ = window.unminimize();
|
||||
}
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Failed to get window visibility: {:?}", e);
|
||||
log::error!(target: "app", "Failed to get window visibility: {:?}", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("No existing window found, creating new window");
|
||||
log::info!(target: "app", "No existing window found, creating new window");
|
||||
resolve::create_window(true);
|
||||
}
|
||||
// 使用统一的窗口管理器切换窗口状态
|
||||
let result = WindowManager::toggle_main_window();
|
||||
log::info!(target: "app", "Window toggle result: {:?}", result);
|
||||
}
|
||||
|
||||
/// 异步优化的应用退出函数
|
||||
pub fn quit() {
|
||||
use crate::process::AsyncHandler;
|
||||
log::debug!(target: "app", "启动退出流程");
|
||||
logging!(debug, Type::System, true, "启动退出流程");
|
||||
|
||||
// 获取应用句柄并设置退出标志
|
||||
let app_handle = handle::Handle::global().app_handle().unwrap();
|
||||
@@ -79,10 +47,16 @@ pub fn quit() {
|
||||
|
||||
// 使用异步任务处理资源清理,避免阻塞
|
||||
AsyncHandler::spawn(move || async move {
|
||||
log::info!(target: "app", "开始异步清理资源");
|
||||
logging!(info, Type::System, true, "开始异步清理资源");
|
||||
let cleanup_result = clean_async().await;
|
||||
|
||||
log::info!(target: "app", "资源清理完成,退出代码: {}", if cleanup_result { 0 } else { 1 });
|
||||
logging!(
|
||||
info,
|
||||
Type::System,
|
||||
true,
|
||||
"资源清理完成,退出代码: {}",
|
||||
if cleanup_result { 0 } else { 1 }
|
||||
);
|
||||
app_handle.exit(if cleanup_result { 0 } else { 1 });
|
||||
});
|
||||
}
|
||||
@@ -90,7 +64,7 @@ pub fn quit() {
|
||||
async fn clean_async() -> bool {
|
||||
use tokio::time::{timeout, Duration};
|
||||
|
||||
log::info!(target: "app", "开始执行异步清理操作...");
|
||||
logging!(info, Type::System, true, "开始执行异步清理操作...");
|
||||
|
||||
// 1. 处理TUN模式
|
||||
let tun_task = async {
|
||||
@@ -156,7 +130,12 @@ async fn clean_async() -> bool {
|
||||
// 4. DNS恢复(仅macOS)
|
||||
#[cfg(target_os = "macos")]
|
||||
let dns_task = async {
|
||||
match timeout(Duration::from_millis(1000), resolve::restore_public_dns()).await {
|
||||
match timeout(
|
||||
Duration::from_millis(1000),
|
||||
crate::utils::resolve::restore_public_dns(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
log::info!(target: "app", "DNS设置已恢复");
|
||||
true
|
||||
@@ -178,10 +157,16 @@ async fn clean_async() -> bool {
|
||||
|
||||
let all_success = tun_success && proxy_success && core_success && dns_success;
|
||||
|
||||
log::info!(
|
||||
target: "app",
|
||||
logging!(
|
||||
info,
|
||||
Type::System,
|
||||
true,
|
||||
"异步清理操作完成 - TUN: {}, 代理: {}, 核心: {}, DNS: {}, 总体: {}",
|
||||
tun_success, proxy_success, core_success, dns_success, all_success
|
||||
tun_success,
|
||||
proxy_success,
|
||||
core_success,
|
||||
dns_success,
|
||||
all_success
|
||||
);
|
||||
|
||||
all_success
|
||||
@@ -193,7 +178,7 @@ pub fn clean() -> bool {
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
|
||||
AsyncHandler::spawn(move || async move {
|
||||
log::info!(target: "app", "开始执行清理操作...");
|
||||
logging!(info, Type::System, true, "开始执行清理操作...");
|
||||
|
||||
// 使用已有的异步清理函数
|
||||
let cleanup_result = clean_async().await;
|
||||
@@ -204,11 +189,16 @@ pub fn clean() -> bool {
|
||||
|
||||
match rx.recv_timeout(std::time::Duration::from_secs(8)) {
|
||||
Ok(result) => {
|
||||
log::info!(target: "app", "清理操作完成,结果: {}", result);
|
||||
logging!(info, Type::System, true, "清理操作完成,结果: {}", result);
|
||||
result
|
||||
}
|
||||
Err(_) => {
|
||||
log::warn!(target: "app", "清理操作超时,返回成功状态避免阻塞");
|
||||
logging!(
|
||||
warn,
|
||||
Type::System,
|
||||
true,
|
||||
"清理操作超时,返回成功状态避免阻塞"
|
||||
);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,6 +162,14 @@ pub fn run() {
|
||||
});
|
||||
});
|
||||
|
||||
// 窗口管理
|
||||
logging!(info, Type::Setup, true, "初始化窗口状态管理...");
|
||||
let window_state_plugin = tauri_plugin_window_state::Builder::new()
|
||||
.with_filename("window_state.json")
|
||||
.with_state_flags(tauri_plugin_window_state::StateFlags::default())
|
||||
.build();
|
||||
let _ = app.handle().plugin(window_state_plugin);
|
||||
|
||||
// 异步处理
|
||||
let app_handle = app.handle().clone();
|
||||
AsyncHandler::spawn(move || async move {
|
||||
@@ -255,6 +263,7 @@ pub fn run() {
|
||||
cmd::invoke_uwp_tool,
|
||||
cmd::copy_clash_env,
|
||||
cmd::get_proxies,
|
||||
cmd::force_refresh_proxies,
|
||||
cmd::get_providers_proxies,
|
||||
cmd::save_dns_config,
|
||||
cmd::apply_dns_config,
|
||||
@@ -368,7 +377,7 @@ pub fn run() {
|
||||
if core::handle::Handle::global().is_exiting() {
|
||||
return;
|
||||
}
|
||||
println!("closing window...");
|
||||
log::info!(target: "app", "closing window...");
|
||||
api.prevent_close();
|
||||
if let Some(window) = core::handle::Handle::global().get_window() {
|
||||
let _ = window.hide();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
config::Config,
|
||||
core::{handle, timer::Timer},
|
||||
core::{handle, timer::Timer, tray::Tray},
|
||||
log_err, logging,
|
||||
state::lightweight::LightWeightState,
|
||||
utils::logging::Type,
|
||||
@@ -30,39 +30,44 @@ where
|
||||
|
||||
pub fn run_once_auto_lightweight() {
|
||||
LightWeightState::default().run_once_time(|| {
|
||||
let is_silent_start = Config::verge().data().enable_silent_start.unwrap_or(false);
|
||||
let is_silent_start = Config::verge().data().enable_silent_start.unwrap_or(true);
|
||||
let enable_auto = Config::verge()
|
||||
.data()
|
||||
.enable_auto_light_weight_mode
|
||||
.unwrap_or(false);
|
||||
.unwrap_or(true);
|
||||
if enable_auto && is_silent_start {
|
||||
logging!(
|
||||
info,
|
||||
Type::Lightweight,
|
||||
true,
|
||||
"Add timer listener when creating window in silent start mode"
|
||||
"正常创建窗口和添加定时器监听器"
|
||||
);
|
||||
set_lightweight_mode(true);
|
||||
enable_auto_light_weight_mode();
|
||||
set_lightweight_mode(false);
|
||||
disable_auto_light_weight_mode();
|
||||
|
||||
// 触发托盘更新
|
||||
if let Err(e) = Tray::global().update_part() {
|
||||
log::warn!("Failed to update tray: {}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn auto_lightweight_mode_init() {
|
||||
if let Some(app_handle) = handle::Handle::global().app_handle() {
|
||||
// 通过 app_handle.state 保证同步
|
||||
let _ = app_handle.state::<Mutex<LightWeightState>>();
|
||||
let is_silent_start = { Config::verge().data().enable_silent_start }.unwrap_or(false);
|
||||
let enable_auto = { Config::verge().data().enable_auto_light_weight_mode }.unwrap_or(false);
|
||||
if enable_auto && !is_silent_start {
|
||||
logging!(
|
||||
info,
|
||||
Type::Lightweight,
|
||||
true,
|
||||
"Add timer listener when creating window normally"
|
||||
);
|
||||
|
||||
if enable_auto && is_silent_start {
|
||||
logging!(info, Type::Lightweight, true, "自动轻量模式静默启动");
|
||||
set_lightweight_mode(true);
|
||||
enable_auto_light_weight_mode();
|
||||
|
||||
// 确保托盘状态更新
|
||||
if let Err(e) = Tray::global().update_part() {
|
||||
log::warn!("Failed to update tray: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,6 +82,11 @@ fn set_lightweight_mode(value: bool) {
|
||||
with_lightweight_status(|state| {
|
||||
state.set_lightweight_mode(value);
|
||||
});
|
||||
|
||||
// 触发托盘更新
|
||||
if let Err(e) = Tray::global().update_part() {
|
||||
log::warn!("Failed to update tray: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enable_auto_light_weight_mode() {
|
||||
@@ -93,10 +103,18 @@ pub fn disable_auto_light_weight_mode() {
|
||||
}
|
||||
|
||||
pub fn entry_lightweight_mode() {
|
||||
use crate::utils::window_manager::WindowManager;
|
||||
|
||||
let result = WindowManager::hide_main_window();
|
||||
logging!(
|
||||
info,
|
||||
Type::Lightweight,
|
||||
true,
|
||||
"轻量模式隐藏窗口结果: {:?}",
|
||||
result
|
||||
);
|
||||
|
||||
if let Some(window) = handle::Handle::global().get_window() {
|
||||
if window.is_visible().unwrap_or(false) {
|
||||
let _ = window.hide();
|
||||
}
|
||||
if let Some(webview) = window.get_webview_window("main") {
|
||||
let _ = webview.destroy();
|
||||
}
|
||||
@@ -106,6 +124,9 @@ pub fn entry_lightweight_mode() {
|
||||
}
|
||||
set_lightweight_mode(true);
|
||||
let _ = cancel_light_weight_timer();
|
||||
|
||||
// 更新托盘显示
|
||||
let _tray = crate::core::tray::Tray::global();
|
||||
}
|
||||
|
||||
// 添加从轻量模式恢复的函数
|
||||
@@ -125,6 +146,9 @@ pub fn exit_lightweight_mode() {
|
||||
|
||||
// 重置UI就绪状态
|
||||
crate::utils::resolve::reset_ui_ready();
|
||||
|
||||
// 更新托盘显示
|
||||
let _tray = crate::core::tray::Tray::global();
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
|
||||
@@ -286,6 +286,9 @@ pub fn init_config() -> Result<()> {
|
||||
<Result<()>>::Ok(())
|
||||
}));
|
||||
|
||||
// 验证并修正verge.yaml中的clash_core配置
|
||||
crate::log_err!(IVerge::validate_and_fix_config());
|
||||
|
||||
crate::log_err!(dirs::profiles_path().map(|path| {
|
||||
if !path.exists() {
|
||||
help::save_yaml(&path, &IProfiles::template(), Some("# Clash Verge"))?;
|
||||
|
||||
@@ -8,3 +8,4 @@ pub mod network;
|
||||
pub mod resolve;
|
||||
pub mod server;
|
||||
pub mod tmpl;
|
||||
pub mod window_manager;
|
||||
|
||||
@@ -293,12 +293,7 @@ pub fn create_window(is_show: bool) -> bool {
|
||||
);
|
||||
|
||||
if !is_show {
|
||||
logging!(
|
||||
info,
|
||||
Type::Window,
|
||||
true,
|
||||
"Not to create window when starting in silent mode"
|
||||
);
|
||||
logging!(info, Type::Window, true, "静默模式启动时不创建窗口");
|
||||
handle::Handle::notify_startup_completed();
|
||||
return false;
|
||||
}
|
||||
@@ -307,8 +302,17 @@ pub fn create_window(is_show: bool) -> bool {
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
logging!(info, Type::Window, true, "主窗口已存在,将显示现有窗口");
|
||||
if is_show {
|
||||
if window.is_minimized().unwrap_or(false) {
|
||||
logging!(info, Type::Window, true, "窗口已最小化,正在取消最小化");
|
||||
let _ = window.unminimize();
|
||||
}
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
AppHandleManager::global().set_activation_policy_regular();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -355,7 +359,7 @@ pub fn create_window(is_show: bool) -> bool {
|
||||
console.log('[Tauri] 加载指示器已存在');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
console.log('[Tauri] 创建加载指示器');
|
||||
const loadingDiv = document.createElement('div');
|
||||
loadingDiv.id = 'initial-loading-overlay';
|
||||
@@ -363,15 +367,15 @@ pub fn create_window(is_show: bool) -> bool {
|
||||
<div style="
|
||||
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: var(--bg-color, #f5f5f5); color: var(--text-color, #333);
|
||||
display: flex; flex-direction: column; align-items: center;
|
||||
justify-content: center; z-index: 9999;
|
||||
display: flex; flex-direction: column; align-items: center;
|
||||
justify-content: center; z-index: 9999;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
transition: opacity 0.3s ease;
|
||||
">
|
||||
<div style="margin-bottom: 20px;">
|
||||
<div style="
|
||||
width: 40px; height: 40px; border: 3px solid #e3e3e3;
|
||||
border-top: 3px solid #3498db; border-radius: 50%;
|
||||
width: 40px; height: 40px; border: 3px solid #e3e3e3;
|
||||
border-top: 3px solid #3498db; border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
"></div>
|
||||
</div>
|
||||
@@ -406,7 +410,7 @@ pub fn create_window(is_show: bool) -> bool {
|
||||
} else {
|
||||
createLoadingOverlay();
|
||||
}
|
||||
|
||||
|
||||
console.log('[Tauri] 窗口初始化脚本执行完成');
|
||||
"#,
|
||||
)
|
||||
|
||||
272
src-tauri/src/utils/window_manager.rs
Normal file
272
src-tauri/src/utils/window_manager.rs
Normal file
@@ -0,0 +1,272 @@
|
||||
use crate::{core::handle, logging, utils::logging::Type};
|
||||
use tauri::{Manager, WebviewWindow, Wry};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use crate::AppHandleManager;
|
||||
|
||||
/// 窗口操作结果
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum WindowOperationResult {
|
||||
/// 窗口已显示并获得焦点
|
||||
Shown,
|
||||
/// 窗口已隐藏
|
||||
Hidden,
|
||||
/// 创建了新窗口
|
||||
Created,
|
||||
/// 操作失败
|
||||
Failed,
|
||||
/// 无需操作
|
||||
NoAction,
|
||||
}
|
||||
|
||||
/// 窗口状态
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum WindowState {
|
||||
/// 窗口可见且有焦点
|
||||
VisibleFocused,
|
||||
/// 窗口可见但无焦点
|
||||
VisibleUnfocused,
|
||||
/// 窗口最小化
|
||||
Minimized,
|
||||
/// 窗口隐藏
|
||||
Hidden,
|
||||
/// 窗口不存在
|
||||
NotExist,
|
||||
}
|
||||
|
||||
/// 统一的窗口管理器
|
||||
pub struct WindowManager;
|
||||
|
||||
impl WindowManager {
|
||||
pub fn get_main_window_state() -> WindowState {
|
||||
if let Some(window) = Self::get_main_window() {
|
||||
if window.is_minimized().unwrap_or(false) {
|
||||
WindowState::Minimized
|
||||
} else if window.is_visible().unwrap_or(false) {
|
||||
if window.is_focused().unwrap_or(false) {
|
||||
WindowState::VisibleFocused
|
||||
} else {
|
||||
WindowState::VisibleUnfocused
|
||||
}
|
||||
} else {
|
||||
WindowState::Hidden
|
||||
}
|
||||
} else {
|
||||
WindowState::NotExist
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取主窗口实例
|
||||
pub fn get_main_window() -> Option<WebviewWindow<Wry>> {
|
||||
handle::Handle::global()
|
||||
.app_handle()
|
||||
.and_then(|app| app.get_webview_window("main"))
|
||||
}
|
||||
|
||||
/// 智能显示主窗口
|
||||
pub fn show_main_window() -> WindowOperationResult {
|
||||
logging!(info, Type::Window, true, "开始智能显示主窗口");
|
||||
logging!(
|
||||
debug,
|
||||
Type::Window,
|
||||
true,
|
||||
"{}",
|
||||
Self::get_window_status_info()
|
||||
);
|
||||
|
||||
let current_state = Self::get_main_window_state();
|
||||
|
||||
match current_state {
|
||||
WindowState::NotExist => {
|
||||
logging!(info, Type::Window, true, "窗口不存在,创建新窗口");
|
||||
if Self::create_new_window() {
|
||||
WindowOperationResult::Created
|
||||
} else {
|
||||
WindowOperationResult::Failed
|
||||
}
|
||||
}
|
||||
WindowState::VisibleFocused => {
|
||||
logging!(info, Type::Window, true, "窗口已经可见且有焦点,无需操作");
|
||||
WindowOperationResult::NoAction
|
||||
}
|
||||
WindowState::VisibleUnfocused | WindowState::Minimized | WindowState::Hidden => {
|
||||
if let Some(window) = Self::get_main_window() {
|
||||
Self::activate_window(&window)
|
||||
} else {
|
||||
WindowOperationResult::Failed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 切换主窗口显示状态(显示/隐藏)
|
||||
pub fn toggle_main_window() -> WindowOperationResult {
|
||||
logging!(info, Type::Window, true, "开始切换主窗口显示状态");
|
||||
|
||||
let current_state = Self::get_main_window_state();
|
||||
logging!(
|
||||
info,
|
||||
Type::Window,
|
||||
true,
|
||||
"当前窗口状态: {:?}",
|
||||
current_state
|
||||
);
|
||||
|
||||
match current_state {
|
||||
WindowState::NotExist => {
|
||||
// 窗口不存在,创建新窗口
|
||||
if Self::create_new_window() {
|
||||
WindowOperationResult::Created
|
||||
} else {
|
||||
WindowOperationResult::Failed
|
||||
}
|
||||
}
|
||||
WindowState::VisibleFocused => {
|
||||
// 窗口可见且有焦点,隐藏它
|
||||
if let Some(window) = Self::get_main_window() {
|
||||
if window.hide().is_ok() {
|
||||
logging!(info, Type::Window, true, "窗口已隐藏");
|
||||
WindowOperationResult::Hidden
|
||||
} else {
|
||||
WindowOperationResult::Failed
|
||||
}
|
||||
} else {
|
||||
WindowOperationResult::Failed
|
||||
}
|
||||
}
|
||||
WindowState::VisibleUnfocused | WindowState::Minimized | WindowState::Hidden => {
|
||||
// 窗口存在但不可见或无焦点,激活它
|
||||
if let Some(window) = Self::get_main_window() {
|
||||
Self::activate_window(&window)
|
||||
} else {
|
||||
WindowOperationResult::Failed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 激活窗口(取消最小化、显示、设置焦点)
|
||||
fn activate_window(window: &WebviewWindow<Wry>) -> WindowOperationResult {
|
||||
logging!(info, Type::Window, true, "开始激活窗口");
|
||||
|
||||
let mut operations_successful = true;
|
||||
|
||||
// 1. 如果窗口最小化,先取消最小化
|
||||
if window.is_minimized().unwrap_or(false) {
|
||||
logging!(info, Type::Window, true, "窗口已最小化,正在取消最小化");
|
||||
if let Err(e) = window.unminimize() {
|
||||
logging!(warn, Type::Window, true, "取消最小化失败: {}", e);
|
||||
operations_successful = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 显示窗口
|
||||
if let Err(e) = window.show() {
|
||||
logging!(warn, Type::Window, true, "显示窗口失败: {}", e);
|
||||
operations_successful = false;
|
||||
}
|
||||
|
||||
// 3. 设置焦点
|
||||
if let Err(e) = window.set_focus() {
|
||||
logging!(warn, Type::Window, true, "设置窗口焦点失败: {}", e);
|
||||
operations_successful = false;
|
||||
}
|
||||
|
||||
// 4. 平台特定的激活策略
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
logging!(info, Type::Window, true, "应用 macOS 特定的激活策略");
|
||||
AppHandleManager::global().set_activation_policy_regular();
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
// Windows 尝试额外的激活方法
|
||||
if let Err(e) = window.set_always_on_top(true) {
|
||||
logging!(
|
||||
debug,
|
||||
Type::Window,
|
||||
true,
|
||||
"设置置顶失败(非关键错误): {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
// 立即取消置顶
|
||||
if let Err(e) = window.set_always_on_top(false) {
|
||||
logging!(
|
||||
debug,
|
||||
Type::Window,
|
||||
true,
|
||||
"取消置顶失败(非关键错误): {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if operations_successful {
|
||||
logging!(info, Type::Window, true, "窗口激活成功");
|
||||
WindowOperationResult::Shown
|
||||
} else {
|
||||
logging!(warn, Type::Window, true, "窗口激活部分失败");
|
||||
WindowOperationResult::Failed
|
||||
}
|
||||
}
|
||||
|
||||
/// 隐藏主窗口
|
||||
pub fn hide_main_window() -> WindowOperationResult {
|
||||
logging!(info, Type::Window, true, "开始隐藏主窗口");
|
||||
|
||||
if let Some(window) = Self::get_main_window() {
|
||||
if window.hide().is_ok() {
|
||||
logging!(info, Type::Window, true, "窗口已隐藏");
|
||||
WindowOperationResult::Hidden
|
||||
} else {
|
||||
logging!(warn, Type::Window, true, "隐藏窗口失败");
|
||||
WindowOperationResult::Failed
|
||||
}
|
||||
} else {
|
||||
logging!(info, Type::Window, true, "窗口不存在,无需隐藏");
|
||||
WindowOperationResult::NoAction
|
||||
}
|
||||
}
|
||||
|
||||
/// 检查窗口是否可见
|
||||
pub fn is_main_window_visible() -> bool {
|
||||
Self::get_main_window()
|
||||
.map(|window| window.is_visible().unwrap_or(false))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// 检查窗口是否有焦点
|
||||
pub fn is_main_window_focused() -> bool {
|
||||
Self::get_main_window()
|
||||
.map(|window| window.is_focused().unwrap_or(false))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// 检查窗口是否最小化
|
||||
pub fn is_main_window_minimized() -> bool {
|
||||
Self::get_main_window()
|
||||
.map(|window| window.is_minimized().unwrap_or(false))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// 创建新窗口现有的实现
|
||||
fn create_new_window() -> bool {
|
||||
use crate::utils::resolve;
|
||||
resolve::create_window(true)
|
||||
}
|
||||
|
||||
/// 获取详细的窗口状态信息
|
||||
pub fn get_window_status_info() -> String {
|
||||
let state = Self::get_main_window_state();
|
||||
let is_visible = Self::is_main_window_visible();
|
||||
let is_focused = Self::is_main_window_focused();
|
||||
let is_minimized = Self::is_main_window_minimized();
|
||||
|
||||
format!(
|
||||
"窗口状态: {:?} | 可见: {} | 有焦点: {} | 最小化: {}",
|
||||
state, is_visible, is_focused, is_minimized
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "2.3.0",
|
||||
"version": "2.3.1",
|
||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||
"bundle": {
|
||||
"active": true,
|
||||
|
||||
@@ -52,19 +52,16 @@ export const useCustomTheme = () => {
|
||||
|
||||
let isMounted = true;
|
||||
|
||||
const timerId = setTimeout(() => {
|
||||
if (!isMounted) return;
|
||||
appWindow
|
||||
.theme()
|
||||
.then((systemTheme) => {
|
||||
if (isMounted && systemTheme) {
|
||||
setMode(systemTheme);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Failed to get initial system theme:", err);
|
||||
});
|
||||
}, 0);
|
||||
appWindow
|
||||
.theme()
|
||||
.then((systemTheme) => {
|
||||
if (isMounted && systemTheme) {
|
||||
setMode(systemTheme);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Failed to get initial system theme:", err);
|
||||
});
|
||||
|
||||
const unlistenPromise = appWindow.onThemeChanged(({ payload }) => {
|
||||
if (isMounted) {
|
||||
@@ -74,7 +71,6 @@ export const useCustomTheme = () => {
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
clearTimeout(timerId);
|
||||
unlistenPromise
|
||||
.then((unlistenFn) => {
|
||||
if (typeof unlistenFn === "function") {
|
||||
@@ -131,6 +127,7 @@ export const useCustomTheme = () => {
|
||||
},
|
||||
background: {
|
||||
paper: dt.background_color,
|
||||
default: dt.background_color,
|
||||
},
|
||||
},
|
||||
shadows: Array(25).fill("none") as Shadows,
|
||||
@@ -157,6 +154,10 @@ export const useCustomTheme = () => {
|
||||
warning: { main: dt.warning_color },
|
||||
success: { main: dt.success_color },
|
||||
text: { primary: dt.primary_text, secondary: dt.secondary_text },
|
||||
background: {
|
||||
paper: dt.background_color,
|
||||
default: dt.background_color,
|
||||
},
|
||||
},
|
||||
typography: { fontFamily: dt.font_family },
|
||||
});
|
||||
@@ -164,9 +165,10 @@ export const useCustomTheme = () => {
|
||||
|
||||
const rootEle = document.documentElement;
|
||||
if (rootEle) {
|
||||
const backgroundColor = mode === "light" ? "#ECECEC" : "#2e303d";
|
||||
const selectColor = mode === "light" ? "#f5f5f5" : "#d5d5d5";
|
||||
const scrollColor = mode === "light" ? "#90939980" : "#3E3E3Eee";
|
||||
const backgroundColor =
|
||||
mode === "light" ? "#ECECEC" : dt.background_color;
|
||||
const selectColor = mode === "light" ? "#f5f5f5" : "#3E3E3E";
|
||||
const scrollColor = mode === "light" ? "#90939980" : "#555555";
|
||||
const dividerColor =
|
||||
mode === "light" ? "rgba(0, 0, 0, 0.06)" : "rgba(255, 255, 255, 0.06)";
|
||||
|
||||
@@ -182,16 +184,68 @@ export const useCustomTheme = () => {
|
||||
"--background-color-alpha",
|
||||
alpha(muiTheme.palette.primary.main, 0.1),
|
||||
);
|
||||
// 添加CSS变量
|
||||
rootEle.style.setProperty(
|
||||
"--window-border-color",
|
||||
mode === "light" ? "#cccccc" : "#1E1E1E",
|
||||
);
|
||||
rootEle.style.setProperty(
|
||||
"--scrollbar-bg",
|
||||
mode === "light" ? "#f1f1f1" : "#2E303D",
|
||||
);
|
||||
rootEle.style.setProperty(
|
||||
"--scrollbar-thumb",
|
||||
mode === "light" ? "#c1c1c1" : "#555555",
|
||||
);
|
||||
}
|
||||
// inject css
|
||||
|
||||
let styleElement = document.querySelector("style#verge-theme");
|
||||
if (!styleElement) {
|
||||
styleElement = document.createElement("style");
|
||||
styleElement.id = "verge-theme";
|
||||
document.head.appendChild(styleElement!);
|
||||
}
|
||||
|
||||
if (styleElement) {
|
||||
styleElement.innerHTML = setting.css_injection || "";
|
||||
// 添加全局样式,确保所有元素都使用暗色主题
|
||||
const globalStyles = `
|
||||
/* 修复滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background-color: var(--scrollbar-bg);
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: var(--scrollbar-thumb);
|
||||
border-radius: 4px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: ${mode === "light" ? "#a1a1a1" : "#666666"};
|
||||
}
|
||||
|
||||
/* 确保所有元素都使用正确的背景色 */
|
||||
body, html {
|
||||
background-color: var(--background-color) !important;
|
||||
}
|
||||
|
||||
/* 修复可能的白色边框 */
|
||||
.MuiPaper-root {
|
||||
border-color: var(--window-border-color) !important;
|
||||
}
|
||||
|
||||
/* 确保模态框和对话框也使用暗色主题 */
|
||||
.MuiDialog-paper {
|
||||
background-color: ${mode === "light" ? "#ffffff" : "#2E303D"} !important;
|
||||
}
|
||||
|
||||
/* 移除可能的白色点或线条 */
|
||||
* {
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
`;
|
||||
|
||||
styleElement.innerHTML = (setting.css_injection || "") + globalStyles;
|
||||
}
|
||||
|
||||
const { palette } = muiTheme;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { BaseDialog, DialogRef } from "@/components/base";
|
||||
import { useClashInfo } from "@/hooks/use-clash";
|
||||
import { patchClashConfig } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { ContentCopy } from "@mui/icons-material";
|
||||
import {
|
||||
@@ -42,19 +41,19 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
// 保存配置
|
||||
const onSave = useLockFn(async () => {
|
||||
if (!controller.trim()) {
|
||||
showNotice("error", t("Controller address cannot be empty"), 3000);
|
||||
showNotice("error", t("Controller address cannot be empty"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!secret.trim()) {
|
||||
showNotice("error", t("Secret cannot be empty"), 3000);
|
||||
showNotice("error", t("Secret cannot be empty"));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsSaving(true);
|
||||
await patchInfo({ "external-controller": controller, secret });
|
||||
showNotice("success", t("Configuration saved successfully"), 2000);
|
||||
showNotice("success", t("Configuration saved successfully"));
|
||||
setOpen(false);
|
||||
} catch (err: any) {
|
||||
showNotice(
|
||||
@@ -73,9 +72,9 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
setCopySuccess(type);
|
||||
setTimeout(() => setCopySuccess(null), 2000);
|
||||
setTimeout(() => setCopySuccess(null));
|
||||
} catch (err) {
|
||||
showNotice("error", t("Failed to copy"), 2000);
|
||||
showNotice("error", t("Failed to copy"));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -10,11 +10,32 @@ export const useProfiles = () => {
|
||||
const { data: profiles, mutate: mutateProfiles } = useSWR(
|
||||
"getProfiles",
|
||||
getProfiles,
|
||||
{
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: false,
|
||||
dedupingInterval: 2000,
|
||||
errorRetryCount: 2,
|
||||
errorRetryInterval: 1000,
|
||||
},
|
||||
);
|
||||
|
||||
const patchProfiles = async (value: Partial<IProfilesConfig>) => {
|
||||
await patchProfilesConfig(value);
|
||||
mutateProfiles();
|
||||
// 立即更新本地状态
|
||||
if (value.current && profiles) {
|
||||
const optimisticUpdate = {
|
||||
...profiles,
|
||||
current: value.current,
|
||||
};
|
||||
mutateProfiles(optimisticUpdate, false); // 不重新验证
|
||||
}
|
||||
|
||||
try {
|
||||
await patchProfilesConfig(value);
|
||||
mutateProfiles();
|
||||
} catch (error) {
|
||||
mutateProfiles();
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const patchCurrent = async (value: Partial<IProfileItem>) => {
|
||||
@@ -26,40 +47,90 @@ export const useProfiles = () => {
|
||||
|
||||
// 根据selected的节点选择
|
||||
const activateSelected = async () => {
|
||||
const proxiesData = await getProxies();
|
||||
const profileData = await getProfiles();
|
||||
try {
|
||||
console.log("[ActivateSelected] 开始处理代理选择");
|
||||
|
||||
if (!profileData || !proxiesData) return;
|
||||
const [proxiesData, profileData] = await Promise.all([
|
||||
getProxies(),
|
||||
getProfiles(),
|
||||
]);
|
||||
|
||||
const current = profileData.items?.find(
|
||||
(e) => e && e.uid === profileData.current,
|
||||
);
|
||||
|
||||
if (!current) return;
|
||||
|
||||
// init selected array
|
||||
const { selected = [] } = current;
|
||||
const selectedMap = Object.fromEntries(
|
||||
selected.map((each) => [each.name!, each.now!]),
|
||||
);
|
||||
|
||||
let hasChange = false;
|
||||
|
||||
const newSelected: typeof selected = [];
|
||||
const { global, groups } = proxiesData;
|
||||
|
||||
[global, ...groups].forEach(({ type, name, now }) => {
|
||||
if (!now || type !== "Selector") return;
|
||||
if (selectedMap[name] != null && selectedMap[name] !== now) {
|
||||
hasChange = true;
|
||||
updateProxy(name, selectedMap[name]);
|
||||
if (!profileData || !proxiesData) {
|
||||
console.log("[ActivateSelected] 代理或配置数据不可用,跳过处理");
|
||||
return;
|
||||
}
|
||||
newSelected.push({ name, now: selectedMap[name] });
|
||||
});
|
||||
|
||||
if (hasChange) {
|
||||
patchProfile(profileData.current!, { selected: newSelected });
|
||||
mutate("getProxies", getProxies());
|
||||
const current = profileData.items?.find(
|
||||
(e) => e && e.uid === profileData.current,
|
||||
);
|
||||
|
||||
if (!current) {
|
||||
console.log("[ActivateSelected] 未找到当前profile配置");
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否有saved的代理选择
|
||||
const { selected = [] } = current;
|
||||
if (selected.length === 0) {
|
||||
console.log("[ActivateSelected] 当前profile无保存的代理选择,跳过");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[ActivateSelected] 当前profile有 ${selected.length} 个代理选择配置`,
|
||||
);
|
||||
|
||||
const selectedMap = Object.fromEntries(
|
||||
selected.map((each) => [each.name!, each.now!]),
|
||||
);
|
||||
|
||||
let hasChange = false;
|
||||
const newSelected: typeof selected = [];
|
||||
const { global, groups } = proxiesData;
|
||||
|
||||
// 处理所有代理组
|
||||
[global, ...groups].forEach(({ type, name, now }) => {
|
||||
if (!now || type !== "Selector") {
|
||||
if (selectedMap[name] != null) {
|
||||
newSelected.push({ name, now: now || selectedMap[name] });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const targetProxy = selectedMap[name];
|
||||
if (targetProxy != null && targetProxy !== now) {
|
||||
console.log(
|
||||
`[ActivateSelected] 需要切换代理组 ${name}: ${now} -> ${targetProxy}`,
|
||||
);
|
||||
hasChange = true;
|
||||
updateProxy(name, targetProxy);
|
||||
}
|
||||
|
||||
newSelected.push({ name, now: targetProxy || now });
|
||||
});
|
||||
|
||||
if (!hasChange) {
|
||||
console.log("[ActivateSelected] 所有代理选择已经是目标状态,无需更新");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[ActivateSelected] 完成代理切换,保存新的选择配置`);
|
||||
|
||||
try {
|
||||
await patchProfile(profileData.current!, { selected: newSelected });
|
||||
console.log("[ActivateSelected] 代理选择配置保存成功");
|
||||
|
||||
setTimeout(() => {
|
||||
mutate("getProxies", getProxies());
|
||||
}, 100);
|
||||
} catch (error: any) {
|
||||
console.error(
|
||||
"[ActivateSelected] 保存代理选择配置失败:",
|
||||
error.message,
|
||||
);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("[ActivateSelected] 处理代理选择失败:", error.message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -419,6 +419,7 @@
|
||||
"Clash Core Restarted": "Clash Core Restarted",
|
||||
"GeoData Updated": "GeoData Updated",
|
||||
"Currently on the Latest Version": "Currently on the Latest Version",
|
||||
"Already Using Latest Core": "Already Using Latest Core",
|
||||
"Import Subscription Successful": "Import subscription successful",
|
||||
"WebDAV Server URL": "WebDAV Server URL",
|
||||
"Username": "Username",
|
||||
|
||||
@@ -419,6 +419,7 @@
|
||||
"Clash Core Restarted": "已重启 Clash 内核",
|
||||
"GeoData Updated": "已更新 GeoData",
|
||||
"Currently on the Latest Version": "当前已是最新版本",
|
||||
"Already Using Latest Core": "已是最新内核版本",
|
||||
"Import Subscription Successful": "导入订阅成功",
|
||||
"WebDAV Server URL": "WebDAV 服务器地址 http(s)://",
|
||||
"Username": "用户名",
|
||||
@@ -602,8 +603,8 @@
|
||||
"Proxy Mode": "代理模式",
|
||||
"Group": "代理组",
|
||||
"Proxy": "节点",
|
||||
"IP Information Card": "IP信息卡",
|
||||
"IP Information": "IP信息",
|
||||
"IP Information Card": "IP 信息卡",
|
||||
"IP Information": "IP 信息",
|
||||
"Failed to get IP info": "获取IP信息失败",
|
||||
"ISP": "服务商",
|
||||
"ASN": "自治域",
|
||||
|
||||
@@ -169,7 +169,13 @@ const Layout = () => {
|
||||
const handleNotice = useCallback(
|
||||
(payload: [string, string]) => {
|
||||
const [status, msg] = payload;
|
||||
handleNoticeMessage(status, msg, t, navigate);
|
||||
setTimeout(() => {
|
||||
try {
|
||||
handleNoticeMessage(status, msg, t, navigate);
|
||||
} catch (error) {
|
||||
console.error("[Layout] 处理通知消息失败:", error);
|
||||
}
|
||||
}, 0);
|
||||
},
|
||||
[t, navigate],
|
||||
);
|
||||
@@ -220,12 +226,35 @@ const Layout = () => {
|
||||
const cleanupWindow = setupWindowListeners();
|
||||
|
||||
return () => {
|
||||
listeners.forEach((listener) => {
|
||||
if (typeof listener.then === "function") {
|
||||
listener.then((unlisten) => unlisten());
|
||||
}
|
||||
});
|
||||
cleanupWindow.then((cleanup) => cleanup());
|
||||
setTimeout(() => {
|
||||
listeners.forEach((listener) => {
|
||||
if (typeof listener.then === "function") {
|
||||
listener
|
||||
.then((unlisten) => {
|
||||
try {
|
||||
unlisten();
|
||||
} catch (error) {
|
||||
console.error("[Layout] 清理事件监听器失败:", error);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("[Layout] 获取unlisten函数失败:", error);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
cleanupWindow
|
||||
.then((cleanup) => {
|
||||
try {
|
||||
cleanup();
|
||||
} catch (error) {
|
||||
console.error("[Layout] 清理窗口监听器失败:", error);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("[Layout] 获取cleanup函数失败:", error);
|
||||
});
|
||||
}, 0);
|
||||
};
|
||||
}, [handleNotice]);
|
||||
|
||||
@@ -471,6 +500,10 @@ const Layout = () => {
|
||||
square
|
||||
elevation={0}
|
||||
className={`${OS} layout`}
|
||||
style={{
|
||||
borderTopLeftRadius: "0px",
|
||||
borderTopRightRadius: "0px",
|
||||
}}
|
||||
onContextMenu={(e) => {
|
||||
if (
|
||||
OS === "windows" &&
|
||||
@@ -488,8 +521,8 @@ const Layout = () => {
|
||||
? {
|
||||
borderRadius: "8px",
|
||||
border: "1px solid var(--divider-color)",
|
||||
width: "calc(100vw - 4px)",
|
||||
height: "calc(100vh - 4px)",
|
||||
width: "100vw",
|
||||
height: "100vh",
|
||||
}
|
||||
: {},
|
||||
]}
|
||||
|
||||
@@ -190,27 +190,53 @@ const ProfilePage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const activateProfile = async (profile: string, notifySuccess: boolean) => {
|
||||
// 避免大多数情况下loading态闪烁
|
||||
const reset = setTimeout(() => {
|
||||
setActivatings((prev) => [...prev, profile]);
|
||||
}, 100);
|
||||
|
||||
try {
|
||||
const success = await patchProfiles({ current: profile });
|
||||
await mutateLogs();
|
||||
closeAllConnections();
|
||||
await activateSelected();
|
||||
if (notifySuccess && success) {
|
||||
showNotice("success", t("Profile Switched"), 1000);
|
||||
const activateProfile = useLockFn(
|
||||
async (profile: string, notifySuccess: boolean) => {
|
||||
if (profiles.current === profile && !notifySuccess) {
|
||||
console.log(
|
||||
`[Profile] 目标profile ${profile} 已经是当前配置,跳过切换`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
} catch (err: any) {
|
||||
showNotice("error", err?.message || err.toString(), 4000);
|
||||
} finally {
|
||||
clearTimeout(reset);
|
||||
setActivatings([]);
|
||||
}
|
||||
};
|
||||
|
||||
// 避免大多数情况下loading态闪烁
|
||||
const reset = setTimeout(() => {
|
||||
setActivatings((prev) => [...prev, profile]);
|
||||
}, 100);
|
||||
|
||||
try {
|
||||
console.log(`[Profile] 开始切换到: ${profile}`);
|
||||
|
||||
const success = await patchProfiles({ current: profile });
|
||||
await mutateLogs();
|
||||
closeAllConnections();
|
||||
|
||||
if (notifySuccess && success) {
|
||||
showNotice("success", t("Profile Switched"), 1000);
|
||||
}
|
||||
|
||||
// 立即清除loading状态
|
||||
clearTimeout(reset);
|
||||
setActivatings([]);
|
||||
|
||||
console.log(`[Profile] 切换到 ${profile} 完成,开始后台处理`);
|
||||
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
await activateSelected();
|
||||
console.log(`[Profile] 后台处理完成`);
|
||||
} catch (err: any) {
|
||||
console.warn("Failed to activate selected proxies:", err);
|
||||
}
|
||||
}, 50);
|
||||
} catch (err: any) {
|
||||
console.error(`[Profile] 切换失败:`, err);
|
||||
showNotice("error", err?.message || err.toString(), 4000);
|
||||
clearTimeout(reset);
|
||||
setActivatings([]);
|
||||
}
|
||||
},
|
||||
);
|
||||
const onSelect = useLockFn(async (current: string, force: boolean) => {
|
||||
if (!force && current === profiles.current) return;
|
||||
await activateProfile(current, true);
|
||||
@@ -300,31 +326,45 @@ const ProfilePage = () => {
|
||||
// 监听后端配置变更
|
||||
useEffect(() => {
|
||||
let unlistenPromise: Promise<() => void> | undefined;
|
||||
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
||||
let lastProfileId: string | null = null;
|
||||
let lastUpdateTime = 0;
|
||||
const debounceDelay = 200;
|
||||
|
||||
const setupListener = async () => {
|
||||
unlistenPromise = listen<string>("profile-changed", (event) => {
|
||||
console.log("Profile changed event received:", event.payload);
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
const newProfileId = event.payload;
|
||||
const now = Date.now();
|
||||
|
||||
console.log(`[Profile] 收到配置变更事件: ${newProfileId}`);
|
||||
|
||||
if (
|
||||
lastProfileId === newProfileId &&
|
||||
now - lastUpdateTime < debounceDelay
|
||||
) {
|
||||
console.log(`[Profile] 重复事件被防抖,跳过`);
|
||||
return;
|
||||
}
|
||||
|
||||
timeoutId = setTimeout(() => {
|
||||
mutateProfiles();
|
||||
timeoutId = undefined;
|
||||
}, 300);
|
||||
lastProfileId = newProfileId;
|
||||
lastUpdateTime = now;
|
||||
|
||||
console.log(`[Profile] 执行配置数据刷新`);
|
||||
|
||||
// 使用异步调度避免阻塞事件处理
|
||||
setTimeout(() => {
|
||||
mutateProfiles().catch((error) => {
|
||||
console.error("[Profile] 配置数据刷新失败:", error);
|
||||
});
|
||||
}, 0);
|
||||
});
|
||||
};
|
||||
|
||||
setupListener();
|
||||
|
||||
return () => {
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
unlistenPromise?.then((unlisten) => unlisten());
|
||||
unlistenPromise?.then((unlisten) => unlisten()).catch(console.error);
|
||||
};
|
||||
}, [mutateProfiles, t]);
|
||||
}, [mutateProfiles]);
|
||||
|
||||
return (
|
||||
<BasePage
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createContext, useContext, useMemo } from "react";
|
||||
import { createContext, useContext, useMemo, useEffect } from "react";
|
||||
import useSWR from "swr";
|
||||
import useSWRSubscription from "swr/subscription";
|
||||
import {
|
||||
@@ -8,10 +8,16 @@ import {
|
||||
getProxyProviders,
|
||||
getRuleProviders,
|
||||
} from "@/services/api";
|
||||
import { getSystemProxy, getRunningMode, getAppUptime } from "@/services/cmds";
|
||||
import {
|
||||
getSystemProxy,
|
||||
getRunningMode,
|
||||
getAppUptime,
|
||||
forceRefreshProxies,
|
||||
} from "@/services/cmds";
|
||||
import { useClashInfo } from "@/hooks/use-clash";
|
||||
import { createAuthSockette } from "@/utils/websocket";
|
||||
import { useVisibility } from "@/hooks/use-visibility";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
|
||||
// 定义AppDataContext类型 - 使用宽松类型
|
||||
interface AppDataContextType {
|
||||
@@ -64,6 +70,126 @@ export const AppDataProvider = ({
|
||||
},
|
||||
);
|
||||
|
||||
// 监听profile和clash配置变更事件
|
||||
useEffect(() => {
|
||||
let profileUnlisten: Promise<() => void> | undefined;
|
||||
let lastProfileId: string | null = null;
|
||||
let lastUpdateTime = 0;
|
||||
const refreshThrottle = 500;
|
||||
|
||||
const setupEventListeners = async () => {
|
||||
try {
|
||||
// 监听profile切换事件
|
||||
profileUnlisten = listen<string>("profile-changed", (event) => {
|
||||
const newProfileId = event.payload;
|
||||
const now = Date.now();
|
||||
|
||||
console.log(`[AppDataProvider] Profile切换事件: ${newProfileId}`);
|
||||
|
||||
if (
|
||||
lastProfileId === newProfileId &&
|
||||
now - lastUpdateTime < refreshThrottle
|
||||
) {
|
||||
console.log("[AppDataProvider] 重复事件被防抖,跳过");
|
||||
return;
|
||||
}
|
||||
|
||||
lastProfileId = newProfileId;
|
||||
lastUpdateTime = now;
|
||||
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
console.log("[AppDataProvider] 强制刷新代理缓存");
|
||||
|
||||
const refreshPromise = Promise.race([
|
||||
forceRefreshProxies(),
|
||||
new Promise((_, reject) =>
|
||||
setTimeout(
|
||||
() => reject(new Error("forceRefreshProxies timeout")),
|
||||
8000,
|
||||
),
|
||||
),
|
||||
]);
|
||||
|
||||
await refreshPromise;
|
||||
|
||||
console.log("[AppDataProvider] 刷新前端代理数据");
|
||||
await refreshProxy();
|
||||
|
||||
console.log("[AppDataProvider] Profile切换的代理数据刷新完成");
|
||||
} catch (error) {
|
||||
console.error("[AppDataProvider] 强制刷新代理缓存失败:", error);
|
||||
|
||||
refreshProxy().catch((e) =>
|
||||
console.warn("[AppDataProvider] 普通刷新也失败:", e),
|
||||
);
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
|
||||
// 监听Clash配置刷新事件(enhance操作等)
|
||||
const handleRefreshClash = () => {
|
||||
const now = Date.now();
|
||||
console.log("[AppDataProvider] Clash配置刷新事件");
|
||||
|
||||
if (now - lastUpdateTime > refreshThrottle) {
|
||||
lastUpdateTime = now;
|
||||
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
console.log("[AppDataProvider] Clash刷新 - 强制刷新代理缓存");
|
||||
|
||||
// 添加超时保护
|
||||
const refreshPromise = Promise.race([
|
||||
forceRefreshProxies(),
|
||||
new Promise((_, reject) =>
|
||||
setTimeout(
|
||||
() => reject(new Error("forceRefreshProxies timeout")),
|
||||
8000,
|
||||
),
|
||||
),
|
||||
]);
|
||||
|
||||
await refreshPromise;
|
||||
await refreshProxy();
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[AppDataProvider] Clash刷新时强制刷新代理缓存失败:",
|
||||
error,
|
||||
);
|
||||
refreshProxy().catch((e) =>
|
||||
console.warn("[AppDataProvider] Clash刷新普通刷新也失败:", e),
|
||||
);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener(
|
||||
"verge://refresh-clash-config",
|
||||
handleRefreshClash,
|
||||
);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener(
|
||||
"verge://refresh-clash-config",
|
||||
handleRefreshClash,
|
||||
);
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("[AppDataProvider] 事件监听器设置失败:", error);
|
||||
return () => {};
|
||||
}
|
||||
};
|
||||
|
||||
const cleanupPromise = setupEventListeners();
|
||||
|
||||
return () => {
|
||||
profileUnlisten?.then((unlisten) => unlisten()).catch(console.error);
|
||||
cleanupPromise.then((cleanup) => cleanup());
|
||||
};
|
||||
}, [refreshProxy]);
|
||||
|
||||
const { data: clashConfig, mutate: refreshClashConfig } = useSWR(
|
||||
"getClashConfig",
|
||||
getClashConfig,
|
||||
|
||||
@@ -220,6 +220,12 @@ export async function cmdGetProxyDelay(
|
||||
}
|
||||
}
|
||||
|
||||
/// 用于profile切换等场景
|
||||
export async function forceRefreshProxies() {
|
||||
console.log("[API] 强制刷新代理缓存");
|
||||
return invoke<any>("force_refresh_proxies");
|
||||
}
|
||||
|
||||
export async function cmdTestDelay(url: string) {
|
||||
return invoke<number>("test_delay", { url });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user