From 51720296ccaaae19da2808516894781fe33781b3 Mon Sep 17 00:00:00 2001 From: xmk23333 Date: Mon, 8 Dec 2025 16:53:00 +0800 Subject: [PATCH 1/5] fix: optimize connections page performance and state management --- src/main/utils/template.ts | 1 + src/renderer/src/pages/connections.tsx | 94 +++++++++++++++----------- src/renderer/src/pages/mihomo.tsx | 4 -- 3 files changed, 56 insertions(+), 43 deletions(-) diff --git a/src/main/utils/template.ts b/src/main/utils/template.ts index cb7db1c..53648f2 100644 --- a/src/main/utils/template.ts +++ b/src/main/utils/template.ts @@ -9,6 +9,7 @@ export const defaultConfig: IAppConfig = { appTheme: 'system', useWindowFrame: false, proxyInTray: true, + showCurrentProxyInTray: false, disableTrayIconColor: false, maxLogDays: 7, proxyCols: 'auto', diff --git a/src/renderer/src/pages/connections.tsx b/src/renderer/src/pages/connections.tsx index fb79c68..f75f780 100644 --- a/src/renderer/src/pages/connections.tsx +++ b/src/renderer/src/pages/connections.tsx @@ -1,6 +1,6 @@ import BasePage from '@renderer/components/base/base-page' import { mihomoCloseAllConnections, mihomoCloseConnection } from '@renderer/utils/ipc' -import { Key, useCallback, useEffect, useMemo, useState } from 'react' +import { Key, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { Badge, Button, Divider, Input, Select, SelectItem, Tab, Tabs } from '@heroui/react' import { calcTraffic } from '@renderer/utils/calc' import ConnectionItem from '@renderer/components/connections/connection-item' @@ -48,6 +48,13 @@ const Connections: React.FC = () => { const [viewMode, setViewMode] = useState<'list' | 'table'>(connectionViewMode) const [visibleColumns, setVisibleColumns] = useState>(new Set(connectionTableColumns)) + const activeConnectionsRef = useRef(activeConnections) + const allConnectionsRef = useRef(allConnections) + useEffect(() => { + activeConnectionsRef.current = activeConnections + allConnectionsRef.current = allConnections + }, [activeConnections, allConnections]) + const handleColumnWidthChange = useCallback(async (widths: Record) => { await patchAppConfig({ connectionTableColumnWidths: widths }) }, [patchAppConfig]) @@ -98,71 +105,80 @@ const Connections: React.FC = () => { const closeAllConnections = useCallback((): void => { tab === 'active' ? mihomoCloseAllConnections() : trashAllClosedConnection() - }, [tab, closedConnections]) + }, [tab]) const closeConnection = useCallback((id: string): void => { tab === 'active' ? mihomoCloseConnection(id) : trashClosedConnection(id) }, [tab]) const trashAllClosedConnection = (): void => { - const trashIds = closedConnections.map((conn) => conn.id) - setAllConnections((allConns) => allConns.filter((conn) => !trashIds.includes(conn.id))) - setClosedConnections([]) - - cachedConnections = allConnections + setClosedConnections((closedConns) => { + const trashIds = new Set(closedConns.map((conn) => conn.id)) + setAllConnections((allConns) => { + const filtered = allConns.filter((conn) => !trashIds.has(conn.id)) + cachedConnections = filtered + return filtered + }) + return [] + }) } const trashClosedConnection = (id: string): void => { - setAllConnections((allConns) => allConns.filter((conn) => conn.id != id)) - setClosedConnections((closedConns) => closedConns.filter((conn) => conn.id != id)) - - cachedConnections = allConnections + setAllConnections((allConns) => { + const filtered = allConns.filter((conn) => conn.id !== id) + cachedConnections = filtered + return filtered + }) + setClosedConnections((closedConns) => closedConns.filter((conn) => conn.id !== id)) } useEffect(() => { - if (isPaused) return - window.electron.ipcRenderer.on('mihomoConnections', (_e, info: IMihomoConnectionsInfo) => { + const handler = (_e: unknown, info: IMihomoConnectionsInfo): void => { setConnectionsInfo(info) if (!info.connections) return - const allConns = unionWith(activeConnections, allConnections, (a, b) => a.id === b.id) + const allConns = unionWith( + activeConnectionsRef.current, + allConnectionsRef.current, + (a, b) => a.id === b.id + ) + const prevConnMap = new Map(activeConnectionsRef.current.map((c) => [c.id, c])) const activeConns = info.connections.map((conn) => { - const preConn = activeConnections.find((c) => c.id === conn.id) - const downloadSpeed = preConn ? conn.download - preConn.download : 0 - const uploadSpeed = preConn ? conn.upload - preConn.upload : 0 + const preConn = prevConnMap.get(conn.id) return { ...conn, isActive: true, - downloadSpeed: downloadSpeed, - uploadSpeed: uploadSpeed + downloadSpeed: preConn ? conn.download - preConn.download : 0, + uploadSpeed: preConn ? conn.upload - preConn.upload : 0 } }) const closedConns = differenceWith(allConns, activeConns, (a, b) => a.id === b.id).map( - (conn) => { - return { - ...conn, - isActive: false, - downloadSpeed: 0, - uploadSpeed: 0 - } - } + (conn) => ({ + ...conn, + isActive: false, + downloadSpeed: 0, + uploadSpeed: 0 + }) ) setActiveConnections(activeConns) setClosedConnections(closedConns) setAllConnections(allConns.slice(-(activeConns.length + 200))) + cachedConnections = allConns + } - cachedConnections = allConnections - }) + if (!isPaused) { + window.electron.ipcRenderer.on('mihomoConnections', handler) + } return (): void => { window.electron.ipcRenderer.removeAllListeners('mihomoConnections') } - }, [allConnections, activeConnections, closedConnections, isPaused]) - const togglePause = () => { - setIsPaused(!isPaused) - } + }, [isPaused]) + const togglePause = useCallback(() => { + setIsPaused((prev) => !prev) + }, []) return ( { color="primary" variant="flat" showOutline={false} - content={`${filteredConnections.length}`} + content={filteredConnections.length} > +
+
+ ) +} + +export const ToastProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [currentToasts, setCurrentToasts] = useState([]) + + const handleRemove = useCallback((id: string) => { + removeToast(id) + }, []) + + useEffect(() => { + const listener: ToastListener = (newToasts) => setCurrentToasts(newToasts) + listeners.push(listener) + return () => { + listeners = listeners.filter((l) => l !== listener) + } + }, []) + + return ( + <> + {children} + {currentToasts.length > 0 && + createPortal( +
+ {currentToasts.map((t) => ( + + ))} +
, + document.body + )} + + ) +} diff --git a/src/renderer/src/components/override/edit-file-modal.tsx b/src/renderer/src/components/override/edit-file-modal.tsx index 5f51284..d5b949e 100644 --- a/src/renderer/src/components/override/edit-file-modal.tsx +++ b/src/renderer/src/components/override/edit-file-modal.tsx @@ -1,4 +1,5 @@ import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@heroui/react' +import { toast } from '@renderer/components/base/toast' import React, { useEffect, useState } from 'react' import { BaseEditor } from '../base/base-editor' import { getOverride, restartCore, setOverride } from '@renderer/utils/ipc' @@ -58,7 +59,7 @@ const EditFileModal: React.FC = (props) => { await restartCore() onClose() } catch (e) { - alert(e) + toast.error(String(e)) } }} > diff --git a/src/renderer/src/components/override/override-item.tsx b/src/renderer/src/components/override/override-item.tsx index 500de82..708ac76 100644 --- a/src/renderer/src/components/override/override-item.tsx +++ b/src/renderer/src/components/override/override-item.tsx @@ -8,6 +8,7 @@ import { DropdownMenu, DropdownTrigger } from '@heroui/react' +import { toast } from '@renderer/components/base/toast' import { IoMdMore, IoMdRefresh } from 'react-icons/io' import dayjs from '@renderer/utils/dayjs' import React, { Key, useMemo, useState } from 'react' @@ -197,7 +198,7 @@ const OverrideItem: React.FC = (props) => { await addOverrideItem(info) await restartCore() } catch (e) { - alert(e) + toast.error(String(e)) } finally { setUpdating(false) } diff --git a/src/renderer/src/components/profiles/edit-info-modal.tsx b/src/renderer/src/components/profiles/edit-info-modal.tsx index 354afa3..3caf3ff 100644 --- a/src/renderer/src/components/profiles/edit-info-modal.tsx +++ b/src/renderer/src/components/profiles/edit-info-modal.tsx @@ -13,6 +13,7 @@ import { DropdownMenu, DropdownItem } from '@heroui/react' +import { toast } from '@renderer/components/base/toast' import React, { useState } from 'react' import SettingItem from '../base/base-setting-item' import { useOverrideConfig } from '@renderer/hooks/use-override-config' @@ -49,7 +50,7 @@ const EditInfoModal: React.FC = (props) => { await restartCore() onClose() } catch (e) { - alert(e) + toast.error(String(e)) } } diff --git a/src/renderer/src/components/profiles/edit-rules-modal.tsx b/src/renderer/src/components/profiles/edit-rules-modal.tsx index 2387c54..f5a9f31 100644 --- a/src/renderer/src/components/profiles/edit-rules-modal.tsx +++ b/src/renderer/src/components/profiles/edit-rules-modal.tsx @@ -22,6 +22,7 @@ import yaml from 'js-yaml' import { IoMdTrash, IoMdArrowUp, IoMdArrowDown, IoMdUndo } from 'react-icons/io' import { MdVerticalAlignTop, MdVerticalAlignBottom } from 'react-icons/md' import { platform } from '@renderer/utils/init' +import { toast } from '@renderer/components/base/toast' interface Props { id: string @@ -642,7 +643,7 @@ const EditRulesModal: React.FC = (props) => { await setRuleStr(id, ruleYaml); onClose(); } catch (e) { - alert(t('profiles.editRules.saveError') + ': ' + (e instanceof Error ? e.message : String(e))); + toast.error(t('profiles.editRules.saveError') + ': ' + (e instanceof Error ? e.message : String(e))); } }, [prependRules, deletedRules, rules, appendRules, id, onClose, t]) @@ -688,7 +689,7 @@ const EditRulesModal: React.FC = (props) => { } if (newRule.type !== 'MATCH' && newRule.payload.trim() !== '' && !validateRulePayload(newRule.type, newRule.payload)) { - alert(t('profiles.editRules.invalidPayload') + ': ' + getRuleExample(newRule.type)); + toast.error(t('profiles.editRules.invalidPayload') + ': ' + getRuleExample(newRule.type)); return; } diff --git a/src/renderer/src/components/resources/geo-data.tsx b/src/renderer/src/components/resources/geo-data.tsx index 34d3b36..b30cff0 100644 --- a/src/renderer/src/components/resources/geo-data.tsx +++ b/src/renderer/src/components/resources/geo-data.tsx @@ -1,4 +1,5 @@ import { Button, Input, Switch, Tab, Tabs } from '@heroui/react' +import { toast } from '@renderer/components/base/toast' import SettingCard from '@renderer/components/base/base-setting-card' import SettingItem from '@renderer/components/base/base-setting-item' import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config' @@ -123,7 +124,7 @@ const GeoData: React.FC = () => { await mihomoUpgradeGeo() new Notification(t('resources.geoData.updateSuccess')) } catch (e) { - alert(e) + toast.error(String(e)) } finally { setUpdating(false) } diff --git a/src/renderer/src/components/resources/proxy-provider.tsx b/src/renderer/src/components/resources/proxy-provider.tsx index f970014..9bf9058 100644 --- a/src/renderer/src/components/resources/proxy-provider.tsx +++ b/src/renderer/src/components/resources/proxy-provider.tsx @@ -9,6 +9,7 @@ import useSWR from 'swr' import SettingCard from '../base/base-setting-card' import SettingItem from '../base/base-setting-item' import { Button, Chip } from '@heroui/react' +import { toast } from '@renderer/components/base/toast' import { IoMdRefresh } from 'react-icons/io' import { CgLoadbarDoc } from 'react-icons/cg' import { MdEditDocument } from 'react-icons/md' @@ -67,7 +68,7 @@ const ProxyProvider: React.FC = () => { await mihomoUpdateProxyProviders(name) mutate() } catch (e) { - alert(e) + toast.error(String(e)) } finally { setUpdating((prev) => { prev[index] = false diff --git a/src/renderer/src/components/resources/rule-provider.tsx b/src/renderer/src/components/resources/rule-provider.tsx index 21604ee..2656cfe 100644 --- a/src/renderer/src/components/resources/rule-provider.tsx +++ b/src/renderer/src/components/resources/rule-provider.tsx @@ -10,6 +10,7 @@ import useSWR from 'swr' import SettingCard from '../base/base-setting-card' import SettingItem from '../base/base-setting-item' import { Button, Chip } from '@heroui/react' +import { toast } from '@renderer/components/base/toast' import { IoMdRefresh } from 'react-icons/io' import { CgLoadbarDoc } from 'react-icons/cg' import { MdEditDocument } from 'react-icons/md' @@ -71,7 +72,7 @@ const RuleProvider: React.FC = () => { await mihomoUpdateRuleProviders(name) mutate() } catch (e) { - alert(e) + toast.error(String(e)) } finally { setUpdating((prev) => { prev[index] = false diff --git a/src/renderer/src/components/settings/actions.tsx b/src/renderer/src/components/settings/actions.tsx index b5dedf1..379293a 100644 --- a/src/renderer/src/components/settings/actions.tsx +++ b/src/renderer/src/components/settings/actions.tsx @@ -1,5 +1,6 @@ import { Button, Tooltip } from '@heroui/react' import SettingCard from '../base/base-setting-card' +import { toast } from '@renderer/components/base/toast' import SettingItem from '../base/base-setting-item' import { checkUpdate, @@ -69,7 +70,7 @@ const Actions: React.FC = () => { }) } } catch (e) { - alert(e) + toast.error(String(e)) } finally { setCheckingUpdate(false) } diff --git a/src/renderer/src/components/settings/general-config.tsx b/src/renderer/src/components/settings/general-config.tsx index dfc193e..92b7a18 100644 --- a/src/renderer/src/components/settings/general-config.tsx +++ b/src/renderer/src/components/settings/general-config.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from 'react' import SettingCard from '../base/base-setting-card' +import { toast } from '@renderer/components/base/toast' import SettingItem from '../base/base-setting-item' import { Button, Input, Select, SelectItem, Switch, Tab, Tabs, Tooltip } from '@heroui/react' import { BiCopy, BiSolidFileImport } from 'react-icons/bi' @@ -105,7 +106,7 @@ const GeneralConfig: React.FC = () => { await patchAppConfig({ disableHardwareAcceleration: pendingHardwareAccelValue }) await relaunchApp() } catch (e) { - alert(e) + toast.error(String(e)) setIsRelaunching(false) } }} @@ -151,7 +152,7 @@ const GeneralConfig: React.FC = () => { await disableAutoRun() } } catch (e) { - alert(e) + toast.error(String(e)) } finally { mutateEnable() } @@ -248,7 +249,7 @@ const GeneralConfig: React.FC = () => { envType: Array.from(v) as ('bash' | 'cmd' | 'powershell')[] }) } catch (e) { - alert(e) + toast.error(String(e)) } }} > @@ -413,7 +414,7 @@ const GeneralConfig: React.FC = () => { await patchAppConfig({ useWindowFrame: v }) await relaunchApp() } catch (e) { - alert(e) + toast.error(String(e)) setIsRelaunching(false) } }, 1000)} @@ -503,7 +504,7 @@ const GeneralConfig: React.FC = () => { await fetchThemes() setCustomThemes(await resolveThemes()) } catch (e) { - alert(e) + toast.error(String(e)) } finally { setFetching(false) } @@ -523,7 +524,7 @@ const GeneralConfig: React.FC = () => { await importThemes(files) setCustomThemes(await resolveThemes()) } catch (e) { - alert(e) + toast.error(String(e)) } }} > @@ -555,7 +556,7 @@ const GeneralConfig: React.FC = () => { try { await patchAppConfig({ customTheme: v.currentKey as string }) } catch (e) { - alert(e) + toast.error(String(e)) } }} > diff --git a/src/renderer/src/components/settings/local-backup-config.tsx b/src/renderer/src/components/settings/local-backup-config.tsx index 9cb2915..bd4fe4d 100644 --- a/src/renderer/src/components/settings/local-backup-config.tsx +++ b/src/renderer/src/components/settings/local-backup-config.tsx @@ -1,5 +1,6 @@ import React, { useState } from 'react' import SettingCard from '../base/base-setting-card' +import { toast } from '@renderer/components/base/toast' import SettingItem from '../base/base-setting-item' import { Button, useDisclosure } from '@heroui/react' import { exportLocalBackup, importLocalBackup } from '@renderer/utils/ipc' @@ -22,7 +23,7 @@ const LocalBackupConfig: React.FC = () => { }) } } catch (e) { - alert(e) + toast.error(String(e)) } finally { setExporting(false) } @@ -38,7 +39,7 @@ const LocalBackupConfig: React.FC = () => { }) } } catch (e) { - alert(t('common.error.importFailed', { error: e })) + toast.error(t('common.error.importFailed', { error: e })) } finally { setImporting(false) onClose() diff --git a/src/renderer/src/components/settings/mihomo-config.tsx b/src/renderer/src/components/settings/mihomo-config.tsx index 239ba5b..9b1020b 100644 --- a/src/renderer/src/components/settings/mihomo-config.tsx +++ b/src/renderer/src/components/settings/mihomo-config.tsx @@ -1,5 +1,6 @@ import React, { useState } from 'react' import SettingCard from '../base/base-setting-card' +import { toast } from '@renderer/components/base/toast' import SettingItem from '../base/base-setting-item' import { Button, Input, Select, SelectItem, Switch, Tooltip } from '@heroui/react' import { useAppConfig } from '@renderer/hooks/use-app-config' @@ -122,7 +123,7 @@ const MihomoConfig: React.FC = () => { await navigator.clipboard.writeText(`${url}/raw/clash-party.yaml`) } } catch (e) { - alert(e) + toast.error(String(e)) } }} > @@ -176,7 +177,7 @@ const MihomoConfig: React.FC = () => { }) await restartCore() } catch (e) { - alert(e) + toast.error(String(e)) } }} > @@ -208,7 +209,7 @@ const MihomoConfig: React.FC = () => { await patchAppConfig({ diffWorkDir: v }) await restartCore() } catch (e) { - alert(e) + toast.error(String(e)) } }} /> diff --git a/src/renderer/src/components/settings/shortcut-config.tsx b/src/renderer/src/components/settings/shortcut-config.tsx index c5e2404..2e3286b 100644 --- a/src/renderer/src/components/settings/shortcut-config.tsx +++ b/src/renderer/src/components/settings/shortcut-config.tsx @@ -1,5 +1,6 @@ import { Button, Input } from '@heroui/react' import SettingCard from '../base/base-setting-card' +import { toast } from '@renderer/components/base/toast' import SettingItem from '../base/base-setting-item' import { useAppConfig } from '@renderer/hooks/use-app-config' import React, { KeyboardEvent, useState } from 'react' @@ -213,10 +214,10 @@ const ShortcutInput: React.FC<{ await patchAppConfig({ [action]: inputValue }) window.electron.ipcRenderer.send('updateTrayMenu') } else { - alert(t('common.error.shortcutRegistrationFailed')) + toast.error(t('common.error.shortcutRegistrationFailed')) } } catch (e) { - alert(t('common.error.shortcutRegistrationFailedWithError', { error: e })) + toast.error(t('common.error.shortcutRegistrationFailedWithError', { error: e })) } }} > diff --git a/src/renderer/src/components/settings/substore-config.tsx b/src/renderer/src/components/settings/substore-config.tsx index 61e3d2d..7ad8477 100644 --- a/src/renderer/src/components/settings/substore-config.tsx +++ b/src/renderer/src/components/settings/substore-config.tsx @@ -1,5 +1,6 @@ import React, { useState } from 'react' import SettingCard from '@renderer/components/base/base-setting-card' +import { toast } from '@renderer/components/base/toast' import SettingItem from '@renderer/components/base/base-setting-item' import { Button, Input, Switch } from '@heroui/react' import { @@ -55,7 +56,7 @@ const SubStoreConfig: React.FC = () => { await stopSubStoreBackendServer() } } catch (e) { - alert(e) + toast.error(String(e)) } }} /> @@ -76,7 +77,7 @@ const SubStoreConfig: React.FC = () => { await startSubStoreFrontendServer() await startSubStoreBackendServer() } catch (e) { - alert(e) + toast.error(String(e)) } }} /> @@ -94,7 +95,7 @@ const SubStoreConfig: React.FC = () => { await startSubStoreBackendServer() } } catch (e) { - alert(e) + toast.error(String(e)) } }} /> @@ -123,7 +124,7 @@ const SubStoreConfig: React.FC = () => { await patchAppConfig({ useProxyInSubStore: v }) await startSubStoreBackendServer() } catch (e) { - alert(e) + toast.error(String(e)) } }} /> @@ -144,7 +145,7 @@ const SubStoreConfig: React.FC = () => { }) new Notification(t('common.notification.restartRequired')) } else { - alert(t('common.error.invalidCron')) + toast.warning(t('common.error.invalidCron')) } }} > @@ -177,7 +178,7 @@ const SubStoreConfig: React.FC = () => { }) new Notification(t('common.notification.restartRequired')) } else { - alert(t('common.error.invalidCron')) + toast.warning(t('common.error.invalidCron')) } }} > @@ -210,7 +211,7 @@ const SubStoreConfig: React.FC = () => { }) new Notification(t('common.notification.restartRequired')) } else { - alert(t('common.error.invalidCron')) + toast.warning(t('common.error.invalidCron')) } }} > diff --git a/src/renderer/src/components/settings/webdav-config.tsx b/src/renderer/src/components/settings/webdav-config.tsx index 6575827..5c28785 100644 --- a/src/renderer/src/components/settings/webdav-config.tsx +++ b/src/renderer/src/components/settings/webdav-config.tsx @@ -1,5 +1,6 @@ import React, { useState } from 'react' import SettingCard from '../base/base-setting-card' +import { toast } from '@renderer/components/base/toast' import SettingItem from '../base/base-setting-item' import { Button, Input, Select, SelectItem } from '@heroui/react' import { listWebdavBackups, webdavBackup, reinitWebdavBackupScheduler } from '@renderer/utils/ipc' @@ -47,7 +48,7 @@ const WebdavConfig: React.FC = () => { body: t('webdav.notification.backupSuccess.body') }) } catch (e) { - alert(e) + toast.error(String(e)) } finally { setBackuping(false) } @@ -60,7 +61,7 @@ const WebdavConfig: React.FC = () => { setFilenames(filenames) setRestoreOpen(true) } catch (e) { - alert(t('common.error.getBackupListFailed', { error: e })) + toast.error(t('common.error.getBackupListFailed', { error: e })) } finally { setRestoring(false) } @@ -156,7 +157,7 @@ const WebdavConfig: React.FC = () => { new Notification(t('webdav.notification.cronUpdateFailed')) } } else { - alert(t('common.error.invalidCron')) + toast.warning(t('common.error.invalidCron')) } }} > diff --git a/src/renderer/src/components/settings/webdav-restore-modal.tsx b/src/renderer/src/components/settings/webdav-restore-modal.tsx index 787eafc..23fb322 100644 --- a/src/renderer/src/components/settings/webdav-restore-modal.tsx +++ b/src/renderer/src/components/settings/webdav-restore-modal.tsx @@ -1,4 +1,5 @@ import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@heroui/react' +import { toast } from '@renderer/components/base/toast' import { relaunchApp, webdavDelete, webdavRestore } from '@renderer/utils/ipc' import React, { useState } from 'react' import { MdDeleteForever } from 'react-icons/md' @@ -43,7 +44,7 @@ const WebdavRestoreModal: React.FC = (props) => { await webdavRestore(filename) await relaunchApp() } catch (e) { - alert(t('common.error.restoreFailed', { error: e })) + toast.error(t('common.error.restoreFailed', { error: e })) } finally { setRestoring(false) } @@ -61,7 +62,7 @@ const WebdavRestoreModal: React.FC = (props) => { await webdavDelete(filename) setFilenames(filenames.filter((name) => name !== filename)) } catch (e) { - alert(t('common.error.deleteFailed', { error: e })) + toast.error(t('common.error.deleteFailed', { error: e })) } }} > diff --git a/src/renderer/src/components/sider/dns-card.tsx b/src/renderer/src/components/sider/dns-card.tsx index a11b35d..06a5691 100644 --- a/src/renderer/src/components/sider/dns-card.tsx +++ b/src/renderer/src/components/sider/dns-card.tsx @@ -1,4 +1,5 @@ import { Button, Card, CardBody, CardFooter, Tooltip } from '@heroui/react' +import { toast } from '@renderer/components/base/toast' import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config' import BorderSwitch from '@renderer/components/base/border-swtich' import { LuServer } from 'react-icons/lu' @@ -39,7 +40,7 @@ const DNSCard: React.FC = (props) => { await patchControledMihomoConfig({}) await restartCore() } catch (e) { - alert(e) + toast.error(String(e)) } } diff --git a/src/renderer/src/components/sider/mihomo-core-card.tsx b/src/renderer/src/components/sider/mihomo-core-card.tsx index 2f1d898..dea4d9f 100644 --- a/src/renderer/src/components/sider/mihomo-core-card.tsx +++ b/src/renderer/src/components/sider/mihomo-core-card.tsx @@ -1,4 +1,5 @@ import { Button, Card, CardBody, CardFooter, Tooltip } from '@heroui/react' +import { toast } from '@renderer/components/base/toast' import { calcTraffic } from '@renderer/utils/calc' import { mihomoVersion, restartCore } from '@renderer/utils/ipc' import React, { useEffect, useState } from 'react' @@ -112,7 +113,7 @@ const MihomoCoreCard: React.FC = (props) => { try { await restartCore() } catch (e) { - alert(e) + toast.error(String(e)) } finally { mutate() } diff --git a/src/renderer/src/components/sider/sniff-card.tsx b/src/renderer/src/components/sider/sniff-card.tsx index 4bd4ef4..51dc805 100644 --- a/src/renderer/src/components/sider/sniff-card.tsx +++ b/src/renderer/src/components/sider/sniff-card.tsx @@ -1,4 +1,5 @@ import { Button, Card, CardBody, CardFooter, Tooltip } from '@heroui/react' +import { toast } from '@renderer/components/base/toast' import BorderSwitch from '@renderer/components/base/border-swtich' import { RiScan2Fill } from 'react-icons/ri' import { useLocation, useNavigate } from 'react-router-dom' @@ -39,7 +40,7 @@ const SniffCard: React.FC = (props) => { await patchControledMihomoConfig({}) await restartCore() } catch (e) { - alert(e) + toast.error(String(e)) } } diff --git a/src/renderer/src/components/sider/sysproxy-switcher.tsx b/src/renderer/src/components/sider/sysproxy-switcher.tsx index 25490b0..d44d059 100644 --- a/src/renderer/src/components/sider/sysproxy-switcher.tsx +++ b/src/renderer/src/components/sider/sysproxy-switcher.tsx @@ -1,4 +1,5 @@ import { Button, Card, CardBody, CardFooter, Tooltip } from '@heroui/react' +import { toast } from '@renderer/components/base/toast' import BorderSwitch from '@renderer/components/base/border-swtich' import { useLocation, useNavigate } from 'react-router-dom' import { useAppConfig } from '@renderer/hooks/use-app-config' @@ -54,7 +55,7 @@ const SysproxySwitcher: React.FC = (props) => { await patchAppConfig({ sysProxy: { enable: previousState } }) // 回滚图标 updateTrayIconImmediate(previousState, tunEnabled) - alert(e) + toast.error(String(e)) } } diff --git a/src/renderer/src/components/updater/updater-modal.tsx b/src/renderer/src/components/updater/updater-modal.tsx index e31c1a8..623f532 100644 --- a/src/renderer/src/components/updater/updater-modal.tsx +++ b/src/renderer/src/components/updater/updater-modal.tsx @@ -1,12 +1,5 @@ -import { - Modal, - ModalContent, - ModalHeader, - ModalBody, - ModalFooter, - Button, - Code -} from '@heroui/react' +import { Button, Code, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader } from '@heroui/react' +import { toast } from '@renderer/components/base/toast' import ReactMarkdown from 'react-markdown' import React, { useState } from 'react' import { downloadAndInstallUpdate } from '@renderer/utils/ipc' @@ -27,7 +20,7 @@ const UpdaterModal: React.FC = (props) => { try { await downloadAndInstallUpdate(version) } catch (e) { - alert(e) + toast.error(String(e)) } } @@ -82,7 +75,7 @@ const UpdaterModal: React.FC = (props) => { await onUpdate() onClose() } catch (e) { - alert(e) + toast.error(String(e)) } finally { setDownloading(false) } diff --git a/src/renderer/src/hooks/use-app-config.tsx b/src/renderer/src/hooks/use-app-config.tsx index 236ab2a..5ad1912 100644 --- a/src/renderer/src/hooks/use-app-config.tsx +++ b/src/renderer/src/hooks/use-app-config.tsx @@ -1,4 +1,5 @@ import React, { createContext, useContext, ReactNode } from 'react' +import { toast } from '@renderer/components/base/toast' import useSWR from 'swr' import { getAppConfig, patchAppConfig as patch } from '@renderer/utils/ipc' @@ -17,7 +18,7 @@ export const AppConfigProvider: React.FC<{ children: ReactNode }> = ({ children try { await patch(value) } catch (e) { - alert(e) + toast.error(String(e)) } finally { mutateAppConfig() } diff --git a/src/renderer/src/hooks/use-controled-mihomo-config.tsx b/src/renderer/src/hooks/use-controled-mihomo-config.tsx index 091941c..882b3fe 100644 --- a/src/renderer/src/hooks/use-controled-mihomo-config.tsx +++ b/src/renderer/src/hooks/use-controled-mihomo-config.tsx @@ -1,4 +1,5 @@ import React, { createContext, useContext, ReactNode } from 'react' +import { toast } from '@renderer/components/base/toast' import useSWR from 'swr' import { getControledMihomoConfig, patchControledMihomoConfig as patch } from '@renderer/utils/ipc' @@ -22,7 +23,7 @@ export const ControledMihomoConfigProvider: React.FC<{ children: ReactNode }> = try { await patch(value) } catch (e) { - alert(e) + toast.error(String(e)) } finally { mutateControledMihomoConfig() } diff --git a/src/renderer/src/hooks/use-override-config.tsx b/src/renderer/src/hooks/use-override-config.tsx index 82c094e..75b087f 100644 --- a/src/renderer/src/hooks/use-override-config.tsx +++ b/src/renderer/src/hooks/use-override-config.tsx @@ -1,4 +1,5 @@ import React, { createContext, useContext, ReactNode } from 'react' +import { toast } from '@renderer/components/base/toast' import useSWR from 'swr' import { getOverrideConfig, @@ -28,7 +29,7 @@ export const OverrideConfigProvider: React.FC<{ children: ReactNode }> = ({ chil try { await set(config) } catch (e) { - alert(e) + toast.error(String(e)) } finally { mutateOverrideConfig() } @@ -38,7 +39,7 @@ export const OverrideConfigProvider: React.FC<{ children: ReactNode }> = ({ chil try { await add(item) } catch (e) { - alert(e) + toast.error(String(e)) } finally { mutateOverrideConfig() } @@ -48,7 +49,7 @@ export const OverrideConfigProvider: React.FC<{ children: ReactNode }> = ({ chil try { await remove(id) } catch (e) { - alert(e) + toast.error(String(e)) } finally { mutateOverrideConfig() } @@ -58,7 +59,7 @@ export const OverrideConfigProvider: React.FC<{ children: ReactNode }> = ({ chil try { await update(item) } catch (e) { - alert(e) + toast.error(String(e)) } finally { mutateOverrideConfig() } diff --git a/src/renderer/src/hooks/use-profile-config.tsx b/src/renderer/src/hooks/use-profile-config.tsx index a6d0cb7..f1954f8 100644 --- a/src/renderer/src/hooks/use-profile-config.tsx +++ b/src/renderer/src/hooks/use-profile-config.tsx @@ -1,4 +1,5 @@ import React, { createContext, ReactNode, useContext } from 'react' +import { toast } from '@renderer/components/base/toast' import useSWR from 'swr' import { addProfileItem as add, @@ -32,7 +33,7 @@ export const ProfileConfigProvider: React.FC<{ children: ReactNode }> = ({ child try { await set(config) } catch (e) { - alert(e) + toast.error(String(e)) } finally { mutateProfileConfig() window.electron.ipcRenderer.send('updateTrayMenu') @@ -43,7 +44,7 @@ export const ProfileConfigProvider: React.FC<{ children: ReactNode }> = ({ child try { await add(item) } catch (e) { - alert(e) + toast.error(String(e)) } finally { mutateProfileConfig() window.electron.ipcRenderer.send('updateTrayMenu') @@ -54,7 +55,7 @@ export const ProfileConfigProvider: React.FC<{ children: ReactNode }> = ({ child try { await remove(id) } catch (e) { - alert(e) + toast.error(String(e)) } finally { mutateProfileConfig() window.electron.ipcRenderer.send('updateTrayMenu') @@ -65,7 +66,7 @@ export const ProfileConfigProvider: React.FC<{ children: ReactNode }> = ({ child try { await update(item) } catch (e) { - alert(e) + toast.error(String(e)) } finally { mutateProfileConfig() window.electron.ipcRenderer.send('updateTrayMenu') @@ -107,7 +108,7 @@ export const ProfileConfigProvider: React.FC<{ children: ReactNode }> = ({ child if (errorMsg.includes('reply was never sent')) { setTimeout(() => mutateProfileConfig(), 1000) } else { - alert(`切换 Profile 失败: ${errorMsg}`) + toast.error(errorMsg, '切换配置失败') mutateProfileConfig() } } finally { diff --git a/src/renderer/src/main.tsx b/src/renderer/src/main.tsx index a391180..2f83753 100644 --- a/src/renderer/src/main.tsx +++ b/src/renderer/src/main.tsx @@ -14,6 +14,7 @@ import { OverrideConfigProvider } from './hooks/use-override-config' import { ProfileConfigProvider } from './hooks/use-profile-config' import { RulesProvider } from './hooks/use-rules' import { GroupsProvider } from './hooks/use-groups' +import { ToastProvider } from './components/base/toast' import './i18n' let F12Count = 0 @@ -53,7 +54,9 @@ init().then(() => { - + + + diff --git a/src/renderer/src/pages/dns.tsx b/src/renderer/src/pages/dns.tsx index a96209b..7cc4c14 100644 --- a/src/renderer/src/pages/dns.tsx +++ b/src/renderer/src/pages/dns.tsx @@ -1,5 +1,6 @@ import { Button, Tab, Input, Switch, Tabs, Divider } from '@heroui/react' import BasePage from '@renderer/components/base/base-page' +import { toast } from '@renderer/components/base/toast' import { MdDeleteForever } from 'react-icons/md' import SettingCard from '@renderer/components/base/base-setting-card' import SettingItem from '@renderer/components/base/base-setting-item' @@ -145,7 +146,7 @@ const DNS: React.FC = () => { await restartCore() } } catch (e) { - alert(e) + toast.error(String(e)) } } diff --git a/src/renderer/src/pages/mihomo.tsx b/src/renderer/src/pages/mihomo.tsx index 6ce119c..0cc1bb7 100644 --- a/src/renderer/src/pages/mihomo.tsx +++ b/src/renderer/src/pages/mihomo.tsx @@ -1,5 +1,6 @@ import { Button, Divider, Input, Select, SelectItem, Switch, Tooltip, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, useDisclosure, Spinner, Chip } from '@heroui/react' import BasePage from '@renderer/components/base/base-page' +import { toast } from '@renderer/components/base/toast' import SettingCard from '@renderer/components/base/base-setting-card' import SettingItem from '@renderer/components/base/base-setting-item' import { useAppConfig } from '@renderer/hooks/use-app-config' @@ -308,7 +309,7 @@ const Mihomo: React.FC = () => { if (errorMessage.includes('配置检查失败') || errorMessage.includes('Profile Check Failed')) { await showDetailedError(t('mihomo.error.profileCheckFailed'), errorMessage) } else { - alert(errorMessage) + toast.error(errorMessage) } } finally { PubSub.publish('mihomo-core-changed') @@ -324,7 +325,7 @@ const Mihomo: React.FC = () => { } catch (error) { console.error('Failed to fetch tags:', error) setTags([]) - alert(t('mihomo.error.fetchTagsFailed')) + toast.error(t('mihomo.error.fetchTagsFailed')) } finally { setLoadingTags(false) } @@ -355,7 +356,7 @@ const Mihomo: React.FC = () => { new Notification(t('mihomo.coreUpgradeSuccess')) } catch (error) { console.error('Failed to install specific core:', error) - alert(t('mihomo.error.installCoreFailed')) + toast.error(t('mihomo.error.installCoreFailed')) } finally { setInstalling(false) } @@ -493,7 +494,7 @@ const Mihomo: React.FC = () => { if (typeof e === 'string' && e.includes('already using latest version')) { new Notification(t('mihomo.alreadyLatestVersion')) } else { - alert(e) + toast.error(String(e)) } } finally { setUpgrading(false) diff --git a/src/renderer/src/pages/override.tsx b/src/renderer/src/pages/override.tsx index 4f0ff1a..6117b52 100644 --- a/src/renderer/src/pages/override.tsx +++ b/src/renderer/src/pages/override.tsx @@ -8,6 +8,7 @@ import { Input } from '@heroui/react' import BasePage from '@renderer/components/base/base-page' +import { toast } from '@renderer/components/base/toast' import { getFilePath, readTextFile } from '@renderer/utils/ipc' import { useEffect, useRef, useState } from 'react' import { MdContentPaste } from 'react-icons/md' @@ -115,7 +116,7 @@ const Override: React.FC = () => { setFileOver(false) } } else { - alert(tRef.current('override.unsupportedFileType')) + toast.warning(tRef.current('override.unsupportedFileType')) } } setFileOver(false) @@ -222,7 +223,7 @@ const Override: React.FC = () => { }) } } catch (e) { - alert(e) + toast.error(String(e)) } } else if (key === 'new-yaml') { await addOverrideItem({ diff --git a/src/renderer/src/pages/profiles.tsx b/src/renderer/src/pages/profiles.tsx index 1355ec9..6570b6f 100644 --- a/src/renderer/src/pages/profiles.tsx +++ b/src/renderer/src/pages/profiles.tsx @@ -11,6 +11,7 @@ import { Tooltip } from '@heroui/react' import BasePage from '@renderer/components/base/base-page' +import { toast } from '@renderer/components/base/toast' import ProfileItem from '@renderer/components/profiles/profile-item' import { useProfileConfig } from '@renderer/hooks/use-profile-config' import { useAppConfig } from '@renderer/hooks/use-app-config' @@ -197,10 +198,10 @@ const Profiles: React.FC = () => { const content = await readTextFile(path) await addProfileItemRef.current({ name: file.name, type: 'local', file: content }) } catch (e) { - alert(e) + toast.error(String(e)) } } else { - alert(tRef.current('profiles.error.unsupportedFileType')) + toast.warning(tRef.current('profiles.error.unsupportedFileType')) } } setFileOver(false) @@ -345,7 +346,7 @@ const Profiles: React.FC = () => { useProxy }) } catch (e) { - alert(e) + toast.error(String(e)) } finally { setSubStoreImporting(false) } @@ -366,7 +367,7 @@ const Profiles: React.FC = () => { useProxy }) } catch (e) { - alert(e) + toast.error(String(e)) } finally { setSubStoreImporting(false) } @@ -398,7 +399,7 @@ const Profiles: React.FC = () => { await addProfileItem({ name: fileName, type: 'local', file: content }) } } catch (e) { - alert(e) + toast.error(String(e)) } } else if (key === 'new') { await addProfileItem({ diff --git a/src/renderer/src/pages/sniffer.tsx b/src/renderer/src/pages/sniffer.tsx index 3bf13c6..f59cc41 100644 --- a/src/renderer/src/pages/sniffer.tsx +++ b/src/renderer/src/pages/sniffer.tsx @@ -1,5 +1,6 @@ import { Button, Divider, Input, Switch } from '@heroui/react' import BasePage from '@renderer/components/base/base-page' +import { toast } from '@renderer/components/base/toast' import SettingCard from '@renderer/components/base/base-setting-card' import SettingItem from '@renderer/components/base/base-setting-item' import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config' @@ -70,7 +71,7 @@ const Sniffer: React.FC = () => { await restartCore() } } catch (e) { - alert(e) + toast.error(String(e)) } } diff --git a/src/renderer/src/pages/sysproxy.tsx b/src/renderer/src/pages/sysproxy.tsx index 4c5d2d7..612f7ef 100644 --- a/src/renderer/src/pages/sysproxy.tsx +++ b/src/renderer/src/pages/sysproxy.tsx @@ -1,5 +1,6 @@ import { Button, Input, Tab, Tabs } from '@heroui/react' import BasePage from '@renderer/components/base/base-page' +import { toast } from '@renderer/components/base/toast' import SettingCard from '@renderer/components/base/base-setting-card' import SettingItem from '@renderer/components/base/base-setting-item' import PacEditorModal from '@renderer/components/sysproxy/pac-editor-modal' @@ -105,7 +106,7 @@ const Sysproxy: React.FC = () => { } catch (e) { setValues({ ...values, enable: previousState }) setChanged(true) - alert(e) + toast.error(String(e)) await patchAppConfig({ sysProxy: { enable: false } }) } diff --git a/src/renderer/src/pages/tun.tsx b/src/renderer/src/pages/tun.tsx index 1fde5f0..362d511 100644 --- a/src/renderer/src/pages/tun.tsx +++ b/src/renderer/src/pages/tun.tsx @@ -1,5 +1,6 @@ import { Button, Input, Switch, Tab, Tabs } from '@heroui/react' import BasePage from '@renderer/components/base/base-page' +import { toast } from '@renderer/components/base/toast' import SettingCard from '@renderer/components/base/base-setting-card' import SettingItem from '@renderer/components/base/base-setting-item' import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config' @@ -112,7 +113,7 @@ const Tun: React.FC = () => { new Notification(t('tun.notifications.firewallResetSuccess')) await restartCore() } catch (e) { - alert(e) + toast.error(String(e)) } finally { setLoading(false) } @@ -133,7 +134,7 @@ const Tun: React.FC = () => { new Notification(t('tun.notifications.coreAuthSuccess')) await restartCore() } catch (e) { - alert(e) + toast.error(String(e)) } }} > diff --git a/src/renderer/src/utils/ipc.ts b/src/renderer/src/utils/ipc.ts index 736797b..772f09d 100644 --- a/src/renderer/src/utils/ipc.ts +++ b/src/renderer/src/utils/ipc.ts @@ -556,10 +556,3 @@ export async function getRuleStr(id: string): Promise { export async function setRuleStr(id: string, str: string): Promise { return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('setRuleStr', id, str)) } - -async function alert(msg: T): Promise { - const msgStr = typeof msg === 'string' ? msg : JSON.stringify(msg) - return await window.electron.ipcRenderer.invoke('alert', msgStr) -} - -window.alert = alert \ No newline at end of file From 972d2fe946e054a362956d7c121774c9daf637c4 Mon Sep 17 00:00:00 2001 From: xmk23333 Date: Mon, 8 Dec 2025 22:32:14 +0800 Subject: [PATCH 5/5] fix: add missing await keywords and refactor duplicate code --- src/main/config/override.ts | 4 +-- src/main/config/smartOverride.ts | 32 +++++------------ src/main/core/manager.ts | 19 +--------- src/main/index.ts | 30 ++++------------ src/main/resolve/backup.ts | 62 +++++++++++--------------------- src/main/utils/init.ts | 10 +++--- 6 files changed, 42 insertions(+), 115 deletions(-) diff --git a/src/main/config/override.ts b/src/main/config/override.ts index 7e7e1ea..16db1a0 100644 --- a/src/main/config/override.ts +++ b/src/main/config/override.ts @@ -40,7 +40,7 @@ export async function addOverrideItem(item: Partial): Promise): Promise { smartCollectorSize ) - // 检查是否已存在 Smart 覆写配置 - const existingOverride = await getOverrideItem(SMART_OVERRIDE_ID) - - if (existingOverride) { - // 如果已存在,更新配置 - await addOverrideItem({ - id: SMART_OVERRIDE_ID, - name: 'Smart Core Override', - type: 'local', - ext: 'js', - global: true, - file: template - }) - } else { - // 如果不存在,创建新的覆写配置 - await addOverrideItem({ - id: SMART_OVERRIDE_ID, - name: 'Smart Core Override', - type: 'local', - ext: 'js', - global: true, - file: template - }) - } + await addOverrideItem({ + id: SMART_OVERRIDE_ID, + name: 'Smart Core Override', + type: 'local', + ext: 'js', + global: true, + file: template + }) } catch (error) { await overrideLogger.error('Failed to create Smart override', error) throw error diff --git a/src/main/core/manager.ts b/src/main/core/manager.ts index bc7dbd2..d076930 100644 --- a/src/main/core/manager.ts +++ b/src/main/core/manager.ts @@ -499,24 +499,7 @@ async function checkProfile(): Promise { } export async function checkTunPermissions(): Promise { - const { core = 'mihomo' } = await getAppConfig() - const corePath = mihomoCorePath(core) - - try { - if (process.platform === 'win32') { - return await checkAdminPrivileges() - } - - if (process.platform === 'darwin' || process.platform === 'linux') { - const { stat } = await import('fs/promises') - const stats = await stat(corePath) - return (stats.mode & 0o4000) !== 0 && stats.uid === 0 - } - } catch { - return false - } - - return false + return checkMihomoCorePermissions() } export async function grantTunPermissions(): Promise { diff --git a/src/main/index.ts b/src/main/index.ts index 12711bb..4ba2fb3 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -7,7 +7,7 @@ import { quitWithoutCore, startCore, stopCore, checkAdminRestartForTun, checkHig import { triggerSysProxy } from './sys/sysproxy' import icon from '../../resources/icon.png?asset' import { createTray, hideDockIcon, showDockIcon } from './resolve/tray' -import { init, initBasic } from './utils/init' +import { init, initBasic, safeShowErrorBox } from './utils/init' import { join } from 'path' import { initShortcut } from './resolve/shortcut' import { spawn, exec } from 'child_process' @@ -23,24 +23,6 @@ import i18next from 'i18next' import { logger } from './utils/logger' import { initWebdavBackupScheduler } from './resolve/backup' -// 错误处理 -function showSafeErrorBox(titleKey: string, message: string): void { - let title: string - try { - title = i18next.t(titleKey) - if (!title || title === titleKey) throw new Error('Translation not ready') - } catch { - const isZh = app.getLocale().startsWith('zh') - const fallbacks: Record = { - 'common.error.initFailed': { zh: '应用初始化失败', en: 'Application initialization failed' }, - 'mihomo.error.coreStartFailed': { zh: '内核启动出错', en: 'Core start failed' }, - 'profiles.error.importFailed': { zh: '配置导入失败', en: 'Profile import failed' }, - 'common.error.adminRequired': { zh: '需要管理员权限', en: 'Administrator privileges required' } - } - title = fallbacks[titleKey] ? (isZh ? fallbacks[titleKey].zh : fallbacks[titleKey].en) : (isZh ? '错误' : 'Error') - } - dialog.showErrorBox(title, message) -} async function fixUserDataPermissions(): Promise { if (process.platform !== 'darwin') return @@ -80,7 +62,7 @@ async function initApp(): Promise { initApp() .catch((e) => { - showSafeErrorBox('common.error.initFailed', `${e}`) + safeShowErrorBox('common.error.initFailed', `${e}`) app.quit() }) @@ -150,7 +132,7 @@ async function checkHighPrivilegeCoreEarly(): Promise { await restartAsAdmin(false) process.exit(0) } catch (error) { - showSafeErrorBox('common.error.adminRequired', `${error}`) + safeShowErrorBox('common.error.adminRequired', `${error}`) process.exit(1) } } else { @@ -234,7 +216,7 @@ app.whenReady().then(async () => { } await initI18n({ lng: appConfig.language }) } catch (e) { - showSafeErrorBox('common.error.initFailed', `${e}`) + safeShowErrorBox('common.error.initFailed', `${e}`) app.quit() } @@ -247,7 +229,7 @@ app.whenReady().then(async () => { await checkAdminRestartForTun() }) } catch (e) { - showSafeErrorBox('mihomo.error.coreStartFailed', `${e}`) + safeShowErrorBox('mihomo.error.coreStartFailed', `${e}`) } try { await startMonitor() @@ -303,7 +285,7 @@ async function handleDeepLink(url: string): Promise { new Notification({ title: i18next.t('profiles.notification.importSuccess') }).show() break } catch (e) { - showSafeErrorBox('profiles.error.importFailed', `${url}\n${e}`) + safeShowErrorBox('profiles.error.importFailed', `${url}\n${e}`) } } } diff --git a/src/main/resolve/backup.ts b/src/main/resolve/backup.ts index 1eb3bdf..639b484 100644 --- a/src/main/resolve/backup.ts +++ b/src/main/resolve/backup.ts @@ -21,7 +21,13 @@ import i18next from 'i18next' let backupCronJob: Cron | null = null -export async function webdavBackup(): Promise { +interface WebDAVContext { + client: ReturnType['createClient']> + webdavDir: string + webdavMaxBackups: number +} + +async function getWebDAVClient(): Promise { const { createClient } = await import('webdav/dist/node/index.js') const { webdavUrl = '', @@ -30,6 +36,17 @@ export async function webdavBackup(): Promise { webdavDir = 'clash-party', webdavMaxBackups = 0 } = await getAppConfig() + + const client = createClient(webdavUrl, { + username: webdavUsername, + password: webdavPassword + }) + + return { client, webdavDir, webdavMaxBackups } +} + +export async function webdavBackup(): Promise { + const { client, webdavDir, webdavMaxBackups } = await getWebDAVClient() const zip = new AdmZip() zip.addLocalFile(appConfigPath()) @@ -44,10 +61,6 @@ export async function webdavBackup(): Promise { const date = new Date() const zipFileName = `${process.platform}_${dayjs(date).format('YYYY-MM-DD_HH-mm-ss')}.zip` - const client = createClient(webdavUrl, { - username: webdavUsername, - password: webdavPassword - }) try { await client.createDirectory(webdavDir) } catch { @@ -92,36 +105,14 @@ export async function webdavBackup(): Promise { } export async function webdavRestore(filename: string): Promise { - const { createClient } = await import('webdav/dist/node/index.js') - const { - webdavUrl = '', - webdavUsername = '', - webdavPassword = '', - webdavDir = 'clash-party' - } = await getAppConfig() - - const client = createClient(webdavUrl, { - username: webdavUsername, - password: webdavPassword - }) + const { client, webdavDir } = await getWebDAVClient() const zipData = await client.getFileContents(`${webdavDir}/${filename}`) const zip = new AdmZip(zipData as Buffer) zip.extractAllTo(dataDir(), true) } export async function listWebdavBackups(): Promise { - const { createClient } = await import('webdav/dist/node/index.js') - const { - webdavUrl = '', - webdavUsername = '', - webdavPassword = '', - webdavDir = 'clash-party' - } = await getAppConfig() - - const client = createClient(webdavUrl, { - username: webdavUsername, - password: webdavPassword - }) + const { client, webdavDir } = await getWebDAVClient() const files = await client.getDirectoryContents(webdavDir, { glob: '*.zip' }) if (Array.isArray(files)) { return files.map((file) => file.basename) @@ -131,18 +122,7 @@ export async function listWebdavBackups(): Promise { } export async function webdavDelete(filename: string): Promise { - const { createClient } = await import('webdav/dist/node/index.js') - const { - webdavUrl = '', - webdavUsername = '', - webdavPassword = '', - webdavDir = 'clash-party' - } = await getAppConfig() - - const client = createClient(webdavUrl, { - username: webdavUsername, - password: webdavPassword - }) + const { client, webdavDir } = await getWebDAVClient() await client.deleteFile(`${webdavDir}/${filename}`) } diff --git a/src/main/utils/init.ts b/src/main/utils/init.ts index 844d021..1bad56c 100644 --- a/src/main/utils/init.ts +++ b/src/main/utils/init.ts @@ -42,7 +42,7 @@ import { } from '../config' import { app, dialog } from 'electron' import { startSSIDCheck } from '../sys/ssid' -import i18next from '../../shared/i18n' +import i18next, { resources } from '../../shared/i18n' import { initLogger } from './logger' let isInitBasicCompleted = false @@ -54,11 +54,9 @@ export function safeShowErrorBox(titleKey: string, message: string): void { title = i18next.t(titleKey) if (!title || title === titleKey) throw new Error('Translation not ready') } catch { - const isZh = process.env.LANG?.startsWith('zh') || process.env.LC_ALL?.startsWith('zh') - const fallbacks: Record = { - 'mihomo.error.coreStartFailed': { zh: '内核启动出错', en: 'Core start failed' } - } - title = fallbacks[titleKey] ? (isZh ? fallbacks[titleKey].zh : fallbacks[titleKey].en) : (isZh ? '错误' : 'Error') + const isZh = app.getLocale().startsWith('zh') + const lang = isZh ? resources['zh-CN'].translation : resources['en-US'].translation + title = lang[titleKey] || (isZh ? '错误' : 'Error') } dialog.showErrorBox(title, message) }