Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8387964bae | ||
|
|
926278617f | ||
|
|
1933737a0c | ||
|
|
b3e5288bde | ||
|
|
ed6e966b2f | ||
|
|
9315fe36b6 | ||
|
|
41fbf5ba36 | ||
|
|
40db481453 | ||
|
|
1b5e295744 | ||
|
|
b42a52f7f6 | ||
|
|
8268df84d0 | ||
|
|
eb6fa5f1f1 | ||
|
|
025c8856ed | ||
|
|
ed421445e9 | ||
|
|
0cda07106b | ||
|
|
f335941b62 | ||
|
|
f0719f8bde | ||
|
|
27fe28661c | ||
|
|
bb13b12de6 | ||
|
|
1117e28b2e | ||
|
|
0f48873c25 | ||
|
|
faf61b4af3 | ||
|
|
e29ce93d32 | ||
|
|
38ab68492e | ||
|
|
53b1576e5f | ||
|
|
2835e79973 | ||
|
|
8e4d7e989b | ||
|
|
603c6b826c | ||
|
|
51bcc77cb3 | ||
|
|
a30d07b924 | ||
|
|
11faa5fe39 | ||
|
|
ce8383b26f | ||
|
|
bd8f07c90a | ||
|
|
fee077bebd | ||
|
|
558f4d93ba | ||
|
|
e77d126349 | ||
|
|
cd8667d6de | ||
|
|
8a2d06d010 | ||
|
|
92741fc733 | ||
|
|
1cff162649 | ||
|
|
f59be465ea | ||
|
|
27305317ce | ||
|
|
8378320e50 | ||
|
|
86a69952db | ||
|
|
e212ebfdb9 | ||
|
|
f235125d48 | ||
|
|
8efa815eb9 | ||
|
|
e54d42576b | ||
|
|
1e18539862 | ||
|
|
673fdf4721 | ||
|
|
57d6bfba51 | ||
|
|
8fa23aecda | ||
|
|
f9c10533e8 | ||
|
|
c891c3723e | ||
|
|
a0625ef2e7 | ||
|
|
b2774abb54 | ||
|
|
fa089bbc40 | ||
|
|
8437d1763f | ||
|
|
0532b0f9df | ||
|
|
5f8eb853f0 | ||
|
|
9125a85382 | ||
|
|
5b3dc44c72 | ||
|
|
ee5147f111 | ||
|
|
f67137fd5e | ||
|
|
b67db4a896 | ||
|
|
067018828c | ||
|
|
e990530ada | ||
|
|
45f863a72a | ||
|
|
519febbeb2 | ||
|
|
854b1a4caf | ||
|
|
08eb95e73a | ||
|
|
66f8fbf08c | ||
|
|
86a30bbb29 | ||
|
|
966c453c27 | ||
|
|
c7bcaf0193 | ||
|
|
0ac91e36a0 | ||
|
|
6f1299ce9e | ||
|
|
83ca29d649 | ||
|
|
d0206044dc | ||
|
|
d4623e4175 | ||
|
|
68ef03377d | ||
|
|
d3f9d3d033 | ||
|
|
a6ccd04471 | ||
|
|
0fd8174e77 | ||
|
|
ebc63f479a | ||
|
|
570c39c20a | ||
|
|
1d57257cf5 | ||
|
|
82d7baee0b | ||
|
|
1d91e1f1f7 | ||
|
|
15e8894614 | ||
|
|
0ae96918e9 | ||
|
|
e970880059 | ||
|
|
69ae86aba8 | ||
|
|
8142e1be49 | ||
|
|
cbc7612451 | ||
|
|
09e6a7b7cc | ||
|
|
082e35668a | ||
|
|
c9e78c837b | ||
|
|
2d171db672 | ||
|
|
a8b11abec8 | ||
|
|
b08333dccd | ||
|
|
98bad48971 | ||
|
|
53375bb536 | ||
|
|
88798093e1 | ||
|
|
45a28751af | ||
|
|
1670c44464 | ||
|
|
a286ac85dc | ||
|
|
1606adc0ab | ||
|
|
b4ce557d92 | ||
|
|
2ba7fff8d4 | ||
|
|
98efc93610 | ||
|
|
2680507eae | ||
|
|
df72392ad2 | ||
|
|
a6acb15a00 | ||
|
|
ba7242a815 | ||
|
|
99740c1324 | ||
|
|
c04b20d1fa | ||
|
|
e127067878 | ||
|
|
6f03b72368 | ||
|
|
7bd3ee9340 | ||
|
|
c94db606e5 | ||
|
|
4d2474226b | ||
|
|
1ffc4f538b | ||
|
|
6702ac957b | ||
|
|
ba42b2e77d | ||
|
|
fba18ca40a | ||
|
|
3a2a7a1476 | ||
|
|
3c6d6b90bc | ||
|
|
47dc9ee304 | ||
|
|
0045bf206c | ||
|
|
669a1a6953 | ||
|
|
e28452cc7b | ||
|
|
5ceac03db1 | ||
|
|
96215e5950 | ||
|
|
7a030b9224 | ||
|
|
e743478a4d | ||
|
|
bc29c80d44 | ||
|
|
82b8a474d7 | ||
|
|
99851b297d | ||
|
|
e3a500e12c | ||
|
|
378666e3cc | ||
|
|
b65ad1ebd7 | ||
|
|
933a821b5f |
@@ -1,8 +1,7 @@
|
||||
# pnpm install --resolution-only
|
||||
pnpm install
|
||||
pnpm check $INPUT_TARGET
|
||||
sed -i "s/#openssl/openssl={version=\"0.10\",features=[\"vendored\"]}/g" src-tauri/Cargo.toml
|
||||
if [ "$INPUT_TARGET" = "x86_64-unknown-linux-gnu" ] || [ "$INPUT_TARGET" = "i686-unknown-linux-gnu" ]; then
|
||||
if [ "$INPUT_TARGET" = "x86_64-unknown-linux-gnu" ]; then
|
||||
pnpm build --target $INPUT_TARGET
|
||||
else
|
||||
pnpm build --target $INPUT_TARGET -b deb
|
||||
|
||||
@@ -10,15 +10,7 @@ rustup target add "$INPUT_TARGET"
|
||||
if [ "$INPUT_TARGET" = "x86_64-unknown-linux-gnu" ]; then
|
||||
apt-get update
|
||||
apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libayatana-appindicator3-dev librsvg2-dev patchelf
|
||||
elif [ "$INPUT_TARGET" = "i686-unknown-linux-gnu" ]; then
|
||||
dpkg --add-architecture i386
|
||||
apt-get update
|
||||
apt-get install -y libstdc++6:i386 libgdk-pixbuf2.0-dev:i386 libatomic1:i386 gcc-multilib g++-multilib libwebkit2gtk-4.0-dev:i386 libssl-dev:i386 libgtk-3-dev:i386 librsvg2-dev:i386 patchelf:i386 libayatana-appindicator3-dev:i386
|
||||
export PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig/:$PKG_CONFIG_PATH
|
||||
export PKG_CONFIG_SYSROOT_DIR=/
|
||||
elif [ "$INPUT_TARGET" = "aarch64-unknown-linux-gnu" ]; then
|
||||
sed 's/http:\/\/\(.*\).ubuntu.com\/ubuntu\//[arch-=amd64,i386] http:\/\/ports.ubuntu.com\/ubuntu-ports\//g' /etc/apt/sources.list | tee /etc/apt/sources.list.d/ports.list
|
||||
sed -i 's/http:\/\/\(.*\).ubuntu.com\/ubuntu\//[arch=amd64,i386] http:\/\/\1.archive.ubuntu.com\/ubuntu\//g' /etc/apt/sources.list
|
||||
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
|
||||
@@ -29,8 +21,6 @@ elif [ "$INPUT_TARGET" = "aarch64-unknown-linux-gnu" ]; then
|
||||
export PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig
|
||||
export PKG_CONFIG_ALLOW_CROSS=1
|
||||
elif [ "$INPUT_TARGET" = "armv7-unknown-linux-gnueabihf" ]; then
|
||||
sed 's/http:\/\/\(.*\).ubuntu.com\/ubuntu\//[arch-=amd64,i386] http:\/\/ports.ubuntu.com\/ubuntu-ports\//g' /etc/apt/sources.list | tee /etc/apt/sources.list.d/ports.list
|
||||
sed -i 's/http:\/\/\(.*\).ubuntu.com\/ubuntu\//[arch=amd64,i386] http:\/\/\1.archive.ubuntu.com\/ubuntu\//g' /etc/apt/sources.list
|
||||
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
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
name: Alpha Build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
tags-ignore: [updater, alpha]
|
||||
permissions: write-all
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUST_BACKTRACE: short
|
||||
|
||||
jobs:
|
||||
alpha:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
- os: windows-latest
|
||||
target: aarch64-pc-windows-msvc
|
||||
- os: macos-latest
|
||||
target: aarch64-apple-darwin
|
||||
- os: macos-latest
|
||||
target: x86_64-apple-darwin
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- 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
|
||||
|
||||
- name: Add Rust Target
|
||||
run: rustup target add ${{ matrix.target }}
|
||||
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: src-tauri
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Pnpm install and check
|
||||
run: |
|
||||
pnpm i
|
||||
pnpm check ${{ matrix.target }}
|
||||
|
||||
- name: Tauri build
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
with:
|
||||
tagName: alpha
|
||||
releaseName: "Clash Verge Rev Alpha"
|
||||
releaseBody: "More new features are now supported."
|
||||
releaseDraft: false
|
||||
prerelease: true
|
||||
tauriScript: pnpm
|
||||
args: --target ${{ matrix.target }}
|
||||
|
||||
- name: Portable Bundle
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: pnpm portable ${{ matrix.target }} --alpha
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
|
||||
alpha-for-linux:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: x86_64-unknown-linux-gnu
|
||||
- os: ubuntu-latest
|
||||
target: aarch64-unknown-linux-gnu
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build for Linux
|
||||
uses: ./.github/build-for-linux
|
||||
env:
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
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
|
||||
with:
|
||||
tag_name: alpha
|
||||
name: "Clash Verge Rev Alpha"
|
||||
body: "More new features are now supported."
|
||||
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."
|
||||
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
|
||||
needs: [alpha, alpha-for-linux]
|
||||
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
|
||||
Created at ${{ env.BUILDTIME }}.
|
||||
EOF
|
||||
- name: Upload Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: alpha
|
||||
name: "Clash Verge Rev Alpha"
|
||||
body_path: release.txt
|
||||
prerelease: true
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
generate_release_notes: true
|
||||
@@ -2,9 +2,6 @@ name: Release Build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- v**
|
||||
permissions: write-all
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
@@ -18,8 +15,6 @@ jobs:
|
||||
include:
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
- os: windows-latest
|
||||
target: i686-pc-windows-msvc
|
||||
- os: windows-latest
|
||||
target: aarch64-pc-windows-msvc
|
||||
- os: macos-latest
|
||||
@@ -78,7 +73,7 @@ jobs:
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
with:
|
||||
tagName: v__VERSION__
|
||||
releaseName: "Clash Verge v__VERSION__"
|
||||
releaseName: "Clash Verge Rev v__VERSION__"
|
||||
releaseBody: "More new features are now supported."
|
||||
releaseDraft: false
|
||||
prerelease: false
|
||||
@@ -92,7 +87,6 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
VITE_WIN_PORTABLE: 1
|
||||
|
||||
release-for-linux:
|
||||
strategy:
|
||||
@@ -101,12 +95,8 @@ jobs:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: x86_64-unknown-linux-gnu
|
||||
- os: ubuntu-latest
|
||||
target: i686-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
|
||||
@@ -125,11 +115,11 @@ jobs:
|
||||
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') || startsWith(matrix.target, 'i686')
|
||||
if: startsWith(matrix.target, 'x86_64')
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: v${{env.VERSION}}
|
||||
name: "Clash Verge v${{env.VERSION}}"
|
||||
name: "Clash Verge Rev v${{env.VERSION}}"
|
||||
body: "More new features are now supported."
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
files: src-tauri/target/${{ matrix.target }}/release/bundle/appimage/*.AppImage*
|
||||
@@ -137,21 +127,20 @@ jobs:
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: v${{env.VERSION}}
|
||||
name: "Clash Verge v${{env.VERSION}}"
|
||||
name: "Clash Verge Rev v${{env.VERSION}}"
|
||||
body: "More new features are now supported."
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
files: src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
|
||||
|
||||
release-update:
|
||||
needs: [release]
|
||||
runs-on: ubuntu-latest
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
needs: [release, release-for-linux]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
# CONTRIBUTING
|
||||
|
||||
Thank you for your interest in contributing to Clash Verge Rev! This document provides guidelines and instructions to help you set up your development environment and start contributing.
|
||||
|
||||
## Development Setup
|
||||
|
||||
Before you start contributing to the project, you need to set up your development environment. Here are the steps you need to follow:
|
||||
|
||||
### Prerequisites
|
||||
|
||||
1. **Install Rust and Node.js**: Our project requires both Rust and Node.js. Please follow the instructions provided [here](https://tauri.app/v1/guides/getting-started/prerequisites) to install them on your system.
|
||||
|
||||
### Setup for Windows Users
|
||||
|
||||
If you're a Windows user, you may need to perform some additional steps:
|
||||
|
||||
- Make sure to add Rust and Node.js to your system's PATH. This is usually done during the installation process, but you can verify and manually add them if necessary.
|
||||
- The gnu `patch` tool should be installed
|
||||
|
||||
### Install Node.js Packages
|
||||
|
||||
After installing Rust and Node.js, install the necessary Node.js packages:
|
||||
|
||||
```shell
|
||||
pnpm i
|
||||
```
|
||||
|
||||
### Download the Clash Binary
|
||||
|
||||
You have two options for downloading the clash binary:
|
||||
|
||||
- Automatically download it via the provided script:
|
||||
```shell
|
||||
pnpm run check
|
||||
# Use '--force' to force update to the latest version
|
||||
# pnpm run check --force
|
||||
```
|
||||
- Manually download it from the [Clash Meta release](https://github.com/MetaCubeX/Clash.Meta/releases). After downloading, rename the binary according to the [Tauri configuration](https://tauri.app/v1/api/config#bundleconfig.externalbin).
|
||||
|
||||
### Run the Development Server
|
||||
|
||||
To run the development server, use the following command:
|
||||
|
||||
```shell
|
||||
pnpm dev
|
||||
# If an app instance already exists, use a different command
|
||||
pnpm dev:diff
|
||||
```
|
||||
|
||||
### Build the Project
|
||||
|
||||
If you want to build the project, use:
|
||||
|
||||
```shell
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## Contributing Your Changes
|
||||
|
||||
Once you have made your changes:
|
||||
|
||||
1. Fork the repository.
|
||||
2. Create a new branch for your feature or bug fix.
|
||||
3. Commit your changes with clear and concise commit messages.
|
||||
4. Push your branch to your fork and submit a pull request to our repository.
|
||||
|
||||
We appreciate your contributions and look forward to your active participation in our project!
|
||||
@@ -9,15 +9,17 @@
|
||||
A Clash Meta GUI based on <a href="https://github.com/tauri-apps/tauri">Tauri</a>.
|
||||
</h3>
|
||||
|
||||
## Features
|
||||
## Install
|
||||
|
||||
- Since the clash core has been removed. The project no longer maintains the clash core, but only the Clash Meta core.
|
||||
- Profiles management and enhancement (by yaml and Javascript). [Doc](https://github.com/clash-verge-rev/clash-verge-rev/wiki/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97)
|
||||
- Simple UI and supports custom theme color.
|
||||
- Built-in support [Clash.Meta(mihomo)](https://github.com/MetaCubeX/mihomo) core.
|
||||
- System proxy setting and guard.
|
||||
请到发布页面下载对应的安装包:[Release page](https://github.com/clash-verge-rev/clash-verge-rev/releases)<br>
|
||||
Go to the [release page](https://github.com/clash-verge-rev/clash-verge-rev/releases) to download the corresponding installation package<br>
|
||||
Supports Windows (x64/x86), Linux (x64/arm64) and macOS 10.15+ (intel/apple).
|
||||
|
||||
#### TG Group: [@clash_verge_rev](https://t.me/clash_verge_rev)
|
||||
### 安装说明和常见问题,请到[文档页](https://clash-verge-rev.github.io/)查看:[Doc](https://clash-verge-rev.github.io/)
|
||||
|
||||
---
|
||||
|
||||
### TG Group: [@clash_verge_rev](https://t.me/clash_verge_rev)
|
||||
|
||||
## Promotion
|
||||
|
||||
@@ -33,77 +35,34 @@ A Clash Meta GUI based on <a href="https://github.com/tauri-apps/tauri">Tauri</a
|
||||
- 解锁流媒体及 ChatGPT
|
||||
- 官网:https://狗狗加速.com
|
||||
|
||||
## Features
|
||||
|
||||
- Since the clash core has been removed. The project no longer maintains the clash core, but only the Clash Meta core.
|
||||
- Profiles management and enhancement (by yaml and Javascript). [Doc](https://clash-verge-rev.github.io)
|
||||
- Improved UI and supports custom theme color.
|
||||
- Built-in support [Clash.Meta(mihomo)](https://github.com/MetaCubeX/mihomo) core.
|
||||
- System proxy setting and guard.
|
||||
|
||||
## Preview
|
||||
|
||||

|
||||
|
||||
## Install
|
||||
|
||||
Download from [release](https://github.com/clash-verge-rev/clash-verge-rev/releases). Supports Windows (x64/x86), Linux (x64/arm64) and macOS 10.15+ (intel/apple).
|
||||
|
||||
- [Windows x64](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.5/Clash.Verge_1.4.5_x64-setup.exe)
|
||||
- [Windows x86](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.5/Clash.Verge_1.4.5_x86-setup.exe)
|
||||
- [Windows arm64](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.5/Clash.Verge_1.4.5_arm64-setup.exe)
|
||||
|
||||
- [macOS intel](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.5/Clash.Verge_1.4.5_x64.dmg)
|
||||
- [macOS apple](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.5/Clash.Verge_1.4.5_aarch64.dmg)
|
||||
|
||||
- [Linux x64 AppImage](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.5/clash-verge_1.4.5_amd64.AppImage)
|
||||
- [Linux x64 deb](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.5/clash-verge_1.4.5_amd64.deb)
|
||||
- [Linux x86 AppImage](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.5/clash-verge_1.4.5_i386.AppImage)
|
||||
- [Linux x86 deb](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.5/clash-verge_1.4.5_i386.deb)
|
||||
- [Linux arm64 deb](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.5/clash-verge_1.4.5_arm64.deb)
|
||||
|
||||
Or you can build it yourself. Supports Windows, Linux and macOS 10.15+
|
||||
|
||||
Notes: If you could not start the app on Windows, please check that you have [Webview2](https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section) installed.
|
||||
|
||||
### FAQ
|
||||
|
||||
#### 1. **macOS** "Clash Verge" is damaged and can't be opened
|
||||
|
||||
open the terminal and run `sudo xattr -r -d com.apple.quarantine /Applications/Clash\ Verge.app`
|
||||
Refer to [Doc FAQ Page](https://clash-verge-rev.github.io/faq.html)
|
||||
|
||||
## Development
|
||||
|
||||
You should install Rust and Nodejs, see [here](https://tauri.app/v1/guides/getting-started/prerequisites) for more details. Then install Nodejs packages.
|
||||
See [CONTRIBUTING.md](./CONTRIBUTING.md) for more details.
|
||||
|
||||
To run the development server, execute the following commands after all prerequisites for **Tauri** are installed:
|
||||
|
||||
```shell
|
||||
pnpm i
|
||||
```
|
||||
|
||||
Then download the clash binary... Or you can download it from [clash meta release](https://github.com/MetaCubeX/Clash.Meta/releases) and rename it according to [tauri config](https://tauri.studio/docs/api/config/#tauri.bundle.externalBin).
|
||||
|
||||
```shell
|
||||
# force update to latest version
|
||||
# pnpm run check --force
|
||||
|
||||
pnpm run check
|
||||
```
|
||||
|
||||
Then run
|
||||
|
||||
```shell
|
||||
pnpm dev
|
||||
|
||||
# run it in another way if app instance exists
|
||||
pnpm dev:diff
|
||||
```
|
||||
|
||||
Or you can build it
|
||||
|
||||
```shell
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## Todos
|
||||
|
||||
> This keng is a little big...
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This is a learning project for Rust practice.
|
||||
|
||||
## Contributions
|
||||
|
||||
Issue and PR welcome!
|
||||
|
||||
@@ -1,3 +1,184 @@
|
||||
## v1.5.6
|
||||
|
||||
### Features
|
||||
|
||||
- 全新专属 Verge rev UI 界面 (by @Amnesiash) 及细节调整
|
||||
- 提供允许无效证书的开关
|
||||
- 删除不必要的快捷键
|
||||
- Provider 更新添加动画
|
||||
- Merge 支持 Provider
|
||||
- 更换订阅框的粘贴按钮,删除默认的"Remote File" Profile 名称
|
||||
- 链接菜单添加节点显示
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
- Linux 下图片显示错误
|
||||
|
||||
---
|
||||
|
||||
## v1.5.4
|
||||
|
||||
### Features
|
||||
|
||||
- 支持自定义托盘图标
|
||||
- 支持禁用代理组图标
|
||||
- 代理组显示当前代理
|
||||
- 修改 `打开面板` 快捷键为`打开/关闭面板`
|
||||
|
||||
---
|
||||
|
||||
## v1.5.3
|
||||
|
||||
### Features
|
||||
|
||||
- Tun 设置添加重置按钮
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
- Tun 设置项显示错误的问题
|
||||
- 修改一些默认值
|
||||
- 启动时不更改启动项设置
|
||||
|
||||
---
|
||||
|
||||
## v1.5.2
|
||||
|
||||
### Features
|
||||
|
||||
- 支持自定义延迟测试超时时间
|
||||
- 优化 Tun 相关设置
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
- Merge 操作出错
|
||||
- 安装后重启服务
|
||||
- 修复管理员权限启动时开机启动失效的问题
|
||||
|
||||
---
|
||||
|
||||
## v1.5.1
|
||||
|
||||
### Features
|
||||
|
||||
- 保存窗口最大化状态
|
||||
- Proxy Provider 显示数量
|
||||
- 不再提供 32 位安装包(因为 32 位经常出现各种奇怪问题,比如 tun 模式无法开启;现在系统也几乎没有 32 位了)
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
- 优化设置项名称
|
||||
- 自定义 GLOBAL 代理组时代理组显示错误的问题
|
||||
|
||||
---
|
||||
|
||||
## v1.5.0
|
||||
|
||||
### Features
|
||||
|
||||
- 删除 Clash 字段过滤功能
|
||||
- 添加 socks 端口和 http 端口设置
|
||||
- 升级内核到 1.18.1
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
- 修复 32 位版本无法显示流量信息的问题
|
||||
|
||||
---
|
||||
|
||||
## v1.4.11
|
||||
|
||||
### Break Changes
|
||||
|
||||
- 此版本更改了 Windows 安装包安装模式,需要卸载后手动安装,否则无法安装到正确位置
|
||||
|
||||
### Features
|
||||
|
||||
- 优化了系统代理开启的代码,解决了稀有场景下代理开启卡顿的问题
|
||||
- 添加 MacOS 下的 debug 日志,以便日后调试稀有场景下 MacOS 下无法开启系统代理的问题
|
||||
- MacOS 关闭 GUI 时同步杀除后台 GUI [#306](https://github.com/clash-verge-rev/clash-verge-rev/issues/306)
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
- 解决自动更新时文件占用问题
|
||||
- 解决稀有场景下系统代理开启失败的问题
|
||||
- 删除冗余内核代码
|
||||
|
||||
---
|
||||
|
||||
## v1.4.10
|
||||
|
||||
### Features
|
||||
|
||||
- 设置中添加退出按钮
|
||||
- 支持自定义软件启动页
|
||||
- 在 Proxy Provider 页面展示订阅信息
|
||||
- 优化 Provider 支持
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
- 更改端口时立即重设系统代理
|
||||
- 网站测试超时错误
|
||||
|
||||
---
|
||||
|
||||
## v1.4.9
|
||||
|
||||
### Features
|
||||
|
||||
- 支持启动时运行脚本
|
||||
- 支持代理组显示图标
|
||||
- 新增测试页面
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
- 连接页面时间排序错误
|
||||
- 连接页面表格宽度优化
|
||||
|
||||
---
|
||||
|
||||
## v1.4.8
|
||||
|
||||
### Features
|
||||
|
||||
- 连接页面总流量显示
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
- 连接页面数据排序错误
|
||||
- 新建订阅时设置更新间隔无效
|
||||
- Windows 拨号网络无法设置系统代理
|
||||
- Windows 开启/关闭系统代理延迟(使用注册表即可)
|
||||
- 删除无效的背景模糊选项
|
||||
|
||||
---
|
||||
|
||||
## v1.4.7
|
||||
|
||||
### Features
|
||||
|
||||
- Windows 便携版禁用应用内更新
|
||||
- 支持代理组 Hidden 选项
|
||||
- 支持 URL Scheme(MacOS & Linux)
|
||||
|
||||
---
|
||||
|
||||
## v1.4.6
|
||||
|
||||
### Features
|
||||
|
||||
- 更新 Clash Meta(mihomo) 内核到 v1.18.0
|
||||
- 支持 URL Scheme(暂时仅支持 Windows)
|
||||
- 添加窗口置顶按钮
|
||||
- UI 优化调整
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
- 修复一些编译错误
|
||||
- 获取订阅名称错误
|
||||
- 订阅信息解析错误
|
||||
|
||||
---
|
||||
|
||||
## v1.4.5
|
||||
|
||||
### Features
|
||||
|
||||
|
Before Width: | Height: | Size: 6.7 MiB After Width: | Height: | Size: 7.3 MiB |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "clash-verge",
|
||||
"version": "1.4.5",
|
||||
"license": "GPL-3.0",
|
||||
"version": "1.5.6",
|
||||
"license": "GPL-3.0-only",
|
||||
"scripts": {
|
||||
"dev": "tauri dev",
|
||||
"dev:diff": "tauri dev -f verge-dev",
|
||||
@@ -19,26 +19,27 @@
|
||||
"@dnd-kit/core": "^6.1.0",
|
||||
"@dnd-kit/sortable": "^8.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/react": "^11.11.3",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@juggle/resize-observer": "^3.4.0",
|
||||
"@mui/icons-material": "^5.14.19",
|
||||
"@mui/icons-material": "^5.15.5",
|
||||
"@mui/lab": "5.0.0-alpha.149",
|
||||
"@mui/material": "^5.14.19",
|
||||
"@mui/x-data-grid": "^6.18.2",
|
||||
"@tauri-apps/api": "^1.5.1",
|
||||
"@mui/material": "^5.15.5",
|
||||
"@mui/x-data-grid": "^6.18.7",
|
||||
"@tauri-apps/api": "^1.5.3",
|
||||
"ahooks": "^3.7.8",
|
||||
"axios": "^1.6.2",
|
||||
"axios": "^1.6.5",
|
||||
"dayjs": "1.11.5",
|
||||
"i18next": "^23.7.7",
|
||||
"i18next": "^23.7.16",
|
||||
"lodash-es": "^4.17.21",
|
||||
"monaco-editor": "^0.34.1",
|
||||
"nanoid": "^5.0.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-error-boundary": "^3.1.4",
|
||||
"react-hook-form": "^7.48.2",
|
||||
"react-hook-form": "^7.49.3",
|
||||
"react-i18next": "^13.5.0",
|
||||
"react-router-dom": "^6.20.0",
|
||||
"react-router-dom": "^6.21.2",
|
||||
"react-transition-group": "^4.4.5",
|
||||
"react-virtuoso": "^4.6.2",
|
||||
"recoil": "^0.7.7",
|
||||
@@ -48,15 +49,14 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/github": "^5.1.1",
|
||||
"@tauri-apps/cli": "^1.5.6",
|
||||
"@tauri-apps/cli": "^1.5.9",
|
||||
"@types/fs-extra": "^9.0.13",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/lodash": "^4.14.202",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/react": "^18.2.39",
|
||||
"@types/react-dom": "^18.2.17",
|
||||
"@types/react-transition-group": "^4.4.9",
|
||||
"@vitejs/plugin-react": "^4.2.0",
|
||||
"@types/react": "^18.2.48",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@types/react-transition-group": "^4.4.10",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"adm-zip": "^0.5.10",
|
||||
"cross-env": "^7.0.3",
|
||||
"fs-extra": "^11.2.0",
|
||||
@@ -64,10 +64,10 @@
|
||||
"husky": "^7.0.4",
|
||||
"node-fetch": "^3.3.2",
|
||||
"prettier": "^2.8.8",
|
||||
"pretty-quick": "^3.1.3",
|
||||
"sass": "^1.69.5",
|
||||
"typescript": "^5.3.2",
|
||||
"vite": "^4.5.0",
|
||||
"pretty-quick": "^3.3.1",
|
||||
"sass": "^1.70.0",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.0.11",
|
||||
"vite-plugin-monaco-editor": "^1.1.0",
|
||||
"vite-plugin-svgr": "^4.2.0"
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
From 8b085aea2f11e64f433244eda092c178a2bb50bc Mon Sep 17 00:00:00 2001
|
||||
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
|
||||
@@ -22,19 +22,19 @@ index 0000000..2eda7e4
|
||||
+ 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 4c6dde5..5fd9ad8 100644
|
||||
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.29"
|
||||
sysinfo = "0.30"
|
||||
-rquickjs = "0.3" # 高版本不支持 Linux aarch64
|
||||
serde_json = "1.0"
|
||||
serde_yaml = "0.9"
|
||||
auto-launch = "0.5"
|
||||
@@ -34,6 +33,7 @@ port_scanner = "0.1.5"
|
||||
delay_timer = "0.11"
|
||||
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" }
|
||||
@@ -49,7 +49,7 @@ index 0000000..78277c4
|
||||
@@ -0,0 +1 @@
|
||||
+Subproject commit 78277c4509c64f18c0fc5c9f2b84671de7c83343
|
||||
diff --git a/src-tauri/src/enhance/script.rs b/src-tauri/src/enhance/script.rs
|
||||
index 6c207d9..d47dc33 100644
|
||||
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;
|
||||
@@ -123,7 +123,7 @@ index 6c207d9..d47dc33 100644
|
||||
- if result == "\"\"" {
|
||||
- anyhow::bail!("main function should return object");
|
||||
- }
|
||||
- return Ok(serde_json::from_str::<Mapping>(result.as_str())?);
|
||||
- Ok(serde_json::from_str::<Mapping>(result.as_str())?)
|
||||
- });
|
||||
-
|
||||
- let mut out = outputs.lock().unwrap();
|
||||
|
||||
@@ -21,6 +21,7 @@ const PLATFORM_MAP = {
|
||||
"i686-unknown-linux-gnu": "linux",
|
||||
"aarch64-unknown-linux-gnu": "linux",
|
||||
"armv7-unknown-linux-gnueabihf": "linux",
|
||||
"loongarch64-unknown-linux-gnu": "linux",
|
||||
};
|
||||
const ARCH_MAP = {
|
||||
"x86_64-pc-windows-msvc": "x64",
|
||||
@@ -32,6 +33,7 @@ const ARCH_MAP = {
|
||||
"i686-unknown-linux-gnu": "ia32",
|
||||
"aarch64-unknown-linux-gnu": "arm64",
|
||||
"armv7-unknown-linux-gnueabihf": "arm",
|
||||
"loongarch64-unknown-linux-gnu": "loong64",
|
||||
};
|
||||
|
||||
const arg1 = process.argv.slice(2)[0];
|
||||
@@ -48,7 +50,7 @@ const SIDECAR_HOST = target
|
||||
.match(/(?<=host: ).+(?=\s*)/g)[0];
|
||||
|
||||
/* ======= clash meta alpha======= */
|
||||
const VERSION_URL =
|
||||
const META_ALPHA_VERSION_URL =
|
||||
"https://github.com/MetaCubeX/mihomo/releases/download/Prerelease-Alpha/version.txt";
|
||||
const META_ALPHA_URL_PREFIX = `https://github.com/MetaCubeX/mihomo/releases/download/Prerelease-Alpha`;
|
||||
let META_ALPHA_VERSION;
|
||||
@@ -63,24 +65,41 @@ const META_ALPHA_MAP = {
|
||||
"linux-ia32": "mihomo-linux-386",
|
||||
"linux-arm64": "mihomo-linux-arm64",
|
||||
"linux-arm": "mihomo-linux-armv7",
|
||||
"linux-loong64": "mihomo-linux-loong64",
|
||||
};
|
||||
|
||||
// Fetch the latest release version from the version.txt file
|
||||
async function getLatestVersion() {
|
||||
// Fetch the latest alpha release version from the version.txt file
|
||||
async function getLatestAlphaVersion() {
|
||||
const options = {};
|
||||
|
||||
const httpProxy =
|
||||
process.env.HTTP_PROXY ||
|
||||
process.env.http_proxy ||
|
||||
process.env.HTTPS_PROXY ||
|
||||
process.env.https_proxy;
|
||||
|
||||
if (httpProxy) {
|
||||
options.agent = proxyAgent(httpProxy);
|
||||
}
|
||||
try {
|
||||
const response = await fetch(VERSION_URL, { method: "GET" });
|
||||
const response = await fetch(META_ALPHA_VERSION_URL, {
|
||||
...options,
|
||||
method: "GET",
|
||||
});
|
||||
let v = await response.text();
|
||||
META_ALPHA_VERSION = v.trim(); // Trim to remove extra whitespaces
|
||||
console.log(`Latest release version: ${META_ALPHA_VERSION}`);
|
||||
console.log(`Latest alpha version: ${META_ALPHA_VERSION}`);
|
||||
} catch (error) {
|
||||
console.error("Error fetching latest release version:", error.message);
|
||||
console.error("Error fetching latest alpha version:", error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* ======= clash meta stable ======= */
|
||||
const META_VERSION_URL =
|
||||
"https://github.com/MetaCubeX/mihomo/releases/latest/download/version.txt";
|
||||
const META_URL_PREFIX = `https://github.com/MetaCubeX/mihomo/releases/download`;
|
||||
let META_VERSION = "v1.17.0";
|
||||
let META_VERSION;
|
||||
|
||||
const META_MAP = {
|
||||
"win32-x64": "mihomo-windows-amd64-compatible",
|
||||
@@ -92,8 +111,36 @@ const META_MAP = {
|
||||
"linux-ia32": "mihomo-linux-386",
|
||||
"linux-arm64": "mihomo-linux-arm64",
|
||||
"linux-arm": "mihomo-linux-armv7",
|
||||
"linux-loong64": "mihomo-linux-loong64",
|
||||
};
|
||||
|
||||
// Fetch the latest release version from the version.txt file
|
||||
async function getLatestReleaseVersion() {
|
||||
const options = {};
|
||||
|
||||
const httpProxy =
|
||||
process.env.HTTP_PROXY ||
|
||||
process.env.http_proxy ||
|
||||
process.env.HTTPS_PROXY ||
|
||||
process.env.https_proxy;
|
||||
|
||||
if (httpProxy) {
|
||||
options.agent = proxyAgent(httpProxy);
|
||||
}
|
||||
try {
|
||||
const response = await fetch(META_VERSION_URL, {
|
||||
...options,
|
||||
method: "GET",
|
||||
});
|
||||
let v = await response.text();
|
||||
META_VERSION = v.trim(); // Trim to remove extra whitespaces
|
||||
console.log(`Latest release version: ${META_VERSION}`);
|
||||
} catch (error) {
|
||||
console.error("Error fetching latest release version:", error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* check available
|
||||
*/
|
||||
@@ -270,11 +317,43 @@ async function downloadFile(url, path) {
|
||||
console.log(`[INFO]: download finished "${url}"`);
|
||||
}
|
||||
|
||||
// SimpleSC.dll
|
||||
const resolvePlugin = async () => {
|
||||
const url =
|
||||
"https://nsis.sourceforge.io/mediawiki/images/e/ef/NSIS_Simple_Service_Plugin_Unicode_1.30.zip";
|
||||
|
||||
const tempDir = path.join(TEMP_DIR, "SimpleSC");
|
||||
const tempZip = path.join(
|
||||
tempDir,
|
||||
"NSIS_Simple_Service_Plugin_Unicode_1.30.zip"
|
||||
);
|
||||
const tempDll = path.join(tempDir, "SimpleSC.dll");
|
||||
const pluginDir = path.join(process.env.APPDATA, "Local/NSIS");
|
||||
const pluginPath = path.join(pluginDir, "SimpleSC.dll");
|
||||
await fs.mkdirp(pluginDir);
|
||||
await fs.mkdirp(tempDir);
|
||||
if (!FORCE && (await fs.pathExists(pluginPath))) return;
|
||||
try {
|
||||
if (!(await fs.pathExists(tempZip))) {
|
||||
await downloadFile(url, tempZip);
|
||||
}
|
||||
const zip = new AdmZip(tempZip);
|
||||
zip.getEntries().forEach((entry) => {
|
||||
console.log(`[DEBUG]: "SimpleSC" entry name`, entry.entryName);
|
||||
});
|
||||
zip.extractAllTo(tempDir, true);
|
||||
await fs.copyFile(tempDll, pluginPath);
|
||||
console.log(`[INFO]: "SimpleSC" unzip finished`);
|
||||
} finally {
|
||||
await fs.remove(tempDir);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* main
|
||||
*/
|
||||
const SERVICE_URL =
|
||||
"https://github.com/zzzgydi/clash-verge-service/releases/download/latest";
|
||||
|
||||
const SERVICE_URL = `https://github.com/clash-verge-rev/clash-verge-service/releases/download/${SIDECAR_HOST}`;
|
||||
|
||||
const resolveService = () =>
|
||||
resolveResource({
|
||||
@@ -316,15 +395,17 @@ const tasks = [
|
||||
// { name: "clash", func: resolveClash, retry: 5 },
|
||||
{
|
||||
name: "clash-meta-alpha",
|
||||
func: () => getLatestVersion().then(() => resolveSidecar(clashMetaAlpha())),
|
||||
func: () =>
|
||||
getLatestAlphaVersion().then(() => resolveSidecar(clashMetaAlpha())),
|
||||
retry: 5,
|
||||
},
|
||||
{
|
||||
name: "clash-meta",
|
||||
func: () => resolveSidecar(clashMeta()),
|
||||
func: () =>
|
||||
getLatestReleaseVersion().then(() => resolveSidecar(clashMeta())),
|
||||
retry: 5,
|
||||
},
|
||||
// { name: "wintun", func: resolveWintun, retry: 5, winOnly: true },
|
||||
{ name: "plugin", func: resolvePlugin, retry: 5, winOnly: true },
|
||||
{ name: "service", func: resolveService, retry: 5, winOnly: true },
|
||||
{ name: "install", func: resolveInstall, retry: 5, winOnly: true },
|
||||
{ name: "uninstall", func: resolveUninstall, retry: 5, winOnly: true },
|
||||
|
||||
@@ -5,9 +5,9 @@ import { createRequire } from "module";
|
||||
import { getOctokit, context } from "@actions/github";
|
||||
|
||||
const target = process.argv.slice(2)[0];
|
||||
const alpha = process.argv.slice(2)[1];
|
||||
|
||||
const ARCH_MAP = {
|
||||
"i686-pc-windows-msvc": "x86",
|
||||
"x86_64-pc-windows-msvc": "x64",
|
||||
"aarch64-pc-windows-msvc": "arm64",
|
||||
};
|
||||
@@ -53,14 +53,25 @@ async function resolvePortable() {
|
||||
|
||||
const options = { owner: context.repo.owner, repo: context.repo.repo };
|
||||
const github = getOctokit(process.env.GITHUB_TOKEN);
|
||||
|
||||
console.log("[INFO]: upload to ", process.env.TAG_NAME || `v${version}`);
|
||||
const tag = alpha ? "alpha" : process.env.TAG_NAME || `v${version}`;
|
||||
console.log("[INFO]: upload to ", tag);
|
||||
|
||||
const { data: release } = await github.rest.repos.getReleaseByTag({
|
||||
...options,
|
||||
tag: process.env.TAG_NAME || `v${version}`,
|
||||
tag,
|
||||
});
|
||||
|
||||
let assets = release.assets.filter((x) => {
|
||||
return x.name === zipFile;
|
||||
});
|
||||
if (assets.length > 0) {
|
||||
let id = assets[0].id;
|
||||
await github.rest.repos.deleteReleaseAsset({
|
||||
...options,
|
||||
asset_id: id,
|
||||
});
|
||||
}
|
||||
|
||||
console.log(release.name);
|
||||
|
||||
await github.rest.repos.uploadReleaseAsset({
|
||||
|
||||
@@ -45,11 +45,9 @@ async function resolveUpdater() {
|
||||
"darwin-intel": { signature: "", url: "" },
|
||||
"darwin-x86_64": { signature: "", url: "" },
|
||||
"linux-x86_64": { signature: "", url: "" },
|
||||
"linux-i686": { signature: "", url: "" },
|
||||
"linux-aarch64": { signature: "", url: "" },
|
||||
"linux-armv7": { signature: "", url: "" },
|
||||
"windows-x86_64": { signature: "", url: "" },
|
||||
"windows-i686": { signature: "", url: "" },
|
||||
"windows-aarch64": { signature: "", url: "" },
|
||||
},
|
||||
};
|
||||
@@ -69,16 +67,6 @@ async function resolveUpdater() {
|
||||
updateData.platforms["windows-x86_64"].signature = sig;
|
||||
}
|
||||
|
||||
// win32 url
|
||||
if (name.endsWith("x86-setup.nsis.zip")) {
|
||||
updateData.platforms["windows-i686"].url = browser_download_url;
|
||||
}
|
||||
// win32 signature
|
||||
if (name.endsWith("x86-setup.nsis.zip.sig")) {
|
||||
const sig = await getSignature(browser_download_url);
|
||||
updateData.platforms["windows-i686"].signature = sig;
|
||||
}
|
||||
|
||||
// win arm url
|
||||
if (name.endsWith("arm64-setup.nsis.zip")) {
|
||||
updateData.platforms["windows-aarch64"].url = browser_download_url;
|
||||
@@ -130,16 +118,6 @@ async function resolveUpdater() {
|
||||
updateData.platforms["linux-aarch64"].signature = sig;
|
||||
updateData.platforms["linux-armv7"].signature = sig;
|
||||
}
|
||||
|
||||
// linux x86 url
|
||||
if (name.endsWith("i386.AppImage.tar.gz")) {
|
||||
updateData.platforms["linux-i686"].url = browser_download_url;
|
||||
}
|
||||
// linux x86 signature
|
||||
if (name.endsWith("i386.AppImage.tar.gz.sig")) {
|
||||
const sig = await getSignature(browser_download_url);
|
||||
updateData.platforms["linux-i686"].signature = sig;
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.allSettled(promises);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
[package]
|
||||
name = "clash-verge"
|
||||
version = "1.4.5"
|
||||
version = "1.5.6"
|
||||
description = "clash verge"
|
||||
authors = ["zzzgydi", "wonfen", "MystiPanda"]
|
||||
license = "GPL-3.0"
|
||||
license = "GPL-3.0-only"
|
||||
repository = "https://github.com/clash-verge-rev/clash-verge-rev.git"
|
||||
default-run = "clash-verge"
|
||||
edition = "2021"
|
||||
@@ -14,7 +14,7 @@ tauri-build = { version = "1", features = [] }
|
||||
|
||||
[dependencies]
|
||||
warp = "0.3"
|
||||
which = "5.0.0"
|
||||
which = "6.0.0"
|
||||
anyhow = "1.0"
|
||||
dirs = "5.0"
|
||||
open = "5.0"
|
||||
@@ -24,14 +24,13 @@ dunce = "1.0"
|
||||
log4rs = "1"
|
||||
nanoid = "0.4"
|
||||
chrono = "0.4"
|
||||
sysinfo = "0.29"
|
||||
sysinfo = "0.30"
|
||||
rquickjs = "0.3" # 高版本不支持 Linux aarch64
|
||||
serde_json = "1.0"
|
||||
serde_yaml = "0.9"
|
||||
auto-launch = "0.5"
|
||||
once_cell = "1.18"
|
||||
port_scanner = "0.1.5"
|
||||
delay_timer = "0.11"
|
||||
delay_timer = "0.11.5"
|
||||
parking_lot = "0.12"
|
||||
percent-encoding = "2.3.1"
|
||||
window-shadows = { version = "0.2" }
|
||||
@@ -39,13 +38,13 @@ tokio = { version = "1", features = ["full"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
reqwest = { version = "0.11", features = ["json", "rustls-tls"] }
|
||||
sysproxy = { git="https://github.com/zzzgydi/sysproxy-rs", branch = "main" }
|
||||
tauri = { version = "1.5", features = ["icon-png", "clipboard-all", "global-shortcut-all", "process-all", "shell-all", "system-tray", "updater", "window-all"] }
|
||||
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"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
runas = "=1.0.0" # 高版本会返回错误 Status
|
||||
deelevate = "0.2.0"
|
||||
winreg = { version = "0.52", features = ["transactions"] }
|
||||
windows-sys = { version = "0.52", features = ["Win32_System_LibraryLoader", "Win32_System_SystemInformation"] }
|
||||
winreg = "0.52.0"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
#openssl
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>Clash Verge</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>clash</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -2,14 +2,14 @@ use crate::{
|
||||
config::*,
|
||||
core::*,
|
||||
feat,
|
||||
utils::{dirs, help},
|
||||
utils::{dirs, help, resolve},
|
||||
};
|
||||
use crate::{ret_err, wrap_err};
|
||||
use anyhow::{Context, Result};
|
||||
use serde_yaml::Mapping;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use sysproxy::Sysproxy;
|
||||
|
||||
use tauri::api;
|
||||
type CmdResult<T = ()> = Result<T, String>;
|
||||
|
||||
#[tauri::command]
|
||||
@@ -219,7 +219,7 @@ pub fn open_app_dir() -> CmdResult<()> {
|
||||
#[tauri::command]
|
||||
pub fn open_core_dir() -> CmdResult<()> {
|
||||
let core_dir = wrap_err!(tauri::utils::platform::current_exe())?;
|
||||
let core_dir = core_dir.parent().ok_or(format!("failed to get core dir"))?;
|
||||
let core_dir = core_dir.parent().ok_or("failed to get core dir")?;
|
||||
wrap_err!(open::that(core_dir))
|
||||
}
|
||||
|
||||
@@ -249,13 +249,60 @@ pub mod uwp {
|
||||
pub async fn clash_api_get_proxy_delay(
|
||||
name: String,
|
||||
url: Option<String>,
|
||||
timeout: i32,
|
||||
) -> CmdResult<clash_api::DelayRes> {
|
||||
match clash_api::get_proxy_delay(name, url).await {
|
||||
match clash_api::get_proxy_delay(name, url, timeout).await {
|
||||
Ok(res) => Ok(res),
|
||||
Err(err) => Err(format!("{}", err.to_string())),
|
||||
Err(err) => Err(err.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_portable_flag() -> CmdResult<bool> {
|
||||
Ok(*dirs::PORTABLE_FLAG.get().unwrap_or(&false))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn test_delay(url: String) -> CmdResult<u32> {
|
||||
Ok(feat::test_delay(url).await.unwrap_or(10000u32))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_app_dir() -> CmdResult<String> {
|
||||
let app_home_dir = wrap_err!(dirs::app_home_dir())?
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
Ok(app_home_dir)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn copy_icon_file(path: String, name: String) -> CmdResult<String> {
|
||||
let file_path = std::path::Path::new(&path);
|
||||
let icon_dir = wrap_err!(dirs::app_home_dir())?.join("icons");
|
||||
if !icon_dir.exists() {
|
||||
let _ = std::fs::create_dir_all(&icon_dir);
|
||||
}
|
||||
let dest_path = icon_dir.join(name);
|
||||
|
||||
if file_path.exists() {
|
||||
match std::fs::copy(file_path, &dest_path) {
|
||||
Ok(_) => Ok(dest_path.to_string_lossy().to_string()),
|
||||
Err(err) => Err(err.to_string()),
|
||||
}
|
||||
} else {
|
||||
return Err("file not found".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn exit_app(app_handle: tauri::AppHandle) {
|
||||
let _ = resolve::save_window_size_position(&app_handle, true);
|
||||
resolve::resolve_reset();
|
||||
api::process::kill_children();
|
||||
app_handle.exit(0);
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub mod service {
|
||||
use super::*;
|
||||
|
||||
@@ -12,33 +12,56 @@ pub struct IClashTemp(pub Mapping);
|
||||
|
||||
impl IClashTemp {
|
||||
pub fn new() -> Self {
|
||||
let template = Self::template();
|
||||
match dirs::clash_path().and_then(|path| help::read_merge_mapping(&path)) {
|
||||
Ok(map) => Self(Self::guard(map)),
|
||||
Ok(mut map) => {
|
||||
template.0.keys().for_each(|key| {
|
||||
if !map.contains_key(key) {
|
||||
map.insert(key.clone(), template.0.get(key).unwrap().clone());
|
||||
}
|
||||
});
|
||||
Self(Self::guard(map))
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!(target: "app", "{err}");
|
||||
Self::template()
|
||||
template
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn template() -> Self {
|
||||
let mut map = Mapping::new();
|
||||
let mut tun = Mapping::new();
|
||||
tun.insert("stack".into(), "gvisor".into());
|
||||
tun.insert("device".into(), "Meta".into());
|
||||
tun.insert("auto-route".into(), true.into());
|
||||
tun.insert("strict-route".into(), false.into());
|
||||
tun.insert("auto-detect-interface".into(), true.into());
|
||||
tun.insert("dns-hijack".into(), vec!["any:53"].into());
|
||||
tun.insert("mtu".into(), 9000.into());
|
||||
|
||||
map.insert("mixed-port".into(), 7897.into());
|
||||
map.insert("socks-port".into(), 7898.into());
|
||||
map.insert("port".into(), 7899.into());
|
||||
map.insert("log-level".into(), "info".into());
|
||||
map.insert("allow-lan".into(), false.into());
|
||||
map.insert("mode".into(), "rule".into());
|
||||
map.insert("external-controller".into(), "127.0.0.1:9097".into());
|
||||
map.insert("secret".into(), "".into());
|
||||
map.insert("tun".into(), tun.into());
|
||||
|
||||
Self(map)
|
||||
}
|
||||
|
||||
fn guard(mut config: Mapping) -> Mapping {
|
||||
let port = Self::guard_mixed_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);
|
||||
|
||||
config.insert("mixed-port".into(), port.into());
|
||||
config.insert("mixed-port".into(), mixed_port.into());
|
||||
config.insert("socks-port".into(), socks_port.into());
|
||||
config.insert("port".into(), port.into());
|
||||
config.insert("external-controller".into(), ctrl.into());
|
||||
config
|
||||
}
|
||||
@@ -61,12 +84,24 @@ impl IClashTemp {
|
||||
Self::guard_mixed_port(&self.0)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn get_socks_port(&self) -> u16 {
|
||||
Self::guard_socks_port(&self.0)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn get_port(&self) -> u16 {
|
||||
Self::guard_port(&self.0)
|
||||
}
|
||||
|
||||
pub fn get_client_info(&self) -> ClashInfo {
|
||||
let config = &self.0;
|
||||
|
||||
ClashInfo {
|
||||
port: Self::guard_mixed_port(&config),
|
||||
server: Self::guard_client_ctrl(&config),
|
||||
mixed_port: Self::guard_mixed_port(config),
|
||||
socks_port: Self::guard_socks_port(config),
|
||||
port: Self::guard_port(config),
|
||||
server: Self::guard_client_ctrl(config),
|
||||
secret: config.get("secret").and_then(|value| match value {
|
||||
Value::String(val_str) => Some(val_str.clone()),
|
||||
Value::Bool(val_bool) => Some(val_bool.to_string()),
|
||||
@@ -91,6 +126,36 @@ impl IClashTemp {
|
||||
port
|
||||
}
|
||||
|
||||
pub fn guard_socks_port(config: &Mapping) -> u16 {
|
||||
let mut port = config
|
||||
.get("socks-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(7898);
|
||||
if port == 0 {
|
||||
port = 7898;
|
||||
}
|
||||
port
|
||||
}
|
||||
|
||||
pub fn guard_port(config: &Mapping) -> u16 {
|
||||
let mut port = config
|
||||
.get("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(7899);
|
||||
if port == 0 {
|
||||
port = 7899;
|
||||
}
|
||||
port
|
||||
}
|
||||
|
||||
pub fn guard_server_ctrl(config: &Mapping) -> String {
|
||||
config
|
||||
.get("external-controller")
|
||||
@@ -98,7 +163,7 @@ impl IClashTemp {
|
||||
Some(val_str) => {
|
||||
let val_str = val_str.trim();
|
||||
|
||||
let val = match val_str.starts_with(":") {
|
||||
let val = match val_str.starts_with(':') {
|
||||
true => format!("127.0.0.1{val_str}"),
|
||||
false => val_str.to_owned(),
|
||||
};
|
||||
@@ -129,6 +194,8 @@ impl IClashTemp {
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||
pub struct ClashInfo {
|
||||
/// clash core port
|
||||
pub mixed_port: u16,
|
||||
pub socks_port: u16,
|
||||
pub port: u16,
|
||||
/// same as `external-controller`
|
||||
pub server: String,
|
||||
@@ -148,7 +215,9 @@ fn test_clash_info() {
|
||||
|
||||
fn get_result<S: Into<String>>(port: u16, server: S) -> ClashInfo {
|
||||
ClashInfo {
|
||||
port,
|
||||
mixed_port: port,
|
||||
socks_port: 7898,
|
||||
port: 7899,
|
||||
server: server.into(),
|
||||
secret: None,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::utils::{dirs, help, tmpl};
|
||||
use crate::utils::{dirs, help, resolve::VERSION, tmpl};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use reqwest::StatusCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -8,7 +8,7 @@ use sysproxy::Sysproxy;
|
||||
|
||||
use super::Config;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
||||
pub struct PrfItem {
|
||||
pub uid: Option<String>,
|
||||
|
||||
@@ -59,10 +59,10 @@ pub struct PrfSelected {
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, Deserialize, Serialize)]
|
||||
pub struct PrfExtra {
|
||||
pub upload: usize,
|
||||
pub download: usize,
|
||||
pub total: usize,
|
||||
pub expire: usize,
|
||||
pub upload: u64,
|
||||
pub download: u64,
|
||||
pub total: u64,
|
||||
pub expire: u64,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||
@@ -84,6 +84,12 @@ pub struct PrfOption {
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub update_interval: Option<u64>,
|
||||
|
||||
/// for `remote` profile
|
||||
/// disable certificate validation
|
||||
/// default is `false`
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub danger_accept_invalid_certs: Option<bool>,
|
||||
}
|
||||
|
||||
impl PrfOption {
|
||||
@@ -93,28 +99,11 @@ impl PrfOption {
|
||||
a.user_agent = b.user_agent.or(a.user_agent);
|
||||
a.with_proxy = b.with_proxy.or(a.with_proxy);
|
||||
a.self_proxy = b.self_proxy.or(a.self_proxy);
|
||||
a.danger_accept_invalid_certs = b.danger_accept_invalid_certs.or(a.danger_accept_invalid_certs);
|
||||
a.update_interval = b.update_interval.or(a.update_interval);
|
||||
Some(a)
|
||||
}
|
||||
t @ _ => t.0.or(t.1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PrfItem {
|
||||
fn default() -> Self {
|
||||
PrfItem {
|
||||
uid: None,
|
||||
itype: None,
|
||||
name: None,
|
||||
desc: None,
|
||||
file: None,
|
||||
url: None,
|
||||
selected: None,
|
||||
extra: None,
|
||||
updated: None,
|
||||
option: None,
|
||||
file_data: None,
|
||||
t => t.0.or(t.1),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,7 +141,7 @@ impl PrfItem {
|
||||
let desc = item.desc.unwrap_or("".into());
|
||||
PrfItem::from_script(name, desc)
|
||||
}
|
||||
typ @ _ => bail!("invalid profile item type \"{typ}\""),
|
||||
typ => bail!("invalid profile item type \"{typ}\""),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +177,9 @@ impl PrfItem {
|
||||
let opt_ref = option.as_ref();
|
||||
let with_proxy = opt_ref.map_or(false, |o| o.with_proxy.unwrap_or(false));
|
||||
let self_proxy = opt_ref.map_or(false, |o| o.self_proxy.unwrap_or(false));
|
||||
let user_agent = opt_ref.map_or(None, |o| o.user_agent.clone());
|
||||
let accept_invalid_certs = opt_ref.map_or(false, |o| o.danger_accept_invalid_certs.unwrap_or(false));
|
||||
let user_agent = opt_ref.and_then(|o| o.user_agent.clone());
|
||||
let update_interval = opt_ref.and_then(|o| o.update_interval);
|
||||
|
||||
let mut builder = reqwest::ClientBuilder::new().use_rustls_tls().no_proxy();
|
||||
|
||||
@@ -213,25 +204,27 @@ impl PrfItem {
|
||||
}
|
||||
// 使用系统代理
|
||||
else if with_proxy {
|
||||
match Sysproxy::get_system_proxy() {
|
||||
Ok(p @ Sysproxy { enable: true, .. }) => {
|
||||
let proxy_scheme = format!("http://{}:{}", p.host, p.port);
|
||||
if let Ok(p @ Sysproxy { enable: true, .. }) = Sysproxy::get_system_proxy() {
|
||||
let proxy_scheme = format!("http://{}:{}", p.host, p.port);
|
||||
|
||||
if let Ok(proxy) = reqwest::Proxy::http(&proxy_scheme) {
|
||||
builder = builder.proxy(proxy);
|
||||
}
|
||||
if let Ok(proxy) = reqwest::Proxy::https(&proxy_scheme) {
|
||||
builder = builder.proxy(proxy);
|
||||
}
|
||||
if let Ok(proxy) = reqwest::Proxy::all(&proxy_scheme) {
|
||||
builder = builder.proxy(proxy);
|
||||
}
|
||||
if let Ok(proxy) = reqwest::Proxy::http(&proxy_scheme) {
|
||||
builder = builder.proxy(proxy);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
if let Ok(proxy) = reqwest::Proxy::https(&proxy_scheme) {
|
||||
builder = builder.proxy(proxy);
|
||||
}
|
||||
if let Ok(proxy) = reqwest::Proxy::all(&proxy_scheme) {
|
||||
builder = builder.proxy(proxy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let version = format!("clash-verge-rev");
|
||||
let version = match VERSION.get() {
|
||||
Some(v) => format!("clash-verge/v{}", v),
|
||||
None => "clash-verge/unknown".to_string(),
|
||||
};
|
||||
|
||||
builder = builder.danger_accept_invalid_certs(accept_invalid_certs);
|
||||
builder = builder.user_agent(user_agent.unwrap_or(version));
|
||||
|
||||
let resp = builder.build()?.get(url).send().await?;
|
||||
@@ -247,12 +240,11 @@ impl PrfItem {
|
||||
let extra = match header.get("Subscription-Userinfo") {
|
||||
Some(value) => {
|
||||
let sub_info = value.to_str().unwrap_or("");
|
||||
|
||||
Some(PrfExtra {
|
||||
upload: help::parse_str(sub_info, "upload=").unwrap_or(0),
|
||||
download: help::parse_str(sub_info, "download=").unwrap_or(0),
|
||||
total: help::parse_str(sub_info, "total=").unwrap_or(0),
|
||||
expire: help::parse_str(sub_info, "expire=").unwrap_or(0),
|
||||
upload: help::parse_str(sub_info, "upload").unwrap_or(0),
|
||||
download: help::parse_str(sub_info, "download").unwrap_or(0),
|
||||
total: help::parse_str(sub_info, "total").unwrap_or(0),
|
||||
expire: help::parse_str(sub_info, "expire").unwrap_or(0),
|
||||
})
|
||||
}
|
||||
None => None,
|
||||
@@ -261,32 +253,42 @@ impl PrfItem {
|
||||
// parse the Content-Disposition
|
||||
let filename = match header.get("Content-Disposition") {
|
||||
Some(value) => {
|
||||
let filename = value.to_str().unwrap_or("");
|
||||
match help::parse_str::<String>(filename, "filename=") {
|
||||
Some(filename) => Some(filename),
|
||||
None => match help::parse_str::<String>(filename, "filename*=") {
|
||||
let filename = format!("{value:?}");
|
||||
let filename = filename.trim_matches('"');
|
||||
match help::parse_str::<String>(filename, "filename*") {
|
||||
Some(filename) => {
|
||||
let iter = percent_encoding::percent_decode(filename.as_bytes());
|
||||
let filename = iter.decode_utf8().unwrap_or_default();
|
||||
filename.split("''").last().map(|s| s.to_string())
|
||||
}
|
||||
None => match help::parse_str::<String>(filename, "filename") {
|
||||
Some(filename) => {
|
||||
let iter = percent_encoding::percent_decode(filename.as_bytes());
|
||||
let filename = iter.decode_utf8().unwrap_or_default();
|
||||
filename.split("''").last().map(|s| s.to_string())
|
||||
let filename = filename.trim_matches('"');
|
||||
Some(filename.to_string())
|
||||
}
|
||||
None => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
None => Some(
|
||||
crate::utils::help::get_last_part_and_decode(url).unwrap_or("Remote File".into()),
|
||||
),
|
||||
};
|
||||
|
||||
// parse the profile-update-interval
|
||||
let option = match header.get("profile-update-interval") {
|
||||
Some(value) => match value.to_str().unwrap_or("").parse::<u64>() {
|
||||
Ok(val) => Some(PrfOption {
|
||||
update_interval: Some(val * 60), // hour -> min
|
||||
..PrfOption::default()
|
||||
}),
|
||||
Err(_) => None,
|
||||
let option = match update_interval {
|
||||
Some(val) => Some(PrfOption {
|
||||
update_interval: Some(val),
|
||||
..PrfOption::default()
|
||||
}),
|
||||
None => match header.get("profile-update-interval") {
|
||||
Some(value) => match value.to_str().unwrap_or("").parse::<u64>() {
|
||||
Ok(val) => Some(PrfOption {
|
||||
update_interval: Some(val * 60), // hour -> min
|
||||
..PrfOption::default()
|
||||
}),
|
||||
Err(_) => None,
|
||||
},
|
||||
None => None,
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
|
||||
let uid = help::get_uid("r");
|
||||
|
||||
@@ -14,9 +14,6 @@ pub struct IProfiles {
|
||||
/// same as PrfConfig.chain
|
||||
pub chain: Option<Vec<String>>,
|
||||
|
||||
/// record valid fields for clash
|
||||
pub valid: Option<Vec<String>>,
|
||||
|
||||
/// profile list
|
||||
pub items: Option<Vec<PrfItem>>,
|
||||
}
|
||||
@@ -37,13 +34,13 @@ impl IProfiles {
|
||||
profiles.items = Some(vec![]);
|
||||
}
|
||||
// compatible with the old old old version
|
||||
profiles.items.as_mut().map(|items| {
|
||||
if let Some(items) = profiles.items.as_mut() {
|
||||
for item in items.iter_mut() {
|
||||
if item.uid.is_none() {
|
||||
item.uid = Some(help::get_uid("d"));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
profiles
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -55,12 +52,6 @@ impl IProfiles {
|
||||
|
||||
pub fn template() -> Self {
|
||||
Self {
|
||||
valid: Some(vec![
|
||||
"dns".into(),
|
||||
"sub-rules".into(),
|
||||
"unified-delay".into(),
|
||||
"tcp-concurrent".into(),
|
||||
]),
|
||||
items: Some(vec![]),
|
||||
..Self::default()
|
||||
}
|
||||
@@ -93,10 +84,6 @@ impl IProfiles {
|
||||
self.chain = Some(chain);
|
||||
}
|
||||
|
||||
if let Some(valid) = patch.valid {
|
||||
self.valid = Some(valid);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -152,17 +139,19 @@ impl IProfiles {
|
||||
self.items = Some(vec![]);
|
||||
}
|
||||
|
||||
self.items.as_mut().map(|items| items.push(item));
|
||||
if let Some(items) = self.items.as_mut() {
|
||||
items.push(item)
|
||||
}
|
||||
self.save_file()
|
||||
}
|
||||
|
||||
/// reorder items
|
||||
pub fn reorder(&mut self, active_id: String, over_id: String) -> Result<()> {
|
||||
let mut items = self.items.take().unwrap_or(vec![]);
|
||||
let mut items = self.items.take().unwrap_or_default();
|
||||
let mut old_index = None;
|
||||
let mut new_index = None;
|
||||
|
||||
for i in 0..items.len() {
|
||||
for (i, _) in items.iter().enumerate() {
|
||||
if items[i].uid == Some(active_id.clone()) {
|
||||
old_index = Some(i);
|
||||
}
|
||||
@@ -182,7 +171,7 @@ impl IProfiles {
|
||||
|
||||
/// update the item value
|
||||
pub fn patch_item(&mut self, uid: String, item: PrfItem) -> Result<()> {
|
||||
let mut items = self.items.take().unwrap_or(vec![]);
|
||||
let mut items = self.items.take().unwrap_or_default();
|
||||
|
||||
for each in items.iter_mut() {
|
||||
if each.uid == Some(uid.clone()) {
|
||||
@@ -255,11 +244,11 @@ impl IProfiles {
|
||||
let current = self.current.as_ref().unwrap_or(&uid);
|
||||
let current = current.clone();
|
||||
|
||||
let mut items = self.items.take().unwrap_or(vec![]);
|
||||
let mut items = self.items.take().unwrap_or_default();
|
||||
let mut index = None;
|
||||
|
||||
// get the index
|
||||
for i in 0..items.len() {
|
||||
for (i, _) in items.iter().enumerate() {
|
||||
if items[i].uid == Some(uid.clone()) {
|
||||
index = Some(i);
|
||||
break;
|
||||
@@ -267,19 +256,19 @@ impl IProfiles {
|
||||
}
|
||||
|
||||
if let Some(index) = index {
|
||||
items.remove(index).file.map(|file| {
|
||||
if let Some(file) = items.remove(index).file {
|
||||
let _ = dirs::app_profiles_dir().map(|path| {
|
||||
let path = path.join(file);
|
||||
if path.exists() {
|
||||
let _ = fs::remove_file(path);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// delete the original uid
|
||||
if current == uid {
|
||||
self.current = match items.len() > 0 {
|
||||
self.current = match !items.is_empty() {
|
||||
true => items[0].uid.clone(),
|
||||
false => None,
|
||||
};
|
||||
@@ -299,7 +288,7 @@ impl IProfiles {
|
||||
Some(file) => dirs::app_profiles_dir()?.join(file),
|
||||
None => bail!("failed to get the file field"),
|
||||
};
|
||||
return Ok(help::read_merge_mapping(&file_path)?);
|
||||
return help::read_merge_mapping(&file_path);
|
||||
}
|
||||
bail!("failed to find the current profile \"uid:{current}\"");
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::enhance::field::use_keys;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_yaml::Mapping;
|
||||
use serde_yaml::{Mapping, Value};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct IRuntime {
|
||||
pub config: Option<Mapping>,
|
||||
@@ -16,7 +16,7 @@ impl IRuntime {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
// 这里只更改 allow-lan | ipv6 | log-level
|
||||
// 这里只更改 allow-lan | ipv6 | log-level | tun
|
||||
pub fn patch_config(&mut self, patch: Mapping) {
|
||||
if let Some(config) = self.config.as_mut() {
|
||||
["allow-lan", "ipv6", "log-level"]
|
||||
@@ -26,6 +26,20 @@ impl IRuntime {
|
||||
config.insert(key.into(), value.clone());
|
||||
}
|
||||
});
|
||||
let tun = config.get("tun");
|
||||
let mut tun = tun.map_or(Mapping::new(), |val| {
|
||||
val.as_mapping().cloned().unwrap_or(Mapping::new())
|
||||
});
|
||||
let patch_tun = patch.get("tun");
|
||||
let patch_tun = patch_tun.map_or(Mapping::new(), |val| {
|
||||
val.as_mapping().cloned().unwrap_or(Mapping::new())
|
||||
});
|
||||
use_keys(&patch_tun).into_iter().for_each(|key| {
|
||||
if let Some(value) = patch_tun.get(&key).to_owned() {
|
||||
tun.insert(key.into(), value.clone());
|
||||
}
|
||||
});
|
||||
config.insert("tun".into(), Value::from(tun));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,22 +19,35 @@ pub struct IVerge {
|
||||
/// `light` or `dark` or `system`
|
||||
pub theme_mode: Option<String>,
|
||||
|
||||
/// enable blur mode
|
||||
/// maybe be able to set the alpha
|
||||
pub theme_blur: Option<bool>,
|
||||
|
||||
/// tray click event
|
||||
pub tray_event: Option<String>,
|
||||
|
||||
/// copy env type
|
||||
pub env_type: Option<String>,
|
||||
|
||||
/// start page
|
||||
pub start_page: Option<String>,
|
||||
/// startup script path
|
||||
pub startup_script: Option<String>,
|
||||
|
||||
/// enable traffic graph default is true
|
||||
pub traffic_graph: Option<bool>,
|
||||
|
||||
/// show memory info (only for Clash Meta)
|
||||
pub enable_memory_usage: Option<bool>,
|
||||
|
||||
/// enable group icon
|
||||
pub enable_group_icon: Option<bool>,
|
||||
|
||||
/// common tray icon
|
||||
pub common_tray_icon: Option<bool>,
|
||||
|
||||
/// sysproxy tray icon
|
||||
pub sysproxy_tray_icon: Option<bool>,
|
||||
|
||||
/// tun tray icon
|
||||
pub tun_tray_icon: Option<bool>,
|
||||
|
||||
/// clash tun mode
|
||||
pub enable_tun_mode: Option<bool>,
|
||||
|
||||
@@ -80,8 +93,8 @@ pub struct IVerge {
|
||||
/// 默认的延迟测试连接
|
||||
pub default_latency_test: Option<String>,
|
||||
|
||||
/// 支持关闭字段过滤,避免meta的新字段都被过滤掉,默认为关闭
|
||||
pub enable_clash_fields: Option<bool>,
|
||||
/// 默认的延迟测试超时时间
|
||||
pub default_latency_timeout: Option<i32>,
|
||||
|
||||
/// 是否使用内部的脚本支持,默认为真
|
||||
pub enable_builtin_enhanced: Option<bool>,
|
||||
@@ -89,6 +102,9 @@ pub struct IVerge {
|
||||
/// proxy 页面布局 列数
|
||||
pub proxy_layout_column: Option<i32>,
|
||||
|
||||
/// 测试网站列表
|
||||
pub test_list: Option<Vec<IVergeTestItem>>,
|
||||
|
||||
/// 日志清理
|
||||
/// 0: 不清理; 1: 7天; 2: 30天; 3: 90天
|
||||
pub auto_log_clean: Option<i32>,
|
||||
@@ -97,11 +113,27 @@ pub struct IVerge {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub window_size_position: Option<Vec<f64>>,
|
||||
|
||||
/// window size and position
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub window_is_maximized: Option<bool>,
|
||||
|
||||
/// 是否启用随机端口
|
||||
pub enable_random_port: Option<bool>,
|
||||
|
||||
/// verge mixed port 用于覆盖 clash 的 mixed port
|
||||
pub verge_mixed_port: Option<u16>,
|
||||
|
||||
pub verge_socks_port: Option<u16>,
|
||||
|
||||
pub verge_port: Option<u16>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct IVergeTestItem {
|
||||
pub uid: Option<String>,
|
||||
pub name: Option<String>,
|
||||
pub icon: Option<String>,
|
||||
pub url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||
@@ -140,19 +172,24 @@ impl IVerge {
|
||||
env_type: Some("bash".into()),
|
||||
#[cfg(target_os = "windows")]
|
||||
env_type: Some("powershell".into()),
|
||||
theme_blur: Some(false),
|
||||
start_page: Some("/".into()),
|
||||
traffic_graph: Some(true),
|
||||
enable_memory_usage: Some(true),
|
||||
enable_group_icon: Some(true),
|
||||
common_tray_icon: Some(false),
|
||||
sysproxy_tray_icon: Some(false),
|
||||
tun_tray_icon: Some(false),
|
||||
enable_auto_launch: Some(false),
|
||||
enable_silent_start: Some(false),
|
||||
enable_system_proxy: Some(false),
|
||||
enable_random_port: Some(false),
|
||||
verge_mixed_port: Some(7897),
|
||||
verge_socks_port: Some(7898),
|
||||
verge_port: Some(7899),
|
||||
enable_proxy_guard: Some(false),
|
||||
proxy_guard_duration: Some(30),
|
||||
auto_close_connection: Some(true),
|
||||
enable_builtin_enhanced: Some(true),
|
||||
enable_clash_fields: Some(true),
|
||||
auto_log_clean: Some(3),
|
||||
..Self::default()
|
||||
}
|
||||
@@ -177,11 +214,16 @@ impl IVerge {
|
||||
patch!(app_log_level);
|
||||
patch!(language);
|
||||
patch!(theme_mode);
|
||||
patch!(theme_blur);
|
||||
patch!(tray_event);
|
||||
patch!(env_type);
|
||||
patch!(start_page);
|
||||
patch!(startup_script);
|
||||
patch!(traffic_graph);
|
||||
patch!(enable_memory_usage);
|
||||
patch!(enable_group_icon);
|
||||
patch!(common_tray_icon);
|
||||
patch!(sysproxy_tray_icon);
|
||||
patch!(tun_tray_icon);
|
||||
|
||||
patch!(enable_tun_mode);
|
||||
patch!(enable_service_mode);
|
||||
@@ -189,6 +231,8 @@ impl IVerge {
|
||||
patch!(enable_silent_start);
|
||||
patch!(enable_random_port);
|
||||
patch!(verge_mixed_port);
|
||||
patch!(verge_socks_port);
|
||||
patch!(verge_port);
|
||||
patch!(enable_system_proxy);
|
||||
patch!(enable_proxy_guard);
|
||||
patch!(system_proxy_bypass);
|
||||
@@ -201,11 +245,13 @@ impl IVerge {
|
||||
|
||||
patch!(auto_close_connection);
|
||||
patch!(default_latency_test);
|
||||
patch!(default_latency_timeout);
|
||||
patch!(enable_builtin_enhanced);
|
||||
patch!(proxy_layout_column);
|
||||
patch!(enable_clash_fields);
|
||||
patch!(test_list);
|
||||
patch!(auto_log_clean);
|
||||
patch!(window_size_position);
|
||||
patch!(window_is_maximized);
|
||||
}
|
||||
|
||||
/// 在初始化前尝试拿到单例端口的值
|
||||
|
||||
@@ -20,7 +20,7 @@ pub async fn put_configs(path: &str) -> Result<()> {
|
||||
|
||||
match response.status().as_u16() {
|
||||
204 => Ok(()),
|
||||
status @ _ => {
|
||||
status => {
|
||||
bail!("failed to put configs with status \"{status}\"")
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,11 @@ pub struct DelayRes {
|
||||
|
||||
/// GET /proxies/{name}/delay
|
||||
/// 获取代理延迟
|
||||
pub async fn get_proxy_delay(name: String, test_url: Option<String>) -> Result<DelayRes> {
|
||||
pub async fn get_proxy_delay(
|
||||
name: String,
|
||||
test_url: Option<String>,
|
||||
timeout: i32,
|
||||
) -> Result<DelayRes> {
|
||||
let (url, headers) = clash_client_info()?;
|
||||
let url = format!("{url}/proxies/{name}/delay");
|
||||
|
||||
@@ -57,7 +61,7 @@ pub async fn get_proxy_delay(name: String, test_url: Option<String>) -> Result<D
|
||||
let builder = client
|
||||
.get(&url)
|
||||
.headers(headers)
|
||||
.query(&[("timeout", "10000"), ("url", &test_url)]);
|
||||
.query(&[("timeout", &format!("{timeout}")), ("url", &test_url)]);
|
||||
let response = builder.send().await?;
|
||||
|
||||
Ok(response.json::<DelayRes>().await?)
|
||||
@@ -83,12 +87,12 @@ fn clash_client_info() -> Result<(String, HeaderMap)> {
|
||||
/// 缩短clash的日志
|
||||
pub fn parse_log(log: String) -> String {
|
||||
if log.starts_with("time=") && log.len() > 33 {
|
||||
return (&log[33..]).to_owned();
|
||||
return (log[33..]).to_owned();
|
||||
}
|
||||
if log.len() > 9 {
|
||||
return (&log[9..]).to_owned();
|
||||
return (log[9..]).to_owned();
|
||||
}
|
||||
return log;
|
||||
log
|
||||
}
|
||||
|
||||
/// 缩短clash -t的错误输出
|
||||
@@ -105,7 +109,7 @@ pub fn parse_check_output(log: String) -> String {
|
||||
};
|
||||
|
||||
if mr > m {
|
||||
return (&log[e..mr]).to_owned();
|
||||
return (log[e..mr]).to_owned();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +117,7 @@ pub fn parse_check_output(log: String) -> String {
|
||||
let r = log.find("path=").or(Some(log.len()));
|
||||
|
||||
if let (Some(l), Some(r)) = (l, r) {
|
||||
return (&log[(l + 6)..(r - 1)]).to_owned();
|
||||
return (log[(l + 6)..(r - 1)]).to_owned();
|
||||
}
|
||||
|
||||
log
|
||||
|
||||
@@ -5,7 +5,7 @@ use anyhow::{bail, Context, Result};
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use std::{fs, io::Write, sync::Arc, time::Duration};
|
||||
use sysinfo::{Pid, PidExt, ProcessExt, System, SystemExt};
|
||||
use sysinfo::{Pid, System};
|
||||
use tauri::api::process::{Command, CommandChild, CommandEvent};
|
||||
use tokio::time::sleep;
|
||||
|
||||
@@ -35,12 +35,12 @@ impl CoreManager {
|
||||
.map(|pid| {
|
||||
let mut system = System::new();
|
||||
system.refresh_all();
|
||||
system.process(Pid::from_u32(pid)).map(|proc| {
|
||||
if let Some(proc) = system.process(Pid::from_u32(pid)) {
|
||||
if proc.name().contains("clash") {
|
||||
log::debug!(target: "app", "kill old clash process");
|
||||
proc.kill();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
tauri::async_runtime::spawn(async {
|
||||
@@ -68,7 +68,7 @@ impl CoreManager {
|
||||
|
||||
if !output.status.success() {
|
||||
let error = clash_api::parse_check_output(output.stdout.clone());
|
||||
let error = match error.len() > 0 {
|
||||
let error = match !error.is_empty() {
|
||||
true => error,
|
||||
false => output.stdout.clone(),
|
||||
};
|
||||
@@ -110,7 +110,7 @@ impl CoreManager {
|
||||
use super::win_service;
|
||||
|
||||
// 服务模式
|
||||
let enable = { Config::verge().latest().enable_service_mode.clone() };
|
||||
let enable = { Config::verge().latest().enable_service_mode };
|
||||
let enable = enable.unwrap_or(false);
|
||||
|
||||
*self.use_service_mode.lock() = enable;
|
||||
@@ -144,10 +144,9 @@ impl CoreManager {
|
||||
|
||||
let config_path = dirs::path_to_str(&config_path)?;
|
||||
|
||||
// fix #212
|
||||
let args = match clash_core.as_str() {
|
||||
"clash-meta" => vec!["-m", "-d", app_dir, "-f", config_path],
|
||||
"clash-meta-alpha" => vec!["-m", "-d", app_dir, "-f", config_path],
|
||||
"clash-meta" => vec!["-d", app_dir, "-f", config_path],
|
||||
"clash-meta-alpha" => vec!["-d", app_dir, "-f", config_path],
|
||||
_ => vec!["-d", app_dir, "-f", config_path],
|
||||
};
|
||||
|
||||
@@ -259,7 +258,7 @@ impl CoreManager {
|
||||
/// 切换核心
|
||||
pub async fn change_core(&self, clash_core: Option<String>) -> Result<()> {
|
||||
let clash_core = clash_core.ok_or(anyhow::anyhow!("clash core is null"))?;
|
||||
const CLASH_CORES: [&str; 3] = ["clash", "clash-meta", "clash-meta-alpha"];
|
||||
const CLASH_CORES: [&str; 2] = ["clash-meta", "clash-meta-alpha"];
|
||||
|
||||
if !CLASH_CORES.contains(&clash_core.as_str()) {
|
||||
bail!("invalid clash core name \"{clash_core}\"");
|
||||
|
||||
@@ -28,7 +28,7 @@ impl Handle {
|
||||
self.app_handle
|
||||
.lock()
|
||||
.as_ref()
|
||||
.map_or(None, |a| a.get_window("main"))
|
||||
.and_then(|a| a.get_window("main"))
|
||||
}
|
||||
|
||||
pub fn refresh_clash() {
|
||||
|
||||
@@ -65,17 +65,12 @@ impl Hotkey {
|
||||
}
|
||||
|
||||
let f = match func.trim() {
|
||||
"open_dashboard" => || feat::open_dashboard(),
|
||||
"open_or_close_dashboard" => feat::open_or_close_dashboard,
|
||||
"clash_mode_rule" => || feat::change_clash_mode("rule".into()),
|
||||
"clash_mode_global" => || feat::change_clash_mode("global".into()),
|
||||
"clash_mode_direct" => || feat::change_clash_mode("direct".into()),
|
||||
"clash_mode_script" => || feat::change_clash_mode("script".into()),
|
||||
"toggle_system_proxy" => || feat::toggle_system_proxy(),
|
||||
"enable_system_proxy" => || feat::enable_system_proxy(),
|
||||
"disable_system_proxy" => || feat::disable_system_proxy(),
|
||||
"toggle_tun_mode" => || feat::toggle_tun_mode(),
|
||||
"enable_tun_mode" => || feat::enable_tun_mode(),
|
||||
"disable_tun_mode" => || feat::disable_tun_mode(),
|
||||
"toggle_system_proxy" => feat::toggle_system_proxy,
|
||||
"toggle_tun_mode" => feat::toggle_tun_mode,
|
||||
|
||||
_ => bail!("invalid function \"{func}\""),
|
||||
};
|
||||
@@ -86,7 +81,7 @@ impl Hotkey {
|
||||
}
|
||||
|
||||
fn unregister(&self, hotkey: &str) -> Result<()> {
|
||||
self.get_manager()?.unregister(&hotkey)?;
|
||||
self.get_manager()?.unregister(hotkey)?;
|
||||
log::info!(target: "app", "unregister hotkey {hotkey}");
|
||||
Ok(())
|
||||
}
|
||||
@@ -110,7 +105,7 @@ impl Hotkey {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_map_from_vec<'a>(hotkeys: &'a Vec<String>) -> HashMap<&'a str, &'a str> {
|
||||
fn get_map_from_vec(hotkeys: &Vec<String>) -> HashMap<&str, &str> {
|
||||
let mut map = HashMap::new();
|
||||
|
||||
hotkeys.iter().for_each(|hotkey| {
|
||||
|
||||
@@ -53,7 +53,7 @@ impl Sysopt {
|
||||
let verge = Config::verge();
|
||||
let verge = verge.latest();
|
||||
(
|
||||
verge.enable_system_proxy.clone().unwrap_or(false),
|
||||
verge.enable_system_proxy.unwrap_or(false),
|
||||
verge.system_proxy_bypass.clone(),
|
||||
)
|
||||
};
|
||||
@@ -66,7 +66,7 @@ impl Sysopt {
|
||||
};
|
||||
|
||||
if enable {
|
||||
let old = Sysproxy::get_system_proxy().map_or(None, |p| Some(p));
|
||||
let old = Sysproxy::get_system_proxy().ok();
|
||||
current.set_system_proxy()?;
|
||||
|
||||
*self.old_sysproxy.lock() = old;
|
||||
@@ -93,7 +93,7 @@ impl Sysopt {
|
||||
let verge = Config::verge();
|
||||
let verge = verge.latest();
|
||||
(
|
||||
verge.enable_system_proxy.clone().unwrap_or(false),
|
||||
verge.enable_system_proxy.unwrap_or(false),
|
||||
verge.system_proxy_bypass.clone(),
|
||||
)
|
||||
};
|
||||
@@ -102,6 +102,12 @@ impl Sysopt {
|
||||
sysproxy.enable = enable;
|
||||
sysproxy.bypass = bypass.unwrap_or(DEFAULT_BYPASS.into());
|
||||
|
||||
let port = Config::verge()
|
||||
.latest()
|
||||
.verge_mixed_port
|
||||
.unwrap_or(Config::clash().data().get_mixed_port());
|
||||
sysproxy.port = port;
|
||||
|
||||
sysproxy.set_system_proxy()?;
|
||||
*cur_sysproxy = Some(sysproxy);
|
||||
|
||||
@@ -142,9 +148,6 @@ impl Sysopt {
|
||||
|
||||
/// init the auto launch
|
||||
pub fn init_launch(&self) -> Result<()> {
|
||||
let enable = { Config::verge().latest().enable_auto_launch.clone() };
|
||||
let enable = enable.unwrap_or(false);
|
||||
|
||||
let app_exe = current_exe()?;
|
||||
let app_exe = dunce::canonicalize(app_exe)?;
|
||||
let app_name = app_exe
|
||||
@@ -198,28 +201,6 @@ impl Sysopt {
|
||||
.set_app_path(&app_path)
|
||||
.build()?;
|
||||
|
||||
// 避免在开发时将自启动关了
|
||||
#[cfg(feature = "verge-dev")]
|
||||
if !enable {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if enable && !auto.is_enabled().unwrap_or(false) {
|
||||
// 避免重复设置登录项
|
||||
let _ = auto.disable();
|
||||
auto.enable()?;
|
||||
} else if !enable {
|
||||
let _ = auto.disable();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
if enable {
|
||||
auto.enable()?;
|
||||
}
|
||||
|
||||
*self.auto_launch.lock() = Some(auto);
|
||||
|
||||
Ok(())
|
||||
@@ -233,7 +214,7 @@ impl Sysopt {
|
||||
drop(auto_launch);
|
||||
return self.init_launch();
|
||||
}
|
||||
let enable = { Config::verge().latest().enable_auto_launch.clone() };
|
||||
let enable = { Config::verge().latest().enable_auto_launch };
|
||||
let enable = enable.unwrap_or(false);
|
||||
let auto_launch = auto_launch.as_ref().unwrap();
|
||||
|
||||
@@ -271,9 +252,9 @@ impl Sysopt {
|
||||
let verge = Config::verge();
|
||||
let verge = verge.latest();
|
||||
(
|
||||
verge.enable_system_proxy.clone().unwrap_or(false),
|
||||
verge.enable_proxy_guard.clone().unwrap_or(false),
|
||||
verge.proxy_guard_duration.clone().unwrap_or(10),
|
||||
verge.enable_system_proxy.unwrap_or(false),
|
||||
verge.enable_proxy_guard.unwrap_or(false),
|
||||
verge.proxy_guard_duration.unwrap_or(10),
|
||||
verge.system_proxy_bypass.clone(),
|
||||
)
|
||||
};
|
||||
|
||||
@@ -40,7 +40,7 @@ impl Timer {
|
||||
let timer_map = self.timer_map.lock();
|
||||
let delay_timer = self.delay_timer.lock();
|
||||
|
||||
Config::profiles().latest().get_items().map(|items| {
|
||||
if let Some(items) = Config::profiles().latest().get_items() {
|
||||
items
|
||||
.iter()
|
||||
.filter_map(|item| {
|
||||
@@ -61,7 +61,7 @@ impl Timer {
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
use crate::{cmds, config::Config, feat, utils::resolve};
|
||||
use crate::{
|
||||
cmds,
|
||||
config::Config,
|
||||
feat,
|
||||
utils::{dirs, resolve},
|
||||
};
|
||||
use anyhow::Result;
|
||||
use tauri::{
|
||||
api, AppHandle, CustomMenuItem, Manager, SystemTrayEvent, SystemTrayMenu, SystemTrayMenuItem,
|
||||
@@ -129,26 +134,47 @@ impl Tray {
|
||||
let verge = verge.latest();
|
||||
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false);
|
||||
let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false);
|
||||
let common_tray_icon = verge.common_tray_icon.as_ref().unwrap_or(&false);
|
||||
let sysproxy_tray_icon = verge.sysproxy_tray_icon.as_ref().unwrap_or(&false);
|
||||
let tun_tray_icon = verge.tun_tray_icon.as_ref().unwrap_or(&false);
|
||||
|
||||
let mut indication_icon = if *system_proxy {
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let icon = include_bytes!("../../icons/tray-icon-sys.png").to_vec();
|
||||
let mut icon = include_bytes!("../../icons/tray-icon-sys.png").to_vec();
|
||||
#[cfg(target_os = "macos")]
|
||||
let icon = include_bytes!("../../icons/mac-tray-icon-sys.png").to_vec();
|
||||
let mut icon = include_bytes!("../../icons/mac-tray-icon-sys.png").to_vec();
|
||||
if *sysproxy_tray_icon {
|
||||
let path = dirs::app_home_dir()?.join("icons").join("sysproxy.png");
|
||||
if path.exists() {
|
||||
icon = std::fs::read(path).unwrap();
|
||||
}
|
||||
}
|
||||
icon
|
||||
} else {
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let icon = include_bytes!("../../icons/tray-icon.png").to_vec();
|
||||
let mut icon = include_bytes!("../../icons/tray-icon.png").to_vec();
|
||||
#[cfg(target_os = "macos")]
|
||||
let icon = include_bytes!("../../icons/mac-tray-icon.png").to_vec();
|
||||
let mut icon = include_bytes!("../../icons/mac-tray-icon.png").to_vec();
|
||||
if *common_tray_icon {
|
||||
let path = dirs::app_home_dir()?.join("icons").join("common.png");
|
||||
if path.exists() {
|
||||
icon = std::fs::read(path).unwrap();
|
||||
}
|
||||
}
|
||||
icon
|
||||
};
|
||||
|
||||
if *tun_mode {
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let icon = include_bytes!("../../icons/tray-icon-tun.png").to_vec();
|
||||
let mut icon = include_bytes!("../../icons/tray-icon-tun.png").to_vec();
|
||||
#[cfg(target_os = "macos")]
|
||||
let icon = include_bytes!("../../icons/mac-tray-icon-tun.png").to_vec();
|
||||
let mut icon = include_bytes!("../../icons/mac-tray-icon-tun.png").to_vec();
|
||||
if *tun_tray_icon {
|
||||
let path = dirs::app_home_dir()?.join("icons").join("tun.png");
|
||||
if path.exists() {
|
||||
icon = std::fs::read(path).unwrap();
|
||||
}
|
||||
}
|
||||
indication_icon = icon
|
||||
}
|
||||
|
||||
@@ -203,14 +229,8 @@ impl Tray {
|
||||
"open_logs_dir" => crate::log_err!(cmds::open_logs_dir()),
|
||||
"restart_clash" => feat::restart_clash_core(),
|
||||
"restart_app" => api::process::restart(&app_handle.env()),
|
||||
"quit" => {
|
||||
let _ = resolve::save_window_size_position(app_handle, true);
|
||||
"quit" => cmds::exit_app(app_handle.clone()),
|
||||
|
||||
resolve::resolve_reset();
|
||||
api::process::kill_children();
|
||||
app_handle.exit(0);
|
||||
std::process::exit(0);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
|
||||
@@ -3,9 +3,9 @@ use std::collections::HashSet;
|
||||
|
||||
pub const HANDLE_FIELDS: [&str; 9] = [
|
||||
"mode",
|
||||
"port",
|
||||
"socks-port",
|
||||
"mixed-port",
|
||||
"socks-port",
|
||||
"port",
|
||||
"allow-lan",
|
||||
"log-level",
|
||||
"ipv6",
|
||||
@@ -15,71 +15,13 @@ pub const HANDLE_FIELDS: [&str; 9] = [
|
||||
|
||||
pub const DEFAULT_FIELDS: [&str; 5] = [
|
||||
"proxies",
|
||||
"proxy-groups",
|
||||
"proxy-providers",
|
||||
"rules",
|
||||
"proxy-groups",
|
||||
"rule-providers",
|
||||
"rules",
|
||||
];
|
||||
|
||||
pub const OTHERS_FIELDS: [&str; 31] = [
|
||||
"dns",
|
||||
"tun",
|
||||
"ebpf",
|
||||
"hosts",
|
||||
"script",
|
||||
"profile",
|
||||
"payload",
|
||||
"tunnels",
|
||||
"auto-redir",
|
||||
"experimental",
|
||||
"interface-name",
|
||||
"routing-mark",
|
||||
"redir-port",
|
||||
"tproxy-port",
|
||||
"iptables",
|
||||
"external-ui",
|
||||
"bind-address",
|
||||
"authentication",
|
||||
"tls", // meta
|
||||
"sniffer", // meta
|
||||
"geox-url", // meta
|
||||
"listeners", // meta
|
||||
"sub-rules", // meta
|
||||
"geodata-mode", // meta
|
||||
"unified-delay", // meta
|
||||
"tcp-concurrent", // meta
|
||||
"enable-process", // meta
|
||||
"find-process-mode", // meta
|
||||
"skip-auth-prefixes", // meta
|
||||
"external-controller-tls", // meta
|
||||
"global-client-fingerprint", // meta
|
||||
];
|
||||
|
||||
pub fn use_clash_fields() -> Vec<String> {
|
||||
DEFAULT_FIELDS
|
||||
.into_iter()
|
||||
.chain(HANDLE_FIELDS)
|
||||
.chain(OTHERS_FIELDS)
|
||||
.map(|s| s.to_string())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn use_valid_fields(mut valid: Vec<String>) -> Vec<String> {
|
||||
let others = Vec::from(OTHERS_FIELDS);
|
||||
|
||||
valid.iter_mut().for_each(|s| s.make_ascii_lowercase());
|
||||
valid
|
||||
.into_iter()
|
||||
.filter(|s| others.contains(&s.as_str()))
|
||||
.chain(DEFAULT_FIELDS.iter().map(|s| s.to_string()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn use_filter(config: Mapping, filter: &Vec<String>, enable: bool) -> Mapping {
|
||||
if !enable {
|
||||
return config;
|
||||
}
|
||||
|
||||
pub fn use_filter(config: Mapping, filter: &Vec<String>) -> Mapping {
|
||||
let mut ret = Mapping::new();
|
||||
|
||||
for (key, value) in config.into_iter() {
|
||||
@@ -105,40 +47,35 @@ pub fn use_lowercase(config: Mapping) -> Mapping {
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn use_sort(config: Mapping, enable_filter: bool) -> Mapping {
|
||||
pub fn use_sort(config: Mapping) -> Mapping {
|
||||
let mut ret = Mapping::new();
|
||||
HANDLE_FIELDS.into_iter().for_each(|key| {
|
||||
let key = Value::from(key);
|
||||
if let Some(value) = config.get(&key) {
|
||||
ret.insert(key, value.clone());
|
||||
}
|
||||
});
|
||||
|
||||
HANDLE_FIELDS
|
||||
let supported_keys: HashSet<&str> = HANDLE_FIELDS.into_iter().chain(DEFAULT_FIELDS).collect();
|
||||
|
||||
let config_keys: HashSet<&str> = config
|
||||
.keys()
|
||||
.filter_map(|e| e.as_str())
|
||||
.into_iter()
|
||||
.chain(OTHERS_FIELDS)
|
||||
.chain(DEFAULT_FIELDS)
|
||||
.for_each(|key| {
|
||||
let key = Value::from(key);
|
||||
config.get(&key).map(|value| {
|
||||
ret.insert(key, value.clone());
|
||||
});
|
||||
});
|
||||
.collect();
|
||||
|
||||
if !enable_filter {
|
||||
let supported_keys: HashSet<&str> = HANDLE_FIELDS
|
||||
.into_iter()
|
||||
.chain(OTHERS_FIELDS)
|
||||
.chain(DEFAULT_FIELDS)
|
||||
.collect();
|
||||
|
||||
let config_keys: HashSet<&str> = config
|
||||
.keys()
|
||||
.filter_map(|e| e.as_str())
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
config_keys.difference(&supported_keys).for_each(|&key| {
|
||||
let key = Value::from(key);
|
||||
config.get(&key).map(|value| {
|
||||
ret.insert(key, value.clone());
|
||||
});
|
||||
});
|
||||
}
|
||||
config_keys.difference(&supported_keys).for_each(|&key| {
|
||||
let key = Value::from(key);
|
||||
if let Some(value) = config.get(&key) {
|
||||
ret.insert(key, value.clone());
|
||||
}
|
||||
});
|
||||
DEFAULT_FIELDS.into_iter().for_each(|key| {
|
||||
let key = Value::from(key);
|
||||
if let Some(value) = config.get(&key) {
|
||||
ret.insert(key, value.clone());
|
||||
}
|
||||
});
|
||||
|
||||
ret
|
||||
}
|
||||
@@ -150,7 +87,7 @@ pub fn use_keys(config: &Mapping) -> Vec<String> {
|
||||
.map(|s| {
|
||||
let mut s = s.to_string();
|
||||
s.make_ascii_lowercase();
|
||||
return s;
|
||||
s
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
use super::{use_filter, use_lowercase};
|
||||
use serde_yaml::{self, Mapping, Sequence, Value};
|
||||
|
||||
const MERGE_FIELDS: [&str; 6] = [
|
||||
const MERGE_FIELDS: [&str; 10] = [
|
||||
"prepend-rules",
|
||||
"append-rules",
|
||||
"prepend-rule-providers",
|
||||
"append-rule-providers",
|
||||
"prepend-proxies",
|
||||
"append-proxies",
|
||||
"prepend-proxy-providers",
|
||||
"append-proxy-providers",
|
||||
"prepend-proxy-groups",
|
||||
"append-proxy-groups",
|
||||
];
|
||||
@@ -14,12 +18,46 @@ pub fn use_merge(merge: Mapping, mut config: Mapping) -> Mapping {
|
||||
// 直接覆盖原字段
|
||||
use_lowercase(merge.clone())
|
||||
.into_iter()
|
||||
.filter(|(key, _)| !MERGE_FIELDS.contains(&key.as_str().unwrap_or_default()))
|
||||
.for_each(|(key, value)| {
|
||||
config.insert(key, value);
|
||||
});
|
||||
|
||||
let merge_list = MERGE_FIELDS.iter().map(|s| s.to_string());
|
||||
let merge = use_filter(merge, &merge_list.collect(), true);
|
||||
let merge = use_filter(merge, &merge_list.collect());
|
||||
|
||||
["rule-providers", "proxy-providers"]
|
||||
.iter()
|
||||
.for_each(|key_str| {
|
||||
let key_val = Value::from(key_str.to_string());
|
||||
|
||||
let mut map = Mapping::default();
|
||||
|
||||
map = config.get(&key_val).map_or(map.clone(), |val| {
|
||||
val.as_mapping().map_or(map, |v| v.clone())
|
||||
});
|
||||
|
||||
let pre_key = Value::from(format!("prepend-{key_str}"));
|
||||
let post_key = Value::from(format!("append-{key_str}"));
|
||||
|
||||
if let Some(pre_val) = merge.get(&pre_key) {
|
||||
if pre_val.is_mapping() {
|
||||
let mut pre_val = pre_val.as_mapping().unwrap().clone();
|
||||
pre_val.extend(map);
|
||||
map = pre_val;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(post_val) = merge.get(&post_key) {
|
||||
if post_val.is_mapping() {
|
||||
map.extend(post_val.as_mapping().unwrap().clone());
|
||||
}
|
||||
}
|
||||
|
||||
if !map.is_empty() {
|
||||
config.insert(key_val, Value::from(map));
|
||||
}
|
||||
});
|
||||
|
||||
["rules", "proxies", "proxy-groups"]
|
||||
.iter()
|
||||
@@ -48,7 +86,9 @@ pub fn use_merge(merge: Mapping, mut config: Mapping) -> Mapping {
|
||||
}
|
||||
}
|
||||
|
||||
config.insert(key_val, Value::from(list));
|
||||
if !list.is_empty() {
|
||||
config.insert(key_val, Value::from(list));
|
||||
}
|
||||
});
|
||||
config
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
mod chain;
|
||||
mod field;
|
||||
pub mod field;
|
||||
mod merge;
|
||||
mod script;
|
||||
mod tun;
|
||||
|
||||
pub(self) use self::field::*;
|
||||
|
||||
use self::chain::*;
|
||||
use self::field::*;
|
||||
use self::merge::*;
|
||||
use self::script::*;
|
||||
use self::tun::*;
|
||||
@@ -23,50 +22,43 @@ pub fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
||||
// config.yaml 的订阅
|
||||
let clash_config = { Config::clash().latest().0.clone() };
|
||||
|
||||
let (clash_core, enable_tun, enable_builtin, enable_filter) = {
|
||||
let (clash_core, enable_tun, enable_builtin) = {
|
||||
let verge = Config::verge();
|
||||
let verge = verge.latest();
|
||||
(
|
||||
verge.clash_core.clone(),
|
||||
verge.enable_tun_mode.clone().unwrap_or(false),
|
||||
verge.enable_builtin_enhanced.clone().unwrap_or(true),
|
||||
verge.enable_clash_fields.clone().unwrap_or(true),
|
||||
verge.enable_tun_mode.unwrap_or(false),
|
||||
verge.enable_builtin_enhanced.unwrap_or(true),
|
||||
)
|
||||
};
|
||||
|
||||
// 从profiles里拿东西
|
||||
let (mut config, chain, valid) = {
|
||||
let (mut config, chain) = {
|
||||
let profiles = Config::profiles();
|
||||
let profiles = profiles.latest();
|
||||
|
||||
let current = profiles.current_mapping().unwrap_or(Mapping::new());
|
||||
let current = profiles.current_mapping().unwrap_or_default();
|
||||
|
||||
let chain = match profiles.chain.as_ref() {
|
||||
Some(chain) => chain
|
||||
.iter()
|
||||
.filter_map(|uid| profiles.get_item(uid).ok())
|
||||
.filter_map(|item| <Option<ChainItem>>::from(item))
|
||||
.filter_map(<Option<ChainItem>>::from)
|
||||
.collect::<Vec<ChainItem>>(),
|
||||
None => vec![],
|
||||
};
|
||||
|
||||
let valid = profiles.valid.clone().unwrap_or(vec![]);
|
||||
|
||||
(current, chain, valid)
|
||||
(current, chain)
|
||||
};
|
||||
|
||||
let mut result_map = HashMap::new(); // 保存脚本日志
|
||||
let mut exists_keys = use_keys(&config); // 保存出现过的keys
|
||||
|
||||
let valid = use_valid_fields(valid);
|
||||
config = use_filter(config, &valid, enable_filter);
|
||||
|
||||
// 处理用户的profile
|
||||
chain.into_iter().for_each(|item| match item.data {
|
||||
ChainType::Merge(merge) => {
|
||||
exists_keys.extend(use_keys(&merge));
|
||||
config = use_merge(merge, config.to_owned());
|
||||
config = use_filter(config.to_owned(), &valid, enable_filter);
|
||||
}
|
||||
ChainType::Script(script) => {
|
||||
let mut logs = vec![];
|
||||
@@ -74,7 +66,7 @@ pub fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
||||
match use_script(script, config.to_owned()) {
|
||||
Ok((res_config, res_logs)) => {
|
||||
exists_keys.extend(use_keys(&res_config));
|
||||
config = use_filter(res_config, &valid, enable_filter);
|
||||
config = res_config;
|
||||
logs.extend(res_logs);
|
||||
}
|
||||
Err(err) => logs.push(("exception".into(), err.to_string())),
|
||||
@@ -86,11 +78,20 @@ pub fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
||||
|
||||
// 合并默认的config
|
||||
for (key, value) in clash_config.into_iter() {
|
||||
config.insert(key, value);
|
||||
if key.as_str() == Some("tun") {
|
||||
let mut tun = config.get_mut("tun").map_or(Mapping::new(), |val| {
|
||||
val.as_mapping().cloned().unwrap_or(Mapping::new())
|
||||
});
|
||||
let patch_tun = value.as_mapping().cloned().unwrap_or(Mapping::new());
|
||||
for (key, value) in patch_tun.into_iter() {
|
||||
tun.insert(key, value);
|
||||
}
|
||||
config.insert("tun".into(), tun.into());
|
||||
} else {
|
||||
config.insert(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
let clash_fields = use_clash_fields();
|
||||
|
||||
// 内建脚本最后跑
|
||||
if enable_builtin {
|
||||
ChainItem::builtin()
|
||||
@@ -103,7 +104,7 @@ pub fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
||||
match item.data {
|
||||
ChainType::Script(script) => match use_script(script, config.to_owned()) {
|
||||
Ok((res_config, _)) => {
|
||||
config = use_filter(res_config, &clash_fields, enable_filter);
|
||||
config = res_config;
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!(target: "app", "builtin script error `{err}`");
|
||||
@@ -114,12 +115,11 @@ pub fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
||||
});
|
||||
}
|
||||
|
||||
config = use_filter(config, &clash_fields, enable_filter);
|
||||
config = use_tun(config, enable_tun);
|
||||
config = use_sort(config, enable_filter);
|
||||
config = use_sort(config);
|
||||
|
||||
let mut exists_set = HashSet::new();
|
||||
exists_set.extend(exists_keys.into_iter().filter(|s| clash_fields.contains(s)));
|
||||
exists_set.extend(exists_keys.into_iter());
|
||||
exists_keys = exists_set.into_iter().collect();
|
||||
|
||||
(config, exists_keys, result_map)
|
||||
|
||||
@@ -47,7 +47,7 @@ pub fn use_script(script: String, config: Mapping) -> Result<(Mapping, Vec<(Stri
|
||||
if result == "\"\"" {
|
||||
anyhow::bail!("main function should return object");
|
||||
}
|
||||
return Ok(serde_json::from_str::<Mapping>(result.as_str())?);
|
||||
Ok(serde_json::from_str::<Mapping>(result.as_str())?)
|
||||
});
|
||||
|
||||
let mut out = outputs.lock().unwrap();
|
||||
|
||||
@@ -30,12 +30,6 @@ pub fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
|
||||
});
|
||||
|
||||
revise!(tun_val, "enable", enable);
|
||||
if enable {
|
||||
append!(tun_val, "stack", "gvisor");
|
||||
append!(tun_val, "dns-hijack", vec!["any:53"]);
|
||||
append!(tun_val, "auto-route", true);
|
||||
append!(tun_val, "auto-detect-interface", true);
|
||||
}
|
||||
|
||||
revise!(config, "tun", tun_val);
|
||||
|
||||
|
||||
@@ -10,13 +10,19 @@ use crate::log_err;
|
||||
use crate::utils::resolve;
|
||||
use anyhow::{bail, Result};
|
||||
use serde_yaml::{Mapping, Value};
|
||||
use tauri::{AppHandle, ClipboardManager};
|
||||
use tauri::{AppHandle, ClipboardManager, Manager};
|
||||
|
||||
// 打开面板
|
||||
pub fn open_dashboard() {
|
||||
pub fn open_or_close_dashboard() {
|
||||
let handle = handle::Handle::global();
|
||||
let app_handle = handle.app_handle.lock();
|
||||
if let Some(app_handle) = app_handle.as_ref() {
|
||||
if let Some(window) = app_handle.get_window("main") {
|
||||
if let Ok(true) = window.is_focused() {
|
||||
let _ = window.close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
resolve::create_window(app_handle);
|
||||
}
|
||||
}
|
||||
@@ -62,7 +68,7 @@ pub fn change_clash_mode(mode: String) {
|
||||
|
||||
// 切换系统代理
|
||||
pub fn toggle_system_proxy() {
|
||||
let enable = Config::verge().draft().enable_system_proxy.clone();
|
||||
let enable = Config::verge().draft().enable_system_proxy;
|
||||
let enable = enable.unwrap_or(false);
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
@@ -78,39 +84,9 @@ pub fn toggle_system_proxy() {
|
||||
});
|
||||
}
|
||||
|
||||
// 打开系统代理
|
||||
pub fn enable_system_proxy() {
|
||||
tauri::async_runtime::spawn(async {
|
||||
match patch_verge(IVerge {
|
||||
enable_system_proxy: Some(true),
|
||||
..IVerge::default()
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(_) => handle::Handle::refresh_verge(),
|
||||
Err(err) => log::error!(target: "app", "{err}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 关闭系统代理
|
||||
pub fn disable_system_proxy() {
|
||||
tauri::async_runtime::spawn(async {
|
||||
match patch_verge(IVerge {
|
||||
enable_system_proxy: Some(false),
|
||||
..IVerge::default()
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(_) => handle::Handle::refresh_verge(),
|
||||
Err(err) => log::error!(target: "app", "{err}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 切换tun模式
|
||||
pub fn toggle_tun_mode() {
|
||||
let enable = Config::verge().data().enable_tun_mode.clone();
|
||||
let enable = Config::verge().data().enable_tun_mode;
|
||||
let enable = enable.unwrap_or(false);
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
@@ -126,52 +102,24 @@ pub fn toggle_tun_mode() {
|
||||
});
|
||||
}
|
||||
|
||||
// 打开tun模式
|
||||
pub fn enable_tun_mode() {
|
||||
tauri::async_runtime::spawn(async {
|
||||
match patch_verge(IVerge {
|
||||
enable_tun_mode: Some(true),
|
||||
..IVerge::default()
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(_) => handle::Handle::refresh_verge(),
|
||||
Err(err) => log::error!(target: "app", "{err}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 关闭tun模式
|
||||
pub fn disable_tun_mode() {
|
||||
tauri::async_runtime::spawn(async {
|
||||
match patch_verge(IVerge {
|
||||
enable_tun_mode: Some(false),
|
||||
..IVerge::default()
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(_) => handle::Handle::refresh_verge(),
|
||||
Err(err) => log::error!(target: "app", "{err}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// 修改clash的订阅
|
||||
pub async fn patch_clash(patch: Mapping) -> Result<()> {
|
||||
Config::clash().draft().patch_config(patch.clone());
|
||||
|
||||
match {
|
||||
let mixed_port = patch.get("mixed-port");
|
||||
let socks_port = patch.get("socks-port");
|
||||
let port = patch.get("port");
|
||||
let enable_random_port = Config::verge().latest().enable_random_port.unwrap_or(false);
|
||||
if mixed_port.is_some() && !enable_random_port {
|
||||
let changed = mixed_port.clone().unwrap()
|
||||
let changed = mixed_port.unwrap()
|
||||
!= Config::verge()
|
||||
.latest()
|
||||
.verge_mixed_port
|
||||
.unwrap_or(Config::clash().data().get_mixed_port());
|
||||
// 检查端口占用
|
||||
if changed {
|
||||
if let Some(port) = mixed_port.clone().unwrap().as_u64() {
|
||||
if let Some(port) = mixed_port.unwrap().as_u64() {
|
||||
if !port_scanner::local_port_available(port as u16) {
|
||||
Config::clash().discard();
|
||||
bail!("port already in use");
|
||||
@@ -182,6 +130,8 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> {
|
||||
|
||||
// 激活订阅
|
||||
if mixed_port.is_some()
|
||||
|| socks_port.is_some()
|
||||
|| port.is_some()
|
||||
|| patch.get("secret").is_some()
|
||||
|| patch.get("external-controller").is_some()
|
||||
{
|
||||
@@ -225,6 +175,10 @@ pub async fn patch_verge(patch: IVerge) -> Result<()> {
|
||||
let system_proxy = patch.enable_system_proxy;
|
||||
let proxy_bypass = patch.system_proxy_bypass;
|
||||
let language = patch.language;
|
||||
let port = patch.verge_mixed_port;
|
||||
let common_tray_icon = patch.common_tray_icon;
|
||||
let sysproxy_tray_icon = patch.sysproxy_tray_icon;
|
||||
let tun_tray_icon = patch.tun_tray_icon;
|
||||
|
||||
match {
|
||||
#[cfg(target_os = "windows")]
|
||||
@@ -249,7 +203,7 @@ pub async fn patch_verge(patch: IVerge) -> Result<()> {
|
||||
if auto_launch.is_some() {
|
||||
sysopt::Sysopt::global().update_launch()?;
|
||||
}
|
||||
if system_proxy.is_some() || proxy_bypass.is_some() {
|
||||
if system_proxy.is_some() || proxy_bypass.is_some() || port.is_some() {
|
||||
sysopt::Sysopt::global().update_sysproxy()?;
|
||||
sysopt::Sysopt::global().guard_proxy();
|
||||
}
|
||||
@@ -264,7 +218,12 @@ pub async fn patch_verge(patch: IVerge) -> Result<()> {
|
||||
|
||||
if language.is_some() {
|
||||
handle::Handle::update_systray()?;
|
||||
} else if system_proxy.or(tun_mode).is_some() {
|
||||
} else if system_proxy.is_some()
|
||||
|| tun_mode.is_some()
|
||||
|| common_tray_icon.is_some()
|
||||
|| sysproxy_tray_icon.is_some()
|
||||
|| tun_tray_icon.is_some()
|
||||
{
|
||||
handle::Handle::update_systray_part()?;
|
||||
}
|
||||
|
||||
@@ -368,3 +327,42 @@ pub fn copy_clash_env(app_handle: &AppHandle) {
|
||||
_ => log::error!(target: "app", "copy_clash_env: Invalid env type! {env_type}"),
|
||||
};
|
||||
}
|
||||
|
||||
pub async fn test_delay(url: String) -> Result<u32> {
|
||||
use tokio::time::{Duration, Instant};
|
||||
let mut builder = reqwest::ClientBuilder::new().use_rustls_tls().no_proxy();
|
||||
|
||||
let port = Config::verge()
|
||||
.latest()
|
||||
.verge_mixed_port
|
||||
.unwrap_or(Config::clash().data().get_mixed_port());
|
||||
let tun_mode = Config::verge().latest().enable_tun_mode.unwrap_or(false);
|
||||
|
||||
let proxy_scheme = format!("http://127.0.0.1:{port}");
|
||||
|
||||
if !tun_mode {
|
||||
if let Ok(proxy) = reqwest::Proxy::http(&proxy_scheme) {
|
||||
builder = builder.proxy(proxy);
|
||||
}
|
||||
if let Ok(proxy) = reqwest::Proxy::https(&proxy_scheme) {
|
||||
builder = builder.proxy(proxy);
|
||||
}
|
||||
if let Ok(proxy) = reqwest::Proxy::all(&proxy_scheme) {
|
||||
builder = builder.proxy(proxy);
|
||||
}
|
||||
}
|
||||
|
||||
let request = builder
|
||||
.timeout(Duration::from_millis(10000))
|
||||
.build()?
|
||||
.get(url).header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0");
|
||||
let start = Instant::now();
|
||||
|
||||
let response = request.send().await?;
|
||||
if response.status().is_success() {
|
||||
let delay = start.elapsed().as_millis() as u32;
|
||||
Ok(delay)
|
||||
} else {
|
||||
Ok(10000u32)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,10 @@ fn main() -> std::io::Result<()> {
|
||||
#[allow(unused_mut)]
|
||||
let mut builder = tauri::Builder::default()
|
||||
.system_tray(SystemTray::new())
|
||||
.setup(|app| Ok(resolve::resolve_setup(app)))
|
||||
.setup(|app| {
|
||||
resolve::resolve_setup(app);
|
||||
Ok(())
|
||||
})
|
||||
.on_system_tray_event(core::tray::Tray::on_system_tray_event)
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
// common
|
||||
@@ -34,6 +37,7 @@ fn main() -> std::io::Result<()> {
|
||||
cmds::open_logs_dir,
|
||||
cmds::open_web_url,
|
||||
cmds::open_core_dir,
|
||||
cmds::get_portable_flag,
|
||||
// cmds::kill_sidecar,
|
||||
cmds::restart_sidecar,
|
||||
cmds::grant_permission,
|
||||
@@ -50,6 +54,10 @@ fn main() -> std::io::Result<()> {
|
||||
// verge
|
||||
cmds::get_verge_config,
|
||||
cmds::patch_verge_config,
|
||||
cmds::test_delay,
|
||||
cmds::get_app_dir,
|
||||
cmds::copy_icon_file,
|
||||
cmds::exit_app,
|
||||
// cmds::update_hotkeys,
|
||||
// profile
|
||||
cmds::get_profiles,
|
||||
@@ -100,45 +108,21 @@ fn main() -> std::io::Result<()> {
|
||||
tauri::RunEvent::ExitRequested { api, .. } => {
|
||||
api.prevent_exit();
|
||||
}
|
||||
tauri::RunEvent::Exit => {
|
||||
resolve::resolve_reset();
|
||||
api::process::kill_children();
|
||||
app_handle.exit(0);
|
||||
}
|
||||
tauri::RunEvent::Updater(tauri::UpdaterEvent::Downloaded) => {
|
||||
resolve::resolve_reset();
|
||||
api::process::kill_children();
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
tauri::RunEvent::WindowEvent { label, event, .. } => {
|
||||
use tauri::Manager;
|
||||
|
||||
if label == "main" {
|
||||
match event {
|
||||
tauri::WindowEvent::CloseRequested { api, .. } => {
|
||||
api.prevent_close();
|
||||
let _ = resolve::save_window_size_position(&app_handle, true);
|
||||
|
||||
app_handle.get_window("main").map(|win| {
|
||||
let _ = win.hide();
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
tauri::RunEvent::WindowEvent { label, event, .. } => {
|
||||
if label == "main" {
|
||||
match event {
|
||||
tauri::WindowEvent::Destroyed => {
|
||||
let _ = resolve::save_window_size_position(&app_handle, true);
|
||||
let _ = resolve::save_window_size_position(app_handle, true);
|
||||
}
|
||||
tauri::WindowEvent::CloseRequested { .. } => {
|
||||
let _ = resolve::save_window_size_position(&app_handle, true);
|
||||
let _ = resolve::save_window_size_position(app_handle, true);
|
||||
}
|
||||
tauri::WindowEvent::Moved(_) | tauri::WindowEvent::Resized(_) => {
|
||||
let _ = resolve::save_window_size_position(&app_handle, false);
|
||||
let _ = resolve::save_window_size_position(app_handle, false);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::core::handle;
|
||||
use anyhow::Result;
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::path::PathBuf;
|
||||
use tauri::{
|
||||
api::path::{data_dir, resource_dir},
|
||||
@@ -7,16 +8,18 @@ use tauri::{
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "verge-dev"))]
|
||||
static APP_ID: &str = "io.github.clash-verge-rev.clash-verge-rev";
|
||||
pub static APP_ID: &str = "io.github.clash-verge-rev.clash-verge-rev";
|
||||
#[cfg(feature = "verge-dev")]
|
||||
static APP_ID: &str = "io.github.clash-verge-rev.clash-verge-rev.dev";
|
||||
pub static APP_ID: &str = "io.github.clash-verge-rev.clash-verge-rev.dev";
|
||||
|
||||
pub static PORTABLE_FLAG: OnceCell<bool> = OnceCell::new();
|
||||
|
||||
static CLASH_CONFIG: &str = "config.yaml";
|
||||
static VERGE_CONFIG: &str = "verge.yaml";
|
||||
static PROFILE_YAML: &str = "profiles.yaml";
|
||||
|
||||
/// get the verge app home dir
|
||||
pub fn app_home_dir() -> Result<PathBuf> {
|
||||
/// init portable flag
|
||||
pub fn init_portable_flag() -> Result<()> {
|
||||
use tauri::utils::platform::current_exe;
|
||||
|
||||
let app_exe = current_exe()?;
|
||||
@@ -24,13 +27,27 @@ pub fn app_home_dir() -> Result<PathBuf> {
|
||||
let dir = PathBuf::from(dir).join(".config/PORTABLE");
|
||||
|
||||
if dir.exists() {
|
||||
let app_exe = dunce::canonicalize(app_exe)?;
|
||||
let app_dir = app_exe
|
||||
.parent()
|
||||
.ok_or(anyhow::anyhow!("failed to get the portable app dir"))?;
|
||||
return Ok(PathBuf::from(app_dir).join(".config").join(APP_ID));
|
||||
PORTABLE_FLAG.get_or_init(|| true);
|
||||
}
|
||||
}
|
||||
PORTABLE_FLAG.get_or_init(|| false);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// get the verge app home dir
|
||||
pub fn app_home_dir() -> Result<PathBuf> {
|
||||
use tauri::utils::platform::current_exe;
|
||||
|
||||
let flag = PORTABLE_FLAG.get().unwrap_or(&false);
|
||||
if *flag {
|
||||
let app_exe = current_exe()?;
|
||||
let app_exe = dunce::canonicalize(app_exe)?;
|
||||
let app_dir = app_exe
|
||||
.parent()
|
||||
.ok_or(anyhow::anyhow!("failed to get the portable app dir"))?;
|
||||
return Ok(PathBuf::from(app_dir).join(".config").join(APP_ID));
|
||||
}
|
||||
|
||||
Ok(data_dir()
|
||||
.ok_or(anyhow::anyhow!("failed to get app home dir"))?
|
||||
.join(APP_ID))
|
||||
@@ -75,14 +92,9 @@ pub fn clash_pid_path() -> Result<PathBuf> {
|
||||
Ok(app_home_dir()?.join("clash.pid"))
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn service_dir() -> Result<PathBuf> {
|
||||
Ok(app_home_dir()?.join("service"))
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn service_path() -> Result<PathBuf> {
|
||||
Ok(service_dir()?.join("clash-verge-service.exe"))
|
||||
Ok(app_resources_dir()?.join("clash-verge-service.exe"))
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
|
||||
@@ -14,7 +14,7 @@ pub fn read_yaml<T: DeserializeOwned>(path: &PathBuf) -> Result<T> {
|
||||
bail!("file not found \"{}\"", path.display());
|
||||
}
|
||||
|
||||
let yaml_str = fs::read_to_string(&path)
|
||||
let yaml_str = fs::read_to_string(path)
|
||||
.with_context(|| format!("failed to read the file \"{}\"", path.display()))?;
|
||||
|
||||
serde_yaml::from_str::<T>(&yaml_str).with_context(|| {
|
||||
@@ -71,18 +71,28 @@ pub fn get_uid(prefix: &str) -> String {
|
||||
/// parse the string
|
||||
/// xxx=123123; => 123123
|
||||
pub fn parse_str<T: FromStr>(target: &str, key: &str) -> Option<T> {
|
||||
target.find(key).and_then(|idx| {
|
||||
let idx = idx + key.len();
|
||||
let value = &target[idx..];
|
||||
|
||||
match value.split(';').nth(0) {
|
||||
Some(value) => value.trim().parse(),
|
||||
None => value.trim().parse(),
|
||||
target.split(';').map(str::trim).find_map(|s| {
|
||||
let mut parts = s.splitn(2, '=');
|
||||
match (parts.next(), parts.next()) {
|
||||
(Some(k), Some(v)) if k == key => v.parse::<T>().ok(),
|
||||
_ => None,
|
||||
}
|
||||
.ok()
|
||||
})
|
||||
}
|
||||
|
||||
/// get the last part of the url, if not found, return empty string
|
||||
pub fn get_last_part_and_decode(url: &str) -> Option<String> {
|
||||
let path = url.split('?').next().unwrap_or(""); // Splits URL and takes the path part
|
||||
let segments: Vec<&str> = path.split('/').collect();
|
||||
let last_segment = segments.last()?;
|
||||
|
||||
Some(
|
||||
percent_encoding::percent_decode_str(last_segment)
|
||||
.decode_utf8_lossy()
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
|
||||
/// open file
|
||||
/// use vscode by default
|
||||
pub fn open_file(app: tauri::AppHandle, path: PathBuf) -> Result<()> {
|
||||
@@ -92,11 +102,11 @@ pub fn open_file(app: tauri::AppHandle, path: PathBuf) -> Result<()> {
|
||||
let code = "code";
|
||||
|
||||
let _ = match Program::from_str(code) {
|
||||
Ok(code) => open(&app.shell_scope(), &path.to_string_lossy(), Some(code)),
|
||||
Ok(code) => open(&app.shell_scope(), path.to_string_lossy(), Some(code)),
|
||||
Err(err) => {
|
||||
log::error!(target: "app", "Can't find VScode `{err}`");
|
||||
// default open
|
||||
open(&app.shell_scope(), &path.to_string_lossy(), None)
|
||||
open(&app.shell_scope(), path.to_string_lossy(), None)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -162,17 +172,17 @@ fn test_parse_value() {
|
||||
let test_1 = "upload=111; download=2222; total=3333; expire=444";
|
||||
let test_2 = "attachment; filename=Clash.yaml";
|
||||
|
||||
assert_eq!(parse_str::<usize>(test_1, "upload=").unwrap(), 111);
|
||||
assert_eq!(parse_str::<usize>(test_1, "download=").unwrap(), 2222);
|
||||
assert_eq!(parse_str::<usize>(test_1, "total=").unwrap(), 3333);
|
||||
assert_eq!(parse_str::<usize>(test_1, "expire=").unwrap(), 444);
|
||||
assert_eq!(parse_str::<usize>(test_1, "upload").unwrap(), 111);
|
||||
assert_eq!(parse_str::<usize>(test_1, "download").unwrap(), 2222);
|
||||
assert_eq!(parse_str::<usize>(test_1, "total").unwrap(), 3333);
|
||||
assert_eq!(parse_str::<usize>(test_1, "expire").unwrap(), 444);
|
||||
assert_eq!(
|
||||
parse_str::<String>(test_2, "filename=").unwrap(),
|
||||
parse_str::<String>(test_2, "filename").unwrap(),
|
||||
format!("Clash.yaml")
|
||||
);
|
||||
|
||||
assert_eq!(parse_str::<usize>(test_1, "aaa="), None);
|
||||
assert_eq!(parse_str::<usize>(test_1, "upload1="), None);
|
||||
assert_eq!(parse_str::<usize>(test_1, "expire1="), None);
|
||||
assert_eq!(parse_str::<usize>(test_2, "attachment="), None);
|
||||
assert_eq!(parse_str::<usize>(test_1, "aaa"), None);
|
||||
assert_eq!(parse_str::<usize>(test_1, "upload1"), None);
|
||||
assert_eq!(parse_str::<usize>(test_1, "expire1"), None);
|
||||
assert_eq!(parse_str::<usize>(test_2, "attachment"), None);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ use log4rs::append::file::FileAppender;
|
||||
use log4rs::config::{Appender, Logger, Root};
|
||||
use log4rs::encode::pattern::PatternEncoder;
|
||||
use std::fs::{self, DirEntry};
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use tauri::api::process::Command;
|
||||
|
||||
/// initialize this instance's log file
|
||||
fn init_log() -> Result<()> {
|
||||
@@ -79,7 +81,7 @@ pub fn delete_log() -> Result<()> {
|
||||
let auto_log_clean = {
|
||||
let verge = Config::verge();
|
||||
let verge = verge.data();
|
||||
verge.auto_log_clean.clone().unwrap_or(0)
|
||||
verge.auto_log_clean.unwrap_or(0)
|
||||
};
|
||||
|
||||
let day = match auto_log_clean {
|
||||
@@ -130,10 +132,8 @@ pub fn delete_log() -> Result<()> {
|
||||
Ok(())
|
||||
};
|
||||
|
||||
for file in fs::read_dir(&log_dir)? {
|
||||
if let Ok(file) = file {
|
||||
let _ = process_file(file);
|
||||
}
|
||||
for file in fs::read_dir(&log_dir)?.flatten() {
|
||||
let _ = process_file(file);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -141,6 +141,7 @@ pub fn delete_log() -> Result<()> {
|
||||
/// Initialize all the config files
|
||||
/// before tauri setup
|
||||
pub fn init_config() -> Result<()> {
|
||||
let _ = dirs::init_portable_flag();
|
||||
let _ = init_log();
|
||||
let _ = delete_log();
|
||||
|
||||
@@ -239,63 +240,85 @@ pub fn init_resources() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// initialize service resources
|
||||
/// after tauri setup
|
||||
/// initialize url scheme
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn init_service() -> Result<()> {
|
||||
let service_dir = dirs::service_dir()?;
|
||||
let res_dir = dirs::app_resources_dir()?;
|
||||
pub fn init_scheme() -> Result<()> {
|
||||
use tauri::utils::platform::current_exe;
|
||||
use winreg::enums::*;
|
||||
use winreg::RegKey;
|
||||
|
||||
if !service_dir.exists() {
|
||||
let _ = fs::create_dir_all(&service_dir);
|
||||
}
|
||||
if !res_dir.exists() {
|
||||
let _ = fs::create_dir_all(&res_dir);
|
||||
}
|
||||
let app_exe = current_exe()?;
|
||||
let app_exe = dunce::canonicalize(app_exe)?;
|
||||
let app_exe = app_exe.to_string_lossy().into_owned();
|
||||
|
||||
let file_list = [
|
||||
"clash-verge-service.exe",
|
||||
"install-service.exe",
|
||||
"uninstall-service.exe",
|
||||
];
|
||||
|
||||
// copy the resource file
|
||||
// if the source file is newer than the destination file, copy it over
|
||||
for file in file_list.iter() {
|
||||
let src_path = res_dir.join(file);
|
||||
let dest_path = service_dir.join(file);
|
||||
|
||||
let handle_copy = || {
|
||||
match fs::copy(&src_path, &dest_path) {
|
||||
Ok(_) => log::debug!(target: "app", "resources copied '{file}'"),
|
||||
Err(err) => {
|
||||
log::error!(target: "app", "failed to copy resources '{file}', {err}")
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
if src_path.exists() && !dest_path.exists() {
|
||||
handle_copy();
|
||||
continue;
|
||||
}
|
||||
|
||||
let src_modified = fs::metadata(&src_path).and_then(|m| m.modified());
|
||||
let dest_modified = fs::metadata(&dest_path).and_then(|m| m.modified());
|
||||
|
||||
match (src_modified, dest_modified) {
|
||||
(Ok(src_modified), Ok(dest_modified)) => {
|
||||
if src_modified > dest_modified {
|
||||
handle_copy();
|
||||
} else {
|
||||
log::debug!(target: "app", "skipping resource copy '{file}'");
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
log::debug!(target: "app", "failed to get modified '{file}'");
|
||||
handle_copy();
|
||||
}
|
||||
};
|
||||
}
|
||||
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
||||
let (clash, _) = hkcu.create_subkey("Software\\Classes\\Clash")?;
|
||||
clash.set_value("", &"Clash Verge")?;
|
||||
clash.set_value("URL Protocol", &"Clash Verge URL Scheme Protocol")?;
|
||||
let (default_icon, _) = hkcu.create_subkey("Software\\Classes\\Clash\\DefaultIcon")?;
|
||||
default_icon.set_value("", &app_exe)?;
|
||||
let (command, _) = hkcu.create_subkey("Software\\Classes\\Clash\\Shell\\Open\\Command")?;
|
||||
command.set_value("", &format!("{app_exe} \"%1\""))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn init_scheme() -> Result<()> {
|
||||
let output = std::process::Command::new("xdg-mime")
|
||||
.arg("default")
|
||||
.arg("clash-verge.desktop")
|
||||
.arg("x-scheme-handler/clash")
|
||||
.output()?;
|
||||
if !output.status.success() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"failed to set clash scheme, {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn init_scheme() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn startup_script() -> Result<()> {
|
||||
let path = {
|
||||
let verge = Config::verge();
|
||||
let verge = verge.latest();
|
||||
verge.startup_script.clone().unwrap_or("".to_string())
|
||||
};
|
||||
|
||||
if !path.is_empty() {
|
||||
let mut shell = "";
|
||||
if path.ends_with(".sh") {
|
||||
shell = "bash";
|
||||
}
|
||||
if path.ends_with(".ps1") {
|
||||
shell = "powershell";
|
||||
}
|
||||
if path.ends_with(".bat") {
|
||||
shell = "cmd";
|
||||
}
|
||||
if shell.is_empty() {
|
||||
return Err(anyhow::anyhow!("unsupported script: {path}"));
|
||||
}
|
||||
let current_dir = PathBuf::from(path.clone());
|
||||
if !current_dir.exists() {
|
||||
return Err(anyhow::anyhow!("script not found: {path}"));
|
||||
}
|
||||
let current_dir = current_dir.parent();
|
||||
match current_dir {
|
||||
Some(dir) => {
|
||||
let _ = Command::new(shell)
|
||||
.current_dir(dir.to_path_buf())
|
||||
.args(&[path])
|
||||
.output()?;
|
||||
}
|
||||
None => {
|
||||
let _ = Command::new(shell).args(&[path]).output()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
use crate::config::IVerge;
|
||||
use crate::{config::Config, core::*, utils::init, utils::server};
|
||||
use crate::config::{IVerge, PrfOption};
|
||||
use crate::{
|
||||
config::{Config, PrfItem},
|
||||
core::*,
|
||||
utils::init,
|
||||
utils::server,
|
||||
};
|
||||
use crate::{log_err, trace_err};
|
||||
use anyhow::Result;
|
||||
use once_cell::sync::OnceCell;
|
||||
use serde_yaml::Mapping;
|
||||
use std::net::TcpListener;
|
||||
use tauri::api::notification;
|
||||
use tauri::{App, AppHandle, Manager};
|
||||
use window_shadows::set_shadow;
|
||||
|
||||
pub static VERSION: OnceCell<String> = OnceCell::new();
|
||||
|
||||
pub fn find_unused_port() -> Result<u16> {
|
||||
match TcpListener::bind("127.0.0.1:0") {
|
||||
@@ -27,12 +37,13 @@ pub fn find_unused_port() -> Result<u16> {
|
||||
pub fn resolve_setup(app: &mut App) {
|
||||
#[cfg(target_os = "macos")]
|
||||
app.set_activation_policy(tauri::ActivationPolicy::Accessory);
|
||||
|
||||
let version = app.package_info().version.to_string();
|
||||
handle::Handle::global().init(app.app_handle());
|
||||
VERSION.get_or_init(|| version.clone());
|
||||
|
||||
log_err!(init::init_resources());
|
||||
#[cfg(target_os = "windows")]
|
||||
log_err!(init::init_service());
|
||||
log_err!(init::init_scheme());
|
||||
log_err!(init::startup_script());
|
||||
// 处理随机端口
|
||||
let enable_random_port = Config::verge().latest().enable_random_port.unwrap_or(false);
|
||||
|
||||
@@ -74,7 +85,7 @@ pub fn resolve_setup(app: &mut App) {
|
||||
log::trace!("init system tray");
|
||||
log_err!(tray::Tray::update_systray(&app.app_handle()));
|
||||
|
||||
let silent_start = { Config::verge().data().enable_silent_start.clone() };
|
||||
let silent_start = { Config::verge().data().enable_silent_start };
|
||||
if !silent_start.unwrap_or(false) {
|
||||
create_window(&app.app_handle());
|
||||
}
|
||||
@@ -85,6 +96,13 @@ pub fn resolve_setup(app: &mut App) {
|
||||
log_err!(handle::Handle::update_systray_part());
|
||||
log_err!(hotkey::Hotkey::global().init(app.app_handle()));
|
||||
log_err!(timer::Timer::global().init());
|
||||
|
||||
let argvs: Vec<String> = std::env::args().collect();
|
||||
if argvs.len() > 1 {
|
||||
tauri::async_runtime::block_on(async {
|
||||
resolve_scheme(argvs[1].to_owned()).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// reset system proxy
|
||||
@@ -123,7 +141,10 @@ pub fn create_window(app_handle: &AppHandle) {
|
||||
_ => {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
builder = builder.inner_size(800.0, 636.0).center();
|
||||
builder = builder
|
||||
.additional_browser_args("--enable-features=msWebView2EnableDraggableRegions")
|
||||
.inner_size(800.0, 636.0)
|
||||
.center();
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -137,61 +158,58 @@ pub fn create_window(app_handle: &AppHandle) {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use window_shadows::set_shadow;
|
||||
|
||||
match builder
|
||||
.decorations(false)
|
||||
.transparent(true)
|
||||
.visible(false)
|
||||
.build()
|
||||
{
|
||||
Ok(win) => {
|
||||
log::trace!("try to calculate the monitor size");
|
||||
let center = (|| -> Result<bool> {
|
||||
let mut center = false;
|
||||
let monitor = win.current_monitor()?.ok_or(anyhow::anyhow!(""))?;
|
||||
let size = monitor.size();
|
||||
let pos = win.outer_position()?;
|
||||
|
||||
if pos.x < -400
|
||||
|| pos.x > (size.width - 200).try_into()?
|
||||
|| pos.y < -200
|
||||
|| pos.y > (size.height - 200).try_into()?
|
||||
{
|
||||
center = true;
|
||||
}
|
||||
Ok(center)
|
||||
})();
|
||||
|
||||
if center.unwrap_or(true) {
|
||||
trace_err!(win.center(), "set win center");
|
||||
}
|
||||
|
||||
log::trace!("try to create window");
|
||||
let app_handle = app_handle.clone();
|
||||
|
||||
if let Some(window) = app_handle.get_window("main") {
|
||||
trace_err!(set_shadow(&window, true), "set win shadow");
|
||||
} else {
|
||||
log::error!(target: "app", "failed to create window, get_window is None")
|
||||
}
|
||||
}
|
||||
Err(err) => log::error!(target: "app", "failed to create window, {err}"),
|
||||
}
|
||||
}
|
||||
|
||||
let window = builder
|
||||
.decorations(false)
|
||||
.transparent(true)
|
||||
.visible(false)
|
||||
.build();
|
||||
#[cfg(target_os = "macos")]
|
||||
crate::log_err!(builder
|
||||
let window = builder
|
||||
.decorations(true)
|
||||
.hidden_title(true)
|
||||
.title_bar_style(tauri::TitleBarStyle::Overlay)
|
||||
.build());
|
||||
|
||||
.build();
|
||||
#[cfg(target_os = "linux")]
|
||||
crate::log_err!(builder.decorations(true).transparent(false).build());
|
||||
let window = builder.decorations(true).transparent(false).build();
|
||||
|
||||
match window {
|
||||
Ok(win) => {
|
||||
let is_maximized = Config::verge()
|
||||
.latest()
|
||||
.window_is_maximized
|
||||
.unwrap_or(false);
|
||||
log::trace!("try to calculate the monitor size");
|
||||
let center = (|| -> Result<bool> {
|
||||
let mut center = false;
|
||||
let monitor = win.current_monitor()?.ok_or(anyhow::anyhow!(""))?;
|
||||
let size = monitor.size();
|
||||
let pos = win.outer_position()?;
|
||||
|
||||
if pos.x < -400
|
||||
|| pos.x > (size.width - 200) as i32
|
||||
|| pos.y < -200
|
||||
|| pos.y > (size.height - 200) as i32
|
||||
{
|
||||
center = true;
|
||||
}
|
||||
Ok(center)
|
||||
})();
|
||||
|
||||
if center.unwrap_or(true) {
|
||||
trace_err!(win.center(), "set win center");
|
||||
}
|
||||
|
||||
trace_err!(set_shadow(&win, true), "set win shadow");
|
||||
if is_maximized {
|
||||
trace_err!(win.maximize(), "set win maximize");
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
log::error!("failed to create window");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// save window size and position
|
||||
@@ -212,10 +230,39 @@ pub fn save_window_size_position(app_handle: &AppHandle, save_to_file: bool) ->
|
||||
let size = size.to_logical::<f64>(scale);
|
||||
let pos = win.outer_position()?;
|
||||
let pos = pos.to_logical::<f64>(scale);
|
||||
|
||||
if size.width >= 600.0 && size.height >= 520.0 {
|
||||
let is_maximized = win.is_maximized()?;
|
||||
verge.window_is_maximized = Some(is_maximized);
|
||||
if !is_maximized && size.width >= 600.0 && size.height >= 520.0 {
|
||||
verge.window_size_position = Some(vec![size.width, size.height, pos.x, pos.y]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn resolve_scheme(param: String) {
|
||||
let url = param
|
||||
.trim_start_matches("clash://install-config/?url=")
|
||||
.trim_start_matches("clash://install-config?url=");
|
||||
let option = PrfOption {
|
||||
user_agent: None,
|
||||
with_proxy: Some(true),
|
||||
self_proxy: None,
|
||||
danger_accept_invalid_certs: None,
|
||||
update_interval: None,
|
||||
};
|
||||
if let Ok(item) = PrfItem::from_url(url, None, None, Some(option)).await {
|
||||
if Config::profiles().data().append_item(item).is_ok() {
|
||||
notification::Notification::new(crate::utils::dirs::APP_ID)
|
||||
.title("Clash Verge")
|
||||
.body("Import profile success")
|
||||
.show()
|
||||
.unwrap();
|
||||
};
|
||||
} else {
|
||||
notification::Notification::new(crate::utils::dirs::APP_ID)
|
||||
.title("Clash Verge")
|
||||
.body("Import profile failed")
|
||||
.show()
|
||||
.unwrap();
|
||||
log::error!("failed to parse url: {}", url);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,19 +4,42 @@ use super::resolve;
|
||||
use crate::config::IVerge;
|
||||
use anyhow::{bail, Result};
|
||||
use port_scanner::local_port_available;
|
||||
use std::convert::Infallible;
|
||||
use tauri::AppHandle;
|
||||
use warp::Filter;
|
||||
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct QueryParam {
|
||||
param: String,
|
||||
}
|
||||
|
||||
/// check whether there is already exists
|
||||
pub fn check_singleton() -> Result<()> {
|
||||
let port = IVerge::get_singleton_port();
|
||||
|
||||
if !local_port_available(port) {
|
||||
tauri::async_runtime::block_on(async {
|
||||
let url = format!("http://127.0.0.1:{port}/commands/visible");
|
||||
let resp = reqwest::get(url).await?.text().await?;
|
||||
let resp = reqwest::get(format!("http://127.0.0.1:{port}/commands/ping"))
|
||||
.await?
|
||||
.text()
|
||||
.await?;
|
||||
|
||||
if &resp == "ok" {
|
||||
let argvs: Vec<String> = std::env::args().collect();
|
||||
if argvs.len() > 1 {
|
||||
let param = argvs[1].as_str();
|
||||
reqwest::get(format!(
|
||||
"http://127.0.0.1:{port}/commands/scheme?param={param}"
|
||||
))
|
||||
.await?
|
||||
.text()
|
||||
.await?;
|
||||
} else {
|
||||
reqwest::get(format!("http://127.0.0.1:{port}/commands/visible"))
|
||||
.await?
|
||||
.text()
|
||||
.await?;
|
||||
}
|
||||
bail!("app exists");
|
||||
}
|
||||
|
||||
@@ -34,11 +57,22 @@ pub fn embed_server(app_handle: AppHandle) {
|
||||
let port = IVerge::get_singleton_port();
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let commands = warp::path!("commands" / "visible").map(move || {
|
||||
let ping = warp::path!("commands" / "ping").map(move || "ok");
|
||||
|
||||
let visible = warp::path!("commands" / "visible").map(move || {
|
||||
resolve::create_window(&app_handle);
|
||||
format!("ok")
|
||||
"ok"
|
||||
});
|
||||
|
||||
warp::serve(commands).bind(([127, 0, 0, 1], port)).await;
|
||||
let scheme = warp::path!("commands" / "scheme")
|
||||
.and(warp::query::<QueryParam>())
|
||||
.and_then(scheme_handler);
|
||||
|
||||
async fn scheme_handler(query: QueryParam) -> Result<impl warp::Reply, Infallible> {
|
||||
resolve::resolve_scheme(query.param).await;
|
||||
Ok("ok")
|
||||
}
|
||||
let commands = ping.or(visible).or(scheme);
|
||||
warp::serve(commands).run(([127, 0, 0, 1], port)).await;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
///! Some config file template
|
||||
//! Some config file template
|
||||
|
||||
/// template for new a profile item
|
||||
pub const ITEM_LOCAL: &str = "# Profile Template for clash verge
|
||||
@@ -16,14 +16,22 @@ pub const ITEM_MERGE: &str = "# Merge Template for clash verge
|
||||
|
||||
prepend-rules:
|
||||
|
||||
prepend-rule-providers:
|
||||
|
||||
prepend-proxies:
|
||||
|
||||
prepend-proxy-providers:
|
||||
|
||||
prepend-proxy-groups:
|
||||
|
||||
append-rules:
|
||||
|
||||
append-rule-providers:
|
||||
|
||||
append-proxies:
|
||||
|
||||
append-proxy-providers:
|
||||
|
||||
append-proxy-groups:
|
||||
";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"package": {
|
||||
"productName": "Clash Verge",
|
||||
"version": "1.4.5"
|
||||
"version": "1.5.6"
|
||||
},
|
||||
"build": {
|
||||
"distDir": "../dist",
|
||||
@@ -51,11 +51,25 @@
|
||||
},
|
||||
"clipboard": {
|
||||
"all": true
|
||||
},
|
||||
"notification": {
|
||||
"all": true
|
||||
},
|
||||
"dialog": {
|
||||
"all": false,
|
||||
"open": true
|
||||
},
|
||||
"protocol": {
|
||||
"asset": true,
|
||||
"assetScope": ["$APPDATA/**", "$RESOURCE/../**"]
|
||||
},
|
||||
"path": {
|
||||
"all": true
|
||||
}
|
||||
},
|
||||
"windows": [],
|
||||
"security": {
|
||||
"csp": "script-src 'unsafe-eval' 'self'; default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self'; img-src data: 'self';"
|
||||
"csp": "script-src 'unsafe-eval' 'self'; default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self'; img-src asset: http: https: data: 'self';"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
"bundle": {
|
||||
"targets": ["deb", "appimage", "updater"],
|
||||
"deb": {
|
||||
"depends": ["openssl"]
|
||||
"depends": ["openssl"],
|
||||
"desktopTemplate": "./template/clash-verge.desktop"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"targets": ["app", "dmg", "updater"],
|
||||
"macOS": {
|
||||
"frameworks": [],
|
||||
"minimumSystemVersion": "",
|
||||
"minimumSystemVersion": "10.15",
|
||||
"exceptionDomain": "",
|
||||
"signingIdentity": null,
|
||||
"entitlements": null
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
"displayLanguageSelector": true,
|
||||
"installerIcon": "icons/icon.ico",
|
||||
"languages": ["SimpChinese", "English"],
|
||||
"license": "../LICENSE"
|
||||
"license": "../LICENSE",
|
||||
"installMode": "perMachine",
|
||||
"template": "./template/installer.nsi"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
[Desktop Entry]
|
||||
Categories={{{categories}}}
|
||||
Comment={{{comment}}}
|
||||
Exec={{{exec}}} %u
|
||||
Icon={{{icon}}}
|
||||
Name={{{name}}}
|
||||
Terminal=false
|
||||
Type=Application
|
||||
MimeType=x-scheme-handler/clash;
|
||||
@@ -0,0 +1,827 @@
|
||||
; This file is copied from https://github.com/tauri-apps/tauri/blob/tauri-v1.5/tooling/bundler/src/bundle/windows/templates/installer.nsi
|
||||
; and edit to fit the needs of the project. the latest tauri 2.x has a different base nsi script.
|
||||
|
||||
Unicode true
|
||||
; Set the compression algorithm. Default is LZMA.
|
||||
!if "{{compression}}" == ""
|
||||
SetCompressor /SOLID lzma
|
||||
!else
|
||||
SetCompressor /SOLID "{{compression}}"
|
||||
!endif
|
||||
|
||||
!include MUI2.nsh
|
||||
!include FileFunc.nsh
|
||||
!include x64.nsh
|
||||
!include WordFunc.nsh
|
||||
!include "LogicLib.nsh"
|
||||
!include "StrFunc.nsh"
|
||||
!addplugindir "$%AppData%\Local\NSIS\"
|
||||
${StrCase}
|
||||
${StrLoc}
|
||||
|
||||
!define MANUFACTURER "{{manufacturer}}"
|
||||
!define PRODUCTNAME "{{product_name}}"
|
||||
!define VERSION "{{version}}"
|
||||
!define VERSIONWITHBUILD "{{version_with_build}}"
|
||||
!define SHORTDESCRIPTION "{{short_description}}"
|
||||
!define INSTALLMODE "{{install_mode}}"
|
||||
!define LICENSE "{{license}}"
|
||||
!define INSTALLERICON "{{installer_icon}}"
|
||||
!define SIDEBARIMAGE "{{sidebar_image}}"
|
||||
!define HEADERIMAGE "{{header_image}}"
|
||||
!define MAINBINARYNAME "{{main_binary_name}}"
|
||||
!define MAINBINARYSRCPATH "{{main_binary_path}}"
|
||||
!define BUNDLEID "{{bundle_id}}"
|
||||
!define COPYRIGHT "{{copyright}}"
|
||||
!define OUTFILE "{{out_file}}"
|
||||
!define ARCH "{{arch}}"
|
||||
!define PLUGINSPATH "{{additional_plugins_path}}"
|
||||
!define ALLOWDOWNGRADES "{{allow_downgrades}}"
|
||||
!define DISPLAYLANGUAGESELECTOR "{{display_language_selector}}"
|
||||
!define INSTALLWEBVIEW2MODE "{{install_webview2_mode}}"
|
||||
!define WEBVIEW2INSTALLERARGS "{{webview2_installer_args}}"
|
||||
!define WEBVIEW2BOOTSTRAPPERPATH "{{webview2_bootstrapper_path}}"
|
||||
!define WEBVIEW2INSTALLERPATH "{{webview2_installer_path}}"
|
||||
!define UNINSTKEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCTNAME}"
|
||||
!define MANUPRODUCTKEY "Software\${MANUFACTURER}\${PRODUCTNAME}"
|
||||
!define UNINSTALLERSIGNCOMMAND "{{uninstaller_sign_cmd}}"
|
||||
!define ESTIMATEDSIZE "{{estimated_size}}"
|
||||
|
||||
Name "${PRODUCTNAME}"
|
||||
BrandingText "${COPYRIGHT}"
|
||||
OutFile "${OUTFILE}"
|
||||
|
||||
VIProductVersion "${VERSIONWITHBUILD}"
|
||||
VIAddVersionKey "ProductName" "${PRODUCTNAME}"
|
||||
VIAddVersionKey "FileDescription" "${SHORTDESCRIPTION}"
|
||||
VIAddVersionKey "LegalCopyright" "${COPYRIGHT}"
|
||||
VIAddVersionKey "FileVersion" "${VERSION}"
|
||||
VIAddVersionKey "ProductVersion" "${VERSION}"
|
||||
|
||||
; Plugins path, currently exists for linux only
|
||||
!if "${PLUGINSPATH}" != ""
|
||||
!addplugindir "${PLUGINSPATH}"
|
||||
!endif
|
||||
|
||||
!if "${UNINSTALLERSIGNCOMMAND}" != ""
|
||||
!uninstfinalize '${UNINSTALLERSIGNCOMMAND}'
|
||||
!endif
|
||||
|
||||
; Handle install mode, `perUser`, `perMachine` or `both`
|
||||
!if "${INSTALLMODE}" == "perMachine"
|
||||
RequestExecutionLevel highest
|
||||
!endif
|
||||
|
||||
!if "${INSTALLMODE}" == "currentUser"
|
||||
RequestExecutionLevel user
|
||||
!endif
|
||||
|
||||
!if "${INSTALLMODE}" == "both"
|
||||
!define MULTIUSER_MUI
|
||||
!define MULTIUSER_INSTALLMODE_INSTDIR "${PRODUCTNAME}"
|
||||
!define MULTIUSER_INSTALLMODE_COMMANDLINE
|
||||
!if "${ARCH}" == "x64"
|
||||
!define MULTIUSER_USE_PROGRAMFILES64
|
||||
!else if "${ARCH}" == "arm64"
|
||||
!define MULTIUSER_USE_PROGRAMFILES64
|
||||
!endif
|
||||
!define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY "${UNINSTKEY}"
|
||||
!define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME "CurrentUser"
|
||||
!define MULTIUSER_INSTALLMODEPAGE_SHOWUSERNAME
|
||||
!define MULTIUSER_INSTALLMODE_FUNCTION RestorePreviousInstallLocation
|
||||
!define MULTIUSER_EXECUTIONLEVEL Highest
|
||||
!include MultiUser.nsh
|
||||
!endif
|
||||
|
||||
; installer icon
|
||||
!if "${INSTALLERICON}" != ""
|
||||
!define MUI_ICON "${INSTALLERICON}"
|
||||
!endif
|
||||
|
||||
; installer sidebar image
|
||||
!if "${SIDEBARIMAGE}" != ""
|
||||
!define MUI_WELCOMEFINISHPAGE_BITMAP "${SIDEBARIMAGE}"
|
||||
!endif
|
||||
|
||||
; installer header image
|
||||
!if "${HEADERIMAGE}" != ""
|
||||
!define MUI_HEADERIMAGE
|
||||
!define MUI_HEADERIMAGE_BITMAP "${HEADERIMAGE}"
|
||||
!endif
|
||||
|
||||
; Define registry key to store installer language
|
||||
!define MUI_LANGDLL_REGISTRY_ROOT "HKCU"
|
||||
!define MUI_LANGDLL_REGISTRY_KEY "${MANUPRODUCTKEY}"
|
||||
!define MUI_LANGDLL_REGISTRY_VALUENAME "Installer Language"
|
||||
|
||||
; Installer pages, must be ordered as they appear
|
||||
; 1. Welcome Page
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive
|
||||
!insertmacro MUI_PAGE_WELCOME
|
||||
|
||||
; 2. License Page (if defined)
|
||||
!if "${LICENSE}" != ""
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive
|
||||
!insertmacro MUI_PAGE_LICENSE "${LICENSE}"
|
||||
!endif
|
||||
|
||||
; 3. Install mode (if it is set to `both`)
|
||||
!if "${INSTALLMODE}" == "both"
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive
|
||||
!insertmacro MULTIUSER_PAGE_INSTALLMODE
|
||||
!endif
|
||||
|
||||
|
||||
; 4. Custom page to ask user if he wants to reinstall/uninstall
|
||||
; only if a previous installtion was detected
|
||||
Var ReinstallPageCheck
|
||||
Page custom PageReinstall PageLeaveReinstall
|
||||
Function PageReinstall
|
||||
; Uninstall previous WiX installation if exists.
|
||||
;
|
||||
; A WiX installer stores the isntallation info in registry
|
||||
; using a UUID and so we have to loop through all keys under
|
||||
; `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall`
|
||||
; and check if `DisplayName` and `Publisher` keys match ${PRODUCTNAME} and ${MANUFACTURER}
|
||||
;
|
||||
; This has a potentional issue that there maybe another installation that matches
|
||||
; our ${PRODUCTNAME} and ${MANUFACTURER} but wasn't installed by our WiX installer,
|
||||
; however, this should be fine since the user will have to confirm the uninstallation
|
||||
; and they can chose to abort it if doesn't make sense.
|
||||
StrCpy $0 0
|
||||
|
||||
wix_loop:
|
||||
EnumRegKey $1 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" $0
|
||||
StrCmp $1 "" wix_done ; Exit loop if there is no more keys to loop on
|
||||
IntOp $0 $0 + 1
|
||||
ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$1" "DisplayName"
|
||||
ReadRegStr $R1 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$1" "Publisher"
|
||||
StrCmp "$R0$R1" "${PRODUCTNAME}${MANUFACTURER}" 0 wix_loop
|
||||
ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$1" "UninstallString"
|
||||
${StrCase} $R1 $R0 "L"
|
||||
${StrLoc} $R0 $R1 "msiexec" ">"
|
||||
StrCmp $R0 0 0 wix_done
|
||||
StrCpy $R7 "wix"
|
||||
StrCpy $R6 "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$1"
|
||||
Goto compare_version
|
||||
wix_done:
|
||||
|
||||
; Check if there is an existing installation, if not, abort the reinstall page
|
||||
ReadRegStr $R0 SHCTX "${UNINSTKEY}" ""
|
||||
ReadRegStr $R1 SHCTX "${UNINSTKEY}" "UninstallString"
|
||||
${IfThen} "$R0$R1" == "" ${|} Abort ${|}
|
||||
|
||||
; Compare this installar version with the existing installation
|
||||
; and modify the messages presented to the user accordingly
|
||||
compare_version:
|
||||
StrCpy $R4 "$(older)"
|
||||
${If} $R7 == "wix"
|
||||
ReadRegStr $R0 HKLM "$R6" "DisplayVersion"
|
||||
${Else}
|
||||
ReadRegStr $R0 SHCTX "${UNINSTKEY}" "DisplayVersion"
|
||||
${EndIf}
|
||||
${IfThen} $R0 == "" ${|} StrCpy $R4 "$(unknown)" ${|}
|
||||
|
||||
nsis_tauri_utils::SemverCompare "${VERSION}" $R0
|
||||
Pop $R0
|
||||
; Reinstalling the same version
|
||||
${If} $R0 == 0
|
||||
StrCpy $R1 "$(alreadyInstalledLong)"
|
||||
StrCpy $R2 "$(addOrReinstall)"
|
||||
StrCpy $R3 "$(uninstallApp)"
|
||||
!insertmacro MUI_HEADER_TEXT "$(alreadyInstalled)" "$(chooseMaintenanceOption)"
|
||||
StrCpy $R5 "2"
|
||||
; Upgrading
|
||||
${ElseIf} $R0 == 1
|
||||
StrCpy $R1 "$(olderOrUnknownVersionInstalled)"
|
||||
StrCpy $R2 "$(uninstallBeforeInstalling)"
|
||||
StrCpy $R3 "$(dontUninstall)"
|
||||
!insertmacro MUI_HEADER_TEXT "$(alreadyInstalled)" "$(choowHowToInstall)"
|
||||
StrCpy $R5 "1"
|
||||
; Downgrading
|
||||
${ElseIf} $R0 == -1
|
||||
StrCpy $R1 "$(newerVersionInstalled)"
|
||||
StrCpy $R2 "$(uninstallBeforeInstalling)"
|
||||
!if "${ALLOWDOWNGRADES}" == "true"
|
||||
StrCpy $R3 "$(dontUninstall)"
|
||||
!else
|
||||
StrCpy $R3 "$(dontUninstallDowngrade)"
|
||||
!endif
|
||||
!insertmacro MUI_HEADER_TEXT "$(alreadyInstalled)" "$(choowHowToInstall)"
|
||||
StrCpy $R5 "1"
|
||||
${Else}
|
||||
Abort
|
||||
${EndIf}
|
||||
|
||||
Call SkipIfPassive
|
||||
|
||||
nsDialogs::Create 1018
|
||||
Pop $R4
|
||||
${IfThen} $(^RTL) == 1 ${|} nsDialogs::SetRTL $(^RTL) ${|}
|
||||
|
||||
${NSD_CreateLabel} 0 0 100% 24u $R1
|
||||
Pop $R1
|
||||
|
||||
${NSD_CreateRadioButton} 30u 50u -30u 8u $R2
|
||||
Pop $R2
|
||||
${NSD_OnClick} $R2 PageReinstallUpdateSelection
|
||||
|
||||
${NSD_CreateRadioButton} 30u 70u -30u 8u $R3
|
||||
Pop $R3
|
||||
; disable this radio button if downgrading and downgrades are disabled
|
||||
!if "${ALLOWDOWNGRADES}" == "false"
|
||||
${IfThen} $R0 == -1 ${|} EnableWindow $R3 0 ${|}
|
||||
!endif
|
||||
${NSD_OnClick} $R3 PageReinstallUpdateSelection
|
||||
|
||||
; Check the first radio button if this the first time
|
||||
; we enter this page or if the second button wasn't
|
||||
; selected the last time we were on this page
|
||||
${If} $ReinstallPageCheck != 2
|
||||
SendMessage $R2 ${BM_SETCHECK} ${BST_CHECKED} 0
|
||||
${Else}
|
||||
SendMessage $R3 ${BM_SETCHECK} ${BST_CHECKED} 0
|
||||
${EndIf}
|
||||
|
||||
${NSD_SetFocus} $R2
|
||||
nsDialogs::Show
|
||||
FunctionEnd
|
||||
Function PageReinstallUpdateSelection
|
||||
${NSD_GetState} $R2 $R1
|
||||
${If} $R1 == ${BST_CHECKED}
|
||||
StrCpy $ReinstallPageCheck 1
|
||||
${Else}
|
||||
StrCpy $ReinstallPageCheck 2
|
||||
${EndIf}
|
||||
FunctionEnd
|
||||
Function PageLeaveReinstall
|
||||
${NSD_GetState} $R2 $R1
|
||||
|
||||
; $R5 holds whether we are reinstalling the same version or not
|
||||
; $R5 == "1" -> different versions
|
||||
; $R5 == "2" -> same version
|
||||
;
|
||||
; $R1 holds the radio buttons state. its meaning is dependant on the context
|
||||
StrCmp $R5 "1" 0 +2 ; Existing install is not the same version?
|
||||
StrCmp $R1 "1" reinst_uninstall reinst_done ; $R1 == "1", then user chose to uninstall existing version, otherwise skip uninstalling
|
||||
StrCmp $R1 "1" reinst_done ; Same version? skip uninstalling
|
||||
|
||||
reinst_uninstall:
|
||||
HideWindow
|
||||
ClearErrors
|
||||
|
||||
${If} $R7 == "wix"
|
||||
ReadRegStr $R1 HKLM "$R6" "UninstallString"
|
||||
ExecWait '$R1' $0
|
||||
${Else}
|
||||
ReadRegStr $4 SHCTX "${MANUPRODUCTKEY}" ""
|
||||
ReadRegStr $R1 SHCTX "${UNINSTKEY}" "UninstallString"
|
||||
ExecWait '$R1 /P _?=$4' $0
|
||||
${EndIf}
|
||||
|
||||
BringToFront
|
||||
|
||||
${IfThen} ${Errors} ${|} StrCpy $0 2 ${|} ; ExecWait failed, set fake exit code
|
||||
|
||||
${If} $0 <> 0
|
||||
${OrIf} ${FileExists} "$INSTDIR\${MAINBINARYNAME}.exe"
|
||||
${If} $0 = 1 ; User aborted uninstaller?
|
||||
StrCmp $R5 "2" 0 +2 ; Is the existing install the same version?
|
||||
Quit ; ...yes, already installed, we are done
|
||||
Abort
|
||||
${EndIf}
|
||||
MessageBox MB_ICONEXCLAMATION "$(unableToUninstall)"
|
||||
Abort
|
||||
${Else}
|
||||
StrCpy $0 $R1 1
|
||||
${IfThen} $0 == '"' ${|} StrCpy $R1 $R1 -1 1 ${|} ; Strip quotes from UninstallString
|
||||
Delete $R1
|
||||
RMDir $INSTDIR
|
||||
${EndIf}
|
||||
reinst_done:
|
||||
FunctionEnd
|
||||
|
||||
; 5. Choose install directoy page
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive
|
||||
!insertmacro MUI_PAGE_DIRECTORY
|
||||
|
||||
; 6. Start menu shortcut page
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive
|
||||
Var AppStartMenuFolder
|
||||
!insertmacro MUI_PAGE_STARTMENU Application $AppStartMenuFolder
|
||||
|
||||
; 7. Installation page
|
||||
!insertmacro MUI_PAGE_INSTFILES
|
||||
|
||||
; 8. Finish page
|
||||
;
|
||||
; Don't auto jump to finish page after installation page,
|
||||
; because the installation page has useful info that can be used debug any issues with the installer.
|
||||
!define MUI_FINISHPAGE_NOAUTOCLOSE
|
||||
; Use show readme button in the finish page as a button create a desktop shortcut
|
||||
!define MUI_FINISHPAGE_SHOWREADME
|
||||
!define MUI_FINISHPAGE_SHOWREADME_TEXT "$(createDesktop)"
|
||||
!define MUI_FINISHPAGE_SHOWREADME_FUNCTION CreateDesktopShortcut
|
||||
; Show run app after installation.
|
||||
!define MUI_FINISHPAGE_RUN "$INSTDIR\${MAINBINARYNAME}.exe"
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive
|
||||
!insertmacro MUI_PAGE_FINISH
|
||||
|
||||
; Uninstaller Pages
|
||||
; 1. Confirm uninstall page
|
||||
Var DeleteAppDataCheckbox
|
||||
Var DeleteAppDataCheckboxState
|
||||
!define /ifndef WS_EX_LAYOUTRTL 0x00400000
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_SHOW un.ConfirmShow
|
||||
Function un.ConfirmShow
|
||||
FindWindow $1 "#32770" "" $HWNDPARENT ; Find inner dialog
|
||||
${If} $(^RTL) == 1
|
||||
System::Call 'USER32::CreateWindowEx(i${__NSD_CheckBox_EXSTYLE}|${WS_EX_LAYOUTRTL},t"${__NSD_CheckBox_CLASS}",t "$(deleteAppData)",i${__NSD_CheckBox_STYLE},i 50,i 100,i 400, i 25,i$1,i0,i0,i0)i.s'
|
||||
${Else}
|
||||
System::Call 'USER32::CreateWindowEx(i${__NSD_CheckBox_EXSTYLE},t"${__NSD_CheckBox_CLASS}",t "$(deleteAppData)",i${__NSD_CheckBox_STYLE},i 0,i 100,i 400, i 25,i$1,i0,i0,i0)i.s'
|
||||
${EndIf}
|
||||
Pop $DeleteAppDataCheckbox
|
||||
SendMessage $HWNDPARENT ${WM_GETFONT} 0 0 $1
|
||||
SendMessage $DeleteAppDataCheckbox ${WM_SETFONT} $1 1
|
||||
FunctionEnd
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_LEAVE un.ConfirmLeave
|
||||
Function un.ConfirmLeave
|
||||
SendMessage $DeleteAppDataCheckbox ${BM_GETCHECK} 0 0 $DeleteAppDataCheckboxState
|
||||
FunctionEnd
|
||||
!insertmacro MUI_UNPAGE_CONFIRM
|
||||
|
||||
; 2. Uninstalling Page
|
||||
!insertmacro MUI_UNPAGE_INSTFILES
|
||||
|
||||
;Languages
|
||||
{{#each languages}}
|
||||
!insertmacro MUI_LANGUAGE "{{this}}"
|
||||
{{/each}}
|
||||
!insertmacro MUI_RESERVEFILE_LANGDLL
|
||||
{{#each language_files}}
|
||||
!include "{{this}}"
|
||||
{{/each}}
|
||||
|
||||
!macro SetContext
|
||||
!if "${INSTALLMODE}" == "currentUser"
|
||||
SetShellVarContext current
|
||||
!else if "${INSTALLMODE}" == "perMachine"
|
||||
SetShellVarContext all
|
||||
!endif
|
||||
|
||||
${If} ${RunningX64}
|
||||
!if "${ARCH}" == "x64"
|
||||
SetRegView 64
|
||||
!else if "${ARCH}" == "arm64"
|
||||
SetRegView 64
|
||||
!else
|
||||
SetRegView 32
|
||||
!endif
|
||||
${EndIf}
|
||||
!macroend
|
||||
|
||||
Var PassiveMode
|
||||
Function .onInit
|
||||
${GetOptions} $CMDLINE "/P" $PassiveMode
|
||||
IfErrors +2 0
|
||||
StrCpy $PassiveMode 1
|
||||
|
||||
!if "${DISPLAYLANGUAGESELECTOR}" == "true"
|
||||
!insertmacro MUI_LANGDLL_DISPLAY
|
||||
!endif
|
||||
|
||||
!insertmacro SetContext
|
||||
|
||||
${If} $INSTDIR == ""
|
||||
; Set default install location
|
||||
!if "${INSTALLMODE}" == "perMachine"
|
||||
${If} ${RunningX64}
|
||||
!if "${ARCH}" == "x64"
|
||||
StrCpy $INSTDIR "$PROGRAMFILES64\${PRODUCTNAME}"
|
||||
!else if "${ARCH}" == "arm64"
|
||||
StrCpy $INSTDIR "$PROGRAMFILES64\${PRODUCTNAME}"
|
||||
!else
|
||||
StrCpy $INSTDIR "$PROGRAMFILES\${PRODUCTNAME}"
|
||||
!endif
|
||||
${Else}
|
||||
StrCpy $INSTDIR "$PROGRAMFILES\${PRODUCTNAME}"
|
||||
${EndIf}
|
||||
!else if "${INSTALLMODE}" == "currentUser"
|
||||
StrCpy $INSTDIR "$LOCALAPPDATA\${PRODUCTNAME}"
|
||||
!endif
|
||||
|
||||
Call RestorePreviousInstallLocation
|
||||
${EndIf}
|
||||
|
||||
|
||||
!if "${INSTALLMODE}" == "both"
|
||||
!insertmacro MULTIUSER_INIT
|
||||
!endif
|
||||
FunctionEnd
|
||||
|
||||
!macro CheckAllVergeProcesses
|
||||
; Check if Clash Verge.exe is running
|
||||
nsis_tauri_utils::FindProcess "Clash Verge.exe"
|
||||
${If} $R0 != 0
|
||||
; Kill the process
|
||||
DetailPrint "Kill Clash Verge.exe..."
|
||||
!if "${INSTALLMODE}" == "currentUser"
|
||||
nsis_tauri_utils::KillProcessCurrentUser "Clash Verge.exe"
|
||||
!else
|
||||
nsis_tauri_utils::KillProcess "Clash Verge.exe"
|
||||
!endif
|
||||
${EndIf}
|
||||
|
||||
|
||||
; Check if clash-verge-service.exe is running
|
||||
nsis_tauri_utils::FindProcess "clash-verge-service.exe"
|
||||
${If} $R0 != 0
|
||||
; Kill the process
|
||||
DetailPrint "Kill clash-verge-service.exe..."
|
||||
!if "${INSTALLMODE}" == "currentUser"
|
||||
nsis_tauri_utils::KillProcessCurrentUser "clash-verge-service.exe"
|
||||
!else
|
||||
nsis_tauri_utils::KillProcess "clash-verge-service.exe"
|
||||
!endif
|
||||
${EndIf}
|
||||
|
||||
|
||||
; Check if clash-meta-alpha.exe is running
|
||||
nsis_tauri_utils::FindProcess "clash-meta-alpha.exe"
|
||||
${If} $R0 != 0
|
||||
; Kill the process
|
||||
DetailPrint "Kill clash-meta-alpha.exe..."
|
||||
!if "${INSTALLMODE}" == "currentUser"
|
||||
nsis_tauri_utils::KillProcessCurrentUser "clash-meta-alpha.exe"
|
||||
!else
|
||||
nsis_tauri_utils::KillProcess "clash-meta-alpha.exe"
|
||||
!endif
|
||||
${EndIf}
|
||||
|
||||
; Check if clash-meta.exe is running
|
||||
nsis_tauri_utils::FindProcess "clash-meta.exe"
|
||||
${If} $R0 != 0
|
||||
; Kill the process
|
||||
DetailPrint "Kill clash-meta.exe..."
|
||||
!if "${INSTALLMODE}" == "currentUser"
|
||||
nsis_tauri_utils::KillProcessCurrentUser "clash-meta.exe"
|
||||
!else
|
||||
nsis_tauri_utils::KillProcess "clash-meta.exe"
|
||||
!endif
|
||||
${EndIf}
|
||||
!macroend
|
||||
|
||||
!macro StartVergeService
|
||||
; Check if the service exists
|
||||
SimpleSC::ExistsService "clash_verge_service"
|
||||
Pop $0 ; 0:service exists;other: service not exists
|
||||
; Service exists
|
||||
${If} $0 == 0
|
||||
Push $0
|
||||
; Check if the service is running
|
||||
SimpleSC::ServiceIsRunning "clash_verge_service"
|
||||
Pop $0 ; returns an errorcode (<>0) otherwise success (0)
|
||||
Pop $1 ; returns 1 (service is running) - returns 0 (service is not running)
|
||||
${If} $0 == 0
|
||||
Push $0
|
||||
${If} $1 == 0
|
||||
DetailPrint "Restart Clash Verge Service..."
|
||||
SimpleSC::StartService "clash_verge_service" "" 30
|
||||
${EndIf}
|
||||
${ElseIf} $0 != 0
|
||||
Push $0
|
||||
SimpleSC::GetErrorMessage
|
||||
Pop $0
|
||||
MessageBox MB_OK|MB_ICONSTOP "Check Service Status Error ($0)"
|
||||
${EndIf}
|
||||
${EndIf}
|
||||
!macroend
|
||||
|
||||
!macro RemoveVergeService
|
||||
; Check if the service exists
|
||||
SimpleSC::ExistsService "clash_verge_service"
|
||||
Pop $0 ; 0:service exists;other: service not exists
|
||||
; Service exists
|
||||
${If} $0 == 0
|
||||
Push $0
|
||||
; Check if the service is running
|
||||
SimpleSC::ServiceIsRunning "clash_verge_service"
|
||||
Pop $0 ; returns an errorcode (<>0) otherwise success (0)
|
||||
Pop $1 ; returns 1 (service is running) - returns 0 (service is not running)
|
||||
${If} $0 == 0
|
||||
Push $0
|
||||
${If} $1 == 1
|
||||
DetailPrint "Stop Clash Verge Service..."
|
||||
SimpleSC::StopService "clash_verge_service" 1 30
|
||||
Pop $0 ; returns an errorcode (<>0) otherwise success (0)
|
||||
${If} $0 == 0
|
||||
DetailPrint "Removing Clash Verge Service..."
|
||||
SimpleSC::RemoveService "clash_verge_service"
|
||||
${ElseIf} $0 != 0
|
||||
Push $0
|
||||
SimpleSC::GetErrorMessage
|
||||
Pop $0
|
||||
MessageBox MB_OK|MB_ICONSTOP "Clash Verge Service Stop Error ($0)"
|
||||
${EndIf}
|
||||
${ElseIf} $1 == 0
|
||||
DetailPrint "Removing Clash Verge Service..."
|
||||
SimpleSC::RemoveService "clash_verge_service"
|
||||
${EndIf}
|
||||
${ElseIf} $0 != 0
|
||||
Push $0
|
||||
SimpleSC::GetErrorMessage
|
||||
Pop $0
|
||||
MessageBox MB_OK|MB_ICONSTOP "Check Service Status Error ($0)"
|
||||
${EndIf}
|
||||
${EndIf}
|
||||
!macroend
|
||||
|
||||
Section EarlyChecks
|
||||
; Abort silent installer if downgrades is disabled
|
||||
!if "${ALLOWDOWNGRADES}" == "false"
|
||||
IfSilent 0 silent_downgrades_done
|
||||
; If downgrading
|
||||
${If} $R0 == -1
|
||||
System::Call 'kernel32::AttachConsole(i -1)i.r0'
|
||||
${If} $0 != 0
|
||||
System::Call 'kernel32::GetStdHandle(i -11)i.r0'
|
||||
System::call 'kernel32::SetConsoleTextAttribute(i r0, i 0x0004)' ; set red color
|
||||
FileWrite $0 "$(silentDowngrades)"
|
||||
${EndIf}
|
||||
Abort
|
||||
${EndIf}
|
||||
silent_downgrades_done:
|
||||
!endif
|
||||
|
||||
SectionEnd
|
||||
|
||||
Section WebView2
|
||||
; Check if Webview2 is already installed and skip this section
|
||||
${If} ${RunningX64}
|
||||
ReadRegStr $4 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
|
||||
${Else}
|
||||
ReadRegStr $4 HKLM "SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
|
||||
${EndIf}
|
||||
ReadRegStr $5 HKCU "SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
|
||||
|
||||
StrCmp $4 "" 0 webview2_done
|
||||
StrCmp $5 "" 0 webview2_done
|
||||
|
||||
; Webview2 install modes
|
||||
!if "${INSTALLWEBVIEW2MODE}" == "downloadBootstrapper"
|
||||
Delete "$TEMP\MicrosoftEdgeWebview2Setup.exe"
|
||||
DetailPrint "$(webview2Downloading)"
|
||||
nsis_tauri_utils::download "https://go.microsoft.com/fwlink/p/?LinkId=2124703" "$TEMP\MicrosoftEdgeWebview2Setup.exe"
|
||||
Pop $0
|
||||
${If} $0 == 0
|
||||
DetailPrint "$(webview2DownloadSuccess)"
|
||||
${Else}
|
||||
DetailPrint "$(webview2DownloadError)"
|
||||
Abort "$(webview2AbortError)"
|
||||
${EndIf}
|
||||
StrCpy $6 "$TEMP\MicrosoftEdgeWebview2Setup.exe"
|
||||
Goto install_webview2
|
||||
!endif
|
||||
|
||||
!if "${INSTALLWEBVIEW2MODE}" == "embedBootstrapper"
|
||||
Delete "$TEMP\MicrosoftEdgeWebview2Setup.exe"
|
||||
File "/oname=$TEMP\MicrosoftEdgeWebview2Setup.exe" "${WEBVIEW2BOOTSTRAPPERPATH}"
|
||||
DetailPrint "$(installingWebview2)"
|
||||
StrCpy $6 "$TEMP\MicrosoftEdgeWebview2Setup.exe"
|
||||
Goto install_webview2
|
||||
!endif
|
||||
|
||||
!if "${INSTALLWEBVIEW2MODE}" == "offlineInstaller"
|
||||
Delete "$TEMP\MicrosoftEdgeWebView2RuntimeInstaller.exe"
|
||||
File "/oname=$TEMP\MicrosoftEdgeWebView2RuntimeInstaller.exe" "${WEBVIEW2INSTALLERPATH}"
|
||||
DetailPrint "$(installingWebview2)"
|
||||
StrCpy $6 "$TEMP\MicrosoftEdgeWebView2RuntimeInstaller.exe"
|
||||
Goto install_webview2
|
||||
!endif
|
||||
|
||||
Goto webview2_done
|
||||
|
||||
install_webview2:
|
||||
DetailPrint "$(installingWebview2)"
|
||||
; $6 holds the path to the webview2 installer
|
||||
ExecWait "$6 ${WEBVIEW2INSTALLERARGS} /install" $1
|
||||
${If} $1 == 0
|
||||
DetailPrint "$(webview2InstallSuccess)"
|
||||
${Else}
|
||||
DetailPrint "$(webview2InstallError)"
|
||||
Abort "$(webview2AbortError)"
|
||||
${EndIf}
|
||||
webview2_done:
|
||||
SectionEnd
|
||||
|
||||
!macro CheckIfAppIsRunning
|
||||
!if "${INSTALLMODE}" == "currentUser"
|
||||
nsis_tauri_utils::FindProcessCurrentUser "${MAINBINARYNAME}.exe"
|
||||
!else
|
||||
nsis_tauri_utils::FindProcess "${MAINBINARYNAME}.exe"
|
||||
!endif
|
||||
Pop $R0
|
||||
${If} $R0 = 0
|
||||
IfSilent kill 0
|
||||
${IfThen} $PassiveMode != 1 ${|} MessageBox MB_OKCANCEL "$(appRunningOkKill)" IDOK kill IDCANCEL cancel ${|}
|
||||
kill:
|
||||
!if "${INSTALLMODE}" == "currentUser"
|
||||
nsis_tauri_utils::KillProcessCurrentUser "${MAINBINARYNAME}.exe"
|
||||
!else
|
||||
nsis_tauri_utils::KillProcess "${MAINBINARYNAME}.exe"
|
||||
!endif
|
||||
Pop $R0
|
||||
Sleep 500
|
||||
${If} $R0 = 0
|
||||
Goto app_check_done
|
||||
${Else}
|
||||
IfSilent silent ui
|
||||
silent:
|
||||
System::Call 'kernel32::AttachConsole(i -1)i.r0'
|
||||
${If} $0 != 0
|
||||
System::Call 'kernel32::GetStdHandle(i -11)i.r0'
|
||||
System::call 'kernel32::SetConsoleTextAttribute(i r0, i 0x0004)' ; set red color
|
||||
FileWrite $0 "$(appRunning)$\n"
|
||||
${EndIf}
|
||||
Abort
|
||||
ui:
|
||||
Abort "$(failedToKillApp)"
|
||||
${EndIf}
|
||||
cancel:
|
||||
Abort "$(appRunning)"
|
||||
${EndIf}
|
||||
app_check_done:
|
||||
!macroend
|
||||
|
||||
Section Install
|
||||
SetOutPath $INSTDIR
|
||||
|
||||
!insertmacro CheckIfAppIsRunning
|
||||
!insertmacro CheckAllVergeProcesses
|
||||
; Copy main executable
|
||||
File "${MAINBINARYSRCPATH}"
|
||||
|
||||
; Copy resources
|
||||
{{#each resources_dirs}}
|
||||
CreateDirectory "$INSTDIR\\{{this}}"
|
||||
{{/each}}
|
||||
{{#each resources}}
|
||||
File /a "/oname={{this.[1]}}" "{{@key}}"
|
||||
{{/each}}
|
||||
|
||||
; Copy external binaries
|
||||
{{#each binaries}}
|
||||
File /a "/oname={{this}}" "{{@key}}"
|
||||
{{/each}}
|
||||
|
||||
!insertmacro StartVergeService
|
||||
|
||||
; Create uninstaller
|
||||
WriteUninstaller "$INSTDIR\uninstall.exe"
|
||||
|
||||
; Save $INSTDIR in registry for future installations
|
||||
WriteRegStr SHCTX "${MANUPRODUCTKEY}" "" $INSTDIR
|
||||
|
||||
!if "${INSTALLMODE}" == "both"
|
||||
; Save install mode to be selected by default for the next installation such as updating
|
||||
; or when uninstalling
|
||||
WriteRegStr SHCTX "${UNINSTKEY}" $MultiUser.InstallMode 1
|
||||
!endif
|
||||
|
||||
; Registry information for add/remove programs
|
||||
WriteRegStr SHCTX "${UNINSTKEY}" "DisplayName" "${PRODUCTNAME}"
|
||||
WriteRegStr SHCTX "${UNINSTKEY}" "DisplayIcon" "$\"$INSTDIR\${MAINBINARYNAME}.exe$\""
|
||||
WriteRegStr SHCTX "${UNINSTKEY}" "DisplayVersion" "${VERSION}"
|
||||
WriteRegStr SHCTX "${UNINSTKEY}" "Publisher" "${MANUFACTURER}"
|
||||
WriteRegStr SHCTX "${UNINSTKEY}" "InstallLocation" "$\"$INSTDIR$\""
|
||||
WriteRegStr SHCTX "${UNINSTKEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\""
|
||||
WriteRegDWORD SHCTX "${UNINSTKEY}" "NoModify" "1"
|
||||
WriteRegDWORD SHCTX "${UNINSTKEY}" "NoRepair" "1"
|
||||
WriteRegDWORD SHCTX "${UNINSTKEY}" "EstimatedSize" "${ESTIMATEDSIZE}"
|
||||
|
||||
; Create start menu shortcut (GUI)
|
||||
!insertmacro MUI_STARTMENU_WRITE_BEGIN Application
|
||||
Call CreateStartMenuShortcut
|
||||
!insertmacro MUI_STARTMENU_WRITE_END
|
||||
|
||||
; Create shortcuts for silent and passive installers, which
|
||||
; can be disabled by passing `/NS` flag
|
||||
; GUI installer has buttons for users to control creating them
|
||||
IfSilent check_ns_flag 0
|
||||
${IfThen} $PassiveMode == 1 ${|} Goto check_ns_flag ${|}
|
||||
Goto shortcuts_done
|
||||
check_ns_flag:
|
||||
${GetOptions} $CMDLINE "/NS" $R0
|
||||
IfErrors 0 shortcuts_done
|
||||
Call CreateDesktopShortcut
|
||||
Call CreateStartMenuShortcut
|
||||
shortcuts_done:
|
||||
|
||||
; Auto close this page for passive mode
|
||||
${IfThen} $PassiveMode == 1 ${|} SetAutoClose true ${|}
|
||||
SectionEnd
|
||||
|
||||
Function .onInstSuccess
|
||||
; Check for `/R` flag only in silent and passive installers because
|
||||
; GUI installer has a toggle for the user to (re)start the app
|
||||
IfSilent check_r_flag 0
|
||||
${IfThen} $PassiveMode == 1 ${|} Goto check_r_flag ${|}
|
||||
Goto run_done
|
||||
check_r_flag:
|
||||
${GetOptions} $CMDLINE "/R" $R0
|
||||
IfErrors run_done 0
|
||||
Exec '"$INSTDIR\${MAINBINARYNAME}.exe"'
|
||||
run_done:
|
||||
FunctionEnd
|
||||
|
||||
Function un.onInit
|
||||
!insertmacro SetContext
|
||||
|
||||
!if "${INSTALLMODE}" == "both"
|
||||
!insertmacro MULTIUSER_UNINIT
|
||||
!endif
|
||||
|
||||
!insertmacro MUI_UNGETLANGUAGE
|
||||
FunctionEnd
|
||||
|
||||
Section Uninstall
|
||||
!insertmacro CheckIfAppIsRunning
|
||||
!insertmacro CheckAllVergeProcesses
|
||||
!insertmacro RemoveVergeService
|
||||
; Delete the app directory and its content from disk
|
||||
; Copy main executable
|
||||
Delete "$INSTDIR\${MAINBINARYNAME}.exe"
|
||||
|
||||
; Delete resources
|
||||
{{#each resources}}
|
||||
Delete "$INSTDIR\\{{this.[1]}}"
|
||||
{{/each}}
|
||||
|
||||
; Delete external binaries
|
||||
{{#each binaries}}
|
||||
Delete "$INSTDIR\\{{this}}"
|
||||
{{/each}}
|
||||
|
||||
; Delete uninstaller
|
||||
Delete "$INSTDIR\uninstall.exe"
|
||||
|
||||
${If} $DeleteAppDataCheckboxState == 1
|
||||
RMDir /R /REBOOTOK "$INSTDIR"
|
||||
${Else}
|
||||
{{#each resources_ancestors}}
|
||||
RMDir /REBOOTOK "$INSTDIR\\{{this}}"
|
||||
{{/each}}
|
||||
RMDir "$INSTDIR"
|
||||
${EndIf}
|
||||
|
||||
; Remove start menu shortcut
|
||||
!insertmacro MUI_STARTMENU_GETFOLDER Application $AppStartMenuFolder
|
||||
Delete "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk"
|
||||
RMDir "$SMPROGRAMS\$AppStartMenuFolder"
|
||||
|
||||
; Remove desktop shortcuts
|
||||
Delete "$DESKTOP\${MAINBINARYNAME}.lnk"
|
||||
|
||||
; Remove registry information for add/remove programs
|
||||
!if "${INSTALLMODE}" == "both"
|
||||
DeleteRegKey SHCTX "${UNINSTKEY}"
|
||||
!else if "${INSTALLMODE}" == "perMachine"
|
||||
DeleteRegKey HKLM "${UNINSTKEY}"
|
||||
!else
|
||||
DeleteRegKey HKCU "${UNINSTKEY}"
|
||||
!endif
|
||||
|
||||
DeleteRegValue HKCU "${MANUPRODUCTKEY}" "Installer Language"
|
||||
|
||||
; Delete app data
|
||||
${If} $DeleteAppDataCheckboxState == 1
|
||||
SetShellVarContext current
|
||||
RmDir /r "$APPDATA\${BUNDLEID}"
|
||||
RmDir /r "$LOCALAPPDATA\${BUNDLEID}"
|
||||
${EndIf}
|
||||
|
||||
${GetOptions} $CMDLINE "/P" $R0
|
||||
IfErrors +2 0
|
||||
SetAutoClose true
|
||||
SectionEnd
|
||||
|
||||
Function RestorePreviousInstallLocation
|
||||
ReadRegStr $4 SHCTX "${MANUPRODUCTKEY}" ""
|
||||
StrCmp $4 "" +2 0
|
||||
StrCpy $INSTDIR $4
|
||||
FunctionEnd
|
||||
|
||||
Function SkipIfPassive
|
||||
${IfThen} $PassiveMode == 1 ${|} Abort ${|}
|
||||
FunctionEnd
|
||||
|
||||
Function CreateDesktopShortcut
|
||||
CreateShortcut "$DESKTOP\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
|
||||
ApplicationID::Set "$DESKTOP\${MAINBINARYNAME}.lnk" "${BUNDLEID}"
|
||||
FunctionEnd
|
||||
|
||||
Function CreateStartMenuShortcut
|
||||
CreateDirectory "$SMPROGRAMS\$AppStartMenuFolder"
|
||||
CreateShortcut "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
|
||||
ApplicationID::Set "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" "${BUNDLEID}"
|
||||
FunctionEnd
|
||||
@@ -0,0 +1,10 @@
|
||||
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="36" height="36" rx="18" fill="url(#paint0_linear_290_83)"/>
|
||||
<path d="M26.1791 19.6042C25.8714 21.1676 25.1224 22.6103 24.0208 23.7615C22.9191 24.9128 21.5109 25.7246 19.9625 26.1008C20.3875 25.5992 20.7558 24.9492 21.0616 24.2117C21.3658 23.4783 21.62 22.6275 21.8083 21.695C23.2058 21.335 24.4716 20.7817 25.5333 20.0742C25.7566 19.925 25.9725 19.7683 26.1791 19.6042ZM9.82082 19.6042C10.0275 19.7683 10.2433 19.925 10.4666 20.0742C11.5275 20.7817 12.7933 21.335 14.1916 21.695C14.3791 22.6275 14.6333 23.4783 14.9383 24.2117C15.2433 24.9483 15.6116 25.5992 16.0366 26.1008C14.4884 25.7244 13.0803 24.9126 11.9788 23.7613C10.8773 22.6101 10.1285 21.1675 9.82082 19.6042ZM20.0225 22.0375C19.8927 22.5611 19.7254 23.0747 19.5216 23.5742C19.2325 24.2725 18.9141 24.7867 18.6125 25.1125C18.3041 25.4458 18.0925 25.5 18 25.5C17.9075 25.5 17.6966 25.4458 17.3875 25.1125C17.0858 24.7875 16.7675 24.2725 16.4783 23.5742C16.2746 23.0747 16.1072 22.5611 15.9775 22.0375C16.6341 22.1225 17.3108 22.1667 18 22.1667C18.6762 22.1671 19.3518 22.1239 20.0225 22.0375ZM20.4691 16.9408C20.5344 18.0629 20.4947 19.1886 20.3508 20.3033C19.6033 20.4317 18.815 20.5 18 20.5C17.2119 20.5009 16.4253 20.4351 15.6483 20.3033C15.5023 19.1888 15.463 18.0629 15.5308 16.9408C16.3191 17.0883 17.1475 17.1667 18 17.1667C18.8282 17.1676 19.6548 17.092 20.4691 16.9408ZM25.555 14.4792C25.9383 15.3008 26.1925 16.1958 26.2883 17.1367C25.8708 17.6975 25.3075 18.2217 24.6083 18.6867C23.8833 19.17 23.0283 19.5783 22.0758 19.8842C22.1845 18.7637 22.1962 17.6359 22.1108 16.5133C22.8976 16.2518 23.6486 15.8932 24.3466 15.4458C24.7812 15.1654 25.186 14.8416 25.555 14.4792ZM10.445 14.4792C10.8033 14.835 11.2116 15.1583 11.6533 15.4458C12.3512 15.8934 13.1023 16.252 13.8891 16.5133C13.804 17.6359 13.8154 18.7636 13.9233 19.8842C12.9708 19.5783 12.1158 19.1708 11.3908 18.6875C10.6925 18.2217 10.1291 17.6975 9.71082 17.1367C9.80748 16.195 10.0608 15.3008 10.4441 14.4783L10.445 14.4792ZM18 10.5C18.0916 10.5 18.3033 10.555 18.6125 10.8883C18.9141 11.2133 19.2325 11.7283 19.5216 12.4267C19.8466 13.2125 20.1166 14.1833 20.29 15.2775C19.5716 15.4217 18.8025 15.5 18 15.5C17.2309 15.5015 16.4636 15.4269 15.7091 15.2775C15.8825 14.1833 16.1525 13.2125 16.4783 12.4275C16.7675 11.7283 17.0858 11.2142 17.3866 10.8883C17.6966 10.555 17.9075 10.5 18 10.5ZM19.9625 9.89917C21.8406 10.3553 23.5019 11.4496 24.6625 12.995C24.3075 13.4034 23.8951 13.7582 23.4383 14.0483C22.9554 14.3584 22.4408 14.6162 21.9033 14.8175C21.7108 13.6833 21.42 12.655 21.0616 11.7892C20.7558 11.0517 20.3883 10.4008 19.9625 9.89917ZM16.0375 9.89917C15.6125 10.4008 15.2441 11.0517 14.9383 11.7892C14.58 12.6558 14.2883 13.6833 14.0966 14.8175C13.5592 14.6162 13.0446 14.3584 12.5616 14.0483C12.1046 13.7583 11.6919 13.4035 11.3366 12.995C12.4974 11.4494 14.1591 10.3551 16.0375 9.89917Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_290_83" x1="31" y1="27.5" x2="6.5" y2="7" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#009038"/>
|
||||
<stop offset="1" stop-color="#1CA350"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
@@ -0,0 +1,21 @@
|
||||
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="36" height="36" rx="18" fill="url(#paint0_linear_289_53)"/>
|
||||
<rect width="36" height="36" rx="18" fill="url(#paint1_linear_289_53)"/>
|
||||
<mask id="mask0_289_53" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="8" y="8" width="20" height="20">
|
||||
<path d="M28 8V28H8V8H28ZM18.4942 27.3817L18.485 27.3833L18.4258 27.4125L18.4092 27.4158L18.3975 27.4125L18.3383 27.3833C18.33 27.38 18.3225 27.3825 18.3183 27.3875L18.315 27.3958L18.3008 27.7525L18.305 27.7692L18.3133 27.78L18.4 27.8417L18.4125 27.845L18.4225 27.8417L18.5092 27.78L18.5192 27.7667L18.5225 27.7525L18.5083 27.3967C18.5067 27.3883 18.5008 27.3825 18.4942 27.3817ZM18.715 27.2875L18.7042 27.2892L18.55 27.3667L18.5417 27.375L18.5392 27.3842L18.5542 27.7425L18.5583 27.7525L18.565 27.7583L18.7325 27.8358C18.7425 27.8392 18.7517 27.8358 18.7567 27.8292L18.76 27.8175L18.7317 27.3058C18.7292 27.2958 18.7233 27.2892 18.715 27.2875ZM18.1192 27.2892C18.1154 27.2874 18.1111 27.287 18.1071 27.2879C18.103 27.2888 18.0994 27.291 18.0967 27.2942L18.0917 27.3058L18.0633 27.8175C18.0633 27.8275 18.0692 27.8342 18.0775 27.8375L18.09 27.8358L18.2575 27.7583L18.2658 27.7517L18.2692 27.7425L18.2833 27.3842L18.2808 27.3742L18.2725 27.3658L18.1192 27.2892Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_289_53)">
|
||||
<path d="M15.9167 9.66669C15.5852 9.66669 15.2672 9.79838 15.0328 10.0328C14.7984 10.2672 14.6667 10.5852 14.6667 10.9167V11.75C14.6667 12.0815 14.7984 12.3995 15.0328 12.6339C15.2672 12.8683 15.5852 13 15.9167 13H20.0834C20.4149 13 20.7328 12.8683 20.9672 12.6339C21.2017 12.3995 21.3334 12.0815 21.3334 11.75V10.9167C21.3334 10.5852 21.2017 10.2672 20.9672 10.0328C20.7328 9.79838 20.4149 9.66669 20.0834 9.66669H15.9167Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.4167 11.3642C12.3683 11.4225 11.7067 11.5892 11.2325 12.0642C10.5 12.7967 10.5 13.975 10.5 16.3317V21.3317C10.5 23.6892 10.5 24.8675 11.2325 25.6C11.9642 26.3317 13.1433 26.3317 15.5 26.3317H20.5C22.8567 26.3317 24.0358 26.3317 24.7675 25.6C25.5 24.8667 25.5 23.6892 25.5 21.3317V16.3317C25.5 13.975 25.5 12.7967 24.7675 12.0642C24.2933 11.5892 23.6317 11.4225 22.5833 11.3642V11.75C22.5833 12.4131 22.3199 13.049 21.8511 13.5178C21.3823 13.9866 20.7464 14.25 20.0833 14.25H15.9167C15.2536 14.25 14.6177 13.9866 14.1489 13.5178C13.6801 13.049 13.4167 12.4131 13.4167 11.75V11.3642ZM13.8333 16.125C13.6676 16.125 13.5086 16.1909 13.3914 16.3081C13.2742 16.4253 13.2083 16.5843 13.2083 16.75C13.2083 16.9158 13.2742 17.0748 13.3914 17.192C13.5086 17.3092 13.6676 17.375 13.8333 17.375H14.25C14.4158 17.375 14.5747 17.3092 14.6919 17.192C14.8092 17.0748 14.875 16.9158 14.875 16.75C14.875 16.5843 14.8092 16.4253 14.6919 16.3081C14.5747 16.1909 14.4158 16.125 14.25 16.125H13.8333ZM16.75 16.125C16.5842 16.125 16.4253 16.1909 16.3081 16.3081C16.1908 16.4253 16.125 16.5843 16.125 16.75C16.125 16.9158 16.1908 17.0748 16.3081 17.192C16.4253 17.3092 16.5842 17.375 16.75 17.375H22.1667C22.3324 17.375 22.4914 17.3092 22.6086 17.192C22.7258 17.0748 22.7917 16.9158 22.7917 16.75C22.7917 16.5843 22.7258 16.4253 22.6086 16.3081C22.4914 16.1909 22.3324 16.125 22.1667 16.125H16.75ZM13.8333 19.0417C13.6676 19.0417 13.5086 19.1075 13.3914 19.2248C13.2742 19.342 13.2083 19.5009 13.2083 19.6667C13.2083 19.8325 13.2742 19.9914 13.3914 20.1086C13.5086 20.2258 13.6676 20.2917 13.8333 20.2917H14.25C14.4158 20.2917 14.5747 20.2258 14.6919 20.1086C14.8092 19.9914 14.875 19.8325 14.875 19.6667C14.875 19.5009 14.8092 19.342 14.6919 19.2248C14.5747 19.1075 14.4158 19.0417 14.25 19.0417H13.8333ZM16.75 19.0417C16.5842 19.0417 16.4253 19.1075 16.3081 19.2248C16.1908 19.342 16.125 19.5009 16.125 19.6667C16.125 19.8325 16.1908 19.9914 16.3081 20.1086C16.4253 20.2258 16.5842 20.2917 16.75 20.2917H22.1667C22.3324 20.2917 22.4914 20.2258 22.6086 20.1086C22.7258 19.9914 22.7917 19.8325 22.7917 19.6667C22.7917 19.5009 22.7258 19.342 22.6086 19.2248C22.4914 19.1075 22.3324 19.0417 22.1667 19.0417H16.75ZM13.8333 21.9584C13.6676 21.9584 13.5086 22.0242 13.3914 22.1414C13.2742 22.2586 13.2083 22.4176 13.2083 22.5834C13.2083 22.7491 13.2742 22.9081 13.3914 23.0253C13.5086 23.1425 13.6676 23.2084 13.8333 23.2084H14.25C14.4158 23.2084 14.5747 23.1425 14.6919 23.0253C14.8092 22.9081 14.875 22.7491 14.875 22.5834C14.875 22.4176 14.8092 22.2586 14.6919 22.1414C14.5747 22.0242 14.4158 21.9584 14.25 21.9584H13.8333ZM16.75 21.9584C16.5842 21.9584 16.4253 22.0242 16.3081 22.1414C16.1908 22.2586 16.125 22.4176 16.125 22.5834C16.125 22.7491 16.1908 22.9081 16.3081 23.0253C16.4253 23.1425 16.5842 23.2084 16.75 23.2084H22.1667C22.3324 23.2084 22.4914 23.1425 22.6086 23.0253C22.7258 22.9081 22.7917 22.7491 22.7917 22.5834C22.7917 22.4176 22.7258 22.2586 22.6086 22.1414C22.4914 22.0242 22.3324 21.9584 22.1667 21.9584H16.75Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_289_53" x1="6" y1="6.5" x2="29.5" y2="30.5" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#56718E"/>
|
||||
<stop offset="1" stop-color="#4B6683"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_289_53" x1="6" y1="6.5" x2="29.5" y2="30.5" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E96038"/>
|
||||
<stop offset="1" stop-color="#E1451D"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
@@ -0,0 +1,15 @@
|
||||
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="36" height="36" rx="18" fill="url(#paint0_linear_293_142)"/>
|
||||
<g clip-path="url(#clip0_293_142)">
|
||||
<path d="M24.5167 13C24.7587 13.0001 24.9978 13.0529 25.2173 13.1547C25.4369 13.2566 25.6316 13.405 25.788 13.5897C25.9444 13.7744 26.0587 13.9909 26.1229 14.2243C26.1872 14.4576 26.1998 14.7021 26.16 14.9408L24.7717 23.2742C24.7068 23.6633 24.5059 24.0168 24.2049 24.2718C23.9038 24.5268 23.5221 24.6667 23.1275 24.6667H12.8725C12.478 24.6667 12.0962 24.5268 11.7952 24.2718C11.4941 24.0168 11.2933 23.6633 11.2284 23.2742L9.84003 14.9408C9.80022 14.7021 9.81287 14.4576 9.87711 14.2243C9.94135 13.9909 10.0556 13.7744 10.212 13.5897C10.3684 13.405 10.5632 13.2566 10.7827 13.1547C11.0023 13.0529 11.2413 13.0001 11.4834 13H24.5167ZM23 10.5C23.221 10.5 23.433 10.5878 23.5893 10.7441C23.7456 10.9004 23.8334 11.1123 23.8334 11.3333C23.8334 11.5543 23.7456 11.7663 23.5893 11.9226C23.433 12.0789 23.221 12.1667 23 12.1667H13C12.779 12.1667 12.5671 12.0789 12.4108 11.9226C12.2545 11.7663 12.1667 11.5543 12.1667 11.3333C12.1667 11.1123 12.2545 10.9004 12.4108 10.7441C12.5671 10.5878 12.779 10.5 13 10.5H23Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_293_142" x1="31" y1="27.5" x2="6.5" y2="7" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6038CB"/>
|
||||
<stop offset="1" stop-color="#704ADC"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_293_142">
|
||||
<rect width="20" height="20" fill="white" transform="translate(8 8)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,15 @@
|
||||
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="36" height="36" rx="18" fill="url(#paint0_linear_294_169)"/>
|
||||
<g clip-path="url(#clip0_294_169)">
|
||||
<path d="M18 22.1667C18.221 22.1667 18.433 22.2545 18.5893 22.4107C18.7455 22.567 18.8333 22.779 18.8333 23C18.8333 23.221 18.7455 23.433 18.5893 23.5893C18.433 23.7455 18.221 23.8333 18 23.8333C17.779 23.8333 17.567 23.7455 17.4107 23.5893C17.2545 23.433 17.1667 23.221 17.1667 23C17.1667 22.779 17.2545 22.567 17.4107 22.4107C17.567 22.2545 17.779 22.1667 18 22.1667ZM18 18.8333C19.15 18.8333 20.1933 19.3 20.9467 20.0533C21.103 20.2097 21.1909 20.4218 21.1909 20.6429C21.1909 20.8641 21.103 21.0761 20.9467 21.2325C20.7903 21.3889 20.5782 21.4767 20.3571 21.4767C20.136 21.4767 19.9239 21.3889 19.7675 21.2325C19.5357 20.9999 19.2603 20.8154 18.9569 20.6897C18.6536 20.564 18.3284 20.4995 18 20.5C17.3092 20.5 16.6858 20.7792 16.2325 21.2325C16.0761 21.3889 15.8641 21.4767 15.6429 21.4767C15.4218 21.4767 15.2097 21.3889 15.0533 21.2325C14.897 21.0761 14.8091 20.8641 14.8091 20.6429C14.8091 20.4218 14.897 20.2097 15.0533 20.0533C15.44 19.666 15.8993 19.3588 16.405 19.1494C16.9107 18.9401 17.4527 18.8326 18 18.8333ZM18 15.5C18.9851 15.4989 19.9607 15.6923 20.8708 16.0693C21.7809 16.4463 22.6076 16.9993 23.3033 17.6967C23.4551 17.8538 23.5391 18.0643 23.5372 18.2828C23.5353 18.5013 23.4477 18.7103 23.2932 18.8648C23.1387 19.0194 22.9297 19.107 22.7112 19.1089C22.4927 19.1108 22.2822 19.0268 22.125 18.875C21.584 18.3324 20.941 17.9022 20.2331 17.609C19.5251 17.3158 18.7662 17.1655 18 17.1667C17.2338 17.1655 16.4749 17.3158 15.7669 17.609C15.059 17.9022 14.416 18.3324 13.875 18.875C13.7178 19.0268 13.5073 19.1108 13.2888 19.1089C13.0703 19.107 12.8613 19.0194 12.7068 18.8648C12.5523 18.7103 12.4647 18.5013 12.4628 18.2828C12.4609 18.0643 12.5449 17.8538 12.6967 17.6967C13.3924 16.9993 14.2191 16.4463 15.1292 16.0693C16.0393 15.6923 17.0149 15.4989 18 15.5ZM18 12.1667C20.9917 12.1667 23.7 13.38 25.66 15.34C25.8118 15.4972 25.8958 15.7077 25.8939 15.9262C25.892 16.1447 25.8044 16.3537 25.6499 16.5082C25.4953 16.6627 25.2863 16.7503 25.0678 16.7522C24.8493 16.7541 24.6388 16.6701 24.4817 16.5183C23.6315 15.6657 22.6212 14.9896 21.5089 14.5288C20.3965 14.068 19.204 13.8316 18 13.8333C16.796 13.8315 15.6035 14.0679 14.4911 14.5286C13.3787 14.9894 12.3684 15.6656 11.5183 16.5183C11.3612 16.6701 11.1507 16.7541 10.9322 16.7522C10.7137 16.7503 10.5047 16.6627 10.3502 16.5082C10.1957 16.3537 10.108 16.1447 10.1061 15.9262C10.1042 15.7077 10.1882 15.4972 10.34 15.34C11.3449 14.3326 12.5389 13.5337 13.8534 12.9891C15.1679 12.4445 16.5771 12.165 18 12.1667Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_294_169" x1="31" y1="27.5" x2="6.5" y2="7" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#21B2CB"/>
|
||||
<stop offset="1" stop-color="#3EC5D2"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_294_169">
|
||||
<rect width="20" height="20" fill="white" transform="translate(8 8)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
@@ -0,0 +1,15 @@
|
||||
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="36" height="36" rx="18" fill="url(#paint0_linear_294_182)"/>
|
||||
<g clip-path="url(#clip0_294_182)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M24.7759 10.6333C24.307 10.1647 23.6713 9.90137 23.0084 9.90137C22.3454 9.90137 21.7097 10.1647 21.2409 10.6333L20.6517 11.2233L24.7767 15.3483L25.365 14.7592C25.5972 14.527 25.7814 14.2514 25.9071 13.9481C26.0327 13.6447 26.0974 13.3196 26.0974 12.9913C26.0974 12.6629 26.0327 12.3378 25.9071 12.0344C25.7814 11.7311 25.5972 11.4555 25.365 11.2233L24.7759 10.6333ZM23.5975 16.5267L19.4725 12.4017L11.8975 19.9775C11.7318 20.1433 11.616 20.3524 11.5634 20.5808L10.7059 24.2933C10.6739 24.4312 10.6776 24.5748 10.7165 24.7109C10.7554 24.8469 10.8284 24.9707 10.9284 25.0708C11.0284 25.1708 11.1523 25.2437 11.2883 25.2827C11.4243 25.3216 11.568 25.3253 11.7059 25.2933L15.4192 24.4367C15.6474 24.3839 15.8561 24.2681 16.0217 24.1025L23.5975 16.5267Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_294_182" x1="31" y1="27.5" x2="6.5" y2="7" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FB4293"/>
|
||||
<stop offset="1" stop-color="#F957A1"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_294_182">
|
||||
<rect width="20" height="20" fill="white" transform="translate(8 8)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,11 @@
|
||||
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="36" height="36" rx="18" fill="url(#paint0_linear_289_39)"/>
|
||||
<path d="M20.0083 9.91083C20.8155 10.1108 21.5881 10.431 22.3 10.8608C22.4427 10.947 22.5563 11.0739 22.6262 11.2253C22.696 11.3767 22.7188 11.5455 22.6916 11.71C22.5975 12.2842 22.74 12.7233 23.0083 12.9917C23.2766 13.26 23.7166 13.4025 24.29 13.3083C24.4545 13.2812 24.6233 13.304 24.7747 13.3738C24.9261 13.4437 25.053 13.5573 25.1391 13.7C25.569 14.4119 25.8893 15.1844 26.0891 15.9917C26.1294 16.1536 26.1201 16.3239 26.0624 16.4805C26.0048 16.6371 25.9015 16.7728 25.7658 16.87C25.2933 17.2092 25.0825 17.62 25.0825 18C25.0825 18.38 25.2933 18.7917 25.7658 19.1308C25.9013 19.228 26.0045 19.3635 26.0622 19.52C26.1198 19.6764 26.1292 19.8465 26.0891 20.0083C25.8892 20.8156 25.569 21.5881 25.1391 22.3C25.053 22.4427 24.9261 22.5564 24.7747 22.6262C24.6233 22.696 24.4545 22.7188 24.29 22.6917C23.7158 22.5975 23.2766 22.7408 23.0083 23.0083C22.74 23.2767 22.5975 23.7167 22.6916 24.29C22.719 24.4546 22.6962 24.6236 22.6264 24.7752C22.5566 24.9267 22.4429 25.0538 22.3 25.14C21.5881 25.5698 20.8155 25.8901 20.0083 26.09C19.8464 26.1302 19.6761 26.1209 19.5195 26.0633C19.3629 26.0056 19.2272 25.9023 19.13 25.7667C18.7908 25.2933 18.38 25.0833 18 25.0833C17.62 25.0833 17.2083 25.2933 16.8691 25.7667C16.772 25.9022 16.6365 26.0054 16.48 26.063C16.3236 26.1207 16.1535 26.1301 15.9916 26.09C15.1844 25.8901 14.4119 25.5698 13.7 25.14C13.5571 25.0538 13.4434 24.9267 13.3736 24.7752C13.3037 24.6236 13.281 24.4546 13.3083 24.29C13.4025 23.7167 13.2591 23.2775 12.9916 23.0092C12.7225 22.7408 12.2833 22.5975 11.71 22.6925C11.5454 22.7198 11.3763 22.6971 11.2248 22.6273C11.0733 22.5574 10.9462 22.4437 10.86 22.3008C10.43 21.5884 10.1098 20.8153 9.90998 20.0075C9.86992 19.8457 9.87931 19.6756 9.93695 19.5191C9.99459 19.3627 10.0978 19.2271 10.2333 19.13C10.7066 18.7917 10.9166 18.38 10.9166 18C10.9166 17.6208 10.7066 17.2083 10.2333 16.87C10.0978 16.7729 9.99459 16.6373 9.93695 16.4809C9.87931 16.3245 9.86992 16.1543 9.90998 15.9925C10.1098 15.185 10.4301 14.4122 10.86 13.7C10.9462 13.5571 11.0733 13.4434 11.2248 13.3736C11.3763 13.3037 11.5454 13.281 11.71 13.3083C12.2833 13.4025 12.7225 13.26 12.9908 12.9917C13.2591 12.7233 13.4025 12.2833 13.3075 11.71C13.2803 11.5455 13.3032 11.3767 13.373 11.2253C13.4428 11.0739 13.5564 10.947 13.6991 10.8608C14.4113 10.4309 15.1841 10.1107 15.9916 9.91083C16.1535 9.87077 16.3236 9.88017 16.48 9.93781C16.6365 9.99545 16.772 10.0987 16.8691 10.2342C17.2083 10.7067 17.62 10.9175 18 10.9175C18.3791 10.9175 18.7916 10.7067 19.13 10.2342C19.2271 10.0987 19.3627 9.99545 19.5191 9.93781C19.6755 9.88017 19.8465 9.87077 20.0083 9.91083ZM18 13.8333C16.8949 13.8333 15.8351 14.2723 15.0537 15.0537C14.2723 15.8351 13.8333 16.8949 13.8333 18C13.8333 19.1051 14.2723 20.1649 15.0537 20.9463C15.8351 21.7277 16.8949 22.1667 18 22.1667C19.105 22.1667 20.1649 21.7277 20.9463 20.9463C21.7277 20.1649 22.1666 19.1051 22.1666 18C22.1666 16.8949 21.7277 15.8351 20.9463 15.0537C20.1649 14.2723 19.105 13.8333 18 13.8333ZM18 15.5C18.663 15.5 19.2989 15.7634 19.7677 16.2322C20.2366 16.7011 20.5 17.337 20.5 18C20.5 18.663 20.2366 19.2989 19.7677 19.7678C19.2989 20.2366 18.663 20.5 18 20.5C17.3369 20.5 16.7011 20.2366 16.2322 19.7678C15.7634 19.2989 15.5 18.663 15.5 18C15.5 17.337 15.7634 16.7011 16.2322 16.2322C16.7011 15.7634 17.3369 15.5 18 15.5Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_289_39" x1="6" y1="6.5" x2="29.5" y2="30.5" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#56718E"/>
|
||||
<stop offset="1" stop-color="#4B6683"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
@@ -0,0 +1,15 @@
|
||||
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="36" height="36" rx="18" fill="url(#paint0_linear_293_156)"/>
|
||||
<g clip-path="url(#clip0_293_156)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M18 11.3334C16.4977 11.3327 15.0392 11.8395 13.861 12.7715C12.6828 13.7036 11.854 15.0063 11.5088 16.4684C11.1637 17.9305 11.3225 19.4663 11.9595 20.8269C12.5965 22.1874 13.6744 23.2929 15.0184 23.9642C15.2161 24.0632 15.3663 24.2367 15.4361 24.4465C15.5059 24.6563 15.4895 24.8852 15.3904 25.0829C15.2914 25.2806 15.1179 25.4309 14.9081 25.5007C14.6983 25.5705 14.4694 25.554 14.2717 25.455C12.8875 24.7626 11.7236 23.6984 10.9102 22.3816C10.0969 21.0649 9.66628 19.5477 9.66669 18C9.66669 13.3975 13.3975 9.66669 18 9.66669C22.6025 9.66669 26.3334 13.3975 26.3334 18C26.3338 19.5477 25.9032 21.0649 25.0898 22.3816C24.2765 23.6984 23.1125 24.7626 21.7284 25.455C21.6305 25.504 21.5239 25.5333 21.4147 25.5411C21.3055 25.549 21.1958 25.5352 21.0919 25.5007C20.988 25.4661 20.892 25.4115 20.8092 25.3398C20.7265 25.2681 20.6586 25.1808 20.6096 25.0829C20.5606 24.985 20.5313 24.8785 20.5235 24.7693C20.5156 24.6601 20.5294 24.5504 20.5639 24.4465C20.5985 24.3426 20.6532 24.2466 20.7248 24.1638C20.7965 24.081 20.8838 24.0132 20.9817 23.9642C22.3257 23.2929 23.4036 22.1874 24.0406 20.8269C24.6776 19.4663 24.8364 17.9305 24.4912 16.4684C24.1461 15.0063 23.3172 13.7036 22.139 12.7715C20.9608 11.8395 19.5023 11.3327 18 11.3334ZM18 14.6667C17.2487 14.6662 16.5193 14.9195 15.93 15.3855C15.3407 15.8516 14.9262 16.503 14.7536 17.2342C14.581 17.9654 14.6604 18.7335 14.9791 19.4139C15.2978 20.0943 15.837 20.647 16.5092 20.9825C16.7017 21.0844 16.8466 21.2575 16.9131 21.4649C16.9795 21.6723 16.9621 21.8974 16.8647 22.0921C16.7672 22.2869 16.5974 22.4357 16.3915 22.5068C16.1857 22.5779 15.9602 22.5656 15.7634 22.4725C14.7561 21.9686 13.9485 21.1394 13.4714 20.1192C12.9942 19.0991 12.8754 17.9477 13.1343 16.8516C13.3932 15.7555 14.0146 14.7789 14.8977 14.08C15.7809 13.3811 16.8742 13.0009 18.0004 13.0009C19.1267 13.0009 20.22 13.3811 21.1031 14.08C21.9863 14.7789 22.6077 15.7555 22.8666 16.8516C23.1254 17.9477 23.0067 19.0991 22.5295 20.1192C22.0524 21.1394 21.2448 21.9686 20.2375 22.4725C20.0402 22.5695 19.8125 22.5846 19.6041 22.5144C19.3957 22.4443 19.2235 22.2946 19.1249 22.098C19.0264 21.9014 19.0096 21.6738 19.0781 21.4649C19.1467 21.2559 19.295 21.0826 19.4909 20.9825C20.1631 20.647 20.7023 20.0943 21.0209 19.4139C21.3396 18.7335 21.4191 17.9654 21.2465 17.2342C21.0739 16.503 20.6593 15.8516 20.07 15.3855C19.4808 14.9195 18.7513 14.6662 18 14.6667ZM16.3334 18C16.3334 17.558 16.5089 17.1341 16.8215 16.8215C17.1341 16.5089 17.558 16.3334 18 16.3334C18.442 16.3334 18.866 16.5089 19.1785 16.8215C19.4911 17.1341 19.6667 17.558 19.6667 18C19.6667 18.442 19.4911 18.866 19.1785 19.1785C18.866 19.4911 18.442 19.6667 18 19.6667C17.558 19.6667 17.1341 19.4911 16.8215 19.1785C16.5089 18.866 16.3334 18.442 16.3334 18Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_293_156" x1="31" y1="27.5" x2="6.5" y2="7" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFA800"/>
|
||||
<stop offset="1" stop-color="#FFAC4B"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_293_156">
|
||||
<rect width="20" height="20" fill="white" transform="translate(8 8)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1,18 @@
|
||||
<svg id="svg" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400"
|
||||
height="400" viewBox="0, 0, 400,400">
|
||||
<defs>
|
||||
<linearGradient id="Gradient2" x1="0" x2="-0.1" y1="0" y2="1">
|
||||
<stop offset="0%" stop-color="#5b5c9d" />
|
||||
<stop offset="80%" stop-color="rgb(63, 64, 109)" />
|
||||
<stop offset="100%" stop-color="rgb(63, 64, 109)" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g id="svgg">
|
||||
<path id="path0"
|
||||
d="M118.124 39.858 C 116.645 40.182,114.562 42.930,113.283 46.247 C 111.908 49.812,110.887 54.542,107.205 74.400 C 106.736 76.930,105.926 81.160,105.404 83.800 C 104.527 88.237,103.654 92.889,102.177 101.000 C 101.856 102.760,101.413 105.100,101.192 106.200 C 100.754 108.375,100.784 108.199,98.436 122.200 C 96.675 132.702,95.947 137.215,94.984 143.600 C 94.636 145.910,94.098 149.420,93.789 151.400 C 92.649 158.697,91.961 164.413,92.180 164.768 C 92.304 164.968,92.223 165.192,92.000 165.266 C 91.777 165.340,96.852 165.347,103.277 165.282 C 116.520 165.149,115.721 165.379,116.358 161.519 C 122.839 122.279,179.334 121.580,186.617 160.650 C 186.981 162.602,187.463 164.425,187.689 164.700 C 188.328 165.481,229.717 165.423,230.367 164.640 C 230.622 164.332,231.013 162.937,231.235 161.540 C 237.531 121.949,295.239 121.807,301.620 161.367 C 302.274 165.421,301.855 165.252,310.962 165.117 C 315.273 165.052,318.861 165.090,318.935 165.200 C 319.009 165.310,319.005 165.152,318.927 164.848 C 318.849 164.545,318.519 162.610,318.193 160.548 C 317.867 158.487,317.330 155.135,317.000 153.100 C 316.162 147.934,315.312 142.663,314.823 139.600 C 314.595 138.170,314.226 136.010,314.004 134.800 C 313.781 133.590,312.423 125.400,310.985 116.600 C 304.278 75.545,304.008 74.156,298.145 50.400 C 295.961 41.553,294.621 39.584,290.800 39.611 C 287.927 39.631,283.053 43.395,279.420 48.400 C 277.509 51.032,261.566 79.109,256.960 87.953 C 253.991 93.654,253.647 93.789,245.600 92.402 C 218.757 87.774,194.454 87.780,170.478 92.420 C 162.868 93.893,163.987 94.596,157.579 84.306 C 133.879 46.247,126.566 38.009,118.124 39.858 M147.277 134.807 C 123.365 138.118,111.083 165.918,124.948 185.349 C 140.543 207.202,173.704 202.274,182.466 176.800 C 190.000 154.900,170.471 131.596,147.277 134.807 M262.051 134.810 C 235.780 138.440,224.730 170.720,243.356 189.422 C 264.398 210.548,299.874 195.275,298.689 165.600 C 297.927 146.523,280.892 132.207,262.051 134.810 M157.600 153.840 C 163.092 156.341,166.343 161.914,165.902 168.074 C 164.875 182.425,145.082 186.221,138.715 173.287 C 132.759 161.189,145.324 148.250,157.600 153.840 M273.193 153.597 C 279.788 156.926,283.287 165.159,280.844 171.600 C 275.229 186.406,253.594 183.910,252.135 168.287 C 251.125 157.467,263.609 148.758,273.193 153.597 M91.200 168.809 C 91.200 169.522,90.939 171.836,90.621 173.952 C 89.933 178.523,87.567 196.170,85.788 210.000 C 85.462 212.530,85.012 215.860,84.786 217.400 C 84.561 218.940,84.191 221.820,83.964 223.800 C 83.737 225.780,83.288 229.560,82.965 232.200 C 81.886 241.026,80.172 255.664,79.404 262.600 C 78.867 267.450,78.532 270.381,76.987 283.800 C 76.251 290.187,75.405 297.881,74.568 305.800 C 74.220 309.100,73.789 313.150,73.613 314.800 C 73.436 316.450,73.063 320.230,72.784 323.200 C 72.504 326.170,72.169 328.723,72.038 328.874 C 71.701 329.262,59.638 327.033,54.028 325.546 C 34.668 320.412,26.096 301.951,35.625 285.911 C 38.026 281.869,41.515 278.587,49.795 272.581 C 58.081 266.570,59.262 265.247,59.510 261.702 C 59.969 255.136,50.677 252.070,40.551 255.447 C -6.127 271.014,-3.894 337.227,43.806 351.951 C 50.541 354.030,58.050 355.239,67.760 355.807 C 72.410 356.079,75.202 356.542,82.181 358.199 C 88.822 359.777,100.215 360.425,156.000 362.398 C 170.725 362.918,255.192 362.921,267.760 362.401 C 273.062 362.182,281.900 361.820,287.400 361.597 C 302.851 360.972,326.558 359.339,333.200 358.444 C 343.055 357.116,343.889 354.966,341.839 336.200 C 341.502 333.120,340.948 327.900,340.607 324.600 C 340.266 321.300,339.709 315.990,339.370 312.800 C 339.030 309.610,338.499 304.570,338.189 301.600 C 337.022 290.393,335.650 278.160,334.622 269.800 C 333.105 257.460,332.638 254.027,330.408 238.800 C 329.716 234.070,328.983 229.030,328.781 227.600 C 328.579 226.170,328.231 223.920,328.008 222.600 C 327.785 221.280,326.695 214.260,325.587 207.000 C 324.478 199.740,323.124 191.010,322.578 187.600 C 322.032 184.190,321.325 179.780,321.007 177.800 C 320.689 175.820,320.203 172.711,319.925 170.892 C 319.508 168.155,319.315 167.635,318.810 167.885 C 318.475 168.051,314.780 168.279,310.600 168.393 C 301.870 168.631,302.159 168.535,301.810 171.300 C 299.468 189.825,283.036 203.371,264.230 202.279 C 247.552 201.310,233.532 188.465,231.203 172.020 C 230.651 168.125,232.337 168.400,209.000 168.400 C 185.703 168.400,187.432 168.114,186.814 172.064 C 180.639 211.474,123.556 212.329,116.559 173.117 C 115.684 168.218,116.787 168.629,103.881 168.400 C 97.676 168.290,92.285 168.046,91.900 167.857 C 91.307 167.567,91.200 167.712,91.200 168.809 M211.015 197.632 C 214.247 200.942,215.394 201.357,220.447 201.050 C 225.236 200.759,225.846 201.071,224.310 203.023 C 221.899 206.089,216.333 205.843,210.779 202.425 C 208.312 200.907,207.846 200.906,205.600 202.405 C 200.180 206.022,193.561 206.136,191.600 202.647 C 190.824 201.267,191.555 200.665,193.600 201.001 C 199.643 201.993,201.561 201.450,204.900 197.805 C 207.534 194.929,208.352 194.906,211.015 197.632 "
|
||||
stroke="none" fill="url(#Gradient2)" fill-rule="evenodd"></path>
|
||||
<path id="path1"
|
||||
d="M148.000 131.622 C 132.020 133.140,119.114 145.293,116.377 161.400 C 115.675 165.535,116.697 165.239,103.579 165.112 C 97.252 165.050,92.159 165.135,92.261 165.300 C 92.364 165.465,92.279 165.600,92.074 165.600 C 91.391 165.600,91.684 167.617,92.424 168.013 C 92.839 168.235,97.730 168.400,103.897 168.400 C 116.551 168.400,115.666 168.070,116.562 173.117 C 123.522 212.326,180.639 211.470,186.814 172.064 C 187.432 168.114,185.703 168.400,209.000 168.400 C 232.337 168.400,230.651 168.125,231.203 172.020 C 233.532 188.465,247.552 201.310,264.230 202.279 C 283.039 203.371,299.468 189.826,301.811 171.294 C 302.169 168.458,302.345 168.400,310.600 168.400 C 318.988 168.400,319.609 168.159,318.852 165.200 C 318.824 165.090,315.273 165.052,310.961 165.117 C 301.855 165.252,302.274 165.421,301.620 161.367 C 295.239 121.807,237.531 121.949,231.235 161.540 C 231.013 162.937,230.622 164.332,230.367 164.640 C 229.717 165.423,188.328 165.481,187.689 164.700 C 187.463 164.425,186.981 162.602,186.617 160.650 C 183.194 142.288,166.648 129.850,148.000 131.622 M159.886 135.622 C 183.880 141.759,192.035 172.033,174.435 189.636 C 156.404 207.671,125.892 198.962,119.944 174.084 C 114.368 150.761,136.343 129.601,159.886 135.622 M274.658 135.615 C 299.082 141.861,307.061 172.817,288.734 190.222 C 267.975 209.936,234.188 195.420,234.219 166.800 C 234.241 145.502,253.935 130.315,274.658 135.615 M148.542 152.793 C 139.499 154.706,134.598 164.925,138.715 173.287 C 145.082 186.221,164.875 182.425,165.902 168.074 C 166.591 158.446,157.916 150.811,148.542 152.793 M262.091 153.188 C 253.922 156.127,249.883 165.408,253.408 173.139 C 257.835 182.850,271.593 184.491,278.201 176.097 C 287.389 164.426,275.974 148.192,262.091 153.188 M204.900 197.805 C 201.561 201.450,199.643 201.993,193.600 201.001 C 191.555 200.665,190.824 201.267,191.600 202.647 C 193.561 206.136,200.180 206.022,205.600 202.405 C 207.846 200.906,208.312 200.907,210.779 202.425 C 216.333 205.843,221.899 206.089,224.310 203.023 C 225.846 201.071,225.236 200.759,220.447 201.050 C 215.394 201.357,214.247 200.942,211.015 197.632 C 208.352 194.906,207.534 194.929,204.900 197.805 "
|
||||
stroke="none" fill="#ffffff" fill-rule="evenodd"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.6 KiB |
@@ -1,18 +1,25 @@
|
||||
<svg id="svg" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400"
|
||||
height="400" viewBox="0, 0, 400,400">
|
||||
<defs>
|
||||
<linearGradient id="Gradient2" x1="0" x2="-0.1" y1="0" y2="1">
|
||||
<stop offset="0%" stop-color="#5b5c9d" />
|
||||
<stop offset="80%" stop-color="rgb(63, 64, 109)" />
|
||||
<stop offset="100%" stop-color="rgb(63, 64, 109)" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g id="svgg">
|
||||
<path id="path0"
|
||||
d="M118.124 39.858 C 116.645 40.182,114.562 42.930,113.283 46.247 C 111.908 49.812,110.887 54.542,107.205 74.400 C 106.736 76.930,105.926 81.160,105.404 83.800 C 104.527 88.237,103.654 92.889,102.177 101.000 C 101.856 102.760,101.413 105.100,101.192 106.200 C 100.754 108.375,100.784 108.199,98.436 122.200 C 96.675 132.702,95.947 137.215,94.984 143.600 C 94.636 145.910,94.098 149.420,93.789 151.400 C 92.649 158.697,91.961 164.413,92.180 164.768 C 92.304 164.968,92.223 165.192,92.000 165.266 C 91.777 165.340,96.852 165.347,103.277 165.282 C 116.520 165.149,115.721 165.379,116.358 161.519 C 122.839 122.279,179.334 121.580,186.617 160.650 C 186.981 162.602,187.463 164.425,187.689 164.700 C 188.328 165.481,229.717 165.423,230.367 164.640 C 230.622 164.332,231.013 162.937,231.235 161.540 C 237.531 121.949,295.239 121.807,301.620 161.367 C 302.274 165.421,301.855 165.252,310.962 165.117 C 315.273 165.052,318.861 165.090,318.935 165.200 C 319.009 165.310,319.005 165.152,318.927 164.848 C 318.849 164.545,318.519 162.610,318.193 160.548 C 317.867 158.487,317.330 155.135,317.000 153.100 C 316.162 147.934,315.312 142.663,314.823 139.600 C 314.595 138.170,314.226 136.010,314.004 134.800 C 313.781 133.590,312.423 125.400,310.985 116.600 C 304.278 75.545,304.008 74.156,298.145 50.400 C 295.961 41.553,294.621 39.584,290.800 39.611 C 287.927 39.631,283.053 43.395,279.420 48.400 C 277.509 51.032,261.566 79.109,256.960 87.953 C 253.991 93.654,253.647 93.789,245.600 92.402 C 218.757 87.774,194.454 87.780,170.478 92.420 C 162.868 93.893,163.987 94.596,157.579 84.306 C 133.879 46.247,126.566 38.009,118.124 39.858 M147.277 134.807 C 123.365 138.118,111.083 165.918,124.948 185.349 C 140.543 207.202,173.704 202.274,182.466 176.800 C 190.000 154.900,170.471 131.596,147.277 134.807 M262.051 134.810 C 235.780 138.440,224.730 170.720,243.356 189.422 C 264.398 210.548,299.874 195.275,298.689 165.600 C 297.927 146.523,280.892 132.207,262.051 134.810 M157.600 153.840 C 163.092 156.341,166.343 161.914,165.902 168.074 C 164.875 182.425,145.082 186.221,138.715 173.287 C 132.759 161.189,145.324 148.250,157.600 153.840 M273.193 153.597 C 279.788 156.926,283.287 165.159,280.844 171.600 C 275.229 186.406,253.594 183.910,252.135 168.287 C 251.125 157.467,263.609 148.758,273.193 153.597 M91.200 168.809 C 91.200 169.522,90.939 171.836,90.621 173.952 C 89.933 178.523,87.567 196.170,85.788 210.000 C 85.462 212.530,85.012 215.860,84.786 217.400 C 84.561 218.940,84.191 221.820,83.964 223.800 C 83.737 225.780,83.288 229.560,82.965 232.200 C 81.886 241.026,80.172 255.664,79.404 262.600 C 78.867 267.450,78.532 270.381,76.987 283.800 C 76.251 290.187,75.405 297.881,74.568 305.800 C 74.220 309.100,73.789 313.150,73.613 314.800 C 73.436 316.450,73.063 320.230,72.784 323.200 C 72.504 326.170,72.169 328.723,72.038 328.874 C 71.701 329.262,59.638 327.033,54.028 325.546 C 34.668 320.412,26.096 301.951,35.625 285.911 C 38.026 281.869,41.515 278.587,49.795 272.581 C 58.081 266.570,59.262 265.247,59.510 261.702 C 59.969 255.136,50.677 252.070,40.551 255.447 C -6.127 271.014,-3.894 337.227,43.806 351.951 C 50.541 354.030,58.050 355.239,67.760 355.807 C 72.410 356.079,75.202 356.542,82.181 358.199 C 88.822 359.777,100.215 360.425,156.000 362.398 C 170.725 362.918,255.192 362.921,267.760 362.401 C 273.062 362.182,281.900 361.820,287.400 361.597 C 302.851 360.972,326.558 359.339,333.200 358.444 C 343.055 357.116,343.889 354.966,341.839 336.200 C 341.502 333.120,340.948 327.900,340.607 324.600 C 340.266 321.300,339.709 315.990,339.370 312.800 C 339.030 309.610,338.499 304.570,338.189 301.600 C 337.022 290.393,335.650 278.160,334.622 269.800 C 333.105 257.460,332.638 254.027,330.408 238.800 C 329.716 234.070,328.983 229.030,328.781 227.600 C 328.579 226.170,328.231 223.920,328.008 222.600 C 327.785 221.280,326.695 214.260,325.587 207.000 C 324.478 199.740,323.124 191.010,322.578 187.600 C 322.032 184.190,321.325 179.780,321.007 177.800 C 320.689 175.820,320.203 172.711,319.925 170.892 C 319.508 168.155,319.315 167.635,318.810 167.885 C 318.475 168.051,314.780 168.279,310.600 168.393 C 301.870 168.631,302.159 168.535,301.810 171.300 C 299.468 189.825,283.036 203.371,264.230 202.279 C 247.552 201.310,233.532 188.465,231.203 172.020 C 230.651 168.125,232.337 168.400,209.000 168.400 C 185.703 168.400,187.432 168.114,186.814 172.064 C 180.639 211.474,123.556 212.329,116.559 173.117 C 115.684 168.218,116.787 168.629,103.881 168.400 C 97.676 168.290,92.285 168.046,91.900 167.857 C 91.307 167.567,91.200 167.712,91.200 168.809 M211.015 197.632 C 214.247 200.942,215.394 201.357,220.447 201.050 C 225.236 200.759,225.846 201.071,224.310 203.023 C 221.899 206.089,216.333 205.843,210.779 202.425 C 208.312 200.907,207.846 200.906,205.600 202.405 C 200.180 206.022,193.561 206.136,191.600 202.647 C 190.824 201.267,191.555 200.665,193.600 201.001 C 199.643 201.993,201.561 201.450,204.900 197.805 C 207.534 194.929,208.352 194.906,211.015 197.632 "
|
||||
stroke="none" fill="url(#Gradient2)" fill-rule="evenodd"></path>
|
||||
<path id="path1"
|
||||
d="M148.000 131.622 C 132.020 133.140,119.114 145.293,116.377 161.400 C 115.675 165.535,116.697 165.239,103.579 165.112 C 97.252 165.050,92.159 165.135,92.261 165.300 C 92.364 165.465,92.279 165.600,92.074 165.600 C 91.391 165.600,91.684 167.617,92.424 168.013 C 92.839 168.235,97.730 168.400,103.897 168.400 C 116.551 168.400,115.666 168.070,116.562 173.117 C 123.522 212.326,180.639 211.470,186.814 172.064 C 187.432 168.114,185.703 168.400,209.000 168.400 C 232.337 168.400,230.651 168.125,231.203 172.020 C 233.532 188.465,247.552 201.310,264.230 202.279 C 283.039 203.371,299.468 189.826,301.811 171.294 C 302.169 168.458,302.345 168.400,310.600 168.400 C 318.988 168.400,319.609 168.159,318.852 165.200 C 318.824 165.090,315.273 165.052,310.961 165.117 C 301.855 165.252,302.274 165.421,301.620 161.367 C 295.239 121.807,237.531 121.949,231.235 161.540 C 231.013 162.937,230.622 164.332,230.367 164.640 C 229.717 165.423,188.328 165.481,187.689 164.700 C 187.463 164.425,186.981 162.602,186.617 160.650 C 183.194 142.288,166.648 129.850,148.000 131.622 M159.886 135.622 C 183.880 141.759,192.035 172.033,174.435 189.636 C 156.404 207.671,125.892 198.962,119.944 174.084 C 114.368 150.761,136.343 129.601,159.886 135.622 M274.658 135.615 C 299.082 141.861,307.061 172.817,288.734 190.222 C 267.975 209.936,234.188 195.420,234.219 166.800 C 234.241 145.502,253.935 130.315,274.658 135.615 M148.542 152.793 C 139.499 154.706,134.598 164.925,138.715 173.287 C 145.082 186.221,164.875 182.425,165.902 168.074 C 166.591 158.446,157.916 150.811,148.542 152.793 M262.091 153.188 C 253.922 156.127,249.883 165.408,253.408 173.139 C 257.835 182.850,271.593 184.491,278.201 176.097 C 287.389 164.426,275.974 148.192,262.091 153.188 M204.900 197.805 C 201.561 201.450,199.643 201.993,193.600 201.001 C 191.555 200.665,190.824 201.267,191.600 202.647 C 193.561 206.136,200.180 206.022,205.600 202.405 C 207.846 200.906,208.312 200.907,210.779 202.425 C 216.333 205.843,221.899 206.089,224.310 203.023 C 225.846 201.071,225.236 200.759,220.447 201.050 C 215.394 201.357,214.247 200.942,211.015 197.632 C 208.352 194.906,207.534 194.929,204.900 197.805 "
|
||||
stroke="none" fill="#ffffff" fill-rule="evenodd"></path>
|
||||
</g>
|
||||
<svg width="157" height="28" viewBox="0 0 157 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="logo" clip-path="url(#clip0_189_16)">
|
||||
<g id="代理组">
|
||||
<path id="Vector" d="M155.711 21.92C155.205 22.2266 154.611 22.4533 153.931 22.6C153.265 22.7466 152.618 22.82 151.991 22.82C149.858 22.82 148.238 22.3133 147.131 21.3C146.038 20.2866 145.491 18.8667 145.491 17.04C145.491 15.2267 145.978 13.84 146.951 12.88C147.938 11.9066 149.278 11.42 150.971 11.42C152.651 11.42 153.978 11.9066 154.951 12.88C155.938 13.84 156.431 15.2267 156.431 17.04C156.431 17.24 156.418 17.5266 156.391 17.9H148.611C148.905 19.62 150.031 20.48 151.991 20.48C153.365 20.48 154.605 20.0733 155.711 19.26V21.92ZM153.411 15.66C153.238 14.9933 152.938 14.5 152.511 14.18C152.085 13.86 151.571 13.7 150.971 13.7C150.411 13.7 149.931 13.8666 149.531 14.2C149.145 14.5333 148.871 15.02 148.711 15.66H153.411Z" fill="black" fill-opacity="0.88"/>
|
||||
<path id="Vector_2" d="M138.386 27.06C137.572 27.06 136.752 27.0067 135.926 26.9C135.112 26.7933 134.379 26.64 133.726 26.44V23.82C135.152 24.14 136.579 24.3 138.006 24.3C138.912 24.3 139.599 24.1 140.066 23.7C140.532 23.3 140.766 22.7467 140.766 22.04C140.032 22.52 139.159 22.76 138.146 22.76C137.159 22.76 136.292 22.52 135.546 22.04C134.799 21.56 134.226 20.9 133.826 20.06C133.426 19.2067 133.226 18.2267 133.226 17.12C133.226 16.0133 133.426 15.0333 133.826 14.18C134.239 13.3133 134.812 12.64 135.546 12.16C136.292 11.68 137.159 11.44 138.146 11.44C139.066 11.44 139.939 11.7467 140.766 12.36V11.7H143.886V22.46C143.886 25.5267 142.052 27.06 138.386 27.06ZM136.286 17.12C136.286 17.7333 136.406 18.2933 136.646 18.8C136.899 19.3067 137.226 19.7133 137.626 20.02C138.039 20.3133 138.472 20.46 138.926 20.46C139.259 20.46 139.632 20.3867 140.046 20.24C140.459 20.08 140.812 19.7533 141.106 19.26L141.206 18.86V15.16C140.926 14.5867 140.566 14.2133 140.126 14.04C139.686 13.8533 139.286 13.76 138.926 13.76C138.472 13.76 138.039 13.9067 137.626 14.2C137.226 14.4933 136.899 14.9 136.646 15.42C136.406 15.9267 136.286 16.4933 136.286 17.12Z" fill="black" fill-opacity="0.88"/>
|
||||
<path id="Vector_3" d="M129.052 11.7V12.86C129.399 12.4867 129.812 12.2 130.292 12C130.786 11.7867 131.272 11.68 131.752 11.68C132.086 11.68 132.399 11.7267 132.692 11.82V14.28C132.319 14.1867 131.966 14.14 131.632 14.14C131.126 14.14 130.646 14.2267 130.192 14.4C129.739 14.56 129.359 14.82 129.052 15.18V22.5H125.852V11.7H129.052Z" fill="black" fill-opacity="0.88"/>
|
||||
<path id="Vector_4" d="M123.524 21.92C123.017 22.2266 122.424 22.4533 121.744 22.6C121.077 22.7466 120.43 22.82 119.804 22.82C117.67 22.82 116.05 22.3133 114.944 21.3C113.85 20.2866 113.304 18.8667 113.304 17.04C113.304 15.2267 113.79 13.84 114.764 12.88C115.75 11.9066 117.09 11.42 118.784 11.42C120.464 11.42 121.79 11.9066 122.764 12.88C123.75 13.84 124.244 15.2267 124.244 17.04C124.244 17.24 124.23 17.5266 124.204 17.9H116.424C116.717 19.62 117.844 20.48 119.804 20.48C121.177 20.48 122.417 20.0733 123.524 19.26V21.92ZM121.224 15.66C121.05 14.9933 120.75 14.5 120.324 14.18C119.897 13.86 119.384 13.7 118.784 13.7C118.224 13.7 117.744 13.8666 117.344 14.2C116.957 14.5333 116.684 15.02 116.524 15.66H121.224Z" fill="black" fill-opacity="0.88"/>
|
||||
<path id="Vector_5" d="M99.3491 8.5H102.929L106.289 20.28H106.369L109.729 8.5H113.309L108.989 22.5H103.669L99.3491 8.5Z" fill="black" fill-opacity="0.88"/>
|
||||
<path id="Vector_6" d="M86.2201 7.12V12.86C86.7001 12.46 87.2335 12.1533 87.8201 11.94C88.4068 11.7133 89.0001 11.6 89.6001 11.6C90.6268 11.6 91.4401 11.9 92.0401 12.5C92.6535 13.1 92.9601 13.88 92.9601 14.84V22.5H89.7601V15.38C89.7601 14.9933 89.6001 14.68 89.2801 14.44C88.9601 14.1867 88.5801 14.06 88.1401 14.06C87.8868 14.06 87.5735 14.14 87.2001 14.3C86.8268 14.46 86.5001 14.66 86.2201 14.9V22.5H83.0201V7.12H86.2201Z" fill="black" fill-opacity="0.88"/>
|
||||
<path id="Vector_7" d="M77.7898 22.74C76.9765 22.78 76.1498 22.7133 75.3098 22.54C74.4832 22.38 73.7632 22.1267 73.1498 21.78V18.9C73.6965 19.3 74.3498 19.6333 75.1098 19.9C75.8832 20.1533 76.5632 20.26 77.1498 20.22C77.5632 20.1933 77.8632 20.1067 78.0498 19.96C78.2365 19.8 78.3432 19.64 78.3698 19.48C78.4365 19.0667 78.3365 18.7533 78.0698 18.54C77.8032 18.3267 77.3032 18.12 76.5698 17.92C75.7565 17.6933 75.1098 17.4333 74.6298 17.14C74.1498 16.8333 73.7832 16.4933 73.5298 16.12C73.2898 15.7333 73.1698 15.2467 73.1698 14.66C73.1698 14.0467 73.3298 13.5 73.6498 13.02C73.9698 12.5267 74.4765 12.14 75.1698 11.86C75.8632 11.58 76.6098 11.44 77.4098 11.44C78.0098 11.44 78.6165 11.5 79.2298 11.62C79.8432 11.7267 80.3565 11.8733 80.7698 12.06V14.64C80.3432 14.4267 79.8365 14.24 79.2498 14.08C78.6765 13.9067 78.1765 13.82 77.7498 13.82C76.8432 13.82 76.3498 14.06 76.2698 14.54C76.2298 14.82 76.3565 15.0667 76.6498 15.28C76.9565 15.4933 77.3898 15.6867 77.9498 15.86C78.7498 16.1133 79.4032 16.3733 79.9098 16.64C80.4298 16.9067 80.8365 17.2533 81.1298 17.68C81.4232 18.1067 81.5698 18.6533 81.5698 19.32C81.5698 20.3067 81.2165 21.1133 80.5098 21.74C79.8032 22.3533 78.8965 22.6867 77.7898 22.74Z" fill="black" fill-opacity="0.88"/>
|
||||
<path id="Vector_8" d="M65.667 22.76C64.6537 22.76 63.767 22.52 63.007 22.04C62.2603 21.56 61.6803 20.9 61.267 20.06C60.867 19.22 60.667 18.24 60.667 17.12C60.667 16.0133 60.867 15.0333 61.267 14.18C61.6803 13.3133 62.2537 12.64 62.987 12.16C63.7337 11.68 64.6003 11.44 65.587 11.44C66.0403 11.44 66.4937 11.5267 66.947 11.7C67.4137 11.86 67.847 12.0867 68.247 12.38V11.7H71.447V19.98C71.447 21.06 71.5803 21.9 71.847 22.5H68.847C68.7537 22.3133 68.6737 22.0733 68.607 21.78C67.7803 22.4333 66.8003 22.76 65.667 22.76ZM63.727 17.12C63.727 17.7333 63.847 18.2933 64.087 18.8C64.3403 19.3067 64.667 19.7133 65.067 20.02C65.4803 20.3133 65.9137 20.46 66.367 20.46C66.687 20.46 67.047 20.3933 67.447 20.26C67.847 20.1133 68.1937 19.8067 68.487 19.34V14.86C68.1937 14.3933 67.847 14.0933 67.447 13.96C67.0603 13.8267 66.7003 13.76 66.367 13.76C65.9137 13.76 65.4803 13.9067 65.067 14.2C64.667 14.4933 64.3403 14.9 64.087 15.42C63.847 15.9267 63.727 16.4933 63.727 17.12Z" fill="black" fill-opacity="0.88"/>
|
||||
<path id="Vector_9" d="M58.3398 22.66C57.1398 22.66 56.2332 22.3267 55.6198 21.66C55.0065 20.98 54.6998 19.9867 54.6998 18.68V7.12H57.8998V17.88C57.8998 18.4133 57.9398 18.82 58.0198 19.1C58.0998 19.38 58.2332 19.5933 58.4198 19.74C58.5665 19.86 58.7265 19.94 58.8998 19.98C59.0865 20.0067 59.4065 20.02 59.8598 20.02V22.66H58.3398Z" fill="black" fill-opacity="0.88"/>
|
||||
<path id="Vector_10" d="M48.6679 22.76C47.1479 22.76 45.7946 22.48 44.6079 21.92C43.4346 21.36 42.5079 20.5467 41.8279 19.48C41.1613 18.4133 40.8279 17.1467 40.8279 15.68C40.8279 14.1333 41.1546 12.8067 41.8079 11.7C42.4613 10.58 43.3746 9.72666 44.5479 9.13999C45.7213 8.53999 47.0613 8.23999 48.5679 8.23999C49.3146 8.23999 50.0679 8.31332 50.8279 8.45999C51.5879 8.60666 52.2146 8.79332 52.7079 9.01999V11.5C51.3613 11.0067 50.1079 10.76 48.9479 10.76C47.5613 10.76 46.4346 11.1667 45.5679 11.98C44.7146 12.7933 44.2879 14.0267 44.2879 15.68C44.2879 16.56 44.5013 17.34 44.9279 18.02C45.3546 18.6867 45.9146 19.2067 46.6079 19.58C47.3013 19.9533 48.0479 20.1667 48.8479 20.22L49.2879 20.24C49.9013 20.24 50.5079 20.1533 51.1079 19.98C51.7079 19.8067 52.2413 19.5733 52.7079 19.28V22.04C52.1346 22.3067 51.5213 22.4933 50.8679 22.6C50.2279 22.7067 49.4946 22.76 48.6679 22.76Z" fill="black" fill-opacity="0.88"/>
|
||||
</g>
|
||||
<g id="svgg">
|
||||
<path id="path0" fill-rule="evenodd" clip-rule="evenodd" d="M9.45822 0.677484C9.3398 0.703426 9.17301 0.923458 9.0706 1.18905C8.96051 1.4745 8.87876 1.85323 8.58394 3.44325C8.54639 3.64583 8.48153 3.98453 8.43973 4.19591C8.36951 4.55118 8.29961 4.92366 8.18135 5.57311C8.15565 5.71403 8.12018 5.9014 8.10248 5.98947C8.06741 6.16362 8.06981 6.14953 7.88181 7.27059C7.7408 8.11148 7.68251 8.47284 7.60541 8.98408C7.57754 9.16904 7.53446 9.45009 7.50972 9.60863C7.41844 10.1929 7.36336 10.6506 7.38089 10.679C7.39082 10.695 7.38433 10.7129 7.36648 10.7189C7.34862 10.7248 7.75498 10.7254 8.26943 10.7202C9.32979 10.7095 9.26581 10.7279 9.31682 10.4189C9.83575 7.27691 14.3593 7.22095 14.9424 10.3493C14.9716 10.5056 15.0102 10.6515 15.0283 10.6736C15.0794 10.7361 18.3935 10.7314 18.4455 10.6687C18.4659 10.6441 18.4972 10.5324 18.515 10.4205C19.0191 7.25049 23.6398 7.23912 24.1507 10.4067C24.2031 10.7313 24.1695 10.7178 24.8987 10.7069C25.2439 10.7017 25.5312 10.7048 25.5371 10.7136C25.543 10.7224 25.5427 10.7097 25.5365 10.6854C25.5302 10.6611 25.5038 10.5062 25.4777 10.3411C25.4516 10.1761 25.4086 9.90769 25.3822 9.74475C25.3151 9.3311 25.247 8.90906 25.2079 8.6638C25.1896 8.5493 25.1601 8.37635 25.1423 8.27947C25.1244 8.18258 25.0157 7.52681 24.9006 6.8222C24.3635 3.53493 24.3419 3.42372 23.8725 1.52158C23.6976 0.813202 23.5903 0.655544 23.2844 0.657706C23.0543 0.659308 22.6641 0.96069 22.3732 1.36144C22.2201 1.57218 20.9436 3.8203 20.5748 4.52844C20.3371 4.98492 20.3095 4.99573 19.6652 4.88467C17.5159 4.51411 15.5699 4.51459 13.6502 4.88611C13.0409 5.00405 13.1305 5.06034 12.6174 4.23642C10.7197 1.18905 10.1342 0.529434 9.45822 0.677484ZM11.7925 8.28003C9.87787 8.54514 8.89445 10.7711 10.0046 12.3269C11.2533 14.0767 13.9085 13.6821 14.6101 11.6424C15.2133 9.88887 13.6496 8.02292 11.7925 8.28003ZM20.9824 8.28027C18.8789 8.57092 17.9941 11.1556 19.4855 12.653C21.1704 14.3446 24.0109 13.1217 23.916 10.7456C23.855 9.21813 22.491 8.07185 20.9824 8.28027ZM12.6191 9.804C13.0588 10.0043 13.3191 10.4505 13.2838 10.9437C13.2016 12.0928 11.6167 12.3967 11.1069 11.3611C10.63 10.3924 11.6361 9.35641 12.6191 9.804ZM21.8746 9.78454C22.4026 10.0511 22.6828 10.7103 22.4872 11.226C22.0376 12.4115 20.3053 12.2117 20.1885 10.9608C20.1076 10.0944 21.1072 9.39708 21.8746 9.78454ZM7.30242 11.0026C7.30242 11.0597 7.28152 11.2449 7.25606 11.4144C7.20097 11.7804 7.01153 13.1933 6.86908 14.3007C6.84298 14.5033 6.80695 14.7699 6.78885 14.8932C6.77084 15.0165 6.74121 15.2471 6.72304 15.4057C6.70486 15.5642 6.66891 15.8669 6.64305 16.0783C6.55665 16.785 6.41941 17.957 6.35792 18.5124C6.31492 18.9007 6.2881 19.1354 6.16439 20.2099C6.10546 20.7213 6.03772 21.3373 5.9707 21.9714C5.94284 22.2356 5.90833 22.5599 5.89423 22.692C5.88006 22.8241 5.8502 23.1268 5.82786 23.3646C5.80544 23.6024 5.77861 23.8068 5.76813 23.8189C5.74114 23.85 4.77526 23.6715 4.32607 23.5525C2.77592 23.1414 2.08956 21.6632 2.85254 20.3789C3.04479 20.0552 3.32416 19.7925 3.98713 19.3116C4.65059 18.8303 4.74515 18.7243 4.76501 18.4405C4.80176 17.9147 4.05775 17.6693 3.24697 17.9396C-0.49053 19.1861 -0.311734 24.4878 3.5076 25.6667C4.04687 25.8332 4.64811 25.93 5.42559 25.9754C5.79791 25.9972 6.02147 26.0343 6.58027 26.167C7.11202 26.2933 8.02425 26.3452 12.4909 26.5032C13.67 26.5448 20.4332 26.5451 21.4395 26.5034C21.8641 26.4859 22.5717 26.4569 23.0121 26.4391C24.2493 26.389 26.1475 26.2583 26.6793 26.1866C27.4684 26.0803 27.5352 25.9081 27.371 24.4055C27.3441 24.1589 27.2997 23.7409 27.2724 23.4767C27.2451 23.2125 27.2005 22.7873 27.1733 22.5319C27.1461 22.2765 27.1036 21.8729 27.0788 21.6351C26.9853 20.7378 26.8755 19.7583 26.7932 19.0889C26.6717 18.1008 26.6343 17.8259 26.4558 16.6067C26.4003 16.228 26.3417 15.8244 26.3255 15.7099C26.3093 15.5954 26.2814 15.4153 26.2636 15.3096C26.2457 15.2039 26.1585 14.6418 26.0697 14.0605C25.9809 13.4792 25.8725 12.7802 25.8288 12.5072C25.7851 12.2341 25.7285 11.881 25.703 11.7225C25.6776 11.5639 25.6386 11.315 25.6164 11.1693C25.583 10.9502 25.5675 10.9086 25.5271 10.9286C25.5003 10.9419 25.2044 10.9601 24.8697 10.9693C24.1707 10.9883 24.1939 10.9806 24.1659 11.202C23.9784 12.6853 22.6627 13.7699 21.1569 13.6825C19.8215 13.6049 18.6989 12.5764 18.5124 11.2597C18.4682 10.9478 18.6032 10.9698 16.7346 10.9698C14.8693 10.9698 15.0077 10.9469 14.9582 11.2632C14.4638 14.4187 9.89316 14.4872 9.33291 11.3475C9.26285 10.9552 9.35117 10.9881 8.31779 10.9698C7.82095 10.961 7.3893 10.9415 7.35847 10.9263C7.31099 10.9031 7.30242 10.9147 7.30242 11.0026ZM16.896 13.3104C17.1548 13.5754 17.2466 13.6087 17.6512 13.5841C18.0347 13.5608 18.0835 13.5858 17.9605 13.7421C17.7675 13.9876 17.3218 13.9679 16.8771 13.6942C16.6796 13.5726 16.6422 13.5726 16.4624 13.6926C16.0284 13.9822 15.4984 13.9913 15.3414 13.712C15.2793 13.6015 15.3378 13.5533 15.5016 13.5802C15.9854 13.6596 16.139 13.6161 16.4064 13.3243C16.6173 13.094 16.6828 13.0921 16.896 13.3104Z" fill="#40416F"/>
|
||||
<path id="path1" fill-rule="evenodd" clip-rule="evenodd" d="M11.8504 8.025C10.5709 8.14655 9.53748 9.11964 9.31833 10.4093C9.26212 10.7404 9.34395 10.7167 8.29359 10.7065C7.78699 10.7016 7.3792 10.7084 7.38736 10.7216C7.39561 10.7348 7.38881 10.7456 7.37239 10.7456C7.3177 10.7456 7.34116 10.9071 7.40042 10.9388C7.43364 10.9566 7.82527 10.9698 8.31906 10.9698C9.33226 10.9698 9.2614 10.9434 9.33314 11.3475C9.89043 14.487 14.4638 14.4184 14.9582 11.2632C15.0077 10.9469 14.8692 10.9698 16.7346 10.9698C18.6032 10.9698 18.4682 10.9478 18.5124 11.2597C18.6989 12.5764 19.8215 13.6049 21.1569 13.6825C22.6629 13.7699 23.9784 12.6854 24.166 11.2015C24.1947 10.9745 24.2087 10.9698 24.8697 10.9698C25.5413 10.9698 25.5911 10.9505 25.5305 10.7136C25.5282 10.7048 25.2439 10.7017 24.8986 10.7069C24.1695 10.7177 24.2031 10.7313 24.1507 10.4067C23.6398 7.23912 19.0191 7.25049 18.515 10.4205C18.4972 10.5324 18.4659 10.6441 18.4455 10.6687C18.3934 10.7314 15.0794 10.7361 15.0283 10.6736C15.0102 10.6515 14.9716 10.5056 14.9424 10.3493C14.6683 8.87903 13.3435 7.88312 11.8504 8.025ZM12.8021 8.34528C14.7233 8.83667 15.3762 11.2607 13.967 12.6702C12.5233 14.1142 10.0802 13.4169 9.60394 11.4249C9.15747 9.55746 10.917 7.86318 12.8021 8.34528ZM21.9918 8.34472C23.9475 8.84484 24.5864 11.3235 23.1189 12.7171C21.4567 14.2956 18.7514 13.1333 18.7539 10.8417C18.7557 9.13637 20.3326 7.92035 21.9918 8.34472ZM11.8938 9.72016C11.1697 9.87333 10.7773 10.6916 11.1069 11.3611C11.6167 12.3967 13.2016 12.0928 13.2838 10.9437C13.339 10.1728 12.6443 9.56146 11.8938 9.72016ZM20.9856 9.75179C20.3315 9.98711 20.0081 10.7302 20.2904 11.3493C20.6448 12.1268 21.7464 12.2582 22.2755 11.5861C23.0112 10.6516 22.0972 9.35176 20.9856 9.75179ZM16.4063 13.3243C16.139 13.6161 15.9854 13.6596 15.5016 13.5802C15.3378 13.5533 15.2793 13.6015 15.3414 13.712C15.4984 13.9913 16.0284 13.9822 16.4624 13.6926C16.6422 13.5726 16.6795 13.5726 16.8771 13.6942C17.3218 13.9679 17.7674 13.9876 17.9605 13.7421C18.0835 13.5858 18.0346 13.5608 17.6512 13.5841C17.2466 13.6087 17.1548 13.5754 16.896 13.3104C16.6827 13.0921 16.6172 13.094 16.4063 13.3243Z" fill="white"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_189_16">
|
||||
<rect width="157" height="27" fill="white" transform="translate(0 0.5)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,20 @@
|
||||
<svg width="157" height="27" viewBox="0 0 157 27" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_745_54)">
|
||||
<path d="M155.711 21.42C155.205 21.7266 154.611 21.9533 153.931 22.1C153.265 22.2466 152.618 22.32 151.991 22.32C149.858 22.32 148.238 21.8133 147.131 20.8C146.038 19.7866 145.491 18.3667 145.491 16.54C145.491 14.7267 145.978 13.34 146.951 12.38C147.938 11.4066 149.278 10.92 150.971 10.92C152.651 10.92 153.978 11.4066 154.951 12.38C155.938 13.34 156.431 14.7267 156.431 16.54C156.431 16.74 156.418 17.0266 156.391 17.4H148.611C148.905 19.12 150.031 19.98 151.991 19.98C153.365 19.98 154.605 19.5733 155.711 18.76V21.42ZM153.411 15.16C153.238 14.4933 152.938 14 152.511 13.68C152.085 13.36 151.571 13.2 150.971 13.2C150.411 13.2 149.931 13.3666 149.531 13.7C149.145 14.0333 148.871 14.52 148.711 15.16H153.411Z" fill="white"/>
|
||||
<path d="M138.386 26.56C137.572 26.56 136.752 26.5067 135.926 26.4C135.112 26.2933 134.379 26.14 133.726 25.94V23.32C135.152 23.64 136.579 23.8 138.006 23.8C138.912 23.8 139.599 23.6 140.066 23.2C140.532 22.8 140.766 22.2467 140.766 21.54C140.032 22.02 139.159 22.26 138.146 22.26C137.159 22.26 136.292 22.02 135.546 21.54C134.799 21.06 134.226 20.4 133.826 19.56C133.426 18.7067 133.226 17.7267 133.226 16.62C133.226 15.5133 133.426 14.5333 133.826 13.68C134.239 12.8133 134.812 12.14 135.546 11.66C136.292 11.18 137.159 10.94 138.146 10.94C139.066 10.94 139.939 11.2467 140.766 11.86V11.2H143.886V21.96C143.886 25.0267 142.052 26.56 138.386 26.56ZM136.286 16.62C136.286 17.2333 136.406 17.7933 136.646 18.3C136.899 18.8067 137.226 19.2133 137.626 19.52C138.039 19.8133 138.472 19.96 138.926 19.96C139.259 19.96 139.632 19.8867 140.046 19.74C140.459 19.58 140.812 19.2533 141.106 18.76L141.206 18.36V14.66C140.926 14.0867 140.566 13.7133 140.126 13.54C139.686 13.3533 139.286 13.26 138.926 13.26C138.472 13.26 138.039 13.4067 137.626 13.7C137.226 13.9933 136.899 14.4 136.646 14.92C136.406 15.4267 136.286 15.9933 136.286 16.62Z" fill="white"/>
|
||||
<path d="M129.052 11.2V12.36C129.399 11.9867 129.812 11.7 130.292 11.5C130.786 11.2867 131.272 11.18 131.752 11.18C132.086 11.18 132.399 11.2267 132.692 11.32V13.78C132.319 13.6867 131.966 13.64 131.632 13.64C131.126 13.64 130.646 13.7267 130.192 13.9C129.739 14.06 129.359 14.32 129.052 14.68V22H125.852V11.2H129.052Z" fill="white"/>
|
||||
<path d="M123.524 21.42C123.017 21.7266 122.424 21.9533 121.744 22.1C121.077 22.2466 120.43 22.32 119.804 22.32C117.67 22.32 116.05 21.8133 114.944 20.8C113.85 19.7866 113.304 18.3667 113.304 16.54C113.304 14.7267 113.79 13.34 114.764 12.38C115.75 11.4066 117.09 10.92 118.784 10.92C120.464 10.92 121.79 11.4066 122.764 12.38C123.75 13.34 124.244 14.7267 124.244 16.54C124.244 16.74 124.23 17.0266 124.204 17.4H116.424C116.717 19.12 117.844 19.98 119.804 19.98C121.177 19.98 122.417 19.5733 123.524 18.76V21.42ZM121.224 15.16C121.05 14.4933 120.75 14 120.324 13.68C119.897 13.36 119.384 13.2 118.784 13.2C118.224 13.2 117.744 13.3666 117.344 13.7C116.957 14.0333 116.684 14.52 116.524 15.16H121.224Z" fill="white"/>
|
||||
<path d="M99.3491 8H102.929L106.289 19.78H106.369L109.729 8H113.309L108.989 22H103.669L99.3491 8Z" fill="white"/>
|
||||
<path d="M86.2201 6.62V12.36C86.7001 11.96 87.2335 11.6533 87.8201 11.44C88.4068 11.2133 89.0001 11.1 89.6001 11.1C90.6268 11.1 91.4401 11.4 92.0401 12C92.6535 12.6 92.9601 13.38 92.9601 14.34V22H89.7601V14.88C89.7601 14.4933 89.6001 14.18 89.2801 13.94C88.9601 13.6867 88.5801 13.56 88.1401 13.56C87.8868 13.56 87.5735 13.64 87.2001 13.8C86.8268 13.96 86.5001 14.16 86.2201 14.4V22H83.0201V6.62H86.2201Z" fill="white"/>
|
||||
<path d="M77.7898 22.24C76.9765 22.28 76.1498 22.2133 75.3098 22.04C74.4832 21.88 73.7632 21.6267 73.1498 21.28V18.4C73.6965 18.8 74.3498 19.1333 75.1098 19.4C75.8832 19.6533 76.5632 19.76 77.1498 19.72C77.5632 19.6933 77.8632 19.6067 78.0498 19.46C78.2365 19.3 78.3432 19.14 78.3698 18.98C78.4365 18.5667 78.3365 18.2533 78.0698 18.04C77.8032 17.8267 77.3032 17.62 76.5698 17.42C75.7565 17.1933 75.1098 16.9333 74.6298 16.64C74.1498 16.3333 73.7832 15.9933 73.5298 15.62C73.2898 15.2333 73.1698 14.7467 73.1698 14.16C73.1698 13.5467 73.3298 13 73.6498 12.52C73.9698 12.0267 74.4765 11.64 75.1698 11.36C75.8632 11.08 76.6098 10.94 77.4098 10.94C78.0098 10.94 78.6165 11 79.2298 11.12C79.8432 11.2267 80.3565 11.3733 80.7698 11.56V14.14C80.3432 13.9267 79.8365 13.74 79.2498 13.58C78.6765 13.4067 78.1765 13.32 77.7498 13.32C76.8432 13.32 76.3498 13.56 76.2698 14.04C76.2298 14.32 76.3565 14.5667 76.6498 14.78C76.9565 14.9933 77.3898 15.1867 77.9498 15.36C78.7498 15.6133 79.4032 15.8733 79.9098 16.14C80.4298 16.4067 80.8365 16.7533 81.1298 17.18C81.4232 17.6067 81.5698 18.1533 81.5698 18.82C81.5698 19.8067 81.2165 20.6133 80.5098 21.24C79.8032 21.8533 78.8965 22.1867 77.7898 22.24Z" fill="white"/>
|
||||
<path d="M65.667 22.26C64.6537 22.26 63.767 22.02 63.007 21.54C62.2603 21.06 61.6803 20.4 61.267 19.56C60.867 18.72 60.667 17.74 60.667 16.62C60.667 15.5133 60.867 14.5333 61.267 13.68C61.6803 12.8133 62.2537 12.14 62.987 11.66C63.7337 11.18 64.6003 10.94 65.587 10.94C66.0403 10.94 66.4937 11.0267 66.947 11.2C67.4137 11.36 67.847 11.5867 68.247 11.88V11.2H71.447V19.48C71.447 20.56 71.5803 21.4 71.847 22H68.847C68.7537 21.8133 68.6737 21.5733 68.607 21.28C67.7803 21.9333 66.8003 22.26 65.667 22.26ZM63.727 16.62C63.727 17.2333 63.847 17.7933 64.087 18.3C64.3403 18.8067 64.667 19.2133 65.067 19.52C65.4803 19.8133 65.9137 19.96 66.367 19.96C66.687 19.96 67.047 19.8933 67.447 19.76C67.847 19.6133 68.1937 19.3067 68.487 18.84V14.36C68.1937 13.8933 67.847 13.5933 67.447 13.46C67.0603 13.3267 66.7003 13.26 66.367 13.26C65.9137 13.26 65.4803 13.4067 65.067 13.7C64.667 13.9933 64.3403 14.4 64.087 14.92C63.847 15.4267 63.727 15.9933 63.727 16.62Z" fill="white"/>
|
||||
<path d="M58.3398 22.16C57.1398 22.16 56.2332 21.8267 55.6198 21.16C55.0065 20.48 54.6998 19.4867 54.6998 18.18V6.62H57.8998V17.38C57.8998 17.9133 57.9398 18.32 58.0198 18.6C58.0998 18.88 58.2332 19.0933 58.4198 19.24C58.5665 19.36 58.7265 19.44 58.8998 19.48C59.0865 19.5067 59.4065 19.52 59.8598 19.52V22.16H58.3398Z" fill="white"/>
|
||||
<path d="M48.6679 22.26C47.1479 22.26 45.7946 21.98 44.6079 21.42C43.4346 20.86 42.5079 20.0467 41.8279 18.98C41.1613 17.9133 40.8279 16.6467 40.8279 15.18C40.8279 13.6333 41.1546 12.3067 41.8079 11.2C42.4613 10.08 43.3746 9.22666 44.5479 8.63999C45.7213 8.03999 47.0613 7.73999 48.5679 7.73999C49.3146 7.73999 50.0679 7.81332 50.8279 7.95999C51.5879 8.10666 52.2146 8.29332 52.7079 8.51999V11C51.3613 10.5067 50.1079 10.26 48.9479 10.26C47.5613 10.26 46.4346 10.6667 45.5679 11.48C44.7146 12.2933 44.2879 13.5267 44.2879 15.18C44.2879 16.06 44.5013 16.84 44.9279 17.52C45.3546 18.1867 45.9146 18.7067 46.6079 19.08C47.3013 19.4533 48.0479 19.6667 48.8479 19.72L49.2879 19.74C49.9013 19.74 50.5079 19.6533 51.1079 19.48C51.7079 19.3067 52.2413 19.0733 52.7079 18.78V21.54C52.1346 21.8067 51.5213 21.9933 50.8679 22.1C50.2279 22.2067 49.4946 22.26 48.6679 22.26Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.45822 0.177484C9.3398 0.203426 9.17301 0.423458 9.0706 0.689049C8.96051 0.974498 8.87876 1.35323 8.58394 2.94325C8.54639 3.14583 8.48153 3.48453 8.43973 3.69591C8.36951 4.05118 8.29961 4.42366 8.18135 5.07311C8.15565 5.21403 8.12018 5.4014 8.10248 5.48947C8.06741 5.66362 8.06981 5.64953 7.88181 6.77059C7.7408 7.61148 7.68251 7.97284 7.60541 8.48408C7.57754 8.66904 7.53446 8.95009 7.50972 9.10863C7.41844 9.69289 7.36336 10.1506 7.38089 10.179C7.39082 10.195 7.38433 10.2129 7.36648 10.2189C7.34862 10.2248 7.75498 10.2254 8.26943 10.2202C9.32979 10.2095 9.26581 10.2279 9.31682 9.91885C9.83575 6.77691 14.3593 6.72095 14.9424 9.84927C14.9716 10.0056 15.0102 10.1515 15.0283 10.1736C15.0794 10.2361 18.3935 10.2314 18.4455 10.1687C18.4659 10.1441 18.4972 10.0324 18.515 9.92053C19.0191 6.75049 23.6398 6.73912 24.1507 9.90668C24.2031 10.2313 24.1695 10.2178 24.8987 10.2069C25.2439 10.2017 25.5312 10.2048 25.5371 10.2136C25.543 10.2224 25.5427 10.2097 25.5365 10.1854C25.5302 10.1611 25.5038 10.0062 25.4777 9.8411C25.4516 9.67608 25.4086 9.40769 25.3822 9.24475C25.3151 8.8311 25.247 8.40906 25.2079 8.1638C25.1896 8.0493 25.1601 7.87635 25.1423 7.77947C25.1244 7.68258 25.0157 7.02681 24.9006 6.3222C24.3635 3.03493 24.3419 2.92372 23.8725 1.02158C23.6976 0.313202 23.5903 0.155544 23.2844 0.157706C23.0543 0.159308 22.6641 0.46069 22.3732 0.86144C22.2201 1.07218 20.9436 3.3203 20.5748 4.02844C20.3371 4.48492 20.3095 4.49573 19.6652 4.38467C17.5159 4.01411 15.5699 4.01459 13.6502 4.38611C13.0409 4.50405 13.1305 4.56034 12.6174 3.73642C10.7197 0.689049 10.1342 0.0294346 9.45822 0.177484ZM11.7925 7.78003C9.87787 8.04514 8.89445 10.2711 10.0046 11.8269C11.2533 13.5767 13.9085 13.1821 14.6101 11.1424C15.2133 9.38887 13.6496 7.52292 11.7925 7.78003ZM20.9824 7.78027C18.8789 8.07092 17.9941 10.6556 19.4855 12.153C21.1704 13.8446 24.0109 12.6217 23.916 10.2456C23.855 8.71813 22.491 7.57185 20.9824 7.78027ZM12.6191 9.304C13.0588 9.50425 13.3191 9.95048 13.2838 10.4437C13.2016 11.5928 11.6167 11.8967 11.1069 10.8611C10.63 9.89243 11.6361 8.85641 12.6191 9.304ZM21.8746 9.28454C22.4026 9.55109 22.6828 10.2103 22.4872 10.726C22.0376 11.9115 20.3053 11.7117 20.1885 10.4608C20.1076 9.59441 21.1072 8.89708 21.8746 9.28454ZM7.30242 10.5026C7.30242 10.5597 7.28152 10.7449 7.25606 10.9144C7.20097 11.2804 7.01153 12.6933 6.86908 13.8007C6.84298 14.0033 6.80695 14.2699 6.78885 14.3932C6.77084 14.5165 6.74121 14.7471 6.72304 14.9057C6.70486 15.0642 6.66891 15.3669 6.64305 15.5783C6.55665 16.285 6.41941 17.457 6.35792 18.0124C6.31492 18.4007 6.2881 18.6354 6.16439 19.7099C6.10546 20.2213 6.03772 20.8373 5.9707 21.4714C5.94284 21.7356 5.90833 22.0599 5.89423 22.192C5.88006 22.3241 5.8502 22.6268 5.82786 22.8646C5.80544 23.1024 5.77861 23.3068 5.76813 23.3189C5.74114 23.35 4.77526 23.1715 4.32607 23.0525C2.77592 22.6414 2.08956 21.1632 2.85254 19.8789C3.04479 19.5552 3.32416 19.2925 3.98713 18.8116C4.65059 18.3303 4.74515 18.2243 4.76501 17.9405C4.80176 17.4147 4.05775 17.1693 3.24697 17.4396C-0.490531 18.6861 -0.311735 23.9878 3.5076 25.1667C4.04687 25.3332 4.64811 25.43 5.42559 25.4754C5.79791 25.4972 6.02147 25.5343 6.58027 25.667C7.11202 25.7933 8.02425 25.8452 12.4909 26.0032C13.67 26.0448 20.4332 26.0451 21.4395 26.0034C21.8641 25.9859 22.5717 25.9569 23.0121 25.9391C24.2493 25.889 26.1475 25.7583 26.6793 25.6866C27.4684 25.5803 27.5352 25.4081 27.371 23.9055C27.3441 23.6589 27.2997 23.2409 27.2724 22.9767C27.2451 22.7125 27.2005 22.2873 27.1733 22.0319C27.1461 21.7765 27.1036 21.3729 27.0788 21.1351C26.9853 20.2378 26.8755 19.2583 26.7932 18.5889C26.6717 17.6008 26.6343 17.3259 26.4558 16.1067C26.4003 15.728 26.3417 15.3244 26.3255 15.2099C26.3093 15.0954 26.2814 14.9153 26.2636 14.8096C26.2457 14.7039 26.1585 14.1418 26.0697 13.5605C25.9809 12.9792 25.8725 12.2802 25.8288 12.0072C25.7851 11.7341 25.7285 11.381 25.703 11.2225C25.6776 11.0639 25.6386 10.815 25.6164 10.6693C25.583 10.4502 25.5675 10.4086 25.5271 10.4286C25.5003 10.4419 25.2044 10.4601 24.8697 10.4693C24.1707 10.4883 24.1939 10.4806 24.1659 10.702C23.9784 12.1853 22.6627 13.2699 21.1569 13.1825C19.8215 13.1049 18.6989 12.0764 18.5124 10.7597C18.4682 10.4478 18.6032 10.4698 16.7346 10.4698C14.8693 10.4698 15.0077 10.4469 14.9582 10.7632C14.4638 13.9187 9.89316 13.9872 9.33291 10.8475C9.26285 10.4552 9.35117 10.4881 8.31779 10.4698C7.82095 10.461 7.3893 10.4415 7.35847 10.4263C7.31099 10.4031 7.30242 10.4147 7.30242 10.5026ZM16.896 12.8104C17.1548 13.0754 17.2466 13.1087 17.6512 13.0841C18.0347 13.0608 18.0835 13.0858 17.9605 13.2421C17.7675 13.4876 17.3218 13.4679 16.8771 13.1942C16.6796 13.0726 16.6422 13.0726 16.4624 13.1926C16.0284 13.4822 15.4984 13.4913 15.3414 13.212C15.2793 13.1015 15.3378 13.0533 15.5016 13.0802C15.9854 13.1596 16.139 13.1161 16.4064 12.8243C16.6173 12.594 16.6828 12.5921 16.896 12.8104Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_745_54">
|
||||
<rect width="157" height="27" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
@@ -13,10 +13,10 @@ body {
|
||||
|
||||
:root {
|
||||
--primary-main: #5b5c9d;
|
||||
--text-primary: #637381;
|
||||
--text-primary: #1f1f1f;
|
||||
--selection-color: #f5f5f5;
|
||||
--scroller-color: #90939980;
|
||||
--background-color: #ffffff;
|
||||
--scroller-color: #8c8c8c;
|
||||
--background-color: #f5f5f5;
|
||||
--background-color-alpha: rgba(24, 103, 192, 0.1);
|
||||
--border-radius: 8px;
|
||||
}
|
||||
@@ -27,8 +27,8 @@ body {
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: transparent;
|
||||
}
|
||||
*::-webkit-scrollbar-thumb {
|
||||
@@ -56,3 +56,7 @@ body {
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
[data-windrag] {
|
||||
app-region: drag;
|
||||
}
|
||||
|
||||
@@ -5,33 +5,44 @@
|
||||
overflow: hidden;
|
||||
|
||||
&__left {
|
||||
flex: 1 0 15%;
|
||||
flex: 1 0 200px;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
max-width: 225px;
|
||||
min-width: 125px;
|
||||
width: 100%;
|
||||
// max-width: 225px;
|
||||
// min-width: 225px;
|
||||
padding: 16px 0 8px;
|
||||
position: relative;
|
||||
// position: relative;
|
||||
flex-direction: column;
|
||||
align-self: stretch;
|
||||
box-sizing: border-box;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
overflow: hidden;
|
||||
background-color: var(--background-color-alpha);
|
||||
border-right: 1px solid var(--divider-color);
|
||||
// background-color: var(--background-color-alpha);
|
||||
|
||||
$maxLogo: 100px;
|
||||
// $maxLogo: 100px;
|
||||
|
||||
.the-logo {
|
||||
position: relative;
|
||||
flex: 0 1 $maxLogo;
|
||||
width: 100%;
|
||||
max-width: $maxLogo + 32px;
|
||||
max-height: $maxLogo;
|
||||
margin: 0 auto;
|
||||
padding: 0 16px;
|
||||
text-align: center;
|
||||
flex: 1 0 58px;
|
||||
// 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);
|
||||
// max-width: $maxLogo + 32px;
|
||||
// max-height: $maxLogo;
|
||||
// margin: 0 auto;
|
||||
// padding: 0 auto;
|
||||
// text-align: center;
|
||||
box-sizing: border-box;
|
||||
|
||||
img,
|
||||
@@ -39,17 +50,19 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
fill: var(--primary-main);
|
||||
// fill: var(--primary-main);
|
||||
|
||||
#bg {
|
||||
fill: var(--background-color);
|
||||
}
|
||||
// #bg {
|
||||
// fill: var(--background-color);
|
||||
// }
|
||||
}
|
||||
|
||||
.the-newbtn {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
bottom: 0px;
|
||||
top: 0px;
|
||||
border-radius: 8px;
|
||||
padding: 2px 4px;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
}
|
||||
@@ -58,6 +71,7 @@
|
||||
flex: 1 1 80%;
|
||||
overflow-y: auto;
|
||||
margin-bottom: 0px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.the-traffic {
|
||||
@@ -65,21 +79,22 @@
|
||||
|
||||
> div {
|
||||
margin: 0 auto;
|
||||
padding: 0px 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__right {
|
||||
position: relative;
|
||||
flex: 1 1 75%;
|
||||
flex: 1 1 100%;
|
||||
height: 100%;
|
||||
background-color: var(--background-color-alpha);
|
||||
// background-color: var(--background-color-alpha);
|
||||
|
||||
.the-bar {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 8px;
|
||||
height: 36px;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
@@ -100,15 +115,16 @@
|
||||
.windows,
|
||||
.unknown {
|
||||
&.layout {
|
||||
$maxLogo: 115px;
|
||||
.layout__left {
|
||||
padding-top: 24px;
|
||||
}
|
||||
|
||||
.layout__left .the-logo {
|
||||
flex: 0 1 $maxLogo;
|
||||
max-width: $maxLogo + 32px;
|
||||
max-height: $maxLogo;
|
||||
flex: 1 0 58px;
|
||||
}
|
||||
|
||||
.layout__right .the-content {
|
||||
top: 30px;
|
||||
top: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,7 +135,7 @@
|
||||
padding-top: 24px;
|
||||
}
|
||||
.layout__right .the-content {
|
||||
top: 20px;
|
||||
top: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,23 +3,24 @@
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> header {
|
||||
flex: 0 0 58px;
|
||||
width: 100%;
|
||||
// max-width: 850px;
|
||||
margin: 0 auto;
|
||||
padding-right: 8px;
|
||||
padding: 0 20px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
|
||||
.base-container {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
border-radius: var(--border-radius);
|
||||
// border-radius: 10px;
|
||||
// border-top-left-radius: var(--border-radius);
|
||||
|
||||
> section {
|
||||
position: relative;
|
||||
@@ -27,15 +28,23 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
padding: 5px 5px;
|
||||
padding: 10px 0;
|
||||
box-sizing: border-box;
|
||||
scrollbar-gutter: stable;
|
||||
|
||||
.base-content {
|
||||
width: 100%;
|
||||
// max-width: 850px;
|
||||
width: calc(100% - 10px * 2);
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
&.no-padding {
|
||||
> section {
|
||||
padding: 0;
|
||||
overflow: visible;
|
||||
.base-content {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { ReactNode, useState } from "react";
|
||||
import { ReactNode, useState, useEffect } from "react";
|
||||
import { Box, IconButton, Slide, Snackbar, Typography } from "@mui/material";
|
||||
import { Close, CheckCircleRounded, ErrorRounded } from "@mui/icons-material";
|
||||
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { appWindow } from "@tauri-apps/api/window";
|
||||
interface InnerProps {
|
||||
type: string;
|
||||
duration?: number;
|
||||
message: ReactNode;
|
||||
isDark?: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const NoticeInner = (props: InnerProps) => {
|
||||
const { type, message, duration = 1500, onClose } = props;
|
||||
const [visible, setVisible] = useState(true);
|
||||
|
||||
const [isDark, setIsDark] = useState(false);
|
||||
const { verge } = useVerge();
|
||||
const { theme_mode } = verge ?? {};
|
||||
const onBtnClose = () => {
|
||||
setVisible(false);
|
||||
onClose();
|
||||
@@ -22,6 +26,26 @@ const NoticeInner = (props: InnerProps) => {
|
||||
if (reason !== "clickaway") onBtnClose();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const themeMode = ["light", "dark", "system"].includes(theme_mode!)
|
||||
? theme_mode!
|
||||
: "light";
|
||||
|
||||
if (themeMode !== "system") {
|
||||
setIsDark(themeMode === "dark");
|
||||
return;
|
||||
}
|
||||
|
||||
appWindow.theme().then((m) => m && setIsDark(m === "dark"));
|
||||
const unlisten = appWindow.onThemeChanged((e) =>
|
||||
setIsDark(e.payload === "dark")
|
||||
);
|
||||
|
||||
return () => {
|
||||
unlisten.then((fn) => fn());
|
||||
};
|
||||
}, [theme_mode]);
|
||||
|
||||
const msgElement =
|
||||
type === "info" ? (
|
||||
message
|
||||
@@ -46,7 +70,13 @@ const NoticeInner = (props: InnerProps) => {
|
||||
autoHideDuration={duration}
|
||||
onClose={onAutoClose}
|
||||
message={msgElement}
|
||||
sx={{ maxWidth: 360 }}
|
||||
sx={{
|
||||
maxWidth: 360,
|
||||
".MuiSnackbarContent-root": {
|
||||
bgcolor: isDark ? "#50515C" : "#ffffff",
|
||||
color: isDark ? "#ffffff" : "#000000",
|
||||
},
|
||||
}}
|
||||
TransitionComponent={(p) => <Slide {...p} direction="left" />}
|
||||
transitionDuration={200}
|
||||
action={
|
||||
@@ -61,9 +91,9 @@ const NoticeInner = (props: InnerProps) => {
|
||||
interface NoticeInstance {
|
||||
(props: Omit<InnerProps, "onClose">): void;
|
||||
|
||||
info(message: ReactNode, duration?: number): void;
|
||||
error(message: ReactNode, duration?: number): void;
|
||||
success(message: ReactNode, duration?: number): void;
|
||||
info(message: ReactNode, duration?: number, isDark?: boolean): void;
|
||||
error(message: ReactNode, duration?: number, isDark?: boolean): void;
|
||||
success(message: ReactNode, duration?: number, isDark?: boolean): void;
|
||||
}
|
||||
|
||||
let parent: HTMLDivElement = null!;
|
||||
|
||||
@@ -8,10 +8,11 @@ interface Props {
|
||||
header?: React.ReactNode; // something behind title
|
||||
contentStyle?: React.CSSProperties;
|
||||
children?: ReactNode;
|
||||
full?: boolean;
|
||||
}
|
||||
|
||||
export const BasePage: React.FC<Props> = (props) => {
|
||||
const { title, header, contentStyle, children } = props;
|
||||
const { title, header, contentStyle, full, children } = props;
|
||||
const { theme } = useCustomTheme();
|
||||
|
||||
const isDark = theme.palette.mode === "dark";
|
||||
@@ -20,7 +21,10 @@ export const BasePage: React.FC<Props> = (props) => {
|
||||
<BaseErrorBoundary>
|
||||
<div className="base-page">
|
||||
<header data-windrag style={{ userSelect: "none" }}>
|
||||
<Typography variant="h4" component="h1" data-windrag>
|
||||
<Typography
|
||||
sx={{ fontSize: "20px", fontWeight: "700 " }}
|
||||
data-windrag
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
|
||||
@@ -28,14 +32,12 @@ export const BasePage: React.FC<Props> = (props) => {
|
||||
</header>
|
||||
|
||||
<div
|
||||
className="base-container"
|
||||
style={{ backgroundColor: isDark ? "#090909" : "#ffffff" }}
|
||||
className={full ? "base-container no-padding" : "base-container"}
|
||||
style={{ backgroundColor: isDark ? "#1e1f27" : "#ffffff" }}
|
||||
>
|
||||
<section
|
||||
style={{
|
||||
backgroundColor: isDark
|
||||
? alpha(theme.palette.primary.main, 0.1)
|
||||
: "",
|
||||
backgroundColor: isDark ? "#1e1f27" : "var(--background-color)",
|
||||
}}
|
||||
>
|
||||
<div className="base-content" style={contentStyle}>
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { styled } from "@mui/material/styles";
|
||||
import { default as MuiSwitch, SwitchProps } from "@mui/material/Switch";
|
||||
|
||||
export const Switch = styled((props: SwitchProps) => (
|
||||
<MuiSwitch
|
||||
focusVisibleClassName=".Mui-focusVisible"
|
||||
disableRipple
|
||||
{...props}
|
||||
/>
|
||||
))(({ theme }) => ({
|
||||
width: 42,
|
||||
height: 26,
|
||||
padding: 0,
|
||||
"& .MuiSwitch-switchBase": {
|
||||
padding: 0,
|
||||
margin: 2,
|
||||
transitionDuration: "300ms",
|
||||
"&.Mui-checked": {
|
||||
transform: "translateX(16px)",
|
||||
color: "#fff",
|
||||
"& + .MuiSwitch-track": {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
opacity: 1,
|
||||
border: 0,
|
||||
},
|
||||
"&.Mui-disabled + .MuiSwitch-track": {
|
||||
opacity: 0.5,
|
||||
},
|
||||
},
|
||||
"&.Mui-focusVisible .MuiSwitch-thumb": {
|
||||
color: "#33cf4d",
|
||||
border: "6px solid #fff",
|
||||
},
|
||||
"&.Mui-disabled .MuiSwitch-thumb": {
|
||||
color:
|
||||
theme.palette.mode === "light"
|
||||
? theme.palette.grey[100]
|
||||
: theme.palette.grey[600],
|
||||
},
|
||||
"&.Mui-disabled + .MuiSwitch-track": {
|
||||
opacity: theme.palette.mode === "light" ? 0.7 : 0.3,
|
||||
},
|
||||
},
|
||||
"& .MuiSwitch-thumb": {
|
||||
boxSizing: "border-box",
|
||||
width: 22,
|
||||
height: 22,
|
||||
},
|
||||
"& .MuiSwitch-track": {
|
||||
borderRadius: 26 / 2,
|
||||
backgroundColor: theme.palette.mode === "light" ? "#E9E9EA" : "#39393D",
|
||||
opacity: 1,
|
||||
transition: theme.transitions.create(["background-color"], {
|
||||
duration: 500,
|
||||
}),
|
||||
},
|
||||
}));
|
||||
@@ -4,3 +4,4 @@ export { BaseEmpty } from "./base-empty";
|
||||
export { BaseLoading } from "./base-loading";
|
||||
export { BaseErrorBoundary } from "./base-error-boundary";
|
||||
export { Notice } from "./base-notice";
|
||||
export { Switch } from "./base-switch";
|
||||
|
||||
@@ -58,7 +58,9 @@ export const ConnectionItem = (props: Props) => {
|
||||
|
||||
{!!metadata.process && <Tag>{metadata.process}</Tag>}
|
||||
|
||||
{chains?.length > 0 && <Tag>{chains[value.chains.length - 1]}</Tag>}
|
||||
{chains?.length > 0 && (
|
||||
<Tag>{[...chains].reverse().join(" / ")}</Tag>
|
||||
)}
|
||||
|
||||
<Tag>{dayjs(start).fromNow()}</Tag>
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import dayjs from "dayjs";
|
||||
import { useMemo, useState } from "react";
|
||||
import { DataGrid, GridColDef } from "@mui/x-data-grid";
|
||||
import {
|
||||
DataGrid,
|
||||
GridColDef,
|
||||
GridValueFormatterParams,
|
||||
} from "@mui/x-data-grid";
|
||||
import { truncateStr } from "@/utils/truncate-str";
|
||||
import parseTraffic from "@/utils/parse-traffic";
|
||||
|
||||
@@ -24,6 +28,8 @@ export const ConnectionTable = (props: Props) => {
|
||||
width: 88,
|
||||
align: "right",
|
||||
headerAlign: "right",
|
||||
valueFormatter: (params: GridValueFormatterParams<number>) =>
|
||||
parseTraffic(params.value).join(" "),
|
||||
},
|
||||
{
|
||||
field: "upload",
|
||||
@@ -31,6 +37,8 @@ export const ConnectionTable = (props: Props) => {
|
||||
width: 88,
|
||||
align: "right",
|
||||
headerAlign: "right",
|
||||
valueFormatter: (params: GridValueFormatterParams<number>) =>
|
||||
parseTraffic(params.value).join(" "),
|
||||
},
|
||||
{
|
||||
field: "dlSpeed",
|
||||
@@ -38,6 +46,8 @@ export const ConnectionTable = (props: Props) => {
|
||||
width: 88,
|
||||
align: "right",
|
||||
headerAlign: "right",
|
||||
valueFormatter: (params: GridValueFormatterParams<number>) =>
|
||||
parseTraffic(params.value).join(" ") + "/s",
|
||||
},
|
||||
{
|
||||
field: "ulSpeed",
|
||||
@@ -45,10 +55,12 @@ export const ConnectionTable = (props: Props) => {
|
||||
width: 88,
|
||||
align: "right",
|
||||
headerAlign: "right",
|
||||
valueFormatter: (params: GridValueFormatterParams<number>) =>
|
||||
parseTraffic(params.value).join(" ") + "/s",
|
||||
},
|
||||
{ field: "chains", headerName: "Chains", flex: 360, minWidth: 360 },
|
||||
{ field: "rule", headerName: "Rule", flex: 300, minWidth: 250 },
|
||||
{ field: "process", headerName: "Process", flex: 480, minWidth: 480 },
|
||||
{ field: "process", headerName: "Process", flex: 240, minWidth: 120 },
|
||||
{
|
||||
field: "time",
|
||||
headerName: "Time",
|
||||
@@ -56,6 +68,11 @@ export const ConnectionTable = (props: Props) => {
|
||||
minWidth: 100,
|
||||
align: "right",
|
||||
headerAlign: "right",
|
||||
sortComparator: (v1, v2) => {
|
||||
return new Date(v2).getTime() - new Date(v1).getTime();
|
||||
},
|
||||
valueFormatter: (params: GridValueFormatterParams<string>) =>
|
||||
dayjs(params.value).fromNow(),
|
||||
},
|
||||
{ field: "source", headerName: "Source", flex: 200, minWidth: 130 },
|
||||
{
|
||||
@@ -72,20 +89,19 @@ export const ConnectionTable = (props: Props) => {
|
||||
const { metadata, rulePayload } = each;
|
||||
const chains = [...each.chains].reverse().join(" / ");
|
||||
const rule = rulePayload ? `${each.rule}(${rulePayload})` : each.rule;
|
||||
|
||||
return {
|
||||
id: each.id,
|
||||
host: metadata.host
|
||||
? `${metadata.host}:${metadata.destinationPort}`
|
||||
: `${metadata.destinationIP}:${metadata.destinationPort}`,
|
||||
download: parseTraffic(each.download).join(" "),
|
||||
upload: parseTraffic(each.upload).join(" "),
|
||||
dlSpeed: parseTraffic(each.curDownload).join(" ") + "/s",
|
||||
ulSpeed: parseTraffic(each.curUpload).join(" ") + "/s",
|
||||
download: each.download,
|
||||
upload: each.upload,
|
||||
dlSpeed: each.curDownload,
|
||||
ulSpeed: each.curUpload,
|
||||
chains,
|
||||
rule,
|
||||
process: truncateStr(metadata.process || metadata.processPath),
|
||||
time: dayjs(each.start).fromNow(),
|
||||
time: each.start,
|
||||
source: `${metadata.sourceIP}:${metadata.sourcePort}`,
|
||||
destinationIP: metadata.destinationIP,
|
||||
type: `${metadata.type}(${metadata.network})`,
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Button } from "@mui/material";
|
||||
import { Button, ButtonGroup } from "@mui/material";
|
||||
import { appWindow } from "@tauri-apps/api/window";
|
||||
import {
|
||||
CloseRounded,
|
||||
CropSquareRounded,
|
||||
FilterNoneRounded,
|
||||
HorizontalRuleRounded,
|
||||
PushPinOutlined,
|
||||
PushPinRounded,
|
||||
} from "@mui/icons-material";
|
||||
import { useState } from "react";
|
||||
|
||||
@@ -12,12 +14,37 @@ export const LayoutControl = () => {
|
||||
const minWidth = 40;
|
||||
|
||||
const [isMaximized, setIsMaximized] = useState(false);
|
||||
const [isPined, setIsPined] = useState(false);
|
||||
appWindow.isMaximized().then((isMaximized) => {
|
||||
setIsMaximized(() => isMaximized);
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<ButtonGroup
|
||||
variant="text"
|
||||
sx={{
|
||||
height: "100%",
|
||||
".MuiButtonGroup-grouped": {
|
||||
borderRadius: "0px",
|
||||
borderRight: "0px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
sx={{ minWidth, svg: { transform: "scale(0.9)" } }}
|
||||
onClick={() => {
|
||||
appWindow.setAlwaysOnTop(!isPined);
|
||||
setIsPined((isPined) => !isPined);
|
||||
}}
|
||||
>
|
||||
{isPined ? (
|
||||
<PushPinRounded fontSize="small" />
|
||||
) : (
|
||||
<PushPinOutlined fontSize="small" />
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
size="small"
|
||||
sx={{ minWidth, svg: { transform: "scale(0.9)" } }}
|
||||
@@ -48,11 +75,15 @@ export const LayoutControl = () => {
|
||||
|
||||
<Button
|
||||
size="small"
|
||||
sx={{ minWidth, svg: { transform: "scale(1.05)" } }}
|
||||
sx={{
|
||||
minWidth,
|
||||
svg: { transform: "scale(1.05)" },
|
||||
":hover": { bgcolor: "#ff000090" },
|
||||
}}
|
||||
onClick={() => appWindow.close()}
|
||||
>
|
||||
<CloseRounded fontSize="small" />
|
||||
</Button>
|
||||
</>
|
||||
</ButtonGroup>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,32 +1,46 @@
|
||||
import { alpha, ListItem, ListItemButton, ListItemText } from "@mui/material";
|
||||
import {
|
||||
alpha,
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
ListItemText,
|
||||
ListItemIcon,
|
||||
} from "@mui/material";
|
||||
import { useMatch, useResolvedPath, useNavigate } from "react-router-dom";
|
||||
import type { LinkProps } from "react-router-dom";
|
||||
|
||||
export const LayoutItem = (props: LinkProps) => {
|
||||
const { to, children } = props;
|
||||
interface Props {
|
||||
to: string;
|
||||
children: string;
|
||||
icon: React.ReactNode;
|
||||
}
|
||||
export const LayoutItem = (props: Props) => {
|
||||
const { to, children, icon } = props;
|
||||
|
||||
const resolved = useResolvedPath(to);
|
||||
const match = useMatch({ path: resolved.pathname, end: true });
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<ListItem sx={{ py: 0.5, maxWidth: 250, mx: "auto", padding: "1px 0px" }}>
|
||||
<ListItem sx={{ py: 0.5, maxWidth: 250, mx: "auto", padding: "4px 0px" }}>
|
||||
<ListItemButton
|
||||
selected={!!match}
|
||||
sx={[
|
||||
{
|
||||
borderRadius: 3,
|
||||
marginLeft: 1,
|
||||
marginRight: 1,
|
||||
textAlign: "center",
|
||||
"& .MuiListItemText-primary": { color: "text.secondary" },
|
||||
borderRadius: 2,
|
||||
marginLeft: 1.25,
|
||||
paddingLeft: 1.5,
|
||||
paddingRight: 1,
|
||||
marginRight: 1.25,
|
||||
"& .MuiListItemText-primary": {
|
||||
color: "text.primary",
|
||||
fontWeight: "700",
|
||||
},
|
||||
},
|
||||
({ palette: { mode, primary } }) => {
|
||||
const bgcolor =
|
||||
mode === "light"
|
||||
? alpha(primary.main, 0.15)
|
||||
: alpha(primary.main, 0.35);
|
||||
const color = mode === "light" ? primary.main : primary.light;
|
||||
const color = mode === "light" ? "#1f1f1f" : "#ffffff";
|
||||
|
||||
return {
|
||||
"&.Mui-selected": { bgcolor },
|
||||
@@ -37,7 +51,11 @@ export const LayoutItem = (props: LinkProps) => {
|
||||
]}
|
||||
onClick={() => navigate(to)}
|
||||
>
|
||||
<ListItemText primary={children} />
|
||||
<ListItemIcon>{icon}</ListItemIcon>
|
||||
<ListItemText
|
||||
sx={{ textAlign: "center", marginLeft: "-35px" }}
|
||||
primary={children}
|
||||
/>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
);
|
||||
|
||||
@@ -75,7 +75,7 @@ export const LayoutTraffic = () => {
|
||||
};
|
||||
const valStyle: any = {
|
||||
component: "span",
|
||||
color: "primary",
|
||||
// color: "primary",
|
||||
textAlign: "center",
|
||||
sx: { flex: "1 1 56px", userSelect: "none" },
|
||||
};
|
||||
@@ -88,11 +88,7 @@ export const LayoutTraffic = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
width="110px"
|
||||
position="relative"
|
||||
onClick={trafficRef.current?.toggleStyle}
|
||||
>
|
||||
<Box position="relative" onClick={trafficRef.current?.toggleStyle}>
|
||||
{trafficGraph && pageVisible && (
|
||||
<div style={{ width: "100%", height: 60, marginBottom: 6 }}>
|
||||
<TrafficGraph ref={trafficRef} />
|
||||
@@ -103,9 +99,11 @@ export const LayoutTraffic = () => {
|
||||
<Box display="flex" alignItems="center" whiteSpace="nowrap">
|
||||
<ArrowUpward
|
||||
{...iconStyle}
|
||||
color={+up > 0 ? "primary" : "disabled"}
|
||||
color={+up > 0 ? "secondary" : "disabled"}
|
||||
/>
|
||||
<Typography {...valStyle}>{up}</Typography>
|
||||
<Typography {...valStyle} color="secondary">
|
||||
{up}
|
||||
</Typography>
|
||||
<Typography {...unitStyle}>{upUnit}/s</Typography>
|
||||
</Box>
|
||||
|
||||
@@ -114,7 +112,9 @@ export const LayoutTraffic = () => {
|
||||
{...iconStyle}
|
||||
color={+down > 0 ? "primary" : "disabled"}
|
||||
/>
|
||||
<Typography {...valStyle}>{down}</Typography>
|
||||
<Typography {...valStyle} color="primary">
|
||||
{down}
|
||||
</Typography>
|
||||
<Typography {...unitStyle}>{downUnit}/s</Typography>
|
||||
</Box>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { useRecoilState } from "recoil";
|
||||
import { alpha, createTheme, Theme } from "@mui/material";
|
||||
import { alpha, createTheme, Shadows, Theme } from "@mui/material";
|
||||
import { appWindow } from "@tauri-apps/api/window";
|
||||
import { atomThemeMode } from "@/services/states";
|
||||
import { defaultTheme, defaultDarkTheme } from "@/pages/_theme";
|
||||
@@ -59,6 +59,7 @@ export const useCustomTheme = () => {
|
||||
paper: dt.background_color,
|
||||
},
|
||||
},
|
||||
shadows: Array(25).fill("none") as Shadows,
|
||||
typography: {
|
||||
// todo
|
||||
fontFamily: setting.font_family
|
||||
@@ -87,11 +88,14 @@ export const useCustomTheme = () => {
|
||||
}
|
||||
|
||||
// css
|
||||
const backgroundColor = mode === "light" ? "#ffffff" : "#0B121C";
|
||||
const backgroundColor = mode === "light" ? "#f0f0f0" : "#2e303d";
|
||||
const selectColor = mode === "light" ? "#f5f5f5" : "#d5d5d5";
|
||||
const scrollColor = mode === "light" ? "#90939980" : "#54545480";
|
||||
const dividerColor =
|
||||
mode === "light" ? "rgba(0, 0, 0, 0.06)" : "rgba(255, 255, 255, 0.06)";
|
||||
|
||||
const rootEle = document.documentElement;
|
||||
rootEle.style.setProperty("--divider-color", dividerColor);
|
||||
rootEle.style.setProperty("--background-color", backgroundColor);
|
||||
rootEle.style.setProperty("--selection-color", selectColor);
|
||||
rootEle.style.setProperty("--scroller-color", scrollColor);
|
||||
|
||||
@@ -2,41 +2,54 @@ import { alpha, Box, styled } from "@mui/material";
|
||||
|
||||
export const ProfileBox = styled(Box)(
|
||||
({ theme, "aria-selected": selected }) => {
|
||||
const { mode, primary, text, grey, background } = theme.palette;
|
||||
const { mode, primary, text } = theme.palette;
|
||||
const key = `${mode}-${!!selected}`;
|
||||
|
||||
const backgroundColor = {
|
||||
"light-true": alpha(primary.main, 0.2),
|
||||
"light-false": alpha(background.paper, 0.75),
|
||||
"dark-true": alpha(primary.main, 0.45),
|
||||
"dark-false": alpha(grey[700], 0.45),
|
||||
}[key]!;
|
||||
const backgroundColor = mode === "light" ? "#ffffff" : "#282A36";
|
||||
|
||||
const color = {
|
||||
"light-true": text.secondary,
|
||||
"light-false": text.secondary,
|
||||
"dark-true": alpha(text.secondary, 0.85),
|
||||
"dark-true": alpha(text.secondary, 0.65),
|
||||
"dark-false": alpha(text.secondary, 0.65),
|
||||
}[key]!;
|
||||
|
||||
const h2color = {
|
||||
"light-true": primary.main,
|
||||
"light-false": text.primary,
|
||||
"dark-true": primary.light,
|
||||
"dark-true": primary.main,
|
||||
"dark-false": text.primary,
|
||||
}[key]!;
|
||||
|
||||
const borderSelect = {
|
||||
"light-true": {
|
||||
borderLeft: `3px solid ${primary.main}`,
|
||||
width: `calc(100% + 3px)`,
|
||||
marginLeft: `-3px`,
|
||||
},
|
||||
"light-false": {
|
||||
width: "100%",
|
||||
},
|
||||
"dark-true": {
|
||||
borderLeft: `3px solid ${primary.main}`,
|
||||
width: `calc(100% + 3px)`,
|
||||
marginLeft: `-3px`,
|
||||
},
|
||||
"dark-false": {
|
||||
width: "100%",
|
||||
},
|
||||
}[key];
|
||||
|
||||
return {
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
display: "block",
|
||||
cursor: "pointer",
|
||||
textAlign: "left",
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
boxShadow: theme.shadows[2],
|
||||
padding: "8px 16px",
|
||||
boxSizing: "border-box",
|
||||
backgroundColor,
|
||||
...borderSelect,
|
||||
borderRadius: "8px",
|
||||
color,
|
||||
"& h2": { color: h2color },
|
||||
};
|
||||
|
||||
@@ -230,7 +230,14 @@ export const ProfileItem = (props: Props) => {
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
>
|
||||
<DragIndicator sx={{ cursor: "move", marginLeft: "-6px" }} />
|
||||
<DragIndicator
|
||||
sx={[
|
||||
{ cursor: "move", marginLeft: "-6px" },
|
||||
({ palette: { text } }) => {
|
||||
return { color: text.primary };
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Typography
|
||||
@@ -303,11 +310,7 @@ export const ProfileItem = (props: Props) => {
|
||||
<span title="Updated Time">{parseExpire(updated)}</span>
|
||||
</Box>
|
||||
)}
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={progress}
|
||||
color="inherit"
|
||||
/>
|
||||
<LinearProgress variant="determinate" value={progress} />
|
||||
</ProfileBox>
|
||||
|
||||
<Menu
|
||||
|
||||
@@ -15,12 +15,11 @@ import {
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
Select,
|
||||
Switch,
|
||||
styled,
|
||||
TextField,
|
||||
} from "@mui/material";
|
||||
import { createProfile, patchProfile } from "@/services/cmds";
|
||||
import { BaseDialog, Notice } from "@/components/base";
|
||||
import { BaseDialog, Notice, Switch } from "@/components/base";
|
||||
import { version } from "@root/package.json";
|
||||
import { FileInput } from "./file-input";
|
||||
|
||||
@@ -48,7 +47,7 @@ export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
|
||||
const { control, watch, register, ...formIns } = useForm<IProfileItem>({
|
||||
defaultValues: {
|
||||
type: "remote",
|
||||
name: "Remote File",
|
||||
name: "",
|
||||
desc: "",
|
||||
url: "",
|
||||
option: {
|
||||
@@ -209,7 +208,7 @@ export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
|
||||
<TextField
|
||||
{...text}
|
||||
{...field}
|
||||
placeholder={`clash-verge-rev`}
|
||||
placeholder={`clash-verge/v${version}`}
|
||||
label="User Agent"
|
||||
/>
|
||||
)}
|
||||
@@ -269,6 +268,17 @@ export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
|
||||
</StyledBox>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="option.danger_accept_invalid_certs"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<StyledBox>
|
||||
<InputLabel>{t("Accept Invalid Certs (Danger)")}</InputLabel>
|
||||
<Switch checked={field.value} {...field} color="primary" />
|
||||
</StyledBox>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</BaseDialog>
|
||||
|
||||
@@ -7,26 +7,57 @@ import {
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
styled,
|
||||
Box,
|
||||
alpha,
|
||||
Typography,
|
||||
Divider,
|
||||
LinearProgress,
|
||||
keyframes,
|
||||
} from "@mui/material";
|
||||
import { RefreshRounded } from "@mui/icons-material";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useLockFn } from "ahooks";
|
||||
import { getProviders, providerUpdate } from "@/services/api";
|
||||
import { getProxyProviders, proxyProviderUpdate } from "@/services/api";
|
||||
import { BaseDialog } from "../base";
|
||||
import parseTraffic from "@/utils/parse-traffic";
|
||||
|
||||
const round = keyframes`
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
`;
|
||||
|
||||
export const ProviderButton = () => {
|
||||
const { t } = useTranslation();
|
||||
const { data } = useSWR("getProviders", getProviders);
|
||||
const { data } = useSWR("getProxyProviders", getProxyProviders);
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const hasProvider = Object.keys(data || {}).length > 0;
|
||||
const [updating, setUpdating] = useState(
|
||||
Object.keys(data || {}).map(() => false)
|
||||
);
|
||||
|
||||
const handleUpdate = useLockFn(async (key: string) => {
|
||||
await providerUpdate(key);
|
||||
await mutate("getProxies");
|
||||
await mutate("getProviders");
|
||||
});
|
||||
const setUpdatingAt = (status: boolean, index: number) => {
|
||||
setUpdating((prev) => {
|
||||
const next = [...prev];
|
||||
next[index] = status;
|
||||
return next;
|
||||
});
|
||||
};
|
||||
const handleUpdate = async (key: string, index: number) => {
|
||||
setUpdatingAt(true, index);
|
||||
proxyProviderUpdate(key)
|
||||
.then(async () => {
|
||||
setUpdatingAt(false, index);
|
||||
await mutate("getProxies");
|
||||
await mutate("getProxyProviders");
|
||||
})
|
||||
.catch(async () => {
|
||||
setUpdatingAt(false, index);
|
||||
await mutate("getProxies");
|
||||
await mutate("getProxyProviders");
|
||||
});
|
||||
};
|
||||
|
||||
if (!hasProvider) return null;
|
||||
|
||||
@@ -43,7 +74,24 @@ export const ProviderButton = () => {
|
||||
|
||||
<BaseDialog
|
||||
open={open}
|
||||
title={t("Proxy Provider")}
|
||||
title={
|
||||
<Box display="flex" justifyContent="space-between" gap={1}>
|
||||
<Typography variant="h6">{t("Proxy Provider")}</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
size="small"
|
||||
onClick={async () => {
|
||||
Object.entries(data || {}).forEach(
|
||||
async ([key, item], index) => {
|
||||
await handleUpdate(key, index);
|
||||
}
|
||||
);
|
||||
}}
|
||||
>
|
||||
{t("Update All")}
|
||||
</Button>
|
||||
</Box>
|
||||
}
|
||||
contentSx={{ width: 400 }}
|
||||
disableOk
|
||||
cancelBtn={t("Cancel")}
|
||||
@@ -51,32 +99,90 @@ export const ProviderButton = () => {
|
||||
onCancel={() => setOpen(false)}
|
||||
>
|
||||
<List sx={{ py: 0, minHeight: 250 }}>
|
||||
{Object.entries(data || {}).map(([key, item]) => {
|
||||
{Object.entries(data || {}).map(([key, item], index) => {
|
||||
const time = dayjs(item.updatedAt);
|
||||
const sub = item.subscriptionInfo;
|
||||
const hasSubInfo = !!sub;
|
||||
const upload = sub?.Upload || 0;
|
||||
const download = sub?.Download || 0;
|
||||
const total = sub?.Total || 0;
|
||||
const expire = sub?.Expire || 0;
|
||||
const progress = Math.round(
|
||||
((download + upload) * 100) / (total + 0.1)
|
||||
);
|
||||
return (
|
||||
<ListItem sx={{ p: 0 }} key={key}>
|
||||
<ListItemText
|
||||
primary={key}
|
||||
secondary={
|
||||
<>
|
||||
<span style={{ marginRight: "4em" }}>
|
||||
Type: {item.vehicleType}
|
||||
</span>
|
||||
<span title={time.format("YYYY-MM-DD HH:mm:ss")}>
|
||||
Updated: {time.fromNow()}
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<IconButton
|
||||
size="small"
|
||||
color="inherit"
|
||||
title="Update Provider"
|
||||
onClick={() => handleUpdate(key)}
|
||||
<>
|
||||
<ListItem
|
||||
sx={{
|
||||
p: 0,
|
||||
borderRadius: "10px",
|
||||
border: "solid 2px var(--divider-color)",
|
||||
mb: 1,
|
||||
}}
|
||||
key={key}
|
||||
>
|
||||
<RefreshRounded />
|
||||
</IconButton>
|
||||
</ListItem>
|
||||
<ListItemText
|
||||
sx={{ px: 1 }}
|
||||
primary={
|
||||
<>
|
||||
<Typography
|
||||
variant="h6"
|
||||
component="span"
|
||||
noWrap
|
||||
title={key}
|
||||
>
|
||||
{key}
|
||||
</Typography>
|
||||
<TypeBox component="span" sx={{ marginLeft: "8px" }}>
|
||||
{item.proxies.length}
|
||||
</TypeBox>
|
||||
</>
|
||||
}
|
||||
secondary={
|
||||
<>
|
||||
<StyledTypeBox component="span">
|
||||
{item.vehicleType}
|
||||
</StyledTypeBox>
|
||||
<StyledTypeBox component="span">
|
||||
{t("Update At")} {time.fromNow()}
|
||||
</StyledTypeBox>
|
||||
{hasSubInfo && (
|
||||
<>
|
||||
<Box sx={{ ...boxStyle, fontSize: 14 }}>
|
||||
<span title="Used / Total">
|
||||
{parseTraffic(upload + download)} /{" "}
|
||||
{parseTraffic(total)}
|
||||
</span>
|
||||
<span title="Expire Time">
|
||||
{parseExpire(expire)}
|
||||
</span>
|
||||
</Box>
|
||||
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={progress}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Divider orientation="vertical" flexItem />
|
||||
<IconButton
|
||||
size="small"
|
||||
color="inherit"
|
||||
title="Update Provider"
|
||||
onClick={() => handleUpdate(key, index)}
|
||||
sx={{
|
||||
animation: updating[index]
|
||||
? `1s linear infinite ${round}`
|
||||
: "none",
|
||||
}}
|
||||
>
|
||||
<RefreshRounded />
|
||||
</IconButton>
|
||||
</ListItem>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
@@ -84,3 +190,38 @@ export const ProviderButton = () => {
|
||||
</>
|
||||
);
|
||||
};
|
||||
const TypeBox = styled(Box)(({ theme }) => ({
|
||||
display: "inline-block",
|
||||
border: "1px solid #ccc",
|
||||
borderColor: alpha(theme.palette.secondary.main, 0.5),
|
||||
color: alpha(theme.palette.secondary.main, 0.8),
|
||||
borderRadius: 4,
|
||||
fontSize: 10,
|
||||
marginRight: "4px",
|
||||
padding: "0 2px",
|
||||
lineHeight: 1.25,
|
||||
}));
|
||||
|
||||
const StyledTypeBox = styled(Box)(({ theme }) => ({
|
||||
display: "inline-block",
|
||||
border: "1px solid #ccc",
|
||||
borderColor: alpha(theme.palette.primary.main, 0.5),
|
||||
color: alpha(theme.palette.primary.main, 0.8),
|
||||
borderRadius: 4,
|
||||
fontSize: 10,
|
||||
marginRight: "4px",
|
||||
padding: "0 2px",
|
||||
lineHeight: 1.25,
|
||||
}));
|
||||
|
||||
const boxStyle = {
|
||||
height: 26,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
};
|
||||
|
||||
function parseExpire(expire?: number) {
|
||||
if (!expire) return "-";
|
||||
return dayjs(expire * 1000).format("YYYY-MM-DD");
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ export const ProxyGroups = (props: Props) => {
|
||||
|
||||
const { verge } = useVerge();
|
||||
const { current, patchCurrent } = useProfiles();
|
||||
const timeout = verge?.default_latency_timeout || 10000;
|
||||
|
||||
const virtuosoRef = useRef<VirtuosoHandle>(null);
|
||||
|
||||
@@ -83,7 +84,7 @@ export const ProxyGroups = (props: Props) => {
|
||||
}
|
||||
|
||||
const names = proxies.filter((p) => !p!.provider).map((p) => p!.name);
|
||||
await delayManager.checkListDelay(names, groupName);
|
||||
await delayManager.checkListDelay(names, groupName, timeout);
|
||||
|
||||
onProxies();
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import { CheckCircleOutlineRounded } from "@mui/icons-material";
|
||||
import { alpha, Box, ListItemButton, styled, Typography } from "@mui/material";
|
||||
import { BaseLoading } from "@/components/base";
|
||||
import delayManager from "@/services/delay";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
|
||||
interface Props {
|
||||
groupName: string;
|
||||
@@ -20,6 +21,8 @@ export const ProxyItemMini = (props: Props) => {
|
||||
// -1/<=0 为 不显示
|
||||
// -2 为 loading
|
||||
const [delay, setDelay] = useState(-1);
|
||||
const { verge } = useVerge();
|
||||
const timeout = verge?.default_latency_timeout || 10000;
|
||||
|
||||
useEffect(() => {
|
||||
delayManager.setListener(proxy.name, groupName, setDelay);
|
||||
@@ -36,7 +39,7 @@ export const ProxyItemMini = (props: Props) => {
|
||||
|
||||
const onDelay = useLockFn(async () => {
|
||||
setDelay(-2);
|
||||
setDelay(await delayManager.checkDelay(proxy.name, groupName));
|
||||
setDelay(await delayManager.checkDelay(proxy.name, groupName, timeout));
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -47,30 +50,32 @@ export const ProxyItemMini = (props: Props) => {
|
||||
sx={[
|
||||
{
|
||||
height: 56,
|
||||
borderRadius: 1,
|
||||
borderRadius: 1.5,
|
||||
pl: 1.5,
|
||||
pr: 1,
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
},
|
||||
({ palette: { mode, primary } }) => {
|
||||
const bgcolor =
|
||||
mode === "light"
|
||||
? alpha(primary.main, 0.15)
|
||||
: alpha(primary.main, 0.35);
|
||||
const bgcolor = mode === "light" ? "#ffffff" : "#24252f";
|
||||
const color = mode === "light" ? primary.main : primary.light;
|
||||
const showDelay = delay > 0;
|
||||
|
||||
const shadowColor =
|
||||
mode === "light" ? "rgba(0,0,0,0.04)" : "rgba(255,255,255,0.08)";
|
||||
const selectColor = mode === "light" ? primary.main : primary.light;
|
||||
|
||||
return {
|
||||
"&:hover .the-check": { display: !showDelay ? "block" : "none" },
|
||||
"&:hover .the-delay": { display: showDelay ? "block" : "none" },
|
||||
"&:hover .the-icon": { display: "none" },
|
||||
"&.Mui-selected": { bgcolor, boxShadow: `0 0 0 1px ${bgcolor}` },
|
||||
"&.Mui-selected .MuiListItemText-secondary": { color },
|
||||
boxShadow: `0 0 0 1px ${shadowColor}`,
|
||||
"&.Mui-selected": {
|
||||
width: `calc(100% + 3px)`,
|
||||
marginLeft: `-3px`,
|
||||
borderLeft: `3px solid ${selectColor}`,
|
||||
bgcolor:
|
||||
mode === "light"
|
||||
? alpha(primary.main, 0.15)
|
||||
: alpha(primary.main, 0.35),
|
||||
},
|
||||
backgroundColor: bgcolor,
|
||||
};
|
||||
},
|
||||
]}
|
||||
@@ -79,7 +84,7 @@ export const ProxyItemMini = (props: Props) => {
|
||||
<Typography
|
||||
variant="body2"
|
||||
component="div"
|
||||
color="text.secondary"
|
||||
color="text.primary"
|
||||
sx={{
|
||||
display: "block",
|
||||
textOverflow: "ellipsis",
|
||||
@@ -92,12 +97,38 @@ export const ProxyItemMini = (props: Props) => {
|
||||
</Typography>
|
||||
|
||||
{showType && (
|
||||
<Box sx={{ display: "flex", flexWrap: "nowrap", flex: "none" }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexWrap: "nowrap",
|
||||
flex: "none",
|
||||
marginTop: "4px",
|
||||
}}
|
||||
>
|
||||
{proxy.now && (
|
||||
<Typography
|
||||
variant="body2"
|
||||
component="div"
|
||||
color="text.secondary"
|
||||
sx={{
|
||||
display: "block",
|
||||
textOverflow: "ellipsis",
|
||||
wordBreak: "break-all",
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
marginRight: "8px",
|
||||
}}
|
||||
>
|
||||
{proxy.now}
|
||||
</Typography>
|
||||
)}
|
||||
{!!proxy.provider && (
|
||||
<TypeBox component="span">{proxy.provider}</TypeBox>
|
||||
)}
|
||||
<TypeBox component="span">{proxy.type}</TypeBox>
|
||||
{proxy.udp && <TypeBox component="span">UDP</TypeBox>}
|
||||
{proxy.xudp && <TypeBox component="span">XUDP</TypeBox>}
|
||||
{proxy.tfo && <TypeBox component="span">TFO</TypeBox>}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
@@ -137,14 +168,14 @@ export const ProxyItemMini = (props: Props) => {
|
||||
e.stopPropagation();
|
||||
onDelay();
|
||||
}}
|
||||
color={delayManager.formatDelayColor(delay)}
|
||||
color={delayManager.formatDelayColor(delay, timeout)}
|
||||
sx={({ palette }) =>
|
||||
!proxy.provider
|
||||
? { ":hover": { bgcolor: alpha(palette.primary.main, 0.15) } }
|
||||
: {}
|
||||
}
|
||||
>
|
||||
{delayManager.formatDelay(delay)}
|
||||
{delayManager.formatDelay(delay, timeout)}
|
||||
</Widget>
|
||||
)}
|
||||
|
||||
@@ -161,8 +192,8 @@ export const ProxyItemMini = (props: Props) => {
|
||||
};
|
||||
|
||||
const Widget = styled(Box)(({ theme: { typography } }) => ({
|
||||
padding: "3px 6px",
|
||||
fontSize: 14,
|
||||
padding: "2px 4px",
|
||||
fontSize: 12,
|
||||
fontFamily: typography.fontFamily,
|
||||
borderRadius: "4px",
|
||||
}));
|
||||
@@ -170,12 +201,13 @@ const Widget = styled(Box)(({ theme: { typography } }) => ({
|
||||
const TypeBox = styled(Box)(({ theme: { palette, typography } }) => ({
|
||||
display: "inline-block",
|
||||
border: "1px solid #ccc",
|
||||
borderColor: alpha(palette.text.secondary, 0.36),
|
||||
color: alpha(palette.text.secondary, 0.42),
|
||||
borderColor: palette.mode === "light" ? "#d9d9d9" : "#424242",
|
||||
color: palette.mode === "light" ? "#8c8c8c" : "#ffffff",
|
||||
borderRadius: 4,
|
||||
fontSize: 10,
|
||||
fontFamily: typography.fontFamily,
|
||||
marginRight: "4px",
|
||||
padding: "0 2px",
|
||||
lineHeight: 1.25,
|
||||
marginTop: "auto",
|
||||
padding: "0 4px",
|
||||
lineHeight: 1.5,
|
||||
}));
|
||||
|
||||
@@ -11,9 +11,11 @@ import {
|
||||
styled,
|
||||
SxProps,
|
||||
Theme,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { BaseLoading } from "@/components/base";
|
||||
import delayManager from "@/services/delay";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
|
||||
interface Props {
|
||||
groupName: string;
|
||||
@@ -48,7 +50,8 @@ export const ProxyItem = (props: Props) => {
|
||||
// -1/<=0 为 不显示
|
||||
// -2 为 loading
|
||||
const [delay, setDelay] = useState(-1);
|
||||
|
||||
const { verge } = useVerge();
|
||||
const timeout = verge?.default_latency_timeout || 10000;
|
||||
useEffect(() => {
|
||||
delayManager.setListener(proxy.name, groupName, setDelay);
|
||||
|
||||
@@ -64,7 +67,7 @@ export const ProxyItem = (props: Props) => {
|
||||
|
||||
const onDelay = useLockFn(async () => {
|
||||
setDelay(-2);
|
||||
setDelay(await delayManager.checkDelay(proxy.name, groupName));
|
||||
setDelay(await delayManager.checkDelay(proxy.name, groupName, timeout));
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -76,19 +79,24 @@ export const ProxyItem = (props: Props) => {
|
||||
sx={[
|
||||
{ borderRadius: 1 },
|
||||
({ palette: { mode, primary } }) => {
|
||||
const bgcolor =
|
||||
mode === "light"
|
||||
? alpha(primary.main, 0.15)
|
||||
: alpha(primary.main, 0.35);
|
||||
const color = mode === "light" ? primary.main : primary.light;
|
||||
const bgcolor = mode === "light" ? "#ffffff" : "#24252f";
|
||||
const selectColor = mode === "light" ? primary.main : primary.light;
|
||||
const showDelay = delay > 0;
|
||||
|
||||
return {
|
||||
"&:hover .the-check": { display: !showDelay ? "block" : "none" },
|
||||
"&:hover .the-delay": { display: showDelay ? "block" : "none" },
|
||||
"&:hover .the-icon": { display: "none" },
|
||||
"&.Mui-selected": { bgcolor },
|
||||
"&.Mui-selected .MuiListItemText-secondary": { color },
|
||||
"&.Mui-selected": {
|
||||
borderLeft: `3px solid ${selectColor}`,
|
||||
bgcolor:
|
||||
mode === "light"
|
||||
? alpha(primary.main, 0.15)
|
||||
: alpha(primary.main, 0.35),
|
||||
},
|
||||
backgroundColor: bgcolor,
|
||||
marginBottom: "8px",
|
||||
height: "40px",
|
||||
};
|
||||
},
|
||||
]}
|
||||
@@ -97,13 +105,26 @@ export const ProxyItem = (props: Props) => {
|
||||
title={proxy.name}
|
||||
secondary={
|
||||
<>
|
||||
<span style={{ marginRight: 4 }}>{proxy.name}</span>
|
||||
|
||||
<span
|
||||
style={{
|
||||
marginRight: "8px",
|
||||
fontSize: "13px",
|
||||
color: "text.primary",
|
||||
fontWeight: "700",
|
||||
}}
|
||||
>
|
||||
{proxy.name}
|
||||
{showType && proxy.now && ` - ${proxy.now}`}
|
||||
</span>
|
||||
{showType && !!proxy.provider && (
|
||||
<TypeBox component="span">{proxy.provider}</TypeBox>
|
||||
)}
|
||||
{showType && <TypeBox component="span">{proxy.type}</TypeBox>}
|
||||
{showType && proxy.udp && <TypeBox component="span">UDP</TypeBox>}
|
||||
{showType && proxy.xudp && (
|
||||
<TypeBox component="span">XUDP</TypeBox>
|
||||
)}
|
||||
{showType && proxy.tfo && <TypeBox component="span">TFO</TypeBox>}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
@@ -145,14 +166,14 @@ export const ProxyItem = (props: Props) => {
|
||||
e.stopPropagation();
|
||||
onDelay();
|
||||
}}
|
||||
color={delayManager.formatDelayColor(delay)}
|
||||
color={delayManager.formatDelayColor(delay, timeout)}
|
||||
sx={({ palette }) =>
|
||||
!proxy.provider
|
||||
? { ":hover": { bgcolor: alpha(palette.primary.main, 0.15) } }
|
||||
: {}
|
||||
}
|
||||
>
|
||||
{delayManager.formatDelay(delay)}
|
||||
{delayManager.formatDelay(delay, timeout)}
|
||||
</Widget>
|
||||
)}
|
||||
|
||||
|
||||
@@ -16,6 +16,9 @@ import { ProxyHead } from "./proxy-head";
|
||||
import { ProxyItem } from "./proxy-item";
|
||||
import { ProxyItemMini } from "./proxy-item-mini";
|
||||
import type { IRenderItem } from "./use-render-list";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { useRecoilState } from "recoil";
|
||||
import { atomThemeMode } from "@/services/states";
|
||||
|
||||
interface RenderProps {
|
||||
item: IRenderItem;
|
||||
@@ -30,17 +33,56 @@ export const ProxyRender = (props: RenderProps) => {
|
||||
const { indent, item, onLocation, onCheckAll, onHeadState, onChangeProxy } =
|
||||
props;
|
||||
const { type, group, headState, proxy, proxyCol } = item;
|
||||
const { verge } = useVerge();
|
||||
const enable_group_icon = verge?.enable_group_icon ?? true;
|
||||
const [mode] = useRecoilState(atomThemeMode);
|
||||
console.log(mode);
|
||||
const isDark = mode === "light" ? false : true;
|
||||
const itembackgroundcolor = isDark ? "#282A36" : "#ffffff";
|
||||
|
||||
if (type === 0) {
|
||||
if (type === 0 && !group.hidden) {
|
||||
return (
|
||||
<ListItemButton
|
||||
dense
|
||||
style={{
|
||||
background: itembackgroundcolor,
|
||||
height: "64px",
|
||||
margin: "8px 8px",
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
onClick={() => onHeadState(group.name, { open: !headState?.open })}
|
||||
>
|
||||
{enable_group_icon &&
|
||||
group.icon &&
|
||||
group.icon.trim().startsWith("http") && (
|
||||
<img
|
||||
src={group.icon}
|
||||
height="32px"
|
||||
style={{ marginRight: "12px", borderRadius: "6px" }}
|
||||
/>
|
||||
)}
|
||||
{enable_group_icon &&
|
||||
group.icon &&
|
||||
group.icon.trim().startsWith("data") && (
|
||||
<img
|
||||
src={group.icon}
|
||||
height="32px"
|
||||
style={{ marginRight: "12px", borderRadius: "6px" }}
|
||||
/>
|
||||
)}
|
||||
{enable_group_icon &&
|
||||
group.icon &&
|
||||
group.icon.trim().startsWith("<svg") && (
|
||||
<img
|
||||
src={`data:image/svg+xml;base64,${btoa(group.icon)}`}
|
||||
height="32px"
|
||||
/>
|
||||
)}
|
||||
<ListItemText
|
||||
primary={group.name}
|
||||
primary={<StyledPrimary>{group.name}</StyledPrimary>}
|
||||
secondary={
|
||||
<ListItemTextChild
|
||||
color="text.secondary"
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
@@ -49,11 +91,18 @@ export const ProxyRender = (props: RenderProps) => {
|
||||
}}
|
||||
>
|
||||
<StyledTypeBox>{group.type}</StyledTypeBox>
|
||||
<StyledSubtitle>{group.now}</StyledSubtitle>
|
||||
<StyledSubtitle
|
||||
sx={{
|
||||
color: isDark ? "#ffffff" : "#8c8c8c",
|
||||
fontWeight: "600",
|
||||
}}
|
||||
>
|
||||
{group.now}
|
||||
</StyledSubtitle>
|
||||
</ListItemTextChild>
|
||||
}
|
||||
secondaryTypographyProps={{
|
||||
sx: { display: "flex", alignItems: "center" },
|
||||
sx: { display: "flex", alignItems: "center", color: "#ccc" },
|
||||
}}
|
||||
/>
|
||||
{headState?.open ? <ExpandLessRounded /> : <ExpandMoreRounded />}
|
||||
@@ -61,10 +110,10 @@ export const ProxyRender = (props: RenderProps) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (type === 1) {
|
||||
if (type === 1 && !group.hidden) {
|
||||
return (
|
||||
<ProxyHead
|
||||
sx={{ pl: indent ? 4.5 : 2.5, pr: 3, mt: indent ? 1 : 0.5, mb: 1 }}
|
||||
sx={{ pl: 2, pr: 3, mt: indent ? 1 : 0.5, mb: 1 }}
|
||||
groupName={group.name}
|
||||
headState={headState!}
|
||||
onLocation={() => onLocation(group)}
|
||||
@@ -74,25 +123,25 @@ export const ProxyRender = (props: RenderProps) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (type === 2) {
|
||||
if (type === 2 && !group.hidden) {
|
||||
return (
|
||||
<ProxyItem
|
||||
groupName={group.name}
|
||||
proxy={proxy!}
|
||||
selected={group.now === proxy?.name}
|
||||
showType={headState?.showType}
|
||||
sx={{ py: 0, pl: indent ? 4 : 2 }}
|
||||
sx={{ py: 0, pl: 1 }}
|
||||
onClick={() => onChangeProxy(group, proxy!)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (type === 3) {
|
||||
if (type === 3 && !group.hidden) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
py: 2,
|
||||
pl: indent ? 4.5 : 0,
|
||||
pl: 0,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
@@ -105,14 +154,14 @@ export const ProxyRender = (props: RenderProps) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (type === 4) {
|
||||
if (type === 4 && !group.hidden) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: 56,
|
||||
display: "grid",
|
||||
gap: 1,
|
||||
pl: indent ? 4 : 2,
|
||||
pl: 2,
|
||||
pr: 2,
|
||||
pb: 1,
|
||||
gridTemplateColumns: `repeat(${item.col! || 2}, 1fr)`,
|
||||
@@ -135,8 +184,16 @@ export const ProxyRender = (props: RenderProps) => {
|
||||
return null;
|
||||
};
|
||||
|
||||
const StyledPrimary = styled("span")`
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 1.5;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
const StyledSubtitle = styled("span")`
|
||||
font-size: 0.8rem;
|
||||
font-size: 12px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@@ -153,7 +210,7 @@ const StyledTypeBox = styled(ListItemTextChild)(({ theme }) => ({
|
||||
color: alpha(theme.palette.primary.main, 0.8),
|
||||
borderRadius: 4,
|
||||
fontSize: 10,
|
||||
padding: "0 2px",
|
||||
lineHeight: 1.25,
|
||||
marginRight: "4px",
|
||||
padding: "0 4px",
|
||||
lineHeight: 1.5,
|
||||
marginRight: "8px",
|
||||
}));
|
||||
|
||||
@@ -16,7 +16,7 @@ type HeadStateStorage = Record<string, Record<string, HeadState>>;
|
||||
const HEAD_STATE_KEY = "proxy-head-state";
|
||||
export const DEFAULT_STATE: HeadState = {
|
||||
open: false,
|
||||
showType: false,
|
||||
showType: true,
|
||||
sortType: 0,
|
||||
filterText: "",
|
||||
textState: null,
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
import dayjs from "dayjs";
|
||||
import useSWR, { mutate } from "swr";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
IconButton,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
Typography,
|
||||
styled,
|
||||
Box,
|
||||
alpha,
|
||||
Divider,
|
||||
keyframes,
|
||||
} from "@mui/material";
|
||||
import { RefreshRounded } from "@mui/icons-material";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getRuleProviders, ruleProviderUpdate } from "@/services/api";
|
||||
import { BaseDialog } from "../base";
|
||||
|
||||
const round = keyframes`
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
`;
|
||||
|
||||
export const ProviderButton = () => {
|
||||
const { t } = useTranslation();
|
||||
const { data } = useSWR("getRuleProviders", getRuleProviders);
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const hasProvider = Object.keys(data || {}).length > 0;
|
||||
const [updating, setUpdating] = useState(
|
||||
Object.keys(data || {}).map(() => false)
|
||||
);
|
||||
|
||||
const setUpdatingAt = (status: boolean, index: number) => {
|
||||
setUpdating((prev) => {
|
||||
const next = [...prev];
|
||||
next[index] = status;
|
||||
return next;
|
||||
});
|
||||
};
|
||||
const handleUpdate = async (key: string, index: number) => {
|
||||
setUpdatingAt(true, index);
|
||||
ruleProviderUpdate(key)
|
||||
.then(async () => {
|
||||
setUpdatingAt(false, index);
|
||||
await mutate("getRules");
|
||||
await mutate("getRuleProviders");
|
||||
})
|
||||
.catch(async () => {
|
||||
setUpdatingAt(false, index);
|
||||
await mutate("getRules");
|
||||
await mutate("getRuleProviders");
|
||||
});
|
||||
};
|
||||
|
||||
if (!hasProvider) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
sx={{ textTransform: "capitalize" }}
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
{t("Provider")}
|
||||
</Button>
|
||||
|
||||
<BaseDialog
|
||||
open={open}
|
||||
title={
|
||||
<Box display="flex" justifyContent="space-between" gap={1}>
|
||||
<Typography variant="h6">{t("Rule Provider")}</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
size="small"
|
||||
onClick={async () => {
|
||||
Object.entries(data || {}).forEach(
|
||||
async ([key, item], index) => {
|
||||
await handleUpdate(key, index);
|
||||
}
|
||||
);
|
||||
}}
|
||||
>
|
||||
{t("Update All")}
|
||||
</Button>
|
||||
</Box>
|
||||
}
|
||||
contentSx={{ width: 400 }}
|
||||
disableOk
|
||||
cancelBtn={t("Cancel")}
|
||||
onClose={() => setOpen(false)}
|
||||
onCancel={() => setOpen(false)}
|
||||
>
|
||||
<List sx={{ py: 0, minHeight: 250 }}>
|
||||
{Object.entries(data || {}).map(([key, item], index) => {
|
||||
const time = dayjs(item.updatedAt);
|
||||
return (
|
||||
<>
|
||||
<ListItem
|
||||
sx={{
|
||||
p: 0,
|
||||
borderRadius: "10px",
|
||||
border: "solid 2px var(--divider-color)",
|
||||
mb: 1,
|
||||
}}
|
||||
key={key}
|
||||
>
|
||||
<ListItemText
|
||||
sx={{ px: 1 }}
|
||||
primary={
|
||||
<>
|
||||
<Typography
|
||||
variant="h6"
|
||||
component="span"
|
||||
noWrap
|
||||
title={key}
|
||||
>
|
||||
{key}
|
||||
</Typography>
|
||||
<TypeBox component="span" sx={{ marginLeft: "8px" }}>
|
||||
{item.ruleCount}
|
||||
</TypeBox>
|
||||
</>
|
||||
}
|
||||
secondary={
|
||||
<>
|
||||
<StyledTypeBox component="span">
|
||||
{item.vehicleType}
|
||||
</StyledTypeBox>
|
||||
<StyledTypeBox component="span">
|
||||
{item.behavior}
|
||||
</StyledTypeBox>
|
||||
<StyledTypeBox component="span">
|
||||
{t("Update At")} {time.fromNow()}
|
||||
</StyledTypeBox>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Divider orientation="vertical" flexItem />
|
||||
<IconButton
|
||||
size="small"
|
||||
color="inherit"
|
||||
title="Update Provider"
|
||||
onClick={() => handleUpdate(key, index)}
|
||||
sx={{
|
||||
animation: updating[index]
|
||||
? `1s linear infinite ${round}`
|
||||
: "none",
|
||||
}}
|
||||
>
|
||||
<RefreshRounded />
|
||||
</IconButton>
|
||||
</ListItem>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
</BaseDialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
const TypeBox = styled(Box)(({ theme }) => ({
|
||||
display: "inline-block",
|
||||
border: "1px solid #ccc",
|
||||
borderColor: alpha(theme.palette.secondary.main, 0.5),
|
||||
color: alpha(theme.palette.secondary.main, 0.8),
|
||||
borderRadius: 4,
|
||||
fontSize: 10,
|
||||
marginRight: "4px",
|
||||
padding: "0 2px",
|
||||
lineHeight: 1.25,
|
||||
}));
|
||||
|
||||
const StyledTypeBox = styled(Box)(({ theme }) => ({
|
||||
display: "inline-block",
|
||||
border: "1px solid #ccc",
|
||||
borderColor: alpha(theme.palette.primary.main, 0.5),
|
||||
color: alpha(theme.palette.primary.main, 0.8),
|
||||
borderRadius: 4,
|
||||
fontSize: 10,
|
||||
marginRight: "4px",
|
||||
padding: "0 2px",
|
||||
lineHeight: 1.25,
|
||||
}));
|
||||
@@ -1,123 +0,0 @@
|
||||
import useSWR from "swr";
|
||||
import { forwardRef, useImperativeHandle, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Checkbox, Divider, Stack, Tooltip, Typography } from "@mui/material";
|
||||
import { InfoRounded } from "@mui/icons-material";
|
||||
import { getRuntimeExists } from "@/services/cmds";
|
||||
import {
|
||||
HANDLE_FIELDS,
|
||||
DEFAULT_FIELDS,
|
||||
OTHERS_FIELDS,
|
||||
} from "@/utils/clash-fields";
|
||||
import { BaseDialog, DialogRef } from "@/components/base";
|
||||
import { useProfiles } from "@/hooks/use-profiles";
|
||||
import { Notice } from "@/components/base";
|
||||
|
||||
const otherFields = [...OTHERS_FIELDS];
|
||||
const handleFields = [...HANDLE_FIELDS, ...DEFAULT_FIELDS];
|
||||
|
||||
export const ClashFieldViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { profiles = {}, patchProfiles } = useProfiles();
|
||||
const { data: existsKeys = [], mutate: mutateExists } = useSWR(
|
||||
"getRuntimeExists",
|
||||
getRuntimeExists
|
||||
);
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [selected, setSelected] = useState<string[]>([]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
open: () => {
|
||||
mutateExists();
|
||||
setSelected(profiles.valid || []);
|
||||
setOpen(true);
|
||||
},
|
||||
close: () => setOpen(false),
|
||||
}));
|
||||
|
||||
const handleChange = (item: string) => {
|
||||
if (!item) return;
|
||||
|
||||
setSelected((old) =>
|
||||
old.includes(item) ? old.filter((e) => e !== item) : [...old, item]
|
||||
);
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
setOpen(false);
|
||||
|
||||
const oldSet = new Set(profiles.valid || []);
|
||||
const curSet = new Set(selected);
|
||||
const joinSet = new Set(selected.concat([...oldSet]));
|
||||
|
||||
if (curSet.size === oldSet.size && curSet.size === joinSet.size) return;
|
||||
|
||||
try {
|
||||
await patchProfiles({ valid: [...curSet] });
|
||||
// Notice.success("Refresh clash config", 1000);
|
||||
} catch (err: any) {
|
||||
Notice.error(err?.message || err.toString());
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseDialog
|
||||
open={open}
|
||||
title={t("Clash Field")}
|
||||
contentSx={{
|
||||
pb: 0,
|
||||
width: 320,
|
||||
height: 300,
|
||||
overflowY: "auto",
|
||||
userSelect: "text",
|
||||
}}
|
||||
okBtn={t("Save")}
|
||||
cancelBtn={t("Back")}
|
||||
onClose={() => setOpen(false)}
|
||||
onCancel={() => setOpen(false)}
|
||||
onOk={handleSave}
|
||||
>
|
||||
{otherFields.map((item) => {
|
||||
const inSelect = selected.includes(item);
|
||||
const inConfig = existsKeys.includes(item);
|
||||
|
||||
return (
|
||||
<Stack key={item} mb={0.5} direction="row" alignItems="center">
|
||||
<Checkbox
|
||||
checked={inSelect}
|
||||
size="small"
|
||||
sx={{ p: 0.5 }}
|
||||
onChange={() => handleChange(item)}
|
||||
/>
|
||||
<Typography width="100%">{item}</Typography>
|
||||
|
||||
{!inSelect && inConfig && <WarnIcon />}
|
||||
</Stack>
|
||||
);
|
||||
})}
|
||||
|
||||
<Divider sx={{ my: 1 }}>
|
||||
<Typography color="text.secondary" fontSize={14}>
|
||||
Clash Verge Control Fields
|
||||
</Typography>
|
||||
</Divider>
|
||||
|
||||
{handleFields.map((item) => (
|
||||
<Stack key={item} mb={0.5} direction="row" alignItems="center">
|
||||
<Checkbox defaultChecked disabled size="small" sx={{ p: 0.5 }} />
|
||||
<Typography>{item}</Typography>
|
||||
</Stack>
|
||||
))}
|
||||
</BaseDialog>
|
||||
);
|
||||
});
|
||||
|
||||
function WarnIcon() {
|
||||
return (
|
||||
<Tooltip title="The field exists in the config but not enabled.">
|
||||
<InfoRounded color="warning" sx={{ cursor: "pointer", opacity: 0.5 }} />
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
@@ -13,26 +13,42 @@ export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
const { verge, patchVerge } = useVerge();
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [mixedPort, setMixedPort] = useState(
|
||||
verge?.verge_mixed_port ?? clashInfo?.mixed_port ?? 7897
|
||||
);
|
||||
const [socksPort, setSocksPort] = useState(
|
||||
verge?.verge_socks_port ?? clashInfo?.socks_port ?? 7898
|
||||
);
|
||||
const [port, setPort] = useState(
|
||||
verge?.verge_mixed_port ?? clashInfo?.port ?? 7897
|
||||
verge?.verge_port ?? clashInfo?.port ?? 7899
|
||||
);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
open: () => {
|
||||
if (verge?.verge_mixed_port) setPort(verge?.verge_mixed_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);
|
||||
setOpen(true);
|
||||
},
|
||||
close: () => setOpen(false),
|
||||
}));
|
||||
|
||||
const onSave = useLockFn(async () => {
|
||||
if (port === verge?.verge_mixed_port) {
|
||||
if (
|
||||
mixedPort === verge?.verge_mixed_port &&
|
||||
socksPort === verge?.verge_socks_port &&
|
||||
port === verge?.verge_port
|
||||
) {
|
||||
setOpen(false);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await patchInfo({ "mixed-port": port });
|
||||
await patchVerge({ verge_mixed_port: port });
|
||||
await patchInfo({ "mixed-port": mixedPort });
|
||||
await patchInfo({ "socks-port": socksPort });
|
||||
await patchInfo({ port });
|
||||
await patchVerge({ verge_mixed_port: mixedPort });
|
||||
await patchVerge({ verge_socks_port: socksPort });
|
||||
await patchVerge({ verge_port: port });
|
||||
setOpen(false);
|
||||
Notice.success("Change Clash port successfully!", 1000);
|
||||
} catch (err: any) {
|
||||
@@ -54,6 +70,30 @@ export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
<List>
|
||||
<ListItem sx={{ padding: "5px 2px" }}>
|
||||
<ListItemText primary="Mixed Port" />
|
||||
<TextField
|
||||
size="small"
|
||||
autoComplete="off"
|
||||
sx={{ width: 135 }}
|
||||
value={mixedPort}
|
||||
onChange={(e) =>
|
||||
setMixedPort(+e.target.value?.replace(/\D+/, "").slice(0, 5))
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem sx={{ padding: "5px 2px" }}>
|
||||
<ListItemText primary="Socks Port" />
|
||||
<TextField
|
||||
size="small"
|
||||
autoComplete="off"
|
||||
sx={{ width: 135 }}
|
||||
value={socksPort}
|
||||
onChange={(e) =>
|
||||
setSocksPort(+e.target.value?.replace(/\D+/, "").slice(0, 5))
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem sx={{ padding: "5px 2px" }}>
|
||||
<ListItemText primary="Http Port" />
|
||||
<TextField
|
||||
size="small"
|
||||
autoComplete="off"
|
||||
|
||||
@@ -36,7 +36,7 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
return (
|
||||
<BaseDialog
|
||||
open={open}
|
||||
title={t("Clash Port")}
|
||||
title={t("External Controller")}
|
||||
contentSx={{ width: 400 }}
|
||||
okBtn={t("Save")}
|
||||
cancelBtn={t("Cancel")}
|
||||
|
||||
@@ -14,17 +14,12 @@ const ItemWrapper = styled("div")`
|
||||
`;
|
||||
|
||||
const HOTKEY_FUNC = [
|
||||
"open_dashboard",
|
||||
"open_or_close_dashboard",
|
||||
"clash_mode_rule",
|
||||
"clash_mode_global",
|
||||
"clash_mode_direct",
|
||||
"clash_mode_script",
|
||||
"toggle_system_proxy",
|
||||
"enable_system_proxy",
|
||||
"disable_system_proxy",
|
||||
"toggle_tun_mode",
|
||||
"enable_tun_mode",
|
||||
"disable_tun_mode",
|
||||
];
|
||||
|
||||
export const HotkeyViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
|
||||
@@ -1,16 +1,38 @@
|
||||
import { forwardRef, useImperativeHandle, useState } from "react";
|
||||
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { List, Switch } from "@mui/material";
|
||||
import { List, Button } from "@mui/material";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { BaseDialog, DialogRef, Notice } from "@/components/base";
|
||||
import { BaseDialog, DialogRef, Notice, Switch } from "@/components/base";
|
||||
import { SettingItem } from "./setting-comp";
|
||||
import { GuardState } from "./guard-state";
|
||||
import { open as openDialog } from "@tauri-apps/api/dialog";
|
||||
import { convertFileSrc } from "@tauri-apps/api/tauri";
|
||||
import { copyIconFile, getAppDir } from "@/services/cmds";
|
||||
import { join } from "@tauri-apps/api/path";
|
||||
|
||||
export const LayoutViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const { verge, patchVerge, mutateVerge } = useVerge();
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [commonIcon, setCommonIcon] = useState("");
|
||||
const [sysproxyIcon, setSysproxyIcon] = useState("");
|
||||
const [tunIcon, setTunIcon] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
initIconPath();
|
||||
}, []);
|
||||
|
||||
async function initIconPath() {
|
||||
const appDir = await getAppDir();
|
||||
const icon_dir = await join(appDir, "icons");
|
||||
const common_icon = await join(icon_dir, "common.png");
|
||||
const sysproxy_icon = await join(icon_dir, "sysproxy.png");
|
||||
const tun_icon = await join(icon_dir, "tun.png");
|
||||
setCommonIcon(common_icon);
|
||||
setSysproxyIcon(sysproxy_icon);
|
||||
setTunIcon(tun_icon);
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
open: () => setOpen(true),
|
||||
@@ -36,19 +58,6 @@ export const LayoutViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
onCancel={() => setOpen(false)}
|
||||
>
|
||||
<List>
|
||||
<SettingItem label={t("Theme Blur")}>
|
||||
<GuardState
|
||||
value={verge?.theme_blur ?? false}
|
||||
valueProps="checked"
|
||||
onCatch={onError}
|
||||
onFormat={onSwitchFormat}
|
||||
onChange={(e) => onChangeData({ theme_blur: e })}
|
||||
onGuard={(e) => patchVerge({ theme_blur: e })}
|
||||
>
|
||||
<Switch edge="end" />
|
||||
</GuardState>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem label={t("Traffic Graph")}>
|
||||
<GuardState
|
||||
value={verge?.traffic_graph ?? true}
|
||||
@@ -74,6 +83,149 @@ export const LayoutViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
<Switch edge="end" />
|
||||
</GuardState>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem label={t("Proxy Group Icon")}>
|
||||
<GuardState
|
||||
value={verge?.enable_group_icon ?? true}
|
||||
valueProps="checked"
|
||||
onCatch={onError}
|
||||
onFormat={onSwitchFormat}
|
||||
onChange={(e) => onChangeData({ enable_group_icon: e })}
|
||||
onGuard={(e) => patchVerge({ enable_group_icon: e })}
|
||||
>
|
||||
<Switch edge="end" />
|
||||
</GuardState>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem label={t("Common Tray Icon")}>
|
||||
<GuardState
|
||||
value={verge?.common_tray_icon}
|
||||
onCatch={onError}
|
||||
onChange={(e) => onChangeData({ common_tray_icon: e })}
|
||||
onGuard={(e) => patchVerge({ common_tray_icon: e })}
|
||||
>
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
startIcon={
|
||||
verge?.common_tray_icon &&
|
||||
commonIcon && (
|
||||
<img height="20px" src={convertFileSrc(commonIcon)} />
|
||||
)
|
||||
}
|
||||
onClick={async () => {
|
||||
if (verge?.common_tray_icon) {
|
||||
onChangeData({ common_tray_icon: false });
|
||||
patchVerge({ common_tray_icon: false });
|
||||
} else {
|
||||
const path = await openDialog({
|
||||
directory: false,
|
||||
multiple: false,
|
||||
filters: [
|
||||
{
|
||||
name: "Tray Icon Image",
|
||||
extensions: ["png"],
|
||||
},
|
||||
],
|
||||
});
|
||||
if (path?.length) {
|
||||
await copyIconFile(`${path}`, "common.png");
|
||||
onChangeData({ common_tray_icon: true });
|
||||
patchVerge({ common_tray_icon: true });
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{verge?.common_tray_icon ? t("Clear") : t("Browse")}
|
||||
</Button>
|
||||
</GuardState>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem label={t("System Proxy Tray Icon")}>
|
||||
<GuardState
|
||||
value={verge?.sysproxy_tray_icon}
|
||||
onCatch={onError}
|
||||
onChange={(e) => onChangeData({ sysproxy_tray_icon: e })}
|
||||
onGuard={(e) => patchVerge({ sysproxy_tray_icon: e })}
|
||||
>
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
startIcon={
|
||||
verge?.sysproxy_tray_icon &&
|
||||
sysproxyIcon && (
|
||||
<img height="20px" src={convertFileSrc(sysproxyIcon)} />
|
||||
)
|
||||
}
|
||||
onClick={async () => {
|
||||
if (verge?.sysproxy_tray_icon) {
|
||||
onChangeData({ sysproxy_tray_icon: false });
|
||||
patchVerge({ sysproxy_tray_icon: false });
|
||||
} else {
|
||||
const path = await openDialog({
|
||||
directory: false,
|
||||
multiple: false,
|
||||
filters: [
|
||||
{
|
||||
name: "Tray Icon Image",
|
||||
extensions: ["png"],
|
||||
},
|
||||
],
|
||||
});
|
||||
if (path?.length) {
|
||||
await copyIconFile(`${path}`, "sysproxy.png");
|
||||
onChangeData({ sysproxy_tray_icon: true });
|
||||
patchVerge({ sysproxy_tray_icon: true });
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{verge?.sysproxy_tray_icon ? t("Clear") : t("Browse")}
|
||||
</Button>
|
||||
</GuardState>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem label={t("Tun Tray Icon")}>
|
||||
<GuardState
|
||||
value={verge?.tun_tray_icon}
|
||||
onCatch={onError}
|
||||
onChange={(e) => onChangeData({ tun_tray_icon: e })}
|
||||
onGuard={(e) => patchVerge({ tun_tray_icon: e })}
|
||||
>
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
startIcon={
|
||||
verge?.tun_tray_icon &&
|
||||
tunIcon && <img height="20px" src={convertFileSrc(tunIcon)} />
|
||||
}
|
||||
onClick={async () => {
|
||||
if (verge?.tun_tray_icon) {
|
||||
onChangeData({ tun_tray_icon: false });
|
||||
patchVerge({ tun_tray_icon: false });
|
||||
} else {
|
||||
const path = await openDialog({
|
||||
directory: false,
|
||||
multiple: false,
|
||||
filters: [
|
||||
{
|
||||
name: "Tray Icon Image",
|
||||
extensions: ["png"],
|
||||
},
|
||||
],
|
||||
});
|
||||
if (path?.length) {
|
||||
await copyIconFile(`${path}`, "tun.png");
|
||||
onChangeData({ tun_tray_icon: true });
|
||||
patchVerge({ tun_tray_icon: true });
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{verge?.tun_tray_icon ? t("Clear") : t("Browse")}
|
||||
</Button>
|
||||
</GuardState>
|
||||
</SettingItem>
|
||||
</List>
|
||||
</BaseDialog>
|
||||
);
|
||||
|
||||
@@ -7,11 +7,10 @@ import {
|
||||
ListItemText,
|
||||
MenuItem,
|
||||
Select,
|
||||
Switch,
|
||||
TextField,
|
||||
} from "@mui/material";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { BaseDialog, DialogRef, Notice } from "@/components/base";
|
||||
import { BaseDialog, DialogRef, Notice, Switch } from "@/components/base";
|
||||
|
||||
export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -21,11 +20,11 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
const [values, setValues] = useState({
|
||||
appLogLevel: "info",
|
||||
autoCloseConnection: true,
|
||||
enableClashFields: true,
|
||||
enableBuiltinEnhanced: true,
|
||||
proxyLayoutColumn: 6,
|
||||
defaultLatencyTest: "",
|
||||
autoLogClean: 0,
|
||||
defaultLatencyTimeout: 10000,
|
||||
});
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
@@ -34,11 +33,11 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
setValues({
|
||||
appLogLevel: verge?.app_log_level ?? "info",
|
||||
autoCloseConnection: verge?.auto_close_connection ?? true,
|
||||
enableClashFields: verge?.enable_clash_fields ?? true,
|
||||
enableBuiltinEnhanced: verge?.enable_builtin_enhanced ?? true,
|
||||
proxyLayoutColumn: verge?.proxy_layout_column || 6,
|
||||
defaultLatencyTest: verge?.default_latency_test || "",
|
||||
autoLogClean: verge?.auto_log_clean || 0,
|
||||
defaultLatencyTimeout: verge?.default_latency_timeout || 10000,
|
||||
});
|
||||
},
|
||||
close: () => setOpen(false),
|
||||
@@ -49,10 +48,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
await patchVerge({
|
||||
app_log_level: values.appLogLevel,
|
||||
auto_close_connection: values.autoCloseConnection,
|
||||
enable_clash_fields: values.enableClashFields,
|
||||
enable_builtin_enhanced: values.enableBuiltinEnhanced,
|
||||
proxy_layout_column: values.proxyLayoutColumn,
|
||||
default_latency_test: values.defaultLatencyTest,
|
||||
default_latency_timeout: values.defaultLatencyTimeout,
|
||||
auto_log_clean: values.autoLogClean as any,
|
||||
});
|
||||
setOpen(false);
|
||||
@@ -105,17 +104,6 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
<ListItem sx={{ padding: "5px 2px" }}>
|
||||
<ListItemText primary={t("Enable Clash Fields Filter")} />
|
||||
<Switch
|
||||
edge="end"
|
||||
checked={values.enableClashFields}
|
||||
onChange={(_, c) =>
|
||||
setValues((v) => ({ ...v, enableClashFields: c }))
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
<ListItem sx={{ padding: "5px 2px" }}>
|
||||
<ListItemText primary={t("Enable Builtin Enhanced")} />
|
||||
<Switch
|
||||
@@ -193,6 +181,27 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
<ListItem sx={{ padding: "5px 2px" }}>
|
||||
<ListItemText primary={t("Default Latency Timeout")} />
|
||||
<TextField
|
||||
size="small"
|
||||
type="number"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
sx={{ width: 250 }}
|
||||
value={values.defaultLatencyTimeout}
|
||||
placeholder="10000"
|
||||
onChange={(e) =>
|
||||
setValues((v) => ({
|
||||
...v,
|
||||
defaultLatencyTimeout: parseInt(e.target.value),
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
</List>
|
||||
</BaseDialog>
|
||||
);
|
||||
|
||||
@@ -18,9 +18,11 @@ export const SettingItem: React.FC<ItemProps> = (props) => {
|
||||
const { label, extra, children, secondary } = props;
|
||||
|
||||
const primary = !extra ? (
|
||||
label
|
||||
<Box sx={{ display: "flex", alignItems: "center", fontSize: "14px" }}>
|
||||
<span>{label}</span>
|
||||
</Box>
|
||||
) : (
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<Box sx={{ display: "flex", alignItems: "center", fontSize: "14px" }}>
|
||||
<span>{label}</span>
|
||||
{extra}
|
||||
</Box>
|
||||
@@ -39,7 +41,17 @@ export const SettingList: React.FC<{
|
||||
children: ReactNode;
|
||||
}> = (props) => (
|
||||
<List>
|
||||
<ListSubheader sx={{ background: "transparent" }} disableSticky>
|
||||
<ListSubheader
|
||||
sx={[
|
||||
{ background: "transparent", fontSize: "16px", fontWeight: "700" },
|
||||
({ palette }) => {
|
||||
return {
|
||||
color: palette.text.primary,
|
||||
};
|
||||
},
|
||||
]}
|
||||
disableSticky
|
||||
>
|
||||
{props.title}
|
||||
</ListSubheader>
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button, ButtonGroup, Tooltip } from "@mui/material";
|
||||
import { checkService } from "@/services/cmds";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import getSystem from "@/utils/get-system";
|
||||
import useSWR from "swr";
|
||||
|
||||
const isWIN = getSystem() === "windows";
|
||||
|
||||
interface Props {
|
||||
value?: string;
|
||||
onChange?: (value: string) => void;
|
||||
}
|
||||
|
||||
export const StackModeSwitch = (props: Props) => {
|
||||
const { value, onChange } = props;
|
||||
const { verge } = useVerge();
|
||||
const { enable_service_mode } = verge ?? {};
|
||||
// service mode
|
||||
const { data: serviceStatus } = useSWR(
|
||||
isWIN ? "checkService" : null,
|
||||
checkService,
|
||||
{
|
||||
revalidateIfStale: false,
|
||||
shouldRetryOnError: false,
|
||||
}
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
title={
|
||||
isWIN && (serviceStatus !== "active" || !enable_service_mode)
|
||||
? t("System and Mixed Can Only be Used in Service Mode")
|
||||
: ""
|
||||
}
|
||||
>
|
||||
<ButtonGroup size="small" sx={{ my: "4px" }}>
|
||||
<Button
|
||||
variant={value?.toLowerCase() === "system" ? "contained" : "outlined"}
|
||||
onClick={() => onChange?.("system")}
|
||||
disabled={
|
||||
isWIN && (serviceStatus !== "active" || !enable_service_mode)
|
||||
}
|
||||
sx={{ textTransform: "capitalize" }}
|
||||
>
|
||||
System
|
||||
</Button>
|
||||
<Button
|
||||
variant={value?.toLowerCase() === "gvisor" ? "contained" : "outlined"}
|
||||
onClick={() => onChange?.("gvisor")}
|
||||
sx={{ textTransform: "capitalize" }}
|
||||
>
|
||||
gVisor
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant={value?.toLowerCase() === "mixed" ? "contained" : "outlined"}
|
||||
onClick={() => onChange?.("mixed")}
|
||||
disabled={
|
||||
isWIN && (serviceStatus !== "active" || !enable_service_mode)
|
||||
}
|
||||
sx={{ textTransform: "capitalize" }}
|
||||
>
|
||||
Mixed
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
@@ -8,13 +8,12 @@ import {
|
||||
ListItem,
|
||||
ListItemText,
|
||||
styled,
|
||||
Switch,
|
||||
TextField,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { getSystemProxy } from "@/services/cmds";
|
||||
import { BaseDialog, DialogRef, Notice } from "@/components/base";
|
||||
import { BaseDialog, DialogRef, Notice, Switch } from "@/components/base";
|
||||
|
||||
export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -168,6 +167,6 @@ const FlexBox = styled("div")`
|
||||
|
||||
.label {
|
||||
flex: none;
|
||||
width: 80px;
|
||||
width: 85px;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
import { forwardRef, useImperativeHandle, useState } from "react";
|
||||
import { useLockFn } from "ahooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
Box,
|
||||
Typography,
|
||||
Button,
|
||||
TextField,
|
||||
} from "@mui/material";
|
||||
import { useClash } from "@/hooks/use-clash";
|
||||
import { BaseDialog, DialogRef, Notice, Switch } from "@/components/base";
|
||||
import { StackModeSwitch } from "./stack-mode-switch";
|
||||
|
||||
export const TunViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { clash, mutateClash, patchClash } = useClash();
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [values, setValues] = useState({
|
||||
stack: "gvisor",
|
||||
device: "Meta",
|
||||
autoRoute: true,
|
||||
autoDetectInterface: true,
|
||||
dnsHijack: ["any:53"],
|
||||
strictRoute: false,
|
||||
mtu: 9000,
|
||||
});
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
open: () => {
|
||||
setOpen(true);
|
||||
setValues({
|
||||
stack: clash?.tun.stack ?? "gvisor",
|
||||
device: clash?.tun.device ?? "Meta",
|
||||
autoRoute: clash?.tun["auto-route"] ?? true,
|
||||
autoDetectInterface: clash?.tun["auto-detect-interface"] ?? true,
|
||||
dnsHijack: clash?.tun["dns-hijack"] ?? ["any:53"],
|
||||
strictRoute: clash?.tun["strict-route"] ?? false,
|
||||
mtu: clash?.tun.mtu ?? 9000,
|
||||
});
|
||||
},
|
||||
close: () => setOpen(false),
|
||||
}));
|
||||
|
||||
const onSave = useLockFn(async () => {
|
||||
try {
|
||||
let tun = {
|
||||
stack: values.stack,
|
||||
device: values.device,
|
||||
"auto-route": values.autoRoute,
|
||||
"auto-detect-interface": values.autoDetectInterface,
|
||||
"dns-hijack": values.dnsHijack,
|
||||
"strict-route": values.strictRoute,
|
||||
mtu: values.mtu,
|
||||
};
|
||||
await patchClash({ tun });
|
||||
await mutateClash(
|
||||
(old) => ({
|
||||
...(old! || {}),
|
||||
tun,
|
||||
}),
|
||||
false
|
||||
);
|
||||
setOpen(false);
|
||||
} catch (err: any) {
|
||||
Notice.error(err.message || err.toString());
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<BaseDialog
|
||||
open={open}
|
||||
title={
|
||||
<Box display="flex" justifyContent="space-between" gap={1}>
|
||||
<Typography variant="h6">{t("Tun Mode")}</Typography>
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
onClick={async () => {
|
||||
let tun = {
|
||||
stack: "gvisor",
|
||||
device: "Meta",
|
||||
"auto-route": true,
|
||||
"auto-detect-interface": true,
|
||||
"dns-hijack": ["any:53"],
|
||||
"strict-route": false,
|
||||
mtu: 9000,
|
||||
};
|
||||
setValues({
|
||||
stack: "gvisor",
|
||||
device: "Meta",
|
||||
autoRoute: true,
|
||||
autoDetectInterface: true,
|
||||
dnsHijack: ["any:53"],
|
||||
strictRoute: false,
|
||||
mtu: 9000,
|
||||
});
|
||||
await patchClash({ tun });
|
||||
await mutateClash(
|
||||
(old) => ({
|
||||
...(old! || {}),
|
||||
tun,
|
||||
}),
|
||||
false
|
||||
);
|
||||
}}
|
||||
>
|
||||
{t("Reset to Default")}
|
||||
</Button>
|
||||
</Box>
|
||||
}
|
||||
contentSx={{ width: 450 }}
|
||||
okBtn={t("Save")}
|
||||
cancelBtn={t("Cancel")}
|
||||
onClose={() => setOpen(false)}
|
||||
onCancel={() => setOpen(false)}
|
||||
onOk={onSave}
|
||||
>
|
||||
<List>
|
||||
<ListItem sx={{ padding: "5px 2px" }}>
|
||||
<ListItemText primary={t("Stack")} />
|
||||
<StackModeSwitch
|
||||
value={values.stack}
|
||||
onChange={(value) => {
|
||||
setValues((v) => ({
|
||||
...v,
|
||||
stack: value,
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
<ListItem sx={{ padding: "5px 2px" }}>
|
||||
<ListItemText primary={t("Device")} />
|
||||
<TextField
|
||||
size="small"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
sx={{ width: 250 }}
|
||||
value={values.device}
|
||||
placeholder="Meta"
|
||||
onChange={(e) =>
|
||||
setValues((v) => ({ ...v, device: e.target.value }))
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
<ListItem sx={{ padding: "5px 2px" }}>
|
||||
<ListItemText primary={t("Auto Route")} />
|
||||
<Switch
|
||||
edge="end"
|
||||
checked={values.autoRoute}
|
||||
onChange={(_, c) => setValues((v) => ({ ...v, autoRoute: c }))}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
<ListItem sx={{ padding: "5px 2px" }}>
|
||||
<ListItemText primary={t("Strict Route")} />
|
||||
<Switch
|
||||
edge="end"
|
||||
checked={values.strictRoute}
|
||||
onChange={(_, c) => setValues((v) => ({ ...v, strictRoute: c }))}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
<ListItem sx={{ padding: "5px 2px" }}>
|
||||
<ListItemText primary={t("Auto Detect Interface")} />
|
||||
<Switch
|
||||
edge="end"
|
||||
checked={values.autoDetectInterface}
|
||||
onChange={(_, c) =>
|
||||
setValues((v) => ({ ...v, autoDetectInterface: c }))
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
<ListItem sx={{ padding: "5px 2px" }}>
|
||||
<ListItemText primary={t("DNS Hijack")} />
|
||||
<TextField
|
||||
size="small"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
sx={{ width: 250 }}
|
||||
value={values.dnsHijack.join(",")}
|
||||
placeholder="Please use , to separate multiple DNS servers"
|
||||
onChange={(e) =>
|
||||
setValues((v) => ({ ...v, dnsHijack: e.target.value.split(",") }))
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
<ListItem sx={{ padding: "5px 2px" }}>
|
||||
<ListItemText primary={t("MTU")} />
|
||||
<TextField
|
||||
size="small"
|
||||
type="number"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
sx={{ width: 250 }}
|
||||
value={values.mtu}
|
||||
placeholder="9000"
|
||||
onChange={(e) =>
|
||||
setValues((v) => ({
|
||||
...v,
|
||||
mtu: parseInt(e.target.value),
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
</List>
|
||||
</BaseDialog>
|
||||
);
|
||||
});
|
||||
@@ -10,6 +10,7 @@ import { checkUpdate, installUpdate } from "@tauri-apps/api/updater";
|
||||
import { BaseDialog, DialogRef, Notice } from "@/components/base";
|
||||
import { atomUpdateState } from "@/services/states";
|
||||
import { listen, Event, UnlistenFn } from "@tauri-apps/api/event";
|
||||
import { portableFlag } from "@/pages/_layout";
|
||||
|
||||
const UpdateLog = styled(Box)(() => ({
|
||||
"h1,h2,h3,ul,ol,p": { margin: "0.5em 0", color: "inherit" },
|
||||
@@ -46,6 +47,10 @@ export const UpdateViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
}, [updateInfo]);
|
||||
|
||||
const onUpdate = useLockFn(async () => {
|
||||
if (portableFlag) {
|
||||
Notice.error(t("Portable Updater Error"));
|
||||
return;
|
||||
}
|
||||
if (updateState) return;
|
||||
setUpdateState(true);
|
||||
if (eventListener !== null) {
|
||||
|
||||
@@ -22,10 +22,13 @@ export const WebUIViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
close: () => setOpen(false),
|
||||
}));
|
||||
|
||||
const webUIList = verge?.web_ui_list || [];
|
||||
const webUIList = verge?.web_ui_list || [
|
||||
"https://d.metacubex.one/#?hostname=%host&port=%port&secret=%secret",
|
||||
"https://yacd.metacubex.one/?host=%host&port=%port&secret=%secret",
|
||||
];
|
||||
|
||||
const handleAdd = useLockFn(async (value: string) => {
|
||||
const newList = [value, ...webUIList];
|
||||
const newList = [...webUIList, value];
|
||||
mutateVerge((old) => (old ? { ...old, web_ui_list: newList } : old), false);
|
||||
await patchVerge({ web_ui_list: newList });
|
||||
});
|
||||
@@ -100,18 +103,6 @@ export const WebUIViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
onClose={() => setOpen(false)}
|
||||
onCancel={() => setOpen(false)}
|
||||
>
|
||||
{editing && (
|
||||
<WebUIItem
|
||||
value=""
|
||||
onlyEdit
|
||||
onChange={(v) => {
|
||||
setEditing(false);
|
||||
handleAdd(v || "");
|
||||
}}
|
||||
onCancel={() => setEditing(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!editing && webUIList.length === 0 && (
|
||||
<BaseEmpty
|
||||
text="Empty List"
|
||||
@@ -132,6 +123,17 @@ export const WebUIViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
onOpenUrl={handleOpenUrl}
|
||||
/>
|
||||
))}
|
||||
{editing && (
|
||||
<WebUIItem
|
||||
value=""
|
||||
onlyEdit
|
||||
onChange={(v) => {
|
||||
setEditing(false);
|
||||
handleAdd(v || "");
|
||||
}}
|
||||
onCancel={() => setEditing(false)}
|
||||
/>
|
||||
)}
|
||||
</BaseDialog>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -3,7 +3,6 @@ import { useTranslation } from "react-i18next";
|
||||
import { useLockFn } from "ahooks";
|
||||
import {
|
||||
TextField,
|
||||
Switch,
|
||||
Select,
|
||||
MenuItem,
|
||||
Typography,
|
||||
@@ -11,11 +10,10 @@ import {
|
||||
Tooltip,
|
||||
} from "@mui/material";
|
||||
import { ArrowForward, Settings, Shuffle } from "@mui/icons-material";
|
||||
import { DialogRef, Notice } from "@/components/base";
|
||||
import { DialogRef, Notice, Switch } from "@/components/base";
|
||||
import { useClash } from "@/hooks/use-clash";
|
||||
import { GuardState } from "./mods/guard-state";
|
||||
import { WebUIViewer } from "./mods/web-ui-viewer";
|
||||
import { ClashFieldViewer } from "./mods/clash-field-viewer";
|
||||
import { ClashPortViewer } from "./mods/clash-port-viewer";
|
||||
import { ControllerViewer } from "./mods/controller-viewer";
|
||||
import { SettingList, SettingItem } from "./mods/setting-comp";
|
||||
@@ -39,14 +37,9 @@ const SettingClash = ({ onError }: Props) => {
|
||||
|
||||
const { ipv6, "allow-lan": allowLan, "log-level": logLevel } = clash ?? {};
|
||||
|
||||
const {
|
||||
enable_random_port = false,
|
||||
verge_mixed_port,
|
||||
enable_clash_fields = true,
|
||||
} = verge ?? {};
|
||||
const { enable_random_port = false, verge_mixed_port } = verge ?? {};
|
||||
|
||||
const webRef = useRef<DialogRef>(null);
|
||||
const fieldRef = useRef<DialogRef>(null);
|
||||
const portRef = useRef<DialogRef>(null);
|
||||
const ctrlRef = useRef<DialogRef>(null);
|
||||
const coreRef = useRef<DialogRef>(null);
|
||||
@@ -70,7 +63,6 @@ const SettingClash = ({ onError }: Props) => {
|
||||
return (
|
||||
<SettingList title={t("Clash Setting")}>
|
||||
<WebUIViewer ref={webRef} />
|
||||
<ClashFieldViewer ref={fieldRef} />
|
||||
<ClashPortViewer ref={portRef} />
|
||||
<ControllerViewer ref={ctrlRef} />
|
||||
<ClashCoreViewer ref={coreRef} />
|
||||
@@ -121,11 +113,11 @@ const SettingClash = ({ onError }: Props) => {
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem
|
||||
label={t("Mixed Port")}
|
||||
label={t("Port Config")}
|
||||
extra={
|
||||
<Tooltip title={t("Random Port")}>
|
||||
<IconButton
|
||||
color={enable_random_port ? "success" : "inherit"}
|
||||
color={enable_random_port ? "primary" : "inherit"}
|
||||
size="small"
|
||||
onClick={() => {
|
||||
Notice.success(t("After restart to take effect"), 1000);
|
||||
@@ -176,19 +168,6 @@ const SettingClash = ({ onError }: Props) => {
|
||||
</IconButton>
|
||||
</SettingItem>
|
||||
|
||||
{enable_clash_fields && (
|
||||
<SettingItem label={t("Clash Field")}>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
size="small"
|
||||
sx={{ my: "2px" }}
|
||||
onClick={() => fieldRef.current?.open()}
|
||||
>
|
||||
<ArrowForward />
|
||||
</IconButton>
|
||||
</SettingItem>
|
||||
)}
|
||||
|
||||
<SettingItem
|
||||
label={t("Clash Core")}
|
||||
extra={
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import useSWR from "swr";
|
||||
import { useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { IconButton, Switch } from "@mui/material";
|
||||
import { ArrowForward, PrivacyTipRounded, Settings } from "@mui/icons-material";
|
||||
import { IconButton, Tooltip } from "@mui/material";
|
||||
import { PrivacyTipRounded, Settings, InfoRounded } from "@mui/icons-material";
|
||||
import { checkService } from "@/services/cmds";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { DialogRef } from "@/components/base";
|
||||
import { DialogRef, Switch } from "@/components/base";
|
||||
import { SettingList, SettingItem } from "./mods/setting-comp";
|
||||
import { GuardState } from "./mods/guard-state";
|
||||
import { ServiceViewer } from "./mods/service-viewer";
|
||||
import { SysproxyViewer } from "./mods/sysproxy-viewer";
|
||||
import { TunViewer } from "./mods/tun-viewer";
|
||||
import getSystem from "@/utils/get-system";
|
||||
|
||||
interface Props {
|
||||
@@ -36,6 +37,7 @@ const SettingSystem = ({ onError }: Props) => {
|
||||
|
||||
const serviceRef = useRef<DialogRef>(null);
|
||||
const sysproxyRef = useRef<DialogRef>(null);
|
||||
const tunRef = useRef<DialogRef>(null);
|
||||
|
||||
const {
|
||||
enable_tun_mode,
|
||||
@@ -53,11 +55,41 @@ const SettingSystem = ({ onError }: Props) => {
|
||||
return (
|
||||
<SettingList title={t("System Setting")}>
|
||||
<SysproxyViewer ref={sysproxyRef} />
|
||||
<TunViewer ref={tunRef} />
|
||||
{isWIN && (
|
||||
<ServiceViewer ref={serviceRef} enable={!!enable_service_mode} />
|
||||
)}
|
||||
|
||||
<SettingItem label={t("Tun Mode")}>
|
||||
<SettingItem
|
||||
label={t("Tun Mode")}
|
||||
extra={
|
||||
<>
|
||||
<Tooltip
|
||||
title={
|
||||
isWIN ? t("Tun Mode Info Windows") : t("Tun Mode Info Unix")
|
||||
}
|
||||
placement="top"
|
||||
>
|
||||
<IconButton color="inherit" size="small">
|
||||
<InfoRounded
|
||||
fontSize="inherit"
|
||||
style={{ cursor: "pointer", opacity: 0.75 }}
|
||||
/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
size="small"
|
||||
onClick={() => tunRef.current?.open()}
|
||||
>
|
||||
<Settings
|
||||
fontSize="inherit"
|
||||
style={{ cursor: "pointer", opacity: 0.75 }}
|
||||
/>
|
||||
</IconButton>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<GuardState
|
||||
value={enable_tun_mode ?? false}
|
||||
valueProps="checked"
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
import { useRef } from "react";
|
||||
import { useLockFn } from "ahooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { IconButton, MenuItem, Select, Typography } from "@mui/material";
|
||||
import { openAppDir, openCoreDir, openLogsDir } from "@/services/cmds";
|
||||
import { open } from "@tauri-apps/api/dialog";
|
||||
import {
|
||||
Button,
|
||||
IconButton,
|
||||
MenuItem,
|
||||
Select,
|
||||
Input,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { exitApp, openAppDir, openCoreDir, openLogsDir } from "@/services/cmds";
|
||||
import { ArrowForward } from "@mui/icons-material";
|
||||
import { checkUpdate } from "@tauri-apps/api/updater";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
@@ -18,7 +26,7 @@ import { GuardState } from "./mods/guard-state";
|
||||
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;
|
||||
}
|
||||
@@ -29,7 +37,14 @@ const SettingVerge = ({ onError }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { verge, patchVerge, mutateVerge } = useVerge();
|
||||
const { theme_mode, language, tray_event, env_type } = verge ?? {};
|
||||
const {
|
||||
theme_mode,
|
||||
language,
|
||||
tray_event,
|
||||
env_type,
|
||||
startup_script,
|
||||
start_page,
|
||||
} = verge ?? {};
|
||||
const configRef = useRef<DialogRef>(null);
|
||||
const hotkeyRef = useRef<DialogRef>(null);
|
||||
const miscRef = useRef<DialogRef>(null);
|
||||
@@ -125,6 +140,71 @@ const SettingVerge = ({ onError }: Props) => {
|
||||
</GuardState>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem label={t("Start Page")}>
|
||||
<GuardState
|
||||
value={start_page ?? "/"}
|
||||
onCatch={onError}
|
||||
onFormat={(e: any) => e.target.value}
|
||||
onChange={(e) => onChangeData({ start_page: e })}
|
||||
onGuard={(e) => patchVerge({ start_page: e })}
|
||||
>
|
||||
<Select size="small" sx={{ width: 140, "> div": { py: "7.5px" } }}>
|
||||
{routers.map((page: { label: string; link: string }) => {
|
||||
return <MenuItem value={page.link}>{t(page.label)}</MenuItem>;
|
||||
})}
|
||||
</Select>
|
||||
</GuardState>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem label={t("Startup Script")}>
|
||||
<GuardState
|
||||
value={startup_script ?? ""}
|
||||
onCatch={onError}
|
||||
onFormat={(e: any) => e.target.value}
|
||||
onChange={(e) => onChangeData({ startup_script: e })}
|
||||
onGuard={(e) => patchVerge({ startup_script: e })}
|
||||
>
|
||||
<Input
|
||||
value={startup_script}
|
||||
disabled
|
||||
sx={{ width: 230 }}
|
||||
endAdornment={
|
||||
<>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
const path = await open({
|
||||
directory: false,
|
||||
multiple: false,
|
||||
filters: [
|
||||
{
|
||||
name: "Shell Script",
|
||||
extensions: ["sh", "bat", "ps1"],
|
||||
},
|
||||
],
|
||||
});
|
||||
if (path?.length) {
|
||||
onChangeData({ startup_script: `${path}` });
|
||||
patchVerge({ startup_script: `${path}` });
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t("Browse")}
|
||||
</Button>
|
||||
{startup_script && (
|
||||
<Button
|
||||
onClick={async () => {
|
||||
onChangeData({ startup_script: "" });
|
||||
patchVerge({ startup_script: "" });
|
||||
}}
|
||||
>
|
||||
{t("Clear")}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
></Input>
|
||||
</GuardState>
|
||||
</SettingItem>
|
||||
<SettingItem label={t("Theme Setting")}>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
@@ -213,18 +293,29 @@ const SettingVerge = ({ onError }: Props) => {
|
||||
</IconButton>
|
||||
</SettingItem>
|
||||
|
||||
{!(OS === "windows" && WIN_PORTABLE) && (
|
||||
<SettingItem label={t("Check for Updates")}>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
size="small"
|
||||
sx={{ my: "2px" }}
|
||||
onClick={onCheckUpdate}
|
||||
>
|
||||
<ArrowForward />
|
||||
</IconButton>
|
||||
</SettingItem>
|
||||
)}
|
||||
<SettingItem label={t("Check for Updates")}>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
size="small"
|
||||
sx={{ my: "2px" }}
|
||||
onClick={onCheckUpdate}
|
||||
>
|
||||
<ArrowForward />
|
||||
</IconButton>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem label={t("Exit")}>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
size="small"
|
||||
sx={{ my: "2px" }}
|
||||
onClick={() => {
|
||||
exitApp();
|
||||
}}
|
||||
>
|
||||
<ArrowForward />
|
||||
</IconButton>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem label={t("Verge Version")}>
|
||||
<Typography sx={{ py: "7px", pr: 1 }}>v{version}</Typography>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { alpha, Box, styled } from "@mui/material";
|
||||
|
||||
export const TestBox = styled(Box)(({ theme, "aria-selected": selected }) => {
|
||||
const { mode, primary, text } = theme.palette;
|
||||
const key = `${mode}-${!!selected}`;
|
||||
|
||||
const backgroundColor = mode === "light" ? "#ffffff" : "#282A36";
|
||||
|
||||
const color = {
|
||||
"light-true": text.secondary,
|
||||
"light-false": text.secondary,
|
||||
"dark-true": alpha(text.secondary, 0.65),
|
||||
"dark-false": alpha(text.secondary, 0.65),
|
||||
}[key]!;
|
||||
|
||||
const h2color = {
|
||||
"light-true": primary.main,
|
||||
"light-false": text.primary,
|
||||
"dark-true": primary.main,
|
||||
"dark-false": text.primary,
|
||||
}[key]!;
|
||||
|
||||
return {
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
display: "block",
|
||||
cursor: "pointer",
|
||||
textAlign: "left",
|
||||
borderRadius: 8,
|
||||
boxShadow: theme.shadows[2],
|
||||
padding: "8px 16px",
|
||||
boxSizing: "border-box",
|
||||
backgroundColor,
|
||||
color,
|
||||
"& h2": { color: h2color },
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,216 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useLockFn } from "ahooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSortable } from "@dnd-kit/sortable";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Divider,
|
||||
MenuItem,
|
||||
Menu,
|
||||
styled,
|
||||
alpha,
|
||||
} from "@mui/material";
|
||||
import { BaseLoading } from "@/components/base";
|
||||
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";
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
itemData: IVergeTestItem;
|
||||
onEdit: () => void;
|
||||
onDelete: (uid: string) => void;
|
||||
}
|
||||
|
||||
let eventListener: UnlistenFn | null = null;
|
||||
|
||||
export const TestItem = (props: Props) => {
|
||||
const { itemData, onEdit, onDelete: onDeleteItem } = props;
|
||||
const { attributes, listeners, setNodeRef, transform, transition } =
|
||||
useSortable({ id: props.id });
|
||||
|
||||
const { t } = useTranslation();
|
||||
const [anchorEl, setAnchorEl] = useState<any>(null);
|
||||
const [position, setPosition] = useState({ left: 0, top: 0 });
|
||||
const [delay, setDelay] = useState(-1);
|
||||
const { uid, name, icon, url } = itemData;
|
||||
|
||||
const onDelay = async () => {
|
||||
setDelay(-2);
|
||||
const result = await cmdTestDelay(url);
|
||||
setDelay(result);
|
||||
};
|
||||
|
||||
const onEditTest = () => {
|
||||
setAnchorEl(null);
|
||||
onEdit();
|
||||
};
|
||||
|
||||
const onDelete = useLockFn(async () => {
|
||||
setAnchorEl(null);
|
||||
try {
|
||||
onDeleteItem(uid);
|
||||
} catch (err: any) {
|
||||
Notice.error(err?.message || err.toString());
|
||||
}
|
||||
});
|
||||
|
||||
const menu = [
|
||||
{ label: "Edit", handler: onEditTest },
|
||||
{ label: "Delete", handler: onDelete },
|
||||
];
|
||||
|
||||
const listenTsetEvent = async () => {
|
||||
if (eventListener !== null) {
|
||||
eventListener();
|
||||
}
|
||||
eventListener = await listen("verge://test-all", () => {
|
||||
onDelay();
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
listenTsetEvent();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
}}
|
||||
>
|
||||
<TestBox
|
||||
onClick={onEditTest}
|
||||
onContextMenu={(event) => {
|
||||
const { clientX, clientY } = event;
|
||||
setPosition({ top: clientY, left: clientX });
|
||||
setAnchorEl(event.currentTarget);
|
||||
event.preventDefault();
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
position="relative"
|
||||
sx={{ cursor: "move" }}
|
||||
ref={setNodeRef}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
>
|
||||
{icon && icon.trim() !== "" ? (
|
||||
<Box sx={{ display: "flex", justifyContent: "center" }}>
|
||||
{icon.trim().startsWith("http") && (
|
||||
<img src={icon} height="40px" />
|
||||
)}
|
||||
{icon.trim().startsWith("data") && (
|
||||
<img src={icon} height="40px" />
|
||||
)}
|
||||
{icon.trim().startsWith("<svg") && (
|
||||
<img
|
||||
src={`data:image/svg+xml;base64,${btoa(icon)}`}
|
||||
height="40px"
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
) : (
|
||||
<Box sx={{ display: "flex", justifyContent: "center" }}>
|
||||
<LanguageTwoTone sx={{ height: "40px" }} fontSize="large" />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box sx={{ display: "flex", justifyContent: "center" }}>
|
||||
<Typography variant="h6" component="h2" noWrap title={name}>
|
||||
{name}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Divider sx={{ marginTop: "8px" }} />
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
marginTop: "8px",
|
||||
color: "primary.main",
|
||||
}}
|
||||
>
|
||||
{delay === -2 && (
|
||||
<Widget>
|
||||
<BaseLoading />
|
||||
</Widget>
|
||||
)}
|
||||
|
||||
{delay === -1 && (
|
||||
<Widget
|
||||
className="the-check"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onDelay();
|
||||
}}
|
||||
sx={({ palette }) => ({
|
||||
":hover": { bgcolor: alpha(palette.primary.main, 0.15) },
|
||||
})}
|
||||
>
|
||||
Check
|
||||
</Widget>
|
||||
)}
|
||||
|
||||
{delay >= 0 && (
|
||||
// 显示延迟
|
||||
<Widget
|
||||
className="the-delay"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onDelay();
|
||||
}}
|
||||
color={delayManager.formatDelayColor(delay)}
|
||||
sx={({ palette }) => ({
|
||||
":hover": {
|
||||
bgcolor: alpha(palette.primary.main, 0.15),
|
||||
},
|
||||
})}
|
||||
>
|
||||
{delayManager.formatDelay(delay)}
|
||||
</Widget>
|
||||
)}
|
||||
</Box>
|
||||
</TestBox>
|
||||
|
||||
<Menu
|
||||
open={!!anchorEl}
|
||||
anchorEl={anchorEl}
|
||||
onClose={() => setAnchorEl(null)}
|
||||
anchorPosition={position}
|
||||
anchorReference="anchorPosition"
|
||||
transitionDuration={225}
|
||||
MenuListProps={{ sx: { py: 0.5 } }}
|
||||
onContextMenu={(e) => {
|
||||
setAnchorEl(null);
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
{menu.map((item) => (
|
||||
<MenuItem
|
||||
key={item.label}
|
||||
onClick={item.handler}
|
||||
sx={{ minWidth: 120 }}
|
||||
dense
|
||||
>
|
||||
{t(item.label)}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
const Widget = styled(Box)(({ theme: { typography } }) => ({
|
||||
padding: "3px 6px",
|
||||
fontSize: 14,
|
||||
fontFamily: typography.fontFamily,
|
||||
borderRadius: "4px",
|
||||
}));
|
||||
@@ -0,0 +1,154 @@
|
||||
import { forwardRef, useImperativeHandle, useState } from "react";
|
||||
import { useLockFn } from "ahooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
import { TextField } from "@mui/material";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { BaseDialog, Notice } from "@/components/base";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
interface Props {
|
||||
onChange: (uid: string, patch?: Partial<IVergeTestItem>) => void;
|
||||
}
|
||||
|
||||
export interface TestViewerRef {
|
||||
create: () => void;
|
||||
edit: (item: IVergeTestItem) => void;
|
||||
}
|
||||
|
||||
// create or edit the test item
|
||||
export const TestViewer = forwardRef<TestViewerRef, Props>((props, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [openType, setOpenType] = useState<"new" | "edit">("new");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { verge, patchVerge } = useVerge();
|
||||
const testList = verge?.test_list ?? [];
|
||||
const { control, watch, register, ...formIns } = useForm<IVergeTestItem>({
|
||||
defaultValues: {
|
||||
name: "",
|
||||
icon: "",
|
||||
url: "",
|
||||
},
|
||||
});
|
||||
|
||||
const patchTestList = async (uid: string, patch: Partial<IVergeTestItem>) => {
|
||||
const newList = testList.map((x) => {
|
||||
if (x.uid === uid) {
|
||||
return { ...x, ...patch };
|
||||
}
|
||||
return x;
|
||||
});
|
||||
await patchVerge({ ...verge, test_list: newList });
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
create: () => {
|
||||
setOpenType("new");
|
||||
setOpen(true);
|
||||
},
|
||||
edit: (item) => {
|
||||
if (item) {
|
||||
Object.entries(item).forEach(([key, value]) => {
|
||||
formIns.setValue(key as any, value);
|
||||
});
|
||||
}
|
||||
setOpenType("edit");
|
||||
setOpen(true);
|
||||
},
|
||||
}));
|
||||
|
||||
const handleOk = useLockFn(
|
||||
formIns.handleSubmit(async (form) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
if (!form.name) throw new Error("`Name` should not be null");
|
||||
if (!form.url) throw new Error("`Url` should not be null");
|
||||
let newList;
|
||||
let uid;
|
||||
|
||||
if (openType === "new") {
|
||||
uid = nanoid();
|
||||
const item = { ...form, uid };
|
||||
newList = [...testList, item];
|
||||
await patchVerge({ test_list: newList });
|
||||
props.onChange(uid);
|
||||
} else {
|
||||
if (!form.uid) throw new Error("UID not found");
|
||||
uid = form.uid;
|
||||
|
||||
await patchTestList(uid, form);
|
||||
props.onChange(uid, form);
|
||||
}
|
||||
setOpen(false);
|
||||
setLoading(false);
|
||||
setTimeout(() => formIns.reset(), 500);
|
||||
} catch (err: any) {
|
||||
Notice.error(err.message || err.toString());
|
||||
setLoading(false);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
setTimeout(() => formIns.reset(), 500);
|
||||
};
|
||||
|
||||
const text = {
|
||||
fullWidth: true,
|
||||
size: "small",
|
||||
margin: "normal",
|
||||
variant: "outlined",
|
||||
autoComplete: "off",
|
||||
autoCorrect: "off",
|
||||
} as const;
|
||||
|
||||
return (
|
||||
<BaseDialog
|
||||
open={open}
|
||||
title={openType === "new" ? t("Create Test") : t("Edit Test")}
|
||||
contentSx={{ width: 375, pb: 0, maxHeight: "80%" }}
|
||||
okBtn={t("Save")}
|
||||
cancelBtn={t("Cancel")}
|
||||
onClose={handleClose}
|
||||
onCancel={handleClose}
|
||||
onOk={handleOk}
|
||||
loading={loading}
|
||||
>
|
||||
<Controller
|
||||
name="name"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TextField {...text} {...field} label={t("Name")} />
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="icon"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...text}
|
||||
{...field}
|
||||
multiline
|
||||
maxRows={5}
|
||||
label={t("Icon")}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="url"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...text}
|
||||
{...field}
|
||||
multiline
|
||||
maxRows={3}
|
||||
label={t("Test URL")}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</BaseDialog>
|
||||
);
|
||||
});
|
||||