Skip to content

Commit

Permalink
♻️ 储存管理
Browse files Browse the repository at this point in the history
  • Loading branch information
CodFrm committed Oct 30, 2022
1 parent 43a332d commit 1067285
Show file tree
Hide file tree
Showing 6 changed files with 315 additions and 5 deletions.
21 changes: 21 additions & 0 deletions src/app/service/value/controller.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import IoC from "@App/app/ioc";
import MessageInternal from "@App/app/message/internal";
import { Script } from "@App/app/repo/scripts";
import { ValueDAO } from "@App/app/repo/value";
import Controller from "../controller";

@IoC.Singleton(MessageInternal)
export default class ValueController extends Controller {
internal: MessageInternal;

valueDAO: ValueDAO;

constructor(internal: MessageInternal) {
super(internal, "value");
this.internal = internal;
this.valueDAO = new ValueDAO();
}

public setValue(scriptId: number, key: string, value: any) {
Expand All @@ -18,4 +23,20 @@ export default class ValueController extends Controller {
value,
});
}

async getValues(script: Script) {
const where: { [key: string]: any } = {};
if (script.metadata.storagename) {
[where.storageName] = script.metadata.storagename;
} else {
where.scriptId = script.id;
}
return Promise.resolve(await this.valueDAO.list(where));
}

watchValue(script: Script) {
const channel = this.internal.channel();
channel.channel("watchValue", script);
return channel;
}
}
26 changes: 26 additions & 0 deletions src/app/service/value/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import CacheKey from "@App/pkg/utils/cache_key";
import Cache from "../../cache";
import Manager from "../manager";
import ScriptManager from "../script/manager";
import Hook from "../hook";

export type ValueEvent = "upsert";

Expand All @@ -24,6 +25,8 @@ export class ValueManager extends Manager {

broadcast: IMessageBroadcast;

static hook: Hook = new Hook<"upsert">();

constructor(message: MessageHander, broadcast: IMessageBroadcast) {
super(message, "value");

Expand All @@ -46,6 +49,27 @@ export class ValueManager extends Manager {
}
);

this.message.setHandlerWithChannel(
"watchValue",
async (channel, _action, script: Script) => {
const hook = (value: Value) => {
// 判断是否是当前脚本关注的value
if (script.metadata.storagename) {
if (value.storageName !== script.metadata.storagename[0]) {
return;
}
} else if (value.scriptId !== script.id) {
return;
}
channel.send(value);
};
ValueManager.hook.addListener("upsert", hook);
channel.setDisChannelHandler(() => {
ValueManager.hook.removeListener("upsert", hook);
});
}
);

ScriptManager.hook.addListener("delete", () => {
// 清理缓存
});
Expand Down Expand Up @@ -140,6 +164,8 @@ export class ValueManager extends Manager {
// 广播value更新
this.broadcast.broadcast({ tag: "all" }, "valueUpdate", sendData);

// 触发hook
ValueManager.hook.trigger("upsert", model);
return Promise.resolve(true);
}
}
Expand Down
257 changes: 253 additions & 4 deletions src/pages/components/ScriptStorage/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
import IoC from "@App/app/ioc";
import { Script } from "@App/app/repo/scripts";
import { Drawer, Empty } from "@arco-design/web-react";
import React from "react";
import { Value } from "@App/app/repo/value";
import ValueController from "@App/app/service/value/controller";
import { valueType } from "@App/pkg/utils/utils";
import {
Button,
Drawer,
Form,
Input,
Message,
Modal,
Popconfirm,
Select,
Space,
Table,
} from "@arco-design/web-react";
import FormItem from "@arco-design/web-react/es/Form/form-item";
import { RefInputType } from "@arco-design/web-react/es/Input/interface";
import { ColumnProps } from "@arco-design/web-react/es/Table";
import { IconDelete, IconEdit, IconSearch } from "@arco-design/web-react/icon";
import React, { useEffect, useRef, useState } from "react";

