@arronqzy/vue-view 0.1.4 → 0.1.7
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 +6 -5
- package/src/panel/VueViewOnlinePreview.vue +55 -19
- package/src/panel/VueViewPanel.vue +123 -66
- package/src/panel/components/BusyOverlay.vue +24 -0
- package/src/panel/components/ConfigHintIcon.vue +10 -10
- package/src/panel/components/ElementsLayer.vue +51 -8
- package/src/panel/components/MaterialPreview.vue +8 -0
- package/src/panel/components/PanelConfigSidebar.vue +32 -20
- package/src/panel/components/WorkspaceConfigSidebar.vue +11 -0
- package/src/panel/components/WorkspaceProjectNav.vue +36 -8
- package/src/panel/components/config/ConfigColorField.vue +33 -9
- package/src/panel/components/config/ConfigFieldGroup.vue +35 -6
- package/src/panel/components/config/PanelConfigAudioSection.vue +37 -19
- package/src/panel/components/config/PanelConfigChartSection.vue +101 -84
- package/src/panel/components/config/PanelConfigGeometrySection.vue +10 -2
- package/src/panel/components/config/PanelConfigImageSection.vue +18 -10
- package/src/panel/components/config/PanelConfigNodeInfo.vue +21 -12
- package/src/panel/components/config/PanelConfigReferenceSection.vue +5 -1
- package/src/panel/components/config/PanelConfigScene3dSection.vue +505 -0
- package/src/panel/components/config/PanelConfigStyleSections.vue +19 -11
- package/src/panel/components/config/PanelConfigTextSection.vue +25 -7
- package/src/panel/components/config/PanelConfigVideoSection.vue +13 -9
- package/src/panel/components/config/shared.ts +16 -21
- package/src/panel/components/elementsLayerNodes.ts +64 -59
- package/src/panel/components/materialSidebarData.ts +2 -0
- package/src/panel/components/scope-config/ScopeTemplateAutocompleteHost.vue +1 -1
- package/src/panel/components/table/PanelConfigTableSection.vue +39 -25
- package/src/panel/components/table/TableNodeContent.vue +36 -15
- package/src/panel/hooks/usePanelElements.ts +13 -1
- package/src/panel/hooks/useWorkspaceProjects.ts +128 -57
- package/src/panel/library/workspace-project-cache.ts +7 -2
- package/src/panel/library/workspace-project-db.ts +29 -11
- package/src/panel/library/workspace-snapshot.ts +21 -4
- package/src/panel/scope/view-scope-store.ts +8 -0
- package/src/panel/types.ts +4 -2
- package/src/panel/utils/async-work.ts +153 -0
- package/src/panel/utils/chartOptionBuilder.ts +7 -1
- package/src/panel/utils/gridPlacement.ts +29 -0
- package/src/panel/utils/outputScale.ts +56 -0
- package/src/panel/utils/panelElementDefaults.ts +37 -2
- package/src/panel/utils/scope-autocomplete.ts +0 -114
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arronqzy/vue-view",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Vue 3 visual view editor for Abuilder (Ant Design Vue)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -23,10 +23,11 @@
|
|
|
23
23
|
"selecto": "^1.26.3",
|
|
24
24
|
"uuid": "^13.0.0",
|
|
25
25
|
"vue": "^3.5.13",
|
|
26
|
-
"@arronqzy/blueprint-dsl": "1.0.
|
|
27
|
-
"@arronqzy/
|
|
28
|
-
"@arronqzy/view-
|
|
29
|
-
"@arronqzy/
|
|
26
|
+
"@arronqzy/blueprint-dsl": "1.0.7",
|
|
27
|
+
"@arronqzy/i18n": "0.1.6",
|
|
28
|
+
"@arronqzy/view-scene3d": "0.1.1",
|
|
29
|
+
"@arronqzy/view-table": "0.1.4",
|
|
30
|
+
"@arronqzy/vue-blueprint": "0.1.5",
|
|
30
31
|
"@arronqzy/vue-rx-store": "0.1.2",
|
|
31
32
|
"@arronqzy/rx-store": "1.0.4"
|
|
32
33
|
},
|
|
@@ -8,8 +8,11 @@ import {
|
|
|
8
8
|
listBlueprintLibrary,
|
|
9
9
|
useBlueprintPageLifecycle,
|
|
10
10
|
type BlueprintLibraryListItem,
|
|
11
|
+
resolveRunnableNodeType,
|
|
12
|
+
resolveViewElementIds,
|
|
11
13
|
} from "@arronqzy/vue-blueprint";
|
|
12
14
|
import type { LibraryBlueprintResolver, PageLifecyclePhase } from "@arronqzy/blueprint-dsl";
|
|
15
|
+
import { collectArmedViewEventBindings, EVENT_NODE_TYPE, LIFECYCLE_NODE_TYPE } from "@arronqzy/blueprint-dsl";
|
|
13
16
|
import type { State } from "@arronqzy/rx-store";
|
|
14
17
|
import ElementsLayer from "./components/ElementsLayer.vue";
|
|
15
18
|
import {
|
|
@@ -31,6 +34,7 @@ import {
|
|
|
31
34
|
parsePanelLayers,
|
|
32
35
|
resolvePreviewLayerElements,
|
|
33
36
|
} from "./utils/panelStateIO";
|
|
37
|
+
import { applyPreviewSceneFill, readOutputScale } from "./utils/outputScale";
|
|
34
38
|
|
|
35
39
|
const { t, locale } = useI18n();
|
|
36
40
|
|
|
@@ -49,6 +53,8 @@ const blueprintLibraryItems = ref<BlueprintLibraryListItem[]>([]);
|
|
|
49
53
|
const layoutRevision = ref(0);
|
|
50
54
|
const layoutReady = ref(false);
|
|
51
55
|
const sceneRef = ref<HTMLDivElement | null>(null);
|
|
56
|
+
const outputScale = ref(readOutputScale());
|
|
57
|
+
const fillScale = ref({ scaleX: 1, scaleY: 1 });
|
|
52
58
|
|
|
53
59
|
const layers = computed(() => (panelState.value ? parsePanelLayers(panelState.value) : []));
|
|
54
60
|
const activeLayerId = computed(() =>
|
|
@@ -146,24 +152,30 @@ const scopedLayerElements = computed(() => {
|
|
|
146
152
|
const sceneBounds = computed(() => computePanelSceneBounds(scopedLayerElements.value));
|
|
147
153
|
|
|
148
154
|
const displayElements = computed(() =>
|
|
149
|
-
scopedLayerElements.value.map((el) =>
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
155
|
+
scopedLayerElements.value.map((el) => {
|
|
156
|
+
const x = el.x - sceneBounds.value.minX;
|
|
157
|
+
const y = el.y - sceneBounds.value.minY;
|
|
158
|
+
if (outputScale.value) return { ...el, x, y };
|
|
159
|
+
return {
|
|
160
|
+
...el,
|
|
161
|
+
x: x * fillScale.value.scaleX,
|
|
162
|
+
y: y * fillScale.value.scaleY,
|
|
163
|
+
width: el.width * fillScale.value.scaleX,
|
|
164
|
+
height: el.height * fillScale.value.scaleY,
|
|
165
|
+
};
|
|
166
|
+
})
|
|
154
167
|
);
|
|
155
168
|
|
|
156
169
|
function applySceneFit() {
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
scene.style.transform = `scale(${scaleX}, ${scaleY})`;
|
|
170
|
+
const enabled = readOutputScale();
|
|
171
|
+
outputScale.value = enabled;
|
|
172
|
+
const fill = applyPreviewSceneFill(
|
|
173
|
+
sceneRef.value,
|
|
174
|
+
sceneBounds.value.width,
|
|
175
|
+
sceneBounds.value.height,
|
|
176
|
+
enabled
|
|
177
|
+
);
|
|
178
|
+
fillScale.value = { scaleX: fill.scaleX, scaleY: fill.scaleY };
|
|
167
179
|
layoutRevision.value += 1;
|
|
168
180
|
notifyPreviewLayoutChanged();
|
|
169
181
|
}
|
|
@@ -195,7 +207,7 @@ const lifecycleReady = computed(
|
|
|
195
207
|
() => Boolean(panelState.value && layerElements.value.length > 0 && layoutReady.value)
|
|
196
208
|
);
|
|
197
209
|
|
|
198
|
-
const { triggerBlueprintNode } = useBlueprintPageLifecycle({
|
|
210
|
+
const { triggerBlueprintNode, emitViewEvent, firedLifecyclePhases } = useBlueprintPageLifecycle({
|
|
199
211
|
graph: blueprintGraph,
|
|
200
212
|
active: ref(true),
|
|
201
213
|
enabled: lifecycleReady,
|
|
@@ -211,6 +223,22 @@ const { triggerBlueprintNode } = useBlueprintPageLifecycle({
|
|
|
211
223
|
onViewScopeUpdate: handleViewScopeUpdate,
|
|
212
224
|
});
|
|
213
225
|
|
|
226
|
+
const boundViewEventTypes = computed(() =>
|
|
227
|
+
collectArmedViewEventBindings(
|
|
228
|
+
blueprintGraph.value.document.nodes.map((node) => ({
|
|
229
|
+
id: node.id,
|
|
230
|
+
nodeType: resolveRunnableNodeType(node),
|
|
231
|
+
lifecyclePhase: node.lifecyclePhase,
|
|
232
|
+
viewElementIds: resolveViewElementIds(node),
|
|
233
|
+
eventConfig: node.eventConfig,
|
|
234
|
+
})),
|
|
235
|
+
blueprintGraph.value.document.edges,
|
|
236
|
+
EVENT_NODE_TYPE,
|
|
237
|
+
LIFECYCLE_NODE_TYPE,
|
|
238
|
+
firedLifecyclePhases.value
|
|
239
|
+
)
|
|
240
|
+
);
|
|
241
|
+
|
|
214
242
|
onMounted(() => {
|
|
215
243
|
const onResize = () => applySceneFit();
|
|
216
244
|
window.addEventListener("resize", onResize);
|
|
@@ -231,8 +259,9 @@ function noopSelect() {}
|
|
|
231
259
|
|
|
232
260
|
<div
|
|
233
261
|
v-else-if="!panelState"
|
|
234
|
-
class="flex min-h-screen w-full items-center justify-center bg-white text-sm text-gray-600"
|
|
262
|
+
class="flex min-h-screen w-full flex-col items-center justify-center gap-3 bg-white text-sm text-gray-600"
|
|
235
263
|
>
|
|
264
|
+
<div class="h-9 w-9 animate-spin rounded-full border-2 border-gray-300 border-t-gray-700" />
|
|
236
265
|
{{ t("panel.workspace.previewLoading") }}
|
|
237
266
|
</div>
|
|
238
267
|
|
|
@@ -258,9 +287,10 @@ function noopSelect() {}
|
|
|
258
287
|
id="preview-scene"
|
|
259
288
|
class="relative shrink-0 origin-top-left"
|
|
260
289
|
:style="{
|
|
261
|
-
width: `${sceneBounds.width}px`,
|
|
262
|
-
height: `${sceneBounds.height}px`,
|
|
290
|
+
width: `${outputScale ? sceneBounds.width : sceneBounds.width * fillScale.scaleX}px`,
|
|
291
|
+
height: `${outputScale ? sceneBounds.height : sceneBounds.height * fillScale.scaleY}px`,
|
|
263
292
|
transformOrigin: 'left top',
|
|
293
|
+
transform: outputScale ? `scale(${fillScale.scaleX}, ${fillScale.scaleY})` : 'none',
|
|
264
294
|
}"
|
|
265
295
|
>
|
|
266
296
|
<ElementsLayer
|
|
@@ -276,6 +306,12 @@ function noopSelect() {}
|
|
|
276
306
|
void triggerBlueprintNode(payload.blueprintNodeId, payload);
|
|
277
307
|
}
|
|
278
308
|
"
|
|
309
|
+
:bound-view-event-types="boundViewEventTypes"
|
|
310
|
+
:on-view-ui-event="
|
|
311
|
+
(payload) => {
|
|
312
|
+
void emitViewEvent(payload);
|
|
313
|
+
}
|
|
314
|
+
"
|
|
279
315
|
@select-ids="noopSelect"
|
|
280
316
|
/>
|
|
281
317
|
</div>
|
|
@@ -24,8 +24,10 @@ import {
|
|
|
24
24
|
type BlueprintGraphNode,
|
|
25
25
|
type BlueprintLibraryListItem,
|
|
26
26
|
type BlueprintMetaDraft,
|
|
27
|
+
resolveRunnableNodeType,
|
|
28
|
+
resolveViewElementIds,
|
|
27
29
|
} from "@arronqzy/vue-blueprint";
|
|
28
|
-
import type
|
|
30
|
+
import { collectArmedViewEventBindings, EVENT_NODE_TYPE, LIFECYCLE_NODE_TYPE, type LibraryBlueprintResolver } from "@arronqzy/blueprint-dsl";
|
|
29
31
|
import {
|
|
30
32
|
Button,
|
|
31
33
|
Checkbox,
|
|
@@ -65,6 +67,8 @@ import {
|
|
|
65
67
|
import { useRafThrottledScroll } from "./hooks/useRafThrottledScroll";
|
|
66
68
|
import { useI18n } from "@arronqzy/i18n/vue";
|
|
67
69
|
import { getPanelMessages } from "./constants/messages";
|
|
70
|
+
import BusyOverlay from "./components/BusyOverlay.vue";
|
|
71
|
+
import { assertFileSize, parseJsonText, runBusyTask } from "./utils/async-work";
|
|
68
72
|
import "../tailwind.css";
|
|
69
73
|
|
|
70
74
|
const { t, locale, setLocale } = useI18n();
|
|
@@ -400,7 +404,7 @@ const workspaceProjects = useWorkspaceProjects({
|
|
|
400
404
|
onProjectApplied: handleWorkspaceProjectApplied,
|
|
401
405
|
});
|
|
402
406
|
|
|
403
|
-
const { triggerBlueprintNode } = useBlueprintPageLifecycle({
|
|
407
|
+
const { triggerBlueprintNode, emitViewEvent, firedLifecyclePhases } = useBlueprintPageLifecycle({
|
|
404
408
|
graph: blueprintGraph,
|
|
405
409
|
active: blueprintOpen,
|
|
406
410
|
bootKey: computed(() => workspaceProjects.activeProjectId.value ?? undefined),
|
|
@@ -412,6 +416,22 @@ const { triggerBlueprintNode } = useBlueprintPageLifecycle({
|
|
|
412
416
|
onViewScopeUpdate: handleViewScopeUpdate,
|
|
413
417
|
});
|
|
414
418
|
|
|
419
|
+
const boundViewEventTypes = computed(() =>
|
|
420
|
+
collectArmedViewEventBindings(
|
|
421
|
+
blueprintGraph.value.document.nodes.map((node) => ({
|
|
422
|
+
id: node.id,
|
|
423
|
+
nodeType: resolveRunnableNodeType(node),
|
|
424
|
+
lifecyclePhase: node.lifecyclePhase,
|
|
425
|
+
viewElementIds: resolveViewElementIds(node),
|
|
426
|
+
eventConfig: node.eventConfig,
|
|
427
|
+
})),
|
|
428
|
+
blueprintGraph.value.document.edges,
|
|
429
|
+
EVENT_NODE_TYPE,
|
|
430
|
+
LIFECYCLE_NODE_TYPE,
|
|
431
|
+
firedLifecyclePhases.value
|
|
432
|
+
)
|
|
433
|
+
);
|
|
434
|
+
|
|
415
435
|
const blueprintNodeOptions = computed(() =>
|
|
416
436
|
blueprintGraph.value.document.nodes.map((node) => ({
|
|
417
437
|
id: node.id,
|
|
@@ -419,19 +439,21 @@ const blueprintNodeOptions = computed(() =>
|
|
|
419
439
|
}))
|
|
420
440
|
);
|
|
421
441
|
async function loadBlueprintFromLibrary(id: string) {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
442
|
+
await runBusyTask(t("common.loadingBlueprint"), async () => {
|
|
443
|
+
const record = await getBlueprintLibraryRecord(id);
|
|
444
|
+
if (!record) {
|
|
445
|
+
message.error(panelMessages().blueprintNotFound);
|
|
446
|
+
void refreshBlueprintLibrary();
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
blueprintGraph.value = BlueprintGraph.fromDocument(record.document);
|
|
450
|
+
blueprintMeta.value = { name: record.name, remark: record.remark ?? "" };
|
|
451
|
+
activeBlueprintLibraryId.value = record.id;
|
|
452
|
+
blueprintSyncedDocument.value = record.document;
|
|
453
|
+
selectedBlueprintNodeId.value = null;
|
|
454
|
+
configFocus.value = "blueprint";
|
|
455
|
+
blueprintOpen.value = true;
|
|
456
|
+
});
|
|
435
457
|
}
|
|
436
458
|
|
|
437
459
|
function snapshotWorkspaceBlueprint() {
|
|
@@ -462,74 +484,97 @@ async function handleSelectBlueprintLibraryItem(id: string) {
|
|
|
462
484
|
}
|
|
463
485
|
|
|
464
486
|
async function syncBlueprintToLibrary() {
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
487
|
+
const libraryId = activeBlueprintLibraryId.value;
|
|
488
|
+
if (!libraryId) return;
|
|
489
|
+
await runBusyTask(t("common.syncingBlueprint"), async () => {
|
|
490
|
+
const record = await getBlueprintLibraryRecord(libraryId);
|
|
491
|
+
if (!record) return;
|
|
492
|
+
await putBlueprintLibraryRecord({
|
|
493
|
+
...record,
|
|
494
|
+
document: blueprintGraph.value.document,
|
|
495
|
+
name: blueprintMeta.value.name,
|
|
496
|
+
remark: blueprintMeta.value.remark,
|
|
497
|
+
});
|
|
498
|
+
blueprintSyncedDocument.value = blueprintGraph.value.document;
|
|
499
|
+
message.success(panelMessages().blueprintSyncedToLibrary);
|
|
500
|
+
await refreshBlueprintLibrary();
|
|
473
501
|
});
|
|
474
|
-
blueprintSyncedDocument.value = blueprintGraph.value.document;
|
|
475
|
-
message.success(panelMessages().blueprintSyncedToLibrary);
|
|
476
|
-
await refreshBlueprintLibrary();
|
|
477
502
|
}
|
|
478
503
|
|
|
479
504
|
async function saveBlueprintToLibrary(meta: BlueprintMetaDraft) {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
505
|
+
await runBusyTask(t("common.savingBlueprint"), async () => {
|
|
506
|
+
const id = activeBlueprintLibraryId.value ?? createLibraryBlueprintId();
|
|
507
|
+
const record = buildLibraryRecord({
|
|
508
|
+
id,
|
|
509
|
+
document: blueprintGraph.value.document,
|
|
510
|
+
meta,
|
|
511
|
+
source: "saved",
|
|
512
|
+
});
|
|
513
|
+
await putBlueprintLibraryRecord(record);
|
|
514
|
+
activeBlueprintLibraryId.value = id;
|
|
515
|
+
blueprintSyncedDocument.value = blueprintGraph.value.document;
|
|
516
|
+
blueprintMeta.value = meta;
|
|
517
|
+
await refreshBlueprintLibrary();
|
|
518
|
+
message.success(panelMessages().blueprintSavedToLibrary);
|
|
486
519
|
});
|
|
487
|
-
await putBlueprintLibraryRecord(record);
|
|
488
|
-
activeBlueprintLibraryId.value = id;
|
|
489
|
-
blueprintSyncedDocument.value = blueprintGraph.value.document;
|
|
490
|
-
blueprintMeta.value = meta;
|
|
491
|
-
await refreshBlueprintLibrary();
|
|
492
|
-
message.success(panelMessages().blueprintSavedToLibrary);
|
|
493
520
|
}
|
|
494
521
|
|
|
495
522
|
function handleExport() {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
.
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
523
|
+
void runBusyTask(t("common.exportingPanel"), async () => {
|
|
524
|
+
const data = exportPanelData();
|
|
525
|
+
const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" });
|
|
526
|
+
const url = URL.createObjectURL(blob);
|
|
527
|
+
const a = document.createElement("a");
|
|
528
|
+
a.href = url;
|
|
529
|
+
const safeName = (productName.value.trim() || "panel")
|
|
530
|
+
.replace(/[\\/:*?"<>|]/g, "-")
|
|
531
|
+
.replace(/\s+/g, "-");
|
|
532
|
+
a.download = `${safeName}-${Date.now()}.json`;
|
|
533
|
+
a.click();
|
|
534
|
+
URL.revokeObjectURL(url);
|
|
535
|
+
});
|
|
507
536
|
}
|
|
508
537
|
|
|
509
538
|
async function handleImportFile(file: File) {
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
539
|
+
try {
|
|
540
|
+
await runBusyTask(t("common.importingPanel"), async () => {
|
|
541
|
+
assertFileSize(file, "json");
|
|
542
|
+
const text = await file.text();
|
|
543
|
+
const parsed = await parseJsonText<State>(text);
|
|
544
|
+
const ok = importPanelData(parsed);
|
|
545
|
+
if (!ok) window.alert(panelMessages().importInvalidFormat);
|
|
546
|
+
else selectedIds.value = [];
|
|
547
|
+
});
|
|
548
|
+
} catch (error) {
|
|
549
|
+
message.error(error instanceof Error ? error.message : panelMessages().importInvalidFormat);
|
|
550
|
+
}
|
|
515
551
|
}
|
|
516
552
|
|
|
517
553
|
function openBlueprintExport() {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
554
|
+
void runBusyTask(t("common.exportingPanel"), async () => {
|
|
555
|
+
downloadBlueprintExport(
|
|
556
|
+
buildBlueprintExportPayload(blueprintGraph.value.document, blueprintMeta.value)
|
|
557
|
+
);
|
|
558
|
+
message.success(panelMessages().blueprintExported);
|
|
559
|
+
});
|
|
522
560
|
}
|
|
523
561
|
|
|
524
562
|
async function handleBlueprintImportFile(file: File) {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
563
|
+
try {
|
|
564
|
+
await runBusyTask(t("common.importingBlueprint"), async () => {
|
|
565
|
+
assertFileSize(file, "json");
|
|
566
|
+
const text = await file.text();
|
|
567
|
+
const payload = parseBlueprintImportFile(await parseJsonText(text));
|
|
568
|
+
const record = libraryRecordFromImport(payload);
|
|
569
|
+
await putBlueprintLibraryRecord(record);
|
|
570
|
+
await refreshBlueprintLibrary();
|
|
571
|
+
if (!activeBlueprintLibraryId.value) snapshotWorkspaceBlueprint();
|
|
572
|
+
await loadBlueprintFromLibrary(record.id);
|
|
573
|
+
message.success(panelMessages().blueprintImported);
|
|
574
|
+
});
|
|
575
|
+
} catch (error) {
|
|
576
|
+
message.error(error instanceof Error ? error.message : panelMessages().importInvalidFormat);
|
|
577
|
+
}
|
|
533
578
|
}
|
|
534
579
|
|
|
535
580
|
async function handleWorkspaceCreateProject() {
|
|
@@ -539,6 +584,7 @@ async function handleWorkspaceCreateProject() {
|
|
|
539
584
|
|
|
540
585
|
async function handleWorkspaceOpenProject(id: string) {
|
|
541
586
|
try {
|
|
587
|
+
stopAllClockSchedules();
|
|
542
588
|
clearViewElementScopes();
|
|
543
589
|
await workspaceProjects.handleOpenProject(id);
|
|
544
590
|
selectedIds.value = [];
|
|
@@ -662,6 +708,7 @@ onUnmounted(() => window.removeEventListener("keydown", onKeyDown));
|
|
|
662
708
|
:active-project-id="workspaceProjects.activeProjectId.value"
|
|
663
709
|
:active-project-name="workspaceProjects.activeProjectName.value"
|
|
664
710
|
:dirty="workspaceProjects.dirty.value"
|
|
711
|
+
:previewing-project-ids="workspaceProjects.previewingProjectIds.value"
|
|
665
712
|
@create-project="handleWorkspaceCreateProject"
|
|
666
713
|
@open-project="handleWorkspaceOpenProject"
|
|
667
714
|
@sync-project="handleWorkspaceSyncProject"
|
|
@@ -763,6 +810,12 @@ onUnmounted(() => window.removeEventListener("keydown", onKeyDown));
|
|
|
763
810
|
void triggerBlueprintNode(payload.blueprintNodeId, payload);
|
|
764
811
|
}
|
|
765
812
|
"
|
|
813
|
+
:bound-view-event-types="boundViewEventTypes"
|
|
814
|
+
:on-view-ui-event="
|
|
815
|
+
(payload) => {
|
|
816
|
+
void emitViewEvent(payload);
|
|
817
|
+
}
|
|
818
|
+
"
|
|
766
819
|
@select-ids="selectIds"
|
|
767
820
|
/>
|
|
768
821
|
<SelectLayer
|
|
@@ -863,6 +916,9 @@ onUnmounted(() => window.removeEventListener("keydown", onKeyDown));
|
|
|
863
916
|
:on-exclude-selected-node="excludeSelectedNode"
|
|
864
917
|
:on-adjust-node-z-order="adjustNodeZOrder"
|
|
865
918
|
:selected-blueprint-node="selectedBlueprintNode"
|
|
919
|
+
:blueprint-graph-nodes="blueprintGraph.document.nodes"
|
|
920
|
+
:blueprint-graph-edges="blueprintGraph.document.edges"
|
|
921
|
+
:blueprint-trace-entries="blueprintDebugSession.entries.value"
|
|
866
922
|
:allow-false-signal-propagation="blueprintGraph.document.allowFalseSignalPropagation"
|
|
867
923
|
:blueprint-library-options="blueprintLibraryOptions"
|
|
868
924
|
:blueprint-node-options="blueprintNodeOptions"
|
|
@@ -873,6 +929,7 @@ onUnmounted(() => window.removeEventListener("keydown", onKeyDown));
|
|
|
873
929
|
</Layout.Sider>
|
|
874
930
|
</Layout>
|
|
875
931
|
|
|
932
|
+
<BusyOverlay />
|
|
876
933
|
<input ref="importInputRef" type="file" accept="application/json" class="hidden" @change="async (e) => { const f = (e.target as HTMLInputElement).files?.[0]; (e.target as HTMLInputElement).value = ''; if (f) await handleImportFile(f); }" />
|
|
877
934
|
<input ref="blueprintImportInputRef" type="file" accept="application/json" class="hidden" @change="async (e) => { const f = (e.target as HTMLInputElement).files?.[0]; (e.target as HTMLInputElement).value = ''; if (f) await handleBlueprintImportFile(f); }" />
|
|
878
935
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useI18n } from "@arronqzy/i18n/vue";
|
|
3
|
+
import { busyOverlayMessage } from "../utils/async-work";
|
|
4
|
+
|
|
5
|
+
const { t } = useI18n();
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<Teleport to="body">
|
|
10
|
+
<div
|
|
11
|
+
v-if="busyOverlayMessage"
|
|
12
|
+
class="fixed inset-0 flex flex-col items-center justify-center gap-3 bg-black/45 px-6"
|
|
13
|
+
style="z-index: 40000"
|
|
14
|
+
role="status"
|
|
15
|
+
aria-live="polite"
|
|
16
|
+
aria-busy="true"
|
|
17
|
+
>
|
|
18
|
+
<div class="h-9 w-9 animate-spin rounded-full border-2 border-white/30 border-t-white" />
|
|
19
|
+
<div class="max-w-sm text-center text-sm text-white">
|
|
20
|
+
{{ busyOverlayMessage || t("common.loading") }}
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</Teleport>
|
|
24
|
+
</template>
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { computed } from "vue";
|
|
2
3
|
import { useI18n } from "@arronqzy/i18n/vue";
|
|
3
4
|
import { Tooltip } from "ant-design-vue";
|
|
4
5
|
|
|
5
|
-
const { t
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
);
|
|
6
|
+
const { t } = useI18n();
|
|
7
|
+
const props = defineProps<{
|
|
8
|
+
label?: string;
|
|
9
|
+
contentClass?: string;
|
|
10
|
+
buttonClass?: string;
|
|
11
|
+
}>();
|
|
12
|
+
|
|
13
|
+
const resolvedLabel = computed(() => props.label || t("common.hint"));
|
|
14
14
|
</script>
|
|
15
15
|
|
|
16
16
|
<template>
|
|
@@ -28,7 +28,7 @@ withDefaults(
|
|
|
28
28
|
buttonClass ??
|
|
29
29
|
'inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full border border-border text-[10px] leading-none text-muted-foreground hover:bg-accent/50'
|
|
30
30
|
"
|
|
31
|
-
:aria-label="t('common.hintAria', { label })"
|
|
31
|
+
:aria-label="t('common.hintAria', { label: resolvedLabel })"
|
|
32
32
|
>
|
|
33
33
|
?
|
|
34
34
|
</button>
|
|
@@ -15,7 +15,17 @@ import {
|
|
|
15
15
|
VideoNodeContent,
|
|
16
16
|
} from "./elementsLayerNodes";
|
|
17
17
|
import TableNodeContent from "./table/TableNodeContent.vue";
|
|
18
|
+
import Scene3dNodeContent from "@arronqzy/view-scene3d/vue";
|
|
19
|
+
import { mergeScene3dConfig } from "@arronqzy/view-scene3d";
|
|
18
20
|
import type { TableCellActionHandler } from "./table/TableNodeContent.vue";
|
|
21
|
+
import { comparePanelElementsPaintOrder } from "../utils/gridPlacement";
|
|
22
|
+
import {
|
|
23
|
+
createViewEventSignal,
|
|
24
|
+
snapshotDomEvent,
|
|
25
|
+
snapshotViewEventNode,
|
|
26
|
+
type ViewEventSignal,
|
|
27
|
+
type ViewEventType,
|
|
28
|
+
} from "@arronqzy/blueprint-dsl";
|
|
19
29
|
|
|
20
30
|
const props = withDefaults(
|
|
21
31
|
defineProps<{
|
|
@@ -31,6 +41,8 @@ const props = withDefaults(
|
|
|
31
41
|
previewMode?: boolean;
|
|
32
42
|
previewLayoutKey?: number;
|
|
33
43
|
onTableCellAction?: TableCellActionHandler;
|
|
44
|
+
boundViewEventTypes?: ReadonlyMap<string, ReadonlySet<ViewEventType>>;
|
|
45
|
+
onViewUiEvent?: (payload: ViewEventSignal) => void;
|
|
34
46
|
}>(),
|
|
35
47
|
{ layerLocked: false, previewMode: false }
|
|
36
48
|
);
|
|
@@ -104,6 +116,17 @@ const NodeContent = defineComponent({
|
|
|
104
116
|
if (el.materialType === "geometry") {
|
|
105
117
|
return h(GeometryNodeContent, { element: el });
|
|
106
118
|
}
|
|
119
|
+
if (el.materialType === "scene3d") {
|
|
120
|
+
return h(Scene3dNodeContent, {
|
|
121
|
+
config: el.scene3d,
|
|
122
|
+
previewMode: p.previewMode,
|
|
123
|
+
selected: p.selected,
|
|
124
|
+
onUpdateConfig: (patch) =>
|
|
125
|
+
p.updateElement(el.id, {
|
|
126
|
+
scene3d: mergeScene3dConfig({ ...el.scene3d, ...patch }),
|
|
127
|
+
}),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
107
130
|
if (el.materialType === "image") {
|
|
108
131
|
return h(ImageNodeContent, { element: el });
|
|
109
132
|
}
|
|
@@ -119,14 +142,11 @@ const NodeContent = defineComponent({
|
|
|
119
142
|
},
|
|
120
143
|
});
|
|
121
144
|
|
|
122
|
-
const sortedElements = computed(() =>
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return a.id.localeCompare(b.id);
|
|
128
|
-
})
|
|
129
|
-
);
|
|
145
|
+
const sortedElements = computed(() => {
|
|
146
|
+
const byId = new Map<string, PanelElement>();
|
|
147
|
+
for (const el of props.allElements) byId.set(el.id, el);
|
|
148
|
+
return [...props.elements].sort((a, b) => comparePanelElementsPaintOrder(a, b, byId));
|
|
149
|
+
});
|
|
130
150
|
|
|
131
151
|
function onSelect(id: string, event: MouseEvent) {
|
|
132
152
|
if (props.previewMode) return;
|
|
@@ -141,6 +161,23 @@ function onSelect(id: string, event: MouseEvent) {
|
|
|
141
161
|
}
|
|
142
162
|
emit("selectIds", [id]);
|
|
143
163
|
}
|
|
164
|
+
|
|
165
|
+
function emitViewUiEvent(el: PanelElement, eventType: ViewEventType, event: MouseEvent) {
|
|
166
|
+
if (!props.onViewUiEvent) return;
|
|
167
|
+
if (!props.boundViewEventTypes?.get(el.id)?.has(eventType)) return;
|
|
168
|
+
props.onViewUiEvent(
|
|
169
|
+
createViewEventSignal({
|
|
170
|
+
eventType,
|
|
171
|
+
event: snapshotDomEvent(event),
|
|
172
|
+
node: snapshotViewEventNode(el),
|
|
173
|
+
})
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function hasViewEvent(el: PanelElement) {
|
|
178
|
+
const types = props.boundViewEventTypes?.get(el.id);
|
|
179
|
+
return Boolean(types && types.size > 0 && props.onViewUiEvent);
|
|
180
|
+
}
|
|
144
181
|
</script>
|
|
145
182
|
|
|
146
183
|
<template>
|
|
@@ -151,6 +188,7 @@ function onSelect(id: string, event: MouseEvent) {
|
|
|
151
188
|
'absolute select-none',
|
|
152
189
|
previewMode ? '' : 'rv-selectable',
|
|
153
190
|
!previewMode && selectedIds.includes(el.id) ? 'ring-2 ring-blue-500/90 ring-offset-0' : '',
|
|
191
|
+
hasViewEvent(el) ? 'cursor-pointer' : '',
|
|
154
192
|
]"
|
|
155
193
|
:data-element-id="el.id"
|
|
156
194
|
:style="{
|
|
@@ -165,6 +203,11 @@ function onSelect(id: string, event: MouseEvent) {
|
|
|
165
203
|
...getNodeVisualStyle(el),
|
|
166
204
|
}"
|
|
167
205
|
@mousedown="onSelect(el.id, $event)"
|
|
206
|
+
@click="emitViewUiEvent(el, 'click', $event)"
|
|
207
|
+
@dblclick="emitViewUiEvent(el, 'dblclick', $event)"
|
|
208
|
+
@contextmenu="emitViewUiEvent(el, 'contextmenu', $event)"
|
|
209
|
+
@mouseenter="emitViewUiEvent(el, 'mouseenter', $event)"
|
|
210
|
+
@mouseleave="emitViewUiEvent(el, 'mouseleave', $event)"
|
|
168
211
|
>
|
|
169
212
|
<NodeContent
|
|
170
213
|
:element="el"
|
|
@@ -106,6 +106,14 @@ const common =
|
|
|
106
106
|
<polygon points="50,36 54,42 50,48 42,48 38,42 42,36" class="fill-primary/70" />
|
|
107
107
|
</svg>
|
|
108
108
|
</div>
|
|
109
|
+
<div v-else-if="id === 'scene3d'" :class="common">
|
|
110
|
+
<svg viewBox="0 0 80 60" class="h-full w-full">
|
|
111
|
+
<polygon points="40,8 62,22 62,44 40,56 18,44 18,22" class="fill-primary/20 stroke-primary/80" stroke-width="1.5" />
|
|
112
|
+
<polygon points="40,8 62,22 40,32 18,22" class="fill-primary/45" />
|
|
113
|
+
<polygon points="40,32 62,22 62,44 40,56" class="fill-primary/30" />
|
|
114
|
+
<polygon points="40,32 18,22 18,44 40,56" class="fill-primary/55" />
|
|
115
|
+
</svg>
|
|
116
|
+
</div>
|
|
109
117
|
<div v-else-if="id === 'reference'" :class="common">
|
|
110
118
|
<div class="absolute inset-2 rounded-md border border-dashed border-primary/70 bg-primary/10" />
|
|
111
119
|
<div class="absolute inset-0 flex items-center justify-center text-[10px] text-primary/80">
|