2023-07-20 13:45:41 +08:00
|
|
|
import Box from "@mui/material/Box";
|
|
|
|
|
import Stack from "@mui/material/Stack";
|
|
|
|
|
import TextField from "@mui/material/TextField";
|
|
|
|
|
import Button from "@mui/material/Button";
|
|
|
|
|
import {
|
2023-08-08 13:29:15 +08:00
|
|
|
GLOBAL_KEY,
|
2023-07-20 13:45:41 +08:00
|
|
|
DEFAULT_RULE,
|
|
|
|
|
OPT_LANGS_FROM,
|
|
|
|
|
OPT_LANGS_TO,
|
|
|
|
|
OPT_TRANS_ALL,
|
|
|
|
|
OPT_STYLE_ALL,
|
2023-08-17 15:55:44 +08:00
|
|
|
BUILTIN_RULES,
|
2023-07-20 13:45:41 +08:00
|
|
|
} from "../../config";
|
|
|
|
|
import { useState, useRef } from "react";
|
|
|
|
|
import { useI18n } from "../../hooks/I18n";
|
|
|
|
|
import Typography from "@mui/material/Typography";
|
|
|
|
|
import Accordion from "@mui/material/Accordion";
|
|
|
|
|
import AccordionSummary from "@mui/material/AccordionSummary";
|
|
|
|
|
import AccordionDetails from "@mui/material/AccordionDetails";
|
|
|
|
|
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
|
|
|
|
import { useRules } from "../../hooks/Rules";
|
|
|
|
|
import MenuItem from "@mui/material/MenuItem";
|
|
|
|
|
import Grid from "@mui/material/Grid";
|
|
|
|
|
import FileDownloadIcon from "@mui/icons-material/FileDownload";
|
|
|
|
|
import FileUploadIcon from "@mui/icons-material/FileUpload";
|
2023-08-17 15:55:44 +08:00
|
|
|
import { useSetting, useSettingUpdate } from "../../hooks/Setting";
|
|
|
|
|
import FormControlLabel from "@mui/material/FormControlLabel";
|
|
|
|
|
import Switch from "@mui/material/Switch";
|
2023-07-20 13:45:41 +08:00
|
|
|
|
2023-07-28 15:34:05 +08:00
|
|
|
function RuleFields({ rule, rules, setShow }) {
|
2023-08-08 14:09:35 +08:00
|
|
|
const initFormValues = rule || { ...DEFAULT_RULE, transOpen: "true" };
|
2023-07-20 13:45:41 +08:00
|
|
|
const editMode = !!rule;
|
|
|
|
|
|
|
|
|
|
const i18n = useI18n();
|
|
|
|
|
const [disabled, setDisabled] = useState(editMode);
|
|
|
|
|
const [errors, setErrors] = useState({});
|
|
|
|
|
const [formValues, setFormValues] = useState(initFormValues);
|
|
|
|
|
const {
|
|
|
|
|
pattern,
|
|
|
|
|
selector,
|
|
|
|
|
translator,
|
|
|
|
|
fromLang,
|
|
|
|
|
toLang,
|
|
|
|
|
textStyle,
|
|
|
|
|
transOpen,
|
2023-08-08 16:41:47 +08:00
|
|
|
bgColor,
|
2023-07-20 13:45:41 +08:00
|
|
|
} = formValues;
|
|
|
|
|
|
|
|
|
|
const hasSamePattern = (str) => {
|
2023-07-22 09:00:33 +08:00
|
|
|
for (const item of rules.list) {
|
2023-07-20 13:45:41 +08:00
|
|
|
if (item.pattern === str && rule?.pattern !== str) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleFocus = (e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
const { name } = e.target;
|
|
|
|
|
setErrors((pre) => ({ ...pre, [name]: "" }));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleChange = (e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
const { name, value } = e.target;
|
|
|
|
|
setFormValues((pre) => ({ ...pre, [name]: value }));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleCancel = (e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
if (editMode) {
|
|
|
|
|
setDisabled(true);
|
|
|
|
|
} else {
|
|
|
|
|
setShow(false);
|
|
|
|
|
}
|
|
|
|
|
setErrors({});
|
|
|
|
|
setFormValues(initFormValues);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleSubmit = (e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
const errors = {};
|
|
|
|
|
if (!pattern.trim()) {
|
|
|
|
|
errors.pattern = i18n("error_cant_be_blank");
|
|
|
|
|
}
|
|
|
|
|
if (hasSamePattern(pattern)) {
|
|
|
|
|
errors.pattern = i18n("error_duplicate_values");
|
|
|
|
|
}
|
2023-08-08 15:36:42 +08:00
|
|
|
if (pattern === "*" && !errors.pattern && !selector.trim()) {
|
|
|
|
|
errors.selector = i18n("error_cant_be_blank");
|
|
|
|
|
}
|
2023-07-20 13:45:41 +08:00
|
|
|
if (Object.keys(errors).length > 0) {
|
|
|
|
|
setErrors(errors);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (editMode) {
|
|
|
|
|
// 编辑
|
|
|
|
|
setDisabled(true);
|
2023-07-28 15:34:05 +08:00
|
|
|
rules.put(rule.pattern, formValues);
|
2023-07-20 13:45:41 +08:00
|
|
|
} else {
|
|
|
|
|
// 添加
|
2023-07-22 09:00:33 +08:00
|
|
|
rules.add(formValues);
|
2023-07-20 13:45:41 +08:00
|
|
|
setShow(false);
|
|
|
|
|
setFormValues(initFormValues);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2023-08-08 13:29:15 +08:00
|
|
|
const globalItem = rule?.pattern !== "*" && (
|
|
|
|
|
<MenuItem key={GLOBAL_KEY} value={GLOBAL_KEY}>
|
|
|
|
|
{GLOBAL_KEY}
|
|
|
|
|
</MenuItem>
|
|
|
|
|
);
|
|
|
|
|
|
2023-07-20 13:45:41 +08:00
|
|
|
return (
|
|
|
|
|
<form onSubmit={handleSubmit}>
|
|
|
|
|
<Stack spacing={2}>
|
|
|
|
|
<TextField
|
|
|
|
|
size="small"
|
|
|
|
|
label={i18n("pattern")}
|
|
|
|
|
error={!!errors.pattern}
|
2023-08-08 11:31:11 +08:00
|
|
|
helperText={errors.pattern || i18n("pattern_helper")}
|
2023-07-20 13:45:41 +08:00
|
|
|
name="pattern"
|
|
|
|
|
value={pattern}
|
|
|
|
|
disabled={rule?.pattern === "*" || disabled}
|
|
|
|
|
onChange={handleChange}
|
|
|
|
|
onFocus={handleFocus}
|
2023-08-17 13:27:22 +08:00
|
|
|
multiline
|
2023-07-20 13:45:41 +08:00
|
|
|
/>
|
|
|
|
|
<TextField
|
|
|
|
|
size="small"
|
|
|
|
|
label={i18n("selector")}
|
|
|
|
|
error={!!errors.selector}
|
2023-08-08 11:31:11 +08:00
|
|
|
helperText={errors.selector || i18n("selector_helper")}
|
2023-07-20 13:45:41 +08:00
|
|
|
name="selector"
|
|
|
|
|
value={selector}
|
|
|
|
|
disabled={disabled}
|
|
|
|
|
onChange={handleChange}
|
|
|
|
|
onFocus={handleFocus}
|
|
|
|
|
multiline
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<Box>
|
2023-08-08 16:41:47 +08:00
|
|
|
<Grid container spacing={2} columns={12}>
|
|
|
|
|
<Grid item xs={12} sm={6} md={3} lg={2}>
|
2023-07-20 13:45:41 +08:00
|
|
|
<TextField
|
|
|
|
|
select
|
|
|
|
|
size="small"
|
|
|
|
|
fullWidth
|
|
|
|
|
name="transOpen"
|
|
|
|
|
value={transOpen}
|
|
|
|
|
label={i18n("translate_switch")}
|
|
|
|
|
disabled={disabled}
|
|
|
|
|
onChange={handleChange}
|
|
|
|
|
>
|
2023-08-08 13:29:15 +08:00
|
|
|
{globalItem}
|
|
|
|
|
<MenuItem value={"true"}>{i18n("default_enabled")}</MenuItem>
|
|
|
|
|
<MenuItem value={"false"}>{i18n("default_disabled")}</MenuItem>
|
2023-07-20 13:45:41 +08:00
|
|
|
</TextField>
|
|
|
|
|
</Grid>
|
2023-08-08 16:41:47 +08:00
|
|
|
<Grid item xs={12} sm={6} md={3} lg={2}>
|
2023-07-20 13:45:41 +08:00
|
|
|
<TextField
|
|
|
|
|
select
|
|
|
|
|
size="small"
|
|
|
|
|
fullWidth
|
|
|
|
|
name="translator"
|
|
|
|
|
value={translator}
|
|
|
|
|
label={i18n("translate_service")}
|
|
|
|
|
disabled={disabled}
|
|
|
|
|
onChange={handleChange}
|
|
|
|
|
>
|
2023-08-08 13:29:15 +08:00
|
|
|
{globalItem}
|
2023-07-20 13:45:41 +08:00
|
|
|
{OPT_TRANS_ALL.map((item) => (
|
2023-08-04 10:08:54 +08:00
|
|
|
<MenuItem key={item} value={item}>
|
|
|
|
|
{item}
|
|
|
|
|
</MenuItem>
|
2023-07-20 13:45:41 +08:00
|
|
|
))}
|
|
|
|
|
</TextField>
|
|
|
|
|
</Grid>
|
2023-08-08 16:41:47 +08:00
|
|
|
<Grid item xs={12} sm={6} md={3} lg={2}>
|
2023-07-20 13:45:41 +08:00
|
|
|
<TextField
|
|
|
|
|
select
|
|
|
|
|
size="small"
|
|
|
|
|
fullWidth
|
|
|
|
|
name="fromLang"
|
|
|
|
|
value={fromLang}
|
|
|
|
|
label={i18n("from_lang")}
|
|
|
|
|
disabled={disabled}
|
|
|
|
|
onChange={handleChange}
|
|
|
|
|
>
|
2023-08-08 13:29:15 +08:00
|
|
|
{globalItem}
|
2023-07-20 13:45:41 +08:00
|
|
|
{OPT_LANGS_FROM.map(([lang, name]) => (
|
2023-08-08 13:29:15 +08:00
|
|
|
<MenuItem key={lang} value={lang}>
|
|
|
|
|
{name}
|
|
|
|
|
</MenuItem>
|
2023-07-20 13:45:41 +08:00
|
|
|
))}
|
|
|
|
|
</TextField>
|
|
|
|
|
</Grid>
|
2023-08-08 16:41:47 +08:00
|
|
|
<Grid item xs={12} sm={6} md={3} lg={2}>
|
2023-07-20 13:45:41 +08:00
|
|
|
<TextField
|
|
|
|
|
select
|
|
|
|
|
size="small"
|
|
|
|
|
fullWidth
|
|
|
|
|
name="toLang"
|
|
|
|
|
value={toLang}
|
|
|
|
|
label={i18n("to_lang")}
|
|
|
|
|
disabled={disabled}
|
|
|
|
|
onChange={handleChange}
|
|
|
|
|
>
|
2023-08-08 13:29:15 +08:00
|
|
|
{globalItem}
|
2023-07-20 13:45:41 +08:00
|
|
|
{OPT_LANGS_TO.map(([lang, name]) => (
|
2023-08-08 13:29:15 +08:00
|
|
|
<MenuItem key={lang} value={lang}>
|
|
|
|
|
{name}
|
|
|
|
|
</MenuItem>
|
2023-07-20 13:45:41 +08:00
|
|
|
))}
|
|
|
|
|
</TextField>
|
|
|
|
|
</Grid>
|
2023-08-08 16:41:47 +08:00
|
|
|
<Grid item xs={12} sm={6} md={3} lg={2}>
|
2023-07-20 13:45:41 +08:00
|
|
|
<TextField
|
|
|
|
|
select
|
|
|
|
|
size="small"
|
|
|
|
|
fullWidth
|
|
|
|
|
name="textStyle"
|
|
|
|
|
value={textStyle}
|
|
|
|
|
label={i18n("text_style")}
|
|
|
|
|
disabled={disabled}
|
|
|
|
|
onChange={handleChange}
|
|
|
|
|
>
|
2023-08-08 13:29:15 +08:00
|
|
|
{globalItem}
|
2023-07-20 13:45:41 +08:00
|
|
|
{OPT_STYLE_ALL.map((item) => (
|
2023-08-08 13:29:15 +08:00
|
|
|
<MenuItem key={item} value={item}>
|
|
|
|
|
{i18n(item)}
|
|
|
|
|
</MenuItem>
|
2023-07-20 13:45:41 +08:00
|
|
|
))}
|
|
|
|
|
</TextField>
|
|
|
|
|
</Grid>
|
2023-08-08 16:41:47 +08:00
|
|
|
<Grid item xs={12} sm={6} md={3} lg={2}>
|
|
|
|
|
<TextField
|
|
|
|
|
size="small"
|
|
|
|
|
fullWidth
|
|
|
|
|
name="bgColor"
|
|
|
|
|
value={bgColor}
|
|
|
|
|
label={i18n("bg_color")}
|
|
|
|
|
disabled={disabled}
|
|
|
|
|
onChange={handleChange}
|
|
|
|
|
/>
|
|
|
|
|
</Grid>
|
2023-07-20 13:45:41 +08:00
|
|
|
</Grid>
|
|
|
|
|
</Box>
|
|
|
|
|
|
2023-08-17 15:55:44 +08:00
|
|
|
{rules &&
|
|
|
|
|
(editMode ? (
|
|
|
|
|
// 编辑
|
|
|
|
|
<Stack direction="row" spacing={2}>
|
|
|
|
|
{disabled ? (
|
|
|
|
|
<>
|
2023-07-20 13:45:41 +08:00
|
|
|
<Button
|
|
|
|
|
size="small"
|
2023-08-17 15:55:44 +08:00
|
|
|
variant="contained"
|
2023-07-20 13:45:41 +08:00
|
|
|
onClick={(e) => {
|
|
|
|
|
e.preventDefault();
|
2023-08-17 15:55:44 +08:00
|
|
|
setDisabled(false);
|
2023-07-20 13:45:41 +08:00
|
|
|
}}
|
|
|
|
|
>
|
2023-08-17 15:55:44 +08:00
|
|
|
{i18n("edit")}
|
2023-07-20 13:45:41 +08:00
|
|
|
</Button>
|
2023-08-17 15:55:44 +08:00
|
|
|
{rule?.pattern !== "*" && (
|
|
|
|
|
<Button
|
|
|
|
|
size="small"
|
|
|
|
|
variant="outlined"
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
rules.del(rule.pattern);
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{i18n("delete")}
|
|
|
|
|
</Button>
|
|
|
|
|
)}
|
|
|
|
|
</>
|
|
|
|
|
) : (
|
|
|
|
|
<>
|
|
|
|
|
<Button size="small" variant="contained" type="submit">
|
|
|
|
|
{i18n("save")}
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
size="small"
|
|
|
|
|
variant="outlined"
|
|
|
|
|
onClick={handleCancel}
|
|
|
|
|
>
|
|
|
|
|
{i18n("cancel")}
|
|
|
|
|
</Button>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</Stack>
|
|
|
|
|
) : (
|
|
|
|
|
// 添加
|
|
|
|
|
<Stack direction="row" spacing={2}>
|
|
|
|
|
<Button size="small" variant="contained" type="submit">
|
|
|
|
|
{i18n("save")}
|
|
|
|
|
</Button>
|
|
|
|
|
<Button size="small" variant="outlined" onClick={handleCancel}>
|
|
|
|
|
{i18n("cancel")}
|
|
|
|
|
</Button>
|
|
|
|
|
</Stack>
|
|
|
|
|
))}
|
2023-07-20 13:45:41 +08:00
|
|
|
</Stack>
|
|
|
|
|
</form>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-17 13:27:22 +08:00
|
|
|
function RuleAccordion({ rule, rules }) {
|
|
|
|
|
const [expanded, setExpanded] = useState(false);
|
|
|
|
|
|
|
|
|
|
const handleChange = (e) => {
|
|
|
|
|
setExpanded((pre) => !pre);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Accordion expanded={expanded} onChange={handleChange}>
|
|
|
|
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
2023-08-17 15:55:44 +08:00
|
|
|
<Typography
|
|
|
|
|
style={{
|
|
|
|
|
opacity: rules ? 1 : 0.5,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{rule.pattern}
|
|
|
|
|
</Typography>
|
2023-08-17 13:27:22 +08:00
|
|
|
</AccordionSummary>
|
|
|
|
|
<AccordionDetails>
|
|
|
|
|
{expanded && <RuleFields rule={rule} rules={rules} />}
|
|
|
|
|
</AccordionDetails>
|
|
|
|
|
</Accordion>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-20 13:45:41 +08:00
|
|
|
function DownloadButton({ data, text, fileName }) {
|
|
|
|
|
const handleClick = (e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
if (data) {
|
|
|
|
|
const url = window.URL.createObjectURL(new Blob([data]));
|
|
|
|
|
const link = document.createElement("a");
|
|
|
|
|
link.href = url;
|
|
|
|
|
link.setAttribute("download", fileName || `${Date.now()}.json`);
|
|
|
|
|
document.body.appendChild(link);
|
|
|
|
|
link.click();
|
|
|
|
|
link.remove();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
return (
|
|
|
|
|
<Button
|
|
|
|
|
size="small"
|
|
|
|
|
variant="outlined"
|
|
|
|
|
onClick={handleClick}
|
|
|
|
|
startIcon={<FileDownloadIcon />}
|
|
|
|
|
>
|
|
|
|
|
{text}
|
|
|
|
|
</Button>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function UploadButton({ onChange, text }) {
|
|
|
|
|
const inputRef = useRef(null);
|
|
|
|
|
const handleClick = () => {
|
|
|
|
|
inputRef.current && inputRef.current.click();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Button
|
|
|
|
|
size="small"
|
|
|
|
|
variant="outlined"
|
|
|
|
|
onClick={handleClick}
|
|
|
|
|
startIcon={<FileUploadIcon />}
|
|
|
|
|
>
|
|
|
|
|
{text}
|
|
|
|
|
<input
|
|
|
|
|
type="file"
|
|
|
|
|
accept=".json"
|
|
|
|
|
ref={inputRef}
|
|
|
|
|
onChange={onChange}
|
|
|
|
|
hidden
|
|
|
|
|
/>
|
|
|
|
|
</Button>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default function Rules() {
|
|
|
|
|
const i18n = useI18n();
|
2023-07-22 09:00:33 +08:00
|
|
|
const rules = useRules();
|
2023-07-20 13:45:41 +08:00
|
|
|
const [showAdd, setShowAdd] = useState(false);
|
2023-08-17 15:55:44 +08:00
|
|
|
const setting = useSetting();
|
|
|
|
|
const updateSetting = useSettingUpdate();
|
|
|
|
|
const injectRules = !!setting?.injectRules;
|
2023-07-20 13:45:41 +08:00
|
|
|
|
|
|
|
|
const handleImport = (e) => {
|
|
|
|
|
const file = e.target.files[0];
|
|
|
|
|
if (!file) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!file.type.includes("json")) {
|
|
|
|
|
alert(i18n("error_wrong_file_type"));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const reader = new FileReader();
|
|
|
|
|
reader.onload = async (e) => {
|
|
|
|
|
try {
|
2023-07-22 09:00:33 +08:00
|
|
|
await rules.merge(JSON.parse(e.target.result));
|
2023-07-20 13:45:41 +08:00
|
|
|
} catch (err) {
|
|
|
|
|
console.log("[import rules]", err);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
reader.readAsText(file);
|
|
|
|
|
};
|
|
|
|
|
|
2023-08-17 15:55:44 +08:00
|
|
|
const handleInject = () => {
|
|
|
|
|
updateSetting({
|
|
|
|
|
injectRules: !injectRules,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2023-07-20 13:45:41 +08:00
|
|
|
return (
|
|
|
|
|
<Box>
|
|
|
|
|
<Stack spacing={3}>
|
|
|
|
|
<Stack direction="row" spacing={2}>
|
|
|
|
|
<Button
|
|
|
|
|
size="small"
|
|
|
|
|
variant="contained"
|
|
|
|
|
disabled={showAdd}
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
setShowAdd(true);
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{i18n("add")}
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
|
|
<UploadButton text={i18n("import")} onChange={handleImport} />
|
|
|
|
|
<DownloadButton
|
2023-07-22 09:00:33 +08:00
|
|
|
data={JSON.stringify([...rules.list].reverse(), null, "\t")}
|
2023-07-20 13:45:41 +08:00
|
|
|
text={i18n("export")}
|
|
|
|
|
/>
|
2023-08-17 15:55:44 +08:00
|
|
|
|
|
|
|
|
<FormControlLabel
|
|
|
|
|
control={
|
|
|
|
|
<Switch
|
|
|
|
|
size="small"
|
|
|
|
|
checked={injectRules}
|
|
|
|
|
onChange={handleInject}
|
|
|
|
|
/>
|
|
|
|
|
}
|
|
|
|
|
label={i18n("inject_rules")}
|
|
|
|
|
/>
|
2023-07-20 13:45:41 +08:00
|
|
|
</Stack>
|
|
|
|
|
|
2023-07-22 09:00:33 +08:00
|
|
|
{showAdd && <RuleFields rules={rules} setShow={setShowAdd} />}
|
2023-07-20 13:45:41 +08:00
|
|
|
|
|
|
|
|
<Box>
|
2023-07-28 15:34:05 +08:00
|
|
|
{rules.list.map((rule) => (
|
2023-08-17 13:27:22 +08:00
|
|
|
<RuleAccordion key={rule.pattern} rule={rule} rules={rules} />
|
2023-07-20 13:45:41 +08:00
|
|
|
))}
|
|
|
|
|
</Box>
|
2023-08-17 15:55:44 +08:00
|
|
|
|
|
|
|
|
{injectRules && (
|
|
|
|
|
<Box>
|
|
|
|
|
{BUILTIN_RULES.map((rule) => (
|
|
|
|
|
<RuleAccordion key={rule.pattern} rule={rule} />
|
|
|
|
|
))}
|
|
|
|
|
</Box>
|
|
|
|
|
)}
|
2023-07-20 13:45:41 +08:00
|
|
|
</Stack>
|
|
|
|
|
</Box>
|
|
|
|
|
);
|
|
|
|
|
}
|