Merge branch 'dev' into refactor/mihomo-api
This commit is contained in:
41
.github/workflows/fmt.yml
vendored
41
.github/workflows/fmt.yml
vendored
@@ -10,28 +10,67 @@ on:
|
||||
jobs:
|
||||
rustfmt:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Check Rust changes
|
||||
id: check_rust
|
||||
uses: dorny/paths-filter@v3
|
||||
with:
|
||||
filters: |
|
||||
rust:
|
||||
- 'src-tauri/**'
|
||||
- '**/*.rs'
|
||||
|
||||
- name: Skip if no Rust changes
|
||||
if: steps.check_rust.outputs.rust != 'true'
|
||||
run: echo "No Rust changes, skipping rustfmt."
|
||||
|
||||
- name: install Rust stable and rustfmt
|
||||
if: steps.check_rust.outputs.rust == 'true'
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt
|
||||
|
||||
- name: run cargo fmt
|
||||
if: steps.check_rust.outputs.rust == 'true'
|
||||
run: cargo fmt --manifest-path ./src-tauri/Cargo.toml --all -- --check
|
||||
|
||||
prettier:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Check Web changes
|
||||
id: check_web
|
||||
uses: dorny/paths-filter@v3
|
||||
with:
|
||||
filters: |
|
||||
web:
|
||||
- 'src/**'
|
||||
- '**/*.js'
|
||||
- '**/*.ts'
|
||||
- '**/*.tsx'
|
||||
- '**/*.css'
|
||||
- '**/*.scss'
|
||||
- '**/*.json'
|
||||
- '**/*.md'
|
||||
- '**/*.json'
|
||||
|
||||
- name: Skip if no Web changes
|
||||
if: steps.check_web.outputs.web != 'true'
|
||||
run: echo "No web changes, skipping prettier."
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
if: steps.check_web.outputs.web == 'true'
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
- run: corepack enable
|
||||
if: steps.check_web.outputs.web == 'true'
|
||||
- run: pnpm install --frozen-lockfile
|
||||
if: steps.check_web.outputs.web == 'true'
|
||||
- run: pnpm format:check
|
||||
if: steps.check_web.outputs.web == 'true'
|
||||
|
||||
# taplo:
|
||||
# name: taplo (.toml files)
|
||||
|
||||
16
.github/workflows/lint-clippy.yml
vendored
16
.github/workflows/lint-clippy.yml
vendored
@@ -19,6 +19,22 @@ jobs:
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Check src-tauri changes
|
||||
id: check_changes
|
||||
uses: dorny/paths-filter@v3
|
||||
with:
|
||||
filters: |
|
||||
rust:
|
||||
- 'src-tauri/**'
|
||||
|
||||
- name: Skip if src-tauri not changed
|
||||
if: steps.check_changes.outputs.rust != 'true'
|
||||
run: echo "No src-tauri changes, skipping clippy lint."
|
||||
|
||||
- name: Continue if src-tauri changed
|
||||
if: steps.check_changes.outputs.rust == 'true'
|
||||
run: echo "src-tauri changed, running clippy lint."
|
||||
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
- 改进 macos 下系统代理设置的方法
|
||||
- 优化 TUN 模式可用性的判断
|
||||
- 移除流媒体检测的系统级提示(使用软件内通知)
|
||||
- 优化后端 i18n 资源占用
|
||||
|
||||
### 🐞 修复问题
|
||||
|
||||
|
||||
20
package.json
20
package.json
@@ -39,7 +39,7 @@
|
||||
"@mui/icons-material": "^7.3.2",
|
||||
"@mui/lab": "7.0.0-beta.17",
|
||||
"@mui/material": "^7.3.2",
|
||||
"@mui/x-data-grid": "^8.11.3",
|
||||
"@mui/x-data-grid": "^8.12.1",
|
||||
"@tauri-apps/api": "2.8.0",
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.3.0",
|
||||
"@tauri-apps/plugin-dialog": "^2.4.0",
|
||||
@@ -64,11 +64,11 @@
|
||||
"react-dom": "19.1.1",
|
||||
"react-error-boundary": "6.0.0",
|
||||
"react-hook-form": "^7.63.0",
|
||||
"react-i18next": "15.7.3",
|
||||
"react-i18next": "16.0.0",
|
||||
"react-markdown": "10.1.0",
|
||||
"react-monaco-editor": "0.59.0",
|
||||
"react-router-dom": "7.9.1",
|
||||
"react-virtuoso": "^4.14.0",
|
||||
"react-router-dom": "7.9.3",
|
||||
"react-virtuoso": "^4.14.1",
|
||||
"swr": "^2.3.6",
|
||||
"types-pac": "^1.0.3",
|
||||
"zustand": "^5.0.8",
|
||||
@@ -76,15 +76,15 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/github": "^6.0.1",
|
||||
"@eslint-react/eslint-plugin": "^1.53.1",
|
||||
"@eslint-react/eslint-plugin": "^2.0.1",
|
||||
"@eslint/js": "^9.36.0",
|
||||
"@tauri-apps/cli": "2.8.4",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/react": "19.1.13",
|
||||
"@types/react": "19.1.15",
|
||||
"@types/react-dom": "19.1.9",
|
||||
"@vitejs/plugin-legacy": "^7.2.1",
|
||||
"@vitejs/plugin-react": "5.0.3",
|
||||
"@vitejs/plugin-react": "5.0.4",
|
||||
"adm-zip": "^0.5.16",
|
||||
"cli-color": "^2.0.4",
|
||||
"commander": "^14.0.1",
|
||||
@@ -95,7 +95,7 @@
|
||||
"eslint-plugin-import-x": "^4.16.1",
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.21",
|
||||
"eslint-plugin-react-refresh": "^0.4.22",
|
||||
"eslint-plugin-unused-imports": "^4.2.0",
|
||||
"glob": "^11.0.3",
|
||||
"globals": "^16.4.0",
|
||||
@@ -104,8 +104,8 @@
|
||||
"meta-json-schema": "^1.19.13",
|
||||
"node-fetch": "^3.3.2",
|
||||
"prettier": "^3.6.2",
|
||||
"sass": "^1.93.1",
|
||||
"tar": "^7.4.4",
|
||||
"sass": "^1.93.2",
|
||||
"tar": "^7.5.1",
|
||||
"terser": "^5.44.0",
|
||||
"typescript": "^5.9.2",
|
||||
"typescript-eslint": "^8.44.1",
|
||||
|
||||
561
pnpm-lock.yaml
generated
561
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
38
src-tauri/Cargo.lock
generated
38
src-tauri/Cargo.lock
generated
@@ -2230,9 +2230,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "flexi_logger"
|
||||
version = "0.31.3"
|
||||
version = "0.31.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce93582299e89591bcb298db76e75498a9372eaef42c9cbedfc57d670176a3cd"
|
||||
checksum = "ff38b61724dd492b5171d5dbb0921dfc8e859022c5993b22f80f74e9afe6d573"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"log",
|
||||
@@ -3782,15 +3782,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kode-bridge"
|
||||
version = "0.2.1"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e80a61472dcdfbd60624634f257c592d513b8df2991bd0946e4d54ccb24ea568"
|
||||
checksum = "368479099245d8ecd5b74e6b2b6279a69b38556a442aefbbaadd3ecf8246ffc3"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures",
|
||||
"http 1.3.1",
|
||||
"httparse",
|
||||
"interprocess",
|
||||
"libc",
|
||||
"parking_lot 0.12.4",
|
||||
"pin-project-lite",
|
||||
"rand 0.9.2",
|
||||
@@ -3802,6 +3803,7 @@ dependencies = [
|
||||
"tokio-util",
|
||||
"toml 0.9.7",
|
||||
"tracing",
|
||||
"widestring",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3854,9 +3856,9 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.175"
|
||||
version = "0.2.176"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
||||
checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
@@ -5844,9 +5846,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.2"
|
||||
version = "1.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
|
||||
checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -5856,9 +5858,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.10"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
|
||||
checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -6318,9 +6320,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.226"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
@@ -6352,18 +6354,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.226"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.226"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -6884,9 +6886,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.37.0"
|
||||
version = "0.37.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07cec4dc2d2e357ca1e610cfb07de2fa7a10fc3e9fe89f72545f3d244ea87753"
|
||||
checksum = "3bddd368fda2f82ead69c03d46d351987cfa0c2a57abfa37a017f3aa3e9bf69a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"memchr",
|
||||
|
||||
@@ -24,7 +24,7 @@ log = "0.4.28"
|
||||
dunce = "1.0.5"
|
||||
nanoid = "0.4"
|
||||
chrono = "0.4.42"
|
||||
sysinfo = { version = "0.37.0", features = ["network", "system"] }
|
||||
sysinfo = { version = "0.37.1", features = ["network", "system"] }
|
||||
boa_engine = "0.20.0"
|
||||
serde_json = "1.0.145"
|
||||
serde_yaml_ng = "0.10.0"
|
||||
@@ -39,9 +39,9 @@ tokio = { version = "1.47.1", features = [
|
||||
"time",
|
||||
"sync",
|
||||
] }
|
||||
serde = { version = "1.0.226", features = ["derive"] }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
reqwest = { version = "0.12.23", features = ["json", "cookies"] }
|
||||
regex = "1.11.2"
|
||||
regex = "1.11.3"
|
||||
sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs" }
|
||||
tauri = { version = "2.8.5", features = [
|
||||
"protocol-asset",
|
||||
@@ -65,13 +65,13 @@ base64 = "0.22.1"
|
||||
getrandom = "0.3.3"
|
||||
futures = "0.3.31"
|
||||
sys-locale = "0.3.2"
|
||||
libc = "0.2.175"
|
||||
libc = "0.2.176"
|
||||
gethostname = "1.0.2"
|
||||
hmac = "0.12.1"
|
||||
sha2 = "0.10.9"
|
||||
hex = "0.4.3"
|
||||
scopeguard = "1.2.0"
|
||||
kode-bridge = "0.2.1"
|
||||
kode-bridge = "0.3.0"
|
||||
dashmap = "6.1.0"
|
||||
tauri-plugin-notification = "2.3.1"
|
||||
tokio-stream = "0.1.17"
|
||||
@@ -81,7 +81,7 @@ isahc = { version = "1.7.2", default-features = false, features = [
|
||||
] }
|
||||
backoff = { version = "0.4.0", features = ["tokio"] }
|
||||
tauri-plugin-http = "2.5.2"
|
||||
flexi_logger = "0.31.3"
|
||||
flexi_logger = "0.31.4"
|
||||
cfg-if = "1.0.3"
|
||||
nu-ansi-term = { version = "0.50.1", optional = true }
|
||||
console-subscriber = { version = "0.4.1", optional = true }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{config::Config, utils::dirs};
|
||||
use once_cell::sync::Lazy;
|
||||
use serde_json::Value;
|
||||
use std::{collections::HashMap, fs, path::PathBuf};
|
||||
use std::{fs, path::PathBuf, sync::RwLock};
|
||||
use sys_locale;
|
||||
|
||||
const DEFAULT_LANGUAGE: &str = "zh";
|
||||
@@ -33,22 +33,20 @@ pub fn get_supported_languages() -> Vec<String> {
|
||||
languages
|
||||
}
|
||||
|
||||
static TRANSLATIONS: Lazy<HashMap<String, Value>> = Lazy::new(|| {
|
||||
let mut translations = HashMap::new();
|
||||
|
||||
if let Some(locales_dir) = get_locales_dir() {
|
||||
for lang in get_supported_languages() {
|
||||
let file_path = locales_dir.join(format!("{lang}.json"));
|
||||
if let Ok(content) = fs::read_to_string(file_path)
|
||||
&& let Ok(json) = serde_json::from_str(&content)
|
||||
{
|
||||
translations.insert(lang.to_string(), json);
|
||||
}
|
||||
}
|
||||
}
|
||||
translations
|
||||
static TRANSLATIONS: Lazy<RwLock<(String, Value)>> = Lazy::new(|| {
|
||||
let lang = get_system_language();
|
||||
let json = load_lang_file(&lang).unwrap_or_else(|| Value::Object(Default::default()));
|
||||
RwLock::new((lang, json))
|
||||
});
|
||||
|
||||
fn load_lang_file(lang: &str) -> Option<Value> {
|
||||
let locales_dir = get_locales_dir()?;
|
||||
let file_path = locales_dir.join(format!("{lang}.json"));
|
||||
fs::read_to_string(file_path)
|
||||
.ok()
|
||||
.and_then(|content| serde_json::from_str(&content).ok())
|
||||
}
|
||||
|
||||
fn get_system_language() -> String {
|
||||
sys_locale::get_locale()
|
||||
.map(|locale| locale.to_lowercase())
|
||||
@@ -58,8 +56,6 @@ fn get_system_language() -> String {
|
||||
}
|
||||
|
||||
pub async fn t(key: &str) -> String {
|
||||
let key = key.to_string(); // own the string
|
||||
|
||||
let current_lang = Config::verge()
|
||||
.await
|
||||
.latest_ref()
|
||||
@@ -68,22 +64,35 @@ pub async fn t(key: &str) -> String {
|
||||
.map(String::from)
|
||||
.unwrap_or_else(get_system_language);
|
||||
|
||||
if let Some(text) = TRANSLATIONS
|
||||
.get(¤t_lang)
|
||||
.and_then(|trans| trans.get(&key))
|
||||
.and_then(|val| val.as_str())
|
||||
{
|
||||
return text.to_string();
|
||||
if let Ok(cache) = TRANSLATIONS.read()
|
||||
&& cache.0 == current_lang
|
||||
&& let Some(text) = cache.1.get(key).and_then(|val| val.as_str())
|
||||
{
|
||||
return text.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(new_json) = load_lang_file(¤t_lang)
|
||||
&& let Ok(mut cache) = TRANSLATIONS.write()
|
||||
{
|
||||
*cache = (current_lang.clone(), new_json);
|
||||
|
||||
if let Some(text) = cache.1.get(key).and_then(|val| val.as_str()) {
|
||||
return text.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
if current_lang != DEFAULT_LANGUAGE
|
||||
&& let Some(text) = TRANSLATIONS
|
||||
.get(DEFAULT_LANGUAGE)
|
||||
.and_then(|trans| trans.get(&key))
|
||||
.and_then(|val| val.as_str())
|
||||
&& let Some(default_json) = load_lang_file(DEFAULT_LANGUAGE)
|
||||
&& let Ok(mut cache) = TRANSLATIONS.write()
|
||||
{
|
||||
return text.to_string();
|
||||
*cache = (DEFAULT_LANGUAGE.to_string(), default_json);
|
||||
|
||||
if let Some(text) = cache.1.get(key).and_then(|val| val.as_str()) {
|
||||
return text.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
key
|
||||
key.to_string()
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ export const RuleItem = (props: Props) => {
|
||||
const proxyPolicy = rule.match(/[^,]+$/)?.[0] ?? "";
|
||||
const ruleContent = rule.slice(ruleType.length + 1, -proxyPolicy.length - 1);
|
||||
|
||||
const $sortable = useSortable({ id: ruleRaw });
|
||||
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
@@ -32,7 +34,7 @@ export const RuleItem = (props: Props) => {
|
||||
transition,
|
||||
isDragging,
|
||||
} = sortable
|
||||
? useSortable({ id: ruleRaw })
|
||||
? $sortable
|
||||
: {
|
||||
attributes: {},
|
||||
listeners: {},
|
||||
|
||||
94
src/components/proxy/proxy-group-navigator.tsx
Normal file
94
src/components/proxy/proxy-group-navigator.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
import { Box, Button, Tooltip } from "@mui/material";
|
||||
import { useCallback, useMemo } from "react";
|
||||
|
||||
interface ProxyGroupNavigatorProps {
|
||||
proxyGroupNames: string[];
|
||||
onGroupLocation: (groupName: string) => void;
|
||||
}
|
||||
|
||||
// 提取代理组名的第一个字符
|
||||
const getGroupDisplayChar = (groupName: string): string => {
|
||||
if (!groupName) return "?";
|
||||
|
||||
// 直接返回第一个字符,支持表情符号
|
||||
const firstChar = Array.from(groupName)[0];
|
||||
return firstChar || "?";
|
||||
};
|
||||
|
||||
export const ProxyGroupNavigator = ({
|
||||
proxyGroupNames,
|
||||
onGroupLocation,
|
||||
}: ProxyGroupNavigatorProps) => {
|
||||
const handleGroupClick = useCallback(
|
||||
(groupName: string) => {
|
||||
onGroupLocation(groupName);
|
||||
},
|
||||
[onGroupLocation],
|
||||
);
|
||||
|
||||
// 处理代理组数据,去重和排序
|
||||
const processedGroups = useMemo(() => {
|
||||
return proxyGroupNames
|
||||
.filter((name) => name && name.trim())
|
||||
.map((name) => ({
|
||||
name,
|
||||
displayChar: getGroupDisplayChar(name),
|
||||
}));
|
||||
}, [proxyGroupNames]);
|
||||
|
||||
if (processedGroups.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
right: 2,
|
||||
top: "50%",
|
||||
transform: "translateY(-50%)",
|
||||
zIndex: 10,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 0.25,
|
||||
bgcolor: "transparent",
|
||||
borderRadius: 0.5,
|
||||
boxShadow: 0,
|
||||
p: 0.25,
|
||||
maxHeight: "70vh",
|
||||
overflowY: "auto",
|
||||
minWidth: "auto",
|
||||
}}
|
||||
>
|
||||
{processedGroups.map(({ name, displayChar }) => (
|
||||
<Tooltip key={name} title={name} placement="left" arrow>
|
||||
<Button
|
||||
size="small"
|
||||
variant="text"
|
||||
onClick={() => handleGroupClick(name)}
|
||||
sx={{
|
||||
minWidth: 28,
|
||||
minHeight: 28,
|
||||
width: 28,
|
||||
height: 28,
|
||||
fontSize: "12px",
|
||||
fontWeight: 600,
|
||||
padding: 0,
|
||||
borderRadius: 0.25,
|
||||
color: "text.secondary",
|
||||
textAlign: "center",
|
||||
justifyContent: "center",
|
||||
textTransform: "none",
|
||||
"&:hover": {
|
||||
bgcolor: "primary.light",
|
||||
color: "primary.contrastText",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{displayChar}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -25,6 +25,7 @@ import { ScrollTopButton } from "../layout/scroll-top-button";
|
||||
|
||||
import { ProxyChain } from "./proxy-chain";
|
||||
import { ProxyRender } from "./proxy-render";
|
||||
import { ProxyGroupNavigator } from "./proxy-group-navigator";
|
||||
import { useRenderList } from "./use-render-list";
|
||||
import { delayGroup, healthcheckProxyProvider } from "tauri-plugin-mihomo-api";
|
||||
|
||||
@@ -318,6 +319,45 @@ export const ProxyGroups = (props: Props) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 获取运行时配置
|
||||
const { data: runtimeConfig } = useSWR("getRuntimeConfig", getRuntimeConfig, {
|
||||
revalidateOnFocus: false,
|
||||
revalidateIfStale: true,
|
||||
});
|
||||
|
||||
// 获取所有代理组名称
|
||||
const getProxyGroupNames = useCallback(() => {
|
||||
const config = runtimeConfig as any;
|
||||
if (!config?.["proxy-groups"]) return [];
|
||||
|
||||
return config["proxy-groups"]
|
||||
.map((group: any) => group.name)
|
||||
.filter((name: string) => name && name.trim() !== "");
|
||||
}, [runtimeConfig]);
|
||||
|
||||
// 定位到指定的代理组
|
||||
const handleGroupLocationByName = useCallback(
|
||||
(groupName: string) => {
|
||||
const index = renderList.findIndex(
|
||||
(item) => item.type === 0 && item.group?.name === groupName,
|
||||
);
|
||||
|
||||
if (index >= 0) {
|
||||
virtuosoRef.current?.scrollToIndex?.({
|
||||
index,
|
||||
align: "start",
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
},
|
||||
[renderList],
|
||||
);
|
||||
|
||||
const proxyGroupNames = useMemo(
|
||||
() => getProxyGroupNames(),
|
||||
[getProxyGroupNames],
|
||||
);
|
||||
|
||||
if (mode === "direct") {
|
||||
return <BaseEmpty text={t("clash_mode_direct")} />;
|
||||
}
|
||||
@@ -514,6 +554,14 @@ export const ProxyGroups = (props: Props) => {
|
||||
<div
|
||||
style={{ position: "relative", height: "100%", willChange: "transform" }}
|
||||
>
|
||||
{/* 代理组导航栏 */}
|
||||
{mode === "rule" && (
|
||||
<ProxyGroupNavigator
|
||||
proxyGroupNames={proxyGroupNames}
|
||||
onGroupLocation={handleGroupLocationByName}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Virtuoso
|
||||
ref={virtuosoRef}
|
||||
style={{ height: "calc(100% - 14px)" }}
|
||||
|
||||
@@ -31,9 +31,15 @@ interface Props {
|
||||
onHeadState: (val: Partial<HeadState>) => void;
|
||||
}
|
||||
|
||||
export const ProxyHead = (props: Props) => {
|
||||
const { sx = {}, url, groupName, headState, onHeadState } = props;
|
||||
|
||||
export const ProxyHead = ({
|
||||
sx = {},
|
||||
url,
|
||||
groupName,
|
||||
headState,
|
||||
onHeadState,
|
||||
onLocation,
|
||||
onCheckDelay,
|
||||
}: Props) => {
|
||||
const { showType, sortType, filterText, textState, testUrl } = headState;
|
||||
|
||||
const { t } = useTranslation();
|
||||
@@ -46,13 +52,11 @@ export const ProxyHead = (props: Props) => {
|
||||
}, []);
|
||||
|
||||
const { verge } = useVerge();
|
||||
const default_latency_test = verge!.default_latency_test!;
|
||||
|
||||
useEffect(() => {
|
||||
delayManager.setUrl(
|
||||
groupName,
|
||||
testUrl || url || verge?.default_latency_test!,
|
||||
);
|
||||
}, [groupName, testUrl, verge?.default_latency_test]);
|
||||
delayManager.setUrl(groupName, testUrl || url || default_latency_test);
|
||||
}, [groupName, testUrl, default_latency_test, url]);
|
||||
|
||||
return (
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 0.5, ...sx }}>
|
||||
@@ -60,7 +64,7 @@ export const ProxyHead = (props: Props) => {
|
||||
size="small"
|
||||
color="inherit"
|
||||
title={t("locate")}
|
||||
onClick={props.onLocation}
|
||||
onClick={onLocation}
|
||||
>
|
||||
<MyLocationRounded />
|
||||
</IconButton>
|
||||
@@ -76,7 +80,7 @@ export const ProxyHead = (props: Props) => {
|
||||
console.log(`[ProxyHead] 使用自定义测试URL: ${testUrl}`);
|
||||
onHeadState({ textState: "url" });
|
||||
}
|
||||
props.onCheckDelay();
|
||||
onCheckDelay();
|
||||
}}
|
||||
>
|
||||
<NetworkCheckRounded />
|
||||
|
||||
@@ -61,12 +61,12 @@ export const ProxyItem = (props: Props) => {
|
||||
return () => {
|
||||
delayManager.removeListener(proxy.name, group.name);
|
||||
};
|
||||
}, [proxy.name, group.name]);
|
||||
}, [proxy.name, group.name, isPreset]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!proxy) return;
|
||||
setDelay(delayManager.getDelayFix(proxy, group.name));
|
||||
}, [proxy]);
|
||||
}, [group.name, proxy]);
|
||||
|
||||
const onDelay = useLockFn(async () => {
|
||||
setDelay(-2);
|
||||
|
||||
@@ -11,7 +11,7 @@ export default function useFilterSort(
|
||||
filterText: string,
|
||||
sortType: ProxySortType,
|
||||
) {
|
||||
const [refresh, setRefresh] = useState({});
|
||||
const [, setRefresh] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
let last = 0;
|
||||
@@ -34,7 +34,7 @@ export default function useFilterSort(
|
||||
const fp = filterProxies(proxies, groupName, filterText);
|
||||
const sp = sortProxies(fp, groupName, sortType);
|
||||
return sp;
|
||||
}, [proxies, groupName, filterText, sortType, refresh]);
|
||||
}, [proxies, groupName, filterText, sortType]);
|
||||
}
|
||||
|
||||
export function filterSort(
|
||||
|
||||
@@ -90,13 +90,11 @@ export const HotkeyInput = (props: Props) => {
|
||||
|
||||
<div className="list">
|
||||
{keys.map((key, index) => (
|
||||
<Box display="flex">
|
||||
<Box display="flex" key={key}>
|
||||
<span className="delimiter" hidden={index === 0}>
|
||||
+
|
||||
</span>
|
||||
<div key={key} className="item">
|
||||
{key}
|
||||
</div>
|
||||
<div className="item">{key}</div>
|
||||
</Box>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -59,13 +59,13 @@ const SettingVergeAdvanced = ({ onError: _ }: Props) => {
|
||||
const onExportDiagnosticInfo = useCallback(async () => {
|
||||
await exportDiagnosticInfo();
|
||||
showNotice("success", t("Copy Success"), 1000);
|
||||
}, []);
|
||||
}, [t]);
|
||||
|
||||
const copyVersion = useCallback(() => {
|
||||
navigator.clipboard.writeText(`v${version}`).then(() => {
|
||||
showNotice("success", t("Version copied to clipboard"), 1000);
|
||||
});
|
||||
}, [version, t]);
|
||||
}, [t]);
|
||||
|
||||
return (
|
||||
<SettingList title={t("Verge Advanced Setting")}>
|
||||
|
||||
@@ -77,7 +77,7 @@ const SettingVergeBasic = ({ onError }: Props) => {
|
||||
const onCopyClashEnv = useCallback(async () => {
|
||||
await copyClashEnv();
|
||||
showNotice("success", t("Copy Success"), 1000);
|
||||
}, []);
|
||||
}, [t]);
|
||||
|
||||
return (
|
||||
<SettingList title={t("Verge Basic Setting")}>
|
||||
|
||||
Reference in New Issue
Block a user