optimize debounce form & sync data

This commit is contained in:
Gabe Yuan
2023-08-17 13:27:22 +08:00
parent 30af4c11d0
commit 5d2e767e74
10 changed files with 155 additions and 144 deletions

View File

@@ -20,7 +20,7 @@ import { fetchData, fetchPool } from "./libs/fetch";
* 插件安装 * 插件安装
*/ */
browser.runtime.onInstalled.addListener(() => { browser.runtime.onInstalled.addListener(() => {
console.log("onInstalled"); console.log("KISS Translator onInstalled");
storage.trySetObj(STOKEY_SETTING, DEFAULT_SETTING); storage.trySetObj(STOKEY_SETTING, DEFAULT_SETTING);
storage.trySetObj(STOKEY_RULES, DEFAULT_RULES); storage.trySetObj(STOKEY_RULES, DEFAULT_RULES);
storage.trySetObj(STOKEY_SYNC, DEFAULT_SYNC); storage.trySetObj(STOKEY_SYNC, DEFAULT_SYNC);
@@ -30,7 +30,7 @@ browser.runtime.onInstalled.addListener(() => {
* 浏览器启动 * 浏览器启动
*/ */
browser.runtime.onStartup.addListener(async () => { browser.runtime.onStartup.addListener(async () => {
console.log("onStartup"); console.log("browser onStartup");
// 同步数据 // 同步数据
await syncAll(); await syncAll();

View File

@@ -25,11 +25,7 @@ export function useRules() {
const updateAt = sync.opt?.rulesUpdateAt ? Date.now() : 0; const updateAt = sync.opt?.rulesUpdateAt ? Date.now() : 0;
await storage.setObj(STOKEY_RULES, rules); await storage.setObj(STOKEY_RULES, rules);
await sync.update({ rulesUpdateAt: updateAt }); await sync.update({ rulesUpdateAt: updateAt });
try { syncRules();
await syncRules();
} catch (err) {
console.log("[sync rules]", err);
}
}; };
const add = async (rule) => { const add = async (rule) => {

View File

@@ -2,6 +2,7 @@ import { STOKEY_SETTING } from "../config";
import storage from "../libs/storage"; import storage from "../libs/storage";
import { useStorages } from "./Storage"; import { useStorages } from "./Storage";
import { useSync } from "./Sync"; import { useSync } from "./Sync";
import { syncSetting } from "../libs/sync";
/** /**
* 设置hook * 设置hook
@@ -22,5 +23,6 @@ export function useSettingUpdate() {
const updateAt = sync.opt?.settingUpdateAt ? Date.now() : 0; const updateAt = sync.opt?.settingUpdateAt ? Date.now() : 0;
await storage.putObj(STOKEY_SETTING, obj); await storage.putObj(STOKEY_SETTING, obj);
await sync.update({ settingUpdateAt: updateAt }); await sync.update({ settingUpdateAt: updateAt });
syncSetting();
}; };
} }

View File

@@ -13,62 +13,66 @@ import { apiSyncData } from "../apis";
const loadOpt = async () => (await storage.getObj(STOKEY_SYNC)) || DEFAULT_SYNC; const loadOpt = async () => (await storage.getObj(STOKEY_SYNC)) || DEFAULT_SYNC;
export const syncSetting = async () => { export const syncSetting = async () => {
const { syncUrl, syncKey, settingUpdateAt } = await loadOpt(); try {
if (!syncUrl || !syncKey) { const { syncUrl, syncKey, settingUpdateAt } = await loadOpt();
return; if (!syncUrl || !syncKey) {
} return;
}
const setting = await getSetting(); const setting = await getSetting();
const res = await apiSyncData(syncUrl, syncKey, { const res = await apiSyncData(syncUrl, syncKey, {
key: KV_SETTING_KEY, key: KV_SETTING_KEY,
value: setting, value: setting,
updateAt: settingUpdateAt, updateAt: settingUpdateAt,
}); });
if (res && res.updateAt > settingUpdateAt) { if (res && res.updateAt > settingUpdateAt) {
await storage.putObj(STOKEY_SYNC, { await storage.putObj(STOKEY_SYNC, {
settingUpdateAt: res.updateAt, settingUpdateAt: res.updateAt,
settingSyncAt: res.updateAt, settingSyncAt: res.updateAt,
}); });
await storage.setObj(STOKEY_SETTING, res.value); await storage.setObj(STOKEY_SETTING, res.value);
} else { } else {
await storage.putObj(STOKEY_SYNC, { await storage.putObj(STOKEY_SYNC, {
settingSyncAt: res.updateAt, settingSyncAt: res.updateAt,
}); });
}
} catch (err) {
console.log("[sync setting]", err);
} }
}; };
export const syncRules = async () => { export const syncRules = async () => {
const { syncUrl, syncKey, rulesUpdateAt } = await loadOpt(); try {
if (!syncUrl || !syncKey) { const { syncUrl, syncKey, rulesUpdateAt } = await loadOpt();
return; if (!syncUrl || !syncKey) {
} return;
}
const rules = await getRules(); const rules = await getRules();
const res = await apiSyncData(syncUrl, syncKey, { const res = await apiSyncData(syncUrl, syncKey, {
key: KV_RULES_KEY, key: KV_RULES_KEY,
value: rules, value: rules,
updateAt: rulesUpdateAt, updateAt: rulesUpdateAt,
}); });
if (res && res.updateAt > rulesUpdateAt) { if (res && res.updateAt > rulesUpdateAt) {
await storage.putObj(STOKEY_SYNC, { await storage.putObj(STOKEY_SYNC, {
rulesUpdateAt: res.updateAt, rulesUpdateAt: res.updateAt,
rulesSyncAt: res.updateAt, rulesSyncAt: res.updateAt,
}); });
await storage.setObj(STOKEY_RULES, res.value); await storage.setObj(STOKEY_RULES, res.value);
} else { } else {
await storage.putObj(STOKEY_SYNC, { await storage.putObj(STOKEY_SYNC, {
rulesSyncAt: res.updateAt, rulesSyncAt: res.updateAt,
}); });
}
} catch (err) {
console.log("[sync rules]", err);
} }
}; };
export const syncAll = async () => { export const syncAll = async () => {
try { await syncSetting();
await syncSetting(); await syncRules();
await syncRules();
} catch (err) {
console.log("[sync all]", err);
}
}; };

View File

@@ -30,6 +30,8 @@ const getEdgePosition = (
edge = "top"; edge = "top";
top = 0; top = 0;
} }
left = limitNumber(left, 0, windowWidth - width);
top = limitNumber(top, 0, windowHeight - height);
return { x: left, y: top, edge, hide: false }; return { x: left, y: top, edge, hide: false };
}; };

View File

@@ -6,7 +6,6 @@ import Box from "@mui/material/Box";
import Navigator from "./Navigator"; import Navigator from "./Navigator";
import Header from "./Header"; import Header from "./Header";
import { useTheme } from "@mui/material/styles"; import { useTheme } from "@mui/material/styles";
import { syncAll } from "../../libs/sync";
export default function Layout() { export default function Layout() {
const navWidth = 256; const navWidth = 256;
@@ -21,7 +20,6 @@ export default function Layout() {
useEffect(() => { useEffect(() => {
setOpen(false); setOpen(false);
syncAll();
}, [location]); }, [location]);
return ( return (

View File

@@ -122,6 +122,7 @@ function RuleFields({ rule, rules, setShow }) {
disabled={rule?.pattern === "*" || disabled} disabled={rule?.pattern === "*" || disabled}
onChange={handleChange} onChange={handleChange}
onFocus={handleFocus} onFocus={handleFocus}
multiline
/> />
<TextField <TextField
size="small" size="small"
@@ -134,8 +135,6 @@ function RuleFields({ rule, rules, setShow }) {
onChange={handleChange} onChange={handleChange}
onFocus={handleFocus} onFocus={handleFocus}
multiline multiline
minRows={2}
maxRows={10}
/> />
<Box> <Box>
@@ -301,6 +300,25 @@ function RuleFields({ rule, rules, setShow }) {
); );
} }
function RuleAccordion({ rule, rules }) {
const [expanded, setExpanded] = useState(false);
const handleChange = (e) => {
setExpanded((pre) => !pre);
};
return (
<Accordion expanded={expanded} onChange={handleChange}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography>{rule.pattern}</Typography>
</AccordionSummary>
<AccordionDetails>
{expanded && <RuleFields rule={rule} rules={rules} />}
</AccordionDetails>
</Accordion>
);
}
function DownloadButton({ data, text, fileName }) { function DownloadButton({ data, text, fileName }) {
const handleClick = (e) => { const handleClick = (e) => {
e.preventDefault(); e.preventDefault();
@@ -405,14 +423,7 @@ export default function Rules() {
<Box> <Box>
{rules.list.map((rule) => ( {rules.list.map((rule) => (
<Accordion key={rule.pattern}> <RuleAccordion key={rule.pattern} rule={rule} rules={rules} />
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography>{rule.pattern}</Typography>
</AccordionSummary>
<AccordionDetails>
<RuleFields rule={rule} rules={rules} />
</AccordionDetails>
</Accordion>
))} ))}
</Box> </Box>
</Stack> </Stack>

View File

@@ -6,15 +6,37 @@ import MenuItem from "@mui/material/MenuItem";
import FormControl from "@mui/material/FormControl"; import FormControl from "@mui/material/FormControl";
import Select from "@mui/material/Select"; import Select from "@mui/material/Select";
import { useSetting, useSettingUpdate } from "../../hooks/Setting"; import { useSetting, useSettingUpdate } from "../../hooks/Setting";
import { limitNumber } from "../../libs/utils"; import { limitNumber, debounce } from "../../libs/utils";
import { useI18n } from "../../hooks/I18n"; import { useI18n } from "../../hooks/I18n";
import { UI_LANGS } from "../../config"; import { UI_LANGS } from "../../config";
import { useMemo } from "react";
export default function Settings() { export default function Settings() {
const i18n = useI18n(); const i18n = useI18n();
const setting = useSetting(); const setting = useSetting();
const updateSetting = useSettingUpdate(); const updateSetting = useSettingUpdate();
const handleChange = useMemo(
() =>
debounce((e) => {
e.preventDefault();
let { name, value } = e.target;
switch (name) {
case "fetchLimit":
value = limitNumber(value, 1, 100);
break;
case "fetchInterval":
value = limitNumber(value, 0, 5000);
break;
default:
}
updateSetting({
[name]: value,
});
}, 500),
[updateSetting]
);
if (!setting) { if (!setting) {
return; return;
} }
@@ -37,13 +59,10 @@ export default function Settings() {
<FormControl size="small"> <FormControl size="small">
<InputLabel>{i18n("ui_lang")}</InputLabel> <InputLabel>{i18n("ui_lang")}</InputLabel>
<Select <Select
name="uiLang"
value={uiLang} value={uiLang}
label={i18n("ui_lang")} label={i18n("ui_lang")}
onChange={(e) => { onChange={handleChange}
updateSetting({
uiLang: e.target.value,
});
}}
> >
{UI_LANGS.map(([lang, name]) => ( {UI_LANGS.map(([lang, name]) => (
<MenuItem key={lang} value={lang}> <MenuItem key={lang} value={lang}>
@@ -57,36 +76,27 @@ export default function Settings() {
size="small" size="small"
label={i18n("fetch_limit")} label={i18n("fetch_limit")}
type="number" type="number"
name="fetchLimit"
defaultValue={fetchLimit} defaultValue={fetchLimit}
onChange={(e) => { onChange={handleChange}
updateSetting({
fetchLimit: limitNumber(e.target.value, 1, 100),
});
}}
/> />
<TextField <TextField
size="small" size="small"
label={i18n("fetch_interval")} label={i18n("fetch_interval")}
type="number" type="number"
name="fetchInterval"
defaultValue={fetchInterval} defaultValue={fetchInterval}
onChange={(e) => { onChange={handleChange}
updateSetting({
fetchInterval: limitNumber(e.target.value, 0, 5000),
});
}}
/> />
<FormControl size="small"> <FormControl size="small">
<InputLabel>{i18n("clear_cache")}</InputLabel> <InputLabel>{i18n("clear_cache")}</InputLabel>
<Select <Select
name="clearCache"
value={clearCache} value={clearCache}
label={i18n("clear_cache")} label={i18n("clear_cache")}
onChange={(e) => { onChange={handleChange}
updateSetting({
clearCache: e.target.value,
});
}}
> >
<MenuItem value={false}>{i18n("clear_cache_never")}</MenuItem> <MenuItem value={false}>{i18n("clear_cache_never")}</MenuItem>
<MenuItem value={true}>{i18n("clear_cache_restart")}</MenuItem> <MenuItem value={true}>{i18n("clear_cache_restart")}</MenuItem>
@@ -96,60 +106,43 @@ export default function Settings() {
<TextField <TextField
size="small" size="small"
label={i18n("google_api")} label={i18n("google_api")}
name="googleUrl"
defaultValue={googleUrl} defaultValue={googleUrl}
onChange={(e) => { onChange={handleChange}
updateSetting({
googleUrl: e.target.value,
});
}}
/> />
<TextField <TextField
size="small" size="small"
label={i18n("openai_api")} label={i18n("openai_api")}
name="openaiUrl"
defaultValue={openaiUrl} defaultValue={openaiUrl}
onChange={(e) => { onChange={handleChange}
updateSetting({
openaiUrl: e.target.value,
});
}}
/> />
<TextField <TextField
size="small" size="small"
type="password" type="password"
label={i18n("openai_key")} label={i18n("openai_key")}
name="openaiKey"
defaultValue={openaiKey} defaultValue={openaiKey}
onChange={(e) => { onChange={handleChange}
updateSetting({
openaiKey: e.target.value,
});
}}
/> />
<TextField <TextField
size="small" size="small"
label={i18n("openai_model")} label={i18n("openai_model")}
name="openaiModel"
defaultValue={openaiModel} defaultValue={openaiModel}
onChange={(e) => { onChange={handleChange}
updateSetting({
openaiModel: e.target.value,
});
}}
/> />
<TextField <TextField
size="small" size="small"
label={i18n("openai_prompt")} label={i18n("openai_prompt")}
name="openaiPrompt"
defaultValue={openaiPrompt} defaultValue={openaiPrompt}
onChange={(e) => { onChange={handleChange}
updateSetting({
openaiPrompt: e.target.value,
});
}}
multiline multiline
minRows={2}
maxRows={10}
/> />
</Stack> </Stack>
</Box> </Box>

View File

@@ -3,23 +3,33 @@ import Stack from "@mui/material/Stack";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import { useI18n } from "../../hooks/I18n"; import { useI18n } from "../../hooks/I18n";
import { useSync } from "../../hooks/Sync"; import { useSync } from "../../hooks/Sync";
import { syncAll } from "../../libs/sync";
import Alert from "@mui/material/Alert"; import Alert from "@mui/material/Alert";
import Link from "@mui/material/Link"; import Link from "@mui/material/Link";
import { URL_KISS_WORKER } from "../../config"; import { URL_KISS_WORKER } from "../../config";
import { debounce } from "../../libs/utils";
import { useMemo } from "react";
export default function SyncSetting() { export default function SyncSetting() {
const i18n = useI18n(); const i18n = useI18n();
const sync = useSync(); const sync = useSync();
const handleChange = useMemo(
() =>
debounce((e) => {
e.preventDefault();
const { name, value } = e.target;
sync.update({
[name]: value,
});
}, 500),
[sync]
);
if (!sync.opt) { if (!sync.opt) {
return; return;
} }
const { syncUrl, syncKey } = sync.opt; const { syncUrl, syncKey } = sync.opt;
const handleSyncBlur = () => {
syncAll();
};
return ( return (
<Box> <Box>
@@ -29,13 +39,9 @@ export default function SyncSetting() {
<TextField <TextField
size="small" size="small"
label={i18n("data_sync_url")} label={i18n("data_sync_url")}
name="syncUrl"
defaultValue={syncUrl} defaultValue={syncUrl}
onChange={(e) => { onChange={handleChange}
sync.update({
syncUrl: e.target.value,
});
}}
onBlur={handleSyncBlur}
helperText={ helperText={
<Link href={URL_KISS_WORKER}>{i18n("about_sync_api")}</Link> <Link href={URL_KISS_WORKER}>{i18n("about_sync_api")}</Link>
} }
@@ -45,13 +51,9 @@ export default function SyncSetting() {
size="small" size="small"
type="password" type="password"
label={i18n("data_sync_key")} label={i18n("data_sync_key")}
name="syncKey"
defaultValue={syncKey} defaultValue={syncKey}
onChange={(e) => { onChange={handleChange}
sync.update({
syncKey: e.target.value,
});
}}
onBlur={handleSyncBlur}
/> />
</Stack> </Stack>
</Box> </Box>

View File

@@ -10,31 +10,34 @@ import { useEffect, useState } from "react";
import { isGm } from "../../libs/browser"; import { isGm } from "../../libs/browser";
import { sleep } from "../../libs/utils"; import { sleep } from "../../libs/utils";
import CircularProgress from "@mui/material/CircularProgress"; import CircularProgress from "@mui/material/CircularProgress";
import { syncAll } from "../../libs/sync";
export default function Options() { export default function Options() {
const [error, setError] = useState(false); const [error, setError] = useState(false);
const [ready, setReady] = useState(false); const [ready, setReady] = useState(false);
useEffect(() => { useEffect(() => {
if (!isGm) {
return;
}
(async () => { (async () => {
let i = 0; if (isGm) {
for (;;) { // 等待GM注入
if (window.APP_NAME === process.env.REACT_APP_NAME) { let i = 0;
setReady(true); for (;;) {
break; if (window.APP_NAME === process.env.REACT_APP_NAME) {
} setReady(true);
break;
}
if (++i > 8) { if (++i > 8) {
setError(true); setError(true);
break; break;
} }
await sleep(1000); await sleep(1000);
}
} }
// 同步数据
syncAll();
})(); })();
}, []); }, []);