feat: display rules list

This commit is contained in:
MystiPanda 2024-06-30 22:34:26 +08:00
parent e2d522803c
commit 9089c30d57
No known key found for this signature in database
2 changed files with 149 additions and 96 deletions

View File

@ -0,0 +1,63 @@
import {
Box,
Divider,
IconButton,
ListItem,
ListItemText,
Typography,
alpha,
} from "@mui/material";
import { DeleteForeverRounded, UndoRounded } from "@mui/icons-material";
interface Props {
type: "prepend" | "original" | "delete" | "append";
ruleRaw: string;
onDelete: () => void;
}
export const RuleItem = (props: Props) => {
let { type, ruleRaw, onDelete } = props;
const rule = ruleRaw.replace(",no-resolve", "").split(",");
return (
<ListItem
sx={({ palette }) => ({
p: 0,
borderRadius: "10px",
border: "solid 2px",
borderColor:
type === "original"
? "var(--divider-color)"
: type === "delete"
? alpha(palette.error.main, 0.5)
: alpha(palette.success.main, 0.5),
mb: 1,
})}
>
<ListItemText
sx={{ px: 1 }}
primary={
<>
<Typography
sx={{ textDecoration: type === "delete" ? "line-through" : "" }}
variant="h6"
component="span"
noWrap
>
{rule.length === 3 ? rule[1] : "-"}
</Typography>
</>
}
secondary={
<Box sx={{ display: "flex", justifyContent: "space-between" }}>
<Box>{rule[0]}</Box>
<Box>{rule.length === 3 ? rule[2] : rule[1]}</Box>
</Box>
}
/>
<Divider orientation="vertical" flexItem />
<IconButton size="small" color="inherit" onClick={onDelete}>
{type === "delete" ? <UndoRounded /> : <DeleteForeverRounded />}
</IconButton>
</ListItem>
);
};

View File

