mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-15 22:00:56 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2cf7a048cf | ||
|
|
3dbb71a076 | ||
|
|
ddf6760543 | ||
|
|
a6fa323f33 | ||
|
|
2df8f1acdb | ||
|
|
74a3290abc | ||
|
|
ab306d21f3 | ||
|
|
1bd7795851 | ||
|
|
302c142346 | ||
|
|
7c254c9b45 | ||
|
|
b1c458359d | ||
|
|
2d925c62f5 | ||
|
|
ef4711987f | ||
|
|
8910dfaecf | ||
|
|
e8b2e79c7d | ||
|
|
333ee1e1f7 | ||
|
|
ba013bbe96 | ||
|
|
636e66fe7a | ||
|
|
372a45e667 | ||
|
|
ab6b796ce2 | ||
|
|
71e6c02897 | ||
|
|
3edeaa7038 | ||
|
|
f2f75d4015 | ||
|
|
69adade738 | ||
|
|
1bd88828e4 | ||
|
|
977fcbe6cd | ||
|
|
aa76a5c436 | ||
|
|
ea5fad428d | ||
|
|
0e1e27b35a | ||
|
|
d41f67a156 | ||
|
|
6f10dec808 | ||
|
|
9b79da2cdf | ||
|
|
c11b8519f1 | ||
|
|
b9e23a0b59 | ||
|
|
75d41b6fe5 | ||
|
|
4256590735 | ||
|
|
462fb05ea8 | ||
|
|
812dbfd836 | ||
|
|
cfca837777 | ||
|
|
ac5fb1948a | ||
|
|
22d8b73625 | ||
|
|
edde40c298 |
@@ -1,23 +1,41 @@
|
||||
name: Bug report
|
||||
description: Create a report to help us improve
|
||||
title: "[BUG]"
|
||||
name: 问题反馈 / Bug report
|
||||
title: "[BUG] "
|
||||
description: 反馈你遇到的问题 / Report the issue you are experiencing
|
||||
labels: ["bug"]
|
||||
|
||||
body:
|
||||
- type: textarea
|
||||
- type: markdown
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: A clear and concise description of what the bug is.
|
||||
value: |
|
||||
## 在提交问题之前,请确认以下事项:
|
||||
1. 请 **确保** 您已经查阅了 [Clash Verge Rev 官方文档](https://clash-verge-rev.github.io/guide.html) 以及 [常见问题](https://clash-verge-rev.github.io/faq.html)
|
||||
2. 请 **确保** [已有的问题](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue) 中没有人提交过相似issue,否则请在已有的issue下进行讨论
|
||||
3. 请 **务必** 给issue填写一个简洁明了的标题,以便他人快速检索
|
||||
4. 请 **务必** 先下载 [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) 版本测试,确保问题依然存在
|
||||
5. 请 **务必** 按照模板规范详细描述问题,否则issue将会被关闭
|
||||
## Before submitting the issue, please make sure of the following checklist:
|
||||
1. Please make sure you have read the [Clash Verge Rev official documentation](https://clash-verge-rev.github.io/guide.html) and [FAQ](https://clash-verge-rev.github.io/faq.html)
|
||||
2. Please make sure there is no similar issue in the [existing issues](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue), otherwise please discuss under the existing issue
|
||||
3. Please be sure to fill in a concise and clear title for the issue so that others can quickly search
|
||||
4. Please be sure to download the [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) version for testing to ensure that the problem still exists
|
||||
5. Please describe the problem in detail according to the template specification, otherwise the issue will be closed
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: 问题描述 / Describe the bug
|
||||
description: 详细清晰地描述你遇到的问题 / A clear and concise description of what the bug is
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: To Reproduce
|
||||
description: Steps to reproduce the behavior.
|
||||
label: 复现步骤 / To Reproduce
|
||||
description: 请提供复现问题的步骤 / Steps to reproduce the behavior
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Platform
|
||||
label: 操作系统 / OS
|
||||
options:
|
||||
- Windows
|
||||
- Linux
|
||||
@@ -26,20 +44,13 @@ body:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: System Version
|
||||
placeholder: "e.g. macOS 10.15.7"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Software Version
|
||||
placeholder: "e.g. 1.4.3"
|
||||
label: 操作系统版本 / OS Version
|
||||
description: 请提供你的操作系统版本,Linux请额外提供桌面环境及窗口系统 / Please provide your OS version, for Linux, please also provide the desktop environment and window system
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Log
|
||||
description: "Log file content or screenshot"
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional Information
|
||||
label: 日志 / Log
|
||||
description: 请提供完整或相关部分的Debug日志 / Please provide the complete or relevant part of the Debug log
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -1,27 +1,35 @@
|
||||
name: Feature request
|
||||
description: Suggest an idea for this project
|
||||
title: "[Feature]"
|
||||
name: 功能请求 / Feature request
|
||||
title: "[Feature] "
|
||||
description: 提出你的功能请求 / Propose your feature request
|
||||
labels: ["enhancement"]
|
||||
|
||||
body:
|
||||
- type: textarea
|
||||
- type: markdown
|
||||
attributes:
|
||||
label: Is your feature request related to a problem? Please describe.
|
||||
description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
value: |
|
||||
## 在提交问题之前,请确认以下事项:
|
||||
1. 请 **确保** 您已经查阅了 [Clash Verge Rev 官方文档](https://clash-verge-rev.github.io/guide.html) 确认软件不存在类似的功能
|
||||
2. 请 **确保** [已有的问题](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue) 中没有人提交过相似issue,否则请在已有的issue下进行讨论
|
||||
3. 请 **务必** 给issue填写一个简洁明了的标题,以便他人快速检索
|
||||
4. 请 **务必** 先下载 [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) 版本测试,确保该功能还未实现
|
||||
5. 请 **务必** 按照模板规范详细描述问题,否则issue将会被关闭
|
||||
## Before submitting the issue, please make sure of the following checklist:
|
||||
1. Please make sure you have read the [Clash Verge Rev official documentation](https://clash-verge-rev.github.io/guide.html) to confirm that the software does not have similar functions
|
||||
2. Please make sure there is no similar issue in the [existing issues](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue), otherwise please discuss under the existing issue
|
||||
3. Please be sure to fill in a concise and clear title for the issue so that others can quickly search
|
||||
4. Please be sure to download the [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) version for testing to ensure that the function has not been implemented
|
||||
5. Please describe the problem in detail according to the template specification, otherwise the issue will be closed
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: 功能描述 / Feature description
|
||||
description: 详细清晰地描述你的功能请求 / A clear and concise description of what the feature is
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the solution you'd like
|
||||
description: A clear and concise description of what you want to happen.
|
||||
label: 使用场景 / Use case
|
||||
description: 请描述你的功能请求的使用场景 / Please describe the use case of your feature request
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe alternatives you've considered
|
||||
description: A clear and concise description of any alternative solutions or features you've considered.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context or screenshots about the feature request here.
|
||||
|
||||
@@ -14,7 +14,7 @@ elif [ "$INPUT_TARGET" = "aarch64-unknown-linux-gnu" ]; then
|
||||
dpkg --add-architecture arm64
|
||||
apt-get update
|
||||
apt-get install -y libncurses6:arm64 libtinfo6:arm64 linux-libc-dev:arm64 libncursesw6:arm64 libssl3:arm64 libcups2:arm64
|
||||
apt-get install -y --no-install-recommends g++-aarch64-linux-gnu libc6-dev-arm64-cross libssl-dev:arm64 libwebkit2gtk-4.0-dev:arm64 libgtk-3-dev:arm64 patchelf:arm64 librsvg2-dev:arm64 libayatana-appindicator3-dev:arm64
|
||||
apt-get install -y --no-install-recommends g++-aarch64-linux-gnu libc6-dev-arm64-cross libwebkit2gtk-4.0-dev:arm64 libgtk-3-dev:arm64 patchelf:arm64 librsvg2-dev:arm64 libayatana-appindicator3-dev:arm64
|
||||
export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc
|
||||
export CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc
|
||||
export CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++
|
||||
@@ -24,12 +24,22 @@ elif [ "$INPUT_TARGET" = "armv7-unknown-linux-gnueabihf" ]; then
|
||||
dpkg --add-architecture armhf
|
||||
apt-get update
|
||||
apt-get install -y libncurses6:armhf libtinfo6:armhf linux-libc-dev:armhf libncursesw6:armhf libssl3:armhf libcups2:armhf
|
||||
apt-get install -y --no-install-recommends g++-arm-linux-gnueabihf libc6-dev-armhf-cross libssl-dev:armhf libwebkit2gtk-4.0-dev:armhf libgtk-3-dev:armhf patchelf:armhf librsvg2-dev:armhf libayatana-appindicator3-dev:armhf
|
||||
apt-get install -y --no-install-recommends g++-arm-linux-gnueabihf libc6-dev-armhf-cross libwebkit2gtk-4.0-dev:armhf libgtk-3-dev:armhf patchelf:armhf librsvg2-dev:armhf libayatana-appindicator3-dev:armhf
|
||||
export CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc
|
||||
export CC_armv7_unknown_linux_gnueabihf=arm-linux-gnueabihf-gcc
|
||||
export CXX_armv7_unknown_linux_gnueabihf=arm-linux-gnueabihf-g++
|
||||
export PKG_CONFIG_PATH=/usr/lib/arm-linux-gnueabihf/pkgconfig
|
||||
export PKG_CONFIG_ALLOW_CROSS=1
|
||||
elif [ "$INPUT_TARGET" = "riscv64gc-unknown-linux-gnu" ]; then
|
||||
dpkg --add-architecture riscv64
|
||||
apt-get update
|
||||
apt-get install -y libncurses6:riscv64 libtinfo6:riscv64 linux-libc-dev:riscv64 libncursesw6:riscv64 libssl3:riscv64 libcups2:riscv64
|
||||
apt-get install -y --no-install-recommends g++-riscv64-linux-gnu libc6-dev-riscv64-cross libwebkit2gtk-4.0-dev:riscv64 libgtk-3-dev:riscv64 patchelf:riscv64 librsvg2-dev:riscv64 libayatana-appindicator3-dev:riscv64
|
||||
export CARGO_TARGET_RISCV64_UNKNOWN_LINUX_GNU_LINKER=riscv64-linux-gnu-gcc
|
||||
export CC_riscv64_unknown_linux_gnu=riscv64-linux-gnu-gcc
|
||||
export CXX_riscv64_unknown_linux_gnu=riscv64-linux-gnu-g++
|
||||
export PKG_CONFIG_PATH=/usr/lib/riscv64-linux-gnu/pkgconfig
|
||||
export PKG_CONFIG_ALLOW_CROSS=1
|
||||
else
|
||||
echo "Unknown target: $INPUT_TARGET" && exit 1
|
||||
fi
|
||||
|
||||
+51
-15
@@ -30,17 +30,6 @@ jobs:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Apply Patch
|
||||
if: matrix.target == 'aarch64-pc-windows-msvc'
|
||||
run: |
|
||||
git config --global user.email "clash-verge-rev@github.io"
|
||||
git config --global user.name "clash-verge-rev"
|
||||
git am patches/support-windows-aarch64.patch
|
||||
|
||||
- name: Init Submodule
|
||||
if: matrix.target == 'aarch64-pc-windows-msvc'
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: Install Rust Stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
@@ -57,7 +46,7 @@ jobs:
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
- uses: pnpm/action-setup@v3
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 8
|
||||
@@ -100,6 +89,8 @@ jobs:
|
||||
target: x86_64-unknown-linux-gnu
|
||||
- os: ubuntu-latest
|
||||
target: aarch64-unknown-linux-gnu
|
||||
- os: ubuntu-latest
|
||||
target: armv7-unknown-linux-gnueabihf
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
@@ -112,30 +103,56 @@ jobs:
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- name: Get Version
|
||||
run: |
|
||||
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
|
||||
|
||||
- run: |
|
||||
cat > release.txt << 'EOF'
|
||||
### 我应该下载哪个版本?
|
||||
|
||||
- Windows x86_64架构: x64-setup.exe (不支持win7)
|
||||
- Windows arm64架构: arm64-setup.exe
|
||||
- MacOS intel芯片: x64.dmg
|
||||
- MacOS apple M芯片: aarch64.dmg (提示文件损坏看下面FAQ)
|
||||
- Linux x64架构: amd64.AppImage/amd64.deb
|
||||
- Linux arm64架构: arm64.deb
|
||||
- Linux armv7架构: armhf.deb
|
||||
- Windows 便携板 x86_64架构: x64_portable.zip (不推荐使用,无法自动更新)
|
||||
- Windows 便携板 arm64架构: arm64_portable.zip (不推荐使用,无法自动更新)
|
||||
|
||||
### FAQ
|
||||
|
||||
- [https://clash-verge-rev.github.io/faq.html](https://clash-verge-rev.github.io/faq.html)
|
||||
|
||||
Created at ${{ env.BUILDTIME }}.
|
||||
EOF
|
||||
|
||||
- name: Upload Release
|
||||
if: startsWith(matrix.target, 'x86_64')
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: alpha
|
||||
name: "Clash Verge Rev Alpha"
|
||||
body: "More new features are now supported."
|
||||
body_path: release.txt
|
||||
prerelease: true
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
files: src-tauri/target/${{ matrix.target }}/release/bundle/appimage/*.AppImage*
|
||||
|
||||
- name: Upload Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: alpha
|
||||
name: "Clash Verge Rev Alpha"
|
||||
body: "More new features are now supported."
|
||||
body_path: release.txt
|
||||
prerelease: true
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
files: src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
|
||||
|
||||
update_tag:
|
||||
name: Update tag
|
||||
runs-on: ubuntu-latest
|
||||
@@ -143,21 +160,40 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set Env
|
||||
run: |
|
||||
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
- name: Update Tag
|
||||
uses: richardsimko/update-tag@v1
|
||||
with:
|
||||
tag_name: alpha
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- run: |
|
||||
cat > release.txt << 'EOF'
|
||||
## Clash Verge Rev Alpha
|
||||
### 我应该下载哪个版本?
|
||||
|
||||
- Windows x86_64架构: x64-setup.exe (不支持win7)
|
||||
- Windows arm64架构: arm64-setup.exe
|
||||
- MacOS intel芯片: x64.dmg
|
||||
- MacOS apple M芯片: aarch64.dmg (提示文件损坏看下面FAQ)
|
||||
- Linux x64架构: amd64.AppImage/amd64.deb
|
||||
- Linux arm64架构: arm64.deb
|
||||
- Linux armv7架构: armhf.deb
|
||||
- Windows 便携板 x86_64架构: x64_portable.zip (不推荐使用,无法自动更新)
|
||||
- Windows 便携板 arm64架构: arm64_portable.zip (不推荐使用,无法自动更新)
|
||||
|
||||
### FAQ
|
||||
|
||||
- [https://clash-verge-rev.github.io/faq.html](https://clash-verge-rev.github.io/faq.html)
|
||||
|
||||
Created at ${{ env.BUILDTIME }}.
|
||||
EOF
|
||||
|
||||
- name: Upload Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
|
||||
@@ -27,17 +27,6 @@ jobs:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Apply Patch
|
||||
if: matrix.target == 'aarch64-pc-windows-msvc'
|
||||
run: |
|
||||
git config --global user.email "clash-verge-rev@github.io"
|
||||
git config --global user.name "clash-verge-rev"
|
||||
git am patches/support-windows-aarch64.patch
|
||||
|
||||
- name: Init Submodule
|
||||
if: matrix.target == 'aarch64-pc-windows-msvc'
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: Install Rust Stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
@@ -54,7 +43,7 @@ jobs:
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
- uses: pnpm/action-setup@v3
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 8
|
||||
@@ -97,6 +86,8 @@ jobs:
|
||||
target: x86_64-unknown-linux-gnu
|
||||
- os: ubuntu-latest
|
||||
target: aarch64-unknown-linux-gnu
|
||||
- os: ubuntu-latest
|
||||
target: armv7-unknown-linux-gnueabihf
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
@@ -109,11 +100,13 @@ jobs:
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- name: Get Version
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install jq
|
||||
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload Release
|
||||
if: startsWith(matrix.target, 'x86_64')
|
||||
uses: softprops/action-gh-release@v1
|
||||
@@ -123,6 +116,7 @@ jobs:
|
||||
body: "More new features are now supported."
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
files: src-tauri/target/${{ matrix.target }}/release/bundle/appimage/*.AppImage*
|
||||
|
||||
- name: Upload Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
|
||||
@@ -6,3 +6,4 @@ dist-ssr
|
||||
update.json
|
||||
scripts/_env.sh
|
||||
.vscode
|
||||
.tool-version
|
||||
@@ -0,0 +1 @@
|
||||
nodejs 21.7.1
|
||||
@@ -9,6 +9,10 @@
|
||||
A Clash Meta GUI based on <a href="https://github.com/tauri-apps/tauri">Tauri</a>.
|
||||
</h3>
|
||||
|
||||
## Preview
|
||||
|
||||

|
||||
|
||||
## Install
|
||||
|
||||
请到发布页面下载对应的安装包:[Release page](https://github.com/clash-verge-rev/clash-verge-rev/releases)<br>
|
||||
@@ -43,10 +47,6 @@ Supports Windows (x64/x86), Linux (x64/arm64) and macOS 10.15+ (intel/apple).
|
||||
- Built-in support [Clash.Meta(mihomo)](https://github.com/MetaCubeX/mihomo) core.
|
||||
- System proxy setting and guard.
|
||||
|
||||
## Preview
|
||||
|
||||

|
||||
|
||||
### FAQ
|
||||
|
||||
Refer to [Doc FAQ Page](https://clash-verge-rev.github.io/faq.html)
|
||||
|
||||
@@ -1,3 +1,66 @@
|
||||
## v1.5.11
|
||||
|
||||
### Features
|
||||
|
||||
- Meta(mihomo)内核更新 1.18.2
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
- 升级图标无法点击的问题
|
||||
- 卸载时检查安装目录是否为空
|
||||
- 代理界面图标重合的问题
|
||||
|
||||
---
|
||||
|
||||
## v1.5.10
|
||||
|
||||
### Features
|
||||
|
||||
- 优化 Linux 托盘菜单显示
|
||||
- 添加透明代理端口设置
|
||||
- 删除订阅前确认
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
- 删除 MacOS 程序坞图标
|
||||
- Windows 下 service 日志没有清理
|
||||
- MacOS 无法开启系统代理
|
||||
|
||||
---
|
||||
|
||||
## v1.5.9
|
||||
|
||||
### Features
|
||||
|
||||
- 缓存代理组图标
|
||||
- 使用`boa_engine` 代替 `rquickjs`
|
||||
- 支持 Linux armv7
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
- Windows 首次安装无法点击
|
||||
- Windows 触摸屏无法拖动
|
||||
- 规则列表 `REJECT-DROP` 颜色
|
||||
- MacOS Dock 栏不显示图标
|
||||
- MacOS 自定义字体无效
|
||||
- 避免使用空 UA 拉取订阅
|
||||
|
||||
---
|
||||
|
||||
## v1.5.8
|
||||
|
||||
### Features
|
||||
|
||||
- 优化 UI 细节
|
||||
- Linux 绘制窗口圆角
|
||||
- 开放 DevTools
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
- 修复 MacOS 下开启 Tun 内核崩溃的问题
|
||||
|
||||
---
|
||||
|
||||
## v1.5.7
|
||||
|
||||
### Features
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.7 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 576 KiB |
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "clash-verge",
|
||||
"version": "1.5.7",
|
||||
"version": "1.5.11",
|
||||
"license": "GPL-3.0-only",
|
||||
"scripts": {
|
||||
"dev": "tauri dev",
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
From 871c9a6d1ed014c93da2436a437df03734e9f76c Mon Sep 17 00:00:00 2001
|
||||
From: MystiPanda <mystipanda@proton.me>
|
||||
Date: Sun, 10 Dec 2023 19:47:45 +0800
|
||||
Subject: [PATCH] feat: Support windows aarch64
|
||||
|
||||
---
|
||||
.gitmodules | 3 +
|
||||
src-tauri/Cargo.toml | 2 +-
|
||||
src-tauri/quick-rs | 1 +
|
||||
src-tauri/src/enhance/script.rs | 130 +++++++++++++++++++-------------
|
||||
4 files changed, 81 insertions(+), 55 deletions(-)
|
||||
create mode 100644 .gitmodules
|
||||
create mode 160000 src-tauri/quick-rs
|
||||
|
||||
diff --git a/.gitmodules b/.gitmodules
|
||||
new file mode 100644
|
||||
index 0000000..2eda7e4
|
||||
--- /dev/null
|
||||
+++ b/.gitmodules
|
||||
@@ -0,0 +1,3 @@
|
||||
+[submodule "src-tauri/quick-rs"]
|
||||
+ path = src-tauri/quick-rs
|
||||
+ url = https://github.com/clash-verge-rev/quick-rs.git
|
||||
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
|
||||
index 2f1a3be..d67f6ed 100644
|
||||
--- a/src-tauri/Cargo.toml
|
||||
+++ b/src-tauri/Cargo.toml
|
||||
@@ -25,7 +25,6 @@ log4rs = "1"
|
||||
nanoid = "0.4"
|
||||
chrono = "0.4"
|
||||
sysinfo = "0.30"
|
||||
-rquickjs = "0.3" # 高版本不支持 Linux aarch64
|
||||
serde_json = "1.0"
|
||||
serde_yaml = "0.9"
|
||||
once_cell = "1.18"
|
||||
@@ -33,6 +32,7 @@ port_scanner = "0.1.5"
|
||||
delay_timer = "0.11.5"
|
||||
parking_lot = "0.12"
|
||||
percent-encoding = "2.3.1"
|
||||
+quick-rs = { path = "quick-rs" }
|
||||
window-shadows = { version = "0.2" }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
diff --git a/src-tauri/quick-rs b/src-tauri/quick-rs
|
||||
new file mode 160000
|
||||
index 0000000..78277c4
|
||||
--- /dev/null
|
||||
+++ b/src-tauri/quick-rs
|
||||
@@ -0,0 +1 @@
|
||||
+Subproject commit 78277c4509c64f18c0fc5c9f2b84671de7c83343
|
||||
diff --git a/src-tauri/src/enhance/script.rs b/src-tauri/src/enhance/script.rs
|
||||
index 30a922f..d47dc33 100644
|
||||
--- a/src-tauri/src/enhance/script.rs
|
||||
+++ b/src-tauri/src/enhance/script.rs
|
||||
@@ -3,61 +3,83 @@ use anyhow::Result;
|
||||
use serde_yaml::Mapping;
|
||||
|
||||
pub fn use_script(script: String, config: Mapping) -> Result<(Mapping, Vec<(String, String)>)> {
|
||||
- use rquickjs::{function::Func, Context, Runtime};
|
||||
- use std::sync::{Arc, Mutex};
|
||||
-
|
||||
- let runtime = Runtime::new().unwrap();
|
||||
- let context = Context::full(&runtime).unwrap();
|
||||
- let outputs = Arc::new(Mutex::new(vec![]));
|
||||
-
|
||||
- let copy_outputs = outputs.clone();
|
||||
- let result = context.with(|ctx| -> Result<Mapping> {
|
||||
- ctx.globals().set(
|
||||
- "__verge_log__",
|
||||
- Func::from(move |level: String, data: String| {
|
||||
- let mut out = copy_outputs.lock().unwrap();
|
||||
- out.push((level, data));
|
||||
- }),
|
||||
- )?;
|
||||
-
|
||||
- ctx.eval(
|
||||
- r#"var console = Object.freeze({
|
||||
- log(data){__verge_log__("log",JSON.stringify(data))},
|
||||
- info(data){__verge_log__("info",JSON.stringify(data))},
|
||||
- error(data){__verge_log__("error",JSON.stringify(data))},
|
||||
- debug(data){__verge_log__("debug",JSON.stringify(data))},
|
||||
- });"#,
|
||||
- )?;
|
||||
-
|
||||
- let config = use_lowercase(config.clone());
|
||||
- let config_str = serde_json::to_string(&config)?;
|
||||
-
|
||||
- let code = format!(
|
||||
- r#"try{{
|
||||
+ use quick_rs::{context::Context, function::Function, module::Module, runtime::Runtime};
|
||||
+
|
||||
+ let config = use_lowercase(config.clone());
|
||||
+ let config_str = serde_json::to_string(&config)?;
|
||||
+
|
||||
+ let runtime = Runtime::new();
|
||||
+ let context = Context::from(&runtime);
|
||||
+
|
||||
+ let code = format!(
|
||||
+ r#"
|
||||
+ let output = [];
|
||||
+
|
||||
+ function __verge_log__(type, data) {{
|
||||
+ output.push([type, data]);
|
||||
+ }}
|
||||
+
|
||||
+ var console = Object.freeze({{
|
||||
+ log(data) {{ __verge_log__("log", JSON.stringify(data)) }},
|
||||
+ info(data) {{ __verge_log__("info", JSON.stringify(data)) }},
|
||||
+ error(data) {{ __verge_log__("error", JSON.stringify(data)) }},
|
||||
+ debug(data) {{ __verge_log__("debug", JSON.stringify(data)) }},
|
||||
+ }});
|
||||
+
|
||||
{script};
|
||||
- JSON.stringify(main({config_str})||'')
|
||||
- }} catch(err) {{
|
||||
- `__error_flag__ ${{err.toString()}}`
|
||||
- }}"#
|
||||
- );
|
||||
- let result: String = ctx.eval(code.as_str())?;
|
||||
- if result.starts_with("__error_flag__") {
|
||||
- anyhow::bail!(result[15..].to_owned());
|
||||
- }
|
||||
- if result == "\"\"" {
|
||||
- anyhow::bail!("main function should return object");
|
||||
- }
|
||||
- Ok(serde_json::from_str::<Mapping>(result.as_str())?)
|
||||
- });
|
||||
-
|
||||
- let mut out = outputs.lock().unwrap();
|
||||
- match result {
|
||||
- Ok(config) => Ok((use_lowercase(config), out.to_vec())),
|
||||
- Err(err) => {
|
||||
- out.push(("exception".into(), err.to_string()));
|
||||
- Ok((config, out.to_vec()))
|
||||
- }
|
||||
- }
|
||||
+
|
||||
+ export function _main(){{
|
||||
+ try{{
|
||||
+ let result = JSON.stringify(main({config_str})||"");
|
||||
+ return JSON.stringify({{result, output}});
|
||||
+ }} catch(err) {{
|
||||
+ output.push(["exception", err.toString()]);
|
||||
+ return JSON.stringify({{result: "__error__", output}});
|
||||
+ }}
|
||||
+ }}
|
||||
+ "#
|
||||
+ );
|
||||
+ let value = context.eval_module(&code, "_main")?;
|
||||
+ let module = Module::new(value)?;
|
||||
+ let value = module.get("_main")?;
|
||||
+ let function = Function::new(value)?;
|
||||
+ let value = function.call(vec![])?;
|
||||
+ let result = serde_json::from_str::<serde_json::Value>(&value.to_string()?)?;
|
||||
+ result
|
||||
+ .as_object()
|
||||
+ .map(|obj| {
|
||||
+ let result = obj.get("result").unwrap().as_str().unwrap();
|
||||
+ let output = obj.get("output").unwrap();
|
||||
+
|
||||
+ let mut out = output
|
||||
+ .as_array()
|
||||
+ .unwrap()
|
||||
+ .iter()
|
||||
+ .map(|item| {
|
||||
+ let item = item.as_array().unwrap();
|
||||
+ (
|
||||
+ item[0].as_str().unwrap().into(),
|
||||
+ item[1].as_str().unwrap().into(),
|
||||
+ )
|
||||
+ })
|
||||
+ .collect::<Vec<_>>();
|
||||
+ if result.is_empty() {
|
||||
+ anyhow::bail!("main function should return object");
|
||||
+ }
|
||||
+ if result == "__error__" {
|
||||
+ return Ok((config, out.to_vec()));
|
||||
+ }
|
||||
+ let result = serde_json::from_str::<Mapping>(result);
|
||||
+
|
||||
+ match result {
|
||||
+ Ok(config) => Ok((use_lowercase(config), out.to_vec())),
|
||||
+ Err(err) => {
|
||||
+ out.push(("exception".into(), err.to_string()));
|
||||
+ Ok((config, out.to_vec()))
|
||||
+ }
|
||||
+ }
|
||||
+ })
|
||||
+ .unwrap_or_else(|| anyhow::bail!("Unknown result"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
--
|
||||
2.43.0.windows.1
|
||||
|
||||
@@ -21,6 +21,7 @@ const PLATFORM_MAP = {
|
||||
"i686-unknown-linux-gnu": "linux",
|
||||
"aarch64-unknown-linux-gnu": "linux",
|
||||
"armv7-unknown-linux-gnueabihf": "linux",
|
||||
"riscv64gc-unknown-linux-gnu": "linux",
|
||||
"loongarch64-unknown-linux-gnu": "linux",
|
||||
};
|
||||
const ARCH_MAP = {
|
||||
@@ -33,6 +34,7 @@ const ARCH_MAP = {
|
||||
"i686-unknown-linux-gnu": "ia32",
|
||||
"aarch64-unknown-linux-gnu": "arm64",
|
||||
"armv7-unknown-linux-gnueabihf": "arm",
|
||||
"riscv64gc-unknown-linux-gnu": "riscv64",
|
||||
"loongarch64-unknown-linux-gnu": "loong64",
|
||||
};
|
||||
|
||||
@@ -65,6 +67,7 @@ const META_ALPHA_MAP = {
|
||||
"linux-ia32": "mihomo-linux-386",
|
||||
"linux-arm64": "mihomo-linux-arm64",
|
||||
"linux-arm": "mihomo-linux-armv7",
|
||||
"linux-riscv64": "mihomo-linux-riscv64",
|
||||
"linux-loong64": "mihomo-linux-loong64",
|
||||
};
|
||||
|
||||
@@ -111,6 +114,7 @@ const META_MAP = {
|
||||
"linux-ia32": "mihomo-linux-386",
|
||||
"linux-arm64": "mihomo-linux-arm64",
|
||||
"linux-arm": "mihomo-linux-armv7",
|
||||
"linux-riscv64": "mihomo-linux-riscv64",
|
||||
"linux-loong64": "mihomo-linux-loong64",
|
||||
};
|
||||
|
||||
@@ -370,6 +374,16 @@ const resolveUninstall = () =>
|
||||
file: "uninstall-service.exe",
|
||||
downloadURL: `${SERVICE_URL}/uninstall-service.exe`,
|
||||
});
|
||||
const resolveSetDnsScript = () =>
|
||||
resolveResource({
|
||||
file: "set_dns.sh",
|
||||
downloadURL: `https://github.com/clash-verge-rev/set-dns-script/releases/download/script/set_dns.sh`,
|
||||
});
|
||||
const resolveUnSetDnsScript = () =>
|
||||
resolveResource({
|
||||
file: "unset_dns.sh",
|
||||
downloadURL: `https://github.com/clash-verge-rev/set-dns-script/releases/download/script/unset_dns.sh`,
|
||||
});
|
||||
const resolveMmdb = () =>
|
||||
resolveResource({
|
||||
file: "Country.mmdb",
|
||||
@@ -409,6 +423,8 @@ const tasks = [
|
||||
{ name: "service", func: resolveService, retry: 5, winOnly: true },
|
||||
{ name: "install", func: resolveInstall, retry: 5, winOnly: true },
|
||||
{ name: "uninstall", func: resolveUninstall, retry: 5, winOnly: true },
|
||||
{ name: "set_dns_script", func: resolveSetDnsScript, retry: 5 },
|
||||
{ name: "unset_dns_script", func: resolveUnSetDnsScript, retry: 5 },
|
||||
{ name: "mmdb", func: resolveMmdb, retry: 5 },
|
||||
{ name: "geosite", func: resolveGeosite, retry: 5 },
|
||||
{ name: "geoip", func: resolveGeoIP, retry: 5 },
|
||||
|
||||
Generated
+1009
-173
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "clash-verge"
|
||||
version = "1.5.7"
|
||||
version = "1.5.11"
|
||||
description = "clash verge"
|
||||
authors = ["zzzgydi", "wonfen", "MystiPanda"]
|
||||
license = "GPL-3.0-only"
|
||||
@@ -14,18 +14,16 @@ tauri-build = { version = "1", features = [] }
|
||||
|
||||
[dependencies]
|
||||
warp = "0.3"
|
||||
which = "6.0.0"
|
||||
anyhow = "1.0"
|
||||
dirs = "5.0"
|
||||
open = "5.0"
|
||||
log = "0.4"
|
||||
ctrlc = "3.4"
|
||||
dunce = "1.0"
|
||||
log4rs = "1"
|
||||
nanoid = "0.4"
|
||||
chrono = "0.4"
|
||||
sysinfo = "0.30"
|
||||
rquickjs = "0.3" # 高版本不支持 Linux aarch64
|
||||
boa_engine = "0.18"
|
||||
serde_json = "1.0"
|
||||
serde_yaml = "0.9"
|
||||
once_cell = "1.18"
|
||||
@@ -36,13 +34,13 @@ percent-encoding = "2.3.1"
|
||||
window-shadows = { version = "0.2" }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
reqwest = { version = "0.11", features = ["json", "rustls-tls"] }
|
||||
reqwest = { version = "0.12", features = ["json", "rustls-tls"] }
|
||||
sysproxy = { git="https://github.com/zzzgydi/sysproxy-rs", branch = "main" }
|
||||
auto-launch = { git="https://github.com/zzzgydi/auto-launch", branch = "main" }
|
||||
tauri = { version = "1.5", features = [ "path-all", "protocol-asset", "dialog-open", "notification-all", "icon-png", "clipboard-all", "global-shortcut-all", "process-all", "shell-all", "system-tray", "updater", "window-all"] }
|
||||
tauri = { version = "1.6", features = [ "path-all", "protocol-asset", "dialog-open", "notification-all", "icon-png", "clipboard-all", "global-shortcut-all", "process-all", "shell-all", "system-tray", "updater", "window-all", "devtools"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
runas = "=1.0.0" # 高版本会返回错误 Status
|
||||
runas = "=1.2.0"
|
||||
deelevate = "0.2.0"
|
||||
winreg = "0.52.0"
|
||||
|
||||
|
||||
+29
-1
@@ -9,7 +9,7 @@ use anyhow::{Context, Result};
|
||||
use serde_yaml::Mapping;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use sysproxy::Sysproxy;
|
||||
use tauri::api;
|
||||
use tauri::{api, Manager};
|
||||
type CmdResult<T = ()> = Result<T, String>;
|
||||
|
||||
#[tauri::command]
|
||||
@@ -275,6 +275,23 @@ pub fn get_app_dir() -> CmdResult<String> {
|
||||
Ok(app_home_dir)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String> {
|
||||
let icon_cache_dir = wrap_err!(dirs::app_home_dir())?.join("icons").join("cache");
|
||||
let icon_path = icon_cache_dir.join(name);
|
||||
if !icon_cache_dir.exists() {
|
||||
let _ = std::fs::create_dir_all(&icon_cache_dir);
|
||||
}
|
||||
if !icon_path.exists() {
|
||||
let response = wrap_err!(reqwest::get(url).await)?;
|
||||
|
||||
let mut file = wrap_err!(std::fs::File::create(&icon_path))?;
|
||||
|
||||
let content = wrap_err!(response.bytes().await)?;
|
||||
wrap_err!(std::io::copy(&mut content.as_ref(), &mut file))?;
|
||||
}
|
||||
Ok(icon_path.to_string_lossy().to_string())
|
||||
}
|
||||
#[tauri::command]
|
||||
pub fn copy_icon_file(path: String, name: String) -> CmdResult<String> {
|
||||
let file_path = std::path::Path::new(&path);
|
||||
@@ -294,6 +311,17 @@ pub fn copy_icon_file(path: String, name: String) -> CmdResult<String> {
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn open_devtools(app_handle: tauri::AppHandle) {
|
||||
if let Some(window) = app_handle.get_window("main") {
|
||||
if !window.is_devtools_open() {
|
||||
window.open_devtools();
|
||||
} else {
|
||||
window.close_devtools();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn exit_app(app_handle: tauri::AppHandle) {
|
||||
let _ = resolve::save_window_size_position(&app_handle, true);
|
||||
|
||||
@@ -39,7 +39,10 @@ impl IClashTemp {
|
||||
tun.insert("auto-detect-interface".into(), true.into());
|
||||
tun.insert("dns-hijack".into(), vec!["any:53"].into());
|
||||
tun.insert("mtu".into(), 9000.into());
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
map.insert("redir-port".into(), 7895.into());
|
||||
#[cfg(target_os = "linux")]
|
||||
map.insert("tproxy-port".into(), 7896.into());
|
||||
map.insert("mixed-port".into(), 7897.into());
|
||||
map.insert("socks-port".into(), 7898.into());
|
||||
map.insert("port".into(), 7899.into());
|
||||
@@ -54,11 +57,18 @@ impl IClashTemp {
|
||||
}
|
||||
|
||||
fn guard(mut config: Mapping) -> Mapping {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let redir_port = Self::guard_redir_port(&config);
|
||||
#[cfg(target_os = "linux")]
|
||||
let tproxy_port = Self::guard_tproxy_port(&config);
|
||||
let mixed_port = Self::guard_mixed_port(&config);
|
||||
let socks_port = Self::guard_socks_port(&config);
|
||||
let port = Self::guard_port(&config);
|
||||
let ctrl = Self::guard_server_ctrl(&config);
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
config.insert("redir-port".into(), redir_port.into());
|
||||
#[cfg(target_os = "linux")]
|
||||
config.insert("tproxy-port".into(), tproxy_port.into());
|
||||
config.insert("mixed-port".into(), mixed_port.into());
|
||||
config.insert("socks-port".into(), socks_port.into());
|
||||
config.insert("port".into(), port.into());
|
||||
@@ -110,6 +120,37 @@ impl IClashTemp {
|
||||
}),
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub fn guard_redir_port(config: &Mapping) -> u16 {
|
||||
let mut port = config
|
||||
.get("redir-port")
|
||||
.and_then(|value| match value {
|
||||
Value::String(val_str) => val_str.parse().ok(),
|
||||
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(7895);
|
||||
if port == 0 {
|
||||
port = 7895;
|
||||
}
|
||||
port
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn guard_tproxy_port(config: &Mapping) -> u16 {
|
||||
let mut port = config
|
||||
.get("tproxy-port")
|
||||
.and_then(|value| match value {
|
||||
Value::String(val_str) => val_str.parse().ok(),
|
||||
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(7896);
|
||||
if port == 0 {
|
||||
port = 7896;
|
||||
}
|
||||
port
|
||||
}
|
||||
|
||||
pub fn guard_mixed_port(config: &Mapping) -> u16 {
|
||||
let mut port = config
|
||||
|
||||
@@ -126,7 +126,13 @@ pub struct IVerge {
|
||||
/// 是否启用随机端口
|
||||
pub enable_random_port: Option<bool>,
|
||||
|
||||
/// verge mixed port 用于覆盖 clash 的 mixed port
|
||||
/// verge 的各种 port 用于覆盖 clash 的各种 port
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub verge_redir_port: Option<u16>,
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub verge_tproxy_port: Option<u16>,
|
||||
|
||||
pub verge_mixed_port: Option<u16>,
|
||||
|
||||
pub verge_socks_port: Option<u16>,
|
||||
@@ -190,6 +196,10 @@ impl IVerge {
|
||||
enable_silent_start: Some(false),
|
||||
enable_system_proxy: Some(false),
|
||||
enable_random_port: Some(false),
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
verge_redir_port: Some(7895),
|
||||
#[cfg(target_os = "linux")]
|
||||
verge_tproxy_port: Some(7896),
|
||||
verge_mixed_port: Some(7897),
|
||||
verge_socks_port: Some(7898),
|
||||
verge_port: Some(7899),
|
||||
@@ -239,6 +249,10 @@ impl IVerge {
|
||||
patch!(enable_auto_launch);
|
||||
patch!(enable_silent_start);
|
||||
patch!(enable_random_port);
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
patch!(verge_redir_port);
|
||||
#[cfg(target_os = "linux")]
|
||||
patch!(verge_tproxy_port);
|
||||
patch!(verge_mixed_port);
|
||||
patch!(verge_socks_port);
|
||||
patch!(verge_port);
|
||||
|
||||
@@ -105,22 +105,6 @@ impl CoreManager {
|
||||
sleep(Duration::from_millis(500)).await;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let enable_tun = Config::verge().latest().enable_tun_mode.clone();
|
||||
let enable_tun = enable_tun.unwrap_or(false);
|
||||
log::debug!(target: "app", "try to set system dns");
|
||||
if enable_tun {
|
||||
let script = include_str!("./script/set_dns.sh");
|
||||
match (|| async { Command::new("bash").args([script]).output() })().await {
|
||||
Ok(_) => return Ok(()),
|
||||
Err(err) => {
|
||||
log::error!(target: "app", "{err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use super::win_service;
|
||||
@@ -263,22 +247,6 @@ impl CoreManager {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let enable_tun = Config::verge().latest().enable_tun_mode.clone();
|
||||
let enable_tun = enable_tun.unwrap_or(false);
|
||||
log::debug!(target: "app", "try to unset system dns");
|
||||
if enable_tun {
|
||||
let script = include_str!("./script/unset_dns.sh");
|
||||
match (|| Command::new("bash").args([script]).output())() {
|
||||
Ok(_) => return Ok(()),
|
||||
Err(err) => {
|
||||
log::error!(target: "app", "{err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut sidecar = self.sidecar.lock();
|
||||
if let Some(child) = sidecar.take() {
|
||||
log::debug!(target: "app", "stop the core by sidecar");
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
nic=$(route -n get default | grep "interface" | awk '{print $2}')
|
||||
|
||||
hardware_port=$(networksetup -listallhardwareports | awk -v dev="$nic" '/Hardware Port/{port=$3} /Device:/{if ($2 == dev) {print port; exit}}')
|
||||
|
||||
networksetup -setdnsservers $hardware_port 223.5.5.5
|
||||
@@ -1,5 +0,0 @@
|
||||
nic=$(route -n get default | grep "interface" | awk '{print $2}')
|
||||
|
||||
hardware_port=$(networksetup -listallhardwareports | awk -v dev="$nic" '/Hardware Port/{port=$3} /Device:/{if ($2 == dev) {print port; exit}}')
|
||||
|
||||
networksetup -setdnsservers $hardware_port Empty
|
||||
@@ -130,6 +130,44 @@ impl Tray {
|
||||
let _ = tray.get_item("global_mode").set_selected(mode == "global");
|
||||
let _ = tray.get_item("direct_mode").set_selected(mode == "direct");
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
match mode.as_str() {
|
||||
"rule" => {
|
||||
let _ = tray
|
||||
.get_item("rule_mode")
|
||||
.set_title(t!("Rule Mode ✔", "规则模式 ✔"));
|
||||
let _ = tray
|
||||
.get_item("global_mode")
|
||||
.set_title(t!("Global Mode", "全局模式"));
|
||||
let _ = tray
|
||||
.get_item("direct_mode")
|
||||
.set_title(t!("Direct Mode", "直连模式"));
|
||||
}
|
||||
"global" => {
|
||||
let _ = tray
|
||||
.get_item("rule_mode")
|
||||
.set_title(t!("Rule Mode", "规则模式"));
|
||||
let _ = tray
|
||||
.get_item("global_mode")
|
||||
.set_title(t!("Global Mode ✔", "全局模式 ✔"));
|
||||
let _ = tray
|
||||
.get_item("direct_mode")
|
||||
.set_title(t!("Direct Mode", "直连模式"));
|
||||
}
|
||||
"direct" => {
|
||||
let _ = tray
|
||||
.get_item("rule_mode")
|
||||
.set_title(t!("Rule Mode", "规则模式"));
|
||||
let _ = tray
|
||||
.get_item("global_mode")
|
||||
.set_title(t!("Global Mode", "全局模式"));
|
||||
let _ = tray
|
||||
.get_item("direct_mode")
|
||||
.set_title(t!("Direct Mode ✔", "直连模式 ✔"));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let verge = Config::verge();
|
||||
let verge = verge.latest();
|
||||
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false);
|
||||
@@ -182,6 +220,27 @@ impl Tray {
|
||||
|
||||
let _ = tray.get_item("system_proxy").set_selected(*system_proxy);
|
||||
let _ = tray.get_item("tun_mode").set_selected(*tun_mode);
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if *system_proxy {
|
||||
let _ = tray
|
||||
.get_item("system_proxy")
|
||||
.set_title(t!("System Proxy ✔", "系统代理 ✔"));
|
||||
} else {
|
||||
let _ = tray
|
||||
.get_item("system_proxy")
|
||||
.set_title(t!("System Proxy", "系统代理"));
|
||||
}
|
||||
if *tun_mode {
|
||||
let _ = tray
|
||||
.get_item("tun_mode")
|
||||
.set_title(t!("TUN Mode ✔", "Tun 模式 ✔"));
|
||||
} else {
|
||||
let _ = tray
|
||||
.get_item("tun_mode")
|
||||
.set_title(t!("TUN Mode", "Tun 模式"));
|
||||
}
|
||||
}
|
||||
|
||||
let switch_map = {
|
||||
let mut map = std::collections::HashMap::new();
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use serde_yaml::{Mapping, Value};
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub const HANDLE_FIELDS: [&str; 9] = [
|
||||
pub const HANDLE_FIELDS: [&str; 11] = [
|
||||
"mode",
|
||||
"redir-port",
|
||||
"tproxy-port",
|
||||
"mixed-port",
|
||||
"socks-port",
|
||||
"port",
|
||||
|
||||
@@ -1,62 +1,75 @@
|
||||
use super::use_lowercase;
|
||||
use anyhow::Result;
|
||||
use anyhow::{Error, Result};
|
||||
use serde_yaml::Mapping;
|
||||
|
||||
pub fn use_script(script: String, config: Mapping) -> Result<(Mapping, Vec<(String, String)>)> {
|
||||
use rquickjs::{function::Func, Context, Runtime};
|
||||
use boa_engine::{native_function::NativeFunction, Context, JsValue, Source};
|
||||
use std::sync::{Arc, Mutex};
|
||||
let mut context = Context::default();
|
||||
|
||||
let runtime = Runtime::new().unwrap();
|
||||
let context = Context::full(&runtime).unwrap();
|
||||
let outputs = Arc::new(Mutex::new(vec![]));
|
||||
|
||||
let copy_outputs = outputs.clone();
|
||||
let result = context.with(|ctx| -> Result<Mapping> {
|
||||
ctx.globals().set(
|
||||
"__verge_log__",
|
||||
Func::from(move |level: String, data: String| {
|
||||
let mut out = copy_outputs.lock().unwrap();
|
||||
out.push((level, data));
|
||||
}),
|
||||
)?;
|
||||
|
||||
ctx.eval(
|
||||
r#"var console = Object.freeze({
|
||||
unsafe {
|
||||
let _ = context.register_global_builtin_callable(
|
||||
"__verge_log__".into(),
|
||||
2,
|
||||
NativeFunction::from_closure(
|
||||
move |_: &JsValue, args: &[JsValue], context: &mut Context| {
|
||||
let level = args.get(0).unwrap().to_string(context)?;
|
||||
let level = level.to_std_string().unwrap();
|
||||
let data = args.get(1).unwrap().to_string(context)?;
|
||||
let data = data.to_std_string().unwrap();
|
||||
let mut out = copy_outputs.lock().unwrap();
|
||||
out.push((level, data));
|
||||
Ok(JsValue::undefined())
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
let _ = context.eval(Source::from_bytes(
|
||||
r#"var console = Object.freeze({
|
||||
log(data){__verge_log__("log",JSON.stringify(data))},
|
||||
info(data){__verge_log__("info",JSON.stringify(data))},
|
||||
error(data){__verge_log__("error",JSON.stringify(data))},
|
||||
debug(data){__verge_log__("debug",JSON.stringify(data))},
|
||||
});"#,
|
||||
)?;
|
||||
));
|
||||
|
||||
let config = use_lowercase(config.clone());
|
||||
let config_str = serde_json::to_string(&config)?;
|
||||
let config = use_lowercase(config.clone());
|
||||
let config_str = serde_json::to_string(&config)?;
|
||||
|
||||
let code = format!(
|
||||
r#"try{{
|
||||
let code = format!(
|
||||
r#"try{{
|
||||
{script};
|
||||
JSON.stringify(main({config_str})||'')
|
||||
}} catch(err) {{
|
||||
`__error_flag__ ${{err.toString()}}`
|
||||
}}"#
|
||||
);
|
||||
let result: String = ctx.eval(code.as_str())?;
|
||||
);
|
||||
if let Ok(result) = context.eval(Source::from_bytes(code.as_str())) {
|
||||
if !result.is_string() {
|
||||
anyhow::bail!("main function should return object");
|
||||
}
|
||||
let result = result.to_string(&mut context).unwrap();
|
||||
let result = result.to_std_string().unwrap();
|
||||
if result.starts_with("__error_flag__") {
|
||||
anyhow::bail!(result[15..].to_owned());
|
||||
}
|
||||
if result == "\"\"" {
|
||||
anyhow::bail!("main function should return object");
|
||||
}
|
||||
Ok(serde_json::from_str::<Mapping>(result.as_str())?)
|
||||
});
|
||||
|
||||
let mut out = outputs.lock().unwrap();
|
||||
match result {
|
||||
Ok(config) => Ok((use_lowercase(config), out.to_vec())),
|
||||
Err(err) => {
|
||||
out.push(("exception".into(), err.to_string()));
|
||||
Ok((config, out.to_vec()))
|
||||
let res: Result<Mapping, Error> = Ok(serde_json::from_str::<Mapping>(result.as_str())?);
|
||||
let mut out = outputs.lock().unwrap();
|
||||
match res {
|
||||
Ok(config) => Ok((use_lowercase(config), out.to_vec())),
|
||||
Err(err) => {
|
||||
out.push(("exception".into(), err.to_string()));
|
||||
Ok((config, out.to_vec()))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
anyhow::bail!("main function should return object");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,8 +34,38 @@ pub fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
|
||||
revise!(config, "tun", tun_val);
|
||||
|
||||
if enable {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use crate::utils::dirs;
|
||||
use tauri::api::process::Command;
|
||||
log::info!(target: "app", "try to set system dns");
|
||||
let resource_dir = dirs::app_resources_dir().unwrap();
|
||||
let script = resource_dir.join("set_dns.sh");
|
||||
let script = script.to_string_lossy();
|
||||
match Command::new("bash").args([script]).output() {
|
||||
Ok(_) => log::info!(target: "app", "set system dns successfully"),
|
||||
Err(err) => {
|
||||
log::error!(target: "app", "set system dns failed: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
use_dns_for_tun(config)
|
||||
} else {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use crate::utils::dirs;
|
||||
use tauri::api::process::Command;
|
||||
log::info!(target: "app", "try to unset system dns");
|
||||
let resource_dir = dirs::app_resources_dir().unwrap();
|
||||
let script = resource_dir.join("unset_dns.sh");
|
||||
let script = script.to_string_lossy();
|
||||
match Command::new("bash").args([script]).output() {
|
||||
Ok(_) => log::info!(target: "app", "unset system dns successfully"),
|
||||
Err(err) => {
|
||||
log::error!(target: "app", "unset system dns failed: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
config
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +107,8 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> {
|
||||
Config::clash().draft().patch_config(patch.clone());
|
||||
|
||||
match {
|
||||
let redir_port = patch.get("redir-port");
|
||||
let tproxy_port = patch.get("tproxy-port");
|
||||
let mixed_port = patch.get("mixed-port");
|
||||
let socks_port = patch.get("socks-port");
|
||||
let port = patch.get("port");
|
||||
@@ -129,7 +131,9 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> {
|
||||
};
|
||||
|
||||
// 激活订阅
|
||||
if mixed_port.is_some()
|
||||
if redir_port.is_some()
|
||||
|| tproxy_port.is_some()
|
||||
|| mixed_port.is_some()
|
||||
|| socks_port.is_some()
|
||||
|| port.is_some()
|
||||
|| patch.get("secret").is_some()
|
||||
|
||||
@@ -57,6 +57,8 @@ fn main() -> std::io::Result<()> {
|
||||
cmds::test_delay,
|
||||
cmds::get_app_dir,
|
||||
cmds::copy_icon_file,
|
||||
cmds::download_icon_cache,
|
||||
cmds::open_devtools,
|
||||
cmds::exit_app,
|
||||
// cmds::update_hotkeys,
|
||||
// profile
|
||||
|
||||
@@ -135,6 +135,13 @@ pub fn delete_log() -> Result<()> {
|
||||
for file in fs::read_dir(&log_dir)?.flatten() {
|
||||
let _ = process_file(file);
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let service_log_dir = log_dir.join("service");
|
||||
for file in fs::read_dir(&service_log_dir)?.flatten() {
|
||||
let _ = process_file(file);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -141,10 +141,7 @@ pub fn create_window(app_handle: &AppHandle) {
|
||||
_ => {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
builder = builder
|
||||
.additional_browser_args("--enable-features=msWebView2EnableDraggableRegions")
|
||||
.inner_size(800.0, 636.0)
|
||||
.center();
|
||||
builder = builder.inner_size(800.0, 636.0).center();
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -161,6 +158,7 @@ pub fn create_window(app_handle: &AppHandle) {
|
||||
#[cfg(target_os = "windows")]
|
||||
let window = builder
|
||||
.decorations(false)
|
||||
.additional_browser_args("--enable-features=msWebView2EnableDraggableRegions --disable-features=OverscrollHistoryNavigation,msExperimentalScrolling")
|
||||
.transparent(true)
|
||||
.visible(false)
|
||||
.build();
|
||||
@@ -171,7 +169,7 @@ pub fn create_window(app_handle: &AppHandle) {
|
||||
.title_bar_style(tauri::TitleBarStyle::Overlay)
|
||||
.build();
|
||||
#[cfg(target_os = "linux")]
|
||||
let window = builder.decorations(true).transparent(false).build();
|
||||
let window = builder.decorations(false).transparent(true).build();
|
||||
|
||||
match window {
|
||||
Ok(win) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"package": {
|
||||
"productName": "Clash Verge",
|
||||
"version": "1.5.7"
|
||||
"version": "1.5.11"
|
||||
},
|
||||
"build": {
|
||||
"distDir": "../dist",
|
||||
|
||||
@@ -743,6 +743,30 @@ Function un.onInit
|
||||
|
||||
!insertmacro MUI_UNGETLANGUAGE
|
||||
FunctionEnd
|
||||
|
||||
Function un.isDirectoryEmpty
|
||||
Exch $0
|
||||
Push $1
|
||||
Push $2
|
||||
StrCpy $2 0
|
||||
FindFirst $1 $2 "$0\*.*"
|
||||
loop:
|
||||
StrCmp $2 "" done
|
||||
StrCmp $2 "." next
|
||||
StrCmp $2 ".." next
|
||||
StrCpy $0 0
|
||||
goto done
|
||||
next:
|
||||
FindNext $1 $2
|
||||
goto loop
|
||||
done:
|
||||
FindClose $1
|
||||
StrCmp $2 "" 0 +2
|
||||
StrCpy $0 1
|
||||
Pop $2
|
||||
Pop $1
|
||||
Exch $0
|
||||
FunctionEnd
|
||||
|
||||
Section Uninstall
|
||||
!insertmacro CheckIfAppIsRunning
|
||||
@@ -756,7 +780,7 @@ Section Uninstall
|
||||
{{#each resources}}
|
||||
Delete "$INSTDIR\\{{this.[1]}}"
|
||||
{{/each}}
|
||||
|
||||
Delete "$INSTDIR\resources"
|
||||
; Delete external binaries
|
||||
{{#each binaries}}
|
||||
Delete "$INSTDIR\\{{this}}"
|
||||
@@ -765,13 +789,14 @@ Section Uninstall
|
||||
; Delete uninstaller
|
||||
Delete "$INSTDIR\uninstall.exe"
|
||||
|
||||
${If} $DeleteAppDataCheckboxState == 1
|
||||
; Remove InstallDir
|
||||
Push "$INSTDIR"
|
||||
Call un.isDirectoryEmpty
|
||||
Pop $0
|
||||
${If} $0 == 1
|
||||
RMDir /R /REBOOTOK "$INSTDIR"
|
||||
${Else}
|
||||
{{#each resources_ancestors}}
|
||||
RMDir /REBOOTOK "$INSTDIR\\{{this}}"
|
||||
{{/each}}
|
||||
RMDir "$INSTDIR"
|
||||
MessageBox MB_OK "Install Directory is not Empty, Please remove it manually."
|
||||
${EndIf}
|
||||
|
||||
; Remove start menu shortcut
|
||||
|
||||
@@ -47,11 +47,11 @@ body {
|
||||
@import "./page.scss";
|
||||
@import "./font.scss";
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
background-color: rgba(18, 18, 18, 1);
|
||||
}
|
||||
}
|
||||
// @media (prefers-color-scheme: dark) {
|
||||
// :root {
|
||||
// background-color: rgba(18, 18, 18, 1);
|
||||
// }
|
||||
// }
|
||||
|
||||
.user-none {
|
||||
user-select: none;
|
||||
@@ -59,7 +59,3 @@ body {
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
[data-tauri-drag-region] {
|
||||
app-region: drag;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
width: 100%;
|
||||
// max-width: 225px;
|
||||
// min-width: 225px;
|
||||
padding: 16px 0 8px;
|
||||
// padding: 16px 0 8px;
|
||||
padding: 0px 0px 8px;
|
||||
// position: relative;
|
||||
flex-direction: column;
|
||||
align-self: stretch;
|
||||
@@ -32,12 +33,11 @@
|
||||
// width: 100%;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
padding: 0px 20px;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
// border-bottom: 1px solid var(--divider-color);
|
||||
// max-width: $maxLogo + 32px;
|
||||
// max-height: $maxLogo;
|
||||
// margin: 0 auto;
|
||||
@@ -60,7 +60,7 @@
|
||||
.the-newbtn {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 0px;
|
||||
top: 15px;
|
||||
border-radius: 8px;
|
||||
padding: 2px 4px;
|
||||
transform: scale(0.8);
|
||||
@@ -94,12 +94,16 @@
|
||||
// position: absolute;
|
||||
// top: 0px;
|
||||
// right: 0px;
|
||||
height: 24px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
// align-items: center;
|
||||
justify-content: end;
|
||||
box-sizing: border-box;
|
||||
z-index: 2;
|
||||
.the-dragbar {
|
||||
margin-top: 5px;
|
||||
app-region: drag;
|
||||
}
|
||||
}
|
||||
|
||||
.the-content {
|
||||
@@ -117,11 +121,17 @@
|
||||
.unknown {
|
||||
&.layout {
|
||||
.layout__left {
|
||||
padding-top: 24px;
|
||||
// padding-top: 24px;
|
||||
}
|
||||
|
||||
.layout__left .the-logo {
|
||||
flex: 1 0 58px;
|
||||
margin-top: 10px;
|
||||
margin-left: 10px;
|
||||
padding-top: 30px;
|
||||
padding-left: 10px;
|
||||
padding-right: 20px;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.layout__right .the-content {
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
} from "@mui/material";
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
title: string;
|
||||
message: string;
|
||||
onClose: () => void;
|
||||
onConfirm: () => void;
|
||||
}
|
||||
|
||||
export const ConfirmViewer = (props: Props) => {
|
||||
const { open, title, message, onClose, onConfirm } = props;
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="xs" fullWidth>
|
||||
<DialogTitle>{t(title)}</DialogTitle>
|
||||
|
||||
<DialogContent sx={{ width: "95%", pb: 1, userSelect: "text" }}>
|
||||
{t(message)}
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} variant="outlined">
|
||||
{t("Cancel")}
|
||||
</Button>
|
||||
<Button onClick={onConfirm} variant="contained">
|
||||
{t("Confirm")}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -23,6 +23,7 @@ import { Notice } from "@/components/base";
|
||||
import { EditorViewer } from "./editor-viewer";
|
||||
import { ProfileBox } from "./profile-box";
|
||||
import parseTraffic from "@/utils/parse-traffic";
|
||||
import { ConfirmViewer } from "./confirm-viewer";
|
||||
|
||||
const round = keyframes`
|
||||
from { transform: rotate(0deg); }
|
||||
@@ -92,6 +93,7 @@ export const ProfileItem = (props: Props) => {
|
||||
}, [hasUrl, updated]);
|
||||
|
||||
const [fileOpen, setFileOpen] = useState(false);
|
||||
const [confirmOpen, setConfirmOpen] = useState(false);
|
||||
|
||||
const onEditInfo = () => {
|
||||
setAnchorEl(null);
|
||||
@@ -171,14 +173,26 @@ export const ProfileItem = (props: Props) => {
|
||||
{ label: "Open File", handler: onOpenFile },
|
||||
{ label: "Update", handler: () => onUpdate(0) },
|
||||
{ label: "Update(Proxy)", handler: () => onUpdate(2) },
|
||||
{ label: "Delete", handler: onDelete },
|
||||
{
|
||||
label: "Delete",
|
||||
handler: () => {
|
||||
setAnchorEl(null);
|
||||
setConfirmOpen(true);
|
||||
},
|
||||
},
|
||||
];
|
||||
const fileModeMenu = [
|
||||
{ label: "Select", handler: onForceSelect },
|
||||
{ label: "Edit Info", handler: onEditInfo },
|
||||
{ label: "Edit File", handler: onEditFile },
|
||||
{ label: "Open File", handler: onOpenFile },
|
||||
{ label: "Delete", handler: onDelete },
|
||||
{
|
||||
label: "Delete",
|
||||
handler: () => {
|
||||
setAnchorEl(null);
|
||||
setConfirmOpen(true);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const boxStyle = {
|
||||
@@ -243,6 +257,7 @@ export const ProfileItem = (props: Props) => {
|
||||
|
||||
<Typography
|
||||
width="calc(100% - 36px)"
|
||||
sx={{ fontSize: "18px", fontWeight: "600", lineHeight: "26px" }}
|
||||
variant="h6"
|
||||
component="h2"
|
||||
noWrap
|
||||
@@ -279,7 +294,11 @@ export const ProfileItem = (props: Props) => {
|
||||
{
|
||||
<>
|
||||
{description ? (
|
||||
<Typography noWrap title={description}>
|
||||
<Typography
|
||||
noWrap
|
||||
title={description}
|
||||
sx={{ fontSize: "14px" }}
|
||||
>
|
||||
{description}
|
||||
</Typography>
|
||||
) : (
|
||||
@@ -312,7 +331,7 @@ export const ProfileItem = (props: Props) => {
|
||||
<span title="Expire Time">{expire}</span>
|
||||
</Box>
|
||||
) : (
|
||||
<Box sx={{ ...boxStyle, fontSize: 14, justifyContent: "flex-end" }}>
|
||||
<Box sx={{ ...boxStyle, fontSize: 12, justifyContent: "flex-end" }}>
|
||||
<span title="Updated Time">{parseExpire(updated)}</span>
|
||||
</Box>
|
||||
)}
|
||||
@@ -336,7 +355,19 @@ export const ProfileItem = (props: Props) => {
|
||||
<MenuItem
|
||||
key={item.label}
|
||||
onClick={item.handler}
|
||||
sx={{ minWidth: 120 }}
|
||||
sx={[
|
||||
{
|
||||
minWidth: 120,
|
||||
},
|
||||
(theme) => {
|
||||
return {
|
||||
color:
|
||||
item.label === "Delete"
|
||||
? theme.palette.error.main
|
||||
: undefined,
|
||||
};
|
||||
},
|
||||
]}
|
||||
dense
|
||||
>
|
||||
{t(item.label)}
|
||||
@@ -350,6 +381,16 @@ export const ProfileItem = (props: Props) => {
|
||||
mode="yaml"
|
||||
onClose={() => setFileOpen(false)}
|
||||
/>
|
||||
<ConfirmViewer
|
||||
title="Confirm deletion"
|
||||
message="This operation is not reversible"
|
||||
open={confirmOpen}
|
||||
onClose={() => setConfirmOpen(false)}
|
||||
onConfirm={() => {
|
||||
onDelete();
|
||||
setConfirmOpen(false);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@ import { Notice } from "@/components/base";
|
||||
import { EditorViewer } from "./editor-viewer";
|
||||
import { ProfileBox } from "./profile-box";
|
||||
import { LogViewer } from "./log-viewer";
|
||||
import { ConfirmViewer } from "./confirm-viewer";
|
||||
|
||||
interface Props {
|
||||
selected: boolean;
|
||||
@@ -51,6 +52,7 @@ export const ProfileMore = (props: Props) => {
|
||||
const [anchorEl, setAnchorEl] = useState<any>(null);
|
||||
const [position, setPosition] = useState({ left: 0, top: 0 });
|
||||
const [fileOpen, setFileOpen] = useState(false);
|
||||
const [confirmOpen, setConfirmOpen] = useState(false);
|
||||
const [logOpen, setLogOpen] = useState(false);
|
||||
|
||||
const onEditInfo = () => {
|
||||
@@ -87,7 +89,13 @@ export const ProfileMore = (props: Props) => {
|
||||
{ label: "Open File", handler: onOpenFile },
|
||||
{ label: "To Top", show: showMove, handler: fnWrapper(onMoveTop) },
|
||||
{ label: "To End", show: showMove, handler: fnWrapper(onMoveEnd) },
|
||||
{ label: "Delete", handler: fnWrapper(onDelete) },
|
||||
{
|
||||
label: "Delete",
|
||||
handler: () => {
|
||||
setAnchorEl(null);
|
||||
setConfirmOpen(true);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const disableMenu = [
|
||||
@@ -95,7 +103,13 @@ export const ProfileMore = (props: Props) => {
|
||||
{ label: "Edit Info", handler: onEditInfo },
|
||||
{ label: "Edit File", handler: onEditFile },
|
||||
{ label: "Open File", handler: onOpenFile },
|
||||
{ label: "Delete", handler: fnWrapper(onDelete) },
|
||||
{
|
||||
label: "Delete",
|
||||
handler: () => {
|
||||
setAnchorEl(null);
|
||||
setConfirmOpen(true);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const boxStyle = {
|
||||
@@ -200,7 +214,17 @@ export const ProfileMore = (props: Props) => {
|
||||
<MenuItem
|
||||
key={item.label}
|
||||
onClick={item.handler}
|
||||
sx={{ minWidth: 120 }}
|
||||
sx={[
|
||||
{ minWidth: 120 },
|
||||
(theme) => {
|
||||
return {
|
||||
color:
|
||||
item.label === "Delete"
|
||||
? theme.palette.error.main
|
||||
: undefined,
|
||||
};
|
||||
},
|
||||
]}
|
||||
dense
|
||||
>
|
||||
{t(item.label)}
|
||||
@@ -214,7 +238,16 @@ export const ProfileMore = (props: Props) => {
|
||||
mode={type === "merge" ? "yaml" : "javascript"}
|
||||
onClose={() => setFileOpen(false)}
|
||||
/>
|
||||
|
||||
<ConfirmViewer
|
||||
title="Confirm deletion"
|
||||
message="This operation is not reversible"
|
||||
open={confirmOpen}
|
||||
onClose={() => setConfirmOpen(false)}
|
||||
onConfirm={() => {
|
||||
onDelete();
|
||||
setConfirmOpen(false);
|
||||
}}
|
||||
/>
|
||||
{selected && (
|
||||
<LogViewer
|
||||
open={logOpen}
|
||||
|
||||
@@ -51,7 +51,6 @@ export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
|
||||
desc: "",
|
||||
url: "",
|
||||
option: {
|
||||
// user_agent: "",
|
||||
with_proxy: false,
|
||||
self_proxy: false,
|
||||
},
|
||||
@@ -99,6 +98,9 @@ export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
|
||||
if (form.option?.update_interval) {
|
||||
form.option.update_interval = +form.option.update_interval;
|
||||
}
|
||||
if (form.option?.user_agent === "") {
|
||||
delete form.option.user_agent;
|
||||
}
|
||||
const name = form.name || `${form.type} file`;
|
||||
const item = { ...form, name };
|
||||
|
||||
|
||||
@@ -19,6 +19,9 @@ import type { IRenderItem } from "./use-render-list";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { useRecoilState } from "recoil";
|
||||
import { atomThemeMode } from "@/services/states";
|
||||
import { useEffect, useState } from "react";
|
||||
import { convertFileSrc } from "@tauri-apps/api/tauri";
|
||||
import { downloadIconCache } from "@/services/cmds";
|
||||
|
||||
interface RenderProps {
|
||||
item: IRenderItem;
|
||||
@@ -38,6 +41,24 @@ export const ProxyRender = (props: RenderProps) => {
|
||||
const [mode] = useRecoilState(atomThemeMode);
|
||||
const isDark = mode === "light" ? false : true;
|
||||
const itembackgroundcolor = isDark ? "#282A36" : "#ffffff";
|
||||
const [iconCachePath, setIconCachePath] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
initIconCachePath();
|
||||
}, [group]);
|
||||
|
||||
async function initIconCachePath() {
|
||||
if (group.icon && group.icon.trim().startsWith("http")) {
|
||||
const fileName =
|
||||
group.name.replaceAll(" ", "") + "-" + getFileName(group.icon);
|
||||
const iconPath = await downloadIconCache(group.icon, fileName);
|
||||
setIconCachePath(convertFileSrc(iconPath));
|
||||
}
|
||||
}
|
||||
|
||||
function getFileName(url: string) {
|
||||
return url.substring(url.lastIndexOf("/") + 1);
|
||||
}
|
||||
|
||||
if (type === 0 && !group.hidden) {
|
||||
return (
|
||||
@@ -55,8 +76,8 @@ export const ProxyRender = (props: RenderProps) => {
|
||||
group.icon &&
|
||||
group.icon.trim().startsWith("http") && (
|
||||
<img
|
||||
src={group.icon}
|
||||
height="32px"
|
||||
src={iconCachePath === "" ? group.icon : iconCachePath}
|
||||
width="32px"
|
||||
style={{ marginRight: "12px", borderRadius: "6px" }}
|
||||
/>
|
||||
)}
|
||||
@@ -65,7 +86,7 @@ export const ProxyRender = (props: RenderProps) => {
|
||||
group.icon.trim().startsWith("data") && (
|
||||
<img
|
||||
src={group.icon}
|
||||
height="32px"
|
||||
width="32px"
|
||||
style={{ marginRight: "12px", borderRadius: "6px" }}
|
||||
/>
|
||||
)}
|
||||
@@ -74,7 +95,7 @@ export const ProxyRender = (props: RenderProps) => {
|
||||
group.icon.trim().startsWith("<svg") && (
|
||||
<img
|
||||
src={`data:image/svg+xml;base64,${btoa(group.icon)}`}
|
||||
height="32px"
|
||||
width="32px"
|
||||
/>
|
||||
)}
|
||||
<ListItemText
|
||||
|
||||
@@ -20,7 +20,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const parseColor = (text: string) => {
|
||||
if (text === "REJECT") return "error.main";
|
||||
if (text === "REJECT" || text === "REJECT-DROP") return "error.main";
|
||||
if (text === "DIRECT") return "text.primary";
|
||||
|
||||
let sum = 0;
|
||||
|
||||
@@ -5,6 +5,8 @@ import { List, ListItem, ListItemText, TextField } from "@mui/material";
|
||||
import { useClashInfo } from "@/hooks/use-clash";
|
||||
import { BaseDialog, DialogRef, Notice } from "@/components/base";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import getSystem from "@/utils/get-system";
|
||||
const OS = getSystem();
|
||||
|
||||
export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -13,6 +15,12 @@ export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
const { verge, patchVerge } = useVerge();
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [redirPort, setRedirPort] = useState(
|
||||
verge?.verge_redir_port ?? clashInfo?.redir_port ?? 7895
|
||||
);
|
||||
const [tproxyPort, setTproxyPort] = useState(
|
||||
verge?.verge_tproxy_port ?? clashInfo?.tproxy_port ?? 7896
|
||||
);
|
||||
const [mixedPort, setMixedPort] = useState(
|
||||
verge?.verge_mixed_port ?? clashInfo?.mixed_port ?? 7897
|
||||
);
|
||||
@@ -25,6 +33,8 @@ export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
open: () => {
|
||||
if (verge?.verge_redir_port) setRedirPort(verge?.verge_redir_port);
|
||||
if (verge?.verge_tproxy_port) setTproxyPort(verge?.verge_tproxy_port);
|
||||
if (verge?.verge_mixed_port) setMixedPort(verge?.verge_mixed_port);
|
||||
if (verge?.verge_socks_port) setSocksPort(verge?.verge_socks_port);
|
||||
if (verge?.verge_port) setPort(verge?.verge_port);
|
||||
@@ -35,6 +45,8 @@ export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
|
||||
const onSave = useLockFn(async () => {
|
||||
if (
|
||||
redirPort === verge?.verge_redir_port &&
|
||||
tproxyPort === verge?.verge_tproxy_port &&
|
||||
mixedPort === verge?.verge_mixed_port &&
|
||||
socksPort === verge?.verge_socks_port &&
|
||||
port === verge?.verge_port
|
||||
@@ -42,7 +54,34 @@ export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
setOpen(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
OS === "linux" &&
|
||||
new Set([redirPort, tproxyPort, mixedPort, socksPort, port]).size !== 5
|
||||
) {
|
||||
Notice.error("Port conflict!", 4000);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
OS === "macos" &&
|
||||
new Set([redirPort, mixedPort, socksPort, port]).size !== 4
|
||||
) {
|
||||
Notice.error("Port conflict!", 4000);
|
||||
return;
|
||||
}
|
||||
if (OS === "windows" && new Set([mixedPort, socksPort, port]).size !== 3) {
|
||||
Notice.error("Port conflict!", 4000);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (OS !== "windows") {
|
||||
await patchInfo({ "redir-port": redirPort });
|
||||
await patchVerge({ verge_redir_port: redirPort });
|
||||
}
|
||||
if (OS === "linux") {
|
||||
await patchInfo({ "tproxy-port": tproxyPort });
|
||||
await patchVerge({ verge_tproxy_port: tproxyPort });
|
||||
}
|
||||
await patchInfo({ "mixed-port": mixedPort });
|
||||
await patchInfo({ "socks-port": socksPort });
|
||||
await patchInfo({ port });
|
||||
@@ -68,6 +107,35 @@ export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
onOk={onSave}
|
||||
>
|
||||
<List>
|
||||
{OS !== "windows" && (
|
||||
<ListItem sx={{ padding: "5px 2px" }}>
|
||||
<ListItemText primary="Redir Port" />
|
||||
<TextField
|
||||
size="small"
|
||||
autoComplete="off"
|
||||
sx={{ width: 135 }}
|
||||
value={redirPort}
|
||||
onChange={(e) =>
|
||||
setRedirPort(+e.target.value?.replace(/\D+/, "").slice(0, 5))
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
)}
|
||||
{OS === "linux" && (
|
||||
<ListItem sx={{ padding: "5px 2px" }}>
|
||||
<ListItemText primary="Tproxy Port" />
|
||||
<TextField
|
||||
size="small"
|
||||
autoComplete="off"
|
||||
sx={{ width: 135 }}
|
||||
value={tproxyPort}
|
||||
onChange={(e) =>
|
||||
setTproxyPort(+e.target.value?.replace(/\D+/, "").slice(0, 5))
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
)}
|
||||
|
||||
<ListItem sx={{ padding: "5px 2px" }}>
|
||||
<ListItemText primary="Mixed Port" />
|
||||
<TextField
|
||||
|
||||
@@ -19,7 +19,6 @@ export const LayoutViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
const [sysproxyIcon, setSysproxyIcon] = useState("");
|
||||
const [tunIcon, setTunIcon] = useState("");
|
||||
|
||||
// const { menu_icon } = verge ?? {};
|
||||
useEffect(() => {
|
||||
initIconPath();
|
||||
}, []);
|
||||
|
||||
@@ -10,7 +10,13 @@ import {
|
||||
Input,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { exitApp, openAppDir, openCoreDir, openLogsDir } from "@/services/cmds";
|
||||
import {
|
||||
exitApp,
|
||||
openAppDir,
|
||||
openCoreDir,
|
||||
openLogsDir,
|
||||
openDevTools,
|
||||
} from "@/services/cmds";
|
||||
import { ArrowForward } from "@mui/icons-material";
|
||||
import { checkUpdate } from "@tauri-apps/api/updater";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
@@ -27,6 +33,7 @@ import { LayoutViewer } from "./mods/layout-viewer";
|
||||
import { UpdateViewer } from "./mods/update-viewer";
|
||||
import getSystem from "@/utils/get-system";
|
||||
import { routers } from "@/pages/_routers";
|
||||
|
||||
interface Props {
|
||||
onError?: (err: Error) => void;
|
||||
}
|
||||
@@ -90,6 +97,7 @@ const SettingVerge = ({ onError }: Props) => {
|
||||
<MenuItem value="zh">中文</MenuItem>
|
||||
<MenuItem value="en">English</MenuItem>
|
||||
<MenuItem value="ru">Русский</MenuItem>
|
||||
<MenuItem value="fa">فارسی</MenuItem>
|
||||
</Select>
|
||||
</GuardState>
|
||||
</SettingItem>
|
||||
@@ -304,6 +312,17 @@ const SettingVerge = ({ onError }: Props) => {
|
||||
</IconButton>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem label={t("Open Dev Tools")}>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
size="small"
|
||||
sx={{ my: "2px" }}
|
||||
onClick={openDevTools}
|
||||
>
|
||||
<ArrowForward />
|
||||
</IconButton>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem label={t("Exit")}>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
|
||||
@@ -17,8 +17,9 @@ import { LanguageTwoTone } from "@mui/icons-material";
|
||||
import { Notice } from "@/components/base";
|
||||
import { TestBox } from "./test-box";
|
||||
import delayManager from "@/services/delay";
|
||||
import { cmdTestDelay } from "@/services/cmds";
|
||||
import { listen, Event, UnlistenFn } from "@tauri-apps/api/event";
|
||||
import { cmdTestDelay, downloadIconCache } from "@/services/cmds";
|
||||
import { listen, UnlistenFn } from "@tauri-apps/api/event";
|
||||
import { convertFileSrc } from "@tauri-apps/api/tauri";
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
@@ -39,6 +40,23 @@ export const TestItem = (props: Props) => {
|
||||
const [position, setPosition] = useState({ left: 0, top: 0 });
|
||||
const [delay, setDelay] = useState(-1);
|
||||
const { uid, name, icon, url } = itemData;
|
||||
const [iconCachePath, setIconCachePath] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
initIconCachePath();
|
||||
}, [icon]);
|
||||
|
||||
async function initIconCachePath() {
|
||||
if (icon && icon.trim().startsWith("http")) {
|
||||
const fileName = uid + "-" + getFileName(icon);
|
||||
const iconPath = await downloadIconCache(icon, fileName);
|
||||
setIconCachePath(convertFileSrc(iconPath));
|
||||
}
|
||||
}
|
||||
|
||||
function getFileName(url: string) {
|
||||
return url.substring(url.lastIndexOf("/") + 1);
|
||||
}
|
||||
|
||||
const onDelay = async () => {
|
||||
setDelay(-2);
|
||||
@@ -104,7 +122,10 @@ export const TestItem = (props: Props) => {
|
||||
{icon && icon.trim() !== "" ? (
|
||||
<Box sx={{ display: "flex", justifyContent: "center" }}>
|
||||
{icon.trim().startsWith("http") && (
|
||||
<img src={icon} height="40px" />
|
||||
<img
|
||||
src={iconCachePath === "" ? icon : iconCachePath}
|
||||
height="40px"
|
||||
/>
|
||||
)}
|
||||
{icon.trim().startsWith("data") && (
|
||||
<img src={icon} height="40px" />
|
||||
|
||||
+29
-1
@@ -49,11 +49,19 @@ export const useClashInfo = () => {
|
||||
patch: Partial<
|
||||
Pick<
|
||||
IConfigData,
|
||||
"port" | "socks-port" | "mixed-port" | "external-controller" | "secret"
|
||||
| "port"
|
||||
| "socks-port"
|
||||
| "mixed-port"
|
||||
| "redir-port"
|
||||
| "tproxy-port"
|
||||
| "external-controller"
|
||||
| "secret"
|
||||
>
|
||||
>
|
||||
) => {
|
||||
const hasInfo =
|
||||
patch["redir-port"] != null ||
|
||||
patch["tproxy-port"] != null ||
|
||||
patch["mixed-port"] != null ||
|
||||
patch["socks-port"] != null ||
|
||||
patch["port"] != null ||
|
||||
@@ -62,6 +70,26 @@ export const useClashInfo = () => {
|
||||
|
||||
if (!hasInfo) return;
|
||||
|
||||
if (patch["redir-port"]) {
|
||||
const port = patch["redir-port"];
|
||||
if (port < 1000) {
|
||||
throw new Error("The port should not < 1000");
|
||||
}
|
||||
if (port > 65536) {
|
||||
throw new Error("The port should not > 65536");
|
||||
}
|
||||
}
|
||||
|
||||
if (patch["tproxy-port"]) {
|
||||
const port = patch["tproxy-port"];
|
||||
if (port < 1000) {
|
||||
throw new Error("The port should not < 1000");
|
||||
}
|
||||
if (port > 65536) {
|
||||
throw new Error("The port should not > 65536");
|
||||
}
|
||||
}
|
||||
|
||||
if (patch["mixed-port"]) {
|
||||
const port = patch["mixed-port"];
|
||||
if (port < 1000) {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -45,6 +45,8 @@
|
||||
"Update All Profiles": "更新所有订阅",
|
||||
"View Runtime Config": "查看运行时订阅",
|
||||
"Reactivate Profiles": "重新激活订阅",
|
||||
"Confirm deletion": "确认删除",
|
||||
"This operation is not reversible": "此操作不可逆",
|
||||
|
||||
"Location": "当前节点",
|
||||
"Delay check": "延迟测试",
|
||||
@@ -123,6 +125,7 @@
|
||||
"Open Core Dir": "内核目录",
|
||||
"Open Logs Dir": "日志目录",
|
||||
"Check for Updates": "检查更新",
|
||||
"Open Dev Tools": "打开开发者工具",
|
||||
"Verge Version": "Verge 版本",
|
||||
"theme.light": "浅色",
|
||||
"theme.dark": "深色",
|
||||
@@ -137,6 +140,7 @@
|
||||
"Save": "保存",
|
||||
"Cancel": "取消",
|
||||
"Exit": "退出",
|
||||
"Confirm": "确认",
|
||||
|
||||
"Default": "默认",
|
||||
"Download Speed": "下载速度",
|
||||
|
||||
+18
-5
@@ -122,9 +122,17 @@ const Layout = () => {
|
||||
({ palette }) => ({
|
||||
bgcolor: palette.background.paper,
|
||||
}),
|
||||
OS === "linux"
|
||||
? {
|
||||
borderRadius: "8px",
|
||||
border: "2px solid var(--divider-color)",
|
||||
width: "calc(100vw - 4px)",
|
||||
height: "calc(100vh - 4px)",
|
||||
}
|
||||
: {},
|
||||
]}
|
||||
>
|
||||
<div className="layout__left" data-tauri-drag-region="true">
|
||||
<div className="layout__left">
|
||||
<div className="the-logo" data-tauri-drag-region="true">
|
||||
{!isDark ? <LogoSvg /> : <LogoSvg_dark />}
|
||||
{<UpdateButton className="the-newbtn" />}
|
||||
@@ -148,11 +156,16 @@ const Layout = () => {
|
||||
</div>
|
||||
|
||||
<div className="layout__right">
|
||||
{OS === "windows" && (
|
||||
<div className="the-bar" data-tauri-drag-region="true">
|
||||
<LayoutControl />
|
||||
{
|
||||
<div className="the-bar">
|
||||
<div
|
||||
className="the-dragbar"
|
||||
data-tauri-drag-region="true"
|
||||
style={{ width: "100%" }}
|
||||
></div>
|
||||
{OS !== "macos" && <LayoutControl />}
|
||||
</div>
|
||||
)}
|
||||
}
|
||||
|
||||
<TransitionGroup className="the-content">
|
||||
<CSSTransition
|
||||
|
||||
@@ -4,7 +4,7 @@ const OS = getSystem();
|
||||
// default theme setting
|
||||
export const defaultTheme = {
|
||||
primary_color: "#007AFF",
|
||||
secondary_color: "#FFCC00",
|
||||
secondary_color: "#fc9b76",
|
||||
primary_text: "#000000",
|
||||
secondary_text: "#3c3c4399",
|
||||
info_color: "#007AFF",
|
||||
@@ -12,8 +12,8 @@ export const defaultTheme = {
|
||||
warning_color: "#FF9500",
|
||||
success_color: "#06943D",
|
||||
background_color: "#f5f5f5",
|
||||
font_family: `-apple-system, BlinkMacSystemFont,"Microsoft YaHei UI", "Microsoft YaHei", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", ${
|
||||
OS === "windows" ? "twemoji mozilla" : ""
|
||||
font_family: `-apple-system, BlinkMacSystemFont,"Microsoft YaHei UI", "Microsoft YaHei", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji"${
|
||||
OS === "windows" ? ", twemoji mozilla" : ""
|
||||
}`,
|
||||
};
|
||||
|
||||
|
||||
@@ -200,10 +200,10 @@ const ConnectionsPage = () => {
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
height="calc(100% - 70px)"
|
||||
height="calc(100% - 65px)"
|
||||
sx={{
|
||||
userSelect: "text",
|
||||
margin: "12px",
|
||||
margin: "10px",
|
||||
borderRadius: "8px",
|
||||
bgcolor: isDark ? "#282a36" : "#ffffff",
|
||||
}}
|
||||
|
||||
+2
-2
@@ -110,9 +110,9 @@ const LogPage = () => {
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
height="calc(100% - 70px)"
|
||||
height="calc(100% - 65px)"
|
||||
sx={{
|
||||
margin: "12px",
|
||||
margin: "10px",
|
||||
borderRadius: "8px",
|
||||
bgcolor: isDark ? "#282a36" : "#ffffff",
|
||||
}}
|
||||
|
||||
+48
-21
@@ -2,7 +2,15 @@ import useSWR, { mutate } from "swr";
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
import { useLockFn } from "ahooks";
|
||||
import { useSetRecoilState } from "recoil";
|
||||
import { Box, Button, Grid, IconButton, Stack, TextField } from "@mui/material";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Grid,
|
||||
IconButton,
|
||||
Stack,
|
||||
TextField,
|
||||
Divider,
|
||||
} from "@mui/material";
|
||||
import {
|
||||
DndContext,
|
||||
closestCenter,
|
||||
@@ -46,6 +54,8 @@ import { ProfileMore } from "@/components/profile/profile-more";
|
||||
import { useProfiles } from "@/hooks/use-profiles";
|
||||
import { ConfigViewer } from "@/components/setting/mods/config-viewer";
|
||||
import { throttle } from "lodash-es";
|
||||
import { useRecoilState } from "recoil";
|
||||
import { atomThemeMode } from "@/services/states";
|
||||
|
||||
const ProfilePage = () => {
|
||||
const { t } = useTranslation();
|
||||
@@ -235,6 +245,11 @@ const ProfilePage = () => {
|
||||
const text = await navigator.clipboard.readText();
|
||||
if (text) setUrl(text);
|
||||
};
|
||||
const [mode] = useRecoilState(atomThemeMode);
|
||||
const islight = mode === "light" ? true : false;
|
||||
const dividercolor = islight
|
||||
? "rgba(0, 0, 0, 0.06)"
|
||||
: "rgba(255, 255, 255, 0.06)";
|
||||
|
||||
return (
|
||||
<BasePage
|
||||
@@ -323,6 +338,7 @@ const ProfilePage = () => {
|
||||
loading={loading}
|
||||
variant="contained"
|
||||
size="small"
|
||||
sx={{ borderRadius: "6px" }}
|
||||
onClick={onImport}
|
||||
>
|
||||
{t("Import")}
|
||||
@@ -330,6 +346,7 @@ const ProfilePage = () => {
|
||||
<Button
|
||||
variant="contained"
|
||||
size="small"
|
||||
sx={{ borderRadius: "6px" }}
|
||||
onClick={() => viewerRef.current?.create()}
|
||||
>
|
||||
{t("New")}
|
||||
@@ -341,7 +358,7 @@ const ProfilePage = () => {
|
||||
mb: 0.5,
|
||||
pl: "10px",
|
||||
mr: "10px",
|
||||
height: "calc(100% - 20px)",
|
||||
height: "calc(100% - 68px)",
|
||||
overflowY: "auto",
|
||||
}}
|
||||
>
|
||||
@@ -350,7 +367,7 @@ const ProfilePage = () => {
|
||||
collisionDetection={closestCenter}
|
||||
onDragEnd={onDragEnd}
|
||||
>
|
||||
<Box sx={{ mb: 4.5 }}>
|
||||
<Box sx={{ mb: 1.5 }}>
|
||||
<Grid container spacing={{ xs: 1, lg: 1 }}>
|
||||
<SortableContext
|
||||
items={regularItems.map((x) => {
|
||||
@@ -375,24 +392,34 @@ const ProfilePage = () => {
|
||||
</DndContext>
|
||||
|
||||
{enhanceItems.length > 0 && (
|
||||
<Grid container spacing={{ xs: 2, lg: 2 }}>
|
||||
{enhanceItems.map((item) => (
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} key={item.file}>
|
||||
<ProfileMore
|
||||
selected={!!chain.includes(item.uid)}
|
||||
itemData={item}
|
||||
enableNum={chain.length || 0}
|
||||
logInfo={chainLogs[item.uid]}
|
||||
onEnable={() => onEnable(item.uid)}
|
||||
onDisable={() => onDisable(item.uid)}
|
||||
onDelete={() => onDelete(item.uid)}
|
||||
onMoveTop={() => onMoveTop(item.uid)}
|
||||
onMoveEnd={() => onMoveEnd(item.uid)}
|
||||
onEdit={() => viewerRef.current?.edit(item)}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
<Divider
|
||||
variant="middle"
|
||||
flexItem
|
||||
sx={{ width: `calc(100% - 32px)`, borderColor: dividercolor }}
|
||||
></Divider>
|
||||
)}
|
||||
|
||||
{enhanceItems.length > 0 && (
|
||||
<Box sx={{ mt: 1.5 }}>
|
||||
<Grid container spacing={{ xs: 1, lg: 1 }}>
|
||||
{enhanceItems.map((item) => (
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} key={item.file}>
|
||||
<ProfileMore
|
||||
selected={!!chain.includes(item.uid)}
|
||||
itemData={item}
|
||||
enableNum={chain.length || 0}
|
||||
logInfo={chainLogs[item.uid]}
|
||||
onEnable={() => onEnable(item.uid)}
|
||||
onDisable={() => onDisable(item.uid)}
|
||||
onDelete={() => onDelete(item.uid)}
|
||||
onMoveTop={() => onMoveTop(item.uid)}
|
||||
onMoveEnd={() => onMoveEnd(item.uid)}
|
||||
onEdit={() => viewerRef.current?.edit(item)}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<ProfileViewer ref={viewerRef} onChange={() => mutateProfiles()} />
|
||||
|
||||
+2
-2
@@ -56,9 +56,9 @@ const RulesPage = () => {
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
height="calc(100% - 70px)"
|
||||
height="calc(100% - 65px)"
|
||||
sx={{
|
||||
margin: "12px",
|
||||
margin: "10px",
|
||||
borderRadius: "8px",
|
||||
bgcolor: isDark ? "#282a36" : "#ffffff",
|
||||
}}
|
||||
|
||||
+1
-1
@@ -134,7 +134,7 @@ const TestPage = () => {
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
pt: 1,
|
||||
pt: 1.25,
|
||||
mb: 0.5,
|
||||
px: "10px",
|
||||
}}
|
||||
|
||||
@@ -213,6 +213,10 @@ export async function getPortableFlag() {
|
||||
return invoke<boolean>("get_portable_flag");
|
||||
}
|
||||
|
||||
export async function openDevTools() {
|
||||
return invoke("open_devtools");
|
||||
}
|
||||
|
||||
export async function exitApp() {
|
||||
return invoke("exit_app");
|
||||
}
|
||||
@@ -223,3 +227,7 @@ export async function copyIconFile(
|
||||
) {
|
||||
return invoke<void>("copy_icon_file", { path, name });
|
||||
}
|
||||
|
||||
export async function downloadIconCache(url: string, name: string) {
|
||||
return invoke<string>("download_icon_cache", { url, name });
|
||||
}
|
||||
|
||||
@@ -3,16 +3,19 @@ import { initReactI18next } from "react-i18next";
|
||||
import en from "@/locales/en.json";
|
||||
import ru from "@/locales/ru.json";
|
||||
import zh from "@/locales/zh.json";
|
||||
import fa from "@/locales/fa.json";
|
||||
|
||||
const resources = {
|
||||
en: { translation: en },
|
||||
ru: { translation: ru },
|
||||
zh: { translation: zh },
|
||||
fa: { translation: fa },
|
||||
};
|
||||
|
||||
i18n.use(initReactI18next).init({
|
||||
resources,
|
||||
lng: "en",
|
||||
fallbackLng: "en",
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
|
||||
Vendored
+4
@@ -142,6 +142,8 @@ interface IClashInfo {
|
||||
// status: string;
|
||||
mixed_port?: number; // clash mixed port
|
||||
socks_port?: number; // clash socks port
|
||||
redir_port?: number; // clash redir port
|
||||
tproxy_port?: number; // clash tproxy port
|
||||
port?: number; // clash http port
|
||||
server?: string; // external-controller
|
||||
secret?: string;
|
||||
@@ -214,6 +216,8 @@ interface IVergeConfig {
|
||||
enable_random_port?: boolean;
|
||||
verge_mixed_port?: number;
|
||||
verge_socks_port?: number;
|
||||
verge_redir_port?: number;
|
||||
verge_tproxy_port?: number;
|
||||
verge_port?: number;
|
||||
enable_proxy_guard?: boolean;
|
||||
proxy_guard_duration?: number;
|
||||
|
||||
Reference in New Issue
Block a user