@arronqzy/react-view 1.0.38 → 1.0.40
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/dist/styles.css +316 -241
- package/package.json +5 -5
- package/readme.md +15 -12
- package/src/index.ts +23 -1
- package/src/panel/ReactViewPanel.tsx +140 -90
- package/src/panel/components/ElementsLayer.tsx +1 -1
- package/src/panel/components/MaterialSidebar.tsx +304 -264
- package/src/panel/components/MoveableLayer.tsx +11 -2
- package/src/panel/components/PanelCanvas.tsx +1 -0
- package/src/panel/components/PanelConfigSidebar.tsx +49 -65
- package/src/panel/components/PanelLayerDock.tsx +397 -316
- package/src/panel/components/PanelRulers.tsx +169 -42
- package/src/panel/components/SelectLayer.tsx +2 -2
- package/src/panel/components/WorkspaceProjectNav.tsx +1 -1
- package/src/panel/hooks/useWorkspaceProjects.ts +228 -52
- package/src/panel/icons.tsx +117 -0
- 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/src/tailwind.css +66 -5
|
@@ -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
|
}
|
package/src/panel/icons.tsx
CHANGED
|
@@ -158,3 +158,120 @@ export function IconImage() {
|
|
|
158
158
|
</svg>
|
|
159
159
|
);
|
|
160
160
|
}
|
|
161
|
+
|
|
162
|
+
export function IconFolder() {
|
|
163
|
+
return (
|
|
164
|
+
<svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth="2">
|
|
165
|
+
<path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7Z" />
|
|
166
|
+
</svg>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function IconGrid() {
|
|
171
|
+
return (
|
|
172
|
+
<svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth="2">
|
|
173
|
+
<rect x="3" y="3" width="7" height="7" rx="1" />
|
|
174
|
+
<rect x="14" y="3" width="7" height="7" rx="1" />
|
|
175
|
+
<rect x="3" y="14" width="7" height="7" rx="1" />
|
|
176
|
+
<rect x="14" y="14" width="7" height="7" rx="1" />
|
|
177
|
+
</svg>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function IconTree() {
|
|
182
|
+
return (
|
|
183
|
+
<svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth="2">
|
|
184
|
+
<path d="M12 4v8" />
|
|
185
|
+
<path d="M6 20v-4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v4" />
|
|
186
|
+
<circle cx="12" cy="4" r="2" />
|
|
187
|
+
<circle cx="6" cy="20" r="2" />
|
|
188
|
+
<circle cx="18" cy="20" r="2" />
|
|
189
|
+
</svg>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function IconSearch() {
|
|
194
|
+
return (
|
|
195
|
+
<svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth="2">
|
|
196
|
+
<circle cx="11" cy="11" r="7" />
|
|
197
|
+
<path d="m20 20-3.5-3.5" />
|
|
198
|
+
</svg>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function IconStar({ filled }: { filled?: boolean }) {
|
|
203
|
+
return (
|
|
204
|
+
<svg
|
|
205
|
+
viewBox="0 0 24 24"
|
|
206
|
+
className="h-3.5 w-3.5"
|
|
207
|
+
fill={filled ? "currentColor" : "none"}
|
|
208
|
+
stroke="currentColor"
|
|
209
|
+
strokeWidth="2"
|
|
210
|
+
>
|
|
211
|
+
<path d="m12 3 2.7 5.5 6 .9-4.4 4.3 1 6L12 16.8 6.7 19.7l1-6L3.3 9.4l6-.9L12 3Z" />
|
|
212
|
+
</svg>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function IconEyeOff() {
|
|
217
|
+
return (
|
|
218
|
+
<svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth="2">
|
|
219
|
+
<path d="M3 3l18 18" />
|
|
220
|
+
<path d="M10.6 10.6a2 2 0 0 0 2.8 2.8" />
|
|
221
|
+
<path d="M9.9 5.1A10.6 10.6 0 0 1 12 5c5 0 9.3 3.1 11 7-.5 1.2-1.2 2.3-2.1 3.2" />
|
|
222
|
+
<path d="M6.1 6.1C4.2 7.3 2.7 9 1.9 11c1.7 3.9 6 7 10.1 7 1.4 0 2.7-.3 3.9-.7" />
|
|
223
|
+
</svg>
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** 仅看引用子树 */
|
|
228
|
+
export function IconRefFilter({ active }: { active?: boolean }) {
|
|
229
|
+
return (
|
|
230
|
+
<svg
|
|
231
|
+
viewBox="0 0 24 24"
|
|
232
|
+
className="h-3.5 w-3.5"
|
|
233
|
+
fill="none"
|
|
234
|
+
stroke="currentColor"
|
|
235
|
+
strokeWidth={active ? 2.2 : 2}
|
|
236
|
+
>
|
|
237
|
+
<path d="M4 5h16l-5.5 6.5V18l-5 2.5v-9L4 5Z" fill={active ? "currentColor" : "none"} fillOpacity={active ? 0.2 : 0} />
|
|
238
|
+
<circle cx="16.5" cy="16.5" r="2.2" />
|
|
239
|
+
<path d="M14.8 15.2 12 12.5" />
|
|
240
|
+
</svg>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function IconBlueprint({ active }: { active?: boolean }) {
|
|
245
|
+
return (
|
|
246
|
+
<svg
|
|
247
|
+
viewBox="0 0 24 24"
|
|
248
|
+
className="h-3.5 w-3.5"
|
|
249
|
+
fill="none"
|
|
250
|
+
stroke="currentColor"
|
|
251
|
+
strokeWidth={active ? 2.2 : 2}
|
|
252
|
+
>
|
|
253
|
+
<rect x="3" y="4" width="18" height="16" rx="2" />
|
|
254
|
+
<circle cx="8" cy="10" r="1.5" fill={active ? "currentColor" : "none"} />
|
|
255
|
+
<circle cx="16" cy="10" r="1.5" fill={active ? "currentColor" : "none"} />
|
|
256
|
+
<circle cx="12" cy="16" r="1.5" fill={active ? "currentColor" : "none"} />
|
|
257
|
+
<path d="M9.2 10.8 10.8 14.2M14.8 10.8 13.2 14.2" />
|
|
258
|
+
</svg>
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function IconSun() {
|
|
263
|
+
return (
|
|
264
|
+
<svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth="2">
|
|
265
|
+
<circle cx="12" cy="12" r="4" />
|
|
266
|
+
<path d="M12 2v2.5M12 19.5V22M4.93 4.93l1.77 1.77M17.3 17.3l1.77 1.77M2 12h2.5M19.5 12H22M4.93 19.07 6.7 17.3M17.3 6.7l1.77-1.77" />
|
|
267
|
+
</svg>
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function IconMoon() {
|
|
272
|
+
return (
|
|
273
|
+
<svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth="2">
|
|
274
|
+
<path d="M21 12.79A9 9 0 1 1 11.21 3c0 .66.08 1.3.22 1.92A7 7 0 0 0 19.08 12c.62.14 1.26.22 1.92.22Z" />
|
|
275
|
+
</svg>
|
|
276
|
+
);
|
|
277
|
+
}
|
|
@@ -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
|
|