@arronqzy/react-view 1.0.38 → 1.0.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -7
- package/readme.md +15 -12
- package/src/index.ts +23 -1
- package/src/panel/ReactViewPanel.tsx +60 -19
- package/src/panel/hooks/useWorkspaceProjects.ts +228 -52
- package/src/panel/library/abuilder-storage.ts +101 -3
- package/src/panel/library/abuilder-view-handle.ts +100 -0
- package/src/panel/library/event-subscription.ts +70 -5
- package/src/panel/library/workspace-project-sync.ts +7 -1
- package/src/panel/library/workspace-storage.ts +58 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arronqzy/react-view",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.39",
|
|
4
4
|
"files": [
|
|
5
5
|
"src",
|
|
6
6
|
"dist",
|
|
@@ -30,14 +30,14 @@
|
|
|
30
30
|
"@codemirror/lang-json": "^6.0.1",
|
|
31
31
|
"@codemirror/theme-one-dark": "^6.1.2",
|
|
32
32
|
"@codemirror/view": "^6.36.2",
|
|
33
|
-
"@arronqzy/blueprint-dsl": "1.0.17",
|
|
34
|
-
"@arronqzy/ui": "1.0.10",
|
|
35
33
|
"@arronqzy/i18n": "0.1.17",
|
|
36
|
-
"@arronqzy/
|
|
34
|
+
"@arronqzy/ui": "1.0.10",
|
|
35
|
+
"@arronqzy/blueprint-dsl": "1.0.17",
|
|
37
36
|
"@arronqzy/rx-store": "1.0.8",
|
|
38
37
|
"@arronqzy/view-scene3d": "0.1.5",
|
|
38
|
+
"@arronqzy/view-table": "0.1.8",
|
|
39
39
|
"@arronqzy/webllm-assistant": "0.1.16",
|
|
40
|
-
"@arronqzy/
|
|
40
|
+
"@arronqzy/react-blueprint": "1.0.24"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/react": "^19.0.1",
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"eslint": "^8.57.0",
|
|
47
47
|
"typescript": "^5.5.4",
|
|
48
48
|
"@arronqzy/eslint-config": "0.0.0",
|
|
49
|
-
"@arronqzy/
|
|
50
|
-
"@arronqzy/
|
|
49
|
+
"@arronqzy/tailwind": "1.0.2",
|
|
50
|
+
"@arronqzy/typescript-config": "1.0.0"
|
|
51
51
|
},
|
|
52
52
|
"scripts": {
|
|
53
53
|
"lint": "eslint \"src/**/*.{ts,tsx}\"",
|
package/readme.md
CHANGED
|
@@ -57,7 +57,8 @@ export function Editor({ workspace }: { workspace?: WorkspaceProjectRecord | nul
|
|
|
57
57
|
|------|------|------|
|
|
58
58
|
| `className` | 根节点 class | — |
|
|
59
59
|
| `initialZoom` | 初始缩放 | `1` |
|
|
60
|
-
| `initialWorkspace` |
|
|
60
|
+
| `initialWorkspace` | 挂载后加载的完整工作区。空则空白画布。带 `id` 时默认同步进 IndexedDB | — |
|
|
61
|
+
| `workspaceId` | 按 IndexedDB id 打开(不 remount) | — |
|
|
61
62
|
|
|
62
63
|
## 在线预览
|
|
63
64
|
|
|
@@ -89,7 +90,7 @@ parseOnlinePreviewSearchParams("?preview=online&projectId=xxx");
|
|
|
89
90
|
|
|
90
91
|
## 工作区事件
|
|
91
92
|
|
|
92
|
-
|
|
93
|
+
回调带完整工作区,用来写自己的后端。`initialWorkspace` 会默认同步进 IndexedDB;本地副本可用导出的 list / get / delete / clear 清理。
|
|
93
94
|
|
|
94
95
|
```ts
|
|
95
96
|
import {
|
|
@@ -111,10 +112,12 @@ syncSub.unsubscribe();
|
|
|
111
112
|
| 事件 | 常量 | 何时触发 |
|
|
112
113
|
|------|------|----------|
|
|
113
114
|
| `workspace:add` | `AbuilderEvents.workspaceAdd` | 创建成功 |
|
|
114
|
-
| `workspace:sync` | `AbuilderEvents.workspaceSync` |
|
|
115
|
+
| `workspace:sync` | `AbuilderEvents.workspaceSync` | 同步成功(载荷含 `nameSpace`) |
|
|
115
116
|
| `workspace:delete` | `AbuilderEvents.workspaceDelete` | 删除工作区,参数 `{ ids, nameSpace }` |
|
|
117
|
+
| `blueprint-library:changed` | `AbuilderEvents.blueprintLibraryChanged` | 蓝图库变更 |
|
|
118
|
+
| `api-collection:changed` | `AbuilderEvents.apiCollectionChanged` | 接口集合变更 |
|
|
116
119
|
|
|
117
|
-
`workspace:add` / `workspace:sync`
|
|
120
|
+
`workspace:add` / `workspace:sync` 的参数字段:上述工作区字段 + `nameSpace`。可用 `addEventSubscription(event, cb, { nameSpace })` 过滤。
|
|
118
121
|
|
|
119
122
|
校验:
|
|
120
123
|
|
|
@@ -155,14 +158,14 @@ const dataUrl = await getPreviewSnapshot({
|
|
|
155
158
|
| `addEventSubscription` / `AbuilderEvents` | 工作区事件 |
|
|
156
159
|
| `getPreviewSnapshot` | 预览截图 data URL |
|
|
157
160
|
| `parseWorkspaceData` 等 | 校验 / 解析 / 空工作区 |
|
|
158
|
-
| `
|
|
159
|
-
| `
|
|
160
|
-
| `
|
|
161
|
-
| `
|
|
162
|
-
| `
|
|
163
|
-
| `clearAbuilderStorage` |
|
|
164
|
-
|
|
165
|
-
|
|
161
|
+
| `putWorkspaceProject` / `upsertWorkspaceProject` / `list` / `get` / `delete` / `clear` | 工作区完整 CRUD |
|
|
162
|
+
| `putBlueprintLibraryRecord` / `list` / `get` / `delete` / `clear` | 蓝图库完整 CRUD |
|
|
163
|
+
| `putExecutionRunRecord` / `list` / `get` / `delete` / `clear` / `purge*` / `trim*` | 执行日志 |
|
|
164
|
+
| `putApiCollection` / `upsertApiCollectionFromJson` / `list` / `delete` / `clear` | 接口集合 |
|
|
165
|
+
| `getCurrentWorkspace` / `openWorkspace` / `createWorkspace` / `saveWorkspace` / `syncWorkspace` | 命令式操作已挂载面板 |
|
|
166
|
+
| `clearAbuilderStorage` | 一次清空四类数据 |
|
|
167
|
+
|
|
168
|
+
本地数据函数的 `nameSpace` 要和面板一致。面板 `ref` 类型为 `AbuilderViewHandle`。
|
|
166
169
|
| `createView` / `createAction` | 视图 DSL 扩展点(`src/core`) |
|
|
167
170
|
|
|
168
171
|
## 样式
|
package/src/index.ts
CHANGED
|
@@ -12,28 +12,38 @@ export {
|
|
|
12
12
|
export type {
|
|
13
13
|
AbuilderEventName,
|
|
14
14
|
AbuilderEventPayloadMap,
|
|
15
|
+
AddEventSubscriptionOptions,
|
|
15
16
|
WorkspaceAddEventPayload,
|
|
16
17
|
WorkspaceSyncEventPayload,
|
|
17
18
|
WorkspaceDeleteEventPayload,
|
|
19
|
+
BlueprintLibraryChangedEventPayload,
|
|
18
20
|
ApiCollectionUploadStartEventPayload,
|
|
19
21
|
ApiCollectionUploadSuccessEventPayload,
|
|
22
|
+
ApiCollectionChangedEventPayload,
|
|
20
23
|
WorkspaceData,
|
|
21
24
|
} from "./panel/library/event-subscription";
|
|
22
25
|
export type { WorkspaceProjectRecord } from "./panel/library/workspace-project-db";
|
|
23
26
|
export {
|
|
24
27
|
listWorkspaceProjects,
|
|
25
28
|
getWorkspaceProject,
|
|
29
|
+
putWorkspaceProject,
|
|
30
|
+
upsertWorkspaceProject,
|
|
26
31
|
deleteWorkspaceProject,
|
|
27
32
|
clearWorkspaceProjects,
|
|
28
33
|
} from "./panel/library/workspace-storage";
|
|
29
|
-
export type {
|
|
34
|
+
export type {
|
|
35
|
+
WorkspaceProjectListItem,
|
|
36
|
+
PutWorkspaceProjectOptions,
|
|
37
|
+
} from "./panel/library/workspace-storage";
|
|
30
38
|
export {
|
|
31
39
|
listBlueprintLibrary,
|
|
32
40
|
getBlueprintLibraryRecord,
|
|
41
|
+
putBlueprintLibraryRecord,
|
|
33
42
|
deleteBlueprintLibraryRecord,
|
|
34
43
|
clearBlueprintLibrary,
|
|
35
44
|
listExecutionRunRecords,
|
|
36
45
|
getExecutionRunRecord,
|
|
46
|
+
putExecutionRunRecord,
|
|
37
47
|
deleteExecutionRunRecord,
|
|
38
48
|
clearAllExecutionRunRecords,
|
|
39
49
|
purgeExecutionRunsOlderThan,
|
|
@@ -41,6 +51,8 @@ export {
|
|
|
41
51
|
readExecutionLogSettings,
|
|
42
52
|
writeExecutionLogSettings,
|
|
43
53
|
listApiCollectionRecords,
|
|
54
|
+
putApiCollection,
|
|
55
|
+
upsertApiCollectionFromJson,
|
|
44
56
|
deleteApiCollection,
|
|
45
57
|
clearApiCollections,
|
|
46
58
|
clearAbuilderStorage,
|
|
@@ -52,6 +64,16 @@ export type {
|
|
|
52
64
|
ExecutionRunRecord,
|
|
53
65
|
ExecutionLogSettings,
|
|
54
66
|
ApiCollectionRecord,
|
|
67
|
+
UpsertApiCollectionInput,
|
|
68
|
+
UpsertApiCollectionResult,
|
|
55
69
|
} from "./panel/library/abuilder-storage";
|
|
70
|
+
export {
|
|
71
|
+
getCurrentWorkspace,
|
|
72
|
+
openWorkspace,
|
|
73
|
+
createWorkspace,
|
|
74
|
+
saveWorkspace,
|
|
75
|
+
syncWorkspace,
|
|
76
|
+
} from "./panel/library/abuilder-view-handle";
|
|
77
|
+
export type { AbuilderViewHandle } from "./panel/library/abuilder-view-handle";
|
|
56
78
|
export { getPreviewSnapshot } from "./panel/library/preview-snapshot";
|
|
57
79
|
export type { GetPreviewSnapshotOptions } from "./panel/library/preview-snapshot";
|
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
forwardRef,
|
|
3
|
+
useCallback,
|
|
4
|
+
useEffect,
|
|
5
|
+
useImperativeHandle,
|
|
6
|
+
useMemo,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
} from "react";
|
|
10
|
+
import {
|
|
11
|
+
registerAbuilderViewHandle,
|
|
12
|
+
type AbuilderViewHandle,
|
|
13
|
+
} from "./library/abuilder-view-handle";
|
|
2
14
|
import { useI18n } from "@arronqzy/i18n/react";
|
|
3
15
|
import { relocalizeMessage, type Locale } from "@arronqzy/i18n";
|
|
4
16
|
import type { State } from "@arronqzy/rx-store";
|
|
@@ -141,6 +153,11 @@ export type ReactViewPanelProps = {
|
|
|
141
153
|
className?: string;
|
|
142
154
|
/** 外部传入的完整工作区数据,挂载后按这份数据完整渲染;空则显示空白,不自动打开 IndexedDB 记录 */
|
|
143
155
|
initialWorkspace?: WorkspaceProjectRecord | null;
|
|
156
|
+
/**
|
|
157
|
+
* 按 IndexedDB id 打开工作区(不 remount)。
|
|
158
|
+
* 与 `initialWorkspace` 同时存在时:首次优先完整记录;之后 id 变化从 IndexedDB 打开。
|
|
159
|
+
*/
|
|
160
|
+
workspaceId?: string | null;
|
|
144
161
|
/** 界面语言;省略时按 localStorage / 浏览器语言解析 */
|
|
145
162
|
locale?: Locale | null;
|
|
146
163
|
onLocaleChange?: (locale: Locale) => void;
|
|
@@ -156,14 +173,18 @@ export type ReactViewPanelProps = {
|
|
|
156
173
|
packageVersion?: string | null;
|
|
157
174
|
};
|
|
158
175
|
|
|
159
|
-
function ReactViewPanelInner(
|
|
176
|
+
const ReactViewPanelInner = forwardRef<AbuilderViewHandle, ReactViewPanelProps>(function ReactViewPanelInner(
|
|
177
|
+
{
|
|
160
178
|
initialZoom = 1,
|
|
161
179
|
className,
|
|
162
180
|
initialWorkspace = null,
|
|
181
|
+
workspaceId = null,
|
|
163
182
|
nameSpace = null,
|
|
164
183
|
packageName = null,
|
|
165
184
|
packageVersion = null,
|
|
166
|
-
}: ReactViewPanelProps
|
|
185
|
+
}: ReactViewPanelProps,
|
|
186
|
+
ref
|
|
187
|
+
) {
|
|
167
188
|
const { t, locale, setLocale } = useI18n();
|
|
168
189
|
const messages = useMemo(() => getPanelMessages(t), [t]);
|
|
169
190
|
const THEME_STORAGE_KEY = appStorageKey("panel:theme", nameSpace);
|
|
@@ -1076,9 +1097,26 @@ function ReactViewPanelInner({
|
|
|
1076
1097
|
panelRevision: `${historyCursor}|${allElements.length}`,
|
|
1077
1098
|
onProjectApplied: handleWorkspaceProjectApplied,
|
|
1078
1099
|
initialWorkspace,
|
|
1100
|
+
workspaceId,
|
|
1079
1101
|
nameSpace,
|
|
1080
1102
|
});
|
|
1081
1103
|
|
|
1104
|
+
const viewHandle = useMemo<AbuilderViewHandle>(
|
|
1105
|
+
() => ({
|
|
1106
|
+
getNameSpace: () => nameSpace ?? null,
|
|
1107
|
+
getCurrentWorkspace: () => workspaceProjects.getCurrentWorkspaceRecord(),
|
|
1108
|
+
openWorkspace: (idOrRecord) => workspaceProjects.handleOpenProject(idOrRecord),
|
|
1109
|
+
createWorkspace: () => workspaceProjects.handleCreateProject(),
|
|
1110
|
+
saveWorkspace: () => workspaceProjects.handleSaveProject(),
|
|
1111
|
+
syncWorkspace: () => workspaceProjects.handleSyncProject(),
|
|
1112
|
+
}),
|
|
1113
|
+
[nameSpace, workspaceProjects]
|
|
1114
|
+
);
|
|
1115
|
+
|
|
1116
|
+
useImperativeHandle(ref, () => viewHandle, [viewHandle]);
|
|
1117
|
+
|
|
1118
|
+
useEffect(() => registerAbuilderViewHandle(viewHandle), [viewHandle]);
|
|
1119
|
+
|
|
1082
1120
|
useEffect(() => {
|
|
1083
1121
|
registerPreviewSnapshotProvider(async () => {
|
|
1084
1122
|
return captureEditorPreviewSnapshot({
|
|
@@ -2798,10 +2836,16 @@ function ReactViewPanelInner({
|
|
|
2798
2836
|
open={apiCollectionManagerOpen}
|
|
2799
2837
|
onOpenChange={setApiCollectionManagerOpen}
|
|
2800
2838
|
onUploadStart={(info) => {
|
|
2801
|
-
emitAbuilderEvent(AbuilderEvents.apiCollectionUploadStart,
|
|
2839
|
+
emitAbuilderEvent(AbuilderEvents.apiCollectionUploadStart, {
|
|
2840
|
+
...info,
|
|
2841
|
+
nameSpace: nameSpace ?? null,
|
|
2842
|
+
});
|
|
2802
2843
|
}}
|
|
2803
2844
|
onUploadSuccess={(info) => {
|
|
2804
|
-
emitAbuilderEvent(AbuilderEvents.apiCollectionUploadSuccess,
|
|
2845
|
+
emitAbuilderEvent(AbuilderEvents.apiCollectionUploadSuccess, {
|
|
2846
|
+
...info,
|
|
2847
|
+
nameSpace: nameSpace ?? null,
|
|
2848
|
+
});
|
|
2805
2849
|
}}
|
|
2806
2850
|
onSyncCollection={(record) => {
|
|
2807
2851
|
const patches = buildApiCollectionSyncPatches({
|
|
@@ -2846,17 +2890,14 @@ function ReactViewPanelInner({
|
|
|
2846
2890
|
<Toaster />
|
|
2847
2891
|
</div>
|
|
2848
2892
|
);
|
|
2849
|
-
}
|
|
2850
|
-
|
|
2851
|
-
export
|
|
2852
|
-
locale,
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
</I18nRoot>
|
|
2861
|
-
);
|
|
2862
|
-
}
|
|
2893
|
+
});
|
|
2894
|
+
|
|
2895
|
+
export const ReactViewPanel = forwardRef<AbuilderViewHandle, ReactViewPanelProps>(
|
|
2896
|
+
function ReactViewPanel({ locale, onLocaleChange, nameSpace, ...rest }, ref) {
|
|
2897
|
+
return (
|
|
2898
|
+
<I18nRoot locale={locale} onLocaleChange={onLocaleChange} nameSpace={nameSpace}>
|
|
2899
|
+
<ReactViewPanelInner ref={ref} nameSpace={nameSpace} {...rest} />
|
|
2900
|
+
</I18nRoot>
|
|
2901
|
+
);
|
|
2902
|
+
}
|
|
2903
|
+
);
|
|
@@ -7,16 +7,16 @@ import {
|
|
|
7
7
|
createWorkspaceProjectId,
|
|
8
8
|
getWorkspaceProject,
|
|
9
9
|
listWorkspaceProjects,
|
|
10
|
-
putWorkspaceProject,
|
|
11
10
|
type WorkspaceProjectListItem,
|
|
12
11
|
type WorkspaceProjectRecord,
|
|
13
12
|
} from "../library/workspace-project-db";
|
|
14
|
-
import { deleteWorkspaceProject } from "../library/workspace-storage";
|
|
15
13
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
} from "../library/workspace-
|
|
14
|
+
deleteWorkspaceProject,
|
|
15
|
+
putWorkspaceProject,
|
|
16
|
+
} from "../library/workspace-storage";
|
|
17
|
+
import { subscribeWorkspaceProjectsChanged } from "../library/workspace-project-sync";
|
|
19
18
|
import { writeWorkspacePreviewCache, readWorkspacePreviewCache } from "../library/workspace-project-cache";
|
|
19
|
+
import { normalizeAppNameSpace } from "@arronqzy/blueprint-dsl";
|
|
20
20
|
import {
|
|
21
21
|
cloneWorkspaceSnapshot,
|
|
22
22
|
workspaceSnapshotsEqual,
|
|
@@ -45,6 +45,11 @@ export type UseWorkspaceProjectsOptions = {
|
|
|
45
45
|
onProjectApplied?: (record: WorkspaceProjectRecord) => void;
|
|
46
46
|
/** 外部传入的初始工作区,挂载后自动加载(不触发 onProjectApplied) */
|
|
47
47
|
initialWorkspace?: WorkspaceProjectRecord | null;
|
|
48
|
+
/**
|
|
49
|
+
* 按 IndexedDB id 打开工作区。与 `initialWorkspace` 同时存在时:
|
|
50
|
+
* 首次优先 `initialWorkspace`;之后 `workspaceId` 变化会从 IndexedDB 打开。
|
|
51
|
+
*/
|
|
52
|
+
workspaceId?: string | null;
|
|
48
53
|
/** 隔离 IndexedDB / 预览缓存 / 同步频道 */
|
|
49
54
|
nameSpace?: string | null;
|
|
50
55
|
};
|
|
@@ -103,17 +108,20 @@ export function useWorkspaceProjects({
|
|
|
103
108
|
panelRevision,
|
|
104
109
|
onProjectApplied,
|
|
105
110
|
initialWorkspace,
|
|
111
|
+
workspaceId = null,
|
|
106
112
|
nameSpace = null,
|
|
107
113
|
}: UseWorkspaceProjectsOptions) {
|
|
108
114
|
const { t } = useI18nOptional();
|
|
109
115
|
const [projects, setProjects] = useState<WorkspaceProjectListItem[]>([]);
|
|
110
116
|
const [activeProjectId, setActiveProjectId] = useState<string | null>(null);
|
|
111
117
|
const [activeProjectName, setActiveProjectName] = useState<string | null>(null);
|
|
118
|
+
const activeCreatedAtRef = useRef<number | null>(null);
|
|
112
119
|
const syncedSnapshotRef = useRef<WorkspaceSnapshot | null>(null);
|
|
113
120
|
const syncedPanelRevisionRef = useRef<unknown>(null);
|
|
114
121
|
const pendingRevisionSyncRef = useRef(false);
|
|
115
122
|
const previewWindowsRef = useRef(new Map<string, Window>());
|
|
116
123
|
const [previewingProjectIds, setPreviewingProjectIds] = useState<string[]>([]);
|
|
124
|
+
const eventNameSpace = normalizeAppNameSpace(nameSpace) || null;
|
|
117
125
|
|
|
118
126
|
const refreshPreviewing = useCallback(() => {
|
|
119
127
|
const ids: string[] = [];
|
|
@@ -166,7 +174,9 @@ export function useWorkspaceProjects({
|
|
|
166
174
|
return subscribeWorkspaceProjectsChanged((message) => {
|
|
167
175
|
const removedActive =
|
|
168
176
|
message.type === "cleared" ||
|
|
169
|
-
(
|
|
177
|
+
(message.type === "deleted" &&
|
|
178
|
+
activeProjectId !== null &&
|
|
179
|
+
message.projectIds.includes(activeProjectId));
|
|
170
180
|
if (removedActive) {
|
|
171
181
|
setActiveProjectId(null);
|
|
172
182
|
setActiveProjectName(null);
|
|
@@ -197,8 +207,67 @@ export function useWorkspaceProjects({
|
|
|
197
207
|
buildCurrentSnapshot,
|
|
198
208
|
]);
|
|
199
209
|
|
|
210
|
+
const ensureWorkspaceInIndexedDb = useCallback(
|
|
211
|
+
async (record: WorkspaceProjectRecord) => {
|
|
212
|
+
const id = typeof record.id === "string" ? record.id.trim() : "";
|
|
213
|
+
if (!id) return;
|
|
214
|
+
try {
|
|
215
|
+
const existing = await getWorkspaceProject(id, nameSpace);
|
|
216
|
+
const incomingUpdatedAt =
|
|
217
|
+
typeof record.updatedAt === "number" && Number.isFinite(record.updatedAt)
|
|
218
|
+
? record.updatedAt
|
|
219
|
+
: 0;
|
|
220
|
+
if (existing && existing.updatedAt >= incomingUpdatedAt) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const now = Date.now();
|
|
224
|
+
const name =
|
|
225
|
+
(typeof record.name === "string" && record.name.trim()) ||
|
|
226
|
+
(typeof record.productName === "string" && record.productName.trim()) ||
|
|
227
|
+
t("panel.defaults.unnamedProduct");
|
|
228
|
+
const toStore: WorkspaceProjectRecord = {
|
|
229
|
+
...record,
|
|
230
|
+
id,
|
|
231
|
+
name,
|
|
232
|
+
createdAt: existing?.createdAt ?? record.createdAt ?? now,
|
|
233
|
+
updatedAt: incomingUpdatedAt || now,
|
|
234
|
+
panelState:
|
|
235
|
+
normalizeImportedPanelState(record.panelState) ??
|
|
236
|
+
existing?.panelState ??
|
|
237
|
+
createEmptyPanelState(),
|
|
238
|
+
blueprintDocument:
|
|
239
|
+
parseBlueprintData(record.blueprintDocument).value ??
|
|
240
|
+
existing?.blueprintDocument ??
|
|
241
|
+
BlueprintGraph.empty().document,
|
|
242
|
+
blueprintMeta: {
|
|
243
|
+
name:
|
|
244
|
+
record.blueprintMeta?.name ??
|
|
245
|
+
existing?.blueprintMeta?.name ??
|
|
246
|
+
t("panel.defaults.unnamedBlueprint"),
|
|
247
|
+
remark:
|
|
248
|
+
record.blueprintMeta?.remark ?? existing?.blueprintMeta?.remark ?? "",
|
|
249
|
+
},
|
|
250
|
+
productName:
|
|
251
|
+
typeof record.productName === "string" && record.productName.trim()
|
|
252
|
+
? record.productName
|
|
253
|
+
: existing?.productName ?? t("panel.defaults.unnamedProduct"),
|
|
254
|
+
titleIconDataUrl:
|
|
255
|
+
record.titleIconDataUrl ?? existing?.titleIconDataUrl,
|
|
256
|
+
};
|
|
257
|
+
await putWorkspaceProject(toStore, nameSpace);
|
|
258
|
+
await refreshProjects();
|
|
259
|
+
} catch (error) {
|
|
260
|
+
console.warn("[abuilder] ensureWorkspaceInIndexedDb failed", error);
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
[nameSpace, refreshProjects, t]
|
|
264
|
+
);
|
|
265
|
+
|
|
200
266
|
const applyProjectRecord = useCallback(
|
|
201
|
-
(
|
|
267
|
+
(
|
|
268
|
+
record: WorkspaceProjectRecord,
|
|
269
|
+
options?: { notifyApplied?: boolean; persistToIndexedDb?: boolean }
|
|
270
|
+
) => {
|
|
202
271
|
const parsedBlueprint = parseBlueprintData(record.blueprintDocument);
|
|
203
272
|
const blueprintDocument =
|
|
204
273
|
parsedBlueprint.value ?? BlueprintGraph.empty().document;
|
|
@@ -221,6 +290,10 @@ export function useWorkspaceProjects({
|
|
|
221
290
|
setTitleIconDataUrl(titleIconDataUrl);
|
|
222
291
|
setActiveProjectId(record.id || null);
|
|
223
292
|
setActiveProjectName(record.name ?? null);
|
|
293
|
+
activeCreatedAtRef.current =
|
|
294
|
+
typeof record.createdAt === "number" && Number.isFinite(record.createdAt)
|
|
295
|
+
? record.createdAt
|
|
296
|
+
: Date.now();
|
|
224
297
|
syncedSnapshotRef.current = cloneWorkspaceSnapshot({
|
|
225
298
|
blueprintDocument,
|
|
226
299
|
blueprintMeta,
|
|
@@ -229,18 +302,27 @@ export function useWorkspaceProjects({
|
|
|
229
302
|
});
|
|
230
303
|
syncedPanelRevisionRef.current = panelRevision;
|
|
231
304
|
pendingRevisionSyncRef.current = true;
|
|
305
|
+
const normalized: WorkspaceProjectRecord = {
|
|
306
|
+
...record,
|
|
307
|
+
id: record.id,
|
|
308
|
+
name: record.name ?? productName,
|
|
309
|
+
createdAt: activeCreatedAtRef.current,
|
|
310
|
+
updatedAt: record.updatedAt ?? Date.now(),
|
|
311
|
+
blueprintDocument,
|
|
312
|
+
blueprintMeta,
|
|
313
|
+
panelState,
|
|
314
|
+
productName,
|
|
315
|
+
titleIconDataUrl: titleIconDataUrl || undefined,
|
|
316
|
+
};
|
|
232
317
|
if (options?.notifyApplied !== false) {
|
|
233
|
-
onProjectApplied?.(
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
panelState,
|
|
238
|
-
productName,
|
|
239
|
-
titleIconDataUrl: titleIconDataUrl || undefined,
|
|
240
|
-
});
|
|
318
|
+
onProjectApplied?.(normalized);
|
|
319
|
+
}
|
|
320
|
+
if (options?.persistToIndexedDb) {
|
|
321
|
+
void ensureWorkspaceInIndexedDb(normalized);
|
|
241
322
|
}
|
|
242
323
|
},
|
|
243
324
|
[
|
|
325
|
+
ensureWorkspaceInIndexedDb,
|
|
244
326
|
importPanelData,
|
|
245
327
|
onProjectApplied,
|
|
246
328
|
setBlueprintGraph,
|
|
@@ -263,6 +345,7 @@ export function useWorkspaceProjects({
|
|
|
263
345
|
setTitleIconDataUrl("");
|
|
264
346
|
setActiveProjectId(null);
|
|
265
347
|
setActiveProjectName(null);
|
|
348
|
+
activeCreatedAtRef.current = null;
|
|
266
349
|
syncedSnapshotRef.current = null;
|
|
267
350
|
}, [
|
|
268
351
|
importPanelData,
|
|
@@ -275,23 +358,7 @@ export function useWorkspaceProjects({
|
|
|
275
358
|
|
|
276
359
|
const initialWorkspaceAppliedKeyRef = useRef<string | null>(null);
|
|
277
360
|
const bootstrappedRef = useRef(false);
|
|
278
|
-
|
|
279
|
-
if (!bootstrappedRef.current) {
|
|
280
|
-
bootstrappedRef.current = true;
|
|
281
|
-
if (hasWorkspacePayload(initialWorkspace)) {
|
|
282
|
-
applyProjectRecord(initialWorkspace, { notifyApplied: false });
|
|
283
|
-
initialWorkspaceAppliedKeyRef.current = workspaceApplyKey(initialWorkspace);
|
|
284
|
-
} else {
|
|
285
|
-
applyEmptyWorkspace();
|
|
286
|
-
}
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
if (!hasWorkspacePayload(initialWorkspace)) return;
|
|
290
|
-
const key = workspaceApplyKey(initialWorkspace);
|
|
291
|
-
if (initialWorkspaceAppliedKeyRef.current === key) return;
|
|
292
|
-
initialWorkspaceAppliedKeyRef.current = key;
|
|
293
|
-
applyProjectRecord(initialWorkspace, { notifyApplied: false });
|
|
294
|
-
}, [applyEmptyWorkspace, applyProjectRecord, initialWorkspace]);
|
|
361
|
+
const workspaceIdAppliedRef = useRef<string | null>(null);
|
|
295
362
|
|
|
296
363
|
const persistProject = useCallback(
|
|
297
364
|
async (options: { id?: string; name: string; createdAt?: number }) => {
|
|
@@ -299,7 +366,7 @@ export function useWorkspaceProjects({
|
|
|
299
366
|
const snapshot = cloneWorkspaceSnapshot(buildCurrentSnapshot());
|
|
300
367
|
const now = Date.now();
|
|
301
368
|
const id = options.id ?? createWorkspaceProjectId();
|
|
302
|
-
const createdAt = options.createdAt ?? now;
|
|
369
|
+
const createdAt = options.createdAt ?? activeCreatedAtRef.current ?? now;
|
|
303
370
|
const name = (options.name ?? "").trim() || resolveProjectName();
|
|
304
371
|
|
|
305
372
|
let panelState: State;
|
|
@@ -325,10 +392,9 @@ export function useWorkspaceProjects({
|
|
|
325
392
|
};
|
|
326
393
|
|
|
327
394
|
await putWorkspaceProject(record, nameSpace);
|
|
328
|
-
writeWorkspacePreviewCache(record, nameSpace);
|
|
329
|
-
broadcastWorkspaceProjectUpdate(id, now, nameSpace);
|
|
330
395
|
setActiveProjectId(id);
|
|
331
396
|
setActiveProjectName(name);
|
|
397
|
+
activeCreatedAtRef.current = createdAt;
|
|
332
398
|
syncedSnapshotRef.current = snapshot;
|
|
333
399
|
syncedPanelRevisionRef.current = panelRevision;
|
|
334
400
|
pendingRevisionSyncRef.current = false;
|
|
@@ -349,14 +415,49 @@ export function useWorkspaceProjects({
|
|
|
349
415
|
]
|
|
350
416
|
);
|
|
351
417
|
|
|
418
|
+
const getCurrentWorkspaceRecord = useCallback((): WorkspaceProjectRecord | null => {
|
|
419
|
+
if (!activeProjectId) return null;
|
|
420
|
+
try {
|
|
421
|
+
const snapshot = buildCurrentSnapshot();
|
|
422
|
+
const panelState = exportPanelData();
|
|
423
|
+
const now = Date.now();
|
|
424
|
+
return {
|
|
425
|
+
id: activeProjectId,
|
|
426
|
+
name: activeProjectName || resolveProjectName(),
|
|
427
|
+
createdAt: activeCreatedAtRef.current ?? now,
|
|
428
|
+
updatedAt: now,
|
|
429
|
+
panelState,
|
|
430
|
+
blueprintDocument: snapshot.blueprintDocument,
|
|
431
|
+
blueprintMeta: snapshot.blueprintMeta,
|
|
432
|
+
productName:
|
|
433
|
+
typeof snapshot.productName === "string"
|
|
434
|
+
? snapshot.productName
|
|
435
|
+
: resolveProjectName(),
|
|
436
|
+
titleIconDataUrl: snapshot.titleIconDataUrl || undefined,
|
|
437
|
+
};
|
|
438
|
+
} catch (error) {
|
|
439
|
+
console.warn("[abuilder] getCurrentWorkspace failed", error);
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
}, [
|
|
443
|
+
activeProjectId,
|
|
444
|
+
activeProjectName,
|
|
445
|
+
buildCurrentSnapshot,
|
|
446
|
+
exportPanelData,
|
|
447
|
+
resolveProjectName,
|
|
448
|
+
]);
|
|
449
|
+
|
|
352
450
|
const handleCreateProject = useCallback(async () => {
|
|
353
451
|
return runBusyTask(t("common.savingWorkspace"), async () => {
|
|
354
452
|
const name = resolveProjectName();
|
|
355
453
|
const record = await persistProject({ name });
|
|
356
|
-
emitAbuilderEvent(AbuilderEvents.workspaceAdd,
|
|
454
|
+
emitAbuilderEvent(AbuilderEvents.workspaceAdd, {
|
|
455
|
+
...record,
|
|
456
|
+
nameSpace: eventNameSpace,
|
|
457
|
+
});
|
|
357
458
|
return { name, id: record.id };
|
|
358
459
|
});
|
|
359
|
-
}, [persistProject, resolveProjectName, t]);
|
|
460
|
+
}, [eventNameSpace, persistProject, resolveProjectName, t]);
|
|
360
461
|
|
|
361
462
|
const handleSaveProject = useCallback(async () => {
|
|
362
463
|
if (!activeProjectId) {
|
|
@@ -368,17 +469,33 @@ export function useWorkspaceProjects({
|
|
|
368
469
|
const record = await persistProject({
|
|
369
470
|
id: activeProjectId,
|
|
370
471
|
name,
|
|
371
|
-
createdAt: existing?.createdAt,
|
|
472
|
+
createdAt: existing?.createdAt ?? activeCreatedAtRef.current ?? undefined,
|
|
473
|
+
});
|
|
474
|
+
emitAbuilderEvent(AbuilderEvents.workspaceSync, {
|
|
475
|
+
...record,
|
|
476
|
+
nameSpace: eventNameSpace,
|
|
372
477
|
});
|
|
373
478
|
return { name, id: record.id };
|
|
374
479
|
});
|
|
375
|
-
}, [
|
|
480
|
+
}, [
|
|
481
|
+
activeProjectId,
|
|
482
|
+
eventNameSpace,
|
|
483
|
+
handleCreateProject,
|
|
484
|
+
nameSpace,
|
|
485
|
+
persistProject,
|
|
486
|
+
resolveProjectName,
|
|
487
|
+
t,
|
|
488
|
+
]);
|
|
376
489
|
|
|
377
490
|
const handleOpenProject = useCallback(
|
|
378
|
-
async (
|
|
491
|
+
async (idOrRecord: string | WorkspaceProjectRecord) => {
|
|
379
492
|
try {
|
|
380
493
|
await runBusyTask(t("common.loadingWorkspace"), async () => {
|
|
381
|
-
|
|
494
|
+
if (typeof idOrRecord !== "string") {
|
|
495
|
+
applyProjectRecord(idOrRecord, { persistToIndexedDb: true });
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
const record = await getWorkspaceProject(idOrRecord, nameSpace);
|
|
382
499
|
if (!record) {
|
|
383
500
|
await refreshProjects();
|
|
384
501
|
throw new Error(t("panel.messages.workspaceNotFound"));
|
|
@@ -397,24 +514,29 @@ export function useWorkspaceProjects({
|
|
|
397
514
|
if (!activeProjectId) {
|
|
398
515
|
throw new Error(t("panel.messages.saveWorkspaceFirst"));
|
|
399
516
|
}
|
|
517
|
+
// 本地无记录时 upsert,避免宿主 initialWorkspace 未落盘后同步报错
|
|
400
518
|
const existing = await getWorkspaceProject(activeProjectId, nameSpace);
|
|
401
|
-
|
|
402
|
-
setActiveProjectId(null);
|
|
403
|
-
setActiveProjectName(null);
|
|
404
|
-
syncedSnapshotRef.current = null;
|
|
405
|
-
await refreshProjects();
|
|
406
|
-
throw new Error(t("panel.messages.workspaceNotFound"));
|
|
407
|
-
}
|
|
408
|
-
const name = resolveProjectName() || existing.name;
|
|
519
|
+
const name = resolveProjectName() || existing?.name || activeProjectName || "";
|
|
409
520
|
const record = await persistProject({
|
|
410
521
|
id: activeProjectId,
|
|
411
522
|
name,
|
|
412
|
-
createdAt: existing.
|
|
523
|
+
createdAt: existing?.createdAt ?? activeCreatedAtRef.current ?? undefined,
|
|
524
|
+
});
|
|
525
|
+
emitAbuilderEvent(AbuilderEvents.workspaceSync, {
|
|
526
|
+
...record,
|
|
527
|
+
nameSpace: eventNameSpace,
|
|
413
528
|
});
|
|
414
|
-
emitAbuilderEvent(AbuilderEvents.workspaceSync, record);
|
|
415
529
|
return name;
|
|
416
530
|
});
|
|
417
|
-
}, [
|
|
531
|
+
}, [
|
|
532
|
+
activeProjectId,
|
|
533
|
+
activeProjectName,
|
|
534
|
+
eventNameSpace,
|
|
535
|
+
nameSpace,
|
|
536
|
+
persistProject,
|
|
537
|
+
resolveProjectName,
|
|
538
|
+
t,
|
|
539
|
+
]);
|
|
418
540
|
|
|
419
541
|
const handleDeleteProject = useCallback(
|
|
420
542
|
async (id: string) => {
|
|
@@ -424,6 +546,7 @@ export function useWorkspaceProjects({
|
|
|
424
546
|
if (activeProjectId === id) {
|
|
425
547
|
setActiveProjectId(null);
|
|
426
548
|
setActiveProjectName(null);
|
|
549
|
+
activeCreatedAtRef.current = null;
|
|
427
550
|
syncedSnapshotRef.current = null;
|
|
428
551
|
}
|
|
429
552
|
previewWindowsRef.current.delete(id);
|
|
@@ -478,6 +601,58 @@ export function useWorkspaceProjects({
|
|
|
478
601
|
[activeProjectId, dirty, handleSyncProject, nameSpace, refreshPreviewing, t]
|
|
479
602
|
);
|
|
480
603
|
|
|
604
|
+
useEffect(() => {
|
|
605
|
+
if (!bootstrappedRef.current) {
|
|
606
|
+
bootstrappedRef.current = true;
|
|
607
|
+
if (hasWorkspacePayload(initialWorkspace)) {
|
|
608
|
+
applyProjectRecord(initialWorkspace, {
|
|
609
|
+
notifyApplied: false,
|
|
610
|
+
persistToIndexedDb: true,
|
|
611
|
+
});
|
|
612
|
+
initialWorkspaceAppliedKeyRef.current = workspaceApplyKey(initialWorkspace);
|
|
613
|
+
if (initialWorkspace.id) {
|
|
614
|
+
workspaceIdAppliedRef.current = initialWorkspace.id;
|
|
615
|
+
}
|
|
616
|
+
} else if (typeof workspaceId === "string" && workspaceId.trim()) {
|
|
617
|
+
workspaceIdAppliedRef.current = workspaceId.trim();
|
|
618
|
+
void handleOpenProject(workspaceId.trim());
|
|
619
|
+
} else {
|
|
620
|
+
applyEmptyWorkspace();
|
|
621
|
+
}
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
if (!hasWorkspacePayload(initialWorkspace)) return;
|
|
625
|
+
const key = workspaceApplyKey(initialWorkspace);
|
|
626
|
+
if (initialWorkspaceAppliedKeyRef.current === key) return;
|
|
627
|
+
initialWorkspaceAppliedKeyRef.current = key;
|
|
628
|
+
if (initialWorkspace.id) {
|
|
629
|
+
workspaceIdAppliedRef.current = initialWorkspace.id;
|
|
630
|
+
}
|
|
631
|
+
applyProjectRecord(initialWorkspace, {
|
|
632
|
+
notifyApplied: false,
|
|
633
|
+
persistToIndexedDb: true,
|
|
634
|
+
});
|
|
635
|
+
}, [
|
|
636
|
+
applyEmptyWorkspace,
|
|
637
|
+
applyProjectRecord,
|
|
638
|
+
handleOpenProject,
|
|
639
|
+
initialWorkspace,
|
|
640
|
+
workspaceId,
|
|
641
|
+
]);
|
|
642
|
+
|
|
643
|
+
useEffect(() => {
|
|
644
|
+
if (!bootstrappedRef.current) return;
|
|
645
|
+
const id = typeof workspaceId === "string" ? workspaceId.trim() : "";
|
|
646
|
+
if (!id) return;
|
|
647
|
+
if (workspaceIdAppliedRef.current === id) return;
|
|
648
|
+
if (activeProjectId === id) {
|
|
649
|
+
workspaceIdAppliedRef.current = id;
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
workspaceIdAppliedRef.current = id;
|
|
653
|
+
void handleOpenProject(id);
|
|
654
|
+
}, [activeProjectId, handleOpenProject, workspaceId]);
|
|
655
|
+
|
|
481
656
|
return {
|
|
482
657
|
projects,
|
|
483
658
|
activeProjectId,
|
|
@@ -490,6 +665,7 @@ export function useWorkspaceProjects({
|
|
|
490
665
|
handleSyncProject,
|
|
491
666
|
handleDeleteProject,
|
|
492
667
|
handlePreviewProject,
|
|
668
|
+
getCurrentWorkspaceRecord,
|
|
493
669
|
refreshProjects,
|
|
494
670
|
};
|
|
495
671
|
}
|
|
@@ -1,10 +1,22 @@
|
|
|
1
|
+
import { normalizeAppNameSpace } from "@arronqzy/blueprint-dsl";
|
|
1
2
|
import {
|
|
2
3
|
clearAllExecutionRunRecords,
|
|
3
|
-
clearApiCollections,
|
|
4
|
+
clearApiCollections as clearApiCollectionsRaw,
|
|
4
5
|
clearBlueprintLibrary as clearBlueprintLibraryRecords,
|
|
6
|
+
deleteApiCollection as deleteApiCollectionRaw,
|
|
5
7
|
deleteBlueprintLibraryRecord as deleteBlueprintLibraryRecordRaw,
|
|
8
|
+
putApiCollection as putApiCollectionRaw,
|
|
9
|
+
putBlueprintLibraryRecord as putBlueprintLibraryRecordRaw,
|
|
10
|
+
putExecutionRunRecord as putExecutionRunRecordRaw,
|
|
11
|
+
upsertApiCollectionFromJson as upsertApiCollectionFromJsonRaw,
|
|
12
|
+
type BlueprintLibraryRecord,
|
|
13
|
+
type ExecutionRunRecord,
|
|
14
|
+
type UpsertApiCollectionInput,
|
|
15
|
+
type UpsertApiCollectionResult,
|
|
6
16
|
} from "@arronqzy/react-blueprint";
|
|
17
|
+
import type { ApiCollectionRecord } from "@arronqzy/blueprint-dsl";
|
|
7
18
|
import { broadcastBlueprintLibraryChanged } from "./blueprint-library-sync";
|
|
19
|
+
import { AbuilderEvents, emitAbuilderEvent } from "./event-subscription";
|
|
8
20
|
import { clearWorkspaceProjects } from "./workspace-storage";
|
|
9
21
|
|
|
10
22
|
export {
|
|
@@ -19,17 +31,36 @@ export {
|
|
|
19
31
|
readExecutionLogSettings,
|
|
20
32
|
writeExecutionLogSettings,
|
|
21
33
|
listApiCollectionRecords,
|
|
22
|
-
deleteApiCollection,
|
|
23
|
-
clearApiCollections,
|
|
24
34
|
} from "@arronqzy/react-blueprint";
|
|
25
35
|
export type {
|
|
26
36
|
BlueprintLibraryListItem,
|
|
27
37
|
BlueprintLibraryRecord,
|
|
28
38
|
ExecutionRunRecord,
|
|
29
39
|
ExecutionLogSettings,
|
|
40
|
+
UpsertApiCollectionInput,
|
|
41
|
+
UpsertApiCollectionResult,
|
|
30
42
|
} from "@arronqzy/react-blueprint";
|
|
31
43
|
export type { ApiCollectionRecord } from "@arronqzy/blueprint-dsl";
|
|
32
44
|
|
|
45
|
+
function resolvedNameSpace(nameSpace?: string | null): string | null {
|
|
46
|
+
return normalizeAppNameSpace(nameSpace) || null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 写入 / 覆盖蓝图库记录;已挂载的编辑器会刷新蓝图库列表 */
|
|
50
|
+
export async function putBlueprintLibraryRecord(
|
|
51
|
+
record: BlueprintLibraryRecord,
|
|
52
|
+
nameSpace?: string | null
|
|
53
|
+
): Promise<BlueprintLibraryRecord> {
|
|
54
|
+
const saved = await putBlueprintLibraryRecordRaw(record, nameSpace);
|
|
55
|
+
broadcastBlueprintLibraryChanged(nameSpace);
|
|
56
|
+
emitAbuilderEvent(AbuilderEvents.blueprintLibraryChanged, {
|
|
57
|
+
nameSpace: resolvedNameSpace(nameSpace),
|
|
58
|
+
action: "put",
|
|
59
|
+
ids: [saved.id],
|
|
60
|
+
});
|
|
61
|
+
return saved;
|
|
62
|
+
}
|
|
63
|
+
|
|
33
64
|
/** 删除蓝图库中的一条蓝图;已挂载的编辑器会刷新蓝图库列表 */
|
|
34
65
|
export async function deleteBlueprintLibraryRecord(
|
|
35
66
|
id: string,
|
|
@@ -37,12 +68,79 @@ export async function deleteBlueprintLibraryRecord(
|
|
|
37
68
|
): Promise<void> {
|
|
38
69
|
await deleteBlueprintLibraryRecordRaw(id, nameSpace);
|
|
39
70
|
broadcastBlueprintLibraryChanged(nameSpace);
|
|
71
|
+
emitAbuilderEvent(AbuilderEvents.blueprintLibraryChanged, {
|
|
72
|
+
nameSpace: resolvedNameSpace(nameSpace),
|
|
73
|
+
action: "delete",
|
|
74
|
+
ids: [id],
|
|
75
|
+
});
|
|
40
76
|
}
|
|
41
77
|
|
|
42
78
|
/** 清空该 nameSpace 下的蓝图库,返回删除数量;已挂载的编辑器会刷新蓝图库列表 */
|
|
43
79
|
export async function clearBlueprintLibrary(nameSpace?: string | null): Promise<number> {
|
|
44
80
|
const count = await clearBlueprintLibraryRecords(nameSpace);
|
|
45
81
|
broadcastBlueprintLibraryChanged(nameSpace);
|
|
82
|
+
emitAbuilderEvent(AbuilderEvents.blueprintLibraryChanged, {
|
|
83
|
+
nameSpace: resolvedNameSpace(nameSpace),
|
|
84
|
+
action: "clear",
|
|
85
|
+
});
|
|
86
|
+
return count;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** 写入一条蓝图执行日志 */
|
|
90
|
+
export async function putExecutionRunRecord(
|
|
91
|
+
record: ExecutionRunRecord,
|
|
92
|
+
nameSpace?: string | null
|
|
93
|
+
): Promise<ExecutionRunRecord> {
|
|
94
|
+
return putExecutionRunRecordRaw(record, nameSpace);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** 写入接口集合;persistLocal 时落 IndexedDB,并同步已挂载编辑器的内存列表 */
|
|
98
|
+
export async function putApiCollection(
|
|
99
|
+
record: ApiCollectionRecord,
|
|
100
|
+
nameSpace?: string | null
|
|
101
|
+
): Promise<ApiCollectionRecord> {
|
|
102
|
+
const saved = await putApiCollectionRaw(record, nameSpace);
|
|
103
|
+
emitAbuilderEvent(AbuilderEvents.apiCollectionChanged, {
|
|
104
|
+
nameSpace: resolvedNameSpace(nameSpace),
|
|
105
|
+
action: "put",
|
|
106
|
+
ids: [saved.id],
|
|
107
|
+
});
|
|
108
|
+
return saved;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** 从 JSON 文本 upsert 接口集合;可传 nameSpace */
|
|
112
|
+
export async function upsertApiCollectionFromJson(
|
|
113
|
+
input: UpsertApiCollectionInput
|
|
114
|
+
): Promise<UpsertApiCollectionResult> {
|
|
115
|
+
const result = await upsertApiCollectionFromJsonRaw(input);
|
|
116
|
+
if (result.ok) {
|
|
117
|
+
emitAbuilderEvent(AbuilderEvents.apiCollectionChanged, {
|
|
118
|
+
nameSpace: resolvedNameSpace(input.nameSpace),
|
|
119
|
+
action: "put",
|
|
120
|
+
ids: [result.record.id],
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function deleteApiCollection(
|
|
127
|
+
id: string,
|
|
128
|
+
nameSpace?: string | null
|
|
129
|
+
): Promise<void> {
|
|
130
|
+
await deleteApiCollectionRaw(id, nameSpace);
|
|
131
|
+
emitAbuilderEvent(AbuilderEvents.apiCollectionChanged, {
|
|
132
|
+
nameSpace: resolvedNameSpace(nameSpace),
|
|
133
|
+
action: "delete",
|
|
134
|
+
ids: [id],
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export async function clearApiCollections(nameSpace?: string | null): Promise<number> {
|
|
139
|
+
const count = await clearApiCollectionsRaw(nameSpace);
|
|
140
|
+
emitAbuilderEvent(AbuilderEvents.apiCollectionChanged, {
|
|
141
|
+
nameSpace: resolvedNameSpace(nameSpace),
|
|
142
|
+
action: "clear",
|
|
143
|
+
});
|
|
46
144
|
return count;
|
|
47
145
|
}
|
|
48
146
|
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { normalizeAppNameSpace } from "@arronqzy/blueprint-dsl";
|
|
2
|
+
import type { WorkspaceProjectRecord } from "./workspace-project-db";
|
|
3
|
+
|
|
4
|
+
/** 已挂载编辑面板的命令式句柄(也可通过 `ref` 拿到) */
|
|
5
|
+
export type AbuilderViewHandle = {
|
|
6
|
+
/** 当前 nameSpace */
|
|
7
|
+
getNameSpace: () => string | null;
|
|
8
|
+
/** 导出当前编辑器中的完整工作区;尚无关联 id 时返回 null */
|
|
9
|
+
getCurrentWorkspace: () => WorkspaceProjectRecord | null;
|
|
10
|
+
/** 按 id 从 IndexedDB 打开,或直接应用完整记录 */
|
|
11
|
+
openWorkspace: (idOrRecord: string | WorkspaceProjectRecord) => Promise<void>;
|
|
12
|
+
/** 创建新工作区并写入 IndexedDB,发出 workspace:add */
|
|
13
|
+
createWorkspace: () => Promise<{ name: string; id: string }>;
|
|
14
|
+
/** 保存当前工作区到 IndexedDB,发出 workspace:sync;无 active 时等同创建 */
|
|
15
|
+
saveWorkspace: () => Promise<{ name: string; id: string }>;
|
|
16
|
+
/** 同步当前工作区到 IndexedDB,发出 workspace:sync */
|
|
17
|
+
syncWorkspace: () => Promise<string>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const handles = new Set<AbuilderViewHandle>();
|
|
21
|
+
|
|
22
|
+
export function registerAbuilderViewHandle(handle: AbuilderViewHandle): () => void {
|
|
23
|
+
handles.add(handle);
|
|
24
|
+
return () => {
|
|
25
|
+
handles.delete(handle);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function findHandle(nameSpace?: string | null): AbuilderViewHandle | null {
|
|
30
|
+
if (handles.size === 0) return null;
|
|
31
|
+
if (nameSpace === undefined) {
|
|
32
|
+
return handles.values().next().value ?? null;
|
|
33
|
+
}
|
|
34
|
+
const wanted = normalizeAppNameSpace(nameSpace);
|
|
35
|
+
for (const handle of handles) {
|
|
36
|
+
if (normalizeAppNameSpace(handle.getNameSpace()) === wanted) return handle;
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 读取当前已挂载编辑器中的工作区快照。
|
|
43
|
+
* 同页多个实例时请传入 `nameSpace`;未挂载或尚无 active 工作区时返回 null。
|
|
44
|
+
*/
|
|
45
|
+
export function getCurrentWorkspace(
|
|
46
|
+
nameSpace?: string | null
|
|
47
|
+
): WorkspaceProjectRecord | null {
|
|
48
|
+
return findHandle(nameSpace)?.getCurrentWorkspace() ?? null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 命令式打开工作区(不 remount)。
|
|
53
|
+
* 同页多个实例时请传入 `nameSpace`。
|
|
54
|
+
*/
|
|
55
|
+
export async function openWorkspace(
|
|
56
|
+
idOrRecord: string | WorkspaceProjectRecord,
|
|
57
|
+
nameSpace?: string | null
|
|
58
|
+
): Promise<void> {
|
|
59
|
+
const handle = findHandle(nameSpace);
|
|
60
|
+
if (!handle) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
"No mounted Abuilder panel. Pass initialWorkspace or mount <App> first."
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
await handle.openWorkspace(idOrRecord);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function createWorkspace(
|
|
69
|
+
nameSpace?: string | null
|
|
70
|
+
): Promise<{ name: string; id: string }> {
|
|
71
|
+
const handle = findHandle(nameSpace);
|
|
72
|
+
if (!handle) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
"No mounted Abuilder panel. Pass initialWorkspace or mount <App> first."
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
return handle.createWorkspace();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function saveWorkspace(
|
|
81
|
+
nameSpace?: string | null
|
|
82
|
+
): Promise<{ name: string; id: string }> {
|
|
83
|
+
const handle = findHandle(nameSpace);
|
|
84
|
+
if (!handle) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
"No mounted Abuilder panel. Pass initialWorkspace or mount <App> first."
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
return handle.saveWorkspace();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function syncWorkspace(nameSpace?: string | null): Promise<string> {
|
|
93
|
+
const handle = findHandle(nameSpace);
|
|
94
|
+
if (!handle) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
"No mounted Abuilder panel. Pass initialWorkspace or mount <App> first."
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
return handle.syncWorkspace();
|
|
100
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeAppNameSpace } from "@arronqzy/blueprint-dsl";
|
|
1
2
|
import type { WorkspaceProjectRecord } from "./workspace-project-db";
|
|
2
3
|
|
|
3
4
|
export const AbuilderEvents = {
|
|
@@ -7,10 +8,14 @@ export const AbuilderEvents = {
|
|
|
7
8
|
workspaceSync: "workspace:sync",
|
|
8
9
|
/** 删除工作区(界面删除,或调用 deleteWorkspaceProject / clearWorkspaceProjects) */
|
|
9
10
|
workspaceDelete: "workspace:delete",
|
|
11
|
+
/** 蓝图库写入 / 删除 / 清空 */
|
|
12
|
+
blueprintLibraryChanged: "blueprint-library:changed",
|
|
10
13
|
/** 开始上传接口集合 */
|
|
11
14
|
apiCollectionUploadStart: "api-collection:upload-start",
|
|
12
15
|
/** 上传接口集合成功 */
|
|
13
16
|
apiCollectionUploadSuccess: "api-collection:upload-success",
|
|
17
|
+
/** 接口集合写入 / 删除 / 清空 */
|
|
18
|
+
apiCollectionChanged: "api-collection:changed",
|
|
14
19
|
} as const;
|
|
15
20
|
|
|
16
21
|
export type AbuilderEventName = (typeof AbuilderEvents)[keyof typeof AbuilderEvents];
|
|
@@ -18,8 +23,14 @@ export type AbuilderEventName = (typeof AbuilderEvents)[keyof typeof AbuilderEve
|
|
|
18
23
|
/** 工作区完整数据(面板 + 蓝图),可用于外部持久化与再次加载 */
|
|
19
24
|
export type WorkspaceData = WorkspaceProjectRecord;
|
|
20
25
|
|
|
21
|
-
export type WorkspaceAddEventPayload = WorkspaceData
|
|
22
|
-
|
|
26
|
+
export type WorkspaceAddEventPayload = WorkspaceData & {
|
|
27
|
+
/** 所属 nameSpace,默认库为 null */
|
|
28
|
+
nameSpace: string | null;
|
|
29
|
+
};
|
|
30
|
+
export type WorkspaceSyncEventPayload = WorkspaceData & {
|
|
31
|
+
/** 所属 nameSpace,默认库为 null */
|
|
32
|
+
nameSpace: string | null;
|
|
33
|
+
};
|
|
23
34
|
export type WorkspaceDeleteEventPayload = {
|
|
24
35
|
/** 被删除的工作区 id */
|
|
25
36
|
ids: string[];
|
|
@@ -27,9 +38,17 @@ export type WorkspaceDeleteEventPayload = {
|
|
|
27
38
|
nameSpace: string | null;
|
|
28
39
|
};
|
|
29
40
|
|
|
41
|
+
export type BlueprintLibraryChangedEventPayload = {
|
|
42
|
+
nameSpace: string | null;
|
|
43
|
+
action: "put" | "delete" | "clear";
|
|
44
|
+
/** 涉及的蓝图 id;clear 时可能为空 */
|
|
45
|
+
ids?: string[];
|
|
46
|
+
};
|
|
47
|
+
|
|
30
48
|
export type ApiCollectionUploadStartEventPayload = {
|
|
31
49
|
fileNames: string[];
|
|
32
50
|
count: number;
|
|
51
|
+
nameSpace: string | null;
|
|
33
52
|
};
|
|
34
53
|
|
|
35
54
|
export type ApiCollectionUploadSuccessEventPayload = {
|
|
@@ -39,20 +58,57 @@ export type ApiCollectionUploadSuccessEventPayload = {
|
|
|
39
58
|
apiCount: number;
|
|
40
59
|
persistLocal: boolean;
|
|
41
60
|
}>;
|
|
61
|
+
nameSpace: string | null;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export type ApiCollectionChangedEventPayload = {
|
|
65
|
+
nameSpace: string | null;
|
|
66
|
+
action: "put" | "delete" | "clear";
|
|
67
|
+
ids?: string[];
|
|
42
68
|
};
|
|
43
69
|
|
|
44
70
|
export type AbuilderEventPayloadMap = {
|
|
45
71
|
[AbuilderEvents.workspaceAdd]: WorkspaceAddEventPayload;
|
|
46
72
|
[AbuilderEvents.workspaceSync]: WorkspaceSyncEventPayload;
|
|
47
73
|
[AbuilderEvents.workspaceDelete]: WorkspaceDeleteEventPayload;
|
|
74
|
+
[AbuilderEvents.blueprintLibraryChanged]: BlueprintLibraryChangedEventPayload;
|
|
48
75
|
[AbuilderEvents.apiCollectionUploadStart]: ApiCollectionUploadStartEventPayload;
|
|
49
76
|
[AbuilderEvents.apiCollectionUploadSuccess]: ApiCollectionUploadSuccessEventPayload;
|
|
77
|
+
[AbuilderEvents.apiCollectionChanged]: ApiCollectionChangedEventPayload;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export type AddEventSubscriptionOptions = {
|
|
81
|
+
/**
|
|
82
|
+
* 仅接收该 nameSpace 的事件。
|
|
83
|
+
* 省略则接收全部;传入空字符串 / null 表示默认库。
|
|
84
|
+
*/
|
|
85
|
+
nameSpace?: string | null;
|
|
50
86
|
};
|
|
51
87
|
|
|
52
88
|
type EventCallback = (payload: unknown) => void;
|
|
53
89
|
|
|
54
90
|
const listeners = new Map<string, Set<EventCallback>>();
|
|
55
91
|
|
|
92
|
+
function resolvePayloadNameSpace(payload: unknown): string | null | undefined {
|
|
93
|
+
if (!payload || typeof payload !== "object") return undefined;
|
|
94
|
+
if (!("nameSpace" in payload)) return undefined;
|
|
95
|
+
const value = (payload as { nameSpace?: unknown }).nameSpace;
|
|
96
|
+
if (value === null || value === undefined) return null;
|
|
97
|
+
if (typeof value === "string") return value;
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function matchesNameSpaceFilter(
|
|
102
|
+
payload: unknown,
|
|
103
|
+
filter: string | null | undefined
|
|
104
|
+
): boolean {
|
|
105
|
+
if (filter === undefined) return true;
|
|
106
|
+
const wanted = normalizeAppNameSpace(filter);
|
|
107
|
+
const actual = resolvePayloadNameSpace(payload);
|
|
108
|
+
if (actual === undefined) return true;
|
|
109
|
+
return normalizeAppNameSpace(actual) === wanted;
|
|
110
|
+
}
|
|
111
|
+
|
|
56
112
|
export function emitAbuilderEvent<T extends AbuilderEventName>(
|
|
57
113
|
eventName: T,
|
|
58
114
|
payload: AbuilderEventPayloadMap[T]
|
|
@@ -70,17 +126,26 @@ export function emitAbuilderEvent<T extends AbuilderEventName>(
|
|
|
70
126
|
|
|
71
127
|
export function addEventSubscription<T extends AbuilderEventName>(
|
|
72
128
|
eventName: T,
|
|
73
|
-
callback: (payload: AbuilderEventPayloadMap[T]) => void
|
|
129
|
+
callback: (payload: AbuilderEventPayloadMap[T]) => void,
|
|
130
|
+
options?: AddEventSubscriptionOptions
|
|
74
131
|
): { unsubscribe: () => void } {
|
|
75
132
|
let set = listeners.get(eventName);
|
|
76
133
|
if (!set) {
|
|
77
134
|
set = new Set();
|
|
78
135
|
listeners.set(eventName, set);
|
|
79
136
|
}
|
|
80
|
-
|
|
137
|
+
const filter =
|
|
138
|
+
options && Object.prototype.hasOwnProperty.call(options, "nameSpace")
|
|
139
|
+
? options.nameSpace
|
|
140
|
+
: undefined;
|
|
141
|
+
const wrapped: EventCallback = (payload) => {
|
|
142
|
+
if (!matchesNameSpaceFilter(payload, filter)) return;
|
|
143
|
+
callback(payload as AbuilderEventPayloadMap[T]);
|
|
144
|
+
};
|
|
145
|
+
set.add(wrapped);
|
|
81
146
|
return {
|
|
82
147
|
unsubscribe: () => {
|
|
83
|
-
set.delete(
|
|
148
|
+
set.delete(wrapped);
|
|
84
149
|
if (set.size === 0) {
|
|
85
150
|
listeners.delete(eventName);
|
|
86
151
|
}
|
|
@@ -11,6 +11,7 @@ export type WorkspaceProjectSyncMessage = {
|
|
|
11
11
|
|
|
12
12
|
export type WorkspaceProjectsChangedMessage =
|
|
13
13
|
| { type: "deleted"; projectIds: string[] }
|
|
14
|
+
| { type: "upserted"; projectIds: string[] }
|
|
14
15
|
| { type: "cleared" };
|
|
15
16
|
|
|
16
17
|
export function broadcastWorkspaceProjectsChanged(
|
|
@@ -33,7 +34,12 @@ export function subscribeWorkspaceProjectsChanged(
|
|
|
33
34
|
const channel = new BroadcastChannel(appStorageKey(LIST_CHANNEL, nameSpace));
|
|
34
35
|
channel.onmessage = (event: MessageEvent<WorkspaceProjectsChangedMessage>) => {
|
|
35
36
|
const data = event.data;
|
|
36
|
-
if (
|
|
37
|
+
if (
|
|
38
|
+
!data ||
|
|
39
|
+
(data.type !== "deleted" && data.type !== "cleared" && data.type !== "upserted")
|
|
40
|
+
) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
37
43
|
onChange(data);
|
|
38
44
|
};
|
|
39
45
|
return () => channel.close();
|
|
@@ -4,16 +4,70 @@ import {
|
|
|
4
4
|
deleteWorkspaceProject as deleteWorkspaceProjectRecord,
|
|
5
5
|
getWorkspaceProject,
|
|
6
6
|
listWorkspaceProjects,
|
|
7
|
+
putWorkspaceProject as putWorkspaceProjectRecord,
|
|
8
|
+
type WorkspaceProjectRecord,
|
|
7
9
|
} from "./workspace-project-db";
|
|
8
10
|
import {
|
|
9
11
|
clearWorkspacePreviewCaches,
|
|
10
12
|
removeWorkspacePreviewCache,
|
|
13
|
+
writeWorkspacePreviewCache,
|
|
11
14
|
} from "./workspace-project-cache";
|
|
12
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
broadcastWorkspaceProjectUpdate,
|
|
17
|
+
broadcastWorkspaceProjectsChanged,
|
|
18
|
+
} from "./workspace-project-sync";
|
|
13
19
|
import { AbuilderEvents, emitAbuilderEvent } from "./event-subscription";
|
|
14
20
|
|
|
15
21
|
export { listWorkspaceProjects, getWorkspaceProject };
|
|
16
|
-
export type { WorkspaceProjectListItem } from "./workspace-project-db";
|
|
22
|
+
export type { WorkspaceProjectListItem, WorkspaceProjectRecord } from "./workspace-project-db";
|
|
23
|
+
|
|
24
|
+
export type PutWorkspaceProjectOptions = {
|
|
25
|
+
/**
|
|
26
|
+
* 写入后是否发出工作区事件。
|
|
27
|
+
* - `false` / 省略:只写 IndexedDB(宿主自己管后端时常用)
|
|
28
|
+
* - `"add"`:发出 `workspace:add`
|
|
29
|
+
* - `"sync"`:发出 `workspace:sync`
|
|
30
|
+
*/
|
|
31
|
+
emitEvent?: false | "add" | "sync";
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function resolvedNameSpace(nameSpace?: string | null): string | null {
|
|
35
|
+
return normalizeAppNameSpace(nameSpace) || null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 写入 / 覆盖 IndexedDB 中的工作区(upsert),并更新预览缓存、通知其他标签页。
|
|
40
|
+
* `nameSpace` 需与面板的 `nameSpace` 一致。
|
|
41
|
+
*/
|
|
42
|
+
export async function putWorkspaceProject(
|
|
43
|
+
record: WorkspaceProjectRecord,
|
|
44
|
+
nameSpace?: string | null,
|
|
45
|
+
options?: PutWorkspaceProjectOptions
|
|
46
|
+
): Promise<WorkspaceProjectRecord> {
|
|
47
|
+
const saved = await putWorkspaceProjectRecord(record, nameSpace);
|
|
48
|
+
writeWorkspacePreviewCache(saved, nameSpace);
|
|
49
|
+
broadcastWorkspaceProjectUpdate(saved.id, saved.updatedAt, nameSpace);
|
|
50
|
+
broadcastWorkspaceProjectsChanged(
|
|
51
|
+
{ type: "upserted", projectIds: [saved.id] },
|
|
52
|
+
nameSpace
|
|
53
|
+
);
|
|
54
|
+
const ns = resolvedNameSpace(nameSpace);
|
|
55
|
+
if (options?.emitEvent === "add") {
|
|
56
|
+
emitAbuilderEvent(AbuilderEvents.workspaceAdd, { ...saved, nameSpace: ns });
|
|
57
|
+
} else if (options?.emitEvent === "sync") {
|
|
58
|
+
emitAbuilderEvent(AbuilderEvents.workspaceSync, { ...saved, nameSpace: ns });
|
|
59
|
+
}
|
|
60
|
+
return saved;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** `putWorkspaceProject` 的别名 */
|
|
64
|
+
export async function upsertWorkspaceProject(
|
|
65
|
+
record: WorkspaceProjectRecord,
|
|
66
|
+
nameSpace?: string | null,
|
|
67
|
+
options?: PutWorkspaceProjectOptions
|
|
68
|
+
): Promise<WorkspaceProjectRecord> {
|
|
69
|
+
return putWorkspaceProject(record, nameSpace, options);
|
|
70
|
+
}
|
|
17
71
|
|
|
18
72
|
/**
|
|
19
73
|
* 删除 IndexedDB 中保存的单个工作区(及其 localStorage 预览标记)。
|
|
@@ -28,7 +82,7 @@ export async function deleteWorkspaceProject(
|
|
|
28
82
|
broadcastWorkspaceProjectsChanged({ type: "deleted", projectIds: [id] }, nameSpace);
|
|
29
83
|
emitAbuilderEvent(AbuilderEvents.workspaceDelete, {
|
|
30
84
|
ids: [id],
|
|
31
|
-
nameSpace:
|
|
85
|
+
nameSpace: resolvedNameSpace(nameSpace),
|
|
32
86
|
});
|
|
33
87
|
}
|
|
34
88
|
|
|
@@ -44,7 +98,7 @@ export async function clearWorkspaceProjects(nameSpace?: string | null): Promise
|
|
|
44
98
|
if (items.length > 0) {
|
|
45
99
|
emitAbuilderEvent(AbuilderEvents.workspaceDelete, {
|
|
46
100
|
ids: items.map((item) => item.id),
|
|
47
|
-
nameSpace:
|
|
101
|
+
nameSpace: resolvedNameSpace(nameSpace),
|
|
48
102
|
});
|
|
49
103
|
}
|
|
50
104
|
return items.length;
|