diff --git a/src/components/profile/rules-editor-viewer.tsx b/src/components/profile/rules-editor-viewer.tsx index 39d7ea18..806d323f 100644 --- a/src/components/profile/rules-editor-viewer.tsx +++ b/src/components/profile/rules-editor-viewer.tsx @@ -36,6 +36,8 @@ import getSystem from "@/utils/get-system"; import { RuleItem } from "@/components/profile/rule-item"; import { BaseSearchBox } from "../base/base-search-box"; import { Virtuoso } from "react-virtuoso"; +import MonacoEditor from "react-monaco-editor"; +import { useThemeMode } from "@/services/states"; interface Props { profileUid: string; @@ -230,8 +232,11 @@ const builtinProxyPolicies = ["DIRECT", "REJECT", "REJECT-DROP", "PASS"]; export const RulesEditorViewer = (props: Props) => { const { title, profileUid, property, open, onClose, onSave } = props; const { t } = useTranslation(); + const themeMode = useThemeMode(); const [prevData, setPrevData] = useState(""); + const [currData, setCurrData] = useState(""); + const [visible, setVisible] = useState(true); const [match, setMatch] = useState(() => (_: string) => true); const [ruleType, setRuleType] = useState<(typeof rules)[number]>(rules[0]); @@ -291,9 +296,28 @@ export const RulesEditorViewer = (props: Props) => { setPrependSeq(obj.prepend || []); setAppendSeq(obj.append || []); setDeleteSeq(obj.delete || []); + setPrevData(data); + setCurrData(data); }; + useEffect(() => { + if (currData === "") return; + if (visible !== true) return; + + let obj = yaml.load(currData) as { prepend: []; append: []; delete: [] }; + setPrependSeq(obj.prepend || []); + setAppendSeq(obj.append || []); + setDeleteSeq(obj.delete || []); + }, [visible]); + + useEffect(() => { + if (prependSeq && appendSeq && deleteSeq) + setCurrData( + yaml.dump({ prepend: prependSeq, append: appendSeq, delete: deleteSeq }) + ); + }, [prependSeq, appendSeq, deleteSeq]); + const fetchProfile = async () => { let data = await readProfileFile(profileUid); let groupsObj = yaml.load(data) as { "proxy-groups": [] }; @@ -338,11 +362,6 @@ export const RulesEditorViewer = (props: Props) => { const handleSave = useLockFn(async () => { try { - let currData = yaml.dump({ - prepend: prependSeq, - append: appendSeq, - delete: deleteSeq, - }); await saveProfileFile(property, currData); onSave?.(prevData, currData); onClose(); @@ -353,234 +372,292 @@ export const RulesEditorViewer = (props: Props) => { return ( - {title ?? t("Edit Rules")} + + { + + {t("Edit Rules")} + + + + + } + - - - - } - options={rules} - value={ruleType} - getOptionLabel={(option) => option.name} - renderOption={(props, option) => ( -
  • - {option.name} -
  • - )} - onChange={(_, value) => value && setRuleType(value)} - /> -
    - - - - {ruleType.name === "RULE-SET" && ( - } - options={ruleSetList} - value={ruleContent} - onChange={(_, value) => value && setRuleContent(value)} - /> - )} - {ruleType.name === "SUB-RULE" && ( - } - options={subRuleList} - value={ruleContent} - onChange={(_, value) => value && setRuleContent(value)} - /> - )} - {ruleType.name !== "RULE-SET" && ruleType.name !== "SUB-RULE" && ( - setRuleContent(e.target.value)} - /> - )} - - - - } - options={proxyPolicyList} - value={proxyPolicy} - renderOption={(props, option) => ( -
  • - {option} -
  • - )} - onChange={(_, value) => value && setProxyPolicy(value)} - /> -
    - {ruleType.noResolve && ( - - - setNoResolve(!noResolve)} - /> - - )} - - - - - - -
    + + + } + options={rules} + value={ruleType} + getOptionLabel={(option) => option.name} + renderOption={(props, option) => ( +
  • + {option.name} +
  • + )} + onChange={(_, value) => value && setRuleType(value)} + /> +
    + + - - setMatch(() => match)} - /> - 0 ? 1 : 0) + - (appendSeq.length > 0 ? 1 : 0) - } - increaseViewportBy={256} - itemContent={(index) => { - let shift = prependSeq.length > 0 ? 1 : 0; - if (prependSeq.length > 0 && index === 0) { - return ( - - { - return x; - })} - > - {prependSeq.map((item, index) => { - return ( - { - setPrependSeq( - prependSeq.filter((v) => v !== item) - ); - }} - /> - ); - })} - - - ); - } else if (index < filteredRuleList.length + shift) { - let newIndex = index - shift; - return ( - { - if (deleteSeq.includes(filteredRuleList[newIndex])) { - setDeleteSeq( - deleteSeq.filter( - (v) => v !== filteredRuleList[newIndex] - ) - ); - } else { - setDeleteSeq((prev) => [ - ...prev, - filteredRuleList[newIndex], - ]); - } - }} + {ruleType.name === "RULE-SET" && ( + } + options={ruleSetList} + value={ruleContent} + onChange={(_, value) => value && setRuleContent(value)} /> - ); - } else { - return ( - - { - return x; - })} - > - {appendSeq.map((item, index) => { - return ( - { - setAppendSeq(appendSeq.filter((v) => v !== item)); - }} - /> - ); - })} - - - ); - } + )} + {ruleType.name === "SUB-RULE" && ( + } + options={subRuleList} + value={ruleContent} + onChange={(_, value) => value && setRuleContent(value)} + /> + )} + {ruleType.name !== "RULE-SET" && + ruleType.name !== "SUB-RULE" && ( + setRuleContent(e.target.value)} + /> + )} + + + + } + options={proxyPolicyList} + value={proxyPolicy} + renderOption={(props, option) => ( +
  • + {option} +
  • + )} + onChange={(_, value) => value && setProxyPolicy(value)} + /> +
    + {ruleType.noResolve && ( + + + setNoResolve(!noResolve)} + /> + + )} + + + + + + + + + + setMatch(() => match)} + /> + 0 ? 1 : 0) + + (appendSeq.length > 0 ? 1 : 0) + } + increaseViewportBy={256} + itemContent={(index) => { + let shift = prependSeq.length > 0 ? 1 : 0; + if (prependSeq.length > 0 && index === 0) { + return ( + + { + return x; + })} + > + {prependSeq.map((item, index) => { + return ( + { + setPrependSeq( + prependSeq.filter((v) => v !== item) + ); + }} + /> + ); + })} + + + ); + } else if (index < filteredRuleList.length + shift) { + let newIndex = index - shift; + return ( + { + if (deleteSeq.includes(filteredRuleList[newIndex])) { + setDeleteSeq( + deleteSeq.filter( + (v) => v !== filteredRuleList[newIndex] + ) + ); + } else { + setDeleteSeq((prev) => [ + ...prev, + filteredRuleList[newIndex], + ]); + } + }} + /> + ); + } else { + return ( + + { + return x; + })} + > + {appendSeq.map((item, index) => { + return ( + { + setAppendSeq( + appendSeq.filter((v) => v !== item) + ); + }} + /> + ); + })} + + + ); + } + }} + /> + + + ) : ( + = 1500, // 超过一定宽度显示minimap滚动条 + }, + mouseWheelZoom: true, // 按住Ctrl滚轮调节缩放比例 + 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, // 平滑滚动 }} + onChange={(value) => setCurrData(value)} /> - + )}
    diff --git a/src/locales/en.json b/src/locales/en.json index 4d706fe9..19aa9136 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -64,6 +64,8 @@ "Delete Rule": "Delete Rule", "Rule Condition Required": "Rule Condition Required", "Invalid Rule": "Invalid Rule", + "Advanced": "Advanced", + "Visible": "Visible", "DOMAIN": "Matches the full domain name", "DOMAIN-SUFFIX": "Matches the domain suffix", "DOMAIN-KEYWORD": "Matches the domain keyword", diff --git a/src/locales/zh.json b/src/locales/zh.json index a2d65b42..138a9e67 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -64,6 +64,8 @@ "Delete Rule": "删除规则", "Rule Condition Required": "规则条件缺失", "Invalid Rule": "无效规则", + "Advanced": "高级", + "Visible": "可视化", "DOMAIN": "匹配完整域名", "DOMAIN-SUFFIX": "匹配域名后缀", "DOMAIN-KEYWORD": "匹配域名关键字",