Compare commits

...

38 Commits

33 changed files with 820 additions and 318 deletions

View File

@@ -1,12 +1,30 @@
## v2.0.1 ## v2.0.2
### Notice ### Notice
- 强烈建议完全删除 1.x 老版本再安装此版本 - !!使用出现异常的,打开设置-->配置目录 备份 后 删除所有文件 尝试是否正常!
- 历时3个月的紧密开发与严格测试稳定版2.0.0终于发布了巨量改进与性能、稳定性提升目前Clash Verge Rev已经有了比肩cfw的健壮性而且更强大易用 - 历时3个月的紧密开发与严格测试稳定版2.0.0终于发布了巨量改进与性能、稳定性提升目前Clash Verge Rev已经有了比肩cfw的健壮性而且更强大易用
- 由于更改了服务安装逻辑Mac/Linux 首次安装需要输入系统密码卸载和安装服务,以后可以丝滑使用 tun(虚拟网卡)模式 - 由于更改了服务安装逻辑Mac/Linux 首次安装需要输入系统密码卸载和安装服务,以后可以丝滑使用 tun(虚拟网卡)模式
- 因 Tauri 2.0 底层 bug关闭窗口后保留webview进程优点是再次打开面板更快缺点是内存使用略有增加 - 因 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.0.1相对于2.0.0改进了:
- 无法从 2.0rc和2.0.0 升级的问题已经安装了2.0版本的需手动下载安装) - 无法从 2.0rc和2.0.0 升级的问题已经安装了2.0版本的需手动下载安装)
@@ -24,7 +42,7 @@
- 重大框架升级:使用 Tauri 2.0(巨量改进与性能提升) - 重大框架升级:使用 Tauri 2.0(巨量改进与性能提升)
- 出现 bug 到 issues 中提出以后不再接受1.x版本的bug反馈。 - 出现 bug 到 issues 中提出以后不再接受1.x版本的bug反馈。
- 强烈建议完全删除 1.x 老版本再安装此版本 - 强烈建议完全删除 1.x 老版本再安装此版本 !!使用出现异常的,打开设置-->配置目录 备份 后 删除所有文件 尝试是否正常!
### Features ### Features
@@ -75,7 +93,7 @@
### Known issues ### Known issues
- Windows 下窗口大小无法记忆(等待上游修复) - Windows 下窗口大小无法记忆(等待上游修复)
- Webdav 备份因为安全性和兼容性问题,暂不支持同步 Webdav 服务器地址和登录信息;跨平台配置同步 - Webdav 备份因为安全性和兼容性问题,暂不支持跨平台配置同步
--- ---

View File