const ScriptStorage: React.FC<{
// eslint-disable-next-line react/require-default-props
Expand All @@ -9,15 +28,245 @@ const ScriptStorage: React.FC<{
onOk: () => void;
onCancel: () => void;
}> = ({ script, visible, onCancel, onOk }) => {
const [data, setData] = useState<Value[]>([]);
const inputRef = useRef<RefInputType>(null);
const valueCtrl = IoC.instance(ValueController) as ValueController;
const [currentValue, setCurrentValue] = useState<Value>();
const [visibleEdit, setVisibleEdit] = useState(false);
const [form] = Form.useForm();

useEffect(() => {
if (!script) {
return () => {};
}
valueCtrl.getValues(script).then((values) => {
setData(values);
});
// 监听值变化
const channel = valueCtrl.watchValue(script);
channel.setHandler((value: Value) => {
console.log("value changed", value);
setData((prev) => {
const index = prev.findIndex((item) => item.key === value.key);
if (index === -1) {
return [value, ...prev];
}
prev[index] = value;
return [...prev];
});
});
return () => {
channel.disChannel();
};
}, [script]);
const columns: ColumnProps[] = [
{
title: "key",
dataIndex: "key",
key: "key",
filterIcon: <IconSearch />,
// eslint-disable-next-line react/no-unstable-nested-components
filterDropdown: ({ filterKeys, setFilterKeys, confirm }: any) => {
return (
<div className="arco-table-custom-filter">
<Input.Search
ref={inputRef}
searchButton
placeholder="请输入key"
value={filterKeys[0] || ""}
onChange={(value) => {
setFilterKeys(value ? [value] : []);
}}
onSearch={() => {
confirm();
}}
/>
</div>
);
},
onFilter: (value, row) => (value ? row.key.indexOf(value) !== -1 : true),
onFilterDropdownVisibleChange: (v) => {
if (v) {
setTimeout(() => inputRef.current!.focus(), 150);
}
},
},
{
title: "value",
dataIndex: "value",
key: "value",
render(col) {
switch (typeof col) {
case "string":
return col;
default:
return JSON.stringify(col);
}
},
},
{
title: "类型",
dataIndex: "value",
render(col) {
return valueType(col);
},
},
{
title: "操作",
render(_col, value: Value, index) {
return (
<Space>
<Button
type="text"
icon={<IconEdit />}
onClick={() => {
setCurrentValue(value);
setVisibleEdit(true);
}}
/>
<Button
type="text"
iconOnly
icon={<IconDelete />}
onClick={() => {
valueCtrl.setValue(script!.id, value.key, undefined);
Message.info({
content: "删除成功",
});
setData(data.filter((_, i) => i !== index));
}}
/>
</Space>
);
},
},
];

return (
<Drawer
width={332}
width={600}
title={<span>{script?.name} 脚本储存</span>}
visible={visible}
onOk={onOk}
onCancel={onCancel}
>
<Empty description="建设中" />
<Modal
title={currentValue ? "编辑值" : "新增值"}
visible={visibleEdit}
onOk={() => {
form
.validate()
.then((value: { key: string; value: any; type: string }) => {
switch (value.type) {
case "number":
value.value = Number(value.value);
break;
case "boolean":
value.value = value.value === "true";
break;
case "object":
value.value = JSON.parse(value.value);
break;
default:
break;
}
valueCtrl.setValue(script!.id, value.key, value.value);
if (currentValue) {
Message.info({
content: "修改成功",
});
setData(
data.map((v) => {
if (v.key === value.key) {
return {
...v,
value: value.value,
};
}
return v;
})
);
} else {
Message.info({
content: "添加成功",
});
setData([
{
id: 0,
scriptId: script!.id,
storageName:
(script?.metadata.storagename &&
script?.metadata.storagename[0]) ||
"",
key: value.key,
value: value.value,
createtime: Date.now(),
},
...data,
]);
}
setVisibleEdit(false);
});
}}
onCancel={() => setVisibleEdit(false)}
>
{visibleEdit && (
<Form
form={form}
initialValues={{
key: currentValue?.key,
value: currentValue?.value,
type: valueType(currentValue?.value || "string"),
}}
>
<FormItem label="Key" field="key" rules={[{ required: true }]}>
<Input placeholder="key" disabled={!!currentValue} />
</FormItem>
<FormItem label="Value" field="value" rules={[{ required: true }]}>
<Input placeholder="当类型为object时,请输入可以JSON解析的数据" />
</FormItem>
<FormItem label="类型" field="type" rules={[{ required: true }]}>
<Select>
<Select.Option value="string">string</Select.Option>
<Select.Option value="number">number</Select.Option>
<Select.Option value="boolean">boolean</Select.Option>
<Select.Option value="object">object</Select.Option>
</Select>
</FormItem>
</Form>
)}
</Modal>
<Space className="w-full" direction="vertical">
<Space className="!flex justify-end">
<Popconfirm
focusLock
title="你真的要清空这个储存空间吗?"
onOk={() => {
data.forEach((v) => {
valueCtrl.setValue(script!.id, v.key, undefined);
});
Message.info({
content: "清空成功",
});
setData([]);
}}
>
<Button type="primary" status="warning">
清空
</Button>
</Popconfirm>
<Button
type="primary"
onClick={() => {
setCurrentValue(undefined);
setVisibleEdit(true);
}}
>
新增
</Button>
</Space>
<Table columns={columns} data={data} />
</Space>
</Drawer>
);
};
Expand Down
1 change: 1 addition & 0 deletions src/pages/components/UserConfigPanel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ const UserConfigPanel: React.FC<{
return (
<TabPane key={itemKey} title={itemKey}>
<Form
key={script.id}
style={{
width: "100%",
}}
Expand Down
3 changes: 2 additions & 1 deletion src/pkg/backup/export.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FileSystem } from "@Pkg/filesystem/filesystem";
import FileSystem from "@Pkg/filesystem/filesystem";
import crypto from "crypto-js";
import ResourceManager from "@App/app/service/resource/manager";
import { base64ToBlob } from "../utils/script";
Expand Down Expand Up @@ -38,6 +38,7 @@ export default class BackupExport {
await this.fs.create(`${name}.options.json`)
).write(JSON.stringify(script.options));
// 写入脚本storage.json
// 不想兼容tm的导出规则了,直接写入storage.json
await (
await this.fs.create(`${name}.storage.json`)
).write(JSON.stringify(script.storage));
Expand Down
Loading

0 comments on commit 1067285

Please sign in to comment.