refactor: notification system

This commit is contained in:
wonfen
2025-05-04 22:17:08 +08:00
parent e2ad2d23f8
commit 8296675574
43 changed files with 384 additions and 355 deletions

View File

@@ -0,0 +1,71 @@
import React, { useState, useEffect } from 'react';
import { Snackbar, Alert, IconButton, Box } from '@mui/material';
import { CloseRounded } from '@mui/icons-material';
import { subscribeNotices, hideNotice, NoticeItem } from '@/services/noticeService';
export const NoticeManager: React.FC = () => {
const [currentNotices, setCurrentNotices] = useState<NoticeItem[]>([]);
useEffect(() => {
const unsubscribe = subscribeNotices((notices) => {
setCurrentNotices(notices);
});
return () => {
unsubscribe();
};
}, []);
const handleClose = (id: number) => {
hideNotice(id);
};
return (
<Box
sx={{
position: 'fixed',
top: '20px',
right: '20px',
zIndex: 1500,
display: 'flex',
flexDirection: 'column',
gap: '10px',
maxWidth: '360px',
}}
>
{currentNotices.map((notice) => (
<Snackbar
key={notice.id}
open={true}
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
sx={{
position: 'relative',
transform: 'none',
top: 'auto',
right: 'auto',
bottom: 'auto',
left: 'auto',
width: '100%',
}}
>
<Alert
severity={notice.type}
variant="filled"
sx={{ width: '100%' }}
action={
<IconButton
size="small"
color="inherit"
onClick={() => handleClose(notice.id)}
>
<CloseRounded fontSize="inherit" />
</IconButton>
}
>
{notice.message}
</Alert>
</Snackbar>
))}
</Box>
);
};

View File

@@ -1,161 +0,0 @@
import { createRoot } from "react-dom/client";
import { ReactNode, useState, useEffect } from "react";
import { Box, IconButton, Slide, Snackbar, Typography } from "@mui/material";
import {
CloseRounded,
CheckCircleRounded,
ErrorRounded,
} from "@mui/icons-material";
import { useVerge } from "@/hooks/use-verge";
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
const appWindow = getCurrentWebviewWindow();
interface InnerProps {
type: string;
duration?: number;
message: ReactNode;
isDark?: boolean;
onClose: () => void;
}
const NoticeInner = (props: InnerProps) => {
const { type, message, duration, onClose } = props;
const [visible, setVisible] = useState(true);
const [isDark, setIsDark] = useState(false);
const { verge } = useVerge();
const { theme_mode } = verge ?? {};
const onBtnClose = () => {
setVisible(false);
onClose();
};
const onAutoClose = (_e: any, reason: string) => {
if (reason !== "clickaway") onBtnClose();
};
useEffect(() => {
const themeMode = ["light", "dark", "system"].includes(theme_mode!)
? theme_mode!
: "light";
if (themeMode !== "system") {
setIsDark(themeMode === "dark");
return;
}
appWindow.theme().then((m) => m && setIsDark(m === "dark"));
const unlisten = appWindow.onThemeChanged((e) =>
setIsDark(e.payload === "dark"),
);
return () => {
unlisten.then((fn) => fn());
};
}, [theme_mode]);
const msgElement =
type === "info" ? (
message
) : (
<Box sx={{ width: 328, display: "flex", alignItems: "center" }}>
{type === "error" && <ErrorRounded color="error" />}
{type === "success" && <CheckCircleRounded color="success" />}
<Typography
component="span"
sx={{ ml: 1, wordWrap: "break-word", width: "calc(100% - 35px)" }}
>
{message}
</Typography>
</Box>
);
return (
<Snackbar
open={visible}
anchorOrigin={{ vertical: "top", horizontal: "right" }}
autoHideDuration={duration === -1 ? null : duration || 1500}
onClose={onAutoClose}
message={msgElement}
sx={{
maxWidth: 360,
".MuiSnackbarContent-root": {
bgcolor: isDark ? "#50515C" : "#ffffff",
color: isDark ? "#ffffff" : "#000000",
},
}}
TransitionComponent={(p) => <Slide {...p} direction="left" />}
transitionDuration={200}
action={
<IconButton size="small" color="inherit" onClick={onBtnClose}>
<CloseRounded fontSize="inherit" />
</IconButton>
}
/>
);
};
interface NoticeInstance {
(props: Omit<InnerProps, "onClose">): void;
info(message: ReactNode, duration?: number, isDark?: boolean): void;
error(message: ReactNode, duration?: number, isDark?: boolean): void;
success(message: ReactNode, duration?: number, isDark?: boolean): void;
}
let parent: HTMLDivElement = null!;
export const Notice: NoticeInstance = (props) => {
const { type, message, duration } = props;
// 验证必要的参数
if (!message) {
return;
}
if (!parent) {
parent = document.createElement("div");
parent.setAttribute("id", "notice-container"); // 添加 id 便于调试
document.body.appendChild(parent);
}
const container = document.createElement("div");
parent.appendChild(container);
const root = createRoot(container);
const onUnmount = () => {
root.unmount();
if (parent && container.parentNode === parent) {
setTimeout(() => {
parent.removeChild(container);
}, 500);
}
};
root.render(
<NoticeInner
type={type}
message={message}
duration={duration || 1500}
onClose={onUnmount}
/>,
);
};
const createNoticeTypeFactory =
(type: keyof NoticeInstance) => (message: ReactNode, duration?: number) => {
// 确保消息不为空
if (!message) {
return;
}
Notice({
type,
message,
// 错误类型通知显示 8 秒,其他类型默认 1.5 秒
duration: type === "error" ? 8000 : duration || 1500,
});
};
Notice.info = createNoticeTypeFactory("info");
Notice.error = createNoticeTypeFactory("error");
Notice.success = createNoticeTypeFactory("success");

View File

@@ -3,6 +3,6 @@ export { BasePage } from "./base-page";
export { BaseEmpty } from "./base-empty";
export { BaseLoading } from "./base-loading";
export { BaseErrorBoundary } from "./base-error-boundary";
export { Notice } from "./base-notice";
export { Switch } from "./base-switch";
export { BaseLoadingOverlay } from "./base-loading-overlay";
export { NoticeManager } from "./NoticeManager";

View File