@@ -1,6 +1,6 @@
{ {
"name": "clash-verge", "name": "clash-verge",
"version": "2.0.1", "version": "2.0.2",
"license": "GPL-3.0-only", "license": "GPL-3.0-only",
"scripts": { "scripts": {
"dev": "cross-env RUST_BACKTRACE=1 tauri dev", "dev": "cross-env RUST_BACKTRACE=1 tauri dev",

View File

@@ -488,8 +488,8 @@ const tasks = [
{ {
name: "service_chmod", name: "service_chmod",
func: resolveServicePermission, func: resolveServicePermission,
retry: 1, retry: 5,
unixOnly: true, unixOnly: platform === "linux" || platform === "darwin",
}, },
{ {
name: "windows-sysproxy", name: "windows-sysproxy",
@@ -514,10 +514,10 @@ const tasks = [
async function runTask() { async function runTask() {
const task = tasks.shift(); const task = tasks.shift();
if (!task) return; 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.unixOnly && platform === "win32") return runTask();
if (task.winOnly && platform !== "win32") return runTask();
if (task.macosOnly && platform !== "darwin") return runTask(); if (task.macosOnly && platform !== "darwin") return runTask();
if (task.linuxOnly && platform !== "linux") return runTask();
for (let i = 0; i < task.retry; i++) { for (let i = 0; i < task.retry; i++) {
try { try {
@@ -532,4 +532,3 @@ async function runTask() {
} }
runTask(); runTask();
runTask();

332
src-tauri/Cargo.lock generated
View File

@@ -85,6 +85,12 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "aligned-vec"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1"
[[package]] [[package]]
name = "alloc-no-stdlib" name = "alloc-no-stdlib"
version = "2.0.4" version = "2.0.4"
@@ -160,6 +166,17 @@ version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" 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]] [[package]]
name = "arrayvec" name = "arrayvec"
version = "0.7.6" version = "0.7.6"
@@ -478,6 +495,29 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 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]] [[package]]
name = "axum" name = "axum"
version = "0.6.20" version = "0.6.20"
@@ -556,6 +596,12 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bit_field"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.3.2" version = "1.3.2"
@@ -571,6 +617,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "bitstream-io"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2"
[[package]] [[package]]
name = "block" name = "block"
version = "0.1.6" version = "0.1.6"
@@ -776,6 +828,12 @@ dependencies = [
"alloc-stdlib", "alloc-stdlib",
] ]
[[package]]
name = "built"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b"
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.16.0" version = "3.16.0"
@@ -988,7 +1046,7 @@ dependencies = [
[[package]] [[package]]
name = "clash-verge" name = "clash-verge"
version = "2.0.1" version = "2.0.2"
dependencies = [ dependencies = [
"aes-gcm", "aes-gcm",
"anyhow", "anyhow",
@@ -1000,6 +1058,7 @@ dependencies = [
"dirs 5.0.1", "dirs 5.0.1",
"dunce", "dunce",
"getrandom 0.2.15", "getrandom 0.2.15",
"image",
"log", "log",
"log4rs", "log4rs",
"nanoid", "nanoid",
@@ -1109,6 +1168,12 @@ dependencies = [
"objc", "objc",
] ]
[[package]]
name = "color_quant"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]] [[package]]
name = "colored" name = "colored"
version = "2.1.0" version = "2.1.0"
@@ -1934,6 +1999,21 @@ dependencies = [
"pin-project-lite", "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]] [[package]]
name = "fast-float" name = "fast-float"
version = "0.2.0" version = "0.2.0"
@@ -2353,6 +2433,16 @@ dependencies = [
"polyval", "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]] [[package]]
name = "gimli" name = "gimli"
version = "0.31.1" version = "0.31.1"
@@ -2562,6 +2652,16 @@ dependencies = [
"tracing", "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]] [[package]]
name = "handlebars" name = "handlebars"
version = "3.5.5" version = "3.5.5"
@@ -3068,11 +3168,37 @@ checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"byteorder-lite", "byteorder-lite",
"color_quant",
"exr",
"gif",
"image-webp",
"num-traits", "num-traits",
"png", "png",
"qoi",
"ravif",
"rayon",
"rgb",
"tiff", "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]] [[package]]
name = "indexmap" name = "indexmap"
version = "1.9.3" version = "1.9.3"
@@ -3138,6 +3264,17 @@ dependencies = [
"serde_derive", "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]] [[package]]
name = "intrusive-collections" name = "intrusive-collections"
version = "0.9.7" version = "0.9.7"
@@ -3345,6 +3482,12 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "lebe"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
[[package]] [[package]]
name = "libappindicator" name = "libappindicator"
version = "0.9.0" version = "0.9.0"
@@ -3375,6 +3518,16 @@ version = "0.2.164"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" 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]] [[package]]
name = "libloading" name = "libloading"
version = "0.7.4" version = "0.7.4"
@@ -3495,6 +3648,15 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "loop9"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062"
dependencies = [
"imgref",
]
[[package]] [[package]]
name = "lru" name = "lru"
version = "0.7.8" version = "0.7.8"
@@ -3577,6 +3739,16 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" 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]] [[package]]
name = "md-5" name = "md-5"
version = "0.10.6" version = "0.10.6"
@@ -3857,6 +4029,12 @@ dependencies = [
"minimal-lexical", "minimal-lexical",
] ]
[[package]]
name = "noop_proc_macro"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
[[package]] [[package]]
name = "notify-rust" name = "notify-rust"
version = "4.11.3" version = "4.11.3"
@@ -3917,6 +4095,17 @@ dependencies = [
"syn 1.0.109", "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]] [[package]]
name = "num-integer" name = "num-integer"
version = "0.1.46" version = "0.1.46"
@@ -3926,6 +4115,17 @@ dependencies = [
"num-traits", "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]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.19" version = "0.2.19"
@@ -4855,6 +5055,25 @@ dependencies = [
"unicode-ident", "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]] [[package]]
name = "prost" name = "prost"
version = "0.12.6" version = "0.12.6"
@@ -4887,6 +5106,15 @@ dependencies = [
"prost", "prost",
] ]
[[package]]
name = "qoi"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
dependencies = [
"bytemuck",
]
[[package]] [[package]]
name = "quick-error" name = "quick-error"
version = "2.0.1" version = "2.0.1"
@@ -5062,6 +5290,56 @@ dependencies = [
"rand_core 0.5.1", "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]] [[package]]
name = "raw-window-handle" name = "raw-window-handle"
version = "0.5.2" version = "0.5.2"
@@ -5261,6 +5539,12 @@ dependencies = [
"windows-sys 0.48.0", "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]] [[package]]
name = "ring" name = "ring"
version = "0.17.8" version = "0.17.8"
@@ -5853,6 +6137,15 @@ version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" 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]] [[package]]
name = "siphasher" name = "siphasher"
version = "0.3.11" version = "0.3.11"
@@ -6740,7 +7033,7 @@ dependencies = [
"libc", "libc",
"log", "log",
"memmem", "memmem",
"num-derive", "num-derive 0.3.3",
"num-traits", "num-traits",
"ordered-float", "ordered-float",
"regex", "regex",
@@ -7494,6 +7787,17 @@ dependencies = [
"serde", "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]] [[package]]
name = "valuable" name = "valuable"
version = "0.1.0" version = "0.1.0"
@@ -8823,6 +9127,30 @@ dependencies = [
"pkg-config", "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]] [[package]]
name = "zvariant" name = "zvariant"
version = "4.2.0" version = "4.2.0"

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "clash-verge" name = "clash-verge"
version = "2.0.1" version = "2.0.2"
description = "clash verge" description = "clash verge"
authors = ["zzzgydi", "wonfen", "MystiPanda"] authors = ["zzzgydi", "wonfen", "MystiPanda"]
license = "GPL-3.0-only" license = "GPL-3.0-only"
@@ -36,6 +36,7 @@ tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
reqwest = { version = "0.12", features = ["json", "rustls-tls"] } reqwest = { version = "0.12", features = ["json", "rustls-tls"] }
sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs", branch = "main" } sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs", branch = "main" }
image = "0.25.5"
tauri = { version = "2.1.1", features = [ tauri = { version = "2.1.1", features = [
"protocol-asset", "protocol-asset",
"devtools", "devtools",

View File

@@ -16,6 +16,12 @@
"identifier": "fs:scope", "identifier": "fs:scope",
"allow": ["$APPDATA/**", "$RESOURCE/../**", "**"] "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-create",
"core:window:allow-center", "core:window:allow-center",
"core:window:allow-request-user-attention", "core:window:allow-request-user-attention",

View File

@@ -766,6 +766,34 @@ Section Install
!insertmacro CheckIfAppIsRunning !insertmacro CheckIfAppIsRunning
!insertmacro CheckAllVergeProcesses !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 old files before installation
; Delete clash-verge.desktop ; Delete clash-verge.desktop
IfFileExists "$INSTDIR\Clash Verge.exe" 0 +2 IfFileExists "$INSTDIR\Clash Verge.exe" 0 +2
@@ -891,6 +919,35 @@ Section Uninstall
!insertmacro CheckIfAppIsRunning !insertmacro CheckIfAppIsRunning
!insertmacro CheckAllVergeProcesses !insertmacro CheckAllVergeProcesses
!insertmacro RemoveVergeService !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 ; Delete the app directory and its content from disk
; Copy main executable ; Copy main executable
Delete "$INSTDIR\${MAINBINARYNAME}.exe" Delete "$INSTDIR\${MAINBINARYNAME}.exe"

View File

@@ -33,7 +33,7 @@ impl IClashTemp {
let mut map = Mapping::new(); let mut map = Mapping::new();
let mut tun = Mapping::new(); let mut tun = Mapping::new();
tun.insert("enable".into(), false.into()); 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("auto-route".into(), true.into());
tun.insert("strict-route".into(), false.into()); tun.insert("strict-route".into(), false.into());
tun.insert("auto-detect-interface".into(), true.into()); tun.insert("auto-detect-interface".into(), true.into());

View File

@@ -4,114 +4,184 @@ use anyhow::Error;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use reqwest_dav::list_cmd::{ListEntity, ListFile}; use reqwest_dav::list_cmd::{ListEntity, ListFile};
use std::collections::HashMap;
use std::env::{consts::OS, temp_dir}; use std::env::{consts::OS, temp_dir};
use std::fs; use std::fs;
use std::io::Write; use std::io::Write;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration;
use tokio::time::timeout;
use zip::write::SimpleFileOptions; 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 { 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 { impl WebDavClient {
pub fn global() -> &'static WebDavClient { pub fn global() -> &'static WebDavClient {
static WEBDAV_CLIENT: OnceCell<WebDavClient> = OnceCell::new(); static WEBDAV_CLIENT: OnceCell<WebDavClient> = OnceCell::new();
WEBDAV_CLIENT.get_or_init(|| WebDavClient { 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> { async fn get_client(&self, op: Operation) -> Result<reqwest_dav::Client, Error> {
if self.client.lock().is_none() { // 先尝试从缓存获取
let verge = Config::verge().latest().clone(); {
if verge.webdav_url.is_none() let clients = self.clients.lock();
|| verge.webdav_username.is_none() if let Some(client) = clients.get(&op) {
|| verge.webdav_password.is_none() return Ok(client.clone());
{
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));
} }
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) { pub fn reset(&self) {
if !self.client.lock().is_none() { *self.config.lock() = None;
self.client.lock().take(); self.clients.lock().clear();
}
} }
pub async fn upload(&self, file_path: PathBuf, file_name: String) -> Result<(), Error> { 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); let webdav_path: String = format!("{}/{}", dirs::BACKUP_DIR, file_name);
client let fut = client.put(webdav_path.as_ref(), fs::read(file_path)?);
.put(webdav_path.as_ref(), fs::read(file_path)?) timeout(Duration::from_secs(TIMEOUT_UPLOAD), fut).await??;
.await?;
Ok(()) Ok(())
} }
pub async fn download(&self, filename: String, storage_path: PathBuf) -> Result<(), Error> { 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 path = format!("{}/{}", dirs::BACKUP_DIR, filename);
let response = client.get(path.as_str()).await?;
let content = response.bytes().await?; let fut = async {
fs::write(&storage_path, &content)?; 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(()) Ok(())
} }
pub async fn list(&self) -> Result<Vec<ListFile>, Error> { 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 path = format!("{}/", dirs::BACKUP_DIR);
let files = client
.list(path.as_str(), reqwest_dav::Depth::Number(1)) let fut = async {
.await?; let files = client
let mut final_files = Vec::new(); .list(path.as_str(), reqwest_dav::Depth::Number(1))
for file in files { .await?;
if let ListEntity::File(file) = file { let mut final_files = Vec::new();
final_files.push(file); for file in files {
if let ListEntity::File(file) = file {
final_files.push(file);
}
} }
} Ok::<Vec<ListFile>, Error>(final_files)
Ok(final_files) };
Ok(timeout(Duration::from_secs(TIMEOUT_LIST), fut).await??)
} }
pub async fn delete(&self, file_name: String) -> Result<(), Error> { 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); 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(()) Ok(())
} }
} }

View File

@@ -96,16 +96,11 @@ impl Tray {
*tun_mode, *tun_mode,
)?)); )?));
#[cfg(target_os = "macos")]
let mut use_custom_icon = false;
#[allow(unused)] #[allow(unused)]
let mut indication_icon = if *system_proxy && !*tun_mode { let mut indication_icon = if *system_proxy && !*tun_mode {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
let mut icon = match tray_icon.as_str() { let mut icon = match tray_icon.as_str() {
"colorful" => { "colorful" => include_bytes!("../../icons/tray-icon-sys.ico").to_vec(),
use_custom_icon = true;
include_bytes!("../../icons/tray-icon-sys.ico").to_vec()
}
_ => include_bytes!("../../icons/tray-icon-sys-mono.ico").to_vec(), _ => include_bytes!("../../icons/tray-icon-sys-mono.ico").to_vec(),
}; };
@@ -120,19 +115,12 @@ impl Tray {
} else if png_path.exists() { } else if png_path.exists() {
icon = std::fs::read(png_path).unwrap(); icon = std::fs::read(png_path).unwrap();
} }
#[cfg(target_os = "macos")]
{
use_custom_icon = true;
}
} }
icon icon
} else if *tun_mode { } else if *tun_mode {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
let mut icon = match tray_icon.as_str() { let mut icon = match tray_icon.as_str() {
"colorful" => { "colorful" => include_bytes!("../../icons/tray-icon-tun.ico").to_vec(),
use_custom_icon = true;
include_bytes!("../../icons/tray-icon-tun.ico").to_vec()
}
_ => include_bytes!("../../icons/tray-icon-tun-mono.ico").to_vec(), _ => include_bytes!("../../icons/tray-icon-tun-mono.ico").to_vec(),
}; };
@@ -147,19 +135,12 @@ impl Tray {
} else if png_path.exists() { } else if png_path.exists() {
icon = std::fs::read(png_path).unwrap(); icon = std::fs::read(png_path).unwrap();
} }
#[cfg(target_os = "macos")]
{
use_custom_icon = true;
}
} }
icon icon
} else { } else {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
let mut icon = match tray_icon.as_str() { let mut icon = match tray_icon.as_str() {
"colorful" => { "colorful" => include_bytes!("../../icons/tray-icon.ico").to_vec(),
use_custom_icon = true;
include_bytes!("../../icons/tray-icon.ico").to_vec()
}
_ => include_bytes!("../../icons/tray-icon-mono.ico").to_vec(), _ => include_bytes!("../../icons/tray-icon-mono.ico").to_vec(),
}; };
@@ -174,22 +155,16 @@ impl Tray {
} else if png_path.exists() { } else if png_path.exists() {
icon = std::fs::read(png_path).unwrap(); icon = std::fs::read(png_path).unwrap();
} }
#[cfg(target_os = "macos")]
{
use_custom_icon = true;
}
} }
icon icon
}; };
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
{ {
if use_custom_icon { let is_template = crate::utils::help::is_monochrome_image_from_bytes(&indication_icon)
let _ = tray.set_icon_as_template(false); .unwrap_or(false);
let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&indication_icon)?)); let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&indication_icon)?));
} else { let _ = tray.set_icon_as_template(is_template);
let _ = tray.set_icon_as_template(true);
}
} }
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]

View File

@@ -60,6 +60,7 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
global_merge, global_merge,
global_script, global_script,
profile_name, profile_name,
dns,
) = { ) = {
let profiles = Config::profiles(); let profiles = Config::profiles();
let profiles = profiles.latest(); let profiles = profiles.latest();
@@ -130,6 +131,10 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
.and_then(|item| item.name.clone()) .and_then(|item| item.name.clone())
.unwrap_or_default(); .unwrap_or_default();
let dns = current.get("dns").map_or(Mapping::new(), |val| {
val.as_mapping().cloned().unwrap_or(Mapping::new())
});
( (
current, current,
merge, merge,
@@ -140,6 +145,7 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
global_merge, global_merge,
global_script, global_script,
name, 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); config = use_sort(config);
let mut exists_set = HashSet::new(); let mut exists_set = HashSet::new();

View File

@@ -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_key = Value::from("tun");
let tun_val = config.get(&tun_key); let tun_val = config.get(&tun_key);
let mut tun_val = tun_val.map_or(Mapping::new(), |val| { 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| { let mut dns_val = dns_val.map_or(Mapping::new(), |val| {
val.as_mapping().cloned().unwrap_or(Mapping::new()) 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 { if enable {
revise!(dns_val, "enable", true); 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, "enhanced-mode", "fake-ip");
revise!(dns_val, "fake-ip-range", "198.18.0.1/16"); revise!(dns_val, "fake-ip-range", "198.18.0.1/16");
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
{ {
crate::utils::resolve::restore_public_dns().await; crate::utils::resolve::restore_public_dns().await;
crate::utils::resolve::set_public_dns("8.8.8.8".to_string()).await; crate::utils::resolve::set_public_dns("223.6.6.6".to_string()).await;
} }
} else { } 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")] #[cfg(target_os = "macos")]
crate::utils::resolve::restore_public_dns().await; crate::utils::resolve::restore_public_dns().await;
} }

View File

@@ -20,12 +20,16 @@ use tauri_plugin_window_state::{AppHandleExt, StateFlags};
// 打开面板 // 打开面板
pub fn open_or_close_dashboard() { pub fn open_or_close_dashboard() {
if let Some(window) = handle::Handle::global().get_window() { 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(); let _ = window.hide();
return; } else {
let _ = window.show();
let _ = window.set_focus();
} }
} else {
resolve::create_window();
} }
resolve::create_window();
} }
// 重启clash // 重启clash
@@ -121,14 +125,6 @@ pub fn quit(code: Option<i32>) {
handle::Handle::global().set_is_exiting(); handle::Handle::global().set_is_exiting();
resolve::resolve_reset(); resolve::resolve_reset();
log_err!(handle::Handle::global().get_window().unwrap().close()); 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)); 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() { if patch.get("secret").is_some() || patch.get("external-controller").is_some() {
Config::generate().await?; Config::generate().await?;
CoreManager::global().restart_core().await?; CoreManager::global().restart_core().await?;
handle::Handle::refresh_clash();
} else { } else {
if patch.get("mode").is_some() { if patch.get("mode").is_some() {
log_err!(handle::Handle::update_systray_part()); log_err!(handle::Handle::update_systray_part());
} }
Config::runtime().latest().patch_config(patch); Config::runtime().latest().patch_config(patch);
update_core_config(false).await?; CoreManager::global().update_config().await?;
} }
handle::Handle::refresh_clash();
<Result<()>>::Ok(()) <Result<()>>::Ok(())
}; };
match res { match res {
@@ -250,11 +244,11 @@ pub async fn patch_verge(patch: IVerge) -> Result<()> {
should_update_systray_part = true; should_update_systray_part = true;
} }
if should_restart_core { if should_restart_core {
Config::generate().await?;
CoreManager::global().restart_core().await?; CoreManager::global().restart_core().await?;
} }
if should_update_clash_config { if should_update_clash_config {
update_core_config(false).await?; CoreManager::global().update_config().await?;
handle::Handle::refresh_clash();
} }
if should_update_launch { if should_update_launch {
sysopt::Sysopt::global().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 { 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(()) 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 /// copy env variable
pub fn copy_clash_env() { pub fn copy_clash_env() {
let app_handle = handle::Handle::global().app_handle().unwrap(); let app_handle = handle::Handle::global().app_handle().unwrap();

View File

@@ -4,7 +4,6 @@ use nanoid::nanoid;
use serde::{de::DeserializeOwned, Serialize}; use serde::{de::DeserializeOwned, Serialize};
use serde_yaml::{Mapping, Value}; use serde_yaml::{Mapping, Value};
use std::{fs, path::PathBuf, str::FromStr}; use std::{fs, path::PathBuf, str::FromStr};
use tauri_plugin_shell::ShellExt;
/// read data from yaml as struct T /// read data from yaml as struct T
pub fn read_yaml<T: DeserializeOwned>(path: &PathBuf) -> Result<T> { pub fn read_yaml<T: DeserializeOwned>(path: &PathBuf) -> Result<T> {
@@ -99,44 +98,24 @@ pub fn get_last_part_and_decode(url: &str) -> Option<String> {
} }
/// open file /// open file
/// try to use vscode first, if not found then use system default app pub fn open_file(_: tauri::AppHandle, path: PathBuf) -> Result<()> {
pub fn open_file(app: tauri::AppHandle, path: PathBuf) -> Result<()> { open::that_detached(&path.as_os_str())?;
#[cfg(target_os = "macos")]
let code = "Visual Studio Code";
#[cfg(not(target_os = "macos"))]
let code = "code";
#[cfg(target_os = "windows")]
let vscode_exists = {
use std::process::Command;
Command::new("where").arg("code").output().is_ok()
};
#[cfg(target_os = "macos")]
let vscode_exists = {
use std::process::Command;
Command::new("which").arg("code").output().is_ok()
};
#[cfg(target_os = "linux")]
let vscode_exists = {
use std::process::Command;
Command::new("which").arg("code").output().is_ok()
};
// 如果 VS Code 存在就用它打开,否则用系统默认程序
if vscode_exists {
if let Err(err) = open::with(&path.as_os_str(), code) {
log::error!(target: "app", "Failed to open with VS Code: {}", err);
app.shell().open(path.to_string_lossy(), None)?;
}
} else {
app.shell().open(path.to_string_lossy(), None)?;
}
Ok(()) 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")] #[cfg(target_os = "linux")]
pub fn linux_elevator() -> String { pub fn linux_elevator() -> String {
use std::process::Command; use std::process::Command;

View File

@@ -193,12 +193,7 @@ pub fn init_resources() -> Result<()> {
let _ = fs::create_dir_all(&res_dir); let _ = fs::create_dir_all(&res_dir);
} }
#[cfg(target_os = "windows")]
let file_list = ["Country.mmdb", "geoip.dat", "geosite.dat"]; 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 // copy the resource file
// if the source file is newer than the destination file, copy it over // if the source file is newer than the destination file, copy it over

View File

@@ -8,7 +8,6 @@ use percent_encoding::percent_decode_str;
use serde_yaml::Mapping; use serde_yaml::Mapping;
use std::net::TcpListener; use std::net::TcpListener;
use tauri::{App, Manager}; use tauri::{App, Manager};
use tauri_plugin_window_state::{StateFlags, WindowExt};
use url::Url; use url::Url;
//#[cfg(not(target_os = "linux"))] //#[cfg(not(target_os = "linux"))]
@@ -128,63 +127,54 @@ pub fn create_window() {
return; 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, &app_handle,
"main".to_string(), "main".to_string(),
tauri::WebviewUrl::App("index.html".into()), tauri::WebviewUrl::App("index.html".into()),
) )
.title("Clash Verge") .title("Clash Verge")
.visible(false) .decorations(false)
.fullscreen(false) .inner_size(890.0, 700.0)
.min_inner_size(600.0, 520.0); .min_inner_size(620.0, 550.0)
.transparent(true)
#[cfg(target_os = "windows")] .build()
let window = builder .unwrap();
.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();
}
};
} }
pub async fn resolve_scheme(param: String) -> Result<()> { pub async fn resolve_scheme(param: String) -> Result<()> {

View File

@@ -25,7 +25,7 @@
"devUrl": "http://localhost:3000/" "devUrl": "http://localhost:3000/"
}, },
"productName": "Clash Verge", "productName": "Clash Verge",
"version": "2.0.1", "version": "2.0.2",
"identifier": "io.github.clash-verge-rev.clash-verge-rev", "identifier": "io.github.clash-verge-rev.clash-verge-rev",
"plugins": { "plugins": {
"updater": { "updater": {

View File

@@ -49,7 +49,7 @@ export const Switch = styled((props: SwitchProps) => (
}, },
"& .MuiSwitch-track": { "& .MuiSwitch-track": {
borderRadius: 26 / 2, borderRadius: 26 / 2,
backgroundColor: theme.palette.mode === "light" ? "#E9E9EA" : "#39393D", backgroundColor: theme.palette.mode === "light" ? "#BBBBBB" : "#39393D",
opacity: 1, opacity: 1,
transition: theme.transitions.create(["background-color"], { transition: theme.transitions.create(["background-color"], {
duration: 500, duration: 500,

View File

@@ -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"; import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
interface Props { interface Props {
onClick: () => void; onClick: () => void;
show: boolean; show: boolean;
sx?: SxProps<Theme>;
} }
export const ScrollTopButton = ({ onClick, show }: Props) => { export const ScrollTopButton = ({ onClick, show, sx }: Props) => {
return ( return (
<Fade in={show}> <Fade in={show}>
<IconButton <IconButton
@@ -26,6 +27,7 @@ export const ScrollTopButton = ({ onClick, show }: Props) => {
: "rgba(0,0,0,0.2)", : "rgba(0,0,0,0.2)",
}, },
visibility: show ? "visible" : "hidden", visibility: show ? "visible" : "hidden",
...sx,
}} }}
> >
<KeyboardArrowUpIcon /> <KeyboardArrowUpIcon />

View File

@@ -90,7 +90,7 @@ export const useCustomTheme = () => {
} }
// css // css
const backgroundColor = mode === "light" ? "#f0f0f0" : "#2e303d"; const backgroundColor = mode === "light" ? "#ECECEC" : "#2e303d";
const selectColor = mode === "light" ? "#f5f5f5" : "#d5d5d5"; const selectColor = mode === "light" ? "#f5f5f5" : "#d5d5d5";
const scrollColor = mode === "light" ? "#90939980" : "#54545480"; const scrollColor = mode === "light" ? "#90939980" : "#54545480";
const dividerColor = const dividerColor =

View File

@@ -104,14 +104,14 @@ export const BackupViewer = forwardRef<DialogRef>((props, ref) => {
<BaseDialog <BaseDialog
open={open} open={open}
title={t("Backup Setting")} title={t("Backup Setting")}
contentSx={{ width: 600, maxHeight: 800 }} // contentSx={{ width: 600, maxHeight: 800 }}
okBtn={t("")} okBtn={t("")}
cancelBtn={t("Close")} cancelBtn={t("Close")}
onClose={() => setOpen(false)} onClose={() => setOpen(false)}
onCancel={() => setOpen(false)} onCancel={() => setOpen(false)}
disableOk disableOk
> >
<Box sx={{ maxWidth: 800 }}> <Box>
<BaseLoadingOverlay isLoading={isLoading} /> <BaseLoadingOverlay isLoading={isLoading} />
<Paper elevation={2} sx={{ padding: 2 }}> <Paper elevation={2} sx={{ padding: 2 }}>
<BackupConfigViewer <BackupConfigViewer

View File

@@ -103,7 +103,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
primary={t("Auto Close Connections")} primary={t("Auto Close Connections")}
sx={{ maxWidth: "fit-content" }} sx={{ maxWidth: "fit-content" }}
/> />
<TooltipIcon title={t("Auto Close Connections Info")} /> <TooltipIcon
title={t("Auto Close Connections Info")}
sx={{ opacity: "0.7" }}
/>
<Switch <Switch
edge="end" edge="end"
checked={values.autoCloseConnection} checked={values.autoCloseConnection}
@@ -130,7 +133,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
primary={t("Enable Builtin Enhanced")} primary={t("Enable Builtin Enhanced")}
sx={{ maxWidth: "fit-content" }} sx={{ maxWidth: "fit-content" }}
/> />
<TooltipIcon title={t("Enable Builtin Enhanced Info")} /> <TooltipIcon
title={t("Enable Builtin Enhanced Info")}
sx={{ opacity: "0.7" }}
/>
<Switch <Switch
edge="end" edge="end"
checked={values.enableBuiltinEnhanced} checked={values.enableBuiltinEnhanced}
@@ -196,7 +202,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
primary={t("Default Latency Test")} primary={t("Default Latency Test")}
sx={{ maxWidth: "fit-content" }} sx={{ maxWidth: "fit-content" }}
/> />
<TooltipIcon title={t("Default Latency Test Info")} /> <TooltipIcon
title={t("Default Latency Test Info")}
sx={{ opacity: "0.7" }}
/>
<TextField <TextField
autoComplete="new-password" autoComplete="new-password"
size="small" size="small"

View File

@@ -166,8 +166,8 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
? t("Enabled") ? t("Enabled")
: t("Disabled") : t("Disabled")
: sysproxy?.enable : sysproxy?.enable
? t("Enabled") ? t("Enabled")
: t("Disabled")} : t("Disabled")}
</Typography> </Typography>
</FlexBox> </FlexBox>
{!value.pac && ( {!value.pac && (
@@ -202,7 +202,7 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
primary={t("Proxy Guard")} primary={t("Proxy Guard")}
sx={{ maxWidth: "fit-content" }} sx={{ maxWidth: "fit-content" }}
/> />
<TooltipIcon title={t("Proxy Guard Info")} /> <TooltipIcon title={t("Proxy Guard Info")} sx={{ opacity: "0.7" }} />
<Switch <Switch
edge="end" edge="end"
disabled={!enabled} disabled={!enabled}

View File

@@ -22,7 +22,7 @@ export const TunViewer = forwardRef<DialogRef>((props, ref) => {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [values, setValues] = useState({ const [values, setValues] = useState({
stack: "gvisor", stack: "mixed",
device: "Mihomo", device: "Mihomo",
autoRoute: true, autoRoute: true,
autoDetectInterface: true, autoDetectInterface: true,
@@ -35,7 +35,7 @@ export const TunViewer = forwardRef<DialogRef>((props, ref) => {
open: () => { open: () => {
setOpen(true); setOpen(true);
setValues({ setValues({
stack: clash?.tun.stack ?? "gvisor", stack: clash?.tun.stack ?? "mixed",
device: clash?.tun.device ?? "Mihomo", device: clash?.tun.device ?? "Mihomo",
autoRoute: clash?.tun["auto-route"] ?? true, autoRoute: clash?.tun["auto-route"] ?? true,
autoDetectInterface: clash?.tun["auto-detect-interface"] ?? true, autoDetectInterface: clash?.tun["auto-detect-interface"] ?? true,
@@ -64,7 +64,7 @@ export const TunViewer = forwardRef<DialogRef>((props, ref) => {
...(old! || {}), ...(old! || {}),
tun, tun,
}), }),
false false,
); );
try { try {
await enhanceProfiles(); await enhanceProfiles();
@@ -89,7 +89,7 @@ export const TunViewer = forwardRef<DialogRef>((props, ref) => {
size="small" size="small"
onClick={async () => { onClick={async () => {
let tun = { let tun = {
stack: "gvisor", stack: "mixed",
device: "Mihomo", device: "Mihomo",
"auto-route": true, "auto-route": true,
"auto-detect-interface": true, "auto-detect-interface": true,
@@ -98,7 +98,7 @@ export const TunViewer = forwardRef<DialogRef>((props, ref) => {
mtu: 1500, mtu: 1500,
}; };
setValues({ setValues({
stack: "gvisor", stack: "mixed",
device: "Mihomo", device: "Mihomo",
autoRoute: true, autoRoute: true,
autoDetectInterface: true, autoDetectInterface: true,
@@ -112,7 +112,7 @@ export const TunViewer = forwardRef<DialogRef>((props, ref) => {
...(old! || {}), ...(old! || {}),
tun, tun,
}), }),
false false,
); );
}} }}
> >

View File

@@ -209,7 +209,12 @@ const SettingClash = ({ onError }: Props) => {
<SettingItem <SettingItem
onClick={invoke_uwp_tool} onClick={invoke_uwp_tool}
label={t("Open 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" }}
/>
}
/> />
)} )}

View File

@@ -257,7 +257,16 @@ const SettingVerge = ({ onError }: Props) => {
label={t("Runtime Config")} 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")} /> <SettingItem onClick={openCoreDir} label={t("Open Core Dir")} />

View File

@@ -335,6 +335,7 @@
"Backup Setting": "Backup Setting", "Backup Setting": "Backup Setting",
"Runtime Config": "Runtime Config", "Runtime Config": "Runtime Config",
"Open Conf Dir": "Open Conf Dir", "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 Core Dir": "Open Core Dir",
"Open Logs Dir": "Open Logs Dir", "Open Logs Dir": "Open Logs Dir",
"Check for Updates": "Check for Updates", "Check for Updates": "Check for Updates",
@@ -352,7 +353,6 @@
"Match Whole Word": "Match Whole Word", "Match Whole Word": "Match Whole Word",
"Use Regular Expression": "Use Regular Expression", "Use Regular Expression": "Use Regular Expression",
"Profile Imported Successfully": "Profile Imported Successfully", "Profile Imported Successfully": "Profile Imported Successfully",
"Clash Config Updated": "Clash Config Updated",
"Profile Switched": "Profile Switched", "Profile Switched": "Profile Switched",
"Profile Reactivated": "Profile Reactivated", "Profile Reactivated": "Profile Reactivated",
"Only YAML Files Supported": "Only YAML Files Supported", "Only YAML Files Supported": "Only YAML Files Supported",

View File

@@ -337,6 +337,7 @@
"Backup Setting Info": "از فایل های پیکربندی پشتیبان WebDAV پشتیبانی می کند", "Backup Setting Info": "از فایل های پیکربندی پشتیبان WebDAV پشتیبانی می کند",
"Runtime Config": "پیکربندی زمان اجرا", "Runtime Config": "پیکربندی زمان اجرا",
"Open Conf Dir": "باز کردن پوشه برنامه", "Open Conf Dir": "باز کردن پوشه برنامه",
"Open Conf Dir Info": "اگر نرم‌افزار به‌طور غیرعادی اجرا می‌شود، از تمام فایل‌های موجود در این پوشه نسخه پشتیبان تهیه و پاک کنید تا نرم‌افزار را مجدداً راه‌اندازی کنید",
"Open Core Dir": "باز کردن پوشه هسته", "Open Core Dir": "باز کردن پوشه هسته",
"Open Logs Dir": "باز کردن پوشه لاگ‌ها", "Open Logs Dir": "باز کردن پوشه لاگ‌ها",
"Check for Updates": "بررسی برای به‌روزرسانی‌ها", "Check for Updates": "بررسی برای به‌روزرسانی‌ها",
@@ -354,7 +355,6 @@
"Match Whole Word": "تطبیق کل کلمه", "Match Whole Word": "تطبیق کل کلمه",
"Use Regular Expression": "استفاده از عبارت منظم", "Use Regular Expression": "استفاده از عبارت منظم",
"Profile Imported Successfully": "پروفایل با موفقیت وارد شد", "Profile Imported Successfully": "پروفایل با موفقیت وارد شد",
"Clash Config Updated": "پیکربندی Clash به‌روزرسانی شد",
"Profile Switched": "پروفایل تغییر یافت", "Profile Switched": "پروفایل تغییر یافت",
"Profile Reactivated": "پروفایل مجدداً فعال شد", "Profile Reactivated": "پروفایل مجدداً فعال شد",
"Only YAML Files Supported": "فقط فایل‌های YAML پشتیبانی می‌شوند", "Only YAML Files Supported": "فقط فایل‌های YAML پشتیبانی می‌شوند",

View File

@@ -337,6 +337,7 @@
"Backup Setting Info": "Поддерживает файлы конфигурации резервного копирования WebDAV", "Backup Setting Info": "Поддерживает файлы конфигурации резервного копирования WebDAV",
"Runtime Config": "Используемый конфиг", "Runtime Config": "Используемый конфиг",
"Open Conf Dir": "Открыть папку приложения", "Open Conf Dir": "Открыть папку приложения",
"Open Conf Dir Info": "Если программное обеспечение работает ненормально, сделайте резервную копию и удалите все файлы в этой папке, а затем перезапустите программное обеспечение",
"Open Core Dir": "Открыть папку ядра", "Open Core Dir": "Открыть папку ядра",
"Open Logs Dir": "Открыть папку логов", "Open Logs Dir": "Открыть папку логов",
"Check for Updates": "Проверить обновления", "Check for Updates": "Проверить обновления",
@@ -354,7 +355,6 @@
"Match Whole Word": "Полное совпадение слова", "Match Whole Word": "Полное совпадение слова",
"Use Regular Expression": "Использовать регулярные выражения", "Use Regular Expression": "Использовать регулярные выражения",
"Profile Imported Successfully": "Профиль успешно импортирован", "Profile Imported Successfully": "Профиль успешно импортирован",
"Clash Config Updated": "Clash конфигурация Обновлена",
"Profile Switched": "Профиль изменен", "Profile Switched": "Профиль изменен",
"Profile Reactivated": "Профиль повторно активирован", "Profile Reactivated": "Профиль повторно активирован",
"Only YAML Files Supported": "Поддерживаются только файлы YAML", "Only YAML Files Supported": "Поддерживаются только файлы YAML",

View File

@@ -336,6 +336,7 @@
"Backup Setting": "备份设置", "Backup Setting": "备份设置",
"Backup Setting Info": "支持WebDAV备份配置文件", "Backup Setting Info": "支持WebDAV备份配置文件",
"Runtime Config": "当前配置", "Runtime Config": "当前配置",
"Open Conf Dir Info": "如果软件运行异常,!备份!并删除此文件夹下的所有文件,重启软件",
"Open Conf Dir": "配置目录", "Open Conf Dir": "配置目录",
"Open Core Dir": "内核目录", "Open Core Dir": "内核目录",
"Open Logs Dir": "日志目录", "Open Logs Dir": "日志目录",
@@ -354,7 +355,6 @@
"Match Whole Word": "全字匹配", "Match Whole Word": "全字匹配",
"Use Regular Expression": "使用正则表达式", "Use Regular Expression": "使用正则表达式",
"Profile Imported Successfully": "导入订阅成功", "Profile Imported Successfully": "导入订阅成功",
"Clash Config Updated": "Clash 配置已更新",
"Profile Switched": "订阅已切换", "Profile Switched": "订阅已切换",
"Profile Reactivated": "订阅已激活", "Profile Reactivated": "订阅已激活",
"Only YAML Files Supported": "仅支持 YAML 文件", "Only YAML Files Supported": "仅支持 YAML 文件",

View File

@@ -77,9 +77,6 @@ const Layout = () => {
navigate("/profile"); navigate("/profile");
Notice.error(msg); Notice.error(msg);
break; break;
case "set_config::ok":
Notice.success(t("Clash Config Updated"));
break;
case "set_config::error": case "set_config::error":
Notice.error(msg); Notice.error(msg);
break; break;

View File

@@ -1,7 +1,7 @@
import useSWR, { mutate } from "swr"; import useSWR from "swr";
import { useEffect, useMemo, useRef, useState } from "react"; import { useEffect, useMemo, useRef, useState } from "react";
import { useLockFn } from "ahooks"; 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 { import {
DndContext, DndContext,
closestCenter, closestCenter,
@@ -25,7 +25,6 @@ import {
} from "@mui/icons-material"; } from "@mui/icons-material";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import {
getProfiles,
importProfile, importProfile,
enhanceProfiles, enhanceProfiles,
getRuntimeLogs, getRuntimeLogs,
@@ -45,12 +44,14 @@ import { ProfileMore } from "@/components/profile/profile-more";
import { ProfileItem } from "@/components/profile/profile-item"; import { ProfileItem } from "@/components/profile/profile-item";
import { useProfiles } from "@/hooks/use-profiles"; import { useProfiles } from "@/hooks/use-profiles";
import { ConfigViewer } from "@/components/setting/mods/config-viewer"; 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 { BaseStyledTextField } from "@/components/base/base-styled-text-field";
import { readTextFile } from "@tauri-apps/plugin-fs"; import { readTextFile } from "@tauri-apps/plugin-fs";
import { readText } from "@tauri-apps/plugin-clipboard-manager"; import { readText } from "@tauri-apps/plugin-clipboard-manager";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import { useListen } from "@/hooks/use-listen"; import { useListen } from "@/hooks/use-listen";
import { listen } from "@tauri-apps/api/event";
import { TauriEvent } from "@tauri-apps/api/event";
const ProfilePage = () => { const ProfilePage = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -69,30 +70,41 @@ const ProfilePage = () => {
const { current } = location.state || {}; const { current } = location.state || {};
useEffect(() => { useEffect(() => {
const unlisten = addListener("tauri://file-drop", async (event) => { const handleFileDrop = async () => {
const fileList = event.payload as string[]; const unlisten = await addListener(
for (let file of fileList) { TauriEvent.DRAG_DROP,
if (!file.endsWith(".yaml") && !file.endsWith(".yml")) { async (event: any) => {
Notice.error(t("Only YAML Files Supported")); const paths = event.payload.paths;
continue;
} for (let file of paths) {
const item = { if (!file.endsWith(".yaml") && !file.endsWith(".yml")) {
type: "local", Notice.error(t("Only YAML Files Supported"));
name: file.split(/\/|\\/).pop() ?? "New Profile", continue;
desc: "", }
url: "", const item = {
option: { type: "local",
with_proxy: false, name: file.split(/\/|\\/).pop() ?? "New Profile",
self_proxy: false, desc: "",
}, url: "",
} as IProfileItem; option: {
let data = await readTextFile(file); with_proxy: false,
await createProfile(item, data); self_proxy: false,
await mutateProfiles(); },
} } as IProfileItem;
}); let data = await readTextFile(file);
await createProfile(item, data);
await mutateProfiles();
}
},
);
return unlisten;
};
const unsubscribe = handleFileDrop();
return () => { return () => {
unlisten.then((fn) => fn()); unsubscribe.then((cleanup) => cleanup());
}; };
}, []); }, []);
@@ -117,9 +129,7 @@ const ProfilePage = () => {
const type1 = ["local", "remote"]; const type1 = ["local", "remote"];
const profileItems = items.filter((i) => i && type1.includes(i.type!)); return items.filter((i) => i && type1.includes(i.type!));
return profileItems;
}, [profiles]); }, [profiles]);
const currentActivatings = () => { const currentActivatings = () => {
@@ -372,14 +382,14 @@ const ProfilePage = () => {
onDragEnd={onDragEnd} onDragEnd={onDragEnd}
> >
<Box sx={{ mb: 1.5 }}> <Box sx={{ mb: 1.5 }}>
<Grid container spacing={{ xs: 1, lg: 1 }}> <Grid2 container spacing={{ xs: 1, lg: 1 }}>
<SortableContext <SortableContext
items={profileItems.map((x) => { items={profileItems.map((x) => {
return x.uid; return x.uid;
})} })}
> >
{profileItems.map((item) => ( {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 <ProfileItem
id={item.uid} id={item.uid}
selected={profiles.current === item.uid} selected={profiles.current === item.uid}
@@ -394,10 +404,10 @@ const ProfilePage = () => {
}} }}
onDelete={() => onDelete(item.uid)} onDelete={() => onDelete(item.uid)}
/> />
</Grid> </Grid2>
))} ))}
</SortableContext> </SortableContext>
</Grid> </Grid2>
</Box> </Box>
</DndContext> </DndContext>
<Divider <Divider
@@ -406,8 +416,8 @@ const ProfilePage = () => {
sx={{ width: `calc(100% - 32px)`, borderColor: dividercolor }} sx={{ width: `calc(100% - 32px)`, borderColor: dividercolor }}
></Divider> ></Divider>
<Box sx={{ mt: 1.5 }}> <Box sx={{ mt: 1.5 }}>
<Grid container spacing={{ xs: 1, lg: 1 }}> <Grid2 container spacing={{ xs: 1, lg: 1 }}>
<Grid item xs={12} sm={6} md={6} lg={6}> <Grid2 size={{ xs: 12, sm: 6, md: 6, lg: 6 }}>
<ProfileMore <ProfileMore
id="Merge" id="Merge"
onSave={async (prev, curr) => { onSave={async (prev, curr) => {
@@ -416,8 +426,8 @@ const ProfilePage = () => {
} }
}} }}
/> />
</Grid> </Grid2>
<Grid item xs={12} sm={6} md={6} lg={6}> <Grid2 size={{ xs: 12, sm: 6, md: 6, lg: 6 }}>
<ProfileMore <ProfileMore
id="Script" id="Script"
logInfo={chainLogs["Script"]} logInfo={chainLogs["Script"]}
@@ -427,8 +437,8 @@ const ProfilePage = () => {
} }
}} }}
/> />
</Grid> </Grid2>
</Grid> </Grid2>
</Box> </Box>
</Box> </Box>

View File

@@ -1,4 +1,4 @@
import { useEffect, useRef } from "react"; import { useEffect, useRef, useState } from "react";
import { useVerge } from "@/hooks/use-verge"; import { useVerge } from "@/hooks/use-verge";
import { Box, Button } from "@mui/material"; import { Box, Button } from "@mui/material";
import Grid2 from "@mui/material/Grid2"; import Grid2 from "@mui/material/Grid2";
@@ -22,6 +22,7 @@ import { TestViewer, TestViewerRef } from "@/components/test/test-viewer";
import { TestItem } from "@/components/test/test-item"; import { TestItem } from "@/components/test/test-item";
import { emit } from "@tauri-apps/api/event"; import { emit } from "@tauri-apps/api/event";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { ScrollTopButton } from "@/components/layout/scroll-top-button";
// test icons // test icons
import apple from "@/assets/image/test/apple.svg?raw"; import apple from "@/assets/image/test/apple.svg?raw";
@@ -121,6 +122,19 @@ const TestPage = () => {
}, [verge]); }, [verge]);
const viewerRef = useRef<TestViewerRef>(null); 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 ( return (
<BasePage <BasePage
@@ -146,10 +160,15 @@ const TestPage = () => {
} }
> >
<Box <Box
ref={containerRef}
onScroll={handleScroll}
sx={{ sx={{
pt: 1.25, pt: 1.25,
mb: 0.5, mb: 0.5,
px: "10px", px: "10px",
height: "calc(100vh - 100px)",
overflow: "auto",
position: "relative",
}} }}
> >
<DndContext <DndContext
@@ -182,6 +201,17 @@ const TestPage = () => {
</Grid2> </Grid2>
</Box> </Box>
</DndContext> </DndContext>
<ScrollTopButton
onClick={scrollToTop}
show={showScrollTop}
sx={{
position: "absolute",
bottom: "20px",
left: "20px",
zIndex: 1000,
}}
/>
</Box> </Box>
<TestViewer ref={viewerRef} onChange={onTestListItemChange} /> <TestViewer ref={viewerRef} onChange={onTestListItemChange} />
</BasePage> </BasePage>