Переглянути джерело

feat: reactive after save when profile content changes

dongchengjie 11 місяців тому
батько
коміт
9ee5390ec7

+ 2 - 2
src/components/profile/confirm-viewer.tsx

@@ -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>

+ 8 - 5
src/components/profile/editor-viewer.tsx

@@ -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());

+ 27 - 21
src/components/profile/profile-item.tsx

@@ -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"
-        message="This operation is not reversible"
+        title={t("Confirm deletion")}
+        message={t("This operation is not reversible")}
         open={confirmOpen}
         open={confirmOpen}
         onClose={() => setConfirmOpen(false)}
         onClose={() => setConfirmOpen(false)}
         onConfirm={() => {
         onConfirm={() => {

+ 27 - 3
src/components/profile/profile-more.tsx

@@ -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"
-        message="This operation is not reversible"
+        title={t("Confirm deletion")}
+        message={t("This operation is not reversible")}
         open={confirmOpen}
         open={confirmOpen}
         onClose={() => setConfirmOpen(false)}
         onClose={() => setConfirmOpen(false)}
         onConfirm={() => {
         onConfirm={() => {

+ 3 - 3
src/components/setting/mods/sysproxy-viewer.tsx

@@ -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) {
-                    pac = content;
+                  if (curr && curr.trim().length > 0) {
+                    pac = curr;
                   }
                   }
                   setValue((v) => ({ ...v, pac_content: pac }));
                   setValue((v) => ({ ...v, pac_content: pac }));
                 }}
                 }}

+ 2 - 2
src/components/setting/mods/theme-viewer.tsx

@@ -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) => {
-              theme.css_injection = content;
+            onChange={(_prev, curr) => {
+              theme.css_injection = curr;
               handleChange("css_injection");
               handleChange("css_injection");
             }}
             }}
             onClose={() => {
             onClose={() => {

+ 7 - 3
src/locales/en.json

@@ -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",

+ 7 - 3
src/locales/fa.json

@@ -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": "نمی‌توان در ویرایشگر فقط خواندنی ویرایش کرد",

+ 7 - 3
src/locales/ru.json

@@ -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": "Невозможно редактировать в режиме только для чтения",

+ 8 - 4
src/locales/zh.json

@@ -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": "无法在只读模式下编辑",

+ 48 - 12
src/pages/profiles.tsx

@@ -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];
-    await patchProfiles({ chain: newChain });
-    mutateLogs();
+    try {
+      setActivatings([...currentActivatings(), uid]);
+      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);
-    await patchProfiles({ chain: newChain });
-    mutateLogs();
+    try {
+      setActivatings([...currentActivatings(), uid]);
+      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>
               ))}
               ))}

+ 8 - 7
src/pages/settings.tsx

@@ -39,23 +39,24 @@ const SettingPage = () => {
           <IconButton
           <IconButton
             size="medium"
             size="medium"
             color="inherit"
             color="inherit"
-            title={t("TG Channel")}
-            onClick={toTelegramChannel}
+            title={t("Manual")}
+            onClick={toGithubDoc}
           >
           >
-            <Telegram fontSize="inherit" />
+            <HelpOutlineSharp fontSize="inherit" />
           </IconButton>
           </IconButton>
           <IconButton
           <IconButton
             size="medium"
             size="medium"
             color="inherit"
             color="inherit"
-            title={t("Doc")}
-            onClick={toGithubDoc}
+            title={t("TG Channel")}
+            onClick={toTelegramChannel}
           >
           >
-            <HelpOutlineSharp fontSize="inherit" />
+            <Telegram fontSize="inherit" />
           </IconButton>
           </IconButton>
+
           <IconButton
           <IconButton
             size="medium"
             size="medium"
             color="inherit"
             color="inherit"
-            title={t("Source Code")}
+            title={t("Github Repo")}
             onClick={toGithubRepo}
             onClick={toGithubRepo}
           >
           >
             <GitHub fontSize="inherit" />
             <GitHub fontSize="inherit" />