Compare commits
56 Commits
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -57,6 +57,6 @@ body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 日志 / Log
|
||||
description: 请提供完整或相关部分的Debug日志(请在“软件左侧菜单”->“设置”->“日志等级”调整到debug,Verge错误请把“杂项设置”->“app日志等级”调整到trace,并重启Verge生效。日志文件在“软件左侧菜单”->“设置”->“日志目录”下) / Please provide a complete or relevant part of the Debug log (please adjust the "Log level" to debug in "Software left menu" -> "Settings" -> "Log level". If there is a Verge error, please adjust "Miscellaneous settings" -> "app log level" to trace, and restart Verge to take effect. The log file is under "Software left menu" -> "Settings" -> "Log directory")
|
||||
description: 请提供完整或相关部分的Debug日志(请在“软件左侧菜单”->“设置”->“日志等级”调整到debug,Verge错误请把“杂项设置”->“app日志等级”调整到debug/trace,并重启Verge生效。日志文件在“软件左侧菜单”->“设置”->“日志目录”下) / Please provide a complete or relevant part of the Debug log (please adjust the "Log level" to debug in "Software left menu" -> "Settings" -> "Log level". If there is a Verge error, please adjust "Miscellaneous settings" -> "app log level" to trace, and restart Verge to take effect. The log file is under "Software left menu" -> "Settings" -> "Log directory")
|
||||
validations:
|
||||
required: true
|
||||
|
||||
18
.github/workflows/alpha.yml
vendored
18
.github/workflows/alpha.yml
vendored
@@ -48,7 +48,7 @@ jobs:
|
||||
workspaces: src-tauri
|
||||
cache-all-crates: true
|
||||
cache-on-failure: true
|
||||
|
||||
|
||||
- name: Install dependencies (ubuntu only)
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
run: |
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
|
||||
- name: Add Rust Target
|
||||
run: rustup target add ${{ matrix.target }}
|
||||
|
||||
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
@@ -129,7 +129,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
node-version: "20"
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
@@ -141,10 +141,10 @@ jobs:
|
||||
pnpm i
|
||||
pnpm check ${{ matrix.target }}
|
||||
|
||||
- name: 'Setup for linux'
|
||||
- name: "Setup for linux"
|
||||
run: |-
|
||||
sudo ls -lR /etc/apt/
|
||||
|
||||
|
||||
cat > /tmp/sources.list << EOF
|
||||
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy main multiverse universe restricted
|
||||
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy-security main multiverse universe restricted
|
||||
@@ -170,20 +170,20 @@ jobs:
|
||||
patchelf:${{ matrix.arch }} \
|
||||
librsvg2-dev:${{ matrix.arch }}
|
||||
|
||||
- name: 'Install aarch64 tools'
|
||||
- name: "Install aarch64 tools"
|
||||
if: matrix.target == 'aarch64-unknown-linux-gnu'
|
||||
run: |
|
||||
sudo apt install -y \
|
||||
gcc-aarch64-linux-gnu \
|
||||
g++-aarch64-linux-gnu
|
||||
|
||||
- name: 'Install armv7 tools'
|
||||
- name: "Install armv7 tools"
|
||||
if: matrix.target == 'armv7-unknown-linux-gnueabihf'
|
||||
run: |
|
||||
sudo apt install -y \
|
||||
gcc-arm-linux-gnueabihf \
|
||||
g++-arm-linux-gnueabihf
|
||||
|
||||
|
||||
- name: Build for Linux
|
||||
run: |
|
||||
export PKG_CONFIG_ALLOW_CROSS=1
|
||||
@@ -196,7 +196,7 @@ jobs:
|
||||
fi
|
||||
pnpm build --target ${{ matrix.target }}
|
||||
env:
|
||||
NODE_OPTIONS: '--max_old_space_size=4096'
|
||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
|
||||
|
||||
33
.github/workflows/release.yml
vendored
33
.github/workflows/release.yml
vendored
@@ -50,7 +50,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
node-version: "22"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -67,8 +67,8 @@ jobs:
|
||||
env:
|
||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
@@ -109,7 +109,7 @@ jobs:
|
||||
|
||||
- name: Add Rust Target
|
||||
run: rustup target add ${{ matrix.target }}
|
||||
|
||||
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
node-version: "22"
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
@@ -131,10 +131,10 @@ jobs:
|
||||
pnpm i
|
||||
pnpm check ${{ matrix.target }}
|
||||
|
||||
- name: 'Setup for linux'
|
||||
- name: "Setup for linux"
|
||||
run: |-
|
||||
sudo ls -lR /etc/apt/
|
||||
|
||||
|
||||
cat > /tmp/sources.list << EOF
|
||||
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy main multiverse universe restricted
|
||||
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy-security main multiverse universe restricted
|
||||
@@ -160,20 +160,20 @@ jobs:
|
||||
patchelf:${{ matrix.arch }} \
|
||||
librsvg2-dev:${{ matrix.arch }}
|
||||
|
||||
- name: 'Install aarch64 tools'
|
||||
- name: "Install aarch64 tools"
|
||||
if: matrix.target == 'aarch64-unknown-linux-gnu'
|
||||
run: |
|
||||
sudo apt install -y \
|
||||
gcc-aarch64-linux-gnu \
|
||||
g++-aarch64-linux-gnu
|
||||
|
||||
- name: 'Install armv7 tools'
|
||||
- name: "Install armv7 tools"
|
||||
if: matrix.target == 'armv7-unknown-linux-gnueabihf'
|
||||
run: |
|
||||
sudo apt install -y \
|
||||
gcc-arm-linux-gnueabihf \
|
||||
g++-arm-linux-gnueabihf
|
||||
|
||||
|
||||
- name: Build for Linux
|
||||
run: |
|
||||
export PKG_CONFIG_ALLOW_CROSS=1
|
||||
@@ -186,7 +186,7 @@ jobs:
|
||||
fi
|
||||
pnpm build --target ${{ matrix.target }}
|
||||
env:
|
||||
NODE_OPTIONS: '--max_old_space_size=4096'
|
||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
|
||||
@@ -195,6 +195,7 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get install jq
|
||||
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
|
||||
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
@@ -237,7 +238,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
node-version: "22"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -262,8 +263,8 @@ jobs:
|
||||
env:
|
||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
with:
|
||||
tauriScript: pnpm
|
||||
args: --target ${{ matrix.target }}
|
||||
@@ -298,7 +299,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
node-version: "22"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -323,7 +324,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
node-version: "22"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
|
||||
4
.github/workflows/updater.yml
vendored
4
.github/workflows/updater.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
node-version: "22"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
node-version: "22"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
|
||||
46
UPDATELOG.md
46
UPDATELOG.md
@@ -1,16 +1,48 @@
|
||||
## v2.0.0
|
||||
## v2.0.2
|
||||
|
||||
### Notice
|
||||
|
||||
- !!使用出现异常的,打开设置-->配置目录 备份 后 删除所有文件 尝试是否正常!!
|
||||
- 历时3个月的紧密开发与严格测试稳定版2.0.0终于发布了:巨量改进与性能、稳定性提升,目前Clash Verge Rev已经有了比肩cfw的健壮性;而且更强大易用!
|
||||
- 由于更改了服务安装逻辑,Mac/Linux 首次安装需要输入 2 遍系统密码卸载和安装服务,以后可以丝滑使用 tun(虚拟网卡)模式
|
||||
- 因 Tauri 2.0 底层 bug,关闭窗口暂时修改为最小化功能
|
||||
- 由于更改了服务安装逻辑,Mac/Linux 首次安装需要输入系统密码卸载和安装服务,以后可以丝滑使用 tun(虚拟网卡)模式
|
||||
- 因 Tauri 2.0 底层 bug,关闭窗口后保留webview进程,优点是再次打开面板更快,缺点是内存使用略有增加
|
||||
|
||||
### 2.0.2相对于2.0.1改进了:
|
||||
|
||||
- MacOS 下自定义图标可以支持彩色、单色切换
|
||||
- 修正了 Linux 下多个内核僵尸进程的问题
|
||||
- 修正了 DNS ipv6 强制覆盖的逻辑
|
||||
- 修改了 MacOS tun 模式下覆盖设置 dns 字段的问题
|
||||
- 修正了 MacOS tray 图标不会随代理模式更改的问题
|
||||
- 静默启动下重复运行会出现多个实例的bug
|
||||
- 安装的时候自动删除历史残留启动项
|
||||
- Tun模式默认是还用内核推荐的 mixed 堆栈
|
||||
- 改进了默认窗口大小(启动软件窗口不会那么小了)
|
||||
- 改进了 WebDAV 备份超时时间机制
|
||||
- 测试菜单添加滚动条
|
||||
- 改进和修正了 Tun 模式下对设置的覆盖逻辑
|
||||
- 修复了打开配置出错的问题
|
||||
- 修复了配置文件无法拖拽添加的问题
|
||||
- 改善了浅色模式的对比度
|
||||
|
||||
### 2.0.1相对于2.0.0改进了:
|
||||
|
||||
- 无法从 2.0rc和2.0.0 升级的问题(已经安装了2.0版本的需手动下载安装)
|
||||
- MacOS 系统下少有的无法安装服务,无法启动的问题,目前更健壮了
|
||||
- 当系统中没有 yaml 编辑器的情况下,打开文件程序崩溃的问题
|
||||
- Windows 应用内升级和覆盖安装不会删除老执行文件的问题
|
||||
- 修改优化了 mac 下 fakeip 段和 dns
|
||||
- 测试菜单 svg 图标格式检查
|
||||
- 应用内升级重复安装 vs runtime 的问题
|
||||
- 修复外部控制下密码有特殊字符认证出错的问题
|
||||
- 修复恢复 Webdav 备份设置后, Webdav 设置丢失的问题
|
||||
- 代理页面增加快速回到顶部的按钮
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- 重大框架升级:使用 Tauri 2.0(巨量改进与性能提升)
|
||||
- 强烈建议完全删除 1.x 老版本再安装此版本
|
||||
- 出现 bug 到 issues 中提出;以后不再接受1.x版本的bug反馈。
|
||||
- 强烈建议完全删除 1.x 老版本再安装此版本 !!使用出现异常的,打开设置-->配置目录 备份 后 删除所有文件 尝试是否正常!!
|
||||
|
||||
### Features
|
||||
|
||||
@@ -30,7 +62,7 @@
|
||||
- 添加统一延迟的设置开关
|
||||
- 添加 Windows 下自动检测并下载 vc runtime 的功能
|
||||
- 支持显示 mux 和 mptcp 的节点标识
|
||||
- 延迟测试连接更换 https 的 cp.cloudflare.com/generate_204 以防止机场劫持(关闭统一延迟的情况下延迟测试结果会有所增加)。
|
||||
- 延迟测试连接更换 http 的 cp.cloudflare.com/generate_204 (关闭统一延迟的情况下延迟测试结果会有所增加)
|
||||
- 重构日志记录逻辑,可以收集和筛选所有日志类型了(之前无法记录debug的日志类型)
|
||||
|
||||
### Performance
|
||||
@@ -58,10 +90,10 @@
|
||||
- 修复快捷键设置的相关 bug
|
||||
- 修复 Win 下点左键菜单闪现的问题(Mac 下的操作逻辑相反,默认情况下不管点左/右键均会打开菜单,闪现不属于 bug)
|
||||
|
||||
### Know issues
|
||||
### Known issues
|
||||
|
||||
- Windows 下窗口大小无法记忆(等待上游修复)
|
||||
- Webdav 备份因为安全性和兼容性问题,暂不支持同步 Webdav 服务器地址和登录信息;跨平台配置同步
|
||||
- Webdav 备份因为安全性和兼容性问题,暂不支持跨平台配置同步
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "clash-verge",
|
||||
"version": "2.0.0",
|
||||
"version": "2.0.2",
|
||||
"license": "GPL-3.0-only",
|
||||
"scripts": {
|
||||
"dev": "cross-env RUST_BACKTRACE=1 tauri dev",
|
||||
|
||||
@@ -153,13 +153,13 @@ async function getLatestReleaseVersion() {
|
||||
*/
|
||||
if (!META_MAP[`${platform}-${arch}`]) {
|
||||
throw new Error(
|
||||
`clash meta alpha unsupported platform "${platform}-${arch}"`
|
||||
`clash meta alpha unsupported platform "${platform}-${arch}"`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!META_ALPHA_MAP[`${platform}-${arch}`]) {
|
||||
throw new Error(
|
||||
`clash meta alpha unsupported platform "${platform}-${arch}"`
|
||||
`clash meta alpha unsupported platform "${platform}-${arch}"`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -354,7 +354,7 @@ const resolvePlugin = async () => {
|
||||
const tempDir = path.join(TEMP_DIR, "SimpleSC");
|
||||
const tempZip = path.join(
|
||||
tempDir,
|
||||
"NSIS_Simple_Service_Plugin_Unicode_1.30.zip"
|
||||
"NSIS_Simple_Service_Plugin_Unicode_1.30.zip",
|
||||
);
|
||||
const tempDll = path.join(tempDir, "SimpleSC.dll");
|
||||
const pluginDir = path.join(process.env.APPDATA, "Local/NSIS");
|
||||
@@ -488,8 +488,8 @@ const tasks = [
|
||||
{
|
||||
name: "service_chmod",
|
||||
func: resolveServicePermission,
|
||||
retry: 1,
|
||||
unixOnly: true,
|
||||
retry: 5,
|
||||
unixOnly: platform === "linux" || platform === "darwin",
|
||||
},
|
||||
{
|
||||
name: "windows-sysproxy",
|
||||
@@ -514,10 +514,10 @@ const tasks = [
|
||||
async function runTask() {
|
||||
const task = tasks.shift();
|
||||
if (!task) return;
|
||||
if (task.winOnly && platform !== "win32") return runTask();
|
||||
if (task.linuxOnly && platform !== "linux") return runTask();
|
||||
if (task.unixOnly && platform === "win32") return runTask();
|
||||
if (task.winOnly && platform !== "win32") return runTask();
|
||||
if (task.macosOnly && platform !== "darwin") return runTask();
|
||||
if (task.linuxOnly && platform !== "linux") return runTask();
|
||||
|
||||
for (let i = 0; i < task.retry; i++) {
|
||||
try {
|
||||
@@ -532,4 +532,3 @@ async function runTask() {
|
||||
}
|
||||
|
||||
runTask();
|
||||
runTask();
|
||||
|
||||
@@ -49,9 +49,9 @@ async function resolvePortable() {
|
||||
zip.addLocalFolder(
|
||||
path.join(
|
||||
releaseDir,
|
||||
`Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${arch}`
|
||||
`Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${arch}`,
|
||||
),
|
||||
`Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${arch}`
|
||||
`Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${arch}`,
|
||||
);
|
||||
zip.addLocalFolder(configDir, ".config");
|
||||
|
||||
|
||||
332
src-tauri/Cargo.lock
generated
332
src-tauri/Cargo.lock
generated
@@ -85,6 +85,12 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aligned-vec"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1"
|
||||
|
||||
[[package]]
|
||||
name = "alloc-no-stdlib"
|
||||
version = "2.0.4"
|
||||
@@ -160,6 +166,17 @@ version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
|
||||
|
||||
[[package]]
|
||||
name = "arg_enum_proc_macro"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.6"
|
||||
@@ -478,6 +495,29 @@ version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "av1-grain"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arrayvec",
|
||||
"log",
|
||||
"nom 7.1.3",
|
||||
"num-rational",
|
||||
"v_frame",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "avif-serialize"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e335041290c43101ca215eed6f43ec437eb5a42125573f600fc3fa42b9bddd62"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.6.20"
|
||||
@@ -556,6 +596,12 @@ version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bit_field"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@@ -571,6 +617,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitstream-io"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2"
|
||||
|
||||
[[package]]
|
||||
name = "block"
|
||||
version = "0.1.6"
|
||||
@@ -776,6 +828,12 @@ dependencies = [
|
||||
"alloc-stdlib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "built"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.16.0"
|
||||
@@ -988,7 +1046,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clash-verge"
|
||||
version = "2.0.0"
|
||||
version = "2.0.2"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"anyhow",
|
||||
@@ -1000,6 +1058,7 @@ dependencies = [
|
||||
"dirs 5.0.1",
|
||||
"dunce",
|
||||
"getrandom 0.2.15",
|
||||
"image",
|
||||
"log",
|
||||
"log4rs",
|
||||
"nanoid",
|
||||
@@ -1109,6 +1168,12 @@ dependencies = [
|
||||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color_quant"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "2.1.0"
|
||||
@@ -1934,6 +1999,21 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "exr"
|
||||
version = "1.73.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0"
|
||||
dependencies = [
|
||||
"bit_field",
|
||||
"half",
|
||||
"lebe",
|
||||
"miniz_oxide",
|
||||
"rayon-core",
|
||||
"smallvec",
|
||||
"zune-inflate",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fast-float"
|
||||
version = "0.2.0"
|
||||
@@ -2353,6 +2433,16 @@ dependencies = [
|
||||
"polyval",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gif"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2"
|
||||
dependencies = [
|
||||
"color_quant",
|
||||
"weezl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.31.1"
|
||||
@@ -2562,6 +2652,16 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "handlebars"
|
||||
version = "3.5.5"
|
||||
@@ -3068,11 +3168,37 @@ checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder-lite",
|
||||
"color_quant",
|
||||
"exr",
|
||||
"gif",
|
||||
"image-webp",
|
||||
"num-traits",
|
||||
"png",
|
||||
"qoi",
|
||||
"ravif",
|
||||
"rayon",
|
||||
"rgb",
|
||||
"tiff",
|
||||
"zune-core",
|
||||
"zune-jpeg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image-webp"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e031e8e3d94711a9ccb5d6ea357439ef3dcbed361798bd4071dc4d9793fbe22f"
|
||||
dependencies = [
|
||||
"byteorder-lite",
|
||||
"quick-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imgref"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
@@ -3138,6 +3264,17 @@ dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "interpolate_name"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "intrusive-collections"
|
||||
version = "0.9.7"
|
||||
@@ -3345,6 +3482,12 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "lebe"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
|
||||
|
||||
[[package]]
|
||||
name = "libappindicator"
|
||||
version = "0.9.0"
|
||||
@@ -3375,6 +3518,16 @@ version = "0.2.164"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b9569d2f74e257076d8c6bfa73fb505b46b851e51ddaecc825944aa3bed17fa"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.7.4"
|
||||
@@ -3495,6 +3648,15 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "loop9"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062"
|
||||
dependencies = [
|
||||
"imgref",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lru"
|
||||
version = "0.7.8"
|
||||
@@ -3577,6 +3739,16 @@ version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
|
||||
|
||||
[[package]]
|
||||
name = "maybe-rayon"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"rayon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "md-5"
|
||||
version = "0.10.6"
|
||||
@@ -3857,6 +4029,12 @@ dependencies = [
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "noop_proc_macro"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
|
||||
|
||||
[[package]]
|
||||
name = "notify-rust"
|
||||
version = "4.11.3"
|
||||
@@ -3917,6 +4095,17 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-derive"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.46"
|
||||
@@ -3926,6 +4115,17 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
@@ -4855,6 +5055,25 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "profiling"
|
||||
version = "1.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d"
|
||||
dependencies = [
|
||||
"profiling-procmacros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "profiling-procmacros"
|
||||
version = "1.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost"
|
||||
version = "0.12.6"
|
||||
@@ -4887,6 +5106,15 @@ dependencies = [
|
||||
"prost",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "qoi"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "2.0.1"
|
||||
@@ -5062,6 +5290,56 @@ dependencies = [
|
||||
"rand_core 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rav1e"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"arg_enum_proc_macro",
|
||||
"arrayvec",
|
||||
"av1-grain",
|
||||
"bitstream-io",
|
||||
"built",
|
||||
"cfg-if",
|
||||
"interpolate_name",
|
||||
"itertools 0.12.1",
|
||||
"libc",
|
||||
"libfuzzer-sys",
|
||||
"log",
|
||||
"maybe-rayon",
|
||||
"new_debug_unreachable",
|
||||
"noop_proc_macro",
|
||||
"num-derive 0.4.2",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"paste",
|
||||
"profiling",
|
||||
"rand 0.8.5",
|
||||
"rand_chacha 0.3.1",
|
||||
"simd_helpers",
|
||||
"system-deps",
|
||||
"thiserror 1.0.69",
|
||||
"v_frame",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ravif"
|
||||
version = "0.11.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2413fd96bd0ea5cdeeb37eaf446a22e6ed7b981d792828721e74ded1980a45c6"
|
||||
dependencies = [
|
||||
"avif-serialize",
|
||||
"imgref",
|
||||
"loop9",
|
||||
"quick-error",
|
||||
"rav1e",
|
||||
"rayon",
|
||||
"rgb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raw-window-handle"
|
||||
version = "0.5.2"
|
||||
@@ -5261,6 +5539,12 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rgb"
|
||||
version = "0.8.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a"
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.8"
|
||||
@@ -5853,6 +6137,15 @@ version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||
|
||||
[[package]]
|
||||
name = "simd_helpers"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6"
|
||||
dependencies = [
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.11"
|
||||
@@ -6740,7 +7033,7 @@ dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"memmem",
|
||||
"num-derive",
|
||||
"num-derive 0.3.3",
|
||||
"num-traits",
|
||||
"ordered-float",
|
||||
"regex",
|
||||
@@ -7494,6 +7787,17 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "v_frame"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b"
|
||||
dependencies = [
|
||||
"aligned-vec",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.0"
|
||||
@@ -8823,6 +9127,30 @@ dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zune-core"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
|
||||
|
||||
[[package]]
|
||||
name = "zune-inflate"
|
||||
version = "0.2.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
|
||||
dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zune-jpeg"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16099418600b4d8f028622f73ff6e3deaabdff330fb9a2a131dea781ee8b0768"
|
||||
dependencies = [
|
||||
"zune-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant"
|
||||
version = "4.2.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "clash-verge"
|
||||
version = "2.0.0"
|
||||
version = "2.0.2"
|
||||
description = "clash verge"
|
||||
authors = ["zzzgydi", "wonfen", "MystiPanda"]
|
||||
license = "GPL-3.0-only"
|
||||
@@ -36,6 +36,7 @@ tokio = { version = "1", features = ["full"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
reqwest = { version = "0.12", features = ["json", "rustls-tls"] }
|
||||
sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs", branch = "main" }
|
||||
image = "0.25.5"
|
||||
tauri = { version = "2.1.1", features = [
|
||||
"protocol-asset",
|
||||
"devtools",
|
||||
|
||||
@@ -16,6 +16,12 @@
|
||||
"identifier": "fs:scope",
|
||||
"allow": ["$APPDATA/**", "$RESOURCE/../**", "**"]
|
||||
},
|
||||
"fs:allow-app-read",
|
||||
"fs:allow-app-read-recursive",
|
||||
"fs:allow-appcache-read",
|
||||
"fs:allow-appcache-read-recursive",
|
||||
"fs:allow-appconfig-read",
|
||||
"fs:allow-appconfig-read-recursive",
|
||||
"core:window:allow-create",
|
||||
"core:window:allow-center",
|
||||
"core:window:allow-request-user-attention",
|
||||
|
||||
@@ -697,25 +697,62 @@ Var VC_REDIST_URL
|
||||
Var VC_REDIST_EXE
|
||||
|
||||
Section CheckAndInstallVSRuntime
|
||||
; 检查是否已安装 Visual C++ Redistributable
|
||||
${If} ${IsNativeARM64}
|
||||
StrCpy $VC_REDIST_URL "https://aka.ms/vs/17/release/vc_redist.arm64.exe"
|
||||
StrCpy $VC_REDIST_EXE "vc_redist.arm64.exe"
|
||||
IfFileExists "$SYSDIR\msvcp140.dll" Done
|
||||
|
||||
; 检查关键DLL
|
||||
IfFileExists "$SYSDIR\vcruntime140.dll" 0 checkInstall
|
||||
IfFileExists "$SYSDIR\msvcp140.dll" Done checkInstall
|
||||
|
||||
${ElseIf} ${RunningX64}
|
||||
StrCpy $VC_REDIST_URL "https://aka.ms/vs/17/release/vc_redist.x64.exe"
|
||||
StrCpy $VC_REDIST_EXE "vc_redist.x64.exe"
|
||||
IfFileExists "$WINDIR\SysWOW64\msvcp140.dll" Done
|
||||
|
||||
; 检查关键DLL
|
||||
IfFileExists "$SYSDIR\vcruntime140.dll" 0 checkInstall
|
||||
IfFileExists "$SYSDIR\msvcp140.dll" Done checkInstall
|
||||
|
||||
${Else}
|
||||
StrCpy $VC_REDIST_URL "https://aka.ms/vs/17/release/vc_redist.x86.exe"
|
||||
StrCpy $VC_REDIST_EXE "vc_redist.x86.exe"
|
||||
IfFileExists "$SYSDIR\msvcp140.dll" Done
|
||||
|
||||
; 检查关键DLL
|
||||
IfFileExists "$SYSDIR\vcruntime140.dll" 0 checkInstall
|
||||
IfFileExists "$SYSDIR\msvcp140.dll" Done checkInstall
|
||||
${EndIf}
|
||||
|
||||
; 下载并安装VC运行库
|
||||
checkInstall:
|
||||
; 检查注册表
|
||||
${If} ${RunningX64}
|
||||
SetRegView 64
|
||||
ReadRegDword $R0 HKLM "SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\${ARCH}" "Installed"
|
||||
${If} $R0 == "1"
|
||||
Goto Done
|
||||
${EndIf}
|
||||
${Else}
|
||||
ReadRegDword $R0 HKLM "SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x86" "Installed"
|
||||
${If} $R0 == "1"
|
||||
Goto Done
|
||||
${EndIf}
|
||||
${EndIf}
|
||||
|
||||
; 如果没有安装,则下载并安装
|
||||
DetailPrint "正在下载 Visual C++ Redistributable..."
|
||||
nsisdl::download "$VC_REDIST_URL" "$TEMP\$VC_REDIST_EXE"
|
||||
Pop $0
|
||||
${If} $0 == "success"
|
||||
nsExec::Exec '"$TEMP\$VC_REDIST_EXE" /quiet /norestart'
|
||||
DetailPrint "正在安装 Visual C++ Redistributable..."
|
||||
ExecWait '"$TEMP\$VC_REDIST_EXE" /quiet /norestart' $0
|
||||
${If} $0 == 0
|
||||
DetailPrint "Visual C++ Redistributable 安装成功"
|
||||
${Else}
|
||||
DetailPrint "Visual C++ Redistributable 安装失败"
|
||||
${EndIf}
|
||||
Delete "$TEMP\$VC_REDIST_EXE"
|
||||
${Else}
|
||||
DetailPrint "Visual C++ Redistributable 下载失败"
|
||||
${EndIf}
|
||||
|
||||
Done:
|
||||
@@ -728,6 +765,40 @@ Section Install
|
||||
nsExec::Exec 'netsh int tcp res'
|
||||
!insertmacro CheckIfAppIsRunning
|
||||
!insertmacro CheckAllVergeProcesses
|
||||
|
||||
; 清理自启动注册表项
|
||||
DetailPrint "Cleaning auto-launch registry entries..."
|
||||
|
||||
StrCpy $R1 "Software\Microsoft\Windows\CurrentVersion\Run"
|
||||
|
||||
SetRegView 64
|
||||
; 清理旧版本的注册表项 (Clash Verge)
|
||||
ReadRegStr $R2 HKCU "$R1" "Clash Verge"
|
||||
${If} $R2 != ""
|
||||
DeleteRegValue HKCU "$R1" "Clash Verge"
|
||||
${EndIf}
|
||||
|
||||
ReadRegStr $R2 HKLM "$R1" "Clash Verge"
|
||||
${If} $R2 != ""
|
||||
DeleteRegValue HKLM "$R1" "Clash Verge"
|
||||
${EndIf}
|
||||
|
||||
; 清理新版本的注册表项 (clash-verge)
|
||||
ReadRegStr $R2 HKCU "$R1" "clash-verge"
|
||||
${If} $R2 != ""
|
||||
DeleteRegValue HKCU "$R1" "clash-verge"
|
||||
${EndIf}
|
||||
|
||||
ReadRegStr $R2 HKLM "$R1" "clash-verge"
|
||||
${If} $R2 != ""
|
||||
DeleteRegValue HKLM "$R1" "clash-verge"
|
||||
${EndIf}
|
||||
|
||||
; Delete old files before installation
|
||||
; Delete clash-verge.desktop
|
||||
IfFileExists "$INSTDIR\Clash Verge.exe" 0 +2
|
||||
Delete "$INSTDIR\Clash Verge.exe"
|
||||
|
||||
; Copy main executable
|
||||
File "${MAINBINARYSRCPATH}"
|
||||
|
||||
@@ -848,6 +919,35 @@ Section Uninstall
|
||||
!insertmacro CheckIfAppIsRunning
|
||||
!insertmacro CheckAllVergeProcesses
|
||||
!insertmacro RemoveVergeService
|
||||
|
||||
; 清理自启动注册表项
|
||||
DetailPrint "Cleaning auto-launch registry entries..."
|
||||
|
||||
StrCpy $R1 "Software\Microsoft\Windows\CurrentVersion\Run"
|
||||
|
||||
SetRegView 64
|
||||
; 清理旧版本的注册表项 (Clash Verge)
|
||||
ReadRegStr $R2 HKCU "$R1" "Clash Verge"
|
||||
${If} $R2 != ""
|
||||
DeleteRegValue HKCU "$R1" "Clash Verge"
|
||||
${EndIf}
|
||||
|
||||
ReadRegStr $R2 HKLM "$R1" "Clash Verge"
|
||||
${If} $R2 != ""
|
||||
DeleteRegValue HKLM "$R1" "Clash Verge"
|
||||
${EndIf}
|
||||
|
||||
; 清理新版本的注册表项 (clash-verge)
|
||||
ReadRegStr $R2 HKCU "$R1" "clash-verge"
|
||||
${If} $R2 != ""
|
||||
DeleteRegValue HKCU "$R1" "clash-verge"
|
||||
${EndIf}
|
||||
|
||||
ReadRegStr $R2 HKLM "$R1" "clash-verge"
|
||||
${If} $R2 != ""
|
||||
DeleteRegValue HKLM "$R1" "clash-verge"
|
||||
${EndIf}
|
||||
|
||||
; Delete the app directory and its content from disk
|
||||
; Copy main executable
|
||||
Delete "$INSTDIR\${MAINBINARYNAME}.exe"
|
||||
@@ -862,6 +962,10 @@ Section Uninstall
|
||||
Delete "$INSTDIR\\{{this}}"
|
||||
{{/each}}
|
||||
|
||||
; Delete clash-verge.desktop
|
||||
IfFileExists "$INSTDIR\Clash Verge.exe" 0 +2
|
||||
Delete "$INSTDIR\Clash Verge.exe"
|
||||
|
||||
; Delete uninstaller
|
||||
Delete "$INSTDIR\uninstall.exe"
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ impl IClashTemp {
|
||||
let mut map = Mapping::new();
|
||||
let mut tun = Mapping::new();
|
||||
tun.insert("enable".into(), false.into());
|
||||
tun.insert("stack".into(), "gvisor".into());
|
||||
tun.insert("stack".into(), "mixed".into());
|
||||
tun.insert("auto-route".into(), true.into());
|
||||
tun.insert("strict-route".into(), false.into());
|
||||
tun.insert("auto-detect-interface".into(), true.into());
|
||||
|
||||
@@ -4,114 +4,184 @@ use anyhow::Error;
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use reqwest_dav::list_cmd::{ListEntity, ListFile};
|
||||
use std::collections::HashMap;
|
||||
use std::env::{consts::OS, temp_dir};
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::time::timeout;
|
||||
use zip::write::SimpleFileOptions;
|
||||
|
||||
const TIMEOUT_UPLOAD: u64 = 300; // 上传超时 5 分钟
|
||||
const TIMEOUT_DOWNLOAD: u64 = 300; // 下载超时 5 分钟
|
||||
const TIMEOUT_LIST: u64 = 3; // 列表超时 30 秒
|
||||
const TIMEOUT_DELETE: u64 = 3; // 删除超时 30 秒
|
||||
|
||||
#[derive(Clone)]
|
||||
struct WebDavConfig {
|
||||
url: String,
|
||||
username: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
|
||||
enum Operation {
|
||||
Upload,
|
||||
Download,
|
||||
List,
|
||||
Delete,
|
||||
}
|
||||
|
||||
impl Operation {
|
||||
fn timeout(&self) -> u64 {
|
||||
match self {
|
||||
Operation::Upload => TIMEOUT_UPLOAD,
|
||||
Operation::Download => TIMEOUT_DOWNLOAD,
|
||||
Operation::List => TIMEOUT_LIST,
|
||||
Operation::Delete => TIMEOUT_DELETE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WebDavClient {
|
||||
client: Arc<Mutex<Option<reqwest_dav::Client>>>,
|
||||
config: Arc<Mutex<Option<WebDavConfig>>>,
|
||||
clients: Arc<Mutex<HashMap<Operation, reqwest_dav::Client>>>,
|
||||
}
|
||||
|
||||
impl WebDavClient {
|
||||
pub fn global() -> &'static WebDavClient {
|
||||
static WEBDAV_CLIENT: OnceCell<WebDavClient> = OnceCell::new();
|
||||
WEBDAV_CLIENT.get_or_init(|| WebDavClient {
|
||||
client: Arc::new(Mutex::new(None)),
|
||||
config: Arc::new(Mutex::new(None)),
|
||||
clients: Arc::new(Mutex::new(HashMap::new())),
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_client(&self) -> Result<reqwest_dav::Client, Error> {
|
||||
if self.client.lock().is_none() {
|
||||
let verge = Config::verge().latest().clone();
|
||||
if verge.webdav_url.is_none()
|
||||
|| verge.webdav_username.is_none()
|
||||
|| verge.webdav_password.is_none()
|
||||
{
|
||||
let msg =
|
||||
"Unable to create web dav client, please make sure the webdav config is correct"
|
||||
.to_string();
|
||||
log::error!(target: "app","{}",msg);
|
||||
return Err(anyhow::Error::msg(msg));
|
||||
async fn get_client(&self, op: Operation) -> Result<reqwest_dav::Client, Error> {
|
||||
// 先尝试从缓存获取
|
||||
{
|
||||
let clients = self.clients.lock();
|
||||
if let Some(client) = clients.get(&op) {
|
||||
return Ok(client.clone());
|
||||
}
|
||||
|
||||
let url = verge.webdav_url.unwrap_or_default();
|
||||
let username = verge.webdav_username.unwrap_or_default();
|
||||
let password = verge.webdav_password.unwrap_or_default();
|
||||
let url = url.trim_end_matches('/');
|
||||
let client = reqwest_dav::ClientBuilder::new()
|
||||
.set_agent(
|
||||
reqwest::Client::builder()
|
||||
.danger_accept_invalid_certs(true)
|
||||
.timeout(std::time::Duration::from_secs(3))
|
||||
.build()
|
||||
.unwrap(),
|
||||
)
|
||||
.set_host(url.to_owned())
|
||||
.set_auth(reqwest_dav::Auth::Basic(
|
||||
username.to_owned(),
|
||||
password.to_owned(),
|
||||
))
|
||||
.build()?;
|
||||
|
||||
if (client
|
||||
.list(dirs::BACKUP_DIR, reqwest_dav::Depth::Number(0))
|
||||
.await)
|
||||
.is_err()
|
||||
{
|
||||
client.mkcol(dirs::BACKUP_DIR).await?;
|
||||
}
|
||||
|
||||
*self.client.lock() = Some(client.clone());
|
||||
}
|
||||
Ok(self.client.lock().clone().unwrap())
|
||||
|
||||
// 获取或创建配置
|
||||
let config = {
|
||||
let mut lock = self.config.lock();
|
||||
if let Some(cfg) = lock.as_ref() {
|
||||
cfg.clone()
|
||||
} else {
|
||||
let verge = Config::verge().latest().clone();
|
||||
if verge.webdav_url.is_none()
|
||||
|| verge.webdav_username.is_none()
|
||||
|| verge.webdav_password.is_none()
|
||||
{
|
||||
let msg = "Unable to create web dav client, please make sure the webdav config is correct".to_string();
|
||||
return Err(anyhow::Error::msg(msg));
|
||||
}
|
||||
|
||||
let config = WebDavConfig {
|
||||
url: verge
|
||||
.webdav_url
|
||||
.unwrap_or_default()
|
||||
.trim_end_matches('/')
|
||||
.to_string(),
|
||||
username: verge.webdav_username.unwrap_or_default(),
|
||||
password: verge.webdav_password.unwrap_or_default(),
|
||||
};
|
||||
|
||||
*lock = Some(config.clone());
|
||||
config
|
||||
}
|
||||
};
|
||||
|
||||
// 创建新的客户端
|
||||
let client = reqwest_dav::ClientBuilder::new()
|
||||
.set_agent(
|
||||
reqwest::Client::builder()
|
||||
.danger_accept_invalid_certs(true)
|
||||
.timeout(Duration::from_secs(op.timeout()))
|
||||
.build()
|
||||
.unwrap(),
|
||||
)
|
||||
.set_host(config.url)
|
||||
.set_auth(reqwest_dav::Auth::Basic(config.username, config.password))
|
||||
.build()?;
|
||||
|
||||
// 确保备份目录存在
|
||||
let list_result = client
|
||||
.list(dirs::BACKUP_DIR, reqwest_dav::Depth::Number(0))
|
||||
.await;
|
||||
if list_result.is_err() {
|
||||
client.mkcol(dirs::BACKUP_DIR).await?;
|
||||
}
|
||||
|
||||
// 缓存客户端
|
||||
{
|
||||
let mut clients = self.clients.lock();
|
||||
clients.insert(op, client.clone());
|
||||
}
|
||||
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
pub fn reset(&self) {
|
||||
if !self.client.lock().is_none() {
|
||||
self.client.lock().take();
|
||||
}
|
||||
*self.config.lock() = None;
|
||||
self.clients.lock().clear();
|
||||
}
|
||||
|
||||
pub async fn upload(&self, file_path: PathBuf, file_name: String) -> Result<(), Error> {
|
||||
let client = self.get_client().await?;
|
||||
let client = self.get_client(Operation::Upload).await?;
|
||||
let webdav_path: String = format!("{}/{}", dirs::BACKUP_DIR, file_name);
|
||||
client
|
||||
.put(webdav_path.as_ref(), fs::read(file_path)?)
|
||||
.await?;
|
||||
let fut = client.put(webdav_path.as_ref(), fs::read(file_path)?);
|
||||
timeout(Duration::from_secs(TIMEOUT_UPLOAD), fut).await??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn download(&self, filename: String, storage_path: PathBuf) -> Result<(), Error> {
|
||||
let client = self.get_client().await?;
|
||||
let client = self.get_client(Operation::Download).await?;
|
||||
let path = format!("{}/{}", dirs::BACKUP_DIR, filename);
|
||||
let response = client.get(path.as_str()).await?;
|
||||
let content = response.bytes().await?;
|
||||
fs::write(&storage_path, &content)?;
|
||||
|
||||
let fut = async {
|
||||
let response = client.get(path.as_str()).await?;
|
||||
let content = response.bytes().await?;
|
||||
fs::write(&storage_path, &content)?;
|
||||
Ok::<(), Error>(())
|
||||
};
|
||||
|
||||
timeout(Duration::from_secs(TIMEOUT_DOWNLOAD), fut).await??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn list(&self) -> Result<Vec<ListFile>, Error> {
|
||||
let client = self.get_client().await?;
|
||||
let client = self.get_client(Operation::List).await?;
|
||||
let path = format!("{}/", dirs::BACKUP_DIR);
|
||||
let files = client
|
||||
.list(path.as_str(), reqwest_dav::Depth::Number(1))
|
||||
.await?;
|
||||
let mut final_files = Vec::new();
|
||||
for file in files {
|
||||
if let ListEntity::File(file) = file {
|
||||
final_files.push(file);
|
||||
|
||||
let fut = async {
|
||||
let files = client
|
||||
.list(path.as_str(), reqwest_dav::Depth::Number(1))
|
||||
.await?;
|
||||
let mut final_files = Vec::new();
|
||||
for file in files {
|
||||
if let ListEntity::File(file) = file {
|
||||
final_files.push(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(final_files)
|
||||
Ok::<Vec<ListFile>, Error>(final_files)
|
||||
};
|
||||
|
||||
Ok(timeout(Duration::from_secs(TIMEOUT_LIST), fut).await??)
|
||||
}
|
||||
|
||||
pub async fn delete(&self, file_name: String) -> Result<(), Error> {
|
||||
let client = self.get_client().await?;
|
||||
let client = self.get_client(Operation::Delete).await?;
|
||||
let path = format!("{}/{}", dirs::BACKUP_DIR, file_name);
|
||||
client.delete(&path).await?;
|
||||
|
||||
let fut = client.delete(&path);
|
||||
timeout(Duration::from_secs(TIMEOUT_DELETE), fut).await??;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,16 +96,11 @@ impl Tray {
|
||||
*tun_mode,
|
||||
)?));
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let mut use_custom_icon = false;
|
||||
#[allow(unused)]
|
||||
let mut indication_icon = if *system_proxy && !*tun_mode {
|
||||
#[cfg(target_os = "macos")]
|
||||
let mut icon = match tray_icon.as_str() {
|
||||
"colorful" => {
|
||||
use_custom_icon = true;
|
||||
include_bytes!("../../icons/tray-icon-sys.ico").to_vec()
|
||||
}
|
||||
"colorful" => include_bytes!("../../icons/tray-icon-sys.ico").to_vec(),
|
||||
_ => include_bytes!("../../icons/tray-icon-sys-mono.ico").to_vec(),
|
||||
};
|
||||
|
||||
@@ -120,19 +115,12 @@ impl Tray {
|
||||
} else if png_path.exists() {
|
||||
icon = std::fs::read(png_path).unwrap();
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use_custom_icon = true;
|
||||
}
|
||||
}
|
||||
icon
|
||||
} else if *tun_mode {
|
||||
#[cfg(target_os = "macos")]
|
||||
let mut icon = match tray_icon.as_str() {
|
||||
"colorful" => {
|
||||
use_custom_icon = true;
|
||||
include_bytes!("../../icons/tray-icon-tun.ico").to_vec()
|
||||
}
|
||||
"colorful" => include_bytes!("../../icons/tray-icon-tun.ico").to_vec(),
|
||||
_ => include_bytes!("../../icons/tray-icon-tun-mono.ico").to_vec(),
|
||||
};
|
||||
|
||||
@@ -147,19 +135,12 @@ impl Tray {
|
||||
} else if png_path.exists() {
|
||||
icon = std::fs::read(png_path).unwrap();
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use_custom_icon = true;
|
||||
}
|
||||
}
|
||||
icon
|
||||
} else {
|
||||
#[cfg(target_os = "macos")]
|
||||
let mut icon = match tray_icon.as_str() {
|
||||
"colorful" => {
|
||||
use_custom_icon = true;
|
||||
include_bytes!("../../icons/tray-icon.ico").to_vec()
|
||||
}
|
||||
"colorful" => include_bytes!("../../icons/tray-icon.ico").to_vec(),
|
||||
_ => include_bytes!("../../icons/tray-icon-mono.ico").to_vec(),
|
||||
};
|
||||
|
||||
@@ -174,22 +155,16 @@ impl Tray {
|
||||
} else if png_path.exists() {
|
||||
icon = std::fs::read(png_path).unwrap();
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use_custom_icon = true;
|
||||
}
|
||||
}
|
||||
icon
|
||||
};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if use_custom_icon {
|
||||
let _ = tray.set_icon_as_template(false);
|
||||
let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&indication_icon)?));
|
||||
} else {
|
||||
let _ = tray.set_icon_as_template(true);
|
||||
}
|
||||
let is_template = crate::utils::help::is_monochrome_image_from_bytes(&indication_icon)
|
||||
.unwrap_or(false);
|
||||
let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&indication_icon)?));
|
||||
let _ = tray.set_icon_as_template(is_template);
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
|
||||
@@ -60,6 +60,7 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
||||
global_merge,
|
||||
global_script,
|
||||
profile_name,
|
||||
dns,
|
||||
) = {
|
||||
let profiles = Config::profiles();
|
||||
let profiles = profiles.latest();
|
||||
@@ -130,6 +131,10 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
||||
.and_then(|item| item.name.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
let dns = current.get("dns").map_or(Mapping::new(), |val| {
|
||||
val.as_mapping().cloned().unwrap_or(Mapping::new())
|
||||
});
|
||||
|
||||
(
|
||||
current,
|
||||
merge,
|
||||
@@ -140,6 +145,7 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
||||
global_merge,
|
||||
global_script,
|
||||
name,
|
||||
dns,
|
||||
)
|
||||
};
|
||||
|
||||
@@ -259,7 +265,7 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
||||
});
|
||||
}
|
||||
|
||||
config = use_tun(config, enable_tun).await;
|
||||
config = use_tun(config, enable_tun, dns).await;
|
||||
config = use_sort(config);
|
||||
|
||||
let mut exists_set = HashSet::new();
|
||||
|
||||
@@ -18,7 +18,7 @@ macro_rules! append {
|
||||
};
|
||||
}
|
||||
|
||||
pub async fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
|
||||
pub async fn use_tun(mut config: Mapping, enable: bool, origin_dns_val: Mapping) -> Mapping {
|
||||
let tun_key = Value::from("tun");
|
||||
let tun_val = config.get(&tun_key);
|
||||
let mut tun_val = tun_val.map_or(Mapping::new(), |val| {
|
||||
@@ -29,19 +29,52 @@ pub async fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
|
||||
let mut dns_val = dns_val.map_or(Mapping::new(), |val| {
|
||||
val.as_mapping().cloned().unwrap_or(Mapping::new())
|
||||
});
|
||||
let ipv6_key = Value::from("ipv6");
|
||||
let ipv6_val = config
|
||||
.get(&ipv6_key)
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false);
|
||||
|
||||
if enable {
|
||||
revise!(dns_val, "enable", true);
|
||||
revise!(dns_val, "ipv6", true);
|
||||
revise!(dns_val, "ipv6", ipv6_val);
|
||||
revise!(dns_val, "enhanced-mode", "fake-ip");
|
||||
revise!(dns_val, "fake-ip-range", "10.96.0.0/16");
|
||||
revise!(dns_val, "fake-ip-range", "198.18.0.1/16");
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
crate::utils::resolve::restore_public_dns().await;
|
||||
crate::utils::resolve::set_public_dns("10.96.0.2".to_string()).await;
|
||||
crate::utils::resolve::set_public_dns("223.6.6.6".to_string()).await;
|
||||
}
|
||||
} else {
|
||||
revise!(dns_val, "enhanced-mode", "redir-host");
|
||||
revise!(
|
||||
dns_val,
|
||||
"enable",
|
||||
origin_dns_val
|
||||
.get("enable")
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(true)
|
||||
);
|
||||
|
||||
revise!(dns_val, "ipv6", ipv6_val);
|
||||
|
||||
revise!(
|
||||
dns_val,
|
||||
"enhanced-mode",
|
||||
origin_dns_val
|
||||
.get("enhanced-mode")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("redir-host")
|
||||
);
|
||||
|
||||
revise!(
|
||||
dns_val,
|
||||
"fake-ip-range",
|
||||
origin_dns_val
|
||||
.get("fake-ip-range")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("198.18.0.1/16")
|
||||
);
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
crate::utils::resolve::restore_public_dns().await;
|
||||
}
|
||||
|
||||
@@ -20,12 +20,16 @@ use tauri_plugin_window_state::{AppHandleExt, StateFlags};
|
||||
// 打开面板
|
||||
pub fn open_or_close_dashboard() {
|
||||
if let Some(window) = handle::Handle::global().get_window() {
|
||||
if let Ok(true) = window.is_focused() {
|
||||
// 如果窗口存在,则切换其显示状态
|
||||
if window.is_visible().unwrap_or(false) {
|
||||
let _ = window.hide();
|
||||
return;
|
||||
} else {
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
} else {
|
||||
resolve::create_window();
|
||||
}
|
||||
resolve::create_window();
|
||||
}
|
||||
|
||||
// 重启clash
|
||||
@@ -121,14 +125,6 @@ pub fn quit(code: Option<i32>) {
|
||||
handle::Handle::global().set_is_exiting();
|
||||
resolve::resolve_reset();
|
||||
log_err!(handle::Handle::global().get_window().unwrap().close());
|
||||
match app_handle.save_window_state(StateFlags::all()) {
|
||||
Ok(_) => {
|
||||
log::info!(target: "app", "window state saved successfully");
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!(target: "app", "failed to save window state: {}", e);
|
||||
}
|
||||
};
|
||||
app_handle.exit(code.unwrap_or(0));
|
||||
}
|
||||
|
||||
@@ -141,16 +137,14 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> {
|
||||
if patch.get("secret").is_some() || patch.get("external-controller").is_some() {
|
||||
Config::generate().await?;
|
||||
CoreManager::global().restart_core().await?;
|
||||
handle::Handle::refresh_clash();
|
||||
} else {
|
||||
if patch.get("mode").is_some() {
|
||||
log_err!(handle::Handle::update_systray_part());
|
||||
}
|
||||
|
||||
Config::runtime().latest().patch_config(patch);
|
||||
update_core_config(false).await?;
|
||||
CoreManager::global().update_config().await?;
|
||||
}
|
||||
|
||||
handle::Handle::refresh_clash();
|
||||
<Result<()>>::Ok(())
|
||||
};
|
||||
match res {
|
||||
@@ -250,11 +244,11 @@ pub async fn patch_verge(patch: IVerge) -> Result<()> {
|
||||
should_update_systray_part = true;
|
||||
}
|
||||
if should_restart_core {
|
||||
Config::generate().await?;
|
||||
CoreManager::global().restart_core().await?;
|
||||
}
|
||||
if should_update_clash_config {
|
||||
update_core_config(false).await?;
|
||||
CoreManager::global().update_config().await?;
|
||||
handle::Handle::refresh_clash();
|
||||
}
|
||||
if should_update_launch {
|
||||
sysopt::Sysopt::global().update_launch()?;
|
||||
@@ -320,31 +314,20 @@ pub async fn update_profile(uid: String, option: Option<PrfOption>) -> Result<()
|
||||
};
|
||||
|
||||
if should_update {
|
||||
update_core_config(true).await?;
|
||||
match CoreManager::global().update_config().await {
|
||||
Ok(_) => {
|
||||
handle::Handle::refresh_clash();
|
||||
}
|
||||
Err(err) => {
|
||||
handle::Handle::notice_message("set_config::error", format!("{err}"));
|
||||
log::error!(target: "app", "{err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 更新订阅
|
||||
async fn update_core_config(notice: bool) -> Result<()> {
|
||||
match CoreManager::global().update_config().await {
|
||||
Ok(_) => {
|
||||
handle::Handle::refresh_clash();
|
||||
if notice {
|
||||
handle::Handle::notice_message("set_config::ok", "ok");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
if notice {
|
||||
handle::Handle::notice_message("set_config::error", format!("{err}"));
|
||||
}
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// copy env variable
|
||||
pub fn copy_clash_env() {
|
||||
let app_handle = handle::Handle::global().app_handle().unwrap();
|
||||
@@ -464,6 +447,12 @@ pub async fn delete_webdav_backup(filename: String) -> Result<()> {
|
||||
}
|
||||
|
||||
pub async fn restore_webdav_backup(filename: String) -> Result<()> {
|
||||
let verge = Config::verge();
|
||||
let verge_data = verge.data().clone();
|
||||
let webdav_url = verge_data.webdav_url.clone();
|
||||
let webdav_username = verge_data.webdav_username.clone();
|
||||
let webdav_password = verge_data.webdav_password.clone();
|
||||
|
||||
let backup_storage_path = app_home_dir().unwrap().join(&filename);
|
||||
backup::WebDavClient::global()
|
||||
.download(filename, backup_storage_path.clone())
|
||||
@@ -477,6 +466,15 @@ pub async fn restore_webdav_backup(filename: String) -> Result<()> {
|
||||
let mut zip = zip::ZipArchive::new(fs::File::open(backup_storage_path.clone())?)?;
|
||||
zip.extract(app_home_dir()?)?;
|
||||
|
||||
log_err!(
|
||||
patch_verge(IVerge {
|
||||
webdav_url: webdav_url,
|
||||
webdav_username: webdav_username,
|
||||
webdav_password: webdav_password,
|
||||
..IVerge::default()
|
||||
})
|
||||
.await
|
||||
);
|
||||
// 最后删除临时文件
|
||||
fs::remove_file(backup_storage_path)?;
|
||||
Ok(())
|
||||
|
||||
@@ -4,7 +4,6 @@ use nanoid::nanoid;
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use serde_yaml::{Mapping, Value};
|
||||
use std::{fs, path::PathBuf, str::FromStr};
|
||||
use tauri_plugin_shell::ShellExt;
|
||||
|
||||
/// read data from yaml as struct T
|
||||
pub fn read_yaml<T: DeserializeOwned>(path: &PathBuf) -> Result<T> {
|
||||
@@ -99,12 +98,24 @@ pub fn get_last_part_and_decode(url: &str) -> Option<String> {
|
||||
}
|
||||
|
||||
/// open file
|
||||
/// use vscode by default
|
||||
pub fn open_file(app: tauri::AppHandle, path: PathBuf) -> Result<()> {
|
||||
app.shell().open(path.to_string_lossy(), None).unwrap();
|
||||
pub fn open_file(_: tauri::AppHandle, path: PathBuf) -> Result<()> {
|
||||
open::that_detached(&path.as_os_str())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn is_monochrome_image_from_bytes(data: &[u8]) -> anyhow::Result<bool> {
|
||||
let img = image::load_from_memory(data)?;
|
||||
let rgb_img = img.to_rgb8();
|
||||
|
||||
for pixel in rgb_img.pixels() {
|
||||
if pixel[0] != pixel[1] || pixel[1] != pixel[2] {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn linux_elevator() -> String {
|
||||
use std::process::Command;
|
||||
|
||||
@@ -193,12 +193,7 @@ pub fn init_resources() -> Result<()> {
|
||||
let _ = fs::create_dir_all(&res_dir);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let file_list = ["Country.mmdb", "geoip.dat", "geosite.dat"];
|
||||
#[cfg(target_os = "macos")]
|
||||
let file_list = ["Country.mmdb", "geoip.dat", "geosite.dat"];
|
||||
#[cfg(target_os = "linux")]
|
||||
let file_list: [&str; 0] = [];
|
||||
|
||||
// copy the resource file
|
||||
// if the source file is newer than the destination file, copy it over
|
||||
|
||||
@@ -8,7 +8,6 @@ use percent_encoding::percent_decode_str;
|
||||
use serde_yaml::Mapping;
|
||||
use std::net::TcpListener;
|
||||
use tauri::{App, Manager};
|
||||
use tauri_plugin_window_state::{StateFlags, WindowExt};
|
||||
|
||||
use url::Url;
|
||||
//#[cfg(not(target_os = "linux"))]
|
||||
@@ -128,63 +127,54 @@ pub fn create_window() {
|
||||
return;
|
||||
}
|
||||
|
||||
let builder = tauri::WebviewWindowBuilder::new(
|
||||
#[cfg(target_os = "windows")]
|
||||
let _ = {
|
||||
let app_handle = app_handle.clone();
|
||||
std::thread::spawn(move || {
|
||||
tauri::WebviewWindowBuilder::new(
|
||||
&app_handle,
|
||||
"main".to_string(),
|
||||
tauri::WebviewUrl::App("index.html".into()),
|
||||
)
|
||||
.title("Clash Verge")
|
||||
.visible(false)
|
||||
.inner_size(890.0, 700.0)
|
||||
.min_inner_size(620.0, 550.0)
|
||||
.decorations(false)
|
||||
.maximizable(true)
|
||||
.additional_browser_args("--enable-features=msWebView2EnableDraggableRegions --disable-features=OverscrollHistoryNavigation,msExperimentalScrolling")
|
||||
.transparent(true)
|
||||
.build()
|
||||
}).join().unwrap()
|
||||
}.unwrap();
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let _ = tauri::WebviewWindowBuilder::new(
|
||||
&app_handle,
|
||||
"main".to_string(),
|
||||
tauri::WebviewUrl::App("index.html".into()),
|
||||
)
|
||||
.decorations(true)
|
||||
.hidden_title(true)
|
||||
.title_bar_style(tauri::TitleBarStyle::Overlay)
|
||||
.inner_size(890.0, 700.0)
|
||||
.min_inner_size(620.0, 550.0)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
let _ = tauri::WebviewWindowBuilder::new(
|
||||
&app_handle,
|
||||
"main".to_string(),
|
||||
tauri::WebviewUrl::App("index.html".into()),
|
||||
)
|
||||
.title("Clash Verge")
|
||||
.visible(false)
|
||||
.fullscreen(false)
|
||||
.min_inner_size(600.0, 520.0);
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let window = builder
|
||||
.decorations(false)
|
||||
.maximizable(true)
|
||||
.additional_browser_args("--enable-features=msWebView2EnableDraggableRegions --disable-features=OverscrollHistoryNavigation,msExperimentalScrolling")
|
||||
.transparent(true)
|
||||
.visible(false)
|
||||
.build().unwrap();
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let window = builder
|
||||
.decorations(true)
|
||||
.hidden_title(true)
|
||||
.title_bar_style(tauri::TitleBarStyle::Overlay)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
let window = builder
|
||||
.decorations(false)
|
||||
.transparent(true)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
match window.restore_state(StateFlags::all()) {
|
||||
Ok(_) => {
|
||||
log::info!(target: "app", "window state restored successfully");
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!(target: "app", "failed to restore window state: {}", e);
|
||||
#[cfg(target_os = "windows")]
|
||||
window
|
||||
.set_size(tauri::Size::Physical(tauri::PhysicalSize {
|
||||
width: 800,
|
||||
height: 636,
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
window
|
||||
.set_size(tauri::Size::Physical(tauri::PhysicalSize {
|
||||
width: 800,
|
||||
height: 642,
|
||||
}))
|
||||
.unwrap();
|
||||
}
|
||||
};
|
||||
.decorations(false)
|
||||
.inner_size(890.0, 700.0)
|
||||
.min_inner_size(620.0, 550.0)
|
||||
.transparent(true)
|
||||
.build()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub async fn resolve_scheme(param: String) -> Result<()> {
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"devUrl": "http://localhost:3000/"
|
||||
},
|
||||
"productName": "Clash Verge",
|
||||
"version": "2.0.0",
|
||||
"version": "2.0.2",
|
||||
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
|
||||
"plugins": {
|
||||
"updater": {
|
||||
|
||||
@@ -49,7 +49,7 @@ export const Switch = styled((props: SwitchProps) => (
|
||||
},
|
||||
"& .MuiSwitch-track": {
|
||||
borderRadius: 26 / 2,
|
||||
backgroundColor: theme.palette.mode === "light" ? "#E9E9EA" : "#39393D",
|
||||
backgroundColor: theme.palette.mode === "light" ? "#BBBBBB" : "#39393D",
|
||||
opacity: 1,
|
||||
transition: theme.transitions.create(["background-color"], {
|
||||
duration: 500,
|
||||
|
||||
@@ -38,7 +38,7 @@ export const ConnectionDetail = forwardRef<ConnectionDetailRef>(
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
interface InnerProps {
|
||||
|
||||
@@ -59,7 +59,7 @@ export const LayoutTraffic = () => {
|
||||
this.close();
|
||||
next(event, { up: 0, down: 0 });
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return () => {
|
||||
@@ -69,7 +69,7 @@ export const LayoutTraffic = () => {
|
||||
{
|
||||
fallbackData: { up: 0, down: 0 },
|
||||
keepPreviousData: true,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
/* --------- meta memory information --------- */
|
||||
@@ -96,7 +96,7 @@ export const LayoutTraffic = () => {
|
||||
this.close();
|
||||
next(event, { inuse: 0 });
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return () => {
|
||||
@@ -106,7 +106,7 @@ export const LayoutTraffic = () => {
|
||||
{
|
||||
fallbackData: { inuse: 0 },
|
||||
keepPreviousData: true,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const [up, upUnit] = parseTraffic(traffic.up);
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { IconButton, Fade } from "@mui/material";
|
||||
import { IconButton, Fade, SxProps, Theme } from "@mui/material";
|
||||
import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
|
||||
|
||||
interface Props {
|
||||
onClick: () => void;
|
||||
show: boolean;
|
||||
sx?: SxProps<Theme>;
|
||||
}
|
||||
|
||||
export const ScrollTopButton = ({ onClick, show }: Props) => {
|
||||
export const ScrollTopButton = ({ onClick, show, sx }: Props) => {
|
||||
return (
|
||||
<Fade in={show}>
|
||||
<IconButton
|
||||
@@ -26,6 +27,7 @@ export const ScrollTopButton = ({ onClick, show }: Props) => {
|
||||
: "rgba(0,0,0,0.2)",
|
||||
},
|
||||
visibility: show ? "visible" : "hidden",
|
||||
...sx,
|
||||
}}
|
||||
>
|
||||
<KeyboardArrowUpIcon />
|
||||
|
||||
@@ -90,7 +90,7 @@ export const useCustomTheme = () => {
|
||||
}
|
||||
|
||||
// css
|
||||
const backgroundColor = mode === "light" ? "#f0f0f0" : "#2e303d";
|
||||
const backgroundColor = mode === "light" ? "#ECECEC" : "#2e303d";
|
||||
const selectColor = mode === "light" ? "#f5f5f5" : "#d5d5d5";
|
||||
const scrollColor = mode === "light" ? "#90939980" : "#54545480";
|
||||
const dividerColor =
|
||||
@@ -104,7 +104,7 @@ export const useCustomTheme = () => {
|
||||
rootEle.style.setProperty("--primary-main", theme.palette.primary.main);
|
||||
rootEle.style.setProperty(
|
||||
"--background-color-alpha",
|
||||
alpha(theme.palette.primary.main, 0.1)
|
||||
alpha(theme.palette.primary.main, 0.1),
|
||||
);
|
||||
|
||||
// inject css
|
||||
|
||||
@@ -34,7 +34,7 @@ export const ProviderButton = () => {
|
||||
|
||||
const hasProvider = Object.keys(data || {}).length > 0;
|
||||
const [updating, setUpdating] = useState(
|
||||
Object.keys(data || {}).map(() => false)
|
||||
Object.keys(data || {}).map(() => false),
|
||||
);
|
||||
|
||||
const setUpdatingAt = (status: boolean, index: number) => {
|
||||
@@ -107,7 +107,7 @@ export const ProviderButton = () => {
|
||||
const expire = sub?.Expire || 0;
|
||||
const progress = Math.min(
|
||||
Math.round(((download + upload) * 100) / (total + 0.01)) + 1,
|
||||
100
|
||||
100,
|
||||
);
|
||||
return (
|
||||
<>
|
||||
@@ -213,7 +213,7 @@ const StyledTypeBox = styled(Box)<{ component?: React.ElementType }>(
|
||||
marginRight: "4px",
|
||||
padding: "0 2px",
|
||||
lineHeight: 1.25,
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
const boxStyle = {
|
||||
|
||||
@@ -25,7 +25,7 @@ export const useRenderList = (mode: string) => {
|
||||
const { data: proxiesData, mutate: mutateProxies } = useSWR(
|
||||
"getProxies",
|
||||
getProxies,
|
||||
{ refreshInterval: 45000 }
|
||||
{ refreshInterval: 45000 },
|
||||
);
|
||||
|
||||
const { verge } = useVerge();
|
||||
@@ -78,7 +78,7 @@ export const useRenderList = (mode: string) => {
|
||||
group.all,
|
||||
group.name,
|
||||
headState.filterText,
|
||||
headState.sortType
|
||||
headState.sortType,
|
||||
);
|
||||
|
||||
ret.push({ type: 1, key: `head-${group.name}`, group, headState });
|
||||
@@ -97,7 +97,7 @@ export const useRenderList = (mode: string) => {
|
||||
headState,
|
||||
col,
|
||||
proxyCol,
|
||||
}))
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ export const useRenderList = (mode: string) => {
|
||||
group,
|
||||
proxy,
|
||||
headState,
|
||||
}))
|
||||
})),
|
||||
);
|
||||
}
|
||||
return ret;
|
||||
|
||||
@@ -32,7 +32,7 @@ export const ProviderButton = () => {
|
||||
|
||||
const hasProvider = Object.keys(data || {}).length > 0;
|
||||
const [updating, setUpdating] = useState(
|
||||
Object.keys(data || {}).map(() => false)
|
||||
Object.keys(data || {}).map(() => false),
|
||||
);
|
||||
|
||||
const setUpdatingAt = (status: boolean, index: number) => {
|
||||
|
||||
@@ -38,7 +38,7 @@ export interface BackupTableViewerProps {
|
||||
page: number;
|
||||
onPageChange: (
|
||||
event: React.MouseEvent<HTMLButtonElement> | null,
|
||||
page: number
|
||||
page: number,
|
||||
) => void;
|
||||
total: number;
|
||||
onRefresh: () => Promise<void>;
|
||||
@@ -109,7 +109,7 @@ export const BackupTableViewer = memo(
|
||||
onClick={async (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
const confirmed = await window.confirm(
|
||||
t("Confirm to delete this backup file?")
|
||||
t("Confirm to delete this backup file?"),
|
||||
);
|
||||
if (confirmed) {
|
||||
await handleDelete(file.filename);
|
||||
@@ -132,7 +132,7 @@ export const BackupTableViewer = memo(
|
||||
onClick={async (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
const confirmed = await window.confirm(
|
||||
t("Confirm to restore this backup file?")
|
||||
t("Confirm to restore this backup file?"),
|
||||
);
|
||||
if (confirmed) {
|
||||
await handleRestore(file.filename);
|
||||
@@ -181,7 +181,7 @@ export const BackupTableViewer = memo(
|
||||
/>
|
||||
</TableContainer>
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
function LinuxIcon(props: SVGProps<SVGSVGElement>) {
|
||||
|
||||
@@ -48,7 +48,7 @@ export const BackupViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
(_: React.MouseEvent<HTMLButtonElement> | null, page: number) => {
|
||||
setPage(page);
|
||||
},
|
||||
[]
|
||||
[],
|
||||
);
|
||||
|
||||
const fetchAndSetBackupFiles = async () => {
|
||||
@@ -95,8 +95,8 @@ export const BackupViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
setDataSource(
|
||||
backupFiles.slice(
|
||||
page * DEFAULT_ROWS_PER_PAGE,
|
||||
page * DEFAULT_ROWS_PER_PAGE + DEFAULT_ROWS_PER_PAGE
|
||||
)
|
||||
page * DEFAULT_ROWS_PER_PAGE + DEFAULT_ROWS_PER_PAGE,
|
||||
),
|
||||
);
|
||||
}, [page, backupFiles]);
|
||||
|
||||
@@ -104,14 +104,14 @@ export const BackupViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
<BaseDialog
|
||||
open={open}
|
||||
title={t("Backup Setting")}
|
||||
contentSx={{ width: 600, maxHeight: 800 }}
|
||||
// contentSx={{ width: 600, maxHeight: 800 }}
|
||||
okBtn={t("")}
|
||||
cancelBtn={t("Close")}
|
||||
onClose={() => setOpen(false)}
|
||||
onCancel={() => setOpen(false)}
|
||||
disableOk
|
||||
>
|
||||
<Box sx={{ maxWidth: 800 }}>
|
||||
<Box>
|
||||
<BaseLoadingOverlay isLoading={isLoading} />
|
||||
<Paper elevation={2} sx={{ padding: 2 }}>
|
||||
<BackupConfigViewer
|
||||
|
||||
@@ -103,7 +103,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
primary={t("Auto Close Connections")}
|
||||
sx={{ maxWidth: "fit-content" }}
|
||||
/>
|
||||
<TooltipIcon title={t("Auto Close Connections Info")} />
|
||||
<TooltipIcon
|
||||
title={t("Auto Close Connections Info")}
|
||||
sx={{ opacity: "0.7" }}
|
||||
/>
|
||||
<Switch
|
||||
edge="end"
|
||||
checked={values.autoCloseConnection}
|
||||
@@ -130,7 +133,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
primary={t("Enable Builtin Enhanced")}
|
||||
sx={{ maxWidth: "fit-content" }}
|
||||
/>
|
||||
<TooltipIcon title={t("Enable Builtin Enhanced Info")} />
|
||||
<TooltipIcon
|
||||
title={t("Enable Builtin Enhanced Info")}
|
||||
sx={{ opacity: "0.7" }}
|
||||
/>
|
||||
<Switch
|
||||
edge="end"
|
||||
checked={values.enableBuiltinEnhanced}
|
||||
@@ -196,7 +202,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
primary={t("Default Latency Test")}
|
||||
sx={{ maxWidth: "fit-content" }}
|
||||
/>
|
||||
<TooltipIcon title={t("Default Latency Test Info")} />
|
||||
<TooltipIcon
|
||||
title={t("Default Latency Test Info")}
|
||||
sx={{ opacity: "0.7" }}
|
||||
/>
|
||||
<TextField
|
||||
autoComplete="new-password"
|
||||
size="small"
|
||||
|
||||
@@ -166,8 +166,8 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
? t("Enabled")
|
||||
: t("Disabled")
|
||||
: sysproxy?.enable
|
||||
? t("Enabled")
|
||||
: t("Disabled")}
|
||||
? t("Enabled")
|
||||
: t("Disabled")}
|
||||
</Typography>
|
||||
</FlexBox>
|
||||
{!value.pac && (
|
||||
@@ -202,7 +202,7 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
primary={t("Proxy Guard")}
|
||||
sx={{ maxWidth: "fit-content" }}
|
||||
/>
|
||||
<TooltipIcon title={t("Proxy Guard Info")} />
|
||||
<TooltipIcon title={t("Proxy Guard Info")} sx={{ opacity: "0.7" }} />
|
||||
<Switch
|
||||
edge="end"
|
||||
disabled={!enabled}
|
||||
|
||||
@@ -22,7 +22,7 @@ export const TunViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [values, setValues] = useState({
|
||||
stack: "gvisor",
|
||||
stack: "mixed",
|
||||
device: "Mihomo",
|
||||
autoRoute: true,
|
||||
autoDetectInterface: true,
|
||||
@@ -35,7 +35,7 @@ export const TunViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
open: () => {
|
||||
setOpen(true);
|
||||
setValues({
|
||||
stack: clash?.tun.stack ?? "gvisor",
|
||||
stack: clash?.tun.stack ?? "mixed",
|
||||
device: clash?.tun.device ?? "Mihomo",
|
||||
autoRoute: clash?.tun["auto-route"] ?? true,
|
||||
autoDetectInterface: clash?.tun["auto-detect-interface"] ?? true,
|
||||
@@ -64,7 +64,7 @@ export const TunViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
...(old! || {}),
|
||||
tun,
|
||||
}),
|
||||
false
|
||||
false,
|
||||
);
|
||||
try {
|
||||
await enhanceProfiles();
|
||||
@@ -89,7 +89,7 @@ export const TunViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
size="small"
|
||||
onClick={async () => {
|
||||
let tun = {
|
||||
stack: "gvisor",
|
||||
stack: "mixed",
|
||||
device: "Mihomo",
|
||||
"auto-route": true,
|
||||
"auto-detect-interface": true,
|
||||
@@ -98,7 +98,7 @@ export const TunViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
mtu: 1500,
|
||||
};
|
||||
setValues({
|
||||
stack: "gvisor",
|
||||
stack: "mixed",
|
||||
device: "Mihomo",
|
||||
autoRoute: true,
|
||||
autoDetectInterface: true,
|
||||
@@ -112,7 +112,7 @@ export const TunViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
...(old! || {}),
|
||||
tun,
|
||||
}),
|
||||
false
|
||||
false,
|
||||
);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -76,10 +76,10 @@ export const UpdateViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
setDownloaded((a) => {
|
||||
return a + e.payload.chunkLength;
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
try {
|
||||
await updateInfo.install();
|
||||
await updateInfo.downloadAndInstall();
|
||||
await relaunch();
|
||||
} catch (err: any) {
|
||||
Notice.error(err?.message || err.toString());
|
||||
@@ -100,7 +100,7 @@ export const UpdateViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
size="small"
|
||||
onClick={() => {
|
||||
openUrl(
|
||||
`https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/v${updateInfo?.version}`
|
||||
`https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/v${updateInfo?.version}`,
|
||||
);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -139,7 +139,7 @@ const SettingClash = ({ onError }: Props) => {
|
||||
>
|
||||
<GuardState
|
||||
// clash premium 2022.08.26 值为warn
|
||||
value={logLevel === "warn" ? "warning" : logLevel ?? "info"}
|
||||
value={logLevel === "warn" ? "warning" : (logLevel ?? "info")}
|
||||
onCatch={onError}
|
||||
onFormat={(e: any) => e.target.value}
|
||||
onChange={(e) => onChangeData({ "log-level": e })}
|
||||
@@ -165,7 +165,7 @@ const SettingClash = ({ onError }: Props) => {
|
||||
onClick={() => {
|
||||
Notice.success(
|
||||
t("Restart Application to Apply Modifications"),
|
||||
1000
|
||||
1000,
|
||||
);
|
||||
onChangeVerge({ enable_random_port: !enable_random_port });
|
||||
patchVerge({ enable_random_port: !enable_random_port });
|
||||
@@ -209,7 +209,12 @@ const SettingClash = ({ onError }: Props) => {
|
||||
<SettingItem
|
||||
onClick={invoke_uwp_tool}
|
||||
label={t("Open UWP tool")}
|
||||
extra={<TooltipIcon title={t("Open UWP tool Info")} />}
|
||||
extra={
|
||||
<TooltipIcon
|
||||
title={t("Open UWP tool Info")}
|
||||
sx={{ opacity: "0.7" }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -257,7 +257,16 @@ const SettingVerge = ({ onError }: Props) => {
|
||||
label={t("Runtime Config")}
|
||||
/>
|
||||
|
||||
<SettingItem onClick={openAppDir} label={t("Open Conf Dir")} />
|
||||
<SettingItem
|
||||
onClick={openAppDir}
|
||||
label={t("Open Conf Dir")}
|
||||
extra={
|
||||
<TooltipIcon
|
||||
title={t("Open Conf Dir Info")}
|
||||
sx={{ opacity: "0.7" }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<SettingItem onClick={openCoreDir} label={t("Open Core Dir")} />
|
||||
|
||||
|
||||
@@ -64,13 +64,18 @@ export const TestViewer = forwardRef<TestViewerRef, Props>((props, ref) => {
|
||||
try {
|
||||
if (!form.name) throw new Error("`Name` should not be null");
|
||||
if (!form.url) throw new Error("`Url` should not be null");
|
||||
|
||||
let newList;
|
||||
let uid;
|
||||
|
||||
if (form.icon && form.icon.startsWith("<svg")) {
|
||||
// 移除 icon 中的注释
|
||||
if (form.icon) {
|
||||
form.icon = form.icon.replace(/<!--[\s\S]*?-->/g, "");
|
||||
}
|
||||
const doc = new DOMParser().parseFromString(
|
||||
form.icon,
|
||||
"image/svg+xml"
|
||||
"image/svg+xml",
|
||||
);
|
||||
if (doc.querySelector("parsererror")) {
|
||||
throw new Error("`Icon`svg format error");
|
||||
@@ -97,7 +102,7 @@ export const TestViewer = forwardRef<TestViewerRef, Props>((props, ref) => {
|
||||
Notice.error(err.message || err.toString());
|
||||
setLoading(false);
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
const handleClose = () => {
|
||||
|
||||
@@ -10,12 +10,12 @@ import {
|
||||
export const useClash = () => {
|
||||
const { data: clash, mutate: mutateClash } = useSWR(
|
||||
"getRuntimeConfig",
|
||||
getRuntimeConfig
|
||||
getRuntimeConfig,
|
||||
);
|
||||
|
||||
const { data: versionData, mutate: mutateVersion } = useSWR(
|
||||
"getVersion",
|
||||
getVersion
|
||||
getVersion,
|
||||
);
|
||||
|
||||
const patchClash = useLockFn(async (patch: Partial<IConfigData>) => {
|
||||
@@ -26,8 +26,8 @@ export const useClash = () => {
|
||||
const version = versionData?.premium
|
||||
? `${versionData.version} Premium`
|
||||
: versionData?.meta
|
||||
? `${versionData.version} Mihomo`
|
||||
: versionData?.version || "-";
|
||||
? `${versionData.version} Mihomo`
|
||||
: versionData?.version || "-";
|
||||
|
||||
return {
|
||||
clash,
|
||||
@@ -41,7 +41,7 @@ export const useClash = () => {
|
||||
export const useClashInfo = () => {
|
||||
const { data: clashInfo, mutate: mutateInfo } = useSWR(
|
||||
"getClashInfo",
|
||||
getClashInfo
|
||||
getClashInfo,
|
||||
);
|
||||
|
||||
const patchInfo = async (
|
||||
@@ -56,7 +56,7 @@ export const useClashInfo = () => {
|
||||
| "external-controller"
|
||||
| "secret"
|
||||
>
|
||||
>
|
||||
>,
|
||||
) => {
|
||||
const hasInfo =
|
||||
patch["redir-port"] != null ||
|
||||
|
||||
@@ -7,7 +7,7 @@ export const useListen = () => {
|
||||
|
||||
const addListener = async <T>(
|
||||
eventName: string,
|
||||
handler: EventCallback<T>
|
||||
handler: EventCallback<T>,
|
||||
) => {
|
||||
const unlisten = await listen(eventName, handler);
|
||||
unlistenFns.current.push(unlisten);
|
||||
|
||||
@@ -21,7 +21,7 @@ const buildWSUrl = (server: string, secret: string, logLevel: LogLevel) => {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (secret) {
|
||||
params.append("token", encodeURIComponent(secret));
|
||||
params.append("token", secret);
|
||||
}
|
||||
if (logLevel === "all") {
|
||||
params.append("level", "debug");
|
||||
|
||||
@@ -335,6 +335,7 @@
|
||||
"Backup Setting": "Backup Setting",
|
||||
"Runtime Config": "Runtime Config",
|
||||
"Open Conf Dir": "Open Conf Dir",
|
||||
"Open Conf Dir Info": "If the software runs abnormally, BACKUP and delete all files in this folder than restart the software",
|
||||
"Open Core Dir": "Open Core Dir",
|
||||
"Open Logs Dir": "Open Logs Dir",
|
||||
"Check for Updates": "Check for Updates",
|
||||
@@ -352,7 +353,6 @@
|
||||
"Match Whole Word": "Match Whole Word",
|
||||
"Use Regular Expression": "Use Regular Expression",
|
||||
"Profile Imported Successfully": "Profile Imported Successfully",
|
||||
"Clash Config Updated": "Clash Config Updated",
|
||||
"Profile Switched": "Profile Switched",
|
||||
"Profile Reactivated": "Profile Reactivated",
|
||||
"Only YAML Files Supported": "Only YAML Files Supported",
|
||||
|
||||
@@ -337,6 +337,7 @@
|
||||
"Backup Setting Info": "از فایل های پیکربندی پشتیبان WebDAV پشتیبانی می کند",
|
||||
"Runtime Config": "پیکربندی زمان اجرا",
|
||||
"Open Conf Dir": "باز کردن پوشه برنامه",
|
||||
"Open Conf Dir Info": "اگر نرمافزار بهطور غیرعادی اجرا میشود، از تمام فایلهای موجود در این پوشه نسخه پشتیبان تهیه و پاک کنید تا نرمافزار را مجدداً راهاندازی کنید",
|
||||
"Open Core Dir": "باز کردن پوشه هسته",
|
||||
"Open Logs Dir": "باز کردن پوشه لاگها",
|
||||
"Check for Updates": "بررسی برای بهروزرسانیها",
|
||||
@@ -354,7 +355,6 @@
|
||||
"Match Whole Word": "تطبیق کل کلمه",
|
||||
"Use Regular Expression": "استفاده از عبارت منظم",
|
||||
"Profile Imported Successfully": "پروفایل با موفقیت وارد شد",
|
||||
"Clash Config Updated": "پیکربندی Clash بهروزرسانی شد",
|
||||
"Profile Switched": "پروفایل تغییر یافت",
|
||||
"Profile Reactivated": "پروفایل مجدداً فعال شد",
|
||||
"Only YAML Files Supported": "فقط فایلهای YAML پشتیبانی میشوند",
|
||||
|
||||
@@ -337,6 +337,7 @@
|
||||
"Backup Setting Info": "Поддерживает файлы конфигурации резервного копирования WebDAV",
|
||||
"Runtime Config": "Используемый конфиг",
|
||||
"Open Conf Dir": "Открыть папку приложения",
|
||||
"Open Conf Dir Info": "Если программное обеспечение работает ненормально, сделайте резервную копию и удалите все файлы в этой папке, а затем перезапустите программное обеспечение",
|
||||
"Open Core Dir": "Открыть папку ядра",
|
||||
"Open Logs Dir": "Открыть папку логов",
|
||||
"Check for Updates": "Проверить обновления",
|
||||
@@ -354,7 +355,6 @@
|
||||
"Match Whole Word": "Полное совпадение слова",
|
||||
"Use Regular Expression": "Использовать регулярные выражения",
|
||||
"Profile Imported Successfully": "Профиль успешно импортирован",
|
||||
"Clash Config Updated": "Clash конфигурация Обновлена",
|
||||
"Profile Switched": "Профиль изменен",
|
||||
"Profile Reactivated": "Профиль повторно активирован",
|
||||
"Only YAML Files Supported": "Поддерживаются только файлы YAML",
|
||||
|
||||
@@ -336,6 +336,7 @@
|
||||
"Backup Setting": "备份设置",
|
||||
"Backup Setting Info": "支持WebDAV备份配置文件",
|
||||
"Runtime Config": "当前配置",
|
||||
"Open Conf Dir Info": "如果软件运行异常,!备份!并删除此文件夹下的所有文件,重启软件",
|
||||
"Open Conf Dir": "配置目录",
|
||||
"Open Core Dir": "内核目录",
|
||||
"Open Logs Dir": "日志目录",
|
||||
@@ -354,7 +355,6 @@
|
||||
"Match Whole Word": "全字匹配",
|
||||
"Use Regular Expression": "使用正则表达式",
|
||||
"Profile Imported Successfully": "导入订阅成功",
|
||||
"Clash Config Updated": "Clash 配置已更新",
|
||||
"Profile Switched": "订阅已切换",
|
||||
"Profile Reactivated": "订阅已激活",
|
||||
"Only YAML Files Supported": "仅支持 YAML 文件",
|
||||
|
||||
@@ -77,9 +77,6 @@ const Layout = () => {
|
||||
navigate("/profile");
|
||||
Notice.error(msg);
|
||||
break;
|
||||
case "set_config::ok":
|
||||
Notice.success(t("Clash Config Updated"));
|
||||
break;
|
||||
case "set_config::error":
|
||||
Notice.error(msg);
|
||||
break;
|
||||
|
||||
@@ -47,7 +47,7 @@ const ConnectionsPage = () => {
|
||||
list.sort(
|
||||
(a, b) =>
|
||||
new Date(b.start || "0").getTime()! -
|
||||
new Date(a.start || "0").getTime()!
|
||||
new Date(a.start || "0").getTime()!,
|
||||
),
|
||||
"Upload Speed": (list) => list.sort((a, b) => b.curUpload! - a.curUpload!),
|
||||
"Download Speed": (list) =>
|
||||
@@ -103,7 +103,7 @@ const ConnectionsPage = () => {
|
||||
next(event);
|
||||
},
|
||||
},
|
||||
3
|
||||
3,
|
||||
);
|
||||
|
||||
return () => {
|
||||
@@ -114,7 +114,7 @@ const ConnectionsPage = () => {
|
||||
const [filterConn, download, upload] = useMemo(() => {
|
||||
const orderFunc = orderOpts[curOrderOpt];
|
||||
let connections = connData.connections.filter((conn) =>
|
||||
match(conn.metadata.host || conn.metadata.destinationIP || "")
|
||||
match(conn.metadata.host || conn.metadata.destinationIP || ""),
|
||||
);
|
||||
|
||||
if (orderFunc) connections = orderFunc(connections);
|
||||
@@ -152,7 +152,7 @@ const ConnectionsPage = () => {
|
||||
setSetting((o) =>
|
||||
o?.layout !== "table"
|
||||
? { ...o, layout: "table" }
|
||||
: { ...o, layout: "list" }
|
||||
: { ...o, layout: "list" },
|
||||
)
|
||||
}
|
||||
>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import useSWR, { mutate } from "swr";
|
||||
import useSWR from "swr";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useLockFn } from "ahooks";
|
||||
import { Box, Button, Grid, IconButton, Stack, Divider } from "@mui/material";
|
||||
import { Box, Button, IconButton, Stack, Divider, Grid2 } from "@mui/material";
|
||||
import {
|
||||
DndContext,
|
||||
closestCenter,
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
} from "@mui/icons-material";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
getProfiles,
|
||||
importProfile,
|
||||
enhanceProfiles,
|
||||
getRuntimeLogs,
|
||||
@@ -45,12 +44,14 @@ import { ProfileMore } from "@/components/profile/profile-more";
|
||||
import { ProfileItem } from "@/components/profile/profile-item";
|
||||
import { useProfiles } from "@/hooks/use-profiles";
|
||||
import { ConfigViewer } from "@/components/setting/mods/config-viewer";
|
||||
import { throttle } from "lodash-es";
|
||||
import { add, throttle } from "lodash-es";
|
||||
import { BaseStyledTextField } from "@/components/base/base-styled-text-field";
|
||||
import { readTextFile } from "@tauri-apps/plugin-fs";
|
||||
import { readText } from "@tauri-apps/plugin-clipboard-manager";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { useListen } from "@/hooks/use-listen";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { TauriEvent } from "@tauri-apps/api/event";
|
||||
|
||||
const ProfilePage = () => {
|
||||
const { t } = useTranslation();
|
||||
@@ -64,35 +65,46 @@ const ProfilePage = () => {
|
||||
useSensor(PointerSensor),
|
||||
useSensor(KeyboardSensor, {
|
||||
coordinateGetter: sortableKeyboardCoordinates,
|
||||
})
|
||||
}),
|
||||
);
|
||||
const { current } = location.state || {};
|
||||
|
||||
useEffect(() => {
|
||||
const unlisten = addListener("tauri://file-drop", async (event) => {
|
||||
const fileList = event.payload as string[];
|
||||
for (let file of fileList) {
|
||||
if (!file.endsWith(".yaml") && !file.endsWith(".yml")) {
|
||||
Notice.error(t("Only YAML Files Supported"));
|
||||
continue;
|
||||
}
|
||||
const item = {
|
||||
type: "local",
|
||||
name: file.split(/\/|\\/).pop() ?? "New Profile",
|
||||
desc: "",
|
||||
url: "",
|
||||
option: {
|
||||
with_proxy: false,
|
||||
self_proxy: false,
|
||||
},
|
||||
} as IProfileItem;
|
||||
let data = await readTextFile(file);
|
||||
await createProfile(item, data);
|
||||
await mutateProfiles();
|
||||
}
|
||||
});
|
||||
const handleFileDrop = async () => {
|
||||
const unlisten = await addListener(
|
||||
TauriEvent.DRAG_DROP,
|
||||
async (event: any) => {
|
||||
const paths = event.payload.paths;
|
||||
|
||||
for (let file of paths) {
|
||||
if (!file.endsWith(".yaml") && !file.endsWith(".yml")) {
|
||||
Notice.error(t("Only YAML Files Supported"));
|
||||
continue;
|
||||
}
|
||||
const item = {
|
||||
type: "local",
|
||||
name: file.split(/\/|\\/).pop() ?? "New Profile",
|
||||
desc: "",
|
||||
url: "",
|
||||
option: {
|
||||
with_proxy: false,
|
||||
self_proxy: false,
|
||||
},
|
||||
} as IProfileItem;
|
||||
let data = await readTextFile(file);
|
||||
await createProfile(item, data);
|
||||
await mutateProfiles();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return unlisten;
|
||||
};
|
||||
|
||||
const unsubscribe = handleFileDrop();
|
||||
|
||||
return () => {
|
||||
unlisten.then((fn) => fn());
|
||||
unsubscribe.then((cleanup) => cleanup());
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -105,7 +117,7 @@ const ProfilePage = () => {
|
||||
|
||||
const { data: chainLogs = {}, mutate: mutateLogs } = useSWR(
|
||||
"getRuntimeLogs",
|
||||
getRuntimeLogs
|
||||
getRuntimeLogs,
|
||||
);
|
||||
|
||||
const viewerRef = useRef<ProfileViewerRef>(null);
|
||||
@@ -117,9 +129,7 @@ const ProfilePage = () => {
|
||||
|
||||
const type1 = ["local", "remote"];
|
||||
|
||||
const profileItems = items.filter((i) => i && type1.includes(i.type!));
|
||||
|
||||
return profileItems;
|
||||
return items.filter((i) => i && type1.includes(i.type!));
|
||||
}, [profiles]);
|
||||
|
||||
const currentActivatings = () => {
|
||||
@@ -240,7 +250,7 @@ const ProfilePage = () => {
|
||||
setLoadingCache((cache) => {
|
||||
// 获取没有正在更新的订阅
|
||||
const items = profileItems.filter(
|
||||
(e) => e.type === "remote" && !cache[e.uid]
|
||||
(e) => e.type === "remote" && !cache[e.uid],
|
||||
);
|
||||
const change = Object.fromEntries(items.map((e) => [e.uid, true]));
|
||||
|
||||
@@ -372,14 +382,14 @@ const ProfilePage = () => {
|
||||
onDragEnd={onDragEnd}
|
||||
>
|
||||
<Box sx={{ mb: 1.5 }}>
|
||||
<Grid container spacing={{ xs: 1, lg: 1 }}>
|
||||
<Grid2 container spacing={{ xs: 1, lg: 1 }}>
|
||||
<SortableContext
|
||||
items={profileItems.map((x) => {
|
||||
return x.uid;
|
||||
})}
|
||||
>
|
||||
{profileItems.map((item) => (
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} key={item.file}>
|
||||
<Grid2 size={{ xs: 12, sm: 6, md: 4, lg: 3 }} key={item.file}>
|
||||
<ProfileItem
|
||||
id={item.uid}
|
||||
selected={profiles.current === item.uid}
|
||||
@@ -394,10 +404,10 @@ const ProfilePage = () => {
|
||||
}}
|
||||
onDelete={() => onDelete(item.uid)}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid2>
|
||||
))}
|
||||
</SortableContext>
|
||||
</Grid>
|
||||
</Grid2>
|
||||
</Box>
|
||||
</DndContext>
|
||||
<Divider
|
||||
@@ -406,8 +416,8 @@ const ProfilePage = () => {
|
||||
sx={{ width: `calc(100% - 32px)`, borderColor: dividercolor }}
|
||||
></Divider>
|
||||
<Box sx={{ mt: 1.5 }}>
|
||||
<Grid container spacing={{ xs: 1, lg: 1 }}>
|
||||
<Grid item xs={12} sm={6} md={6} lg={6}>
|
||||
<Grid2 container spacing={{ xs: 1, lg: 1 }}>
|
||||
<Grid2 size={{ xs: 12, sm: 6, md: 6, lg: 6 }}>
|
||||
<ProfileMore
|
||||
id="Merge"
|
||||
onSave={async (prev, curr) => {
|
||||
@@ -416,8 +426,8 @@ const ProfilePage = () => {
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6} md={6} lg={6}>
|
||||
</Grid2>
|
||||
<Grid2 size={{ xs: 12, sm: 6, md: 6, lg: 6 }}>
|
||||
<ProfileMore
|
||||
id="Script"
|
||||
logInfo={chainLogs["Script"]}
|
||||
@@ -427,8 +437,8 @@ const ProfilePage = () => {
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid2>
|
||||
</Grid2>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ const ProxyPage = () => {
|
||||
|
||||
const { data: clashConfig, mutate: mutateClash } = useSWR(
|
||||
"getClashConfig",
|
||||
getClashConfig
|
||||
getClashConfig,
|
||||
);
|
||||
|
||||
const { verge } = useVerge();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { Box, Button, Grid } from "@mui/material";
|
||||
import { Box, Button } from "@mui/material";
|
||||
import Grid2 from "@mui/material/Grid2";
|
||||
import {
|
||||
DndContext,
|
||||
closestCenter,
|
||||
@@ -21,6 +22,7 @@ import { TestViewer, TestViewerRef } from "@/components/test/test-viewer";
|
||||
import { TestItem } from "@/components/test/test-item";
|
||||
import { emit } from "@tauri-apps/api/event";
|
||||
import { nanoid } from "nanoid";
|
||||
import { ScrollTopButton } from "@/components/layout/scroll-top-button";
|
||||
|
||||
// test icons
|
||||
import apple from "@/assets/image/test/apple.svg?raw";
|
||||
@@ -34,7 +36,7 @@ const TestPage = () => {
|
||||
useSensor(PointerSensor),
|
||||
useSensor(KeyboardSensor, {
|
||||
coordinateGetter: sortableKeyboardCoordinates,
|
||||
})
|
||||
}),
|
||||
);
|
||||
const { verge, mutateVerge, patchVerge } = useVerge();
|
||||
|
||||
@@ -68,7 +70,7 @@ const TestPage = () => {
|
||||
|
||||
const onTestListItemChange = (
|
||||
uid: string,
|
||||
patch?: Partial<IVergeTestItem>
|
||||
patch?: Partial<IVergeTestItem>,
|
||||
) => {
|
||||
if (patch) {
|
||||
const newList = testList.map((x) => {
|
||||
@@ -120,6 +122,19 @@ const TestPage = () => {
|
||||
}, [verge]);
|
||||
|
||||
const viewerRef = useRef<TestViewerRef>(null);
|
||||
const [showScrollTop, setShowScrollTop] = useState(false);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const scrollToTop = () => {
|
||||
containerRef.current?.scrollTo({
|
||||
top: 0,
|
||||
behavior: "smooth",
|
||||
});
|
||||
};
|
||||
|
||||
const handleScroll = (e: any) => {
|
||||
setShowScrollTop(e.target.scrollTop > 100);
|
||||
};
|
||||
|
||||
return (
|
||||
<BasePage
|
||||
@@ -145,10 +160,15 @@ const TestPage = () => {
|
||||
}
|
||||
>
|
||||
<Box
|
||||
ref={containerRef}
|
||||
onScroll={handleScroll}
|
||||
sx={{
|
||||
pt: 1.25,
|
||||
mb: 0.5,
|
||||
px: "10px",
|
||||
height: "calc(100vh - 100px)",
|
||||
overflow: "auto",
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
<DndContext
|
||||
@@ -157,26 +177,41 @@ const TestPage = () => {
|
||||
onDragEnd={onDragEnd}
|
||||
>
|
||||
<Box sx={{ mb: 4.5 }}>
|
||||
<Grid container spacing={{ xs: 1, lg: 1 }}>
|
||||
<Grid2 container spacing={{ xs: 1, lg: 1 }}>
|
||||
<SortableContext
|
||||
items={testList.map((x) => {
|
||||
return x.uid;
|
||||
})}
|
||||
>
|
||||
{testList.map((item) => (
|
||||
<Grid item xs={6} sm={4} md={3} lg={2} key={item.uid}>
|
||||
<Grid2
|
||||
component={"div"}
|
||||
size={{ xs: 6, lg: 2, sm: 4, md: 3 }}
|
||||
key={item.uid}
|
||||
>
|
||||
<TestItem
|
||||
id={item.uid}
|
||||
itemData={item}
|
||||
onEdit={() => viewerRef.current?.edit(item)}
|
||||
onDelete={onDeleteTestListItem}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid2>
|
||||
))}
|
||||
</SortableContext>
|
||||
</Grid>
|
||||
</Grid2>
|
||||
</Box>
|
||||
</DndContext>
|
||||
|
||||
<ScrollTopButton
|
||||
onClick={scrollToTop}
|
||||
show={showScrollTop}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
bottom: "20px",
|
||||
left: "20px",
|
||||
zIndex: 1000,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<TestViewer ref={viewerRef} onChange={onTestListItemChange} />
|
||||
</BasePage>
|
||||
|
||||
@@ -20,7 +20,7 @@ export const useConnectionSetting = () =>
|
||||
{
|
||||
serializer: JSON.stringify,
|
||||
deserializer: JSON.parse,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// save the state of each profile item loading
|
||||
|
||||
@@ -6,7 +6,7 @@ import Sockette, { type SocketteOptions } from "sockette";
|
||||
export const createSockette = (
|
||||
url: string,
|
||||
opt: SocketteOptions,
|
||||
maxError = 10
|
||||
maxError = 10,
|
||||
) => {
|
||||
let remainRetryCount = maxError;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import monacoEditorPlugin, {
|
||||
type IMonacoEditorOpts,
|
||||
} from "vite-plugin-monaco-editor";
|
||||
const monacoEditorPluginDefault = (monacoEditorPlugin as any).default as (
|
||||
options: IMonacoEditorOpts
|
||||
options: IMonacoEditorOpts,
|
||||
) => any;
|
||||
|
||||
export default defineConfig({
|
||||
|
||||
Reference in New Issue
Block a user