Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df595f4835 | ||
|
|
63e4d2f686 | ||
|
|
971580def8 | ||
|
|
ffd32426b5 | ||
|
|
d2d26cc822 | ||
|
|
a373b0b6eb | ||
|
|
f515fa1443 | ||
|
|
e32e83d45e | ||
|
|
7be3cdeb65 | ||
|
|
b234b9166d | ||
|
|
2c485b5efb | ||
|
|
b7d7e1a1af | ||
|
|
01be6ae70a | ||
|
|
445eaadac3 | ||
|
|
d5b1dfddee | ||
|
|
c68ea04f06 | ||
|
|
9abc30b60c | ||
|
|
1f7561298c | ||
|
|
611c5757e0 | ||
|
|
ab56e82173 | ||
|
|
34350fadb6 | ||
|
|
77786da53f | ||
|
|
f794ca5426 | ||
|
|
a2010e6d1d | ||
|
|
4ce6e9bfd7 | ||
|
|
9a3794073b | ||
|
|
d6197d6d21 | ||
|
|
1f321cf6bc | ||
|
|
5c6d3f4078 | ||
|
|
6b8b95e4ca | ||
|
|
ae08d48641 | ||
|
|
d1ce5566cf | ||
|
|
5f027ebc79 | ||
|
|
8cf83f8338 | ||
|
|
b96e2c1fe0 | ||
|
|
4ad1379773 | ||
|
|
ef0883f732 | ||
|
|
a2076b4e2d | ||
|
|
0a3998530e | ||
|
|
ed2ec56a44 | ||
|
|
87473bdf92 | ||
|
|
8186a6841a | ||
|
|
0a0b5b6612 | ||
|
|
72704f9dc9 | ||
|
|
06ad23d904 | ||
|
|
fbd1c55f44 | ||
|
|
9668a04a1a | ||
|
|
24af375a8e | ||
|
|
a32c973ab8 | ||
|
|
50beb913de | ||
|
|
05f1ec7b34 | ||
|
|
9271b107b6 | ||
|
|
e7208dd7d2 |
2
.github/FUNDING.yml
vendored
@@ -1 +1 @@
|
|||||||
github: clash-verge-rev
|
custom: ['https://t.me/tribute/app?startapp=dtfk','https://t.me/tribute/app?startapp=dtLE']
|
||||||
|
|||||||
239
.github/workflows/release.yml
vendored
@@ -40,9 +40,91 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "Tag and package.json version are consistent."
|
echo "Tag and package.json version are consistent."
|
||||||
|
|
||||||
|
create_release_notes:
|
||||||
|
name: Create Release Notes
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Fetch UPDATE logs
|
||||||
|
id: fetch_update_logs
|
||||||
|
run: |
|
||||||
|
if [ -f "UPDATELOG.md" ]; then
|
||||||
|
UPDATE_LOGS=$(awk '/^## v/{if(flag) exit; flag=1} flag' UPDATELOG.md)
|
||||||
|
if [ -n "$UPDATE_LOGS" ]; then
|
||||||
|
echo "Found update logs"
|
||||||
|
echo "UPDATE_LOGS<<EOF" >> $GITHUB_ENV
|
||||||
|
echo "$UPDATE_LOGS" >> $GITHUB_ENV
|
||||||
|
echo "EOF" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "No update sections found in UPDATELOG.md"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "UPDATELOG.md file not found"
|
||||||
|
fi
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Get Version
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install jq
|
||||||
|
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
|
||||||
|
echo "BUILDTIME=$(TZ=Europe/Moscow date)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- run: |
|
||||||
|
if [ -z "$UPDATE_LOGS" ]; then
|
||||||
|
echo "No update logs found, using default message"
|
||||||
|
UPDATE_LOGS="More new features are now supported. Check for detailed changelog soon."
|
||||||
|
else
|
||||||
|
echo "Using found update logs"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat > release.txt << EOF
|
||||||
|
$UPDATE_LOGS
|
||||||
|
|
||||||
|
## Which version should I download?
|
||||||
|
|
||||||
|
### macOS
|
||||||
|
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Koala.Clash_aarch64.dmg"><img src="https://img.shields.io/badge/DMG-default?style=flat&logo=apple&label=Apple%20Silicon"></a><br>
|
||||||
|
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Koala.Clash_x64.dmg"><img src="https://img.shields.io/badge/DMG-default?style=flat&logo=apple&label=Intel"></a><br>
|
||||||
|
> :warning: **Warning**
|
||||||
|
If you get a notification that the application is corrupted when you run it on macOS, run this command:<br>
|
||||||
|
<code>sudo xattr -r -c /Applications/Koala\ Clash.app</code>
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Koala.Clash_amd64.deb"><img src="https://img.shields.io/badge/x64-default?style=flat&logo=debian&label=DEB"> </a><br>
|
||||||
|
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Koala.Clash.x86_64.rpm"><img src="https://img.shields.io/badge/x64-default?style=flat&logo=fedora&label=RPM"> </a>
|
||||||
|
|
||||||
|
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Koala.Clash_arm64.deb"><img src="https://img.shields.io/badge/arm64-default?style=flat&logo=debian&label=DEB"> </a><br>
|
||||||
|
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Koala.Clash.aarch64.rpm"><img src="https://img.shields.io/badge/aarch64-default?style=flat&logo=fedora&label=RPM"> </a>
|
||||||
|
|
||||||
|
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Koala.Clash_armhf.deb"><img src="https://img.shields.io/badge/armhf-default?style=flat&logo=debian&label=DEB"> </a><br>
|
||||||
|
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Koala.Clash.armhfp.rpm"><img src="https://img.shields.io/badge/armhfp-default?style=flat&logo=fedora&label=RPM"> </a>
|
||||||
|
|
||||||
|
### Windows (Win7 is no longer supported)
|
||||||
|
#### Normal version (recommended)
|
||||||
|
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Koala.Clash_x64-setup.exe"><img src="https://badgen.net/badge/icon/x64?icon=windows&label=exe"></a><br>
|
||||||
|
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Koala.Clash_arm64-setup.exe"><img src="https://badgen.net/badge/icon/arm64?icon=windows&label=exe"></a>
|
||||||
|
#### Portable version is no longer available with many problems
|
||||||
|
#### Built-in Webview version 2 (large size, only used in enterprise version of the system or can not install webview2)
|
||||||
|
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Koala.Clash_x64_fixed_webview2-setup.exe"><img src="https://badgen.net/badge/icon/x64?icon=windows&label=exe"></a><br>
|
||||||
|
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Koala.Clash_arm64_fixed_webview2-setup.exe"><img src="https://badgen.net/badge/icon/arm64?icon=windows&label=exe"></a>
|
||||||
|
|
||||||
|
Created at ${{ env.BUILDTIME }}.
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Upload Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
tag_name: v${{env.VERSION}}
|
||||||
|
name: "Koala Clash v${{env.VERSION}}"
|
||||||
|
body_path: release.txt
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
release:
|
release:
|
||||||
name: Release Build
|
name: Release Build
|
||||||
needs: check_tag_version
|
needs: [check_tag_version, create_release_notes]
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -96,19 +178,78 @@ jobs:
|
|||||||
pnpm i
|
pnpm i
|
||||||
pnpm run prebuild ${{ matrix.target }}
|
pnpm run prebuild ${{ matrix.target }}
|
||||||
|
|
||||||
|
- name: Create .p8 file
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.appstoreconnect/private_keys
|
||||||
|
echo "${{ secrets.APPLE_API_KEY_CONTENT }}" > ~/.appstoreconnect/private_keys/AuthKey_${{ secrets.APPLE_API_KEY }}.p8
|
||||||
|
|
||||||
- name: Tauri build
|
- name: Tauri build
|
||||||
|
id: build
|
||||||
uses: tauri-apps/tauri-action@v0
|
uses: tauri-apps/tauri-action@v0
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
NODE_OPTIONS: "--max_old_space_size=4096"
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||||
|
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||||
|
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||||
|
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||||
|
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
||||||
|
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
|
||||||
|
APPLE_API_KEY_PATH: "~/.appstoreconnect/private_keys/AuthKey_${{ secrets.APPLE_API_KEY }}.p8"
|
||||||
with:
|
with:
|
||||||
tagName: v__VERSION__
|
|
||||||
releaseName: "Clash Verge Rev Lite v__VERSION__"
|
|
||||||
tauriScript: pnpm
|
tauriScript: pnpm
|
||||||
args: --target ${{ matrix.target }}
|
args: --target ${{ matrix.target }}
|
||||||
|
|
||||||
|
- name: Rename Artifact (Windows)
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$version = ${{steps.build.outputs.appVersion}}
|
||||||
|
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe"
|
||||||
|
foreach ($file in $files) {
|
||||||
|
$newName = $file.Name -replace "_${version}_", "_"
|
||||||
|
Rename-Item $file.FullName $newName
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Rename Artifact (Linux/macOS)
|
||||||
|
if: runner.os == 'Linux' || runner.os == 'macOS'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
TARGET_DIR="src-tauri/target/${{ matrix.target }}/release/bundle"
|
||||||
|
|
||||||
|
if [ ! -d "$TARGET_DIR" ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
find "$TARGET_DIR" -type f \( -name "*.dmg" -o -name "*.deb" -o -name "*.rpm" \) -print0 | while IFS= read -r -d '' old_path; do
|
||||||
|
dir_path=$(dirname "$old_path")
|
||||||
|
old_filename=$(basename "$old_path")
|
||||||
|
new_filename=$(echo "$old_filename" \
|
||||||
|
| sed -E 's/_[0-9]+\.[0-9]+\.[0-9]+_/_/' \
|
||||||
|
| sed -E 's/-[0-9]+\.[0-9]+\.[0-9]+-[0-9]+//' \
|
||||||
|
)
|
||||||
|
new_path="${dir_path}/${new_filename}"
|
||||||
|
if [ "$old_path" != "$new_path" ]; then
|
||||||
|
echo " - '$old_filename' -> '$new_filename'"
|
||||||
|
mv "$old_path" "$new_path"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Upload Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
tag_name: v${{steps.build.outputs.appVersion}}
|
||||||
|
name: "Koala Clash v${{steps.build.outputs.appVersion}}"
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
files: |
|
||||||
|
src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
|
||||||
|
src-tauri/target/${{ matrix.target }}/release/bundle/rpm/*.rpm
|
||||||
|
src-tauri/target/${{ matrix.target }}/release/bundle/nsis/*setup*
|
||||||
|
src-tauri/target/${{ matrix.target }}/release/bundle/dmg/*.dmg
|
||||||
|
src-tauri/target/${{ matrix.target }}/release/bundle/macos/*.tar.gz
|
||||||
|
src-tauri/target/${{ matrix.target }}/release/bundle/macos/*.tar.gz.sig
|
||||||
|
|
||||||
release-for-linux-arm:
|
release-for-linux-arm:
|
||||||
name: Release Build for Linux ARM
|
name: Release Build for Linux ARM
|
||||||
strategy:
|
strategy:
|
||||||
@@ -220,11 +361,34 @@ jobs:
|
|||||||
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
|
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
|
||||||
echo "BUILDTIME=$(TZ=Europe/Moscow date)" >> $GITHUB_ENV
|
echo "BUILDTIME=$(TZ=Europe/Moscow date)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Rename
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
TARGET_DIR="src-tauri/target/${{ matrix.target }}/release/bundle"
|
||||||
|
|
||||||
|
if [ ! -d "$TARGET_DIR" ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
find "$TARGET_DIR" -type f \( -name "*.dmg" -o -name "*.deb" -o -name "*.rpm" \) -print0 | while IFS= read -r -d '' old_path; do
|
||||||
|
dir_path=$(dirname "$old_path")
|
||||||
|
old_filename=$(basename "$old_path")
|
||||||
|
new_filename=$(echo "$old_filename" \
|
||||||
|
| sed -E 's/_[0-9]+\.[0-9]+\.[0-9]+_/_/' \
|
||||||
|
| sed -E 's/-[0-9]+\.[0-9]+\.[0-9]+-[0-9]+//' \
|
||||||
|
)
|
||||||
|
new_path="${dir_path}/${new_filename}"
|
||||||
|
if [ "$old_path" != "$new_path" ]; then
|
||||||
|
echo " - '$old_filename' -> '$new_filename'"
|
||||||
|
mv "$old_path" "$new_path"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
- name: Upload Release
|
- name: Upload Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
tag_name: v${{env.VERSION}}
|
tag_name: v${{env.VERSION}}
|
||||||
name: "Clash Verge Rev Lite v${{env.VERSION}}"
|
name: "Koala Clash v${{env.VERSION}}"
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
files: |
|
files: |
|
||||||
src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
|
src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
|
||||||
@@ -294,19 +458,19 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe"
|
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe"
|
||||||
foreach ($file in $files) {
|
foreach ($file in $files) {
|
||||||
$newName = $file.Name -replace "-setup\.exe$", "_fixed_webview2-setup.exe"
|
$newName = $file.Name -replace "_${{steps.build.outputs.appVersion}}_", "_" -replace "-setup\.exe$", "_fixed_webview2-setup.exe"
|
||||||
Rename-Item $file.FullName $newName
|
Rename-Item $file.FullName $newName
|
||||||
}
|
}
|
||||||
|
|
||||||
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*.nsis.zip"
|
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*.nsis.zip"
|
||||||
foreach ($file in $files) {
|
foreach ($file in $files) {
|
||||||
$newName = $file.Name -replace "-setup\.nsis\.zip$", "_fixed_webview2-setup.nsis.zip"
|
$newName = $file.Name -replace "_${{steps.build.outputs.appVersion}}_", "_" -replace "-setup\.nsis\.zip$", "_fixed_webview2-setup.nsis.zip"
|
||||||
Rename-Item $file.FullName $newName
|
Rename-Item $file.FullName $newName
|
||||||
}
|
}
|
||||||
|
|
||||||
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe.sig"
|
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe.sig"
|
||||||
foreach ($file in $files) {
|
foreach ($file in $files) {
|
||||||
$newName = $file.Name -replace "-setup\.exe\.sig$", "_fixed_webview2-setup.exe.sig"
|
$newName = $file.Name -replace "_${{steps.build.outputs.appVersion}}_", "_" -replace "-setup\.exe\.sig$", "_fixed_webview2-setup.exe.sig"
|
||||||
Rename-Item $file.FullName $newName
|
Rename-Item $file.FullName $newName
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,7 +478,7 @@ jobs:
|
|||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
tag_name: v${{steps.build.outputs.appVersion}}
|
tag_name: v${{steps.build.outputs.appVersion}}
|
||||||
name: "Clash Verge Rev Lite v${{steps.build.outputs.appVersion}}"
|
name: "Koala Clash v${{steps.build.outputs.appVersion}}"
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
files: src-tauri/target/${{ matrix.target }}/release/bundle/nsis/*setup*
|
files: src-tauri/target/${{ matrix.target }}/release/bundle/nsis/*setup*
|
||||||
|
|
||||||
@@ -374,9 +538,9 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
create_release_notes:
|
push-notify-to-telegram:
|
||||||
name: Create Release Notes
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
needs: [release-update, release-update-for-fixed-webview2]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -412,46 +576,31 @@ jobs:
|
|||||||
UPDATE_LOGS="More new features are now supported. Check for detailed changelog soon."
|
UPDATE_LOGS="More new features are now supported. Check for detailed changelog soon."
|
||||||
else
|
else
|
||||||
echo "Using found update logs"
|
echo "Using found update logs"
|
||||||
|
UPDATE_LOGS=$(echo "$UPDATE_LOGS" | sed 's/^## \(v.*\)/\*\1\*/')
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cat > release.txt << EOF
|
cat > release.txt << EOF
|
||||||
|
Вышло обновление!
|
||||||
|
|
||||||
$UPDATE_LOGS
|
$UPDATE_LOGS
|
||||||
|
|
||||||
## Which version should I download?
|
|
||||||
|
|
||||||
### macOS
|
|
||||||
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Clash.Verge.Rev.Lite_${{ env.VERSION }}_aarch64.dmg"><img src="https://img.shields.io/badge/DMG-default?style=flat&logo=apple&label=Apple%20Silicon"></a><br>
|
|
||||||
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Clash.Verge.Rev.Lite_${{ env.VERSION }}_x64.dmg"><img src="https://img.shields.io/badge/DMG-default?style=flat&logo=apple&label=Intel"></a><br>
|
|
||||||
> :warning: **Warning**
|
|
||||||
If you get a notification that the application is corrupted when you run it on macOS, run this command:<br>
|
|
||||||
`sudo xattr -r -c /Applications/Clash\ Verge\ Rev\ Lite.app`
|
|
||||||
|
|
||||||
### Linux
|
[Ссылка на релиз](https://github.com/coolcoala/clash-verge-rev-lite/releases/latest)
|
||||||
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Clash.Verge.Rev.Lite_${{ env.VERSION }}_amd64.deb"><img src="https://img.shields.io/badge/x64-default?style=flat&logo=debian&label=DEB"> </a><br>
|
|
||||||
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Clash.Verge.Rev.Lite-${{ env.VERSION }}-1.x86_64.rpm"><img src="https://img.shields.io/badge/x64-default?style=flat&logo=fedora&label=RPM"> </a>
|
|
||||||
|
|
||||||
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Clash.Verge.Rev.Lite_${{ env.VERSION }}_arm64.deb"><img src="https://img.shields.io/badge/arm64-default?style=flat&logo=debian&label=DEB"> </a><br>
|
|
||||||
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Clash.Verge.Rev.Lite-${{ env.VERSION }}-1.aarch64.rpm"><img src="https://img.shields.io/badge/aarch64-default?style=flat&logo=fedora&label=RPM"> </a>
|
|
||||||
|
|
||||||
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Clash.Verge.Rev.Lite_${{ env.VERSION }}_armhf.deb"><img src="https://img.shields.io/badge/armhf-default?style=flat&logo=debian&label=DEB"> </a><br>
|
|
||||||
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Clash.Verge.Rev.Lite-${{ env.VERSION }}-1.armhfp.rpm"><img src="https://img.shields.io/badge/armhfp-default?style=flat&logo=fedora&label=RPM"> </a>
|
|
||||||
|
|
||||||
### Windows (Win7 is no longer supported)
|
|
||||||
#### Normal version (recommended)
|
|
||||||
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Clash.Verge.Rev.Lite_${{ env.VERSION }}_x64-setup.exe"><img src="https://badgen.net/badge/icon/x64?icon=windows&label=exe"></a><br>
|
|
||||||
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Clash.Verge.Rev.Lite_${{ env.VERSION }}_arm64-setup.exe"><img src="https://badgen.net/badge/icon/arm64?icon=windows&label=exe"></a>
|
|
||||||
#### Portable version is no longer available with many problems
|
|
||||||
#### Built-in Webview version 2 (large size, only used in enterprise version of the system or can not install webview2)
|
|
||||||
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Clash.Verge.Rev.Lite_${{ env.VERSION }}_x64_fixed_webview2-setup.exe"><img src="https://badgen.net/badge/icon/x64?icon=windows&label=exe"></a><br>
|
|
||||||
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Clash.Verge.Rev.Lite_${{ env.VERSION }}_arm64_fixed_webview2-setup.exe"><img src="https://badgen.net/badge/icon/arm64?icon=windows&label=exe"></a>
|
|
||||||
|
|
||||||
Created at ${{ env.BUILDTIME }}.
|
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
- name: Upload Release
|
- name: notify to channel
|
||||||
uses: softprops/action-gh-release@v2
|
uses: appleboy/telegram-action@master
|
||||||
with:
|
with:
|
||||||
tag_name: v${{env.VERSION}}
|
to: ${{ secrets.TELEGRAM_TO_CHANNEL }}
|
||||||
name: "Clash Verge Rev Lite v${{env.VERSION}}"
|
token: ${{ secrets.TELEGRAM_TOKEN }}
|
||||||
body_path: release.txt
|
message_file: release.txt
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
format: markdown
|
||||||
|
|
||||||
|
- name: notify to group
|
||||||
|
uses: appleboy/telegram-action@master
|
||||||
|
with:
|
||||||
|
to: ${{ secrets.TELEGRAM_TO_GROUP }}
|
||||||
|
token: ${{ secrets.TELEGRAM_TOKEN }}
|
||||||
|
message_file: release.txt
|
||||||
|
format: markdown
|
||||||
|
|
||||||
|
|||||||
30
UPDATELOG.md
@@ -1,3 +1,33 @@
|
|||||||
|
## v0.2.5
|
||||||
|
|
||||||
|
- new main page
|
||||||
|
- fixed issue with opening via shortcut
|
||||||
|
- fixed logo in sidebar
|
||||||
|
- fixed issue with changing tray settings
|
||||||
|
- name changed to koala clash
|
||||||
|
- added signing for installer on macOS
|
||||||
|
|
||||||
|
## v0.2.4
|
||||||
|
|
||||||
|
- added auto-scaling and scaling via key combination
|
||||||
|
- direct was removed, and the translation for rules and global was replaced
|
||||||
|
- added icons for proxy groups on main page
|
||||||
|
- fixed log color when dark theme is enabled
|
||||||
|
- the alphabetical index has been removed, and additional information about proxies is now hidden by default
|
||||||
|
- notification of exceeding the number of devices in the subscription
|
||||||
|
- support for vless:// links with templates by @legiz-ru
|
||||||
|
- started the process of renaming to Koala Clash, replaced icons
|
||||||
|
- traffic information has been reworked on profile page
|
||||||
|
|
||||||
|
## v0.2.3
|
||||||
|
|
||||||
|
- fixed problem with profile inactivation after adding via deeplink on windows
|
||||||
|
- corrected layout on the proxy page, now all cards are the same size
|
||||||
|
- corrected announe transposition by \n
|
||||||
|
- corrected side menu in compressed window
|
||||||
|
- added check at the main toggle switch, now it cannot be enabled if there are no profiles.
|
||||||
|
|
||||||
|
|
||||||
## v0.2.1
|
## v0.2.1
|
||||||
|
|
||||||
- added headers "announce-url", "update-always"
|
- added headers "announce-url", "update-always"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "clash-verge",
|
"name": "koala-clash",
|
||||||
"version": "0.2.2",
|
"version": "0.2.5",
|
||||||
"license": "GPL-3.0-only",
|
"license": "GPL-3.0-only",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "cross-env RUST_BACKTRACE=1 tauri dev -f verge-dev",
|
"dev": "cross-env RUST_BACKTRACE=1 tauri dev -f verge-dev",
|
||||||
|
|||||||
@@ -42,9 +42,9 @@ async function resolvePortable() {
|
|||||||
|
|
||||||
const zip = new AdmZip();
|
const zip = new AdmZip();
|
||||||
|
|
||||||
zip.addLocalFile(path.join(releaseDir, "Clash Verge.exe"));
|
zip.addLocalFile(path.join(releaseDir, "Koala Clash.exe"));
|
||||||
zip.addLocalFile(path.join(releaseDir, "verge-mihomo.exe"));
|
zip.addLocalFile(path.join(releaseDir, "koala-mihomo.exe"));
|
||||||
zip.addLocalFile(path.join(releaseDir, "verge-mihomo-alpha.exe"));
|
zip.addLocalFile(path.join(releaseDir, "koala-mihomo-alpha.exe"));
|
||||||
zip.addLocalFolder(path.join(releaseDir, "resources"), "resources");
|
zip.addLocalFolder(path.join(releaseDir, "resources"), "resources");
|
||||||
zip.addLocalFolder(
|
zip.addLocalFolder(
|
||||||
path.join(
|
path.join(
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ async function resolvePortable() {
|
|||||||
}
|
}
|
||||||
const zip = new AdmZip();
|
const zip = new AdmZip();
|
||||||
|
|
||||||
zip.addLocalFile(path.join(releaseDir, "clash-verge.exe"));
|
zip.addLocalFile(path.join(releaseDir, "koala-clash.exe"));
|
||||||
zip.addLocalFile(path.join(releaseDir, "verge-mihomo.exe"));
|
zip.addLocalFile(path.join(releaseDir, "koala-mihomo.exe"));
|
||||||
zip.addLocalFile(path.join(releaseDir, "verge-mihomo-alpha.exe"));
|
zip.addLocalFile(path.join(releaseDir, "koala-mihomo-alpha.exe"));
|
||||||
zip.addLocalFolder(path.join(releaseDir, "resources"), "resources");
|
zip.addLocalFolder(path.join(releaseDir, "resources"), "resources");
|
||||||
zip.addLocalFolder(configDir, ".config");
|
zip.addLocalFolder(configDir, ".config");
|
||||||
|
|
||||||
|
|||||||
@@ -175,8 +175,8 @@ function clashMetaAlpha() {
|
|||||||
const zipFile = `${name}-${META_ALPHA_VERSION}.${urlExt}`;
|
const zipFile = `${name}-${META_ALPHA_VERSION}.${urlExt}`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "verge-mihomo-alpha",
|
name: "koala-mihomo-alpha",
|
||||||
targetFile: `verge-mihomo-alpha-${SIDECAR_HOST}${isWin ? ".exe" : ""}`,
|
targetFile: `koala-mihomo-alpha-${SIDECAR_HOST}${isWin ? ".exe" : ""}`,
|
||||||
exeFile,
|
exeFile,
|
||||||
zipFile,
|
zipFile,
|
||||||
downloadURL,
|
downloadURL,
|
||||||
@@ -192,8 +192,8 @@ function clashMeta() {
|
|||||||
const zipFile = `${name}-${META_VERSION}.${urlExt}`;
|
const zipFile = `${name}-${META_VERSION}.${urlExt}`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "verge-mihomo",
|
name: "koala-mihomo",
|
||||||
targetFile: `verge-mihomo-${SIDECAR_HOST}${isWin ? ".exe" : ""}`,
|
targetFile: `koala-mihomo-${SIDECAR_HOST}${isWin ? ".exe" : ""}`,
|
||||||
exeFile,
|
exeFile,
|
||||||
zipFile,
|
zipFile,
|
||||||
downloadURL,
|
downloadURL,
|
||||||
@@ -381,7 +381,7 @@ const resolvePlugin = async () => {
|
|||||||
// service chmod
|
// service chmod
|
||||||
const resolveServicePermission = async () => {
|
const resolveServicePermission = async () => {
|
||||||
const serviceExecutables = [
|
const serviceExecutables = [
|
||||||
"clash-verge-service*",
|
"koala-clash-service*",
|
||||||
"install-service*",
|
"install-service*",
|
||||||
"uninstall-service*",
|
"uninstall-service*",
|
||||||
];
|
];
|
||||||
@@ -429,14 +429,14 @@ async function resolveLocales() {
|
|||||||
/**
|
/**
|
||||||
* main
|
* main
|
||||||
*/
|
*/
|
||||||
const SERVICE_URL = `https://github.com/clash-verge-rev/clash-verge-service/releases/download/${SIDECAR_HOST}`;
|
const SERVICE_URL = `https://github.com/coolcoala/koala-clash-service/releases/download/${SIDECAR_HOST}`;
|
||||||
|
|
||||||
const resolveService = () => {
|
const resolveService = () => {
|
||||||
let ext = platform === "win32" ? ".exe" : "";
|
let ext = platform === "win32" ? ".exe" : "";
|
||||||
let suffix = platform === "linux" ? "-" + SIDECAR_HOST : "";
|
let suffix = platform === "linux" ? "-" + SIDECAR_HOST : "";
|
||||||
resolveResource({
|
resolveResource({
|
||||||
file: "clash-verge-service" + suffix + ext,
|
file: "koala-clash-service" + suffix + ext,
|
||||||
downloadURL: `${SERVICE_URL}/clash-verge-service${ext}`,
|
downloadURL: `${SERVICE_URL}/koala-clash-service${ext}`,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -489,13 +489,13 @@ const resolveWinSysproxy = () =>
|
|||||||
const tasks = [
|
const tasks = [
|
||||||
// { name: "clash", func: resolveClash, retry: 5 },
|
// { name: "clash", func: resolveClash, retry: 5 },
|
||||||
{
|
{
|
||||||
name: "verge-mihomo-alpha",
|
name: "koala-mihomo-alpha",
|
||||||
func: () =>
|
func: () =>
|
||||||
getLatestAlphaVersion().then(() => resolveSidecar(clashMetaAlpha())),
|
getLatestAlphaVersion().then(() => resolveSidecar(clashMetaAlpha())),
|
||||||
retry: 5,
|
retry: 5,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "verge-mihomo",
|
name: "koala-mihomo",
|
||||||
func: () =>
|
func: () =>
|
||||||
getLatestReleaseVersion().then(() => resolveSidecar(clashMeta())),
|
getLatestReleaseVersion().then(() => resolveSidecar(clashMeta())),
|
||||||
retry: 5,
|
retry: 5,
|
||||||
|
|||||||
192
src-tauri/Cargo.lock
generated
@@ -1059,80 +1059,6 @@ dependencies = [
|
|||||||
"inout",
|
"inout",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clash-verge"
|
|
||||||
version = "0.2.2"
|
|
||||||
dependencies = [
|
|
||||||
"ab_glyph",
|
|
||||||
"aes-gcm",
|
|
||||||
"anyhow",
|
|
||||||
"async-trait",
|
|
||||||
"base64 0.22.1",
|
|
||||||
"boa_engine",
|
|
||||||
"chrono",
|
|
||||||
"deelevate",
|
|
||||||
"delay_timer",
|
|
||||||
"dirs 6.0.0",
|
|
||||||
"dunce",
|
|
||||||
"futures",
|
|
||||||
"gethostname 1.0.2",
|
|
||||||
"getrandom 0.3.3",
|
|
||||||
"hex",
|
|
||||||
"hmac",
|
|
||||||
"image",
|
|
||||||
"imageproc",
|
|
||||||
"lazy_static",
|
|
||||||
"libc",
|
|
||||||
"log",
|
|
||||||
"log4rs",
|
|
||||||
"machine-uid",
|
|
||||||
"mihomo_api",
|
|
||||||
"nanoid",
|
|
||||||
"network-interface",
|
|
||||||
"once_cell",
|
|
||||||
"open",
|
|
||||||
"os_info",
|
|
||||||
"parking_lot",
|
|
||||||
"percent-encoding",
|
|
||||||
"port_scanner",
|
|
||||||
"regex",
|
|
||||||
"reqwest",
|
|
||||||
"reqwest_dav",
|
|
||||||
"runas",
|
|
||||||
"scopeguard",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"serde_yaml",
|
|
||||||
"sha2 0.10.9",
|
|
||||||
"sys-locale",
|
|
||||||
"sysinfo",
|
|
||||||
"sysproxy",
|
|
||||||
"tauri",
|
|
||||||
"tauri-build",
|
|
||||||
"tauri-plugin-autostart",
|
|
||||||
"tauri-plugin-clipboard-manager",
|
|
||||||
"tauri-plugin-deep-link",
|
|
||||||
"tauri-plugin-devtools",
|
|
||||||
"tauri-plugin-dialog",
|
|
||||||
"tauri-plugin-fs",
|
|
||||||
"tauri-plugin-global-shortcut",
|
|
||||||
"tauri-plugin-notification",
|
|
||||||
"tauri-plugin-process",
|
|
||||||
"tauri-plugin-shell",
|
|
||||||
"tauri-plugin-updater",
|
|
||||||
"tauri-plugin-window-state",
|
|
||||||
"tempfile",
|
|
||||||
"tokio",
|
|
||||||
"tokio-tungstenite 0.27.0",
|
|
||||||
"tungstenite 0.27.0",
|
|
||||||
"url",
|
|
||||||
"users",
|
|
||||||
"warp",
|
|
||||||
"winapi",
|
|
||||||
"winreg 0.55.0",
|
|
||||||
"zip",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clipboard-win"
|
name = "clipboard-win"
|
||||||
version = "5.4.0"
|
version = "5.4.0"
|
||||||
@@ -3622,6 +3548,81 @@ dependencies = [
|
|||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "koala-clash"
|
||||||
|
version = "0.2.5"
|
||||||
|
dependencies = [
|
||||||
|
"ab_glyph",
|
||||||
|
"aes-gcm",
|
||||||
|
"anyhow",
|
||||||
|
"async-trait",
|
||||||
|
"base64 0.22.1",
|
||||||
|
"boa_engine",
|
||||||
|
"chrono",
|
||||||
|
"deelevate",
|
||||||
|
"delay_timer",
|
||||||
|
"dirs 6.0.0",
|
||||||
|
"dunce",
|
||||||
|
"futures",
|
||||||
|
"gethostname 1.0.2",
|
||||||
|
"getrandom 0.3.3",
|
||||||
|
"hex",
|
||||||
|
"hmac",
|
||||||
|
"image",
|
||||||
|
"imageproc",
|
||||||
|
"lazy_static",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"log4rs",
|
||||||
|
"machine-uid",
|
||||||
|
"mihomo_api",
|
||||||
|
"nanoid",
|
||||||
|
"network-interface",
|
||||||
|
"once_cell",
|
||||||
|
"open",
|
||||||
|
"os_info",
|
||||||
|
"parking_lot",
|
||||||
|
"percent-encoding",
|
||||||
|
"port_scanner",
|
||||||
|
"regex",
|
||||||
|
"reqwest",
|
||||||
|
"reqwest_dav",
|
||||||
|
"runas",
|
||||||
|
"scopeguard",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_yaml",
|
||||||
|
"sha2 0.10.9",
|
||||||
|
"sys-locale",
|
||||||
|
"sysinfo",
|
||||||
|
"sysproxy",
|
||||||
|
"tauri",
|
||||||
|
"tauri-build",
|
||||||
|
"tauri-plugin-autostart",
|
||||||
|
"tauri-plugin-clipboard-manager",
|
||||||
|
"tauri-plugin-deep-link",
|
||||||
|
"tauri-plugin-devtools",
|
||||||
|
"tauri-plugin-dialog",
|
||||||
|
"tauri-plugin-fs",
|
||||||
|
"tauri-plugin-global-shortcut",
|
||||||
|
"tauri-plugin-notification",
|
||||||
|
"tauri-plugin-process",
|
||||||
|
"tauri-plugin-shell",
|
||||||
|
"tauri-plugin-single-instance",
|
||||||
|
"tauri-plugin-updater",
|
||||||
|
"tauri-plugin-window-state",
|
||||||
|
"tempfile",
|
||||||
|
"tokio",
|
||||||
|
"tokio-tungstenite 0.27.0",
|
||||||
|
"tungstenite 0.27.0",
|
||||||
|
"url",
|
||||||
|
"users",
|
||||||
|
"warp",
|
||||||
|
"winapi",
|
||||||
|
"winreg 0.55.0",
|
||||||
|
"zip",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kuchikiki"
|
name = "kuchikiki"
|
||||||
version = "0.8.8-speedreader"
|
version = "0.8.8-speedreader"
|
||||||
@@ -3887,11 +3888,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "machine-uid"
|
name = "machine-uid"
|
||||||
version = "0.2.0"
|
version = "0.5.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1f1595709b0a7386bcd56ba34d250d626e5503917d05d32cdccddcd68603e212"
|
checksum = "0c4506fa0abb0a2ea93f5862f55973da0a662d2ad0e98f337a1c5aac657f0892"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winreg 0.6.2",
|
"libc",
|
||||||
|
"winreg 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6846,9 +6848,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sysinfo"
|
name = "sysinfo"
|
||||||
version = "0.35.2"
|
version = "0.36.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3c3ffa3e4ff2b324a57f7aeb3c349656c7b127c3c189520251a648102a92496e"
|
checksum = "252800745060e7b9ffb7b2badbd8b31cfa4aa2e61af879d0a3bf2a317c20217d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -7294,6 +7296,21 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-plugin-single-instance"
|
||||||
|
version = "2.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "50a0e5a4ce43cb3a733c3aef85e8478bc769dac743c615e26639cbf5d953faf7"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tauri",
|
||||||
|
"thiserror 2.0.12",
|
||||||
|
"tracing",
|
||||||
|
"windows-sys 0.60.2",
|
||||||
|
"zbus",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-updater"
|
name = "tauri-plugin-updater"
|
||||||
version = "2.9.0"
|
version = "2.9.0"
|
||||||
@@ -9323,15 +9340,6 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winreg"
|
|
||||||
version = "0.6.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9"
|
|
||||||
dependencies = [
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winreg"
|
name = "winreg"
|
||||||
version = "0.10.1"
|
version = "0.10.1"
|
||||||
@@ -9568,9 +9576,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zbus"
|
name = "zbus"
|
||||||
version = "5.7.1"
|
version = "5.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d3a7c7cee313d044fca3f48fa782cb750c79e4ca76ba7bc7718cd4024cdf6f68"
|
checksum = "4bb4f9a464286d42851d18a605f7193b8febaf5b0919d71c6399b7b26e5b0aad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-broadcast",
|
"async-broadcast",
|
||||||
"async-executor",
|
"async-executor",
|
||||||
@@ -9602,9 +9610,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zbus_macros"
|
name = "zbus_macros"
|
||||||
version = "5.7.1"
|
version = "5.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a17e7e5eec1550f747e71a058df81a9a83813ba0f6a95f39c4e218bdc7ba366a"
|
checksum = "ef9859f68ee0c4ee2e8cde84737c78e3f4c54f946f2a38645d0d4c7a95327659"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro-crate 3.3.0",
|
"proc-macro-crate 3.3.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "clash-verge"
|
name = "koala-clash"
|
||||||
version = "0.2.2"
|
version = "0.2.5"
|
||||||
description = "clash verge"
|
description = "koala clash"
|
||||||
authors = ["zzzgydi", "wonfen", "MystiPanda", "coolcoala"]
|
authors = ["zzzgydi", "wonfen", "MystiPanda", "coolcoala"]
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
repository = "https://github.com/coolcoala/clash-verge-rev-lite.git"
|
repository = "https://github.com/coolcoala/clash-verge-rev-lite.git"
|
||||||
default-run = "clash-verge"
|
default-run = "koala-clash"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
[package.metadata.bundle]
|
[package.metadata.bundle]
|
||||||
identifier = "io.github.clash-verge-rev.clash-verge-rev"
|
identifier = "io.github.koala-clash"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tauri-build = { version = "2.3.0", features = [] }
|
tauri-build = { version = "2.3.0", features = [] }
|
||||||
@@ -18,7 +18,7 @@ tauri-build = { version = "2.3.0", features = [] }
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
url = "2.5.4"
|
url = "2.5.4"
|
||||||
os_info = "3.0"
|
os_info = "3.0"
|
||||||
machine-uid = "0.2"
|
machine-uid = "0.5.3"
|
||||||
warp = "0.3.7"
|
warp = "0.3.7"
|
||||||
anyhow = "1.0.98"
|
anyhow = "1.0.98"
|
||||||
dirs = "6.0"
|
dirs = "6.0"
|
||||||
@@ -28,7 +28,7 @@ dunce = "1.0.5"
|
|||||||
log4rs = "1.3.0"
|
log4rs = "1.3.0"
|
||||||
nanoid = "0.4"
|
nanoid = "0.4"
|
||||||
chrono = "0.4.41"
|
chrono = "0.4.41"
|
||||||
sysinfo = "0.35.2"
|
sysinfo = "0.36.1"
|
||||||
boa_engine = "0.20.0"
|
boa_engine = "0.20.0"
|
||||||
serde_json = "1.0.140"
|
serde_json = "1.0.140"
|
||||||
serde_yaml = "0.9.34-deprecated"
|
serde_yaml = "0.9.34-deprecated"
|
||||||
@@ -110,6 +110,7 @@ users = "0.11.0"
|
|||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
tauri-plugin-autostart = "2.5.0"
|
tauri-plugin-autostart = "2.5.0"
|
||||||
tauri-plugin-global-shortcut = "2.3.0"
|
tauri-plugin-global-shortcut = "2.3.0"
|
||||||
|
tauri-plugin-single-instance = "2"
|
||||||
tauri-plugin-updater = "2.9.0"
|
tauri-plugin-updater = "2.9.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"autostart:allow-disable",
|
"autostart:allow-disable",
|
||||||
"autostart:allow-is-enabled",
|
"autostart:allow-is-enabled",
|
||||||
"core:window:allow-set-theme",
|
"core:window:allow-set-theme",
|
||||||
"notification:default"
|
"notification:default",
|
||||||
|
"core:webview:allow-set-webview-zoom"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 204 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 14 KiB |
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
chmod +x /usr/bin/install-service
|
chmod +x /usr/bin/install-service
|
||||||
chmod +x /usr/bin/uninstall-service
|
chmod +x /usr/bin/uninstall-service
|
||||||
chmod +x /usr/bin/clash-verge-service
|
chmod +x /usr/bin/koala-clash-service
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<false/>
|
<false/>
|
||||||
<key>com.apple.security.application-groups</key>
|
<key>com.apple.security.application-groups</key>
|
||||||
<array>
|
<array>
|
||||||
<string>io.github.clash-verge-rev.clash-verge-rev</string>
|
<string>io.github.koala-clash</string>
|
||||||
</array>
|
</array>
|
||||||
<key>com.apple.security.inherit</key>
|
<key>com.apple.security.inherit</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
|||||||
@@ -427,52 +427,52 @@ Function .onInit
|
|||||||
!endif
|
!endif
|
||||||
FunctionEnd
|
FunctionEnd
|
||||||
|
|
||||||
!macro CheckAllVergeProcesses
|
!macro CheckAllKoalaProcesses
|
||||||
; Check if clash-verge-service.exe is running
|
; Check if koala-clash-service.exe is running
|
||||||
!if "${INSTALLMODE}" == "currentUser"
|
!if "${INSTALLMODE}" == "currentUser"
|
||||||
nsis_tauri_utils::FindProcessCurrentUser "clash-verge-service.exe"
|
nsis_tauri_utils::FindProcessCurrentUser "koala-clash-service.exe"
|
||||||
!else
|
!else
|
||||||
nsis_tauri_utils::FindProcess "clash-verge-service.exe"
|
nsis_tauri_utils::FindProcess "koala-clash-service.exe"
|
||||||
!endif
|
!endif
|
||||||
Pop $R0
|
Pop $R0
|
||||||
${If} $R0 = 0
|
${If} $R0 = 0
|
||||||
DetailPrint "Kill clash-verge-service.exe..."
|
DetailPrint "Kill koala-clash-service.exe..."
|
||||||
!if "${INSTALLMODE}" == "currentUser"
|
!if "${INSTALLMODE}" == "currentUser"
|
||||||
nsis_tauri_utils::KillProcessCurrentUser "clash-verge-service.exe"
|
nsis_tauri_utils::KillProcessCurrentUser "koala-clash-service.exe"
|
||||||
!else
|
!else
|
||||||
nsis_tauri_utils::KillProcess "clash-verge-service.exe"
|
nsis_tauri_utils::KillProcess "koala-clash-service.exe"
|
||||||
!endif
|
!endif
|
||||||
${EndIf}
|
${EndIf}
|
||||||
|
|
||||||
; Check if verge-mihomo-alpha.exe is running
|
; Check if koala-mihomo-alpha.exe is running
|
||||||
!if "${INSTALLMODE}" == "currentUser"
|
!if "${INSTALLMODE}" == "currentUser"
|
||||||
nsis_tauri_utils::FindProcessCurrentUser "verge-mihomo-alpha.exe"
|
nsis_tauri_utils::FindProcessCurrentUser "koala-mihomo-alpha.exe"
|
||||||
!else
|
!else
|
||||||
nsis_tauri_utils::FindProcess "verge-mihomo-alpha.exe"
|
nsis_tauri_utils::FindProcess "koala-mihomo-alpha.exe"
|
||||||
!endif
|
!endif
|
||||||
Pop $R0
|
Pop $R0
|
||||||
${If} $R0 = 0
|
${If} $R0 = 0
|
||||||
DetailPrint "Kill verge-mihomo-alpha.exe..."
|
DetailPrint "Kill koala-mihomo-alpha.exe..."
|
||||||
!if "${INSTALLMODE}" == "currentUser"
|
!if "${INSTALLMODE}" == "currentUser"
|
||||||
nsis_tauri_utils::KillProcessCurrentUser "verge-mihomo-alpha.exe"
|
nsis_tauri_utils::KillProcessCurrentUser "koala-mihomo-alpha.exe"
|
||||||
!else
|
!else
|
||||||
nsis_tauri_utils::KillProcess "verge-mihomo-alpha.exe"
|
nsis_tauri_utils::KillProcess "koala-mihomo-alpha.exe"
|
||||||
!endif
|
!endif
|
||||||
${EndIf}
|
${EndIf}
|
||||||
|
|
||||||
; Check if verge-mihomo.exe is running
|
; Check if koala-mihomo.exe is running
|
||||||
!if "${INSTALLMODE}" == "currentUser"
|
!if "${INSTALLMODE}" == "currentUser"
|
||||||
nsis_tauri_utils::FindProcessCurrentUser "verge-mihomo.exe"
|
nsis_tauri_utils::FindProcessCurrentUser "koala-mihomo.exe"
|
||||||
!else
|
!else
|
||||||
nsis_tauri_utils::FindProcess "verge-mihomo.exe"
|
nsis_tauri_utils::FindProcess "koala-mihomo.exe"
|
||||||
!endif
|
!endif
|
||||||
Pop $R0
|
Pop $R0
|
||||||
${If} $R0 = 0
|
${If} $R0 = 0
|
||||||
DetailPrint "Kill verge-mihomo.exe..."
|
DetailPrint "Kill koala-mihomo.exe..."
|
||||||
!if "${INSTALLMODE}" == "currentUser"
|
!if "${INSTALLMODE}" == "currentUser"
|
||||||
nsis_tauri_utils::KillProcessCurrentUser "verge-mihomo.exe"
|
nsis_tauri_utils::KillProcessCurrentUser "koala-mihomo.exe"
|
||||||
!else
|
!else
|
||||||
nsis_tauri_utils::KillProcess "verge-mihomo.exe"
|
nsis_tauri_utils::KillProcess "koala-mihomo.exe"
|
||||||
!endif
|
!endif
|
||||||
${EndIf}
|
${EndIf}
|
||||||
|
|
||||||
@@ -509,22 +509,22 @@ FunctionEnd
|
|||||||
${EndIf}
|
${EndIf}
|
||||||
!macroend
|
!macroend
|
||||||
|
|
||||||
!macro StartVergeService
|
!macro StartKoalaService
|
||||||
; Check if the service exists
|
; Check if the service exists
|
||||||
SimpleSC::ExistsService "clash_verge_service"
|
SimpleSC::ExistsService "koala_clash_service"
|
||||||
Pop $0 ; 0:service exists;other: service not exists
|
Pop $0 ; 0:service exists;other: service not exists
|
||||||
; Service exists
|
; Service exists
|
||||||
${If} $0 == 0
|
${If} $0 == 0
|
||||||
Push $0
|
Push $0
|
||||||
; Check if the service is running
|
; Check if the service is running
|
||||||
SimpleSC::ServiceIsRunning "clash_verge_service"
|
SimpleSC::ServiceIsRunning "koala_clash_service"
|
||||||
Pop $0 ; returns an errorcode (<>0) otherwise success (0)
|
Pop $0 ; returns an errorcode (<>0) otherwise success (0)
|
||||||
Pop $1 ; returns 1 (service is running) - returns 0 (service is not running)
|
Pop $1 ; returns 1 (service is running) - returns 0 (service is not running)
|
||||||
${If} $0 == 0
|
${If} $0 == 0
|
||||||
Push $0
|
Push $0
|
||||||
${If} $1 == 0
|
${If} $1 == 0
|
||||||
DetailPrint "Restart Clash Verge Service..."
|
DetailPrint "Restart Koala Clash Service..."
|
||||||
SimpleSC::StartService "clash_verge_service" "" 30
|
SimpleSC::StartService "koala_clash_service" "" 30
|
||||||
${EndIf}
|
${EndIf}
|
||||||
${ElseIf} $0 != 0
|
${ElseIf} $0 != 0
|
||||||
Push $0
|
Push $0
|
||||||
@@ -535,35 +535,35 @@ FunctionEnd
|
|||||||
${EndIf}
|
${EndIf}
|
||||||
!macroend
|
!macroend
|
||||||
|
|
||||||
!macro RemoveVergeService
|
!macro RemoveKoalaService
|
||||||
; Check if the service exists
|
; Check if the service exists
|
||||||
SimpleSC::ExistsService "clash_verge_service"
|
SimpleSC::ExistsService "koala_clash_service"
|
||||||
Pop $0 ; 0:service exists;other: service not exists
|
Pop $0 ; 0:service exists;other: service not exists
|
||||||
; Service exists
|
; Service exists
|
||||||
${If} $0 == 0
|
${If} $0 == 0
|
||||||
Push $0
|
Push $0
|
||||||
; Check if the service is running
|
; Check if the service is running
|
||||||
SimpleSC::ServiceIsRunning "clash_verge_service"
|
SimpleSC::ServiceIsRunning "koala_clash_service"
|
||||||
Pop $0 ; returns an errorcode (<>0) otherwise success (0)
|
Pop $0 ; returns an errorcode (<>0) otherwise success (0)
|
||||||
Pop $1 ; returns 1 (service is running) - returns 0 (service is not running)
|
Pop $1 ; returns 1 (service is running) - returns 0 (service is not running)
|
||||||
${If} $0 == 0
|
${If} $0 == 0
|
||||||
Push $0
|
Push $0
|
||||||
${If} $1 == 1
|
${If} $1 == 1
|
||||||
DetailPrint "Stop Clash Verge Service..."
|
DetailPrint "Stop Koala Clash Service..."
|
||||||
SimpleSC::StopService "clash_verge_service" 1 30
|
SimpleSC::StopService "koala_clash_service" 1 30
|
||||||
Pop $0 ; returns an errorcode (<>0) otherwise success (0)
|
Pop $0 ; returns an errorcode (<>0) otherwise success (0)
|
||||||
${If} $0 == 0
|
${If} $0 == 0
|
||||||
DetailPrint "Removing Clash Verge Service..."
|
DetailPrint "Removing Koala Clash Service..."
|
||||||
SimpleSC::RemoveService "clash_verge_service"
|
SimpleSC::RemoveService "koala_clash_service"
|
||||||
${ElseIf} $0 != 0
|
${ElseIf} $0 != 0
|
||||||
Push $0
|
Push $0
|
||||||
SimpleSC::GetErrorMessage
|
SimpleSC::GetErrorMessage
|
||||||
Pop $0
|
Pop $0
|
||||||
MessageBox MB_OK|MB_ICONSTOP "Clash Verge Service Stop Error ($0)"
|
MessageBox MB_OK|MB_ICONSTOP "Koala Clash Service Stop Error ($0)"
|
||||||
${EndIf}
|
${EndIf}
|
||||||
${ElseIf} $1 == 0
|
${ElseIf} $1 == 0
|
||||||
DetailPrint "Removing Clash Verge Service..."
|
DetailPrint "Removing Koala Clash Service..."
|
||||||
SimpleSC::RemoveService "clash_verge_service"
|
SimpleSC::RemoveService "koala_clash_service"
|
||||||
${EndIf}
|
${EndIf}
|
||||||
${ElseIf} $0 != 0
|
${ElseIf} $0 != 0
|
||||||
Push $0
|
Push $0
|
||||||
@@ -764,7 +764,7 @@ Section Install
|
|||||||
SetOutPath $INSTDIR
|
SetOutPath $INSTDIR
|
||||||
nsExec::Exec 'netsh int tcp res'
|
nsExec::Exec 'netsh int tcp res'
|
||||||
!insertmacro CheckIfAppIsRunning
|
!insertmacro CheckIfAppIsRunning
|
||||||
!insertmacro CheckAllVergeProcesses
|
!insertmacro CheckAllKoalaProcesses
|
||||||
|
|
||||||
; 清理自启动注册表项
|
; 清理自启动注册表项
|
||||||
DetailPrint "Cleaning auto-launch registry entries..."
|
DetailPrint "Cleaning auto-launch registry entries..."
|
||||||
@@ -772,32 +772,32 @@ Section Install
|
|||||||
StrCpy $R1 "Software\Microsoft\Windows\CurrentVersion\Run"
|
StrCpy $R1 "Software\Microsoft\Windows\CurrentVersion\Run"
|
||||||
|
|
||||||
SetRegView 64
|
SetRegView 64
|
||||||
; 清理旧版本的注册表项 (Clash Verge)
|
; 清理旧版本的注册表项 (Koala Clash)
|
||||||
ReadRegStr $R2 HKCU "$R1" "Clash Verge"
|
ReadRegStr $R2 HKCU "$R1" "Koala Clash"
|
||||||
${If} $R2 != ""
|
${If} $R2 != ""
|
||||||
DeleteRegValue HKCU "$R1" "Clash Verge"
|
DeleteRegValue HKCU "$R1" "Koala Clash"
|
||||||
${EndIf}
|
${EndIf}
|
||||||
|
|
||||||
ReadRegStr $R2 HKLM "$R1" "Clash Verge"
|
ReadRegStr $R2 HKLM "$R1" "Koala Clash"
|
||||||
${If} $R2 != ""
|
${If} $R2 != ""
|
||||||
DeleteRegValue HKLM "$R1" "Clash Verge"
|
DeleteRegValue HKLM "$R1" "Koala Clash"
|
||||||
${EndIf}
|
${EndIf}
|
||||||
|
|
||||||
; 清理新版本的注册表项 (clash-verge)
|
; 清理新版本的注册表项 (koala-clash)
|
||||||
ReadRegStr $R2 HKCU "$R1" "clash-verge"
|
ReadRegStr $R2 HKCU "$R1" "koala-clash"
|
||||||
${If} $R2 != ""
|
${If} $R2 != ""
|
||||||
DeleteRegValue HKCU "$R1" "clash-verge"
|
DeleteRegValue HKCU "$R1" "koala-clash"
|
||||||
${EndIf}
|
${EndIf}
|
||||||
|
|
||||||
ReadRegStr $R2 HKLM "$R1" "clash-verge"
|
ReadRegStr $R2 HKLM "$R1" "koala-clash"
|
||||||
${If} $R2 != ""
|
${If} $R2 != ""
|
||||||
DeleteRegValue HKLM "$R1" "clash-verge"
|
DeleteRegValue HKLM "$R1" "koala-clash"
|
||||||
${EndIf}
|
${EndIf}
|
||||||
|
|
||||||
; Delete old files before installation
|
; Delete old files before installation
|
||||||
; Delete clash-verge.desktop
|
; Delete koala-clash.desktop
|
||||||
IfFileExists "$INSTDIR\Clash Verge.exe" 0 +2
|
IfFileExists "$INSTDIR\Koala Clash.exe" 0 +2
|
||||||
Delete "$INSTDIR\Clash Verge.exe"
|
Delete "$INSTDIR\Koala Clash.exe"
|
||||||
|
|
||||||
; Copy main executable
|
; Copy main executable
|
||||||
File "${MAINBINARYSRCPATH}"
|
File "${MAINBINARYSRCPATH}"
|
||||||
@@ -815,7 +815,7 @@ Section Install
|
|||||||
File /a "/oname={{this}}" "{{@key}}"
|
File /a "/oname={{this}}" "{{@key}}"
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
||||||
!insertmacro StartVergeService
|
!insertmacro StartKoalaService
|
||||||
|
|
||||||
; Create uninstaller
|
; Create uninstaller
|
||||||
WriteUninstaller "$INSTDIR\uninstall.exe"
|
WriteUninstaller "$INSTDIR\uninstall.exe"
|
||||||
@@ -918,11 +918,11 @@ FunctionEnd
|
|||||||
Section Uninstall
|
Section Uninstall
|
||||||
;删除 window-state.json 文件
|
;删除 window-state.json 文件
|
||||||
SetShellVarContext current
|
SetShellVarContext current
|
||||||
Delete "$APPDATA\io.github.clash-verge-rev.clash-verge-rev\window-state.json"
|
Delete "$APPDATA\io.github.koala-clash\window-state.json"
|
||||||
|
|
||||||
!insertmacro CheckIfAppIsRunning
|
!insertmacro CheckIfAppIsRunning
|
||||||
!insertmacro CheckAllVergeProcesses
|
!insertmacro CheckAllKoalaProcesses
|
||||||
!insertmacro RemoveVergeService
|
!insertmacro RemoveKoalaService
|
||||||
|
|
||||||
; 清理自启动注册表项
|
; 清理自启动注册表项
|
||||||
DetailPrint "Cleaning auto-launch registry entries..."
|
DetailPrint "Cleaning auto-launch registry entries..."
|
||||||
@@ -930,26 +930,26 @@ Section Uninstall
|
|||||||
StrCpy $R1 "Software\Microsoft\Windows\CurrentVersion\Run"
|
StrCpy $R1 "Software\Microsoft\Windows\CurrentVersion\Run"
|
||||||
|
|
||||||
SetRegView 64
|
SetRegView 64
|
||||||
; 清理旧版本的注册表项 (Clash Verge)
|
; 清理旧版本的注册表项 (Koala Clash)
|
||||||
ReadRegStr $R2 HKCU "$R1" "Clash Verge"
|
ReadRegStr $R2 HKCU "$R1" "Koala Clash"
|
||||||
${If} $R2 != ""
|
${If} $R2 != ""
|
||||||
DeleteRegValue HKCU "$R1" "Clash Verge"
|
DeleteRegValue HKCU "$R1" "Koala Clash"
|
||||||
${EndIf}
|
${EndIf}
|
||||||
|
|
||||||
ReadRegStr $R2 HKLM "$R1" "Clash Verge"
|
ReadRegStr $R2 HKLM "$R1" "Koala Clash"
|
||||||
${If} $R2 != ""
|
${If} $R2 != ""
|
||||||
DeleteRegValue HKLM "$R1" "Clash Verge"
|
DeleteRegValue HKLM "$R1" "Koala Clash"
|
||||||
${EndIf}
|
${EndIf}
|
||||||
|
|
||||||
; 清理新版本的注册表项 (clash-verge)
|
; 清理新版本的注册表项 (koala-clash)
|
||||||
ReadRegStr $R2 HKCU "$R1" "clash-verge"
|
ReadRegStr $R2 HKCU "$R1" "koala-clash"
|
||||||
${If} $R2 != ""
|
${If} $R2 != ""
|
||||||
DeleteRegValue HKCU "$R1" "clash-verge"
|
DeleteRegValue HKCU "$R1" "koala-clash"
|
||||||
${EndIf}
|
${EndIf}
|
||||||
|
|
||||||
ReadRegStr $R2 HKLM "$R1" "clash-verge"
|
ReadRegStr $R2 HKLM "$R1" "koala-clash"
|
||||||
${If} $R2 != ""
|
${If} $R2 != ""
|
||||||
DeleteRegValue HKLM "$R1" "clash-verge"
|
DeleteRegValue HKLM "$R1" "koala-clash"
|
||||||
${EndIf}
|
${EndIf}
|
||||||
|
|
||||||
; Delete the app directory and its content from disk
|
; Delete the app directory and its content from disk
|
||||||
@@ -966,9 +966,9 @@ Section Uninstall
|
|||||||
Delete "$INSTDIR\\{{this}}"
|
Delete "$INSTDIR\\{{this}}"
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
||||||
; Delete clash-verge.desktop
|
; Delete koala-clash.desktop
|
||||||
IfFileExists "$INSTDIR\Clash Verge.exe" 0 +2
|
IfFileExists "$INSTDIR\Koala Clash.exe" 0 +2
|
||||||
Delete "$INSTDIR\Clash Verge.exe"
|
Delete "$INSTDIR\Koala Clash.exe"
|
||||||
|
|
||||||
; Delete uninstaller
|
; Delete uninstaller
|
||||||
Delete "$INSTDIR\uninstall.exe"
|
Delete "$INSTDIR\uninstall.exe"
|
||||||
@@ -982,20 +982,20 @@ Section Uninstall
|
|||||||
!insertmacro UnpinShortcut "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk"
|
!insertmacro UnpinShortcut "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk"
|
||||||
!insertmacro UnpinShortcut "$DESKTOP\${PRODUCTNAME}.lnk"
|
!insertmacro UnpinShortcut "$DESKTOP\${PRODUCTNAME}.lnk"
|
||||||
; 兼容旧名称快捷方式
|
; 兼容旧名称快捷方式
|
||||||
!insertmacro UnpinShortcut "$SMPROGRAMS\$AppStartMenuFolder\clash-verge.lnk"
|
!insertmacro UnpinShortcut "$SMPROGRAMS\$AppStartMenuFolder\koala-clash.lnk"
|
||||||
!insertmacro UnpinShortcut "$DESKTOP\clash-verge.lnk"
|
!insertmacro UnpinShortcut "$DESKTOP\koala-clash.lnk"
|
||||||
|
|
||||||
; Remove start menu shortcut
|
; Remove start menu shortcut
|
||||||
!insertmacro MUI_STARTMENU_GETFOLDER Application $AppStartMenuFolder
|
!insertmacro MUI_STARTMENU_GETFOLDER Application $AppStartMenuFolder
|
||||||
Delete "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk"
|
Delete "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk"
|
||||||
; 兼容旧名称快捷方式
|
; 兼容旧名称快捷方式
|
||||||
Delete "$SMPROGRAMS\$AppStartMenuFolder\clash-verge.lnk"
|
Delete "$SMPROGRAMS\$AppStartMenuFolder\koala-clash.lnk"
|
||||||
RMDir "$SMPROGRAMS\$AppStartMenuFolder"
|
RMDir "$SMPROGRAMS\$AppStartMenuFolder"
|
||||||
|
|
||||||
; Remove desktop shortcuts
|
; Remove desktop shortcuts
|
||||||
Delete "$DESKTOP\${PRODUCTNAME}.lnk"
|
Delete "$DESKTOP\${PRODUCTNAME}.lnk"
|
||||||
; 兼容旧名称快捷方式
|
; 兼容旧名称快捷方式
|
||||||
Delete "$DESKTOP\clash-verge.lnk"
|
Delete "$DESKTOP\koala-clash.lnk"
|
||||||
|
|
||||||
; Remove registry information for add/remove programs
|
; Remove registry information for add/remove programs
|
||||||
!if "${INSTALLMODE}" == "both"
|
!if "${INSTALLMODE}" == "both"
|
||||||
@@ -1017,7 +1017,7 @@ Section Uninstall
|
|||||||
|
|
||||||
;删除 window-state.json 文件
|
;删除 window-state.json 文件
|
||||||
SetShellVarContext current
|
SetShellVarContext current
|
||||||
Delete "$APPDATA\io.github.clash-verge-rev.clash-verge-rev\window-state.json"
|
Delete "$APPDATA\io.github.koala-clash\window-state.json"
|
||||||
|
|
||||||
${GetOptions} $CMDLINE "/P" $R0
|
${GetOptions} $CMDLINE "/P" $R0
|
||||||
IfErrors +2 0
|
IfErrors +2 0
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ use crate::{
|
|||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio::sync::{Mutex, RwLock};
|
use tokio::sync::{Mutex, RwLock};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use url::Url;
|
||||||
|
use serde_yaml::Value;
|
||||||
|
use base64::{engine::general_purpose::STANDARD, Engine as _};
|
||||||
|
use percent_encoding::percent_decode_str;
|
||||||
|
|
||||||
// 全局互斥锁防止并发配置更新
|
// 全局互斥锁防止并发配置更新
|
||||||
static PROFILE_UPDATE_MUTEX: Mutex<()> = Mutex::const_new(());
|
static PROFILE_UPDATE_MUTEX: Mutex<()> = Mutex::const_new(());
|
||||||
@@ -171,7 +176,34 @@ pub async fn update_profile(index: String, option: Option<PrfOption>) -> CmdResu
|
|||||||
/// 删除配置文件
|
/// 删除配置文件
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn delete_profile(index: String) -> CmdResult {
|
pub async fn delete_profile(index: String) -> CmdResult {
|
||||||
let should_update = wrap_err!({ Config::profiles().data().delete_item(index) })?;
|
let should_update;
|
||||||
|
|
||||||
|
{
|
||||||
|
let profiles_config = Config::profiles();
|
||||||
|
let mut profiles_data = profiles_config.data();
|
||||||
|
should_update = profiles_data.delete_item(index.clone()).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let was_last_profile = profiles_data.items.as_ref().map_or(true, |items| {
|
||||||
|
!items.iter().any(|item|
|
||||||
|
item.itype == Some("remote".to_string()) || item.itype == Some("local".to_string())
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
if was_last_profile {
|
||||||
|
logging!(info, Type::Cmd, true, "The last profile has been deleted. Disabling proxy modes...");
|
||||||
|
let verge_config = Config::verge();
|
||||||
|
let mut verge_data = verge_config.data();
|
||||||
|
|
||||||
|
if verge_data.enable_tun_mode == Some(true) || verge_data.enable_system_proxy == Some(true) {
|
||||||
|
verge_data.enable_tun_mode = Some(false);
|
||||||
|
verge_data.enable_system_proxy = Some(false);
|
||||||
|
verge_data.save_file().map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
handle::Handle::refresh_verge();
|
||||||
|
handle::Handle::notice_message("info", "All profiles deleted, proxy disabled.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 删除后自动清理冗余文件
|
// 删除后自动清理冗余文件
|
||||||
let _ = Config::profiles().latest().auto_cleanup();
|
let _ = Config::profiles().latest().auto_cleanup();
|
||||||
@@ -708,3 +740,467 @@ pub async fn update_profiles_on_startup() -> CmdResult {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn create_profile_from_share_link(link: String, template_name: String) -> CmdResult {
|
||||||
|
|
||||||
|
const DEFAULT_TEMPLATE: &str = r#"
|
||||||
|
mixed-port: 2080
|
||||||
|
allow-lan: true
|
||||||
|
tcp-concurrent: true
|
||||||
|
enable-process: true
|
||||||
|
find-process-mode: always
|
||||||
|
global-client-fingerprint: chrome
|
||||||
|
mode: rule
|
||||||
|
log-level: debug
|
||||||
|
ipv6: false
|
||||||
|
keep-alive-interval: 30
|
||||||
|
unified-delay: false
|
||||||
|
profile:
|
||||||
|
store-selected: true
|
||||||
|
store-fake-ip: true
|
||||||
|
sniffer:
|
||||||
|
enable: true
|
||||||
|
sniff:
|
||||||
|
HTTP:
|
||||||
|
ports: [80, 8080-8880]
|
||||||
|
override-destination: true
|
||||||
|
TLS:
|
||||||
|
ports: [443, 8443]
|
||||||
|
QUIC:
|
||||||
|
ports: [443, 8443]
|
||||||
|
tun:
|
||||||
|
enable: true
|
||||||
|
stack: mixed
|
||||||
|
dns-hijack: ['any:53']
|
||||||
|
auto-route: true
|
||||||
|
auto-detect-interface: true
|
||||||
|
strict-route: true
|
||||||
|
dns:
|
||||||
|
enable: true
|
||||||
|
listen: :1053
|
||||||
|
prefer-h3: false
|
||||||
|
ipv6: false
|
||||||
|
enhanced-mode: fake-ip
|
||||||
|
fake-ip-filter: ['+.lan', '+.local']
|
||||||
|
nameserver: ['https://doh.dns.sb/dns-query']
|
||||||
|
proxies:
|
||||||
|
- name: myproxy
|
||||||
|
type: vless
|
||||||
|
server: YOURDOMAIN
|
||||||
|
port: 443
|
||||||
|
uuid: YOURUUID
|
||||||
|
network: tcp
|
||||||
|
flow: xtls-rprx-vision
|
||||||
|
udp: true
|
||||||
|
tls: true
|
||||||
|
reality-opts:
|
||||||
|
public-key: YOURPUBLIC
|
||||||
|
short-id: YOURSHORTID
|
||||||
|
servername: YOURREALITYDEST
|
||||||
|
client-fingerprint: chrome
|
||||||
|
proxy-groups:
|
||||||
|
- name: PROXY
|
||||||
|
type: select
|
||||||
|
proxies:
|
||||||
|
- myproxy
|
||||||
|
rule-providers:
|
||||||
|
ru-bundle:
|
||||||
|
type: http
|
||||||
|
behavior: domain
|
||||||
|
format: mrs
|
||||||
|
url: https://github.com/legiz-ru/mihomo-rule-sets/raw/main/ru-bundle/rule.mrs
|
||||||
|
path: ./ru-bundle/rule.mrs
|
||||||
|
interval: 86400
|
||||||
|
refilter_domains:
|
||||||
|
type: http
|
||||||
|
behavior: domain
|
||||||
|
format: mrs
|
||||||
|
url: https://github.com/legiz-ru/mihomo-rule-sets/raw/main/re-filter/domain-rule.mrs
|
||||||
|
path: ./re-filter/domain-rule.mrs
|
||||||
|
interval: 86400
|
||||||
|
refilter_ipsum:
|
||||||
|
type: http
|
||||||
|
behavior: ipcidr
|
||||||
|
format: mrs
|
||||||
|
url: https://github.com/legiz-ru/mihomo-rule-sets/raw/main/re-filter/ip-rule.mrs
|
||||||
|
path: ./re-filter/ip-rule.mrs
|
||||||
|
interval: 86400
|
||||||
|
oisd_big:
|
||||||
|
type: http
|
||||||
|
behavior: domain
|
||||||
|
format: mrs
|
||||||
|
url: https://github.com/legiz-ru/mihomo-rule-sets/raw/main/oisd/big.mrs
|
||||||
|
path: ./oisd/big.mrs
|
||||||
|
interval: 86400
|
||||||
|
rules:
|
||||||
|
- OR,((DOMAIN,ipwhois.app),(DOMAIN,ipwho.is),(DOMAIN,api.ip.sb),(DOMAIN,ipapi.co),(DOMAIN,ipinfo.io)),PROXY
|
||||||
|
- RULE-SET,oisd_big,REJECT
|
||||||
|
- PROCESS-NAME,Discord.exe,PROXY
|
||||||
|
- RULE-SET,ru-bundle,PROXY
|
||||||
|
- RULE-SET,refilter_domains,PROXY
|
||||||
|
- RULE-SET,refilter_ipsum,PROXY
|
||||||
|
- MATCH,DIRECT
|
||||||
|
"#;
|
||||||
|
|
||||||
|
const WITHOUT_RU_TEMPLATE: &str = r#"
|
||||||
|
mixed-port: 7890
|
||||||
|
allow-lan: true
|
||||||
|
tcp-concurrent: true
|
||||||
|
enable-process: true
|
||||||
|
find-process-mode: always
|
||||||
|
mode: rule
|
||||||
|
log-level: debug
|
||||||
|
ipv6: false
|
||||||
|
keep-alive-interval: 30
|
||||||
|
unified-delay: false
|
||||||
|
profile:
|
||||||
|
store-selected: true
|
||||||
|
store-fake-ip: true
|
||||||
|
sniffer:
|
||||||
|
enable: true
|
||||||
|
force-dns-mapping: true
|
||||||
|
parse-pure-ip: true
|
||||||
|
sniff:
|
||||||
|
HTTP:
|
||||||
|
ports:
|
||||||
|
- 80
|
||||||
|
- 8080-8880
|
||||||
|
override-destination: true
|
||||||
|
TLS:
|
||||||
|
ports:
|
||||||
|
- 443
|
||||||
|
- 8443
|
||||||
|
tun:
|
||||||
|
enable: true
|
||||||
|
stack: gvisor
|
||||||
|
auto-route: true
|
||||||
|
auto-detect-interface: false
|
||||||
|
dns-hijack:
|
||||||
|
- any:53
|
||||||
|
strict-route: true
|
||||||
|
mtu: 1500
|
||||||
|
dns:
|
||||||
|
enable: true
|
||||||
|
prefer-h3: true
|
||||||
|
use-hosts: true
|
||||||
|
use-system-hosts: true
|
||||||
|
listen: 127.0.0.1:6868
|
||||||
|
ipv6: false
|
||||||
|
enhanced-mode: redir-host
|
||||||
|
default-nameserver:
|
||||||
|
- tls://1.1.1.1
|
||||||
|
- tls://1.0.0.1
|
||||||
|
proxy-server-nameserver:
|
||||||
|
- tls://1.1.1.1
|
||||||
|
- tls://1.0.0.1
|
||||||
|
direct-nameserver:
|
||||||
|
- tls://77.88.8.8
|
||||||
|
nameserver:
|
||||||
|
- https://cloudflare-dns.com/dns-query
|
||||||
|
|
||||||
|
proxies:
|
||||||
|
- name: myproxy
|
||||||
|
type: vless
|
||||||
|
server: YOURDOMAIN
|
||||||
|
port: 443
|
||||||
|
uuid: YOURUUID
|
||||||
|
network: tcp
|
||||||
|
flow: xtls-rprx-vision
|
||||||
|
udp: true
|
||||||
|
tls: true
|
||||||
|
reality-opts:
|
||||||
|
public-key: YOURPUBLIC
|
||||||
|
short-id: YOURSHORTID
|
||||||
|
servername: YOURREALITYDEST
|
||||||
|
client-fingerprint: chrome
|
||||||
|
|
||||||
|
proxy-groups:
|
||||||
|
- name: PROXY
|
||||||
|
icon: https://cdn.jsdelivr.net/gh/Koolson/Qure@master/IconSet/Color/Hijacking.png
|
||||||
|
type: select
|
||||||
|
proxies:
|
||||||
|
- ⚡️ Fastest
|
||||||
|
- 📶 First Available
|
||||||
|
- myproxy
|
||||||
|
- name: ⚡️ Fastest
|
||||||
|
icon: https://cdn.jsdelivr.net/gh/Koolson/Qure@master/IconSet/Color/Auto.png
|
||||||
|
type: url-test
|
||||||
|
tolerance: 150
|
||||||
|
url: https://cp.cloudflare.com/generate_204
|
||||||
|
interval: 300
|
||||||
|
proxies:
|
||||||
|
- myproxy
|
||||||
|
- name: 📶 First Available
|
||||||
|
icon: https://cdn.jsdelivr.net/gh/Koolson/Qure@master/IconSet/Color/Download.png
|
||||||
|
type: fallback
|
||||||
|
url: https://cp.cloudflare.com/generate_204
|
||||||
|
interval: 300
|
||||||
|
proxies:
|
||||||
|
- myproxy
|
||||||
|
|
||||||
|
|
||||||
|
rule-providers:
|
||||||
|
torrent-trackers:
|
||||||
|
type: http
|
||||||
|
behavior: domain
|
||||||
|
format: mrs
|
||||||
|
url: https://github.com/legiz-ru/mihomo-rule-sets/raw/main/other/torrent-trackers.mrs
|
||||||
|
path: ./rule-sets/torrent-trackers.mrs
|
||||||
|
interval: 86400
|
||||||
|
torrent-clients:
|
||||||
|
type: http
|
||||||
|
behavior: classical
|
||||||
|
format: yaml
|
||||||
|
url: https://github.com/legiz-ru/mihomo-rule-sets/raw/main/other/torrent-clients.yaml
|
||||||
|
path: ./rule-sets/torrent-clients.yaml
|
||||||
|
interval: 86400
|
||||||
|
geosite-ru:
|
||||||
|
type: http
|
||||||
|
behavior: domain
|
||||||
|
format: mrs
|
||||||
|
url: https://github.com/MetaCubeX/meta-rules-dat/raw/meta/geo/geosite/category-ru.mrs
|
||||||
|
path: ./geosite-ru.mrs
|
||||||
|
interval: 86400
|
||||||
|
xiaomi:
|
||||||
|
type: http
|
||||||
|
behavior: domain
|
||||||
|
format: mrs
|
||||||
|
url: https://github.com/MetaCubeX/meta-rules-dat/raw/meta/geo/geosite/xiaomi.mrs
|
||||||
|
path: ./rule-sets/xiaomi.mrs
|
||||||
|
interval: 86400
|
||||||
|
blender:
|
||||||
|
type: http
|
||||||
|
behavior: domain
|
||||||
|
format: mrs
|
||||||
|
url: https://github.com/MetaCubeX/meta-rules-dat/raw/meta/geo/geosite/blender.mrs
|
||||||
|
path: ./rule-sets/blender.mrs
|
||||||
|
interval: 86400
|
||||||
|
drweb:
|
||||||
|
type: http
|
||||||
|
behavior: domain
|
||||||
|
format: mrs
|
||||||
|
url: https://github.com/MetaCubeX/meta-rules-dat/raw/meta/geo/geosite/drweb.mrs
|
||||||
|
path: ./rule-sets/drweb.mrs
|
||||||
|
interval: 86400
|
||||||
|
debian:
|
||||||
|
type: http
|
||||||
|
behavior: domain
|
||||||
|
format: mrs
|
||||||
|
url: https://github.com/MetaCubeX/meta-rules-dat/raw/meta/geo/geosite/debian.mrs
|
||||||
|
path: ./rule-sets/debian.mrs
|
||||||
|
interval: 86400
|
||||||
|
canonical:
|
||||||
|
type: http
|
||||||
|
behavior: domain
|
||||||
|
format: mrs
|
||||||
|
url: https://github.com/MetaCubeX/meta-rules-dat/raw/meta/geo/geosite/canonical.mrs
|
||||||
|
path: ./rule-sets/canonical.mrs
|
||||||
|
interval: 86400
|
||||||
|
python:
|
||||||
|
type: http
|
||||||
|
behavior: domain
|
||||||
|
format: mrs
|
||||||
|
url: https://github.com/MetaCubeX/meta-rules-dat/raw/meta/geo/geosite/python.mrs
|
||||||
|
path: ./rule-sets/python.mrs
|
||||||
|
interval: 86400
|
||||||
|
geoip-ru:
|
||||||
|
type: http
|
||||||
|
behavior: ipcidr
|
||||||
|
format: mrs
|
||||||
|
url: https://github.com/MetaCubeX/meta-rules-dat/raw/meta/geo/geoip/ru.mrs
|
||||||
|
path: ./geoip-ru.mrs
|
||||||
|
interval: 86400
|
||||||
|
geosite-private:
|
||||||
|
type: http
|
||||||
|
behavior: domain
|
||||||
|
format: mrs
|
||||||
|
url: https://github.com/MetaCubeX/meta-rules-dat/raw/meta/geo/geosite/private.mrs
|
||||||
|
path: ./geosite-private.mrs
|
||||||
|
interval: 86400
|
||||||
|
geoip-private:
|
||||||
|
type: http
|
||||||
|
behavior: ipcidr
|
||||||
|
format: mrs
|
||||||
|
url: https://github.com/MetaCubeX/meta-rules-dat/raw/meta/geo/geoip/private.mrs
|
||||||
|
path: ./geoip-private.mrs
|
||||||
|
interval: 86400
|
||||||
|
|
||||||
|
rules:
|
||||||
|
- DOMAIN-SUFFIX,habr.com,PROXY
|
||||||
|
- DOMAIN-SUFFIX,kemono.su,PROXY
|
||||||
|
- DOMAIN-SUFFIX,jut.su,PROXY
|
||||||
|
- DOMAIN-SUFFIX,kara.su,PROXY
|
||||||
|
- DOMAIN-SUFFIX,theins.ru,PROXY
|
||||||
|
- DOMAIN-SUFFIX,tvrain.ru,PROXY
|
||||||
|
- DOMAIN-SUFFIX,echo.msk.ru,PROXY
|
||||||
|
- DOMAIN-SUFFIX,the-village.ru,PROXY
|
||||||
|
- DOMAIN-SUFFIX,snob.ru,PROXY
|
||||||
|
- DOMAIN-SUFFIX,novayagazeta.ru,PROXY
|
||||||
|
- DOMAIN-SUFFIX,moscowtimes.ru,PROXY
|
||||||
|
- DOMAIN-KEYWORD,animego,PROXY
|
||||||
|
- DOMAIN-KEYWORD,yummyanime,PROXY
|
||||||
|
- DOMAIN-KEYWORD,yummy-anime,PROXY
|
||||||
|
- DOMAIN-KEYWORD,animeportal,PROXY
|
||||||
|
- DOMAIN-KEYWORD,anime-portal,PROXY
|
||||||
|
- DOMAIN-KEYWORD,animedub,PROXY
|
||||||
|
- DOMAIN-KEYWORD,anidub,PROXY
|
||||||
|
- DOMAIN-KEYWORD,animelib,PROXY
|
||||||
|
- DOMAIN-KEYWORD,ikianime,PROXY
|
||||||
|
- DOMAIN-KEYWORD,anilibria,PROXY
|
||||||
|
- PROCESS-NAME,Discord.exe,PROXY
|
||||||
|
- PROCESS-NAME,discord,PROXY
|
||||||
|
- RULE-SET,geosite-private,DIRECT,no-resolve
|
||||||
|
- RULE-SET,geoip-private,DIRECT
|
||||||
|
- RULE-SET,torrent-clients,DIRECT
|
||||||
|
- RULE-SET,torrent-trackers,DIRECT
|
||||||
|
- DOMAIN-SUFFIX,.ru,DIRECT
|
||||||
|
- DOMAIN-SUFFIX,.su,DIRECT
|
||||||
|
- DOMAIN-SUFFIX,.ru.com,DIRECT
|
||||||
|
- DOMAIN-SUFFIX,.ru.net,DIRECT
|
||||||
|
- DOMAIN-SUFFIX,wikipedia.org,DIRECT
|
||||||
|
- DOMAIN-SUFFIX,kudago.com,DIRECT
|
||||||
|
- DOMAIN-SUFFIX,kinescope.io,DIRECT
|
||||||
|
- DOMAIN-SUFFIX,redheadsound.studio,DIRECT
|
||||||
|
- DOMAIN-SUFFIX,plplayer.online,DIRECT
|
||||||
|
- DOMAIN-SUFFIX,lomont.site,DIRECT
|
||||||
|
- DOMAIN-SUFFIX,remanga.org,DIRECT
|
||||||
|
- DOMAIN-SUFFIX,shopstory.live,DIRECT
|
||||||
|
- DOMAIN-KEYWORD,miradres,DIRECT
|
||||||
|
- DOMAIN-KEYWORD,premier,DIRECT
|
||||||
|
- DOMAIN-KEYWORD,shutterstock,DIRECT
|
||||||
|
- DOMAIN-KEYWORD,2gis,DIRECT
|
||||||
|
- DOMAIN-KEYWORD,diginetica,DIRECT
|
||||||
|
- DOMAIN-KEYWORD,kinescopecdn,DIRECT
|
||||||
|
- DOMAIN-KEYWORD,researchgate,DIRECT
|
||||||
|
- DOMAIN-KEYWORD,springer,DIRECT
|
||||||
|
- DOMAIN-KEYWORD,nextcloud,DIRECT
|
||||||
|
- DOMAIN-KEYWORD,wiki,DIRECT
|
||||||
|
- DOMAIN-KEYWORD,kaspersky,DIRECT
|
||||||
|
- DOMAIN-KEYWORD,stepik,DIRECT
|
||||||
|
- DOMAIN-KEYWORD,likee,DIRECT
|
||||||
|
- DOMAIN-KEYWORD,snapchat,DIRECT
|
||||||
|
- DOMAIN-KEYWORD,yappy,DIRECT
|
||||||
|
- DOMAIN-KEYWORD,pikabu,DIRECT
|
||||||
|
- DOMAIN-KEYWORD,okko,DIRECT
|
||||||
|
- DOMAIN-KEYWORD,wink,DIRECT
|
||||||
|
- DOMAIN-KEYWORD,kion,DIRECT
|
||||||
|
- DOMAIN-KEYWORD,roblox,DIRECT
|
||||||
|
- DOMAIN-KEYWORD,ozon,DIRECT
|
||||||
|
- DOMAIN-KEYWORD,wildberries,DIRECT
|
||||||
|
- DOMAIN-KEYWORD,aliexpress,DIRECT
|
||||||
|
- RULE-SET,geosite-ru,DIRECT
|
||||||
|
- RULE-SET,xiaomi,DIRECT
|
||||||
|
- RULE-SET,blender,DIRECT
|
||||||
|
- RULE-SET,drweb,DIRECT
|
||||||
|
- RULE-SET,debian,DIRECT
|
||||||
|
- RULE-SET,canonical,DIRECT
|
||||||
|
- RULE-SET,python,DIRECT
|
||||||
|
- RULE-SET,geoip-ru,DIRECT
|
||||||
|
- MATCH,PROXY
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let template_yaml = match template_name.as_str() {
|
||||||
|
"without_ru" => WITHOUT_RU_TEMPLATE,
|
||||||
|
_ => DEFAULT_TEMPLATE,
|
||||||
|
};
|
||||||
|
|
||||||
|
let parsed_url = Url::parse(&link).map_err(|e| e.to_string())?;
|
||||||
|
let scheme = parsed_url.scheme();
|
||||||
|
let proxy_name = parsed_url.fragment()
|
||||||
|
.map(|f| percent_decode_str(f).decode_utf8_lossy().to_string())
|
||||||
|
.unwrap_or_else(|| "Proxy from Link".to_string());
|
||||||
|
|
||||||
|
let mut proxy_map: BTreeMap<String, Value> = BTreeMap::new();
|
||||||
|
proxy_map.insert("name".into(), proxy_name.clone().into());
|
||||||
|
proxy_map.insert("type".into(), scheme.into());
|
||||||
|
proxy_map.insert("server".into(), parsed_url.host_str().unwrap_or_default().into());
|
||||||
|
proxy_map.insert("port".into(), parsed_url.port().unwrap_or(443).into());
|
||||||
|
proxy_map.insert("udp".into(), true.into());
|
||||||
|
|
||||||
|
match scheme {
|
||||||
|
"vless" | "trojan" => {
|
||||||
|
proxy_map.insert("uuid".into(), parsed_url.username().into());
|
||||||
|
let mut reality_opts: BTreeMap<String, Value> = BTreeMap::new();
|
||||||
|
for (key, value) in parsed_url.query_pairs() {
|
||||||
|
match key.as_ref() {
|
||||||
|
"security" if value == "reality" => {
|
||||||
|
proxy_map.insert("tls".into(), true.into());
|
||||||
|
}
|
||||||
|
"security" if value == "tls" => {
|
||||||
|
proxy_map.insert("tls".into(), true.into());
|
||||||
|
}
|
||||||
|
"flow" => { proxy_map.insert("flow".into(), value.to_string().into()); }
|
||||||
|
"sni" => { proxy_map.insert("servername".into(), value.to_string().into()); }
|
||||||
|
"fp" => { proxy_map.insert("client-fingerprint".into(), value.to_string().into()); }
|
||||||
|
"pbk" => { reality_opts.insert("public-key".into(), value.to_string().into()); }
|
||||||
|
"sid" => { reality_opts.insert("short-id".into(), value.to_string().into()); }
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !reality_opts.is_empty() {
|
||||||
|
proxy_map.insert("reality-opts".into(), serde_yaml::to_value(reality_opts).map_err(|e| e.to_string())?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"ss" => {
|
||||||
|
if let Ok(decoded_user) = STANDARD.decode(parsed_url.username()) {
|
||||||
|
if let Ok(user_str) = String::from_utf8(decoded_user) {
|
||||||
|
if let Some((cipher, password)) = user_str.split_once(':') {
|
||||||
|
proxy_map.insert("cipher".into(), cipher.into());
|
||||||
|
proxy_map.insert("password".into(), password.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"vmess" => {
|
||||||
|
if let Ok(decoded_bytes) = STANDARD.decode(parsed_url.host_str().unwrap_or_default()) {
|
||||||
|
if let Ok(json_str) = String::from_utf8(decoded_bytes) {
|
||||||
|
if let Ok(vmess_params) = serde_json::from_str::<BTreeMap<String, Value>>(&json_str) {
|
||||||
|
if let Some(add) = vmess_params.get("add") { proxy_map.insert("server".into(), add.clone()); }
|
||||||
|
if let Some(port) = vmess_params.get("port") { proxy_map.insert("port".into(), port.clone()); }
|
||||||
|
if let Some(id) = vmess_params.get("id") { proxy_map.insert("uuid".into(), id.clone()); }
|
||||||
|
if let Some(aid) = vmess_params.get("aid") { proxy_map.insert("alterId".into(), aid.clone()); }
|
||||||
|
if let Some(net) = vmess_params.get("net") { proxy_map.insert("network".into(), net.clone()); }
|
||||||
|
if let Some(ps) = vmess_params.get("ps") { proxy_map.insert("name".into(), ps.clone()); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut config: Value = serde_yaml::from_str(template_yaml).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
if let Some(proxies) = config.get_mut("proxies").and_then(|v| v.as_sequence_mut()) {
|
||||||
|
proxies.clear();
|
||||||
|
proxies.push(serde_yaml::to_value(proxy_map).map_err(|e| e.to_string())?);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(groups) = config.get_mut("proxy-groups").and_then(|v| v.as_sequence_mut()) {
|
||||||
|
for group in groups.iter_mut() {
|
||||||
|
if let Some(mapping) = group.as_mapping_mut() {
|
||||||
|
if let Some(proxies_list) = mapping.get_mut("proxies").and_then(|p| p.as_sequence_mut()) {
|
||||||
|
let new_proxies_list: Vec<Value> = proxies_list
|
||||||
|
.iter()
|
||||||
|
.map(|p| {
|
||||||
|
if p.as_str() == Some("myproxy") {
|
||||||
|
proxy_name.clone().into()
|
||||||
|
} else {
|
||||||
|
p.clone()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
*proxies_list = new_proxies_list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_yaml_content = serde_yaml::to_string(&config).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let item = PrfItem::from_local(proxy_name, "Created from share link".into(), Some(new_yaml_content), None)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
wrap_err!(Config::profiles().data().append_item(item))
|
||||||
|
}
|
||||||
@@ -129,7 +129,7 @@ impl IClashTemp {
|
|||||||
help::save_yaml(
|
help::save_yaml(
|
||||||
&dirs::clash_path()?,
|
&dirs::clash_path()?,
|
||||||
&self.0,
|
&self.0,
|
||||||
Some("# Generated by Clash Verge"),
|
Some("# Generated by Koala Clash"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ use once_cell::sync::OnceCell;
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tokio::time::{sleep, Duration};
|
use tokio::time::{sleep, Duration};
|
||||||
|
|
||||||
pub const RUNTIME_CONFIG: &str = "clash-verge.yaml";
|
pub const RUNTIME_CONFIG: &str = "koala-clash.yaml";
|
||||||
pub const CHECK_CONFIG: &str = "clash-verge-check.yaml";
|
pub const CHECK_CONFIG: &str = "koala-clash-check.yaml";
|
||||||
|
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
clash_config: Draft<Box<IClashTemp>>,
|
clash_config: Draft<Box<IClashTemp>>,
|
||||||
@@ -141,7 +141,7 @@ impl Config {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or(anyhow!("failed to get runtime config"))?;
|
.ok_or(anyhow!("failed to get runtime config"))?;
|
||||||
|
|
||||||
help::save_yaml(&path, &config, Some("# Generated by Clash Verge"))?;
|
help::save_yaml(&path, &config, Some("# Generated by Koala Clash"))?;
|
||||||
Ok(path)
|
Ok(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ use crate::utils::{
|
|||||||
tmpl,
|
tmpl,
|
||||||
};
|
};
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
|
use base64::{engine::general_purpose::STANDARD, Engine as _};
|
||||||
use reqwest::StatusCode;
|
use reqwest::StatusCode;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_yaml::Mapping;
|
use serde_yaml::Mapping;
|
||||||
use std::{fs, time::Duration};
|
use std::{fs, time::Duration};
|
||||||
use base64::{engine::general_purpose::STANDARD, Engine as _};
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use super::Config;
|
use super::Config;
|
||||||
@@ -407,7 +407,8 @@ impl PrfItem {
|
|||||||
Some(value) => {
|
Some(value) => {
|
||||||
let str_value = value.to_str().unwrap_or("");
|
let str_value = value.to_str().unwrap_or("");
|
||||||
if let Some(b64_data) = str_value.strip_prefix("base64:") {
|
if let Some(b64_data) = str_value.strip_prefix("base64:") {
|
||||||
STANDARD.decode(b64_data)
|
STANDARD
|
||||||
|
.decode(b64_data)
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|bytes| String::from_utf8(bytes).ok())
|
.and_then(|bytes| String::from_utf8(bytes).ok())
|
||||||
} else {
|
} else {
|
||||||
@@ -417,6 +418,13 @@ impl PrfItem {
|
|||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Some(announce_msg) = &announce {
|
||||||
|
let lower_msg = announce_msg.to_lowercase();
|
||||||
|
if lower_msg.contains("device") || lower_msg.contains("устройств") {
|
||||||
|
bail!(announce_msg.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let announce_url = match header.get("announce-url") {
|
let announce_url = match header.get("announce-url") {
|
||||||
Some(value) => {
|
Some(value) => {
|
||||||
let str_value = value.to_str().unwrap_or("");
|
let str_value = value.to_str().unwrap_or("");
|
||||||
@@ -429,7 +437,8 @@ impl PrfItem {
|
|||||||
Some(value) => {
|
Some(value) => {
|
||||||
let str_value = value.to_str().unwrap_or("");
|
let str_value = value.to_str().unwrap_or("");
|
||||||
if let Some(b64_data) = str_value.strip_prefix("base64:") {
|
if let Some(b64_data) = str_value.strip_prefix("base64:") {
|
||||||
STANDARD.decode(b64_data)
|
STANDARD
|
||||||
|
.decode(b64_data)
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|bytes| String::from_utf8(bytes).ok())
|
.and_then(|bytes| String::from_utf8(bytes).ok())
|
||||||
} else {
|
} else {
|
||||||
@@ -441,7 +450,9 @@ impl PrfItem {
|
|||||||
|
|
||||||
let uid = help::get_uid("R");
|
let uid = help::get_uid("R");
|
||||||
let file = format!("{uid}.yaml");
|
let file = format!("{uid}.yaml");
|
||||||
let name = name.or(profile_title).unwrap_or(filename.unwrap_or("Remote File".into()));
|
let name = name
|
||||||
|
.or(profile_title)
|
||||||
|
.unwrap_or(filename.unwrap_or("Remote File".into()));
|
||||||
let data = resp.text_with_charset("utf-8").await?;
|
let data = resp.text_with_charset("utf-8").await?;
|
||||||
|
|
||||||
// process the charset "UTF-8 with BOM"
|
// process the charset "UTF-8 with BOM"
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ impl IProfiles {
|
|||||||
help::save_yaml(
|
help::save_yaml(
|
||||||
&dirs::profiles_path()?,
|
&dirs::profiles_path()?,
|
||||||
self,
|
self,
|
||||||
Some("# Profiles Config for Clash Verge"),
|
Some("# Profiles Config for Koala Clash"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ pub struct IVergeTheme {
|
|||||||
|
|
||||||
impl IVerge {
|
impl IVerge {
|
||||||
/// 有效的clash核心名称
|
/// 有效的clash核心名称
|
||||||
pub const VALID_CLASH_CORES: &'static [&'static str] = &["verge-mihomo", "verge-mihomo-alpha"];
|
pub const VALID_CLASH_CORES: &'static [&'static str] = &["koala-mihomo", "koala-mihomo-alpha"];
|
||||||
|
|
||||||
/// 验证并修正配置文件中的clash_core值
|
/// 验证并修正配置文件中的clash_core值
|
||||||
pub fn validate_and_fix_config() -> Result<()> {
|
pub fn validate_and_fix_config() -> Result<()> {
|
||||||
@@ -257,10 +257,10 @@ impl IVerge {
|
|||||||
warn,
|
warn,
|
||||||
Type::Config,
|
Type::Config,
|
||||||
true,
|
true,
|
||||||
"启动时发现无效的clash_core配置: '{}', 将自动修正为 'verge-mihomo'",
|
"启动时发现无效的clash_core配置: '{}', 将自动修正为 'koala-mihomo'",
|
||||||
core
|
core
|
||||||
);
|
);
|
||||||
config.clash_core = Some("verge-mihomo".to_string());
|
config.clash_core = Some("koala-mihomo".to_string());
|
||||||
needs_fix = true;
|
needs_fix = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -268,16 +268,16 @@ impl IVerge {
|
|||||||
info,
|
info,
|
||||||
Type::Config,
|
Type::Config,
|
||||||
true,
|
true,
|
||||||
"启动时发现未配置clash_core, 将设置为默认值 'verge-mihomo'"
|
"启动时发现未配置clash_core, 将设置为默认值 'koala-mihomo'"
|
||||||
);
|
);
|
||||||
config.clash_core = Some("verge-mihomo".to_string());
|
config.clash_core = Some("koala-mihomo".to_string());
|
||||||
needs_fix = true;
|
needs_fix = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 修正后保存配置
|
// 修正后保存配置
|
||||||
if needs_fix {
|
if needs_fix {
|
||||||
logging!(info, Type::Config, true, "正在保存修正后的配置文件...");
|
logging!(info, Type::Config, true, "正在保存修正后的配置文件...");
|
||||||
help::save_yaml(&config_path, &config, Some("# Clash Verge Config"))?;
|
help::save_yaml(&config_path, &config, Some("# Koala Clash Config"))?;
|
||||||
logging!(
|
logging!(
|
||||||
info,
|
info,
|
||||||
Type::Config,
|
Type::Config,
|
||||||
@@ -321,7 +321,7 @@ impl IVerge {
|
|||||||
pub fn get_valid_clash_core(&self) -> String {
|
pub fn get_valid_clash_core(&self) -> String {
|
||||||
self.clash_core
|
self.clash_core
|
||||||
.clone()
|
.clone()
|
||||||
.unwrap_or_else(|| "verge-mihomo".to_string())
|
.unwrap_or_else(|| "koala-mihomo".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_system_language() -> String {
|
fn get_system_language() -> String {
|
||||||
@@ -340,18 +340,15 @@ impl IVerge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
match dirs::verge_path().and_then(|path| help::read_yaml::<IVerge>(&path)) {
|
dirs::verge_path().and_then(|path| help::read_yaml::<IVerge>(&path)).unwrap_or_else(|err| {
|
||||||
Ok(config) => config,
|
log::error!(target: "app", "{err}");
|
||||||
Err(err) => {
|
Self::template()
|
||||||
log::error!(target: "app", "{err}");
|
})
|
||||||
Self::template()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn template() -> Self {
|
pub fn template() -> Self {
|
||||||
Self {
|
Self {
|
||||||
clash_core: Some("verge-mihomo".into()),
|
clash_core: Some("koala-mihomo".into()),
|
||||||
language: Some(Self::get_system_language()),
|
language: Some(Self::get_system_language()),
|
||||||
theme_mode: Some("system".into()),
|
theme_mode: Some("system".into()),
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
@@ -415,7 +412,7 @@ impl IVerge {
|
|||||||
|
|
||||||
/// Save IVerge App Config
|
/// Save IVerge App Config
|
||||||
pub fn save_file(&self) -> Result<()> {
|
pub fn save_file(&self) -> Result<()> {
|
||||||
help::save_yaml(&dirs::verge_path()?, &self, Some("# Clash Verge Config"))
|
help::save_yaml(&dirs::verge_path()?, &self, Some("# Koala Clash Config"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// patch verge config
|
/// patch verge config
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ impl WebDavClient {
|
|||||||
reqwest::Client::builder()
|
reqwest::Client::builder()
|
||||||
.danger_accept_invalid_certs(true)
|
.danger_accept_invalid_certs(true)
|
||||||
.timeout(Duration::from_secs(op.timeout()))
|
.timeout(Duration::from_secs(op.timeout()))
|
||||||
.user_agent(format!("clash-verge/{APP_VERSION} ({OS} WebDAV-Client)"))
|
.user_agent(format!("koala-clash/{APP_VERSION} ({OS} WebDAV-Client)"))
|
||||||
.redirect(reqwest::redirect::Policy::custom(|attempt| {
|
.redirect(reqwest::redirect::Policy::custom(|attempt| {
|
||||||
// 允许所有请求类型的重定向,包括PUT
|
// 允许所有请求类型的重定向,包括PUT
|
||||||
if attempt.previous().len() >= 5 {
|
if attempt.previous().len() >= 5 {
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ impl CoreManager {
|
|||||||
help::save_yaml(
|
help::save_yaml(
|
||||||
&runtime_path,
|
&runtime_path,
|
||||||
&Config::clash().latest().0,
|
&Config::clash().latest().0,
|
||||||
Some("# Clash Verge Runtime"),
|
Some("# Koala Clash Runtime"),
|
||||||
)?;
|
)?;
|
||||||
handle::Handle::notice_message(msg_type, msg_content);
|
handle::Handle::notice_message(msg_type, msg_content);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -443,7 +443,7 @@ impl CoreManager {
|
|||||||
child_guard.as_ref().map(|child| child.pid())
|
child_guard.as_ref().map(|child| child.pid())
|
||||||
};
|
};
|
||||||
|
|
||||||
let target_processes = ["verge-mihomo", "verge-mihomo-alpha"];
|
let target_processes = ["koala-mihomo", "koala-mihomo-alpha"];
|
||||||
|
|
||||||
// 并行查找所有目标进程
|
// 并行查找所有目标进程
|
||||||
let mut process_futures = Vec::new();
|
let mut process_futures = Vec::new();
|
||||||
|
|||||||
@@ -578,7 +578,7 @@ pub async fn check_ipc_service_status() -> Result<JsonResponse> {
|
|||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logging!(error, Type::Service, true, "IPC通信失败: {}", e);
|
logging!(error, Type::Service, true, "IPC通信失败: {}", e);
|
||||||
bail!("无法连接到Clash Verge Service: {}", e)
|
bail!("无法连接到Koala Clash Service: {}", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -667,7 +667,7 @@ pub async fn check_service_version() -> Result<String> {
|
|||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logging!(error, Type::Service, true, "IPC通信失败: {}", e);
|
logging!(error, Type::Service, true, "IPC通信失败: {}", e);
|
||||||
bail!("无法连接到Clash Verge Service: {}", e)
|
bail!("无法连接到Koala Clash Service: {}", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -814,7 +814,7 @@ pub(super) async fn start_with_existing_service(config_file: &PathBuf) -> Result
|
|||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logging!(error, Type::Service, true, "启动核心IPC通信失败: {}", e);
|
logging!(error, Type::Service, true, "启动核心IPC通信失败: {}", e);
|
||||||
bail!("无法连接到Clash Verge Service: {}", e)
|
bail!("无法连接到Koala Clash Service: {}", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -910,7 +910,7 @@ pub(super) async fn stop_core_by_service() -> Result<()> {
|
|||||||
let payload = serde_json::json!({});
|
let payload = serde_json::json!({});
|
||||||
let response = send_ipc_request(IpcCommand::StopClash, payload)
|
let response = send_ipc_request(IpcCommand::StopClash, payload)
|
||||||
.await
|
.await
|
||||||
.context("无法连接到Clash Verge Service")?;
|
.context("无法连接到Koala Clash Service")?;
|
||||||
|
|
||||||
if !response.success {
|
if !response.success {
|
||||||
bail!(response.error.unwrap_or_else(|| "停止核心失败".to_string()));
|
bail!(response.error.unwrap_or_else(|| "停止核心失败".to_string()));
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ use sha2::{Digest, Sha256};
|
|||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
const IPC_SOCKET_NAME: &str = if cfg!(windows) {
|
const IPC_SOCKET_NAME: &str = if cfg!(windows) {
|
||||||
r"\\.\pipe\clash-verge-service"
|
r"\\.\pipe\koala-clash-service"
|
||||||
} else {
|
} else {
|
||||||
"/tmp/clash-verge-service.sock"
|
"/tmp/koala-clash-service.sock"
|
||||||
};
|
};
|
||||||
|
|
||||||
// 定义命令类型
|
// 定义命令类型
|
||||||
@@ -43,7 +43,7 @@ pub struct IpcResponse {
|
|||||||
fn derive_secret_key() -> Vec<u8> {
|
fn derive_secret_key() -> Vec<u8> {
|
||||||
// to do
|
// to do
|
||||||
// 从系统安全存储中获取或从程序安装时生成的密钥文件中读取
|
// 从系统安全存储中获取或从程序安装时生成的密钥文件中读取
|
||||||
let unique_app_id = "clash-verge-app-secret-fuck-me-until-daylight";
|
let unique_app_id = "koala-clash-app-secret-fuck-me-until-daylight";
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
hasher.update(unique_app_id.as_bytes());
|
hasher.update(unique_app_id.as_bytes());
|
||||||
hasher.finalize().to_vec()
|
hasher.finalize().to_vec()
|
||||||
|
|||||||
@@ -414,7 +414,7 @@ impl Tray {
|
|||||||
|
|
||||||
if let Some(tray) = app_handle.tray_by_id("main") {
|
if let Some(tray) = app_handle.tray_by_id("main") {
|
||||||
let _ = tray.set_tooltip(Some(&format!(
|
let _ = tray.set_tooltip(Some(&format!(
|
||||||
"Clash Verge {version}\n{}: {}\n{}: {}\n{}: {}",
|
"Koala Clash {version}\n{}: {}\n{}: {}\n{}: {}",
|
||||||
t("SysProxy"),
|
t("SysProxy"),
|
||||||
switch_map[system_proxy],
|
switch_map[system_proxy],
|
||||||
t("TUN"),
|
t("TUN"),
|
||||||
@@ -601,16 +601,6 @@ fn create_tray_menu(
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let direct_mode = &CheckMenuItem::with_id(
|
|
||||||
app_handle,
|
|
||||||
"direct_mode",
|
|
||||||
t("Direct Mode"),
|
|
||||||
true,
|
|
||||||
mode == "direct",
|
|
||||||
hotkeys.get("clash_mode_direct").map(|s| s.as_str()),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let profiles = &Submenu::with_id_and_items(
|
let profiles = &Submenu::with_id_and_items(
|
||||||
app_handle,
|
app_handle,
|
||||||
"profiles",
|
"profiles",
|
||||||
@@ -650,45 +640,6 @@ fn create_tray_menu(
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let copy_env =
|
|
||||||
&MenuItem::with_id(app_handle, "copy_env", t("Copy Env"), true, None::<&str>).unwrap();
|
|
||||||
|
|
||||||
let open_app_dir = &MenuItem::with_id(
|
|
||||||
app_handle,
|
|
||||||
"open_app_dir",
|
|
||||||
t("Conf Dir"),
|
|
||||||
true,
|
|
||||||
None::<&str>,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let open_core_dir = &MenuItem::with_id(
|
|
||||||
app_handle,
|
|
||||||
"open_core_dir",
|
|
||||||
t("Core Dir"),
|
|
||||||
true,
|
|
||||||
None::<&str>,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let open_logs_dir = &MenuItem::with_id(
|
|
||||||
app_handle,
|
|
||||||
"open_logs_dir",
|
|
||||||
t("Logs Dir"),
|
|
||||||
true,
|
|
||||||
None::<&str>,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let open_dir = &Submenu::with_id_and_items(
|
|
||||||
app_handle,
|
|
||||||
"open_dir",
|
|
||||||
t("Open Dir"),
|
|
||||||
true,
|
|
||||||
&[open_app_dir, open_core_dir, open_logs_dir],
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let restart_clash = &MenuItem::with_id(
|
let restart_clash = &MenuItem::with_id(
|
||||||
app_handle,
|
app_handle,
|
||||||
"restart_clash",
|
"restart_clash",
|
||||||
@@ -736,7 +687,6 @@ fn create_tray_menu(
|
|||||||
separator,
|
separator,
|
||||||
rule_mode,
|
rule_mode,
|
||||||
global_mode,
|
global_mode,
|
||||||
direct_mode,
|
|
||||||
separator,
|
separator,
|
||||||
profiles,
|
profiles,
|
||||||
separator,
|
separator,
|
||||||
@@ -744,8 +694,6 @@ fn create_tray_menu(
|
|||||||
tun_mode,
|
tun_mode,
|
||||||
separator,
|
separator,
|
||||||
lighteweight_mode,
|
lighteweight_mode,
|
||||||
copy_env,
|
|
||||||
open_dir,
|
|
||||||
more,
|
more,
|
||||||
separator,
|
separator,
|
||||||
quit,
|
quit,
|
||||||
@@ -789,16 +737,6 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
|
|||||||
"tun_mode" => {
|
"tun_mode" => {
|
||||||
feat::toggle_tun_mode(None);
|
feat::toggle_tun_mode(None);
|
||||||
}
|
}
|
||||||
"copy_env" => feat::copy_clash_env(),
|
|
||||||
"open_app_dir" => {
|
|
||||||
let _ = cmd::open_app_dir();
|
|
||||||
}
|
|
||||||
"open_core_dir" => {
|
|
||||||
let _ = cmd::open_core_dir();
|
|
||||||
}
|
|
||||||
"open_logs_dir" => {
|
|
||||||
let _ = cmd::open_logs_dir();
|
|
||||||
}
|
|
||||||
"restart_clash" => feat::restart_clash_core(),
|
"restart_clash" => feat::restart_clash_core(),
|
||||||
"restart_app" => feat::restart_app(),
|
"restart_app" => feat::restart_app(),
|
||||||
"entry_lightweight_mode" => {
|
"entry_lightweight_mode" => {
|
||||||
|
|||||||
@@ -108,8 +108,8 @@ impl ChainSupport {
|
|||||||
(self, core.as_str()),
|
(self, core.as_str()),
|
||||||
(ChainSupport::All, _)
|
(ChainSupport::All, _)
|
||||||
| (ChainSupport::Clash, "clash")
|
| (ChainSupport::Clash, "clash")
|
||||||
| (ChainSupport::ClashMeta, "verge-mihomo")
|
| (ChainSupport::ClashMeta, "koala-mihomo")
|
||||||
| (ChainSupport::ClashMetaAlpha, "verge-mihomo-alpha")
|
| (ChainSupport::ClashMetaAlpha, "koala-mihomo-alpha")
|
||||||
),
|
),
|
||||||
None => true,
|
None => true,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ mod utils;
|
|||||||
use crate::{
|
use crate::{
|
||||||
core::hotkey,
|
core::hotkey,
|
||||||
process::AsyncHandler,
|
process::AsyncHandler,
|
||||||
utils::{resolve, resolve::resolve_scheme, server},
|
utils::{resolve, resolve::resolve_scheme},
|
||||||
};
|
};
|
||||||
use config::Config;
|
use config::Config;
|
||||||
use std::sync::{Mutex, Once};
|
use std::sync::{Mutex, Once};
|
||||||
@@ -90,33 +90,6 @@ pub fn run() {
|
|||||||
|
|
||||||
let _ = utils::dirs::init_portable_flag();
|
let _ = utils::dirs::init_portable_flag();
|
||||||
|
|
||||||
// 异步单例检测
|
|
||||||
AsyncHandler::spawn(move || async move {
|
|
||||||
logging!(info, Type::Setup, true, "开始检查单例实例...");
|
|
||||||
match timeout(Duration::from_secs(3), server::check_singleton()).await {
|
|
||||||
Ok(result) => {
|
|
||||||
if result.is_err() {
|
|
||||||
logging!(info, Type::Setup, true, "检测到已有应用实例运行");
|
|
||||||
if let Some(app_handle) = AppHandleManager::global().get() {
|
|
||||||
app_handle.exit(0);
|
|
||||||
} else {
|
|
||||||
std::process::exit(0);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logging!(info, Type::Setup, true, "未检测到其他应用实例");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
logging!(
|
|
||||||
warn,
|
|
||||||
Type::Setup,
|
|
||||||
true,
|
|
||||||
"单例检查超时,假定没有其他实例运行"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
|
std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
|
||||||
|
|
||||||
@@ -125,6 +98,13 @@ pub fn run() {
|
|||||||
|
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
let mut builder = tauri::Builder::default()
|
let mut builder = tauri::Builder::default()
|
||||||
|
.plugin(tauri_plugin_single_instance::init(|app, _argv, _cwd| {
|
||||||
|
if let Some(window) = app.get_webview_window("main") {
|
||||||
|
let _ = window.show();
|
||||||
|
let _ = window.unminimize();
|
||||||
|
let _ = window.set_focus();
|
||||||
|
}
|
||||||
|
}))
|
||||||
.plugin(tauri_plugin_notification::init())
|
.plugin(tauri_plugin_notification::init())
|
||||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||||
.plugin(tauri_plugin_clipboard_manager::init())
|
.plugin(tauri_plugin_clipboard_manager::init())
|
||||||
@@ -304,6 +284,7 @@ pub fn run() {
|
|||||||
cmd::save_profile_file,
|
cmd::save_profile_file,
|
||||||
cmd::get_next_update_time,
|
cmd::get_next_update_time,
|
||||||
cmd::update_profiles_on_startup,
|
cmd::update_profiles_on_startup,
|
||||||
|
cmd::create_profile_from_share_link,
|
||||||
// script validation
|
// script validation
|
||||||
cmd::script_validate_notice,
|
cmd::script_validate_notice,
|
||||||
cmd::validate_script_file,
|
cmd::validate_script_file,
|
||||||
@@ -352,7 +333,7 @@ pub fn run() {
|
|||||||
.get_webview_window("main")
|
.get_webview_window("main")
|
||||||
{
|
{
|
||||||
logging!(info, Type::Window, true, "设置macOS窗口标题");
|
logging!(info, Type::Window, true, "设置macOS窗口标题");
|
||||||
let _ = window.set_title("Clash Verge Rev Lite");
|
let _ = window.set_title("Koala Clash");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -363,6 +344,10 @@ pub fn run() {
|
|||||||
} => {
|
} => {
|
||||||
if !has_visible_windows {
|
if !has_visible_windows {
|
||||||
AppHandleManager::global().set_activation_policy_regular();
|
AppHandleManager::global().set_activation_policy_regular();
|
||||||
|
if let Some(window) = app_handle.get_webview_window("main") {
|
||||||
|
let _ = window.show();
|
||||||
|
let _ = window.set_focus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AppHandleManager::global().init(app_handle.clone());
|
AppHandleManager::global().init(app_handle.clone());
|
||||||
}
|
}
|
||||||
@@ -383,7 +368,6 @@ pub fn run() {
|
|||||||
match event {
|
match event {
|
||||||
tauri::WindowEvent::CloseRequested { api, .. } => {
|
tauri::WindowEvent::CloseRequested { api, .. } => {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
AppHandleManager::global().set_activation_policy_accessory();
|
|
||||||
if core::handle::Handle::global().is_exiting() {
|
if core::handle::Handle::global().is_exiting() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ pub fn get_exe_path() -> Result<PathBuf> {
|
|||||||
pub fn create_shortcut() -> Result<()> {
|
pub fn create_shortcut() -> Result<()> {
|
||||||
let exe_path = get_exe_path()?;
|
let exe_path = get_exe_path()?;
|
||||||
let startup_dir = get_startup_dir()?;
|
let startup_dir = get_startup_dir()?;
|
||||||
let shortcut_path = startup_dir.join("Clash-Verge.lnk");
|
let shortcut_path = startup_dir.join("Koala-Clash.lnk");
|
||||||
|
|
||||||
// 如果快捷方式已存在,直接返回成功
|
// 如果快捷方式已存在,直接返回成功
|
||||||
if shortcut_path.exists() {
|
if shortcut_path.exists() {
|
||||||
@@ -77,7 +77,7 @@ pub fn create_shortcut() -> Result<()> {
|
|||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub fn remove_shortcut() -> Result<()> {
|
pub fn remove_shortcut() -> Result<()> {
|
||||||
let startup_dir = get_startup_dir()?;
|
let startup_dir = get_startup_dir()?;
|
||||||
let shortcut_path = startup_dir.join("Clash-Verge.lnk");
|
let shortcut_path = startup_dir.join("Koala-Clash.lnk");
|
||||||
|
|
||||||
// 如果快捷方式不存在,直接返回成功
|
// 如果快捷方式不存在,直接返回成功
|
||||||
if !shortcut_path.exists() {
|
if !shortcut_path.exists() {
|
||||||
@@ -96,7 +96,7 @@ pub fn remove_shortcut() -> Result<()> {
|
|||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub fn is_shortcut_enabled() -> Result<bool> {
|
pub fn is_shortcut_enabled() -> Result<bool> {
|
||||||
let startup_dir = get_startup_dir()?;
|
let startup_dir = get_startup_dir()?;
|
||||||
let shortcut_path = startup_dir.join("Clash-Verge.lnk");
|
let shortcut_path = startup_dir.join("Koala-Clash.lnk");
|
||||||
|
|
||||||
Ok(shortcut_path.exists())
|
Ok(shortcut_path.exists())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ use std::{fs, path::PathBuf};
|
|||||||
use tauri::Manager;
|
use tauri::Manager;
|
||||||
|
|
||||||
#[cfg(not(feature = "verge-dev"))]
|
#[cfg(not(feature = "verge-dev"))]
|
||||||
pub static APP_ID: &str = "io.github.clash-verge-rev.clash-verge-rev";
|
pub static APP_ID: &str = "io.github.koala-clash";
|
||||||
#[cfg(not(feature = "verge-dev"))]
|
#[cfg(not(feature = "verge-dev"))]
|
||||||
pub static BACKUP_DIR: &str = "clash-verge-rev-backup";
|
pub static BACKUP_DIR: &str = "io.github.koala-clash-backup";
|
||||||
|
|
||||||
#[cfg(feature = "verge-dev")]
|
#[cfg(feature = "verge-dev")]
|
||||||
pub static APP_ID: &str = "io.github.clash-verge-rev.clash-verge-rev.dev";
|
pub static APP_ID: &str = "io.github.koala-clash.dev";
|
||||||
#[cfg(feature = "verge-dev")]
|
#[cfg(feature = "verge-dev")]
|
||||||
pub static BACKUP_DIR: &str = "clash-verge-rev-backup-dev";
|
pub static BACKUP_DIR: &str = "io.github.koala-clash-backup-dev";
|
||||||
|
|
||||||
pub static PORTABLE_FLAG: OnceCell<bool> = OnceCell::new();
|
pub static PORTABLE_FLAG: OnceCell<bool> = OnceCell::new();
|
||||||
|
|
||||||
@@ -188,13 +188,13 @@ pub fn profiles_path() -> Result<PathBuf> {
|
|||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub fn service_path() -> Result<PathBuf> {
|
pub fn service_path() -> Result<PathBuf> {
|
||||||
let res_dir = app_resources_dir()?;
|
let res_dir = app_resources_dir()?;
|
||||||
Ok(res_dir.join("clash-verge-service"))
|
Ok(res_dir.join("koala-clash-service"))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub fn service_path() -> Result<PathBuf> {
|
pub fn service_path() -> Result<PathBuf> {
|
||||||
let res_dir = app_resources_dir()?;
|
let res_dir = app_resources_dir()?;
|
||||||
Ok(res_dir.join("clash-verge-service.exe"))
|
Ok(res_dir.join("koala-clash-service.exe"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn service_log_file() -> Result<PathBuf> {
|
pub fn service_log_file() -> Result<PathBuf> {
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ fn init_dns_config() -> Result<()> {
|
|||||||
help::save_yaml(
|
help::save_yaml(
|
||||||
&dns_path,
|
&dns_path,
|
||||||
&default_dns_config,
|
&default_dns_config,
|
||||||
Some("# Clash Verge DNS Config"),
|
Some("# Koala Clash DNS Config"),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,14 +274,14 @@ pub fn init_config() -> Result<()> {
|
|||||||
|
|
||||||
crate::log_err!(dirs::clash_path().map(|path| {
|
crate::log_err!(dirs::clash_path().map(|path| {
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
help::save_yaml(&path, &IClashTemp::template().0, Some("# Clash Vergeasu"))?;
|
help::save_yaml(&path, &IClashTemp::template().0, Some("# Koala Clash"))?;
|
||||||
}
|
}
|
||||||
<Result<()>>::Ok(())
|
<Result<()>>::Ok(())
|
||||||
}));
|
}));
|
||||||
|
|
||||||
crate::log_err!(dirs::verge_path().map(|path| {
|
crate::log_err!(dirs::verge_path().map(|path| {
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
help::save_yaml(&path, &IVerge::template(), Some("# Clash Verge"))?;
|
help::save_yaml(&path, &IVerge::template(), Some("# Koala Clash"))?;
|
||||||
}
|
}
|
||||||
<Result<()>>::Ok(())
|
<Result<()>>::Ok(())
|
||||||
}));
|
}));
|
||||||
@@ -291,7 +291,7 @@ pub fn init_config() -> Result<()> {
|
|||||||
|
|
||||||
crate::log_err!(dirs::profiles_path().map(|path| {
|
crate::log_err!(dirs::profiles_path().map(|path| {
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
help::save_yaml(&path, &IProfiles::template(), Some("# Clash Verge"))?;
|
help::save_yaml(&path, &IProfiles::template(), Some("# Koala Clash"))?;
|
||||||
}
|
}
|
||||||
<Result<()>>::Ok(())
|
<Result<()>>::Ok(())
|
||||||
}));
|
}));
|
||||||
@@ -371,8 +371,8 @@ pub fn init_scheme() -> Result<()> {
|
|||||||
|
|
||||||
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
||||||
let (clash, _) = hkcu.create_subkey("Software\\Classes\\Clash")?;
|
let (clash, _) = hkcu.create_subkey("Software\\Classes\\Clash")?;
|
||||||
clash.set_value("", &"Clash Verge")?;
|
clash.set_value("", &"Koala Clash")?;
|
||||||
clash.set_value("URL Protocol", &"Clash Verge URL Scheme Protocol")?;
|
clash.set_value("URL Protocol", &"Koala Clash URL Scheme Protocol")?;
|
||||||
let (default_icon, _) = hkcu.create_subkey("Software\\Classes\\Clash\\DefaultIcon")?;
|
let (default_icon, _) = hkcu.create_subkey("Software\\Classes\\Clash\\DefaultIcon")?;
|
||||||
default_icon.set_value("", &app_exe)?;
|
default_icon.set_value("", &app_exe)?;
|
||||||
let (command, _) = hkcu.create_subkey("Software\\Classes\\Clash\\Shell\\Open\\Command")?;
|
let (command, _) = hkcu.create_subkey("Software\\Classes\\Clash\\Shell\\Open\\Command")?;
|
||||||
@@ -384,7 +384,7 @@ pub fn init_scheme() -> Result<()> {
|
|||||||
pub fn init_scheme() -> Result<()> {
|
pub fn init_scheme() -> Result<()> {
|
||||||
let output = std::process::Command::new("xdg-mime")
|
let output = std::process::Command::new("xdg-mime")
|
||||||
.arg("default")
|
.arg("default")
|
||||||
.arg("clash-verge.desktop")
|
.arg("koala-clash.desktop")
|
||||||
.arg("x-scheme-handler/clash")
|
.arg("x-scheme-handler/clash")
|
||||||
.output()?;
|
.output()?;
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ pub mod network;
|
|||||||
pub mod notification;
|
pub mod notification;
|
||||||
pub mod resolve;
|
pub mod resolve;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
|
pub mod sys_info;
|
||||||
pub mod tmpl;
|
pub mod tmpl;
|
||||||
pub mod window_manager;
|
pub mod window_manager;
|
||||||
pub mod sys_info;
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ impl NetworkManager {
|
|||||||
// 创建专用的异步运行时,线程数限制为4个
|
// 创建专用的异步运行时,线程数限制为4个
|
||||||
let runtime = Builder::new_multi_thread()
|
let runtime = Builder::new_multi_thread()
|
||||||
.worker_threads(4)
|
.worker_threads(4)
|
||||||
.thread_name("clash-verge-network")
|
.thread_name("koala-clash-network")
|
||||||
.enable_io()
|
.enable_io()
|
||||||
.enable_time()
|
.enable_time()
|
||||||
.build()
|
.build()
|
||||||
@@ -323,8 +323,8 @@ impl NetworkManager {
|
|||||||
use crate::utils::resolve::VERSION;
|
use crate::utils::resolve::VERSION;
|
||||||
|
|
||||||
let version = match VERSION.get() {
|
let version = match VERSION.get() {
|
||||||
Some(v) => format!("clash-verge/v{v}"),
|
Some(v) => format!("koala-clash/v{v}"),
|
||||||
None => "clash-verge/unknown".to_string(),
|
None => "koala-clash/unknown".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
builder = builder.user_agent(version);
|
builder = builder.user_agent(version);
|
||||||
|
|||||||
@@ -335,12 +335,12 @@ pub fn create_window(is_show: bool) -> bool {
|
|||||||
"main", /* the unique window label */
|
"main", /* the unique window label */
|
||||||
tauri::WebviewUrl::App("index.html".into()),
|
tauri::WebviewUrl::App("index.html".into()),
|
||||||
)
|
)
|
||||||
.title("Clash Verge Rev Lite")
|
.title("Koala Clash")
|
||||||
.center()
|
.center()
|
||||||
.decorations(true)
|
.decorations(true)
|
||||||
.fullscreen(false)
|
.fullscreen(false)
|
||||||
.inner_size(DEFAULT_WIDTH as f64, DEFAULT_HEIGHT as f64)
|
.inner_size(DEFAULT_WIDTH as f64, DEFAULT_HEIGHT as f64)
|
||||||
.min_inner_size(520.0, 520.0)
|
.min_inner_size(1000.0, 800.0)
|
||||||
.visible(true) // 立即显示窗口,避免用户等待
|
.visible(true) // 立即显示窗口,避免用户等待
|
||||||
.initialization_script(
|
.initialization_script(
|
||||||
r#"
|
r#"
|
||||||
@@ -549,7 +549,7 @@ pub async fn resolve_scheme(param: String) -> Result<()> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if link_parsed.scheme() == "clash" || link_parsed.scheme() == "clash-verge" {
|
if link_parsed.scheme() == "clash" || link_parsed.scheme() == "koala-clash" {
|
||||||
let mut name: Option<String> = None;
|
let mut name: Option<String> = None;
|
||||||
let mut url_param: Option<String> = None;
|
let mut url_param: Option<String> = None;
|
||||||
|
|
||||||
@@ -565,7 +565,7 @@ pub async fn resolve_scheme(param: String) -> Result<()> {
|
|||||||
Some(url) => {
|
Some(url) => {
|
||||||
log::info!(target:"app", "decoded subscription url: {url}");
|
log::info!(target:"app", "decoded subscription url: {url}");
|
||||||
|
|
||||||
create_window(false);
|
create_window(true);
|
||||||
match PrfItem::from_url(url.as_ref(), name, None, None).await {
|
match PrfItem::from_url(url.as_ref(), name, None, None).await {
|
||||||
Ok(item) => {
|
Ok(item) => {
|
||||||
let uid = item.uid.clone().unwrap();
|
let uid = item.uid.clone().unwrap();
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ use crate::{
|
|||||||
process::AsyncHandler,
|
process::AsyncHandler,
|
||||||
utils::logging::Type,
|
utils::logging::Type,
|
||||||
};
|
};
|
||||||
use anyhow::{bail, Result};
|
use anyhow::Result;
|
||||||
use port_scanner::local_port_available;
|
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
use warp::Filter;
|
use warp::Filter;
|
||||||
|
|
||||||
@@ -17,32 +16,6 @@ struct QueryParam {
|
|||||||
param: String,
|
param: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// check whether there is already exists
|
|
||||||
pub async fn check_singleton() -> Result<()> {
|
|
||||||
let port = IVerge::get_singleton_port();
|
|
||||||
if !local_port_available(port) {
|
|
||||||
let argvs: Vec<String> = std::env::args().collect();
|
|
||||||
if argvs.len() > 1 {
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
|
||||||
{
|
|
||||||
let param = argvs[1].as_str();
|
|
||||||
if param.starts_with("clash:") {
|
|
||||||
let _ = reqwest::get(format!(
|
|
||||||
"http://127.0.0.1:{port}/commands/scheme?param={param}"
|
|
||||||
))
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let _ = reqwest::get(format!("http://127.0.0.1:{port}/commands/visible")).await;
|
|
||||||
}
|
|
||||||
log::error!("failed to setup singleton listen server");
|
|
||||||
bail!("app exists");
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The embed server only be used to implement singleton process
|
/// The embed server only be used to implement singleton process
|
||||||
/// maybe it can be used as pac server later
|
/// maybe it can be used as pac server later
|
||||||
pub fn embed_server() {
|
pub fn embed_server() {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! Some config file template
|
//! Some config file template
|
||||||
|
|
||||||
/// template for new a profile item
|
/// template for new a profile item
|
||||||
pub const ITEM_LOCAL: &str = "# Profile Template for Clash Verge
|
pub const ITEM_LOCAL: &str = "# Profile Template for Koala Clash
|
||||||
|
|
||||||
proxies: []
|
proxies: []
|
||||||
|
|
||||||
@@ -11,13 +11,13 @@ rules: []
|
|||||||
";
|
";
|
||||||
|
|
||||||
/// enhanced profile
|
/// enhanced profile
|
||||||
pub const ITEM_MERGE: &str = "# Profile Enhancement Merge Template for Clash Verge
|
pub const ITEM_MERGE: &str = "# Profile Enhancement Merge Template for Koala Clash
|
||||||
|
|
||||||
profile:
|
profile:
|
||||||
store-selected: true
|
store-selected: true
|
||||||
";
|
";
|
||||||
|
|
||||||
pub const ITEM_MERGE_EMPTY: &str = "# Profile Enhancement Merge Template for Clash Verge
|
pub const ITEM_MERGE_EMPTY: &str = "# Profile Enhancement Merge Template for Koala Clash
|
||||||
|
|
||||||
";
|
";
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ function main(config, profileName) {
|
|||||||
";
|
";
|
||||||
|
|
||||||
/// enhanced profile
|
/// enhanced profile
|
||||||
pub const ITEM_RULES: &str = "# Profile Enhancement Rules Template for Clash Verge
|
pub const ITEM_RULES: &str = "# Profile Enhancement Rules Template for Koala Clash
|
||||||
|
|
||||||
prepend: []
|
prepend: []
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ delete: []
|
|||||||
";
|
";
|
||||||
|
|
||||||
/// enhanced profile
|
/// enhanced profile
|
||||||
pub const ITEM_PROXIES: &str = "# Profile Enhancement Proxies Template for Clash Verge
|
pub const ITEM_PROXIES: &str = "# Profile Enhancement Proxies Template for Koala Clash
|
||||||
|
|
||||||
prepend: []
|
prepend: []
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ delete: []
|
|||||||
";
|
";
|
||||||
|
|
||||||
/// enhanced profile
|
/// enhanced profile
|
||||||
pub const ITEM_GROUPS: &str = "# Profile Enhancement Groups Template for Clash Verge
|
pub const ITEM_GROUPS: &str = "# Profile Enhancement Groups Template for Koala Clash
|
||||||
|
|
||||||
prepend: []
|
prepend: []
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"version": "0.2.2",
|
"version": "0.2.5",
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"active": true,
|
"active": true,
|
||||||
"longDescription": "Clash Verge Rev Lite",
|
"longDescription": "Koala Clash",
|
||||||
"icon": [
|
"icon": [
|
||||||
"icons/32x32.png",
|
"icons/32x32.png",
|
||||||
"icons/128x128.png",
|
"icons/128x128.png",
|
||||||
@@ -12,11 +12,11 @@
|
|||||||
"icons/icon.ico"
|
"icons/icon.ico"
|
||||||
],
|
],
|
||||||
"resources": ["resources", "resources/locales/*"],
|
"resources": ["resources", "resources/locales/*"],
|
||||||
"publisher": "Clash Verge Rev Lite",
|
"publisher": "Koala Clash",
|
||||||
"externalBin": ["sidecar/verge-mihomo", "sidecar/verge-mihomo-alpha"],
|
"externalBin": ["sidecar/koala-mihomo", "sidecar/koala-mihomo-alpha"],
|
||||||
"copyright": "GNU General Public License v3.0",
|
"copyright": "GNU General Public License v3.0",
|
||||||
"category": "DeveloperTool",
|
"category": "DeveloperTool",
|
||||||
"shortDescription": "Clash Verge Rev Lite",
|
"shortDescription": "Koala Clash",
|
||||||
"createUpdaterArtifacts": true
|
"createUpdaterArtifacts": true
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
@@ -25,8 +25,8 @@
|
|||||||
"beforeDevCommand": "pnpm run web:dev",
|
"beforeDevCommand": "pnpm run web:dev",
|
||||||
"devUrl": "http://localhost:3000/"
|
"devUrl": "http://localhost:3000/"
|
||||||
},
|
},
|
||||||
"productName": "Clash Verge Rev Lite",
|
"productName": "Koala Clash",
|
||||||
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
|
"identifier": "io.github.koala-clash",
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"updater": {
|
"updater": {
|
||||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IERCQjQ1QjQ0QUJDQTU1RTkKUldUcFZjcXJSRnUwMjdXSERoZVQ1R0hHRDMrT3VkSmpvbDJmb01sN3ZpYWhVYnEwaWpYUWU4YU0K",
|
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IERCQjQ1QjQ0QUJDQTU1RTkKUldUcFZjcXJSRnUwMjdXSERoZVQ1R0hHRDMrT3VkSmpvbDJmb01sN3ZpYWhVYnEwaWpYUWU4YU0K",
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
},
|
},
|
||||||
"deep-link": {
|
"deep-link": {
|
||||||
"desktop": {
|
"desktop": {
|
||||||
"schemes": ["clash", "clash-verge"]
|
"schemes": ["clash", "koala-clash"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,34 +1,34 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||||
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
|
"identifier": "io.github.koala-clash",
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"targets": ["deb", "rpm"],
|
"targets": ["deb", "rpm"],
|
||||||
"linux": {
|
"linux": {
|
||||||
"deb": {
|
"deb": {
|
||||||
"depends": ["openssl"],
|
"depends": ["openssl"],
|
||||||
"desktopTemplate": "./packages/linux/clash-verge.desktop",
|
"desktopTemplate": "./packages/linux/koala-clash.desktop",
|
||||||
"provides": ["clash-verge"],
|
"provides": ["koala-clash"],
|
||||||
"conflicts": ["clash-verge"],
|
"conflicts": ["koala-clash"],
|
||||||
"replaces": ["clash-verge"],
|
"replaces": ["koala-clash"],
|
||||||
"postInstallScript": "./packages/linux/post-install.sh",
|
"postInstallScript": "./packages/linux/post-install.sh",
|
||||||
"preRemoveScript": "./packages/linux/pre-remove.sh"
|
"preRemoveScript": "./packages/linux/pre-remove.sh"
|
||||||
},
|
},
|
||||||
"rpm": {
|
"rpm": {
|
||||||
"depends": ["openssl"],
|
"depends": ["openssl"],
|
||||||
"desktopTemplate": "./packages/linux/clash-verge.desktop",
|
"desktopTemplate": "./packages/linux/koala-clash.desktop",
|
||||||
"provides": ["clash-verge"],
|
"provides": ["koala-clash"],
|
||||||
"conflicts": ["clash-verge"],
|
"conflicts": ["koala-clash"],
|
||||||
"obsoletes": ["clash-verge"],
|
"obsoletes": ["koala-clash"],
|
||||||
"postInstallScript": "./packages/linux/post-install.sh",
|
"postInstallScript": "./packages/linux/post-install.sh",
|
||||||
"preRemoveScript": "./packages/linux/pre-remove.sh"
|
"preRemoveScript": "./packages/linux/pre-remove.sh"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"externalBin": [
|
"externalBin": [
|
||||||
"./resources/clash-verge-service",
|
"./resources/koala-clash-service",
|
||||||
"./resources/install-service",
|
"./resources/install-service",
|
||||||
"./resources/uninstall-service",
|
"./resources/uninstall-service",
|
||||||
"./sidecar/verge-mihomo",
|
"./sidecar/koala-mihomo",
|
||||||
"./sidecar/verge-mihomo-alpha"
|
"./sidecar/koala-mihomo-alpha"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||||
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
|
"identifier": "io.github.koala-clash",
|
||||||
"productName": "Clash Verge Rev Lite",
|
"productName": "Koala Clash",
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"targets": ["app", "dmg"],
|
"targets": ["app", "dmg"],
|
||||||
"macOS": {
|
"macOS": {
|
||||||
@@ -14,11 +14,11 @@
|
|||||||
"background": "images/background.png",
|
"background": "images/background.png",
|
||||||
"appPosition": {
|
"appPosition": {
|
||||||
"x": 180,
|
"x": 180,
|
||||||
"y": 170
|
"y": 200
|
||||||
},
|
},
|
||||||
"applicationFolderPosition": {
|
"applicationFolderPosition": {
|
||||||
"x": 480,
|
"x": 480,
|
||||||
"y": 170
|
"y": 200
|
||||||
},
|
},
|
||||||
"windowSize": {
|
"windowSize": {
|
||||||
"height": 400,
|
"height": 400,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||||
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
|
"identifier": "io.github.koala-clash",
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"targets": ["nsis"],
|
"targets": ["nsis"],
|
||||||
"windows": {
|
"windows": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||||
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
|
"identifier": "io.github.koala-clash",
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"targets": ["nsis"],
|
"targets": ["nsis"],
|
||||||
"windows": {
|
"windows": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||||
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
|
"identifier": "io.github.koala-clash",
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"targets": ["nsis"],
|
"targets": ["nsis"],
|
||||||
"windows": {
|
"windows": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||||
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
|
"identifier": "io.github.koala-clash",
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"targets": ["nsis"],
|
"targets": ["nsis"],
|
||||||
"windows": {
|
"windows": {
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
import { AppDataProvider } from "./providers/app-data-provider";
|
import { AppDataProvider } from "./providers/app-data-provider";
|
||||||
import { ThemeProvider } from "@/components/layout/theme-provider";
|
|
||||||
import Layout from "./pages/_layout";
|
import Layout from "./pages/_layout";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<ThemeProvider>
|
<AppDataProvider>
|
||||||
<AppDataProvider>
|
<Layout />
|
||||||
<Layout />
|
</AppDataProvider>
|
||||||
</AppDataProvider>
|
|
||||||
</ThemeProvider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default App;
|
export default App;
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 2.4 KiB |
BIN
src/assets/image/logo.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
@@ -1,50 +1,108 @@
|
|||||||
<svg version="1.1" id="layout1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
<svg width="1024" height="963" viewBox="0 0 1024 963" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
viewBox="0 0 117 27" style="enable-background:new 0 0 117 27;" xml:space="preserve">
|
<g filter="url(#filter0_f_40_29)">
|
||||||
<g>
|
<ellipse cx="512" cy="516" rx="254" ry="216" fill="url(#paint0_radial_40_29)" fill-opacity="0.3"/>
|
||||||
<defs>
|
|
||||||
<rect id="SVGID_1_" x="-39.9" width="157" height="27"/>
|
|
||||||
</defs>
|
|
||||||
<clipPath id="SVGID_00000023248255305809236420000007367745325967865768_">
|
|
||||||
<use xlink:href="#SVGID_1_" style="overflow:visible;"/>
|
|
||||||
</clipPath>
|
|
||||||
<g style="clip-path:url(#SVGID_00000023248255305809236420000007367745325967865768_);">
|
|
||||||
<path class="st1" d="M115.9,21.4c-0.5,0.3-1.1,0.5-1.8,0.7c-0.7,0.1-1.3,0.2-1.9,0.2c-2.1,0-3.8-0.5-4.9-1.5
|
|
||||||
c-1.1-1-1.6-2.4-1.6-4.3c0-1.8,0.5-3.2,1.5-4.2c1-1,2.3-1.5,4-1.5c1.7,0,3,0.5,4,1.5c1,1,1.5,2.3,1.5,4.2c0,0.2,0,0.5,0,0.9h-7.8
|
|
||||||
c0.3,1.7,1.4,2.6,3.4,2.6c1.4,0,2.6-0.4,3.7-1.2V21.4z M113.6,15.2c-0.2-0.7-0.5-1.2-0.9-1.5c-0.4-0.3-0.9-0.5-1.5-0.5
|
|
||||||
c-0.6,0-1,0.2-1.4,0.5c-0.4,0.3-0.7,0.8-0.8,1.5H113.6z"/>
|
|
||||||
<path class="st1" d="M98.5,26.6c-0.8,0-1.6-0.1-2.5-0.2c-0.8-0.1-1.5-0.3-2.2-0.5v-2.6c1.4,0.3,2.9,0.5,4.3,0.5
|
|
||||||
c0.9,0,1.6-0.2,2.1-0.6c0.5-0.4,0.7-1,0.7-1.7c-0.7,0.5-1.6,0.7-2.6,0.7c-1,0-1.9-0.2-2.6-0.7c-0.7-0.5-1.3-1.1-1.7-2
|
|
||||||
c-0.4-0.9-0.6-1.8-0.6-2.9c0-1.1,0.2-2.1,0.6-2.9c0.4-0.9,1-1.5,1.7-2c0.7-0.5,1.6-0.7,2.6-0.7c0.9,0,1.8,0.3,2.6,0.9v-0.7h3.1V22
|
|
||||||
C104,25,102.2,26.6,98.5,26.6z M96.4,16.6c0,0.6,0.1,1.2,0.4,1.7c0.3,0.5,0.6,0.9,1,1.2c0.4,0.3,0.8,0.4,1.3,0.4
|
|
||||||
c0.3,0,0.7-0.1,1.1-0.2c0.4-0.2,0.8-0.5,1.1-1l0.1-0.4v-3.7c-0.3-0.6-0.6-0.9-1.1-1.1c-0.4-0.2-0.8-0.3-1.2-0.3
|
|
||||||
c-0.5,0-0.9,0.1-1.3,0.4c-0.4,0.3-0.7,0.7-1,1.2C96.6,15.4,96.4,16,96.4,16.6z"/>
|
|
||||||
<path class="st1" d="M89.2,11.2v1.2c0.3-0.4,0.8-0.7,1.2-0.9c0.5-0.2,1-0.3,1.5-0.3c0.3,0,0.6,0,0.9,0.1v2.5
|
|
||||||
c-0.4-0.1-0.7-0.1-1.1-0.1c-0.5,0-1,0.1-1.4,0.3c-0.5,0.2-0.8,0.4-1.1,0.8V22H86V11.2H89.2z"/>
|
|
||||||
<path class="st1" d="M83.7,21.4c-0.5,0.3-1.1,0.5-1.8,0.7c-0.7,0.1-1.3,0.2-1.9,0.2c-2.1,0-3.8-0.5-4.9-1.5
|
|
||||||
c-1.1-1-1.6-2.4-1.6-4.3c0-1.8,0.5-3.2,1.5-4.2c1-1,2.3-1.5,4-1.5c1.7,0,3,0.5,4,1.5c1,1,1.5,2.3,1.5,4.2c0,0.2,0,0.5,0,0.9h-7.8
|
|
||||||
C76.9,19.1,78,20,80,20c1.4,0,2.6-0.4,3.7-1.2V21.4z M81.4,15.2c-0.2-0.7-0.5-1.2-0.9-1.5c-0.4-0.3-0.9-0.5-1.5-0.5
|
|
||||||
c-0.6,0-1,0.2-1.4,0.5c-0.4,0.3-0.7,0.8-0.8,1.5H81.4z"/>
|
|
||||||
<path class="st1" d="M59.5,8h3.6l3.4,11.8h0.1L69.9,8h3.6l-4.3,14h-5.3L59.5,8z"/>
|
|
||||||
<path class="st1" d="M46.4,6.6v5.7c0.5-0.4,1-0.7,1.6-0.9c0.6-0.2,1.2-0.3,1.8-0.3c1,0,1.8,0.3,2.4,0.9c0.6,0.6,0.9,1.4,0.9,2.3
|
|
||||||
V22h-3.2v-7.1c0-0.4-0.2-0.7-0.5-0.9c-0.3-0.3-0.7-0.4-1.1-0.4c-0.3,0-0.6,0.1-0.9,0.2c-0.4,0.2-0.7,0.4-1,0.6V22h-3.2V6.6H46.4z"
|
|
||||||
/>
|
|
||||||
<path class="st1" d="M37.9,22.2c-0.8,0-1.6,0-2.5-0.2c-0.8-0.2-1.5-0.4-2.2-0.8v-2.9c0.5,0.4,1.2,0.7,2,1c0.8,0.3,1.5,0.4,2,0.3
|
|
||||||
c0.4,0,0.7-0.1,0.9-0.3c0.2-0.2,0.3-0.3,0.3-0.5c0.1-0.4,0-0.7-0.3-0.9c-0.3-0.2-0.8-0.4-1.5-0.6c-0.8-0.2-1.5-0.5-1.9-0.8
|
|
||||||
c-0.5-0.3-0.8-0.6-1.1-1c-0.2-0.4-0.4-0.9-0.4-1.5c0-0.6,0.2-1.2,0.5-1.6c0.3-0.5,0.8-0.9,1.5-1.2c0.7-0.3,1.4-0.4,2.2-0.4
|
|
||||||
c0.6,0,1.2,0.1,1.8,0.2c0.6,0.1,1.1,0.3,1.5,0.4v2.6c-0.4-0.2-0.9-0.4-1.5-0.6c-0.6-0.2-1.1-0.3-1.5-0.3c-0.9,0-1.4,0.2-1.5,0.7
|
|
||||||
c0,0.3,0.1,0.5,0.4,0.7c0.3,0.2,0.7,0.4,1.3,0.6c0.8,0.3,1.5,0.5,2,0.8c0.5,0.3,0.9,0.6,1.2,1c0.3,0.4,0.4,1,0.4,1.6
|
|
||||||
c0,1-0.4,1.8-1.1,2.4C40,21.9,39,22.2,37.9,22.2z"/>
|
|
||||||
<path class="st1" d="M25.8,22.3c-1,0-1.9-0.2-2.7-0.7c-0.7-0.5-1.3-1.1-1.7-2c-0.4-0.8-0.6-1.8-0.6-2.9c0-1.1,0.2-2.1,0.6-2.9
|
|
||||||
c0.4-0.9,1-1.5,1.7-2c0.7-0.5,1.6-0.7,2.6-0.7c0.5,0,0.9,0.1,1.4,0.3c0.5,0.2,0.9,0.4,1.3,0.7v-0.7h3.2v8.3c0,1.1,0.1,1.9,0.4,2.5
|
|
||||||
h-3c-0.1-0.2-0.2-0.4-0.2-0.7C27.9,21.9,26.9,22.3,25.8,22.3z M23.9,16.6c0,0.6,0.1,1.2,0.4,1.7c0.3,0.5,0.6,0.9,1,1.2
|
|
||||||
c0.4,0.3,0.8,0.4,1.3,0.4c0.3,0,0.7-0.1,1.1-0.2c0.4-0.1,0.7-0.5,1-0.9v-4.5c-0.3-0.5-0.6-0.8-1-0.9c-0.4-0.1-0.7-0.2-1.1-0.2
|
|
||||||
c-0.5,0-0.9,0.1-1.3,0.4c-0.4,0.3-0.7,0.7-1,1.2C24,15.4,23.9,16,23.9,16.6z"/>
|
|
||||||
<path class="st1" d="M18.5,22.2c-1.2,0-2.1-0.3-2.7-1c-0.6-0.7-0.9-1.7-0.9-3V6.6H18v10.8c0,0.5,0,0.9,0.1,1.2
|
|
||||||
c0.1,0.3,0.2,0.5,0.4,0.6c0.1,0.1,0.3,0.2,0.5,0.2c0.2,0,0.5,0,1,0v2.6H18.5z"/>
|
|
||||||
<path class="st1" d="M8.8,22.3c-1.5,0-2.9-0.3-4.1-0.8C3.6,20.9,2.7,20,2,19c-0.7-1.1-1-2.3-1-3.8c0-1.5,0.3-2.9,1-4
|
|
||||||
c0.7-1.1,1.6-2,2.7-2.6c1.2-0.6,2.5-0.9,4-0.9c0.7,0,1.5,0.1,2.3,0.2s1.4,0.3,1.9,0.6V11c-1.3-0.5-2.6-0.7-3.8-0.7
|
|
||||||
c-1.4,0-2.5,0.4-3.4,1.2c-0.9,0.8-1.3,2-1.3,3.7c0,0.9,0.2,1.7,0.6,2.3c0.4,0.7,1,1.2,1.7,1.6c0.7,0.4,1.4,0.6,2.2,0.6l0.4,0
|
|
||||||
c0.6,0,1.2-0.1,1.8-0.3c0.6-0.2,1.1-0.4,1.6-0.7v2.8c-0.6,0.3-1.2,0.5-1.8,0.6C10.4,22.2,9.6,22.3,8.8,22.3z"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
</g>
|
||||||
|
<g style="mix-blend-mode:hard-light" filter="url(#filter1_f_40_29)">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M253.555 253.691C234.166 257.325 210.787 267.678 194.817 279.704C167.671 300.145 143 336.666 143 356.409C143 364.909 148.741 370.132 158.085 370.132C161.927 370.132 162.182 370.298 161.535 372.382C161.15 373.62 160.598 381.832 160.308 390.632C159.843 404.693 160.039 407.723 161.922 415.632C165.896 432.325 175.89 449.337 188.675 461.172C204.384 475.712 222.721 483.721 246.31 486.344C256.49 487.476 257.088 487.667 256.498 489.589C255.106 494.128 254.72 539.224 255.981 549.919C259.914 583.268 273.139 615.139 294.865 643.632C306.099 658.365 327.932 678.511 345 689.893C389.521 719.583 458.858 734.6 535.26 731.098C567.926 729.601 590.289 726.361 615.5 719.475C682.821 701.087 736.946 652.57 757.886 591.84C766.113 567.983 768.638 548.162 767.596 515.632C767.226 504.082 766.675 493.013 766.37 491.033L765.817 487.435L774.158 486.74C817.12 483.16 850.475 457.074 861.243 418.632C863.868 409.261 864.639 388.453 862.753 377.882L861.37 370.132L865.435 370.13C873.132 370.128 881 363.641 881 357.298C881 349.587 875.454 334.787 868.01 322.632C833.048 265.543 764.751 237.996 710.358 259.044C700.723 262.772 689.715 269.233 678 278.038C667.891 285.635 644.942 308.477 640 315.862C637.584 319.472 636.036 320.902 635 320.482C615.25 312.464 578.262 303.843 547 299.972C533.368 298.284 489.859 298.263 475.5 299.938C447.293 303.227 421.534 308.84 399.752 316.442L387.004 320.891L382.317 314.262C376.27 305.71 356.765 286.218 346.479 278.449C331.01 266.765 317.133 259.319 303 255.12C293.122 252.185 265.803 251.396 253.555 253.691ZM525.775 474.141C542.235 479.919 552.81 493.069 560.962 517.895C572.367 552.627 577.508 595.826 572.237 612.632C569.49 621.391 566.737 625.917 560.043 632.68C553.631 639.158 543.575 644.286 532.5 646.726C522.255 648.984 501.437 649.041 491.343 646.839C461.912 640.42 447.619 620.374 449.483 588.132C451.047 561.084 460.42 521.584 470.009 501.632C476.59 487.939 487.236 478.078 499.762 474.074C507.624 471.56 518.502 471.589 525.775 474.141ZM389.384 484.362C395.107 486.072 401.281 492.491 402.955 498.473C406.617 511.549 396.619 525.041 383.218 525.11C364.274 525.207 354.944 501.775 368.761 488.803C374.23 483.668 381.688 482.062 389.384 484.362ZM648.277 484.192C656.216 486.397 662.933 495.325 662.978 503.733C663.025 512.411 659.127 518.893 651.611 522.639C642.535 527.161 632.471 525.166 626.243 517.61C613.065 501.623 628.229 478.625 648.277 484.192Z" stroke="url(#paint1_linear_40_29)" stroke-width="6" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
<g style="mix-blend-mode:hard-light" filter="url(#filter2_f_40_29)">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M253.555 253.691C234.166 257.325 210.787 267.678 194.817 279.704C167.671 300.145 143 336.666 143 356.409C143 364.909 148.741 370.132 158.085 370.132C161.927 370.132 162.182 370.298 161.535 372.382C161.15 373.62 160.598 381.832 160.308 390.632C159.843 404.693 160.039 407.723 161.922 415.632C165.896 432.325 175.89 449.337 188.675 461.172C204.384 475.712 222.721 483.721 246.31 486.344C256.49 487.476 257.088 487.667 256.498 489.589C255.106 494.128 254.72 539.224 255.981 549.919C259.914 583.268 273.139 615.139 294.865 643.632C306.099 658.365 327.932 678.511 345 689.893C389.521 719.583 458.858 734.6 535.26 731.098C567.926 729.601 590.289 726.361 615.5 719.475C682.821 701.087 736.946 652.57 757.886 591.84C766.113 567.983 768.638 548.162 767.596 515.632C767.226 504.082 766.675 493.013 766.37 491.033L765.817 487.435L774.158 486.74C817.12 483.16 850.475 457.074 861.243 418.632C863.868 409.261 864.639 388.453 862.753 377.882L861.37 370.132L865.435 370.13C873.132 370.128 881 363.641 881 357.298C881 349.587 875.454 334.787 868.01 322.632C833.048 265.543 764.751 237.996 710.358 259.044C700.723 262.772 689.715 269.233 678 278.038C667.891 285.635 644.942 308.477 640 315.862C637.584 319.472 636.036 320.902 635 320.482C615.25 312.464 578.262 303.843 547 299.972C533.368 298.284 489.859 298.263 475.5 299.938C447.293 303.227 421.534 308.84 399.752 316.442L387.004 320.891L382.317 314.262C376.27 305.71 356.765 286.218 346.479 278.449C331.01 266.765 317.133 259.319 303 255.12C293.122 252.185 265.803 251.396 253.555 253.691ZM525.775 474.141C542.235 479.919 552.81 493.069 560.962 517.895C572.367 552.627 577.508 595.826 572.237 612.632C569.49 621.391 566.737 625.917 560.043 632.68C553.631 639.158 543.575 644.286 532.5 646.726C522.255 648.984 501.437 649.041 491.343 646.839C461.912 640.42 447.619 620.374 449.483 588.132C451.047 561.084 460.42 521.584 470.009 501.632C476.59 487.939 487.236 478.078 499.762 474.074C507.624 471.56 518.502 471.589 525.775 474.141ZM389.384 484.362C395.107 486.072 401.281 492.491 402.955 498.473C406.617 511.549 396.619 525.041 383.218 525.11C364.274 525.207 354.944 501.775 368.761 488.803C374.23 483.668 381.688 482.062 389.384 484.362ZM648.277 484.192C656.216 486.397 662.933 495.325 662.978 503.733C663.025 512.411 659.127 518.893 651.611 522.639C642.535 527.161 632.471 525.166 626.243 517.61C613.065 501.623 628.229 478.625 648.277 484.192Z" stroke="url(#paint2_linear_40_29)" stroke-opacity="0.7" stroke-width="8" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
<g style="mix-blend-mode:hard-light" filter="url(#filter3_f_40_29)">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M253.555 253.691C234.166 257.325 210.787 267.678 194.817 279.704C167.671 300.145 143 336.666 143 356.409C143 364.909 148.741 370.132 158.085 370.132C161.927 370.132 162.182 370.298 161.535 372.382C161.15 373.62 160.598 381.832 160.308 390.632C159.843 404.693 160.039 407.723 161.922 415.632C165.896 432.325 175.89 449.337 188.675 461.172C204.384 475.712 222.721 483.721 246.31 486.344C256.49 487.476 257.088 487.667 256.498 489.589C255.106 494.128 254.72 539.224 255.981 549.919C259.914 583.268 273.139 615.139 294.865 643.632C306.099 658.365 327.932 678.511 345 689.893C389.521 719.583 458.858 734.6 535.26 731.098C567.926 729.601 590.289 726.361 615.5 719.475C682.821 701.087 736.946 652.57 757.886 591.84C766.113 567.983 768.638 548.162 767.596 515.632C767.226 504.082 766.675 493.013 766.37 491.033L765.817 487.435L774.158 486.74C817.12 483.16 850.475 457.074 861.243 418.632C863.868 409.261 864.639 388.453 862.753 377.882L861.37 370.132L865.435 370.13C873.132 370.128 881 363.641 881 357.298C881 349.587 875.454 334.787 868.01 322.632C833.048 265.543 764.751 237.996 710.358 259.044C700.723 262.772 689.715 269.233 678 278.038C667.891 285.635 644.942 308.477 640 315.862C637.584 319.472 636.036 320.902 635 320.482C615.25 312.464 578.262 303.843 547 299.972C533.368 298.284 489.859 298.263 475.5 299.938C447.293 303.227 421.534 308.84 399.752 316.442L387.004 320.891L382.317 314.262C376.27 305.71 356.765 286.218 346.479 278.449C331.01 266.765 317.133 259.319 303 255.12C293.122 252.185 265.803 251.396 253.555 253.691ZM525.775 474.141C542.235 479.919 552.81 493.069 560.962 517.895C572.367 552.627 577.508 595.826 572.237 612.632C569.49 621.391 566.737 625.917 560.043 632.68C553.631 639.158 543.575 644.286 532.5 646.726C522.255 648.984 501.437 649.041 491.343 646.839C461.912 640.42 447.619 620.374 449.483 588.132C451.047 561.084 460.42 521.584 470.009 501.632C476.59 487.939 487.236 478.078 499.762 474.074C507.624 471.56 518.502 471.589 525.775 474.141ZM389.384 484.362C395.107 486.072 401.281 492.491 402.955 498.473C406.617 511.549 396.619 525.041 383.218 525.11C364.274 525.207 354.944 501.775 368.761 488.803C374.23 483.668 381.688 482.062 389.384 484.362ZM648.277 484.192C656.216 486.397 662.933 495.325 662.978 503.733C663.025 512.411 659.127 518.893 651.611 522.639C642.535 527.161 632.471 525.166 626.243 517.61C613.065 501.623 628.229 478.625 648.277 484.192Z" stroke="url(#paint3_linear_40_29)" stroke-opacity="0.4" stroke-width="7" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
<g style="mix-blend-mode:hard-light" filter="url(#filter4_ddif_40_29)">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M253.555 253.691C234.166 257.325 210.787 267.678 194.817 279.704C167.671 300.145 143 336.666 143 356.409C143 364.909 148.741 370.132 158.085 370.132C161.927 370.132 162.182 370.298 161.535 372.382C161.15 373.62 160.598 381.832 160.308 390.632C159.843 404.693 160.039 407.723 161.922 415.632C165.896 432.325 175.89 449.337 188.675 461.172C204.384 475.712 222.721 483.721 246.31 486.344C256.49 487.476 257.088 487.667 256.498 489.589C255.106 494.128 254.72 539.224 255.981 549.919C259.914 583.268 273.139 615.139 294.865 643.632C306.099 658.365 327.932 678.511 345 689.893C389.521 719.583 458.858 734.6 535.26 731.098C567.926 729.601 590.289 726.361 615.5 719.475C682.821 701.087 736.946 652.57 757.886 591.84C766.113 567.983 768.638 548.162 767.596 515.632C767.226 504.082 766.675 493.013 766.37 491.033L765.817 487.435L774.158 486.74C817.12 483.16 850.475 457.074 861.243 418.632C863.868 409.261 864.639 388.453 862.753 377.882L861.37 370.132L865.435 370.13C873.132 370.128 881 363.641 881 357.298C881 349.587 875.454 334.787 868.01 322.632C833.048 265.543 764.751 237.996 710.358 259.044C700.723 262.772 689.715 269.233 678 278.038C667.891 285.635 644.942 308.477 640 315.862C637.584 319.472 636.036 320.902 635 320.482C615.25 312.464 578.262 303.843 547 299.972C533.368 298.284 489.859 298.263 475.5 299.938C447.293 303.227 421.534 308.84 399.752 316.442L387.004 320.891L382.317 314.262C376.27 305.71 356.765 286.218 346.479 278.449C331.01 266.765 317.133 259.319 303 255.12C293.122 252.185 265.803 251.396 253.555 253.691ZM525.775 474.141C542.235 479.919 552.81 493.069 560.962 517.895C572.367 552.627 577.508 595.826 572.237 612.632C569.49 621.391 566.737 625.917 560.043 632.68C553.631 639.158 543.575 644.286 532.5 646.726C522.255 648.984 501.437 649.041 491.343 646.839C461.912 640.42 447.619 620.374 449.483 588.132C451.047 561.084 460.42 521.584 470.009 501.632C476.59 487.939 487.236 478.078 499.762 474.074C507.624 471.56 518.502 471.589 525.775 474.141ZM389.384 484.362C395.107 486.072 401.281 492.491 402.955 498.473C406.617 511.549 396.619 525.041 383.218 525.11C364.274 525.207 354.944 501.775 368.761 488.803C374.23 483.668 381.688 482.062 389.384 484.362ZM648.277 484.192C656.216 486.397 662.933 495.325 662.978 503.733C663.025 512.411 659.127 518.893 651.611 522.639C642.535 527.161 632.471 525.166 626.243 517.61C613.065 501.623 628.229 478.625 648.277 484.192Z" stroke="url(#paint4_linear_40_29)" stroke-opacity="0.01" stroke-width="5" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
<g filter="url(#filter5_f_40_29)">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M253.555 253.691C234.166 257.325 210.787 267.678 194.817 279.704C167.671 300.145 143 336.666 143 356.409C143 364.909 148.741 370.132 158.085 370.132C161.927 370.132 162.182 370.298 161.535 372.382C161.15 373.62 160.598 381.832 160.308 390.632C159.843 404.693 160.039 407.723 161.922 415.632C165.896 432.325 175.89 449.337 188.675 461.172C204.384 475.712 222.721 483.721 246.31 486.344C256.49 487.476 257.088 487.667 256.498 489.589C255.106 494.128 254.72 539.224 255.981 549.919C259.914 583.268 273.139 615.139 294.865 643.632C306.099 658.365 327.932 678.511 345 689.893C389.521 719.583 458.858 734.6 535.26 731.098C567.926 729.601 590.289 726.361 615.5 719.475C682.821 701.087 736.946 652.57 757.886 591.84C766.113 567.983 768.638 548.162 767.596 515.632C767.226 504.082 766.675 493.013 766.37 491.033L765.817 487.435L774.158 486.74C817.12 483.16 850.475 457.074 861.243 418.632C863.868 409.261 864.639 388.453 862.753 377.882L861.37 370.132L865.435 370.13C873.132 370.128 881 363.641 881 357.298C881 349.587 875.454 334.787 868.01 322.632C833.048 265.543 764.751 237.996 710.358 259.044C700.723 262.772 689.715 269.233 678 278.038C667.891 285.635 644.942 308.477 640 315.862C637.584 319.472 636.036 320.902 635 320.482C615.25 312.464 578.262 303.843 547 299.972C533.368 298.284 489.859 298.263 475.5 299.938C447.293 303.227 421.534 308.84 399.752 316.442L387.004 320.891L382.317 314.262C376.27 305.71 356.765 286.218 346.479 278.449C331.01 266.765 317.133 259.319 303 255.12C293.122 252.185 265.803 251.396 253.555 253.691ZM525.775 474.141C542.235 479.919 552.81 493.069 560.962 517.895C572.367 552.627 577.508 595.826 572.237 612.632C569.49 621.391 566.737 625.917 560.043 632.68C553.631 639.158 543.575 644.286 532.5 646.726C522.255 648.984 501.437 649.041 491.343 646.839C461.912 640.42 447.619 620.374 449.483 588.132C451.047 561.084 460.42 521.584 470.009 501.632C476.59 487.939 487.236 478.078 499.762 474.074C507.624 471.56 518.502 471.589 525.775 474.141ZM389.384 484.362C395.107 486.072 401.281 492.491 402.955 498.473C406.617 511.549 396.619 525.041 383.218 525.11C364.274 525.207 354.944 501.775 368.761 488.803C374.23 483.668 381.688 482.062 389.384 484.362ZM648.277 484.192C656.216 486.397 662.933 495.325 662.978 503.733C663.025 512.411 659.127 518.893 651.611 522.639C642.535 527.161 632.471 525.166 626.243 517.61C613.065 501.623 628.229 478.625 648.277 484.192Z" stroke="url(#paint5_linear_40_29)" stroke-width="9.5" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
<g filter="url(#filter6_f_40_29)">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M253.555 253.691C234.166 257.325 210.787 267.678 194.817 279.704C167.671 300.145 143 336.666 143 356.409C143 364.909 148.741 370.132 158.085 370.132C161.927 370.132 162.182 370.298 161.535 372.382C161.15 373.62 160.598 381.832 160.308 390.632C159.843 404.693 160.039 407.723 161.922 415.632C165.896 432.325 175.89 449.337 188.675 461.172C204.384 475.712 222.721 483.721 246.31 486.344C256.49 487.476 257.088 487.667 256.498 489.589C255.106 494.128 254.72 539.224 255.981 549.919C259.914 583.268 273.139 615.139 294.865 643.632C306.099 658.365 327.932 678.511 345 689.893C389.521 719.583 458.858 734.6 535.26 731.098C567.926 729.601 590.289 726.361 615.5 719.475C682.821 701.087 736.946 652.57 757.886 591.84C766.113 567.983 768.638 548.162 767.596 515.632C767.226 504.082 766.675 493.013 766.37 491.033L765.817 487.435L774.158 486.74C817.12 483.16 850.475 457.074 861.243 418.632C863.868 409.261 864.639 388.453 862.753 377.882L861.37 370.132L865.435 370.13C873.132 370.128 881 363.641 881 357.298C881 349.587 875.454 334.787 868.01 322.632C833.048 265.543 764.751 237.996 710.358 259.044C700.723 262.772 689.715 269.233 678 278.038C667.891 285.635 644.942 308.477 640 315.862C637.584 319.472 636.036 320.902 635 320.482C615.25 312.464 578.262 303.843 547 299.972C533.368 298.284 489.859 298.263 475.5 299.938C447.293 303.227 421.534 308.84 399.752 316.442L387.004 320.891L382.317 314.262C376.27 305.71 356.765 286.218 346.479 278.449C331.01 266.765 317.133 259.319 303 255.12C293.122 252.185 265.803 251.396 253.555 253.691ZM525.775 474.141C542.235 479.919 552.81 493.069 560.962 517.895C572.367 552.627 577.508 595.826 572.237 612.632C569.49 621.391 566.737 625.917 560.043 632.68C553.631 639.158 543.575 644.286 532.5 646.726C522.255 648.984 501.437 649.041 491.343 646.839C461.912 640.42 447.619 620.374 449.483 588.132C451.047 561.084 460.42 521.584 470.009 501.632C476.59 487.939 487.236 478.078 499.762 474.074C507.624 471.56 518.502 471.589 525.775 474.141ZM389.384 484.362C395.107 486.072 401.281 492.491 402.955 498.473C406.617 511.549 396.619 525.041 383.218 525.11C364.274 525.207 354.944 501.775 368.761 488.803C374.23 483.668 381.688 482.062 389.384 484.362ZM648.277 484.192C656.216 486.397 662.933 495.325 662.978 503.733C663.025 512.411 659.127 518.893 651.611 522.639C642.535 527.161 632.471 525.166 626.243 517.61C613.065 501.623 628.229 478.625 648.277 484.192Z" stroke="white" stroke-width="2.9" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<filter id="filter0_f_40_29" x="-42" y="0" width="1108" height="1032" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||||
|
<feGaussianBlur stdDeviation="150" result="effect1_foregroundBlur_40_29"/>
|
||||||
|
</filter>
|
||||||
|
<filter id="filter1_f_40_29" x="118.94" y="227.932" width="786.12" height="527.726" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||||
|
<feGaussianBlur stdDeviation="10.53" result="effect1_foregroundBlur_40_29"/>
|
||||||
|
</filter>
|
||||||
|
<filter id="filter2_f_40_29" x="89" y="197.99" width="846" height="587.608" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||||
|
<feGaussianBlur stdDeviation="25" result="effect1_foregroundBlur_40_29"/>
|
||||||
|
</filter>
|
||||||
|
<filter id="filter3_f_40_29" x="132.48" y="241.471" width="759.04" height="500.647" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||||
|
<feGaussianBlur stdDeviation="3.51" result="effect1_foregroundBlur_40_29"/>
|
||||||
|
</filter>
|
||||||
|
<filter id="filter4_ddif_40_29" x="110.5" y="219.494" width="803" height="544.604" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||||
|
<feOffset dy="3.9"/>
|
||||||
|
<feGaussianBlur stdDeviation="1.5"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0.109804 0 0 0 0 0.886275 0 0 0 0 0.968627 0 0 0 1 0"/>
|
||||||
|
<feBlend mode="multiply" in2="BackgroundImageFix" result="effect1_dropShadow_40_29"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||||
|
<feOffset dy="7.02"/>
|
||||||
|
<feGaussianBlur stdDeviation="4.563"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0.819608 0 0 0 0 0.054902 0 0 0 0 0.996078 0 0 0 1 0"/>
|
||||||
|
<feBlend mode="color-dodge" in2="effect1_dropShadow_40_29" result="effect2_dropShadow_40_29"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_40_29" result="shape"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||||
|
<feOffset dx="-0.39" dy="0.78"/>
|
||||||
|
<feGaussianBlur stdDeviation="0.195"/>
|
||||||
|
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
|
||||||
|
<feBlend mode="normal" in2="shape" result="effect3_innerShadow_40_29"/>
|
||||||
|
<feGaussianBlur stdDeviation="15" result="effect4_foregroundBlur_40_29"/>
|
||||||
|
</filter>
|
||||||
|
<filter id="filter5_f_40_29" x="137.15" y="246.138" width="749.7" height="491.31" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||||
|
<feGaussianBlur stdDeviation="0.55" result="effect1_foregroundBlur_40_29"/>
|
||||||
|
</filter>
|
||||||
|
<filter id="filter6_f_40_29" x="137.15" y="246.146" width="749.7" height="491.302" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||||
|
<feGaussianBlur stdDeviation="2.2" result="effect1_foregroundBlur_40_29"/>
|
||||||
|
</filter>
|
||||||
|
<radialGradient id="paint0_radial_40_29" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(512 516) rotate(20.8768) scale(331.625 512.736)">
|
||||||
|
<stop stop-color="#0CF6F7"/>
|
||||||
|
<stop offset="1" stop-color="#D10EFE"/>
|
||||||
|
</radialGradient>
|
||||||
|
<linearGradient id="paint1_linear_40_29" x1="826.587" y1="628.647" x2="310.413" y2="316.853" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#0CF6F7"/>
|
||||||
|
<stop offset="0.461563" stop-color="#6B86FA"/>
|
||||||
|
<stop offset="1" stop-color="#D10EFE"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint2_linear_40_29" x1="826.587" y1="628.647" x2="310.413" y2="316.853" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#0CF6F7"/>
|
||||||
|
<stop offset="0.225986" stop-color="#6B86FA"/>
|
||||||
|
<stop offset="0.673077" stop-color="#6B86FA"/>
|
||||||
|
<stop offset="1" stop-color="#D10EFE"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint3_linear_40_29" x1="826.587" y1="628.647" x2="310.413" y2="316.853" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#0CF6F7"/>
|
||||||
|
<stop offset="0.225986" stop-color="#6B86FA"/>
|
||||||
|
<stop offset="0.673077" stop-color="#6B86FA"/>
|
||||||
|
<stop offset="1" stop-color="#D10EFE"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint4_linear_40_29" x1="826.587" y1="628.647" x2="310.413" y2="316.853" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#0CF6F7"/>
|
||||||
|
<stop offset="0.225986" stop-color="#6B86FA"/>
|
||||||
|
<stop offset="0.673077" stop-color="#6B86FA"/>
|
||||||
|
<stop offset="1" stop-color="#D10EFE"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint5_linear_40_29" x1="826.587" y1="628.647" x2="310.413" y2="316.853" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#0CF6F7"/>
|
||||||
|
<stop offset="0.461563" stop-color="#6B86FA"/>
|
||||||
|
<stop offset="1" stop-color="#D10EFE"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 21 KiB |
422
src/assets/image/map.svg
Normal file
|
After Width: | Height: | Size: 453 KiB |
@@ -1,30 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { Toaster, toast } from "sonner";
|
|
||||||
import { useEffect, useSyncExternalStore } from "react";
|
|
||||||
import {
|
|
||||||
getSnapshotNotices,
|
|
||||||
hideNotice,
|
|
||||||
subscribeNotices,
|
|
||||||
} from "@/services/noticeService";
|
|
||||||
|
|
||||||
export const NoticeManager = () => {
|
|
||||||
const currentNotices = useSyncExternalStore(
|
|
||||||
subscribeNotices,
|
|
||||||
getSnapshotNotices,
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
for (const notice of currentNotices) {
|
|
||||||
const toastId = toast(notice.message, {
|
|
||||||
id: notice.id,
|
|
||||||
duration: notice.duration,
|
|
||||||
onDismiss: (t) => {
|
|
||||||
hideNotice(t.id as number);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [currentNotices]);
|
|
||||||
|
|
||||||
return <Toaster />;
|
|
||||||
};
|
|
||||||
@@ -5,4 +5,3 @@ export { BaseLoading } from "./base-loading";
|
|||||||
export { BaseErrorBoundary } from "./base-error-boundary";
|
export { BaseErrorBoundary } from "./base-error-boundary";
|
||||||
export { Switch } from "./base-switch";
|
export { Switch } from "./base-switch";
|
||||||
export { BaseLoadingOverlay } from "./base-loading-overlay";
|
export { BaseLoadingOverlay } from "./base-loading-overlay";
|
||||||
export { NoticeManager } from "./NoticeManager";
|
|
||||||
|
|||||||
64
src/components/home/power-button.tsx
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { cn } from '@root/lib/utils';
|
||||||
|
import { Power } from 'lucide-react';
|
||||||
|
|
||||||
|
export interface PowerButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||||
|
checked?: boolean;
|
||||||
|
loading?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PowerButton = React.forwardRef<HTMLButtonElement, PowerButtonProps>(
|
||||||
|
({ className, checked = false, loading = false, ...props }, ref) => {
|
||||||
|
const state = checked ? 'on' : 'off';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative flex items-center justify-center h-44 w-44">
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'absolute h-28 w-28 rounded-full blur-3xl transition-all duration-500',
|
||||||
|
state === 'on' ? 'bg-green-400/60' : 'bg-red-500/40',
|
||||||
|
props.disabled && 'opacity-0'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button
|
||||||
|
ref={ref}
|
||||||
|
type="button"
|
||||||
|
disabled={loading || props.disabled}
|
||||||
|
data-state={state}
|
||||||
|
className={cn(
|
||||||
|
'relative z-10 flex items-center justify-center h-36 w-36 rounded-full border-2',
|
||||||
|
'backdrop-blur-sm bg-white/10 border-white/20',
|
||||||
|
'text-red-500 shadow-[0_0_30px_rgba(239,68,68,0.6)]',
|
||||||
|
'data-[state=on]:text-green-500 dark:data-[state=on]:text-white',
|
||||||
|
'data-[state=on]:shadow-[0_0_50px_rgba(34,197,94,1)]',
|
||||||
|
'transition-all duration-300 hover:scale-105 active:scale-95 focus:outline-none',
|
||||||
|
'disabled:cursor-not-allowed disabled:scale-100',
|
||||||
|
|
||||||
|
// Стили ТОЛЬКО для отключенного состояния (но не для загрузки)
|
||||||
|
(props.disabled && !loading) && 'grayscale opacity-50 shadow-none bg-slate-100/70 border-slate-300/80',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<Power className={cn(
|
||||||
|
"h-20 w-20",
|
||||||
|
!props.disabled && "active:scale-90 transition-transform duration-300"
|
||||||
|
)} />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{loading && (
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center z-20">
|
||||||
|
<div className={cn(
|
||||||
|
'h-full w-full animate-spin rounded-full border-4',
|
||||||
|
'border-transparent',
|
||||||
|
checked ? 'border-t-green-500' : 'border-t-red-500',
|
||||||
|
'blur-xs'
|
||||||
|
)} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -37,6 +37,7 @@ interface IProxyGroup {
|
|||||||
now: string;
|
now: string;
|
||||||
hidden: boolean;
|
hidden: boolean;
|
||||||
all: (string | { name: string })[];
|
all: (string | { name: string })[];
|
||||||
|
icon?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Вспомогательная функция для цвета задержки ---
|
// --- Вспомогательная функция для цвета задержки ---
|
||||||
@@ -112,6 +113,7 @@ export const ProxySelectors: React.FC = () => {
|
|||||||
(localStorage.getItem(STORAGE_KEY_SORT_TYPE) as ProxySortType) ||
|
(localStorage.getItem(STORAGE_KEY_SORT_TYPE) as ProxySortType) ||
|
||||||
"default",
|
"default",
|
||||||
);
|
);
|
||||||
|
const enable_group_icon = verge?.enable_group_icon ?? true;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!proxies?.groups) return;
|
if (!proxies?.groups) return;
|
||||||
@@ -291,21 +293,31 @@ export const ProxySelectors: React.FC = () => {
|
|||||||
disabled={isGlobalMode || isDirectMode}
|
disabled={isGlobalMode || isDirectMode}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-100">
|
<SelectTrigger className="w-100">
|
||||||
<Tooltip>
|
<div className="flex items-center gap-2 truncate">
|
||||||
<TooltipTrigger asChild>
|
<span className="truncate">
|
||||||
<span className="truncate">
|
<SelectValue placeholder={t("Select a group...")} />
|
||||||
<SelectValue placeholder={t("Select a group...")} />
|
</span>
|
||||||
</span>
|
</div>
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>{selectedGroup}</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{selectorGroups.map((group: IProxyGroup) => (
|
{selectorGroups.map((group: IProxyGroup) => (
|
||||||
<SelectItem key={group.name} value={group.name}>
|
<SelectItem key={group.name} value={group.name}>
|
||||||
{group.name}
|
<div className="flex items-center gap-2">
|
||||||
|
{enable_group_icon && group.icon && (
|
||||||
|
<img
|
||||||
|
src={
|
||||||
|
group.icon.startsWith("data")
|
||||||
|
? group.icon
|
||||||
|
: group.icon.startsWith("<svg")
|
||||||
|
? `data:image/svg+xml;base64,${btoa(group.icon)}`
|
||||||
|
: group.icon
|
||||||
|
}
|
||||||
|
className="w-4 h-4 rounded-sm"
|
||||||
|
alt={group.name}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<span>{group.name}</span>
|
||||||
|
</div>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
SidebarHeader,
|
SidebarHeader,
|
||||||
SidebarMenu,
|
SidebarMenu,
|
||||||
SidebarMenuButton,
|
SidebarMenuButton,
|
||||||
SidebarMenuItem,
|
SidebarMenuItem, useSidebar,
|
||||||
} from "@/components/ui/sidebar"
|
} from "@/components/ui/sidebar"
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { cn } from '@root/lib/utils';
|
import { cn } from '@root/lib/utils';
|
||||||
@@ -23,6 +23,8 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { UpdateButton } from "@/components/layout/update-button";
|
import { UpdateButton } from "@/components/layout/update-button";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { SheetClose } from '@/components/ui/sheet';
|
||||||
|
import logo from "@/assets/image/logo.png"
|
||||||
|
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
{ title: 'Home', url: '/home', icon: Home },
|
{ title: 'Home', url: '/home', icon: Home },
|
||||||
@@ -35,13 +37,24 @@ const menuItems = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export function AppSidebar() {
|
export function AppSidebar() {
|
||||||
|
const { isMobile } = useSidebar();
|
||||||
return (
|
return (
|
||||||
<Sidebar variant="floating" collapsible="icon">
|
<Sidebar variant="floating" collapsible="icon">
|
||||||
<SidebarHeader>
|
<SidebarHeader>
|
||||||
<SidebarMenuButton>
|
<SidebarMenuButton size="lg"
|
||||||
<EarthLock/>
|
className={cn(
|
||||||
<span className="font-semibold group-data-[state=collapsed]:hidden">
|
"flex h-12 items-center transition-all duration-200",
|
||||||
Clash Koala
|
"group-data-[state=expanded]:w-full group-data-[state=expanded]:gap-2 group-data-[state=expanded]:px-3",
|
||||||
|
"group-data-[state=collapsed]:w-full group-data-[state=collapsed]:justify-center"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={logo}
|
||||||
|
alt="logo"
|
||||||
|
className="h-6 w-6 flex-shrink-0"
|
||||||
|
/>
|
||||||
|
<span className="font-semibold whitespace-nowrap group-data-[state=collapsed]:hidden">
|
||||||
|
Koala Clash
|
||||||
</span>
|
</span>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</SidebarHeader>
|
</SidebarHeader>
|
||||||
@@ -51,13 +64,8 @@ export function AppSidebar() {
|
|||||||
<SidebarMenu className="gap-3">
|
<SidebarMenu className="gap-3">
|
||||||
{menuItems.map((item) => {
|
{menuItems.map((item) => {
|
||||||
const isActive = location.pathname === item.url;
|
const isActive = location.pathname === item.url;
|
||||||
return (
|
const linkElement = (
|
||||||
<SidebarMenuItem key={item.title} className="my-1">
|
<Link
|
||||||
<SidebarMenuButton
|
|
||||||
asChild
|
|
||||||
isActive={isActive}
|
|
||||||
tooltip={t(item.title)}>
|
|
||||||
<Link
|
|
||||||
key={item.title}
|
key={item.title}
|
||||||
to={item.url}
|
to={item.url}
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -68,6 +76,20 @@ export function AppSidebar() {
|
|||||||
<item.icon className="h-4 w-4 drop-shadow-md" />
|
<item.icon className="h-4 w-4 drop-shadow-md" />
|
||||||
{t(item.title)}
|
{t(item.title)}
|
||||||
</Link>
|
</Link>
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
<SidebarMenuItem key={item.title} className="my-1">
|
||||||
|
<SidebarMenuButton
|
||||||
|
asChild
|
||||||
|
isActive={isActive}
|
||||||
|
tooltip={t(item.title)}>
|
||||||
|
{isMobile ? (
|
||||||
|
<SheetClose asChild>
|
||||||
|
{linkElement}
|
||||||
|
</SheetClose>
|
||||||
|
) : (
|
||||||
|
linkElement
|
||||||
|
)}
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,47 +1,52 @@
|
|||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { useSetThemeMode, useThemeMode } from "@/services/states";
|
import { useSetThemeMode, useThemeMode } from "@/services/states";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
import { getCurrentWebviewWindow, WebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||||
import { Theme } from "@tauri-apps/api/window";
|
import { Theme } from "@tauri-apps/api/window";
|
||||||
|
|
||||||
export const useCustomTheme = () => {
|
export const useCustomTheme = () => {
|
||||||
const appWindow = useMemo(() => getCurrentWebviewWindow(), []);
|
const appWindow: WebviewWindow = useMemo(() => getCurrentWebviewWindow(), []);
|
||||||
const { verge } = useVerge();
|
const { verge } = useVerge();
|
||||||
const { theme_mode } = verge ?? {};
|
const { theme_mode } = verge ?? {};
|
||||||
|
|
||||||
const mode = useThemeMode();
|
const mode = useThemeMode();
|
||||||
const setMode = useSetThemeMode();
|
const setMode = useSetThemeMode();
|
||||||
|
|
||||||
|
const [systemTheme, setSystemTheme] = useState(
|
||||||
|
() => window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMode(
|
setMode(
|
||||||
theme_mode === "light" || theme_mode === "dark" ? theme_mode : "system",
|
theme_mode === "light" || theme_mode === "dark" ? theme_mode : "system",
|
||||||
);
|
);
|
||||||
}, [theme_mode, setMode]);
|
}, [theme_mode, setMode]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const root = document.documentElement;
|
if (mode !== 'system') return;
|
||||||
|
|
||||||
const activeTheme =
|
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
mode === "system"
|
const handleChange = (e: MediaQueryListEvent) => {
|
||||||
? window.matchMedia("(prefers-color-scheme: dark)").matches
|
setSystemTheme(e.matches ? "dark" : "light");
|
||||||
? "dark"
|
};
|
||||||
: "light"
|
|
||||||
: mode;
|
|
||||||
|
|
||||||
root.classList.remove("light", "dark");
|
mediaQuery.addEventListener('change', handleChange);
|
||||||
root.classList.add(activeTheme);
|
return () => mediaQuery.removeEventListener('change', handleChange);
|
||||||
appWindow.setTheme(activeTheme as Theme).catch(console.error);
|
}, [mode]);
|
||||||
}, [mode, appWindow]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (theme_mode !== "system") return;
|
const root = document.documentElement;
|
||||||
const unlistenPromise = appWindow.onThemeChanged(({ payload }) => {
|
const activeTheme = mode === "system" ? systemTheme : mode;
|
||||||
setMode(payload);
|
root.classList.remove("light", "dark");
|
||||||
});
|
root.classList.add(activeTheme);
|
||||||
return () => {
|
|
||||||
unlistenPromise.then((f) => f());
|
if (theme_mode === "system") {
|
||||||
};
|
appWindow.setTheme(null).catch(console.error);
|
||||||
}, [theme_mode, appWindow, setMode]);
|
} else {
|
||||||
|
appWindow.setTheme(activeTheme as Theme).catch(console.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [mode, systemTheme, appWindow, theme_mode]);
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
};
|
};
|
||||||
@@ -69,7 +69,7 @@ const LogItem = ({ value, searchState }: Props) => {
|
|||||||
{renderHighlightText(value.type)}
|
{renderHighlightText(value.type)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-gray-800 dark:text-gray-200 break-all whitespace-pre-wrap">
|
<div className="text-foreground break-all whitespace-pre-wrap">
|
||||||
{renderHighlightText(value.payload)}
|
{renderHighlightText(value.payload)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -562,7 +562,7 @@ export const GroupsEditorViewer = (props: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onClose}>
|
<Dialog open={open} onOpenChange={onClose}>
|
||||||
<DialogContent className="lg:min-w-5xl h-[90vh] flex flex-col">
|
<DialogContent className="min-w-5xl h-[90vh] flex flex-col">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<div className="flex justify-between items-center pr-8">
|
<div className="flex justify-between items-center pr-8">
|
||||||
<DialogTitle>{t("Edit Groups")}</DialogTitle>
|
<DialogTitle>{t("Edit Groups")}</DialogTitle>
|
||||||
|
|||||||
53
src/components/profile/hwid-error-dialog.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { AlertTriangle } from "lucide-react";
|
||||||
|
|
||||||
|
export const HwidErrorDialog = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleShowHwidError = (event: Event) => {
|
||||||
|
const customEvent = event as CustomEvent<string>;
|
||||||
|
setErrorMessage(customEvent.detail);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('show-hwid-error', handleShowHwidError);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('show-hwid-error', handleShowHwidError);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!errorMessage) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={!!errorMessage} onOpenChange={() => setErrorMessage(null)}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="flex items-center gap-2">
|
||||||
|
<AlertTriangle className="h-5 w-5 text-destructive" />
|
||||||
|
{t("Device Limit Reached")}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogDescription className="pt-4 text-left">
|
||||||
|
{errorMessage}
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button onClick={() => setErrorMessage(null)}>{t("OK")}</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -56,6 +56,7 @@ import {
|
|||||||
Loader2,
|
Loader2,
|
||||||
Info,
|
Info,
|
||||||
DownloadCloud,
|
DownloadCloud,
|
||||||
|
Download,
|
||||||
Trash2,
|
Trash2,
|
||||||
Edit3,
|
Edit3,
|
||||||
FileText as FileTextIcon,
|
FileText as FileTextIcon,
|
||||||
@@ -66,7 +67,7 @@ import {
|
|||||||
ListTree,
|
ListTree,
|
||||||
CheckCircle,
|
CheckCircle,
|
||||||
Infinity,
|
Infinity,
|
||||||
RefreshCw,
|
RefreshCw, Network,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
|
|
||||||
@@ -343,8 +344,8 @@ export const ProfileItem = (props: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center flex-shrink-0">
|
<div className="flex items-center flex-shrink-0">
|
||||||
<Badge
|
<Badge
|
||||||
variant={type === "local" ? "secondary" : "outline"}
|
variant="outline"
|
||||||
className="text-xs"
|
className="text-xs shadow-sm"
|
||||||
>
|
>
|
||||||
{type}
|
{type}
|
||||||
</Badge>
|
</Badge>
|
||||||
@@ -384,20 +385,21 @@ export const ProfileItem = (props: Props) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="flex items-center justify-between">
|
||||||
</div>
|
<div className="flex items-center">
|
||||||
|
<Download className="h-3 w-3 inline mr-1.5" />
|
||||||
|
<span className="pr-5">
|
||||||
|
{parseTraffic(download)}
|
||||||
|
</span>
|
||||||
|
<Network className="h-3 w-3 inline mr-1.5" />
|
||||||
|
{total > 0 ? (
|
||||||
|
<span>{parseTraffic(total)}</span>
|
||||||
|
) : <Infinity className="h-3 w-3 inline mr-1.5" />}
|
||||||
|
|
||||||
{hasExtra && total > 0 && (
|
</div>
|
||||||
<div className="relative h-5">
|
|
||||||
<Progress value={progress} className="h-full rounded-none" />
|
|
||||||
<div className="absolute inset-0 flex items-center justify-between px-2 text-xs text-white/90 font-medium">
|
|
||||||
<span>
|
|
||||||
{parseTraffic(download)}↓ / {parseTraffic(upload)}↑
|
|
||||||
</span>
|
|
||||||
<span>{parseTraffic(total)}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</ContextMenuTrigger>
|
</ContextMenuTrigger>
|
||||||
|
|
||||||
@@ -405,7 +407,6 @@ export const ProfileItem = (props: Props) => {
|
|||||||
className="w-56"
|
className="w-56"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{/* Объединяем все части меню */}
|
|
||||||
{[...homeMenuItem, ...mainMenuItems].map((item) => (
|
{[...homeMenuItem, ...mainMenuItems].map((item) => (
|
||||||
<ContextMenuItem
|
<ContextMenuItem
|
||||||
key={item.label}
|
key={item.label}
|
||||||
@@ -420,7 +421,7 @@ export const ProfileItem = (props: Props) => {
|
|||||||
<ContextMenuSub>
|
<ContextMenuSub>
|
||||||
<ContextMenuSubTrigger disabled={!hasUrl || isLoading}>
|
<ContextMenuSubTrigger disabled={!hasUrl || isLoading}>
|
||||||
<DownloadCloud className="mr-2 h-4 w-4" />
|
<DownloadCloud className="mr-2 h-4 w-4" />
|
||||||
<span>{t("Update")}</span>
|
<span className="px-2">{t("Update")}</span>
|
||||||
</ContextMenuSubTrigger>
|
</ContextMenuSubTrigger>
|
||||||
<ContextMenuPortal>
|
<ContextMenuPortal>
|
||||||
<ContextMenuSubContent>
|
<ContextMenuSubContent>
|
||||||
|
|||||||
@@ -12,13 +12,14 @@ import {
|
|||||||
createProfile,
|
createProfile,
|
||||||
patchProfile,
|
patchProfile,
|
||||||
importProfile,
|
importProfile,
|
||||||
enhanceProfiles,
|
enhanceProfiles, createProfileFromShareLink,
|
||||||
} from "@/services/cmds";
|
} from "@/services/cmds";
|
||||||
import { useProfiles } from "@/hooks/use-profiles";
|
import { useProfiles } from "@/hooks/use-profiles";
|
||||||
import { showNotice } from "@/services/noticeService";
|
import { showNotice } from "@/services/noticeService";
|
||||||
import { version } from "@root/package.json";
|
import { version } from "@root/package.json";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -72,6 +73,7 @@ export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
|
|||||||
const [isCheckingUrl, setIsCheckingUrl] = useState(false);
|
const [isCheckingUrl, setIsCheckingUrl] = useState(false);
|
||||||
const [isImporting, setIsImporting] = useState(false);
|
const [isImporting, setIsImporting] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [selectedTemplate, setSelectedTemplate] = useState("default");
|
||||||
|
|
||||||
const form = useForm<IProfileItem>({
|
const form = useForm<IProfileItem>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -136,14 +138,9 @@ export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
|
|||||||
setIsCheckingUrl(true);
|
setIsCheckingUrl(true);
|
||||||
|
|
||||||
const handler = setTimeout(() => {
|
const handler = setTimeout(() => {
|
||||||
try {
|
const isValid = /^(https?|vmess|vless|ss|socks|trojan):\/\//.test(importUrl);
|
||||||
new URL(importUrl);
|
setIsUrlValid(isValid);
|
||||||
setIsUrlValid(true);
|
setIsCheckingUrl(false);
|
||||||
} catch (error) {
|
|
||||||
setIsUrlValid(false);
|
|
||||||
} finally {
|
|
||||||
setIsCheckingUrl(false);
|
|
||||||
}
|
|
||||||
}, 500);
|
}, 500);
|
||||||
return () => {
|
return () => {
|
||||||
clearTimeout(handler);
|
clearTimeout(handler);
|
||||||
@@ -151,30 +148,40 @@ export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
|
|||||||
}, [importUrl]);
|
}, [importUrl]);
|
||||||
|
|
||||||
const handleImport = useLockFn(async () => {
|
const handleImport = useLockFn(async () => {
|
||||||
if (!importUrl) return;
|
if (!importUrl || !isUrlValid) return;
|
||||||
setIsImporting(true);
|
setIsImporting(true);
|
||||||
|
|
||||||
|
const isShareLink = /^(vmess|vless|ss|socks|trojan):\/\//.test(importUrl);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await importProfile(importUrl);
|
if (isShareLink) {
|
||||||
showNotice("success", t("Profile Imported Successfully"));
|
await createProfileFromShareLink(importUrl, selectedTemplate);
|
||||||
|
showNotice("success", t("Profile created from link successfully"));
|
||||||
|
} else {
|
||||||
|
await importProfile(importUrl);
|
||||||
|
showNotice("success", t("Profile Imported Successfully"));
|
||||||
|
}
|
||||||
props.onChange();
|
props.onChange();
|
||||||
await enhanceProfiles();
|
await enhanceProfiles();
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
showNotice("info", t("Import failed, retrying with Clash proxy..."));
|
const errorMessage = typeof err === 'string' ? err : (err.message || String(err));
|
||||||
try {
|
const lowerErrorMessage = errorMessage.toLowerCase();
|
||||||
await importProfile(importUrl, {
|
if (lowerErrorMessage.includes('device') || lowerErrorMessage.includes('устройств')) {
|
||||||
with_proxy: false,
|
window.dispatchEvent(new CustomEvent('show-hwid-error', { detail: errorMessage }));
|
||||||
self_proxy: true,
|
} else if (!isShareLink && errorMessage.includes("failed to fetch")) {
|
||||||
});
|
showNotice("info", t("Import failed, retrying with Clash proxy..."));
|
||||||
showNotice("success", t("Profile Imported with Clash proxy"));
|
try {
|
||||||
props.onChange();
|
await importProfile(importUrl, { with_proxy: false, self_proxy: true });
|
||||||
await enhanceProfiles();
|
showNotice("success", t("Profile Imported with Clash proxy"));
|
||||||
setOpen(false);
|
props.onChange();
|
||||||
} catch (retryErr: any) {
|
await enhanceProfiles();
|
||||||
showNotice(
|
setOpen(false);
|
||||||
"error",
|
} catch (retryErr: any) {
|
||||||
`${t("Import failed even with Clash proxy")}: ${retryErr?.message || retryErr.toString()}`,
|
showNotice("error", `${t("Import failed even with Clash proxy")}: ${retryErr?.message || retryErr.toString()}`);
|
||||||
);
|
}
|
||||||
|
} else {
|
||||||
|
showNotice("error", errorMessage);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setIsImporting(false);
|
setIsImporting(false);
|
||||||
@@ -289,11 +296,26 @@ export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
|
|||||||
</Button>
|
</Button>
|
||||||
{!isUrlValid && importUrl && (
|
{!isUrlValid && importUrl && (
|
||||||
<p className="text-sm text-destructive px-1">
|
<p className="text-sm text-destructive px-1">
|
||||||
{t("Please enter a valid URL")}
|
{t("Invalid Profile URL")}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/^(vmess|vless|ss|socks|trojan):\/\//.test(importUrl) && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>{t("Template")}</Label>
|
||||||
|
<Select value={selectedTemplate} onValueChange={setSelectedTemplate}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a template..." />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="default">{t("Default Template")}</SelectItem>
|
||||||
|
<SelectItem value="without_ru">{t("Template without RU Rules")}</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => setShowAdvanced(!showAdvanced)}
|
onClick={() => setShowAdvanced(!showAdvanced)}
|
||||||
@@ -440,7 +462,7 @@ export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
|
|||||||
<FormLabel>User Agent</FormLabel>
|
<FormLabel>User Agent</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
placeholder={`clash-verge/v${version}`}
|
placeholder={`koala-clash/v${version}`}
|
||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|||||||
@@ -302,7 +302,7 @@ export const ProxiesEditorViewer = (props: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onClose}>
|
<Dialog open={open} onOpenChange={onClose}>
|
||||||
<DialogContent className="lg:max-w-4xl h-[80vh] flex flex-col">
|
<DialogContent className="min-w-4xl h-[90vh] flex flex-col">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<div className="flex justify-between items-center pr-8">
|
<div className="flex justify-between items-center pr-8">
|
||||||
<DialogTitle>{t("Edit Proxies")}</DialogTitle>
|
<DialogTitle>{t("Edit Proxies")}</DialogTitle>
|
||||||
|
|||||||
@@ -513,7 +513,7 @@ export const RulesEditorViewer = (props: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onClose}>
|
<Dialog open={open} onOpenChange={onClose}>
|
||||||
<DialogContent className="w-[95vw] lg:min-w-5xl h-[80vh] flex flex-col">
|
<DialogContent className="min-w-4xl h-[90vh] flex flex-col">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<div className="flex justify-between items-center pr-8">
|
<div className="flex justify-between items-center pr-8">
|
||||||
<DialogTitle>{t("Edit Rules")}</DialogTitle>
|
<DialogTitle>{t("Edit Rules")}</DialogTitle>
|
||||||
@@ -529,8 +529,8 @@ export const RulesEditorViewer = (props: Props) => {
|
|||||||
|
|
||||||
<div className="flex-1 min-h-0 mt-4">
|
<div className="flex-1 min-h-0 mt-4">
|
||||||
{visualization ? (
|
{visualization ? (
|
||||||
<div className="h-full flex flex-col lg:flex-row gap-4">
|
<div className="h-full flex flex-row gap-4">
|
||||||
<div className="w-full lg:w-1/3 flex flex-col gap-4 p-1">
|
<div className="w-1/3 flex flex-col gap-4 p-1">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>{t("Rule Type")}</Label>
|
<Label>{t("Rule Type")}</Label>
|
||||||
<Combobox
|
<Combobox
|
||||||
@@ -617,8 +617,8 @@ export const RulesEditorViewer = (props: Props) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Separator orientation="vertical" className="hidden lg:flex" />
|
<Separator orientation="vertical" className="flex" />
|
||||||
<div className="w-full lg:w-2/3 flex flex-col min-w-0 flex-1">
|
<div className="w-2/3 flex flex-col min-w-0 flex-1">
|
||||||
<BaseSearchBox
|
<BaseSearchBox
|
||||||
onSearch={(matcher) => setMatch(() => matcher)}
|
onSearch={(matcher) => setMatch(() => matcher)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -23,14 +23,8 @@ import { ProxyRender } from "./proxy-render";
|
|||||||
import delayManager from "@/services/delay";
|
import delayManager from "@/services/delay";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { ScrollTopButton } from "../layout/scroll-top-button";
|
import { ScrollTopButton } from "../layout/scroll-top-button";
|
||||||
import {
|
|
||||||
Tooltip,
|
|
||||||
TooltipContent,
|
|
||||||
TooltipProvider,
|
|
||||||
TooltipTrigger,
|
|
||||||
} from "@/components/ui/tooltip";
|
|
||||||
|
|
||||||
// Вспомогательная функция для плавного скролла (взята из вашего оригинального файла)
|
|
||||||
function throttle<T extends (...args: any[]) => any>(
|
function throttle<T extends (...args: any[]) => any>(
|
||||||
func: T,
|
func: T,
|
||||||
wait: number,
|
wait: number,
|
||||||
@@ -59,36 +53,6 @@ function throttle<T extends (...args: any[]) => any>(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Компонент для одной буквы в навигаторе, переписанный на Tailwind и shadcn/ui
|
|
||||||
const LetterItem = memo(
|
|
||||||
({
|
|
||||||
name,
|
|
||||||
onClick,
|
|
||||||
getFirstChar,
|
|
||||||
}: {
|
|
||||||
name: string;
|
|
||||||
onClick: (name: string) => void;
|
|
||||||
getFirstChar: (str: string) => string;
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<TooltipProvider delayDuration={100}>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<div
|
|
||||||
className="flex items-center justify-center w-6 h-6 text-xs rounded-md border shadow-sm cursor-pointer text-muted-foreground transition-transform hover:bg-accent hover:text-accent-foreground hover:scale-125"
|
|
||||||
onClick={() => onClick(name)}
|
|
||||||
>
|
|
||||||
{getFirstChar(name)}
|
|
||||||
</div>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="left">
|
|
||||||
<p>{name}</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
mode: string;
|
mode: string;
|
||||||
@@ -108,33 +72,6 @@ export const ProxyGroups = memo((props: Props) => {
|
|||||||
const scrollerRef = useRef<Element | null>(null);
|
const scrollerRef = useRef<Element | null>(null);
|
||||||
const [showScrollTop, setShowScrollTop] = useState(false);
|
const [showScrollTop, setShowScrollTop] = useState(false);
|
||||||
|
|
||||||
// Мемоизация вычисления букв и индексов для навигатора
|
|
||||||
const { groupFirstLetters, letterIndexMap } = useMemo(() => {
|
|
||||||
const letters = new Set<string>();
|
|
||||||
const indexMap: Record<string, number> = {};
|
|
||||||
renderList.forEach((item, index) => {
|
|
||||||
if (item.type === 0) {
|
|
||||||
// type 0 - это заголовок группы
|
|
||||||
const fullName = item.group.name;
|
|
||||||
letters.add(fullName);
|
|
||||||
if (!(fullName in indexMap)) {
|
|
||||||
indexMap[fullName] = index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
groupFirstLetters: Array.from(letters),
|
|
||||||
letterIndexMap: indexMap,
|
|
||||||
};
|
|
||||||
}, [renderList]);
|
|
||||||
|
|
||||||
// Мемоизация функции для получения первой буквы (поддерживает эмодзи)
|
|
||||||
const getFirstChar = useCallback((str: string) => {
|
|
||||||
const match = str.match(
|
|
||||||
/\p{Regional_Indicator}{2}|\p{Extended_Pictographic}|\p{L}|\p{N}|./u,
|
|
||||||
);
|
|
||||||
return match ? match[0] : str.charAt(0);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Обработчик скролла для показа/скрытия кнопки "Наверх"
|
// Обработчик скролла для показа/скрытия кнопки "Наверх"
|
||||||
const handleScroll = useCallback(
|
const handleScroll = useCallback(
|
||||||
@@ -161,20 +98,6 @@ export const ProxyGroups = memo((props: Props) => {
|
|||||||
virtuosoRef.current?.scrollTo({ top: 0, behavior: "smooth" });
|
virtuosoRef.current?.scrollTo({ top: 0, behavior: "smooth" });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleLetterClick = useCallback(
|
|
||||||
(name: string) => {
|
|
||||||
const index = letterIndexMap[name];
|
|
||||||
if (index !== undefined) {
|
|
||||||
virtuosoRef.current?.scrollToIndex({
|
|
||||||
index,
|
|
||||||
align: "start",
|
|
||||||
behavior: "smooth",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[letterIndexMap],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Вся бизнес-логика из оригинального файла
|
// Вся бизнес-логика из оригинального файла
|
||||||
const handleChangeProxy = useLockFn(
|
const handleChangeProxy = useLockFn(
|
||||||
async (group: IProxyGroupItem, proxy: IProxyItem) => {
|
async (group: IProxyGroupItem, proxy: IProxyItem) => {
|
||||||
@@ -288,18 +211,6 @@ export const ProxyGroups = memo((props: Props) => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<ScrollTopButton show={showScrollTop} onClick={scrollToTop} />
|
<ScrollTopButton show={showScrollTop} onClick={scrollToTop} />
|
||||||
|
|
||||||
{/* Алфавитный указатель */}
|
|
||||||
<div className="fixed top-1/2 right-4 z-50 flex -translate-y-1/2 flex-col gap-1 rounded-md bg-background/50 p-1 backdrop-blur-sm">
|
|
||||||
{groupFirstLetters.map((name) => (
|
|
||||||
<LetterItem
|
|
||||||
key={name}
|
|
||||||
name={name}
|
|
||||||
onClick={handleLetterClick}
|
|
||||||
getFirstChar={getFirstChar}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export const ProxyItemMini = (props: Props) => {
|
|||||||
title={`${proxy.name}\n${proxy.now ?? ""}`}
|
title={`${proxy.name}\n${proxy.now ?? ""}`}
|
||||||
className="group relative flex h-16 cursor-pointer items-center justify-between rounded-lg border bg-card p-3 shadow-sm transition-colors duration-200 hover:bg-accent data-[selected=true]:ring-2 data-[selected=true]:ring-primary"
|
className="group relative flex h-16 cursor-pointer items-center justify-between rounded-lg border bg-card p-3 shadow-sm transition-colors duration-200 hover:bg-accent data-[selected=true]:ring-2 data-[selected=true]:ring-primary"
|
||||||
>
|
>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0 w-0">
|
||||||
<p className="truncate text-sm font-medium">{proxy.name}</p>
|
<p className="truncate text-sm font-medium">{proxy.name}</p>
|
||||||
|
|
||||||
{showType && (
|
{showType && (
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ type HeadStateStorage = Record<string, Record<string, HeadState>>;
|
|||||||
const HEAD_STATE_KEY = "proxy-head-state";
|
const HEAD_STATE_KEY = "proxy-head-state";
|
||||||
export const DEFAULT_STATE: HeadState = {
|
export const DEFAULT_STATE: HeadState = {
|
||||||
open: false,
|
open: false,
|
||||||
showType: true,
|
showType: false,
|
||||||
sortType: 0,
|
sortType: 0,
|
||||||
filterText: "",
|
filterText: "",
|
||||||
textState: null,
|
textState: null,
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ import { showNotice } from "@/services/noticeService";
|
|||||||
|
|
||||||
// Константы и интерфейсы
|
// Константы и интерфейсы
|
||||||
const VALID_CORE = [
|
const VALID_CORE = [
|
||||||
{ name: "Mihomo", core: "verge-mihomo", chip: "Release Version" },
|
{ name: "Mihomo", core: "koala-mihomo", chip: "Release Version" },
|
||||||
{ name: "Mihomo Alpha", core: "verge-mihomo-alpha", chip: "Alpha Version" },
|
{ name: "Mihomo Alpha", core: "koala-mihomo-alpha", chip: "Alpha Version" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const ClashCoreViewer = forwardRef<DialogRef>((props, ref) => {
|
export const ClashCoreViewer = forwardRef<DialogRef>((props, ref) => {
|
||||||
@@ -44,7 +44,7 @@ export const ClashCoreViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
close: () => setOpen(false),
|
close: () => setOpen(false),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const { clash_core = "verge-mihomo" } = verge ?? {};
|
const { clash_core = "koala-mihomo" } = verge ?? {};
|
||||||
|
|
||||||
const onCoreChange = useLockFn(async (core: string) => {
|
const onCoreChange = useLockFn(async (core: string) => {
|
||||||
if (core === clash_core) return;
|
if (core === clash_core) return;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import getSystem from "@/utils/get-system";
|
import getSystem from "@/utils/get-system";
|
||||||
|
import { Loader2 } from "lucide-react";
|
||||||
|
|
||||||
const OS = getSystem();
|
const OS = getSystem();
|
||||||
|
|
||||||
@@ -69,6 +70,9 @@ export const LayoutViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { verge, patchVerge, mutateVerge } = useVerge();
|
const { verge, patchVerge, mutateVerge } = useVerge();
|
||||||
|
|
||||||
|
const [localConfig, setLocalConfig] = useState<Partial<IVergeConfig>>({});
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [commonIcon, setCommonIcon] = useState("");
|
const [commonIcon, setCommonIcon] = useState("");
|
||||||
const [sysproxyIcon, setSysproxyIcon] = useState("");
|
const [sysproxyIcon, setSysproxyIcon] = useState("");
|
||||||
@@ -96,28 +100,26 @@ export const LayoutViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open) initIconPath();
|
if (open) {
|
||||||
}, [open, initIconPath]);
|
setLocalConfig(verge ?? {});
|
||||||
|
initIconPath();
|
||||||
|
}
|
||||||
|
}, [open, verge, initIconPath]);
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
open: () => setOpen(true),
|
open: () => setOpen(true),
|
||||||
close: () => setOpen(false),
|
close: () => setOpen(false),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const onSwitchFormat = (_e: any, value: boolean) => value;
|
const handleConfigChange = (patch: Partial<IVergeConfig>) => {
|
||||||
const onError = (err: any) => {
|
setLocalConfig(prev => ({ ...prev, ...patch }));
|
||||||
showNotice("error", err.message || err.toString());
|
|
||||||
};
|
|
||||||
const onChangeData = (patch: Partial<IVergeConfig>) => {
|
|
||||||
mutateVerge({ ...verge, ...patch }, false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleIconChange = useLockFn(
|
const handleIconChange = useLockFn(
|
||||||
async (type: "common" | "sysproxy" | "tun") => {
|
async (type: "common" | "sysproxy" | "tun") => {
|
||||||
const key = `${type}_tray_icon` as keyof IVergeConfig;
|
const key = `${type}_tray_icon` as keyof IVergeConfig;
|
||||||
if (verge?.[key]) {
|
if (localConfig[key]) {
|
||||||
onChangeData({ [key]: false });
|
handleConfigChange({ [key]: false });
|
||||||
await patchVerge({ [key]: false });
|
|
||||||
} else {
|
} else {
|
||||||
const selected = await openDialog({
|
const selected = await openDialog({
|
||||||
directory: false,
|
directory: false,
|
||||||
@@ -128,213 +130,94 @@ export const LayoutViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
const path = Array.isArray(selected) ? selected[0] : selected;
|
const path = Array.isArray(selected) ? selected[0] : selected;
|
||||||
await copyIconFile(path, type);
|
await copyIconFile(path, type);
|
||||||
await initIconPath();
|
await initIconPath();
|
||||||
onChangeData({ [key]: true });
|
handleConfigChange({ [key]: true });
|
||||||
await patchVerge({ [key]: true });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
const handleSave = useLockFn(async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
await patchVerge(localConfig);
|
||||||
|
showNotice("success", t("Settings saved successfully"));
|
||||||
|
setOpen(false);
|
||||||
|
} catch (err: any) {
|
||||||
|
showNotice("error", err.message || err.toString());
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
<DialogContent className="sm:max-w-md">
|
<DialogContent className="sm:max-w-md">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{t("Layout Setting")}</DialogTitle>
|
<DialogTitle>{t("Layout Setting")}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="py-4 space-y-1">
|
<div className="py-4 space-y-1">
|
||||||
<SettingRow label={t("Traffic Graph")}>
|
{OS === "macos" && (
|
||||||
<GuardState
|
<>
|
||||||
value={verge?.traffic_graph ?? true}
|
<SettingRow label={t("Tray Icon")}>
|
||||||
valueProps="checked"
|
<Select
|
||||||
onCatch={onError}
|
onValueChange={(value) => handleConfigChange({ tray_icon: value as any })}
|
||||||
onFormat={onSwitchFormat}
|
value={localConfig.tray_icon ?? "monochrome"}
|
||||||
onChange={(e) => onChangeData({ traffic_graph: e })}
|
>
|
||||||
onGuard={(e) => patchVerge({ traffic_graph: e })}
|
<SelectTrigger className="w-40 h-8"><SelectValue /></SelectTrigger>
|
||||||
>
|
<SelectContent>
|
||||||
<Switch />
|
<SelectItem value="monochrome">{t("Monochrome")}</SelectItem>
|
||||||
</GuardState>
|
<SelectItem value="colorful">{t("Colorful")}</SelectItem>
|
||||||
</SettingRow>
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</SettingRow>
|
||||||
|
|
||||||
<SettingRow label={t("Memory Usage")}>
|
<SettingRow label={t("Enable Tray Icon")}>
|
||||||
<GuardState
|
<Switch
|
||||||
value={verge?.enable_memory_usage ?? true}
|
checked={localConfig.enable_tray_icon ?? true}
|
||||||
valueProps="checked"
|
onCheckedChange={(checked) => handleConfigChange({ enable_tray_icon: checked })}
|
||||||
onCatch={onError}
|
/>
|
||||||
onFormat={onSwitchFormat}
|
</SettingRow>
|
||||||
onChange={(e) => onChangeData({ enable_memory_usage: e })}
|
</>
|
||||||
onGuard={(e) => patchVerge({ enable_memory_usage: e })}
|
)}
|
||||||
>
|
|
||||||
<Switch />
|
|
||||||
</GuardState>
|
|
||||||
</SettingRow>
|
|
||||||
|
|
||||||
<SettingRow label={t("Proxy Group Icon")}>
|
<SettingRow label={t("Common Tray Icon")}>
|
||||||
<GuardState
|
<Button variant="outline" size="sm" className="h-8" onClick={() => handleIconChange("common")}>
|
||||||
value={verge?.enable_group_icon ?? true}
|
{localConfig.common_tray_icon && commonIcon && (
|
||||||
valueProps="checked"
|
<img src={convertFileSrc(commonIcon)} className="h-5 mr-2" alt="common tray icon" />
|
||||||
onCatch={onError}
|
)}
|
||||||
onFormat={onSwitchFormat}
|
{localConfig.common_tray_icon ? t("Clear") : t("Browse")}
|
||||||
onChange={(e) => onChangeData({ enable_group_icon: e })}
|
</Button>
|
||||||
onGuard={(e) => patchVerge({ enable_group_icon: e })}
|
</SettingRow>
|
||||||
>
|
|
||||||
<Switch />
|
|
||||||
</GuardState>
|
|
||||||
</SettingRow>
|
|
||||||
|
|
||||||
<SettingRow
|
<SettingRow label={t("System Proxy Tray Icon")}>
|
||||||
label={t("Hover Jump Navigator")}
|
<Button variant="outline" size="sm" className="h-8" onClick={() => handleIconChange("sysproxy")}>
|
||||||
extra={<TooltipIcon tooltip={t("Hover Jump Navigator Info")} />}
|
{localConfig.sysproxy_tray_icon && sysproxyIcon && (
|
||||||
>
|
<img src={convertFileSrc(sysproxyIcon)} className="h-5 mr-2" alt="system proxy tray icon" />
|
||||||
<GuardState
|
)}
|
||||||
value={verge?.enable_hover_jump_navigator ?? true}
|
{localConfig.sysproxy_tray_icon ? t("Clear") : t("Browse")}
|
||||||
valueProps="checked"
|
</Button>
|
||||||
onCatch={onError}
|
</SettingRow>
|
||||||
onFormat={onSwitchFormat}
|
|
||||||
onChange={(e) => onChangeData({ enable_hover_jump_navigator: e })}
|
|
||||||
onGuard={(e) => patchVerge({ enable_hover_jump_navigator: e })}
|
|
||||||
>
|
|
||||||
<Switch />
|
|
||||||
</GuardState>
|
|
||||||
</SettingRow>
|
|
||||||
|
|
||||||
<SettingRow label={t("Nav Icon")}>
|
<SettingRow label={t("Tun Tray Icon")}>
|
||||||
<GuardState
|
<Button variant="outline" size="sm" className="h-8" onClick={() => handleIconChange("tun")}>
|
||||||
value={verge?.menu_icon ?? "monochrome"}
|
{localConfig.tun_tray_icon && tunIcon && (
|
||||||
onCatch={onError}
|
<img src={convertFileSrc(tunIcon)} className="h-5 mr-2" alt="tun mode tray icon" />
|
||||||
onFormat={(v) => v}
|
)}
|
||||||
onChange={(e) => onChangeData({ menu_icon: e })}
|
{localConfig.tun_tray_icon ? t("Clear") : t("Browse")}
|
||||||
onGuard={(e) => patchVerge({ menu_icon: e })}
|
</Button>
|
||||||
>
|
</SettingRow>
|
||||||
{/* --- НАЧАЛО ИЗМЕНЕНИЙ 1 --- */}
|
</div>
|
||||||
<Select
|
|
||||||
onValueChange={(value) =>
|
|
||||||
onChangeData({ menu_icon: value as any })
|
|
||||||
}
|
|
||||||
value={verge?.menu_icon}
|
|
||||||
>
|
|
||||||
{/* --- КОНЕЦ ИЗМЕНЕНИЙ 1 --- */}
|
|
||||||
<SelectTrigger className="w-40 h-8">
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="monochrome">{t("Monochrome")}</SelectItem>
|
|
||||||
<SelectItem value="colorful">{t("Colorful")}</SelectItem>
|
|
||||||
<SelectItem value="disable">{t("Disable")}</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</GuardState>
|
|
||||||
</SettingRow>
|
|
||||||
|
|
||||||
{OS === "macos" && (
|
<DialogFooter>
|
||||||
<>
|
<DialogClose asChild>
|
||||||
<SettingRow label={t("Tray Icon")}>
|
<Button type="button" variant="outline">{t("Cancel")}</Button>
|
||||||
<GuardState
|
</DialogClose>
|
||||||
value={verge?.tray_icon ?? "monochrome"}
|
<Button type="button" onClick={handleSave} disabled={loading}>
|
||||||
onCatch={onError}
|
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||||
onFormat={(v) => v}
|
{t("Save")}
|
||||||
onChange={(e) => onChangeData({ tray_icon: e })}
|
|
||||||
onGuard={(e) => patchVerge({ tray_icon: e })}
|
|
||||||
>
|
|
||||||
{/* --- НАЧАЛО ИЗМЕНЕНИЙ 2 --- */}
|
|
||||||
<Select
|
|
||||||
onValueChange={(value) =>
|
|
||||||
onChangeData({ tray_icon: value as any })
|
|
||||||
}
|
|
||||||
value={verge?.tray_icon}
|
|
||||||
>
|
|
||||||
{/* --- КОНЕЦ ИЗМЕНЕНИЙ 2 --- */}
|
|
||||||
<SelectTrigger className="w-40 h-8">
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="monochrome">
|
|
||||||
{t("Monochrome")}
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem value="colorful">{t("Colorful")}</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</GuardState>
|
|
||||||
</SettingRow>
|
|
||||||
|
|
||||||
<SettingRow label={t("Enable Tray Icon")}>
|
|
||||||
<GuardState
|
|
||||||
value={verge?.enable_tray_icon ?? true}
|
|
||||||
valueProps="checked"
|
|
||||||
onCatch={onError}
|
|
||||||
onFormat={onSwitchFormat}
|
|
||||||
onChange={(e) => onChangeData({ enable_tray_icon: e })}
|
|
||||||
onGuard={(e) => patchVerge({ enable_tray_icon: e })}
|
|
||||||
>
|
|
||||||
<Switch />
|
|
||||||
</GuardState>
|
|
||||||
</SettingRow>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<SettingRow label={t("Common Tray Icon")}>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
className="h-8"
|
|
||||||
onClick={() => handleIconChange("common")}
|
|
||||||
>
|
|
||||||
{verge?.common_tray_icon && commonIcon && (
|
|
||||||
<img
|
|
||||||
src={convertFileSrc(commonIcon)}
|
|
||||||
className="h-5 mr-2"
|
|
||||||
alt="common tray icon"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{verge?.common_tray_icon ? t("Clear") : t("Browse")}
|
|
||||||
</Button>
|
</Button>
|
||||||
</SettingRow>
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
<SettingRow label={t("System Proxy Tray Icon")}>
|
</Dialog>
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
className="h-8"
|
|
||||||
onClick={() => handleIconChange("sysproxy")}
|
|
||||||
>
|
|
||||||
{verge?.sysproxy_tray_icon && sysproxyIcon && (
|
|
||||||
<img
|
|
||||||
src={convertFileSrc(sysproxyIcon)}
|
|
||||||
className="h-5 mr-2"
|
|
||||||
alt="system proxy tray icon"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{verge?.sysproxy_tray_icon ? t("Clear") : t("Browse")}
|
|
||||||
</Button>
|
|
||||||
</SettingRow>
|
|
||||||
|
|
||||||
<SettingRow label={t("Tun Tray Icon")}>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
className="h-8"
|
|
||||||
onClick={() => handleIconChange("tun")}
|
|
||||||
>
|
|
||||||
{verge?.tun_tray_icon && tunIcon && (
|
|
||||||
<img
|
|
||||||
src={convertFileSrc(tunIcon)}
|
|
||||||
className="h-5 mr-2"
|
|
||||||
alt="tun mode tray icon"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{verge?.tun_tray_icon ? t("Clear") : t("Browse")}
|
|
||||||
</Button>
|
|
||||||
</SettingRow>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<DialogFooter>
|
|
||||||
<DialogClose asChild>
|
|
||||||
<Button type="button" variant="outline">
|
|
||||||
{t("Close")}
|
|
||||||
</Button>
|
|
||||||
</DialogClose>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -127,7 +127,7 @@ export const UpdateViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
openUrl(
|
openUrl(
|
||||||
`https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/v${updateInfo?.version}`,
|
`https://github.com/coolcoala/clash-verge-rev-lite/releases/tag/v${updateInfo?.version}`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useRef, useState } from "react";
|
import {useMemo, useRef, useState} from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useLockFn } from "ahooks";
|
import { useLockFn } from "ahooks";
|
||||||
import { mutate } from "swr";
|
import { mutate } from "swr";
|
||||||
@@ -56,6 +56,7 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
|
import {useProfiles} from "@/hooks/use-profiles";
|
||||||
|
|
||||||
const isWIN = getSystem() === "windows";
|
const isWIN = getSystem() === "windows";
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -106,6 +107,12 @@ const SettingSystem = ({ onError }: Props) => {
|
|||||||
const { verge, patchVerge, mutateVerge } = useVerge();
|
const { verge, patchVerge, mutateVerge } = useVerge();
|
||||||
const { installServiceAndRestartCore } = useServiceInstaller();
|
const { installServiceAndRestartCore } = useServiceInstaller();
|
||||||
|
|
||||||
|
const { profiles } = useProfiles();
|
||||||
|
const hasProfiles = useMemo(() => {
|
||||||
|
const items = profiles?.items ?? [];
|
||||||
|
return items.some(p => p.type === 'local' || p.type === 'remote');
|
||||||
|
}, [profiles]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
actualState: systemProxyActualState,
|
actualState: systemProxyActualState,
|
||||||
indicator: systemProxyIndicator,
|
indicator: systemProxyIndicator,
|
||||||
@@ -261,7 +268,7 @@ const SettingSystem = ({ onError }: Props) => {
|
|||||||
}}
|
}}
|
||||||
onCatch={onError}
|
onCatch={onError}
|
||||||
>
|
>
|
||||||
<Switch disabled={!isTunAvailable} />
|
<Switch disabled={!isTunAvailable || !hasProfiles} />
|
||||||
</GuardState>
|
</GuardState>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
|
|
||||||
@@ -297,7 +304,7 @@ const SettingSystem = ({ onError }: Props) => {
|
|||||||
}}
|
}}
|
||||||
onCatch={onError}
|
onCatch={onError}
|
||||||
>
|
>
|
||||||
<Switch />
|
<Switch disabled={!hasProfiles} />
|
||||||
</GuardState>
|
</GuardState>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
|
|
||||||
@@ -396,7 +403,7 @@ const SettingSystem = ({ onError }: Props) => {
|
|||||||
label={<LabelWithIcon icon={Fingerprint} text={t("Send HWID")} />}
|
label={<LabelWithIcon icon={Fingerprint} text={t("Send HWID")} />}
|
||||||
>
|
>
|
||||||
<GuardState
|
<GuardState
|
||||||
value={verge?.enable_send_hwid ?? true} // По умолчанию включено
|
value={verge?.enable_send_hwid ?? true}
|
||||||
valueProps="checked"
|
valueProps="checked"
|
||||||
onChangeProps="onCheckedChange"
|
onChangeProps="onCheckedChange"
|
||||||
onFormat={onSwitchFormat}
|
onFormat={onSwitchFormat}
|
||||||
@@ -404,7 +411,7 @@ const SettingSystem = ({ onError }: Props) => {
|
|||||||
onGuard={(e) => patchVerge({ enable_send_hwid: e })}
|
onGuard={(e) => patchVerge({ enable_send_hwid: e })}
|
||||||
onCatch={onError}
|
onCatch={onError}
|
||||||
>
|
>
|
||||||
<Switch />
|
<Switch disabled={true} />
|
||||||
</GuardState>
|
</GuardState>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -180,32 +180,12 @@ const SettingVergeAdvanced = ({ onError }: Props) => {
|
|||||||
extra={<TooltipIcon tooltip={t("LightWeight Mode Info")} />}
|
extra={<TooltipIcon tooltip={t("LightWeight Mode Info")} />}
|
||||||
onClick={() => liteModeRef.current?.open()}
|
onClick={() => liteModeRef.current?.open()}
|
||||||
/>
|
/>
|
||||||
<SettingRow
|
|
||||||
onClick={exitApp}
|
|
||||||
label={<LabelWithIcon icon={LogOut} text={t("Exit")} />}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<SettingRow
|
|
||||||
label={
|
|
||||||
<LabelWithIcon
|
|
||||||
icon={ClipboardList}
|
|
||||||
text={t("Export Diagnostic Info")}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<TooltipIcon
|
|
||||||
tooltip={t("Copy")}
|
|
||||||
icon={<Copy className="h-4 w-4" />}
|
|
||||||
onClick={onExportDiagnosticInfo}
|
|
||||||
/>
|
|
||||||
</SettingRow>
|
|
||||||
|
|
||||||
<SettingRow
|
<SettingRow
|
||||||
label={<LabelWithIcon icon={Info} text={t("Verge Version")} />}
|
label={<LabelWithIcon icon={Info} text={t("Verge Version")} />}
|
||||||
>
|
>
|
||||||
<p className="text-sm font-medium pr-2 font-mono">v{version}</p>
|
<p className="text-sm font-medium pr-2 font-mono">v{version}</p>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
{/* --- КОНЕЦ ИЗМЕНЕНИЙ 2 --- */}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -188,24 +188,22 @@ const SettingVergeBasic = ({ onError }: Props) => {
|
|||||||
|
|
||||||
{OS !== "linux" && (
|
{OS !== "linux" && (
|
||||||
<SettingRow
|
<SettingRow
|
||||||
label={
|
label={
|
||||||
<LabelWithIcon
|
<LabelWithIcon
|
||||||
icon={MousePointerClick}
|
icon={MousePointerClick}
|
||||||
text={t("Tray Click Event")}
|
text={t("Tray Click Event")}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<GuardState
|
<GuardState
|
||||||
value={tray_event ?? "main_window"}
|
value={tray_event ?? "main_window"}
|
||||||
onCatch={onError}
|
onCatch={onError}
|
||||||
onFormat={(v) => v}
|
onFormat={(v) => v}
|
||||||
onChange={(e) => onChangeData({ tray_event: e })}
|
onChange={(e) => onChangeData({ tray_event: e })}
|
||||||
onGuard={(e) => patchVerge({ tray_event: e })}
|
onGuard={(e) => patchVerge({ tray_event: e })}
|
||||||
|
onChangeProps="onValueChange"
|
||||||
>
|
>
|
||||||
<Select
|
<Select>
|
||||||
onValueChange={(value) => onChangeData({ tray_event: value })}
|
|
||||||
value={tray_event}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="w-40 h-8">
|
<SelectTrigger className="w-40 h-8">
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
@@ -227,41 +225,6 @@ const SettingVergeBasic = ({ onError }: Props) => {
|
|||||||
</SettingRow>
|
</SettingRow>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<SettingRow
|
|
||||||
label={<LabelWithIcon icon={Copy} text={t("Copy Env Type")} />}
|
|
||||||
extra={
|
|
||||||
<TooltipIcon
|
|
||||||
tooltip={t("Copy")}
|
|
||||||
icon={<Copy className="h-4 w-4" />}
|
|
||||||
onClick={onCopyClashEnv}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<GuardState
|
|
||||||
value={env_type ?? (OS === "windows" ? "powershell" : "bash")}
|
|
||||||
onCatch={onError}
|
|
||||||
onFormat={(v) => v}
|
|
||||||
onChange={(e) => onChangeData({ env_type: e })}
|
|
||||||
onGuard={(e) => patchVerge({ env_type: e })}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
onValueChange={(value) => onChangeData({ env_type: value })}
|
|
||||||
value={env_type}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="w-36 h-8">
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="bash">Bash</SelectItem>
|
|
||||||
<SelectItem value="fish">Fish</SelectItem>
|
|
||||||
<SelectItem value="nushell">Nushell</SelectItem>
|
|
||||||
<SelectItem value="cmd">CMD</SelectItem>
|
|
||||||
<SelectItem value="powershell">PowerShell</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</GuardState>
|
|
||||||
</SettingRow>
|
|
||||||
|
|
||||||
<SettingRow
|
<SettingRow
|
||||||
label={<LabelWithIcon icon={Home} text={t("Start Page")} />}
|
label={<LabelWithIcon icon={Home} text={t("Start Page")} />}
|
||||||
>
|
>
|
||||||
@@ -290,59 +253,10 @@ const SettingVergeBasic = ({ onError }: Props) => {
|
|||||||
</GuardState>
|
</GuardState>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
|
|
||||||
<SettingRow
|
{/*<SettingRow*/}
|
||||||
label={
|
{/* onClick={() => themeRef.current?.open()}*/}
|
||||||
<LabelWithIcon icon={FileTerminal} text={t("Startup Script")} />
|
{/* label={<LabelWithIcon icon={SwatchBook} text={t("Theme Setting")} />}*/}
|
||||||
}
|
{/*/>*/}
|
||||||
>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Input
|
|
||||||
readOnly
|
|
||||||
value={startup_script ?? ""}
|
|
||||||
placeholder={t("Not Set")}
|
|
||||||
className="h-8 flex-1"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
className="h-8"
|
|
||||||
onClick={async () => {
|
|
||||||
const selected = await open({
|
|
||||||
directory: false,
|
|
||||||
multiple: false,
|
|
||||||
filters: [
|
|
||||||
{ name: "Shell Script", extensions: ["sh", "bat", "ps1"] },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
if (selected) {
|
|
||||||
const path = Array.isArray(selected) ? selected[0] : selected;
|
|
||||||
onChangeData({ startup_script: path });
|
|
||||||
patchVerge({ startup_script: path });
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("Browse")}
|
|
||||||
</Button>
|
|
||||||
{startup_script && (
|
|
||||||
<Button
|
|
||||||
variant="destructive"
|
|
||||||
size="sm"
|
|
||||||
className="h-8"
|
|
||||||
onClick={async () => {
|
|
||||||
onChangeData({ startup_script: "" });
|
|
||||||
patchVerge({ startup_script: "" });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("Clear")}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</SettingRow>
|
|
||||||
|
|
||||||
<SettingRow
|
|
||||||
onClick={() => themeRef.current?.open()}
|
|
||||||
label={<LabelWithIcon icon={SwatchBook} text={t("Theme Setting")} />}
|
|
||||||
/>
|
|
||||||
<SettingRow
|
<SettingRow
|
||||||
onClick={() => layoutRef.current?.open()}
|
onClick={() => layoutRef.current?.open()}
|
||||||
label={
|
label={
|
||||||
|
|||||||
93
src/hooks/useZoomControls.ts
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import { useEffect, useState, useCallback } from 'react';
|
||||||
|
import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
|
||||||
|
|
||||||
|
// Константы для управления масштабом
|
||||||
|
const ZOOM_STEP = 0.1;
|
||||||
|
const ZOOM_WHEEL_STEP = 0.05;
|
||||||
|
const MIN_ZOOM = 0.5; // 50%
|
||||||
|
const MAX_ZOOM = 2.0; // 200%
|
||||||
|
|
||||||
|
export const useZoomControls = () => {
|
||||||
|
const [zoomLevel, setZoomLevel] = useState(1.0);
|
||||||
|
const appWindow = WebviewWindow.getCurrent();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const setInitialZoom = async () => {
|
||||||
|
// 1. Получаем и физический размер, и коэффициент масштабирования
|
||||||
|
const size = await appWindow.innerSize();
|
||||||
|
const scaleFactor = await appWindow.scaleFactor();
|
||||||
|
|
||||||
|
// 2. Вычисляем логическую ширину
|
||||||
|
const logicalWidth = size.width / scaleFactor;
|
||||||
|
|
||||||
|
let initialZoom = 1.0;
|
||||||
|
|
||||||
|
console.log(`Physical width: ${size.width}, Scale Factor: ${scaleFactor}, Logical width: ${logicalWidth}`);
|
||||||
|
|
||||||
|
// 3. Используем логическую ширину для принятия решения
|
||||||
|
if (logicalWidth < 1300) {
|
||||||
|
initialZoom = 1.0;
|
||||||
|
} else if (logicalWidth > 2000) {
|
||||||
|
initialZoom = 2.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
await appWindow.setZoom(initialZoom);
|
||||||
|
setZoomLevel(initialZoom);
|
||||||
|
};
|
||||||
|
|
||||||
|
setInitialZoom();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleZoom = useCallback((delta: number, isReset = false) => {
|
||||||
|
setZoomLevel(currentZoom => {
|
||||||
|
const newZoom = isReset ? 1.0 : currentZoom + delta;
|
||||||
|
const clampedZoom = Math.max(MIN_ZOOM, Math.min(newZoom, MAX_ZOOM));
|
||||||
|
const roundedZoom = Math.round(clampedZoom * 100) / 100;
|
||||||
|
|
||||||
|
appWindow.setZoom(roundedZoom);
|
||||||
|
const newStrokeWidth = 2 / roundedZoom;
|
||||||
|
document.documentElement.style.setProperty('--icon-stroke-width', newStrokeWidth.toString());
|
||||||
|
return roundedZoom;
|
||||||
|
});
|
||||||
|
}, [appWindow]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleWheel = (event: WheelEvent) => {
|
||||||
|
if (event.ctrlKey || event.metaKey) {
|
||||||
|
event.preventDefault();
|
||||||
|
const delta = event.deltaY > 0 ? -ZOOM_WHEEL_STEP : ZOOM_WHEEL_STEP;
|
||||||
|
handleZoom(delta);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
|
if (event.ctrlKey || event.metaKey) {
|
||||||
|
switch (event.code) {
|
||||||
|
case 'Equal':
|
||||||
|
case 'NumpadAdd':
|
||||||
|
event.preventDefault();
|
||||||
|
handleZoom(ZOOM_STEP);
|
||||||
|
break;
|
||||||
|
case 'Minus':
|
||||||
|
case 'NumpadSubtract':
|
||||||
|
event.preventDefault();
|
||||||
|
handleZoom(-ZOOM_STEP);
|
||||||
|
break;
|
||||||
|
case 'Digit0':
|
||||||
|
case 'Numpad0':
|
||||||
|
event.preventDefault();
|
||||||
|
handleZoom(0, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('wheel', handleWheel, { passive: false });
|
||||||
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('wheel', handleWheel);
|
||||||
|
window.removeEventListener('keydown', handleKeyDown);
|
||||||
|
};
|
||||||
|
}, [handleZoom]);
|
||||||
|
};
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
@import "tw-animate-css";
|
@import "tw-animate-css";
|
||||||
|
|
||||||
@variant dark .dark &;
|
@theme {
|
||||||
|
--tailwind-darkMode: 'class';
|
||||||
|
}
|
||||||
|
|
||||||
@theme inline {
|
@theme inline {
|
||||||
--radius-sm: calc(var(--radius) - 4px);
|
--radius-sm: calc(var(--radius) - 4px);
|
||||||
@@ -125,3 +127,25 @@
|
|||||||
/* h-full уже применен выше к body */
|
/* h-full уже применен выше к body */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
stroke-width: var(--icon-stroke-width, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@keyframes gradient-wave {
|
||||||
|
0% {
|
||||||
|
background-position: -200% center;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 200% center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-gradient-wave {
|
||||||
|
background-size: 200% auto;
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
color: transparent;
|
||||||
|
animation: gradient-wave 2s linear infinite;
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
type="image/x-icon"
|
type="image/x-icon"
|
||||||
/>
|
/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Clash Verge Rev Lite</title>
|
<title>Koala Clash</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -511,7 +511,7 @@
|
|||||||
"Validate Merge File": "Validate Merge File",
|
"Validate Merge File": "Validate Merge File",
|
||||||
"Validation Success": "Validation Success",
|
"Validation Success": "Validation Success",
|
||||||
"Validation Failed": "Validation Failed",
|
"Validation Failed": "Validation Failed",
|
||||||
"Service Administrator Prompt": "Clash Verge requires administrator privileges to reinstall the system service",
|
"Service Administrator Prompt": "Koala Clash requires administrator privileges to reinstall the system service",
|
||||||
"DNS Settings": "DNS Settings",
|
"DNS Settings": "DNS Settings",
|
||||||
"DNS settings saved": "DNS settings saved",
|
"DNS settings saved": "DNS settings saved",
|
||||||
"DNS Overwrite": "DNS Overwrite",
|
"DNS Overwrite": "DNS Overwrite",
|
||||||
@@ -645,6 +645,10 @@
|
|||||||
"Attention Required": "Attention Required",
|
"Attention Required": "Attention Required",
|
||||||
"Menu": "Menu",
|
"Menu": "Menu",
|
||||||
"Add Profile": "Add Profile",
|
"Add Profile": "Add Profile",
|
||||||
|
"Proxy enabled": "Proxy enabled",
|
||||||
|
"Proxy disabled": "Proxy disabled",
|
||||||
|
"Connecting...": "Connecting...",
|
||||||
|
"Disconnecting...": "Disconnecting...",
|
||||||
"Delete Profile": "Delete Profile {{name}}?",
|
"Delete Profile": "Delete Profile {{name}}?",
|
||||||
"This action cannot be undone.": "This action cannot be undone.",
|
"This action cannot be undone.": "This action cannot be undone.",
|
||||||
"Check Group Latency": "Check Group Latency",
|
"Check Group Latency": "Check Group Latency",
|
||||||
@@ -665,5 +669,14 @@
|
|||||||
"Send HWID": "Send HWID",
|
"Send HWID": "Send HWID",
|
||||||
"New Version is available": "New Version is available",
|
"New Version is available": "New Version is available",
|
||||||
"New Version": "New Version",
|
"New Version": "New Version",
|
||||||
"New update": "New update"
|
"New update": "New update",
|
||||||
|
"Device Limit Reached": "Device Limit Reached",
|
||||||
|
"Update Profile": "Update Profile",
|
||||||
|
"Template": "Template",
|
||||||
|
"Select a template...": "Select a template...",
|
||||||
|
"Default Template": "Ru-bundle template",
|
||||||
|
"Template without RU Rules": "Without-ru template",
|
||||||
|
"Stopping Core...": "Stopping Core...",
|
||||||
|
"Uninstalling Service...": "Uninstalling Service...",
|
||||||
|
"Try running core as Sidecar...": "Try running core as Sidecar..."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,10 +27,11 @@
|
|||||||
"Proxies": "Прокси",
|
"Proxies": "Прокси",
|
||||||
"Proxy Groups": "Группы прокси",
|
"Proxy Groups": "Группы прокси",
|
||||||
"Proxy Provider": "Провайдер прокси",
|
"Proxy Provider": "Провайдер прокси",
|
||||||
|
"Proxy Count": "Число прокси",
|
||||||
"Update All": "Обновить все",
|
"Update All": "Обновить все",
|
||||||
"Update At": "Обновлено в",
|
"Update At": "Обновлено в",
|
||||||
"rule": "правила",
|
"rule": "По правилам",
|
||||||
"global": "глобальный",
|
"global": "Глобально",
|
||||||
"direct": "прямой",
|
"direct": "прямой",
|
||||||
"script": "скриптовый",
|
"script": "скриптовый",
|
||||||
"locate": "Местоположение",
|
"locate": "Местоположение",
|
||||||
@@ -156,6 +157,7 @@
|
|||||||
"Edit File": "Изменить файл",
|
"Edit File": "Изменить файл",
|
||||||
"Open File": "Открыть файл",
|
"Open File": "Открыть файл",
|
||||||
"Update": "Обновить",
|
"Update": "Обновить",
|
||||||
|
"Update via proxy": "Обновить через прокси",
|
||||||
"Update(Proxy)": "Обновить (прокси)",
|
"Update(Proxy)": "Обновить (прокси)",
|
||||||
"Confirm deletion": "Подтвердите удаление",
|
"Confirm deletion": "Подтвердите удаление",
|
||||||
"This operation is not reversible": "Эта операция необратима",
|
"This operation is not reversible": "Эта операция необратима",
|
||||||
@@ -200,15 +202,19 @@
|
|||||||
"Settings": "Настройки",
|
"Settings": "Настройки",
|
||||||
"System Setting": "Настройки системы",
|
"System Setting": "Настройки системы",
|
||||||
"Tun Mode": "Режим TUN",
|
"Tun Mode": "Режим TUN",
|
||||||
"TUN requires Service Mode": "Режим TUN требует установленную службу Clash Verge",
|
"TUN requires Service Mode": "Режим TUN требует установленную службу Koala Clash",
|
||||||
"Install Service": "Установить службу",
|
"Install Service": "Установить службу",
|
||||||
|
"Install Service failed": "Установка сервиса не удалась",
|
||||||
|
"Uninstall Service": "Удалить сервис",
|
||||||
|
"Restart Core failed": "Перезапуск ядра не удалась",
|
||||||
"Reset to Default": "Сбросить настройки",
|
"Reset to Default": "Сбросить настройки",
|
||||||
"Tun Mode Info": "Режим Tun: захватывает весь системный трафик, при включении нет необходимости включать системный прокси-сервер.",
|
"Tun Mode Info": "Режим Tun: захватывает весь системный трафик, при включении нет необходимости включать системный прокси-сервер.",
|
||||||
|
"TUN requires Service Mode or Admin Mode": "TUN режим требует Режима Службы или прав Администратора",
|
||||||
"System Proxy Enabled": "Системный прокси включен, ваши приложения будут получать доступ к сети через него",
|
"System Proxy Enabled": "Системный прокси включен, ваши приложения будут получать доступ к сети через него",
|
||||||
"System Proxy Disabled": "Системный прокси отключен, большинству пользователей рекомендуется включить эту опцию",
|
"System Proxy Disabled": "Системный прокси отключен, большинству пользователей рекомендуется включить эту опцию",
|
||||||
"TUN Mode Enabled": "Режим TUN включен, приложения будут получать доступ к сети через виртуальную сетевую карту",
|
"TUN Mode Enabled": "Режим TUN включен, приложения будут получать доступ к сети через виртуальную сетевую карту",
|
||||||
"TUN Mode Disabled": "Режим TUN отключен",
|
"TUN Mode Disabled": "Режим TUN отключен",
|
||||||
"TUN Mode Service Required": "Режим TUN требует установленную службу Clash Verge",
|
"TUN Mode Service Required": "Режим TUN требует установленную службу Koala Clash",
|
||||||
"TUN Mode Intercept Info": "Режим TUN может перехватить трафик всех приложений, подходит для приложений, которые не работают в режиме системного прокси.",
|
"TUN Mode Intercept Info": "Режим TUN может перехватить трафик всех приложений, подходит для приложений, которые не работают в режиме системного прокси.",
|
||||||
"Rule Mode Description": "Направляет трафик в соответствии с предустановленными правилами",
|
"Rule Mode Description": "Направляет трафик в соответствии с предустановленными правилами",
|
||||||
"Global Mode Description": "Направляет весь трафик через прокси-серверы",
|
"Global Mode Description": "Направляет весь трафик через прокси-серверы",
|
||||||
@@ -255,8 +261,11 @@
|
|||||||
"PAC Script Content": "Содержание сценария PAC",
|
"PAC Script Content": "Содержание сценария PAC",
|
||||||
"PAC URL": "Адрес PAC: ",
|
"PAC URL": "Адрес PAC: ",
|
||||||
"Auto Launch": "Автозапуск",
|
"Auto Launch": "Автозапуск",
|
||||||
|
"Administrator mode may not support auto launch": "Режим администратора может не поддерживать автоматический запуск",
|
||||||
"Silent Start": "Тихий запуск",
|
"Silent Start": "Тихий запуск",
|
||||||
"Silent Start Info": "Запускать программу в фоновом режиме без отображения панели",
|
"Silent Start Info": "Запускать программу в фоновом режиме без отображения панели",
|
||||||
|
"Hover Jump Navigator": "Hover Jump Navigator",
|
||||||
|
"Hover Jump Navigator Info": "Автоматически переходить к соответствующей группе прокси при наведении курсора на буквы алфавита",
|
||||||
"TG Channel": "Telegram-канал",
|
"TG Channel": "Telegram-канал",
|
||||||
"Manual": "Документация",
|
"Manual": "Документация",
|
||||||
"Github Repo": "GitHub репозиторий",
|
"Github Repo": "GitHub репозиторий",
|
||||||
@@ -321,7 +330,7 @@
|
|||||||
"Success Color": "Цвет успеха",
|
"Success Color": "Цвет успеха",
|
||||||
"Font Family": "Семейство шрифтов",
|
"Font Family": "Семейство шрифтов",
|
||||||
"CSS Injection": "Внедрение CSS",
|
"CSS Injection": "Внедрение CSS",
|
||||||
"Layout Setting": "Настройки раскладки",
|
"Layout Setting": "Настройки макета",
|
||||||
"Traffic Graph": "График трафика",
|
"Traffic Graph": "График трафика",
|
||||||
"Memory Usage": "Использование памяти",
|
"Memory Usage": "Использование памяти",
|
||||||
"Memory Cleanup": "Нажмите, чтобы очистить память",
|
"Memory Cleanup": "Нажмите, чтобы очистить память",
|
||||||
@@ -372,7 +381,7 @@
|
|||||||
"Export Diagnostic Info": "Экспорт диагностической информации",
|
"Export Diagnostic Info": "Экспорт диагностической информации",
|
||||||
"Export Diagnostic Info For Issue Reporting": "Экспорт диагностической информации для отчета об ошибке",
|
"Export Diagnostic Info For Issue Reporting": "Экспорт диагностической информации для отчета об ошибке",
|
||||||
"Exit": "Выход",
|
"Exit": "Выход",
|
||||||
"Verge Version": "Версия Clash Verge Rev",
|
"Verge Version": "Версия Koala Clash",
|
||||||
"ReadOnly": "Только для чтения",
|
"ReadOnly": "Только для чтения",
|
||||||
"ReadOnlyMessage": "Невозможно редактировать в режиме только для чтения",
|
"ReadOnlyMessage": "Невозможно редактировать в режиме только для чтения",
|
||||||
"Filter": "Фильтр",
|
"Filter": "Фильтр",
|
||||||
@@ -383,6 +392,7 @@
|
|||||||
"Profile Imported Successfully": "Профиль успешно импортирован",
|
"Profile Imported Successfully": "Профиль успешно импортирован",
|
||||||
"Profile Switched": "Профиль изменен",
|
"Profile Switched": "Профиль изменен",
|
||||||
"Profile Reactivated": "Профиль перезапущен",
|
"Profile Reactivated": "Профиль перезапущен",
|
||||||
|
"Profile switch interrupted by new selection": "Переключение профилей прервано новым выбором",
|
||||||
"Only YAML Files Supported": "Поддерживаются только файлы YAML",
|
"Only YAML Files Supported": "Поддерживаются только файлы YAML",
|
||||||
"Settings Applied": "Настройки применены",
|
"Settings Applied": "Настройки применены",
|
||||||
"Installing Service...": "Установка службы...",
|
"Installing Service...": "Установка службы...",
|
||||||
@@ -390,6 +400,17 @@
|
|||||||
"Service Uninstalled Successfully": "Служба успешно удалена",
|
"Service Uninstalled Successfully": "Служба успешно удалена",
|
||||||
"Proxy Daemon Duration Cannot be Less than 1 Second": "Продолжительность работы прокси-демона не может быть меньше 1 секунды",
|
"Proxy Daemon Duration Cannot be Less than 1 Second": "Продолжительность работы прокси-демона не может быть меньше 1 секунды",
|
||||||
"Invalid Bypass Format": "Неверный формат обхода",
|
"Invalid Bypass Format": "Неверный формат обхода",
|
||||||
|
"Waiting for service to be ready...": "Ожидание готовности сервиса...",
|
||||||
|
"Service not ready, retrying attempt {count}/{total}...": "Служба не готова, повторная попытка {{count}}/{{total}}...",
|
||||||
|
"Failed to check service status, retrying attempt {count}/{total}...": "Не удалось проверить состояние службы, повторная попытка {{count}}/{{total}}...",
|
||||||
|
"Service did not become ready after attempts. Proceeding with core restart.": "Служба не была готова после нескольких попыток. Продолжаем перезапуск ядра.",
|
||||||
|
"Restarting Core...": "Перезапуск ядра...",
|
||||||
|
"Service was ready, but core restart might have issues or service became unavailable. Please check.": "Служба была готова, но при перезапуске ядра могли возникнуть проблемы или служба стала недоступна. Пожалуйста, проверьте.",
|
||||||
|
"Service installation or core restart encountered issues. Service might not be available. Please check system logs.": "При установке службы или перезапуске ядра возникли проблемы. Служба может быть недоступна. Проверьте системные журналы.",
|
||||||
|
"Attempting to restart core as a fallback...": "Попытка перезапустить ядро в резервном режиме...",
|
||||||
|
"Fallback core restart also failed: {message}": "Перезапуск резервного ядра также не удался: {{message}}",
|
||||||
|
"Service is ready and core restarted": "Служба готова, ядро перезапущено",
|
||||||
|
"Core restarted. Service is now available.": "Ядро перезапущено. Сервис теперь доступен.",
|
||||||
"Clash Port Modified": "Порт Clash изменен",
|
"Clash Port Modified": "Порт Clash изменен",
|
||||||
"Port Conflict": "Конфликт портов",
|
"Port Conflict": "Конфликт портов",
|
||||||
"Restart Application to Apply Modifications": "Чтобы изменения вступили в силу, необходимо перезапустить приложение",
|
"Restart Application to Apply Modifications": "Чтобы изменения вступили в силу, необходимо перезапустить приложение",
|
||||||
@@ -399,6 +420,7 @@
|
|||||||
"Clash Core Restarted": "Ядро перезапущено",
|
"Clash Core Restarted": "Ядро перезапущено",
|
||||||
"GeoData Updated": "Файлы GeoData обновлены",
|
"GeoData Updated": "Файлы GeoData обновлены",
|
||||||
"Currently on the Latest Version": "Обновление не требуется",
|
"Currently on the Latest Version": "Обновление не требуется",
|
||||||
|
"Already Using Latest Core": "Уже используется последняя версия ядра",
|
||||||
"Import Subscription Successful": "Подписка успешно импортирована",
|
"Import Subscription Successful": "Подписка успешно импортирована",
|
||||||
"WebDAV Server URL": "URL-адрес сервера WebDAV http(s)://",
|
"WebDAV Server URL": "URL-адрес сервера WebDAV http(s)://",
|
||||||
"Username": "Имя пользователя",
|
"Username": "Имя пользователя",
|
||||||
@@ -489,8 +511,9 @@
|
|||||||
"Validate Merge File": "Проверить Merge File",
|
"Validate Merge File": "Проверить Merge File",
|
||||||
"Validation Success": "Файл успешно проверен",
|
"Validation Success": "Файл успешно проверен",
|
||||||
"Validation Failed": "Проверка не удалась",
|
"Validation Failed": "Проверка не удалась",
|
||||||
"Service Administrator Prompt": "Clash Verge требует прав администратора для переустановки системной службы",
|
"Service Administrator Prompt": "Koala Clash требует прав администратора для переустановки системной службы",
|
||||||
"DNS Settings": "Настройки DNS",
|
"DNS Settings": "Настройки DNS",
|
||||||
|
"DNS settings saved": "Настройки DNS сохранены",
|
||||||
"DNS Overwrite": "Переопределение настроек DNS",
|
"DNS Overwrite": "Переопределение настроек DNS",
|
||||||
"DNS Settings Warning": "Если вы не знакомы с этими настройками, пожалуйста, не изменяйте и не отключайте их",
|
"DNS Settings Warning": "Если вы не знакомы с этими настройками, пожалуйста, не изменяйте и не отключайте их",
|
||||||
"Enable DNS": "Включить DNS",
|
"Enable DNS": "Включить DNS",
|
||||||
@@ -498,6 +521,7 @@
|
|||||||
"Enhanced Mode": "Enhanced Mode",
|
"Enhanced Mode": "Enhanced Mode",
|
||||||
"Fake IP Range": "Диапазон FakeIP",
|
"Fake IP Range": "Диапазон FakeIP",
|
||||||
"Fake IP Filter Mode": "FakeIP Filter Mode",
|
"Fake IP Filter Mode": "FakeIP Filter Mode",
|
||||||
|
"Enable IPv6 DNS resolution": "Включить разрешение DNS по IPv6",
|
||||||
"Prefer H3": "Предпочитать H3",
|
"Prefer H3": "Предпочитать H3",
|
||||||
"DNS DOH使用HTTP/3": "DNS DOH использует http/3",
|
"DNS DOH使用HTTP/3": "DNS DOH использует http/3",
|
||||||
"Respect Rules": "Приоритизировать правила",
|
"Respect Rules": "Приоритизировать правила",
|
||||||
@@ -530,6 +554,9 @@
|
|||||||
"IP CIDRs not using fallback servers": "Диапазоны IP-адресов, не использующие резервные серверы, разделенные запятой",
|
"IP CIDRs not using fallback servers": "Диапазоны IP-адресов, не использующие резервные серверы, разделенные запятой",
|
||||||
"Fallback Domain": "Fallback домены",
|
"Fallback Domain": "Fallback домены",
|
||||||
"Domains using fallback servers": "Домены, использующие резервные серверы, разделенные запятой",
|
"Domains using fallback servers": "Домены, использующие резервные серверы, разделенные запятой",
|
||||||
|
"Hosts Settings": "Настройки хостов",
|
||||||
|
"Hosts": "Хосты",
|
||||||
|
"Custom domain to IP or domain mapping": "Настраиваемое сопоставление домена с IP-адресом или доменом",
|
||||||
"Enable Alpha Channel": "Включить альфа-канал",
|
"Enable Alpha Channel": "Включить альфа-канал",
|
||||||
"Alpha versions may contain experimental features and bugs": "Альфа-версии могут содержать экспериментальные функции и ошибки",
|
"Alpha versions may contain experimental features and bugs": "Альфа-версии могут содержать экспериментальные функции и ошибки",
|
||||||
"Home Settings": "Настройки главной страницы",
|
"Home Settings": "Настройки главной страницы",
|
||||||
@@ -553,9 +580,24 @@
|
|||||||
"OS Info": "Версия ОС",
|
"OS Info": "Версия ОС",
|
||||||
"Running Mode": "Режим работы",
|
"Running Mode": "Режим работы",
|
||||||
"Sidecar Mode": "Пользовательский режим",
|
"Sidecar Mode": "Пользовательский режим",
|
||||||
|
"Administrator Mode": "Режим администратора",
|
||||||
|
"Administrator + Service Mode": "Административный + сервисный режим",
|
||||||
"Last Check Update": "Последняя проверка обновлений",
|
"Last Check Update": "Последняя проверка обновлений",
|
||||||
"Click to import subscription": "Нажмите, чтобы импортировать подписку",
|
"Click to import subscription": "Нажмите, чтобы импортировать подписку",
|
||||||
|
"Last Update failed": "Последнее обновление не удалось",
|
||||||
|
"Next Up": "Далее",
|
||||||
|
"No schedule": "Нет расписания",
|
||||||
|
"Unknown": "Неизвестно",
|
||||||
|
"Auto update disabled": "Автоматическое обновление отключено",
|
||||||
"Update subscription successfully": "Подписка успешно обновлена",
|
"Update subscription successfully": "Подписка успешно обновлена",
|
||||||
|
"Update failed, retrying with Clash proxy...": "Обновление не удалось, пробую повторно с помощью прокси Clash...",
|
||||||
|
"Update with Clash proxy successfully": "Обновление с помощью прокси Clash прошло успешно",
|
||||||
|
"Update failed even with Clash proxy": "Обновление не удалось даже с помощью прокси Clash",
|
||||||
|
"Profile creation failed, retrying with Clash proxy...": "Создание профиля не удалось, повторная попытка с прокси Clash...",
|
||||||
|
"Profile creation succeeded with Clash proxy": "Создание профиля с помощью прокси Clash прошло успешно",
|
||||||
|
"Import failed, retrying with Clash proxy...": "Импорт не удался, повторная попытка с прокси Clash...",
|
||||||
|
"Profile Imported with Clash proxy": "Профиль импортирован с помощью прокси Clash",
|
||||||
|
"Import failed even with Clash proxy": "Импорт не удался даже с прокси Clash",
|
||||||
"Current Node": "Текущий сервер",
|
"Current Node": "Текущий сервер",
|
||||||
"No active proxy node": "Нет активного прокси-узла",
|
"No active proxy node": "Нет активного прокси-узла",
|
||||||
"Network Settings": "Настройки сети",
|
"Network Settings": "Настройки сети",
|
||||||
@@ -582,27 +624,37 @@
|
|||||||
"No (IP Banned By Disney+)": "Нет (IP забанен Disney+)",
|
"No (IP Banned By Disney+)": "Нет (IP забанен Disney+)",
|
||||||
"Unsupported Country/Region": "Страна/регион не поддерживается",
|
"Unsupported Country/Region": "Страна/регион не поддерживается",
|
||||||
"Failed (Network Connection)": "Ошибка подключения",
|
"Failed (Network Connection)": "Ошибка подключения",
|
||||||
|
"DashboardToggledTitle": "Панель управления переключена",
|
||||||
|
"DashboardToggledBody": "Видимость панели инструментов переключена с помощью горячей клавиши",
|
||||||
|
"ClashModeChangedTitle": "Режим Clash изменен",
|
||||||
|
"ClashModeChangedBody": "Переключено в режим {{mode}}",
|
||||||
|
"SystemProxyToggledTitle": "Системный прокси переключен",
|
||||||
|
"SystemProxyToggledBody": "Состояние системного прокси-сервера переключена с помощью горячей клавиши",
|
||||||
|
"TunModeToggledTitle": "Режим TUN переключен",
|
||||||
|
"TunModeToggledBody": "Режим TUN переключен с помощью горячей клавиши",
|
||||||
|
"LightweightModeEnteredTitle": "Легкий режим",
|
||||||
|
"LightweightModeEnteredBody": "Вход в легкий режим с помощью горячей клавиши",
|
||||||
|
"AppQuitTitle": "Выход из приложения",
|
||||||
|
"AppQuitBody": "Приложение закрыто с помощью горячей клавиши",
|
||||||
|
"AppHiddenTitle": "Приложение скрыто",
|
||||||
|
"AppHiddenBody": "Окно приложения скрыто с помощью горячей клавиши",
|
||||||
|
"Invalid Profile URL": "Неверный URL-адрес профиля. Введите URL-адрес, начинающийся с http:// или https://",
|
||||||
|
"Saved Successfully": "Успешно сохранено",
|
||||||
"Connected": "Подключено",
|
"Connected": "Подключено",
|
||||||
"Disconnected": "Отключено",
|
"Disconnected": "Отключено",
|
||||||
"Attention Required": "Требуется внимание",
|
"Attention Required": "Требуется внимание",
|
||||||
"TUN requires Service Mode or Admin Mode": "TUN режим требует Режима Службы или прав Администратора",
|
|
||||||
"Menu": "Меню",
|
"Menu": "Меню",
|
||||||
|
"Add Profile": "Добавить профиль",
|
||||||
"Proxy enabled": "Прокси включено",
|
"Proxy enabled": "Прокси включено",
|
||||||
"Proxy disabled": "Прокси выключено",
|
"Proxy disabled": "Прокси выключено",
|
||||||
"Connecting...": "Подключение...",
|
"Connecting...": "Подключение...",
|
||||||
"Disconnecting...": "Отключение...",
|
"Disconnecting...": "Отключение...",
|
||||||
"Add Profile": "Добавить профиль",
|
|
||||||
"Delete Profile": "Удалить профиль {{name}}?",
|
"Delete Profile": "Удалить профиль {{name}}?",
|
||||||
"This action cannot be undone.": "Это действие не может быть отменено",
|
"This action cannot be undone.": "Это действие не может быть отменено",
|
||||||
"Update via proxy": "Обновить через прокси",
|
|
||||||
"Check Group Latency": "Проверка задержки в группе",
|
"Check Group Latency": "Проверка задержки в группе",
|
||||||
"Locate Current Proxy": "Найти текущий прокси",
|
"Locate Current Proxy": "Найти текущий прокси",
|
||||||
"Show Basic Info": "Показать основную информацию",
|
"Show Basic Info": "Показать основную информацию",
|
||||||
"Show Detailed Info": "Показать подробную информацию",
|
"Show Detailed Info": "Показать подробную информацию",
|
||||||
"Update failed, retrying with Clash proxy...": "Обновление не удалось, пробую повторно с помощью прокси Clash...",
|
|
||||||
"Update failed even with Clash proxy": "Обновление не удалось даже с помощью прокси Clash",
|
|
||||||
"Update with Clash proxy successfully": "Обновление с помощью прокси Clash прошло успешно",
|
|
||||||
"Proxy Count": "Число прокси",
|
|
||||||
"Set Latency Test URL": "Установить URL-адрес тестирования задержки",
|
"Set Latency Test URL": "Установить URL-адрес тестирования задержки",
|
||||||
"Filter by Name": "Фильтр по имени",
|
"Filter by Name": "Фильтр по имени",
|
||||||
"Expires in": "Истекает через {{duration}}",
|
"Expires in": "Истекает через {{duration}}",
|
||||||
@@ -617,5 +669,14 @@
|
|||||||
"Send HWID": "Отправлять HWID",
|
"Send HWID": "Отправлять HWID",
|
||||||
"New Version is available": "Доступна новая версия",
|
"New Version is available": "Доступна новая версия",
|
||||||
"New Version": "Новая версия",
|
"New Version": "Новая версия",
|
||||||
"New update": "Доступно обновление"
|
"New update": "Доступно обновление",
|
||||||
|
"Device Limit Reached": "Достигнут лимит устройств",
|
||||||
|
"Update Profile": "Обновить профиль",
|
||||||
|
"Template": "Шаблон",
|
||||||
|
"Select a template...": "Выберите шаблон...",
|
||||||
|
"Default Template": "Шаблон ru-bundle",
|
||||||
|
"Template without RU Rules": "Шаблон without-ru",
|
||||||
|
"Stopping Core...": "Остановка ядра...",
|
||||||
|
"Uninstalling Service...": "Удаление сервиса...",
|
||||||
|
"Try running core as Sidecar...": "Попытка запустить ядро как Sidecar..."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,9 +21,12 @@ import { useClashInfo } from "@/hooks/use-clash";
|
|||||||
import { initGlobalLogService } from "@/services/global-log-service";
|
import { initGlobalLogService } from "@/services/global-log-service";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { showNotice } from "@/services/noticeService";
|
import { showNotice } from "@/services/noticeService";
|
||||||
import { NoticeManager } from "@/components/base/NoticeManager";
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
import { SidebarProvider, useSidebar } from "@/components/ui/sidebar";
|
import { SidebarProvider, useSidebar } from "@/components/ui/sidebar";
|
||||||
import { AppSidebar } from "@/components/layout/sidebar";
|
import { AppSidebar } from "@/components/layout/sidebar";
|
||||||
|
import { useZoomControls } from "@/hooks/useZoomControls";
|
||||||
|
import { HwidErrorDialog } from "@/components/profile/hwid-error-dialog";
|
||||||
|
|
||||||
|
|
||||||
const appWindow = getCurrentWebviewWindow();
|
const appWindow = getCurrentWebviewWindow();
|
||||||
export let portableFlag = false;
|
export let portableFlag = false;
|
||||||
@@ -32,25 +35,30 @@ dayjs.extend(relativeTime);
|
|||||||
|
|
||||||
const OS = getSystem();
|
const OS = getSystem();
|
||||||
|
|
||||||
// 通知处理函数
|
// Notification Handler
|
||||||
const handleNoticeMessage = (
|
const handleNoticeMessage = (
|
||||||
status: string,
|
status: string,
|
||||||
msg: string,
|
msg: string,
|
||||||
t: (key: string) => string,
|
t: (key: string) => string,
|
||||||
navigate: (path: string, options?: any) => void,
|
navigate: (path: string, options?: any) => void,
|
||||||
) => {
|
) => {
|
||||||
console.log("[通知监听 V2] 收到消息:", status, msg);
|
console.log("[Notification Listener V2] Receiving a message:", status, msg);
|
||||||
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "import_sub_url::ok":
|
case "import_sub_url::ok":
|
||||||
mutate("getProfiles");
|
mutate("getProfiles");
|
||||||
navigate("/", { state: { activateProfile: msg } });
|
navigate("/");
|
||||||
showNotice("success", t("Import Subscription Successful"));
|
showNotice("success", t("Import Subscription Successful"));
|
||||||
window.dispatchEvent(new CustomEvent('activate-profile', { detail: msg }));
|
sessionStorage.setItem('activateProfile', msg);
|
||||||
break;
|
break;
|
||||||
case "import_sub_url::error":
|
case "import_sub_url::error":
|
||||||
showNotice("error", msg);
|
console.log(msg);
|
||||||
break;
|
if (msg.toLowerCase().includes('device') || msg.toLowerCase().includes('устройств')) {
|
||||||
|
window.dispatchEvent(new CustomEvent('show-hwid-error', { detail: msg }));
|
||||||
|
} else {
|
||||||
|
showNotice("error", msg);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case "set_config::error":
|
case "set_config::error":
|
||||||
showNotice("error", msg);
|
showNotice("error", msg);
|
||||||
break;
|
break;
|
||||||
@@ -136,13 +144,14 @@ const handleNoticeMessage = (
|
|||||||
showNotice("error", `${t("Failed to Change Core")}: ${msg}`);
|
showNotice("error", `${t("Failed to Change Core")}: ${msg}`);
|
||||||
break;
|
break;
|
||||||
default: // Optional: Log unhandled statuses
|
default: // Optional: Log unhandled statuses
|
||||||
console.warn(`[通知监听 V2] 未处理的状态: ${status}`);
|
console.warn(`[Notification Listener V2] Unprocessed state: ${status}`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const Layout = () => {
|
const Layout = () => {
|
||||||
const mode = useThemeMode();
|
const mode = useThemeMode();
|
||||||
|
useZoomControls();
|
||||||
const isDark = mode === "light" ? false : true;
|
const isDark = mode === "light" ? false : true;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
useCustomTheme();
|
useCustomTheme();
|
||||||
@@ -163,14 +172,14 @@ const Layout = () => {
|
|||||||
try {
|
try {
|
||||||
handleNoticeMessage(status, msg, t, navigate);
|
handleNoticeMessage(status, msg, t, navigate);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[Layout] 处理通知消息失败:", error);
|
console.error("[Layout] Failure to process a notification message:", error);
|
||||||
}
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
},
|
},
|
||||||
[t, navigate],
|
[t, navigate],
|
||||||
);
|
);
|
||||||
|
|
||||||
// 初始化全局日志服务
|
// Initialize the global logging service
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (clashInfo) {
|
if (clashInfo) {
|
||||||
const { server = "", secret = "" } = clashInfo;
|
const { server = "", secret = "" } = clashInfo;
|
||||||
@@ -178,7 +187,7 @@ const Layout = () => {
|
|||||||
}
|
}
|
||||||
}, [clashInfo, enableLog]);
|
}, [clashInfo, enableLog]);
|
||||||
|
|
||||||
// 设置监听器
|
// Setting up a listener
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const listeners = [
|
const listeners = [
|
||||||
addListener("verge://refresh-clash-config", async () => {
|
addListener("verge://refresh-clash-config", async () => {
|
||||||
@@ -224,11 +233,11 @@ const Layout = () => {
|
|||||||
try {
|
try {
|
||||||
unlisten();
|
unlisten();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[Layout] 清理事件监听器失败:", error);
|
console.error("[Layout] Failed to clear event listener:", error);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error("[Layout] 获取unlisten函数失败:", error);
|
console.error("[Layout] Failed to get unlisten function:", error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -238,11 +247,11 @@ const Layout = () => {
|
|||||||
try {
|
try {
|
||||||
cleanup();
|
cleanup();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[Layout] 清理窗口监听器失败:", error);
|
console.error("[Layout] Failed to clear window listener:", error);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error("[Layout] 获取cleanup函数失败:", error);
|
console.error("[Layout] Failed to get cleanup function:", error);
|
||||||
});
|
});
|
||||||
}, 0);
|
}, 0);
|
||||||
};
|
};
|
||||||
@@ -250,10 +259,10 @@ const Layout = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (initRef.current) {
|
if (initRef.current) {
|
||||||
console.log("[Layout] 初始化代码已执行过,跳过");
|
console.log("[Layout] Initialization code has already been executed, skip");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log("[Layout] 开始执行初始化代码");
|
console.log("[Layout] Begin executing initialization code");
|
||||||
initRef.current = true;
|
initRef.current = true;
|
||||||
|
|
||||||
let isInitialized = false;
|
let isInitialized = false;
|
||||||
@@ -263,27 +272,27 @@ const Layout = () => {
|
|||||||
const notifyBackend = async (action: string, stage?: string) => {
|
const notifyBackend = async (action: string, stage?: string) => {
|
||||||
try {
|
try {
|
||||||
if (stage) {
|
if (stage) {
|
||||||
console.log(`[Layout] 通知后端 ${action}: ${stage}`);
|
console.log(`[Layout] Notification Backend ${action}: ${stage}`);
|
||||||
await invoke("update_ui_stage", { stage });
|
await invoke("update_ui_stage", { stage });
|
||||||
} else {
|
} else {
|
||||||
console.log(`[Layout] 通知后端 ${action}`);
|
console.log(`[Layout] Notification Backend ${action}`);
|
||||||
await invoke("notify_ui_ready");
|
await invoke("notify_ui_ready");
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`[Layout] 通知失败 ${action}:`, err);
|
console.error(`[Layout] Notification failure ${action}:`, err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeLoadingOverlay = () => {
|
const removeLoadingOverlay = () => {
|
||||||
const initialOverlay = document.getElementById("initial-loading-overlay");
|
const initialOverlay = document.getElementById("initial-loading-overlay");
|
||||||
if (initialOverlay) {
|
if (initialOverlay) {
|
||||||
console.log("[Layout] 移除加载指示器");
|
console.log("[Layout] Remove loading indicator");
|
||||||
initialOverlay.style.opacity = "0";
|
initialOverlay.style.opacity = "0";
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
try {
|
try {
|
||||||
initialOverlay.remove();
|
initialOverlay.remove();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("[Layout] 加载指示器已被移除");
|
console.log("[Layout] Load indicator has been removed");
|
||||||
}
|
}
|
||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
@@ -291,23 +300,23 @@ const Layout = () => {
|
|||||||
|
|
||||||
const performInitialization = async () => {
|
const performInitialization = async () => {
|
||||||
if (isInitialized) {
|
if (isInitialized) {
|
||||||
console.log("[Layout] 已经初始化过,跳过");
|
console.log("[Layout] Already initialized, skip");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
initializationAttempts++;
|
initializationAttempts++;
|
||||||
console.log(`[Layout] 开始第 ${initializationAttempts} 次初始化尝试`);
|
console.log(`[Layout] Start ${initializationAttempts} for the first time`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
removeLoadingOverlay();
|
removeLoadingOverlay();
|
||||||
|
|
||||||
await notifyBackend("加载阶段", "Loading");
|
await notifyBackend("Loading phase", "Loading");
|
||||||
|
|
||||||
await new Promise<void>((resolve) => {
|
await new Promise<void>((resolve) => {
|
||||||
const checkReactMount = () => {
|
const checkReactMount = () => {
|
||||||
const rootElement = document.getElementById("root");
|
const rootElement = document.getElementById("root");
|
||||||
if (rootElement && rootElement.children.length > 0) {
|
if (rootElement && rootElement.children.length > 0) {
|
||||||
console.log("[Layout] React组件已挂载");
|
console.log("[Layout] React components are mounted");
|
||||||
resolve();
|
resolve();
|
||||||
} else {
|
} else {
|
||||||
setTimeout(checkReactMount, 50);
|
setTimeout(checkReactMount, 50);
|
||||||
@@ -317,43 +326,43 @@ const Layout = () => {
|
|||||||
checkReactMount();
|
checkReactMount();
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
console.log("[Layout] React组件挂载检查超时,继续执行");
|
console.log("[Layout] React components mount check timeout, continue execution");
|
||||||
resolve();
|
resolve();
|
||||||
}, 2000);
|
}, 2000);
|
||||||
});
|
});
|
||||||
|
|
||||||
await notifyBackend("DOM就绪", "DomReady");
|
await notifyBackend("DOM ready", "DomReady");
|
||||||
|
|
||||||
await new Promise<void>((resolve) => {
|
await new Promise<void>((resolve) => {
|
||||||
requestAnimationFrame(() => resolve());
|
requestAnimationFrame(() => resolve());
|
||||||
});
|
});
|
||||||
|
|
||||||
await notifyBackend("资源加载完成", "ResourcesLoaded");
|
await notifyBackend("Resource loading completed", "ResourcesLoaded");
|
||||||
|
|
||||||
await notifyBackend("UI就绪");
|
await notifyBackend("UI ready");
|
||||||
|
|
||||||
isInitialized = true;
|
isInitialized = true;
|
||||||
console.log(`[Layout] 第 ${initializationAttempts} 次初始化完成`);
|
console.log(`[Layout] The ${initializationAttempts} initialization is complete`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
console.error(
|
||||||
`[Layout] 第 ${initializationAttempts} 次初始化失败:`,
|
`[Layout] Initialization failure at ${initializationAttempts}:`,
|
||||||
error,
|
error,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (initializationAttempts < maxAttempts) {
|
if (initializationAttempts < maxAttempts) {
|
||||||
console.log(
|
console.log(
|
||||||
`[Layout] 将在500ms后进行第 ${initializationAttempts + 1} 次重试`,
|
`[Layout] The first ${initializationAttempts + 1} retry will be made after 500ms`,
|
||||||
);
|
);
|
||||||
setTimeout(performInitialization, 500);
|
setTimeout(performInitialization, 500);
|
||||||
} else {
|
} else {
|
||||||
console.error("[Layout] 所有初始化尝试都失败,执行紧急初始化");
|
console.error("[Layout] All initialization attempts fail, perform emergency initialization");
|
||||||
|
|
||||||
removeLoadingOverlay();
|
removeLoadingOverlay();
|
||||||
try {
|
try {
|
||||||
await notifyBackend("UI就绪");
|
await notifyBackend("UI ready");
|
||||||
isInitialized = true;
|
isInitialized = true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("[Layout] 紧急初始化也失败:", e);
|
console.error("[Layout] Emergency initialization also failed:", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -363,39 +372,39 @@ const Layout = () => {
|
|||||||
|
|
||||||
const setupEventListener = async () => {
|
const setupEventListener = async () => {
|
||||||
try {
|
try {
|
||||||
console.log("[Layout] 开始监听启动完成事件");
|
console.log("[Layout] Start listening for startup completion events");
|
||||||
const unlisten = await listen("verge://startup-completed", () => {
|
const unlisten = await listen("verge://startup-completed", () => {
|
||||||
if (!hasEventTriggered) {
|
if (!hasEventTriggered) {
|
||||||
console.log("[Layout] 收到启动完成事件,开始初始化");
|
console.log("[Layout] Receive startup completion event, start initialization");
|
||||||
hasEventTriggered = true;
|
hasEventTriggered = true;
|
||||||
performInitialization();
|
performInitialization();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return unlisten;
|
return unlisten;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("[Layout] 监听启动完成事件失败:", err);
|
console.error("[Layout] Failed to listen for startup completion event:", err);
|
||||||
return () => {};
|
return () => {};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkImmediateInitialization = async () => {
|
const checkImmediateInitialization = async () => {
|
||||||
try {
|
try {
|
||||||
console.log("[Layout] 检查后端是否已就绪");
|
console.log("[Layout] Check if the backend is ready");
|
||||||
await invoke("update_ui_stage", { stage: "Loading" });
|
await invoke("update_ui_stage", { stage: "Loading" });
|
||||||
|
|
||||||
if (!hasEventTriggered && !isInitialized) {
|
if (!hasEventTriggered && !isInitialized) {
|
||||||
console.log("[Layout] 后端已就绪,立即开始初始化");
|
console.log("[Layout] Backend is ready, start initialization immediately");
|
||||||
hasEventTriggered = true;
|
hasEventTriggered = true;
|
||||||
performInitialization();
|
performInitialization();
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[Layout] 后端尚未就绪,等待启动完成事件");
|
console.log("[Layout] Backend not yet ready, waiting for startup completion event");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const backupInitialization = setTimeout(() => {
|
const backupInitialization = setTimeout(() => {
|
||||||
if (!hasEventTriggered && !isInitialized) {
|
if (!hasEventTriggered && !isInitialized) {
|
||||||
console.warn("[Layout] 备用初始化触发:1.5秒内未开始初始化");
|
console.warn("[Layout] Standby initialization trigger: initialization not started within 1.5 seconds");
|
||||||
hasEventTriggered = true;
|
hasEventTriggered = true;
|
||||||
performInitialization();
|
performInitialization();
|
||||||
}
|
}
|
||||||
@@ -403,9 +412,9 @@ const Layout = () => {
|
|||||||
|
|
||||||
const emergencyInitialization = setTimeout(() => {
|
const emergencyInitialization = setTimeout(() => {
|
||||||
if (!isInitialized) {
|
if (!isInitialized) {
|
||||||
console.error("[Layout] 紧急初始化触发:5秒内未完成初始化");
|
console.error("[Layout] Emergency initialization trigger: initialization not completed within 5 seconds");
|
||||||
removeLoadingOverlay();
|
removeLoadingOverlay();
|
||||||
notifyBackend("UI就绪").catch(() => {});
|
notifyBackend("UI ready").catch(() => {});
|
||||||
isInitialized = true;
|
isInitialized = true;
|
||||||
}
|
}
|
||||||
}, 5000);
|
}, 5000);
|
||||||
@@ -421,10 +430,10 @@ const Layout = () => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 语言和起始页设置
|
// Language and start page settings
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (language) {
|
if (language) {
|
||||||
dayjs.locale(language === "zh" ? "zh-cn" : language);
|
dayjs.locale(language === "ru" ? "ru-ru" : language);
|
||||||
i18next.changeLanguage(language);
|
i18next.changeLanguage(language);
|
||||||
}
|
}
|
||||||
}, [language]);
|
}, [language]);
|
||||||
@@ -454,6 +463,7 @@ const Layout = () => {
|
|||||||
{routersEles && React.cloneElement(routersEles, { key: location.pathname })}
|
{routersEles && React.cloneElement(routersEles, { key: location.pathname })}
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
<HwidErrorDialog />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -462,9 +472,9 @@ const Layout = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SWRConfig value={{ errorRetryCount: 3 }}>
|
<SWRConfig value={{ errorRetryCount: 3 }}>
|
||||||
<NoticeManager />
|
|
||||||
<SidebarProvider defaultOpen={false}>
|
<SidebarProvider defaultOpen={false}>
|
||||||
<AppLayout />
|
<AppLayout />
|
||||||
|
<Toaster />
|
||||||
</SidebarProvider>
|
</SidebarProvider>
|
||||||
</SWRConfig>
|
</SWRConfig>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import {
|
|||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
Loader2,
|
Loader2,
|
||||||
Globe,
|
Globe,
|
||||||
Send, ExternalLink, RefreshCw,
|
Send, ExternalLink, RefreshCw, ArrowDown, ArrowUp,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import { useSystemState } from "@/hooks/use-system-state";
|
import { useSystemState } from "@/hooks/use-system-state";
|
||||||
@@ -37,6 +37,11 @@ import { closeAllConnections } from "@/services/api";
|
|||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
import { updateProfile } from "@/services/cmds";
|
import { updateProfile } from "@/services/cmds";
|
||||||
import { SidebarTrigger } from "@/components/ui/sidebar";
|
import { SidebarTrigger } from "@/components/ui/sidebar";
|
||||||
|
import parseTraffic from "@/utils/parse-traffic";
|
||||||
|
import { useAppData } from "@/providers/app-data-provider";
|
||||||
|
import { PowerButton } from "@/components/home/power-button";
|
||||||
|
import { cn } from "@root/lib/utils";
|
||||||
|
import map from "../assets/image/map.svg";
|
||||||
|
|
||||||
const MinimalHomePage: React.FC = () => {
|
const MinimalHomePage: React.FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -46,6 +51,7 @@ const MinimalHomePage: React.FC = () => {
|
|||||||
useProfiles();
|
useProfiles();
|
||||||
const viewerRef = useRef<ProfileViewerRef>(null);
|
const viewerRef = useRef<ProfileViewerRef>(null);
|
||||||
const [uidToActivate, setUidToActivate] = useState<string | null>(null);
|
const [uidToActivate, setUidToActivate] = useState<string | null>(null);
|
||||||
|
const { connections } = useAppData();
|
||||||
|
|
||||||
const profileItems = useMemo(() => {
|
const profileItems = useMemo(() => {
|
||||||
const items =
|
const items =
|
||||||
@@ -57,7 +63,6 @@ const MinimalHomePage: React.FC = () => {
|
|||||||
const currentProfile = useMemo(() => {
|
const currentProfile = useMemo(() => {
|
||||||
return profileItems.find(p => p.uid === profiles?.current);
|
return profileItems.find(p => p.uid === profiles?.current);
|
||||||
}, [profileItems, profiles?.current]);
|
}, [profileItems, profiles?.current]);
|
||||||
console.log("Current profile", currentProfile);
|
|
||||||
const currentProfileName = currentProfile?.name || profiles?.current;
|
const currentProfileName = currentProfile?.name || profiles?.current;
|
||||||
|
|
||||||
const activateProfile = useCallback(
|
const activateProfile = useCallback(
|
||||||
@@ -78,26 +83,13 @@ const MinimalHomePage: React.FC = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleActivationEvent = (event: Event) => {
|
const uidToActivate = sessionStorage.getItem('activateProfile');
|
||||||
const customEvent = event as CustomEvent<string>;
|
|
||||||
const profileId = customEvent.detail;
|
|
||||||
if (profileId) {
|
|
||||||
setUidToActivate(profileId);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener('activate-profile', handleActivationEvent);
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('activate-profile', handleActivationEvent);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (uidToActivate && profileItems.some(p => p.uid === uidToActivate)) {
|
if (uidToActivate && profileItems.some(p => p.uid === uidToActivate)) {
|
||||||
activateProfile(uidToActivate, false);
|
activateProfile(uidToActivate, false);
|
||||||
setUidToActivate(null);
|
sessionStorage.removeItem('activateProfile');
|
||||||
}
|
}
|
||||||
}, [uidToActivate, profileItems, activateProfile]);
|
}, [profileItems, activateProfile]);
|
||||||
|
|
||||||
|
|
||||||
const handleProfileChange = useLockFn(async (uid: string) => {
|
const handleProfileChange = useLockFn(async (uid: string) => {
|
||||||
if (profiles?.current === uid) return;
|
if (profiles?.current === uid) return;
|
||||||
@@ -157,7 +149,7 @@ const MinimalHomePage: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
await updateProfile(currentProfile.uid);
|
await updateProfile(currentProfile.uid);
|
||||||
toast.success(t("Profile Updated Successfully"));
|
toast.success(t("Profile Updated Successfully"));
|
||||||
mutateProfiles(); // Обновляем данные в UI
|
mutateProfiles();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
toast.error(t("Failed to update profile"), { description: err.message });
|
toast.error(t("Failed to update profile"), { description: err.message });
|
||||||
} finally {
|
} finally {
|
||||||
@@ -165,8 +157,48 @@ const MinimalHomePage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const statusInfo = useMemo(() => {
|
||||||
|
if (isToggling) {
|
||||||
|
return {
|
||||||
|
text: isProxyEnabled ? t('Disconnecting...') : t('Connecting...'),
|
||||||
|
color: isProxyEnabled ? '#f59e0b' : '#84cc16',
|
||||||
|
isAnimating: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (isProxyEnabled) {
|
||||||
|
return {
|
||||||
|
text: t('Connected'),
|
||||||
|
color: '#22c55e',
|
||||||
|
isAnimating: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
text: t('Disconnected'),
|
||||||
|
color: '#ef4444',
|
||||||
|
isAnimating: false,
|
||||||
|
};
|
||||||
|
}, [isToggling, isProxyEnabled, t]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-full flex flex-col">
|
<div className="h-full w-full flex flex-col">
|
||||||
|
<div className="absolute inset-0 opacity-20 pointer-events-none z-0 [transform:translateZ(0)]">
|
||||||
|
<img
|
||||||
|
src={map}
|
||||||
|
alt="World map"
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isProxyEnabled && (
|
||||||
|
<div
|
||||||
|
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 h-[500px] w-[500px] rounded-full pointer-events-none z-0 transition-opacity duration-500"
|
||||||
|
style={{
|
||||||
|
background: 'radial-gradient(circle, rgba(34,197,94,0.3) 0%, transparent 70%)',
|
||||||
|
filter: 'blur(100px)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<header className="flex-shrink-0 p-5 grid grid-cols-3 items-center z-10">
|
<header className="flex-shrink-0 p-5 grid grid-cols-3 items-center z-10">
|
||||||
<div className="flex justify-start">
|
<div className="flex justify-start">
|
||||||
<SidebarTrigger />
|
<SidebarTrigger />
|
||||||
@@ -240,38 +272,50 @@ const MinimalHomePage: React.FC = () => {
|
|||||||
href={currentProfile.announce_url}
|
href={currentProfile.announce_url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="inline-flex items-center gap-2 text-base font-semibold text-foreground hover:underline hover:opacity-80 transition-all"
|
className="inline-flex items-center gap-2 text-base font-semibold text-foreground hover:underline hover:opacity-80 transition-all whitespace-pre-wrap"
|
||||||
title={currentProfile.announce_url}
|
title={currentProfile.announce_url.replace(/\\n/g, '\n')}
|
||||||
>
|
>
|
||||||
<span>{currentProfile.announce}</span>
|
<span>{currentProfile.announce.replace(/\\n/g, '\n')}</span>
|
||||||
<ExternalLink className="h-4 w-4 flex-shrink-0" />
|
<ExternalLink className="h-4 w-4 flex-shrink-0" />
|
||||||
</a>
|
</a>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-base font-semibold text-foreground">
|
<p className="text-base font-semibold text-foreground whitespace-pre-wrap">
|
||||||
{currentProfile.announce}
|
{currentProfile.announce}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="text-center">
|
<div className="relative text-center">
|
||||||
<h1
|
<h1
|
||||||
className="text-4xl mb-2 font-semibold"
|
className={cn(
|
||||||
style={{ color: isProxyEnabled ? "#22c55e" : "#ef4444" }}
|
"text-4xl mb-2 font-semibold transition-colors duration-300",
|
||||||
>
|
statusInfo.isAnimating && "animate-pulse"
|
||||||
{isProxyEnabled ? t("Connected") : t("Disconnected")}
|
)}
|
||||||
</h1>
|
style={{ color: statusInfo.color }}
|
||||||
<p className="h-6 text-sm text-muted-foreground transition-opacity duration-300">
|
>
|
||||||
{isToggling &&
|
{statusInfo.text}
|
||||||
(isProxyEnabled ? t("Disconnecting...") : t("Connecting..."))}
|
</h1>
|
||||||
</p>
|
{isProxyEnabled && (
|
||||||
|
<div className="absolute top-full left-1/2 -translate-x-1/2 mt-52 flex justify-center items-center text-sm text-muted-foreground gap-6">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<ArrowDown className="h-4 w-4 text-green-500" />
|
||||||
|
{parseTraffic(connections.downloadTotal)}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<ArrowUp className="h-4 w-4 text-sky-500" />
|
||||||
|
{parseTraffic(connections.uploadTotal)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="scale-[7] my-16">
|
<div className="relative -translate-y-6">
|
||||||
<Switch
|
<PowerButton
|
||||||
disabled={showTunAlert || isToggling}
|
loading={isToggling}
|
||||||
checked={!!isProxyEnabled}
|
checked={!!isProxyEnabled}
|
||||||
onCheckedChange={handleToggleProxy}
|
onClick={handleToggleProxy}
|
||||||
aria-label={t("Toggle Proxy")}
|
disabled={showTunAlert || isToggling || profileItems.length === 0}
|
||||||
|
aria-label={t("Toggle Proxy")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -300,7 +344,7 @@ const MinimalHomePage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="w-full mt-4 flex justify-center">
|
<div className="w-full max-w-sm mt-4 flex justify-center">
|
||||||
{profileItems.length > 0 ? (
|
{profileItems.length > 0 ? (
|
||||||
<ProxySelectors />
|
<ProxySelectors />
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const ProxyPage = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { verge } = useVerge();
|
const { verge } = useVerge();
|
||||||
const modeList = ["rule", "global", "direct"];
|
const modeList = ["rule", "global"];
|
||||||
const curMode = clashConfig?.mode?.toLowerCase();
|
const curMode = clashConfig?.mode?.toLowerCase();
|
||||||
|
|
||||||
const onChangeMode = useLockFn(async (mode: string) => {
|
const onChangeMode = useLockFn(async (mode: string) => {
|
||||||
@@ -58,7 +58,7 @@ const ProxyPage = () => {
|
|||||||
variant={mode === curMode ? "default" : "ghost"}
|
variant={mode === curMode ? "default" : "ghost"}
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => onChangeMode(mode)}
|
onClick={() => onChangeMode(mode)}
|
||||||
className="capitalize px-3 py-1 h-auto"
|
className="px-3 py-1 h-auto"
|
||||||
>
|
>
|
||||||
{t(mode)}
|
{t(mode)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -400,3 +400,7 @@ export const isAdmin = async () => {
|
|||||||
export async function getNextUpdateTime(uid: string) {
|
export async function getNextUpdateTime(uid: string) {
|
||||||
return invoke<number | null>("get_next_update_time", { uid });
|
return invoke<number | null>("get_next_update_time", { uid });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function createProfileFromShareLink(link: string, templateName: string) {
|
||||||
|
return invoke<void>("create_profile_from_share_link", { link, templateName });
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,81 +1,25 @@
|
|||||||
import { ReactNode } from "react";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
export interface NoticeItem {
|
type NoticeType = 'success' | 'error' | 'info' | 'warning';
|
||||||
id: number;
|
|
||||||
type: "success" | "error" | "info";
|
|
||||||
message: ReactNode;
|
|
||||||
duration: number;
|
|
||||||
timerId?: ReturnType<typeof setTimeout>;
|
|
||||||
}
|
|
||||||
|
|
||||||
type Listener = (notices: NoticeItem[]) => void;
|
export const showNotice = (type: NoticeType, message: string, duration?: number) => {
|
||||||
|
const options = duration ? { duration } : {};
|
||||||
|
|
||||||
let nextId = 0;
|
switch (type) {
|
||||||
let notices: NoticeItem[] = [];
|
case 'success':
|
||||||
const listeners: Set<Listener> = new Set();
|
toast.success(message, options);
|
||||||
|
break;
|
||||||
function notifyListeners() {
|
case 'error':
|
||||||
listeners.forEach((listener) => listener([...notices])); // Pass a copy
|
toast.error(message, options);
|
||||||
}
|
break;
|
||||||
|
case 'info':
|
||||||
// Shows a notification.
|
toast.info(message, options);
|
||||||
|
break;
|
||||||
export function showNotice(
|
case 'warning':
|
||||||
type: "success" | "error" | "info",
|
toast.warning(message, options);
|
||||||
message: ReactNode,
|
break;
|
||||||
duration?: number,
|
default:
|
||||||
): number {
|
toast(message, options);
|
||||||
const id = nextId++;
|
break;
|
||||||
const effectiveDuration =
|
|
||||||
duration ?? (type === "error" ? 8000 : type === "info" ? 5000 : 3000); // Longer defaults
|
|
||||||
|
|
||||||
const newNotice: NoticeItem = {
|
|
||||||
id,
|
|
||||||
type,
|
|
||||||
message,
|
|
||||||
duration: effectiveDuration,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Auto-hide timer (only if duration is not null/0)
|
|
||||||
if (effectiveDuration > 0) {
|
|
||||||
newNotice.timerId = setTimeout(() => {
|
|
||||||
hideNotice(id);
|
|
||||||
}, effectiveDuration);
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
notices = [...notices, newNotice];
|
|
||||||
notifyListeners();
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hides a specific notification by its ID.
|
|
||||||
|
|
||||||
export function hideNotice(id: number) {
|
|
||||||
const notice = notices.find((n) => n.id === id);
|
|
||||||
if (notice?.timerId) {
|
|
||||||
clearTimeout(notice.timerId); // Clear timeout if manually closed
|
|
||||||
}
|
|
||||||
notices = notices.filter((n) => n.id !== id);
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subscribes a listener function to notice state changes.
|
|
||||||
|
|
||||||
export function subscribeNotices(listener: () => void) {
|
|
||||||
listeners.add(listener);
|
|
||||||
return () => {
|
|
||||||
listeners.delete(listener);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
export function getSnapshotNotices() {
|
|
||||||
return notices;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to clear all notices at once
|
|
||||||
export function clearAllNotices() {
|
|
||||||
notices.forEach((n) => {
|
|
||||||
if (n.timerId) clearTimeout(n.timerId);
|
|
||||||
});
|
|
||||||
notices = [];
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||