mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2024-11-15 19:22:26 +08:00
feat: display rules list
This commit is contained in:
parent
e2d522803c
commit
9089c30d57
63
src/components/profile/rule-item.tsx
Normal file
63
src/components/profile/rule-item.tsx
Normal 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>
|
||||
);
|
||||
};
|
|
@ -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>
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user