input box trans
This commit is contained in:
@@ -547,4 +547,28 @@ export const I18N = {
|
|||||||
zh: `全局规则`,
|
zh: `全局规则`,
|
||||||
en: `Global Rule`,
|
en: `Global Rule`,
|
||||||
},
|
},
|
||||||
|
input_setting: {
|
||||||
|
zh: `输入框设置`,
|
||||||
|
en: `Input Box Setting`,
|
||||||
|
},
|
||||||
|
input_box_translation: {
|
||||||
|
zh: `启用输入框翻译`,
|
||||||
|
en: `Input Box Translation`,
|
||||||
|
},
|
||||||
|
input_selector: {
|
||||||
|
zh: `输入框选择器`,
|
||||||
|
en: `Input Selector`,
|
||||||
|
},
|
||||||
|
input_selector_helper: {
|
||||||
|
zh: `用于输入框翻译。`,
|
||||||
|
en: `Used for input box translation.`,
|
||||||
|
},
|
||||||
|
trigger_trans_shortcut: {
|
||||||
|
zh: `触发翻译快捷键`,
|
||||||
|
en: `Trigger Translation Shortcut Keys`,
|
||||||
|
},
|
||||||
|
trigger_trans_count: {
|
||||||
|
zh: `触发翻译连击次数`,
|
||||||
|
en: `Trigger Translation Press Nunber`,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -189,6 +189,7 @@ export const DEFAULT_COLOR = "#209CEE"; // 默认高亮背景色/线条颜色
|
|||||||
export const GLOBLA_RULE = {
|
export const GLOBLA_RULE = {
|
||||||
pattern: "*",
|
pattern: "*",
|
||||||
selector: DEFAULT_SELECTOR,
|
selector: DEFAULT_SELECTOR,
|
||||||
|
inputSelector: "",
|
||||||
translator: OPT_TRANS_MICROSOFT,
|
translator: OPT_TRANS_MICROSOFT,
|
||||||
fromLang: "auto",
|
fromLang: "auto",
|
||||||
toLang: "zh-CN",
|
toLang: "zh-CN",
|
||||||
@@ -198,6 +199,15 @@ export const GLOBLA_RULE = {
|
|||||||
textDiyStyle: "",
|
textDiyStyle: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const DEFAULT_INPUT_RULE = {
|
||||||
|
transOpen: true,
|
||||||
|
translator: OPT_TRANS_MICROSOFT,
|
||||||
|
fromLang: "auto",
|
||||||
|
toLang: "en",
|
||||||
|
triggerShortcut: ["Alt", "i"],
|
||||||
|
triggerCount: 1,
|
||||||
|
};
|
||||||
|
|
||||||
// 订阅列表
|
// 订阅列表
|
||||||
export const DEFAULT_SUBRULES_LIST = [
|
export const DEFAULT_SUBRULES_LIST = [
|
||||||
{
|
{
|
||||||
@@ -273,6 +283,7 @@ export const DEFAULT_SETTING = {
|
|||||||
mouseKey: OPT_MOUSEKEY_DISABLE, // 鼠标悬停翻译
|
mouseKey: OPT_MOUSEKEY_DISABLE, // 鼠标悬停翻译
|
||||||
shortcuts: DEFAULT_SHORTCUTS, // 快捷键
|
shortcuts: DEFAULT_SHORTCUTS, // 快捷键
|
||||||
hideFab: false, // 是否隐藏按钮
|
hideFab: false, // 是否隐藏按钮
|
||||||
|
inputRule: DEFAULT_INPUT_RULE, // 输入框设置
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_RULES = [GLOBLA_RULE];
|
export const DEFAULT_RULES = [GLOBLA_RULE];
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export const SHADOW_KEY = ">>>";
|
|||||||
export const DEFAULT_RULE = {
|
export const DEFAULT_RULE = {
|
||||||
pattern: "",
|
pattern: "",
|
||||||
selector: "",
|
selector: "",
|
||||||
|
inputSelector: "",
|
||||||
translator: GLOBAL_KEY,
|
translator: GLOBAL_KEY,
|
||||||
fromLang: GLOBAL_KEY,
|
fromLang: GLOBAL_KEY,
|
||||||
toLang: GLOBAL_KEY,
|
toLang: GLOBAL_KEY,
|
||||||
|
|||||||
18
src/hooks/InputRule.js
Normal file
18
src/hooks/InputRule.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { useCallback } from "react";
|
||||||
|
import { DEFAULT_INPUT_RULE } from "../config";
|
||||||
|
import { useSetting } from "./Setting";
|
||||||
|
|
||||||
|
export function useInputRule() {
|
||||||
|
const { setting, updateSetting } = useSetting();
|
||||||
|
const inputRule = setting?.inputRule || DEFAULT_INPUT_RULE;
|
||||||
|
|
||||||
|
const updateInputRule = useCallback(
|
||||||
|
async (obj) => {
|
||||||
|
Object.assign(inputRule, obj);
|
||||||
|
await updateSetting({ inputRule });
|
||||||
|
},
|
||||||
|
[inputRule, updateSetting]
|
||||||
|
);
|
||||||
|
|
||||||
|
return { inputRule, updateInputRule };
|
||||||
|
}
|
||||||
@@ -65,3 +65,41 @@ export const shortcutRegister = (targetKeys = [], fn, target = document) => {
|
|||||||
}
|
}
|
||||||
}, target);
|
}, target);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册连续快捷键
|
||||||
|
* @param {*} targetKeys
|
||||||
|
* @param {*} fn
|
||||||
|
* @param {*} step
|
||||||
|
* @param {*} timeout
|
||||||
|
* @param {*} target
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const stepShortcutRegister = (
|
||||||
|
targetKeys = [],
|
||||||
|
fn,
|
||||||
|
step = 3,
|
||||||
|
timeout = 500,
|
||||||
|
target = document
|
||||||
|
) => {
|
||||||
|
let count = 0;
|
||||||
|
let pre = Date.now();
|
||||||
|
return shortcutListener((curkeys) => {
|
||||||
|
if (targetKeys.length > 0) {
|
||||||
|
const now = Date.now();
|
||||||
|
if (
|
||||||
|
(count === 0 || now - pre < timeout) &&
|
||||||
|
isSameSet(new Set(targetKeys), new Set(curkeys))
|
||||||
|
) {
|
||||||
|
count++;
|
||||||
|
if (count === step) {
|
||||||
|
count = 0;
|
||||||
|
fn();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
pre = now;
|
||||||
|
}
|
||||||
|
}, target);
|
||||||
|
};
|
||||||
|
|||||||
134
src/views/Options/InputSetting.js
Normal file
134
src/views/Options/InputSetting.js
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
import Box from "@mui/material/Box";
|
||||||
|
import Stack from "@mui/material/Stack";
|
||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
|
import { limitNumber } from "../../libs/utils";
|
||||||
|
import { useI18n } from "../../hooks/I18n";
|
||||||
|
import { OPT_TRANS_ALL, OPT_LANGS_FROM, OPT_LANGS_TO } from "../../config";
|
||||||
|
import ShortcutInput from "./ShortcutInput";
|
||||||
|
import FormControlLabel from "@mui/material/FormControlLabel";
|
||||||
|
import Switch from "@mui/material/Switch";
|
||||||
|
import { useInputRule } from "../../hooks/InputRule";
|
||||||
|
import { useCallback } from "react";
|
||||||
|
|
||||||
|
export default function InputSetting() {
|
||||||
|
const i18n = useI18n();
|
||||||
|
const { inputRule, updateInputRule } = useInputRule();
|
||||||
|
|
||||||
|
const handleChange = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
let { name, value } = e.target;
|
||||||
|
console.log({ name, value });
|
||||||
|
switch (name) {
|
||||||
|
case "triggerCount":
|
||||||
|
value = limitNumber(value, 1, 3);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
updateInputRule({
|
||||||
|
[name]: value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleShortcutInput = useCallback(
|
||||||
|
(val) => {
|
||||||
|
updateInputRule({ triggerShortcut: val });
|
||||||
|
},
|
||||||
|
[updateInputRule]
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
transOpen,
|
||||||
|
translator,
|
||||||
|
fromLang,
|
||||||
|
toLang,
|
||||||
|
triggerShortcut,
|
||||||
|
triggerCount,
|
||||||
|
} = inputRule;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Stack spacing={3}>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
size="small"
|
||||||
|
name="transOpen"
|
||||||
|
checked={transOpen}
|
||||||
|
onChange={() => {
|
||||||
|
updateInputRule({ transOpen: !transOpen });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={i18n("input_box_translation")}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
select
|
||||||
|
size="small"
|
||||||
|
name="translator"
|
||||||
|
value={translator}
|
||||||
|
label={i18n("translate_service")}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
{OPT_TRANS_ALL.map((item) => (
|
||||||
|
<MenuItem key={item} value={item}>
|
||||||
|
{item}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
select
|
||||||
|
size="small"
|
||||||
|
name="fromLang"
|
||||||
|
value={fromLang}
|
||||||
|
label={i18n("from_lang")}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
{OPT_LANGS_FROM.map(([lang, name]) => (
|
||||||
|
<MenuItem key={lang} value={lang}>
|
||||||
|
{name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
select
|
||||||
|
size="small"
|
||||||
|
name="toLang"
|
||||||
|
value={toLang}
|
||||||
|
label={i18n("to_lang")}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
{OPT_LANGS_TO.map(([lang, name]) => (
|
||||||
|
<MenuItem key={lang} value={lang}>
|
||||||
|
{name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
|
||||||
|
<ShortcutInput
|
||||||
|
value={triggerShortcut}
|
||||||
|
onChange={handleShortcutInput}
|
||||||
|
label={i18n("trigger_trans_shortcut")}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
select
|
||||||
|
size="small"
|
||||||
|
name="triggerCount"
|
||||||
|
value={triggerCount}
|
||||||
|
label={i18n("trigger_trans_count")}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
{[1, 2, 3].map((val) => (
|
||||||
|
<MenuItem key={val} value={val}>
|
||||||
|
{val}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import { useI18n } from "../../hooks/I18n";
|
|||||||
import SyncIcon from "@mui/icons-material/Sync";
|
import SyncIcon from "@mui/icons-material/Sync";
|
||||||
import ApiIcon from "@mui/icons-material/Api";
|
import ApiIcon from "@mui/icons-material/Api";
|
||||||
import SendTimeExtensionIcon from "@mui/icons-material/SendTimeExtension";
|
import SendTimeExtensionIcon from "@mui/icons-material/SendTimeExtension";
|
||||||
|
import InputIcon from "@mui/icons-material/Input";
|
||||||
|
|
||||||
function LinkItem({ label, url, icon }) {
|
function LinkItem({ label, url, icon }) {
|
||||||
const match = useMatch(url);
|
const match = useMatch(url);
|
||||||
@@ -38,6 +39,12 @@ export default function Navigator(props) {
|
|||||||
url: "/rules",
|
url: "/rules",
|
||||||
icon: <DesignServicesIcon />,
|
icon: <DesignServicesIcon />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "input_setting",
|
||||||
|
label: i18n("input_setting"),
|
||||||
|
url: "/input",
|
||||||
|
icon: <InputIcon />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "apis_setting",
|
id: "apis_setting",
|
||||||
label: i18n("apis_setting"),
|
label: i18n("apis_setting"),
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
|
|||||||
const {
|
const {
|
||||||
pattern,
|
pattern,
|
||||||
selector,
|
selector,
|
||||||
|
inputSelector = "",
|
||||||
translator,
|
translator,
|
||||||
fromLang,
|
fromLang,
|
||||||
toLang,
|
toLang,
|
||||||
@@ -178,6 +179,17 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
|
|||||||
onFocus={handleFocus}
|
onFocus={handleFocus}
|
||||||
multiline
|
multiline
|
||||||
/>
|
/>
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
label={i18n("input_selector")}
|
||||||
|
helperText={i18n("input_selector_helper")}
|
||||||
|
name="inputSelector"
|
||||||
|
value={inputSelector}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={handleChange}
|
||||||
|
onFocus={handleFocus}
|
||||||
|
multiline
|
||||||
|
/>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Grid container spacing={2} columns={12}>
|
<Grid container spacing={2} columns={12}>
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ import { limitNumber } from "../../libs/utils";
|
|||||||
import { useI18n } from "../../hooks/I18n";
|
import { useI18n } from "../../hooks/I18n";
|
||||||
import { useAlert } from "../../hooks/Alert";
|
import { useAlert } from "../../hooks/Alert";
|
||||||
import { isExt } from "../../libs/client";
|
import { isExt } from "../../libs/client";
|
||||||
import IconButton from "@mui/material/IconButton";
|
|
||||||
import EditIcon from "@mui/icons-material/Edit";
|
|
||||||
import Grid from "@mui/material/Grid";
|
import Grid from "@mui/material/Grid";
|
||||||
import {
|
import {
|
||||||
UI_LANGS,
|
UI_LANGS,
|
||||||
@@ -26,57 +24,13 @@ import {
|
|||||||
OPT_SHORTCUT_POPUP,
|
OPT_SHORTCUT_POPUP,
|
||||||
OPT_SHORTCUT_SETTING,
|
OPT_SHORTCUT_SETTING,
|
||||||
} from "../../config";
|
} from "../../config";
|
||||||
import { useEffect, useState, useRef } from "react";
|
|
||||||
import { useShortcut } from "../../hooks/Shortcut";
|
import { useShortcut } from "../../hooks/Shortcut";
|
||||||
import { shortcutListener } from "../../libs/shortcut";
|
import ShortcutInput from "./ShortcutInput";
|
||||||
|
|
||||||
function ShortcutItem({ action, label }) {
|
function ShortcutItem({ action, label }) {
|
||||||
const { shortcut, setShortcut } = useShortcut(action);
|
const { shortcut, setShortcut } = useShortcut(action);
|
||||||
const [disabled, setDisabled] = useState(true);
|
|
||||||
const inputRef = useRef(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (disabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
inputRef.current.focus();
|
|
||||||
setShortcut([]);
|
|
||||||
|
|
||||||
const clearShortcut = shortcutListener((curkeys, allkeys) => {
|
|
||||||
setShortcut(allkeys);
|
|
||||||
if (curkeys.length === 0) {
|
|
||||||
setDisabled(true);
|
|
||||||
}
|
|
||||||
}, inputRef.current);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
clearShortcut();
|
|
||||||
};
|
|
||||||
}, [disabled, setShortcut]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack direction="row">
|
<ShortcutInput value={shortcut} onChange={setShortcut} label={label} />
|
||||||
<TextField
|
|
||||||
size="small"
|
|
||||||
label={label}
|
|
||||||
name={label}
|
|
||||||
value={shortcut.join(" + ")}
|
|
||||||
fullWidth
|
|
||||||
inputRef={inputRef}
|
|
||||||
disabled={disabled}
|
|
||||||
onBlur={() => {
|
|
||||||
setDisabled(true);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
onClick={() => {
|
|
||||||
setDisabled(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{<EditIcon />}
|
|
||||||
</IconButton>
|
|
||||||
</Stack>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
55
src/views/Options/ShortcutInput.js
Normal file
55
src/views/Options/ShortcutInput.js
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import Stack from "@mui/material/Stack";
|
||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
import IconButton from "@mui/material/IconButton";
|
||||||
|
import EditIcon from "@mui/icons-material/Edit";
|
||||||
|
import { useEffect, useState, useRef } from "react";
|
||||||
|
import { shortcutListener } from "../../libs/shortcut";
|
||||||
|
|
||||||
|
export default function ShortcutInput({ value, onChange, label }) {
|
||||||
|
const [disabled, setDisabled] = useState(true);
|
||||||
|
const inputRef = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (disabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
inputRef.current.focus();
|
||||||
|
onChange([]);
|
||||||
|
|
||||||
|
const clearShortcut = shortcutListener((curkeys, allkeys) => {
|
||||||
|
onChange(allkeys);
|
||||||
|
if (curkeys.length === 0) {
|
||||||
|
setDisabled(true);
|
||||||
|
}
|
||||||
|
}, inputRef.current);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearShortcut();
|
||||||
|
};
|
||||||
|
}, [disabled, onChange]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack direction="row">
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
label={label}
|
||||||
|
name={label}
|
||||||
|
value={value.map((item) => (item === " " ? "Space" : item)).join(" + ")}
|
||||||
|
fullWidth
|
||||||
|
inputRef={inputRef}
|
||||||
|
disabled={disabled}
|
||||||
|
onBlur={() => {
|
||||||
|
setDisabled(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
setDisabled(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{<EditIcon />}
|
||||||
|
</IconButton>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ import { adaptScript } from "../../libs/gm";
|
|||||||
import Alert from "@mui/material/Alert";
|
import Alert from "@mui/material/Alert";
|
||||||
import Apis from "./Apis";
|
import Apis from "./Apis";
|
||||||
import Webfix from "./Webfix";
|
import Webfix from "./Webfix";
|
||||||
|
import InputSetting from "./InputSetting";
|
||||||
|
|
||||||
export default function Options() {
|
export default function Options() {
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
@@ -82,16 +83,20 @@ export default function Options() {
|
|||||||
</h2>
|
</h2>
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
<Link href={process.env.REACT_APP_USERSCRIPT_DOWNLOADURL}>
|
<Link href={process.env.REACT_APP_USERSCRIPT_DOWNLOADURL}>
|
||||||
Install Userscript for Tampermonkey/Violentmonkey 1 (油猴脚本 安装地址 1)
|
Install Userscript for Tampermonkey/Violentmonkey 1 (油猴脚本
|
||||||
|
安装地址 1)
|
||||||
</Link>
|
</Link>
|
||||||
<Link href={process.env.REACT_APP_USERSCRIPT_DOWNLOADURL2}>
|
<Link href={process.env.REACT_APP_USERSCRIPT_DOWNLOADURL2}>
|
||||||
Install Userscript for Tampermonkey/Violentmonkey 2 (油猴脚本 安装地址 2)
|
Install Userscript for Tampermonkey/Violentmonkey 2 (油猴脚本
|
||||||
|
安装地址 2)
|
||||||
</Link>
|
</Link>
|
||||||
<Link href={process.env.REACT_APP_USERSCRIPT_IOS_DOWNLOADURL}>
|
<Link href={process.env.REACT_APP_USERSCRIPT_IOS_DOWNLOADURL}>
|
||||||
Install Userscript for iOS Safari 1 (油猴脚本 iOS Safari专用 安装地址 1)
|
Install Userscript for iOS Safari 1 (油猴脚本 iOS Safari专用
|
||||||
|
安装地址 1)
|
||||||
</Link>
|
</Link>
|
||||||
<Link href={process.env.REACT_APP_USERSCRIPT_IOS_DOWNLOADURL2}>
|
<Link href={process.env.REACT_APP_USERSCRIPT_IOS_DOWNLOADURL2}>
|
||||||
Install Userscript for iOS Safari 2 (油猴脚本 iOS Safari专用 安装地址 2)
|
Install Userscript for iOS Safari 2 (油猴脚本 iOS Safari专用
|
||||||
|
安装地址 2)
|
||||||
</Link>
|
</Link>
|
||||||
<Link href={process.env.REACT_APP_OPTIONSPAGE}>
|
<Link href={process.env.REACT_APP_OPTIONSPAGE}>
|
||||||
Open Options Page 1 (打开设置页面 1)
|
Open Options Page 1 (打开设置页面 1)
|
||||||
@@ -126,6 +131,7 @@ export default function Options() {
|
|||||||
<Route path="/" element={<Layout />}>
|
<Route path="/" element={<Layout />}>
|
||||||
<Route index element={<Setting />} />
|
<Route index element={<Setting />} />
|
||||||
<Route path="rules" element={<Rules />} />
|
<Route path="rules" element={<Rules />} />
|
||||||
|
<Route path="input" element={<InputSetting />} />
|
||||||
<Route path="apis" element={<Apis />} />
|
<Route path="apis" element={<Apis />} />
|
||||||
<Route path="sync" element={<SyncSetting />} />
|
<Route path="sync" element={<SyncSetting />} />
|
||||||
<Route path="webfix" element={<Webfix />} />
|
<Route path="webfix" element={<Webfix />} />
|
||||||
|
|||||||
Reference in New Issue
Block a user