@arronqzy/vue-view 0.1.24 → 0.1.25
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/README.md +1 -11
- package/package.json +7 -7
- package/src/index.ts +25 -1
- package/src/panel/VueViewOnlinePreview.vue +25 -0
- package/src/panel/VueViewPanel.vue +53 -2
- package/src/panel/hooks/useWorkspaceProjects.ts +209 -67
- package/src/panel/library/abuilder-storage.ts +101 -3
- package/src/panel/library/abuilder-view-handle.ts +100 -0
- package/src/panel/library/capture-preview-snapshot.ts +800 -0
- package/src/panel/library/event-subscription.ts +70 -5
- package/src/panel/library/preview-snapshot.ts +252 -0
- package/src/panel/library/workspace-project-sync.ts +7 -1
- package/src/panel/library/workspace-storage.ts +58 -4
package/README.md
CHANGED
|
@@ -49,17 +49,7 @@ import {
|
|
|
49
49
|
} from "@arronqzy/vue-view";
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
创建 / 同步发出 `workspace:add` / `workspace:sync
|
|
53
|
-
|
|
54
|
-
浏览器里的本地数据可以读取和清理,最后一个参数都是 `nameSpace`:
|
|
55
|
-
|
|
56
|
-
- 工作区:`listWorkspaceProjects` / `getWorkspaceProject` / `deleteWorkspaceProject` / `clearWorkspaceProjects`
|
|
57
|
-
- 蓝图库:`listBlueprintLibrary` / `getBlueprintLibraryRecord` / `deleteBlueprintLibraryRecord` / `clearBlueprintLibrary`
|
|
58
|
-
- 蓝图执行日志:`listExecutionRunRecords` / `getExecutionRunRecord` / `deleteExecutionRunRecord` / `clearAllExecutionRunRecords` / `purgeExecutionRunsOlderThan` / `trimExecutionRunRecordsToMax`,保留策略用 `readExecutionLogSettings` / `writeExecutionLogSettings`
|
|
59
|
-
- 接口集合:`listApiCollectionRecords` / `deleteApiCollection` / `clearApiCollections`
|
|
60
|
-
- `clearAbuilderStorage`:一次清空以上四类,返回各自删除数量
|
|
61
|
-
|
|
62
|
-
工作区、蓝图库的写入接口不导出(由编辑器自己维护)。`getPreviewSnapshot` 只在 React 包实现。
|
|
52
|
+
创建 / 同步发出 `workspace:add` / `workspace:sync`(载荷含 `nameSpace`),删除发出 `workspace:delete`。另有 `blueprint-library:changed` / `api-collection:changed`。`parseWorkspaceData` 的 `value` 可作 `initialWorkspace`;带 `id` 时默认同步进 IndexedDB。也支持 `workspaceId` prop、`ref`(`AbuilderViewHandle`)以及 `put*` / `openWorkspace` / `getCurrentWorkspace` / `getPreviewSnapshot` 等与 React 对齐的公开 API。
|
|
63
53
|
|
|
64
54
|
## 和 React 版的差别
|
|
65
55
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arronqzy/vue-view",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.25",
|
|
4
4
|
"description": "Vue 3 visual view editor for Abuilder (Ant Design Vue)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -23,13 +23,13 @@
|
|
|
23
23
|
"selecto": "^1.26.3",
|
|
24
24
|
"uuid": "^13.0.0",
|
|
25
25
|
"vue": "^3.5.13",
|
|
26
|
-
"@arronqzy/i18n": "0.1.17",
|
|
27
|
-
"@arronqzy/rx-store": "1.0.8",
|
|
28
26
|
"@arronqzy/blueprint-dsl": "1.0.17",
|
|
27
|
+
"@arronqzy/i18n": "0.1.17",
|
|
29
28
|
"@arronqzy/view-scene3d": "0.1.5",
|
|
30
|
-
"@arronqzy/view-table": "0.1.8",
|
|
31
29
|
"@arronqzy/vue-rx-store": "0.1.6",
|
|
32
|
-
"@arronqzy/
|
|
30
|
+
"@arronqzy/view-table": "0.1.8",
|
|
31
|
+
"@arronqzy/vue-blueprint": "0.1.19",
|
|
32
|
+
"@arronqzy/rx-store": "1.0.8"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"vue": "^3.4.0"
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"eslint": "^8.57.0",
|
|
40
40
|
"typescript": "^5.5.4",
|
|
41
41
|
"vue-tsc": "^2.2.0",
|
|
42
|
-
"@arronqzy/
|
|
43
|
-
"@arronqzy/
|
|
42
|
+
"@arronqzy/typescript-config": "1.0.0",
|
|
43
|
+
"@arronqzy/eslint-config": "0.0.0"
|
|
44
44
|
},
|
|
45
45
|
"publishConfig": {
|
|
46
46
|
"access": "public"
|
package/src/index.ts
CHANGED
|
@@ -16,17 +16,24 @@ export { createWorkspaceProjectId } from "./panel/library/workspace-project-db";
|
|
|
16
16
|
export {
|
|
17
17
|
listWorkspaceProjects,
|
|
18
18
|
getWorkspaceProject,
|
|
19
|
+
putWorkspaceProject,
|
|
20
|
+
upsertWorkspaceProject,
|
|
19
21
|
deleteWorkspaceProject,
|
|
20
22
|
clearWorkspaceProjects,
|
|
21
23
|
} from "./panel/library/workspace-storage";
|
|
22
|
-
export type {
|
|
24
|
+
export type {
|
|
25
|
+
WorkspaceProjectListItem,
|
|
26
|
+
PutWorkspaceProjectOptions,
|
|
27
|
+
} from "./panel/library/workspace-storage";
|
|
23
28
|
export {
|
|
24
29
|
listBlueprintLibrary,
|
|
25
30
|
getBlueprintLibraryRecord,
|
|
31
|
+
putBlueprintLibraryRecord,
|
|
26
32
|
deleteBlueprintLibraryRecord,
|
|
27
33
|
clearBlueprintLibrary,
|
|
28
34
|
listExecutionRunRecords,
|
|
29
35
|
getExecutionRunRecord,
|
|
36
|
+
putExecutionRunRecord,
|
|
30
37
|
deleteExecutionRunRecord,
|
|
31
38
|
clearAllExecutionRunRecords,
|
|
32
39
|
purgeExecutionRunsOlderThan,
|
|
@@ -34,6 +41,8 @@ export {
|
|
|
34
41
|
readExecutionLogSettings,
|
|
35
42
|
writeExecutionLogSettings,
|
|
36
43
|
listApiCollectionRecords,
|
|
44
|
+
putApiCollection,
|
|
45
|
+
upsertApiCollectionFromJson,
|
|
37
46
|
deleteApiCollection,
|
|
38
47
|
clearApiCollections,
|
|
39
48
|
clearAbuilderStorage,
|
|
@@ -45,6 +54,8 @@ export type {
|
|
|
45
54
|
ExecutionRunRecord,
|
|
46
55
|
ExecutionLogSettings,
|
|
47
56
|
ApiCollectionRecord,
|
|
57
|
+
UpsertApiCollectionInput,
|
|
58
|
+
UpsertApiCollectionResult,
|
|
48
59
|
} from "./panel/library/abuilder-storage";
|
|
49
60
|
export {
|
|
50
61
|
addEventSubscription,
|
|
@@ -53,14 +64,27 @@ export {
|
|
|
53
64
|
export type {
|
|
54
65
|
AbuilderEventName,
|
|
55
66
|
AbuilderEventPayloadMap,
|
|
67
|
+
AddEventSubscriptionOptions,
|
|
56
68
|
WorkspaceAddEventPayload,
|
|
57
69
|
WorkspaceSyncEventPayload,
|
|
58
70
|
WorkspaceDeleteEventPayload,
|
|
71
|
+
BlueprintLibraryChangedEventPayload,
|
|
59
72
|
ApiCollectionUploadStartEventPayload,
|
|
60
73
|
ApiCollectionUploadSuccessEventPayload,
|
|
74
|
+
ApiCollectionChangedEventPayload,
|
|
61
75
|
WorkspaceData,
|
|
62
76
|
} from "./panel/library/event-subscription";
|
|
77
|
+
export {
|
|
78
|
+
getCurrentWorkspace,
|
|
79
|
+
openWorkspace,
|
|
80
|
+
createWorkspace,
|
|
81
|
+
saveWorkspace,
|
|
82
|
+
syncWorkspace,
|
|
83
|
+
} from "./panel/library/abuilder-view-handle";
|
|
84
|
+
export type { AbuilderViewHandle } from "./panel/library/abuilder-view-handle";
|
|
63
85
|
export type {
|
|
64
86
|
ParseCheckResult,
|
|
65
87
|
WorkspaceParseCheckResult,
|
|
66
88
|
} from "./panel/library/parse-workspace-data";
|
|
89
|
+
export { getPreviewSnapshot } from "./panel/library/preview-snapshot";
|
|
90
|
+
export type { GetPreviewSnapshotOptions } from "./panel/library/preview-snapshot";
|
|
@@ -40,6 +40,8 @@ import {
|
|
|
40
40
|
resolvePreviewLayerElements,
|
|
41
41
|
} from "./utils/panelStateIO";
|
|
42
42
|
import { applyPreviewSceneFill, readOutputScale } from "./utils/outputScale";
|
|
43
|
+
import { registerPreviewSnapshotProvider } from "./library/preview-snapshot";
|
|
44
|
+
import { capturePreviewSceneElement } from "./library/capture-preview-snapshot";
|
|
43
45
|
|
|
44
46
|
const { t, locale } = useI18n();
|
|
45
47
|
useBlueprintDslLocaleSync();
|
|
@@ -292,6 +294,29 @@ onMounted(() => {
|
|
|
292
294
|
onUnmounted(() => window.removeEventListener("resize", onResize));
|
|
293
295
|
});
|
|
294
296
|
|
|
297
|
+
watch(
|
|
298
|
+
[layoutReady, sceneRef, sceneBounds, fillScale, outputScale],
|
|
299
|
+
() => {
|
|
300
|
+
if (!layoutReady.value || !sceneRef.value) {
|
|
301
|
+
registerPreviewSnapshotProvider(null);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
registerPreviewSnapshotProvider(async () => {
|
|
305
|
+
const scene = sceneRef.value;
|
|
306
|
+
if (!scene) return null;
|
|
307
|
+
const width = outputScale.value
|
|
308
|
+
? sceneBounds.value.width
|
|
309
|
+
: sceneBounds.value.width * fillScale.value.scaleX;
|
|
310
|
+
const height = outputScale.value
|
|
311
|
+
? sceneBounds.value.height
|
|
312
|
+
: sceneBounds.value.height * fillScale.value.scaleY;
|
|
313
|
+
return capturePreviewSceneElement(scene, width, height);
|
|
314
|
+
});
|
|
315
|
+
},
|
|
316
|
+
{ immediate: true }
|
|
317
|
+
);
|
|
318
|
+
onUnmounted(() => registerPreviewSnapshotProvider(null));
|
|
319
|
+
|
|
295
320
|
function noopUpdate() {}
|
|
296
321
|
function noopSelect() {}
|
|
297
322
|
</script>
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, onMounted, onUnmounted, ref, shallowRef, watch } from "vue";
|
|
3
|
+
import {
|
|
4
|
+
registerAbuilderViewHandle,
|
|
5
|
+
type AbuilderViewHandle,
|
|
6
|
+
} from "./library/abuilder-view-handle";
|
|
7
|
+
import {
|
|
8
|
+
captureEditorPreviewSnapshot,
|
|
9
|
+
registerPreviewSnapshotProvider,
|
|
10
|
+
} from "./library/preview-snapshot";
|
|
11
|
+
import { readOutputScale } from "./utils/outputScale";
|
|
3
12
|
import type { State } from "@arronqzy/rx-store";
|
|
4
13
|
import {
|
|
5
14
|
useBlueprintDslLocaleSync,
|
|
@@ -90,6 +99,11 @@ const props = withDefaults(
|
|
|
90
99
|
initialZoom?: number;
|
|
91
100
|
nameSpace?: string | null;
|
|
92
101
|
initialWorkspace?: WorkspaceProjectRecord | null;
|
|
102
|
+
/**
|
|
103
|
+
* 按 IndexedDB id 打开工作区(不 remount)。
|
|
104
|
+
* 与 initialWorkspace 同时存在时:首次优先完整记录;之后 id 变化从 IndexedDB 打开。
|
|
105
|
+
*/
|
|
106
|
+
workspaceId?: string | null;
|
|
93
107
|
/** 顶栏「设置 → 关于」展示的 npm 包名 */
|
|
94
108
|
packageName?: string | null;
|
|
95
109
|
/** 顶栏「设置 → 关于」展示的包版本号 */
|
|
@@ -99,6 +113,7 @@ const props = withDefaults(
|
|
|
99
113
|
initialZoom: 1,
|
|
100
114
|
nameSpace: null,
|
|
101
115
|
initialWorkspace: null,
|
|
116
|
+
workspaceId: null,
|
|
102
117
|
packageName: null,
|
|
103
118
|
packageVersion: null,
|
|
104
119
|
}
|
|
@@ -470,9 +485,39 @@ const workspaceProjects = useWorkspaceProjects({
|
|
|
470
485
|
panelRevision: computed(() => `${historyCursor.value}|${allElements.value.length}`),
|
|
471
486
|
onProjectApplied: handleWorkspaceProjectApplied,
|
|
472
487
|
initialWorkspace: () => props.initialWorkspace,
|
|
488
|
+
workspaceId: () => props.workspaceId,
|
|
473
489
|
nameSpace: () => props.nameSpace,
|
|
474
490
|
});
|
|
475
491
|
|
|
492
|
+
const viewHandle: AbuilderViewHandle = {
|
|
493
|
+
getNameSpace: () => props.nameSpace ?? null,
|
|
494
|
+
getCurrentWorkspace: () => workspaceProjects.getCurrentWorkspaceRecord(),
|
|
495
|
+
openWorkspace: (idOrRecord) => workspaceProjects.handleOpenProject(idOrRecord),
|
|
496
|
+
createWorkspace: () => workspaceProjects.handleCreateProject(),
|
|
497
|
+
saveWorkspace: () => workspaceProjects.handleSaveProject(),
|
|
498
|
+
syncWorkspace: () => workspaceProjects.handleSyncProject(),
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
defineExpose(viewHandle);
|
|
502
|
+
onUnmounted(registerAbuilderViewHandle(viewHandle));
|
|
503
|
+
|
|
504
|
+
watch(
|
|
505
|
+
[canvasEl, allElements, layers, activeLayerId, () => props.nameSpace],
|
|
506
|
+
() => {
|
|
507
|
+
registerPreviewSnapshotProvider(async () =>
|
|
508
|
+
captureEditorPreviewSnapshot({
|
|
509
|
+
canvasRoot: canvasEl.value,
|
|
510
|
+
allElements: allElements.value,
|
|
511
|
+
layers: layers.value,
|
|
512
|
+
activeLayerId: activeLayerId.value,
|
|
513
|
+
outputScale: readOutputScale(props.nameSpace),
|
|
514
|
+
})
|
|
515
|
+
);
|
|
516
|
+
},
|
|
517
|
+
{ immediate: true }
|
|
518
|
+
);
|
|
519
|
+
onUnmounted(() => registerPreviewSnapshotProvider(null));
|
|
520
|
+
|
|
476
521
|
const blueprintLifecycleToken = computed(() => {
|
|
477
522
|
const doc = blueprintGraph.value.document;
|
|
478
523
|
const nodes = doc.nodes;
|
|
@@ -646,7 +691,10 @@ function handleApiCollectionUploadStart(info: {
|
|
|
646
691
|
fileNames: string[];
|
|
647
692
|
count: number;
|
|
648
693
|
}) {
|
|
649
|
-
emitAbuilderEvent(AbuilderEvents.apiCollectionUploadStart,
|
|
694
|
+
emitAbuilderEvent(AbuilderEvents.apiCollectionUploadStart, {
|
|
695
|
+
...info,
|
|
696
|
+
nameSpace: props.nameSpace ?? null,
|
|
697
|
+
});
|
|
650
698
|
}
|
|
651
699
|
|
|
652
700
|
function handleApiCollectionUploadSuccess(info: {
|
|
@@ -657,7 +705,10 @@ function handleApiCollectionUploadSuccess(info: {
|
|
|
657
705
|
persistLocal: boolean;
|
|
658
706
|
}>;
|
|
659
707
|
}) {
|
|
660
|
-
emitAbuilderEvent(AbuilderEvents.apiCollectionUploadSuccess,
|
|
708
|
+
emitAbuilderEvent(AbuilderEvents.apiCollectionUploadSuccess, {
|
|
709
|
+
...info,
|
|
710
|
+
nameSpace: props.nameSpace ?? null,
|
|
711
|
+
});
|
|
661
712
|
}
|
|
662
713
|
|
|
663
714
|
function handleApiCollectionSync(record: {
|
|
@@ -11,16 +11,16 @@ import {
|
|
|
11
11
|
createWorkspaceProjectId,
|
|
12
12
|
getWorkspaceProject,
|
|
13
13
|
listWorkspaceProjects,
|
|
14
|
-
putWorkspaceProject,
|
|
15
14
|
type WorkspaceProjectListItem,
|
|
16
15
|
type WorkspaceProjectRecord,
|
|
17
16
|
} from "../library/workspace-project-db";
|
|
18
|
-
import { deleteWorkspaceProject } from "../library/workspace-storage";
|
|
19
17
|
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
} from "../library/workspace-
|
|
18
|
+
deleteWorkspaceProject,
|
|
19
|
+
putWorkspaceProject,
|
|
20
|
+
} from "../library/workspace-storage";
|
|
21
|
+
import { subscribeWorkspaceProjectsChanged } from "../library/workspace-project-sync";
|
|
23
22
|
import { writeWorkspacePreviewCache, readWorkspacePreviewCache } from "../library/workspace-project-cache";
|
|
23
|
+
import { normalizeAppNameSpace } from "@arronqzy/blueprint-dsl";
|
|
24
24
|
import {
|
|
25
25
|
cloneWorkspaceSnapshot,
|
|
26
26
|
workspaceSnapshotsEqual,
|
|
@@ -48,6 +48,11 @@ export type UseWorkspaceProjectsOptions = {
|
|
|
48
48
|
onProjectApplied?: (record: WorkspaceProjectRecord) => void;
|
|
49
49
|
/** 外部传入的初始工作区,挂载后自动加载(不触发 onProjectApplied) */
|
|
50
50
|
initialWorkspace?: MaybeRefOrGetter<WorkspaceProjectRecord | null | undefined>;
|
|
51
|
+
/**
|
|
52
|
+
* 按 IndexedDB id 打开工作区。与 initialWorkspace 同时存在时:
|
|
53
|
+
* 首次优先 initialWorkspace;之后 workspaceId 变化会从 IndexedDB 打开。
|
|
54
|
+
*/
|
|
55
|
+
workspaceId?: MaybeRefOrGetter<string | null | undefined>;
|
|
51
56
|
nameSpace?: MaybeRefOrGetter<string | null | undefined>;
|
|
52
57
|
};
|
|
53
58
|
|
|
@@ -94,9 +99,11 @@ function workspaceApplyKey(record: WorkspaceProjectRecord): string {
|
|
|
94
99
|
export function useWorkspaceProjects(options: UseWorkspaceProjectsOptions) {
|
|
95
100
|
const { t, locale } = useI18nOptional();
|
|
96
101
|
const nameSpace = () => toValue(options.nameSpace) ?? null;
|
|
102
|
+
const eventNameSpace = () => normalizeAppNameSpace(nameSpace()) || null;
|
|
97
103
|
const projects = ref<WorkspaceProjectListItem[]>([]);
|
|
98
104
|
const activeProjectId = ref<string | null>(null);
|
|
99
105
|
const activeProjectName = ref<string | null>(null);
|
|
106
|
+
let activeCreatedAt: number | null = null;
|
|
100
107
|
const syncedSnapshotRef = shallowRef<WorkspaceSnapshot | null>(null);
|
|
101
108
|
let syncedPanelRevision: unknown = null;
|
|
102
109
|
let pendingRevisionSync = false;
|
|
@@ -150,7 +157,9 @@ export function useWorkspaceProjects(options: UseWorkspaceProjectsOptions) {
|
|
|
150
157
|
const active = activeProjectId.value;
|
|
151
158
|
const removedActive =
|
|
152
159
|
message.type === "cleared" ||
|
|
153
|
-
(
|
|
160
|
+
(message.type === "deleted" &&
|
|
161
|
+
active !== null &&
|
|
162
|
+
message.projectIds.includes(active));
|
|
154
163
|
if (removedActive) {
|
|
155
164
|
activeProjectId.value = null;
|
|
156
165
|
activeProjectName.value = null;
|
|
@@ -187,9 +196,61 @@ export function useWorkspaceProjects(options: UseWorkspaceProjectsOptions) {
|
|
|
187
196
|
return !workspaceSnapshotsEqual(buildCurrentSnapshot(), syncedSnapshotRef.value);
|
|
188
197
|
});
|
|
189
198
|
|
|
199
|
+
async function ensureWorkspaceInIndexedDb(record: WorkspaceProjectRecord) {
|
|
200
|
+
const id = typeof record.id === "string" ? record.id.trim() : "";
|
|
201
|
+
if (!id) return;
|
|
202
|
+
try {
|
|
203
|
+
const existing = await getWorkspaceProject(id, nameSpace());
|
|
204
|
+
const incomingUpdatedAt =
|
|
205
|
+
typeof record.updatedAt === "number" && Number.isFinite(record.updatedAt)
|
|
206
|
+
? record.updatedAt
|
|
207
|
+
: 0;
|
|
208
|
+
if (existing && existing.updatedAt >= incomingUpdatedAt) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const now = Date.now();
|
|
212
|
+
const name =
|
|
213
|
+
(typeof record.name === "string" && record.name.trim()) ||
|
|
214
|
+
(typeof record.productName === "string" && record.productName.trim()) ||
|
|
215
|
+
t("panel.defaults.unnamedProduct");
|
|
216
|
+
const toStore: WorkspaceProjectRecord = {
|
|
217
|
+
...record,
|
|
218
|
+
id,
|
|
219
|
+
name,
|
|
220
|
+
createdAt: existing?.createdAt ?? record.createdAt ?? now,
|
|
221
|
+
updatedAt: incomingUpdatedAt || now,
|
|
222
|
+
panelState:
|
|
223
|
+
normalizeImportedPanelState(record.panelState) ??
|
|
224
|
+
existing?.panelState ??
|
|
225
|
+
createEmptyPanelState(),
|
|
226
|
+
blueprintDocument:
|
|
227
|
+
parseBlueprintData(record.blueprintDocument).value ??
|
|
228
|
+
existing?.blueprintDocument ??
|
|
229
|
+
BlueprintGraph.empty().document,
|
|
230
|
+
blueprintMeta: {
|
|
231
|
+
name:
|
|
232
|
+
record.blueprintMeta?.name ??
|
|
233
|
+
existing?.blueprintMeta?.name ??
|
|
234
|
+
t("panel.defaults.unnamedBlueprint"),
|
|
235
|
+
remark:
|
|
236
|
+
record.blueprintMeta?.remark ?? existing?.blueprintMeta?.remark ?? "",
|
|
237
|
+
},
|
|
238
|
+
productName:
|
|
239
|
+
typeof record.productName === "string" && record.productName.trim()
|
|
240
|
+
? record.productName
|
|
241
|
+
: existing?.productName ?? t("panel.defaults.unnamedProduct"),
|
|
242
|
+
titleIconDataUrl: record.titleIconDataUrl ?? existing?.titleIconDataUrl,
|
|
243
|
+
};
|
|
244
|
+
await putWorkspaceProject(toStore, nameSpace());
|
|
245
|
+
await refreshProjects();
|
|
246
|
+
} catch (error) {
|
|
247
|
+
console.warn("[abuilder] ensureWorkspaceInIndexedDb failed", error);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
190
251
|
function applyProjectRecord(
|
|
191
252
|
record: WorkspaceProjectRecord,
|
|
192
|
-
applyOptions?: { notifyApplied?: boolean }
|
|
253
|
+
applyOptions?: { notifyApplied?: boolean; persistToIndexedDb?: boolean }
|
|
193
254
|
) {
|
|
194
255
|
const parsedBlueprint = parseBlueprintData(record.blueprintDocument);
|
|
195
256
|
const blueprintDocument =
|
|
@@ -213,6 +274,10 @@ export function useWorkspaceProjects(options: UseWorkspaceProjectsOptions) {
|
|
|
213
274
|
options.setTitleIconDataUrl(titleIconDataUrl);
|
|
214
275
|
activeProjectId.value = record.id || null;
|
|
215
276
|
activeProjectName.value = record.name ?? null;
|
|
277
|
+
activeCreatedAt =
|
|
278
|
+
typeof record.createdAt === "number" && Number.isFinite(record.createdAt)
|
|
279
|
+
? record.createdAt
|
|
280
|
+
: Date.now();
|
|
216
281
|
syncedSnapshotRef.value = cloneWorkspaceSnapshot({
|
|
217
282
|
blueprintDocument,
|
|
218
283
|
blueprintMeta,
|
|
@@ -221,15 +286,23 @@ export function useWorkspaceProjects(options: UseWorkspaceProjectsOptions) {
|
|
|
221
286
|
});
|
|
222
287
|
syncedPanelRevision = toValue(options.panelRevision);
|
|
223
288
|
pendingRevisionSync = true;
|
|
289
|
+
const normalized: WorkspaceProjectRecord = {
|
|
290
|
+
...record,
|
|
291
|
+
id: record.id,
|
|
292
|
+
name: record.name ?? productName,
|
|
293
|
+
createdAt: activeCreatedAt,
|
|
294
|
+
updatedAt: record.updatedAt ?? Date.now(),
|
|
295
|
+
blueprintDocument,
|
|
296
|
+
blueprintMeta,
|
|
297
|
+
panelState,
|
|
298
|
+
productName,
|
|
299
|
+
titleIconDataUrl: titleIconDataUrl || undefined,
|
|
300
|
+
};
|
|
224
301
|
if (applyOptions?.notifyApplied !== false) {
|
|
225
|
-
options.onProjectApplied?.(
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
panelState,
|
|
230
|
-
productName,
|
|
231
|
-
titleIconDataUrl: titleIconDataUrl || undefined,
|
|
232
|
-
});
|
|
302
|
+
options.onProjectApplied?.(normalized);
|
|
303
|
+
}
|
|
304
|
+
if (applyOptions?.persistToIndexedDb) {
|
|
305
|
+
void ensureWorkspaceInIndexedDb(normalized);
|
|
233
306
|
}
|
|
234
307
|
}
|
|
235
308
|
|
|
@@ -244,46 +317,13 @@ export function useWorkspaceProjects(options: UseWorkspaceProjectsOptions) {
|
|
|
244
317
|
options.setTitleIconDataUrl("");
|
|
245
318
|
activeProjectId.value = null;
|
|
246
319
|
activeProjectName.value = null;
|
|
320
|
+
activeCreatedAt = null;
|
|
247
321
|
syncedSnapshotRef.value = null;
|
|
248
322
|
}
|
|
249
323
|
|
|
250
324
|
let bootstrapped = false;
|
|
251
325
|
let appliedWorkspaceKey: string | null = null;
|
|
252
|
-
|
|
253
|
-
function bootstrapInitialWorkspace() {
|
|
254
|
-
const initialWorkspace = toValue(options.initialWorkspace) ?? null;
|
|
255
|
-
if (!bootstrapped) {
|
|
256
|
-
bootstrapped = true;
|
|
257
|
-
if (hasWorkspacePayload(initialWorkspace)) {
|
|
258
|
-
applyProjectRecord(initialWorkspace, { notifyApplied: false });
|
|
259
|
-
appliedWorkspaceKey = workspaceApplyKey(initialWorkspace);
|
|
260
|
-
} else {
|
|
261
|
-
applyEmptyWorkspace();
|
|
262
|
-
}
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
if (!hasWorkspacePayload(initialWorkspace)) return;
|
|
266
|
-
const key = workspaceApplyKey(initialWorkspace);
|
|
267
|
-
if (appliedWorkspaceKey === key) return;
|
|
268
|
-
appliedWorkspaceKey = key;
|
|
269
|
-
applyProjectRecord(initialWorkspace, { notifyApplied: false });
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
onMounted(() => {
|
|
273
|
-
bootstrapInitialWorkspace();
|
|
274
|
-
void refreshProjects();
|
|
275
|
-
previewPollTimer = window.setInterval(refreshPreviewing, 2000);
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
watch(
|
|
279
|
-
() => {
|
|
280
|
-
const record = toValue(options.initialWorkspace);
|
|
281
|
-
return record ? workspaceApplyKey(record) : "";
|
|
282
|
-
},
|
|
283
|
-
() => {
|
|
284
|
-
bootstrapInitialWorkspace();
|
|
285
|
-
}
|
|
286
|
-
);
|
|
326
|
+
let workspaceIdApplied: string | null = null;
|
|
287
327
|
|
|
288
328
|
async function persistProject(persistOptions: {
|
|
289
329
|
id?: string;
|
|
@@ -294,7 +334,7 @@ export function useWorkspaceProjects(options: UseWorkspaceProjectsOptions) {
|
|
|
294
334
|
const snapshot = cloneWorkspaceSnapshot(buildCurrentSnapshot());
|
|
295
335
|
const now = Date.now();
|
|
296
336
|
const id = persistOptions.id ?? createWorkspaceProjectId();
|
|
297
|
-
const createdAt = persistOptions.createdAt ?? now;
|
|
337
|
+
const createdAt = persistOptions.createdAt ?? activeCreatedAt ?? now;
|
|
298
338
|
const name = (persistOptions.name ?? "").trim() || resolveProjectName();
|
|
299
339
|
|
|
300
340
|
let panelState: State;
|
|
@@ -320,10 +360,9 @@ export function useWorkspaceProjects(options: UseWorkspaceProjectsOptions) {
|
|
|
320
360
|
};
|
|
321
361
|
|
|
322
362
|
await putWorkspaceProject(record, nameSpace());
|
|
323
|
-
writeWorkspacePreviewCache(record, nameSpace());
|
|
324
|
-
broadcastWorkspaceProjectUpdate(id, now, nameSpace());
|
|
325
363
|
activeProjectId.value = id;
|
|
326
364
|
activeProjectName.value = name;
|
|
365
|
+
activeCreatedAt = createdAt;
|
|
327
366
|
syncedSnapshotRef.value = snapshot;
|
|
328
367
|
syncedPanelRevision = toValue(options.panelRevision);
|
|
329
368
|
pendingRevisionSync = false;
|
|
@@ -334,11 +373,40 @@ export function useWorkspaceProjects(options: UseWorkspaceProjectsOptions) {
|
|
|
334
373
|
}
|
|
335
374
|
}
|
|
336
375
|
|
|
376
|
+
function getCurrentWorkspaceRecord(): WorkspaceProjectRecord | null {
|
|
377
|
+
if (!activeProjectId.value) return null;
|
|
378
|
+
try {
|
|
379
|
+
const snapshot = buildCurrentSnapshot();
|
|
380
|
+
const panelState = options.exportPanelData();
|
|
381
|
+
const now = Date.now();
|
|
382
|
+
return {
|
|
383
|
+
id: activeProjectId.value,
|
|
384
|
+
name: activeProjectName.value || resolveProjectName(),
|
|
385
|
+
createdAt: activeCreatedAt ?? now,
|
|
386
|
+
updatedAt: now,
|
|
387
|
+
panelState,
|
|
388
|
+
blueprintDocument: snapshot.blueprintDocument,
|
|
389
|
+
blueprintMeta: snapshot.blueprintMeta,
|
|
390
|
+
productName:
|
|
391
|
+
typeof snapshot.productName === "string"
|
|
392
|
+
? snapshot.productName
|
|
393
|
+
: resolveProjectName(),
|
|
394
|
+
titleIconDataUrl: snapshot.titleIconDataUrl || undefined,
|
|
395
|
+
};
|
|
396
|
+
} catch (error) {
|
|
397
|
+
console.warn("[abuilder] getCurrentWorkspace failed", error);
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
337
402
|
async function handleCreateProject() {
|
|
338
403
|
return runBusyTask(t("common.savingWorkspace"), async () => {
|
|
339
404
|
const name = resolveProjectName();
|
|
340
405
|
const record = await persistProject({ name });
|
|
341
|
-
emitAbuilderEvent(AbuilderEvents.workspaceAdd,
|
|
406
|
+
emitAbuilderEvent(AbuilderEvents.workspaceAdd, {
|
|
407
|
+
...record,
|
|
408
|
+
nameSpace: eventNameSpace(),
|
|
409
|
+
});
|
|
342
410
|
return { name, id: record.id };
|
|
343
411
|
});
|
|
344
412
|
}
|
|
@@ -354,16 +422,24 @@ export function useWorkspaceProjects(options: UseWorkspaceProjectsOptions) {
|
|
|
354
422
|
const record = await persistProject({
|
|
355
423
|
id: projectId,
|
|
356
424
|
name,
|
|
357
|
-
createdAt: existing?.createdAt,
|
|
425
|
+
createdAt: existing?.createdAt ?? activeCreatedAt ?? undefined,
|
|
426
|
+
});
|
|
427
|
+
emitAbuilderEvent(AbuilderEvents.workspaceSync, {
|
|
428
|
+
...record,
|
|
429
|
+
nameSpace: eventNameSpace(),
|
|
358
430
|
});
|
|
359
431
|
return { name, id: record.id };
|
|
360
432
|
});
|
|
361
433
|
}
|
|
362
434
|
|
|
363
|
-
async function handleOpenProject(
|
|
435
|
+
async function handleOpenProject(idOrRecord: string | WorkspaceProjectRecord) {
|
|
364
436
|
try {
|
|
365
437
|
await runBusyTask(t("common.loadingWorkspace"), async () => {
|
|
366
|
-
|
|
438
|
+
if (typeof idOrRecord !== "string") {
|
|
439
|
+
applyProjectRecord(idOrRecord, { persistToIndexedDb: true });
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
const record = await getWorkspaceProject(idOrRecord, nameSpace());
|
|
367
443
|
if (!record) {
|
|
368
444
|
await refreshProjects();
|
|
369
445
|
throw new Error(t("panel.messages.workspaceNotFound"));
|
|
@@ -381,20 +457,17 @@ export function useWorkspaceProjects(options: UseWorkspaceProjectsOptions) {
|
|
|
381
457
|
throw new Error(t("panel.messages.saveWorkspaceFirst"));
|
|
382
458
|
}
|
|
383
459
|
const existing = await getWorkspaceProject(activeProjectId.value, nameSpace());
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
activeProjectName.value = null;
|
|
387
|
-
syncedSnapshotRef.value = null;
|
|
388
|
-
await refreshProjects();
|
|
389
|
-
throw new Error(t("panel.messages.workspaceNotFound"));
|
|
390
|
-
}
|
|
391
|
-
const name = resolveProjectName() || existing.name;
|
|
460
|
+
const name =
|
|
461
|
+
resolveProjectName() || existing?.name || activeProjectName.value || "";
|
|
392
462
|
const record = await persistProject({
|
|
393
463
|
id: activeProjectId.value,
|
|
394
464
|
name,
|
|
395
|
-
createdAt: existing
|
|
465
|
+
createdAt: existing?.createdAt ?? activeCreatedAt ?? undefined,
|
|
466
|
+
});
|
|
467
|
+
emitAbuilderEvent(AbuilderEvents.workspaceSync, {
|
|
468
|
+
...record,
|
|
469
|
+
nameSpace: eventNameSpace(),
|
|
396
470
|
});
|
|
397
|
-
emitAbuilderEvent(AbuilderEvents.workspaceSync, record);
|
|
398
471
|
return name;
|
|
399
472
|
});
|
|
400
473
|
}
|
|
@@ -406,6 +479,7 @@ export function useWorkspaceProjects(options: UseWorkspaceProjectsOptions) {
|
|
|
406
479
|
if (activeProjectId.value === id) {
|
|
407
480
|
activeProjectId.value = null;
|
|
408
481
|
activeProjectName.value = null;
|
|
482
|
+
activeCreatedAt = null;
|
|
409
483
|
syncedSnapshotRef.value = null;
|
|
410
484
|
}
|
|
411
485
|
previewWindows.delete(id);
|
|
@@ -456,6 +530,73 @@ export function useWorkspaceProjects(options: UseWorkspaceProjectsOptions) {
|
|
|
456
530
|
});
|
|
457
531
|
}
|
|
458
532
|
|
|
533
|
+
function bootstrapInitialWorkspace() {
|
|
534
|
+
const initialWorkspace = toValue(options.initialWorkspace) ?? null;
|
|
535
|
+
const workspaceIdRaw = toValue(options.workspaceId);
|
|
536
|
+
const workspaceId =
|
|
537
|
+
typeof workspaceIdRaw === "string" ? workspaceIdRaw.trim() : "";
|
|
538
|
+
if (!bootstrapped) {
|
|
539
|
+
bootstrapped = true;
|
|
540
|
+
if (hasWorkspacePayload(initialWorkspace)) {
|
|
541
|
+
applyProjectRecord(initialWorkspace, {
|
|
542
|
+
notifyApplied: false,
|
|
543
|
+
persistToIndexedDb: true,
|
|
544
|
+
});
|
|
545
|
+
appliedWorkspaceKey = workspaceApplyKey(initialWorkspace);
|
|
546
|
+
if (initialWorkspace.id) workspaceIdApplied = initialWorkspace.id;
|
|
547
|
+
} else if (workspaceId) {
|
|
548
|
+
workspaceIdApplied = workspaceId;
|
|
549
|
+
void handleOpenProject(workspaceId);
|
|
550
|
+
} else {
|
|
551
|
+
applyEmptyWorkspace();
|
|
552
|
+
}
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
if (!hasWorkspacePayload(initialWorkspace)) return;
|
|
556
|
+
const key = workspaceApplyKey(initialWorkspace);
|
|
557
|
+
if (appliedWorkspaceKey === key) return;
|
|
558
|
+
appliedWorkspaceKey = key;
|
|
559
|
+
if (initialWorkspace.id) workspaceIdApplied = initialWorkspace.id;
|
|
560
|
+
applyProjectRecord(initialWorkspace, {
|
|
561
|
+
notifyApplied: false,
|
|
562
|
+
persistToIndexedDb: true,
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
onMounted(() => {
|
|
567
|
+
bootstrapInitialWorkspace();
|
|
568
|
+
void refreshProjects();
|
|
569
|
+
previewPollTimer = window.setInterval(refreshPreviewing, 2000);
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
watch(
|
|
573
|
+
() => {
|
|
574
|
+
const record = toValue(options.initialWorkspace);
|
|
575
|
+
return record ? workspaceApplyKey(record) : "";
|
|
576
|
+
},
|
|
577
|
+
() => {
|
|
578
|
+
bootstrapInitialWorkspace();
|
|
579
|
+
}
|
|
580
|
+
);
|
|
581
|
+
|
|
582
|
+
watch(
|
|
583
|
+
() => {
|
|
584
|
+
const raw = toValue(options.workspaceId);
|
|
585
|
+
return typeof raw === "string" ? raw.trim() : "";
|
|
586
|
+
},
|
|
587
|
+
(id) => {
|
|
588
|
+
if (!bootstrapped) return;
|
|
589
|
+
if (!id) return;
|
|
590
|
+
if (workspaceIdApplied === id) return;
|
|
591
|
+
if (activeProjectId.value === id) {
|
|
592
|
+
workspaceIdApplied = id;
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
workspaceIdApplied = id;
|
|
596
|
+
void handleOpenProject(id);
|
|
597
|
+
}
|
|
598
|
+
);
|
|
599
|
+
|
|
459
600
|
return {
|
|
460
601
|
projects,
|
|
461
602
|
activeProjectId,
|
|
@@ -468,6 +609,7 @@ export function useWorkspaceProjects(options: UseWorkspaceProjectsOptions) {
|
|
|
468
609
|
handleSyncProject,
|
|
469
610
|
handleDeleteProject,
|
|
470
611
|
handlePreviewProject,
|
|
612
|
+
getCurrentWorkspaceRecord,
|
|
471
613
|
refreshProjects,
|
|
472
614
|
};
|
|
473
615
|
}
|