mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-30 02:32:09 +08:00
refactor(frontend): drop unused params, props and api surface
Remove the never-read _revalidate argument of mutateVerge and its 16 call-site placeholders, seven never-passed props of the split chip editor, the unconsumed refreshDecorated context member, the now-orphaned toggleFullscreen control, the unused translate argument of the notice message handler, the reserved-but-unset ServiceConfig.timeout field and unreachable branches in parseNoticeExtras.
This commit is contained in:
@@ -22,23 +22,16 @@ interface BaseSplitChipEditorProps {
|
||||
error?: boolean
|
||||
helperText?: ReactNode
|
||||
placeholder?: string
|
||||
rows?: number
|
||||
separator?: string
|
||||
splitPattern?: RegExp
|
||||
defaultMode?: BaseSplitChipEditorMode
|
||||
showModeToggle?: boolean
|
||||
ariaLabel?: string
|
||||
addLabel?: ReactNode
|
||||
emptyLabel?: ReactNode
|
||||
modeLabels?: Partial<Record<BaseSplitChipEditorMode, ReactNode>>
|
||||
renderHeader?: (modeToggle: ReactNode) => ReactNode
|
||||
}
|
||||
|
||||
const DEFAULT_SPLIT_PATTERN = /[,\n;\r]+/
|
||||
|
||||
const splitValue = (value: string, splitPattern: RegExp) =>
|
||||
const splitValue = (value: string) =>
|
||||
value
|
||||
.split(splitPattern)
|
||||
.split(DEFAULT_SPLIT_PATTERN)
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean)
|
||||
|
||||
@@ -49,35 +42,25 @@ export const BaseSplitChipEditor = ({
|
||||
error = false,
|
||||
helperText,
|
||||
placeholder,
|
||||
rows = 4,
|
||||
separator = ',',
|
||||
splitPattern = DEFAULT_SPLIT_PATTERN,
|
||||
defaultMode = 'visual',
|
||||
showModeToggle = true,
|
||||
ariaLabel,
|
||||
addLabel,
|
||||
emptyLabel,
|
||||
modeLabels,
|
||||
renderHeader,
|
||||
}: BaseSplitChipEditorProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [mode, setMode] = useState<BaseSplitChipEditorMode>(defaultMode)
|
||||
const [mode, setMode] = useState<BaseSplitChipEditorMode>('visual')
|
||||
const [draft, setDraft] = useState('')
|
||||
|
||||
const resolvedLabels = useMemo(
|
||||
() => ({
|
||||
visual: modeLabels?.visual ?? t('shared.editorModes.visualization'),
|
||||
advanced: modeLabels?.advanced ?? t('shared.editorModes.advanced'),
|
||||
add: addLabel ?? t('shared.actions.new'),
|
||||
empty: emptyLabel ?? t('shared.statuses.empty'),
|
||||
visual: t('shared.editorModes.visualization'),
|
||||
advanced: t('shared.editorModes.advanced'),
|
||||
add: t('shared.actions.new'),
|
||||
empty: t('shared.statuses.empty'),
|
||||
}),
|
||||
[t, modeLabels, addLabel, emptyLabel],
|
||||
[t],
|
||||
)
|
||||
|
||||
const values = useMemo(
|
||||
() => splitValue(value, splitPattern),
|
||||
[value, splitPattern],
|
||||
)
|
||||
const values = useMemo(() => splitValue(value), [value])
|
||||
|
||||
const items = useMemo(() => {
|
||||
const counts = new Map<string, number>()
|
||||
@@ -92,7 +75,7 @@ export const BaseSplitChipEditor = ({
|
||||
}, [values])
|
||||
|
||||
const handleAddDraft = () => {
|
||||
const nextValues = splitValue(draft, splitPattern)
|
||||
const nextValues = splitValue(draft)
|
||||
if (!nextValues.length) {
|
||||
return
|
||||
}
|
||||
@@ -113,7 +96,7 @@ export const BaseSplitChipEditor = ({
|
||||
const resolvedAriaLabel =
|
||||
ariaLabel ?? (typeof toggleLabel === 'string' ? toggleLabel : undefined)
|
||||
|
||||
const modeToggle = showModeToggle ? (
|
||||
const modeToggle = (
|
||||
<Tooltip title={toggleLabel}>
|
||||
<IconButton
|
||||
size="small"
|
||||
@@ -128,7 +111,7 @@ export const BaseSplitChipEditor = ({
|
||||
<ToggleIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
) : null
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -202,7 +185,7 @@ export const BaseSplitChipEditor = ({
|
||||
disabled={disabled}
|
||||
size="small"
|
||||
multiline
|
||||
rows={rows}
|
||||
rows={4}
|
||||
sx={{ width: '100%' }}
|
||||
value={value}
|
||||
helperText={helperText}
|
||||
|
||||
@@ -87,7 +87,7 @@ export const TestCard = () => {
|
||||
x.uid === uid ? { ...x, ...patch } : x,
|
||||
)
|
||||
|
||||
mutateVerge({ ...verge, test_list: newList }, false)
|
||||
mutateVerge({ ...verge, test_list: newList })
|
||||
},
|
||||
[testList, verge, mutateVerge],
|
||||
)
|
||||
@@ -96,7 +96,7 @@ export const TestCard = () => {
|
||||
(uid: string) => {
|
||||
const newList = testList.filter((x) => x.uid !== uid)
|
||||
patchVerge({ test_list: newList })
|
||||
mutateVerge({ ...verge, test_list: newList }, false)
|
||||
mutateVerge({ ...verge, test_list: newList })
|
||||
},
|
||||
[testList, verge, patchVerge, mutateVerge],
|
||||
)
|
||||
@@ -129,7 +129,7 @@ export const TestCard = () => {
|
||||
newList.splice(newIndex, 0, removed)
|
||||
|
||||
// 优化:先本地更新,再异步 patch,避免UI卡死
|
||||
mutateVerge({ ...verge, test_list: newList }, false)
|
||||
mutateVerge({ ...verge, test_list: newList })
|
||||
const patchFn = () => {
|
||||
try {
|
||||
patchVerge({ test_list: newList })
|
||||
|
||||
@@ -35,10 +35,7 @@ export const LayoutSidebar = (props: LayoutSidebarProps) => {
|
||||
|
||||
const handleMenuOrderOptimisticUpdate = useCallback(
|
||||
(order: string[]) => {
|
||||
mutateVerge(
|
||||
(prev) => (prev ? { ...prev, menu_order: order } : prev),
|
||||
false,
|
||||
)
|
||||
mutateVerge((prev) => (prev ? { ...prev, menu_order: order } : prev))
|
||||
},
|
||||
[mutateVerge],
|
||||
)
|
||||
|
||||
@@ -30,10 +30,8 @@ export const useProxyGroupHeaderLayout = () => {
|
||||
if (canceled || !operation.source || !operation.target) return
|
||||
|
||||
const position = toolsOnLeft ? 'right' : 'left'
|
||||
mutateVerge(
|
||||
(prev) =>
|
||||
prev ? { ...prev, proxy_group_tools_position: position } : prev,
|
||||
false,
|
||||
mutateVerge((prev) =>
|
||||
prev ? { ...prev, proxy_group_tools_position: position } : prev,
|
||||
)
|
||||
try {
|
||||
await patchVerge({ proxy_group_tools_position: position })
|
||||
|
||||
@@ -113,17 +113,15 @@ export const BackupConfigViewer = memo(
|
||||
errorNotice: false,
|
||||
},
|
||||
)
|
||||
await mutateVerge(
|
||||
(current) =>
|
||||
current
|
||||
? {
|
||||
...current,
|
||||
webdav_url: trimmedUrl,
|
||||
webdav_username: trimmedUsername,
|
||||
webdav_password: data.password,
|
||||
}
|
||||
: current,
|
||||
false,
|
||||
await mutateVerge((current) =>
|
||||
current
|
||||
? {
|
||||
...current,
|
||||
webdav_url: trimmedUrl,
|
||||
webdav_username: trimmedUsername,
|
||||
webdav_password: data.password,
|
||||
}
|
||||
: current,
|
||||
)
|
||||
setWebdavStatus(signature, 'unknown')
|
||||
showNotice.success('settings.modals.backup.messages.webdavConfigSaved')
|
||||
|
||||
@@ -107,7 +107,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
|
||||
showNotice.error(err)
|
||||
}
|
||||
const onChangeData = (patch: Partial<IVergeConfig>) => {
|
||||
mutateVerge({ ...verge, ...patch }, false)
|
||||
mutateVerge({ ...verge, ...patch })
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -339,7 +339,7 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
Promise.resolve().then(async () => {
|
||||
try {
|
||||
if (Object.keys(patch).length > 0) {
|
||||
mutateVerge({ ...verge, ...patch }, false)
|
||||
mutateVerge({ ...verge, ...patch })
|
||||
}
|
||||
if (Object.keys(patch).length > 0) {
|
||||
await patchVerge(patch)
|
||||
|
||||
@@ -50,21 +50,21 @@ export function WebUIViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
|
||||
const handleAdd = useLockFn(async (value: string) => {
|
||||
const newList = [...webUIList, value]
|
||||
mutateVerge((old) => (old ? { ...old, web_ui_list: newList } : old), false)
|
||||
mutateVerge((old) => (old ? { ...old, web_ui_list: newList } : old))
|
||||
await patchVerge({ web_ui_list: newList })
|
||||
})
|
||||
|
||||
const handleChange = useLockFn(async (index: number, value?: string) => {
|
||||
const newList = [...webUIList]
|
||||
newList[index] = value ?? ''
|
||||
mutateVerge((old) => (old ? { ...old, web_ui_list: newList } : old), false)
|
||||
mutateVerge((old) => (old ? { ...old, web_ui_list: newList } : old))
|
||||
await patchVerge({ web_ui_list: newList })
|
||||
})
|
||||
|
||||
const handleDelete = useLockFn(async (index: number) => {
|
||||
const newList = [...webUIList]
|
||||
newList.splice(index, 1)
|
||||
mutateVerge((old) => (old ? { ...old, web_ui_list: newList } : old), false)
|
||||
mutateVerge((old) => (old ? { ...old, web_ui_list: newList } : old))
|
||||
await patchVerge({ web_ui_list: newList })
|
||||
})
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ const SettingSystem = ({ onError }: Props) => {
|
||||
value: boolean,
|
||||
) => value
|
||||
const onChangeData = (patch: Partial<IVergeConfig>) => {
|
||||
mutateVerge({ ...verge, ...patch }, false)
|
||||
mutateVerge({ ...verge, ...patch })
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -72,7 +72,7 @@ const SettingVergeBasic = ({ onError }: Props) => {
|
||||
const backupRef = useRef<DialogRef>(null)
|
||||
|
||||
const onChangeData = (patch: any) => {
|
||||
mutateVerge({ ...verge, ...patch }, false)
|
||||
mutateVerge({ ...verge, ...patch })
|
||||
}
|
||||
|
||||
const onCopyClashEnv = useCallback(async () => {
|
||||
|
||||
@@ -163,7 +163,7 @@ const ProxyControlSwitches = ({
|
||||
})
|
||||
return false
|
||||
}
|
||||
mutateVerge({ ...verge, enable_tun_mode: value }, false)
|
||||
mutateVerge({ ...verge, enable_tun_mode: value })
|
||||
await patchVerge({ enable_tun_mode: value })
|
||||
}
|
||||
|
||||
|
||||
@@ -52,9 +52,8 @@ export const useSystemProxyState = () => {
|
||||
const toggleSystemProxy = async (enabled: boolean) => {
|
||||
// Roll failed optimistic writes back to the latest confirmed state.
|
||||
let confirmed = verge?.enable_system_proxy ?? false
|
||||
mutateVerge(
|
||||
(prev) => (prev ? { ...prev, enable_system_proxy: enabled } : prev),
|
||||
false,
|
||||
mutateVerge((prev) =>
|
||||
prev ? { ...prev, enable_system_proxy: enabled } : prev,
|
||||
)
|
||||
pendingRef.current = enabled
|
||||
|
||||
@@ -78,9 +77,8 @@ export const useSystemProxyState = () => {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
mutateVerge(
|
||||
(prev) => (prev ? { ...prev, enable_system_proxy: confirmed } : prev),
|
||||
false,
|
||||
mutateVerge((prev) =>
|
||||
prev ? { ...prev, enable_system_proxy: confirmed } : prev,
|
||||
)
|
||||
// Queued requests were based on a state that never landed.
|
||||
pendingRef.current = null
|
||||
|
||||
@@ -25,7 +25,6 @@ export const useVerge = () => {
|
||||
| IVergeConfig
|
||||
| ((prev: IVergeConfig | undefined) => IVergeConfig | undefined)
|
||||
| undefined,
|
||||
_revalidate?: boolean,
|
||||
) => {
|
||||
if (updaterOrData === undefined) {
|
||||
void refetch()
|
||||
|
||||
+6
-18
@@ -11,36 +11,24 @@ const useWindow = () => {
|
||||
}
|
||||
|
||||
export const useWindowControls = () => {
|
||||
const {
|
||||
maximized,
|
||||
minimize,
|
||||
toggleMaximize,
|
||||
close,
|
||||
toggleFullscreen,
|
||||
currentWindow,
|
||||
} = useWindow()
|
||||
const { maximized, minimize, toggleMaximize, close, currentWindow } =
|
||||
useWindow()
|
||||
return {
|
||||
maximized,
|
||||
minimize,
|
||||
toggleMaximize,
|
||||
close,
|
||||
toggleFullscreen,
|
||||
currentWindow,
|
||||
} satisfies Pick<
|
||||
WindowContextType,
|
||||
| 'maximized'
|
||||
| 'minimize'
|
||||
| 'toggleMaximize'
|
||||
| 'close'
|
||||
| 'toggleFullscreen'
|
||||
| 'currentWindow'
|
||||
'maximized' | 'minimize' | 'toggleMaximize' | 'close' | 'currentWindow'
|
||||
>
|
||||
}
|
||||
|
||||
export const useWindowDecorations = () => {
|
||||
const { decorated, toggleDecorations, refreshDecorated } = useWindow()
|
||||
return { decorated, toggleDecorations, refreshDecorated } satisfies Pick<
|
||||
const { decorated, toggleDecorations } = useWindow()
|
||||
return { decorated, toggleDecorations } satisfies Pick<
|
||||
WindowContextType,
|
||||
'decorated' | 'toggleDecorations' | 'refreshDecorated'
|
||||
'decorated' | 'toggleDecorations'
|
||||
>
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Paper, ThemeProvider } from '@mui/material'
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import { useCallback, useEffect, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Outlet, useNavigate } from 'react-router'
|
||||
|
||||
import { BaseErrorBoundary } from '@/components/base'
|
||||
@@ -38,7 +37,6 @@ const OS = getSystem()
|
||||
const Layout = () => {
|
||||
const mode = useThemeMode()
|
||||
const isDark = mode !== 'light'
|
||||
const { t } = useTranslation()
|
||||
const { theme } = useCustomTheme()
|
||||
const { verge } = useVerge()
|
||||
const { language } = verge ?? {}
|
||||
@@ -68,12 +66,12 @@ const Layout = () => {
|
||||
(payload: [string, string]) => {
|
||||
const [status, msg] = payload
|
||||
try {
|
||||
handleNoticeMessage(status, msg, t, navigate)
|
||||
handleNoticeMessage(status, msg, navigate)
|
||||
} catch (error) {
|
||||
console.error('[通知处理] 失败:', error)
|
||||
}
|
||||
},
|
||||
[t, navigate],
|
||||
[navigate],
|
||||
)
|
||||
|
||||
useEventBus(handleNotice)
|
||||
|
||||
@@ -5,11 +5,9 @@ export interface WindowContextType {
|
||||
decorated: boolean | null
|
||||
maximized: boolean | null
|
||||
toggleDecorations: () => Promise<void>
|
||||
refreshDecorated: () => Promise<boolean>
|
||||
minimize: () => Promise<void>
|
||||
close: () => Promise<void>
|
||||
toggleMaximize: () => Promise<void>
|
||||
toggleFullscreen: () => Promise<void>
|
||||
currentWindow: ReturnType<typeof getCurrentWindow>
|
||||
}
|
||||
|
||||
|
||||
@@ -61,10 +61,6 @@ export const WindowProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
}
|
||||
}, [currentWindow])
|
||||
|
||||
const toggleFullscreen = useCallback(async () => {
|
||||
await currentWindow.setFullscreen(!(await currentWindow.isFullscreen()))
|
||||
}, [currentWindow])
|
||||
|
||||
const refreshDecorated = useCallback(async () => {
|
||||
const val = await currentWindow.isDecorated()
|
||||
setDecorated(val)
|
||||
@@ -87,22 +83,18 @@ export const WindowProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
decorated,
|
||||
maximized,
|
||||
toggleDecorations,
|
||||
refreshDecorated,
|
||||
minimize,
|
||||
close,
|
||||
toggleMaximize,
|
||||
toggleFullscreen,
|
||||
currentWindow,
|
||||
}),
|
||||
[
|
||||
decorated,
|
||||
maximized,
|
||||
toggleDecorations,
|
||||
refreshDecorated,
|
||||
minimize,
|
||||
close,
|
||||
toggleMaximize,
|
||||
toggleFullscreen,
|
||||
currentWindow,
|
||||
],
|
||||
)
|
||||
|
||||
+3
-4
@@ -35,7 +35,6 @@ interface IpInfo {
|
||||
interface ServiceConfig {
|
||||
url: string
|
||||
mapping: (data: any) => IpInfo
|
||||
timeout?: number // 保留timeout字段(如有需要)
|
||||
}
|
||||
|
||||
// 可用的IP检测服务列表及字段映射
|
||||
@@ -159,7 +158,7 @@ export const getIpInfo = async (): Promise<
|
||||
const timeoutController = new AbortController()
|
||||
const timeoutId = setTimeout(() => {
|
||||
timeoutController.abort()
|
||||
}, service.timeout || serviceTimeout)
|
||||
}, serviceTimeout)
|
||||
|
||||
try {
|
||||
return await asyncRetry(
|
||||
@@ -168,8 +167,8 @@ export const getIpInfo = async (): Promise<
|
||||
|
||||
const response = await fetch(service.url, {
|
||||
method: 'GET',
|
||||
signal: timeoutController.signal, // AbortSignal.timeout(service.timeout || serviceTimeout),
|
||||
connectTimeout: service.timeout || serviceTimeout,
|
||||
signal: timeoutController.signal, // AbortSignal.timeout(serviceTimeout),
|
||||
connectTimeout: serviceTimeout,
|
||||
headers: {
|
||||
'User-Agent': userAgent,
|
||||
},
|
||||
|
||||
@@ -51,7 +51,7 @@ const mountBus = async () => {
|
||||
return createEventBus({
|
||||
dispatch,
|
||||
handleNotice: ([status, message]: [string, string]) => {
|
||||
notice(status, message, (key) => key, vi.fn())
|
||||
notice(status, message, vi.fn())
|
||||
},
|
||||
revalidateKeys: (keys) => void revalidateQueries(keys.map((key) => [key])),
|
||||
readPendingFailures: () => {},
|
||||
@@ -72,7 +72,7 @@ it('does not replay a recovered startup error after WebView recreation', async (
|
||||
kind: 'startFailed',
|
||||
detail: 'startup failed',
|
||||
})
|
||||
first.handleNoticeMessage('core_start::error', '', (key) => key, vi.fn())
|
||||
first.handleNoticeMessage('core_start::error', '', vi.fn())
|
||||
await flush()
|
||||
expect(showNotice.error).toHaveBeenCalledOnce()
|
||||
|
||||
@@ -81,7 +81,7 @@ it('does not replay a recovered startup error after WebView recreation', async (
|
||||
vi.mocked(getCoreStartupError).mockResolvedValue(null)
|
||||
vi.resetModules()
|
||||
const recreated = await import('@/services/notice-handlers')
|
||||
recreated.handleNoticeMessage('core_start::error', '', (key) => key, vi.fn())
|
||||
recreated.handleNoticeMessage('core_start::error', '', vi.fn())
|
||||
await flush()
|
||||
expect(showNotice.error).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
@@ -13,7 +13,6 @@ import { hideNotice, showNotice } from '@/services/notice-service'
|
||||
import { requestService } from '@/services/service-request'
|
||||
|
||||
type NavigateFunction = (path: string, options?: any) => void
|
||||
type TranslateFunction = (key: string) => string
|
||||
|
||||
let shownStartupError: string | null = null
|
||||
// Reads can settle out of order; only the newest may act, and a reset counts as one.
|
||||
@@ -241,7 +240,6 @@ const handlers: Record<
|
||||
export const handleNoticeMessage = (
|
||||
status: string,
|
||||
msg: string,
|
||||
t: TranslateFunction,
|
||||
navigate: NavigateFunction,
|
||||
) => {
|
||||
const handler = handlers[status as NoticeStatus]
|
||||
|
||||
@@ -148,15 +148,6 @@ function parseNoticeExtras(extras: NoticeExtra[]): ParsedNoticeExtras {
|
||||
raw = extra
|
||||
continue
|
||||
}
|
||||
|
||||
if (!params && isPlainRecord(extra)) {
|
||||
params = extra
|
||||
continue
|
||||
}
|
||||
|
||||
if (duration === undefined && typeof extra === 'number') {
|
||||
duration = extra
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user