@@ -25,7 +25,7 @@ import parseTraffic from "@/utils/parse-traffic";
import { useMemo, useCallback, useState } from "react";
import { openWebUrl, updateProfile } from "@/services/cmds";
import { useLockFn } from "ahooks";
import { Notice } from "@/components/base";
import { showNotice } from "@/services/noticeService";
import { EnhancedCard } from "./enhanced-card";
import { useAppData } from "@/providers/app-data-provider";
@@ -272,7 +272,7 @@ export const HomeProfileCard = ({ current, onProfileUpdated }: HomeProfileCardPr
const { t } = useTranslation();
const navigate = useNavigate();
const { refreshAll } = useAppData();
// 更新当前订阅
const [updating, setUpdating] = useState(false);
@@ -281,14 +281,14 @@ export const HomeProfileCard = ({ current, onProfileUpdated }: HomeProfileCardPr
setUpdating(true);
try {
await updateProfile(current.uid);
Notice.success(t("Update subscription successfully"));
await updateProfile(current.uid, current.option);
showNotice('success', t("Update subscription successfully"), 1000);
onProfileUpdated?.();
// 刷新首页数据
refreshAll();
} catch (err: any) {
Notice.error(err?.message || err.toString());
showNotice('error', err.message || err.toString(), 3000);
} finally {
setUpdating(false);
}

View File

@@ -11,7 +11,6 @@ import {
} from "@mui/material";
import { useState, useMemo, memo, FC } from "react";
import ProxyControlSwitches from "@/components/shared/ProxyControlSwitches";
import { Notice } from "@/components/base";
import {
ComputerRounded,
TroubleshootRounded,
@@ -20,6 +19,7 @@ import {
} from "@mui/icons-material";
import { useVerge } from "@/hooks/use-verge";
import { useSystemState } from "@/hooks/use-system-state";
import { showNotice } from "@/services/noticeService";
const LOCAL_STORAGE_TAB_KEY = "clash-verge-proxy-active-tab";
@@ -151,7 +151,7 @@ export const ProxyTunCard: FC = () => {
// 处理错误
const handleError = (err: Error) => {
Notice.error(err.message || err.toString(), 3000);
showNotice('error', err.message || err.toString(), 3000);
};
// 处理标签切换并保存到localStorage

View File

@@ -11,13 +11,13 @@ import {
import { useVerge } from "@/hooks/use-verge";
import { EnhancedCard } from "./enhanced-card";
import useSWR from "swr";
import { getSystemInfo, installService } from "@/services/cmds";
import { getSystemInfo, installService, restartApp } from "@/services/cmds";
import { useNavigate } from "react-router-dom";
import { version as appVersion } from "@root/package.json";
import { useCallback, useEffect, useMemo, useState } from "react";
import { check as checkUpdate } from "@tauri-apps/plugin-updater";
import { useLockFn } from "ahooks";
import { Notice } from "@/components/base";
import { showNotice } from "@/services/noticeService";
import { useSystemState } from "@/hooks/use-system-state";
export const SystemInfoCard = () => {
@@ -117,14 +117,14 @@ export const SystemInfoCard = () => {
// 安装系统服务
const onInstallService = useLockFn(async () => {
try {
Notice.info(t("Installing Service..."), 1000);
showNotice('info', t("Installing Service..."), 1000);
await installService();
Notice.success(t("Service Installed Successfully"), 2000);
showNotice('success', t("Service Installed Successfully"), 2000);
await mutateRunningMode();
await mutateRunningMode();
} catch (err: any) {
Notice.error(err.message || err.toString(), 3000);
showNotice('error', err.message || err.toString(), 3000);
}
});
@@ -140,13 +140,13 @@ export const SystemInfoCard = () => {
try {
const info = await checkUpdate();
if (!info?.available) {
Notice.success(t("Currently on the Latest Version"));
showNotice('success', t("Currently on the Latest Version"));
} else {
Notice.info(t("Update Available"), 2000);
showNotice('info', t("Update Available"), 2000);
goToSettings();
}
} catch (err: any) {
Notice.error(err.message || err.toString());
showNotice('error', err.message || err.toString());
}
});

View File

@@ -16,9 +16,9 @@ import {
CloseFullscreenRounded,
} from "@mui/icons-material";
import { useThemeMode } from "@/services/states";
import { Notice } from "@/components/base";
import { nanoid } from "nanoid";
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
import { showNotice } from "@/services/noticeService";
import getSystem from "@/utils/get-system";
import debounce from "@/utils/debounce";
@@ -127,7 +127,7 @@ export const EditorViewer = <T extends Language>(props: Props<T>) => {
currData.current = value;
onChange?.(prevData.current, currData.current);
} catch (err: any) {
Notice.error(err.message || err.toString());
showNotice('error', err.message || err.toString());
}
});
@@ -136,7 +136,7 @@ export const EditorViewer = <T extends Language>(props: Props<T>) => {
!readOnly && onSave?.(prevData.current, currData.current);
onClose();
} catch (err: any) {
Notice.error(err.message || err.toString());
showNotice('error', err.message || err.toString());
}
});
@@ -144,7 +144,7 @@ export const EditorViewer = <T extends Language>(props: Props<T>) => {
try {
onClose();
} catch (err: any) {
Notice.error(err.message || err.toString());
showNotice('error', err.message || err.toString());
}
});

View File

@@ -40,13 +40,14 @@ import {
readProfileFile,
saveProfileFile,
} from "@/services/cmds";
import { Notice, Switch } from "@/components/base";
import { Switch } from "@/components/base";
import getSystem from "@/utils/get-system";
import { BaseSearchBox } from "../base/base-search-box";
import { Virtuoso } from "react-virtuoso";
import MonacoEditor from "react-monaco-editor";
import { useThemeMode } from "@/services/states";
import { Controller, useForm } from "react-hook-form";
import { showNotice } from "@/services/noticeService";
interface Props {
proxiesUid: string;
@@ -285,10 +286,11 @@ export const GroupsEditorViewer = (props: Props) => {
const handleSave = useLockFn(async () => {
try {
await saveProfileFile(property, currData);
showNotice('success', t("Saved Successfully"));
onSave?.(prevData, currData);
onClose();
} catch (err: any) {
Notice.error(err.message || err.toString());
showNotice('error', err.toString());
}
});
@@ -725,7 +727,7 @@ export const GroupsEditorViewer = (props: Props) => {
}
setPrependSeq([formIns.getValues(), ...prependSeq]);
} catch (err: any) {
Notice.error(err.message || err.toString());
showNotice('error', err.message || err.toString());
}
}}
>
@@ -747,7 +749,7 @@ export const GroupsEditorViewer = (props: Props) => {
}
setAppendSeq([...appendSeq, formIns.getValues()]);
} catch (err: any) {
Notice.error(err.message || err.toString());
showNotice('error', err.message || err.toString());
}
}}
>

View File

