refactor: Associate Profile with Merge/Script.

This commit is contained in:
MystiPanda
2024-06-29 23:07:44 +08:00
parent b85929772e
commit 3efef52398
15 changed files with 286 additions and 613 deletions

View File

@@ -59,7 +59,7 @@ export const ProfileItem = (props: Props) => {
const loadingCache = useLoadingCache();
const setLoadingCache = useSetLoadingCache();
const { uid, name = "Profile", extra, updated = 0 } = itemData;
const { uid, name = "Profile", extra, updated = 0, option } = itemData;
// local file mode
// remote file mode
@@ -105,6 +105,8 @@ export const ProfileItem = (props: Props) => {
}, [hasUrl, updated]);
const [fileOpen, setFileOpen] = useState(false);
const [mergeOpen, setMergeOpen] = useState(false);
const [scriptOpen, setScriptOpen] = useState(false);
const [confirmOpen, setConfirmOpen] = useState(false);
const onOpenHome = () => {
@@ -122,6 +124,16 @@ export const ProfileItem = (props: Props) => {
setFileOpen(true);
};
const onEditMerge = () => {
setAnchorEl(null);
setMergeOpen(true);
};
const onEditScript = () => {
setAnchorEl(null);
setScriptOpen(true);
};
const onForceSelect = () => {
setAnchorEl(null);
onSelect(true);
@@ -174,33 +186,55 @@ export const ProfileItem = (props: Props) => {
});
const urlModeMenu = (
hasHome ? [{ label: "Home", handler: onOpenHome }] : []
hasHome ? [{ label: "Home", handler: onOpenHome, disabled: false }] : []
).concat([
{ label: "Select", handler: onForceSelect },
{ label: "Edit Info", handler: onEditInfo },
{ label: "Edit File", handler: onEditFile },
{ label: "Open File", handler: onOpenFile },
{ label: "Update", handler: () => onUpdate(0) },
{ label: "Update(Proxy)", handler: () => onUpdate(2) },
{ label: "Select", handler: onForceSelect, disabled: false },
{ label: "Edit Info", handler: onEditInfo, disabled: false },
{ label: "Edit File", handler: onEditFile, disabled: false },
{
label: "Edit Merge",
handler: onEditMerge,
disabled: option?.merge === null,
},
{
label: "Edit Script",
handler: onEditScript,
disabled: option?.script === null,
},
{ label: "Open File", handler: onOpenFile, disabled: false },
{ label: "Update", handler: () => onUpdate(0), disabled: false },
{ label: "Update(Proxy)", handler: () => onUpdate(2), disabled: false },
{
label: "Delete",
handler: () => {
setAnchorEl(null);
setConfirmOpen(true);
},
disabled: false,
},
]);
const fileModeMenu = [
{ label: "Select", handler: onForceSelect },
{ label: "Edit Info", handler: onEditInfo },
{ label: "Edit File", handler: onEditFile },
{ label: "Open File", handler: onOpenFile },
{ label: "Select", handler: onForceSelect, disabled: false },
{ label: "Edit Info", handler: onEditInfo, disabled: false },
{ label: "Edit File", handler: onEditFile, disabled: false },
{
label: "Edit Merge",
handler: onEditMerge,
disabled: option?.merge === null,
},
{
label: "Edit Script",
handler: onEditScript,
disabled: option?.script === null,
},
{ label: "Open File", handler: onOpenFile, disabled: false },
{
label: "Delete",
handler: () => {
setAnchorEl(null);
setConfirmOpen(true);
},
disabled: false,
},
];
@@ -369,6 +403,7 @@ export const ProfileItem = (props: Props) => {
<MenuItem
key={item.label}
onClick={item.handler}
disabled={item.disabled}
sx={[
{
minWidth: 120,
@@ -398,6 +433,24 @@ export const ProfileItem = (props: Props) => {
onChange={onChange}
onClose={() => setFileOpen(false)}
/>
<EditorViewer
mode="profile"
property={option?.merge ?? "123"}
open={mergeOpen}
language="yaml"
schema="merge"
onChange={onChange}
onClose={() => setMergeOpen(false)}
/>
<EditorViewer
mode="profile"
property={option?.script ?? ""}
open={scriptOpen}
language="javascript"
schema={undefined}
onChange={onChange}
onClose={() => setScriptOpen(false)}
/>
<ConfirmViewer
title={t("Confirm deletion")}
message={t("This operation is not reversible")}

View File

@@ -1,285 +0,0 @@
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useLockFn } from "ahooks";
import {
Box,
Badge,
Chip,
Typography,
MenuItem,
Menu,
IconButton,
CircularProgress,
} from "@mui/material";
import { FeaturedPlayListRounded } from "@mui/icons-material";
import { viewProfile } from "@/services/cmds";
import { Notice } from "@/components/base";
import { EditorViewer } from "@/components/profile/editor-viewer";
import { ProfileBox } from "./profile-box";
import { LogViewer } from "./log-viewer";
import { ConfirmViewer } from "./confirm-viewer";
interface Props {
selected: boolean;
activating: boolean;
itemData: IProfileItem;
enableNum: number;
logInfo?: [string, string][];
onEnable: () => void;
onDisable: () => void;
onMoveTop: () => void;
onMoveEnd: () => 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 = [],
onEnable,
onDisable,
onMoveTop,
onMoveEnd,
onDelete,
onEdit,
onChange,
} = props;
const { uid, type } = itemData;
const { t, i18n } = useTranslation();
const [anchorEl, setAnchorEl] = useState<any>(null);
const [position, setPosition] = useState({ left: 0, top: 0 });
const [fileOpen, setFileOpen] = useState(false);
const [confirmOpen, setConfirmOpen] = useState(false);
const [logOpen, setLogOpen] = useState(false);
const onEditInfo = () => {
setAnchorEl(null);
onEdit();
};
const onEditFile = () => {
setAnchorEl(null);
setFileOpen(true);
};
const onOpenFile = useLockFn(async () => {
setAnchorEl(null);
try {
await viewProfile(itemData.uid);
} catch (err: any) {
Notice.error(err?.message || err.toString());
}
});
const fnWrapper = (fn: () => void) => () => {
setAnchorEl(null);
return fn();
};
const hasError = !!logInfo.find((e) => e[0] === "exception");
const showMove = enableNum > 1 && !hasError;
const enableMenu = [
{ label: "Disable", handler: fnWrapper(onDisable) },
{ label: "Edit Info", handler: onEditInfo },
{ label: "Edit File", handler: onEditFile },
{ label: "Open File", handler: onOpenFile },
{ label: "To Top", show: showMove, handler: fnWrapper(onMoveTop) },
{ label: "To End", show: showMove, handler: fnWrapper(onMoveEnd) },
{
label: "Delete",
handler: () => {
setAnchorEl(null);
setConfirmOpen(true);
},
},
];
const disableMenu = [
{ label: "Enable", handler: fnWrapper(onEnable) },
{ label: "Edit Info", handler: onEditInfo },
{ label: "Edit File", handler: onEditFile },
{ label: "Open File", handler: onOpenFile },
{
label: "Delete",
handler: () => {
setAnchorEl(null);
setConfirmOpen(true);
},
},
];
const boxStyle = {
height: 26,
display: "flex",
alignItems: "center",
justifyContent: "space-between",
lineHeight: 1,
};
return (
<>
<ProfileBox
aria-selected={selected}
onDoubleClick={onEditFile}
// onClick={() => onSelect(false)}
onContextMenu={(event) => {
const { clientX, clientY } = event;
setPosition({ top: clientY, left: clientX });
setAnchorEl(event.currentTarget);
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"
alignItems="center"
mb={0.5}
>
<Typography
width="calc(100% - 52px)"
variant="h6"
component="h2"
noWrap
title={itemData.name}
>
{itemData.name}
</Typography>
<Chip
label={type}
color="primary"
size="small"
variant="outlined"
sx={{ height: 20, textTransform: "capitalize" }}
/>
</Box>
<Box sx={boxStyle}>
{selected && type === "script" ? (
hasError ? (
<Badge color="error" variant="dot" overlap="circular">
<IconButton
size="small"
edge="start"
color="error"
title={t("Script Console")}
onClick={() => setLogOpen(true)}
>
<FeaturedPlayListRounded fontSize="inherit" />
</IconButton>
</Badge>
) : (
<IconButton
size="small"
edge="start"
color="inherit"
title={t("Script Console")}
onClick={() => setLogOpen(true)}
>
<FeaturedPlayListRounded fontSize="inherit" />
</IconButton>
)
) : (
<Typography
noWrap
title={itemData.desc}
sx={i18n.language === "zh" ? { width: "calc(100% - 75px)" } : {}}
>
{itemData.desc}
</Typography>
)}
</Box>
</ProfileBox>
<Menu
open={!!anchorEl}
anchorEl={anchorEl}
onClose={() => setAnchorEl(null)}
anchorPosition={position}
anchorReference="anchorPosition"
transitionDuration={225}
MenuListProps={{ sx: { py: 0.5 } }}
onContextMenu={(e) => {
setAnchorEl(null);
e.preventDefault();
}}
>
{(selected ? enableMenu : disableMenu)
.filter((item: any) => item.show !== false)
.map((item) => (
<MenuItem
key={item.label}
onClick={item.handler}
sx={[
{ minWidth: 120 },
(theme) => {
return {
color:
item.label === "Delete"
? theme.palette.error.main
: undefined,
};
},
]}
dense
>
{t(item.label)}
</MenuItem>
))}
</Menu>
<EditorViewer
mode="profile"
property={uid}
open={fileOpen}
language={type === "merge" ? "yaml" : "javascript"}
schema={type === "merge" ? "merge" : undefined}
onChange={onChange}
onClose={() => setFileOpen(false)}
/>
<ConfirmViewer
title={t("Confirm deletion")}
message={t("This operation is not reversible")}
open={confirmOpen}
onClose={() => setConfirmOpen(false)}
onConfirm={() => {
onDelete();
setConfirmOpen(false);
}}
/>
{selected && (
<LogViewer
open={logOpen}
logInfo={logInfo}
onClose={() => setLogOpen(false)}
/>
)}
</>
);
};

View File

@@ -33,7 +33,7 @@ export interface ProfileViewerRef {
}
// create or edit the profile
// remote / local / merge / script
// remote / local
export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
(props, ref) => {
const { t } = useTranslation();
@@ -92,9 +92,6 @@ export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
if (form.type === "remote" && !form.url) {
throw new Error("The URL should not be null");
}
if (form.type !== "remote" && form.type !== "local") {
delete form.option;
}
if (form.option?.update_interval) {
form.option.update_interval = +form.option.update_interval;
@@ -168,8 +165,6 @@ export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
<Select {...field} autoFocus label={t("Type")}>
<MenuItem value="remote">Remote</MenuItem>
<MenuItem value="local">Local</MenuItem>
<MenuItem value="script">Script</MenuItem>
<MenuItem value="merge">Merge</MenuItem>
</Select>
</FormControl>
)}

View File

@@ -1,20 +1,17 @@
{
"millis": "millis",
"mins": "mins",
"Back": "Back",
"Close": "Close",
"Cancel": "Cancel",
"Confirm": "Confirm",
"Empty": "Empty",
"New": "New",
"Edit": "Edit",
"Save": "Save",
"Delete": "Delete",
"Enable": "Enable",
"Disable": "Disable",
"Label-Proxies": "Proxies",
"Label-Profiles": "Profiles",
"Label-Connections": "Connections",
@@ -22,7 +19,6 @@
"Label-Logs": "Logs",
"Label-Test": "Test",
"Label-Settings": "Settings",
"Proxies": "Proxies",
"Proxy Groups": "Proxy Groups",
"Proxy Provider": "Proxy Provider",
@@ -41,7 +37,6 @@
"Delay check to cancel fixed": "Delay check to cancel fixed",
"Proxy basic": "Proxy basic",
"Proxy detail": "Proxy detail",
"Profiles": "Profiles",
"Update All Profiles": "Update All Profiles",
"View Runtime Config": "View Runtime Config",
@@ -55,6 +50,8 @@
"Expire Time": "Expire Time",
"Create Profile": "Create Profile",
"Edit Profile": "Edit Profile",
"Edit Merge": "Edit Merge",
"Edit Script": "Edit Script",
"Type": "Type",
"Name": "Name",
"Descriptions": "Descriptions",
@@ -77,7 +74,6 @@
"Script Console": "Script Console",
"To Top": "To Top",
"To End": "To End",
"Connections": "Connections",
"Table View": "Table View",
"List View": "List View",
@@ -97,21 +93,17 @@
"Source": "Source",
"Destination IP": "Destination IP",
"Close Connection": "Close Connection",
"Rules": "Rules",
"Rule Provider": "Rule Provider",
"Logs": "Logs",
"Pause": "Pause",
"Clear": "Clear",
"Test": "Test",
"Test All": "Test All",
"Create Test": "Create Test",
"Edit Test": "Edit Test",
"Icon": "Icon",
"Test URL": "Test URL",
"Settings": "Settings",
"System Setting": "System Setting",
"Tun Mode": "Tun Mode",
@@ -157,7 +149,6 @@
"Auto Launch": "Auto Launch",
"Silent Start": "Silent Start",
"Silent Start Info": "Start the program in background mode without displaying the panel",
"Clash Setting": "Clash Setting",
"Allow Lan": "Allow Lan",
"IPv6": "IPv6",
@@ -181,7 +172,6 @@
"Open UWP tool": "Open UWP tool",
"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",
@@ -253,7 +243,6 @@
"Open Dev Tools": "Open Dev Tools",
"Exit": "Exit",
"Verge Version": "Verge Version",
"ReadOnly": "ReadOnly",
"ReadOnlyMessage": "Cannot edit in read-only editor",
"Filter": "Filter",
@@ -261,7 +250,6 @@
"Match Case": "Match Case",
"Match Whole Word": "Match Whole Word",
"Use Regular Expression": "Use Regular Expression",
"Profile Imported Successfully": "Profile Imported Successfully",
"Clash Config Updated": "Clash Config Updated",
"Profile Switched": "Profile Switched",

View File

@@ -1,20 +1,17 @@
{
"millis": "میلی‌ثانیه",
"mins": "دقیقه",
"Back": "بازگشت",
"Close": "بستن",
"Cancel": "لغو",
"Confirm": "تأیید",
"Empty": "خالی خالی",
"New": "جدید",
"Edit": "ویرایش",
"Save": "ذخیره",
"Delete": "حذف",
"Enable": "فعال کردن",
"Disable": "غیرفعال کردن",
"Label-Proxies": "پراکسی‌ها",
"Label-Profiles": "پروفایل‌ها",
"Label-Connections": "اتصالات",
@@ -22,7 +19,6 @@
"Label-Logs": "لاگ‌ها",
"Label-Test": "آزمون",
"Label-Settings": "تنظیمات",
"Proxies": "پراکسی‌ها",
"Proxy Groups": "گروه‌های پراکسی",
"Proxy Provider": "تأمین‌کننده پروکسی",
@@ -41,7 +37,6 @@
"Delay check to cancel fixed": "بررسی تأخیر برای لغو ثابت",
"Proxy basic": "پراکسی پایه",
"Proxy detail": "جزئیات پراکسی",
"Profiles": "پروفایل‌ها",
"Update All Profiles": "به‌روزرسانی همه پروفایل‌ها",
"View Runtime Config": "مشاهده پیکربندی زمان اجرا",
@@ -55,6 +50,8 @@
"Expire Time": "زمان انقضا",
"Create Profile": "ایجاد پروفایل",
"Edit Profile": "ویرایش پروفایل",
"Edit Merge": "ادغام ویرایش",
"Edit Script": "ویرایش اسکریپت",
"Type": "نوع",
"Name": "نام",
"Descriptions": "توضیحات",
@@ -77,7 +74,6 @@
"Script Console": "کنسول اسکریپت",
"To Top": "به بالا",
"To End": "به پایان",
"Connections": "اتصالات",
"Table View": "نمای جدولی",
"List View": "نمای لیستی",
@@ -97,21 +93,17 @@
"Source": "منبع",
"Destination IP": "آدرس IP مقصد",
"Close Connection": "بستن اتصال",
"Rules": "قوانین",
"Rule Provider": "تأمین‌کننده قانون",
"Logs": "لاگ‌ها",
"Pause": "توقف",
"Clear": "پاک کردن",
"Test": "آزمون",
"Test All": "آزمون همه",
"Create Test": "ایجاد آزمون",
"Edit Test": "ویرایش آزمون",
"Icon": "آیکون",
"Test URL": "آدرس آزمون",
"Settings": "تنظیمات",
"System Setting": "تنظیمات سیستم",
"Tun Mode": "حالت Tun",
@@ -157,7 +149,6 @@
"Auto Launch": "راه‌اندازی خودکار",
"Silent Start": "شروع بی‌صدا",
"Silent Start Info": "برنامه را در حالت پس‌زمینه بدون نمایش پانل اجرا کنید",
"Clash Setting": "تنظیمات Clash",
"Allow Lan": "اجازه LAN",
"IPv6": "IPv6",
@@ -186,7 +177,6 @@
"Open UWP tool": "باز کردن ابزار UWP",
"Open UWP tool Info": "از ویندوز 8 به بعد، برنامه‌های UWP (مانند Microsoft Store) از دسترسی مستقیم به خدمات شبکه محلی محدود شده‌اند و این ابزار می‌تواند برای دور زدن این محدودیت استفاده شود",
"Update GeoData": "به‌روزرسانی GeoData",
"TG Channel": "کانال تلگرام",
"Manual": "راهنما",
"Github Repo": "مخزن GitHub",
@@ -258,7 +248,6 @@
"Open Dev Tools": "باز کردن ابزارهای توسعه‌دهنده",
"Exit": "خروج",
"Verge Version": "نسخه Verge",
"ReadOnly": "فقط خواندنی",
"ReadOnlyMessage": "نمی‌توان در ویرایشگر فقط خواندنی ویرایش کرد",
"Filter": "فیلتر",
@@ -266,7 +255,6 @@
"Match Case": "تطبیق حروف کوچک و بزرگ",
"Match Whole Word": "تطبیق کل کلمه",
"Use Regular Expression": "استفاده از عبارت منظم",
"Profile Imported Successfully": "پروفایل با موفقیت وارد شد",
"Clash Config Updated": "پیکربندی Clash به‌روزرسانی شد",
"Profile Switched": "پروفایل تغییر یافت",

View File

@@ -1,20 +1,17 @@
{
"millis": "миллисекунды",
"mins": "минуты",
"Back": "Назад",
"Close": "Закрыть",
"Cancel": "Отмена",
"Confirm": "Подтвердить",
"Empty": "Пусто",
"New": "Новый",
"Edit": "Редактировать",
"Save": "Сохранить",
"Delete": "Удалить",
"Enable": "Включить",
"Disable": "Отключить",
"Label-Proxies": "Прокси",
"Label-Profiles": "Профили",
"Label-Connections": "Соединения",
@@ -22,7 +19,6 @@
"Label-Logs": "Логи",
"Label-Test": "Тест",
"Label-Settings": "Настройки",
"Proxies": "Прокси",
"Proxy Groups": "Группы прокси",
"Proxy Provider": "Провайдер прокси",
@@ -41,7 +37,6 @@
"Delay check to cancel fixed": "Проверка задержки для отмены фиксированного",
"Proxy basic": "Резюме о прокси",
"Proxy detail": "Подробности о прокси",
"Profiles": "Профили",
"Update All Profiles": "Обновить все профили",
"View Runtime Config": "Просмотреть используемый конфиг",
@@ -55,6 +50,8 @@
"Expire Time": "Время окончания",
"Create Profile": "Создать профиль",
"Edit Profile": "Изменить профиль",
"Edit Merge": "Изменить Merge.",
"Edit Script": "Изменить Script",
"Type": "Тип",
"Name": "Название",
"Descriptions": "Описания",
@@ -77,7 +74,6 @@
"Script Console": "Консоль скрипта",
"To Top": "Наверх",
"To End": "Вниз",
"Connections": "Соединения",
"Table View": "Tablichnyy vid",
"List View": "Spiskovyy vid",
@@ -97,21 +93,17 @@
"Source": "Исходный адрес",
"Destination IP": "IP-адрес назначения",
"Close Connection": "Закрыть соединение",
"Rules": "Правила",
"Rule Provider": "Провайдер правило",
"Logs": "Логи",
"Pause": "Пауза",
"Clear": "Очистить",
"Test": "Тест",
"Test All": "Тест Все",
"Create Test": "Создать тест",
"Edit Test": "Редактировать тест",
"Icon": "Икона",
"Test URL": "Тестовый URL",
"Settings": "Настройки",
"System Setting": "Настройки системы",
"Tun Mode": "Режим туннеля",
@@ -157,7 +149,6 @@
"Auto Launch": "Автозапуск",
"Silent Start": "Тихий запуск",
"Silent Start Info": "Запускать программу в фоновом режиме без отображения панели",
"Clash Setting": "Настройки Clash",
"Allow Lan": "Разрешить локальную сеть",
"IPv6": "IPv6",
@@ -186,7 +177,6 @@
"Open UWP tool": "Открыть UWP инструмент",
"Open UWP tool Info": "С Windows 8 приложения UWP (такие как Microsoft Store) ограничены в прямом доступе к сетевым службам локального хоста, и этот инструмент позволяет обойти это ограничение",
"Update GeoData": "Обновление GeoData",
"TG Channel": "Канал Telegram",
"Manual": "Документация",
"Github Repo": "GitHub репозиторий",
@@ -258,7 +248,6 @@
"Open Dev Tools": "Открыть инструменты разработчика",
"Exit": "Выход",
"Verge Version": "Версия Verge",
"ReadOnly": "Только для чтения",
"ReadOnlyMessage": "Невозможно редактировать в режиме только для чтения",
"Filter": "Фильтр",
@@ -266,7 +255,6 @@
"Match Case": "Учитывать регистр",
"Match Whole Word": "Полное совпадение слова",
"Use Regular Expression": "Использовать регулярные выражения",
"Profile Imported Successfully": "Профиль успешно импортирован",
"Clash Config Updated": "Clash конфигурация Обновлена",
"Profile Switched": "Профиль изменен",

View File

@@ -1,20 +1,17 @@
{
"millis": "毫秒",
"mins": "分钟",
"Back": "返回",
"Close": "关闭",
"Cancel": "取消",
"Confirm": "确认",
"Empty": "空空如也",
"New": "新建",
"Edit": "编辑",
"Save": "保存",
"Delete": "删除",
"Enable": "启用",
"Disable": "禁用",
"Label-Proxies": "代 理",
"Label-Profiles": "订 阅",
"Label-Connections": "连 接",
@@ -22,7 +19,6 @@
"Label-Logs": "日 志",
"Label-Test": "测 试",
"Label-Settings": "设 置",
"Proxies": "代理",
"Proxy Groups": "代理组",
"Proxy Provider": "代理集合",
@@ -41,7 +37,6 @@
"Delay check to cancel fixed": "进行延迟测试,以取消固定",
"Proxy basic": "隐藏节点细节",
"Proxy detail": "展示节点细节",
"Profiles": "订阅",
"Update All Profiles": "更新所有订阅",
"View Runtime Config": "查看运行时订阅",
@@ -55,6 +50,8 @@
"Expire Time": "到期时间",
"Create Profile": "新建配置",
"Edit Profile": "编辑配置",
"Edit Merge": "编辑 Merge",
"Edit Script": "编辑 Script",
"Type": "类型",
"Name": "名称",
"Descriptions": "描述",
@@ -77,7 +74,6 @@
"Script Console": "脚本控制台输出",
"To Top": "移到最前",
"To End": "移到末尾",
"Connections": "连接",
"Table View": "表格视图",
"List View": "列表视图",
@@ -97,21 +93,17 @@
"Source": "源地址",
"Destination IP": "目标地址",
"Close Connection": "关闭连接",
"Rules": "规则",
"Rule Provider": "规则集合",
"Logs": "日志",
"Pause": "暂停",
"Clear": "清除",
"Test": "测试",
"Test All": "测试全部",
"Create Test": "新建测试",
"Edit Test": "编辑测试",
"Icon": "图标",
"Test URL": "测试地址",
"Settings": "设置",
"System Setting": "系统设置",
"Tun Mode": "Tun 模式",
@@ -157,7 +149,6 @@
"Auto Launch": "开机自启",
"Silent Start": "静默启动",
"Silent Start Info": "程序启动时以后台模式运行,不显示程序面板",
"TG Channel": "Telegram 频道",
"Manual": "使用手册",
"Github Repo": "GitHub 项目地址",
@@ -189,7 +180,6 @@
"Open UWP tool": "UWP 工具",
"Open UWP tool Info": "Windows 8开始限制 UWP 应用(如微软商店)直接访问本地主机的网络服务,使用此工具可绕过该限制",
"Update GeoData": "更新 GeoData",
"Verge Setting": "Verge 设置",
"Language": "语言设置",
"Theme Mode": "主题模式",
@@ -258,7 +248,6 @@
"Open Dev Tools": "打开开发者工具",
"Exit": "退出",
"Verge Version": "Verge 版本",
"ReadOnly": "只读",
"ReadOnlyMessage": "无法在只读模式下编辑",
"Filter": "过滤节点",
@@ -266,7 +255,6 @@
"Match Case": "区分大小写",
"Match Whole Word": "全字匹配",
"Use Regular Expression": "使用正则表达式",
"Profile Imported Successfully": "导入订阅成功",
"Clash Config Updated": "Clash 配置已更新",
"Profile Switched": "订阅已切换",

View File

@@ -42,7 +42,6 @@ import {
ProfileViewerRef,
} from "@/components/profile/profile-viewer";
import { ProfileItem } from "@/components/profile/profile-item";
import { ProfileMore } from "@/components/profile/profile-more";
import { useProfiles } from "@/hooks/use-profiles";
import { ConfigViewer } from "@/components/setting/mods/config-viewer";
import { throttle } from "lodash-es";
@@ -105,31 +104,22 @@ const ProfilePage = () => {
getRuntimeLogs
);
const chain = profiles.chain || [];
const viewerRef = useRef<ProfileViewerRef>(null);
const configRef = useRef<DialogRef>(null);
// distinguish type
const { regularItems, enhanceItems } = useMemo(() => {
const profileItems = useMemo(() => {
const items = profiles.items || [];
const chain = profiles.chain || [];
const type1 = ["local", "remote"];
const type2 = ["merge", "script"];
const regularItems = items.filter((i) => i && type1.includes(i.type!));
const restItems = items.filter((i) => i && type2.includes(i.type!));
const restMap = Object.fromEntries(restItems.map((i) => [i.uid, i]));
const enhanceItems = chain
.map((i) => restMap[i]!)
.filter(Boolean)
.concat(restItems.filter((i) => !chain.includes(i.uid)));
const profileItems = items.filter((i) => i && type1.includes(i.type!));
return { regularItems, enhanceItems };
return profileItems;
}, [profiles]);
const currentActivatings = () => {
return [...new Set([profiles.current ?? "", ...chain])].filter(Boolean);
return [...new Set([profiles.current ?? ""])].filter(Boolean);
};
const onImport = async () => {
@@ -205,38 +195,9 @@ const ProfilePage = () => {
}
});
const onEnable = useLockFn(async (uid: string) => {
if (chain.includes(uid)) return;
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;
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();
@@ -249,20 +210,6 @@ const ProfilePage = () => {
}
});
const onMoveTop = useLockFn(async (uid: string) => {
if (!chain.includes(uid)) return;
const newChain = [uid].concat(chain.filter((i) => i !== uid));
await patchProfiles({ chain: newChain });
mutateLogs();
});
const onMoveEnd = useLockFn(async (uid: string) => {
if (!chain.includes(uid)) return;
const newChain = chain.filter((i) => i !== uid).concat([uid]);
await patchProfiles({ chain: newChain });
mutateLogs();
});
// 更新所有订阅
const setLoadingCache = useSetLoadingCache();
const onUpdateAll = useLockFn(async () => {
@@ -281,7 +228,7 @@ const ProfilePage = () => {
return new Promise((resolve) => {
setLoadingCache((cache) => {
// 获取没有正在更新的订阅
const items = regularItems.filter(
const items = profileItems.filter(
(e) => e.type === "remote" && !cache[e.uid]
);
const change = Object.fromEntries(items.map((e) => [e.uid, true]));
@@ -296,11 +243,6 @@ const ProfilePage = () => {
const text = await readText();
if (text) setUrl(text);
};
const mode = useThemeMode();
const islight = mode === "light" ? true : false;
const dividercolor = islight
? "rgba(0, 0, 0, 0.06)"
: "rgba(255, 255, 255, 0.06)";
return (
<BasePage
@@ -415,11 +357,11 @@ const ProfilePage = () => {
<Box sx={{ mb: 1.5 }}>
<Grid container spacing={{ xs: 1, lg: 1 }}>
<SortableContext
items={regularItems.map((x) => {
items={profileItems.map((x) => {
return x.uid;
})}
>
{regularItems.map((item) => (
{profileItems.map((item) => (
<Grid item xs={12} sm={6} md={4} lg={3} key={item.file}>
<ProfileItem
id={item.uid}
@@ -441,43 +383,6 @@ const ProfilePage = () => {
</Grid>
</Box>
</DndContext>
{enhanceItems.length > 0 && (
<Divider
variant="middle"
flexItem
sx={{ width: `calc(100% - 32px)`, borderColor: dividercolor }}
></Divider>
)}
{enhanceItems.length > 0 && (
<Box sx={{ mt: 1.5 }}>
<Grid container spacing={{ xs: 1, lg: 1 }}>
{enhanceItems.map((item) => (
<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]}
onEnable={() => onEnable(item.uid)}
onDisable={() => onDisable(item.uid)}
onDelete={() => onDelete(item.uid)}
onMoveTop={() => onMoveTop(item.uid)}
onMoveEnd={() => onMoveEnd(item.uid)}
onEdit={() => viewerRef.current?.edit(item)}
onChange={async (prev, curr) => {
if (prev !== curr && chain.includes(item.uid)) {
await onEnhance();
}
}}
/>
</Grid>
))}
</Grid>
</Box>
)}
</Box>
<ProfileViewer ref={viewerRef} onChange={() => mutateProfiles()} />
<ConfigViewer ref={configRef} />

View File

@@ -178,11 +178,15 @@ interface IProfileOption {
self_proxy?: boolean;
update_interval?: number;
danger_accept_invalid_certs?: boolean;
merge?: string;
script?: string;
rules?: string;
proxies?: string;
groups?: string;
}
interface IProfilesConfig {
current?: string;
chain?: string[];
valid?: string[];
items?: IProfileItem[];
}
@@ -254,75 +258,3 @@ interface IVergeConfig {
proxy_layout_column?: number;
test_list?: IVergeTestItem[];
}
type IClashConfigValue = any;
interface IProfileMerge {
// clash config fields (default supports)
rules?: IClashConfigValue;
proxies?: IClashConfigValue;
"proxy-groups"?: IClashConfigValue;
"proxy-providers"?: IClashConfigValue;
"rule-providers"?: IClashConfigValue;
// clash config fields (use flag)
tun?: IClashConfigValue;
dns?: IClashConfigValue;
hosts?: IClashConfigValue;
script?: IClashConfigValue;
profile?: IClashConfigValue;
payload?: IClashConfigValue;
"interface-name"?: IClashConfigValue;
"routing-mark"?: IClashConfigValue;
// functional fields
use?: string[];
"prepend-rules"?: any[];
"append-rules"?: any[];
"prepend-proxies"?: any[];
"append-proxies"?: any[];
"prepend-proxy-groups"?: any[];
"append-proxy-groups"?: any[];
// fix
ebpf?: any;
experimental?: any;
iptables?: any;
sniffer?: any;
authentication?: any;
"bind-address"?: any;
"external-ui"?: any;
"auto-redir"?: any;
"socks-port"?: any;
"redir-port"?: any;
"tproxy-port"?: any;
"geodata-mode"?: any;
"tcp-concurrent"?: any;
}
// partial of the clash config
type IProfileData = Partial<{
rules: any[];
proxies: any[];
"proxy-groups": any[];
"proxy-providers": any[];
"rule-providers": any[];
[k: string]: any;
}>;
interface IChainItem {
item: IProfileItem;
merge?: IProfileMerge;
script?: string;
}
interface IEnhancedPayload {
chain: IChainItem[];
valid: string[];
current: IProfileData;
callback: string;
}
interface IEnhancedResult {
data: IProfileData;
status: string;
error?: string;
}