mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2024-11-16 03:32:36 +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,
|
TextField,
|
||||||
styled,
|
styled,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useThemeMode } from "@/services/states";
|
|
||||||
import { readProfileFile, saveProfileFile } from "@/services/cmds";
|
import { readProfileFile, saveProfileFile } from "@/services/cmds";
|
||||||
import { Notice, Switch } from "@/components/base";
|
import { Notice, Switch } from "@/components/base";
|
||||||
import getSystem from "@/utils/get-system";
|
import getSystem from "@/utils/get-system";
|
||||||
import MonacoEditor from "react-monaco-editor";
|
import { RuleItem } from "@/components/profile/rule-item";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
profileUid: string;
|
profileUid: string;
|
||||||
|
@ -119,10 +119,7 @@ export const RulesEditorViewer = (props: Props) => {
|
||||||
const { title, profileUid, property, open, onClose, onChange } = props;
|
const { title, profileUid, property, open, onClose, onChange } = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const themeMode = useThemeMode();
|
|
||||||
const [prevData, setPrevData] = useState("");
|
const [prevData, setPrevData] = useState("");
|
||||||
const [currData, setCurrData] = useState("");
|
|
||||||
const [rule, setRule] = useState("");
|
|
||||||
const [ruleType, setRuleType] =
|
const [ruleType, setRuleType] =
|
||||||
useState<(typeof RuleTypeList)[number]>("DOMAIN");
|
useState<(typeof RuleTypeList)[number]>("DOMAIN");
|
||||||
const [ruleContent, setRuleContent] = useState("");
|
const [ruleContent, setRuleContent] = useState("");
|
||||||
|
@ -131,28 +128,17 @@ export const RulesEditorViewer = (props: Props) => {
|
||||||
const [proxyPolicyList, setProxyPolicyList] = useState<string[]>([]);
|
const [proxyPolicyList, setProxyPolicyList] = useState<string[]>([]);
|
||||||
const [ruleList, setRuleList] = useState<string[]>([]);
|
const [ruleList, setRuleList] = useState<string[]>([]);
|
||||||
|
|
||||||
const editorOptions = {
|
const [prependSeq, setPrependSeq] = useState<string[]>([]);
|
||||||
tabSize: 2,
|
const [appendSeq, setAppendSeq] = useState<string[]>([]);
|
||||||
minimap: { enabled: false },
|
const [deleteSeq, setDeleteSeq] = useState<string[]>([]);
|
||||||
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 fetchContent = async () => {
|
const fetchContent = async () => {
|
||||||
let data = await readProfileFile(property);
|
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);
|
setPrevData(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -170,42 +156,6 @@ export const RulesEditorViewer = (props: Props) => {
|
||||||
setRuleList(obj.rules);
|
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(() => {
|
useEffect(() => {
|
||||||
fetchContent();
|
fetchContent();
|
||||||
fetchProfile();
|
fetchProfile();
|
||||||
|
@ -213,6 +163,11 @@ export const RulesEditorViewer = (props: Props) => {
|
||||||
|
|
||||||
const onSave = useLockFn(async () => {
|
const onSave = useLockFn(async () => {
|
||||||
try {
|
try {
|
||||||
|
let currData = yaml.dump({
|
||||||
|
prepend: prependSeq,
|
||||||
|
append: appendSeq,
|
||||||
|
delete: deleteSeq,
|
||||||
|
});
|
||||||
await saveProfileFile(property, currData);
|
await saveProfileFile(property, currData);
|
||||||
onChange?.(prevData, currData);
|
onChange?.(prevData, currData);
|
||||||
onClose();
|
onClose();
|
||||||
|
@ -288,7 +243,15 @@ export const RulesEditorViewer = (props: Props) => {
|
||||||
fullWidth
|
fullWidth
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={() => {
|
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")}
|
{t("Add Prepend Rule")}
|
||||||
|
@ -299,55 +262,82 @@ export const RulesEditorViewer = (props: Props) => {
|
||||||
fullWidth
|
fullWidth
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={() => {
|
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")}
|
{t("Add Append Rule")}
|
||||||
</Button>
|
</Button>
|
||||||
</Item>
|
</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>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "inline-block",
|
display: "inline-block",
|
||||||
width: "50%",
|
width: "50%",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
|
overflow: "auto",
|
||||||
marginLeft: "10px",
|
marginLeft: "10px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MonacoEditor
|
{prependSeq.length > 0 && (
|
||||||
language="yaml"
|
<List sx={{ borderBottom: "solid 1px var(--divider-color)" }}>
|
||||||
theme={themeMode === "light" ? "vs" : "vs-dark"}
|
{prependSeq.map((item, index) => {
|
||||||
height="100%"
|
return (
|
||||||
value={currData}
|
<RuleItem
|
||||||
onChange={(value, _) => {
|
key={`${item}-${index}`}
|
||||||
if (value) setCurrData(value);
|
type="prepend"
|
||||||
}}
|
ruleRaw={item}
|
||||||
options={editorOptions}
|
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>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user