mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-19 16:10:46 +08:00
Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7965711945 | ||
|
|
396d343a3a | ||
|
|
b729e59650 | ||
|
|
a3cc58013b | ||
|
|
cef66cd745 | ||
|
|
3b7bfa10fd | ||
|
|
b238175f18 | ||
|
|
8f00096a9f | ||
|
|
9f498a85d0 | ||
|
|
e014b913d2 | ||
|
|
f38ceae3fb | ||
|
|
089635c8bf | ||
|
|
dc5a8a5ff4 | ||
|
|
476ddb63c3 | ||
|
|
c6b51171a1 | ||
|
|
d4287970bc | ||
|
|
b20fdc9b96 | ||
|
|
5b10331022 | ||
|
|
3ec858eb9f |
@@ -4,11 +4,11 @@
|
|||||||
|
|
||||||
"features": {
|
"features": {
|
||||||
"ghcr.io/devcontainers/features/node:2": {
|
"ghcr.io/devcontainers/features/node:2": {
|
||||||
"version": "24.21.0",
|
"version": "24.22.0",
|
||||||
"pnpmVersion": "12.11.2"
|
"pnpmVersion": "12.14.0"
|
||||||
},
|
},
|
||||||
"ghcr.io/devcontainers/features/rust:1": {
|
"ghcr.io/devcontainers/features/rust:1": {
|
||||||
"version": "1.99.0",
|
"version": "1.99.1",
|
||||||
"profile": "default"
|
"profile": "default"
|
||||||
},
|
},
|
||||||
"ghcr.io/devcontainers/features/git:1": {},
|
"ghcr.io/devcontainers/features/git:1": {},
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.21.0'
|
node-version: '24.22.0'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
@@ -154,7 +154,7 @@ jobs:
|
|||||||
- name: Install Rust Stable
|
- name: Install Rust Stable
|
||||||
uses: dtolnay/rust-toolchain@master
|
uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
toolchain: '1.99.0'
|
toolchain: '1.99.1'
|
||||||
targets: ${{ matrix.target }}
|
targets: ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Add Rust Target
|
- name: Add Rust Target
|
||||||
@@ -185,7 +185,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.21.0'
|
node-version: '24.22.0'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
- name: Pnpm Cache
|
- name: Pnpm Cache
|
||||||
@@ -206,7 +206,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Add Rust Target
|
- name: Add Rust Target
|
||||||
run: |
|
run: |
|
||||||
rustup target add ${{ matrix.target }} --toolchain 1.99.0 || rustup target add ${{ matrix.target }}
|
rustup target add ${{ matrix.target }} --toolchain 1.99.1 || rustup target add ${{ matrix.target }}
|
||||||
rustup target list --installed
|
rustup target list --installed
|
||||||
echo "Rust target ${{ matrix.target }} installed."
|
echo "Rust target ${{ matrix.target }} installed."
|
||||||
|
|
||||||
@@ -257,7 +257,7 @@ jobs:
|
|||||||
- name: Install Rust Stable
|
- name: Install Rust Stable
|
||||||
uses: dtolnay/rust-toolchain@master
|
uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
toolchain: '1.99.0'
|
toolchain: '1.99.1'
|
||||||
targets: ${{ matrix.target }}
|
targets: ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Add Rust Target
|
- name: Add Rust Target
|
||||||
@@ -282,7 +282,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.21.0'
|
node-version: '24.22.0'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
- name: Pnpm Cache
|
- name: Pnpm Cache
|
||||||
@@ -347,7 +347,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Add Rust Target
|
- name: Add Rust Target
|
||||||
run: |
|
run: |
|
||||||
rustup target add ${{ matrix.target }} --toolchain 1.99.0 || rustup target add ${{ matrix.target }}
|
rustup target add ${{ matrix.target }} --toolchain 1.99.1 || rustup target add ${{ matrix.target }}
|
||||||
rustup target list --installed
|
rustup target list --installed
|
||||||
echo "Rust target ${{ matrix.target }} installed."
|
echo "Rust target ${{ matrix.target }} installed."
|
||||||
|
|
||||||
@@ -426,7 +426,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.21.0'
|
node-version: '24.22.0'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
- name: Pnpm Cache
|
- name: Pnpm Cache
|
||||||
@@ -454,7 +454,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Add Rust Target
|
- name: Add Rust Target
|
||||||
run: |
|
run: |
|
||||||
rustup target add ${{ matrix.target }} --toolchain 1.99.0 || rustup target add ${{ matrix.target }}
|
rustup target add ${{ matrix.target }} --toolchain 1.99.1 || rustup target add ${{ matrix.target }}
|
||||||
rustup target list --installed
|
rustup target list --installed
|
||||||
echo "Rust target ${{ matrix.target }} installed."
|
echo "Rust target ${{ matrix.target }} installed."
|
||||||
|
|
||||||
@@ -524,7 +524,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.21.0'
|
node-version: '24.22.0'
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v6.1.0
|
- uses: pnpm/action-setup@v6.1.0
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.21.0'
|
node-version: '24.22.0'
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v6
|
- uses: pnpm/action-setup@v6
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Install Rust Stable
|
- name: Install Rust Stable
|
||||||
if: github.event.inputs[matrix.input] == 'true'
|
if: github.event.inputs[matrix.input] == 'true'
|
||||||
uses: dtolnay/rust-toolchain@1.99.0
|
uses: dtolnay/rust-toolchain@1.99.1
|
||||||
|
|
||||||
- name: Rust Cache
|
- name: Rust Cache
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
@@ -103,7 +103,7 @@ jobs:
|
|||||||
if: github.event.inputs[matrix.input] == 'true'
|
if: github.event.inputs[matrix.input] == 'true'
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.21.0'
|
node-version: '24.22.0'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
- name: Pnpm Cache
|
- name: Pnpm Cache
|
||||||
@@ -129,7 +129,7 @@ jobs:
|
|||||||
if: github.event.inputs[matrix.input] == 'true'
|
if: github.event.inputs[matrix.input] == 'true'
|
||||||
run: |
|
run: |
|
||||||
# Ensure cross target is installed for the pinned toolchain; fallback without explicit toolchain if needed
|
# Ensure cross target is installed for the pinned toolchain; fallback without explicit toolchain if needed
|
||||||
rustup target add ${{ matrix.target }} --toolchain 1.99.0 || rustup target add ${{ matrix.target }}
|
rustup target add ${{ matrix.target }} --toolchain 1.99.1 || rustup target add ${{ matrix.target }}
|
||||||
rustup target list --installed
|
rustup target list --installed
|
||||||
echo "Rust target ${{ matrix.target }} installed."
|
echo "Rust target ${{ matrix.target }} installed."
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ jobs:
|
|||||||
- uses: actions/setup-node@v7
|
- uses: actions/setup-node@v7
|
||||||
if: steps.check_frontend.outputs.frontend == 'true'
|
if: steps.check_frontend.outputs.frontend == 'true'
|
||||||
with:
|
with:
|
||||||
node-version: '24.21.0'
|
node-version: '24.22.0'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
- name: Restore pnpm cache
|
- name: Restore pnpm cache
|
||||||
@@ -138,7 +138,7 @@ jobs:
|
|||||||
- uses: actions/setup-node@v7
|
- uses: actions/setup-node@v7
|
||||||
if: steps.check_inputs.outputs.contract == 'true'
|
if: steps.check_inputs.outputs.contract == 'true'
|
||||||
with:
|
with:
|
||||||
node-version: '24.21.0'
|
node-version: '24.22.0'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
- run: pnpm install --frozen-lockfile
|
- run: pnpm install --frozen-lockfile
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ jobs:
|
|||||||
- name: Install Rust Stable
|
- name: Install Rust Stable
|
||||||
uses: dtolnay/rust-toolchain@master
|
uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
toolchain: '1.99.0'
|
toolchain: '1.99.1'
|
||||||
targets: ${{ matrix.target }}
|
targets: ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Add Rust Target
|
- name: Add Rust Target
|
||||||
@@ -230,7 +230,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.21.0'
|
node-version: '24.22.0'
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v6
|
- uses: pnpm/action-setup@v6
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
@@ -244,7 +244,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Add Rust Target
|
- name: Add Rust Target
|
||||||
run: |
|
run: |
|
||||||
rustup target add ${{ matrix.target }} --toolchain 1.99.0 || rustup target add ${{ matrix.target }}
|
rustup target add ${{ matrix.target }} --toolchain 1.99.1 || rustup target add ${{ matrix.target }}
|
||||||
rustup target list --installed
|
rustup target list --installed
|
||||||
echo "Rust target ${{ matrix.target }} installed."
|
echo "Rust target ${{ matrix.target }} installed."
|
||||||
|
|
||||||
@@ -311,7 +311,7 @@ jobs:
|
|||||||
- name: Install Rust Stable
|
- name: Install Rust Stable
|
||||||
uses: dtolnay/rust-toolchain@master
|
uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
toolchain: '1.99.0'
|
toolchain: '1.99.1'
|
||||||
targets: ${{ matrix.target }}
|
targets: ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Add Rust Target
|
- name: Add Rust Target
|
||||||
@@ -331,7 +331,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.21.0'
|
node-version: '24.22.0'
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v6
|
uses: pnpm/action-setup@v6
|
||||||
@@ -389,7 +389,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Add Rust Target
|
- name: Add Rust Target
|
||||||
run: |
|
run: |
|
||||||
rustup target add ${{ matrix.target }} --toolchain 1.99.0 || rustup target add ${{ matrix.target }}
|
rustup target add ${{ matrix.target }} --toolchain 1.99.1 || rustup target add ${{ matrix.target }}
|
||||||
rustup target list --installed
|
rustup target list --installed
|
||||||
echo "Rust target ${{ matrix.target }} installed."
|
echo "Rust target ${{ matrix.target }} installed."
|
||||||
|
|
||||||
@@ -446,7 +446,7 @@ jobs:
|
|||||||
- name: Install Rust Stable
|
- name: Install Rust Stable
|
||||||
uses: dtolnay/rust-toolchain@master
|
uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
toolchain: '1.99.0'
|
toolchain: '1.99.1'
|
||||||
targets: ${{ matrix.target }}
|
targets: ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Add Rust Target
|
- name: Add Rust Target
|
||||||
@@ -466,7 +466,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.21.0'
|
node-version: '24.22.0'
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v6
|
- uses: pnpm/action-setup@v6
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
@@ -487,7 +487,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Add Rust Target
|
- name: Add Rust Target
|
||||||
run: |
|
run: |
|
||||||
rustup target add ${{ matrix.target }} --toolchain 1.99.0 || rustup target add ${{ matrix.target }}
|
rustup target add ${{ matrix.target }} --toolchain 1.99.1 || rustup target add ${{ matrix.target }}
|
||||||
rustup target list --installed
|
rustup target list --installed
|
||||||
echo "Rust target ${{ matrix.target }} installed."
|
echo "Rust target ${{ matrix.target }} installed."
|
||||||
|
|
||||||
@@ -547,7 +547,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.21.0'
|
node-version: '24.22.0'
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v6
|
- uses: pnpm/action-setup@v6
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
@@ -573,7 +573,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.21.0'
|
node-version: '24.22.0'
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v6
|
- uses: pnpm/action-setup@v6
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
@@ -638,7 +638,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.21.0'
|
node-version: '24.22.0'
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v6
|
- uses: pnpm/action-setup@v6
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
|
|||||||
@@ -22,13 +22,13 @@ jobs:
|
|||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: dtolnay/rust-toolchain@master
|
uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
toolchain: '1.99.0'
|
toolchain: '1.99.1'
|
||||||
targets: x86_64-pc-windows-msvc
|
targets: x86_64-pc-windows-msvc
|
||||||
|
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.21.0'
|
node-version: '24.22.0'
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v6
|
uses: pnpm/action-setup@v6
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.21.0'
|
node-version: '24.22.0'
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v6
|
- uses: pnpm/action-setup@v6
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.21.0'
|
node-version: '24.22.0'
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v6
|
- uses: pnpm/action-setup@v6
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
@@ -39,7 +39,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.21.0'
|
node-version: '24.22.0'
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v6
|
- uses: pnpm/action-setup@v6
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
|
|||||||
+3
-3
@@ -1,3 +1,3 @@
|
|||||||
nodejs 24.21.0
|
nodejs 24.22.0
|
||||||
pnpm 12.11.2
|
pnpm 12.14.0
|
||||||
rust 1.99.0
|
rust 1.99.1
|
||||||
|
|||||||
Generated
+34
-31
@@ -1138,7 +1138,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_yaml_ng",
|
"serde_yaml_ng",
|
||||||
"sha2 0.11.0",
|
"sha2 0.11.1",
|
||||||
"smartstring",
|
"smartstring",
|
||||||
"socket2",
|
"socket2",
|
||||||
"specta",
|
"specta",
|
||||||
@@ -1175,7 +1175,7 @@ dependencies = [
|
|||||||
"windows-service",
|
"windows-service",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
"winreg 0.56.0",
|
"winreg 0.56.0",
|
||||||
"zip 9.0.1",
|
"zip 9.0.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1247,7 +1247,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2 0.11.0",
|
"sha2 0.11.1",
|
||||||
"strum",
|
"strum",
|
||||||
"strum_macros",
|
"strum_macros",
|
||||||
"sysproxy",
|
"sysproxy",
|
||||||
@@ -1462,9 +1462,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "constant_time_eq"
|
name = "constant_time_eq"
|
||||||
version = "0.4.2"
|
version = "0.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
|
checksum = "9eb6feb175df65d0a3ccd3e73484e940d0ac664e8034f0519fd49a93db3071d9"
|
||||||
|
dependencies = [
|
||||||
|
"rustversion",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cookie"
|
name = "cookie"
|
||||||
@@ -3951,9 +3954,9 @@ checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.190"
|
version = "0.2.191"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"
|
checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libdbus-sys"
|
name = "libdbus-sys"
|
||||||
@@ -4048,11 +4051,11 @@ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lzma-rust2"
|
name = "lzma-rust2"
|
||||||
version = "0.21.0"
|
version = "0.22.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fde178a3caf126c440fa15628147772d8ee590a39477d711353fbe2e58a73a5b"
|
checksum = "7277c9742229dc25243236d578b6471a798c554512c6f6a04365ec2e87c2a3bb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"sha2 0.11.0",
|
"sha2 0.11.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5343,9 +5346,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppmd-rust"
|
name = "ppmd-rust"
|
||||||
version = "1.4.0"
|
version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24"
|
checksum = "196a7c80b9a7652aba7cc070827516c2abe4ccdf53d128e1944003cf5726cff1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
@@ -6067,9 +6070,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.23.45"
|
version = "0.23.46"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634"
|
checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-lc-rs",
|
"aws-lc-rs",
|
||||||
"log",
|
"log",
|
||||||
@@ -6410,9 +6413,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.151"
|
version = "1.0.154"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -6597,9 +6600,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha2"
|
name = "sha2"
|
||||||
version = "0.11.0"
|
version = "0.11.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
|
checksum = "47d7069beb7d6ac7b9acd1039986e73443f24234f41074da099d6f994ac9ad19"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cpufeatures 0.3.0",
|
"cpufeatures 0.3.0",
|
||||||
@@ -7492,7 +7495,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-mihomo"
|
name = "tauri-plugin-mihomo"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
source = "git+https://github.com/clash-verge-rev/tauri-plugin-mihomo?branch=main#7a03d16f4d39b1bb885b7babe66b9e153e34b615"
|
source = "git+https://github.com/clash-verge-rev/tauri-plugin-mihomo?rev=7a03d16f4d39b1bb885b7babe66b9e153e34b615#7a03d16f4d39b1bb885b7babe66b9e153e34b615"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
"clashmap",
|
"clashmap",
|
||||||
@@ -8073,9 +8076,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.53.2"
|
version = "1.53.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e95f91fcc7a621e8b030f6aa23c71fe9838ae2fb4d8118b75602a328f5144044"
|
checksum = "ce3335fa71841cda333a58d7615b03901380ecf09d59b3296d21f8bbac0dde4e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -8135,9 +8138,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.19"
|
version = "0.7.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52"
|
checksum = "e464cf451ba96ebfc6f9b6542f17ee8b8956e33f1e40d9690624e59d7a7f8a4b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@@ -10150,9 +10153,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zip"
|
name = "zip"
|
||||||
version = "9.0.1"
|
version = "9.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e49ade69c4434c55dd651d36fe05f38e0578db948b773cbc01c48c4b5e560692"
|
checksum = "a954114b47a54df49f83d2722814e86b23d4bb1b54a1b90fb7d92b41619ddc22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"bzip2",
|
"bzip2",
|
||||||
@@ -10202,27 +10205,27 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zstd"
|
name = "zstd"
|
||||||
version = "0.13.3"
|
version = "0.14.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
|
checksum = "db3ae9bf90af6e40197bef767eeefc46ad6397420fe873830bd85e5fbbfb3c70"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zstd-safe",
|
"zstd-safe",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zstd-safe"
|
name = "zstd-safe"
|
||||||
version = "7.2.4"
|
version = "8.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
|
checksum = "ded520f16b29e1a9b771c722221b2b0a0398c2316d9bb3c42b458a5414d7434b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zstd-sys",
|
"zstd-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zstd-sys"
|
name = "zstd-sys"
|
||||||
version = "2.0.16+zstd.1.5.7"
|
version = "2.1.2+zstd.1.5.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748"
|
checksum = "481ada9c543b2323cbb7bbdbb5249177fbf0188ed959ed2c15e4b22421c84ed3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
|
|||||||
+2
-2
@@ -67,7 +67,7 @@ parking_lot = { version = "0.12.5", features = ["hardware-lock-elision"] }
|
|||||||
anyhow = "1.0.104"
|
anyhow = "1.0.104"
|
||||||
chrono = "0.4.45"
|
chrono = "0.4.45"
|
||||||
criterion = { version = "0.8.2", features = ["async_tokio"] }
|
criterion = { version = "0.8.2", features = ["async_tokio"] }
|
||||||
tokio = { version = "1.53.2", features = [
|
tokio = { version = "1.53.3", features = [
|
||||||
"rt-multi-thread",
|
"rt-multi-thread",
|
||||||
"macros",
|
"macros",
|
||||||
"time",
|
"time",
|
||||||
@@ -88,7 +88,7 @@ rust_iso3166 = "0.2.0"
|
|||||||
smartstring = { version = "1.0.1" }
|
smartstring = { version = "1.0.1" }
|
||||||
|
|
||||||
serde = { version = "1.0.229" }
|
serde = { version = "1.0.229" }
|
||||||
serde_json = { version = "1.0.151" }
|
serde_json = { version = "1.0.154" }
|
||||||
serde_yaml_ng = { version = "0.10.0" }
|
serde_yaml_ng = { version = "0.10.0" }
|
||||||
bitflags = { version = "2.13.2" }
|
bitflags = { version = "2.13.2" }
|
||||||
socket2 = "0.6.5"
|
socket2 = "0.6.5"
|
||||||
|
|||||||
+7
-3
@@ -3,8 +3,13 @@
|
|||||||
<details>
|
<details>
|
||||||
<summary><strong> 🐞 修复问题 </strong></summary>
|
<summary><strong> 🐞 修复问题 </strong></summary>
|
||||||
|
|
||||||
- 修复通过 URL 导入订阅时,即使导入的订阅并非当前使用的订阅,也会重新加载当前配置的问题
|
- 修复通过 URL 导入订阅时,即使导入的订阅并非当前使用的订阅,也会重新加载当前配置
|
||||||
- 修复「首页」网站测速失败后卡片一直显示测试中的问题
|
- 修复 首页 网站测速失败后卡片一直显示测试中
|
||||||
|
- 修复 代理 页展开分组后不显示最新测速结果,需切换节点才刷新
|
||||||
|
|
||||||
|
**🐧 Linux**
|
||||||
|
|
||||||
|
- Linux:修复 deb/rpm 安装包错误包含多余的 `frontend-contract` 可执行文件,导致下游重新打包失败的问题
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
@@ -17,7 +22,6 @@
|
|||||||
<details>
|
<details>
|
||||||
<summary><strong> 🚀 优化改进 </strong></summary>
|
<summary><strong> 🚀 优化改进 </strong></summary>
|
||||||
|
|
||||||
- 记住「启用系统标题栏」设置,Windows/macOS 即时生效,Linux 重启后生效
|
|
||||||
|
|
||||||
**🖥️ Windows**
|
**🖥️ Windows**
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
//! Linux-only termination signal bridge.
|
||||||
|
//!
|
||||||
|
//! Upstream Tauri delivers session-end and termination events to the app on
|
||||||
|
//! Windows and macOS, but has no equivalent on Linux: a desktop session may
|
||||||
|
//! end without any window or run event reaching the process. Linux builds
|
||||||
|
//! keep this crate to listen for SIGTERM, SIGINT and SIGHUP and forward them
|
||||||
|
//! to the app quit flow, so system proxy and TUN cleanup still runs before
|
||||||
|
//! the process dies. Non-Linux builds compile this crate to nothing and must
|
||||||
|
//! not depend on it.
|
||||||
|
|
||||||
|
#![cfg(target_os = "linux")]
|
||||||
|
|
||||||
use std::sync::{
|
use std::sync::{
|
||||||
OnceLock,
|
OnceLock,
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
@@ -5,20 +17,13 @@ use std::sync::{
|
|||||||
|
|
||||||
use clash_verge_logging::{Type, logging};
|
use clash_verge_logging::{Type, logging};
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
mod unix;
|
mod unix;
|
||||||
#[cfg(windows)]
|
|
||||||
mod windows;
|
|
||||||
|
|
||||||
pub(crate) static RUNTIME: OnceLock<Option<tokio::runtime::Runtime>> = OnceLock::new();
|
pub(crate) static RUNTIME: OnceLock<Option<tokio::runtime::Runtime>> = OnceLock::new();
|
||||||
pub(crate) static SHUTDOWN_LATCH: ShutdownLatch = ShutdownLatch::new();
|
pub(crate) static SHUTDOWN_LATCH: ShutdownLatch = ShutdownLatch::new();
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
/// Serializes signal-triggered cleanups: a signal arriving while a quit
|
||||||
pub enum ShutdownOutcome {
|
/// attempt is still in flight is dropped instead of overlapping it.
|
||||||
Committed,
|
|
||||||
Canceled,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct ShutdownLatch {
|
pub(crate) struct ShutdownLatch {
|
||||||
is_cleaning_up: AtomicBool,
|
is_cleaning_up: AtomicBool,
|
||||||
}
|
}
|
||||||
@@ -36,17 +41,18 @@ impl ShutdownLatch {
|
|||||||
.is_ok()
|
.is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(&self, outcome: ShutdownOutcome) {
|
fn finish(&self) {
|
||||||
if matches!(outcome, ShutdownOutcome::Canceled) {
|
self.is_cleaning_up.store(false, Ordering::Release);
|
||||||
self.is_cleaning_up.store(false, Ordering::Release);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Spawns the Linux signal loop on a dedicated runtime so it survives the
|
||||||
|
/// teardown of any app-owned runtime. The handler runs once per accepted
|
||||||
|
/// signal; the quit flow it invokes owns the exit-or-cancel decision.
|
||||||
pub fn register<F, Fut>(f: F)
|
pub fn register<F, Fut>(f: F)
|
||||||
where
|
where
|
||||||
F: Fn() -> Fut + Send + Sync + 'static,
|
F: Fn() -> Fut + Send + Sync + 'static,
|
||||||
Fut: Future<Output = ShutdownOutcome> + Send + 'static,
|
Fut: Future<Output = ()> + Send + 'static,
|
||||||
{
|
{
|
||||||
RUNTIME.get_or_init(|| match tokio::runtime::Runtime::new() {
|
RUNTIME.get_or_init(|| match tokio::runtime::Runtime::new() {
|
||||||
Ok(rt) => Some(rt),
|
Ok(rt) => Some(rt),
|
||||||
@@ -61,36 +67,22 @@ where
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
unix::register(f);
|
unix::register(f);
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
windows::register(f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{ShutdownLatch, ShutdownOutcome};
|
use super::ShutdownLatch;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn canceled_shutdown_releases_latch_and_permits_next_attempt() {
|
fn finished_attempt_releases_latch_for_the_next_signal() {
|
||||||
let latch = ShutdownLatch::new();
|
let latch = ShutdownLatch::new();
|
||||||
|
|
||||||
assert!(latch.try_begin());
|
assert!(latch.try_begin());
|
||||||
assert!(!latch.try_begin());
|
assert!(!latch.try_begin());
|
||||||
|
|
||||||
latch.finish(ShutdownOutcome::Canceled);
|
latch.finish();
|
||||||
|
|
||||||
assert!(latch.try_begin());
|
assert!(latch.try_begin());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn committed_shutdown_retains_latch_and_suppresses_repeats() {
|
|
||||||
let latch = ShutdownLatch::new();
|
|
||||||
|
|
||||||
assert!(latch.try_begin());
|
|
||||||
latch.finish(ShutdownOutcome::Committed);
|
|
||||||
|
|
||||||
assert!(!latch.try_begin());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
use clash_verge_logging::{Type, logging};
|
use clash_verge_logging::{Type, logging};
|
||||||
use tokio::signal::unix::{SignalKind, signal};
|
use tokio::signal::unix::{SignalKind, signal};
|
||||||
|
|
||||||
use crate::{RUNTIME, SHUTDOWN_LATCH, ShutdownOutcome};
|
use crate::{RUNTIME, SHUTDOWN_LATCH};
|
||||||
|
|
||||||
pub fn register<F, Fut>(f: F)
|
pub fn register<F, Fut>(f: F)
|
||||||
where
|
where
|
||||||
F: Fn() -> Fut + Send + Sync + 'static,
|
F: Fn() -> Fut + Send + Sync + 'static,
|
||||||
Fut: Future<Output = ShutdownOutcome> + Send + 'static,
|
Fut: Future<Output = ()> + Send + 'static,
|
||||||
{
|
{
|
||||||
if let Some(Some(rt)) = RUNTIME.get() {
|
if let Some(Some(rt)) = RUNTIME.get() {
|
||||||
rt.spawn(async move {
|
rt.spawn(async move {
|
||||||
@@ -60,7 +60,8 @@ where
|
|||||||
}
|
}
|
||||||
logging!(info, Type::SystemSignal, "Caught signal {}", signal_name);
|
logging!(info, Type::SystemSignal, "Caught signal {}", signal_name);
|
||||||
|
|
||||||
SHUTDOWN_LATCH.finish(f().await);
|
f().await;
|
||||||
|
SHUTDOWN_LATCH.finish();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
use clash_verge_logging::{Type, logging};
|
|
||||||
use tokio::signal::windows;
|
|
||||||
|
|
||||||
use crate::{RUNTIME, SHUTDOWN_LATCH, ShutdownOutcome};
|
|
||||||
|
|
||||||
pub fn register<F, Fut>(f: F)
|
|
||||||
where
|
|
||||||
F: Fn() -> Fut + Send + Sync + 'static,
|
|
||||||
Fut: Future<Output = ShutdownOutcome> + Send + 'static,
|
|
||||||
{
|
|
||||||
if let Some(Some(rt)) = RUNTIME.get() {
|
|
||||||
rt.spawn(async move {
|
|
||||||
let mut ctrl_c = match windows::ctrl_c() {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(e) => {
|
|
||||||
logging!(error, Type::SystemSignal, "Failed to register Ctrl+C: {}", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut ctrl_close = match windows::ctrl_close() {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(e) => {
|
|
||||||
logging!(error, Type::SystemSignal, "Failed to register Ctrl+Close: {}", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut ctrl_shutdown = match windows::ctrl_shutdown() {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(e) => {
|
|
||||||
logging!(error, Type::SystemSignal, "Failed to register Ctrl+Shutdown: {}", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut ctrl_logoff = match windows::ctrl_logoff() {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(e) => {
|
|
||||||
logging!(error, Type::SystemSignal, "Failed to register Ctrl+Logoff: {}", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let signal_name;
|
|
||||||
tokio::select! {
|
|
||||||
_ = ctrl_c.recv() => {
|
|
||||||
signal_name = "Ctrl+C";
|
|
||||||
}
|
|
||||||
_ = ctrl_close.recv() => {
|
|
||||||
signal_name = "Ctrl+Close";
|
|
||||||
}
|
|
||||||
_ = ctrl_shutdown.recv() => {
|
|
||||||
signal_name = "Ctrl+Shutdown";
|
|
||||||
}
|
|
||||||
_ = ctrl_logoff.recv() => {
|
|
||||||
signal_name = "Ctrl+Logoff";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !SHUTDOWN_LATCH.try_begin() {
|
|
||||||
logging!(
|
|
||||||
info,
|
|
||||||
Type::SystemSignal,
|
|
||||||
"Already shutting down, ignoring repeated signal: {}",
|
|
||||||
signal_name
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
logging!(info, Type::SystemSignal, "Caught Windows signal: {}", signal_name);
|
|
||||||
|
|
||||||
SHUTDOWN_LATCH.finish(f().await);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
logging!(
|
|
||||||
error,
|
|
||||||
Type::SystemSignal,
|
|
||||||
"register shutdown signal failed, RUNTIME is not available"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -15,7 +15,7 @@ serde = { workspace = true, features = ["derive"] }
|
|||||||
sysinfo = { version = "0.39.6", features = ["network", "system"] }
|
sysinfo = { version = "0.39.6", features = ["network", "system"] }
|
||||||
|
|
||||||
[target.'cfg(not(windows))'.dependencies]
|
[target.'cfg(not(windows))'.dependencies]
|
||||||
libc = "0.2.190"
|
libc = "0.2.191"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
deelevate = { workspace = true }
|
deelevate = { workspace = true }
|
||||||
|
|||||||
+6
-6
@@ -75,11 +75,11 @@
|
|||||||
"monaco-yaml": "^5.5.1",
|
"monaco-yaml": "^5.5.1",
|
||||||
"nanoid": "^6.0.1",
|
"nanoid": "^6.0.1",
|
||||||
"qrcode.react": "^4.2.0",
|
"qrcode.react": "^4.2.0",
|
||||||
"react": "19.3.0",
|
"react": "19.3.1",
|
||||||
"react-dom": "19.3.0",
|
"react-dom": "19.3.1",
|
||||||
"react-error-boundary": "6.1.6",
|
"react-error-boundary": "6.1.6",
|
||||||
"react-hook-form": "^7.88.0",
|
"react-hook-form": "^7.88.0",
|
||||||
"react-i18next": "17.0.15",
|
"react-i18next": "17.0.19",
|
||||||
"react-markdown": "10.1.0",
|
"react-markdown": "10.1.0",
|
||||||
"react-router": "^8.4.0",
|
"react-router": "^8.4.0",
|
||||||
"rehype-raw": "^7.0.0",
|
"rehype-raw": "^7.0.0",
|
||||||
@@ -123,7 +123,7 @@
|
|||||||
"https-proxy-agent": "^9.1.0",
|
"https-proxy-agent": "^9.1.0",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"jiti": "^2.7.0",
|
"jiti": "^2.7.0",
|
||||||
"knip": "6.39.0",
|
"knip": "6.41.0",
|
||||||
"lint-staged": "^17.5.1",
|
"lint-staged": "^17.5.1",
|
||||||
"sass": "^1.104.1",
|
"sass": "^1.104.1",
|
||||||
"tar": "^7.5.22",
|
"tar": "^7.5.22",
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
"vite": "^8.3.0",
|
"vite": "^8.3.0",
|
||||||
"vite-plugin-svgr": "^5.2.0",
|
"vite-plugin-svgr": "^5.2.0",
|
||||||
"vitest": "^5.0.1",
|
"vitest": "^5.0.1",
|
||||||
"webdriverio": "10.0.0",
|
"webdriverio": "10.0.2",
|
||||||
"ws": "8.22.0"
|
"ws": "8.22.0"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
@@ -146,5 +146,5 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "pnpm@12.11.2"
|
"packageManager": "pnpm@12.14.0"
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+313
-309
File diff suppressed because it is too large
Load Diff
@@ -45,6 +45,17 @@
|
|||||||
"matchManagers": ["npm"],
|
"matchManagers": ["npm"],
|
||||||
"groupName": "npm dependencies"
|
"groupName": "npm dependencies"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Update Tauri npm packages and Cargo crates together so frontend and backend stay in sync",
|
||||||
|
"matchPackageNames": [
|
||||||
|
"@tauri-apps/**",
|
||||||
|
"@wdio/tauri-service",
|
||||||
|
"tauri",
|
||||||
|
"tauri-*"
|
||||||
|
],
|
||||||
|
"groupName": "tauri",
|
||||||
|
"addLabels": ["tauri"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Group all GitHub Actions updates into a single PR",
|
"description": "Group all GitHub Actions updates into a single PR",
|
||||||
"matchManagers": ["github-actions"],
|
"matchManagers": ["github-actions"],
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "1.99.0"
|
channel = "1.99.1"
|
||||||
components = ["rustfmt", "clippy"]
|
components = ["rustfmt", "clippy"]
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ try {
|
|||||||
'run',
|
'run',
|
||||||
'-p',
|
'-p',
|
||||||
'clash-verge',
|
'clash-verge',
|
||||||
'--bin',
|
'--example',
|
||||||
'frontend-contract',
|
'frontend-contract',
|
||||||
'--locked',
|
'--locked',
|
||||||
'-q',
|
'-q',
|
||||||
|
|||||||
+10
-7
@@ -35,7 +35,6 @@ tauri-build = { version = "2.7.1", features = [] }
|
|||||||
clash-verge-draft = { workspace = true }
|
clash-verge-draft = { workspace = true }
|
||||||
clash-verge-media-unlock = { workspace = true }
|
clash-verge-media-unlock = { workspace = true }
|
||||||
clash-verge-logging = { workspace = true }
|
clash-verge-logging = { workspace = true }
|
||||||
clash-verge-signal = { workspace = true }
|
|
||||||
clash-verge-i18n = { workspace = true }
|
clash-verge-i18n = { workspace = true }
|
||||||
clash-verge-limiter = { workspace = true }
|
clash-verge-limiter = { workspace = true }
|
||||||
tauri-plugin-clash-verge-sysinfo = { workspace = true }
|
tauri-plugin-clash-verge-sysinfo = { workspace = true }
|
||||||
@@ -80,13 +79,13 @@ tauri-plugin-fs = "2.6.0"
|
|||||||
tauri-plugin-process = "2.4.0"
|
tauri-plugin-process = "2.4.0"
|
||||||
tauri-plugin-deep-link = "2.6.1"
|
tauri-plugin-deep-link = "2.6.1"
|
||||||
tauri-plugin-window-state = "2.5.0"
|
tauri-plugin-window-state = "2.5.0"
|
||||||
zip = "9.0.1"
|
zip = "9.0.4"
|
||||||
flate2 = "1.1.10"
|
flate2 = "1.1.10"
|
||||||
reqwest_dav = "0.3.3"
|
reqwest_dav = "0.3.3"
|
||||||
aes-gcm = { version = "0.11.1" }
|
aes-gcm = { version = "0.11.1" }
|
||||||
base64 = "0.23.1"
|
base64 = "0.23.1"
|
||||||
minisign-verify = "0.3.0"
|
minisign-verify = "0.3.0"
|
||||||
sha2 = "0.11"
|
sha2 = "0.11.1"
|
||||||
getrandom = "0.4.3"
|
getrandom = "0.4.3"
|
||||||
futures = "0.3.34"
|
futures = "0.3.34"
|
||||||
gethostname = "1.1.0"
|
gethostname = "1.1.0"
|
||||||
@@ -97,24 +96,28 @@ backon = { version = "1.6.0", features = ["tokio-sleep"] }
|
|||||||
tauri-plugin-http = "2.8.0"
|
tauri-plugin-http = "2.8.0"
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
tauri-plugin-devtools = { version = "2.2.2", optional = true }
|
tauri-plugin-devtools = { version = "2.2.2", optional = true }
|
||||||
tauri-plugin-mihomo = { git = "https://github.com/clash-verge-rev/tauri-plugin-mihomo", branch = "main" }
|
tauri-plugin-mihomo = { git = "https://github.com/clash-verge-rev/tauri-plugin-mihomo", rev = "7a03d16f4d39b1bb885b7babe66b9e153e34b615" }
|
||||||
async-trait = "0.1.92"
|
async-trait = "0.1.92"
|
||||||
clash_verge_service_ipc = { git = "https://github.com/clash-verge-rev/clash-verge-service-ipc.git", tag = "v2.7.6", version = "2.7.6", features = [
|
clash_verge_service_ipc = { git = "https://github.com/clash-verge-rev/clash-verge-service-ipc.git", tag = "v2.7.6", version = "2.7.6", features = [
|
||||||
"client",
|
"client",
|
||||||
] }
|
] }
|
||||||
arc-swap = "1.9.2"
|
arc-swap = "1.9.2"
|
||||||
tokio-rustls = "0.26.6"
|
tokio-rustls = "0.26.6"
|
||||||
rustls = { version = "0.23.45", features = ["ring"] }
|
rustls = { version = "0.23.46", features = ["ring"] }
|
||||||
webpki-roots = "1.0.9"
|
webpki-roots = "1.0.9"
|
||||||
dark-light = "3.0.0"
|
dark-light = "3.0.0"
|
||||||
bytes = "1.12.1"
|
bytes = "1.12.1"
|
||||||
tokio-util = { version = "0.7.19", features = ["time"] }
|
tokio-util = { version = "0.7.20", features = ["time"] }
|
||||||
specta = { version = "=2.0.0-rc.25", features = ["derive", "function", "serde_json"] }
|
specta = { version = "=2.0.0-rc.25", features = ["derive", "function", "serde_json"] }
|
||||||
tauri-specta = { version = "=2.0.0-rc.25", features = ["derive", "typescript"] }
|
tauri-specta = { version = "=2.0.0-rc.25", features = ["derive", "typescript"] }
|
||||||
specta-typescript = "=0.0.12"
|
specta-typescript = "=0.0.12"
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
libc = "0.2.190"
|
libc = "0.2.191"
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
|
# Upstream has no session-end signal support on Linux; see crates/clash-verge-signal.
|
||||||
|
clash-verge-signal = { workspace = true }
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
tauri-plugin-wdio-webdriver = { version = "=1.4.0", optional = true }
|
tauri-plugin-wdio-webdriver = { version = "=1.4.0", optional = true }
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// Dev-only target: Tauri ships everything under src/bin/ to /usr/bin in deb/rpm
|
||||||
|
// bundles (PR #8131), so this generator must stay an example, never a bin.
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let mut args = std::env::args_os().skip(1);
|
let mut args = std::env::args_os().skip(1);
|
||||||
let output = args
|
let output = args
|
||||||
@@ -282,7 +282,9 @@ async fn handle_validation_failure(
|
|||||||
) -> CmdResult<ValidationOutcome> {
|
) -> CmdResult<ValidationOutcome> {
|
||||||
logging!(warn, Type::Cmd, "配置验证失败: {}", outcome);
|
logging!(warn, Type::Cmd, "配置验证失败: {}", outcome);
|
||||||
discard_and_restore(current_profile).await?;
|
discard_and_restore(current_profile).await?;
|
||||||
handle_validation_notice(&outcome, ValidationNoticeTarget::Runtime, "运行时配置");
|
if !matches!(outcome, ValidationOutcome::Busy) {
|
||||||
|
handle_validation_notice(&outcome, ValidationNoticeTarget::Runtime, "运行时配置");
|
||||||
|
}
|
||||||
Ok(outcome)
|
Ok(outcome)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,9 +313,6 @@ async fn perform_config_update(
|
|||||||
current_value: Option<&String>,
|
current_value: Option<&String>,
|
||||||
current_profile: Option<&String>,
|
current_profile: Option<&String>,
|
||||||
) -> CmdResult<ValidationOutcome> {
|
) -> CmdResult<ValidationOutcome> {
|
||||||
defer! {
|
|
||||||
CURRENT_SWITCHING_PROFILE.store(false, Ordering::Release);
|
|
||||||
}
|
|
||||||
let update_result = run_profile_config_update_transition(|| CoreManager::global().update_config_forced()).await;
|
let update_result = run_profile_config_update_transition(|| CoreManager::global().update_config_forced()).await;
|
||||||
|
|
||||||
match update_result {
|
match update_result {
|
||||||
@@ -409,6 +408,9 @@ pub async fn patch_profiles_config(profiles: ProfileSelection) -> CmdResult<Vali
|
|||||||
logging!(info, Type::Cmd, "当前正在切换配置,放弃请求");
|
logging!(info, Type::Cmd, "当前正在切换配置,放弃请求");
|
||||||
return Ok(ValidationOutcome::Busy);
|
return Ok(ValidationOutcome::Busy);
|
||||||
}
|
}
|
||||||
|
defer! {
|
||||||
|
CURRENT_SWITCHING_PROFILE.store(false, Ordering::Release);
|
||||||
|
}
|
||||||
let _profile_write_guard = PROFILE_WRITE_LOCK.lock().await;
|
let _profile_write_guard = PROFILE_WRITE_LOCK.lock().await;
|
||||||
|
|
||||||
let profiles = IProfiles {
|
let profiles = IProfiles {
|
||||||
|
|||||||
@@ -73,9 +73,6 @@ pub struct IVerge {
|
|||||||
|
|
||||||
pub collapse_navbar: Option<bool>,
|
pub collapse_navbar: Option<bool>,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub enable_system_titlebar: Option<bool>,
|
|
||||||
|
|
||||||
pub sysproxy_tray_icon: Option<bool>,
|
pub sysproxy_tray_icon: Option<bool>,
|
||||||
|
|
||||||
pub tun_tray_icon: Option<bool>,
|
pub tun_tray_icon: Option<bool>,
|
||||||
@@ -377,7 +374,6 @@ impl IVerge {
|
|||||||
menu_icon: Some("monochrome".into()),
|
menu_icon: Some("monochrome".into()),
|
||||||
notice_position: Some("top-right".into()),
|
notice_position: Some("top-right".into()),
|
||||||
collapse_navbar: Some(false),
|
collapse_navbar: Some(false),
|
||||||
enable_system_titlebar: Some(true),
|
|
||||||
common_tray_icon: Some(false),
|
common_tray_icon: Some(false),
|
||||||
sysproxy_tray_icon: Some(false),
|
sysproxy_tray_icon: Some(false),
|
||||||
tun_tray_icon: Some(false),
|
tun_tray_icon: Some(false),
|
||||||
@@ -465,7 +461,6 @@ impl IVerge {
|
|||||||
patch!(proxy_group_tools_position);
|
patch!(proxy_group_tools_position);
|
||||||
patch!(notice_position);
|
patch!(notice_position);
|
||||||
patch!(collapse_navbar);
|
patch!(collapse_navbar);
|
||||||
patch!(enable_system_titlebar);
|
|
||||||
patch!(common_tray_icon);
|
patch!(common_tray_icon);
|
||||||
patch!(sysproxy_tray_icon);
|
patch!(sysproxy_tray_icon);
|
||||||
patch!(tun_tray_icon);
|
patch!(tun_tray_icon);
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ verge_registry! {
|
|||||||
proxy_group_tools_position => NoEffect;
|
proxy_group_tools_position => NoEffect;
|
||||||
notice_position => NoEffect;
|
notice_position => NoEffect;
|
||||||
collapse_navbar => NoEffect;
|
collapse_navbar => NoEffect;
|
||||||
enable_system_titlebar => NoEffect;
|
|
||||||
sysproxy_tray_icon => [TrayIcon];
|
sysproxy_tray_icon => [TrayIcon];
|
||||||
tun_tray_icon => [TrayIcon];
|
tun_tray_icon => [TrayIcon];
|
||||||
enable_tun_mode => Tun;
|
enable_tun_mode => Tun;
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ pub async fn open_or_close_dashboard() {
|
|||||||
logging!(info, Type::Window, "Window toggle result: {result:?}");
|
logging!(info, Type::Window, "Window toggle result: {result:?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn quit() -> clash_verge_signal::ShutdownOutcome {
|
pub async fn quit() {
|
||||||
logging!(debug, Type::System, "启动退出流程");
|
logging!(debug, Type::System, "启动退出流程");
|
||||||
// 设置退出标志
|
// 设置退出标志
|
||||||
handle::Handle::global().set_is_exiting();
|
handle::Handle::global().set_is_exiting();
|
||||||
@@ -165,13 +165,11 @@ pub async fn quit() -> clash_verge_signal::ShutdownOutcome {
|
|||||||
NoticeStatus::AppQuitCoreStopFailed,
|
NoticeStatus::AppQuitCoreStopFailed,
|
||||||
cleanup_result.stop_error.unwrap_or_default(),
|
cleanup_result.stop_error.unwrap_or_default(),
|
||||||
);
|
);
|
||||||
return clash_verge_signal::ShutdownOutcome::Canceled;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
utils::server::shutdown_embedded_server();
|
utils::server::shutdown_embedded_server();
|
||||||
let app_handle = handle::Handle::app_handle();
|
let app_handle = handle::Handle::app_handle();
|
||||||
app_handle.exit(if cleanup_result.all_success { 0 } else { 1 });
|
app_handle.exit(if cleanup_result.all_success { 0 } else { 1 });
|
||||||
clash_verge_signal::ShutdownOutcome::Committed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn clean_async() -> CleanupResult {
|
pub async fn clean_async() -> CleanupResult {
|
||||||
|
|||||||
@@ -116,13 +116,9 @@ mod app_init {
|
|||||||
/// Setup window state management
|
/// Setup window state management
|
||||||
pub fn setup_window_state(app: &tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn setup_window_state(app: &tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
logging!(debug, Type::Setup, "初始化窗口状态管理...");
|
logging!(debug, Type::Setup, "初始化窗口状态管理...");
|
||||||
// Decorations are config-driven (`enable_system_titlebar`); restoring a stale
|
|
||||||
// value here would override the builder's `.decorations(...)` on window recreate.
|
|
||||||
let state_flags =
|
|
||||||
tauri_plugin_window_state::StateFlags::all() & !tauri_plugin_window_state::StateFlags::DECORATIONS;
|
|
||||||
let window_state_plugin = tauri_plugin_window_state::Builder::new()
|
let window_state_plugin = tauri_plugin_window_state::Builder::new()
|
||||||
.with_filename(files::WINDOW_STATE)
|
.with_filename(files::WINDOW_STATE)
|
||||||
.with_state_flags(state_flags)
|
.with_state_flags(tauri_plugin_window_state::StateFlags::default())
|
||||||
.build();
|
.build();
|
||||||
app.handle().plugin(window_state_plugin)?;
|
app.handle().plugin(window_state_plugin)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -234,6 +230,7 @@ pub fn run() -> std::process::ExitCode {
|
|||||||
if let Err(panic) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
if let Err(panic) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||||
resolve::resolve_setup_async();
|
resolve::resolve_setup_async();
|
||||||
resolve::resolve_setup_sync();
|
resolve::resolve_setup_sync();
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
resolve::init_signal();
|
resolve::init_signal();
|
||||||
})) {
|
})) {
|
||||||
log_setup_panic("window-core", panic);
|
log_setup_panic("window-core", panic);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ fn main() -> ExitCode {
|
|||||||
#[cfg(not(all(feature = "perf-harness", target_os = "macos")))]
|
#[cfg(not(all(feature = "perf-harness", target_os = "macos")))]
|
||||||
fn main() -> ExitCode {
|
fn main() -> ExitCode {
|
||||||
let default_parallelism = std::thread::available_parallelism().map(|n| n.get()).unwrap_or(1);
|
let default_parallelism = std::thread::available_parallelism().map(|n| n.get()).unwrap_or(1);
|
||||||
let worker_limit = std::cmp::min(default_parallelism, 8);
|
let worker_limit = std::cmp::min(default_parallelism, 4);
|
||||||
let blocking_limit = 2 * worker_limit;
|
let blocking_limit = 2 * worker_limit;
|
||||||
|
|
||||||
#[allow(clippy::unwrap_used)]
|
#[allow(clippy::unwrap_used)]
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ use crate::{
|
|||||||
utils::{init, server, window_manager::WindowManager},
|
utils::{init, server, window_manager::WindowManager},
|
||||||
};
|
};
|
||||||
use clash_verge_logging::{Type, logging, logging_error};
|
use clash_verge_logging::{Type, logging, logging_error};
|
||||||
use clash_verge_signal;
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub mod dns;
|
pub mod dns;
|
||||||
@@ -189,6 +188,8 @@ async fn init_silent_updater() {
|
|||||||
logging!(info, Type::Setup, "Silent updater initialized");
|
logging!(info, Type::Setup, "Silent updater initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Upstream has no session-end signal support on Linux; see crates/clash-verge-signal.
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
pub(crate) fn init_signal() {
|
pub(crate) fn init_signal() {
|
||||||
logging!(debug, Type::Setup, "Initializing signal handlers...");
|
logging!(debug, Type::Setup, "Initializing signal handlers...");
|
||||||
clash_verge_signal::register(feat::quit);
|
clash_verge_signal::register(feat::quit);
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ const DEFAULT_HEIGHT: f64 = 700.0;
|
|||||||
const MINIMAL_WIDTH: f64 = 520.0;
|
const MINIMAL_WIDTH: f64 = 520.0;
|
||||||
const MINIMAL_HEIGHT: f64 = 520.0;
|
const MINIMAL_HEIGHT: f64 = 520.0;
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
const DEFAULT_DECORATIONS: bool = false;
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
const DEFAULT_DECORATIONS: bool = true;
|
||||||
|
|
||||||
const fn restored_window_size_is_too_small(width: u32, height: u32) -> bool {
|
const fn restored_window_size_is_too_small(width: u32, height: u32) -> bool {
|
||||||
width < MINIMAL_WIDTH as u32 || height < MINIMAL_HEIGHT as u32
|
width < MINIMAL_WIDTH as u32 || height < MINIMAL_HEIGHT as u32
|
||||||
}
|
}
|
||||||
@@ -44,7 +49,6 @@ pub async fn build_new_window() -> Result<WebviewWindow, String> {
|
|||||||
|
|
||||||
let config = Config::verge().await;
|
let config = Config::verge().await;
|
||||||
let latest = config.latest_arc();
|
let latest = config.latest_arc();
|
||||||
let decorations = latest.enable_system_titlebar.unwrap_or(true);
|
|
||||||
let start_page = latest.start_page.as_deref().unwrap_or("/");
|
let start_page = latest.start_page.as_deref().unwrap_or("/");
|
||||||
let initial_theme_mode = match latest.theme_mode.as_deref() {
|
let initial_theme_mode = match latest.theme_mode.as_deref() {
|
||||||
Some("dark") => "dark",
|
Some("dark") => "dark",
|
||||||
@@ -79,7 +83,7 @@ pub async fn build_new_window() -> Result<WebviewWindow, String> {
|
|||||||
)
|
)
|
||||||
.title("Clash Verge")
|
.title("Clash Verge")
|
||||||
.center()
|
.center()
|
||||||
.decorations(decorations)
|
.decorations(DEFAULT_DECORATIONS)
|
||||||
.fullscreen(false)
|
.fullscreen(false)
|
||||||
.inner_size(DEFAULT_WIDTH, DEFAULT_HEIGHT)
|
.inner_size(DEFAULT_WIDTH, DEFAULT_HEIGHT)
|
||||||
.min_inner_size(MINIMAL_WIDTH, MINIMAL_HEIGHT)
|
.min_inner_size(MINIMAL_WIDTH, MINIMAL_HEIGHT)
|
||||||
|
|||||||
@@ -39,8 +39,6 @@ static INSTANCE_LOCK: OnceLock<std::fs::File> = OnceLock::new();
|
|||||||
const PAC_INITIAL_AVAILABLE: bool = false;
|
const PAC_INITIAL_AVAILABLE: bool = false;
|
||||||
static PAC_AVAILABLE: AtomicBool = AtomicBool::new(PAC_INITIAL_AVAILABLE);
|
static PAC_AVAILABLE: AtomicBool = AtomicBool::new(PAC_INITIAL_AVAILABLE);
|
||||||
static COMMANDS_READY: AtomicBool = AtomicBool::new(false);
|
static COMMANDS_READY: AtomicBool = AtomicBool::new(false);
|
||||||
#[cfg(feature = "verge-dev")]
|
|
||||||
static DEV_QUIT_REQUESTED: AtomicBool = AtomicBool::new(false);
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum SingletonDisposition {
|
pub enum SingletonDisposition {
|
||||||
@@ -164,55 +162,12 @@ fn instance_auth(token: String) -> impl warp::Filter<Extract = (), Error = warp:
|
|||||||
.untuple_one()
|
.untuple_one()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "verge-dev")]
|
|
||||||
fn dev_quit_route<F>(
|
|
||||||
token: String,
|
|
||||||
commands_ready: &'static AtomicBool,
|
|
||||||
quit_requested: &'static AtomicBool,
|
|
||||||
trigger: F,
|
|
||||||
) -> impl warp::Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone
|
|
||||||
where
|
|
||||||
F: Fn() + Clone + Send + Sync + 'static,
|
|
||||||
{
|
|
||||||
instance_auth(token)
|
|
||||||
.and(warp::path!("commands" / "dev" / "quit"))
|
|
||||||
.and(warp::post())
|
|
||||||
.and_then(move || {
|
|
||||||
let trigger = trigger.clone();
|
|
||||||
async move {
|
|
||||||
if !commands_ready.load(Ordering::Acquire) {
|
|
||||||
return Ok::<_, warp::Rejection>(warp::reply::with_status(
|
|
||||||
String::from("starting"),
|
|
||||||
warp::http::StatusCode::SERVICE_UNAVAILABLE,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
if quit_requested
|
|
||||||
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
|
|
||||||
.is_ok()
|
|
||||||
{
|
|
||||||
trigger();
|
|
||||||
}
|
|
||||||
Ok(warp::reply::with_status(
|
|
||||||
String::from("accepted"),
|
|
||||||
warp::http::StatusCode::ACCEPTED,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "verge-dev")]
|
|
||||||
fn release_dev_quit_latch(quit_requested: &AtomicBool, outcome: clash_verge_signal::ShutdownOutcome) {
|
|
||||||
if matches!(outcome, clash_verge_signal::ShutdownOutcome::Canceled) {
|
|
||||||
quit_requested.store(false, Ordering::Release);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start_embedded_server(listener: tokio::net::TcpListener, token: String) {
|
fn start_embedded_server(listener: tokio::net::TcpListener, token: String) {
|
||||||
let (shutdown_tx, shutdown_rx) = oneshot::channel();
|
let (shutdown_tx, shutdown_rx) = oneshot::channel();
|
||||||
let _ = SHUTDOWN_SENDER.set(Mutex::new(Some(shutdown_tx)));
|
let _ = SHUTDOWN_SENDER.set(Mutex::new(Some(shutdown_tx)));
|
||||||
|
|
||||||
#[cfg(feature = "verge-dev")]
|
#[cfg(feature = "verge-dev")]
|
||||||
let auth = instance_auth(token.clone());
|
let auth = instance_auth(token);
|
||||||
#[cfg(not(feature = "verge-dev"))]
|
#[cfg(not(feature = "verge-dev"))]
|
||||||
let auth = instance_auth(token);
|
let auth = instance_auth(token);
|
||||||
|
|
||||||
@@ -278,12 +233,6 @@ fn start_embedded_server(listener: tokio::net::TcpListener, token: String) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let commands = auth.clone().and(visible).or(auth.and(scheme)).or(pac);
|
let commands = auth.clone().and(visible).or(auth.and(scheme)).or(pac);
|
||||||
#[cfg(feature = "verge-dev")]
|
|
||||||
let commands = commands.or(dev_quit_route(token, &COMMANDS_READY, &DEV_QUIT_REQUESTED, || {
|
|
||||||
AsyncHandler::spawn(|| async {
|
|
||||||
release_dev_quit_latch(&DEV_QUIT_REQUESTED, crate::feat::quit().await);
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
AsyncHandler::spawn(move || async move {
|
AsyncHandler::spawn(move || async move {
|
||||||
warp::serve(commands)
|
warp::serve(commands)
|
||||||
.incoming(listener)
|
.incoming(listener)
|
||||||
|
|||||||
@@ -27,8 +27,12 @@ const TRAY_STATUS_ITEM_EXTRA_WIDTH: f64 = 30.0;
|
|||||||
const TRAY_STATUS_ITEM_MIN_LENGTH: f64 = 58.0;
|
const TRAY_STATUS_ITEM_MIN_LENGTH: f64 = 58.0;
|
||||||
const NS_VARIABLE_STATUS_ITEM_LENGTH: f64 = -1.0;
|
const NS_VARIABLE_STATUS_ITEM_LENGTH: f64 = -1.0;
|
||||||
|
|
||||||
|
/// (button_height, mono_advance, attributes).
|
||||||
|
type CachedAttrs = (f64, f64, Retained<NSDictionary<NSString, AnyObject>>);
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static LAST_DISPLAY_STR: RefCell<String> = const { RefCell::new(String::new()) };
|
static LAST_DISPLAY_STR: RefCell<String> = const { RefCell::new(String::new()) };
|
||||||
|
static CACHED_ATTRS: RefCell<Option<CachedAttrs>> = const { RefCell::new(None) };
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_tray_speed(up: u64, down: u64) -> String {
|
fn format_tray_speed(up: u64, down: u64) -> String {
|
||||||
@@ -74,8 +78,26 @@ fn create_attributed_string(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn status_item_length_for_speed(attr_str: &NSAttributedString) -> f64 {
|
/// Attributes and per-glyph advance, rebuilt only when the button height changes.
|
||||||
(attr_str.size().width.ceil() + TRAY_STATUS_ITEM_EXTRA_WIDTH).max(TRAY_STATUS_ITEM_MIN_LENGTH)
|
fn cached_attributes(button_height: f64) -> (f64, Retained<NSDictionary<NSString, AnyObject>>) {
|
||||||
|
CACHED_ATTRS.with(|cache| {
|
||||||
|
if let Some((height, advance, attrs)) = cache.borrow().as_ref()
|
||||||
|
&& *height == button_height
|
||||||
|
{
|
||||||
|
return (*advance, attrs.clone());
|
||||||
|
}
|
||||||
|
let attrs = build_attributes(button_height);
|
||||||
|
let probe = NSString::from_str("0");
|
||||||
|
let advance = create_attributed_string(&probe, Some(&attrs)).size().width;
|
||||||
|
*cache.borrow_mut() = Some((button_height, advance, attrs.clone()));
|
||||||
|
(advance, attrs)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Longest line times the monospaced advance; matches `size().width` for the ASCII-only speed text.
|
||||||
|
fn arithmetic_width(text: &str, advance: f64) -> f64 {
|
||||||
|
let glyphs = text.split('\n').map(str::len).max().unwrap_or(0) as f64;
|
||||||
|
glyphs * advance
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sync_click_target_frame(button: &NSStatusBarButton) {
|
fn sync_click_target_frame(button: &NSStatusBarButton) {
|
||||||
@@ -89,7 +111,7 @@ fn sync_click_target_frame(button: &NSStatusBarButton) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// `with_inner_tray_icon` should run this on the main thread; fail safely if it does not.
|
/// `with_inner_tray_icon` should run this on the main thread; fail safely if it does not.
|
||||||
fn apply_status_item_attributed_title(status_item: &NSStatusItem, text: &NSString, show_speed: bool) {
|
fn apply_status_item_attributed_title(status_item: &NSStatusItem, text: &str, show_speed: bool) {
|
||||||
let Some(mtm) = MainThreadMarker::new() else {
|
let Some(mtm) = MainThreadMarker::new() else {
|
||||||
logging!(warn, Type::Tray, "托盘速率富文本设置跳过:非主线程调用");
|
logging!(warn, Type::Tray, "托盘速率富文本设置跳过:非主线程调用");
|
||||||
return;
|
return;
|
||||||
@@ -97,17 +119,19 @@ fn apply_status_item_attributed_title(status_item: &NSStatusItem, text: &NSStrin
|
|||||||
let Some(button) = status_item.button(mtm) else {
|
let Some(button) = status_item.button(mtm) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
let ns_string = NSString::from_str(text);
|
||||||
let attr_str = if show_speed {
|
let attr_str = if show_speed {
|
||||||
let attrs = build_attributes(button.bounds().size.height);
|
let (advance, attrs) = cached_attributes(button.bounds().size.height);
|
||||||
let attrs: &NSDictionary<NSString, AnyObject> = &attrs;
|
let attrs: &NSDictionary<NSString, AnyObject> = &attrs;
|
||||||
let attr_str = create_attributed_string(text, Some(attrs));
|
let attr_str = create_attributed_string(&ns_string, Some(attrs));
|
||||||
let length = status_item_length_for_speed(&attr_str);
|
let width = arithmetic_width(text, advance);
|
||||||
|
let length = (width.ceil() + TRAY_STATUS_ITEM_EXTRA_WIDTH).max(TRAY_STATUS_ITEM_MIN_LENGTH);
|
||||||
if status_item.length() != length {
|
if status_item.length() != length {
|
||||||
status_item.setLength(length);
|
status_item.setLength(length);
|
||||||
}
|
}
|
||||||
attr_str
|
attr_str
|
||||||
} else {
|
} else {
|
||||||
create_attributed_string(text, None)
|
create_attributed_string(&ns_string, None)
|
||||||
};
|
};
|
||||||
button.setAttributedTitle(&attr_str);
|
button.setAttributedTitle(&attr_str);
|
||||||
sync_click_target_frame(&button);
|
sync_click_target_frame(&button);
|
||||||
@@ -121,8 +145,7 @@ pub fn set_speed_attributed_title(status_item: &NSStatusItem, up: u64, down: u64
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
*last = speed_text;
|
*last = speed_text;
|
||||||
let ns_string = NSString::from_str(last.as_str());
|
apply_status_item_attributed_title(status_item, last.as_str(), true);
|
||||||
apply_status_item_attributed_title(status_item, &ns_string, true);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +153,6 @@ pub fn clear_speed_attributed_title(status_item: &NSStatusItem) {
|
|||||||
LAST_DISPLAY_STR.with(|last| {
|
LAST_DISPLAY_STR.with(|last| {
|
||||||
last.borrow_mut().clear();
|
last.borrow_mut().clear();
|
||||||
});
|
});
|
||||||
let empty = NSString::from_str("");
|
|
||||||
status_item.setLength(NS_VARIABLE_STATUS_ITEM_LENGTH);
|
status_item.setLength(NS_VARIABLE_STATUS_ITEM_LENGTH);
|
||||||
apply_status_item_attributed_title(status_item, &empty, false);
|
apply_status_item_attributed_title(status_item, "", false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -202,9 +202,15 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
padding: 10px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
border-bottom: 1px solid var(--divider-color);
|
border-bottom: 1px solid var(--divider-color);
|
||||||
|
|
||||||
|
&-drag-region {
|
||||||
|
align-self: stretch;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-content__left .the-logo {
|
.layout-content__left .the-logo {
|
||||||
@@ -229,9 +235,16 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
padding: 10px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
border-bottom: 1px solid var(--divider-color);
|
border-bottom: 1px solid var(--divider-color);
|
||||||
|
|
||||||
|
&-drag-region {
|
||||||
|
align-self: stretch;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
order: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-content__left {
|
.layout-content__left {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { Close, CropSquare, FilterNone, Minimize } from '@mui/icons-material'
|
import { Close, CropSquare, FilterNone, Minimize } from '@mui/icons-material'
|
||||||
import { Box, IconButton } from '@mui/material'
|
import { Box, IconButton } from '@mui/material'
|
||||||
import { getCurrentWindow } from '@tauri-apps/api/window'
|
|
||||||
import {
|
import {
|
||||||
type MouseEvent,
|
forwardRef,
|
||||||
type PointerEvent,
|
type PointerEvent,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useImperativeHandle,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
|
|
||||||
|
import { useWindowControls } from '@/hooks/use-window'
|
||||||
import getSystem from '@/utils/get-system'
|
import getSystem from '@/utils/get-system'
|
||||||
|
|
||||||
const RESIZE_HANDLES = [
|
const RESIZE_HANDLES = [
|
||||||
@@ -21,7 +21,9 @@ const RESIZE_HANDLES = [
|
|||||||
{ direction: 'NorthWest', position: 'north-west' },
|
{ direction: 'NorthWest', position: 'north-west' },
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
export const WindowResizeHandles = ({ maximized }: { maximized: boolean }) => {
|
export const WindowResizeHandles = () => {
|
||||||
|
const { currentWindow, maximized } = useWindowControls()
|
||||||
|
|
||||||
const startResizeDragging = useCallback(
|
const startResizeDragging = useCallback(
|
||||||
(event: PointerEvent<HTMLDivElement>) => {
|
(event: PointerEvent<HTMLDivElement>) => {
|
||||||
if (event.button !== 0) return
|
if (event.button !== 0) return
|
||||||
@@ -31,14 +33,14 @@ export const WindowResizeHandles = ({ maximized }: { maximized: boolean }) => {
|
|||||||
const handle = RESIZE_HANDLES.find((item) => item.direction === direction)
|
const handle = RESIZE_HANDLES.find((item) => item.direction === direction)
|
||||||
|
|
||||||
if (handle) {
|
if (handle) {
|
||||||
void getCurrentWindow()
|
void currentWindow
|
||||||
.startResizeDragging(handle.direction)
|
.startResizeDragging(handle.direction)
|
||||||
.catch((error) =>
|
.catch((error) =>
|
||||||
console.warn('[WindowResizeHandles] 调整窗口大小失败:', error),
|
console.warn('[WindowResizeHandles] 调整窗口大小失败:', error),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[],
|
[currentWindow],
|
||||||
)
|
)
|
||||||
|
|
||||||
if (getSystem() !== 'linux' || maximized) return null
|
if (getSystem() !== 'linux' || maximized) return null
|
||||||
@@ -61,77 +63,44 @@ export const WindowResizeHandles = ({ maximized }: { maximized: boolean }) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WindowControls = ({
|
export const WindowControls = forwardRef(function WindowControls(props, ref) {
|
||||||
maximized,
|
|
||||||
setMaximized,
|
|
||||||
}: {
|
|
||||||
maximized: boolean
|
|
||||||
setMaximized: (value: boolean) => void
|
|
||||||
}) => {
|
|
||||||
const OS = getSystem()
|
const OS = getSystem()
|
||||||
|
const {
|
||||||
|
currentWindow,
|
||||||
|
maximized,
|
||||||
|
minimize,
|
||||||
|
close,
|
||||||
|
toggleFullscreen,
|
||||||
|
toggleMaximize,
|
||||||
|
} = useWindowControls()
|
||||||
|
|
||||||
const close = useCallback(async () => {
|
useImperativeHandle(
|
||||||
// Delay one frame so the UI can clear :hover before the window hides.
|
ref,
|
||||||
await new Promise((resolve) => setTimeout(resolve, 20))
|
() => ({
|
||||||
await getCurrentWindow().close()
|
currentWindow,
|
||||||
}, [])
|
maximized,
|
||||||
|
minimize,
|
||||||
const minimize = useCallback(async () => {
|
close,
|
||||||
// Delay one frame so the UI can clear :hover before the window hides.
|
toggleFullscreen,
|
||||||
await new Promise((resolve) => setTimeout(resolve, 10))
|
toggleMaximize,
|
||||||
await getCurrentWindow().minimize()
|
}),
|
||||||
}, [])
|
[
|
||||||
|
currentWindow,
|
||||||
const toggleMaximize = useCallback(async () => {
|
maximized,
|
||||||
const currentWindow = getCurrentWindow()
|
minimize,
|
||||||
if (await currentWindow.isMaximized()) {
|
close,
|
||||||
await currentWindow.unmaximize()
|
toggleFullscreen,
|
||||||
setMaximized(false)
|
toggleMaximize,
|
||||||
} else {
|
],
|
||||||
await currentWindow.maximize()
|
|
||||||
setMaximized(true)
|
|
||||||
}
|
|
||||||
}, [setMaximized])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getCurrentWindow().setMinimizable(true)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const handleTitlebarMouseDown = useCallback(
|
|
||||||
(event: MouseEvent<HTMLDivElement>) => {
|
|
||||||
// Left button only, and only on the first press: detail 2 is the second
|
|
||||||
// press of a double-click, which `handleTitlebarDoubleClick` turns into maximize.
|
|
||||||
if (event.button !== 0 || event.detail !== 1) return
|
|
||||||
// Only the empty bar area drags; the control buttons keep their own clicks.
|
|
||||||
if (event.target !== event.currentTarget) return
|
|
||||||
event.preventDefault()
|
|
||||||
void getCurrentWindow()
|
|
||||||
.startDragging()
|
|
||||||
.catch((error) => console.warn('[WindowControls] 开始拖动失败:', error))
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleTitlebarDoubleClick = useCallback(
|
// 通过前端对 tauri 窗口进行翻转全屏时会短暂地与系统图标重叠渲染。
|
||||||
(event: MouseEvent<HTMLDivElement>) => {
|
// 这可能是上游缺陷,保险起见跨平台以窗口的最大化翻转为准。
|
||||||
if (event.target !== event.currentTarget) return
|
|
||||||
void toggleMaximize().catch((error) =>
|
|
||||||
console.warn('[WindowControls] 切换最大化失败:', error),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
[toggleMaximize],
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
onMouseDown={handleTitlebarMouseDown}
|
|
||||||
onDoubleClick={handleTitlebarDoubleClick}
|
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: OS === 'macos' ? 'flex-start' : 'flex-end',
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
px: 1,
|
|
||||||
gap: 1,
|
gap: 1,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
'> button': {
|
'> button': {
|
||||||
@@ -217,4 +186,4 @@ export const WindowControls = ({
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import {
|
|||||||
ProxyGroupNavigator,
|
ProxyGroupNavigator,
|
||||||
} from './proxy-group-navigator'
|
} from './proxy-group-navigator'
|
||||||
import { ProxyRender } from './proxy-render'
|
import { ProxyRender } from './proxy-render'
|
||||||
|
import type { HeadState } from './use-head-state'
|
||||||
import {
|
import {
|
||||||
PROXY_GROUP_HEADER_SENSORS,
|
PROXY_GROUP_HEADER_SENSORS,
|
||||||
useProxyGroupHeaderLayout,
|
useProxyGroupHeaderLayout,
|
||||||
@@ -90,11 +91,11 @@ function useProxyRenderState(
|
|||||||
) {
|
) {
|
||||||
const { verge } = useVerge()
|
const { verge } = useVerge()
|
||||||
const { proxyView } = useProxiesData()
|
const { proxyView } = useProxiesData()
|
||||||
const { renderList, onProxies, onHeadState } = useRenderList(
|
const {
|
||||||
mode,
|
renderList,
|
||||||
isChainMode,
|
onProxies,
|
||||||
activeSelectedGroup,
|
onHeadState: setListHeadState,
|
||||||
)
|
} = useRenderList(mode, isChainMode, activeSelectedGroup)
|
||||||
const scrollPositionKey = useMemo(
|
const scrollPositionKey = useMemo(
|
||||||
() =>
|
() =>
|
||||||
isChainMode
|
isChainMode
|
||||||
@@ -103,6 +104,14 @@ function useProxyRenderState(
|
|||||||
[activeSelectedGroup, isChainMode, mode],
|
[activeSelectedGroup, isChainMode, mode],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const onHeadState = useCallback(
|
||||||
|
(groupName: string, patch: Partial<HeadState>) => {
|
||||||
|
if (patch.open) onProxies()
|
||||||
|
setListHeadState(groupName, patch)
|
||||||
|
},
|
||||||
|
[onProxies, setListHeadState],
|
||||||
|
)
|
||||||
|
|
||||||
const timeout = verge?.default_latency_timeout || 10000
|
const timeout = verge?.default_latency_timeout || 10000
|
||||||
|
|
||||||
const handleCheckAll = useStableCallback(
|
const handleCheckAll = useStableCallback(
|
||||||
|
|||||||
@@ -1,20 +1,11 @@
|
|||||||
import Visibility from '@mui/icons-material/Visibility'
|
import { Button, Grid, Stack, TextField } from '@mui/material'
|
||||||
import VisibilityOff from '@mui/icons-material/VisibilityOff'
|
|
||||||
import {
|
|
||||||
TextField,
|
|
||||||
Button,
|
|
||||||
Grid,
|
|
||||||
Stack,
|
|
||||||
IconButton,
|
|
||||||
InputAdornment,
|
|
||||||
} from '@mui/material'
|
|
||||||
import { useLockFn } from 'ahooks'
|
import { useLockFn } from 'ahooks'
|
||||||
import { useState, useRef, memo, useEffect } from 'react'
|
import { memo, useEffect, useRef } from 'react'
|
||||||
import { useForm } from 'react-hook-form'
|
import { useForm } from 'react-hook-form'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { useVerge } from '@/hooks/use-verge'
|
import { useVerge } from '@/hooks/use-verge'
|
||||||
import { saveWebdavConfig, createWebdavBackup } from '@/services/cmds'
|
import { createWebdavBackup, saveWebdavConfig } from '@/services/cmds'
|
||||||
import { mutate } from '@/services/mutate'
|
import { mutate } from '@/services/mutate'
|
||||||
import { errorDetail, showNotice } from '@/services/notice-service'
|
import { errorDetail, showNotice } from '@/services/notice-service'
|
||||||
import {
|
import {
|
||||||
@@ -43,7 +34,6 @@ export const BackupConfigViewer = memo(
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { verge, mutateVerge } = useVerge()
|
const { verge, mutateVerge } = useVerge()
|
||||||
const { webdav_url, webdav_username, webdav_password } = verge || {}
|
const { webdav_url, webdav_username, webdav_password } = verge || {}
|
||||||
const [showPassword, setShowPassword] = useState(false)
|
|
||||||
const usernameRef = useRef<HTMLInputElement>(null)
|
const usernameRef = useRef<HTMLInputElement>(null)
|
||||||
const passwordRef = useRef<HTMLInputElement>(null)
|
const passwordRef = useRef<HTMLInputElement>(null)
|
||||||
const urlRef = useRef<HTMLInputElement>(null)
|
const urlRef = useRef<HTMLInputElement>(null)
|
||||||
@@ -68,10 +58,6 @@ export const BackupConfigViewer = memo(
|
|||||||
const webdavStatus = getWebdavStatus(webdavSignature)
|
const webdavStatus = getWebdavStatus(webdavSignature)
|
||||||
const shouldAutoInit = webdavStatus !== 'failed'
|
const shouldAutoInit = webdavStatus !== 'failed'
|
||||||
|
|
||||||
const handleClickShowPassword = () => {
|
|
||||||
setShowPassword((prev) => !prev)
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!webdav_url || !webdav_username || !webdav_password) {
|
if (!webdav_url || !webdav_username || !webdav_password) {
|
||||||
return
|
return
|
||||||
@@ -215,7 +201,7 @@ export const BackupConfigViewer = memo(
|
|||||||
<Grid size={{ xs: 6 }}>
|
<Grid size={{ xs: 6 }}>
|
||||||
<TextField
|
<TextField
|
||||||
label={t('shared.labels.password')}
|
label={t('shared.labels.password')}
|
||||||
type={showPassword ? 'text' : 'password'}
|
type="password"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small"
|
size="small"
|
||||||
autoCorrect="off"
|
autoCorrect="off"
|
||||||
@@ -223,20 +209,6 @@ export const BackupConfigViewer = memo(
|
|||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
inputRef={passwordRef}
|
inputRef={passwordRef}
|
||||||
{...register('password')}
|
{...register('password')}
|
||||||
slotProps={{
|
|
||||||
input: {
|
|
||||||
endAdornment: (
|
|
||||||
<InputAdornment position="end">
|
|
||||||
<IconButton
|
|
||||||
onClick={handleClickShowPassword}
|
|
||||||
edge="end"
|
|
||||||
>
|
|
||||||
{showPassword ? <VisibilityOff /> : <Visibility />}
|
|
||||||
</IconButton>
|
|
||||||
</InputAdornment>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -12,21 +12,16 @@ import {
|
|||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||||
import { join } from '@tauri-apps/api/path'
|
import { join } from '@tauri-apps/api/path'
|
||||||
import { getCurrentWindow } from '@tauri-apps/api/window'
|
|
||||||
import { open as openDialog } from '@tauri-apps/plugin-dialog'
|
import { open as openDialog } from '@tauri-apps/plugin-dialog'
|
||||||
import { exists } from '@tauri-apps/plugin-fs'
|
import { exists } from '@tauri-apps/plugin-fs'
|
||||||
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'
|
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import {
|
import { BaseDialog, DialogRef, Switch, TooltipIcon } from '@/components/base'
|
||||||
BaseDialog,
|
|
||||||
type DialogRef,
|
|
||||||
Switch,
|
|
||||||
TooltipIcon,
|
|
||||||
} from '@/components/base'
|
|
||||||
import { DEFAULT_HOVER_DELAY } from '@/components/proxy/proxy-group-navigator'
|
import { DEFAULT_HOVER_DELAY } from '@/components/proxy/proxy-group-navigator'
|
||||||
import { useVerge } from '@/hooks/use-verge'
|
import { useVerge } from '@/hooks/use-verge'
|
||||||
import { copyIconFile, getAppDir, restartApp } from '@/services/cmds'
|
import { useWindowDecorations } from '@/hooks/use-window'
|
||||||
|
import { copyIconFile, getAppDir } from '@/services/cmds'
|
||||||
import { mutate } from '@/services/mutate'
|
import { mutate } from '@/services/mutate'
|
||||||
import { showNotice } from '@/services/notice-service'
|
import { showNotice } from '@/services/notice-service'
|
||||||
import getSystem from '@/utils/get-system'
|
import getSystem from '@/utils/get-system'
|
||||||
@@ -63,6 +58,8 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
|
|||||||
const [sysproxyIcon, setSysproxyIcon] = useState('')
|
const [sysproxyIcon, setSysproxyIcon] = useState('')
|
||||||
const [tunIcon, setTunIcon] = useState('')
|
const [tunIcon, setTunIcon] = useState('')
|
||||||
|
|
||||||
|
const { decorated, toggleDecorations } = useWindowDecorations()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initIconPath()
|
initIconPath()
|
||||||
}, [])
|
}, [])
|
||||||
@@ -126,50 +123,17 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
|
|||||||
<List>
|
<List>
|
||||||
<Item>
|
<Item>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={
|
primary={t(
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
'settings.components.verge.layout.fields.preferSystemTitlebar',
|
||||||
<span>
|
)}
|
||||||
{t(
|
|
||||||
'settings.components.verge.layout.fields.enableSystemTitlebar',
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<TooltipIcon
|
|
||||||
title={t(
|
|
||||||
'settings.components.verge.layout.tooltips.enableSystemTitlebar',
|
|
||||||
)}
|
|
||||||
sx={{ opacity: '0.7' }}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<GuardState
|
<GuardState
|
||||||
value={verge?.enable_system_titlebar ?? true}
|
value={decorated}
|
||||||
valueProps="checked"
|
valueProps="checked"
|
||||||
onCatch={onError}
|
onCatch={onError}
|
||||||
onFormat={onSwitchFormat}
|
onFormat={onSwitchFormat}
|
||||||
onChange={(e) => onChangeData({ enable_system_titlebar: e })}
|
onChange={async () => {
|
||||||
onGuard={async (e) => {
|
await toggleDecorations()
|
||||||
await patchVerge({ enable_system_titlebar: e })
|
|
||||||
if (OS === 'linux') {
|
|
||||||
void mutate(() => restartApp(), {
|
|
||||||
id: 'restart-app',
|
|
||||||
errorNotice: false,
|
|
||||||
}).catch((error) => {
|
|
||||||
console.warn('[LayoutViewer] failed to restart app:', error)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
const currentWindow = getCurrentWindow()
|
|
||||||
try {
|
|
||||||
await currentWindow.setDecorations(e)
|
|
||||||
// macOS clears the Miniaturizable style bit when decorations are disabled; re-enable minimize.
|
|
||||||
await currentWindow.setMinimizable(true)
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(
|
|
||||||
'[LayoutViewer] failed to apply system titlebar setting:',
|
|
||||||
error,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Switch edge="end" />
|
<Switch edge="end" />
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ export const useProfiles = () => {
|
|||||||
try {
|
try {
|
||||||
const result = await mutate(() => patchProfilesConfig(value), {
|
const result = await mutate(() => patchProfilesConfig(value), {
|
||||||
id: 'patch-profiles-config',
|
id: 'patch-profiles-config',
|
||||||
|
errorNotice: false,
|
||||||
onFulfilled: (outcome) => {
|
onFulfilled: (outcome) => {
|
||||||
if (outcome.status === 'valid') {
|
if (outcome.status === 'valid') {
|
||||||
void setCacheData<IProfilesConfig>(profilesQueryKey, (current) =>
|
void setCacheData<IProfilesConfig>(profilesQueryKey, (current) =>
|
||||||
@@ -60,12 +61,8 @@ export const useProfiles = () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!result.ok) {
|
|
||||||
// Backend Busy keeps local state untouched, as before.
|
|
||||||
return result.value
|
|
||||||
}
|
|
||||||
const outcome = result.value
|
const outcome = result.value
|
||||||
if (outcome.status !== 'valid' && outcome.status !== 'busy') {
|
if (outcome.status !== 'valid') {
|
||||||
await mutateProfiles()
|
await mutateProfiles()
|
||||||
}
|
}
|
||||||
return outcome
|
return outcome
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
import { getCurrentWindow } from '@tauri-apps/api/window'
|
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
|
|
||||||
import debounce from '@/utils/debounce'
|
|
||||||
|
|
||||||
export const useWindowMaximized = (enabled: boolean) => {
|
|
||||||
const [maximized, setMaximized] = useState(false)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!enabled) return
|
|
||||||
|
|
||||||
const currentWindow = getCurrentWindow()
|
|
||||||
let isUnmounted = false
|
|
||||||
|
|
||||||
const syncMaximized = async () => {
|
|
||||||
if (isUnmounted) return
|
|
||||||
try {
|
|
||||||
setMaximized(await currentWindow.isMaximized())
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('[useWindowMaximized] 读取最大化状态失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void syncMaximized()
|
|
||||||
|
|
||||||
const unlistenPromise = currentWindow.onResized(
|
|
||||||
debounce(syncMaximized, 300),
|
|
||||||
)
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
isUnmounted = true
|
|
||||||
unlistenPromise
|
|
||||||
.then((unlisten) => unlisten())
|
|
||||||
.catch((err) =>
|
|
||||||
console.warn('[useWindowMaximized] 清理监听器失败:', err),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}, [enabled])
|
|
||||||
|
|
||||||
return { maximized, setMaximized }
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { use } from 'react'
|
||||||
|
|
||||||
|
import { WindowContext, type WindowContextType } from '@/providers/window'
|
||||||
|
|
||||||
|
const useWindow = () => {
|
||||||
|
const context = use(WindowContext)
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error('useWindow must be used within WindowProvider')
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useWindowControls = () => {
|
||||||
|
const {
|
||||||
|
maximized,
|
||||||
|
minimize,
|
||||||
|
toggleMaximize,
|
||||||
|
close,
|
||||||
|
toggleFullscreen,
|
||||||
|
currentWindow,
|
||||||
|
} = useWindow()
|
||||||
|
return {
|
||||||
|
maximized,
|
||||||
|
minimize,
|
||||||
|
toggleMaximize,
|
||||||
|
close,
|
||||||
|
toggleFullscreen,
|
||||||
|
currentWindow,
|
||||||
|
} satisfies Pick<
|
||||||
|
WindowContextType,
|
||||||
|
| 'maximized'
|
||||||
|
| 'minimize'
|
||||||
|
| 'toggleMaximize'
|
||||||
|
| 'close'
|
||||||
|
| 'toggleFullscreen'
|
||||||
|
| 'currentWindow'
|
||||||
|
>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useWindowDecorations = () => {
|
||||||
|
const { decorated, toggleDecorations, refreshDecorated } = useWindow()
|
||||||
|
return { decorated, toggleDecorations, refreshDecorated } satisfies Pick<
|
||||||
|
WindowContextType,
|
||||||
|
'decorated' | 'toggleDecorations' | 'refreshDecorated'
|
||||||
|
>
|
||||||
|
}
|
||||||
@@ -231,7 +231,7 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"title": "إعدادات التخطيط",
|
"title": "إعدادات التخطيط",
|
||||||
"fields": {
|
"fields": {
|
||||||
"enableSystemTitlebar": "تفعيل شريط عنوان النظام",
|
"preferSystemTitlebar": "Prefer System Titlebar",
|
||||||
"trafficGraph": "مخطط حركة المرور",
|
"trafficGraph": "مخطط حركة المرور",
|
||||||
"memoryUsage": "استهلاك الذاكرة",
|
"memoryUsage": "استهلاك الذاكرة",
|
||||||
"proxyGroupIcon": "أيقونة مجموعة الوكلاء",
|
"proxyGroupIcon": "أيقونة مجموعة الوكلاء",
|
||||||
@@ -251,7 +251,6 @@
|
|||||||
"pauseRenderTrafficStatsOnBlur": "إيقاف عرض إحصاءات حركة المرور مؤقتًا عند فقدان التركيز"
|
"pauseRenderTrafficStatsOnBlur": "إيقاف عرض إحصاءات حركة المرور مؤقتًا عند فقدان التركيز"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"enableSystemTitlebar": "استخدام شريط عنوان النظام الأصلي بدلاً من شريط العنوان المخصص المدمج (يلزم إعادة التشغيل على Linux).",
|
|
||||||
"hoverNavigator": "Automatically scroll to the corresponding proxy group when hovering over alphabet letters",
|
"hoverNavigator": "Automatically scroll to the corresponding proxy group when hovering over alphabet letters",
|
||||||
"hoverNavigatorDelay": "Delay before auto scrolling when hovering, in milliseconds"
|
"hoverNavigatorDelay": "Delay before auto scrolling when hovering, in milliseconds"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -231,7 +231,7 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"title": "Layout-Einstellungen",
|
"title": "Layout-Einstellungen",
|
||||||
"fields": {
|
"fields": {
|
||||||
"enableSystemTitlebar": "System-Titelleiste aktivieren",
|
"preferSystemTitlebar": "System-Titelleiste bevorzugen",
|
||||||
"trafficGraph": "Verkehrsdiagramm",
|
"trafficGraph": "Verkehrsdiagramm",
|
||||||
"memoryUsage": "Kern-Speichernutzung",
|
"memoryUsage": "Kern-Speichernutzung",
|
||||||
"proxyGroupIcon": "Proxy-Gruppen-Symbol",
|
"proxyGroupIcon": "Proxy-Gruppen-Symbol",
|
||||||
@@ -251,7 +251,6 @@
|
|||||||
"pauseRenderTrafficStatsOnBlur": "Rendering der Verkehrsstatistiken bei Fokusverlust pausieren"
|
"pauseRenderTrafficStatsOnBlur": "Rendering der Verkehrsstatistiken bei Fokusverlust pausieren"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"enableSystemTitlebar": "Die native Titelleiste des Systems anstelle der integrierten benutzerdefinierten Titelleiste verwenden (unter Linux ist ein Neustart erforderlich).",
|
|
||||||
"hoverNavigator": "Beim Darüberfahren mit der Maus über die Buchstaben automatisch zur passenden Proxy-Gruppe scrollen",
|
"hoverNavigator": "Beim Darüberfahren mit der Maus über die Buchstaben automatisch zur passenden Proxy-Gruppe scrollen",
|
||||||
"hoverNavigatorDelay": "Wartezeit vor dem automatischen Scrollen beim Darüberfahren, in Millisekunden"
|
"hoverNavigatorDelay": "Wartezeit vor dem automatischen Scrollen beim Darüberfahren, in Millisekunden"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -231,7 +231,7 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"title": "Layout Setting",
|
"title": "Layout Setting",
|
||||||
"fields": {
|
"fields": {
|
||||||
"enableSystemTitlebar": "Enable System Titlebar",
|
"preferSystemTitlebar": "Prefer System Titlebar",
|
||||||
"trafficGraph": "Traffic Graph",
|
"trafficGraph": "Traffic Graph",
|
||||||
"memoryUsage": "Core Usage",
|
"memoryUsage": "Core Usage",
|
||||||
"proxyGroupIcon": "Proxy Group Icon",
|
"proxyGroupIcon": "Proxy Group Icon",
|
||||||
@@ -251,7 +251,6 @@
|
|||||||
"pauseRenderTrafficStatsOnBlur": "Pause rendering traffic statistics when blurred"
|
"pauseRenderTrafficStatsOnBlur": "Pause rendering traffic statistics when blurred"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"enableSystemTitlebar": "Use the system's native title bar instead of the built-in custom title bar (restart required on Linux).",
|
|
||||||
"hoverNavigator": "Automatically scroll to the corresponding proxy group when hovering over alphabet letters",
|
"hoverNavigator": "Automatically scroll to the corresponding proxy group when hovering over alphabet letters",
|
||||||
"hoverNavigatorDelay": "Delay before auto scrolling when hovering, in milliseconds"
|
"hoverNavigatorDelay": "Delay before auto scrolling when hovering, in milliseconds"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -231,7 +231,7 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"title": "Configuración de la interfaz",
|
"title": "Configuración de la interfaz",
|
||||||
"fields": {
|
"fields": {
|
||||||
"enableSystemTitlebar": "Activar barra de título del sistema",
|
"preferSystemTitlebar": "Prefer System Titlebar",
|
||||||
"trafficGraph": "Gráfico de tráfico",
|
"trafficGraph": "Gráfico de tráfico",
|
||||||
"memoryUsage": "Uso de memoria del núcleo",
|
"memoryUsage": "Uso de memoria del núcleo",
|
||||||
"proxyGroupIcon": "Icono del grupo de proxy",
|
"proxyGroupIcon": "Icono del grupo de proxy",
|
||||||
@@ -251,7 +251,6 @@
|
|||||||
"pauseRenderTrafficStatsOnBlur": "Pausar el renderizado de estadísticas de tráfico al perder el foco"
|
"pauseRenderTrafficStatsOnBlur": "Pausar el renderizado de estadísticas de tráfico al perder el foco"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"enableSystemTitlebar": "Usar la barra de título nativa del sistema en lugar de la barra de título personalizada integrada (se requiere reiniciar en Linux).",
|
|
||||||
"hoverNavigator": "Automatically scroll to the corresponding proxy group when hovering over alphabet letters",
|
"hoverNavigator": "Automatically scroll to the corresponding proxy group when hovering over alphabet letters",
|
||||||
"hoverNavigatorDelay": "Delay before auto scrolling when hovering, in milliseconds"
|
"hoverNavigatorDelay": "Delay before auto scrolling when hovering, in milliseconds"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -231,7 +231,7 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"title": "تنظیمات چیدمان",
|
"title": "تنظیمات چیدمان",
|
||||||
"fields": {
|
"fields": {
|
||||||
"enableSystemTitlebar": "فعالسازی نوار عنوان سیستم",
|
"preferSystemTitlebar": "Prefer System Titlebar",
|
||||||
"trafficGraph": "نمودار ترافیک",
|
"trafficGraph": "نمودار ترافیک",
|
||||||
"memoryUsage": "استفاده از حافظه",
|
"memoryUsage": "استفاده از حافظه",
|
||||||
"proxyGroupIcon": "آیکون گروه پراکسی",
|
"proxyGroupIcon": "آیکون گروه پراکسی",
|
||||||
@@ -251,7 +251,6 @@
|
|||||||
"pauseRenderTrafficStatsOnBlur": "توقف رندر آمار ترافیک هنگام از دست رفتن فوکوس"
|
"pauseRenderTrafficStatsOnBlur": "توقف رندر آمار ترافیک هنگام از دست رفتن فوکوس"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"enableSystemTitlebar": "استفاده از نوار عنوان بومی سیستم بهجای نوار عنوان سفارشی داخلی (در لینوکس نیاز به راهاندازی مجدد است).",
|
|
||||||
"hoverNavigator": "Automatically scroll to the corresponding proxy group when hovering over alphabet letters",
|
"hoverNavigator": "Automatically scroll to the corresponding proxy group when hovering over alphabet letters",
|
||||||
"hoverNavigatorDelay": "Delay before auto scrolling when hovering, in milliseconds"
|
"hoverNavigatorDelay": "Delay before auto scrolling when hovering, in milliseconds"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -231,7 +231,7 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"title": "Pengaturan Tata Letak",
|
"title": "Pengaturan Tata Letak",
|
||||||
"fields": {
|
"fields": {
|
||||||
"enableSystemTitlebar": "Aktifkan bilah judul sistem",
|
"preferSystemTitlebar": "Prefer System Titlebar",
|
||||||
"trafficGraph": "Grafik Lalu Lintas",
|
"trafficGraph": "Grafik Lalu Lintas",
|
||||||
"memoryUsage": "Penggunaan Memori",
|
"memoryUsage": "Penggunaan Memori",
|
||||||
"proxyGroupIcon": "Ikon Grup Proksi",
|
"proxyGroupIcon": "Ikon Grup Proksi",
|
||||||
@@ -251,7 +251,6 @@
|
|||||||
"pauseRenderTrafficStatsOnBlur": "Jeda render statistik lalu lintas saat jendela tidak fokus"
|
"pauseRenderTrafficStatsOnBlur": "Jeda render statistik lalu lintas saat jendela tidak fokus"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"enableSystemTitlebar": "Gunakan bilah judul asli sistem sebagai ganti bilah judul kustom bawaan (perlu mulai ulang di Linux).",
|
|
||||||
"hoverNavigator": "Automatically scroll to the corresponding proxy group when hovering over alphabet letters",
|
"hoverNavigator": "Automatically scroll to the corresponding proxy group when hovering over alphabet letters",
|
||||||
"hoverNavigatorDelay": "Delay before auto scrolling when hovering, in milliseconds"
|
"hoverNavigatorDelay": "Delay before auto scrolling when hovering, in milliseconds"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -231,7 +231,7 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"title": "レイアウト設定",
|
"title": "レイアウト設定",
|
||||||
"fields": {
|
"fields": {
|
||||||
"enableSystemTitlebar": "システムタイトルバーを有効にする",
|
"preferSystemTitlebar": "Prefer System Titlebar",
|
||||||
"trafficGraph": "トラフィックグラフ",
|
"trafficGraph": "トラフィックグラフ",
|
||||||
"memoryUsage": "コアメモリ使用量",
|
"memoryUsage": "コアメモリ使用量",
|
||||||
"proxyGroupIcon": "プロキシグループアイコン",
|
"proxyGroupIcon": "プロキシグループアイコン",
|
||||||
@@ -251,7 +251,6 @@
|
|||||||
"pauseRenderTrafficStatsOnBlur": "フォーカスを失ったときにトラフィック統計の描画を一時停止"
|
"pauseRenderTrafficStatsOnBlur": "フォーカスを失ったときにトラフィック統計の描画を一時停止"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"enableSystemTitlebar": "内蔵のカスタムタイトルバーの代わりにシステム標準のタイトルバーを使用します(Linux では再起動が必要です)。",
|
|
||||||
"hoverNavigator": "Automatically scroll to the corresponding proxy group when hovering over alphabet letters",
|
"hoverNavigator": "Automatically scroll to the corresponding proxy group when hovering over alphabet letters",
|
||||||
"hoverNavigatorDelay": "Delay before auto scrolling when hovering, in milliseconds"
|
"hoverNavigatorDelay": "Delay before auto scrolling when hovering, in milliseconds"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -231,7 +231,7 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"title": "레이아웃 설정",
|
"title": "레이아웃 설정",
|
||||||
"fields": {
|
"fields": {
|
||||||
"enableSystemTitlebar": "시스템 제목 표시줄 활성화",
|
"preferSystemTitlebar": "시스템 제목 표시줄 사용",
|
||||||
"trafficGraph": "트래픽 그래프",
|
"trafficGraph": "트래픽 그래프",
|
||||||
"memoryUsage": "메모리 사용량",
|
"memoryUsage": "메모리 사용량",
|
||||||
"proxyGroupIcon": "프록시 그룹 아이콘",
|
"proxyGroupIcon": "프록시 그룹 아이콘",
|
||||||
@@ -251,7 +251,6 @@
|
|||||||
"pauseRenderTrafficStatsOnBlur": "포커스를 잃으면 트래픽 통계 렌더링 일시 중지"
|
"pauseRenderTrafficStatsOnBlur": "포커스를 잃으면 트래픽 통계 렌더링 일시 중지"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"enableSystemTitlebar": "내장 사용자 지정 제목 표시줄 대신 시스템 기본 제목 표시줄을 사용합니다(Linux에서는 다시 시작해야 함).",
|
|
||||||
"hoverNavigator": "알파벳에 마우스를 올리면 해당 프록시 그룹으로 자동 스크롤합니다",
|
"hoverNavigator": "알파벳에 마우스를 올리면 해당 프록시 그룹으로 자동 스크롤합니다",
|
||||||
"hoverNavigatorDelay": "호버 시 자동 스크롤까지의 지연(밀리초)"
|
"hoverNavigatorDelay": "호버 시 자동 스크롤까지의 지연(밀리초)"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -231,7 +231,7 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"title": "Configurações de Layout",
|
"title": "Configurações de Layout",
|
||||||
"fields": {
|
"fields": {
|
||||||
"enableSystemTitlebar": "Ativar barra de título do sistema",
|
"preferSystemTitlebar": "Preferir Barra de Título do Sistema",
|
||||||
"trafficGraph": "Gráfico de Tráfego",
|
"trafficGraph": "Gráfico de Tráfego",
|
||||||
"memoryUsage": "Uso do Núcleo",
|
"memoryUsage": "Uso do Núcleo",
|
||||||
"proxyGroupIcon": "Ícone do Grupo de Proxy",
|
"proxyGroupIcon": "Ícone do Grupo de Proxy",
|
||||||
@@ -251,7 +251,6 @@
|
|||||||
"pauseRenderTrafficStatsOnBlur": "Pausar a renderização das estatísticas de tráfego quando sem foco"
|
"pauseRenderTrafficStatsOnBlur": "Pausar a renderização das estatísticas de tráfego quando sem foco"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"enableSystemTitlebar": "Usar a barra de título nativa do sistema em vez da barra de título personalizada integrada (é necessário reiniciar no Linux).",
|
|
||||||
"hoverNavigator": "Rola automaticamente até o grupo de proxy correspondente ao passar o mouse sobre as letras do alfabeto",
|
"hoverNavigator": "Rola automaticamente até o grupo de proxy correspondente ao passar o mouse sobre as letras do alfabeto",
|
||||||
"hoverNavigatorDelay": "Atraso antes da rolagem automática ao passar o mouse, em milissegundos"
|
"hoverNavigatorDelay": "Atraso antes da rolagem automática ao passar o mouse, em milissegundos"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -231,7 +231,7 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"title": "Настройки раскладки",
|
"title": "Настройки раскладки",
|
||||||
"fields": {
|
"fields": {
|
||||||
"enableSystemTitlebar": "Включить системную панель заголовка",
|
"preferSystemTitlebar": "Использовать системную панель заголовка",
|
||||||
"trafficGraph": "График трафика",
|
"trafficGraph": "График трафика",
|
||||||
"memoryUsage": "Использование памяти",
|
"memoryUsage": "Использование памяти",
|
||||||
"proxyGroupIcon": "Значок группы прокси",
|
"proxyGroupIcon": "Значок группы прокси",
|
||||||
@@ -251,7 +251,6 @@
|
|||||||
"pauseRenderTrafficStatsOnBlur": "При потере фокуса приостанавливать отрисовку статистики трафика"
|
"pauseRenderTrafficStatsOnBlur": "При потере фокуса приостанавливать отрисовку статистики трафика"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"enableSystemTitlebar": "Использовать системную панель заголовка вместо встроенной настраиваемой (в Linux требуется перезапуск).",
|
|
||||||
"hoverNavigator": "Автоматически прокручивает к группе прокси при наведении на буквы",
|
"hoverNavigator": "Автоматически прокручивает к группе прокси при наведении на буквы",
|
||||||
"hoverNavigatorDelay": "Задержка перед прокруткой при наведении (в миллисекундах)"
|
"hoverNavigatorDelay": "Задержка перед прокруткой при наведении (в миллисекундах)"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -231,7 +231,7 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"title": "Düzen Ayarı",
|
"title": "Düzen Ayarı",
|
||||||
"fields": {
|
"fields": {
|
||||||
"enableSystemTitlebar": "Sistem başlık çubuğunu etkinleştir",
|
"preferSystemTitlebar": "Sistem Başlık Çubuğunu Tercih Et",
|
||||||
"trafficGraph": "Trafik Grafiği",
|
"trafficGraph": "Trafik Grafiği",
|
||||||
"memoryUsage": "Çekirdek Kullanımı",
|
"memoryUsage": "Çekirdek Kullanımı",
|
||||||
"proxyGroupIcon": "Vekil Grup Simgesi",
|
"proxyGroupIcon": "Vekil Grup Simgesi",
|
||||||
@@ -251,7 +251,6 @@
|
|||||||
"pauseRenderTrafficStatsOnBlur": "Odak kaybolduğunda trafik istatistiklerinin işlenmesini duraklat"
|
"pauseRenderTrafficStatsOnBlur": "Odak kaybolduğunda trafik istatistiklerinin işlenmesini duraklat"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"enableSystemTitlebar": "Yerleşik özel başlık çubuğu yerine sistemin yerel başlık çubuğunu kullan (Linux'ta yeniden başlatma gerekir).",
|
|
||||||
"hoverNavigator": "Alfabe harflerinin üzerine gelindiğinde ilgili vekil grubuna otomatik olarak kaydırır",
|
"hoverNavigator": "Alfabe harflerinin üzerine gelindiğinde ilgili vekil grubuna otomatik olarak kaydırır",
|
||||||
"hoverNavigatorDelay": "Üzerine gelindiğinde otomatik kaydırmadan önceki gecikme, milisaniye cinsinden"
|
"hoverNavigatorDelay": "Üzerine gelindiğinde otomatik kaydırmadan önceki gecikme, milisaniye cinsinden"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -231,7 +231,7 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"title": "Расположение көйләүләре",
|
"title": "Расположение көйләүләре",
|
||||||
"fields": {
|
"fields": {
|
||||||
"enableSystemTitlebar": "Enable System Titlebar",
|
"preferSystemTitlebar": "Prefer System Titlebar",
|
||||||
"trafficGraph": "Трафик графигы",
|
"trafficGraph": "Трафик графигы",
|
||||||
"memoryUsage": "Хәтер куллану",
|
"memoryUsage": "Хәтер куллану",
|
||||||
"proxyGroupIcon": "Прокси төркеме иконкасы",
|
"proxyGroupIcon": "Прокси төркеме иконкасы",
|
||||||
@@ -251,7 +251,6 @@
|
|||||||
"pauseRenderTrafficStatsOnBlur": "Фокус югалганда трафик статистикасын сызуны туктатып тору"
|
"pauseRenderTrafficStatsOnBlur": "Фокус югалганда трафик статистикасын сызуны туктатып тору"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"enableSystemTitlebar": "Use the system's native title bar instead of the built-in custom title bar (restart required on Linux).",
|
|
||||||
"hoverNavigator": "Automatically scroll to the corresponding proxy group when hovering over alphabet letters",
|
"hoverNavigator": "Automatically scroll to the corresponding proxy group when hovering over alphabet letters",
|
||||||
"hoverNavigatorDelay": "Delay before auto scrolling when hovering, in milliseconds"
|
"hoverNavigatorDelay": "Delay before auto scrolling when hovering, in milliseconds"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -231,7 +231,7 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"title": "界面设置",
|
"title": "界面设置",
|
||||||
"fields": {
|
"fields": {
|
||||||
"enableSystemTitlebar": "启用系统标题栏",
|
"preferSystemTitlebar": "优先使用系统标题栏",
|
||||||
"trafficGraph": "流量图显",
|
"trafficGraph": "流量图显",
|
||||||
"memoryUsage": "内核占用",
|
"memoryUsage": "内核占用",
|
||||||
"proxyGroupIcon": "代理组图标",
|
"proxyGroupIcon": "代理组图标",
|
||||||
@@ -251,7 +251,6 @@
|
|||||||
"pauseRenderTrafficStatsOnBlur": "失去焦点时暂停渲染流量统计"
|
"pauseRenderTrafficStatsOnBlur": "失去焦点时暂停渲染流量统计"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"enableSystemTitlebar": "使用系统原生标题栏替代内置的自定义标题栏(Linux 下需重启生效)",
|
|
||||||
"hoverNavigator": "鼠标悬停在字母上时自动滚动到对应代理组",
|
"hoverNavigator": "鼠标悬停在字母上时自动滚动到对应代理组",
|
||||||
"hoverNavigatorDelay": "鼠标悬停后触发自动跳转前等待的毫秒数"
|
"hoverNavigatorDelay": "鼠标悬停后触发自动跳转前等待的毫秒数"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -231,7 +231,7 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"title": "介面設定",
|
"title": "介面設定",
|
||||||
"fields": {
|
"fields": {
|
||||||
"enableSystemTitlebar": "啟用系統標題欄",
|
"preferSystemTitlebar": "優先使用系統標題欄",
|
||||||
"trafficGraph": "流量圖表",
|
"trafficGraph": "流量圖表",
|
||||||
"memoryUsage": "內核佔用",
|
"memoryUsage": "內核佔用",
|
||||||
"proxyGroupIcon": "代理組圖示",
|
"proxyGroupIcon": "代理組圖示",
|
||||||
@@ -251,7 +251,6 @@
|
|||||||
"pauseRenderTrafficStatsOnBlur": "失去焦點時暫停渲染流量統計"
|
"pauseRenderTrafficStatsOnBlur": "失去焦點時暫停渲染流量統計"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"enableSystemTitlebar": "使用系統原生標題欄取代內建的自訂標題欄(Linux 需重新啟動生效)",
|
|
||||||
"hoverNavigator": "滑鼠懸停在字母上時自動捲動到對應代理組",
|
"hoverNavigator": "滑鼠懸停在字母上時自動捲動到對應代理組",
|
||||||
"hoverNavigatorDelay": "滑鼠懸停後觸發自動跳轉前等待的毫秒數"
|
"hoverNavigatorDelay": "滑鼠懸停後觸發自動跳轉前等待的毫秒數"
|
||||||
},
|
},
|
||||||
|
|||||||
+6
-3
@@ -12,6 +12,7 @@ import { BaseErrorBoundary } from './components/base'
|
|||||||
import { router } from './pages/_routers'
|
import { router } from './pages/_routers'
|
||||||
import { preloadHomePageCards } from './pages/home'
|
import { preloadHomePageCards } from './pages/home'
|
||||||
import { AppDataProvider } from './providers/app-data-provider'
|
import { AppDataProvider } from './providers/app-data-provider'
|
||||||
|
import { WindowProvider } from './providers/window'
|
||||||
import { FALLBACK_LANGUAGE, initializeLanguage } from './services/i18n'
|
import { FALLBACK_LANGUAGE, initializeLanguage } from './services/i18n'
|
||||||
import {
|
import {
|
||||||
preloadAppData,
|
preloadAppData,
|
||||||
@@ -49,9 +50,11 @@ const initializeApp = (initialThemeMode: 'light' | 'dark') => {
|
|||||||
<ComposeContextProvider contexts={contexts}>
|
<ComposeContextProvider contexts={contexts}>
|
||||||
<BaseErrorBoundary>
|
<BaseErrorBoundary>
|
||||||
<SWRConfig value={swrConfig}>
|
<SWRConfig value={swrConfig}>
|
||||||
<AppDataProvider>
|
<WindowProvider>
|
||||||
<RouterProvider router={router} />
|
<AppDataProvider>
|
||||||
</AppDataProvider>
|
<RouterProvider router={router} />
|
||||||
|
</AppDataProvider>
|
||||||
|
</WindowProvider>
|
||||||
</SWRConfig>
|
</SWRConfig>
|
||||||
</BaseErrorBoundary>
|
</BaseErrorBoundary>
|
||||||
</ComposeContextProvider>
|
</ComposeContextProvider>
|
||||||
|
|||||||
+13
-9
@@ -1,7 +1,7 @@
|
|||||||
import { Paper, ThemeProvider } from '@mui/material'
|
import { Paper, ThemeProvider } from '@mui/material'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||||
import { useCallback, useEffect, useMemo } from 'react'
|
import { useCallback, useEffect, useMemo, useRef } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Outlet, useNavigate } from 'react-router'
|
import { Outlet, useNavigate } from 'react-router'
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
} from '@/components/layout/window-controller'
|
} from '@/components/layout/window-controller'
|
||||||
import { useI18n } from '@/hooks/use-i18n'
|
import { useI18n } from '@/hooks/use-i18n'
|
||||||
import { useVerge } from '@/hooks/use-verge'
|
import { useVerge } from '@/hooks/use-verge'
|
||||||
import { useWindowMaximized } from '@/hooks/use-window-maximized'
|
import { useWindowDecorations } from '@/hooks/use-window'
|
||||||
import { useEventBus } from '@/services/bus'
|
import { useEventBus } from '@/services/bus'
|
||||||
import { handleNoticeMessage } from '@/services/notice-handlers'
|
import { handleNoticeMessage } from '@/services/notice-handlers'
|
||||||
import { useThemeMode } from '@/services/states'
|
import { useThemeMode } from '@/services/states'
|
||||||
@@ -46,17 +46,21 @@ const Layout = () => {
|
|||||||
const { switchLanguage } = useI18n()
|
const { switchLanguage } = useI18n()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const themeReady = useMemo(() => Boolean(theme), [theme])
|
const themeReady = useMemo(() => Boolean(theme), [theme])
|
||||||
const showCustomTitlebar = verge?.enable_system_titlebar === false
|
const windowControlsRef = useRef<any>(null)
|
||||||
const { maximized, setMaximized } = useWindowMaximized(showCustomTitlebar)
|
const { decorated } = useWindowDecorations()
|
||||||
|
|
||||||
const customTitlebar = useMemo(
|
const customTitlebar = useMemo(
|
||||||
() =>
|
() =>
|
||||||
showCustomTitlebar ? (
|
decorated === false ? (
|
||||||
<div className="the_titlebar">
|
<div className="the_titlebar">
|
||||||
<WindowControls maximized={maximized} setMaximized={setMaximized} />
|
<div
|
||||||
|
className="the_titlebar-drag-region"
|
||||||
|
data-tauri-drag-region="true"
|
||||||
|
/>
|
||||||
|
<WindowControls ref={windowControlsRef} />
|
||||||
</div>
|
</div>
|
||||||
) : null,
|
) : null,
|
||||||
[showCustomTitlebar, maximized, setMaximized],
|
[decorated],
|
||||||
)
|
)
|
||||||
|
|
||||||
useLoadingOverlay(themeReady)
|
useLoadingOverlay(themeReady)
|
||||||
@@ -150,9 +154,9 @@ const Layout = () => {
|
|||||||
: {},
|
: {},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{showCustomTitlebar && <WindowResizeHandles maximized={maximized} />}
|
{decorated === false && <WindowResizeHandles />}
|
||||||
|
|
||||||
{/* Custom titlebar - rendered only when the system titlebar is not preferred, memoized for performance */}
|
{/* Custom titlebar - rendered only when decorated is false, memoized for performance */}
|
||||||
{customTitlebar}
|
{customTitlebar}
|
||||||
|
|
||||||
<div className="layout-content">
|
<div className="layout-content">
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export { WindowContext } from './window-context'
|
||||||
|
export type { WindowContextType } from './window-context'
|
||||||
|
export { WindowProvider } from './window-provider'
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||||
|
import { createContext } from 'react'
|
||||||
|
|
||||||
|
export interface WindowContextType {
|
||||||
|
decorated: boolean | null
|
||||||
|
maximized: boolean | null
|
||||||
|
toggleDecorations: () => Promise<void>
|
||||||
|
refreshDecorated: () => Promise<boolean>
|
||||||
|
minimize: () => Promise<void>
|
||||||
|
close: () => Promise<void>
|
||||||
|
toggleMaximize: () => Promise<void>
|
||||||
|
toggleFullscreen: () => Promise<void>
|
||||||
|
currentWindow: ReturnType<typeof getCurrentWindow>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WindowContext = createContext<WindowContextType | undefined>(
|
||||||
|
undefined,
|
||||||
|
)
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||||
|
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
|
|
||||||
|
import debounce from '@/utils/debounce'
|
||||||
|
|
||||||
|
import { WindowContext } from './window-context'
|
||||||
|
|
||||||
|
export const WindowProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const currentWindow = useMemo(() => getCurrentWindow(), [])
|
||||||
|
const [decorated, setDecorated] = useState<boolean | null>(null)
|
||||||
|
const [maximized, setMaximized] = useState<boolean | null>(null)
|
||||||
|
|
||||||
|
const close = useCallback(async () => {
|
||||||
|
// Delay one frame so the UI can clear :hover before the window hides.
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 20))
|
||||||
|
await currentWindow.close()
|
||||||
|
}, [currentWindow])
|
||||||
|
const minimize = useCallback(async () => {
|
||||||
|
// Delay one frame so the UI can clear :hover before the window hides.
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 10))
|
||||||
|
await currentWindow.minimize()
|
||||||
|
}, [currentWindow])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let isUnmounted = false
|
||||||
|
let lastWidth = -1
|
||||||
|
let lastHeight = -1
|
||||||
|
|
||||||
|
const checkMaximized = debounce(
|
||||||
|
async (event: { payload: { width: number; height: number } }) => {
|
||||||
|
if (isUnmounted) return
|
||||||
|
const { width, height } = event.payload
|
||||||
|
if (width === lastWidth && height === lastHeight) return
|
||||||
|
lastWidth = width
|
||||||
|
lastHeight = height
|
||||||
|
const value = await currentWindow.isMaximized()
|
||||||
|
setMaximized(value)
|
||||||
|
},
|
||||||
|
300,
|
||||||
|
)
|
||||||
|
|
||||||
|
const unlistenPromise = currentWindow.onResized(checkMaximized)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
isUnmounted = true
|
||||||
|
unlistenPromise
|
||||||
|
.then((unlisten) => unlisten())
|
||||||
|
.catch((err) => console.warn('[WindowProvider] 清理监听器失败:', err))
|
||||||
|
}
|
||||||
|
}, [currentWindow])
|
||||||
|
|
||||||
|
const toggleMaximize = useCallback(async () => {
|
||||||
|
if (await currentWindow.isMaximized()) {
|
||||||
|
await currentWindow.unmaximize()
|
||||||
|
setMaximized(false)
|
||||||
|
} else {
|
||||||
|
await currentWindow.maximize()
|
||||||
|
setMaximized(true)
|
||||||
|
}
|
||||||
|
}, [currentWindow])
|
||||||
|
|
||||||
|
const toggleFullscreen = useCallback(async () => {
|
||||||
|
await currentWindow.setFullscreen(!(await currentWindow.isFullscreen()))
|
||||||
|
}, [currentWindow])
|
||||||
|
|
||||||
|
const refreshDecorated = useCallback(async () => {
|
||||||
|
const val = await currentWindow.isDecorated()
|
||||||
|
setDecorated(val)
|
||||||
|
return val
|
||||||
|
}, [currentWindow])
|
||||||
|
|
||||||
|
const toggleDecorations = useCallback(async () => {
|
||||||
|
const currentVal = await currentWindow.isDecorated()
|
||||||
|
await currentWindow.setDecorations(!currentVal)
|
||||||
|
setDecorated(!currentVal)
|
||||||
|
}, [currentWindow])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
refreshDecorated()
|
||||||
|
currentWindow.setMinimizable?.(true)
|
||||||
|
}, [currentWindow, refreshDecorated])
|
||||||
|
|
||||||
|
const contextValue = useMemo(
|
||||||
|
() => ({
|
||||||
|
decorated,
|
||||||
|
maximized,
|
||||||
|
toggleDecorations,
|
||||||
|
refreshDecorated,
|
||||||
|
minimize,
|
||||||
|
close,
|
||||||
|
toggleMaximize,
|
||||||
|
toggleFullscreen,
|
||||||
|
currentWindow,
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
decorated,
|
||||||
|
maximized,
|
||||||
|
toggleDecorations,
|
||||||
|
refreshDecorated,
|
||||||
|
minimize,
|
||||||
|
close,
|
||||||
|
toggleMaximize,
|
||||||
|
toggleFullscreen,
|
||||||
|
currentWindow,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
return <WindowContext value={contextValue}>{children}</WindowContext>
|
||||||
|
}
|
||||||
@@ -375,7 +375,6 @@ export type IVerge_Deserialize = {
|
|||||||
proxy_group_tools_position?: string | null
|
proxy_group_tools_position?: string | null
|
||||||
notice_position?: string | null
|
notice_position?: string | null
|
||||||
collapse_navbar?: boolean | null
|
collapse_navbar?: boolean | null
|
||||||
enable_system_titlebar?: boolean | null
|
|
||||||
sysproxy_tray_icon?: boolean | null
|
sysproxy_tray_icon?: boolean | null
|
||||||
tun_tray_icon?: boolean | null
|
tun_tray_icon?: boolean | null
|
||||||
enable_tun_mode?: boolean | null
|
enable_tun_mode?: boolean | null
|
||||||
@@ -466,7 +465,6 @@ export type IVerge_Serialize = {
|
|||||||
proxy_group_tools_position?: string | null
|
proxy_group_tools_position?: string | null
|
||||||
notice_position?: string | null
|
notice_position?: string | null
|
||||||
collapse_navbar: boolean | null
|
collapse_navbar: boolean | null
|
||||||
enable_system_titlebar?: boolean | null
|
|
||||||
sysproxy_tray_icon: boolean | null
|
sysproxy_tray_icon: boolean | null
|
||||||
tun_tray_icon: boolean | null
|
tun_tray_icon: boolean | null
|
||||||
enable_tun_mode: boolean | null
|
enable_tun_mode: boolean | null
|
||||||
|
|||||||
+207
-216
@@ -32,230 +32,221 @@ export const forgetShownStartupError = () => {
|
|||||||
* type makes a backend status without a frontend entry a compile error (and
|
* type makes a backend status without a frontend entry a compile error (and
|
||||||
* contract regeneration keeps the union in sync).
|
* contract regeneration keeps the union in sync).
|
||||||
*/
|
*/
|
||||||
|
const handlers: Record<
|
||||||
|
NoticeStatus,
|
||||||
|
(msg: string, navigate: NavigateFunction) => void
|
||||||
|
> = {
|
||||||
|
info: (msg) => {
|
||||||
|
if (msg) showNotice.info(msg)
|
||||||
|
},
|
||||||
|
'import_sub_url::ok': (_msg, navigate) => {
|
||||||
|
// 空 msg 传入,我们不希望导致 后端-前端-后端 死循环,这里只做提醒。
|
||||||
|
// 未来细分事件通知时,可以考虑传入订阅 ID 或其他标识符
|
||||||
|
// navigate("/profile", { state: { current: msg } });
|
||||||
|
navigate('/profile')
|
||||||
|
showNotice.success(
|
||||||
|
'shared.feedback.notifications.importSubscriptionSuccess',
|
||||||
|
)
|
||||||
|
},
|
||||||
|
'import_sub_url::error': (msg, navigate) => {
|
||||||
|
navigate('/profile')
|
||||||
|
showNotice.error(msg)
|
||||||
|
},
|
||||||
|
'set_config::error': (msg) => showNotice.error(msg),
|
||||||
|
'core_start::error': () => {
|
||||||
|
const read = ++startupErrorReads
|
||||||
|
void getCoreStartupError()
|
||||||
|
.then(async (failure) => {
|
||||||
|
if (read < settledStartupErrorRead) return
|
||||||
|
settledStartupErrorRead = read
|
||||||
|
if (!failure) {
|
||||||
|
shownStartupError = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const window = getCurrentWindow()
|
||||||
|
const [visible, minimized] = await Promise.all([
|
||||||
|
window.isVisible(),
|
||||||
|
window.isMinimized(),
|
||||||
|
])
|
||||||
|
const shown = `${failure.kind}:${failure.detail}`
|
||||||
|
if (
|
||||||
|
read === settledStartupErrorRead &&
|
||||||
|
visible &&
|
||||||
|
!minimized &&
|
||||||
|
shownStartupError !== shown
|
||||||
|
) {
|
||||||
|
shownStartupError = shown
|
||||||
|
showNotice.error(
|
||||||
|
failure.kind === 'serviceCoreStopped'
|
||||||
|
? 'settings.feedback.errors.clash.serviceCoreStopped'
|
||||||
|
: 'settings.feedback.errors.clash.startFailed',
|
||||||
|
failure.detail,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Failed to read the pending core startup error', error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
'service_core::repair_required': () => {
|
||||||
|
void takeServiceRepairNotice()
|
||||||
|
.then((pending) => {
|
||||||
|
if (pending) {
|
||||||
|
requestService({ reason: 'serviceLocationRefused' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Failed to read the pending service repair notice', error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
'service_core::app_data_not_owned': () => {
|
||||||
|
void takeServiceOwnerNotice()
|
||||||
|
.then((command) => {
|
||||||
|
if (command) {
|
||||||
|
if (shownOwnerNotice !== null) hideNotice(shownOwnerNotice)
|
||||||
|
shownOwnerNotice = showNotice.warning(
|
||||||
|
'settings.feedback.notifications.clashService.appDataNotOwned',
|
||||||
|
{ command },
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Failed to read the pending service owner notice', error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
'service_core::sidecar_fallback': () => {
|
||||||
|
void takeServiceFallbackNotice()
|
||||||
|
.then((notice) => {
|
||||||
|
if (notice?.kind === 'coreRejected') {
|
||||||
|
showNotice.warning(
|
||||||
|
'settings.feedback.notifications.clashService.permissionFallback',
|
||||||
|
{ reason: notice.reason },
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
} else if (notice?.kind === 'notAutoStarted') {
|
||||||
|
requestService({ reason: 'serviceNotAutoStarted' })
|
||||||
|
} else if (notice) {
|
||||||
|
showNotice.warning(
|
||||||
|
'settings.feedback.notifications.clashService.sidecarFallback',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(
|
||||||
|
'Failed to read the pending service fallback notice',
|
||||||
|
error,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
'dns_override::auto_disabled': () => {
|
||||||
|
void takeDnsOverrideNotice()
|
||||||
|
.then((pending) => {
|
||||||
|
if (pending) {
|
||||||
|
showNotice.info('settings.modals.dns.protection.autoDisabled')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Failed to read the pending DNS override notice', error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
'enhance::discarded_keys': () => {
|
||||||
|
void takeDiscardedKeysNotice()
|
||||||
|
.then((keys) => {
|
||||||
|
if (keys) {
|
||||||
|
showNotice.warning('profiles.page.feedback.notices.discardedKeys', {
|
||||||
|
keys,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Failed to read the pending discarded keys notice', error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
'tun_mode::auto_disabled': () =>
|
||||||
|
showNotice.info(
|
||||||
|
'settings.sections.system.notifications.tunMode.autoDisabled',
|
||||||
|
),
|
||||||
|
'tun_mode::auto_disable_failed': () =>
|
||||||
|
showNotice.error(
|
||||||
|
'settings.sections.system.notifications.tunMode.autoDisableFailed',
|
||||||
|
),
|
||||||
|
'app_restart::core_stop_failed': (msg) =>
|
||||||
|
showNotice.error(
|
||||||
|
'layout.feedback.errors.restartCoreStopFailed',
|
||||||
|
msg || undefined,
|
||||||
|
),
|
||||||
|
'app_quit::core_stop_failed': (msg) =>
|
||||||
|
showNotice.error(
|
||||||
|
'layout.feedback.errors.quitCoreStopFailed',
|
||||||
|
msg || undefined,
|
||||||
|
),
|
||||||
|
update_with_clash_proxy: (msg) =>
|
||||||
|
showNotice.success(
|
||||||
|
'settings.feedback.notifications.updater.withClashProxySuccess',
|
||||||
|
msg,
|
||||||
|
),
|
||||||
|
update_failed_even_with_clash: (msg) =>
|
||||||
|
showNotice.error(
|
||||||
|
'settings.feedback.notifications.updater.withClashProxyFailed',
|
||||||
|
msg,
|
||||||
|
),
|
||||||
|
'reactivate_profiles::error': (msg) => showNotice.error(msg),
|
||||||
|
update_failed: (msg) => showNotice.error(msg),
|
||||||
|
'config_validate::boot_error': (msg) =>
|
||||||
|
showNotice.error('shared.feedback.validation.config.bootFailed', msg),
|
||||||
|
'config_validate::error': (msg) =>
|
||||||
|
showNotice.error('shared.feedback.validation.config.failed', msg),
|
||||||
|
'config_validate::process_terminated': () =>
|
||||||
|
showNotice.error('shared.feedback.validation.config.processTerminated'),
|
||||||
|
'config_validate::script_error': (msg) =>
|
||||||
|
showNotice.error('shared.feedback.validation.script.fileError', msg),
|
||||||
|
'config_validate::script_syntax_error': (msg) =>
|
||||||
|
showNotice.error('shared.feedback.validation.script.syntaxError', msg),
|
||||||
|
'config_validate::script_missing_main': (msg) =>
|
||||||
|
showNotice.error('shared.feedback.validation.script.missingMain', msg),
|
||||||
|
'config_validate::file_not_found': (msg) =>
|
||||||
|
showNotice.error('shared.feedback.validation.script.fileNotFound', msg),
|
||||||
|
'config_validate::yaml_syntax_error': (msg) =>
|
||||||
|
showNotice.error('shared.feedback.validation.yaml.syntaxError', msg),
|
||||||
|
'config_validate::yaml_read_error': (msg) =>
|
||||||
|
showNotice.error('shared.feedback.validation.yaml.readError', msg),
|
||||||
|
'config_validate::yaml_mapping_error': (msg) =>
|
||||||
|
showNotice.error('shared.feedback.validation.yaml.mappingError', msg),
|
||||||
|
'config_validate::merge_syntax_error': (msg) =>
|
||||||
|
showNotice.error('shared.feedback.validation.merge.syntaxError', msg),
|
||||||
|
'config_validate::merge_mapping_error': (msg) =>
|
||||||
|
showNotice.error('shared.feedback.validation.merge.mappingError', msg),
|
||||||
|
'config_core::change_success': (msg) =>
|
||||||
|
showNotice.success(
|
||||||
|
'settings.feedback.notifications.clash.changeSuccess',
|
||||||
|
msg,
|
||||||
|
),
|
||||||
|
'config_core::change_error': (msg) =>
|
||||||
|
showNotice.error('settings.feedback.notifications.clash.changeFailed', msg),
|
||||||
|
'mixed_port::fallback': (msg) => {
|
||||||
|
const [oldPort, newPort] = msg.split(',')
|
||||||
|
showNotice.info('settings.modals.clashPort.messages.automaticFallback', {
|
||||||
|
oldPort,
|
||||||
|
newPort,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
'mixed_port::fallback_error': (msg) =>
|
||||||
|
showNotice.error(
|
||||||
|
'settings.modals.clashPort.messages.automaticFallbackFailed',
|
||||||
|
{ error: msg },
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
export const handleNoticeMessage = (
|
export const handleNoticeMessage = (
|
||||||
status: string,
|
status: string,
|
||||||
msg: string,
|
msg: string,
|
||||||
t: TranslateFunction,
|
t: TranslateFunction,
|
||||||
navigate: NavigateFunction,
|
navigate: NavigateFunction,
|
||||||
) => {
|
) => {
|
||||||
const handlers: Record<NoticeStatus, () => void> = {
|
|
||||||
info: () => {
|
|
||||||
if (msg) showNotice.info(msg)
|
|
||||||
},
|
|
||||||
'import_sub_url::ok': () => {
|
|
||||||
// 空 msg 传入,我们不希望导致 后端-前端-后端 死循环,这里只做提醒。
|
|
||||||
// 未来细分事件通知时,可以考虑传入订阅 ID 或其他标识符
|
|
||||||
// navigate("/profile", { state: { current: msg } });
|
|
||||||
navigate('/profile')
|
|
||||||
showNotice.success(
|
|
||||||
'shared.feedback.notifications.importSubscriptionSuccess',
|
|
||||||
)
|
|
||||||
},
|
|
||||||
'import_sub_url::error': () => {
|
|
||||||
navigate('/profile')
|
|
||||||
showNotice.error(msg)
|
|
||||||
},
|
|
||||||
'set_config::error': () => showNotice.error(msg),
|
|
||||||
'core_start::error': () => {
|
|
||||||
const read = ++startupErrorReads
|
|
||||||
void getCoreStartupError()
|
|
||||||
.then(async (failure) => {
|
|
||||||
if (read < settledStartupErrorRead) return
|
|
||||||
settledStartupErrorRead = read
|
|
||||||
if (!failure) {
|
|
||||||
shownStartupError = null
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const window = getCurrentWindow()
|
|
||||||
const [visible, minimized] = await Promise.all([
|
|
||||||
window.isVisible(),
|
|
||||||
window.isMinimized(),
|
|
||||||
])
|
|
||||||
const shown = `${failure.kind}:${failure.detail}`
|
|
||||||
if (
|
|
||||||
read === settledStartupErrorRead &&
|
|
||||||
visible &&
|
|
||||||
!minimized &&
|
|
||||||
shownStartupError !== shown
|
|
||||||
) {
|
|
||||||
shownStartupError = shown
|
|
||||||
showNotice.error(
|
|
||||||
failure.kind === 'serviceCoreStopped'
|
|
||||||
? 'settings.feedback.errors.clash.serviceCoreStopped'
|
|
||||||
: 'settings.feedback.errors.clash.startFailed',
|
|
||||||
failure.detail,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Failed to read the pending core startup error', error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
'service_core::repair_required': () => {
|
|
||||||
void takeServiceRepairNotice()
|
|
||||||
.then((pending) => {
|
|
||||||
if (pending) {
|
|
||||||
requestService({ reason: 'serviceLocationRefused' })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error(
|
|
||||||
'Failed to read the pending service repair notice',
|
|
||||||
error,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
'service_core::app_data_not_owned': () => {
|
|
||||||
void takeServiceOwnerNotice()
|
|
||||||
.then((command) => {
|
|
||||||
if (command) {
|
|
||||||
if (shownOwnerNotice !== null) hideNotice(shownOwnerNotice)
|
|
||||||
shownOwnerNotice = showNotice.warning(
|
|
||||||
'settings.feedback.notifications.clashService.appDataNotOwned',
|
|
||||||
{ command },
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error(
|
|
||||||
'Failed to read the pending service owner notice',
|
|
||||||
error,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
'service_core::sidecar_fallback': () => {
|
|
||||||
void takeServiceFallbackNotice()
|
|
||||||
.then((notice) => {
|
|
||||||
if (notice?.kind === 'coreRejected') {
|
|
||||||
showNotice.warning(
|
|
||||||
'settings.feedback.notifications.clashService.permissionFallback',
|
|
||||||
{ reason: notice.reason },
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
} else if (notice?.kind === 'notAutoStarted') {
|
|
||||||
requestService({ reason: 'serviceNotAutoStarted' })
|
|
||||||
} else if (notice) {
|
|
||||||
showNotice.warning(
|
|
||||||
'settings.feedback.notifications.clashService.sidecarFallback',
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error(
|
|
||||||
'Failed to read the pending service fallback notice',
|
|
||||||
error,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
'dns_override::auto_disabled': () => {
|
|
||||||
void takeDnsOverrideNotice()
|
|
||||||
.then((pending) => {
|
|
||||||
if (pending) {
|
|
||||||
showNotice.info('settings.modals.dns.protection.autoDisabled')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Failed to read the pending DNS override notice', error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
'enhance::discarded_keys': () => {
|
|
||||||
void takeDiscardedKeysNotice()
|
|
||||||
.then((keys) => {
|
|
||||||
if (keys) {
|
|
||||||
showNotice.warning('profiles.page.feedback.notices.discardedKeys', {
|
|
||||||
keys,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error(
|
|
||||||
'Failed to read the pending discarded keys notice',
|
|
||||||
error,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
'tun_mode::auto_disabled': () =>
|
|
||||||
showNotice.info(
|
|
||||||
'settings.sections.system.notifications.tunMode.autoDisabled',
|
|
||||||
),
|
|
||||||
'tun_mode::auto_disable_failed': () =>
|
|
||||||
showNotice.error(
|
|
||||||
'settings.sections.system.notifications.tunMode.autoDisableFailed',
|
|
||||||
),
|
|
||||||
'app_restart::core_stop_failed': () =>
|
|
||||||
showNotice.error(
|
|
||||||
'layout.feedback.errors.restartCoreStopFailed',
|
|
||||||
msg || undefined,
|
|
||||||
),
|
|
||||||
'app_quit::core_stop_failed': () =>
|
|
||||||
showNotice.error(
|
|
||||||
'layout.feedback.errors.quitCoreStopFailed',
|
|
||||||
msg || undefined,
|
|
||||||
),
|
|
||||||
update_with_clash_proxy: () =>
|
|
||||||
showNotice.success(
|
|
||||||
'settings.feedback.notifications.updater.withClashProxySuccess',
|
|
||||||
msg,
|
|
||||||
),
|
|
||||||
update_failed_even_with_clash: () =>
|
|
||||||
showNotice.error(
|
|
||||||
'settings.feedback.notifications.updater.withClashProxyFailed',
|
|
||||||
msg,
|
|
||||||
),
|
|
||||||
'reactivate_profiles::error': () => showNotice.error(msg),
|
|
||||||
update_failed: () => showNotice.error(msg),
|
|
||||||
'config_validate::boot_error': () =>
|
|
||||||
showNotice.error('shared.feedback.validation.config.bootFailed', msg),
|
|
||||||
'config_validate::error': () =>
|
|
||||||
showNotice.error('shared.feedback.validation.config.failed', msg),
|
|
||||||
'config_validate::process_terminated': () =>
|
|
||||||
showNotice.error('shared.feedback.validation.config.processTerminated'),
|
|
||||||
'config_validate::script_error': () =>
|
|
||||||
showNotice.error('shared.feedback.validation.script.fileError', msg),
|
|
||||||
'config_validate::script_syntax_error': () =>
|
|
||||||
showNotice.error('shared.feedback.validation.script.syntaxError', msg),
|
|
||||||
'config_validate::script_missing_main': () =>
|
|
||||||
showNotice.error('shared.feedback.validation.script.missingMain', msg),
|
|
||||||
'config_validate::file_not_found': () =>
|
|
||||||
showNotice.error('shared.feedback.validation.script.fileNotFound', msg),
|
|
||||||
'config_validate::yaml_syntax_error': () =>
|
|
||||||
showNotice.error('shared.feedback.validation.yaml.syntaxError', msg),
|
|
||||||
'config_validate::yaml_read_error': () =>
|
|
||||||
showNotice.error('shared.feedback.validation.yaml.readError', msg),
|
|
||||||
'config_validate::yaml_mapping_error': () =>
|
|
||||||
showNotice.error('shared.feedback.validation.yaml.mappingError', msg),
|
|
||||||
'config_validate::merge_syntax_error': () =>
|
|
||||||
showNotice.error('shared.feedback.validation.merge.syntaxError', msg),
|
|
||||||
'config_validate::merge_mapping_error': () =>
|
|
||||||
showNotice.error('shared.feedback.validation.merge.mappingError', msg),
|
|
||||||
'config_core::change_success': () =>
|
|
||||||
showNotice.success(
|
|
||||||
'settings.feedback.notifications.clash.changeSuccess',
|
|
||||||
msg,
|
|
||||||
),
|
|
||||||
'config_core::change_error': () =>
|
|
||||||
showNotice.error(
|
|
||||||
'settings.feedback.notifications.clash.changeFailed',
|
|
||||||
msg,
|
|
||||||
),
|
|
||||||
'mixed_port::fallback': () => {
|
|
||||||
const [oldPort, newPort] = msg.split(',')
|
|
||||||
showNotice.info('settings.modals.clashPort.messages.automaticFallback', {
|
|
||||||
oldPort,
|
|
||||||
newPort,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
'mixed_port::fallback_error': () =>
|
|
||||||
showNotice.error(
|
|
||||||
'settings.modals.clashPort.messages.automaticFallbackFailed',
|
|
||||||
{ error: msg },
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
const handler = handlers[status as NoticeStatus]
|
const handler = handlers[status as NoticeStatus]
|
||||||
if (handler) {
|
if (handler) {
|
||||||
handler()
|
handler(msg, navigate)
|
||||||
} else {
|
} else {
|
||||||
console.warn(`未处理的通知状态: ${status}`)
|
console.warn(`未处理的通知状态: ${status}`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ const withLoading = (
|
|||||||
next.delete(uid)
|
next.delete(uid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (next.size === state.profileUpdates.loading.size) return state
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
profileUpdates: { ...state.profileUpdates, loading: next },
|
profileUpdates: { ...state.profileUpdates, loading: next },
|
||||||
|
|||||||
@@ -533,7 +533,7 @@ export const translationKeys = [
|
|||||||
'settings.components.verge.theme.actions.editCss',
|
'settings.components.verge.theme.actions.editCss',
|
||||||
'settings.components.verge.theme.dialogs.editCssTitle',
|
'settings.components.verge.theme.dialogs.editCssTitle',
|
||||||
'settings.components.verge.layout.title',
|
'settings.components.verge.layout.title',
|
||||||
'settings.components.verge.layout.fields.enableSystemTitlebar',
|
'settings.components.verge.layout.fields.preferSystemTitlebar',
|
||||||
'settings.components.verge.layout.fields.trafficGraph',
|
'settings.components.verge.layout.fields.trafficGraph',
|
||||||
'settings.components.verge.layout.fields.memoryUsage',
|
'settings.components.verge.layout.fields.memoryUsage',
|
||||||
'settings.components.verge.layout.fields.proxyGroupIcon',
|
'settings.components.verge.layout.fields.proxyGroupIcon',
|
||||||
@@ -551,7 +551,6 @@ export const translationKeys = [
|
|||||||
'settings.components.verge.layout.fields.enableTrayIcon',
|
'settings.components.verge.layout.fields.enableTrayIcon',
|
||||||
'settings.components.verge.layout.fields.enableTraySpeed',
|
'settings.components.verge.layout.fields.enableTraySpeed',
|
||||||
'settings.components.verge.layout.fields.pauseRenderTrafficStatsOnBlur',
|
'settings.components.verge.layout.fields.pauseRenderTrafficStatsOnBlur',
|
||||||
'settings.components.verge.layout.tooltips.enableSystemTitlebar',
|
|
||||||
'settings.components.verge.layout.tooltips.hoverNavigator',
|
'settings.components.verge.layout.tooltips.hoverNavigator',
|
||||||
'settings.components.verge.layout.tooltips.hoverNavigatorDelay',
|
'settings.components.verge.layout.tooltips.hoverNavigatorDelay',
|
||||||
'settings.components.verge.layout.options.icon.monochrome',
|
'settings.components.verge.layout.options.icon.monochrome',
|
||||||
|
|||||||
@@ -746,7 +746,6 @@ export interface TranslationResources {
|
|||||||
fields: {
|
fields: {
|
||||||
collapseNavBar: string
|
collapseNavBar: string
|
||||||
commonTrayIcon: string
|
commonTrayIcon: string
|
||||||
enableSystemTitlebar: string
|
|
||||||
enableTrayIcon: string
|
enableTrayIcon: string
|
||||||
enableTraySpeed: string
|
enableTraySpeed: string
|
||||||
hoverNavigator: string
|
hoverNavigator: string
|
||||||
@@ -754,6 +753,7 @@ export interface TranslationResources {
|
|||||||
memoryUsage: string
|
memoryUsage: string
|
||||||
navIcon: string
|
navIcon: string
|
||||||
pauseRenderTrafficStatsOnBlur: string
|
pauseRenderTrafficStatsOnBlur: string
|
||||||
|
preferSystemTitlebar: string
|
||||||
proxyGroupIcon: string
|
proxyGroupIcon: string
|
||||||
proxyGroupsDisplayMode: string
|
proxyGroupsDisplayMode: string
|
||||||
showOutboundModesInline: string
|
showOutboundModesInline: string
|
||||||
@@ -783,7 +783,6 @@ export interface TranslationResources {
|
|||||||
}
|
}
|
||||||
title: string
|
title: string
|
||||||
tooltips: {
|
tooltips: {
|
||||||
enableSystemTitlebar: string
|
|
||||||
hoverNavigator: string
|
hoverNavigator: string
|
||||||
hoverNavigatorDelay: string
|
hoverNavigatorDelay: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export default function getSystem() {
|
|||||||
|
|
||||||
if (/win64|win32/i.test(ua) || platform === 'win32') return 'windows'
|
if (/win64|win32/i.test(ua) || platform === 'win32') return 'windows'
|
||||||
|
|
||||||
if (/linux/i.test(ua) || platform === 'linux') return 'linux'
|
if (/linux/i.test(ua)) return 'linux'
|
||||||
|
|
||||||
return 'unknown'
|
return 'unknown'
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user