summaryrefslogtreecommitdiff
path: root/src/modules/config
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules/config')
-rw-r--r--src/modules/config/hooks.ts59
-rw-r--r--src/modules/config/utils.ts4
2 files changed, 51 insertions, 12 deletions
diff --git a/src/modules/config/hooks.ts b/src/modules/config/hooks.ts
index e09a4dd..4efaaf5 100644
--- a/src/modules/config/hooks.ts
+++ b/src/modules/config/hooks.ts
@@ -1,6 +1,8 @@
import { useSuspenseQuery } from '@tanstack/react-query';
import * as React from 'react';
+import { useTranslation } from 'react-i18next';
+import type { UpgradeChannel } from '~/api/configs';
import * as logsApi from '~/api/logs';
import { fetchVersion } from '~/api/version';
import {
@@ -14,11 +16,16 @@ import {
upgradeUI,
} from '~/store/configs';
import { openModal } from '~/store/modals';
-import { ClashGeneralConfig, DispatchFn } from '~/store/types';
+import { toast } from '~/store/toast';
+import { ClashGeneralConfig, ClashTunConfig, DispatchFn } from '~/store/types';
+import { unregisterAndReload } from '~/swRegistration';
import { ClashAPIConfig } from '~/types';
const { useCallback, useEffect, useRef, useState } = React;
+// 面板更新成功后到自动刷新之间的间隔,够看清通知即可
+const UI_RELOAD_DELAY_MS = 1500;
+
type UpdateAppConfigFn = (name: string, value: unknown) => void;
function useConfigVersionQuery(apiConfig: ClashAPIConfig) {
@@ -46,7 +53,7 @@ export function useConfigState(configs: ClashGeneralConfig) {
const setTunConfigState = useCallback((name: string, value: any) => {
setConfigStateInternal((prev) => ({
...prev,
- tun: { ...prev.tun, [name]: value },
+ tun: { ...prev.tun, [name]: value } as ClashTunConfig,
}));
}, []);
@@ -68,6 +75,8 @@ export function useConfigPage({
dispatch: DispatchFn;
updateAppConfig: UpdateAppConfigFn;
}) {
+ const { t } = useTranslation();
+
useEffect(() => {
dispatch(fetchConfigs(apiConfig));
}, [apiConfig, dispatch]);
@@ -112,14 +121,14 @@ export function useConfigPage({
return;
}
},
- [apiConfig, dispatch, setConfigState, setTunConfigState]
+ [apiConfig, dispatch, setConfigState, setTunConfigState],
);
const handleInputOnBlur = useCallback(
(
e:
| React.FocusEvent<HTMLSelectElement | HTMLInputElement>
- | React.ChangeEvent<HTMLSelectElement | HTMLInputElement>
+ | React.ChangeEvent<HTMLSelectElement | HTMLInputElement>,
) => {
const { name, value } = e.target;
@@ -144,7 +153,7 @@ export function useConfigPage({
throw new Error(`unknown input name ${name}`);
}
},
- [apiConfig, dispatch, updateAppConfig]
+ [apiConfig, dispatch, updateAppConfig],
);
const handleReloadConfigFile = useCallback(() => {
@@ -155,17 +164,43 @@ export function useConfigPage({
dispatch(restartCore(apiConfig));
}, [apiConfig, dispatch]);
- const handleUpgradeCore = useCallback(() => {
- dispatch(upgradeCore(apiConfig));
- }, [apiConfig, dispatch]);
+ // 正在更新的通道,null 表示空闲;同时用来给两个按钮做 loading / 互斥
+ const [upgradingChannel, setUpgradingChannel] = useState<UpgradeChannel | null>(null);
+
+ const handleUpgradeCore = useCallback(
+ async (channel: UpgradeChannel) => {
+ if (upgradingChannel !== null) return;
+ setUpgradingChannel(channel);
+ const result = await dispatch(upgradeCore(apiConfig, channel));
+ setUpgradingChannel(null);
+ if (result.ok) {
+ toast('success', t('upgrade_core_success'));
+ } else {
+ toast('error', t('upgrade_core_failed', { message: result.message }));
+ }
+ },
+ [apiConfig, dispatch, t, upgradingChannel],
+ );
const handleUpgradeGeo = useCallback(() => {
dispatch(upgradeGeo(apiConfig));
}, [apiConfig, dispatch]);
- const handleUpgradeUI = useCallback(() => {
- dispatch(upgradeUI(apiConfig));
- }, [apiConfig, dispatch]);
+ const [isUpgradingUI, setIsUpgradingUI] = useState(false);
+
+ const handleUpgradeUI = useCallback(async () => {
+ if (isUpgradingUI) return;
+ setIsUpgradingUI(true);
+ const result = await dispatch(upgradeUI(apiConfig));
+ setIsUpgradingUI(false);
+ if (result.ok) {
+ toast('success', t('upgrade_ui_success'));
+ // 留一点时间让通知露个面,再带着清缓存整页刷新
+ setTimeout(unregisterAndReload, UI_RELOAD_DELAY_MS);
+ } else {
+ toast('error', t('upgrade_ui_failed', { message: result.message }));
+ }
+ }, [apiConfig, dispatch, isUpgradingUI, t]);
const handleFlushFakeIPPool = useCallback(() => {
dispatch(flushFakeIPPool(apiConfig));
@@ -179,8 +214,10 @@ export function useConfigPage({
handleReloadConfigFile,
handleRestartCore,
handleUpgradeCore,
+ upgradingChannel,
handleUpgradeGeo,
handleUpgradeUI,
+ isUpgradingUI,
handleFlushFakeIPPool,
versionQuery,
};
diff --git a/src/modules/config/utils.ts b/src/modules/config/utils.ts
index 8300311..444622e 100644
--- a/src/modules/config/utils.ts
+++ b/src/modules/config/utils.ts
@@ -1,6 +1,8 @@
export type SelectOption = [string, string];
+/** 只有这几个端口字段会被渲染成输入框,故意收窄成字面量联合,好让 configState[key] 能推出 number */
+export type PortFieldKey = 'port' | 'socks-port' | 'mixed-port' | 'redir-port' | 'mitm-port';
export type PortField = {
- key: string;
+ key: PortFieldKey;
label: string;
};