mirror of
https://github.com/clash-verge-rev/clash-verge-rev
synced 2025-05-05 13:03:44 +08:00
feat: reactive after save when profile content changes
This commit is contained in:
parent
3f1caa702b
commit
9ee5390ec7
@ -27,10 +27,10 @@ export const ConfirmViewer = (props: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onClose={onClose} maxWidth="xs" fullWidth>
|
<Dialog open={open} onClose={onClose} maxWidth="xs" fullWidth>
|
||||||
<DialogTitle>{t(title)}</DialogTitle>
|
<DialogTitle>{title}</DialogTitle>
|
||||||
|
|
||||||
<DialogContent sx={{ pb: 1, userSelect: "text" }}>
|
<DialogContent sx={{ pb: 1, userSelect: "text" }}>
|
||||||
{t(message)}
|
{message}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
|
@ -32,7 +32,7 @@ interface Props {
|
|||||||
language: "yaml" | "javascript" | "css";
|
language: "yaml" | "javascript" | "css";
|
||||||
schema?: "clash" | "merge";
|
schema?: "clash" | "merge";
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onChange?: (content?: string) => void;
|
onChange?: (prev?: string, curr?: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// yaml worker
|
// yaml worker
|
||||||
@ -90,6 +90,7 @@ export const EditorViewer = (props: Props) => {
|
|||||||
const editorRef = useRef<any>();
|
const editorRef = useRef<any>();
|
||||||
const instanceRef = useRef<editor.IStandaloneCodeEditor | null>(null);
|
const instanceRef = useRef<editor.IStandaloneCodeEditor | null>(null);
|
||||||
const themeMode = useThemeMode();
|
const themeMode = useThemeMode();
|
||||||
|
const prevData = useRef<string>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open) return;
|
if (!open) return;
|
||||||
@ -136,6 +137,8 @@ export const EditorViewer = (props: Props) => {
|
|||||||
fontLigatures: true, // 连字符
|
fontLigatures: true, // 连字符
|
||||||
smoothScrolling: true, // 平滑滚动
|
smoothScrolling: true, // 平滑滚动
|
||||||
});
|
});
|
||||||
|
|
||||||
|
prevData.current = data;
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@ -147,15 +150,15 @@ export const EditorViewer = (props: Props) => {
|
|||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
const onSave = useLockFn(async () => {
|
const onSave = useLockFn(async () => {
|
||||||
const value = instanceRef.current?.getValue();
|
const currData = instanceRef.current?.getValue();
|
||||||
|
|
||||||
if (value == null) return;
|
if (currData == null) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (mode === "profile") {
|
if (mode === "profile") {
|
||||||
await saveProfileFile(property, value);
|
await saveProfileFile(property, currData);
|
||||||
}
|
}
|
||||||
onChange?.(value);
|
onChange?.(prevData.current, currData);
|
||||||
onClose();
|
onClose();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err.message || err.toString());
|
Notice.error(err.message || err.toString());
|
||||||
|
@ -17,7 +17,7 @@ import {
|
|||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { RefreshRounded, DragIndicator } from "@mui/icons-material";
|
import { RefreshRounded, DragIndicator } from "@mui/icons-material";
|
||||||
import { useLoadingCache, useSetLoadingCache } from "@/services/states";
|
import { useLoadingCache, useSetLoadingCache } from "@/services/states";
|
||||||
import { updateProfile, deleteProfile, viewProfile } from "@/services/cmds";
|
import { updateProfile, viewProfile } from "@/services/cmds";
|
||||||
import { Notice } from "@/components/base";
|
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";
|
||||||
@ -36,10 +36,20 @@ interface Props {
|
|||||||
itemData: IProfileItem;
|
itemData: IProfileItem;
|
||||||
onSelect: (force: boolean) => void;
|
onSelect: (force: boolean) => void;
|
||||||
onEdit: () => void;
|
onEdit: () => void;
|
||||||
|
onChange?: (prev?: string, curr?: string) => void;
|
||||||
|
onDelete: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProfileItem = (props: Props) => {
|
export const ProfileItem = (props: Props) => {
|
||||||
const { selected, activating, itemData, onSelect, onEdit } = props;
|
const {
|
||||||
|
selected,
|
||||||
|
activating,
|
||||||
|
itemData,
|
||||||
|
onSelect,
|
||||||
|
onEdit,
|
||||||
|
onChange,
|
||||||
|
onDelete,
|
||||||
|
} = props;
|
||||||
const { attributes, listeners, setNodeRef, transform, transition } =
|
const { attributes, listeners, setNodeRef, transform, transition } =
|
||||||
useSortable({ id: props.id });
|
useSortable({ id: props.id });
|
||||||
|
|
||||||
@ -53,6 +63,7 @@ export const ProfileItem = (props: Props) => {
|
|||||||
|
|
||||||
// local file mode
|
// local file mode
|
||||||
// remote file mode
|
// remote file mode
|
||||||
|
// remote file mode
|
||||||
const hasUrl = !!itemData.url;
|
const hasUrl = !!itemData.url;
|
||||||
const hasExtra = !!extra; // only subscription url has extra info
|
const hasExtra = !!extra; // only subscription url has extra info
|
||||||
const hasHome = !!itemData.home; // only subscription url has home page
|
const hasHome = !!itemData.home; // only subscription url has home page
|
||||||
@ -162,16 +173,6 @@ export const ProfileItem = (props: Props) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const onDelete = useLockFn(async () => {
|
|
||||||
setAnchorEl(null);
|
|
||||||
try {
|
|
||||||
await deleteProfile(itemData.uid);
|
|
||||||
mutate("getProfiles");
|
|
||||||
} catch (err: any) {
|
|
||||||
Notice.error(err?.message || err.toString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const urlModeMenu = (
|
const urlModeMenu = (
|
||||||
hasHome ? [{ label: "Home", handler: onOpenHome }] : []
|
hasHome ? [{ label: "Home", handler: onOpenHome }] : []
|
||||||
).concat([
|
).concat([
|
||||||
@ -242,7 +243,7 @@ export const ProfileItem = (props: Props) => {
|
|||||||
backdropFilter: "blur(2px)",
|
backdropFilter: "blur(2px)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CircularProgress size={20} />
|
<CircularProgress color="inherit" size={20} />
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<Box position="relative">
|
<Box position="relative">
|
||||||
@ -312,7 +313,7 @@ export const ProfileItem = (props: Props) => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
) : (
|
) : (
|
||||||
hasUrl && (
|
hasUrl && (
|
||||||
<Typography noWrap title={`From ${from}`}>
|
<Typography noWrap title={`${t("From")} ${from}`}>
|
||||||
{from}
|
{from}
|
||||||
</Typography>
|
</Typography>
|
||||||
)
|
)
|
||||||
@ -323,7 +324,7 @@ export const ProfileItem = (props: Props) => {
|
|||||||
flex="1 0 auto"
|
flex="1 0 auto"
|
||||||
fontSize={14}
|
fontSize={14}
|
||||||
textAlign="right"
|
textAlign="right"
|
||||||
title={`Updated Time: ${parseExpire(updated)}`}
|
title={`${t("Update Time")}: ${parseExpire(updated)}`}
|
||||||
>
|
>
|
||||||
{updated > 0 ? dayjs(updated * 1000).fromNow() : ""}
|
{updated > 0 ? dayjs(updated * 1000).fromNow() : ""}
|
||||||
</Typography>
|
</Typography>
|
||||||
@ -334,17 +335,21 @@ export const ProfileItem = (props: Props) => {
|
|||||||
{/* the third line show extra info or last updated time */}
|
{/* the third line show extra info or last updated time */}
|
||||||
{hasExtra ? (
|
{hasExtra ? (
|
||||||
<Box sx={{ ...boxStyle, fontSize: 14 }}>
|
<Box sx={{ ...boxStyle, fontSize: 14 }}>
|
||||||
<span title="Used / Total">
|
<span title={t("Used / Total")}>
|
||||||
{parseTraffic(upload + download)} / {parseTraffic(total)}
|
{parseTraffic(upload + download)} / {parseTraffic(total)}
|
||||||
</span>
|
</span>
|
||||||
<span title="Expire Time">{expire}</span>
|
<span title={t("Expire Time")}>{expire}</span>
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<Box sx={{ ...boxStyle, fontSize: 12, justifyContent: "flex-end" }}>
|
<Box sx={{ ...boxStyle, fontSize: 12, justifyContent: "flex-end" }}>
|
||||||
<span title="Updated Time">{parseExpire(updated)}</span>
|
<span title={t("Update Time")}>{parseExpire(updated)}</span>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<LinearProgress variant="determinate" value={progress} />
|
<LinearProgress
|
||||||
|
variant="determinate"
|
||||||
|
value={progress}
|
||||||
|
style={{ opacity: progress > 0 ? 1 : 0 }}
|
||||||
|
/>
|
||||||
</ProfileBox>
|
</ProfileBox>
|
||||||
|
|
||||||
<Menu
|
<Menu
|
||||||
@ -390,11 +395,12 @@ export const ProfileItem = (props: Props) => {
|
|||||||
open={fileOpen}
|
open={fileOpen}
|
||||||
language="yaml"
|
language="yaml"
|
||||||
schema="clash"
|
schema="clash"
|
||||||
|
onChange={onChange}
|
||||||
onClose={() => setFileOpen(false)}
|
onClose={() => setFileOpen(false)}
|
||||||
/>
|
/>
|
||||||
<ConfirmViewer
|
<ConfirmViewer
|
||||||
title="Confirm deletion"
|
title={t("Confirm deletion")}
|
||||||
message="This operation is not reversible"
|
message={t("This operation is not reversible")}
|
||||||
open={confirmOpen}
|
open={confirmOpen}
|
||||||
onClose={() => setConfirmOpen(false)}
|
onClose={() => setConfirmOpen(false)}
|
||||||
onConfirm={() => {
|
onConfirm={() => {
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
Menu,
|
Menu,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
CircularProgress,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { FeaturedPlayListRounded } from "@mui/icons-material";
|
import { FeaturedPlayListRounded } from "@mui/icons-material";
|
||||||
import { viewProfile } from "@/services/cmds";
|
import { viewProfile } from "@/services/cmds";
|
||||||
@ -20,6 +21,7 @@ import { ConfirmViewer } from "./confirm-viewer";
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
|
activating: boolean;
|
||||||
itemData: IProfileItem;
|
itemData: IProfileItem;
|
||||||
enableNum: number;
|
enableNum: number;
|
||||||
logInfo?: [string, string][];
|
logInfo?: [string, string][];
|
||||||
@ -27,14 +29,16 @@ interface Props {
|
|||||||
onDisable: () => void;
|
onDisable: () => void;
|
||||||
onMoveTop: () => void;
|
onMoveTop: () => void;
|
||||||
onMoveEnd: () => void;
|
onMoveEnd: () => void;
|
||||||
onDelete: () => void;
|
|
||||||
onEdit: () => void;
|
onEdit: () => void;
|
||||||
|
onChange?: (prev?: string, curr?: string) => void;
|
||||||
|
onDelete: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// profile enhanced item
|
// profile enhanced item
|
||||||
export const ProfileMore = (props: Props) => {
|
export const ProfileMore = (props: Props) => {
|
||||||
const {
|
const {
|
||||||
selected,
|
selected,
|
||||||
|
activating,
|
||||||
itemData,
|
itemData,
|
||||||
enableNum,
|
enableNum,
|
||||||
logInfo = [],
|
logInfo = [],
|
||||||
@ -44,6 +48,7 @@ export const ProfileMore = (props: Props) => {
|
|||||||
onMoveEnd,
|
onMoveEnd,
|
||||||
onDelete,
|
onDelete,
|
||||||
onEdit,
|
onEdit,
|
||||||
|
onChange,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const { uid, type } = itemData;
|
const { uid, type } = itemData;
|
||||||
@ -132,6 +137,24 @@ export const ProfileMore = (props: Props) => {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{activating && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
top: 10,
|
||||||
|
left: 10,
|
||||||
|
right: 10,
|
||||||
|
bottom: 2,
|
||||||
|
zIndex: 10,
|
||||||
|
backdropFilter: "blur(2px)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CircularProgress color="inherit" size={20} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
<Box
|
<Box
|
||||||
display="flex"
|
display="flex"
|
||||||
justifyContent="space-between"
|
justifyContent="space-between"
|
||||||
@ -237,11 +260,12 @@ export const ProfileMore = (props: Props) => {
|
|||||||
open={fileOpen}
|
open={fileOpen}
|
||||||
language={type === "merge" ? "yaml" : "javascript"}
|
language={type === "merge" ? "yaml" : "javascript"}
|
||||||
schema={type === "merge" ? "merge" : undefined}
|
schema={type === "merge" ? "merge" : undefined}
|
||||||
|
onChange={onChange}
|
||||||
onClose={() => setFileOpen(false)}
|
onClose={() => setFileOpen(false)}
|
||||||
/>
|
/>
|
||||||
<ConfirmViewer
|
<ConfirmViewer
|
||||||
title="Confirm deletion"
|
title={t("Confirm deletion")}
|
||||||
message="This operation is not reversible"
|
message={t("This operation is not reversible")}
|
||||||
open={confirmOpen}
|
open={confirmOpen}
|
||||||
onClose={() => setConfirmOpen(false)}
|
onClose={() => setConfirmOpen(false)}
|
||||||
onConfirm={() => {
|
onConfirm={() => {
|
||||||
|
@ -249,10 +249,10 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
property={value.pac_content ?? ""}
|
property={value.pac_content ?? ""}
|
||||||
open={editorOpen}
|
open={editorOpen}
|
||||||
language="javascript"
|
language="javascript"
|
||||||
onChange={(content) => {
|
onChange={(_prev, curr) => {
|
||||||
let pac = DEFAULT_PAC;
|
let pac = DEFAULT_PAC;
|
||||||
if (content && content.trim().length > 0) {
|
if (curr && curr.trim().length > 0) {
|
||||||
pac = content;
|
pac = curr;
|
||||||
}
|
}
|
||||||
setValue((v) => ({ ...v, pac_content: pac }));
|
setValue((v) => ({ ...v, pac_content: pac }));
|
||||||
}}
|
}}
|
||||||
|
@ -129,8 +129,8 @@ export const ThemeViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
property={theme.css_injection ?? ""}
|
property={theme.css_injection ?? ""}
|
||||||
open={editorOpen}
|
open={editorOpen}
|
||||||
language="css"
|
language="css"
|
||||||
onChange={(content) => {
|
onChange={(_prev, curr) => {
|
||||||
theme.css_injection = content;
|
theme.css_injection = curr;
|
||||||
handleChange("css_injection");
|
handleChange("css_injection");
|
||||||
}}
|
}}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
|
@ -49,6 +49,10 @@
|
|||||||
"Paste": "Paste",
|
"Paste": "Paste",
|
||||||
"Profile URL": "Profile URL",
|
"Profile URL": "Profile URL",
|
||||||
"Import": "Import",
|
"Import": "Import",
|
||||||
|
"From": "From",
|
||||||
|
"Update Time": "Update Time",
|
||||||
|
"Used / Total": "Used / Total",
|
||||||
|
"Expire Time": "Expire Time",
|
||||||
"Create Profile": "Create Profile",
|
"Create Profile": "Create Profile",
|
||||||
"Edit Profile": "Edit Profile",
|
"Edit Profile": "Edit Profile",
|
||||||
"Type": "Type",
|
"Type": "Type",
|
||||||
@ -178,6 +182,9 @@
|
|||||||
"Open UWP tool Info": "Since Windows 8, UWP apps (such as Microsoft Store) are restricted from directly accessing local host network services, and this tool can be used to bypass this restriction",
|
"Open UWP tool Info": "Since Windows 8, UWP apps (such as Microsoft Store) are restricted from directly accessing local host network services, and this tool can be used to bypass this restriction",
|
||||||
"Update GeoData": "Update GeoData",
|
"Update GeoData": "Update GeoData",
|
||||||
|
|
||||||
|
"TG Channel": "Telegram Channel",
|
||||||
|
"Manual": "Manual",
|
||||||
|
"Github Repo": "Github Repo",
|
||||||
"Verge Setting": "Verge Setting",
|
"Verge Setting": "Verge Setting",
|
||||||
"Language": "Language",
|
"Language": "Language",
|
||||||
"Theme Mode": "Theme Mode",
|
"Theme Mode": "Theme Mode",
|
||||||
@ -246,9 +253,6 @@
|
|||||||
"Open Dev Tools": "Open Dev Tools",
|
"Open Dev Tools": "Open Dev Tools",
|
||||||
"Exit": "Exit",
|
"Exit": "Exit",
|
||||||
"Verge Version": "Verge Version",
|
"Verge Version": "Verge Version",
|
||||||
"TG Channel": "Telegram Channel",
|
|
||||||
"Doc": "Docs",
|
|
||||||
"Source Code": "Source Code",
|
|
||||||
|
|
||||||
"ReadOnly": "ReadOnly",
|
"ReadOnly": "ReadOnly",
|
||||||
"ReadOnlyMessage": "Cannot edit in read-only editor",
|
"ReadOnlyMessage": "Cannot edit in read-only editor",
|
||||||
|
@ -49,6 +49,10 @@
|
|||||||
"Paste": "چسباندن",
|
"Paste": "چسباندن",
|
||||||
"Profile URL": "آدرس پروفایل",
|
"Profile URL": "آدرس پروفایل",
|
||||||
"Import": "وارد کردن",
|
"Import": "وارد کردن",
|
||||||
|
"From": "از",
|
||||||
|
"Update Time": "زمان بهروزرسانی",
|
||||||
|
"Used / Total": "استفادهشده / کل",
|
||||||
|
"Expire Time": "زمان انقضا",
|
||||||
"Create Profile": "ایجاد پروفایل",
|
"Create Profile": "ایجاد پروفایل",
|
||||||
"Edit Profile": "ویرایش پروفایل",
|
"Edit Profile": "ویرایش پروفایل",
|
||||||
"Type": "نوع",
|
"Type": "نوع",
|
||||||
@ -183,6 +187,9 @@
|
|||||||
"Open UWP tool Info": "از ویندوز 8 به بعد، برنامههای UWP (مانند Microsoft Store) از دسترسی مستقیم به خدمات شبکه محلی محدود شدهاند و این ابزار میتواند برای دور زدن این محدودیت استفاده شود",
|
"Open UWP tool Info": "از ویندوز 8 به بعد، برنامههای UWP (مانند Microsoft Store) از دسترسی مستقیم به خدمات شبکه محلی محدود شدهاند و این ابزار میتواند برای دور زدن این محدودیت استفاده شود",
|
||||||
"Update GeoData": "بهروزرسانی GeoData",
|
"Update GeoData": "بهروزرسانی GeoData",
|
||||||
|
|
||||||
|
"TG Channel": "کانال تلگرام",
|
||||||
|
"Manual": "راهنما",
|
||||||
|
"Github Repo": "مخزن GitHub",
|
||||||
"Verge Setting": "تنظیمات Verge",
|
"Verge Setting": "تنظیمات Verge",
|
||||||
"Language": "زبان",
|
"Language": "زبان",
|
||||||
"Theme Mode": "حالت تم",
|
"Theme Mode": "حالت تم",
|
||||||
@ -251,9 +258,6 @@
|
|||||||
"Open Dev Tools": "باز کردن ابزارهای توسعهدهنده",
|
"Open Dev Tools": "باز کردن ابزارهای توسعهدهنده",
|
||||||
"Exit": "خروج",
|
"Exit": "خروج",
|
||||||
"Verge Version": "نسخه Verge",
|
"Verge Version": "نسخه Verge",
|
||||||
"TG Channel": "کانال تلگرام",
|
|
||||||
"Doc": "سند",
|
|
||||||
"Source Code": "کد منبع",
|
|
||||||
|
|
||||||
"ReadOnly": "فقط خواندنی",
|
"ReadOnly": "فقط خواندنی",
|
||||||
"ReadOnlyMessage": "نمیتوان در ویرایشگر فقط خواندنی ویرایش کرد",
|
"ReadOnlyMessage": "نمیتوان در ویرایشگر فقط خواندنی ویرایش کرد",
|
||||||
|
@ -49,6 +49,10 @@
|
|||||||
"Paste": "Вставить",
|
"Paste": "Вставить",
|
||||||
"Profile URL": "URL профиля",
|
"Profile URL": "URL профиля",
|
||||||
"Import": "Импорт",
|
"Import": "Импорт",
|
||||||
|
"From": "От",
|
||||||
|
"Update Time": "Время обновления",
|
||||||
|
"Used / Total": "Использовано / Всего",
|
||||||
|
"Expire Time": "Время окончания",
|
||||||
"Create Profile": "Создать профиль",
|
"Create Profile": "Создать профиль",
|
||||||
"Edit Profile": "Изменить профиль",
|
"Edit Profile": "Изменить профиль",
|
||||||
"Type": "Тип",
|
"Type": "Тип",
|
||||||
@ -183,6 +187,9 @@
|
|||||||
"Open UWP tool Info": "С Windows 8 приложения UWP (такие как Microsoft Store) ограничены в прямом доступе к сетевым службам локального хоста, и этот инструмент позволяет обойти это ограничение",
|
"Open UWP tool Info": "С Windows 8 приложения UWP (такие как Microsoft Store) ограничены в прямом доступе к сетевым службам локального хоста, и этот инструмент позволяет обойти это ограничение",
|
||||||
"Update GeoData": "Обновление GeoData",
|
"Update GeoData": "Обновление GeoData",
|
||||||
|
|
||||||
|
"TG Channel": "Канал Telegram",
|
||||||
|
"Manual": "Документация",
|
||||||
|
"Github Repo": "GitHub репозиторий",
|
||||||
"Verge Setting": "Настройки Verge",
|
"Verge Setting": "Настройки Verge",
|
||||||
"Language": "Язык",
|
"Language": "Язык",
|
||||||
"Theme Mode": "Режим темы",
|
"Theme Mode": "Режим темы",
|
||||||
@ -251,9 +258,6 @@
|
|||||||
"Open Dev Tools": "Открыть инструменты разработчика",
|
"Open Dev Tools": "Открыть инструменты разработчика",
|
||||||
"Exit": "Выход",
|
"Exit": "Выход",
|
||||||
"Verge Version": "Версия Verge",
|
"Verge Version": "Версия Verge",
|
||||||
"TG Channel": "Канал Telegram",
|
|
||||||
"Doc": "документ",
|
|
||||||
"Source Code": "Исходный код",
|
|
||||||
|
|
||||||
"ReadOnly": "Только для чтения",
|
"ReadOnly": "Только для чтения",
|
||||||
"ReadOnlyMessage": "Невозможно редактировать в режиме только для чтения",
|
"ReadOnlyMessage": "Невозможно редактировать в режиме только для чтения",
|
||||||
|
@ -49,6 +49,10 @@
|
|||||||
"Paste": "粘贴",
|
"Paste": "粘贴",
|
||||||
"Profile URL": "订阅文件链接",
|
"Profile URL": "订阅文件链接",
|
||||||
"Import": "导入",
|
"Import": "导入",
|
||||||
|
"From": "来自",
|
||||||
|
"Update Time": "更新时间",
|
||||||
|
"Used / Total": "已使用 / 总量",
|
||||||
|
"Expire Time": "到期时间",
|
||||||
"Create Profile": "新建配置",
|
"Create Profile": "新建配置",
|
||||||
"Edit Profile": "编辑配置",
|
"Edit Profile": "编辑配置",
|
||||||
"Type": "类型",
|
"Type": "类型",
|
||||||
@ -154,6 +158,9 @@
|
|||||||
"Silent Start": "静默启动",
|
"Silent Start": "静默启动",
|
||||||
"Silent Start Info": "程序启动时以后台模式运行,不显示程序面板",
|
"Silent Start Info": "程序启动时以后台模式运行,不显示程序面板",
|
||||||
|
|
||||||
|
"TG Channel": "Telegram 频道",
|
||||||
|
"Manual": "使用手册",
|
||||||
|
"Github Repo": "GitHub 项目地址",
|
||||||
"Clash Setting": "Clash 设置",
|
"Clash Setting": "Clash 设置",
|
||||||
"Allow Lan": "局域网连接",
|
"Allow Lan": "局域网连接",
|
||||||
"IPv6": "IPv6",
|
"IPv6": "IPv6",
|
||||||
@ -176,7 +183,7 @@
|
|||||||
"Upgrade": "升级内核",
|
"Upgrade": "升级内核",
|
||||||
"Restart": "重启内核",
|
"Restart": "重启内核",
|
||||||
"Release Version": "正式版",
|
"Release Version": "正式版",
|
||||||
"Alpha Version": "内测版",
|
"Alpha Version": "测试版",
|
||||||
"Tun mode requires": "如需启用 Tun 模式需要授权",
|
"Tun mode requires": "如需启用 Tun 模式需要授权",
|
||||||
"Grant": "授权",
|
"Grant": "授权",
|
||||||
"Open UWP tool": "UWP 工具",
|
"Open UWP tool": "UWP 工具",
|
||||||
@ -251,9 +258,6 @@
|
|||||||
"Open Dev Tools": "打开开发者工具",
|
"Open Dev Tools": "打开开发者工具",
|
||||||
"Exit": "退出",
|
"Exit": "退出",
|
||||||
"Verge Version": "Verge 版本",
|
"Verge Version": "Verge 版本",
|
||||||
"TG Channel": "Telegram 频道",
|
|
||||||
"Doc": "文档",
|
|
||||||
"Source Code": "源代码",
|
|
||||||
|
|
||||||
"ReadOnly": "只读",
|
"ReadOnly": "只读",
|
||||||
"ReadOnlyMessage": "无法在只读模式下编辑",
|
"ReadOnlyMessage": "无法在只读模式下编辑",
|
||||||
|
@ -56,7 +56,7 @@ const ProfilePage = () => {
|
|||||||
|
|
||||||
const [url, setUrl] = useState("");
|
const [url, setUrl] = useState("");
|
||||||
const [disabled, setDisabled] = useState(false);
|
const [disabled, setDisabled] = useState(false);
|
||||||
const [activating, setActivating] = useState("");
|
const [activatings, setActivatings] = useState<string[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const sensors = useSensors(
|
const sensors = useSensors(
|
||||||
useSensor(PointerSensor),
|
useSensor(PointerSensor),
|
||||||
@ -128,6 +128,10 @@ const ProfilePage = () => {
|
|||||||
return { regularItems, enhanceItems };
|
return { regularItems, enhanceItems };
|
||||||
}, [profiles]);
|
}, [profiles]);
|
||||||
|
|
||||||
|
const currentActivatings = () => {
|
||||||
|
return [...new Set([profiles.current ?? "", ...chain])].filter(Boolean);
|
||||||
|
};
|
||||||
|
|
||||||
const onImport = async () => {
|
const onImport = async () => {
|
||||||
if (!url) return;
|
if (!url) return;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@ -138,13 +142,13 @@ const ProfilePage = () => {
|
|||||||
setUrl("");
|
setUrl("");
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|
||||||
getProfiles().then((newProfiles) => {
|
getProfiles().then(async (newProfiles) => {
|
||||||
mutate("getProfiles", newProfiles);
|
mutate("getProfiles", newProfiles);
|
||||||
|
|
||||||
const remoteItem = newProfiles.items?.find((e) => e.type === "remote");
|
const remoteItem = newProfiles.items?.find((e) => e.type === "remote");
|
||||||
if (!newProfiles.current && remoteItem) {
|
if (!newProfiles.current && remoteItem) {
|
||||||
const current = remoteItem.uid;
|
const current = remoteItem.uid;
|
||||||
patchProfiles({ current });
|
await patchProfiles({ current });
|
||||||
mutateLogs();
|
mutateLogs();
|
||||||
setTimeout(() => activateSelected(), 2000);
|
setTimeout(() => activateSelected(), 2000);
|
||||||
}
|
}
|
||||||
@ -171,7 +175,9 @@ const ProfilePage = () => {
|
|||||||
const onSelect = useLockFn(async (current: string, force: boolean) => {
|
const onSelect = useLockFn(async (current: string, force: boolean) => {
|
||||||
if (!force && current === profiles.current) return;
|
if (!force && current === profiles.current) return;
|
||||||
// 避免大多数情况下loading态闪烁
|
// 避免大多数情况下loading态闪烁
|
||||||
const reset = setTimeout(() => setActivating(current), 100);
|
const reset = setTimeout(() => {
|
||||||
|
setActivatings([...currentActivatings(), current]);
|
||||||
|
}, 100);
|
||||||
try {
|
try {
|
||||||
await patchProfiles({ current });
|
await patchProfiles({ current });
|
||||||
mutateLogs();
|
mutateLogs();
|
||||||
@ -182,42 +188,64 @@ const ProfilePage = () => {
|
|||||||
Notice.error(err?.message || err.toString(), 4000);
|
Notice.error(err?.message || err.toString(), 4000);
|
||||||
} finally {
|
} finally {
|
||||||
clearTimeout(reset);
|
clearTimeout(reset);
|
||||||
setActivating("");
|
setActivatings([]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const onEnhance = useLockFn(async () => {
|
const onEnhance = useLockFn(async () => {
|
||||||
|
setActivatings(currentActivatings());
|
||||||
try {
|
try {
|
||||||
await enhanceProfiles();
|
await enhanceProfiles();
|
||||||
mutateLogs();
|
mutateLogs();
|
||||||
Notice.success(t("Profile Reactivated"), 1000);
|
Notice.success(t("Profile Reactivated"), 1000);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err.message || err.toString(), 3000);
|
Notice.error(err.message || err.toString(), 3000);
|
||||||
|
} finally {
|
||||||
|
setActivatings([]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const onEnable = useLockFn(async (uid: string) => {
|
const onEnable = useLockFn(async (uid: string) => {
|
||||||
if (chain.includes(uid)) return;
|
if (chain.includes(uid)) return;
|
||||||
const newChain = [...chain, uid];
|
try {
|
||||||
await patchProfiles({ chain: newChain });
|
setActivatings([...currentActivatings(), uid]);
|
||||||
mutateLogs();
|
const newChain = [...chain, uid];
|
||||||
|
await patchProfiles({ chain: newChain });
|
||||||
|
mutateLogs();
|
||||||
|
} catch (err: any) {
|
||||||
|
Notice.error(err.message || err.toString(), 3000);
|
||||||
|
} finally {
|
||||||
|
setActivatings([]);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const onDisable = useLockFn(async (uid: string) => {
|
const onDisable = useLockFn(async (uid: string) => {
|
||||||
if (!chain.includes(uid)) return;
|
if (!chain.includes(uid)) return;
|
||||||
const newChain = chain.filter((i) => i !== uid);
|
try {
|
||||||
await patchProfiles({ chain: newChain });
|
setActivatings([...currentActivatings(), uid]);
|
||||||
mutateLogs();
|
const newChain = chain.filter((i) => i !== uid);
|
||||||
|
await patchProfiles({ chain: newChain });
|
||||||
|
mutateLogs();
|
||||||
|
} catch (err: any) {
|
||||||
|
Notice.error(err.message || err.toString(), 3000);
|
||||||
|
} finally {
|
||||||
|
setActivatings([]);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const onDelete = useLockFn(async (uid: string) => {
|
const onDelete = useLockFn(async (uid: string) => {
|
||||||
|
const current = profiles.current === uid;
|
||||||
try {
|
try {
|
||||||
await onDisable(uid);
|
await onDisable(uid);
|
||||||
|
setActivatings([...(current ? currentActivatings() : []), uid]);
|
||||||
await deleteProfile(uid);
|
await deleteProfile(uid);
|
||||||
mutateProfiles();
|
mutateProfiles();
|
||||||
mutateLogs();
|
mutateLogs();
|
||||||
|
current && (await onEnhance());
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err?.message || err.toString());
|
Notice.error(err?.message || err.toString());
|
||||||
|
} finally {
|
||||||
|
setActivatings([]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -396,10 +424,14 @@ const ProfilePage = () => {
|
|||||||
<ProfileItem
|
<ProfileItem
|
||||||
id={item.uid}
|
id={item.uid}
|
||||||
selected={profiles.current === item.uid}
|
selected={profiles.current === item.uid}
|
||||||
activating={activating === item.uid}
|
activating={activatings.includes(item.uid)}
|
||||||
itemData={item}
|
itemData={item}
|
||||||
onSelect={(f) => onSelect(item.uid, f)}
|
onSelect={(f) => onSelect(item.uid, f)}
|
||||||
onEdit={() => viewerRef.current?.edit(item)}
|
onEdit={() => viewerRef.current?.edit(item)}
|
||||||
|
onChange={async (prev, curr) => {
|
||||||
|
prev !== curr && (await onEnhance());
|
||||||
|
}}
|
||||||
|
onDelete={() => onDelete(item.uid)}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
))}
|
))}
|
||||||
@ -423,6 +455,7 @@ const ProfilePage = () => {
|
|||||||
<Grid item xs={12} sm={6} md={4} lg={3} key={item.file}>
|
<Grid item xs={12} sm={6} md={4} lg={3} key={item.file}>
|
||||||
<ProfileMore
|
<ProfileMore
|
||||||
selected={!!chain.includes(item.uid)}
|
selected={!!chain.includes(item.uid)}
|
||||||
|
activating={activatings.includes(item.uid)}
|
||||||
itemData={item}
|
itemData={item}
|
||||||
enableNum={chain.length || 0}
|
enableNum={chain.length || 0}
|
||||||
logInfo={chainLogs[item.uid]}
|
logInfo={chainLogs[item.uid]}
|
||||||
@ -432,6 +465,9 @@ const ProfilePage = () => {
|
|||||||
onMoveTop={() => onMoveTop(item.uid)}
|
onMoveTop={() => onMoveTop(item.uid)}
|
||||||
onMoveEnd={() => onMoveEnd(item.uid)}
|
onMoveEnd={() => onMoveEnd(item.uid)}
|
||||||
onEdit={() => viewerRef.current?.edit(item)}
|
onEdit={() => viewerRef.current?.edit(item)}
|
||||||
|
onChange={async (prev, curr) => {
|
||||||
|
prev !== curr && (await onEnhance());
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
))}
|
))}
|
||||||
|
@ -39,15 +39,7 @@ const SettingPage = () => {
|
|||||||
<IconButton
|
<IconButton
|
||||||
size="medium"
|
size="medium"
|
||||||
color="inherit"
|
color="inherit"
|
||||||
title={t("TG Channel")}
|
title={t("Manual")}
|
||||||
onClick={toTelegramChannel}
|
|
||||||
>
|
|
||||||
<Telegram fontSize="inherit" />
|
|
||||||
</IconButton>
|
|
||||||
<IconButton
|
|
||||||
size="medium"
|
|
||||||
color="inherit"
|
|
||||||
title={t("Doc")}
|
|
||||||
onClick={toGithubDoc}
|
onClick={toGithubDoc}
|
||||||
>
|
>
|
||||||
<HelpOutlineSharp fontSize="inherit" />
|
<HelpOutlineSharp fontSize="inherit" />
|
||||||
@ -55,7 +47,16 @@ const SettingPage = () => {
|
|||||||
<IconButton
|
<IconButton
|
||||||
size="medium"
|
size="medium"
|
||||||
color="inherit"
|
color="inherit"
|
||||||
title={t("Source Code")}
|
title={t("TG Channel")}
|
||||||
|
onClick={toTelegramChannel}
|
||||||
|
>
|
||||||
|
<Telegram fontSize="inherit" />
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
size="medium"
|
||||||
|
color="inherit"
|
||||||
|
title={t("Github Repo")}
|
||||||
onClick={toGithubRepo}
|
onClick={toGithubRepo}
|
||||||
>
|
>
|
||||||
<GitHub fontSize="inherit" />
|
<GitHub fontSize="inherit" />
|
||||||
|
Loading…
x
Reference in New Issue
Block a user