feat: reactive after save when profile content changes
Some checks are pending
Alpha Build / alpha (macos-latest, aarch64-apple-darwin) (push) Waiting to run
Alpha Build / alpha (macos-latest, x86_64-apple-darwin) (push) Waiting to run
Alpha Build / alpha (windows-latest, aarch64-pc-windows-msvc) (push) Waiting to run
Alpha Build / alpha (windows-latest, i686-pc-windows-msvc) (push) Waiting to run
Alpha Build / alpha (windows-latest, x86_64-pc-windows-msvc) (push) Waiting to run
Alpha Build / alpha-for-linux (ubuntu-latest, aarch64-unknown-linux-gnu) (push) Waiting to run
Alpha Build / alpha-for-linux (ubuntu-latest, armv7-unknown-linux-gnueabihf) (push) Waiting to run
Alpha Build / alpha-for-linux (ubuntu-latest, i686-unknown-linux-gnu) (push) Waiting to run
Alpha Build / alpha-for-linux (ubuntu-latest, x86_64-unknown-linux-gnu) (push) Waiting to run
Alpha Build / alpha-for-fixed-webview2 (arm64, windows-latest, aarch64-pc-windows-msvc) (push) Waiting to run
Alpha Build / alpha-for-fixed-webview2 (x64, windows-latest, x86_64-pc-windows-msvc) (push) Waiting to run
Alpha Build / alpha-for-fixed-webview2 (x86, windows-latest, i686-pc-windows-msvc) (push) Waiting to run
Alpha Build / Update tag (push) Blocked by required conditions

This commit is contained in:
dongchengjie 2024-06-29 09:21:50 +08:00
parent 3f1caa702b
commit 9ee5390ec7
12 changed files with 157 additions and 71 deletions

View File

