mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-31 03:02:08 +08:00
refactor(proxies): route proxy/home/misc commands through the mutate funnel
Ports mode switching (page and home card), chain runtime updates, node selection with tray sync, selection recording, provider updates and syncs, and lightweight-mode entry onto the funnel; every site keeps its existing optimistic/revert and error-handling behavior.
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
||||
useCoreDataStatus,
|
||||
} from '@/providers/app-data-context'
|
||||
import { patchClashMode } from '@/services/cmds'
|
||||
import { mutate } from '@/services/mutate'
|
||||
import { showNotice } from '@/services/notice-service'
|
||||
import { setCacheData } from '@/services/query-client'
|
||||
import type { TranslationKey } from '@/types/generated/i18n-keys'
|
||||
@@ -90,7 +91,10 @@ export const ClashModeCard = () => {
|
||||
|
||||
setOptimisticMode(mode)
|
||||
try {
|
||||
await patchClashMode(mode)
|
||||
await mutate(() => patchClashMode(mode), {
|
||||
id: 'patch-clash-mode',
|
||||
errorNotice: false,
|
||||
})
|
||||
} catch (error) {
|
||||
setOptimisticMode(null)
|
||||
showNotice.error(error)
|
||||
|
||||
@@ -24,6 +24,7 @@ import { updateProxyProvider } from 'tauri-plugin-mihomo-api'
|
||||
|
||||
import { useAppRefreshers, useProxiesData } from '@/providers/app-data-context'
|
||||
import { syncRuntimeProviders } from '@/services/cmds'
|
||||
import { mutate } from '@/services/mutate'
|
||||
import { showNotice } from '@/services/notice-service'
|
||||
import parseTraffic from '@/utils/parse-traffic'
|
||||
|
||||
@@ -57,10 +58,16 @@ export const ProviderButton = () => {
|
||||
try {
|
||||
setUpdating((prev) => ({ ...prev, [name]: true }))
|
||||
|
||||
await updateProxyProvider(name)
|
||||
await mutate(() => updateProxyProvider(name), {
|
||||
id: `update-proxy-provider-${name}`,
|
||||
errorNotice: false,
|
||||
})
|
||||
|
||||
await refreshProxy()
|
||||
void syncRuntimeProviders()
|
||||
void mutate(() => syncRuntimeProviders(), {
|
||||
id: 'sync-runtime-providers',
|
||||
errorNotice: false,
|
||||
})
|
||||
|
||||
showNotice.success(
|
||||
'proxies.feedback.notifications.provider.updateSuccess',
|
||||
@@ -97,7 +104,10 @@ export const ProviderButton = () => {
|
||||
|
||||
for (const name of allProviders) {
|
||||
try {
|
||||
await updateProxyProvider(name)
|
||||
await mutate(() => updateProxyProvider(name), {
|
||||
id: `update-proxy-provider-${name}`,
|
||||
errorNotice: false,
|
||||
})
|
||||
setUpdating((prev) => ({ ...prev, [name]: false }))
|
||||
} catch (err) {
|
||||
console.error(`更新 ${name} 失败`, err)
|
||||
@@ -105,7 +115,10 @@ export const ProviderButton = () => {
|
||||
}
|
||||
|
||||
await refreshProxy()
|
||||
void syncRuntimeProviders()
|
||||
void mutate(() => syncRuntimeProviders(), {
|
||||
id: 'sync-runtime-providers',
|
||||
errorNotice: false,
|
||||
})
|
||||
|
||||
showNotice.success('proxies.feedback.notifications.provider.allUpdated')
|
||||
} catch (err) {
|
||||
|
||||
@@ -45,6 +45,7 @@ import { useRuntimeConfig } from '@/hooks/use-clash'
|
||||
import { useRecordSelection } from '@/hooks/use-record-selection'
|
||||
import { useAppRefreshers, useProxiesData } from '@/providers/app-data-context'
|
||||
import { updateProxyChainConfigInRuntime } from '@/services/cmds'
|
||||
import { mutate } from '@/services/mutate'
|
||||
import {
|
||||
selectGlobalChainNodes,
|
||||
selectRuleChainMembers,
|
||||
@@ -444,7 +445,10 @@ export const ProxyChain = ({
|
||||
if (isConnected) {
|
||||
setIsConnecting(true)
|
||||
try {
|
||||
await updateProxyChainConfigInRuntime(null)
|
||||
await mutate(() => updateProxyChainConfigInRuntime(null), {
|
||||
id: 'update-proxy-chain-runtime',
|
||||
errorNotice: false,
|
||||
})
|
||||
|
||||
const targetGroup =
|
||||
mode === 'global'
|
||||
@@ -453,12 +457,22 @@ export const ProxyChain = ({
|
||||
|
||||
if (targetGroup) {
|
||||
try {
|
||||
await selectNodeForGroup(targetGroup, 'DIRECT')
|
||||
await mutate(() => selectNodeForGroup(targetGroup, 'DIRECT'), {
|
||||
id: 'select-proxy-node',
|
||||
errorNotice: false,
|
||||
})
|
||||
recordSelection(targetGroup, 'DIRECT')
|
||||
} catch {
|
||||
if (currentProxyChain.length >= 1) {
|
||||
try {
|
||||
await selectNodeForGroup(targetGroup, currentProxyChain[0].name)
|
||||
await mutate(
|
||||
() =>
|
||||
selectNodeForGroup(targetGroup, currentProxyChain[0].name),
|
||||
{
|
||||
id: 'select-proxy-node',
|
||||
errorNotice: false,
|
||||
},
|
||||
)
|
||||
recordSelection(targetGroup, currentProxyChain[0].name)
|
||||
} catch {
|
||||
// ignore
|
||||
@@ -471,7 +485,10 @@ export const ProxyChain = ({
|
||||
localStorage.removeItem('proxy-chain-exit-node')
|
||||
localStorage.removeItem('proxy-chain-items')
|
||||
|
||||
await closeAllConnections()
|
||||
await mutate(() => closeAllConnections(), {
|
||||
id: 'close-all-connections',
|
||||
errorNotice: false,
|
||||
})
|
||||
await refreshProxy()
|
||||
|
||||
onUpdateChain([])
|
||||
@@ -502,7 +519,10 @@ export const ProxyChain = ({
|
||||
// 第一步:保存链式代理配置
|
||||
const chainProxies = currentProxyChain.map((node) => node.name)
|
||||
debugLog('Saving chain config:', chainProxies)
|
||||
await updateProxyChainConfigInRuntime(chainProxies)
|
||||
await mutate(() => updateProxyChainConfigInRuntime(chainProxies), {
|
||||
id: 'update-proxy-chain-runtime',
|
||||
errorNotice: false,
|
||||
})
|
||||
debugLog('Chain configuration saved successfully')
|
||||
|
||||
// 第二步:连接到代理链的最后一个节点
|
||||
@@ -516,7 +536,13 @@ export const ProxyChain = ({
|
||||
|
||||
const targetGroup = mode === 'global' ? 'GLOBAL' : selectedGroup
|
||||
|
||||
await selectNodeForGroup(targetGroup || 'GLOBAL', lastNode.name)
|
||||
await mutate(
|
||||
() => selectNodeForGroup(targetGroup || 'GLOBAL', lastNode.name),
|
||||
{
|
||||
id: 'select-proxy-node',
|
||||
errorNotice: false,
|
||||
},
|
||||
)
|
||||
// The chain moves the group like any other selection, so the profile has to learn about
|
||||
// it: what the profile holds is what gets re-applied the next time the core starts.
|
||||
recordSelection(targetGroup || 'GLOBAL', lastNode.name)
|
||||
@@ -593,7 +619,10 @@ export const ProxyChain = ({
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => {
|
||||
updateProxyChainConfigInRuntime(null)
|
||||
mutate(() => updateProxyChainConfigInRuntime(null), {
|
||||
id: 'update-proxy-chain-runtime',
|
||||
errorNotice: false,
|
||||
})
|
||||
localStorage.removeItem('proxy-chain-group')
|
||||
localStorage.removeItem('proxy-chain-exit-node')
|
||||
localStorage.removeItem('proxy-chain-items')
|
||||
|
||||
@@ -24,6 +24,7 @@ import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { useProxiesData } from '@/providers/app-data-context'
|
||||
import { updateProxyChainConfigInRuntime } from '@/services/cmds'
|
||||
import { mutate } from '@/services/mutate'
|
||||
import {
|
||||
isInteractableMember,
|
||||
type ProxyGroupView,
|
||||
@@ -404,7 +405,10 @@ export function ProxyGroupsChain(props: ProxyGroupsChainProps) {
|
||||
handleGroupMenuClose()
|
||||
|
||||
if (mode === 'rule') {
|
||||
updateProxyChainConfigInRuntime(null)
|
||||
mutate(() => updateProxyChainConfigInRuntime(null), {
|
||||
id: 'update-proxy-chain-runtime',
|
||||
errorNotice: false,
|
||||
})
|
||||
localStorage.removeItem('proxy-chain-group')
|
||||
localStorage.removeItem('proxy-chain-exit-node')
|
||||
localStorage.removeItem('proxy-chain-items')
|
||||
|
||||
@@ -23,6 +23,7 @@ import { updateRuleProvider } from 'tauri-plugin-mihomo-api'
|
||||
|
||||
import { useAppRefreshers, useRulesData } from '@/providers/app-data-context'
|
||||
import { syncRuntimeProviders } from '@/services/cmds'
|
||||
import { mutate } from '@/services/mutate'
|
||||
import { showNotice } from '@/services/notice-service'
|
||||
|
||||
const TypeBox = styled(Box)<{ component?: React.ElementType }>(({ theme }) => ({
|
||||
@@ -50,11 +51,17 @@ export const ProviderButton = () => {
|
||||
try {
|
||||
setUpdating((prev) => ({ ...prev, [name]: true }))
|
||||
|
||||
await updateRuleProvider(name)
|
||||
await mutate(() => updateRuleProvider(name), {
|
||||
id: `update-rule-provider-${name}`,
|
||||
errorNotice: false,
|
||||
})
|
||||
|
||||
await refreshRules()
|
||||
await refreshRuleProviders()
|
||||
void syncRuntimeProviders()
|
||||
void mutate(() => syncRuntimeProviders(), {
|
||||
id: 'sync-runtime-providers',
|
||||
errorNotice: false,
|
||||
})
|
||||
|
||||
showNotice.success(
|
||||
'rules.feedback.notifications.provider.updateSuccess',
|
||||
@@ -91,7 +98,10 @@ export const ProviderButton = () => {
|
||||
|
||||
for (const name of allProviders) {
|
||||
try {
|
||||
await updateRuleProvider(name)
|
||||
await mutate(() => updateRuleProvider(name), {
|
||||
id: `update-rule-provider-${name}`,
|
||||
errorNotice: false,
|
||||
})
|
||||
setUpdating((prev) => ({ ...prev, [name]: false }))
|
||||
} catch (err) {
|
||||
console.error(`更新 ${name} 失败`, err)
|
||||
@@ -100,7 +110,10 @@ export const ProviderButton = () => {
|
||||
|
||||
await refreshRules()
|
||||
await refreshRuleProviders()
|
||||
void syncRuntimeProviders()
|
||||
void mutate(() => syncRuntimeProviders(), {
|
||||
id: 'sync-runtime-providers',
|
||||
errorNotice: false,
|
||||
})
|
||||
|
||||
showNotice.success('rules.feedback.notifications.provider.allUpdated')
|
||||
} catch (err) {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { BaseDialog, DialogRef, Switch, TooltipIcon } from '@/components/base'
|
||||
import { useVerge } from '@/hooks/use-verge'
|
||||
import { entry_lightweight_mode } from '@/services/cmds'
|
||||
import { mutate } from '@/services/mutate'
|
||||
import { showNotice } from '@/services/notice-service'
|
||||
|
||||
export function LiteModeViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
@@ -72,7 +73,12 @@ export function LiteModeViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
color: 'primary.main',
|
||||
'&:hover': { textDecoration: 'underline' },
|
||||
}}
|
||||
onClick={async () => await entry_lightweight_mode()}
|
||||
onClick={async () =>
|
||||
await mutate(() => entry_lightweight_mode(), {
|
||||
id: 'entry-lightweight-mode',
|
||||
errorNotice: false,
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('shared.actions.enable')}
|
||||
</Typography>
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from '@/hooks/use-record-selection'
|
||||
import { useVerge } from '@/hooks/use-verge'
|
||||
import { syncTrayProxySelection } from '@/services/cmds'
|
||||
import { mutate } from '@/services/mutate'
|
||||
import { debugLog } from '@/utils/debug'
|
||||
|
||||
// 缓存连接清理
|
||||
@@ -58,7 +59,10 @@ export const useProxySelection = (options: ProxySelectionOptions = {}) => {
|
||||
|
||||
// 切换节点
|
||||
const syncTraySelection = useCallback(() => {
|
||||
syncTrayProxySelection().catch((error) => {
|
||||
mutate(() => syncTrayProxySelection(), {
|
||||
id: 'sync-tray-selection',
|
||||
errorNotice: false,
|
||||
}).catch((error) => {
|
||||
console.error('[ProxySelection] 托盘状态同步失败:', error)
|
||||
})
|
||||
}, [])
|
||||
@@ -75,9 +79,15 @@ export const useProxySelection = (options: ProxySelectionOptions = {}) => {
|
||||
|
||||
try {
|
||||
if (isFixedProxy) {
|
||||
await unfixedProxy(groupName)
|
||||
await mutate(() => unfixedProxy(groupName), {
|
||||
id: 'select-proxy-node',
|
||||
errorNotice: false,
|
||||
})
|
||||
} else {
|
||||
await selectNodeForGroup(groupName, proxyName)
|
||||
await mutate(() => selectNodeForGroup(groupName, proxyName), {
|
||||
id: 'select-proxy-node',
|
||||
errorNotice: false,
|
||||
})
|
||||
}
|
||||
onSuccess?.()
|
||||
syncTraySelection()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useCallback } from 'react'
|
||||
|
||||
import { forgetSelectedNode, recordSelectedNode } from '@/services/cmds'
|
||||
import { mutate } from '@/services/mutate'
|
||||
|
||||
/**
|
||||
* Persists every group selection because core-local state is not durable across all run modes.
|
||||
@@ -9,7 +10,10 @@ import { forgetSelectedNode, recordSelectedNode } from '@/services/cmds'
|
||||
export const useRecordSelection = () => {
|
||||
return useCallback(async (groupName: string, proxyName: string) => {
|
||||
try {
|
||||
await recordSelectedNode(groupName, proxyName)
|
||||
await mutate(() => recordSelectedNode(groupName, proxyName), {
|
||||
id: 'record-selected-node',
|
||||
errorNotice: false,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('[Selection] 保存代理选择失败:', error)
|
||||
}
|
||||
@@ -19,7 +23,10 @@ export const useRecordSelection = () => {
|
||||
export const useForgetSelection = () => {
|
||||
return useCallback(async (groupName: string) => {
|
||||
try {
|
||||
await forgetSelectedNode(groupName)
|
||||
await mutate(() => forgetSelectedNode(groupName), {
|
||||
id: 'forget-selected-node',
|
||||
errorNotice: false,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('[Selection] 清除代理选择失败:', error)
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ import { useConnectionData } from '@/hooks/use-connection-data'
|
||||
import { useConnectionSetting } from '@/hooks/use-connection-setting'
|
||||
import { useTrafficData } from '@/hooks/use-traffic-data'
|
||||
import { useVisibility } from '@/hooks/use-visibility'
|
||||
import { mutate } from '@/services/mutate'
|
||||
import parseTraffic from '@/utils/parse-traffic'
|
||||
|
||||
type OrderFunc = (list: IConnectionsItem[]) => IConnectionsItem[]
|
||||
@@ -152,7 +153,12 @@ const ConnectionsPage = () => {
|
||||
[connectionsType, filterConn],
|
||||
)
|
||||
|
||||
const onCloseAll = useLockFn(closeAllConnections)
|
||||
const onCloseAll = useLockFn(() =>
|
||||
mutate(() => closeAllConnections(), {
|
||||
id: 'close-all-connections',
|
||||
errorNotice: false,
|
||||
}),
|
||||
)
|
||||
|
||||
const handleSearch = useCallback(
|
||||
(match: (content: string) => boolean, state: SearchState) => {
|
||||
|
||||
+7
-1
@@ -35,6 +35,7 @@ import { ProxyTunCard } from '@/components/home/proxy-tun-card'
|
||||
import { useProfiles } from '@/hooks/use-profiles'
|
||||
import { useVerge } from '@/hooks/use-verge'
|
||||
import { entry_lightweight_mode } from '@/services/cmds'
|
||||
import { mutate } from '@/services/mutate'
|
||||
import { showNotice } from '@/services/notice-service'
|
||||
import { openExternalUrl } from '@/utils/open-external-url'
|
||||
|
||||
@@ -327,7 +328,12 @@ const HomePage = () => {
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Tooltip title={t('home.page.tooltips.lightweightMode')} arrow>
|
||||
<IconButton
|
||||
onClick={async () => await entry_lightweight_mode()}
|
||||
onClick={async () =>
|
||||
await mutate(() => entry_lightweight_mode(), {
|
||||
id: 'entry-lightweight-mode',
|
||||
errorNotice: false,
|
||||
})
|
||||
}
|
||||
size="small"
|
||||
color="inherit"
|
||||
>
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
patchClashMode,
|
||||
updateProxyChainConfigInRuntime,
|
||||
} from '@/services/cmds'
|
||||
import { mutate } from '@/services/mutate'
|
||||
import { showNotice } from '@/services/notice-service'
|
||||
import { debugLog } from '@/utils/debug'
|
||||
|
||||
@@ -57,7 +58,10 @@ const ProxyPage = () => {
|
||||
const onChangeMode = useLockFn(async (mode: Mode) => {
|
||||
try {
|
||||
// patchClashMode 在后端 PATCH 失败时会 reject,需提示用户而非静默失败
|
||||
await patchClashMode(mode)
|
||||
await mutate(() => patchClashMode(mode), {
|
||||
id: 'patch-clash-mode',
|
||||
errorNotice: false,
|
||||
})
|
||||
refreshClashConfig()
|
||||
} catch (error) {
|
||||
showNotice.error(error)
|
||||
@@ -75,7 +79,10 @@ const ProxyPage = () => {
|
||||
// 退出链式代理模式时,清除链式代理配置
|
||||
try {
|
||||
debugLog('Exiting chain mode, clearing chain configuration')
|
||||
await updateProxyChainConfigInRuntime(null)
|
||||
await mutate(() => updateProxyChainConfigInRuntime(null), {
|
||||
id: 'update-proxy-chain-runtime',
|
||||
errorNotice: false,
|
||||
})
|
||||
debugLog('Chain configuration cleared successfully')
|
||||
} catch (error) {
|
||||
console.error('Failed to clear chain configuration:', error)
|
||||
|
||||
Reference in New Issue
Block a user