@@ -24,7 +24,7 @@ import {
saveProfileFile,
getNextUpdateTime,
} from "@/services/cmds";
import { Notice } from "@/components/base";
import { showNotice } from "@/services/noticeService";
import { GroupsEditorViewer } from "@/components/profile/groups-editor-viewer";
import { RulesEditorViewer } from "@/components/profile/rules-editor-viewer";
import { EditorViewer } from "@/components/profile/editor-viewer";
@@ -271,7 +271,7 @@ export const ProfileItem = (props: Props) => {
try {
await viewProfile(itemData.uid);
} catch (err: any) {
Notice.error(err?.message || err.toString());
showNotice('error', err?.message || err.toString());
}
});
@@ -302,7 +302,7 @@ export const ProfileItem = (props: Props) => {
await updateProfile(itemData.uid, option);
// 更新成功,刷新列表
Notice.success(t("Update subscription successfully"));
showNotice('success', t("Update subscription successfully"));
mutate("getProfiles");
} catch (err: any) {
// 更新完全失败(包括后端的回退尝试)

View File

@@ -12,10 +12,10 @@ import {
} from "@mui/material";
import { FeaturedPlayListRounded } from "@mui/icons-material";
import { viewProfile, readProfileFile, saveProfileFile } from "@/services/cmds";
import { Notice } from "@/components/base";
import { EditorViewer } from "@/components/profile/editor-viewer";
import { ProfileBox } from "./profile-box";
import { LogViewer } from "./log-viewer";
import { showNotice } from "@/services/noticeService";
interface Props {
logInfo?: [string, string][];
@@ -43,7 +43,7 @@ export const ProfileMore = (props: Props) => {
try {
await viewProfile(id);
} catch (err: any) {
Notice.error(err?.message || err.toString());
showNotice('error', err?.message || err.toString());
}
});

View File

@@ -19,10 +19,11 @@ import {
TextField,
} from "@mui/material";
import { createProfile, patchProfile } from "@/services/cmds";
import { BaseDialog, Notice, Switch } from "@/components/base";
import { BaseDialog, Switch } from "@/components/base";
import { version } from "@root/package.json";
import { FileInput } from "./file-input";
import { useProfiles } from "@/hooks/use-profiles";
import { showNotice } from "@/services/noticeService";
interface Props {
onChange: (isActivating?: boolean) => void;
@@ -140,7 +141,7 @@ export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
}
} catch (err) {
// 首次创建/更新失败,尝试使用自身代理
Notice.info(t("Profile creation failed, retrying with Clash proxy..."));
showNotice('info', t("Profile creation failed, retrying with Clash proxy..."));
// 使用自身代理的配置
const retryItem = {
@@ -163,7 +164,7 @@ export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
await patchProfile(form.uid, { option: originalOptions });
}
Notice.success(t("Profile creation succeeded with Clash proxy"));
showNotice('success', t("Profile creation succeeded with Clash proxy"));
}
}
@@ -175,7 +176,7 @@ export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
// 只传递当前配置激活状态,让父组件决定是否需要触发配置重载
props.onChange(isActivating);
} catch (err: any) {
Notice.error(err.message || err.toString());
showNotice('error', err.message || err.toString());
} finally {
setLoading(false);
}

View File

@@ -33,13 +33,13 @@ import {
} from "@mui/icons-material";
import { ProxyItem } from "@/components/profile/proxy-item";
import { readProfileFile, saveProfileFile } from "@/services/cmds";
import { Notice } from "@/components/base";
import getSystem from "@/utils/get-system";
import { BaseSearchBox } from "../base/base-search-box";
import { Virtuoso } from "react-virtuoso";
import MonacoEditor from "react-monaco-editor";
import { useThemeMode } from "@/services/states";
import parseUri from "@/utils/uri-parser";
import { showNotice } from "@/services/noticeService";
interface Props {
profileUid: string;
@@ -150,7 +150,7 @@ export const ProxiesEditorViewer = (props: Props) => {
names.push(proxy.name);
}
} catch (err: any) {
Notice.error(err.message || err.toString());
showNotice('error', err.message || err.toString());
}
});
return proxies;
@@ -212,10 +212,11 @@ export const ProxiesEditorViewer = (props: Props) => {
const handleSave = useLockFn(async () => {
try {
await saveProfileFile(property, currData);
showNotice('success', t("Saved Successfully"));
onSave?.(prevData, currData);
onClose();
} catch (err: any) {
Notice.error(err.message || err.toString());
showNotice('error', err.toString());
}
});

View File

@@ -34,13 +34,14 @@ import {
VerticalAlignBottomRounded,
} from "@mui/icons-material";
import { readProfileFile, saveProfileFile } from "@/services/cmds";
import { Notice, Switch } from "@/components/base";
import { Switch } from "@/components/base";
import getSystem from "@/utils/get-system";
import { RuleItem } from "@/components/profile/rule-item";
import { BaseSearchBox } from "../base/base-search-box";
import { Virtuoso } from "react-virtuoso";
import MonacoEditor from "react-monaco-editor";
import { useThemeMode } from "@/services/states";
import { showNotice } from "@/services/noticeService";
interface Props {
groupsUid: string;
@@ -414,10 +415,11 @@ export const RulesEditorViewer = (props: Props) => {
const handleSave = useLockFn(async () => {
try {
await saveProfileFile(property, currData);
showNotice('success', t("Saved Successfully"));
onSave?.(prevData, currData);
onClose();
} catch (err: any) {
Notice.error(err.message || err.toString());
showNotice('error', err.toString());
}
});
@@ -545,7 +547,7 @@ export const RulesEditorViewer = (props: Props) => {
if (prependSeq.includes(raw)) return;
setPrependSeq([raw, ...prependSeq]);
} catch (err: any) {
Notice.error(err.message || err.toString());
showNotice('error', err.message || err.toString());
}
}}
>
@@ -563,7 +565,7 @@ export const RulesEditorViewer = (props: Props) => {
if (appendSeq.includes(raw)) return;
setAppendSeq([...appendSeq, raw]);
} catch (err: any) {
Notice.error(err.message || err.toString());
showNotice('error', err.message || err.toString());
}
}}
>

View File

