@arronqzy/vue-view 0.1.6 → 0.1.8
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 +8 -7
- package/src/panel/VueViewOnlinePreview.vue +28 -2
- package/src/panel/VueViewPanel.vue +120 -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 +11 -0
- package/src/panel/components/WorkspaceConfigSidebar.vue +1 -0
- package/src/panel/components/WorkspaceProjectNav.vue +36 -8
- package/src/panel/components/config/ConfigColorField.vue +33 -9
- package/src/panel/components/config/PanelConfigAudioSection.vue +32 -18
- package/src/panel/components/config/PanelConfigImageSection.vue +13 -9
- package/src/panel/components/config/PanelConfigScene3dSection.vue +505 -0
- package/src/panel/components/config/PanelConfigStyleSections.vue +13 -9
- package/src/panel/components/config/PanelConfigTextSection.vue +20 -6
- 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/gridPlacement.ts +29 -0
- package/src/panel/utils/panelElementDefaults.ts +37 -1
- 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.8",
|
|
4
4
|
"description": "Vue 3 visual view editor for Abuilder (Ant Design Vue)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -23,12 +23,13 @@
|
|
|
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/rx-store": "1.0.
|
|
28
|
-
"@arronqzy/
|
|
29
|
-
"@arronqzy/view-
|
|
30
|
-
"@arronqzy/
|
|
31
|
-
"@arronqzy/
|
|
26
|
+
"@arronqzy/blueprint-dsl": "1.0.8",
|
|
27
|
+
"@arronqzy/rx-store": "1.0.5",
|
|
28
|
+
"@arronqzy/i18n": "0.1.7",
|
|
29
|
+
"@arronqzy/view-scene3d": "0.1.2",
|
|
30
|
+
"@arronqzy/view-table": "0.1.5",
|
|
31
|
+
"@arronqzy/vue-blueprint": "0.1.6",
|
|
32
|
+
"@arronqzy/vue-rx-store": "0.1.3"
|
|
32
33
|
},
|
|
33
34
|
"peerDependencies": {
|
|
34
35
|
"vue": "^3.4.0"
|
|
@@ -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 {
|
|
@@ -204,7 +207,7 @@ const lifecycleReady = computed(
|
|
|
204
207
|
() => Boolean(panelState.value && layerElements.value.length > 0 && layoutReady.value)
|
|
205
208
|
);
|
|
206
209
|
|
|
207
|
-
const { triggerBlueprintNode } = useBlueprintPageLifecycle({
|
|
210
|
+
const { triggerBlueprintNode, emitViewEvent, firedLifecyclePhases } = useBlueprintPageLifecycle({
|
|
208
211
|
graph: blueprintGraph,
|
|
209
212
|
active: ref(true),
|
|
210
213
|
enabled: lifecycleReady,
|
|
@@ -220,6 +223,22 @@ const { triggerBlueprintNode } = useBlueprintPageLifecycle({
|
|
|
220
223
|
onViewScopeUpdate: handleViewScopeUpdate,
|
|
221
224
|
});
|
|
222
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
|
+
|
|
223
242
|
onMounted(() => {
|
|
224
243
|
const onResize = () => applySceneFit();
|
|
225
244
|
window.addEventListener("resize", onResize);
|
|
@@ -240,8 +259,9 @@ function noopSelect() {}
|
|
|
240
259
|
|
|
241
260
|
<div
|
|
242
261
|
v-else-if="!panelState"
|
|
243
|
-
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"
|
|
244
263
|
>
|
|
264
|
+
<div class="h-9 w-9 animate-spin rounded-full border-2 border-gray-300 border-t-gray-700" />
|
|
245
265
|
{{ t("panel.workspace.previewLoading") }}
|
|
246
266
|
</div>
|
|
247
267
|
|
|
@@ -286,6 +306,12 @@ function noopSelect() {}
|
|
|
286
306
|
void triggerBlueprintNode(payload.blueprintNodeId, payload);
|
|
287
307
|
}
|
|
288
308
|
"
|
|
309
|
+
:bound-view-event-types="boundViewEventTypes"
|
|
310
|
+
:on-view-ui-event="
|
|
311
|
+
(payload) => {
|
|
312
|
+
void emitViewEvent(payload);
|
|
313
|
+
}
|
|
314
|
+
"
|
|
289
315
|
@select-ids="noopSelect"
|
|
290
316
|
/>
|
|
291
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
|
|
@@ -876,6 +929,7 @@ onUnmounted(() => window.removeEventListener("keydown", onKeyDown));
|
|
|
876
929
|
</Layout.Sider>
|
|
877
930
|
</Layout>
|
|
878
931
|
|
|
932
|
+
<BusyOverlay />
|
|
879
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); }" />
|
|
880
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); }" />
|
|
881
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">
|
|
@@ -20,6 +20,7 @@ import PanelConfigTextSection from "./config/PanelConfigTextSection.vue";
|
|
|
20
20
|
import PanelConfigAudioSection from "./config/PanelConfigAudioSection.vue";
|
|
21
21
|
import PanelConfigVideoSection from "./config/PanelConfigVideoSection.vue";
|
|
22
22
|
import PanelConfigGeometrySection from "./config/PanelConfigGeometrySection.vue";
|
|
23
|
+
import PanelConfigScene3dSection from "./config/PanelConfigScene3dSection.vue";
|
|
23
24
|
import PanelConfigGridSection from "./config/PanelConfigGridSection.vue";
|
|
24
25
|
import PanelConfigGridChildSpan from "./config/PanelConfigGridChildSpan.vue";
|
|
25
26
|
import PanelConfigReferenceSection from "./config/PanelConfigReferenceSection.vue";
|
|
@@ -333,6 +334,16 @@ watch(
|
|
|
333
334
|
@update:open="(v) => setSectionExpanded('geometryConfig', v)"
|
|
334
335
|
/>
|
|
335
336
|
|
|
337
|
+
<PanelConfigScene3dSection
|
|
338
|
+
v-if="materialType === 'scene3d' && shouldShowSection('scene3dConfig', t('panel.config.sectionScene3d'), [t('panel.material.scene3d'), '3d', 'glb', t('panel.config.groupScene3dCamera')])"
|
|
339
|
+
:element="selectedElement"
|
|
340
|
+
:is-editable="isNodeEditable"
|
|
341
|
+
:open="isSectionExpanded('scene3dConfig')"
|
|
342
|
+
:force-open="forceOpenSections"
|
|
343
|
+
:update-element="updateElement"
|
|
344
|
+
@update:open="(v) => setSectionExpanded('scene3dConfig', v)"
|
|
345
|
+
/>
|
|
346
|
+
|
|
336
347
|
<PanelConfigGridSection
|
|
337
348
|
v-if="materialType === 'grid' && shouldShowSection('gridConfig', t('panel.config.sectionGrid'), [t('panel.config.searchKwGrid'), t('panel.config.rowsShort'), t('panel.config.colsShort'), t('panel.config.searchKwGap')])"
|
|
338
349
|
:element="selectedElement"
|
|
@@ -19,6 +19,7 @@ const props = defineProps<{
|
|
|
19
19
|
activeProjectId: string | null;
|
|
20
20
|
activeProjectName: string | null;
|
|
21
21
|
dirty: boolean;
|
|
22
|
+
previewingProjectIds?: string[];
|
|
22
23
|
}>();
|
|
23
24
|
|
|
24
25
|
const emit = defineEmits<{
|
|
@@ -76,8 +77,10 @@ const projectMenu = computed(() =>
|
|
|
76
77
|
default: () =>
|
|
77
78
|
props.projects.length === 0
|
|
78
79
|
? [h(Menu.Item, { key: "empty", disabled: true }, () => t("panel.workspace.noSavedWorkspaces"))]
|
|
79
|
-
: props.projects.map((project) =>
|
|
80
|
-
|
|
80
|
+
: props.projects.map((project) => {
|
|
81
|
+
const isCurrent = props.activeProjectId === project.id;
|
|
82
|
+
const isPreviewing = (props.previewingProjectIds ?? []).includes(project.id);
|
|
83
|
+
return h(
|
|
81
84
|
"div",
|
|
82
85
|
{ key: project.id, class: "px-1 py-0.5" },
|
|
83
86
|
h("div", { class: "flex items-center gap-1" }, [
|
|
@@ -86,16 +89,41 @@ const projectMenu = computed(() =>
|
|
|
86
89
|
{
|
|
87
90
|
type: "button",
|
|
88
91
|
class: [
|
|
89
|
-
"min-w-0 flex-1 rounded px-2 py-1.5 text-left text-xs hover:bg-
|
|
90
|
-
|
|
91
|
-
? "bg-
|
|
92
|
+
"min-w-0 flex-1 rounded px-2 py-1.5 text-left text-xs hover:bg-gray-100",
|
|
93
|
+
isCurrent
|
|
94
|
+
? "bg-blue-50 font-medium text-blue-700 ring-1 ring-inset ring-blue-300"
|
|
95
|
+
: "",
|
|
96
|
+
!isCurrent && isPreviewing
|
|
97
|
+
? "bg-sky-50 ring-1 ring-inset ring-sky-300"
|
|
92
98
|
: "",
|
|
93
99
|
].join(" "),
|
|
94
100
|
onClick: () =>
|
|
95
101
|
void runAction(() => emit("openProject", project.id)),
|
|
96
102
|
},
|
|
97
103
|
[
|
|
98
|
-
h("div", { class: "
|
|
104
|
+
h("div", { class: "flex items-center gap-1" }, [
|
|
105
|
+
h("div", { class: "min-w-0 flex-1 truncate" }, project.name),
|
|
106
|
+
isCurrent
|
|
107
|
+
? h(
|
|
108
|
+
"span",
|
|
109
|
+
{
|
|
110
|
+
class:
|
|
111
|
+
"shrink-0 rounded bg-blue-600 px-1 py-px text-[9px] leading-none text-white",
|
|
112
|
+
},
|
|
113
|
+
t("panel.workspace.currentBadge")
|
|
114
|
+
)
|
|
115
|
+
: null,
|
|
116
|
+
isPreviewing
|
|
117
|
+
? h(
|
|
118
|
+
"span",
|
|
119
|
+
{
|
|
120
|
+
class:
|
|
121
|
+
"shrink-0 rounded bg-sky-600 px-1 py-px text-[9px] leading-none text-white",
|
|
122
|
+
},
|
|
123
|
+
t("panel.workspace.previewingBadge")
|
|
124
|
+
)
|
|
125
|
+
: null,
|
|
126
|
+
]),
|
|
99
127
|
h(
|
|
100
128
|
"div",
|
|
101
129
|
{ class: "truncate text-[10px] text-muted-foreground" },
|
|
@@ -132,8 +160,8 @@ const projectMenu = computed(() =>
|
|
|
132
160
|
() => t("common.delete")
|
|
133
161
|
),
|
|
134
162
|
])
|
|
135
|
-
)
|
|
136
|
-
),
|
|
163
|
+
);
|
|
164
|
+
}),
|
|
137
165
|
}
|
|
138
166
|
)
|
|
139
167
|
);
|