mirror of
https://github.com/clash-verge-rev/clash-verge-rev
synced 2025-05-05 16:03:44 +08:00
refactor: notification system
This commit is contained in:
parent
e2ad2d23f8
commit
8296675574
71
src/components/base/NoticeManager.tsx
Normal file
71
src/components/base/NoticeManager.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
@ -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");
|
|
@ -3,6 +3,6 @@ export { BasePage } from "./base-page";
|
|||||||
export { BaseEmpty } from "./base-empty";
|
export { BaseEmpty } from "./base-empty";
|
||||||
export { BaseLoading } from "./base-loading";
|
export { BaseLoading } from "./base-loading";
|
||||||
export { BaseErrorBoundary } from "./base-error-boundary";
|
export { BaseErrorBoundary } from "./base-error-boundary";
|
||||||
export { Notice } from "./base-notice";
|
|
||||||
export { Switch } from "./base-switch";
|
export { Switch } from "./base-switch";
|
||||||
export { BaseLoadingOverlay } from "./base-loading-overlay";
|
export { BaseLoadingOverlay } from "./base-loading-overlay";
|
||||||
|
export { NoticeManager } from "./NoticeManager";
|
||||||
|
@ -25,7 +25,7 @@ import parseTraffic from "@/utils/parse-traffic";
|
|||||||
import { useMemo, useCallback, useState } from "react";
|
import { useMemo, useCallback, useState } from "react";
|
||||||
import { openWebUrl, updateProfile } from "@/services/cmds";
|
import { openWebUrl, updateProfile } from "@/services/cmds";
|
||||||
import { useLockFn } from "ahooks";
|
import { useLockFn } from "ahooks";
|
||||||
import { Notice } from "@/components/base";
|
import { showNotice } from "@/services/noticeService";
|
||||||
import { EnhancedCard } from "./enhanced-card";
|
import { EnhancedCard } from "./enhanced-card";
|
||||||
import { useAppData } from "@/providers/app-data-provider";
|
import { useAppData } from "@/providers/app-data-provider";
|
||||||
|
|
||||||
@ -281,14 +281,14 @@ export const HomeProfileCard = ({ current, onProfileUpdated }: HomeProfileCardPr
|
|||||||
|
|
||||||
setUpdating(true);
|
setUpdating(true);
|
||||||
try {
|
try {
|
||||||
await updateProfile(current.uid);
|
await updateProfile(current.uid, current.option);
|
||||||
Notice.success(t("Update subscription successfully"));
|
showNotice('success', t("Update subscription successfully"), 1000);
|
||||||
onProfileUpdated?.();
|
onProfileUpdated?.();
|
||||||
|
|
||||||
// 刷新首页数据
|
// 刷新首页数据
|
||||||
refreshAll();
|
refreshAll();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err?.message || err.toString());
|
showNotice('error', err.message || err.toString(), 3000);
|
||||||
} finally {
|
} finally {
|
||||||
setUpdating(false);
|
setUpdating(false);
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ import {
|
|||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useState, useMemo, memo, FC } from "react";
|
import { useState, useMemo, memo, FC } from "react";
|
||||||
import ProxyControlSwitches from "@/components/shared/ProxyControlSwitches";
|
import ProxyControlSwitches from "@/components/shared/ProxyControlSwitches";
|
||||||
import { Notice } from "@/components/base";
|
|
||||||
import {
|
import {
|
||||||
ComputerRounded,
|
ComputerRounded,
|
||||||
TroubleshootRounded,
|
TroubleshootRounded,
|
||||||
@ -20,6 +19,7 @@ import {
|
|||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import { useSystemState } from "@/hooks/use-system-state";
|
import { useSystemState } from "@/hooks/use-system-state";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
|
||||||
const LOCAL_STORAGE_TAB_KEY = "clash-verge-proxy-active-tab";
|
const LOCAL_STORAGE_TAB_KEY = "clash-verge-proxy-active-tab";
|
||||||
|
|
||||||
@ -151,7 +151,7 @@ export const ProxyTunCard: FC = () => {
|
|||||||
|
|
||||||
// 处理错误
|
// 处理错误
|
||||||
const handleError = (err: Error) => {
|
const handleError = (err: Error) => {
|
||||||
Notice.error(err.message || err.toString(), 3000);
|
showNotice('error', err.message || err.toString(), 3000);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理标签切换并保存到localStorage
|
// 处理标签切换并保存到localStorage
|
||||||
|
@ -11,13 +11,13 @@ import {
|
|||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import { EnhancedCard } from "./enhanced-card";
|
import { EnhancedCard } from "./enhanced-card";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { getSystemInfo, installService } from "@/services/cmds";
|
import { getSystemInfo, installService, restartApp } from "@/services/cmds";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { version as appVersion } from "@root/package.json";
|
import { version as appVersion } from "@root/package.json";
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { check as checkUpdate } from "@tauri-apps/plugin-updater";
|
import { check as checkUpdate } from "@tauri-apps/plugin-updater";
|
||||||
import { useLockFn } from "ahooks";
|
import { useLockFn } from "ahooks";
|
||||||
import { Notice } from "@/components/base";
|
import { showNotice } from "@/services/noticeService";
|
||||||
import { useSystemState } from "@/hooks/use-system-state";
|
import { useSystemState } from "@/hooks/use-system-state";
|
||||||
|
|
||||||
export const SystemInfoCard = () => {
|
export const SystemInfoCard = () => {
|
||||||
@ -117,14 +117,14 @@ export const SystemInfoCard = () => {
|
|||||||
// 安装系统服务
|
// 安装系统服务
|
||||||
const onInstallService = useLockFn(async () => {
|
const onInstallService = useLockFn(async () => {
|
||||||
try {
|
try {
|
||||||
Notice.info(t("Installing Service..."), 1000);
|
showNotice('info', t("Installing Service..."), 1000);
|
||||||
await installService();
|
await installService();
|
||||||
Notice.success(t("Service Installed Successfully"), 2000);
|
showNotice('success', t("Service Installed Successfully"), 2000);
|
||||||
|
|
||||||
await mutateRunningMode();
|
await mutateRunningMode();
|
||||||
|
|
||||||
} catch (err: any) {
|
} 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 {
|
try {
|
||||||
const info = await checkUpdate();
|
const info = await checkUpdate();
|
||||||
if (!info?.available) {
|
if (!info?.available) {
|
||||||
Notice.success(t("Currently on the Latest Version"));
|
showNotice('success', t("Currently on the Latest Version"));
|
||||||
} else {
|
} else {
|
||||||
Notice.info(t("Update Available"), 2000);
|
showNotice('info', t("Update Available"), 2000);
|
||||||
goToSettings();
|
goToSettings();
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err.message || err.toString());
|
showNotice('error', err.message || err.toString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -16,9 +16,9 @@ import {
|
|||||||
CloseFullscreenRounded,
|
CloseFullscreenRounded,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
import { useThemeMode } from "@/services/states";
|
import { useThemeMode } from "@/services/states";
|
||||||
import { Notice } from "@/components/base";
|
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
import getSystem from "@/utils/get-system";
|
import getSystem from "@/utils/get-system";
|
||||||
import debounce from "@/utils/debounce";
|
import debounce from "@/utils/debounce";
|
||||||
|
|
||||||
@ -127,7 +127,7 @@ export const EditorViewer = <T extends Language>(props: Props<T>) => {
|
|||||||
currData.current = value;
|
currData.current = value;
|
||||||
onChange?.(prevData.current, currData.current);
|
onChange?.(prevData.current, currData.current);
|
||||||
} catch (err: any) {
|
} 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);
|
!readOnly && onSave?.(prevData.current, currData.current);
|
||||||
onClose();
|
onClose();
|
||||||
} catch (err: any) {
|
} 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 {
|
try {
|
||||||
onClose();
|
onClose();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err.message || err.toString());
|
showNotice('error', err.message || err.toString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -40,13 +40,14 @@ import {
|
|||||||
readProfileFile,
|
readProfileFile,
|
||||||
saveProfileFile,
|
saveProfileFile,
|
||||||
} from "@/services/cmds";
|
} from "@/services/cmds";
|
||||||
import { Notice, Switch } from "@/components/base";
|
import { Switch } from "@/components/base";
|
||||||
import getSystem from "@/utils/get-system";
|
import getSystem from "@/utils/get-system";
|
||||||
import { BaseSearchBox } from "../base/base-search-box";
|
import { BaseSearchBox } from "../base/base-search-box";
|
||||||
import { Virtuoso } from "react-virtuoso";
|
import { Virtuoso } from "react-virtuoso";
|
||||||
import MonacoEditor from "react-monaco-editor";
|
import MonacoEditor from "react-monaco-editor";
|
||||||
import { useThemeMode } from "@/services/states";
|
import { useThemeMode } from "@/services/states";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
proxiesUid: string;
|
proxiesUid: string;
|
||||||
@ -285,10 +286,11 @@ export const GroupsEditorViewer = (props: Props) => {
|
|||||||
const handleSave = useLockFn(async () => {
|
const handleSave = useLockFn(async () => {
|
||||||
try {
|
try {
|
||||||
await saveProfileFile(property, currData);
|
await saveProfileFile(property, currData);
|
||||||
|
showNotice('success', t("Saved Successfully"));
|
||||||
onSave?.(prevData, currData);
|
onSave?.(prevData, currData);
|
||||||
onClose();
|
onClose();
|
||||||
} catch (err: any) {
|
} 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]);
|
setPrependSeq([formIns.getValues(), ...prependSeq]);
|
||||||
} catch (err: any) {
|
} 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()]);
|
setAppendSeq([...appendSeq, formIns.getValues()]);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err.message || err.toString());
|
showNotice('error', err.message || err.toString());
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -24,7 +24,7 @@ import {
|
|||||||
saveProfileFile,
|
saveProfileFile,
|
||||||
getNextUpdateTime,
|
getNextUpdateTime,
|
||||||
} from "@/services/cmds";
|
} from "@/services/cmds";
|
||||||
import { Notice } from "@/components/base";
|
import { showNotice } from "@/services/noticeService";
|
||||||
import { GroupsEditorViewer } from "@/components/profile/groups-editor-viewer";
|
import { GroupsEditorViewer } from "@/components/profile/groups-editor-viewer";
|
||||||
import { RulesEditorViewer } from "@/components/profile/rules-editor-viewer";
|
import { RulesEditorViewer } from "@/components/profile/rules-editor-viewer";
|
||||||
import { EditorViewer } from "@/components/profile/editor-viewer";
|
import { EditorViewer } from "@/components/profile/editor-viewer";
|
||||||
@ -271,7 +271,7 @@ export const ProfileItem = (props: Props) => {
|
|||||||
try {
|
try {
|
||||||
await viewProfile(itemData.uid);
|
await viewProfile(itemData.uid);
|
||||||
} catch (err: any) {
|
} 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);
|
await updateProfile(itemData.uid, option);
|
||||||
|
|
||||||
// 更新成功,刷新列表
|
// 更新成功,刷新列表
|
||||||
Notice.success(t("Update subscription successfully"));
|
showNotice('success', t("Update subscription successfully"));
|
||||||
mutate("getProfiles");
|
mutate("getProfiles");
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
// 更新完全失败(包括后端的回退尝试)
|
// 更新完全失败(包括后端的回退尝试)
|
||||||
|
@ -12,10 +12,10 @@ import {
|
|||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { FeaturedPlayListRounded } from "@mui/icons-material";
|
import { FeaturedPlayListRounded } from "@mui/icons-material";
|
||||||
import { viewProfile, readProfileFile, saveProfileFile } from "@/services/cmds";
|
import { viewProfile, readProfileFile, saveProfileFile } from "@/services/cmds";
|
||||||
import { Notice } from "@/components/base";
|
|
||||||
import { EditorViewer } from "@/components/profile/editor-viewer";
|
import { EditorViewer } from "@/components/profile/editor-viewer";
|
||||||
import { ProfileBox } from "./profile-box";
|
import { ProfileBox } from "./profile-box";
|
||||||
import { LogViewer } from "./log-viewer";
|
import { LogViewer } from "./log-viewer";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
logInfo?: [string, string][];
|
logInfo?: [string, string][];
|
||||||
@ -43,7 +43,7 @@ export const ProfileMore = (props: Props) => {
|
|||||||
try {
|
try {
|
||||||
await viewProfile(id);
|
await viewProfile(id);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err?.message || err.toString());
|
showNotice('error', err?.message || err.toString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -19,10 +19,11 @@ import {
|
|||||||
TextField,
|
TextField,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { createProfile, patchProfile } from "@/services/cmds";
|
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 { version } from "@root/package.json";
|
||||||
import { FileInput } from "./file-input";
|
import { FileInput } from "./file-input";
|
||||||
import { useProfiles } from "@/hooks/use-profiles";
|
import { useProfiles } from "@/hooks/use-profiles";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onChange: (isActivating?: boolean) => void;
|
onChange: (isActivating?: boolean) => void;
|
||||||
@ -140,7 +141,7 @@ export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// 首次创建/更新失败,尝试使用自身代理
|
// 首次创建/更新失败,尝试使用自身代理
|
||||||
Notice.info(t("Profile creation failed, retrying with Clash proxy..."));
|
showNotice('info', t("Profile creation failed, retrying with Clash proxy..."));
|
||||||
|
|
||||||
// 使用自身代理的配置
|
// 使用自身代理的配置
|
||||||
const retryItem = {
|
const retryItem = {
|
||||||
@ -163,7 +164,7 @@ export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
|
|||||||
await patchProfile(form.uid, { option: originalOptions });
|
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);
|
props.onChange(isActivating);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err.message || err.toString());
|
showNotice('error', err.message || err.toString());
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
@ -33,13 +33,13 @@ import {
|
|||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
import { ProxyItem } from "@/components/profile/proxy-item";
|
import { ProxyItem } from "@/components/profile/proxy-item";
|
||||||
import { readProfileFile, saveProfileFile } from "@/services/cmds";
|
import { readProfileFile, saveProfileFile } from "@/services/cmds";
|
||||||
import { Notice } from "@/components/base";
|
|
||||||
import getSystem from "@/utils/get-system";
|
import getSystem from "@/utils/get-system";
|
||||||
import { BaseSearchBox } from "../base/base-search-box";
|
import { BaseSearchBox } from "../base/base-search-box";
|
||||||
import { Virtuoso } from "react-virtuoso";
|
import { Virtuoso } from "react-virtuoso";
|
||||||
import MonacoEditor from "react-monaco-editor";
|
import MonacoEditor from "react-monaco-editor";
|
||||||
import { useThemeMode } from "@/services/states";
|
import { useThemeMode } from "@/services/states";
|
||||||
import parseUri from "@/utils/uri-parser";
|
import parseUri from "@/utils/uri-parser";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
profileUid: string;
|
profileUid: string;
|
||||||
@ -150,7 +150,7 @@ export const ProxiesEditorViewer = (props: Props) => {
|
|||||||
names.push(proxy.name);
|
names.push(proxy.name);
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err.message || err.toString());
|
showNotice('error', err.message || err.toString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return proxies;
|
return proxies;
|
||||||
@ -212,10 +212,11 @@ export const ProxiesEditorViewer = (props: Props) => {
|
|||||||
const handleSave = useLockFn(async () => {
|
const handleSave = useLockFn(async () => {
|
||||||
try {
|
try {
|
||||||
await saveProfileFile(property, currData);
|
await saveProfileFile(property, currData);
|
||||||
|
showNotice('success', t("Saved Successfully"));
|
||||||
onSave?.(prevData, currData);
|
onSave?.(prevData, currData);
|
||||||
onClose();
|
onClose();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err.message || err.toString());
|
showNotice('error', err.toString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -34,13 +34,14 @@ import {
|
|||||||
VerticalAlignBottomRounded,
|
VerticalAlignBottomRounded,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
import { readProfileFile, saveProfileFile } from "@/services/cmds";
|
import { readProfileFile, saveProfileFile } from "@/services/cmds";
|
||||||
import { Notice, Switch } from "@/components/base";
|
import { Switch } from "@/components/base";
|
||||||
import getSystem from "@/utils/get-system";
|
import getSystem from "@/utils/get-system";
|
||||||
import { RuleItem } from "@/components/profile/rule-item";
|
import { RuleItem } from "@/components/profile/rule-item";
|
||||||
import { BaseSearchBox } from "../base/base-search-box";
|
import { BaseSearchBox } from "../base/base-search-box";
|
||||||
import { Virtuoso } from "react-virtuoso";
|
import { Virtuoso } from "react-virtuoso";
|
||||||
import MonacoEditor from "react-monaco-editor";
|
import MonacoEditor from "react-monaco-editor";
|
||||||
import { useThemeMode } from "@/services/states";
|
import { useThemeMode } from "@/services/states";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
groupsUid: string;
|
groupsUid: string;
|
||||||
@ -414,10 +415,11 @@ export const RulesEditorViewer = (props: Props) => {
|
|||||||
const handleSave = useLockFn(async () => {
|
const handleSave = useLockFn(async () => {
|
||||||
try {
|
try {
|
||||||
await saveProfileFile(property, currData);
|
await saveProfileFile(property, currData);
|
||||||
|
showNotice('success', t("Saved Successfully"));
|
||||||
onSave?.(prevData, currData);
|
onSave?.(prevData, currData);
|
||||||
onClose();
|
onClose();
|
||||||
} catch (err: any) {
|
} 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;
|
if (prependSeq.includes(raw)) return;
|
||||||
setPrependSeq([raw, ...prependSeq]);
|
setPrependSeq([raw, ...prependSeq]);
|
||||||
} catch (err: any) {
|
} 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;
|
if (appendSeq.includes(raw)) return;
|
||||||
setAppendSeq([...appendSeq, raw]);
|
setAppendSeq([...appendSeq, raw]);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err.message || err.toString());
|
showNotice('error', err.message || err.toString());
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -21,7 +21,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { useLockFn } from "ahooks";
|
import { useLockFn } from "ahooks";
|
||||||
import { proxyProviderUpdate } from "@/services/api";
|
import { proxyProviderUpdate } from "@/services/api";
|
||||||
import { useAppData } from "@/providers/app-data-provider";
|
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 { StorageOutlined, RefreshRounded } from "@mui/icons-material";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import parseTraffic from "@/utils/parse-traffic";
|
import parseTraffic from "@/utils/parse-traffic";
|
||||||
@ -81,9 +81,9 @@ export const ProviderButton = () => {
|
|||||||
await refreshProxy();
|
await refreshProxy();
|
||||||
await refreshProxyProviders();
|
await refreshProxyProviders();
|
||||||
|
|
||||||
Notice.success(`${name} 更新成功`);
|
showNotice('success', `${name} 更新成功`);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(`${name} 更新失败: ${err?.message || err.toString()}`);
|
showNotice('error', `${name} 更新失败: ${err?.message || err.toString()}`);
|
||||||
} finally {
|
} finally {
|
||||||
// 清除更新状态
|
// 清除更新状态
|
||||||
setUpdating(prev => ({ ...prev, [name]: false }));
|
setUpdating(prev => ({ ...prev, [name]: false }));
|
||||||
@ -96,7 +96,7 @@ export const ProviderButton = () => {
|
|||||||
// 获取所有provider的名称
|
// 获取所有provider的名称
|
||||||
const allProviders = Object.keys(proxyProviders || {});
|
const allProviders = Object.keys(proxyProviders || {});
|
||||||
if (allProviders.length === 0) {
|
if (allProviders.length === 0) {
|
||||||
Notice.info("没有可更新的代理提供者");
|
showNotice('info', "没有可更新的代理提供者");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,9 +123,9 @@ export const ProviderButton = () => {
|
|||||||
await refreshProxy();
|
await refreshProxy();
|
||||||
await refreshProxyProviders();
|
await refreshProxyProviders();
|
||||||
|
|
||||||
Notice.success("全部代理提供者更新成功");
|
showNotice('success', "全部代理提供者更新成功");
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(`更新失败: ${err?.message || err.toString()}`);
|
showNotice('error', `更新失败: ${err?.message || err.toString()}`);
|
||||||
} finally {
|
} finally {
|
||||||
// 清除所有更新状态
|
// 清除所有更新状态
|
||||||
setUpdating({});
|
setUpdating({});
|
||||||
|
@ -19,10 +19,10 @@ import {
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useLockFn } from "ahooks";
|
import { useLockFn } from "ahooks";
|
||||||
import { ruleProviderUpdate } from "@/services/api";
|
import { ruleProviderUpdate } from "@/services/api";
|
||||||
import { Notice } from "@/components/base";
|
|
||||||
import { StorageOutlined, RefreshRounded } from "@mui/icons-material";
|
import { StorageOutlined, RefreshRounded } from "@mui/icons-material";
|
||||||
import { useAppData } from "@/providers/app-data-provider";
|
import { useAppData } from "@/providers/app-data-provider";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
|
||||||
// 定义规则提供者类型
|
// 定义规则提供者类型
|
||||||
interface RuleProviderItem {
|
interface RuleProviderItem {
|
||||||
@ -67,9 +67,9 @@ export const ProviderButton = () => {
|
|||||||
await refreshRules();
|
await refreshRules();
|
||||||
await refreshRuleProviders();
|
await refreshRuleProviders();
|
||||||
|
|
||||||
Notice.success(`${name} 更新成功`);
|
showNotice('success', `${name} 更新成功`);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(`${name} 更新失败: ${err?.message || err.toString()}`);
|
showNotice('error', `${name} 更新失败: ${err?.message || err.toString()}`);
|
||||||
} finally {
|
} finally {
|
||||||
// 清除更新状态
|
// 清除更新状态
|
||||||
setUpdating(prev => ({ ...prev, [name]: false }));
|
setUpdating(prev => ({ ...prev, [name]: false }));
|
||||||
@ -82,7 +82,7 @@ export const ProviderButton = () => {
|
|||||||
// 获取所有provider的名称
|
// 获取所有provider的名称
|
||||||
const allProviders = Object.keys(ruleProviders || {});
|
const allProviders = Object.keys(ruleProviders || {});
|
||||||
if (allProviders.length === 0) {
|
if (allProviders.length === 0) {
|
||||||
Notice.info("没有可更新的规则提供者");
|
showNotice('info', "没有可更新的规则提供者");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,9 +109,9 @@ export const ProviderButton = () => {
|
|||||||
await refreshRules();
|
await refreshRules();
|
||||||
await refreshRuleProviders();
|
await refreshRuleProviders();
|
||||||
|
|
||||||
Notice.success("全部规则提供者更新成功");
|
showNotice('success', "全部规则提供者更新成功");
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(`更新失败: ${err?.message || err.toString()}`);
|
showNotice('error', `更新失败: ${err?.message || err.toString()}`);
|
||||||
} finally {
|
} finally {
|
||||||
// 清除所有更新状态
|
// 清除所有更新状态
|
||||||
setUpdating({});
|
setUpdating({});
|
||||||
|
@ -2,7 +2,6 @@ import { useState, useRef, memo, useEffect } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import { Notice } from "@/components/base";
|
|
||||||
import { isValidUrl } from "@/utils/helper";
|
import { isValidUrl } from "@/utils/helper";
|
||||||
import { useLockFn } from "ahooks";
|
import { useLockFn } from "ahooks";
|
||||||
import {
|
import {
|
||||||
@ -17,6 +16,7 @@ import {
|
|||||||
import Visibility from "@mui/icons-material/Visibility";
|
import Visibility from "@mui/icons-material/Visibility";
|
||||||
import VisibilityOff from "@mui/icons-material/VisibilityOff";
|
import VisibilityOff from "@mui/icons-material/VisibilityOff";
|
||||||
import { saveWebdavConfig, createWebdavBackup } from "@/services/cmds";
|
import { saveWebdavConfig, createWebdavBackup } from "@/services/cmds";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
|
||||||
export interface BackupConfigViewerProps {
|
export interface BackupConfigViewerProps {
|
||||||
onBackupSuccess: () => Promise<void>;
|
onBackupSuccess: () => Promise<void>;
|
||||||
@ -83,21 +83,21 @@ export const BackupConfigViewer = memo(
|
|||||||
|
|
||||||
if (!url) {
|
if (!url) {
|
||||||
urlRef.current?.focus();
|
urlRef.current?.focus();
|
||||||
Notice.error(t("WebDAV URL Required"));
|
showNotice('error', t("WebDAV URL Required"));
|
||||||
throw new Error(t("WebDAV URL Required"));
|
throw new Error(t("WebDAV URL Required"));
|
||||||
} else if (!isValidUrl(url)) {
|
} else if (!isValidUrl(url)) {
|
||||||
urlRef.current?.focus();
|
urlRef.current?.focus();
|
||||||
Notice.error(t("Invalid WebDAV URL"));
|
showNotice('error', t("Invalid WebDAV URL"));
|
||||||
throw new Error(t("Invalid WebDAV URL"));
|
throw new Error(t("Invalid WebDAV URL"));
|
||||||
}
|
}
|
||||||
if (!username) {
|
if (!username) {
|
||||||
usernameRef.current?.focus();
|
usernameRef.current?.focus();
|
||||||
Notice.error(t("WebDAV URL Required"));
|
showNotice('error', t("WebDAV URL Required"));
|
||||||
throw new Error(t("Username Required"));
|
throw new Error(t("Username Required"));
|
||||||
}
|
}
|
||||||
if (!password) {
|
if (!password) {
|
||||||
passwordRef.current?.focus();
|
passwordRef.current?.focus();
|
||||||
Notice.error(t("WebDAV URL Required"));
|
showNotice('error', t("WebDAV URL Required"));
|
||||||
throw new Error(t("Password Required"));
|
throw new Error(t("Password Required"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -111,11 +111,11 @@ export const BackupConfigViewer = memo(
|
|||||||
data.username.trim(),
|
data.username.trim(),
|
||||||
data.password,
|
data.password,
|
||||||
).then(() => {
|
).then(() => {
|
||||||
Notice.success(t("WebDAV Config Saved"));
|
showNotice('success', t("WebDAV Config Saved"));
|
||||||
onSaveSuccess();
|
onSaveSuccess();
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Notice.error(t("WebDAV Config Save Failed", { error }), 3000);
|
showNotice('error', t("WebDAV Config Save Failed", { error }), 3000);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@ -126,11 +126,11 @@ export const BackupConfigViewer = memo(
|
|||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
await createWebdavBackup().then(async () => {
|
await createWebdavBackup().then(async () => {
|
||||||
|
showNotice('success', t("Backup Created"));
|
||||||
await onBackupSuccess();
|
await onBackupSuccess();
|
||||||
Notice.success(t("Backup Created"));
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Notice.error(t("Backup Failed", { error }));
|
showNotice('error', t("Backup Failed", { error }));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
TablePagination,
|
TablePagination,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { Notice } from "@/components/base";
|
|
||||||
import { Typography } from "@mui/material";
|
import { Typography } from "@mui/material";
|
||||||
import { useLockFn } from "ahooks";
|
import { useLockFn } from "ahooks";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@ -24,6 +23,7 @@ import {
|
|||||||
} from "@/services/cmds";
|
} from "@/services/cmds";
|
||||||
import DeleteIcon from "@mui/icons-material/Delete";
|
import DeleteIcon from "@mui/icons-material/Delete";
|
||||||
import RestoreIcon from "@mui/icons-material/Restore";
|
import RestoreIcon from "@mui/icons-material/Restore";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
|
||||||
export type BackupFile = IWebDavFile & {
|
export type BackupFile = IWebDavFile & {
|
||||||
platform: string;
|
platform: string;
|
||||||
@ -61,7 +61,7 @@ export const BackupTableViewer = memo(
|
|||||||
|
|
||||||
const handleRestore = useLockFn(async (filename: string) => {
|
const handleRestore = useLockFn(async (filename: string) => {
|
||||||
await restoreWebDavBackup(filename).then(() => {
|
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();
|
await restartApp();
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { mutate } from "swr";
|
import { mutate } from "swr";
|
||||||
import { forwardRef, useImperativeHandle, useState } from "react";
|
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 { useTranslation } from "react-i18next";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import { useLockFn } from "ahooks";
|
import { useLockFn } from "ahooks";
|
||||||
@ -19,6 +19,7 @@ import {
|
|||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { changeClashCore, restartCore } from "@/services/cmds";
|
import { changeClashCore, restartCore } from "@/services/cmds";
|
||||||
import { closeAllConnections, upgradeCore } from "@/services/api";
|
import { closeAllConnections, upgradeCore } from "@/services/api";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
|
||||||
const VALID_CORE = [
|
const VALID_CORE = [
|
||||||
{ name: "Mihomo", core: "verge-mihomo", chip: "Release Version" },
|
{ name: "Mihomo", core: "verge-mihomo", chip: "Release Version" },
|
||||||
@ -48,7 +49,7 @@ export const ClashCoreViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
const errorMsg = await changeClashCore(core);
|
const errorMsg = await changeClashCore(core);
|
||||||
|
|
||||||
if (errorMsg) {
|
if (errorMsg) {
|
||||||
Notice.error(errorMsg);
|
showNotice('error', errorMsg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,16 +59,16 @@ export const ClashCoreViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
mutate("getVersion");
|
mutate("getVersion");
|
||||||
}, 500);
|
}, 500);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err?.message || err.toString());
|
showNotice('error', err.message || err.toString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const onRestart = useLockFn(async () => {
|
const onRestart = useLockFn(async () => {
|
||||||
try {
|
try {
|
||||||
await restartCore();
|
await restartCore();
|
||||||
Notice.success(t(`Clash Core Restarted`), 1000);
|
showNotice('success', t(`Clash Core Restarted`), 1000);
|
||||||
} catch (err: any) {
|
} 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);
|
setUpgrading(true);
|
||||||
await upgradeCore();
|
await upgradeCore();
|
||||||
setUpgrading(false);
|
setUpgrading(false);
|
||||||
Notice.success(t(`Core Version Updated`), 1000);
|
showNotice('success', t(`Core Version Updated`), 1000);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setUpgrading(false);
|
setUpgrading(false);
|
||||||
Notice.error(err?.response.data.message || err.toString());
|
showNotice('error', err.response?.data?.message || err.toString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -3,9 +3,10 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { useLockFn } from "ahooks";
|
import { useLockFn } from "ahooks";
|
||||||
import { List, ListItem, ListItemText, TextField } from "@mui/material";
|
import { List, ListItem, ListItemText, TextField } from "@mui/material";
|
||||||
import { useClashInfo } from "@/hooks/use-clash";
|
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 { useVerge } from "@/hooks/use-verge";
|
||||||
import getSystem from "@/utils/get-system";
|
import getSystem from "@/utils/get-system";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
const OS = getSystem();
|
const OS = getSystem();
|
||||||
|
|
||||||
export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
|
export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
|
||||||
@ -78,18 +79,18 @@ export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
OS === "linux" &&
|
OS === "linux" &&
|
||||||
new Set([redirPort, tproxyPort, mixedPort, socksPort, port]).size !== 5
|
new Set([redirPort, tproxyPort, mixedPort, socksPort, port]).size !== 5
|
||||||
) {
|
) {
|
||||||
Notice.error(t("Port Conflict"), 4000);
|
showNotice('error', t("Port Conflict"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
OS === "macos" &&
|
OS === "macos" &&
|
||||||
new Set([redirPort, mixedPort, socksPort, port]).size !== 4
|
new Set([redirPort, mixedPort, socksPort, port]).size !== 4
|
||||||
) {
|
) {
|
||||||
Notice.error(t("Port Conflict"), 4000);
|
showNotice('error', t("Port Conflict"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (OS === "windows" && new Set([mixedPort, socksPort, port]).size !== 3) {
|
if (OS === "windows" && new Set([mixedPort, socksPort, port]).size !== 3) {
|
||||||
Notice.error(t("Port Conflict"), 4000);
|
showNotice('error', t("Port Conflict"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@ -145,9 +146,9 @@ export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
Notice.success(t("Clash Port Modified"), 1000);
|
showNotice('success', t("Clash Port Modified"));
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err.message || err.toString(), 4000);
|
showNotice('error', err.message || err.toString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -3,7 +3,8 @@ import { useLockFn } from "ahooks";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { List, ListItem, ListItemText, TextField } from "@mui/material";
|
import { List, ListItem, ListItemText, TextField } from "@mui/material";
|
||||||
import { useClashInfo } from "@/hooks/use-clash";
|
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) => {
|
export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -26,10 +27,10 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
const onSave = useLockFn(async () => {
|
const onSave = useLockFn(async () => {
|
||||||
try {
|
try {
|
||||||
await patchInfo({ "external-controller": controller, secret });
|
await patchInfo({ "external-controller": controller, secret });
|
||||||
Notice.success(t("External Controller Address Modified"), 1000);
|
showNotice('success', t("External Controller Address Modified"), 1000);
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err.message || err.toString(), 4000);
|
showNotice('error', err.message || err.toString(), 4000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -17,12 +17,13 @@ import {
|
|||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { RestartAltRounded } from "@mui/icons-material";
|
import { RestartAltRounded } from "@mui/icons-material";
|
||||||
import { useClash } from "@/hooks/use-clash";
|
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 yaml from "js-yaml";
|
||||||
import MonacoEditor from "react-monaco-editor";
|
import MonacoEditor from "react-monaco-editor";
|
||||||
import { useThemeMode } from "@/services/states";
|
import { useThemeMode } from "@/services/states";
|
||||||
import getSystem from "@/utils/get-system";
|
import getSystem from "@/utils/get-system";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
|
||||||
const Item = styled(ListItem)(({ theme }) => ({
|
const Item = styled(ListItem)(({ theme }) => ({
|
||||||
padding: "8px 0",
|
padding: "8px 0",
|
||||||
@ -374,7 +375,7 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
formatNameserverPolicy(dnsConfig["nameserver-policy"]) || "",
|
formatNameserverPolicy(dnsConfig["nameserver-policy"]) || "",
|
||||||
});
|
});
|
||||||
} catch (err: any) {
|
} 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);
|
setOpen(false);
|
||||||
Notice.success(t("DNS settings saved"));
|
showNotice('success', t("DNS settings saved"));
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err.message || err.toString());
|
showNotice('error', err.message || err.toString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -3,8 +3,9 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { useLockFn } from "ahooks";
|
import { useLockFn } from "ahooks";
|
||||||
import { styled, Typography, Switch } from "@mui/material";
|
import { styled, Typography, Switch } from "@mui/material";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
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 { HotkeyInput } from "./hotkey-input";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
|
||||||
const ItemWrapper = styled("div")`
|
const ItemWrapper = styled("div")`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -79,7 +80,7 @@ export const HotkeyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
});
|
});
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err.message || err.toString());
|
showNotice('error', err.toString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
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 { GuardState } from "./guard-state";
|
||||||
import { open as openDialog } from "@tauri-apps/plugin-dialog";
|
import { open as openDialog } from "@tauri-apps/plugin-dialog";
|
||||||
import { convertFileSrc } from "@tauri-apps/api/core";
|
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 { join } from "@tauri-apps/api/path";
|
||||||
import { exists } from "@tauri-apps/plugin-fs";
|
import { exists } from "@tauri-apps/plugin-fs";
|
||||||
import getSystem from "@/utils/get-system";
|
import getSystem from "@/utils/get-system";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
|
||||||
const OS = getSystem();
|
const OS = getSystem();
|
||||||
|
|
||||||
@ -87,7 +88,7 @@ export const LayoutViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
|
|
||||||
const onSwitchFormat = (_e: any, value: boolean) => value;
|
const onSwitchFormat = (_e: any, value: boolean) => value;
|
||||||
const onError = (err: any) => {
|
const onError = (err: any) => {
|
||||||
Notice.error(err.message || err.toString());
|
showNotice('error', err.message || err.toString());
|
||||||
};
|
};
|
||||||
const onChangeData = (patch: Partial<IVergeConfig>) => {
|
const onChangeData = (patch: Partial<IVergeConfig>) => {
|
||||||
mutateVerge({ ...verge, ...patch }, false);
|
mutateVerge({ ...verge, ...patch }, false);
|
||||||
|
@ -10,9 +10,10 @@ import {
|
|||||||
InputAdornment,
|
InputAdornment,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
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 { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
||||||
import { entry_lightweight_mode } from "@/services/cmds";
|
import { entry_lightweight_mode } from "@/services/cmds";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
|
||||||
export const LiteModeViewer = forwardRef<DialogRef>((props, ref) => {
|
export const LiteModeViewer = forwardRef<DialogRef>((props, ref) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -43,7 +44,7 @@ export const LiteModeViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
});
|
});
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err.message || err.toString());
|
showNotice('error', err.message || err.toString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -11,8 +11,9 @@ import {
|
|||||||
InputAdornment,
|
InputAdornment,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
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 { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
|
||||||
export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -61,7 +62,7 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
});
|
});
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err.message || err.toString());
|
showNotice('error', err.toString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
|
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { BaseDialog, DialogRef, Notice } from "@/components/base";
|
import { BaseDialog, DialogRef } from "@/components/base";
|
||||||
import { getNetworkInterfacesInfo } from "@/services/cmds";
|
import { getNetworkInterfacesInfo } from "@/services/cmds";
|
||||||
import { alpha, Box, Button, Chip, IconButton } from "@mui/material";
|
import { alpha, Box, Button, Chip, IconButton } from "@mui/material";
|
||||||
import { ContentCopyRounded } from "@mui/icons-material";
|
import { ContentCopyRounded } from "@mui/icons-material";
|
||||||
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
|
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
|
||||||
export const NetworkInterfaceViewer = forwardRef<DialogRef>((props, ref) => {
|
export const NetworkInterfaceViewer = forwardRef<DialogRef>((props, ref) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -128,7 +129,7 @@ const AddressDisplay = (props: { label: string; content: string }) => {
|
|||||||
size="small"
|
size="small"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await writeText(props.content);
|
await writeText(props.content);
|
||||||
Notice.success(t("Copy Success"));
|
showNotice('success', t("Copy Success"));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ContentCopyRounded sx={{ fontSize: "18px" }} />
|
<ContentCopyRounded sx={{ fontSize: "18px" }} />
|
||||||
|
@ -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 { BaseFieldset } from "@/components/base/base-fieldset";
|
||||||
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
||||||
import { EditorViewer } from "@/components/profile/editor-viewer";
|
import { EditorViewer } from "@/components/profile/editor-viewer";
|
||||||
@ -36,6 +36,7 @@ import {
|
|||||||
} from "react";
|
} from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { mutate } from "swr";
|
import { mutate } from "swr";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
const DEFAULT_PAC = `function FindProxyForURL(url, host) {
|
const DEFAULT_PAC = `function FindProxyForURL(url, host) {
|
||||||
return "PROXY %proxy_host%:%mixed-port%; SOCKS5 %proxy_host%:%mixed-port%; DIRECT;";
|
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 () => {
|
const onSave = useLockFn(async () => {
|
||||||
if (value.duration < 1) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
if (value.bypass && !validReg.test(value.bypass)) {
|
if (value.bypass && !validReg.test(value.bypass)) {
|
||||||
Notice.error(t("Invalid Bypass Format"));
|
showNotice('error', t("Invalid Bypass Format"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,7 +223,7 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
!ipv6Regex.test(value.proxy_host) &&
|
!ipv6Regex.test(value.proxy_host) &&
|
||||||
!hostnameRegex.test(value.proxy_host)
|
!hostnameRegex.test(value.proxy_host)
|
||||||
) {
|
) {
|
||||||
Notice.error(t("Invalid Proxy Host Format"));
|
showNotice('error', t("Invalid Proxy Host Format"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,7 +305,7 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
|
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err.message || err.toString());
|
showNotice('error', err.toString());
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
}
|
}
|
||||||
|
@ -12,9 +12,10 @@ import {
|
|||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import { defaultTheme, defaultDarkTheme } from "@/pages/_theme";
|
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 { EditorViewer } from "@/components/profile/editor-viewer";
|
||||||
import { EditRounded } from "@mui/icons-material";
|
import { EditRounded } from "@mui/icons-material";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
|
||||||
export const ThemeViewer = forwardRef<DialogRef>((props, ref) => {
|
export const ThemeViewer = forwardRef<DialogRef>((props, ref) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -48,7 +49,7 @@ export const ThemeViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
await patchVerge({ theme_setting: theme });
|
await patchVerge({ theme_setting: theme });
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err.message || err.toString());
|
showNotice('error', err.toString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -11,10 +11,11 @@ import {
|
|||||||
TextField,
|
TextField,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useClash } from "@/hooks/use-clash";
|
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 { StackModeSwitch } from "./stack-mode-switch";
|
||||||
import { enhanceProfiles } from "@/services/cmds";
|
import { enhanceProfiles } from "@/services/cmds";
|
||||||
import getSystem from "@/utils/get-system";
|
import getSystem from "@/utils/get-system";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
|
||||||
const OS = getSystem();
|
const OS = getSystem();
|
||||||
|
|
||||||
@ -76,13 +77,13 @@ export const TunViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
await enhanceProfiles();
|
await enhanceProfiles();
|
||||||
Notice.success(t("Settings Applied"), 1000);
|
showNotice('success', t("Settings Applied"));
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err.message || err.toString(), 3000);
|
showNotice('error', err.message || err.toString());
|
||||||
}
|
}
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err.message || err.toString());
|
showNotice('error', err.message || err.toString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -5,13 +5,14 @@ import { Box, LinearProgress, Button } from "@mui/material";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { relaunch } from "@tauri-apps/plugin-process";
|
import { relaunch } from "@tauri-apps/plugin-process";
|
||||||
import { check as checkUpdate } from "@tauri-apps/plugin-updater";
|
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 { useUpdateState, useSetUpdateState } from "@/services/states";
|
||||||
import { Event, UnlistenFn } from "@tauri-apps/api/event";
|
import { Event, UnlistenFn } from "@tauri-apps/api/event";
|
||||||
import { portableFlag } from "@/pages/_layout";
|
import { portableFlag } from "@/pages/_layout";
|
||||||
import { open as openUrl } from "@tauri-apps/plugin-shell";
|
import { open as openUrl } from "@tauri-apps/plugin-shell";
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
import { useListen } from "@/hooks/use-listen";
|
import { useListen } from "@/hooks/use-listen";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
|
||||||
let eventListener: UnlistenFn | null = null;
|
let eventListener: UnlistenFn | null = null;
|
||||||
|
|
||||||
@ -55,12 +56,12 @@ export const UpdateViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
|
|
||||||
const onUpdate = useLockFn(async () => {
|
const onUpdate = useLockFn(async () => {
|
||||||
if (portableFlag) {
|
if (portableFlag) {
|
||||||
Notice.error(t("Portable Updater Error"));
|
showNotice('error', t("Portable Updater Error"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!updateInfo?.body) return;
|
if (!updateInfo?.body) return;
|
||||||
if (breakChangeFlag) {
|
if (breakChangeFlag) {
|
||||||
Notice.error(t("Break Change Update Error"));
|
showNotice('error', t("Break Change Update Error"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (updateState) return;
|
if (updateState) return;
|
||||||
@ -82,7 +83,7 @@ export const UpdateViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
await updateInfo.downloadAndInstall();
|
await updateInfo.downloadAndInstall();
|
||||||
await relaunch();
|
await relaunch();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err?.message || err.toString());
|
showNotice('error', err?.message || err.toString());
|
||||||
} finally {
|
} finally {
|
||||||
setUpdateState(false);
|
setUpdateState(false);
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,10 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { Button, Box, Typography } from "@mui/material";
|
import { Button, Box, Typography } from "@mui/material";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import { openWebUrl } from "@/services/cmds";
|
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 { useClashInfo } from "@/hooks/use-clash";
|
||||||
import { WebUIItem } from "./web-ui-item";
|
import { WebUIItem } from "./web-ui-item";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
|
||||||
export const WebUIViewer = forwardRef<DialogRef>((props, ref) => {
|
export const WebUIViewer = forwardRef<DialogRef>((props, ref) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -71,7 +72,7 @@ export const WebUIViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
|
|
||||||
await openWebUrl(url);
|
await openWebUrl(url);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
Notice.error(e.message || e.toString());
|
showNotice('error', e.message || e.toString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import {
|
|||||||
ShuffleRounded,
|
ShuffleRounded,
|
||||||
LanRounded,
|
LanRounded,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
import { DialogRef, Notice, Switch } from "@/components/base";
|
import { DialogRef, Switch } from "@/components/base";
|
||||||
import { useClash } from "@/hooks/use-clash";
|
import { useClash } from "@/hooks/use-clash";
|
||||||
import { GuardState } from "./mods/guard-state";
|
import { GuardState } from "./mods/guard-state";
|
||||||
import { WebUIViewer } from "./mods/web-ui-viewer";
|
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 { invoke } from "@tauri-apps/api/core";
|
||||||
import { useLockFn } from "ahooks";
|
import { useLockFn } from "ahooks";
|
||||||
import { useListen } from "@/hooks/use-listen";
|
import { useListen } from "@/hooks/use-listen";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
|
||||||
const isWIN = getSystem() === "windows";
|
const isWIN = getSystem() === "windows";
|
||||||
|
|
||||||
@ -77,9 +78,9 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
const onUpdateGeo = async () => {
|
const onUpdateGeo = async () => {
|
||||||
try {
|
try {
|
||||||
await updateGeoData();
|
await updateGeoData();
|
||||||
Notice.success(t("GeoData Updated"));
|
showNotice('success', t("GeoData Updated"));
|
||||||
} catch (err: any) {
|
} 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);
|
setDnsSettingsEnabled(!enable);
|
||||||
localStorage.setItem("dns_settings_enabled", String(!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(() => {
|
await patchVerge({ enable_dns_settings: !enable }).catch(() => {
|
||||||
// 忽略恢复状态时的错误
|
// 忽略恢复状态时的错误
|
||||||
});
|
});
|
||||||
@ -224,10 +225,7 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
color={enable_random_port ? "primary" : "inherit"}
|
color={enable_random_port ? "primary" : "inherit"}
|
||||||
icon={ShuffleRounded}
|
icon={ShuffleRounded}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
Notice.success(
|
showNotice('success', t("Restart Application to Apply Modifications"), 1000);
|
||||||
t("Restart Application to Apply Modifications"),
|
|
||||||
1000,
|
|
||||||
);
|
|
||||||
onChangeVerge({ enable_random_port: !enable_random_port });
|
onChangeVerge({ enable_random_port: !enable_random_port });
|
||||||
patchVerge({ enable_random_port: !enable_random_port });
|
patchVerge({ enable_random_port: !enable_random_port });
|
||||||
}}
|
}}
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
BuildRounded,
|
BuildRounded,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
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 { SettingList, SettingItem } from "./mods/setting-comp";
|
||||||
import { GuardState } from "./mods/guard-state";
|
import { GuardState } from "./mods/guard-state";
|
||||||
import { SysproxyViewer } from "./mods/sysproxy-viewer";
|
import { SysproxyViewer } from "./mods/sysproxy-viewer";
|
||||||
@ -25,6 +25,7 @@ import { useLockFn } from "ahooks";
|
|||||||
import { Button, Tooltip } from "@mui/material";
|
import { Button, Tooltip } from "@mui/material";
|
||||||
import { useSystemState } from "@/hooks/use-system-state";
|
import { useSystemState } from "@/hooks/use-system-state";
|
||||||
import { closeAllConnections } from "@/services/api";
|
import { closeAllConnections } from "@/services/api";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onError?: (err: Error) => void;
|
onError?: (err: Error) => void;
|
||||||
@ -86,13 +87,13 @@ const SettingSystem = ({ onError }: Props) => {
|
|||||||
// 安装系统服务
|
// 安装系统服务
|
||||||
const onInstallService = useLockFn(async () => {
|
const onInstallService = useLockFn(async () => {
|
||||||
try {
|
try {
|
||||||
Notice.info(t("Installing Service..."), 1000);
|
showNotice('info', t("Installing Service..."), 1000);
|
||||||
await installService();
|
await installService();
|
||||||
Notice.success(t("Service Installed Successfully"), 2000);
|
showNotice('success', t("Service Installed Successfully"), 2000);
|
||||||
// 重新获取运行模式
|
// 重新获取运行模式
|
||||||
await mutateRunningMode();
|
await mutateRunningMode();
|
||||||
} catch (err: any) {
|
} 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) => {
|
onGuard={(e) => {
|
||||||
// 当在sidecar模式下且非管理员模式时禁用切换
|
// 当在sidecar模式下且非管理员模式时禁用切换
|
||||||
if (isSidecarMode && !isAdminMode) {
|
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 Promise.reject(new Error(t("TUN requires Service Mode")));
|
||||||
}
|
}
|
||||||
return patchVerge({ enable_tun_mode: e });
|
return patchVerge({ enable_tun_mode: e });
|
||||||
@ -215,7 +216,7 @@ const SettingSystem = ({ onError }: Props) => {
|
|||||||
}}
|
}}
|
||||||
onGuard={async (e) => {
|
onGuard={async (e) => {
|
||||||
if (isAdminMode) {
|
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 {
|
try {
|
||||||
|
@ -12,7 +12,7 @@ import {
|
|||||||
import { check as checkUpdate } from "@tauri-apps/plugin-updater";
|
import { check as checkUpdate } from "@tauri-apps/plugin-updater";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import { version } from "@root/package.json";
|
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 { SettingList, SettingItem } from "./mods/setting-comp";
|
||||||
import { ConfigViewer } from "./mods/config-viewer";
|
import { ConfigViewer } from "./mods/config-viewer";
|
||||||
import { HotkeyViewer } from "./mods/hotkey-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 { LiteModeViewer } from "./mods/lite-mode-viewer";
|
||||||
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
||||||
import { ContentCopyRounded } from "@mui/icons-material";
|
import { ContentCopyRounded } from "@mui/icons-material";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onError?: (err: Error) => void;
|
onError?: (err: Error) => void;
|
||||||
@ -46,18 +47,18 @@ const SettingVergeAdvanced = ({ onError }: Props) => {
|
|||||||
try {
|
try {
|
||||||
const info = await checkUpdate();
|
const info = await checkUpdate();
|
||||||
if (!info?.available) {
|
if (!info?.available) {
|
||||||
Notice.success(t("Currently on the Latest Version"));
|
showNotice('success', t("Currently on the Latest Version"));
|
||||||
} else {
|
} else {
|
||||||
updateRef.current?.open();
|
updateRef.current?.open();
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err.message || err.toString());
|
showNotice('error', err.message || err.toString());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onExportDiagnosticInfo = useCallback(async () => {
|
const onExportDiagnosticInfo = useCallback(async () => {
|
||||||
await exportDiagnosticInfo();
|
await exportDiagnosticInfo();
|
||||||
Notice.success(t("Copy Success"), 1000);
|
showNotice('success', t("Copy Success"), 1000);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -4,7 +4,7 @@ import { open } from "@tauri-apps/plugin-dialog";
|
|||||||
import { Button, MenuItem, Select, Input } from "@mui/material";
|
import { Button, MenuItem, Select, Input } from "@mui/material";
|
||||||
import { copyClashEnv } from "@/services/cmds";
|
import { copyClashEnv } from "@/services/cmds";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
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 { SettingList, SettingItem } from "./mods/setting-comp";
|
||||||
import { ThemeModeSwitch } from "./mods/theme-mode-switch";
|
import { ThemeModeSwitch } from "./mods/theme-mode-switch";
|
||||||
import { ConfigViewer } from "./mods/config-viewer";
|
import { ConfigViewer } from "./mods/config-viewer";
|
||||||
@ -20,6 +20,7 @@ import { routers } from "@/pages/_routers";
|
|||||||
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
||||||
import { ContentCopyRounded } from "@mui/icons-material";
|
import { ContentCopyRounded } from "@mui/icons-material";
|
||||||
import { languages } from "@/services/i18n";
|
import { languages } from "@/services/i18n";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onError?: (err: Error) => void;
|
onError?: (err: Error) => void;
|
||||||
@ -66,7 +67,7 @@ const SettingVergeBasic = ({ onError }: Props) => {
|
|||||||
|
|
||||||
const onCopyClashEnv = useCallback(async () => {
|
const onCopyClashEnv = useCallback(async () => {
|
||||||
await copyClashEnv();
|
await copyClashEnv();
|
||||||
Notice.success(t("Copy Success"), 1000);
|
showNotice('success', t("Copy Success"), 1000);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -15,7 +15,7 @@ import {
|
|||||||
alpha,
|
alpha,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} 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 { GuardState } from "@/components/setting/mods/guard-state";
|
||||||
import { SysproxyViewer } from "@/components/setting/mods/sysproxy-viewer";
|
import { SysproxyViewer } from "@/components/setting/mods/sysproxy-viewer";
|
||||||
import { TunViewer } from "@/components/setting/mods/tun-viewer";
|
import { TunViewer } from "@/components/setting/mods/tun-viewer";
|
||||||
@ -28,6 +28,7 @@ import {
|
|||||||
} from "@/services/cmds";
|
} from "@/services/cmds";
|
||||||
import { useLockFn } from "ahooks";
|
import { useLockFn } from "ahooks";
|
||||||
import { closeAllConnections } from "@/services/api";
|
import { closeAllConnections } from "@/services/api";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
|
||||||
interface ProxySwitchProps {
|
interface ProxySwitchProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
@ -78,13 +79,13 @@ const ProxyControlSwitches = ({ label, onError }: ProxySwitchProps) => {
|
|||||||
// 安装系统服务
|
// 安装系统服务
|
||||||
const onInstallService = useLockFn(async () => {
|
const onInstallService = useLockFn(async () => {
|
||||||
try {
|
try {
|
||||||
Notice.info(t("Installing Service..."), 1000);
|
showNotice('info', t("Installing Service..."), 1000);
|
||||||
await installService();
|
await installService();
|
||||||
Notice.success(t("Service Installed Successfully"), 2000);
|
showNotice('success', t("Service Installed Successfully"), 2000);
|
||||||
// 重新获取运行模式
|
// 重新获取运行模式
|
||||||
await mutateRunningMode();
|
await mutateRunningMode();
|
||||||
} catch (err: any) {
|
} 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}
|
onFormat={onSwitchFormat}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
// 当在sidecar模式下禁用切换
|
// 当在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 });
|
onChangeData({ enable_tun_mode: e });
|
||||||
}}
|
}}
|
||||||
onGuard={(e) => {
|
onGuard={(e) => {
|
||||||
// 当在sidecar模式下禁用切换
|
// 当在sidecar模式下禁用切换
|
||||||
if (isSidecarMode) {
|
if (isSidecarMode) {
|
||||||
Notice.error(t("TUN requires Service Mode"), 2000);
|
showNotice('error', t("TUN requires Service Mode"), 2000);
|
||||||
return Promise.reject(
|
return Promise.reject(
|
||||||
new Error(t("TUN requires Service Mode")),
|
new Error(t("TUN requires Service Mode")),
|
||||||
);
|
);
|
||||||
|
@ -6,13 +6,14 @@ import { CSS } from "@dnd-kit/utilities";
|
|||||||
import { Box, Divider, MenuItem, Menu, styled, alpha } from "@mui/material";
|
import { Box, Divider, MenuItem, Menu, styled, alpha } from "@mui/material";
|
||||||
import { BaseLoading } from "@/components/base";
|
import { BaseLoading } from "@/components/base";
|
||||||
import { LanguageRounded } from "@mui/icons-material";
|
import { LanguageRounded } from "@mui/icons-material";
|
||||||
import { Notice } from "@/components/base";
|
import { showNotice } from "@/services/noticeService";
|
||||||
import { TestBox } from "./test-box";
|
import { TestBox } from "./test-box";
|
||||||
import delayManager from "@/services/delay";
|
import delayManager from "@/services/delay";
|
||||||
import { cmdTestDelay, downloadIconCache } from "@/services/cmds";
|
import { cmdTestDelay, downloadIconCache } from "@/services/cmds";
|
||||||
import { UnlistenFn } from "@tauri-apps/api/event";
|
import { UnlistenFn } from "@tauri-apps/api/event";
|
||||||
import { convertFileSrc } from "@tauri-apps/api/core";
|
import { convertFileSrc } from "@tauri-apps/api/core";
|
||||||
import { useListen } from "@/hooks/use-listen";
|
import { useListen } from "@/hooks/use-listen";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
itemData: IVergeTestItem;
|
itemData: IVergeTestItem;
|
||||||
@ -73,7 +74,7 @@ export const TestItem = (props: Props) => {
|
|||||||
try {
|
try {
|
||||||
onDeleteItem(uid);
|
onDeleteItem(uid);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err?.message || err.toString());
|
showNotice('error', err.message || err.toString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -4,8 +4,9 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { useForm, Controller } from "react-hook-form";
|
import { useForm, Controller } from "react-hook-form";
|
||||||
import { TextField } from "@mui/material";
|
import { TextField } from "@mui/material";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import { BaseDialog, Notice } from "@/components/base";
|
import { BaseDialog } from "@/components/base";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onChange: (uid: string, patch?: Partial<IVergeTestItem>) => void;
|
onChange: (uid: string, patch?: Partial<IVergeTestItem>) => void;
|
||||||
@ -99,7 +100,7 @@ export const TestViewer = forwardRef<TestViewerRef, Props>((props, ref) => {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
setTimeout(() => formIns.reset(), 500);
|
setTimeout(() => formIns.reset(), 500);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err.message || err.toString());
|
showNotice('error', err.message || err.toString());
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
@ -14,7 +14,6 @@ import LogoSvg from "@/assets/image/logo.svg?react";
|
|||||||
import iconLight from "@/assets/image/icon_light.svg?react";
|
import iconLight from "@/assets/image/icon_light.svg?react";
|
||||||
import iconDark from "@/assets/image/icon_dark.svg?react";
|
import iconDark from "@/assets/image/icon_dark.svg?react";
|
||||||
import { useThemeMode, useEnableLog } from "@/services/states";
|
import { useThemeMode, useEnableLog } from "@/services/states";
|
||||||
import { Notice } from "@/components/base";
|
|
||||||
import { LayoutItem } from "@/components/layout/layout-item";
|
import { LayoutItem } from "@/components/layout/layout-item";
|
||||||
import { LayoutControl } from "@/components/layout/layout-control";
|
import { LayoutControl } from "@/components/layout/layout-control";
|
||||||
import { LayoutTraffic } from "@/components/layout/layout-traffic";
|
import { LayoutTraffic } from "@/components/layout/layout-traffic";
|
||||||
@ -31,6 +30,8 @@ import { listen } from "@tauri-apps/api/event";
|
|||||||
import { useClashInfo } from "@/hooks/use-clash";
|
import { useClashInfo } from "@/hooks/use-clash";
|
||||||
import { initGlobalLogService } from "@/services/global-log-service";
|
import { initGlobalLogService } from "@/services/global-log-service";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
import { NoticeManager } from "@/components/base/NoticeManager";
|
||||||
|
|
||||||
const appWindow = getCurrentWebviewWindow();
|
const appWindow = getCurrentWebviewWindow();
|
||||||
export let portableFlag = false;
|
export let portableFlag = false;
|
||||||
@ -46,92 +47,95 @@ const handleNoticeMessage = (
|
|||||||
t: (key: string) => string,
|
t: (key: string) => string,
|
||||||
navigate: (path: string, options?: any) => void,
|
navigate: (path: string, options?: any) => void,
|
||||||
) => {
|
) => {
|
||||||
console.log("[通知监听] 收到消息:", status, msg);
|
console.log("[通知监听 V2] 收到消息:", status, msg);
|
||||||
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "import_sub_url::ok":
|
case "import_sub_url::ok":
|
||||||
navigate("/profile", { state: { current: msg } });
|
navigate("/profile", { state: { current: msg } });
|
||||||
Notice.success(t("Import Subscription Successful"));
|
showNotice('success', t("Import Subscription Successful"));
|
||||||
break;
|
break;
|
||||||
case "import_sub_url::error":
|
case "import_sub_url::error":
|
||||||
navigate("/profile");
|
navigate("/profile");
|
||||||
Notice.error(msg);
|
showNotice('error', msg);
|
||||||
break;
|
break;
|
||||||
case "set_config::error":
|
case "set_config::error":
|
||||||
Notice.error(msg);
|
showNotice('error', msg);
|
||||||
break;
|
break;
|
||||||
case "update_with_clash_proxy":
|
case "update_with_clash_proxy":
|
||||||
Notice.success(`${t("Update with Clash proxy successfully")} ${msg}`);
|
showNotice('success', `${t("Update with Clash proxy successfully")} ${msg}`);
|
||||||
break;
|
break;
|
||||||
case "update_retry_with_clash":
|
case "update_retry_with_clash":
|
||||||
Notice.info(t("Update failed, retrying with Clash proxy..."));
|
showNotice('info', t("Update failed, retrying with Clash proxy..."));
|
||||||
break;
|
break;
|
||||||
case "update_failed_even_with_clash":
|
case "update_failed_even_with_clash":
|
||||||
Notice.error(`${t("Update failed even with Clash proxy")}: ${msg}`);
|
showNotice('error', `${t("Update failed even with Clash proxy")}: ${msg}`);
|
||||||
break;
|
break;
|
||||||
case "update_failed":
|
case "update_failed":
|
||||||
Notice.error(msg);
|
showNotice('error', msg);
|
||||||
break;
|
break;
|
||||||
case "config_validate::boot_error":
|
case "config_validate::boot_error":
|
||||||
Notice.error(`${t("Boot Config Validation Failed")} ${msg}`);
|
showNotice('error', `${t("Boot Config Validation Failed")} ${msg}`);
|
||||||
break;
|
break;
|
||||||
case "config_validate::core_change":
|
case "config_validate::core_change":
|
||||||
Notice.error(`${t("Core Change Config Validation Failed")} ${msg}`);
|
showNotice('error', `${t("Core Change Config Validation Failed")} ${msg}`);
|
||||||
break;
|
break;
|
||||||
case "config_validate::error":
|
case "config_validate::error":
|
||||||
Notice.error(`${t("Config Validation Failed")} ${msg}`);
|
showNotice('error', `${t("Config Validation Failed")} ${msg}`);
|
||||||
break;
|
break;
|
||||||
case "config_validate::process_terminated":
|
case "config_validate::process_terminated":
|
||||||
Notice.error(t("Config Validation Process Terminated"));
|
showNotice('error', t("Config Validation Process Terminated"));
|
||||||
break;
|
break;
|
||||||
case "config_validate::stdout_error":
|
case "config_validate::stdout_error":
|
||||||
Notice.error(`${t("Config Validation Failed")} ${msg}`);
|
showNotice('error', `${t("Config Validation Failed")} ${msg}`);
|
||||||
break;
|
break;
|
||||||
case "config_validate::script_error":
|
case "config_validate::script_error":
|
||||||
Notice.error(`${t("Script File Error")} ${msg}`);
|
showNotice('error', `${t("Script File Error")} ${msg}`);
|
||||||
break;
|
break;
|
||||||
case "config_validate::script_syntax_error":
|
case "config_validate::script_syntax_error":
|
||||||
Notice.error(`${t("Script Syntax Error")} ${msg}`);
|
showNotice('error', `${t("Script Syntax Error")} ${msg}`);
|
||||||
break;
|
break;
|
||||||
case "config_validate::script_missing_main":
|
case "config_validate::script_missing_main":
|
||||||
Notice.error(`${t("Script Missing Main")} ${msg}`);
|
showNotice('error', `${t("Script Missing Main")} ${msg}`);
|
||||||
break;
|
break;
|
||||||
case "config_validate::file_not_found":
|
case "config_validate::file_not_found":
|
||||||
Notice.error(`${t("File Not Found")} ${msg}`);
|
showNotice('error', `${t("File Not Found")} ${msg}`);
|
||||||
break;
|
break;
|
||||||
case "config_validate::yaml_syntax_error":
|
case "config_validate::yaml_syntax_error":
|
||||||
Notice.error(`${t("YAML Syntax Error")} ${msg}`);
|
showNotice('error', `${t("YAML Syntax Error")} ${msg}`);
|
||||||
break;
|
break;
|
||||||
case "config_validate::yaml_read_error":
|
case "config_validate::yaml_read_error":
|
||||||
Notice.error(`${t("YAML Read Error")} ${msg}`);
|
showNotice('error', `${t("YAML Read Error")} ${msg}`);
|
||||||
break;
|
break;
|
||||||
case "config_validate::yaml_mapping_error":
|
case "config_validate::yaml_mapping_error":
|
||||||
Notice.error(`${t("YAML Mapping Error")} ${msg}`);
|
showNotice('error', `${t("YAML Mapping Error")} ${msg}`);
|
||||||
break;
|
break;
|
||||||
case "config_validate::yaml_key_error":
|
case "config_validate::yaml_key_error":
|
||||||
Notice.error(`${t("YAML Key Error")} ${msg}`);
|
showNotice('error', `${t("YAML Key Error")} ${msg}`);
|
||||||
break;
|
break;
|
||||||
case "config_validate::yaml_error":
|
case "config_validate::yaml_error":
|
||||||
Notice.error(`${t("YAML Error")} ${msg}`);
|
showNotice('error', `${t("YAML Error")} ${msg}`);
|
||||||
break;
|
break;
|
||||||
case "config_validate::merge_syntax_error":
|
case "config_validate::merge_syntax_error":
|
||||||
Notice.error(`${t("Merge File Syntax Error")} ${msg}`);
|
showNotice('error', `${t("Merge File Syntax Error")} ${msg}`);
|
||||||
break;
|
break;
|
||||||
case "config_validate::merge_mapping_error":
|
case "config_validate::merge_mapping_error":
|
||||||
Notice.error(`${t("Merge File Mapping Error")} ${msg}`);
|
showNotice('error', `${t("Merge File Mapping Error")} ${msg}`);
|
||||||
break;
|
break;
|
||||||
case "config_validate::merge_key_error":
|
case "config_validate::merge_key_error":
|
||||||
Notice.error(`${t("Merge File Key Error")} ${msg}`);
|
showNotice('error', `${t("Merge File Key Error")} ${msg}`);
|
||||||
break;
|
break;
|
||||||
case "config_validate::merge_error":
|
case "config_validate::merge_error":
|
||||||
Notice.error(`${t("Merge File Error")} ${msg}`);
|
showNotice('error', `${t("Merge File Error")} ${msg}`);
|
||||||
break;
|
break;
|
||||||
case "config_core::change_success":
|
case "config_core::change_success":
|
||||||
Notice.success(`${t("Core Changed Successfully")}: ${msg}`);
|
showNotice('success', `${t("Core Changed Successfully")}: ${msg}`);
|
||||||
break;
|
break;
|
||||||
case "config_core::change_error":
|
case "config_core::change_error":
|
||||||
Notice.error(`${t("Failed to Change Core")}: ${msg}`);
|
showNotice('error', `${t("Failed to Change Core")}: ${msg}`);
|
||||||
break;
|
break;
|
||||||
|
default: // Optional: Log unhandled statuses
|
||||||
|
console.warn(`[通知监听 V2] 未处理的状态: ${status}`);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -276,6 +280,8 @@ const Layout = () => {
|
|||||||
return (
|
return (
|
||||||
<SWRConfig value={{ errorRetryCount: 3 }}>
|
<SWRConfig value={{ errorRetryCount: 3 }}>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
|
<NoticeManager />
|
||||||
|
|
||||||
<Paper
|
<Paper
|
||||||
square
|
square
|
||||||
elevation={0}
|
elevation={0}
|
||||||
|
@ -36,7 +36,7 @@ import {
|
|||||||
} from "@/services/cmds";
|
} from "@/services/cmds";
|
||||||
import { useSetLoadingCache, useThemeMode } from "@/services/states";
|
import { useSetLoadingCache, useThemeMode } from "@/services/states";
|
||||||
import { closeAllConnections } from "@/services/api";
|
import { closeAllConnections } from "@/services/api";
|
||||||
import { BasePage, DialogRef, Notice } from "@/components/base";
|
import { BasePage, DialogRef } from "@/components/base";
|
||||||
import {
|
import {
|
||||||
ProfileViewer,
|
ProfileViewer,
|
||||||
ProfileViewerRef,
|
ProfileViewerRef,
|
||||||
@ -53,6 +53,7 @@ import { useLocation } from "react-router-dom";
|
|||||||
import { useListen } from "@/hooks/use-listen";
|
import { useListen } from "@/hooks/use-listen";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { TauriEvent } from "@tauri-apps/api/event";
|
import { TauriEvent } from "@tauri-apps/api/event";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
|
||||||
const ProfilePage = () => {
|
const ProfilePage = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -79,7 +80,7 @@ const ProfilePage = () => {
|
|||||||
|
|
||||||
for (let file of paths) {
|
for (let file of paths) {
|
||||||
if (!file.endsWith(".yaml") && !file.endsWith(".yml")) {
|
if (!file.endsWith(".yaml") && !file.endsWith(".yml")) {
|
||||||
Notice.error(t("Only YAML Files Supported"));
|
showNotice('error', t("Only YAML Files Supported"));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const item = {
|
const item = {
|
||||||
@ -144,14 +145,14 @@ const ProfilePage = () => {
|
|||||||
try {
|
try {
|
||||||
// 尝试正常导入
|
// 尝试正常导入
|
||||||
await importProfile(url);
|
await importProfile(url);
|
||||||
Notice.success(t("Profile Imported Successfully"));
|
showNotice('success', t("Profile Imported Successfully"));
|
||||||
setUrl("");
|
setUrl("");
|
||||||
mutateProfiles();
|
mutateProfiles();
|
||||||
await onEnhance(false);
|
await onEnhance(false);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
// 首次导入失败,尝试使用自身代理
|
// 首次导入失败,尝试使用自身代理
|
||||||
const errmsg = err.message || err.toString();
|
const errmsg = err.message || err.toString();
|
||||||
Notice.info(t("Import failed, retrying with Clash proxy..."));
|
showNotice('info', t("Import failed, retrying with Clash proxy..."));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 使用自身代理尝试导入
|
// 使用自身代理尝试导入
|
||||||
@ -161,16 +162,14 @@ const ProfilePage = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 回退导入成功
|
// 回退导入成功
|
||||||
Notice.success(t("Profile Imported with Clash proxy"));
|
showNotice('success', t("Profile Imported with Clash proxy"));
|
||||||
setUrl("");
|
setUrl("");
|
||||||
mutateProfiles();
|
mutateProfiles();
|
||||||
await onEnhance(false);
|
await onEnhance(false);
|
||||||
} catch (retryErr: any) {
|
} catch (retryErr: any) {
|
||||||
// 回退导入也失败
|
// 回退导入也失败
|
||||||
const retryErrmsg = retryErr?.message || retryErr.toString();
|
const retryErrmsg = retryErr?.message || retryErr.toString();
|
||||||
Notice.error(
|
showNotice('error', `${t("Import failed even with Clash proxy")}: ${retryErrmsg}`);
|
||||||
`${t("Import failed even with Clash proxy")}: ${retryErrmsg}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setDisabled(false);
|
setDisabled(false);
|
||||||
@ -200,10 +199,10 @@ const ProfilePage = () => {
|
|||||||
closeAllConnections();
|
closeAllConnections();
|
||||||
await activateSelected();
|
await activateSelected();
|
||||||
if (notifySuccess && success) {
|
if (notifySuccess && success) {
|
||||||
Notice.success(t("Profile Switched"), 1000);
|
showNotice('success', t("Profile Switched"), 1000);
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err?.message || err.toString(), 4000);
|
showNotice('error', err?.message || err.toString(), 4000);
|
||||||
} finally {
|
} finally {
|
||||||
clearTimeout(reset);
|
clearTimeout(reset);
|
||||||
setActivatings([]);
|
setActivatings([]);
|
||||||
@ -229,10 +228,10 @@ const ProfilePage = () => {
|
|||||||
await enhanceProfiles();
|
await enhanceProfiles();
|
||||||
mutateLogs();
|
mutateLogs();
|
||||||
if (notifySuccess) {
|
if (notifySuccess) {
|
||||||
Notice.success(t("Profile Reactivated"), 1000);
|
showNotice('success', t("Profile Reactivated"), 1000);
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err.message || err.toString(), 3000);
|
showNotice('error', err.message || err.toString(), 3000);
|
||||||
} finally {
|
} finally {
|
||||||
setActivatings([]);
|
setActivatings([]);
|
||||||
}
|
}
|
||||||
@ -247,7 +246,7 @@ const ProfilePage = () => {
|
|||||||
mutateLogs();
|
mutateLogs();
|
||||||
current && (await onEnhance(false));
|
current && (await onEnhance(false));
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err?.message || err.toString());
|
showNotice('error', err?.message || err.toString());
|
||||||
} finally {
|
} finally {
|
||||||
setActivatings([]);
|
setActivatings([]);
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import { Box, ButtonGroup, IconButton, Select, MenuItem } from "@mui/material";
|
|||||||
import Grid from "@mui/material/Grid2";
|
import Grid from "@mui/material/Grid2";
|
||||||
import { useLockFn } from "ahooks";
|
import { useLockFn } from "ahooks";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { BasePage, Notice } from "@/components/base";
|
import { BasePage } from "@/components/base";
|
||||||
import { GitHub, HelpOutlineRounded, Telegram } from "@mui/icons-material";
|
import { GitHub, HelpOutlineRounded, Telegram } from "@mui/icons-material";
|
||||||
import { openWebUrl } from "@/services/cmds";
|
import { openWebUrl } from "@/services/cmds";
|
||||||
import SettingVergeBasic from "@/components/setting/setting-verge-basic";
|
import SettingVergeBasic from "@/components/setting/setting-verge-basic";
|
||||||
@ -10,12 +10,13 @@ import SettingVergeAdvanced from "@/components/setting/setting-verge-advanced";
|
|||||||
import SettingClash from "@/components/setting/setting-clash";
|
import SettingClash from "@/components/setting/setting-clash";
|
||||||
import SettingSystem from "@/components/setting/setting-system";
|
import SettingSystem from "@/components/setting/setting-system";
|
||||||
import { useThemeMode } from "@/services/states";
|
import { useThemeMode } from "@/services/states";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
|
||||||
const SettingPage = () => {
|
const SettingPage = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const onError = (err: any) => {
|
const onError = (err: any) => {
|
||||||
Notice.error(err?.message || err.toString());
|
showNotice('error', err?.message || err.toString());
|
||||||
};
|
};
|
||||||
|
|
||||||
const toGithubRepo = useLockFn(() => {
|
const toGithubRepo = useLockFn(() => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { Notice } from "@/components/base";
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
|
||||||
export async function copyClashEnv() {
|
export async function copyClashEnv() {
|
||||||
return invoke<void>("copy_clash_env");
|
return invoke<void>("copy_clash_env");
|
||||||
@ -145,25 +145,29 @@ export async function getAppDir() {
|
|||||||
|
|
||||||
export async function openAppDir() {
|
export async function openAppDir() {
|
||||||
return invoke<void>("open_app_dir").catch((err) =>
|
return invoke<void>("open_app_dir").catch((err) =>
|
||||||
Notice.error(err?.message || err.toString(), 1500),
|
showNotice('error', err?.message || err.toString()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function openCoreDir() {
|
export async function openCoreDir() {
|
||||||
return invoke<void>("open_core_dir").catch((err) =>
|
return invoke<void>("open_core_dir").catch((err) =>
|
||||||
Notice.error(err?.message || err.toString(), 1500),
|
showNotice('error', err?.message || err.toString()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function openLogsDir() {
|
export async function openLogsDir() {
|
||||||
return invoke<void>("open_logs_dir").catch((err) =>
|
return invoke<void>("open_logs_dir").catch((err) =>
|
||||||
Notice.error(err?.message || err.toString(), 1500),
|
showNotice('error', err?.message || err.toString()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function openWebUrl(url: string) {
|
export const openWebUrl = async (url: string) => {
|
||||||
return invoke<void>("open_web_url", { url });
|
try {
|
||||||
}
|
await invoke("open_web_url", { url });
|
||||||
|
} catch (err: any) {
|
||||||
|
showNotice('error', err.toString());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export async function cmdGetProxyDelay(
|
export async function cmdGetProxyDelay(
|
||||||
name: string,
|
name: string,
|
||||||
@ -214,7 +218,7 @@ export async function cmdTestDelay(url: string) {
|
|||||||
|
|
||||||
export async function invoke_uwp_tool() {
|
export async function invoke_uwp_tool() {
|
||||||
return invoke<void>("invoke_uwp_tool").catch((err) =>
|
return invoke<void>("invoke_uwp_tool").catch((err) =>
|
||||||
Notice.error(err?.message || err.toString(), 1500),
|
showNotice('error', err?.message || err.toString(), 1500),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
80
src/services/noticeService.ts
Normal file
80
src/services/noticeService.ts
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
export interface NoticeItem {
|
||||||
|
id: number;
|
||||||
|
type: 'success' | 'error' | 'info';
|
||||||
|
message: ReactNode;
|
||||||
|
duration: number;
|
||||||
|
timerId?: ReturnType<typeof setTimeout>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Listener = (notices: NoticeItem[]) => void;
|
||||||
|
|
||||||
|
let nextId = 0;
|
||||||
|
let notices: NoticeItem[] = [];
|
||||||
|
const listeners: Set<Listener> = new Set();
|
||||||
|
|
||||||
|
function notifyListeners() {
|
||||||
|
listeners.forEach((listener) => listener([...notices])); // Pass a copy
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shows a notification.
|
||||||
|
|
||||||
|
export function showNotice(
|
||||||
|
type: 'success' | 'error' | 'info',
|
||||||
|
message: ReactNode,
|
||||||
|
duration?: number,
|
||||||
|
): number {
|
||||||
|
const id = nextId++;
|
||||||
|
const effectiveDuration =
|
||||||
|
duration ?? (type === 'error' ? 8000 : type === 'info' ? 5000 : 3000); // Longer defaults
|
||||||
|
|
||||||
|
const newNotice: NoticeItem = {
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
message,
|
||||||
|
duration: effectiveDuration,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Auto-hide timer (only if duration is not null/0)
|
||||||
|
if (effectiveDuration > 0) {
|
||||||
|
newNotice.timerId = setTimeout(() => {
|
||||||
|
hideNotice(id);
|
||||||
|
}, effectiveDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
notices = [...notices, newNotice];
|
||||||
|
notifyListeners();
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hides a specific notification by its ID.
|
||||||
|
|
||||||
|
export function hideNotice(id: number) {
|
||||||
|
const notice = notices.find((n) => n.id === id);
|
||||||
|
if (notice?.timerId) {
|
||||||
|
clearTimeout(notice.timerId); // Clear timeout if manually closed
|
||||||
|
}
|
||||||
|
notices = notices.filter((n) => n.id !== id);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribes a listener function to notice state changes.
|
||||||
|
|
||||||
|
export function subscribeNotices(listener: Listener): () => void {
|
||||||
|
listeners.add(listener);
|
||||||
|
listener([...notices]);
|
||||||
|
return () => {
|
||||||
|
listeners.delete(listener);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to clear all notices at once
|
||||||
|
export function clearAllNotices() {
|
||||||
|
notices.forEach(n => {
|
||||||
|
if (n.timerId) clearTimeout(n.timerId);
|
||||||
|
});
|
||||||
|
notices = [];
|
||||||
|
notifyListeners();
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user