mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-21 01:32:08 +08:00
feat: allow manually unfixing proxy nodes (#7756)
* feat(proxy): add ability to clear persisted node selection for groups * feat(hooks): make proxy selection operations asynchronous * chore: update tauri-plugin-mihomo deps * refactor: rename clear_selected_node to forget_selected_node * refactor(hooks): rename isFixed to isFixedProxy in useProxySelection * docs: update changelog with manual node cancellation support * chore(layout): remove dev-only mihomo websocket debugging interval
This commit is contained in:
Generated
+11
-31
@@ -1363,7 +1363,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2359,15 +2359,6 @@ version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8eb564c5c7423d25c886fb561d1e4ee69f72354d16918afa32c08811f6b6a55"
|
||||
|
||||
[[package]]
|
||||
name = "fast-uuid-v7"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65d7a30239eba18ae6a60dc3c3df98af18f00bba69d2b1d5f2816a831e83bff3"
|
||||
dependencies = [
|
||||
"rand 0.10.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.5.0"
|
||||
@@ -3406,12 +3397,12 @@ dependencies = [
|
||||
"libc",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2 0.5.10",
|
||||
"socket2 0.6.5",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"windows-registry 0.6.1",
|
||||
"windows-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3426,7 +3417,7 @@ dependencies = [
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core 0.61.2",
|
||||
"windows-core 0.62.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4839,7 +4830,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.45.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5513,7 +5504,7 @@ dependencies = [
|
||||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"socket2 0.5.10",
|
||||
"socket2 0.6.5",
|
||||
"thiserror 2.0.19",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -5552,7 +5543,7 @@ dependencies = [
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2 0.5.10",
|
||||
"socket2 0.6.5",
|
||||
"tracing",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
@@ -7338,7 +7329,7 @@ dependencies = [
|
||||
"thiserror 2.0.19",
|
||||
"tracing",
|
||||
"url",
|
||||
"windows-registry 0.5.3",
|
||||
"windows-registry",
|
||||
"windows-result 0.3.4",
|
||||
]
|
||||
|
||||
@@ -7453,11 +7444,10 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "tauri-plugin-mihomo"
|
||||
version = "0.5.5"
|
||||
source = "git+https://github.com/clash-verge-rev/tauri-plugin-mihomo?branch=refactor-use-reqwest-ipc#d9398bf8e862c0cd613b79ada45cc5d893820ed6"
|
||||
source = "git+https://github.com/clash-verge-rev/tauri-plugin-mihomo?branch=refactor-use-reqwest-ipc#a9c359edb2154c6e92bcf423e489a8082f2aa006"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"clashmap",
|
||||
"fast-uuid-v7",
|
||||
"futures-util",
|
||||
"http 1.5.0",
|
||||
"log",
|
||||
@@ -7472,6 +7462,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"ts-rs",
|
||||
"uuid",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
@@ -9102,7 +9093,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9282,17 +9273,6 @@ dependencies = [
|
||||
"windows-strings 0.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
|
||||
dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
"windows-result 0.4.1",
|
||||
"windows-strings 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.4"
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
- 引导窗口显示当前阶段:安装服务 / 重启内核 / 应用代理
|
||||
- 系统代理失败会保留到窗口可见时再呈现,不会因窗口隐藏或最小化而丢失
|
||||
- 区分「需要安装服务」与「服务已就绪但内核仍以 Sidecar 运行」,分别给出对应操作
|
||||
- 支持手动点击取消代理组中固定的代理节点
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
Generated
+4
-4
@@ -127,7 +127,7 @@ importers:
|
||||
version: 2.4.2(react@19.2.8)
|
||||
tauri-plugin-mihomo-api:
|
||||
specifier: github:clash-verge-rev/tauri-plugin-mihomo#refactor-use-reqwest-ipc
|
||||
version: https://codeload.github.com/clash-verge-rev/tauri-plugin-mihomo/tar.gz/0a97faea2cba28501a51f17cba5aac42c8353adb
|
||||
version: https://codeload.github.com/clash-verge-rev/tauri-plugin-mihomo/tar.gz/a9c359edb2154c6e92bcf423e489a8082f2aa006
|
||||
types-pac:
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3
|
||||
@@ -3822,8 +3822,8 @@ packages:
|
||||
resolution: {integrity: sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
tauri-plugin-mihomo-api@https://codeload.github.com/clash-verge-rev/tauri-plugin-mihomo/tar.gz/0a97faea2cba28501a51f17cba5aac42c8353adb:
|
||||
resolution: {gitHosted: true, tarball: https://codeload.github.com/clash-verge-rev/tauri-plugin-mihomo/tar.gz/0a97faea2cba28501a51f17cba5aac42c8353adb}
|
||||
tauri-plugin-mihomo-api@https://codeload.github.com/clash-verge-rev/tauri-plugin-mihomo/tar.gz/a9c359edb2154c6e92bcf423e489a8082f2aa006:
|
||||
resolution: {gitHosted: true, integrity: sha512-/HV/tKAz4Mw0sOpqL3Gjisn5jfUu+gkukjHHe0ngD2X2trooM4puU6kmG+546Xz5VAv/PR78gpm+gWBvm9yexQ==, tarball: https://codeload.github.com/clash-verge-rev/tauri-plugin-mihomo/tar.gz/a9c359edb2154c6e92bcf423e489a8082f2aa006}
|
||||
version: 0.5.5
|
||||
|
||||
terser@5.49.0:
|
||||
@@ -8045,7 +8045,7 @@ snapshots:
|
||||
minizlib: 3.1.0
|
||||
yallist: 5.0.0
|
||||
|
||||
tauri-plugin-mihomo-api@https://codeload.github.com/clash-verge-rev/tauri-plugin-mihomo/tar.gz/0a97faea2cba28501a51f17cba5aac42c8353adb:
|
||||
tauri-plugin-mihomo-api@https://codeload.github.com/clash-verge-rev/tauri-plugin-mihomo/tar.gz/a9c359edb2154c6e92bcf423e489a8082f2aa006:
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.11.1
|
||||
|
||||
|
||||
@@ -32,6 +32,14 @@ pub async fn record_selected_node(group_name: String, node: String) -> CmdResult
|
||||
.stringify_err()
|
||||
}
|
||||
|
||||
/// Forget the persisted node selection for a proxy group in the current profile.
|
||||
#[tauri::command]
|
||||
pub async fn forget_selected_node(group_name: String) -> CmdResult<()> {
|
||||
crate::config::profiles::forget_selected_node(&group_name)
|
||||
.await
|
||||
.stringify_err()
|
||||
}
|
||||
|
||||
static TRAY_SYNC_RUNNING: AtomicBool = AtomicBool::new(false);
|
||||
static TRAY_SYNC_PENDING: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
|
||||
@@ -977,6 +977,45 @@ pub async fn record_selected_node(group_name: &str, node: &str) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_selected_node(selected: &mut Vec<PrfSelected>, group_name: &str) -> bool {
|
||||
let original_len = selected.len();
|
||||
selected.retain(|entry| entry.name.as_deref() != Some(group_name));
|
||||
selected.len() != original_len
|
||||
}
|
||||
|
||||
pub async fn forget_selected_node(group_name: &str) -> Result<()> {
|
||||
let cleared = Config::profiles()
|
||||
.await
|
||||
.with_data_modify(move |mut profiles| async move {
|
||||
let Some(current) = profiles.current.clone() else {
|
||||
return Ok((profiles, false));
|
||||
};
|
||||
let Some(item) = profiles
|
||||
.items
|
||||
.as_mut()
|
||||
.and_then(|items| items.iter_mut().find(|item| item.uid.as_ref() == Some(¤t)))
|
||||
else {
|
||||
return Ok((profiles, false));
|
||||
};
|
||||
|
||||
let mut selected = item.selected.clone().unwrap_or_default();
|
||||
if !remove_selected_node(&mut selected, group_name) {
|
||||
return Ok((profiles, false));
|
||||
}
|
||||
|
||||
item.selected = (!selected.is_empty()).then_some(selected);
|
||||
profiles.save_file().await?;
|
||||
Ok((profiles, true))
|
||||
})
|
||||
.await?;
|
||||
|
||||
if cleared {
|
||||
supersede_selected_activation();
|
||||
handle::Handle::refresh_profiles();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn persist_reconciled_selected(
|
||||
profile_uid: &String,
|
||||
original_selected: &[PrfSelected],
|
||||
@@ -1395,6 +1434,22 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn removes_only_the_requested_group_selection() {
|
||||
let mut selections = vec![selected("Proxy", "Node A"), selected("Fallback", "Node B")];
|
||||
|
||||
assert!(remove_selected_node(&mut selections, "Proxy"));
|
||||
assert_eq!(selections, vec![selected("Fallback", "Node B")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn removing_a_missing_group_is_idempotent() {
|
||||
let mut selections = vec![selected("Proxy", "Node A")];
|
||||
|
||||
assert!(!remove_selected_node(&mut selections, "Missing"));
|
||||
assert_eq!(selections, vec![selected("Proxy", "Node A")]);
|
||||
}
|
||||
|
||||
fn proxies(groups: Vec<(&str, &[&str], Option<&str>)>) -> Proxies {
|
||||
Proxies {
|
||||
proxies: groups
|
||||
|
||||
@@ -165,6 +165,7 @@ mod app_init {
|
||||
cmd::copy_clash_env,
|
||||
cmd::sync_tray_proxy_selection,
|
||||
cmd::record_selected_node,
|
||||
cmd::forget_selected_node,
|
||||
cmd::save_dns_config,
|
||||
cmd::apply_dns_config,
|
||||
cmd::check_dns_config_exists,
|
||||
|
||||
@@ -623,6 +623,7 @@ export const CurrentProxyCard = () => {
|
||||
handleSelectChange(
|
||||
selectedGroup.name,
|
||||
previousProxy,
|
||||
selectedGroup.fixed,
|
||||
)({
|
||||
target: { value: nextName },
|
||||
})
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { useCallback, useMemo, useRef } from 'react'
|
||||
import { useCallback, useRef } from 'react'
|
||||
import {
|
||||
closeConnection,
|
||||
getConnections,
|
||||
selectNodeForGroup,
|
||||
unfixedProxy,
|
||||
} from 'tauri-plugin-mihomo-api'
|
||||
|
||||
import { useRecordSelection } from '@/hooks/use-record-selection'
|
||||
import {
|
||||
useForgetSelection,
|
||||
useRecordSelection,
|
||||
} from '@/hooks/use-record-selection'
|
||||
import { useVerge } from '@/hooks/use-verge'
|
||||
import { syncTrayProxySelection } from '@/services/cmds'
|
||||
import { debugLog } from '@/utils/debug'
|
||||
@@ -37,25 +41,20 @@ interface ProxyChangeRequest {
|
||||
groupName: string
|
||||
proxyName: string
|
||||
previousProxy?: string
|
||||
fixed?: string
|
||||
}
|
||||
|
||||
// 代理选择 Hook
|
||||
export const useProxySelection = (options: ProxySelectionOptions = {}) => {
|
||||
const recordSelection = useRecordSelection()
|
||||
const forgetSelection = useForgetSelection()
|
||||
const { verge } = useVerge()
|
||||
const pendingRequestRef = useRef<ProxyChangeRequest | null>(null)
|
||||
const isProcessingRef = useRef(false)
|
||||
|
||||
const { onSuccess, onError, enableConnectionCleanup = true } = options
|
||||
|
||||
// 缓存
|
||||
const config = useMemo(
|
||||
() => ({
|
||||
autoCloseConnection: verge?.auto_close_connection ?? false,
|
||||
enableConnectionCleanup,
|
||||
}),
|
||||
[verge?.auto_close_connection, enableConnectionCleanup],
|
||||
)
|
||||
const autoCloseConnection = verge?.auto_close_connection ?? false
|
||||
|
||||
// 切换节点
|
||||
const syncTraySelection = useCallback(() => {
|
||||
@@ -66,23 +65,33 @@ export const useProxySelection = (options: ProxySelectionOptions = {}) => {
|
||||
|
||||
const executeChange = useCallback(
|
||||
async (request: ProxyChangeRequest) => {
|
||||
const { groupName, proxyName, previousProxy } = request
|
||||
debugLog(`[ProxySelection] 代理切换: ${groupName} -> ${proxyName}`)
|
||||
const { groupName, proxyName, previousProxy, fixed } = request
|
||||
const isFixedProxy = fixed === proxyName
|
||||
if (isFixedProxy) {
|
||||
debugLog(`[ProxySelection] 代理取消固定: ${groupName} -> ${proxyName}`)
|
||||
} else {
|
||||
debugLog(`[ProxySelection] 代理切换: ${groupName} -> ${proxyName}`)
|
||||
}
|
||||
|
||||
try {
|
||||
await selectNodeForGroup(groupName, proxyName)
|
||||
if (isFixedProxy) {
|
||||
await unfixedProxy(groupName)
|
||||
} else {
|
||||
await selectNodeForGroup(groupName, proxyName)
|
||||
}
|
||||
onSuccess?.()
|
||||
syncTraySelection()
|
||||
recordSelection(groupName, proxyName)
|
||||
debugLog(
|
||||
`[ProxySelection] 代理和状态同步完成: ${groupName} -> ${proxyName}`,
|
||||
)
|
||||
if (isFixedProxy) {
|
||||
await forgetSelection(groupName)
|
||||
debugLog(`[ProxySelection] 代理和状态同步完成: ${groupName}`)
|
||||
} else {
|
||||
await recordSelection(groupName, proxyName)
|
||||
debugLog(
|
||||
`[ProxySelection] 代理和状态同步完成: ${groupName} -> ${proxyName}`,
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
config.enableConnectionCleanup &&
|
||||
config.autoCloseConnection &&
|
||||
previousProxy
|
||||
) {
|
||||
if (enableConnectionCleanup && autoCloseConnection && previousProxy) {
|
||||
void cleanupConnections(previousProxy)
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -93,7 +102,15 @@ export const useProxySelection = (options: ProxySelectionOptions = {}) => {
|
||||
onError?.(error)
|
||||
}
|
||||
},
|
||||
[config, onError, onSuccess, recordSelection, syncTraySelection],
|
||||
[
|
||||
autoCloseConnection,
|
||||
forgetSelection,
|
||||
enableConnectionCleanup,
|
||||
onError,
|
||||
onSuccess,
|
||||
recordSelection,
|
||||
syncTraySelection,
|
||||
],
|
||||
)
|
||||
|
||||
const flushChangeQueue = useCallback(async () => {
|
||||
@@ -115,11 +132,17 @@ export const useProxySelection = (options: ProxySelectionOptions = {}) => {
|
||||
}, [executeChange])
|
||||
|
||||
const changeProxy = useCallback(
|
||||
(groupName: string, proxyName: string, previousProxy?: string) => {
|
||||
(
|
||||
groupName: string,
|
||||
proxyName: string,
|
||||
previousProxy?: string,
|
||||
fixed?: string,
|
||||
) => {
|
||||
pendingRequestRef.current = {
|
||||
groupName,
|
||||
proxyName,
|
||||
previousProxy,
|
||||
fixed,
|
||||
}
|
||||
void flushChangeQueue()
|
||||
},
|
||||
@@ -127,17 +150,19 @@ export const useProxySelection = (options: ProxySelectionOptions = {}) => {
|
||||
)
|
||||
|
||||
const handleSelectChange = useCallback(
|
||||
(groupName: string, previousProxy?: string) =>
|
||||
(groupName: string, previousProxy?: string, fixed?: string) =>
|
||||
(event: { target: { value: string } }) => {
|
||||
const newProxy = event.target.value
|
||||
changeProxy(groupName, newProxy, previousProxy)
|
||||
changeProxy(groupName, event.target.value, previousProxy, fixed)
|
||||
},
|
||||
[changeProxy],
|
||||
)
|
||||
|
||||
const handleProxyGroupChange = useCallback(
|
||||
(group: { name: string; now?: string }, proxy: { name: string }) => {
|
||||
changeProxy(group.name, proxy.name, group.now)
|
||||
(
|
||||
group: { name: string; now?: string; fixed?: string },
|
||||
proxy: { name: string },
|
||||
) => {
|
||||
changeProxy(group.name, proxy.name, group.now, group.fixed)
|
||||
},
|
||||
[changeProxy],
|
||||
)
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
import { renderHook } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { useRecordSelection } from './use-record-selection'
|
||||
import { useForgetSelection, useRecordSelection } from './use-record-selection'
|
||||
|
||||
const recordSelectedNode = vi.hoisted(() => vi.fn(() => Promise.resolve()))
|
||||
const forgetSelectedNode = vi.hoisted(() => vi.fn(() => Promise.resolve()))
|
||||
|
||||
vi.mock('@/services/cmds', () => ({ recordSelectedNode }))
|
||||
vi.mock('@/services/cmds', () => ({ forgetSelectedNode, recordSelectedNode }))
|
||||
|
||||
const record = (groupName: string, proxyName: string) => {
|
||||
const { result } = renderHook(() => useRecordSelection())
|
||||
@@ -15,6 +16,49 @@ const record = (groupName: string, proxyName: string) => {
|
||||
|
||||
beforeEach(() => {
|
||||
recordSelectedNode.mockClear()
|
||||
forgetSelectedNode.mockClear()
|
||||
})
|
||||
|
||||
describe('useForgetSelection', () => {
|
||||
it('sends only the group name', () => {
|
||||
const { result } = renderHook(() => useForgetSelection())
|
||||
|
||||
result.current('Proxy')
|
||||
|
||||
expect(forgetSelectedNode).toHaveBeenCalledWith('Proxy')
|
||||
})
|
||||
|
||||
it('resolves after the clear request finishes', async () => {
|
||||
let resolveForget!: () => void
|
||||
forgetSelectedNode.mockImplementationOnce(
|
||||
() => new Promise<void>((resolve) => (resolveForget = resolve)),
|
||||
)
|
||||
const { result } = renderHook(() => useForgetSelection())
|
||||
|
||||
const forget = result.current('Proxy')
|
||||
let settled = false
|
||||
forget.then(() => {
|
||||
settled = true
|
||||
})
|
||||
|
||||
await Promise.resolve()
|
||||
expect(settled).toBe(false)
|
||||
|
||||
resolveForget()
|
||||
await forget
|
||||
expect(settled).toBe(true)
|
||||
})
|
||||
|
||||
it('reports a failed clear without throwing at the caller', async () => {
|
||||
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
forgetSelectedNode.mockRejectedValueOnce(new Error('no profile'))
|
||||
const { result } = renderHook(() => useForgetSelection())
|
||||
|
||||
expect(() => result.current('Proxy')).not.toThrow()
|
||||
|
||||
await vi.waitFor(() => expect(consoleError).toHaveBeenCalled())
|
||||
consoleError.mockRestore()
|
||||
})
|
||||
})
|
||||
|
||||
describe('useRecordSelection', () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback } from 'react'
|
||||
|
||||
import { recordSelectedNode } from '@/services/cmds'
|
||||
import { forgetSelectedNode, recordSelectedNode } from '@/services/cmds'
|
||||
|
||||
/**
|
||||
* Record which node a group is on, in the profile.
|
||||
@@ -20,9 +20,24 @@ import { recordSelectedNode } from '@/services/cmds'
|
||||
* stale snapshot. The merge happens on the backend, against whatever the profile holds by then.
|
||||
*/
|
||||
export const useRecordSelection = () => {
|
||||
return useCallback((groupName: string, proxyName: string) => {
|
||||
recordSelectedNode(groupName, proxyName).catch((error) => {
|
||||
return useCallback(async (groupName: string, proxyName: string) => {
|
||||
try {
|
||||
await recordSelectedNode(groupName, proxyName)
|
||||
} catch (error) {
|
||||
console.error('[Selection] 保存代理选择失败:', error)
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
}
|
||||
|
||||
/**
|
||||
* Forget the persisted node selection for a group after its runtime fixed node is released.
|
||||
*/
|
||||
export const useForgetSelection = () => {
|
||||
return useCallback(async (groupName: string) => {
|
||||
try {
|
||||
await forgetSelectedNode(groupName)
|
||||
} catch (error) {
|
||||
console.error('[Selection] 清除代理选择失败:', error)
|
||||
}
|
||||
}, [])
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ import type { CSSProperties } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Outlet, useNavigate } from 'react-router'
|
||||
import { MihomoWebSocket } from 'tauri-plugin-mihomo-api'
|
||||
|
||||
import iconDark from '@/assets/image/icon_dark.svg?react'
|
||||
import iconLight from '@/assets/image/icon_light.svg?react'
|
||||
@@ -125,24 +124,6 @@ const Layout = () => {
|
||||
const navigate = useNavigate()
|
||||
const themeReady = useMemo(() => Boolean(theme), [theme])
|
||||
|
||||
// 开发环境下检测 MihomoWebSocket 的所有实例
|
||||
useEffect(() => {
|
||||
let id: number
|
||||
if (import.meta.env.DEV) {
|
||||
id = setInterval(() => {
|
||||
MihomoWebSocket.get_all_instances().then((list) => {
|
||||
console.log('Mihomo ws instances', list)
|
||||
})
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (id) {
|
||||
clearInterval(id)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const [menuUnlocked, setMenuUnlocked] = useState(false)
|
||||
const [menuContextPosition, setMenuContextPosition] =
|
||||
useState<MenuContextPosition | null>(null)
|
||||
|
||||
@@ -136,6 +136,11 @@ export async function recordSelectedNode(groupName: string, node: string) {
|
||||
return invoke<void>('record_selected_node', { groupName, node })
|
||||
}
|
||||
|
||||
/** Forget the persisted node selection for one proxy group in the current profile. */
|
||||
export async function forgetSelectedNode(groupName: string) {
|
||||
return invoke<void>('forget_selected_node', { groupName })
|
||||
}
|
||||
|
||||
export async function getProxyView(): Promise<ProxyViewV1> {
|
||||
const view = await invoke<ProxyViewV1>('get_proxy_view')
|
||||
if (view.schemaVersion !== 1) {
|
||||
|
||||
Reference in New Issue
Block a user