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 CircularProgress from "@mui/material/CircularProgress";
import Alert from "@mui/material/Alert";
import {
GLOBAL_KEY,
DEFAULT_RULE,
OPT_LANGS_FROM,
OPT_LANGS_TO,
OPT_TRANS_ALL,
OPT_STYLE_ALL,
OPT_STYLE_DIY,
OPT_STYLE_USE_COLOR,
URL_KISS_RULES_NEW_ISSUE,
} from "../../config";
import { useState, useRef, useEffect, useMemo } 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";
import { useSetting } from "../../hooks/Setting";
import FormControlLabel from "@mui/material/FormControlLabel";
import Switch from "@mui/material/Switch";
import Tabs from "@mui/material/Tabs";
import Tab from "@mui/material/Tab";
import Radio from "@mui/material/Radio";
import RadioGroup from "@mui/material/RadioGroup";
import DeleteIcon from "@mui/icons-material/Delete";
import IconButton from "@mui/material/IconButton";
import ShareIcon from "@mui/icons-material/Share";
import SyncIcon from "@mui/icons-material/Sync";
import { useSubRules } from "../../hooks/SubRules";
import { syncSubRules } from "../../libs/subRules";
import { loadOrFetchSubRules } from "../../libs/subRules";
import { useAlert } from "../../hooks/Alert";
import { syncShareRules } from "../../libs/sync";
import { debounce } from "../../libs/utils";
import { delSubRules, getSyncWithDefault } from "../../libs/storage";
import OwSubRule from "./OwSubRule";
import ClearAllIcon from "@mui/icons-material/ClearAll";
import HelpButton from "./HelpButton";
function RuleFields({ rule, rules, setShow, setKeyword }) {
const initFormValues = rule || {
...DEFAULT_RULE,
transOpen: "true",
};
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,
bgColor,
textDiyStyle,
} = formValues;
const hasSamePattern = (str) => {
for (const item of rules.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 handlePatternChange = useMemo(
() =>
debounce(async (patterns) => {
setKeyword(patterns.trim());
}, 500),
[setKeyword]
);
const handleChange = (e) => {
e.preventDefault();
const { name, value } = e.target;
setFormValues((pre) => ({ ...pre, [name]: value }));
if (name === "pattern" && !editMode) {
handlePatternChange(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");
}
if (pattern === "*" && !errors.pattern && !selector.trim()) {
errors.selector = i18n("error_cant_be_blank");
}
if (Object.keys(errors).length > 0) {
setErrors(errors);
return;
}
if (editMode) {
// 编辑
setDisabled(true);
rules.put(rule.pattern, formValues);
} else {
// 添加
rules.add(formValues);
setShow(false);
setFormValues(initFormValues);
}
};
const GlobalItem = rule?.pattern !== "*" && (
);
return (
);
}
function RuleAccordion({ rule, rules }) {
const [expanded, setExpanded] = useState(false);
const handleChange = (e) => {
setExpanded((pre) => !pre);
};
return (
}>
{rule.pattern}
{expanded && }
);
}
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 (
}
>
{text}
);
}
function UploadButton({ onChange, text }) {
const inputRef = useRef(null);
const handleClick = () => {
inputRef.current && inputRef.current.click();
};
return (
}
>
{text}
);
}
function ShareButton({ rules, injectRules, selectedUrl }) {
const alert = useAlert();
const i18n = useI18n();
const handleClick = async () => {
try {
const { syncUrl, syncKey } = await getSyncWithDefault();
if (!syncUrl || !syncKey) {
alert.warning(i18n("error_sync_setting"));
return;
}
const shareRules = [...rules.list];
if (injectRules) {
const subRules = await loadOrFetchSubRules(selectedUrl);
shareRules.splice(-1, 0, ...subRules);
}
const url = await syncShareRules({
rules: shareRules,
syncUrl,
syncKey,
});
window.open(url, "_blank");
} catch (err) {
alert.warning(i18n("error_got_some_wrong"));
console.log("[share rules]", err);
}
};
return (
}
>
{i18n("share")}
);
}
function UserRules({ subRules }) {
const i18n = useI18n();
const rules = useRules();
const [showAdd, setShowAdd] = useState(false);
const { setting, updateSetting } = useSetting();
const [keyword, setKeyword] = useState("");
const injectRules = !!setting?.injectRules;
const { selectedUrl, selectedRules } = subRules;
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 {
await rules.merge(JSON.parse(e.target.result));
} catch (err) {
console.log("[import rules]", err);
}
};
reader.readAsText(file);
};
const handleInject = () => {
updateSetting({
injectRules: !injectRules,
});
};
useEffect(() => {
if (!showAdd) {
setKeyword("");
}
}, [showAdd]);
return (
}
label={i18n("inject_rules")}
/>
{showAdd && (
)}
{rules.list
.filter(
(rule) =>
rule.pattern.includes(keyword) || keyword.includes(rule.pattern)
)
.map((rule) => (
))}
{injectRules && (
{selectedRules
.filter(
(rule) =>
rule.pattern.includes(keyword) || keyword.includes(rule.pattern)
)
.map((rule) => (
))}
)}
);
}
function SubRulesItem({
index,
url,
syncAt,
selectedUrl,
delSub,
updateSub,
setSelectedRules,
}) {
const [loading, setLoading] = useState(false);
const handleDel = async () => {
try {
await delSub(url);
await delSubRules(url);
} catch (err) {
console.log("[del subrules]", err);
}
};
const handleSync = async () => {
try {
setLoading(true);
const rules = await syncSubRules(url);
if (rules.length > 0 && url === selectedUrl) {
setSelectedRules(rules);
}
await updateSub(url, { syncAt: Date.now() });
} catch (err) {
console.log("[sync sub rules]", err);
} finally {
setLoading(false);
}
};
return (
} label={url} />
{syncAt && (
[{new Date(syncAt).toLocaleString()}]
)}
{loading ? (
) : (
)}
{index !== 0 && selectedUrl !== url && (
)}
);
}
function SubRulesEdit({ subList, addSub }) {
const i18n = useI18n();
const [inputText, setInputText] = useState("");
const [inputError, setInputError] = useState("");
const [showInput, setShowInput] = useState(false);
const [loading, setLoading] = useState(false);
const handleCancel = (e) => {
e.preventDefault();
setShowInput(false);
setInputText("");
setInputError("");
};
const handleSave = async (e) => {
e.preventDefault();
const url = inputText.trim();
if (!url) {
setInputError(i18n("error_cant_be_blank"));
return;
}
if (subList.find((item) => item.url === url)) {
setInputError(i18n("error_duplicate_values"));
return;
}
try {
setLoading(true);
const rules = await syncSubRules(url);
if (rules.length === 0) {
throw new Error("empty rules");
}
await addSub(url);
setShowInput(false);
setInputText("");
} catch (err) {
console.log("[fetch rules]", err);
setInputError(i18n("error_fetch_url"));
} finally {
setLoading(false);
}
};
const handleInput = (e) => {
e.preventDefault();
setInputText(e.target.value);
};
const handleFocus = (e) => {
e.preventDefault();
setInputError("");
};
return (
<>
{showInput && (
<>
>
)}
>
);
}
function SubRules({ subRules }) {
const {
subList,
selectSub,
updateSub,
addSub,
delSub,
selectedUrl,
selectedRules,
setSelectedRules,
loading,
} = subRules;
const handleSelect = (e) => {
const url = e.target.value;
selectSub(url);
};
return (
{subList.map((item, index) => (
))}
{loading ? (
) : (
selectedRules.map((rule) => (
))
)}
);
}
export default function Rules() {
const i18n = useI18n();
const [activeTab, setActiveTab] = useState(0);
const subRules = useSubRules();
const handleTabChange = (e, newValue) => {
setActiveTab(newValue);
};
return (
{i18n("rules_warn_1")}
{i18n("rules_warn_2")}
{activeTab === 0 && }
{activeTab === 1 && }
{activeTab === 2 && }
);
}