@@ -21,7 +21,7 @@ import { useTranslation } from "react-i18next";
import { useLockFn } from "ahooks";
import { proxyProviderUpdate } from "@/services/api";
import { useAppData } from "@/providers/app-data-provider";
import { Notice } from "@/components/base";
import { showNotice } from "@/services/noticeService";
import { StorageOutlined, RefreshRounded } from "@mui/icons-material";
import dayjs from "dayjs";
import parseTraffic from "@/utils/parse-traffic";
@@ -81,9 +81,9 @@ export const ProviderButton = () => {
await refreshProxy();
await refreshProxyProviders();
Notice.success(`${name} 更新成功`);
showNotice('success', `${name} 更新成功`);
} catch (err: any) {
Notice.error(`${name} 更新失败: ${err?.message || err.toString()}`);
showNotice('error', `${name} 更新失败: ${err?.message || err.toString()}`);
} finally {
// 清除更新状态
setUpdating(prev => ({ ...prev, [name]: false }));
@@ -96,7 +96,7 @@ export const ProviderButton = () => {
// 获取所有provider的名称
const allProviders = Object.keys(proxyProviders || {});
if (allProviders.length === 0) {
Notice.info("没有可更新的代理提供者");
showNotice('info', "没有可更新的代理提供者");
return;
}
@@ -123,9 +123,9 @@ export const ProviderButton = () => {
await refreshProxy();
await refreshProxyProviders();
Notice.success("全部代理提供者更新成功");
showNotice('success', "全部代理提供者更新成功");
} catch (err: any) {
Notice.error(`更新失败: ${err?.message || err.toString()}`);
showNotice('error', `更新失败: ${err?.message || err.toString()}`);
} finally {
// 清除所有更新状态
setUpdating({});

View File

@@ -19,10 +19,10 @@ import {
import { useTranslation } from "react-i18next";
import { useLockFn } from "ahooks";
import { ruleProviderUpdate } from "@/services/api";
import { Notice } from "@/components/base";
import { StorageOutlined, RefreshRounded } from "@mui/icons-material";
import { useAppData } from "@/providers/app-data-provider";
import dayjs from "dayjs";
import { showNotice } from "@/services/noticeService";
// 定义规则提供者类型
interface RuleProviderItem {
@@ -67,9 +67,9 @@ export const ProviderButton = () => {
await refreshRules();
await refreshRuleProviders();
Notice.success(`${name} 更新成功`);
showNotice('success', `${name} 更新成功`);
} catch (err: any) {
Notice.error(`${name} 更新失败: ${err?.message || err.toString()}`);
showNotice('error', `${name} 更新失败: ${err?.message || err.toString()}`);
} finally {
// 清除更新状态
setUpdating(prev => ({ ...prev, [name]: false }));
@@ -82,7 +82,7 @@ export const ProviderButton = () => {
// 获取所有provider的名称
const allProviders = Object.keys(ruleProviders || {});
if (allProviders.length === 0) {
Notice.info("没有可更新的规则提供者");
showNotice('info', "没有可更新的规则提供者");
return;
}
@@ -109,9 +109,9 @@ export const ProviderButton = () => {
await refreshRules();
await refreshRuleProviders();
Notice.success("全部规则提供者更新成功");
showNotice('success', "全部规则提供者更新成功");
} catch (err: any) {
Notice.error(`更新失败: ${err?.message || err.toString()}`);
showNotice('error', `更新失败: ${err?.message || err.toString()}`);
} finally {
// 清除所有更新状态
setUpdating({});

View File

@@ -2,7 +2,6 @@ import { useState, useRef, memo, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { useForm } from "react-hook-form";
import { useVerge } from "@/hooks/use-verge";
import { Notice } from "@/components/base";
import { isValidUrl } from "@/utils/helper";
import { useLockFn } from "ahooks";
import {
@@ -17,6 +16,7 @@ import {
import Visibility from "@mui/icons-material/Visibility";
import VisibilityOff from "@mui/icons-material/VisibilityOff";
import { saveWebdavConfig, createWebdavBackup } from "@/services/cmds";
import { showNotice } from "@/services/noticeService";
export interface BackupConfigViewerProps {
onBackupSuccess: () => Promise<void>;
@@ -83,21 +83,21 @@ export const BackupConfigViewer = memo(
if (!url) {
urlRef.current?.focus();
Notice.error(t("WebDAV URL Required"));
showNotice('error', t("WebDAV URL Required"));
throw new Error(t("WebDAV URL Required"));
} else if (!isValidUrl(url)) {
urlRef.current?.focus();
Notice.error(t("Invalid WebDAV URL"));
showNotice('error', t("Invalid WebDAV URL"));
throw new Error(t("Invalid WebDAV URL"));
}
if (!username) {
usernameRef.current?.focus();
Notice.error(t("WebDAV URL Required"));
showNotice('error', t("WebDAV URL Required"));
throw new Error(t("Username Required"));
}
if (!password) {
passwordRef.current?.focus();
Notice.error(t("WebDAV URL Required"));
showNotice('error', t("WebDAV URL Required"));
throw new Error(t("Password Required"));
}
};
@@ -111,11 +111,11 @@ export const BackupConfigViewer = memo(
data.username.trim(),
data.password,
).then(() => {
Notice.success(t("WebDAV Config Saved"));
showNotice('success', t("WebDAV Config Saved"));
onSaveSuccess();
});
} catch (error) {
Notice.error(t("WebDAV Config Save Failed", { error }), 3000);
showNotice('error', t("WebDAV Config Save Failed", { error }), 3000);
} finally {
setLoading(false);
}
@@ -126,11 +126,11 @@ export const BackupConfigViewer = memo(
try {
setLoading(true);
await createWebdavBackup().then(async () => {
showNotice('success', t("Backup Created"));
await onBackupSuccess();
Notice.success(t("Backup Created"));
});
} catch (error) {
Notice.error(t("Backup Failed", { error }));
showNotice('error', t("Backup Failed", { error }));
} finally {
setLoading(false);
}

View File

@@ -12,7 +12,6 @@ import {
TableRow,
TablePagination,
} from "@mui/material";
import { Notice } from "@/components/base";
import { Typography } from "@mui/material";
import { useLockFn } from "ahooks";
import { useTranslation } from "react-i18next";
@@ -24,6 +23,7 @@ import {
} from "@/services/cmds";
import DeleteIcon from "@mui/icons-material/Delete";
import RestoreIcon from "@mui/icons-material/Restore";
import { showNotice } from "@/services/noticeService";
export type BackupFile = IWebDavFile & {
platform: string;
@@ -61,7 +61,7 @@ export const BackupTableViewer = memo(
const handleRestore = useLockFn(async (filename: string) => {
await restoreWebDavBackup(filename).then(() => {
Notice.success(t("Restore Success, App will restart in 1s"));
showNotice('success', t("Restore Success, App will restart in 1s"));
});
await restartApp();
});

View File

@@ -1,6 +1,6 @@
import { mutate } from "swr";
import { forwardRef, useImperativeHandle, useState } from "react";
import { BaseDialog, DialogRef, Notice } from "@/components/base";
import { BaseDialog, DialogRef } from "@/components/base";
import { useTranslation } from "react-i18next";
import { useVerge } from "@/hooks/use-verge";
import { useLockFn } from "ahooks";
@@ -19,6 +19,7 @@ import {
} from "@mui/material";
import { changeClashCore, restartCore } from "@/services/cmds";
import { closeAllConnections, upgradeCore } from "@/services/api";
import { showNotice } from "@/services/noticeService";
const VALID_CORE = [
{ name: "Mihomo", core: "verge-mihomo", chip: "Release Version" },
@@ -48,7 +49,7 @@ export const ClashCoreViewer = forwardRef<DialogRef>((props, ref) => {
const errorMsg = await changeClashCore(core);
if (errorMsg) {
Notice.error(errorMsg);
showNotice('error', errorMsg);
return;
}
@@ -58,16 +59,16 @@ export const ClashCoreViewer = forwardRef<DialogRef>((props, ref) => {
mutate("getVersion");
}, 500);
} catch (err: any) {
Notice.error(err?.message || err.toString());
showNotice('error', err.message || err.toString());
}
});
const onRestart = useLockFn(async () => {
try {
await restartCore();
Notice.success(t(`Clash Core Restarted`), 1000);
showNotice('success', t(`Clash Core Restarted`), 1000);
} catch (err: any) {
Notice.error(err?.message || err.toString());
showNotice('error', err.message || err.toString());
}
});
@@ -76,10 +77,10 @@ export const ClashCoreViewer = forwardRef<DialogRef>((props, ref) => {
setUpgrading(true);
await upgradeCore();
setUpgrading(false);
Notice.success(t(`Core Version Updated`), 1000);
showNotice('success', t(`Core Version Updated`), 1000);
} catch (err: any) {
setUpgrading(false);
Notice.error(err?.response.data.message || err.toString());
showNotice('error', err.response?.data?.message || err.toString());
}
});

View File

@@ -3,9 +3,10 @@ import { useTranslation } from "react-i18next";
import { useLockFn } from "ahooks";
import { List, ListItem, ListItemText, TextField } from "@mui/material";
import { useClashInfo } from "@/hooks/use-clash";
import { BaseDialog, DialogRef, Notice, Switch } from "@/components/base";
import { BaseDialog, DialogRef, Switch } from "@/components/base";
import { useVerge } from "@/hooks/use-verge";
import getSystem from "@/utils/get-system";
import { showNotice } from "@/services/noticeService";
const OS = getSystem();
export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
@@ -78,18 +79,18 @@ export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
OS === "linux" &&
new Set([redirPort, tproxyPort, mixedPort, socksPort, port]).size !== 5
) {
Notice.error(t("Port Conflict"), 4000);
showNotice('error', t("Port Conflict"));
return;
}
if (
OS === "macos" &&
new Set([redirPort, mixedPort, socksPort, port]).size !== 4
) {
Notice.error(t("Port Conflict"), 4000);
showNotice('error', t("Port Conflict"));
return;
}
if (OS === "windows" && new Set([mixedPort, socksPort, port]).size !== 3) {
Notice.error(t("Port Conflict"), 4000);
showNotice('error', t("Port Conflict"));
return;
}
try {
@@ -145,9 +146,9 @@ export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
});
}
setOpen(false);
Notice.success(t("Clash Port Modified"), 1000);
showNotice('success', t("Clash Port Modified"));
} catch (err: any) {
Notice.error(err.message || err.toString(), 4000);
showNotice('error', err.message || err.toString());
}
});

View File

@@ -3,7 +3,8 @@ import { useLockFn } from "ahooks";
import { useTranslation } from "react-i18next";
import { List, ListItem, ListItemText, TextField } from "@mui/material";
import { useClashInfo } from "@/hooks/use-clash";
import { BaseDialog, DialogRef, Notice } from "@/components/base";
import { BaseDialog, DialogRef } from "@/components/base";
import { showNotice } from "@/services/noticeService";
export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
const { t } = useTranslation();
@@ -26,10 +27,10 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
const onSave = useLockFn(async () => {
try {
await patchInfo({ "external-controller": controller, secret });
Notice.success(t("External Controller Address Modified"), 1000);
showNotice('success', t("External Controller Address Modified"), 1000);
setOpen(false);
} catch (err: any) {
Notice.error(err.message || err.toString(), 4000);
showNotice('error', err.message || err.toString(), 4000);
}
});

View File

@@ -17,12 +17,13 @@ import {
} from "@mui/material";
import { RestartAltRounded } from "@mui/icons-material";
import { useClash } from "@/hooks/use-clash";
import { BaseDialog, DialogRef, Notice } from "@/components/base";
import { BaseDialog, DialogRef } from "@/components/base";
import yaml from "js-yaml";
import MonacoEditor from "react-monaco-editor";
import { useThemeMode } from "@/services/states";
import getSystem from "@/utils/get-system";
import { invoke } from "@tauri-apps/api/core";
import { showNotice } from "@/services/noticeService";
const Item = styled(ListItem)(({ theme }) => ({
padding: "8px 0",
@@ -374,7 +375,7 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
formatNameserverPolicy(dnsConfig["nameserver-policy"]) || "",
});
} catch (err: any) {
Notice.error(t("Invalid YAML format"));
showNotice('error', t("Invalid YAML format"));
}
};
@@ -526,9 +527,9 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
}
setOpen(false);
Notice.success(t("DNS settings saved"));
showNotice('success', t("DNS settings saved"));
} catch (err: any) {
Notice.error(err.message || err.toString());
showNotice('error', err.message || err.toString());
}
});

View File

@@ -3,8 +3,9 @@ import { useTranslation } from "react-i18next";
import { useLockFn } from "ahooks";
import { styled, Typography, Switch } from "@mui/material";
import { useVerge } from "@/hooks/use-verge";
import { BaseDialog, DialogRef, Notice } from "@/components/base";
import { BaseDialog, DialogRef } from "@/components/base";
import { HotkeyInput } from "./hotkey-input";
import { showNotice } from "@/services/noticeService";
const ItemWrapper = styled("div")`
display: flex;
@@ -79,7 +80,7 @@ export const HotkeyViewer = forwardRef<DialogRef>((props, ref) => {
});
setOpen(false);
} catch (err: any) {
Notice.error(err.message || err.toString());
showNotice('error', err.toString());
}
});

View File

@@ -11,7 +11,7 @@ import {
Box,
} from "@mui/material";
import { useVerge } from "@/hooks/use-verge";
import { BaseDialog, DialogRef, Notice, Switch } from "@/components/base";
import { BaseDialog, DialogRef, Switch } from "@/components/base";
import { GuardState } from "./guard-state";
import { open as openDialog } from "@tauri-apps/plugin-dialog";
import { convertFileSrc } from "@tauri-apps/api/core";
@@ -19,6 +19,7 @@ import { copyIconFile, getAppDir } from "@/services/cmds";
import { join } from "@tauri-apps/api/path";
import { exists } from "@tauri-apps/plugin-fs";
import getSystem from "@/utils/get-system";
import { showNotice } from "@/services/noticeService";
const OS = getSystem();
@@ -87,7 +88,7 @@ export const LayoutViewer = forwardRef<DialogRef>((props, ref) => {
const onSwitchFormat = (_e: any, value: boolean) => value;
const onError = (err: any) => {
Notice.error(err.message || err.toString());
showNotice('error', err.message || err.toString());
};
const onChangeData = (patch: Partial<IVergeConfig>) => {
mutateVerge({ ...verge, ...patch }, false);

View File

@@ -10,9 +10,10 @@ import {
InputAdornment,
} from "@mui/material";
import { useVerge } from "@/hooks/use-verge";
import { BaseDialog, DialogRef, Notice, Switch } from "@/components/base";
import { BaseDialog, DialogRef, Switch } from "@/components/base";
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import { entry_lightweight_mode } from "@/services/cmds";
import { showNotice } from "@/services/noticeService";
export const LiteModeViewer = forwardRef<DialogRef>((props, ref) => {
const { t } = useTranslation();
@@ -43,7 +44,7 @@ export const LiteModeViewer = forwardRef<DialogRef>((props, ref) => {
});
setOpen(false);
} catch (err: any) {
Notice.error(err.message || err.toString());
showNotice('error', err.message || err.toString());
}
});

View File

@@ -11,8 +11,9 @@ import {
InputAdornment,
} from "@mui/material";
import { useVerge } from "@/hooks/use-verge";
import { BaseDialog, DialogRef, Notice, Switch } from "@/components/base";
import { BaseDialog, DialogRef, Switch } from "@/components/base";
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import { showNotice } from "@/services/noticeService";
export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
const { t } = useTranslation();
@@ -61,7 +62,7 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
});
setOpen(false);
} catch (err: any) {
Notice.error(err.message || err.toString());
showNotice('error', err.toString());
}
});

View File

@@ -1,10 +1,11 @@
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
import { BaseDialog, DialogRef, Notice } from "@/components/base";
import { BaseDialog, DialogRef } from "@/components/base";
import { getNetworkInterfacesInfo } from "@/services/cmds";
import { alpha, Box, Button, Chip, IconButton } from "@mui/material";
import { ContentCopyRounded } from "@mui/icons-material";
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
import { showNotice } from "@/services/noticeService";
export const NetworkInterfaceViewer = forwardRef<DialogRef>((props, ref) => {
const { t } = useTranslation();
@@ -128,7 +129,7 @@ const AddressDisplay = (props: { label: string; content: string }) => {
size="small"
onClick={async () => {
await writeText(props.content);
Notice.success(t("Copy Success"));
showNotice('success', t("Copy Success"));
}}
>
<ContentCopyRounded sx={{ fontSize: "18px" }} />

View File

@@ -1,4 +1,4 @@
import { BaseDialog, DialogRef, Notice, Switch } from "@/components/base";
import { BaseDialog, DialogRef, Switch } from "@/components/base";
import { BaseFieldset } from "@/components/base/base-fieldset";
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import { EditorViewer } from "@/components/profile/editor-viewer";
@@ -36,6 +36,7 @@ import {
} from "react";
import { useTranslation } from "react-i18next";
import { mutate } from "swr";
import { showNotice } from "@/services/noticeService";
const DEFAULT_PAC = `function FindProxyForURL(url, host) {
return "PROXY %proxy_host%:%mixed-port%; SOCKS5 %proxy_host%:%mixed-port%; DIRECT;";
}`;
@@ -201,11 +202,11 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
const onSave = useLockFn(async () => {
if (value.duration < 1) {
Notice.error(t("Proxy Daemon Duration Cannot be Less than 1 Second"));
showNotice('error', t("Proxy Daemon Duration Cannot be Less than 1 Second"));
return;
}
if (value.bypass && !validReg.test(value.bypass)) {
Notice.error(t("Invalid Bypass Format"));
showNotice('error', t("Invalid Bypass Format"));
return;
}
@@ -222,7 +223,7 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
!ipv6Regex.test(value.proxy_host) &&
!hostnameRegex.test(value.proxy_host)
) {
Notice.error(t("Invalid Proxy Host Format"));
showNotice('error', t("Invalid Proxy Host Format"));
return;
}
@@ -301,10 +302,10 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
await Promise.all([mutate("getSystemProxy"), mutate("getAutotemProxy")]);
}
}
setOpen(false);
} catch (err: any) {
Notice.error(err.message || err.toString());
showNotice('error', err.toString());
} finally {
setSaving(false);
}

View File

@@ -12,9 +12,10 @@ import {
} from "@mui/material";
import { useVerge } from "@/hooks/use-verge";
import { defaultTheme, defaultDarkTheme } from "@/pages/_theme";
import { BaseDialog, DialogRef, Notice } from "@/components/base";
import { BaseDialog, DialogRef } from "@/components/base";
import { EditorViewer } from "@/components/profile/editor-viewer";
import { EditRounded } from "@mui/icons-material";
import { showNotice } from "@/services/noticeService";
export const ThemeViewer = forwardRef<DialogRef>((props, ref) => {
const { t } = useTranslation();
@@ -48,7 +49,7 @@ export const ThemeViewer = forwardRef<DialogRef>((props, ref) => {
await patchVerge({ theme_setting: theme });
setOpen(false);
} catch (err: any) {
Notice.error(err.message || err.toString());
showNotice('error', err.toString());
}
});

View File

@@ -11,10 +11,11 @@ import {
TextField,
} from "@mui/material";
import { useClash } from "@/hooks/use-clash";
import { BaseDialog, DialogRef, Notice, Switch } from "@/components/base";
import { BaseDialog, DialogRef, Switch } from "@/components/base";
import { StackModeSwitch } from "./stack-mode-switch";
import { enhanceProfiles } from "@/services/cmds";
import getSystem from "@/utils/get-system";
import { showNotice } from "@/services/noticeService";
const OS = getSystem();
@@ -76,13 +77,13 @@ export const TunViewer = forwardRef<DialogRef>((props, ref) => {
);
try {
await enhanceProfiles();
Notice.success(t("Settings Applied"), 1000);
showNotice('success', t("Settings Applied"));
} catch (err: any) {
Notice.error(err.message || err.toString(), 3000);
showNotice('error', err.message || err.toString());
}
setOpen(false);
} catch (err: any) {
Notice.error(err.message || err.toString());
showNotice('error', err.message || err.toString());
}
});

View File

@@ -5,13 +5,14 @@ import { Box, LinearProgress, Button } from "@mui/material";
import { useTranslation } from "react-i18next";
import { relaunch } from "@tauri-apps/plugin-process";
import { check as checkUpdate } from "@tauri-apps/plugin-updater";
import { BaseDialog, DialogRef, Notice } from "@/components/base";
import { BaseDialog, DialogRef } from "@/components/base";
import { useUpdateState, useSetUpdateState } from "@/services/states";
import { Event, UnlistenFn } from "@tauri-apps/api/event";
import { portableFlag } from "@/pages/_layout";
import { open as openUrl } from "@tauri-apps/plugin-shell";
import ReactMarkdown from "react-markdown";
import { useListen } from "@/hooks/use-listen";
import { showNotice } from "@/services/noticeService";
let eventListener: UnlistenFn | null = null;
@@ -55,12 +56,12 @@ export const UpdateViewer = forwardRef<DialogRef>((props, ref) => {
const onUpdate = useLockFn(async () => {
if (portableFlag) {
Notice.error(t("Portable Updater Error"));
showNotice('error', t("Portable Updater Error"));
return;
}
if (!updateInfo?.body) return;
if (breakChangeFlag) {
Notice.error(t("Break Change Update Error"));
showNotice('error', t("Break Change Update Error"));
return;
}
if (updateState) return;
@@ -82,7 +83,7 @@ export const UpdateViewer = forwardRef<DialogRef>((props, ref) => {
await updateInfo.downloadAndInstall();
await relaunch();
} catch (err: any) {
Notice.error(err?.message || err.toString());
showNotice('error', err?.message || err.toString());
} finally {
setUpdateState(false);
}

View File

@@ -4,9 +4,10 @@ import { useTranslation } from "react-i18next";
import { Button, Box, Typography } from "@mui/material";
import { useVerge } from "@/hooks/use-verge";
import { openWebUrl } from "@/services/cmds";
import { BaseDialog, BaseEmpty, DialogRef, Notice } from "@/components/base";
import { BaseDialog, BaseEmpty, DialogRef } from "@/components/base";
import { useClashInfo } from "@/hooks/use-clash";
import { WebUIItem } from "./web-ui-item";
import { showNotice } from "@/services/noticeService";
export const WebUIViewer = forwardRef<DialogRef>((props, ref) => {
const { t } = useTranslation();
@@ -71,7 +72,7 @@ export const WebUIViewer = forwardRef<DialogRef>((props, ref) => {
await openWebUrl(url);
} catch (e: any) {
Notice.error(e.message || e.toString());
showNotice('error', e.message || e.toString());
}
});

View File

@@ -6,7 +6,7 @@ import {
ShuffleRounded,
LanRounded,
} from "@mui/icons-material";
import { DialogRef, Notice, Switch } from "@/components/base";
import { DialogRef, Switch } from "@/components/base";
import { useClash } from "@/hooks/use-clash";
import { GuardState } from "./mods/guard-state";
import { WebUIViewer } from "./mods/web-ui-viewer";
@@ -24,6 +24,7 @@ import { DnsViewer } from "./mods/dns-viewer";
import { invoke } from "@tauri-apps/api/core";
import { useLockFn } from "ahooks";
import { useListen } from "@/hooks/use-listen";
import { showNotice } from "@/services/noticeService";
const isWIN = getSystem() === "windows";
@@ -77,9 +78,9 @@ const SettingClash = ({ onError }: Props) => {
const onUpdateGeo = async () => {
try {
await updateGeoData();
Notice.success(t("GeoData Updated"));
showNotice('success', t("GeoData Updated"));
} catch (err: any) {
Notice.error(err?.response.data.message || err.toString());
showNotice('error', err?.response.data.message || err.toString());
}
};
@@ -100,7 +101,7 @@ const SettingClash = ({ onError }: Props) => {
// 如果出错,恢复原始状态
setDnsSettingsEnabled(!enable);
localStorage.setItem("dns_settings_enabled", String(!enable));
Notice.error(err.message || err.toString());
showNotice('error', err.message || err.toString());
await patchVerge({ enable_dns_settings: !enable }).catch(() => {
// 忽略恢复状态时的错误
});
@@ -224,10 +225,7 @@ const SettingClash = ({ onError }: Props) => {
color={enable_random_port ? "primary" : "inherit"}
icon={ShuffleRounded}
onClick={() => {
Notice.success(
t("Restart Application to Apply Modifications"),
1000,
);
showNotice('success', t("Restart Application to Apply Modifications"), 1000);
onChangeVerge({ enable_random_port: !enable_random_port });
patchVerge({ enable_random_port: !enable_random_port });
}}

View File

@@ -9,7 +9,7 @@ import {
BuildRounded,
} from "@mui/icons-material";
import { useVerge } from "@/hooks/use-verge";
import { DialogRef, Notice, Switch } from "@/components/base";
import { DialogRef, Switch } from "@/components/base";
import { SettingList, SettingItem } from "./mods/setting-comp";
import { GuardState } from "./mods/guard-state";
import { SysproxyViewer } from "./mods/sysproxy-viewer";
@@ -25,6 +25,7 @@ import { useLockFn } from "ahooks";
import { Button, Tooltip } from "@mui/material";
import { useSystemState } from "@/hooks/use-system-state";
import { closeAllConnections } from "@/services/api";
import { showNotice } from "@/services/noticeService";
interface Props {
onError?: (err: Error) => void;
@@ -86,13 +87,13 @@ const SettingSystem = ({ onError }: Props) => {
// 安装系统服务
const onInstallService = useLockFn(async () => {
try {
Notice.info(t("Installing Service..."), 1000);
showNotice('info', t("Installing Service..."), 1000);
await installService();
Notice.success(t("Service Installed Successfully"), 2000);
showNotice('success', t("Service Installed Successfully"), 2000);
// 重新获取运行模式
await mutateRunningMode();
} catch (err: any) {
Notice.error(err.message || err.toString(), 3000);
showNotice('error', err.message || err.toString(), 3000);
}
});
@@ -144,7 +145,7 @@ const SettingSystem = ({ onError }: Props) => {
onGuard={(e) => {
// 当在sidecar模式下且非管理员模式时禁用切换
if (isSidecarMode && !isAdminMode) {
Notice.error(t("TUN requires Service Mode"), 2000);
showNotice('error', t("TUN requires Service Mode"), 2000);
return Promise.reject(new Error(t("TUN requires Service Mode")));
}
return patchVerge({ enable_tun_mode: e });
@@ -215,7 +216,7 @@ const SettingSystem = ({ onError }: Props) => {
}}
onGuard={async (e) => {
if (isAdminMode) {
Notice.info(t("Administrator mode may not support auto launch"), 2000);
showNotice('info', t("Administrator mode may not support auto launch"), 2000);
}
try {

View File

@@ -12,7 +12,7 @@ import {
import { check as checkUpdate } from "@tauri-apps/plugin-updater";
import { useVerge } from "@/hooks/use-verge";
import { version } from "@root/package.json";
import { DialogRef, Notice } from "@/components/base";
import { DialogRef } from "@/components/base";
import { SettingList, SettingItem } from "./mods/setting-comp";
import { ConfigViewer } from "./mods/config-viewer";
import { HotkeyViewer } from "./mods/hotkey-viewer";
@@ -24,6 +24,7 @@ import { BackupViewer } from "./mods/backup-viewer";
import { LiteModeViewer } from "./mods/lite-mode-viewer";
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import { ContentCopyRounded } from "@mui/icons-material";
import { showNotice } from "@/services/noticeService";
interface Props {
onError?: (err: Error) => void;
@@ -46,18 +47,18 @@ const SettingVergeAdvanced = ({ onError }: Props) => {
try {
const info = await checkUpdate();
if (!info?.available) {
Notice.success(t("Currently on the Latest Version"));
showNotice('success', t("Currently on the Latest Version"));
} else {
updateRef.current?.open();
}
} catch (err: any) {
Notice.error(err.message || err.toString());
showNotice('error', err.message || err.toString());
}
};
const onExportDiagnosticInfo = useCallback(async () => {
await exportDiagnosticInfo();
Notice.success(t("Copy Success"), 1000);
showNotice('success', t("Copy Success"), 1000);
}, []);
return (

View File

@@ -4,7 +4,7 @@ import { open } from "@tauri-apps/plugin-dialog";
import { Button, MenuItem, Select, Input } from "@mui/material";
import { copyClashEnv } from "@/services/cmds";
import { useVerge } from "@/hooks/use-verge";
import { DialogRef, Notice } from "@/components/base";
import { DialogRef } from "@/components/base";
import { SettingList, SettingItem } from "./mods/setting-comp";
import { ThemeModeSwitch } from "./mods/theme-mode-switch";
import { ConfigViewer } from "./mods/config-viewer";
@@ -20,6 +20,7 @@ import { routers } from "@/pages/_routers";
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import { ContentCopyRounded } from "@mui/icons-material";
import { languages } from "@/services/i18n";
import { showNotice } from "@/services/noticeService";
interface Props {
onError?: (err: Error) => void;
@@ -66,7 +67,7 @@ const SettingVergeBasic = ({ onError }: Props) => {
const onCopyClashEnv = useCallback(async () => {
await copyClashEnv();
Notice.success(t("Copy Success"), 1000);
showNotice('success', t("Copy Success"), 1000);
}, []);
return (

View File

@@ -15,7 +15,7 @@ import {
alpha,
useTheme,
} from "@mui/material";
import { DialogRef, Notice, Switch } from "@/components/base";
import { DialogRef, Switch } from "@/components/base";
import { GuardState } from "@/components/setting/mods/guard-state";
import { SysproxyViewer } from "@/components/setting/mods/sysproxy-viewer";
import { TunViewer } from "@/components/setting/mods/tun-viewer";
@@ -28,6 +28,7 @@ import {
} from "@/services/cmds";
import { useLockFn } from "ahooks";
import { closeAllConnections } from "@/services/api";
import { showNotice } from "@/services/noticeService";
interface ProxySwitchProps {
label?: string;
@@ -78,13 +79,13 @@ const ProxyControlSwitches = ({ label, onError }: ProxySwitchProps) => {
// 安装系统服务
const onInstallService = useLockFn(async () => {
try {
Notice.info(t("Installing Service..."), 1000);
showNotice('info', t("Installing Service..."), 1000);
await installService();
Notice.success(t("Service Installed Successfully"), 2000);
showNotice('success', t("Service Installed Successfully"), 2000);
// 重新获取运行模式
await mutateRunningMode();
} catch (err: any) {
Notice.error(err.message || err.toString(), 3000);
showNotice('error', err.message || err.toString(), 3000);
}
});
@@ -258,13 +259,18 @@ const ProxyControlSwitches = ({ label, onError }: ProxySwitchProps) => {
onFormat={onSwitchFormat}
onChange={(e) => {
// 当在sidecar模式下禁用切换
if (isSidecarMode) return;
if (isSidecarMode) {
showNotice('error', t("TUN requires Service Mode"), 2000);
return Promise.reject(
new Error(t("TUN requires Service Mode")),
);
}
onChangeData({ enable_tun_mode: e });
}}
onGuard={(e) => {
// 当在sidecar模式下禁用切换
if (isSidecarMode) {
Notice.error(t("TUN requires Service Mode"), 2000);
showNotice('error', t("TUN requires Service Mode"), 2000);
return Promise.reject(
new Error(t("TUN requires Service Mode")),
);

View File

@@ -6,13 +6,14 @@ import { CSS } from "@dnd-kit/utilities";
import { Box, Divider, MenuItem, Menu, styled, alpha } from "@mui/material";
import { BaseLoading } from "@/components/base";
import { LanguageRounded } from "@mui/icons-material";
import { Notice } from "@/components/base";
import { showNotice } from "@/services/noticeService";
import { TestBox } from "./test-box";
import delayManager from "@/services/delay";
import { cmdTestDelay, downloadIconCache } from "@/services/cmds";
import { UnlistenFn } from "@tauri-apps/api/event";
import { convertFileSrc } from "@tauri-apps/api/core";
import { useListen } from "@/hooks/use-listen";
interface Props {
id: string;
itemData: IVergeTestItem;
@@ -73,7 +74,7 @@ export const TestItem = (props: Props) => {
try {
onDeleteItem(uid);
} catch (err: any) {
Notice.error(err?.message || err.toString());
showNotice('error', err.message || err.toString());
}
});

View File

@@ -4,8 +4,9 @@ import { useTranslation } from "react-i18next";
import { useForm, Controller } from "react-hook-form";
import { TextField } from "@mui/material";
import { useVerge } from "@/hooks/use-verge";
import { BaseDialog, Notice } from "@/components/base";
import { BaseDialog } from "@/components/base";
import { nanoid } from "nanoid";
import { showNotice } from "@/services/noticeService";
interface Props {
onChange: (uid: string, patch?: Partial<IVergeTestItem>) => void;
@@ -99,7 +100,7 @@ export const TestViewer = forwardRef<TestViewerRef, Props>((props, ref) => {
setLoading(false);
setTimeout(() => formIns.reset(), 500);
} catch (err: any) {
Notice.error(err.message || err.toString());
showNotice('error', err.message || err.toString());
setLoading(false);
}
}),