Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/app/repo/repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,33 @@ export abstract class Repo<T> {
});
}

public getRecord(keys: string[]): Promise<Partial<Record<string, T>>> {
keys = keys.map((key) => this.joinKey(key));
if (this.useCache) {
return loadCache().then((cache) => {
const record: Partial<Record<string, T>> = {};
for (const key of keys) {
if (cache[key]) {
record[key] = Object.assign({}, cache[key]);
} else {
record[key] = cache[key];
}
}
return record;
});
}
return new Promise((resolve) => {
chrome.storage.local.get(keys, (result) => {
const lastError = chrome.runtime.lastError;
if (lastError) {
console.error("chrome.runtime.lastError in chrome.storage.local.get:", lastError);
// 无视storage API错误,继续执行
}
resolve(result as Partial<Record<string, T>>);
});
});
}

Comment on lines +207 to +233
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

這個是不是沒用到?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

改了写法后没用了,不过觉得以后还是需要这种操作,就留下来了

private filter(data: { [key: string]: T }, filters?: (key: string, value: T) => boolean): T[] {
const ret: T[] = [];
for (const key in data) {
Expand Down
73 changes: 24 additions & 49 deletions src/pages/components/ScriptMenuList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import type {
ScriptMenuItemOption,
} from "@App/app/service/service_worker/types";
import { popupClient, runtimeClient, scriptClient } from "@App/pages/store/features/script";
import { i18nLang, i18nName } from "@App/locales/locales";
import { i18nName } from "@App/locales/locales";

// 用于读取 metadata
const scriptDAO = new ScriptDAO();
Expand Down Expand Up @@ -329,8 +329,6 @@ type ScriptMenuEntry = ScriptMenu & {
metadata: SCMetadata;
};

let scriptDataAsyncCounter = 0;

// Popup 页面使用的脚本/选单清单元件:只负责渲染与互动,状态与持久化交由外部 client 处理。
const ScriptMenuList = React.memo(
({
Expand All @@ -346,15 +344,6 @@ const ScriptMenuList = React.memo(
currentUrl: string;
menuExpandNum: number;
}) => {
// extraData 为 undefined 时先等待异步加载完成,避免重复渲染
const [extraData, setExtraData] = useState<
| {
uuids: string;
lang: string;
metadata: Record<string, SCMetadata>;
}
| undefined
>(undefined);
const [scriptMenuList, setScriptMenuList] = useState<ScriptMenuEntry[]>([]);
const { t } = useTranslation();

Expand Down Expand Up @@ -417,48 +406,34 @@ const ScriptMenuList = React.memo(
return url;
}, [currentUrl]);

// string memo 避免 uuids 以外的改变影响
const uuids = useMemo(() => script.map((item) => item.uuid).join("\n"), [script]);
// eslint-disable-next-line react-hooks/exhaustive-deps
const lang = useMemo(() => i18nLang(), [t]); // 当 t 改变时,重新检查当前页面语言

const cache = useMemo(() => new Map<string, SCMetadata | undefined>(), []);
// 以 异步方式 取得 metadata 放入 extraData
// script 或 extraData 的更新时都会再次执行
useEffect(() => {
if (extraData && extraData.uuids === uuids && extraData.lang === lang) {
// extraData 已取得
// 把 getPopupData() 的 scriptMenuList 和 异步结果 的 metadata 合并至 scriptMenuList
const metadata = extraData.metadata;
const newScriptMenuList = script.map((item) => ({ ...item, metadata: metadata[item.uuid] || {} }));
updateScriptMenuList(newScriptMenuList);
} else {
// 取得 extraData
scriptDataAsyncCounter = (scriptDataAsyncCounter % 255) + 1; // 轮出 1 ~ 255
const lastCounter = scriptDataAsyncCounter;
scriptDAO.gets(uuids.split("\n")).then((res) => {
if (lastCounter !== scriptDataAsyncCounter) {
// 由于 state 改变,在结果取得前 useEffect 再次执行,因此需要忽略上次结果
return;
}
const metadataRecord = {} as Record<string, SCMetadata>;
const nameKey = `name:${lang}`;
for (const entry of res) {
if (entry) {
const m = entry.metadata;
const [icon] = m.icon || m.iconurl || m.icon64 || m.icon64url || [];
// metadataRecord 的储存量不影响 storage.session 但影响页面的记忆体
// 按需要可以增加其他 metadata, 例如 @match @include @exclude
metadataRecord[entry.uuid] = {
icon: [icon], // 只储存单个 icon
[nameKey]: [i18nName(entry)], // 只储存 i18n 的 name
} satisfies SCMetadata;
let isMounted = true;
// 先从 cache 读取,避免重复请求相同 uuid 的 metadata
Promise.all(
script.map(async (item) => {
let metadata = cache.get(item.uuid);
if (!metadata) {
const script = await scriptDAO.get(item.uuid);
if (script) {
metadata = script.metadata || {};
}
cache.set(item.uuid, metadata);
}
setExtraData({ uuids, lang, metadata: metadataRecord });
// 再次触发 useEffect
});
}
}, [script, uuids, lang, extraData]);
return { ...item, metadata: metadata || {} };
})
).then((newScriptMenuList) => {
if (!isMounted) {
return;
}
updateScriptMenuList(newScriptMenuList);
});
return () => {
isMounted = false;
};
}, [cache, script]);

useEffect(() => {
// 注册菜单快速键(accessKey):以各分组第一个项目的 accessKey 作为触发条件。
Expand Down
28 changes: 6 additions & 22 deletions src/pages/popup/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,39 +59,23 @@ function App() {
const { t } = useTranslation();
const pageTabIdRef = useRef(0);

// 只随 script 数量和启动状态而改变的state
const normalEnables = useMemo(() => {
// 返回字串让 React 比对 state 有否改动
return scriptList.map((script) => (script.enable ? 1 : 0)).join(",");
}, [scriptList]);

// 只随 script 数量和启动状态而改变的state
const backEnables = useMemo(() => {
// 返回字串让 React 比对 state 有否改动
return backScriptList.map((script) => (script.enable ? 1 : 0)).join(",");
}, [backScriptList]);

const normalScriptCounts = useMemo(() => {
// 拆回array
const enables = normalEnables.split(",");
// 计算已开启了的数量
const running = enables.reduce((p, c) => p + (+c ? 1 : 0), 0);
const running = scriptList.reduce((p, c) => p + (c.enable ? 1 : 0), 0);
return {
running,
total: enables.length, // 总数
total: scriptList.length, // 总数
};
}, [normalEnables]);
}, [scriptList]);

const backScriptCounts = useMemo(() => {
// 拆回array
const enables = backEnables.split(",");
// 计算已开启了的数量
const running = enables.reduce((p, c) => p + (+c ? 1 : 0), 0);
const running = backScriptList.reduce((p, c) => p + (c.enable ? 1 : 0), 0);
return {
running,
total: enables.length, // 总数
total: backScriptList.length, // 总数
};
}, [backEnables]);
}, [backScriptList]);

const urlHost = useMemo(() => {
let url: URL | undefined;
Expand Down
8 changes: 4 additions & 4 deletions tests/pages/popup/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,8 @@ describe("Popup App Component", () => {
await waitFor(
() => {
// 检查是否存在折叠面板结构
expect(screen.getByText("current_page_scripts (0/1)")).toBeInTheDocument();
expect(screen.getByText("enabled_background_scripts (0/1)")).toBeInTheDocument();
expect(screen.getByText("current_page_scripts (0/0)")).toBeInTheDocument();
expect(screen.getByText("enabled_background_scripts (0/0)")).toBeInTheDocument();
},
{ timeout: 3000 }
);
Expand All @@ -191,8 +191,8 @@ describe("Popup App Component", () => {
await waitFor(
() => {
expect(screen.getByText("ScriptCat")).toBeInTheDocument();
expect(screen.getByText("current_page_scripts (0/1)")).toBeInTheDocument();
expect(screen.getByText("enabled_background_scripts (0/1)")).toBeInTheDocument();
expect(screen.getByText("current_page_scripts (0/0)")).toBeInTheDocument();
expect(screen.getByText("enabled_background_scripts (0/0)")).toBeInTheDocument();
expect(screen.getByText("v" + ExtVersion)).toBeInTheDocument();
},
{ timeout: 3000 }
Expand Down
Loading