358 lines
9.6 KiB
JavaScript
358 lines
9.6 KiB
JavaScript
import Stack from "@mui/material/Stack";
|
|
import TextField from "@mui/material/TextField";
|
|
import { useCallback, useEffect, useState } 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 Alert from "@mui/material/Alert";
|
|
import Box from "@mui/material/Box";
|
|
import FormControlLabel from "@mui/material/FormControlLabel";
|
|
import Switch from "@mui/material/Switch";
|
|
import { useSetting } from "../../hooks/Setting";
|
|
import CircularProgress from "@mui/material/CircularProgress";
|
|
import { syncWebfix, loadOrFetchWebfix, FIXER_ALL } from "../../libs/webfix";
|
|
import Button from "@mui/material/Button";
|
|
import SyncIcon from "@mui/icons-material/Sync";
|
|
import { useAlert } from "../../hooks/Alert";
|
|
import HelpButton from "./HelpButton";
|
|
import { URL_KISS_RULES_NEW_ISSUE } from "../../config";
|
|
import MenuItem from "@mui/material/MenuItem";
|
|
import { useWebfixRules } from "../../hooks/WebfixRules";
|
|
|
|
function WebfixFields({ rule, webfix, setShow }) {
|
|
const editMode = !!rule;
|
|
const initFormValues = rule || {
|
|
pattern: "",
|
|
selector: "",
|
|
rootSelector: "",
|
|
fixer: FIXER_ALL[0],
|
|
};
|
|
const i18n = useI18n();
|
|
const [disabled, setDisabled] = useState(editMode);
|
|
const [errors, setErrors] = useState({});
|
|
const [formValues, setFormValues] = useState(initFormValues);
|
|
|
|
const { pattern, selector, rootSelector, fixer } = formValues;
|
|
|
|
const hasSamePattern = (str) => {
|
|
for (const item of webfix.list || []) {
|
|
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);
|
|
}
|
|
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");
|
|
}
|
|
if (!selector.trim()) {
|
|
errors.selector = i18n("error_cant_be_blank");
|
|
}
|
|
if (Object.keys(errors).length > 0) {
|
|
setErrors(errors);
|
|
return;
|
|
}
|
|
|
|
if (editMode) {
|
|
// 编辑
|
|
setDisabled(true);
|
|
webfix.put(rule.pattern, formValues);
|
|
} else {
|
|
// 添加
|
|
webfix.add(formValues);
|
|
setShow(false);
|
|
setFormValues(initFormValues);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<form onSubmit={handleSubmit}>
|
|
<Stack spacing={3}>
|
|
<TextField
|
|
size="small"
|
|
label={i18n("pattern")}
|
|
error={!!errors.pattern}
|
|
helperText={errors.pattern}
|
|
name="pattern"
|
|
value={pattern}
|
|
disabled={disabled}
|
|
onChange={handleChange}
|
|
onFocus={handleFocus}
|
|
multiline
|
|
/>
|
|
<TextField
|
|
size="small"
|
|
label={i18n("root_selector")}
|
|
error={!!errors.rootSelector}
|
|
helperText={errors.rootSelector}
|
|
name="rootSelector"
|
|
value={rootSelector}
|
|
disabled={disabled}
|
|
onChange={handleChange}
|
|
onFocus={handleFocus}
|
|
multiline
|
|
/>
|
|
<TextField
|
|
size="small"
|
|
label={i18n("selector")}
|
|
error={!!errors.selector}
|
|
helperText={errors.selector}
|
|
name="selector"
|
|
value={selector}
|
|
disabled={disabled}
|
|
onChange={handleChange}
|
|
onFocus={handleFocus}
|
|
multiline
|
|
/>
|
|
<TextField
|
|
select
|
|
size="small"
|
|
name="fixer"
|
|
value={fixer}
|
|
label={i18n("fixer_function")}
|
|
disabled={disabled}
|
|
onChange={handleChange}
|
|
>
|
|
{FIXER_ALL.map((item) => (
|
|
<MenuItem key={item} value={item}>
|
|
{item}
|
|
</MenuItem>
|
|
))}
|
|
</TextField>
|
|
|
|
{webfix &&
|
|
(editMode ? (
|
|
// 编辑
|
|
<Stack direction="row" spacing={2}>
|
|
{disabled ? (
|
|
<>
|
|
<Button
|
|
size="small"
|
|
variant="contained"
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
setDisabled(false);
|
|
}}
|
|
>
|
|
{i18n("edit")}
|
|
</Button>
|
|
<Button
|
|
size="small"
|
|
variant="outlined"
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
webfix.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>
|
|
))}
|
|
</Stack>
|
|
</form>
|
|
);
|
|
}
|
|
|
|
function WebfixAccordion({ rule, webfix }) {
|
|
const [expanded, setExpanded] = useState(false);
|
|
|
|
const handleChange = (e) => {
|
|
setExpanded((pre) => !pre);
|
|
};
|
|
|
|
return (
|
|
<Accordion expanded={expanded} onChange={handleChange}>
|
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
|
<Typography
|
|
sx={{
|
|
opacity: webfix ? 1 : 0.5,
|
|
overflowWrap: "anywhere",
|
|
}}
|
|
>
|
|
{rule.pattern}
|
|
</Typography>
|
|
</AccordionSummary>
|
|
<AccordionDetails>
|
|
{expanded && <WebfixFields rule={rule} webfix={webfix} />}
|
|
</AccordionDetails>
|
|
</Accordion>
|
|
);
|
|
}
|
|
|
|
export default function Webfix() {
|
|
const [loading, setLoading] = useState(false);
|
|
const [sites, setSites] = useState([]);
|
|
const i18n = useI18n();
|
|
const alert = useAlert();
|
|
const { setting, updateSetting } = useSetting();
|
|
const [showAdd, setShowAdd] = useState(false);
|
|
const webfix = useWebfixRules();
|
|
|
|
const loadSites = useCallback(async () => {
|
|
const sites = await loadOrFetchWebfix(process.env.REACT_APP_WEBFIXURL);
|
|
setSites(sites);
|
|
}, []);
|
|
|
|
const handleSyncTest = async (e) => {
|
|
e.preventDefault();
|
|
try {
|
|
setLoading(true);
|
|
await syncWebfix(process.env.REACT_APP_WEBFIXURL);
|
|
await loadSites();
|
|
alert.success(i18n("sync_success"));
|
|
} catch (err) {
|
|
console.log("[sync webfix]", err);
|
|
alert.error(i18n("sync_failed"));
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
(async () => {
|
|
try {
|
|
setLoading(true);
|
|
await loadSites();
|
|
} catch (err) {
|
|
console.log("[load webfix]", err.message);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
})();
|
|
}, [loadSites]);
|
|
|
|
return (
|
|
<Box>
|
|
<Stack spacing={3}>
|
|
<Alert severity="info">{i18n("patch_setting_help")}</Alert>
|
|
|
|
<Stack
|
|
direction="row"
|
|
alignItems="center"
|
|
spacing={2}
|
|
useFlexGap
|
|
flexWrap="wrap"
|
|
>
|
|
<Button
|
|
size="small"
|
|
variant="contained"
|
|
disabled={showAdd}
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
setShowAdd(true);
|
|
}}
|
|
>
|
|
{i18n("add")}
|
|
</Button>
|
|
|
|
<Button
|
|
size="small"
|
|
variant="outlined"
|
|
disabled={loading}
|
|
onClick={handleSyncTest}
|
|
startIcon={<SyncIcon />}
|
|
>
|
|
{i18n("sync_now")}
|
|
</Button>
|
|
<HelpButton url={URL_KISS_RULES_NEW_ISSUE} />
|
|
<FormControlLabel
|
|
control={
|
|
<Switch
|
|
size="small"
|
|
checked={!!setting.injectWebfix}
|
|
onChange={() => {
|
|
updateSetting({
|
|
injectWebfix: !setting.injectWebfix,
|
|
});
|
|
}}
|
|
/>
|
|
}
|
|
label={i18n("inject_webfix")}
|
|
/>
|
|
</Stack>
|
|
|
|
{showAdd && <WebfixFields webfix={webfix} setShow={setShowAdd} />}
|
|
|
|
{webfix.list?.length > 0 && (
|
|
<Box>
|
|
{webfix.list.map((rule) => (
|
|
<WebfixAccordion key={rule.pattern} rule={rule} webfix={webfix} />
|
|
))}
|
|
</Box>
|
|
)}
|
|
|
|
{setting.injectWebfix && (
|
|
<Box>
|
|
{loading ? (
|
|
<center>
|
|
<CircularProgress size={16} />
|
|
</center>
|
|
) : (
|
|
sites.map((rule) => (
|
|
<WebfixAccordion key={rule.pattern} rule={rule} />
|
|
))
|
|
)}
|
|
</Box>
|
|
)}
|
|
</Stack>
|
|
</Box>
|
|
);
|
|
}
|