@arronqzy/vue-view 0.1.9 → 0.1.11
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 +15 -1
- package/package.json +8 -8
- package/src/index.ts +26 -0
- package/src/panel/VueViewOnlinePreview.vue +68 -26
- package/src/panel/VueViewPanel.vue +15 -10
- package/src/panel/components/ElementsLayer.vue +4 -2
- package/src/panel/components/MaterialPreview.vue +8 -0
- package/src/panel/components/MaterialSidebar.vue +15 -18
- package/src/panel/components/MaterialSidebarTreeNode.vue +4 -2
- package/src/panel/components/PanelConfigSidebar.vue +17 -1
- package/src/panel/components/WorkspaceConfigSidebar.vue +1 -0
- package/src/panel/components/config/PanelConfigMultiSelect.vue +30 -1
- package/src/panel/components/config/PanelConfigViewportSection.vue +114 -0
- package/src/panel/components/elementsLayerNodes.ts +75 -17
- package/src/panel/components/materialSidebarData.ts +2 -0
- package/src/panel/hooks/usePanelElements.ts +10 -4
- package/src/panel/hooks/useWorkspaceProjects.ts +144 -34
- package/src/panel/library/event-subscription.ts +60 -0
- package/src/panel/library/parse-workspace-data.ts +142 -0
- package/src/panel/library/workspace-project-cache.ts +13 -4
- package/src/panel/library/workspace-project-db.ts +24 -13
- package/src/panel/library/workspace-project-sync.ts +17 -6
- package/src/panel/parseOnlinePreviewSearchParams.ts +3 -0
- package/src/panel/types.ts +4 -0
- package/src/panel/utils/outputScale.ts +15 -2
- package/src/panel/utils/panelElementDefaults.ts +10 -0
- package/src/panel/utils/panelElementNodes.ts +5 -3
- package/src/panel/utils/panelStateIO.ts +18 -0
- package/src/panel/utils/viewportPlacement.ts +73 -0
package/README.md
CHANGED
|
@@ -29,7 +29,21 @@ createApp(VueViewPanel).use(Antd).mount("#app");
|
|
|
29
29
|
import { VueViewOnlinePreview, parseOnlinePreviewSearchParams } from "@arronqzy/vue-view";
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
URL 参数:`?preview=online&projectId=<id>&pid=<instanceId
|
|
32
|
+
URL 参数:`?preview=online&projectId=<id>&pid=<instanceId>`。也可传入 `workspace` 直接预览宿主数据(优先于 `projectId`)。
|
|
33
|
+
|
|
34
|
+
### 宿主集成
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import {
|
|
38
|
+
addEventSubscription,
|
|
39
|
+
AbuilderEvents,
|
|
40
|
+
parseWorkspaceData,
|
|
41
|
+
createEmptyWorkspace,
|
|
42
|
+
createWorkspaceProjectId,
|
|
43
|
+
} from "@arronqzy/vue-view";
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
创建 / 同步工作区会发出 `workspace:add` / `workspace:sync`。`parseWorkspaceData` 的 `value` 可作 `initialWorkspace`。IndexedDB CRUD 与 `getPreviewSnapshot` **不导出**(后者仅 React 实现)。
|
|
33
47
|
|
|
34
48
|
## 依赖
|
|
35
49
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arronqzy/vue-view",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "Vue 3 visual view editor for Abuilder (Ant Design Vue)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -23,13 +23,13 @@
|
|
|
23
23
|
"selecto": "^1.26.3",
|
|
24
24
|
"uuid": "^13.0.0",
|
|
25
25
|
"vue": "^3.5.13",
|
|
26
|
-
"@arronqzy/
|
|
27
|
-
"@arronqzy/
|
|
26
|
+
"@arronqzy/i18n": "0.1.8",
|
|
27
|
+
"@arronqzy/blueprint-dsl": "1.0.11",
|
|
28
|
+
"@arronqzy/rx-store": "1.0.5",
|
|
28
29
|
"@arronqzy/view-scene3d": "0.1.2",
|
|
29
30
|
"@arronqzy/view-table": "0.1.5",
|
|
30
|
-
"@arronqzy/vue-
|
|
31
|
-
"@arronqzy/rx-store": "1.
|
|
32
|
-
"@arronqzy/vue-blueprint": "0.1.7"
|
|
31
|
+
"@arronqzy/vue-blueprint": "0.1.9",
|
|
32
|
+
"@arronqzy/vue-rx-store": "0.1.3"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"vue": "^3.4.0"
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"eslint": "^8.57.0",
|
|
40
40
|
"typescript": "^5.5.4",
|
|
41
41
|
"vue-tsc": "^2.2.0",
|
|
42
|
-
"@arronqzy/
|
|
43
|
-
"@arronqzy/
|
|
42
|
+
"@arronqzy/eslint-config": "0.0.0",
|
|
43
|
+
"@arronqzy/typescript-config": "1.0.0"
|
|
44
44
|
},
|
|
45
45
|
"publishConfig": {
|
|
46
46
|
"access": "public"
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,30 @@
|
|
|
1
1
|
export { default as VueViewPanel } from "./panel/VueViewPanel.vue";
|
|
2
2
|
export { default as VueViewOnlinePreview } from "./panel/VueViewOnlinePreview.vue";
|
|
3
3
|
export { parseOnlinePreviewSearchParams } from "./panel/parseOnlinePreviewSearchParams";
|
|
4
|
+
export type { WorkspaceProjectRecord } from "./panel/library/workspace-project-db";
|
|
4
5
|
export type * from "./panel/types";
|
|
6
|
+
export {
|
|
7
|
+
parseViewData,
|
|
8
|
+
validateViewData,
|
|
9
|
+
parseBlueprintData,
|
|
10
|
+
validateBlueprintData,
|
|
11
|
+
validateWorkspaceData,
|
|
12
|
+
parseWorkspaceData,
|
|
13
|
+
createEmptyWorkspace,
|
|
14
|
+
} from "./panel/library/parse-workspace-data";
|
|
15
|
+
export { createWorkspaceProjectId } from "./panel/library/workspace-project-db";
|
|
16
|
+
export {
|
|
17
|
+
addEventSubscription,
|
|
18
|
+
AbuilderEvents,
|
|
19
|
+
} from "./panel/library/event-subscription";
|
|
20
|
+
export type {
|
|
21
|
+
AbuilderEventName,
|
|
22
|
+
AbuilderEventPayloadMap,
|
|
23
|
+
WorkspaceAddEventPayload,
|
|
24
|
+
WorkspaceSyncEventPayload,
|
|
25
|
+
WorkspaceData,
|
|
26
|
+
} from "./panel/library/event-subscription";
|
|
27
|
+
export type {
|
|
28
|
+
ParseCheckResult,
|
|
29
|
+
WorkspaceParseCheckResult,
|
|
30
|
+
} from "./panel/library/parse-workspace-data";
|
|
@@ -22,7 +22,10 @@ import {
|
|
|
22
22
|
useViewScopeStoreVersion,
|
|
23
23
|
} from "./scope/view-scope-store";
|
|
24
24
|
import { resolvePanelElementScope } from "./utils/scope-template";
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
getWorkspaceProject,
|
|
27
|
+
type WorkspaceProjectRecord,
|
|
28
|
+
} from "./library/workspace-project-db";
|
|
26
29
|
import { readWorkspacePreviewCache } from "./library/workspace-project-cache";
|
|
27
30
|
import { subscribeWorkspaceProjectUpdates } from "./library/workspace-project-sync";
|
|
28
31
|
import {
|
|
@@ -41,8 +44,12 @@ const { t, locale } = useI18n();
|
|
|
41
44
|
const PREVIEW_BOOT_PHASES: PageLifecyclePhase[] = ["mounted"];
|
|
42
45
|
|
|
43
46
|
const props = defineProps<{
|
|
44
|
-
|
|
47
|
+
/** 从 IndexedDB / 缓存按 ID 加载;与 `workspace` 二选一,优先 `workspace` */
|
|
48
|
+
projectId?: string;
|
|
45
49
|
previewInstanceId?: string;
|
|
50
|
+
nameSpace?: string | null;
|
|
51
|
+
/** 直接传入完整工作区,用于宿主后端加载后预览 */
|
|
52
|
+
workspace?: WorkspaceProjectRecord | null;
|
|
46
53
|
}>();
|
|
47
54
|
|
|
48
55
|
const loadError = ref<string | null>(null);
|
|
@@ -53,7 +60,7 @@ const blueprintLibraryItems = ref<BlueprintLibraryListItem[]>([]);
|
|
|
53
60
|
const layoutRevision = ref(0);
|
|
54
61
|
const layoutReady = ref(false);
|
|
55
62
|
const sceneRef = ref<HTMLDivElement | null>(null);
|
|
56
|
-
const outputScale = ref(readOutputScale());
|
|
63
|
+
const outputScale = ref(readOutputScale(props.nameSpace));
|
|
57
64
|
const fillScale = ref({ scaleX: 1, scaleY: 1 });
|
|
58
65
|
|
|
59
66
|
const layers = computed(() => (panelState.value ? parsePanelLayers(panelState.value) : []));
|
|
@@ -64,10 +71,19 @@ const allElements = computed(() =>
|
|
|
64
71
|
panelState.value ? parseAllPanelElements(panelState.value) : []
|
|
65
72
|
);
|
|
66
73
|
|
|
74
|
+
const resolvedProjectId = computed(
|
|
75
|
+
() => props.workspace?.id ?? props.projectId ?? ""
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
function workspaceTitle(record: WorkspaceProjectRecord) {
|
|
79
|
+
const product = typeof record.productName === "string" ? record.productName : "";
|
|
80
|
+
return product.trim() || record.name || t("panel.workspace.previewDocTitle");
|
|
81
|
+
}
|
|
82
|
+
|
|
67
83
|
async function loadWorkspaceRecord(projectId: string) {
|
|
68
|
-
const fromDb = await getWorkspaceProject(projectId);
|
|
84
|
+
const fromDb = await getWorkspaceProject(projectId, props.nameSpace);
|
|
69
85
|
if (fromDb) return fromDb;
|
|
70
|
-
return readWorkspacePreviewCache(projectId);
|
|
86
|
+
return readWorkspacePreviewCache(projectId, props.nameSpace);
|
|
71
87
|
}
|
|
72
88
|
|
|
73
89
|
function applyTitleIcon(titleIconDataUrl?: string) {
|
|
@@ -82,13 +98,7 @@ function applyTitleIcon(titleIconDataUrl?: string) {
|
|
|
82
98
|
}
|
|
83
99
|
}
|
|
84
100
|
|
|
85
|
-
|
|
86
|
-
const record = await loadWorkspaceRecord(props.projectId);
|
|
87
|
-
if (!record) {
|
|
88
|
-
loadError.value = t("panel.messages.workspaceNotFound");
|
|
89
|
-
panelState.value = null;
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
101
|
+
function applyWorkspaceRecord(record: WorkspaceProjectRecord) {
|
|
92
102
|
const normalized = normalizeImportedPanelState(record.panelState);
|
|
93
103
|
if (!normalized) {
|
|
94
104
|
loadError.value = t("panel.messages.workspaceDataInvalid");
|
|
@@ -99,24 +109,56 @@ async function loadProject() {
|
|
|
99
109
|
clearViewElementScopes();
|
|
100
110
|
panelState.value = normalized;
|
|
101
111
|
blueprintGraph.value = BlueprintGraph.fromDocument(record.blueprintDocument);
|
|
102
|
-
document.title =
|
|
112
|
+
document.title = workspaceTitle(record);
|
|
103
113
|
applyTitleIcon(record.titleIconDataUrl);
|
|
104
114
|
projectRevision.value += 1;
|
|
105
115
|
}
|
|
106
116
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
117
|
+
async function loadProject() {
|
|
118
|
+
if (props.workspace) {
|
|
119
|
+
applyWorkspaceRecord(props.workspace);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (!props.projectId) {
|
|
123
|
+
loadError.value = t("panel.messages.workspaceNotFound");
|
|
124
|
+
panelState.value = null;
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const record = await loadWorkspaceRecord(props.projectId);
|
|
128
|
+
if (!record) {
|
|
129
|
+
loadError.value = t("panel.messages.workspaceNotFound");
|
|
130
|
+
panelState.value = null;
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
applyWorkspaceRecord(record);
|
|
134
|
+
}
|
|
110
135
|
|
|
111
|
-
|
|
112
|
-
|
|
136
|
+
watch(
|
|
137
|
+
() => [props.workspace, props.projectId, props.nameSpace] as const,
|
|
138
|
+
() => {
|
|
113
139
|
void loadProject();
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
140
|
+
},
|
|
141
|
+
{ immediate: true }
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
watch(
|
|
145
|
+
() => [props.workspace, props.projectId, props.nameSpace] as const,
|
|
146
|
+
([workspace, projectId, nameSpace], _prev, onCleanup) => {
|
|
147
|
+
if (workspace || !projectId) return;
|
|
148
|
+
const unsub = subscribeWorkspaceProjectUpdates(
|
|
149
|
+
projectId,
|
|
150
|
+
() => {
|
|
151
|
+
void loadProject();
|
|
152
|
+
},
|
|
153
|
+
nameSpace
|
|
154
|
+
);
|
|
155
|
+
onCleanup(unsub);
|
|
156
|
+
},
|
|
157
|
+
{ immediate: true }
|
|
158
|
+
);
|
|
117
159
|
|
|
118
160
|
watch(projectRevision, () => {
|
|
119
|
-
void listBlueprintLibrary().then((items) => {
|
|
161
|
+
void listBlueprintLibrary(props.nameSpace).then((items) => {
|
|
120
162
|
blueprintLibraryItems.value = items;
|
|
121
163
|
});
|
|
122
164
|
});
|
|
@@ -126,9 +168,9 @@ const blueprintLibraryNameById = computed(
|
|
|
126
168
|
);
|
|
127
169
|
|
|
128
170
|
const resolveLibraryBlueprint: LibraryBlueprintResolver = async (libraryBlueprintId) => {
|
|
129
|
-
const record = await getBlueprintLibraryRecord(libraryBlueprintId);
|
|
171
|
+
const record = await getBlueprintLibraryRecord(libraryBlueprintId, props.nameSpace);
|
|
130
172
|
if (!record) return null;
|
|
131
|
-
const items = await listBlueprintLibrary();
|
|
173
|
+
const items = await listBlueprintLibrary(props.nameSpace);
|
|
132
174
|
const nameById = new Map(items.map((item) => [item.id, item.name]));
|
|
133
175
|
return documentToRunnableGraph(record.document, { libraryNameById: nameById });
|
|
134
176
|
};
|
|
@@ -167,7 +209,7 @@ const displayElements = computed(() =>
|
|
|
167
209
|
);
|
|
168
210
|
|
|
169
211
|
function applySceneFit() {
|
|
170
|
-
const enabled = readOutputScale();
|
|
212
|
+
const enabled = readOutputScale(props.nameSpace);
|
|
171
213
|
outputScale.value = enabled;
|
|
172
214
|
const fill = applyPreviewSceneFill(
|
|
173
215
|
sceneRef.value,
|
|
@@ -277,7 +319,7 @@ function noopSelect() {}
|
|
|
277
319
|
v-else
|
|
278
320
|
class="min-h-screen w-full overflow-hidden bg-white text-gray-900"
|
|
279
321
|
data-preview-mode="online"
|
|
280
|
-
:data-project-id="
|
|
322
|
+
:data-project-id="resolvedProjectId"
|
|
281
323
|
:data-preview-instance-id="previewInstanceId ?? ''"
|
|
282
324
|
:data-preview-node-count="String(displayElements.length)"
|
|
283
325
|
>
|
|
@@ -78,8 +78,10 @@ const props = withDefaults(
|
|
|
78
78
|
defineProps<{
|
|
79
79
|
class?: string;
|
|
80
80
|
initialZoom?: number;
|
|
81
|
+
nameSpace?: string | null;
|
|
82
|
+
initialWorkspace?: WorkspaceProjectRecord | null;
|
|
81
83
|
}>(),
|
|
82
|
-
{ initialZoom: 1 }
|
|
84
|
+
{ initialZoom: 1, nameSpace: null, initialWorkspace: null }
|
|
83
85
|
);
|
|
84
86
|
|
|
85
87
|
const {
|
|
@@ -296,9 +298,9 @@ const {
|
|
|
296
298
|
} = useBlueprintNodeSelectionGuard(selectedBlueprintNodeId, applyBlueprintNodeSelection);
|
|
297
299
|
|
|
298
300
|
const resolveLibraryBlueprint: LibraryBlueprintResolver = async (libraryBlueprintId) => {
|
|
299
|
-
const record = await getBlueprintLibraryRecord(libraryBlueprintId);
|
|
301
|
+
const record = await getBlueprintLibraryRecord(libraryBlueprintId, props.nameSpace);
|
|
300
302
|
if (!record) return null;
|
|
301
|
-
const items = await listBlueprintLibrary();
|
|
303
|
+
const items = await listBlueprintLibrary(props.nameSpace);
|
|
302
304
|
const nameById = new Map(items.map((item) => [item.id, item.name]));
|
|
303
305
|
return documentToRunnableGraph(record.document, { libraryNameById: nameById });
|
|
304
306
|
};
|
|
@@ -319,6 +321,7 @@ const blueprintDebugSession = useBlueprintDebugSession({
|
|
|
319
321
|
libraryNameById: blueprintLibraryNameById,
|
|
320
322
|
onExecutionBlocked: handleBlueprintExecutionBlocked,
|
|
321
323
|
onViewScopeUpdate: handleViewScopeUpdate,
|
|
324
|
+
nameSpace: () => props.nameSpace,
|
|
322
325
|
});
|
|
323
326
|
|
|
324
327
|
const blueprintDebugToolbar = computed(() => ({
|
|
@@ -359,7 +362,7 @@ function handleUpdateAllowFalseSignalPropagation(value: boolean) {
|
|
|
359
362
|
}
|
|
360
363
|
|
|
361
364
|
async function refreshBlueprintLibrary() {
|
|
362
|
-
blueprintLibraryItems.value = await listBlueprintLibrary();
|
|
365
|
+
blueprintLibraryItems.value = await listBlueprintLibrary(props.nameSpace);
|
|
363
366
|
}
|
|
364
367
|
|
|
365
368
|
onMounted(() => {
|
|
@@ -402,6 +405,8 @@ const workspaceProjects = useWorkspaceProjects({
|
|
|
402
405
|
},
|
|
403
406
|
panelRevision: computed(() => `${historyCursor.value}|${allElements.value.length}`),
|
|
404
407
|
onProjectApplied: handleWorkspaceProjectApplied,
|
|
408
|
+
initialWorkspace: () => props.initialWorkspace,
|
|
409
|
+
nameSpace: () => props.nameSpace,
|
|
405
410
|
});
|
|
406
411
|
|
|
407
412
|
const { triggerBlueprintNode, emitViewEvent, firedLifecyclePhases } = useBlueprintPageLifecycle({
|
|
@@ -440,7 +445,7 @@ const blueprintNodeOptions = computed(() =>
|
|
|
440
445
|
);
|
|
441
446
|
async function loadBlueprintFromLibrary(id: string) {
|
|
442
447
|
await runBusyTask(t("common.loadingBlueprint"), async () => {
|
|
443
|
-
const record = await getBlueprintLibraryRecord(id);
|
|
448
|
+
const record = await getBlueprintLibraryRecord(id, props.nameSpace);
|
|
444
449
|
if (!record) {
|
|
445
450
|
message.error(panelMessages().blueprintNotFound);
|
|
446
451
|
void refreshBlueprintLibrary();
|
|
@@ -487,14 +492,14 @@ async function syncBlueprintToLibrary() {
|
|
|
487
492
|
const libraryId = activeBlueprintLibraryId.value;
|
|
488
493
|
if (!libraryId) return;
|
|
489
494
|
await runBusyTask(t("common.syncingBlueprint"), async () => {
|
|
490
|
-
const record = await getBlueprintLibraryRecord(libraryId);
|
|
495
|
+
const record = await getBlueprintLibraryRecord(libraryId, props.nameSpace);
|
|
491
496
|
if (!record) return;
|
|
492
497
|
await putBlueprintLibraryRecord({
|
|
493
498
|
...record,
|
|
494
499
|
document: blueprintGraph.value.document,
|
|
495
500
|
name: blueprintMeta.value.name,
|
|
496
501
|
remark: blueprintMeta.value.remark,
|
|
497
|
-
});
|
|
502
|
+
}, props.nameSpace);
|
|
498
503
|
blueprintSyncedDocument.value = blueprintGraph.value.document;
|
|
499
504
|
message.success(panelMessages().blueprintSyncedToLibrary);
|
|
500
505
|
await refreshBlueprintLibrary();
|
|
@@ -510,7 +515,7 @@ async function saveBlueprintToLibrary(meta: BlueprintMetaDraft) {
|
|
|
510
515
|
meta,
|
|
511
516
|
source: "saved",
|
|
512
517
|
});
|
|
513
|
-
await putBlueprintLibraryRecord(record);
|
|
518
|
+
await putBlueprintLibraryRecord(record, props.nameSpace);
|
|
514
519
|
activeBlueprintLibraryId.value = id;
|
|
515
520
|
blueprintSyncedDocument.value = blueprintGraph.value.document;
|
|
516
521
|
blueprintMeta.value = meta;
|
|
@@ -526,7 +531,7 @@ function handleExport() {
|
|
|
526
531
|
const url = URL.createObjectURL(blob);
|
|
527
532
|
const a = document.createElement("a");
|
|
528
533
|
a.href = url;
|
|
529
|
-
const safeName = (productName.value.trim() || "panel")
|
|
534
|
+
const safeName = ((productName.value ?? "").trim() || "panel")
|
|
530
535
|
.replace(/[\\/:*?"<>|]/g, "-")
|
|
531
536
|
.replace(/\s+/g, "-");
|
|
532
537
|
a.download = `${safeName}-${Date.now()}.json`;
|
|
@@ -566,7 +571,7 @@ async function handleBlueprintImportFile(file: File) {
|
|
|
566
571
|
const text = await file.text();
|
|
567
572
|
const payload = parseBlueprintImportFile(await parseJsonText(text));
|
|
568
573
|
const record = libraryRecordFromImport(payload);
|
|
569
|
-
await putBlueprintLibraryRecord(record);
|
|
574
|
+
await putBlueprintLibraryRecord(record, props.nameSpace);
|
|
570
575
|
await refreshBlueprintLibrary();
|
|
571
576
|
if (!activeBlueprintLibraryId.value) snapshotWorkspaceBlueprint();
|
|
572
577
|
await loadBlueprintFromLibrary(record.id);
|
|
@@ -85,7 +85,7 @@ const NodeContent = defineComponent({
|
|
|
85
85
|
previewMode: p.previewMode,
|
|
86
86
|
});
|
|
87
87
|
}
|
|
88
|
-
if (el.materialType === "reference") {
|
|
88
|
+
if (el.materialType === "reference" || el.materialType === "viewport") {
|
|
89
89
|
return h(ReferenceNodeContent, {
|
|
90
90
|
element: el,
|
|
91
91
|
allElements: p.allElements,
|
|
@@ -145,7 +145,8 @@ const NodeContent = defineComponent({
|
|
|
145
145
|
const sortedElements = computed(() => {
|
|
146
146
|
const byId = new Map<string, PanelElement>();
|
|
147
147
|
for (const el of props.allElements) byId.set(el.id, el);
|
|
148
|
-
return [...props.elements]
|
|
148
|
+
return [...props.elements]
|
|
149
|
+
.sort((a, b) => comparePanelElementsPaintOrder(a, b, byId));
|
|
149
150
|
});
|
|
150
151
|
|
|
151
152
|
function onSelect(id: string, event: MouseEvent) {
|
|
@@ -200,6 +201,7 @@ function hasViewEvent(el: PanelElement) {
|
|
|
200
201
|
transform: `rotate(${el.rotate ?? 0}deg)`,
|
|
201
202
|
transformOrigin: 'center center',
|
|
202
203
|
boxSizing: 'border-box',
|
|
204
|
+
overflow: el.materialType === 'viewport' ? 'hidden' : undefined,
|
|
203
205
|
...getNodeVisualStyle(el),
|
|
204
206
|
}"
|
|
205
207
|
@mousedown="onSelect(el.id, $event)"
|
|
@@ -157,5 +157,13 @@ const common =
|
|
|
157
157
|
/>
|
|
158
158
|
</div>
|
|
159
159
|
</div>
|
|
160
|
+
<div v-else-if="id === 'viewport'" :class="common">
|
|
161
|
+
<div class="absolute inset-1.5 overflow-hidden rounded-sm border border-primary/70 bg-primary/10">
|
|
162
|
+
<div class="absolute left-1 top-1 h-6 w-8 rounded-[2px] bg-primary/45" />
|
|
163
|
+
<div class="absolute left-6 top-4 h-7 w-10 rounded-[2px] bg-primary/30" />
|
|
164
|
+
<div class="absolute bottom-0 right-0 h-1.5 w-6 rounded-tl bg-primary/55" />
|
|
165
|
+
<div class="absolute bottom-0 right-0 top-2 w-1.5 rounded-l bg-primary/40" />
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
160
168
|
<div v-else :class="common" />
|
|
161
169
|
</template>
|
|
@@ -157,17 +157,26 @@ function setExpanded(key: string, next: boolean) {
|
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
function hasRefInSubtree(node: PanelElement, visited: Set<string>): boolean {
|
|
160
|
-
if (node.materialType === "reference") return true;
|
|
160
|
+
if (node.materialType === "reference" || node.materialType === "viewport") return true;
|
|
161
161
|
if (visited.has(node.id)) return false;
|
|
162
162
|
const nextVisited = new Set(visited);
|
|
163
163
|
nextVisited.add(node.id);
|
|
164
|
-
const children =
|
|
165
|
-
|
|
166
|
-
|
|
164
|
+
const children = getTreeChildren(node);
|
|
165
|
+
return children.some((child) => hasRefInSubtree(child, nextVisited));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function getTreeChildren(node: PanelElement, sourceOverride?: PanelElement[]) {
|
|
169
|
+
if (node.materialType === "reference" || node.materialType === "viewport") {
|
|
170
|
+
return node.refCopyMode === "deep"
|
|
171
|
+
? node.refSnapshot ?? sourceOverride ?? []
|
|
167
172
|
: node.refLayerId
|
|
168
173
|
? elementsByLayer.value.get(node.refLayerId) ?? []
|
|
169
174
|
: [];
|
|
170
|
-
|
|
175
|
+
}
|
|
176
|
+
if (node.materialType === "grid") {
|
|
177
|
+
return [...(childrenByGridByLayer.value.get(node.layerId)?.get(node.id) ?? [])];
|
|
178
|
+
}
|
|
179
|
+
return [];
|
|
171
180
|
}
|
|
172
181
|
|
|
173
182
|
function nodeMatchesTreeSearch(node: PanelElement, visited: Set<string>): boolean {
|
|
@@ -177,19 +186,7 @@ function nodeMatchesTreeSearch(node: PanelElement, visited: Set<string>): boolea
|
|
|
177
186
|
if (visited.has(node.id)) return false;
|
|
178
187
|
const nextVisited = new Set(visited);
|
|
179
188
|
nextVisited.add(node.id);
|
|
180
|
-
|
|
181
|
-
const isGrid = node.materialType === "grid";
|
|
182
|
-
const gridChildren = isGrid
|
|
183
|
-
? [...(childrenByGridByLayer.value.get(node.layerId)?.get(node.id) ?? [])]
|
|
184
|
-
: [];
|
|
185
|
-
const children = isRef
|
|
186
|
-
? node.refCopyMode === "deep"
|
|
187
|
-
? node.refSnapshot ?? []
|
|
188
|
-
: node.refLayerId
|
|
189
|
-
? elementsByLayer.value.get(node.refLayerId) ?? []
|
|
190
|
-
: []
|
|
191
|
-
: gridChildren;
|
|
192
|
-
return children.some((child) => nodeMatchesTreeSearch(child, nextVisited));
|
|
189
|
+
return getTreeChildren(node).some((child) => nodeMatchesTreeSearch(child, nextVisited));
|
|
193
190
|
}
|
|
194
191
|
|
|
195
192
|
function getRootNodes(layerId: string) {
|
|
@@ -38,7 +38,9 @@ const emit = defineEmits<{
|
|
|
38
38
|
|
|
39
39
|
const nodeKey = computed(() => `node:${props.path}`);
|
|
40
40
|
const selected = computed(() => props.selectedIds.includes(props.node.id));
|
|
41
|
-
const isRef = computed(
|
|
41
|
+
const isRef = computed(
|
|
42
|
+
() => props.node.materialType === "reference" || props.node.materialType === "viewport"
|
|
43
|
+
);
|
|
42
44
|
const refMode = computed(() => props.node.refCopyMode ?? "shallow");
|
|
43
45
|
const isDeepRef = computed(() => isRef.value && refMode.value === "deep");
|
|
44
46
|
const nodeHomeLayer = computed(() => props.layerById.get(props.node.layerId));
|
|
@@ -82,7 +84,7 @@ function nodeMatchesTreeSearch(
|
|
|
82
84
|
const next = new Set(visited);
|
|
83
85
|
next.add(node.id);
|
|
84
86
|
const childList =
|
|
85
|
-
node.materialType === "reference"
|
|
87
|
+
node.materialType === "reference" || node.materialType === "viewport"
|
|
86
88
|
? node.refCopyMode === "deep"
|
|
87
89
|
? node.refSnapshot ?? sourceOverride ?? []
|
|
88
90
|
: node.refLayerId
|
|
@@ -22,6 +22,7 @@ import PanelConfigVideoSection from "./config/PanelConfigVideoSection.vue";
|
|
|
22
22
|
import PanelConfigGeometrySection from "./config/PanelConfigGeometrySection.vue";
|
|
23
23
|
import PanelConfigScene3dSection from "./config/PanelConfigScene3dSection.vue";
|
|
24
24
|
import PanelConfigGridSection from "./config/PanelConfigGridSection.vue";
|
|
25
|
+
import PanelConfigViewportSection from "./config/PanelConfigViewportSection.vue";
|
|
25
26
|
import PanelConfigGridChildSpan from "./config/PanelConfigGridChildSpan.vue";
|
|
26
27
|
import PanelConfigReferenceSection from "./config/PanelConfigReferenceSection.vue";
|
|
27
28
|
import PanelConfigTableSection from "./table/PanelConfigTableSection.vue";
|
|
@@ -156,6 +157,9 @@ const visibleSectionCount = computed(() => {
|
|
|
156
157
|
if (materialType.value === "image") checks.push(["imageConfig", t("panel.config.sectionImage"), [t("panel.defaults.image")]]);
|
|
157
158
|
if (materialType.value === "geometry") checks.push(["geometryConfig", t("panel.config.sectionGeometry"), [t("panel.defaults.geometry")]]);
|
|
158
159
|
if (materialType.value === "grid") checks.push(["gridConfig", t("panel.config.sectionGrid"), [t("panel.config.searchKwGrid")]]);
|
|
160
|
+
if (materialType.value === "viewport") {
|
|
161
|
+
checks.push(["viewportConfig", t("panel.config.sectionViewport"), [t("panel.config.searchKwViewport"), t("panel.config.refLayer")]]);
|
|
162
|
+
}
|
|
159
163
|
if (props.selectedElement?.parentGridId) {
|
|
160
164
|
checks.push(["gridChildSpan", t("panel.config.sectionGridChildSpan"), [t("panel.config.searchKwCrossCol")]]);
|
|
161
165
|
}
|
|
@@ -354,6 +358,18 @@ watch(
|
|
|
354
358
|
@update:open="(v) => setSectionExpanded('gridConfig', v)"
|
|
355
359
|
/>
|
|
356
360
|
|
|
361
|
+
<PanelConfigViewportSection
|
|
362
|
+
v-if="materialType === 'viewport' && shouldShowSection('viewportConfig', t('panel.config.sectionViewport'), [t('panel.config.searchKwViewport'), t('panel.config.viewportOverflow'), t('panel.config.refLayer')])"
|
|
363
|
+
:element="selectedElement"
|
|
364
|
+
:layers="layers"
|
|
365
|
+
:is-editable="isNodeEditable"
|
|
366
|
+
:open="isSectionExpanded('viewportConfig')"
|
|
367
|
+
:force-open="forceOpenSections"
|
|
368
|
+
:set-reference-copy-mode="setReferenceCopyMode"
|
|
369
|
+
:update-element="updateElement"
|
|
370
|
+
@update:open="(v) => setSectionExpanded('viewportConfig', v)"
|
|
371
|
+
/>
|
|
372
|
+
|
|
357
373
|
<PanelConfigGridChildSpan
|
|
358
374
|
v-if="selectedElement.parentGridId && shouldShowSection('gridChildSpan', t('panel.config.sectionGridChildSpan'), [t('panel.config.searchKwCrossCol'), t('panel.config.searchKwCrossRow')])"
|
|
359
375
|
:element="selectedElement"
|
|
@@ -394,7 +410,7 @@ watch(
|
|
|
394
410
|
/>
|
|
395
411
|
|
|
396
412
|
<div
|
|
397
|
-
v-if="!isChartElement && materialType && !['text', 'table', 'audio', 'video', 'image', 'geometry', 'grid', 'reference'].includes(materialType)"
|
|
413
|
+
v-if="!isChartElement && materialType && !['text', 'table', 'audio', 'video', 'image', 'geometry', 'grid', 'viewport', 'reference'].includes(materialType)"
|
|
398
414
|
class="text-xs leading-6 text-gray-500"
|
|
399
415
|
>
|
|
400
416
|
{{ t("panel.config.notChartType") }}
|
|
@@ -4,6 +4,11 @@ import { computed, ref } from "vue";
|
|
|
4
4
|
import { Card, Checkbox, Input, InputNumber, Select } from "ant-design-vue";
|
|
5
5
|
import type { PanelElement, PanelLayer, ReferenceCopyMode } from "../../types";
|
|
6
6
|
import { CHART_TYPES } from "../../utils/chartOptionBuilder";
|
|
7
|
+
import {
|
|
8
|
+
VIEWPORT_OVERFLOW_MODES,
|
|
9
|
+
normalizeViewportOverflow,
|
|
10
|
+
type ViewportOverflowMode,
|
|
11
|
+
} from "../../utils/viewportPlacement";
|
|
7
12
|
|
|
8
13
|
const { t, locale } = useI18n();
|
|
9
14
|
const props = defineProps<{
|
|
@@ -357,7 +362,7 @@ function setNodeCardExpanded(id: string, open: boolean) {
|
|
|
357
362
|
</div>
|
|
358
363
|
</template>
|
|
359
364
|
|
|
360
|
-
<template v-if="el.materialType === 'reference'">
|
|
365
|
+
<template v-if="el.materialType === 'reference' || el.materialType === 'viewport'">
|
|
361
366
|
<div class="grid grid-cols-2 gap-2">
|
|
362
367
|
<label class="block space-y-1">
|
|
363
368
|
<div>{{ t("panel.config.refLayer") }}</div>
|
|
@@ -392,6 +397,30 @@ function setNodeCardExpanded(id: string, open: boolean) {
|
|
|
392
397
|
</div>
|
|
393
398
|
</template>
|
|
394
399
|
|
|
400
|
+
<template v-if="el.materialType === 'viewport'">
|
|
401
|
+
<label class="block space-y-1">
|
|
402
|
+
<div>{{ t("panel.config.viewportOverflow") }}</div>
|
|
403
|
+
<Select
|
|
404
|
+
size="small"
|
|
405
|
+
class="w-full"
|
|
406
|
+
:value="normalizeViewportOverflow(el.viewportOverflow)"
|
|
407
|
+
@update:value="(v) => updateElement(el.id, { viewportOverflow: v as ViewportOverflowMode })"
|
|
408
|
+
>
|
|
409
|
+
<Select.Option v-for="mode in VIEWPORT_OVERFLOW_MODES" :key="mode" :value="mode">
|
|
410
|
+
{{
|
|
411
|
+
mode === "scroll-x"
|
|
412
|
+
? t("panel.config.viewportOverflowScrollX")
|
|
413
|
+
: mode === "scroll-y"
|
|
414
|
+
? t("panel.config.viewportOverflowScrollY")
|
|
415
|
+
: mode === "scroll"
|
|
416
|
+
? t("panel.config.viewportOverflowScroll")
|
|
417
|
+
: t("panel.config.viewportOverflowClip")
|
|
418
|
+
}}
|
|
419
|
+
</Select.Option>
|
|
420
|
+
</Select>
|
|
421
|
+
</label>
|
|
422
|
+
</template>
|
|
423
|
+
|
|
395
424
|
<template v-if="el.materialType === 'image'">
|
|
396
425
|
<label class="block space-y-1">
|
|
397
426
|
<div>{{ t("panel.config.backgroundImageUrl") }}</div>
|