add codes

This commit is contained in:
Gabe Yuan
2023-07-20 13:45:41 +08:00
parent 10183e3013
commit 0041d6d528
44 changed files with 13020 additions and 0 deletions

22
src/hooks/ColorMode.js Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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 };
}