add codes
This commit is contained in:
22
src/hooks/ColorMode.js
Normal file
22
src/hooks/ColorMode.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { useSetting, useSettingUpdate } from "./Setting";
|
||||
|
||||
/**
|
||||
* 深色模式hook
|
||||
* @returns
|
||||
*/
|
||||
export function useDarkMode() {
|
||||
const setting = useSetting();
|
||||
return !!setting?.darkMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换深色模式
|
||||
* @returns
|
||||
*/
|
||||
export function useDarkModeSwitch() {
|
||||
const darkMode = useDarkMode();
|
||||
const updateSetting = useSettingUpdate();
|
||||
return async () => {
|
||||
await updateSetting({ darkMode: !darkMode });
|
||||
};
|
||||
}
|
||||
41
src/hooks/I18n.js
Normal file
41
src/hooks/I18n.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import { useSetting } from "./Setting";
|
||||
import { I18N, URL_RAW_PREFIX } from "../config";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
/**
|
||||
* 多语言 hook
|
||||
* @returns
|
||||
*/
|
||||
export const useI18n = () => {
|
||||
const { uiLang } = useSetting() ?? {};
|
||||
return (key, defaultText = "") => I18N?.[key]?.[uiLang] ?? defaultText;
|
||||
};
|
||||
|
||||
export const useI18nMd = (key) => {
|
||||
const i18n = useI18n();
|
||||
const [md, setMd] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
const fileName = i18n(key);
|
||||
|
||||
useEffect(() => {
|
||||
if (!fileName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const url = `${URL_RAW_PREFIX}/${fileName}`;
|
||||
setLoading(true);
|
||||
fetch(url)
|
||||
.then((res) => {
|
||||
if (res.ok) {
|
||||
return res.text().then(setMd);
|
||||
}
|
||||
setError(`[${res.status}] ${res.statusText}`);
|
||||
})
|
||||
.catch(setError)
|
||||
.finally(() => setLoading(false));
|
||||
}, [fileName]);
|
||||
|
||||
return [md, loading, error];
|
||||
};
|
||||
99
src/hooks/Rules.js
Normal file
99
src/hooks/Rules.js
Normal file
@@ -0,0 +1,99 @@
|
||||
import {
|
||||
STOKEY_RULES,
|
||||
OPT_TRANS_ALL,
|
||||
OPT_STYLE_ALL,
|
||||
OPT_LANGS_FROM,
|
||||
OPT_LANGS_TO,
|
||||
} from "../config";
|
||||
import storage from "../libs/storage";
|
||||
import { useStorages } from "./Storage";
|
||||
import { matchValue } from "../libs/utils";
|
||||
|
||||
/**
|
||||
* 匹配规则增删改查 hook
|
||||
* @returns
|
||||
*/
|
||||
export function useRules() {
|
||||
const storages = useStorages();
|
||||
let rules = storages?.[STOKEY_RULES] || [];
|
||||
|
||||
const add = async (rule) => {
|
||||
rules = [...rules];
|
||||
if (rule.pattern === "*") {
|
||||
return;
|
||||
}
|
||||
if (rules.map((item) => item.pattern).includes(rule.pattern)) {
|
||||
return;
|
||||
}
|
||||
await storage.setObj(STOKEY_RULES, [rule, ...rules]);
|
||||
};
|
||||
|
||||
const del = async (pattern) => {
|
||||
rules = [...rules];
|
||||
if (pattern === "*") {
|
||||
return;
|
||||
}
|
||||
await storage.setObj(
|
||||
STOKEY_RULES,
|
||||
rules.filter((item) => item.pattern !== pattern)
|
||||
);
|
||||
};
|
||||
|
||||
const put = async (index, obj) => {
|
||||
rules = [...rules];
|
||||
if (!rules[index]) {
|
||||
return;
|
||||
}
|
||||
if (index === rules.length - 1) {
|
||||
obj.pattern = "*";
|
||||
}
|
||||
rules[index] = { ...rules[index], ...obj };
|
||||
await storage.setObj(STOKEY_RULES, rules);
|
||||
};
|
||||
|
||||
const merge = async (newRules) => {
|
||||
const fromLangs = OPT_LANGS_FROM.map((item) => item[0]);
|
||||
const toLangs = OPT_LANGS_TO.map((item) => item[0]);
|
||||
rules = [...rules];
|
||||
newRules
|
||||
.filter(
|
||||
({ pattern, selector }) =>
|
||||
pattern &&
|
||||
selector &&
|
||||
typeof pattern === "string" &&
|
||||
typeof selector === "string"
|
||||
)
|
||||
.map(
|
||||
({
|
||||
pattern,
|
||||
selector,
|
||||
translator,
|
||||
fromLang,
|
||||
toLang,
|
||||
textStyle,
|
||||
transOpen,
|
||||
}) => ({
|
||||
pattern,
|
||||
selector,
|
||||
translator: matchValue(OPT_TRANS_ALL, translator),
|
||||
fromLang: matchValue(fromLangs, fromLang),
|
||||
toLang: matchValue(toLangs, toLang),
|
||||
textStyle: matchValue(OPT_STYLE_ALL, textStyle),
|
||||
transOpen: matchValue([true, false], transOpen),
|
||||
})
|
||||
)
|
||||
.forEach((newRule) => {
|
||||
const rule = rules.find(
|
||||
(oldRule) => oldRule.pattern === newRule.pattern
|
||||
);
|
||||
if (rule) {
|
||||
Object.assign(rule, newRule);
|
||||
} else {
|
||||
rules.unshift(newRule);
|
||||
}
|
||||
});
|
||||
await storage.setObj(STOKEY_RULES, rules);
|
||||
};
|
||||
|
||||
return [rules, add, del, put, merge];
|
||||
}
|
||||
22
src/hooks/Setting.js
Normal file
22
src/hooks/Setting.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { STOKEY_SETTING } from "../config";
|
||||
import storage from "../libs/storage";
|
||||
import { useStorages } from "./Storage";
|
||||
|
||||
/**
|
||||
* 设置hook
|
||||
* @returns
|
||||
*/
|
||||
export function useSetting() {
|
||||
const storages = useStorages();
|
||||
return storages?.[STOKEY_SETTING];
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新设置
|
||||
* @returns
|
||||
*/
|
||||
export function useSettingUpdate() {
|
||||
return async (obj) => {
|
||||
await storage.putObj(STOKEY_SETTING, obj);
|
||||
};
|
||||
}
|
||||
86
src/hooks/Storage.js
Normal file
86
src/hooks/Storage.js
Normal file
@@ -0,0 +1,86 @@
|
||||
import { createContext, useContext, useEffect, useState } from "react";
|
||||
import browser from "../libs/browser";
|
||||
import {
|
||||
STOKEY_SETTING,
|
||||
STOKEY_RULES,
|
||||
STOKEY_MSAUTH,
|
||||
DEFAULT_SETTING,
|
||||
DEFAULT_RULES,
|
||||
} from "../config";
|
||||
import storage from "../libs/storage";
|
||||
|
||||
/**
|
||||
* 默认配置
|
||||
*/
|
||||
export const defaultStorage = {
|
||||
[STOKEY_MSAUTH]: null,
|
||||
[STOKEY_SETTING]: DEFAULT_SETTING,
|
||||
[STOKEY_RULES]: DEFAULT_RULES,
|
||||
};
|
||||
|
||||
const StoragesContext = createContext(null);
|
||||
|
||||
export function StoragesProvider({ children }) {
|
||||
const [storages, setStorages] = useState(null);
|
||||
|
||||
const handleChanged = (changes) => {
|
||||
if (!browser) {
|
||||
const { key, oldValue, newValue } = changes;
|
||||
changes = {
|
||||
[key]: {
|
||||
oldValue,
|
||||
newValue,
|
||||
},
|
||||
};
|
||||
}
|
||||
const newStorages = {};
|
||||
Object.entries(changes)
|
||||
.filter(([_, { oldValue, newValue }]) => oldValue !== newValue)
|
||||
.forEach(([key, { newValue }]) => {
|
||||
newStorages[key] = JSON.parse(newValue);
|
||||
});
|
||||
if (Object.keys(newStorages).length !== 0) {
|
||||
setStorages((pre) => ({ ...pre, ...newStorages }));
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// 首次从storage同步配置到内存
|
||||
(async () => {
|
||||
const curStorages = {};
|
||||
const keys = Object.keys(defaultStorage);
|
||||
for (const key of keys) {
|
||||
const val = await storage.get(key);
|
||||
if (val) {
|
||||
curStorages[key] = JSON.parse(val);
|
||||
} else {
|
||||
await storage.setObj(key, defaultStorage[key]);
|
||||
curStorages[key] = defaultStorage[key];
|
||||
}
|
||||
}
|
||||
setStorages(curStorages);
|
||||
})();
|
||||
|
||||
// 监听storage,并同步到内存中
|
||||
storage.onChanged(handleChanged);
|
||||
|
||||
// 解除监听
|
||||
return () => {
|
||||
if (browser?.storage) {
|
||||
browser.storage.onChanged.removeListener(handleChanged);
|
||||
} else {
|
||||
window.removeEventListener("storage", handleChanged);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<StoragesContext.Provider value={storages}>
|
||||
{children}
|
||||
</StoragesContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useStorages() {
|
||||
return useContext(StoragesContext);
|
||||
}
|
||||
30
src/hooks/Theme.js
Normal file
30
src/hooks/Theme.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import { useMemo } from "react";
|
||||
import { ThemeProvider, createTheme } from "@mui/material/styles";
|
||||
import CssBaseline from "@mui/material/CssBaseline";
|
||||
import { useDarkMode } from "./ColorMode";
|
||||
import { THEME_DARK, THEME_LIGHT } from "../config";
|
||||
|
||||
/**
|
||||
* mui 主题配置
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export default function MuiThemeProvider({ children, options }) {
|
||||
const darkMode = useDarkMode();
|
||||
const theme = useMemo(() => {
|
||||
return createTheme({
|
||||
palette: {
|
||||
mode: darkMode ? THEME_DARK : THEME_LIGHT,
|
||||
},
|
||||
...options,
|
||||
});
|
||||
}, [darkMode, options]);
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
|
||||
<CssBaseline />
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
79
src/hooks/Translate.js
Normal file
79
src/hooks/Translate.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import { useEffect } from "react";
|
||||
import { useState } from "react";
|
||||
import { apiTranslate } from "../apis";
|
||||
import browser from "../libs/browser";
|
||||
import {
|
||||
TRANS_MIN_LENGTH,
|
||||
TRANS_MAX_LENGTH,
|
||||
MSG_TRANS_PUTRULE,
|
||||
DEFAULT_FETCH_LIMIT,
|
||||
MSG_FETCH_LIMIT,
|
||||
} from "../config";
|
||||
import { useSetting } from "./Setting";
|
||||
import { sendMsg } from "../libs/msg";
|
||||
import { detectLang } from "../libs";
|
||||
|
||||
/**
|
||||
* 翻译hook
|
||||
* @param {*} q
|
||||
* @returns
|
||||
*/
|
||||
export function useTranslate(q, initRule) {
|
||||
const [text, setText] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [sameLang, setSamelang] = useState(false);
|
||||
const [rule, setRule] = useState(initRule);
|
||||
const { fetchLimit = DEFAULT_FETCH_LIMIT } = useSetting() || {};
|
||||
|
||||
const { translator, fromLang, toLang, textStyle } = rule;
|
||||
|
||||
const handleMessage = ({ action, args }) => {
|
||||
if (action === MSG_TRANS_PUTRULE) {
|
||||
setRule((pre) => ({ ...pre, ...args }));
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
browser?.runtime.onMessage.addListener(handleMessage);
|
||||
return () => {
|
||||
browser?.runtime.onMessage.removeListener(handleMessage);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
sendMsg(MSG_FETCH_LIMIT, { limit: fetchLimit });
|
||||
}, [fetchLimit]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
// 太长或太短不翻译
|
||||
if (q.length < TRANS_MIN_LENGTH || q.length > TRANS_MAX_LENGTH) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
const deLang = await detectLang(q);
|
||||
if (toLang.includes(deLang)) {
|
||||
setSamelang(true);
|
||||
} else {
|
||||
const [trText, isSame] = await apiTranslate({
|
||||
translator,
|
||||
q,
|
||||
fromLang,
|
||||
toLang,
|
||||
});
|
||||
setText(trText);
|
||||
setSamelang(isSame);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("[translate]", err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
})();
|
||||
}, [q, translator, fromLang, toLang]);
|
||||
|
||||
return { text, sameLang, loading, textStyle };
|
||||
}
|
||||
Reference in New Issue
Block a user