@arronqzy/vue-view 0.1.23 → 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 -3
- package/package.json +7 -7
- package/src/index.ts +53 -1
- package/src/panel/VueViewOnlinePreview.vue +25 -0
- package/src/panel/VueViewPanel.vue +62 -2
- package/src/panel/hooks/useWorkspaceProjects.ts +209 -67
- package/src/panel/library/abuilder-storage.ts +168 -0
- package/src/panel/library/abuilder-view-handle.ts +100 -0
- package/src/panel/library/blueprint-library-sync.ts +22 -0
- package/src/panel/library/capture-preview-snapshot.ts +800 -0
- package/src/panel/library/event-subscription.ts +79 -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 +70 -3
|
@@ -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
|
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { normalizeAppNameSpace } from "@arronqzy/blueprint-dsl";
|
|
2
|
+
import {
|
|
3
|
+
clearAllExecutionRunRecords,
|
|
4
|
+
clearApiCollections as clearApiCollectionsRaw,
|
|
5
|
+
clearBlueprintLibrary as clearBlueprintLibraryRecords,
|
|
6
|
+
deleteApiCollection as deleteApiCollectionRaw,
|
|
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,
|
|
16
|
+
} from "@arronqzy/vue-blueprint";
|
|
17
|
+
import type { ApiCollectionRecord } from "@arronqzy/blueprint-dsl";
|
|
18
|
+
import { broadcastBlueprintLibraryChanged } from "./blueprint-library-sync";
|
|
19
|
+
import { AbuilderEvents, emitAbuilderEvent } from "./event-subscription";
|
|
20
|
+
import { clearWorkspaceProjects } from "./workspace-storage";
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
listBlueprintLibrary,
|
|
24
|
+
getBlueprintLibraryRecord,
|
|
25
|
+
listExecutionRunRecords,
|
|
26
|
+
getExecutionRunRecord,
|
|
27
|
+
deleteExecutionRunRecord,
|
|
28
|
+
clearAllExecutionRunRecords,
|
|
29
|
+
purgeExecutionRunsOlderThan,
|
|
30
|
+
trimExecutionRunRecordsToMax,
|
|
31
|
+
readExecutionLogSettings,
|
|
32
|
+
writeExecutionLogSettings,
|
|
33
|
+
listApiCollectionRecords,
|
|
34
|
+
} from "@arronqzy/vue-blueprint";
|
|
35
|
+
export type {
|
|
36
|
+
BlueprintLibraryListItem,
|
|
37
|
+
BlueprintLibraryRecord,
|
|
38
|
+
ExecutionRunRecord,
|
|
39
|
+
ExecutionLogSettings,
|
|
40
|
+
UpsertApiCollectionInput,
|
|
41
|
+
UpsertApiCollectionResult,
|
|
42
|
+
} from "@arronqzy/vue-blueprint";
|
|
43
|
+
export type { ApiCollectionRecord } from "@arronqzy/blueprint-dsl";
|
|
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
|
+
|
|
64
|
+
/** 删除蓝图库中的一条蓝图;已挂载的编辑器会刷新蓝图库列表 */
|
|
65
|
+
export async function deleteBlueprintLibraryRecord(
|
|
66
|
+
id: string,
|
|
67
|
+
nameSpace?: string | null
|
|
68
|
+
): Promise<void> {
|
|
69
|
+
await deleteBlueprintLibraryRecordRaw(id, nameSpace);
|
|
70
|
+
broadcastBlueprintLibraryChanged(nameSpace);
|
|
71
|
+
emitAbuilderEvent(AbuilderEvents.blueprintLibraryChanged, {
|
|
72
|
+
nameSpace: resolvedNameSpace(nameSpace),
|
|
73
|
+
action: "delete",
|
|
74
|
+
ids: [id],
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** 清空该 nameSpace 下的蓝图库,返回删除数量;已挂载的编辑器会刷新蓝图库列表 */
|
|
79
|
+
export async function clearBlueprintLibrary(nameSpace?: string | null): Promise<number> {
|
|
80
|
+
const count = await clearBlueprintLibraryRecords(nameSpace);
|
|
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
|
+
});
|
|
144
|
+
return count;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export type AbuilderStorageClearResult = {
|
|
148
|
+
workspaces: number;
|
|
149
|
+
blueprints: number;
|
|
150
|
+
executionRuns: number;
|
|
151
|
+
apiCollections: number;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 清空该 nameSpace 下编辑器写入浏览器的全部数据:工作区、蓝图库、执行日志、接口集合。
|
|
156
|
+
* 主题、字号、日志保留设置等偏好不受影响。
|
|
157
|
+
*/
|
|
158
|
+
export async function clearAbuilderStorage(
|
|
159
|
+
nameSpace?: string | null
|
|
160
|
+
): Promise<AbuilderStorageClearResult> {
|
|
161
|
+
const [workspaces, blueprints, executionRuns, apiCollections] = await Promise.all([
|
|
162
|
+
clearWorkspaceProjects(nameSpace),
|
|
163
|
+
clearBlueprintLibrary(nameSpace),
|
|
164
|
+
clearAllExecutionRunRecords(nameSpace),
|
|
165
|
+
clearApiCollections(nameSpace),
|
|
166
|
+
]);
|
|
167
|
+
return { workspaces, blueprints, executionRuns, apiCollections };
|
|
168
|
+
}
|
|
@@ -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
|
+
}
|