Files
kiss-translator/src/views/Options/Webfix.js
2023-11-14 15:15:19 +08:00

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>
);
}