mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2024-11-16 03:32:36 +08:00
refactor: editor-viewer using react-monaco-editor
This commit is contained in:
parent
0021fc24bb
commit
f69e1d2a0c
|
@ -9,176 +9,184 @@ import {
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useThemeMode } from "@/services/states";
|
import { useThemeMode } from "@/services/states";
|
||||||
import { readProfileFile, saveProfileFile } from "@/services/cmds";
|
|
||||||
import { Notice } from "@/components/base";
|
import { Notice } from "@/components/base";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import getSystem from "@/utils/get-system";
|
import getSystem from "@/utils/get-system";
|
||||||
|
|
||||||
import * as monaco from "monaco-editor";
|
import * as monaco from "monaco-editor";
|
||||||
import { editor } from "monaco-editor/esm/vs/editor/editor.api";
|
import MonacoEditor from "react-monaco-editor";
|
||||||
import { configureMonacoYaml } from "monaco-yaml";
|
import { configureMonacoYaml } from "monaco-yaml";
|
||||||
|
|
||||||
import { type JSONSchema7 } from "json-schema";
|
import { type JSONSchema7 } from "json-schema";
|
||||||
import metaSchema from "meta-json-schema/schemas/meta-json-schema.json";
|
import metaSchema from "meta-json-schema/schemas/meta-json-schema.json";
|
||||||
import mergeSchema from "meta-json-schema/schemas/clash-verge-merge-json-schema.json";
|
import mergeSchema from "meta-json-schema/schemas/clash-verge-merge-json-schema.json";
|
||||||
import pac from "types-pac/pac.d.ts?raw";
|
import pac from "types-pac/pac.d.ts?raw";
|
||||||
|
|
||||||
interface Props {
|
type Language = "yaml" | "javascript" | "css";
|
||||||
title?: string | ReactNode;
|
type Schema<T extends Language> = LanguageSchemaMap[T];
|
||||||
mode: "profile" | "text";
|
interface LanguageSchemaMap {
|
||||||
property: string;
|
yaml: "clash" | "merge";
|
||||||
open: boolean;
|
javascript: never;
|
||||||
readOnly?: boolean;
|
css: never;
|
||||||
language: "yaml" | "javascript" | "css";
|
|
||||||
schema?: "clash" | "merge";
|
|
||||||
onClose: () => void;
|
|
||||||
onChange?: (prev?: string, curr?: string) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// yaml worker
|
interface Props<T extends Language> {
|
||||||
configureMonacoYaml(monaco, {
|
open: boolean;
|
||||||
validate: true,
|
title?: string | ReactNode;
|
||||||
enableSchemaRequest: true,
|
initialData: Promise<string>;
|
||||||
schemas: [
|
readOnly?: boolean;
|
||||||
{
|
language: T;
|
||||||
uri: "http://example.com/meta-json-schema.json",
|
schema?: Schema<T>;
|
||||||
fileMatch: ["**/*.clash.yaml"],
|
onChange?: (prev?: string, curr?: string) => void;
|
||||||
//@ts-ignore
|
onSave?: (prev?: string, curr?: string) => void;
|
||||||
schema: metaSchema as JSONSchema7,
|
onClose: () => void;
|
||||||
},
|
}
|
||||||
{
|
|
||||||
uri: "http://example.com/clash-verge-merge-json-schema.json",
|
let initialized = false;
|
||||||
fileMatch: ["**/*.merge.yaml"],
|
const monacoInitialization = () => {
|
||||||
//@ts-ignore
|
if (initialized) return;
|
||||||
schema: mergeSchema as JSONSchema7,
|
|
||||||
},
|
// configure yaml worker
|
||||||
],
|
configureMonacoYaml(monaco, {
|
||||||
});
|
validate: true,
|
||||||
// PAC definition
|
enableSchemaRequest: true,
|
||||||
monaco.languages.typescript.javascriptDefaults.addExtraLib(pac, "pac.d.ts");
|
schemas: [
|
||||||
monaco.languages.registerCompletionItemProvider("javascript", {
|
|
||||||
provideCompletionItems: (model, position) => ({
|
|
||||||
suggestions: [
|
|
||||||
{
|
{
|
||||||
label: "%mixed-port%",
|
uri: "http://example.com/meta-json-schema.json",
|
||||||
kind: monaco.languages.CompletionItemKind.Text,
|
fileMatch: ["**/*.clash.yaml"],
|
||||||
insertText: "%mixed-port%",
|
// @ts-ignore
|
||||||
range: {
|
schema: metaSchema as JSONSchema7,
|
||||||
startLineNumber: position.lineNumber,
|
},
|
||||||
endLineNumber: position.lineNumber,
|
{
|
||||||
startColumn: model.getWordUntilPosition(position).startColumn - 1,
|
uri: "http://example.com/clash-verge-merge-json-schema.json",
|
||||||
endColumn: model.getWordUntilPosition(position).endColumn - 1,
|
fileMatch: ["**/*.merge.yaml"],
|
||||||
},
|
// @ts-ignore
|
||||||
|
schema: mergeSchema as JSONSchema7,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
});
|
||||||
});
|
// configure PAC definition
|
||||||
|
monaco.languages.typescript.javascriptDefaults.addExtraLib(pac, "pac.d.ts");
|
||||||
|
|
||||||
export const EditorViewer = (props: Props) => {
|
initialized = true;
|
||||||
const {
|
};
|
||||||
title,
|
|
||||||
mode,
|
export const EditorViewer = <T extends Language>(props: Props<T>) => {
|
||||||
property,
|
|
||||||
open,
|
|
||||||
readOnly,
|
|
||||||
language,
|
|
||||||
schema,
|
|
||||||
onClose,
|
|
||||||
onChange,
|
|
||||||
} = props;
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const editorRef = useRef<any>();
|
|
||||||
const instanceRef = useRef<editor.IStandaloneCodeEditor | null>(null);
|
|
||||||
const themeMode = useThemeMode();
|
const themeMode = useThemeMode();
|
||||||
const prevData = useRef<string>();
|
|
||||||
|
|
||||||
useEffect(() => {
|
const {
|
||||||
if (!open) return;
|
open = false,
|
||||||
|
title = t("Edit File"),
|
||||||
|
initialData = Promise.resolve(""),
|
||||||
|
readOnly = false,
|
||||||
|
language = "yaml",
|
||||||
|
schema,
|
||||||
|
onChange,
|
||||||
|
onSave,
|
||||||
|
onClose,
|
||||||
|
} = props;
|
||||||
|
|
||||||
let fetchContent;
|
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>();
|
||||||
switch (mode) {
|
const prevData = useRef<string | undefined>("");
|
||||||
case "profile": // profile文件
|
const currData = useRef<string | undefined>("");
|
||||||
fetchContent = readProfileFile(property);
|
|
||||||
break;
|
|
||||||
case "text": // 文本内容
|
|
||||||
fetchContent = Promise.resolve(property);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
fetchContent.then((data) => {
|
|
||||||
const dom = editorRef.current;
|
|
||||||
|
|
||||||
if (!dom) return;
|
const editorWillMount = () => {
|
||||||
|
monacoInitialization(); // initialize monaco
|
||||||
|
};
|
||||||
|
|
||||||
if (instanceRef.current) instanceRef.current.dispose();
|
const editorDidMount = async (
|
||||||
|
editor: monaco.editor.IStandaloneCodeEditor
|
||||||
|
) => {
|
||||||
|
editorRef.current = editor;
|
||||||
|
|
||||||
|
// retrieve initial data
|
||||||
|
await initialData.then((data) => {
|
||||||
|
prevData.current = data;
|
||||||
|
currData.current = data;
|
||||||
|
|
||||||
|
// create and set model
|
||||||
const uri = monaco.Uri.parse(`${nanoid()}.${schema}.${language}`);
|
const uri = monaco.Uri.parse(`${nanoid()}.${schema}.${language}`);
|
||||||
const model = monaco.editor.createModel(data, language, uri);
|
const model = monaco.editor.createModel(data, language, uri);
|
||||||
instanceRef.current = editor.create(editorRef.current, {
|
editorRef.current?.setModel(model);
|
||||||
model: model,
|
|
||||||
language: language,
|
|
||||||
tabSize: ["yaml", "javascript", "css"].includes(language) ? 2 : 4, // 根据语言类型设置缩进大小
|
|
||||||
theme: themeMode === "light" ? "vs" : "vs-dark",
|
|
||||||
minimap: { enabled: dom.clientWidth >= 1000 }, // 超过一定宽度显示minimap滚动条
|
|
||||||
mouseWheelZoom: true, // 按住Ctrl滚轮调节缩放比例
|
|
||||||
readOnly: readOnly, // 只读模式
|
|
||||||
readOnlyMessage: { value: t("ReadOnlyMessage") }, // 只读模式尝试编辑时的提示信息
|
|
||||||
renderValidationDecorations: "on", // 只读模式下显示校验信息
|
|
||||||
quickSuggestions: {
|
|
||||||
strings: true, // 字符串类型的建议
|
|
||||||
comments: true, // 注释类型的建议
|
|
||||||
other: true, // 其他类型的建议
|
|
||||||
},
|
|
||||||
padding: {
|
|
||||||
top: 33, // 顶部padding防止遮挡snippets
|
|
||||||
},
|
|
||||||
fontFamily: `Fira Code, JetBrains Mono, Roboto Mono, "Source Code Pro", Consolas, Menlo, Monaco, monospace, "Courier New", "Apple Color Emoji"${
|
|
||||||
getSystem() === "windows" ? ", twemoji mozilla" : ""
|
|
||||||
}`,
|
|
||||||
fontLigatures: true, // 连字符
|
|
||||||
smoothScrolling: true, // 平滑滚动
|
|
||||||
});
|
|
||||||
|
|
||||||
prevData.current = data;
|
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return () => {
|
const handleChange = useLockFn(async (value: string | undefined) => {
|
||||||
if (instanceRef.current) {
|
|
||||||
instanceRef.current.dispose();
|
|
||||||
instanceRef.current = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [open]);
|
|
||||||
|
|
||||||
const onSave = useLockFn(async () => {
|
|
||||||
const currData = instanceRef.current?.getValue();
|
|
||||||
|
|
||||||
if (currData == null) return;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (mode === "profile") {
|
currData.current = value;
|
||||||
await saveProfileFile(property, currData);
|
onChange?.(prevData.current, currData.current);
|
||||||
}
|
} catch (err: any) {
|
||||||
onChange?.(prevData.current, currData);
|
Notice.error(err.message || err.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSave = useLockFn(async () => {
|
||||||
|
try {
|
||||||
|
!readOnly && onSave?.(prevData.current, currData.current);
|
||||||
onClose();
|
onClose();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err.message || err.toString());
|
Notice.error(err.message || err.toString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleClose = useLockFn(async () => {
|
||||||
|
try {
|
||||||
|
onClose();
|
||||||
|
} catch (err: any) {
|
||||||
|
Notice.error(err.message || err.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
editorRef.current?.dispose();
|
||||||
|
editorRef.current = undefined;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onClose={onClose} maxWidth="xl" fullWidth>
|
<Dialog open={open} onClose={onClose} maxWidth="xl" fullWidth>
|
||||||
<DialogTitle>{title ?? t("Edit File")}</DialogTitle>
|
<DialogTitle>{title}</DialogTitle>
|
||||||
|
|
||||||
<DialogContent sx={{ width: "auto", height: "100vh" }}>
|
<DialogContent sx={{ width: "auto", height: "100vh" }}>
|
||||||
<div style={{ width: "100%", height: "100%" }} ref={editorRef} />
|
<MonacoEditor
|
||||||
|
language={language}
|
||||||
|
theme={themeMode === "light" ? "vs" : "vs-dark"}
|
||||||
|
options={{
|
||||||
|
tabSize: ["yaml", "javascript", "css"].includes(language) ? 2 : 4, // 根据语言类型设置缩进大小
|
||||||
|
minimap: {
|
||||||
|
enabled: document.documentElement.clientWidth >= 1500, // 超过一定宽度显示minimap滚动条
|
||||||
|
},
|
||||||
|
mouseWheelZoom: true, // 按住Ctrl滚轮调节缩放比例
|
||||||
|
readOnly: readOnly, // 只读模式
|
||||||
|
readOnlyMessage: { value: t("ReadOnlyMessage") }, // 只读模式尝试编辑时的提示信息
|
||||||
|
renderValidationDecorations: "on", // 只读模式下显示校验信息
|
||||||
|
quickSuggestions: {
|
||||||
|
strings: true, // 字符串类型的建议
|
||||||
|
comments: true, // 注释类型的建议
|
||||||
|
other: true, // 其他类型的建议
|
||||||
|
},
|
||||||
|
padding: {
|
||||||
|
top: 33, // 顶部padding防止遮挡snippets
|
||||||
|
},
|
||||||
|
fontFamily: `Fira Code, JetBrains Mono, Roboto Mono, "Source Code Pro", Consolas, Menlo, Monaco, monospace, "Courier New", "Apple Color Emoji"${
|
||||||
|
getSystem() === "windows" ? ", twemoji mozilla" : ""
|
||||||
|
}`,
|
||||||
|
fontLigatures: true, // 连字符
|
||||||
|
smoothScrolling: true, // 平滑滚动
|
||||||
|
}}
|
||||||
|
editorWillMount={editorWillMount}
|
||||||
|
editorDidMount={editorDidMount}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={onClose} variant="outlined">
|
<Button onClick={handleClose} variant="outlined">
|
||||||
{t(readOnly ? "Close" : "Cancel")}
|
{t(readOnly ? "Close" : "Cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
{!readOnly && (
|
{!readOnly && (
|
||||||
<Button onClick={onSave} variant="contained">
|
<Button onClick={handleSave} variant="contained">
|
||||||
{t("Save")}
|
{t("Save")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -17,7 +17,12 @@ 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, viewProfile } from "@/services/cmds";
|
import {
|
||||||
|
viewProfile,
|
||||||
|
readProfileFile,
|
||||||
|
updateProfile,
|
||||||
|
saveProfileFile,
|
||||||
|
} from "@/services/cmds";
|
||||||
import { Notice } from "@/components/base";
|
import { Notice } from "@/components/base";
|
||||||
import { RulesEditorViewer } from "@/components/profile/rules-editor-viewer";
|
import { RulesEditorViewer } from "@/components/profile/rules-editor-viewer";
|
||||||
import { EditorViewer } from "@/components/profile/editor-viewer";
|
import { EditorViewer } from "@/components/profile/editor-viewer";
|
||||||
|
@ -37,20 +42,13 @@ interface Props {
|
||||||
itemData: IProfileItem;
|
itemData: IProfileItem;
|
||||||
onSelect: (force: boolean) => void;
|
onSelect: (force: boolean) => void;
|
||||||
onEdit: () => void;
|
onEdit: () => void;
|
||||||
onChange?: (prev?: string, curr?: string) => void;
|
onSave?: (prev?: string, curr?: string) => void;
|
||||||
onDelete: () => void;
|
onDelete: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProfileItem = (props: Props) => {
|
export const ProfileItem = (props: Props) => {
|
||||||
const {
|
const { selected, activating, itemData, onSelect, onEdit, onSave, onDelete } =
|
||||||
selected,
|
props;
|
||||||
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 });
|
||||||
|
|
||||||
|
@ -474,52 +472,62 @@ export const ProfileItem = (props: Props) => {
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
||||||
<EditorViewer
|
<EditorViewer
|
||||||
mode="profile"
|
|
||||||
property={uid}
|
|
||||||
open={fileOpen}
|
open={fileOpen}
|
||||||
|
initialData={readProfileFile(uid)}
|
||||||
language="yaml"
|
language="yaml"
|
||||||
schema="clash"
|
schema="clash"
|
||||||
onChange={onChange}
|
onSave={async (prev, curr) => {
|
||||||
|
await saveProfileFile(uid, curr ?? "");
|
||||||
|
onSave && onSave(prev, curr);
|
||||||
|
}}
|
||||||
onClose={() => setFileOpen(false)}
|
onClose={() => setFileOpen(false)}
|
||||||
/>
|
/>
|
||||||
<RulesEditorViewer
|
<RulesEditorViewer
|
||||||
profileUid={uid}
|
profileUid={uid}
|
||||||
property={option?.rules ?? ""}
|
property={option?.rules ?? ""}
|
||||||
open={rulesOpen}
|
open={rulesOpen}
|
||||||
onChange={onChange}
|
onSave={onSave}
|
||||||
onClose={() => setRulesOpen(false)}
|
onClose={() => setRulesOpen(false)}
|
||||||
/>
|
/>
|
||||||
<EditorViewer
|
<EditorViewer
|
||||||
mode="profile"
|
|
||||||
property={option?.proxies ?? ""}
|
|
||||||
open={proxiesOpen}
|
open={proxiesOpen}
|
||||||
|
initialData={readProfileFile(option?.proxies ?? "")}
|
||||||
language="yaml"
|
language="yaml"
|
||||||
onChange={onChange}
|
onSave={async (prev, curr) => {
|
||||||
|
await saveProfileFile(option?.proxies ?? "", curr ?? "");
|
||||||
|
onSave && onSave(prev, curr);
|
||||||
|
}}
|
||||||
onClose={() => setProxiesOpen(false)}
|
onClose={() => setProxiesOpen(false)}
|
||||||
/>
|
/>
|
||||||
<EditorViewer
|
<EditorViewer
|
||||||
mode="profile"
|
|
||||||
property={option?.groups ?? ""}
|
|
||||||
open={groupsOpen}
|
open={groupsOpen}
|
||||||
|
initialData={readProfileFile(option?.proxies ?? "")}
|
||||||
language="yaml"
|
language="yaml"
|
||||||
onChange={onChange}
|
onSave={async (prev, curr) => {
|
||||||
|
await saveProfileFile(option?.proxies ?? "", curr ?? "");
|
||||||
|
onSave && onSave(prev, curr);
|
||||||
|
}}
|
||||||
onClose={() => setGroupsOpen(false)}
|
onClose={() => setGroupsOpen(false)}
|
||||||
/>
|
/>
|
||||||
<EditorViewer
|
<EditorViewer
|
||||||
mode="profile"
|
|
||||||
property={option?.merge ?? ""}
|
|
||||||
open={mergeOpen}
|
open={mergeOpen}
|
||||||
|
initialData={readProfileFile(option?.merge ?? "")}
|
||||||
language="yaml"
|
language="yaml"
|
||||||
schema="clash"
|
schema="clash"
|
||||||
onChange={onChange}
|
onSave={async (prev, curr) => {
|
||||||
|
await saveProfileFile(option?.merge ?? "", curr ?? "");
|
||||||
|
onSave && onSave(prev, curr);
|
||||||
|
}}
|
||||||
onClose={() => setMergeOpen(false)}
|
onClose={() => setMergeOpen(false)}
|
||||||
/>
|
/>
|
||||||
<EditorViewer
|
<EditorViewer
|
||||||
mode="profile"
|
|
||||||
property={option?.script ?? ""}
|
|
||||||
open={scriptOpen}
|
open={scriptOpen}
|
||||||
|
initialData={readProfileFile(option?.script ?? "")}
|
||||||
language="javascript"
|
language="javascript"
|
||||||
onChange={onChange}
|
onSave={async (prev, curr) => {
|
||||||
|
await saveProfileFile(option?.script ?? "", curr ?? "");
|
||||||
|
onSave && onSave(prev, curr);
|
||||||
|
}}
|
||||||
onClose={() => setScriptOpen(false)}
|
onClose={() => setScriptOpen(false)}
|
||||||
/>
|
/>
|
||||||
<ConfirmViewer
|
<ConfirmViewer
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
IconButton,
|
IconButton,
|
||||||
} 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, readProfileFile, saveProfileFile } 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";
|
||||||
|
@ -20,14 +20,14 @@ import { LogViewer } from "./log-viewer";
|
||||||
interface Props {
|
interface Props {
|
||||||
logInfo?: [string, string][];
|
logInfo?: [string, string][];
|
||||||
id: "Merge" | "Script";
|
id: "Merge" | "Script";
|
||||||
onChange?: (prev?: string, curr?: string) => void;
|
onSave?: (prev?: string, curr?: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// profile enhanced item
|
// profile enhanced item
|
||||||
export const ProfileMore = (props: Props) => {
|
export const ProfileMore = (props: Props) => {
|
||||||
const { id, logInfo = [], onChange } = props;
|
const { id, logInfo = [], onSave } = props;
|
||||||
|
|
||||||
const { t, i18n } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [anchorEl, setAnchorEl] = useState<any>(null);
|
const [anchorEl, setAnchorEl] = useState<any>(null);
|
||||||
const [position, setPosition] = useState({ left: 0, top: 0 });
|
const [position, setPosition] = useState({ left: 0, top: 0 });
|
||||||
const [fileOpen, setFileOpen] = useState(false);
|
const [fileOpen, setFileOpen] = useState(false);
|
||||||
|
@ -169,12 +169,15 @@ export const ProfileMore = (props: Props) => {
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
||||||
<EditorViewer
|
<EditorViewer
|
||||||
mode="profile"
|
|
||||||
property={id}
|
|
||||||
open={fileOpen}
|
open={fileOpen}
|
||||||
|
title={`${t("Global " + id)}`}
|
||||||
|
initialData={readProfileFile(id)}
|
||||||
language={id === "Merge" ? "yaml" : "javascript"}
|
language={id === "Merge" ? "yaml" : "javascript"}
|
||||||
schema={id === "Merge" ? "clash" : undefined}
|
schema={id === "Merge" ? "clash" : undefined}
|
||||||
onChange={onChange}
|
onSave={async (prev, curr) => {
|
||||||
|
await saveProfileFile(id, curr ?? "");
|
||||||
|
onSave && onSave(prev, curr);
|
||||||
|
}}
|
||||||
onClose={() => setFileOpen(false)}
|
onClose={() => setFileOpen(false)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ interface Props {
|
||||||
property: string;
|
property: string;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onChange?: (prev?: string, curr?: string) => void;
|
onSave?: (prev?: string, curr?: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const portValidator = (value: string): boolean => {
|
const portValidator = (value: string): boolean => {
|
||||||
|
@ -227,7 +227,7 @@ const rules: {
|
||||||
const builtinProxyPolicies = ["DIRECT", "REJECT", "REJECT-DROP", "PASS"];
|
const builtinProxyPolicies = ["DIRECT", "REJECT", "REJECT-DROP", "PASS"];
|
||||||
|
|
||||||
export const RulesEditorViewer = (props: Props) => {
|
export const RulesEditorViewer = (props: Props) => {
|
||||||
const { title, profileUid, property, open, onClose, onChange } = props;
|
const { title, profileUid, property, open, onClose, onSave } = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [prevData, setPrevData] = useState("");
|
const [prevData, setPrevData] = useState("");
|
||||||
|
@ -330,7 +330,7 @@ export const RulesEditorViewer = (props: Props) => {
|
||||||
},${proxyPolicy}${ruleType.noResolve && noResolve ? ",no-resolve" : ""}`;
|
},${proxyPolicy}${ruleType.noResolve && noResolve ? ",no-resolve" : ""}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSave = useLockFn(async () => {
|
const handleSave = useLockFn(async () => {
|
||||||
try {
|
try {
|
||||||
let currData = yaml.dump({
|
let currData = yaml.dump({
|
||||||
prepend: prependSeq,
|
prepend: prependSeq,
|
||||||
|
@ -338,7 +338,7 @@ export const RulesEditorViewer = (props: Props) => {
|
||||||
delete: deleteSeq,
|
delete: deleteSeq,
|
||||||
});
|
});
|
||||||
await saveProfileFile(property, currData);
|
await saveProfileFile(property, currData);
|
||||||
onChange?.(prevData, currData);
|
onSave?.(prevData, currData);
|
||||||
onClose();
|
onClose();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err.message || err.toString());
|
Notice.error(err.message || err.toString());
|
||||||
|
@ -575,7 +575,7 @@ export const RulesEditorViewer = (props: Props) => {
|
||||||
{t("Cancel")}
|
{t("Cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button onClick={onSave} variant="contained">
|
<Button onClick={handleSave} variant="contained">
|
||||||
{t("Save")}
|
{t("Save")}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
|
|
|
@ -22,15 +22,14 @@ export const ConfigViewer = forwardRef<DialogRef>((_, ref) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EditorViewer
|
<EditorViewer
|
||||||
|
open={open}
|
||||||
title={
|
title={
|
||||||
<Box>
|
<Box>
|
||||||
{t("Runtime Config")}
|
{t("Runtime Config")}
|
||||||
<Chip label={t("ReadOnly")} size="small" />
|
<Chip label={t("ReadOnly")} size="small" />
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
mode="text"
|
initialData={Promise.resolve(runtimeConfig)}
|
||||||
property={runtimeConfig}
|
|
||||||
open={open}
|
|
||||||
readOnly
|
readOnly
|
||||||
language="yaml"
|
language="yaml"
|
||||||
schema="clash"
|
schema="clash"
|
||||||
|
|
|
@ -262,21 +262,18 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
||||||
{t("Edit")} PAC
|
{t("Edit")} PAC
|
||||||
</Button>
|
</Button>
|
||||||
<EditorViewer
|
<EditorViewer
|
||||||
title={`${t("Edit")} PAC`}
|
|
||||||
mode="text"
|
|
||||||
property={value.pac_content ?? ""}
|
|
||||||
open={editorOpen}
|
open={editorOpen}
|
||||||
|
title={`${t("Edit")} PAC`}
|
||||||
|
initialData={Promise.resolve(value.pac_content ?? "")}
|
||||||
language="javascript"
|
language="javascript"
|
||||||
onChange={(_prev, curr) => {
|
onSave={(_prev, curr) => {
|
||||||
let pac = DEFAULT_PAC;
|
let pac = DEFAULT_PAC;
|
||||||
if (curr && curr.trim().length > 0) {
|
if (curr && curr.trim().length > 0) {
|
||||||
pac = curr;
|
pac = curr;
|
||||||
}
|
}
|
||||||
setValue((v) => ({ ...v, pac_content: pac }));
|
setValue((v) => ({ ...v, pac_content: pac }));
|
||||||
}}
|
}}
|
||||||
onClose={() => {
|
onClose={() => setEditorOpen(false)}
|
||||||
setEditorOpen(false);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -124,12 +124,11 @@ export const ThemeViewer = forwardRef<DialogRef>((props, ref) => {
|
||||||
{t("Edit")} CSS
|
{t("Edit")} CSS
|
||||||
</Button>
|
</Button>
|
||||||
<EditorViewer
|
<EditorViewer
|
||||||
title={`${t("Edit")} CSS`}
|
|
||||||
mode="text"
|
|
||||||
property={theme.css_injection ?? ""}
|
|
||||||
open={editorOpen}
|
open={editorOpen}
|
||||||
|
title={`${t("Edit")} CSS`}
|
||||||
|
initialData={Promise.resolve(theme.css_injection ?? "")}
|
||||||
language="css"
|
language="css"
|
||||||
onChange={(_prev, curr) => {
|
onSave={(_prev, curr) => {
|
||||||
theme.css_injection = curr;
|
theme.css_injection = curr;
|
||||||
handleChange("css_injection");
|
handleChange("css_injection");
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -50,7 +50,6 @@ import { BaseStyledTextField } from "@/components/base/base-styled-text-field";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { readTextFile } from "@tauri-apps/api/fs";
|
import { readTextFile } from "@tauri-apps/api/fs";
|
||||||
import { readText } from "@tauri-apps/api/clipboard";
|
import { readText } from "@tauri-apps/api/clipboard";
|
||||||
import { EditorViewer } from "@/components/profile/editor-viewer";
|
|
||||||
|
|
||||||
const ProfilePage = () => {
|
const ProfilePage = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
@ -378,7 +377,7 @@ const ProfilePage = () => {
|
||||||
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) => {
|
onSave={async (prev, curr) => {
|
||||||
if (prev !== curr && profiles.current === item.uid) {
|
if (prev !== curr && profiles.current === item.uid) {
|
||||||
await onEnhance();
|
await onEnhance();
|
||||||
}
|
}
|
||||||
|
@ -401,7 +400,7 @@ const ProfilePage = () => {
|
||||||
<Grid item xs={12} sm={6} md={6} lg={6}>
|
<Grid item xs={12} sm={6} md={6} lg={6}>
|
||||||
<ProfileMore
|
<ProfileMore
|
||||||
id="Merge"
|
id="Merge"
|
||||||
onChange={async (prev, curr) => {
|
onSave={async (prev, curr) => {
|
||||||
if (prev !== curr) {
|
if (prev !== curr) {
|
||||||
await onEnhance();
|
await onEnhance();
|
||||||
}
|
}
|
||||||
|
@ -412,7 +411,7 @@ const ProfilePage = () => {
|
||||||
<ProfileMore
|
<ProfileMore
|
||||||
id="Script"
|
id="Script"
|
||||||
logInfo={chainLogs["Script"]}
|
logInfo={chainLogs["Script"]}
|
||||||
onChange={async (prev, curr) => {
|
onSave={async (prev, curr) => {
|
||||||
if (prev !== curr) {
|
if (prev !== curr) {
|
||||||
await onEnhance();
|
await onEnhance();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user