@ -27,10 +27,10 @@ export const ConfirmViewer = (props: Props) => {
return (
<Dialog open={open} onClose={onClose} maxWidth="xs" fullWidth>
<DialogTitle>{t(title)}</DialogTitle>
<DialogTitle>{title}</DialogTitle>
<DialogContent sx={{ pb: 1, userSelect: "text" }}>
{t(message)}
{message}
</DialogContent>
<DialogActions>

View File

@ -32,7 +32,7 @@ interface Props {
language: "yaml" | "javascript" | "css";
schema?: "clash" | "merge";
onClose: () => void;
onChange?: (content?: string) => void;
onChange?: (prev?: string, curr?: string) => void;
}
// yaml worker
@ -90,6 +90,7 @@ export const EditorViewer = (props: Props) => {
const editorRef = useRef<any>();
const instanceRef = useRef<editor.IStandaloneCodeEditor | null>(null);
const themeMode = useThemeMode();
const prevData = useRef<string>();
useEffect(() => {
if (!open) return;
@ -136,6 +137,8 @@ export const EditorViewer = (props: Props) => {
fontLigatures: true, // 连字符
smoothScrolling: true, // 平滑滚动
});
prevData.current = data;
});
return () => {
@ -147,15 +150,15 @@ export const EditorViewer = (props: Props) => {
}, [open]);
const onSave = useLockFn(async () => {
const value = instanceRef.current?.getValue();
const currData = instanceRef.current?.getValue();
if (value == null) return;
if (currData == null) return;
try {
if (mode === "profile") {
await saveProfileFile(property, value);
await saveProfileFile(property, currData);
}
onChange?.(value);
onChange?.(prevData.current, currData);
onClose();
} catch (err: any) {
Notice.error(err.message || err.toString());

View File

@ -17,7 +17,7 @@ import {
} from "@mui/material";
import { RefreshRounded, DragIndicator } from "@mui/icons-material";
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 { EditorViewer } from "@/components/profile/editor-viewer";
import { ProfileBox } from "./profile-box";
@ -36,10 +36,20 @@ interface Props {
itemData: IProfileItem;
onSelect: (force: boolean) => void;
onEdit: () => void;
onChange?: (prev?: string, curr?: string) => void;
onDelete: () => void;
}
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 } =
useSortable({ id: props.id });
@ -53,6 +63,7 @@ export const ProfileItem = (props: Props) => {
// local file mode
// remote file mode
// remote file mode
const hasUrl = !!itemData.url;
const hasExtra = !!extra; // only subscription url has extra info
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 = (
hasHome ? [{ label: "Home", handler: onOpenHome }] : []
).concat([
@ -242,7 +243,7 @@ export const ProfileItem = (props: Props) => {
backdropFilter: "blur(2px)",
}}
>
<CircularProgress size={20} />
<CircularProgress color="inherit" size={20} />
</Box>
)}
<Box position="relative">
@ -312,7 +313,7 @@ export const ProfileItem = (props: Props) => {
</Typography>
) : (
hasUrl && (
<Typography noWrap title={`From ${from}`}>
<Typography noWrap title={`${t("From")} ${from}`}>
{from}
</Typography>
)
@ -323,7 +324,7 @@ export const ProfileItem = (props: Props) => {
flex="1 0 auto"
fontSize={14}
textAlign="right"
title={`Updated Time: ${parseExpire(updated)}`}
title={`${t("Update Time")}: ${parseExpire(updated)}`}
>
{updated > 0 ? dayjs(updated * 1000).fromNow() : ""}
</Typography>
@ -334,17 +335,21 @@ export const ProfileItem = (props: Props) => {
{/* the third line show extra info or last updated time */}
{hasExtra ? (
<Box sx={{ ...boxStyle, fontSize: 14 }}>
<span title="Used / Total">
<span title={t("Used / Total")}>
{parseTraffic(upload + download)} / {parseTraffic(total)}
</span>
<span title="Expire Time">{expire}</span>
<span title={t("Expire Time")}>{expire}</span>
</Box>
) : (
<Box sx={{ ...boxStyle, fontSize: 12, justifyContent: "flex-end" }}>
<span title="Updated Time">{parseExpire(updated)}</span>
<span title={t("Update Time")}>{parseExpire(updated)}</span>
</Box>
)}
<LinearProgress variant="determinate" value={progress} />
<LinearProgress
variant="determinate"
value={progress}
style={{ opacity: progress > 0 ? 1 : 0 }}
/>
</ProfileBox>
<Menu
@ -390,11 +395,12 @@ export const ProfileItem = (props: Props) => {
open={fileOpen}
language="yaml"
schema="clash"
onChange={onChange}
onClose={() => setFileOpen(false)}
/>
<ConfirmViewer
title="Confirm deletion"
message="This operation is not reversible"
title={t("Confirm deletion")}
message={t("This operation is not reversible")}
open={confirmOpen}
onClose={() => setConfirmOpen(false)}
onConfirm={() => {

View File

@ -9,6 +9,7 @@ import {
MenuItem,
Menu,
IconButton,
CircularProgress,
} from "@mui/material";
import { FeaturedPlayListRounded } from "@mui/icons-material";
import { viewProfile } from "@/services/cmds";
@ -20,6 +21,7 @@ import { ConfirmViewer } from "./confirm-viewer";
interface Props {
selected: boolean;
activating: boolean;
itemData: IProfileItem;
enableNum: number;
logInfo?: [string, string][];
@ -27,14 +29,16 @@ interface Props {
onDisable: () => void;
onMoveTop: () => void;
onMoveEnd: () => void;
onDelete: () => void;
onEdit: () => void;
onChange?: (prev?: string, curr?: string) => void;
onDelete: () => void;
}
// profile enhanced item
export const ProfileMore = (props: Props) => {
const {
selected,
activating,
itemData,
enableNum,
logInfo = [],
@ -44,6 +48,7 @@ export const ProfileMore = (props: Props) => {
onMoveEnd,
onDelete,
onEdit,
onChange,
} = props;
const { uid, type } = itemData;
@ -132,6 +137,24 @@ export const ProfileMore = (props: Props) => {
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
display="flex"
justifyContent="space-between"
@ -237,11 +260,12 @@ export const ProfileMore = (props: Props) => {
open={fileOpen}
language={type === "merge" ? "yaml" : "javascript"}
schema={type === "merge" ? "merge" : undefined}
onChange={onChange}
onClose={() => setFileOpen(false)}
/>
<ConfirmViewer
title="Confirm deletion"
message="This operation is not reversible"
title={t("Confirm deletion")}
message={t("This operation is not reversible")}
open={confirmOpen}
onClose={() => setConfirmOpen(false)}
onConfirm={() => {

View File

@ -249,10 +249,10 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
property={value.pac_content ?? ""}
open={editorOpen}
language="javascript"
onChange={(content) => {
onChange={(_prev, curr) => {
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 }));
}}

View File

@ -129,8 +129,8 @@ export const ThemeViewer = forwardRef<DialogRef>((props, ref) => {
property={theme.css_injection ?? ""}
open={editorOpen}
language="css"
onChange={(content) => {
theme.css_injection = content;
onChange={(_prev, curr) => {
theme.css_injection = curr;
handleChange("css_injection");
}}
onClose={() => {

View File

@ -49,6 +49,10 @@
"Paste": "Paste",
"Profile URL": "Profile URL",
"Import": "Import",
"From": "From",
"Update Time": "Update Time",
"Used / Total": "Used / Total",
"Expire Time": "Expire Time",
"Create Profile": "Create Profile",
"Edit Profile": "Edit Profile",
"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",
"Update GeoData": "Update GeoData",
"TG Channel": "Telegram Channel",
"Manual": "Manual",
"Github Repo": "Github Repo",
"Verge Setting": "Verge Setting",
"Language": "Language",
"Theme Mode": "Theme Mode",
@ -246,9 +253,6 @@
"Open Dev Tools": "Open Dev Tools",
"Exit": "Exit",
"Verge Version": "Verge Version",
"TG Channel": "Telegram Channel",
"Doc": "Docs",
"Source Code": "Source Code",
"ReadOnly": "ReadOnly",
"ReadOnlyMessage": "Cannot edit in read-only editor",

View File

@ -49,6 +49,10 @@
"Paste": "چسباندن",
"Profile URL": "آدرس پروفایل",
"Import": "وارد کردن",
"From": "از",
"Update Time": "زمان به‌روزرسانی",
"Used / Total": "استفاده‌شده / کل",
"Expire Time": "زمان انقضا",
"Create Profile": "ایجاد پروفایل",
"Edit Profile": "ویرایش پروفایل",
"Type": "نوع",
@ -183,6 +187,9 @@
"Open UWP tool Info": "از ویندوز 8 به بعد، برنامه‌های UWP (مانند Microsoft Store) از دسترسی مستقیم به خدمات شبکه محلی محدود شده‌اند و این ابزار می‌تواند برای دور زدن این محدودیت استفاده شود",
"Update GeoData": "به‌روزرسانی GeoData",
"TG Channel": "کانال تلگرام",
"Manual": "راهنما",
"Github Repo": "مخزن GitHub",
"Verge Setting": "تنظیمات Verge",
"Language": "زبان",
"Theme Mode": "حالت تم",
@ -251,9 +258,6 @@
"Open Dev Tools": "باز کردن ابزارهای توسعه‌دهنده",
"Exit": "خروج",
"Verge Version": "نسخه Verge",
"TG Channel": "کانال تلگرام",
"Doc": "سند",
"Source Code": "کد منبع",
"ReadOnly": "فقط خواندنی",
"ReadOnlyMessage": "نمی‌توان در ویرایشگر فقط خواندنی ویرایش کرد",

View File

@ -49,6 +49,10 @@
"Paste": "Вставить",
"Profile URL": "URL профиля",
"Import": "Импорт",
"From": "От",
"Update Time": "Время обновления",
"Used / Total": "Использовано / Всего",
"Expire Time": "Время окончания",
"Create Profile": "Создать профиль",
"Edit Profile": "Изменить профиль",
"Type": "Тип",
@ -183,6 +187,9 @@
"Open UWP tool Info": "С Windows 8 приложения UWP (такие как Microsoft Store) ограничены в прямом доступе к сетевым службам локального хоста, и этот инструмент позволяет обойти это ограничение",
"Update GeoData": "Обновление GeoData",
"TG Channel": "Канал Telegram",
"Manual": "Документация",
"Github Repo": "GitHub репозиторий",
"Verge Setting": "Настройки Verge",
"Language": "Язык",
"Theme Mode": "Режим темы",
@ -251,9 +258,6 @@
"Open Dev Tools": "Открыть инструменты разработчика",
"Exit": "Выход",
"Verge Version": "Версия Verge",
"TG Channel": "Канал Telegram",
"Doc": "документ",
"Source Code": "Исходный код",
"ReadOnly": "Только для чтения",
"ReadOnlyMessage": "Невозможно редактировать в режиме только для чтения",

View File

@ -49,6 +49,10 @@
"Paste": "粘贴",
"Profile URL": "订阅文件链接",
"Import": "导入",
"From": "来自",
"Update Time": "更新时间",
"Used / Total": "已使用 / 总量",
"Expire Time": "到期时间",
"Create Profile": "新建配置",
"Edit Profile": "编辑配置",
"Type": "类型",
@ -154,6 +158,9 @@
"Silent Start": "静默启动",
"Silent Start Info": "程序启动时以后台模式运行,不显示程序面板",
"TG Channel": "Telegram 频道",
"Manual": "使用手册",
"Github Repo": "GitHub 项目地址",
"Clash Setting": "Clash 设置",
"Allow Lan": "局域网连接",
"IPv6": "IPv6",
@ -176,7 +183,7 @@
"Upgrade": "升级内核",
"Restart": "重启内核",
"Release Version": "正式版",
"Alpha Version": "测版",
"Alpha Version": "版",
"Tun mode requires": "如需启用 Tun 模式需要授权",
"Grant": "授权",
"Open UWP tool": "UWP 工具",
@ -251,9 +258,6 @@
"Open Dev Tools": "打开开发者工具",
"Exit": "退出",
"Verge Version": "Verge 版本",
"TG Channel": "Telegram 频道",
"Doc": "文档",
"Source Code": "源代码",
"ReadOnly": "只读",
"ReadOnlyMessage": "无法在只读模式下编辑",

View File

@ -56,7 +56,7 @@ const ProfilePage = () => {
const [url, setUrl] = useState("");
const [disabled, setDisabled] = useState(false);
const [activating, setActivating] = useState("");
const [activatings, setActivatings] = useState<string[]>([]);
const [loading, setLoading] = useState(false);
const sensors = useSensors(
useSensor(PointerSensor),
@ -128,6 +128,10 @@ const ProfilePage = () => {
return { regularItems, enhanceItems };
}, [profiles]);
const currentActivatings = () => {
return [...new Set([profiles.current ?? "", ...chain])].filter(Boolean);
};
const onImport = async () => {
if (!url) return;
setLoading(true);
@ -138,13 +142,13 @@ const ProfilePage = () => {
setUrl("");
setLoading(false);
getProfiles().then((newProfiles) => {
getProfiles().then(async (newProfiles) => {
mutate("getProfiles", newProfiles);
const remoteItem = newProfiles.items?.find((e) => e.type === "remote");
if (!newProfiles.current && remoteItem) {
const current = remoteItem.uid;
patchProfiles({ current });
await patchProfiles({ current });
mutateLogs();
setTimeout(() => activateSelected(), 2000);
}
@ -171,7 +175,9 @@ const ProfilePage = () => {
const onSelect = useLockFn(async (current: string, force: boolean) => {
if (!force && current === profiles.current) return;
// 避免大多数情况下loading态闪烁
const reset = setTimeout(() => setActivating(current), 100);
const reset = setTimeout(() => {
setActivatings([...currentActivatings(), current]);
}, 100);
try {
await patchProfiles({ current });
mutateLogs();
@ -182,42 +188,64 @@ const ProfilePage = () => {
Notice.error(err?.message || err.toString(), 4000);
} finally {
clearTimeout(reset);
setActivating("");
setActivatings([]);
}
});
const onEnhance = useLockFn(async () => {
setActivatings(currentActivatings());
try {
await enhanceProfiles();
mutateLogs();
Notice.success(t("Profile Reactivated"), 1000);
} catch (err: any) {
Notice.error(err.message || err.toString(), 3000);
} finally {
setActivatings([]);
}
});
const onEnable = useLockFn(async (uid: string) => {
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) => {
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 current = profiles.current === uid;
try {
await onDisable(uid);
setActivatings([...(current ? currentActivatings() : []), uid]);
await deleteProfile(uid);
mutateProfiles();
mutateLogs();
current && (await onEnhance());
} catch (err: any) {
Notice.error(err?.message || err.toString());
} finally {
setActivatings([]);
}
});
@ -396,10 +424,14 @@ const ProfilePage = () => {
<ProfileItem
id={item.uid}
selected={profiles.current === item.uid}
activating={activating === item.uid}
activating={activatings.includes(item.uid)}
itemData={item}
onSelect={(f) => onSelect(item.uid, f)}
onEdit={() => viewerRef.current?.edit(item)}
onChange={async (prev, curr) => {
prev !== curr && (await onEnhance());
}}
onDelete={() => onDelete(item.uid)}
/>
</Grid>
))}
@ -423,6 +455,7 @@ const ProfilePage = () => {
<Grid item xs={12} sm={6} md={4} lg={3} key={item.file}>
<ProfileMore
selected={!!chain.includes(item.uid)}
activating={activatings.includes(item.uid)}
itemData={item}
enableNum={chain.length || 0}
logInfo={chainLogs[item.uid]}
@ -432,6 +465,9 @@ const ProfilePage = () => {
onMoveTop={() => onMoveTop(item.uid)}
onMoveEnd={() => onMoveEnd(item.uid)}
onEdit={() => viewerRef.current?.edit(item)}
onChange={async (prev, curr) => {
prev !== curr && (await onEnhance());
}}
/>
</Grid>
))}

View File

@ -39,15 +39,7 @@ const SettingPage = () => {
<IconButton
size="medium"
color="inherit"
title={t("TG Channel")}
onClick={toTelegramChannel}
>
<Telegram fontSize="inherit" />
</IconButton>
<IconButton
size="medium"
color="inherit"
title={t("Doc")}
title={t("Manual")}
onClick={toGithubDoc}
>
<HelpOutlineSharp fontSize="inherit" />
@ -55,7 +47,16 @@ const SettingPage = () => {
<IconButton
size="medium"
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}
>
<GitHub fontSize="inherit" />