value设置

This commit is contained in:
王一之 2025-04-24 22:48:22 +08:00
parent d200809fee
commit d761c62500
13 changed files with 109 additions and 102 deletions

View File

@ -0,0 +1,34 @@
// ==UserScript==
// @name gm value
// @namespace https://bbs.tampermonkey.net.cn/
// @version 0.1.0
// @description 可以持久化存储数据, 并且可以监听数据变化
// @author You
// @match https://bbs.tampermonkey.net.cn/
// @run-at document-start
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addValueChangeListener
// @grant GM_listValues
// @grant GM_deleteValue
// @grant GM_cookie
// ==/UserScript==
GM_addValueChangeListener("test_set", function (name, oldval, newval, remote) {
console.log("test_set change", name, oldval, newval, remote);
});
setInterval(() => {
console.log(GM_getValue("test_set"));
console.log(GM_listValues());
}, 2000);
setTimeout(() => {
GM_deleteValue("test_set");
}, 3000);
GM_setValue("test_set", new Date().getTime());
console.log(GM_getValue("test_set2"));
GM_setValue("test_set2", new Date().getTime());

View File

@ -36,9 +36,8 @@ function renameField() {
// export是0.10.x时的兼容性处理
export: "++id,&scriptId",
});
const v = 36;
// 将脚本数据迁移到chrome.storage
db.version(v).upgrade(() => {
db.version(18).upgrade(() => {
// 默认使用的事务这里加个延时用db.open()打开数据库后,再执行
setTimeout(async () => {
try {

View File

@ -159,10 +159,11 @@ export default class GMApi {
}
if (value === undefined) {
delete this.scriptRes.value[key];
return this.sendMessage("GM_setValue", [key]);
} else {
this.scriptRes.value[key] = value;
return this.sendMessage("GM_setValue", [key, value]);
}
return this.sendMessage("GM_setValue", [key, value]);
}
@GMContext.API({ depend: ["GM_setValue"] })

View File

@ -72,6 +72,10 @@ export class ResourceClient extends Client {
getScriptResources(script: Script): Promise<{ [key: string]: Resource }> {
return this.do("getScriptResources", script);
}
deleteResource(url: string) {
return this.do("deleteResource", url);
}
}
export class ValueClient extends Client {
@ -79,7 +83,7 @@ export class ValueClient extends Client {
super(msg, "serviceWorker/value");
}
getScriptValue(script: Script) {
getScriptValue(script: Script): Promise<{ [key: string]: any }> {
return this.do("getScriptValue", script);
}

View File

@ -242,7 +242,7 @@ export default class GMApi {
@PermissionVerify.API()
async GM_setValue(request: Request, sender: GetSender) {
if (!request.params || request.params.length !== 2) {
if (!request.params || request.params.length < 1) {
throw new Error("param is failed");
}
const [key, value] = request.params;

View File

@ -305,7 +305,12 @@ export class ResourceService {
return { url: urls[0], hash };
}
deleteResource(url: string) {
return this.resourceDAO.delete(url);
}
init() {
this.group.on("getScriptResources", this.getScriptResources.bind(this));
this.group.on("deleteResource", this.deleteResource.bind(this));
}
}

View File

@ -42,6 +42,7 @@ export class ValueService {
const storageName = getStorageName(script);
let oldValue;
// 使用事务来保证数据一致性
console.log("setValue", key, value);
await Cache.getInstance().tx("setValue:" + storageName, async () => {
const valueModel = await this.valueDAO.get(storageName);
if (!valueModel) {
@ -54,7 +55,11 @@ export class ValueService {
});
} else {
oldValue = valueModel.data[key];
valueModel.data[key] = value;
if (value === undefined) {
delete valueModel.data[key];
} else {
valueModel.data[key] = value;
}
await this.valueDAO.save(storageName, valueModel);
}
return true;

View File

@ -68,6 +68,7 @@
"confirm_delete_backup_file": "确认删除备份文件",
"confirm_update": "确认更新",
"delete_success": "删除成功",
"deleting": "删除中",
"backup_strategy": "备份策略",
"under_construction": "建设中",
"development_debugging": "开发调试",

View File

@ -1,22 +1,12 @@
import { Resource } from "@App/app/repo/resource";
import { Script } from "@App/app/repo/scripts";
import { ResourceClient } from "@App/app/service/service_worker/client";
import { message } from "@App/pages/store/global";
import { base64ToBlob } from "@App/pkg/utils/script";
import {
Button,
Drawer,
Input,
Message,
Popconfirm,
Space,
Table,
} from "@arco-design/web-react";
import { Button, Drawer, Input, Message, Popconfirm, Space, Table } from "@arco-design/web-react";
import { RefInputType } from "@arco-design/web-react/es/Input/interface";
import { ColumnProps } from "@arco-design/web-react/es/Table";
import {
IconDelete,
IconDownload,
IconSearch,
} from "@arco-design/web-react/icon";
import { IconDelete, IconDownload, IconSearch } from "@arco-design/web-react/icon";
import React, { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
@ -34,12 +24,13 @@ const ScriptResource: React.FC<{
const [data, setData] = useState<ResourceListItem[]>([]);
const inputRef = useRef<RefInputType>(null);
const { t } = useTranslation();
const resourceClient = new ResourceClient(message);
useEffect(() => {
if (!script) {
return () => {};
}
resourceCtrl.getResource(script).then((res) => {
resourceClient.getScriptResources(script).then((res) => {
const arr: ResourceListItem[] = [];
Object.keys(res).forEach((key) => {
// @ts-ignore
@ -119,10 +110,21 @@ const ScriptResource: React.FC<{
title={t("confirm_delete_resource")}
onOk={() => {
Message.info({
content: t("delete_success"),
content: t("deleting"),
});
resourceCtrl.deleteResource(value.id);
setData(data.filter((_, i) => i !== index));
resourceClient
.deleteResource(value.url)
.then(() => {
Message.info({
content: t("delete_success"),
});
setData(data.filter((_, i) => i !== index));
})
.catch((e) => {
Message.error({
content: t("delete_failed") + ": " + e.message,
});
});
}}
>
<Button type="text" iconOnly icon={<IconDelete />} />
@ -153,7 +155,7 @@ const ScriptResource: React.FC<{
onOk={() => {
setData((prev) => {
prev.forEach((v) => {
resourceCtrl.deleteResource(v.id);
resourceClient.deleteResource(v.url);
});
Message.info({
content: t("clear_success"),

View File

@ -1,15 +1,7 @@
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Script } from "@App/app/repo/scripts";
import {
Space,
Popconfirm,
Button,
Divider,
Typography,
Modal,
Input,
} from "@arco-design/web-react";
import { Script, ScriptDAO } from "@App/app/repo/scripts";
import { Space, Popconfirm, Button, Divider, Typography, Modal, Input } from "@arco-design/web-react";
import Table, { ColumnProps } from "@arco-design/web-react/es/Table";
import { IconDelete } from "@arco-design/web-react/icon";
@ -25,7 +17,7 @@ type MatchItem = {
const Match: React.FC<{
script: Script;
}> = ({ script }) => {
// const scriptCtrl = IoC.instance(ScriptController) as ScriptController;
const scriptDAO = new ScriptDAO();
const [match, setMatch] = useState<MatchItem[]>([]);
const [exclude, setExclude] = useState<MatchItem[]>([]);
const [matchValue, setMatchValue] = useState<string>("");
@ -37,7 +29,7 @@ const Match: React.FC<{
useEffect(() => {
if (script) {
// 从数据库中获取是简单处理数据一致性的问题
scriptCtrl.scriptDAO.findById(script.id).then((res) => {
scriptDAO.get(script.uuid).then((res) => {
if (!res) {
return;
}
@ -68,8 +60,7 @@ const Match: React.FC<{
});
setMatch(v);
const excludeArr =
res.selfMetadata?.exclude || res.metadata.exclude || [];
const excludeArr = res.selfMetadata?.exclude || res.metadata.exclude || [];
const excludeMap = new Map<string, boolean>();
res.metadata.exclude?.forEach((m) => {
excludeMap.set(m, true);
@ -125,9 +116,7 @@ const Match: React.FC<{
return (
<Space>
<Popconfirm
title={`${t("confirm_delete_exclude")}${
item.hasMatch ? ` ${t("after_deleting_match_item")}` : ""
}`}
title={`${t("confirm_delete_exclude")}${item.hasMatch ? ` ${t("after_deleting_match_item")}` : ""}`}
onOk={() => {
exclude.splice(exclude.indexOf(item), 1);
scriptCtrl
@ -159,9 +148,7 @@ const Match: React.FC<{
return (
<Space>
<Popconfirm
title={`${t("confirm_delete_match")}${
item.self ? "" : ` ${t("after_deleting_exclude_item")}`
}`}
title={`${t("confirm_delete_match")}${item.self ? "" : ` ${t("after_deleting_exclude_item")}`}`}
onOk={() => {
match.splice(match.indexOf(item), 1);
scriptCtrl

View File

@ -19,9 +19,6 @@ import { IconDelete } from "@arco-design/web-react/icon";
const PermissionManager: React.FC<{
script: Script;
}> = ({ script }) => {
// const permissionCtrl = IoC.instance(
// PermissionController
// ) as PermissionController;
const [permission, setPermission] = useState<Permission[]>([]);
const [permissionVisible, setPermissionVisible] = useState<boolean>(false);
const [permissionValue, setPermissionValue] = useState<Permission>();

View File

@ -1,13 +1,6 @@
import { Script } from "@App/app/repo/scripts";
import { Script, ScriptDAO } from "@App/app/repo/scripts";
import { formatUnixTime } from "@App/pkg/utils/utils";
import {
Descriptions,
Divider,
Drawer,
Empty,
Input,
Message,
} from "@arco-design/web-react";
import { Descriptions, Divider, Drawer, Empty, Input, Message } from "@arco-design/web-react";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import Match from "./Match";
@ -19,14 +12,14 @@ const ScriptSetting: React.FC<{
onOk: () => void;
onCancel: () => void;
}> = ({ script, visible, onCancel, onOk }) => {
// const scriptCtrl = IoC.instance(ScriptController) as ScriptController;
const scriptDAO = new ScriptDAO();
const [checkUpdateUrl, setCheckUpdateUrl] = useState<string>("");
const { t } = useTranslation();
useEffect(() => {
if (script) {
scriptCtrl.scriptDAO.findById(script.id).then((v) => {
scriptDAO.get(script.uuid).then((v) => {
setCheckUpdateUrl(v?.downloadUrl || "");
});
}
@ -56,9 +49,7 @@ const ScriptSetting: React.FC<{
data={[
{
label: t("last_updated"),
value: formatUnixTime(
(script?.updatetime || script?.createtime || 0) / 1000
),
value: formatUnixTime((script?.updatetime || script?.createtime || 0) / 1000),
},
{
label: "UUID",
@ -83,8 +74,8 @@ const ScriptSetting: React.FC<{
setCheckUpdateUrl(e);
}}
onBlur={() => {
scriptCtrl
.updateCheckUpdateUrl(script!.id, checkUpdateUrl)
scriptDAO
.update(script.uuid, { downloadUrl: checkUpdateUrl, checkUpdateUrl: checkUpdateUrl })
.then(() => {
Message.success(t("update_success")!);
});

View File

@ -1,5 +1,6 @@
import { Script } from "@App/app/repo/scripts";
import { Value } from "@App/app/repo/value";
import { valueClient } from "@App/pages/store/features/script";
import { valueType } from "@App/pkg/utils/utils";
import { Button, Drawer, Form, Input, Message, Modal, Popconfirm, Select, Space, Table } from "@arco-design/web-react";
import { RefInputType } from "@arco-design/web-react/es/Input/interface";
@ -10,16 +11,20 @@ import { useTranslation } from "react-i18next";
const FormItem = Form.Item;
interface ValueModel {
key: string;
value: any;
}
const ScriptStorage: React.FC<{
// eslint-disable-next-line react/require-default-props
script?: Script;
visible: boolean;
onOk: () => void;
onCancel: () => void;
}> = ({ script, visible, onCancel, onOk }) => {
const [data, setData] = useState<Value[]>([]);
const [data, setData] = useState<ValueModel[]>([]);
const inputRef = useRef<RefInputType>(null);
const [currentValue, setCurrentValue] = useState<Value>();
const [currentValue, setCurrentValue] = useState<ValueModel>();
const [visibleEdit, setVisibleEdit] = useState(false);
const [form] = Form.useForm();
const { t } = useTranslation();
@ -28,31 +33,13 @@ const ScriptStorage: React.FC<{
if (!script) {
return () => {};
}
// valueCtrl.getValues(script).then((values) => {
// setData(values);
// });
// Monitor value changes
// const channel = valueCtrl.watchValue(script);
// channel.setHandler((value: Value) => {
// setData((prev) => {
// const index = prev.findIndex((item) => item.key === value.key);
// if (index === -1) {
// if (value.value === undefined) {
// return prev;
// }
// return [value, ...prev];
// }
// if (value.value === undefined) {
// prev.splice(index, 1);
// return [...prev];
// }
// prev[index] = value;
// return [...prev];
// });
// });
return () => {
// channel.disChannel();
};
valueClient.getScriptValue(script).then((value) => {
setData(
Object.keys(value).map((key) => {
return { key: key, value: value[key] };
})
);
});
}, [script]);
const columns: ColumnProps[] = [
{
@ -61,7 +48,6 @@ const ScriptStorage: React.FC<{
key: "key",
filterIcon: <IconSearch />,
width: 140,
// eslint-disable-next-line react/no-unstable-nested-components
filterDropdown: ({ filterKeys, setFilterKeys, confirm }: any) => {
return (
<div className="arco-table-custom-filter">
@ -120,7 +106,7 @@ const ScriptStorage: React.FC<{
},
{
title: t("action"),
render(_col, value: Value, index) {
render(_col, value: { key: string; value: string }, index) {
return (
<Space>
<Button
@ -136,7 +122,7 @@ const ScriptStorage: React.FC<{
iconOnly
icon={<IconDelete />}
onClick={() => {
valueCtrl.setValue(script!.id, value.key, undefined);
valueClient.setScriptValue(script!.uuid, value.key, undefined);
Message.info({
content: t("delete_success"),
});
@ -179,7 +165,7 @@ const ScriptStorage: React.FC<{
default:
break;
}
valueCtrl.setValue(script!.id, value.key, value.value);
valueClient.setScriptValue(script!.uuid, value.key, value.value);
if (currentValue) {
Message.info({
content: t("update_success"),
@ -201,13 +187,8 @@ const ScriptStorage: React.FC<{
});
setData([
{
id: 0,
scriptId: script!.id,
storageName: (script?.metadata.storagename && script?.metadata.storagename[0]) || "",
key: value.key,
value: value.value,
createtime: Date.now(),
updatetime: 0,
},
...data,
]);
@ -254,7 +235,7 @@ const ScriptStorage: React.FC<{
onOk={() => {
setData((prev) => {
prev.forEach((v) => {
valueCtrl.setValue(script!.id, v.key, undefined);
valueClient.setScriptValue(script!.uuid, v.key, undefined);
});
Message.info({
content: t("clear_success"),