@ -16,11 +16,11 @@ import {
TextField,
styled,
} from "@mui/material";
import { useThemeMode } from "@/services/states";
import { readProfileFile, saveProfileFile } from "@/services/cmds";
import { Notice, Switch } from "@/components/base";
import getSystem from "@/utils/get-system";
import MonacoEditor from "react-monaco-editor";
import { RuleItem } from "@/components/profile/rule-item";
interface Props {
profileUid: string;
@ -119,10 +119,7 @@ export const RulesEditorViewer = (props: Props) => {
const { title, profileUid, property, open, onClose, onChange } = props;
const { t } = useTranslation();
const themeMode = useThemeMode();
const [prevData, setPrevData] = useState("");
const [currData, setCurrData] = useState("");
const [rule, setRule] = useState("");
const [ruleType, setRuleType] =
useState<(typeof RuleTypeList)[number]>("DOMAIN");
const [ruleContent, setRuleContent] = useState("");
@ -131,28 +128,17 @@ export const RulesEditorViewer = (props: Props) => {
const [proxyPolicyList, setProxyPolicyList] = useState<string[]>([]);
const [ruleList, setRuleList] = useState<string[]>([]);
const editorOptions = {
tabSize: 2,
minimap: { enabled: false },
mouseWheelZoom: true,
quickSuggestions: {
strings: true,
comments: true,
other: true,
},
padding: {
top: 33,
},
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,
};
const [prependSeq, setPrependSeq] = useState<string[]>([]);
const [appendSeq, setAppendSeq] = useState<string[]>([]);
const [deleteSeq, setDeleteSeq] = useState<string[]>([]);
const fetchContent = async () => {
let data = await readProfileFile(property);
setCurrData(data);
let obj = yaml.load(data) as { prepend: []; append: []; delete: [] };
setPrependSeq(obj.prepend || []);
setAppendSeq(obj.append || []);
setDeleteSeq(obj.delete || []);
setPrevData(data);
};
@ -170,42 +156,6 @@ export const RulesEditorViewer = (props: Props) => {
setRuleList(obj.rules);
};
const addSeq = async (method: "prepend" | "append" | "delete") => {
let obj = yaml.load(currData) as ISeqProfileConfig;
if (!obj.prepend) {
obj = { prepend: [], append: [], delete: [] };
}
switch (method) {
case "append": {
obj.append.push(
`${ruleType}${
ruleType === "MATCH" ? "" : "," + ruleContent
},${proxyPolicy}${
NoResolveList.includes(ruleType) && noResolve ? ",no-resolve" : ""
}`
);
break;
}
case "prepend": {
obj.prepend.push(
`${ruleType}${
ruleType === "MATCH" ? "" : "," + ruleContent
},${proxyPolicy}${
NoResolveList.includes(ruleType) && noResolve ? ",no-resolve" : ""
}`
);
break;
}
case "delete": {
obj.delete.push(rule);
break;
}
}
let raw = yaml.dump(obj);
setCurrData(raw);
};
useEffect(() => {
fetchContent();
fetchProfile();
@ -213,6 +163,11 @@ export const RulesEditorViewer = (props: Props) => {
const onSave = useLockFn(async () => {
try {
let currData = yaml.dump({
prepend: prependSeq,
append: appendSeq,
delete: deleteSeq,
});
await saveProfileFile(property, currData);
onChange?.(prevData, currData);
onClose();
@ -288,7 +243,15 @@ export const RulesEditorViewer = (props: Props) => {
fullWidth
variant="contained"
onClick={() => {
addSeq("prepend");
let raw = `${ruleType}${
ruleType === "MATCH" ? "" : "," + ruleContent
},${proxyPolicy}${
NoResolveList.includes(ruleType) && noResolve
? ",no-resolve"
: ""
}`;
if (prependSeq.includes(raw)) return;
setPrependSeq([...prependSeq, raw]);
}}
>
{t("Add Prepend Rule")}
@ -299,55 +262,82 @@ export const RulesEditorViewer = (props: Props) => {
fullWidth
variant="contained"
onClick={() => {
addSeq("append");
let raw = `${ruleType}${
ruleType === "MATCH" ? "" : "," + ruleContent
},${proxyPolicy}${
NoResolveList.includes(ruleType) && noResolve
? ",no-resolve"
: ""
}`;
if (appendSeq.includes(raw)) return;
setAppendSeq([...appendSeq, raw]);
}}
>
{t("Add Append Rule")}
</Button>
</Item>
<Item>
<Autocomplete
fullWidth
size="small"
sx={{ minWidth: "240px" }}
value={rule}
options={ruleList}
onChange={(_, v) => {
if (v) setRule(v);
}}
renderInput={(params) => <TextField {...params} />}
/>
</Item>
<Item>
<Button
fullWidth
variant="contained"
onClick={() => {
addSeq("delete");
}}
>
{t("Delete Rule")}
</Button>
</Item>
</div>
<div
style={{
display: "inline-block",
width: "50%",
height: "100%",
overflow: "auto",
marginLeft: "10px",
}}
>
<MonacoEditor
language="yaml"
theme={themeMode === "light" ? "vs" : "vs-dark"}
height="100%"
value={currData}
onChange={(value, _) => {
if (value) setCurrData(value);
}}
options={editorOptions}
/>
{prependSeq.length > 0 && (
<List sx={{ borderBottom: "solid 1px var(--divider-color)" }}>
{prependSeq.map((item, index) => {
return (
<RuleItem
key={`${item}-${index}`}
type="prepend"
ruleRaw={item}
onDelete={() => {
setPrependSeq(prependSeq.filter((v) => v !== item));
}}
/>
);
})}
</List>
)}
<List>
{ruleList.map((item, index) => {
return (
<RuleItem
key={`${item}-${index}`}
type={deleteSeq.includes(item) ? "delete" : "original"}
ruleRaw={item}
onDelete={() => {
if (deleteSeq.includes(item)) {
setDeleteSeq(deleteSeq.filter((v) => v !== item));
} else {
setDeleteSeq([...deleteSeq, item]);
}
}}
/>
);
})}
</List>
{appendSeq.length > 0 && (
<List sx={{ borderTop: "solid 1px var(--divider-color)" }}>
{appendSeq.map((item, index) => {
return (
<RuleItem
key={`${item}-${index}`}
type="append"
ruleRaw={item}
onDelete={() => {
setAppendSeq(appendSeq.filter((v) => v !== item));
}}
/>
);
})}
</List>
)}
</div>
</DialogContent>