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,
|
||||
} from "@mui/material";
|
||||
import { useThemeMode } from "@/services/states";
|
||||
import { readProfileFile, saveProfileFile } from "@/services/cmds";
|
||||
import { Notice } from "@/components/base";
|
||||
import { nanoid } from "nanoid";
|
||||
import getSystem from "@/utils/get-system";
|
||||
|
||||
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 { type JSONSchema7 } from "json-schema";
|
||||
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 pac from "types-pac/pac.d.ts?raw";
|
||||
|
||||
interface Props {
|
||||
title?: string | ReactNode;
|
||||
mode: "profile" | "text";
|
||||
property: string;
|
||||
open: boolean;
|
||||
readOnly?: boolean;
|
||||
language: "yaml" | "javascript" | "css";
|
||||
schema?: "clash" | "merge";
|
||||
onClose: () => void;
|
||||
onChange?: (prev?: string, curr?: string) => void;
|
||||
type Language = "yaml" | "javascript" | "css";
|
||||
type Schema<T extends Language> = LanguageSchemaMap[T];
|
||||
interface LanguageSchemaMap {
|
||||
yaml: "clash" | "merge";
|
||||
javascript: never;
|
||||
css: never;
|
||||
}
|
||||
|
||||
// yaml worker
|
||||
configureMonacoYaml(monaco, {
|
||||
validate: true,
|
||||
enableSchemaRequest: true,
|
||||
schemas: [
|
||||
{
|
||||
uri: "http://example.com/meta-json-schema.json",
|
||||
fileMatch: ["**/*.clash.yaml"],
|
||||
//@ts-ignore
|
||||
schema: metaSchema as JSONSchema7,
|
||||
},
|
||||
{
|
||||
uri: "http://example.com/clash-verge-merge-json-schema.json",
|
||||
fileMatch: ["**/*.merge.yaml"],
|
||||
//@ts-ignore
|
||||
schema: mergeSchema as JSONSchema7,
|
||||
},
|
||||
],
|
||||
});
|
||||
// PAC definition
|
||||
monaco.languages.typescript.javascriptDefaults.addExtraLib(pac, "pac.d.ts");
|
||||
monaco.languages.registerCompletionItemProvider("javascript", {
|
||||
provideCompletionItems: (model, position) => ({
|
||||
suggestions: [
|
||||
interface Props<T extends Language> {
|
||||
open: boolean;
|
||||
title?: string | ReactNode;
|
||||
initialData: Promise<string>;
|
||||
readOnly?: boolean;
|
||||
language: T;
|
||||
schema?: Schema<T>;
|
||||
onChange?: (prev?: string, curr?: string) => void;
|
||||
onSave?: (prev?: string, curr?: string) => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
let initialized = false;
|
||||
const monacoInitialization = () => {
|
||||
if (initialized) return;
|
||||
|
||||
// configure yaml worker
|
||||
configureMonacoYaml(monaco, {
|
||||
validate: true,
|
||||
enableSchemaRequest: true,
|
||||
schemas: [
|
||||
{
|
||||
label: "%mixed-port%",
|
||||
kind: monaco.languages.CompletionItemKind.Text,
|
||||
insertText: "%mixed-port%",
|
||||
range: {
|
||||
startLineNumber: position.lineNumber,
|
||||
endLineNumber: position.lineNumber,
|
||||
startColumn: model.getWordUntilPosition(position).startColumn - 1,
|
||||
endColumn: model.getWordUntilPosition(position).endColumn - 1,
|
||||
},
|
||||
uri: "http://example.com/meta-json-schema.json",
|
||||
fileMatch: ["**/*.clash.yaml"],
|
||||
// @ts-ignore
|
||||
schema: metaSchema as JSONSchema7,
|
||||
},
|
||||
{
|
||||
uri: "http://example.com/clash-verge-merge-json-schema.json",
|
||||
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) => {
|
||||
const {
|
||||
title,
|
||||
mode,
|
||||
property,
|
||||
open,
|
||||
readOnly,
|
||||
language,
|
||||
schema,
|
||||
onClose,
|
||||
onChange,
|
||||
} = props;
|
||||
initialized = true;
|
||||
};
|
||||
|
||||
export const EditorViewer = <T extends Language>(props: Props<T>) => {
|
||||
const { t } = useTranslation();
|
||||
const editorRef = useRef<any>();
|
||||
const instanceRef = useRef<editor.IStandaloneCodeEditor | null>(null);
|
||||
const themeMode = useThemeMode();
|
||||
const prevData = useRef<string>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const {
|
||||
open = false,
|
||||
title = t("Edit File"),
|
||||
initialData = Promise.resolve(""),
|
||||
readOnly = false,
|
||||
language = "yaml",
|
||||
schema,
|
||||
onChange,
|
||||
onSave,
|
||||
onClose,
|
||||
} = props;
|
||||
|
||||
let fetchContent;
|
||||
switch (mode) {
|
||||
case "profile": // profile文件
|
||||
fetchContent = readProfileFile(property);
|
||||
break;
|
||||
case "text": // 文本内容
|
||||
fetchContent = Promise.resolve(property);
|
||||
break;
|
||||
}
|
||||
fetchContent.then((data) => {
|
||||
const dom = editorRef.current;
|
||||
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>();
|
||||
const prevData = useRef<string | undefined>("");
|
||||
const currData = useRef<string | undefined>("");
|
||||
|
||||
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 model = monaco.editor.createModel(data, language, uri);
|
||||
instanceRef.current = editor.create(editorRef.current, {
|
||||
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;
|
||||
editorRef.current?.setModel(model);
|
||||
});
|
||||
};
|
||||
|
||||
return () => {
|
||||
if (instanceRef.current) {
|
||||
instanceRef.current.dispose();
|
||||
instanceRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [open]);
|
||||
|
||||
const onSave = useLockFn(async () => {
|
||||
const currData = instanceRef.current?.getValue();
|
||||
|
||||
if (currData == null) return;
|
||||
|
||||
const handleChange = useLockFn(async (value: string | undefined) => {
|
||||
try {
|
||||
if (mode === "profile") {
|
||||
await saveProfileFile(property, currData);
|
||||
}
|
||||
onChange?.(prevData.current, currData);
|
||||
currData.current = value;
|
||||
onChange?.(prevData.current, currData.current);
|
||||
} catch (err: any) {
|
||||
Notice.error(err.message || err.toString());
|
||||
}
|
||||
});
|
||||
|
||||
const handleSave = useLockFn(async () => {
|
||||
try {
|
||||
!readOnly && onSave?.(prevData.current, currData.current);
|
||||
onClose();
|
||||
} catch (err: any) {
|
||||
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 (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="xl" fullWidth>
|
||||
<DialogTitle>{title ?? t("Edit File")}</DialogTitle>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
|
||||
<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>
|
||||
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} variant="outlined">
|
||||
<Button onClick={handleClose} variant="outlined">
|
||||
{t(readOnly ? "Close" : "Cancel")}
|
||||
</Button>
|
||||
{!readOnly && (
|
||||
<Button onClick={onSave} variant="contained">
|
||||
<Button onClick={handleSave} variant="contained">
|
||||
{t("Save")}
|
||||
</Button>
|
||||
)}
|
||||
|
|
|
@ -17,7 +17,12 @@ import {
|
|||
} from "@mui/material";
|
||||
import { RefreshRounded, DragIndicator } from "@mui/icons-material";
|
||||
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 { RulesEditorViewer } from "@/components/profile/rules-editor-viewer";
|
||||
import { EditorViewer } from "@/components/profile/editor-viewer";
|
||||
|
@ -37,20 +42,13 @@ interface Props {
|
|||
itemData: IProfileItem;
|
||||
onSelect: (force: boolean) => void;
|
||||
onEdit: () => void;
|
||||
onChange?: (prev?: string, curr?: string) => void;
|
||||
onSave?: (prev?: string, curr?: string) => void;
|
||||
onDelete: () => void;
|
||||
}
|
||||
|
||||
export const ProfileItem = (props: Props) => {
|
||||
const {
|
||||
selected,
|
||||
activating,
|
||||
itemData,
|
||||
onSelect,
|
||||
onEdit,
|
||||
onChange,
|
||||
onDelete,
|
||||
} = props;
|
||||
const { selected, activating, itemData, onSelect, onEdit, onSave, onDelete } =
|
||||
props;
|
||||
const { attributes, listeners, setNodeRef, transform, transition } =
|
||||
useSortable({ id: props.id });
|
||||
|
||||
|
@ -474,52 +472,62 @@ export const ProfileItem = (props: Props) => {
|
|||
</Menu>
|
||||
|
||||
<EditorViewer
|
||||
mode="profile"
|
||||
property={uid}
|
||||
open={fileOpen}
|
||||
initialData={readProfileFile(uid)}
|
||||
language="yaml"
|
||||
schema="clash"
|
||||
onChange={onChange}
|
||||
onSave={async (prev, curr) => {
|
||||
await saveProfileFile(uid, curr ?? "");
|
||||
onSave && onSave(prev, curr);
|
||||
}}
|
||||
onClose={() => setFileOpen(false)}
|
||||
/>
|
||||
<RulesEditorViewer
|
||||
profileUid={uid}
|
||||
property={option?.rules ?? ""}
|
||||
open={rulesOpen}
|
||||
onChange={onChange}
|
||||
onSave={onSave}
|
||||
onClose={() => setRulesOpen(false)}
|
||||
/>
|
||||
<EditorViewer
|
||||
mode="profile"
|
||||
property={option?.proxies ?? ""}
|
||||
open={proxiesOpen}
|
||||
initialData={readProfileFile(option?.proxies ?? "")}
|
||||
language="yaml"
|
||||
onChange={onChange}
|
||||
onSave={async (prev, curr) => {
|
||||
await saveProfileFile(option?.proxies ?? "", curr ?? "");
|
||||
onSave && onSave(prev, curr);
|
||||
}}
|
||||
onClose={() => setProxiesOpen(false)}
|
||||
/>
|
||||
<EditorViewer
|
||||
mode="profile"
|
||||
property={option?.groups ?? ""}
|
||||
open={groupsOpen}
|
||||
initialData={readProfileFile(option?.proxies ?? "")}
|
||||
language="yaml"
|
||||
onChange={onChange}
|
||||
onSave={async (prev, curr) => {
|
||||
await saveProfileFile(option?.proxies ?? "", curr ?? "");
|
||||
onSave && onSave(prev, curr);
|
||||
}}
|
||||
onClose={() => setGroupsOpen(false)}
|
||||
/>
|
||||
<EditorViewer
|
||||
mode="profile"
|
||||
property={option?.merge ?? ""}
|
||||
open={mergeOpen}
|
||||
initialData={readProfileFile(option?.merge ?? "")}
|
||||
language="yaml"
|
||||
schema="clash"
|
||||
onChange={onChange}
|
||||
onSave={async (prev, curr) => {
|
||||
await saveProfileFile(option?.merge ?? "", curr ?? "");
|
||||
onSave && onSave(prev, curr);
|
||||
}}
|
||||
onClose={() => setMergeOpen(false)}
|
||||
/>
|
||||
<EditorViewer
|
||||
mode="profile"
|
||||
property={option?.script ?? ""}
|
||||
open={scriptOpen}
|
||||
initialData={readProfileFile(option?.script ?? "")}
|
||||
language="javascript"
|
||||
onChange={onChange}
|
||||
onSave={async (prev, curr) => {
|
||||
await saveProfileFile(option?.script ?? "", curr ?? "");
|
||||
onSave && onSave(prev, curr);
|
||||
}}
|
||||
onClose={() => setScriptOpen(false)}
|
||||
/>
|
||||
<ConfirmViewer
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
IconButton,
|
||||
} from "@mui/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 { EditorViewer } from "@/components/profile/editor-viewer";
|
||||
import { ProfileBox } from "./profile-box";
|
||||
|
@ -20,14 +20,14 @@ import { LogViewer } from "./log-viewer";
|
|||
interface Props {
|
||||
logInfo?: [string, string][];
|
||||
id: "Merge" | "Script";
|
||||
onChange?: (prev?: string, curr?: string) => void;
|
||||
onSave?: (prev?: string, curr?: string) => void;
|
||||
}
|
||||
|
||||
// profile enhanced item
|
||||
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 [position, setPosition] = useState({ left: 0, top: 0 });
|
||||
const [fileOpen, setFileOpen] = useState(false);
|
||||
|
@ -169,12 +169,15 @@ export const ProfileMore = (props: Props) => {
|
|||
</Menu>
|
||||
|
||||
<EditorViewer
|
||||
mode="profile"
|
||||
property={id}
|
||||
open={fileOpen}
|
||||
title={`${t("Global " + id)}`}
|
||||
initialData={readProfileFile(id)}
|
||||
language={id === "Merge" ? "yaml" : "javascript"}
|
||||
schema={id === "Merge" ? "clash" : undefined}
|
||||
onChange={onChange}
|
||||
onSave={async (prev, curr) => {
|
||||
await saveProfileFile(id, curr ?? "");
|
||||
onSave && onSave(prev, curr);
|
||||
}}
|
||||
onClose={() => setFileOpen(false)}
|
||||
/>
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ interface Props {
|
|||
property: string;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onChange?: (prev?: string, curr?: string) => void;
|
||||
onSave?: (prev?: string, curr?: string) => void;
|
||||
}
|
||||
|
||||
const portValidator = (value: string): boolean => {
|
||||
|
@ -227,7 +227,7 @@ const rules: {
|
|||
const builtinProxyPolicies = ["DIRECT", "REJECT", "REJECT-DROP", "PASS"];
|
||||
|
||||
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 [prevData, setPrevData] = useState("");
|
||||
|
@ -330,7 +330,7 @@ export const RulesEditorViewer = (props: Props) => {
|
|||
},${proxyPolicy}${ruleType.noResolve && noResolve ? ",no-resolve" : ""}`;
|
||||
};
|
||||
|
||||
const onSave = useLockFn(async () => {
|
||||
const handleSave = useLockFn(async () => {
|
||||
try {
|
||||
let currData = yaml.dump({
|
||||
prepend: prependSeq,
|
||||
|
@ -338,7 +338,7 @@ export const RulesEditorViewer = (props: Props) => {
|
|||
delete: deleteSeq,
|
||||
});
|
||||
await saveProfileFile(property, currData);
|
||||
onChange?.(prevData, currData);
|
||||
onSave?.(prevData, currData);
|
||||
onClose();
|
||||
} catch (err: any) {
|
||||
Notice.error(err.message || err.toString());
|
||||
|
@ -575,7 +575,7 @@ export const RulesEditorViewer = (props: Props) => {
|
|||
{t("Cancel")}
|
||||
</Button>
|
||||
|
||||
<Button onClick={onSave} variant="contained">
|
||||
<Button onClick={handleSave} variant="contained">
|
||||
{t("Save")}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
|
|
|
@ -22,15 +22,14 @@ export const ConfigViewer = forwardRef<DialogRef>((_, ref) => {
|
|||
|
||||
return (
|
||||
<EditorViewer
|
||||
open={open}
|
||||
title={
|
||||
<Box>
|
||||
{t("Runtime Config")}
|
||||
<Chip label={t("ReadOnly")} size="small" />
|
||||
</Box>
|
||||
}
|
||||
mode="text"
|
||||
property={runtimeConfig}
|
||||
open={open}
|
||||
initialData={Promise.resolve(runtimeConfig)}
|
||||
readOnly
|
||||
language="yaml"
|
||||
schema="clash"
|
||||
|
|
|
@ -262,21 +262,18 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||
{t("Edit")} PAC
|
||||
</Button>
|
||||
<EditorViewer
|
||||
title={`${t("Edit")} PAC`}
|
||||
mode="text"
|
||||
property={value.pac_content ?? ""}
|
||||
open={editorOpen}
|
||||
title={`${t("Edit")} PAC`}
|
||||
initialData={Promise.resolve(value.pac_content ?? "")}
|
||||
language="javascript"
|
||||
onChange={(_prev, curr) => {
|
||||
onSave={(_prev, curr) => {
|
||||
let pac = DEFAULT_PAC;
|
||||
if (curr && curr.trim().length > 0) {
|
||||
pac = curr;
|
||||
}
|
||||
setValue((v) => ({ ...v, pac_content: pac }));
|
||||
}}
|
||||
onClose={() => {
|
||||
setEditorOpen(false);
|
||||
}}
|
||||
onClose={() => setEditorOpen(false)}
|
||||
/>
|
||||
</ListItem>
|
||||
</>
|
||||
|
|
|
@ -124,12 +124,11 @@ export const ThemeViewer = forwardRef<DialogRef>((props, ref) => {
|
|||
{t("Edit")} CSS
|
||||
</Button>
|
||||
<EditorViewer
|
||||
title={`${t("Edit")} CSS`}
|
||||
mode="text"
|
||||
property={theme.css_injection ?? ""}
|
||||
open={editorOpen}
|
||||
title={`${t("Edit")} CSS`}
|
||||
initialData={Promise.resolve(theme.css_injection ?? "")}
|
||||
language="css"
|
||||
onChange={(_prev, curr) => {
|
||||
onSave={(_prev, curr) => {
|
||||
theme.css_injection = curr;
|
||||
handleChange("css_injection");
|
||||
}}
|
||||
|
|
|
@ -50,7 +50,6 @@ import { BaseStyledTextField } from "@/components/base/base-styled-text-field";
|
|||
import { listen } from "@tauri-apps/api/event";
|
||||
import { readTextFile } from "@tauri-apps/api/fs";
|
||||
import { readText } from "@tauri-apps/api/clipboard";
|
||||
import { EditorViewer } from "@/components/profile/editor-viewer";
|
||||
|
||||
const ProfilePage = () => {
|
||||
const { t } = useTranslation();
|
||||
|
@ -378,7 +377,7 @@ const ProfilePage = () => {
|
|||
itemData={item}
|
||||
onSelect={(f) => onSelect(item.uid, f)}
|
||||
onEdit={() => viewerRef.current?.edit(item)}
|
||||
onChange={async (prev, curr) => {
|
||||
onSave={async (prev, curr) => {
|
||||
if (prev !== curr && profiles.current === item.uid) {
|
||||
await onEnhance();
|
||||
}
|
||||
|
@ -401,7 +400,7 @@ const ProfilePage = () => {
|
|||
<Grid item xs={12} sm={6} md={6} lg={6}>
|
||||
<ProfileMore
|
||||
id="Merge"
|
||||
onChange={async (prev, curr) => {
|
||||
onSave={async (prev, curr) => {
|
||||
if (prev !== curr) {
|
||||
await onEnhance();
|
||||
}
|
||||
|
@ -412,7 +411,7 @@ const ProfilePage = () => {
|
|||
<ProfileMore
|
||||
id="Script"
|
||||
logInfo={chainLogs["Script"]}
|
||||
onChange={async (prev, curr) => {
|
||||
onSave={async (prev, curr) => {
|
||||
if (prev !== curr) {
|
||||
await onEnhance();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user