@arronqzy/vue-view 0.1.9 → 0.1.10
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 +9 -9
- 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/types.ts +4 -0
- package/src/panel/utils/panelElementDefaults.ts +10 -0
- package/src/panel/utils/panelElementNodes.ts +5 -3
- package/src/panel/utils/viewportPlacement.ts +73 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arronqzy/vue-view",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
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/blueprint-dsl": "1.0.
|
|
27
|
-
"@arronqzy/i18n": "0.1.7",
|
|
28
|
-
"@arronqzy/view-scene3d": "0.1.2",
|
|
29
|
-
"@arronqzy/view-table": "0.1.5",
|
|
30
|
-
"@arronqzy/vue-rx-store": "0.1.3",
|
|
26
|
+
"@arronqzy/blueprint-dsl": "1.0.10",
|
|
31
27
|
"@arronqzy/rx-store": "1.0.5",
|
|
32
|
-
"@arronqzy/
|
|
28
|
+
"@arronqzy/view-table": "0.1.5",
|
|
29
|
+
"@arronqzy/vue-blueprint": "0.1.8",
|
|
30
|
+
"@arronqzy/i18n": "0.1.8",
|
|
31
|
+
"@arronqzy/view-scene3d": "0.1.2",
|
|
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"
|
|
@@ -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>
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useI18n } from "@arronqzy/i18n/vue";
|
|
3
|
+
import { Select } from "ant-design-vue";
|
|
4
|
+
import type { PanelElement, PanelLayer, ReferenceCopyMode } from "../../types";
|
|
5
|
+
import {
|
|
6
|
+
VIEWPORT_OVERFLOW_MODES,
|
|
7
|
+
normalizeViewportOverflow,
|
|
8
|
+
type ViewportOverflowMode,
|
|
9
|
+
} from "../../utils/viewportPlacement";
|
|
10
|
+
import ConfigFieldGroup from "./ConfigFieldGroup.vue";
|
|
11
|
+
import ConfigSection from "./ConfigSection.vue";
|
|
12
|
+
|
|
13
|
+
const { t } = useI18n();
|
|
14
|
+
const props = defineProps<{
|
|
15
|
+
element: PanelElement;
|
|
16
|
+
layers: PanelLayer[];
|
|
17
|
+
isEditable: boolean;
|
|
18
|
+
open: boolean;
|
|
19
|
+
forceOpen?: boolean;
|
|
20
|
+
setReferenceCopyMode?: (id: string, mode: ReferenceCopyMode) => void;
|
|
21
|
+
updateElement: (
|
|
22
|
+
id: string,
|
|
23
|
+
patch: Partial<PanelElement>,
|
|
24
|
+
options?: { batchId?: string; meta?: Record<string, unknown> }
|
|
25
|
+
) => void;
|
|
26
|
+
}>();
|
|
27
|
+
|
|
28
|
+
const emit = defineEmits<{
|
|
29
|
+
"update:open": [value: boolean];
|
|
30
|
+
}>();
|
|
31
|
+
|
|
32
|
+
const overflowLabelKey: Record<ViewportOverflowMode, string> = {
|
|
33
|
+
"scroll-x": "panel.config.viewportOverflowScrollX",
|
|
34
|
+
"scroll-y": "panel.config.viewportOverflowScrollY",
|
|
35
|
+
scroll: "panel.config.viewportOverflowScroll",
|
|
36
|
+
clip: "panel.config.viewportOverflowClip",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
function patch(next: Partial<PanelElement>) {
|
|
40
|
+
props.updateElement(props.element.id, next);
|
|
41
|
+
}
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<template>
|
|
45
|
+
<ConfigSection
|
|
46
|
+
:title="t('panel.config.sectionViewport')"
|
|
47
|
+
:open="open"
|
|
48
|
+
:force-open="forceOpen"
|
|
49
|
+
@update:open="emit('update:open', $event)"
|
|
50
|
+
>
|
|
51
|
+
<template #hint>
|
|
52
|
+
{{ t("panel.config.viewportHint") }}
|
|
53
|
+
</template>
|
|
54
|
+
<ConfigFieldGroup :title="t('panel.config.groupRefSource')">
|
|
55
|
+
<label class="block space-y-1">
|
|
56
|
+
<div>{{ t("panel.config.refLayer") }}</div>
|
|
57
|
+
<Select
|
|
58
|
+
size="small"
|
|
59
|
+
class="w-full"
|
|
60
|
+
:value="element.refLayerId ?? '__none__'"
|
|
61
|
+
:disabled="!isEditable"
|
|
62
|
+
@update:value="(v) => patch({ refLayerId: v === '__none__' ? undefined : String(v) })"
|
|
63
|
+
>
|
|
64
|
+
<Select.Option value="__none__">{{ t("panel.config.noneNoRef") }}</Select.Option>
|
|
65
|
+
<Select.Option
|
|
66
|
+
v-for="layer in layers.filter((l) => l.id !== element.layerId)"
|
|
67
|
+
:key="layer.id"
|
|
68
|
+
:value="layer.id"
|
|
69
|
+
>
|
|
70
|
+
{{ layer.name }}
|
|
71
|
+
</Select.Option>
|
|
72
|
+
</Select>
|
|
73
|
+
</label>
|
|
74
|
+
</ConfigFieldGroup>
|
|
75
|
+
<ConfigFieldGroup
|
|
76
|
+
:title="t('panel.config.groupCopyStrategy')"
|
|
77
|
+
collapsible
|
|
78
|
+
:default-open="false"
|
|
79
|
+
>
|
|
80
|
+
<template #hint>
|
|
81
|
+
{{ t("panel.config.copyStrategyHint") }}
|
|
82
|
+
</template>
|
|
83
|
+
<label class="block space-y-1">
|
|
84
|
+
<div>{{ t("panel.config.copyMode") }}</div>
|
|
85
|
+
<Select
|
|
86
|
+
size="small"
|
|
87
|
+
class="w-full"
|
|
88
|
+
:value="element.refCopyMode ?? 'shallow'"
|
|
89
|
+
:disabled="!isEditable"
|
|
90
|
+
@update:value="(v) => setReferenceCopyMode?.(element.id, v as ReferenceCopyMode)"
|
|
91
|
+
>
|
|
92
|
+
<Select.Option value="shallow">{{ t("panel.config.shallowFollow") }}</Select.Option>
|
|
93
|
+
<Select.Option value="deep">{{ t("panel.config.deepFreeze") }}</Select.Option>
|
|
94
|
+
</Select>
|
|
95
|
+
</label>
|
|
96
|
+
</ConfigFieldGroup>
|
|
97
|
+
<ConfigFieldGroup :title="t('panel.config.groupViewportOverflow')">
|
|
98
|
+
<label class="block space-y-1">
|
|
99
|
+
<div>{{ t("panel.config.viewportOverflow") }}</div>
|
|
100
|
+
<Select
|
|
101
|
+
size="small"
|
|
102
|
+
class="w-full"
|
|
103
|
+
:value="normalizeViewportOverflow(element.viewportOverflow)"
|
|
104
|
+
:disabled="!isEditable"
|
|
105
|
+
@update:value="(v) => patch({ viewportOverflow: v as ViewportOverflowMode })"
|
|
106
|
+
>
|
|
107
|
+
<Select.Option v-for="mode in VIEWPORT_OVERFLOW_MODES" :key="mode" :value="mode">
|
|
108
|
+
{{ t(overflowLabelKey[mode]) }}
|
|
109
|
+
</Select.Option>
|
|
110
|
+
</Select>
|
|
111
|
+
</label>
|
|
112
|
+
</ConfigFieldGroup>
|
|
113
|
+
</ConfigSection>
|
|
114
|
+
</template>
|
|
@@ -16,6 +16,12 @@ import type { PanelElement } from "../types";
|
|
|
16
16
|
import { buildChartOption, CHART_TYPES } from "../utils/chartOptionBuilder";
|
|
17
17
|
import { PREVIEW_LAYOUT_EVENT } from "../utils/panelStateIO";
|
|
18
18
|
import { cssTextLineHeight, cssTextAlignStyle } from "../utils/panelElementDefaults";
|
|
19
|
+
import {
|
|
20
|
+
isViewportNode,
|
|
21
|
+
normalizeViewportOverflow,
|
|
22
|
+
viewportOverflowStyle,
|
|
23
|
+
viewportWindowContentScale,
|
|
24
|
+
} from "../utils/viewportPlacement";
|
|
19
25
|
import TableNodeContent from "./table/TableNodeContent.vue";
|
|
20
26
|
import Scene3dNodeContent from "@arronqzy/view-scene3d/vue";
|
|
21
27
|
|
|
@@ -712,15 +718,25 @@ export const ReferenceNodeContent = defineComponent({
|
|
|
712
718
|
const sourceHeight = Math.max(1, maxY - minY);
|
|
713
719
|
const innerW = Math.max(1, p.element.width);
|
|
714
720
|
const innerH = Math.max(1, p.element.height);
|
|
715
|
-
const
|
|
716
|
-
const
|
|
717
|
-
|
|
721
|
+
const windowFit = isViewportNode(p.element);
|
|
722
|
+
const windowScale = windowFit
|
|
723
|
+
? viewportWindowContentScale(p.element, p.allElements)
|
|
724
|
+
: { scaleX: 1, scaleY: 1 };
|
|
725
|
+
const scaleX = windowFit
|
|
726
|
+
? windowScale.scaleX
|
|
727
|
+
: Math.max(0.05, Math.min(innerW / sourceWidth, innerH / sourceHeight));
|
|
728
|
+
const scaleY = windowFit ? windowScale.scaleY : scaleX;
|
|
729
|
+
const mappedW = sourceWidth * scaleX;
|
|
730
|
+
const mappedH = sourceHeight * scaleY;
|
|
718
731
|
return {
|
|
719
732
|
minX,
|
|
720
733
|
minY,
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
734
|
+
scaleX,
|
|
735
|
+
scaleY,
|
|
736
|
+
sourceWidth: mappedW,
|
|
737
|
+
sourceHeight: mappedH,
|
|
738
|
+
offsetX: windowFit ? 0 : (innerW - mappedW) / 2,
|
|
739
|
+
offsetY: windowFit ? 0 : (innerH - mappedH) / 2,
|
|
724
740
|
};
|
|
725
741
|
});
|
|
726
742
|
|
|
@@ -739,20 +755,27 @@ export const ReferenceNodeContent = defineComponent({
|
|
|
739
755
|
]);
|
|
740
756
|
}
|
|
741
757
|
|
|
742
|
-
|
|
743
|
-
|
|
758
|
+
const overflowCss = isViewportNode(p.element)
|
|
759
|
+
? viewportOverflowStyle(normalizeViewportOverflow(p.element.viewportOverflow))
|
|
760
|
+
: { overflowX: "hidden" as const, overflowY: "hidden" as const };
|
|
761
|
+
const canScroll =
|
|
762
|
+
isViewportNode(p.element) &&
|
|
763
|
+
(overflowCss.overflowX === "auto" || overflowCss.overflowY === "auto");
|
|
764
|
+
|
|
765
|
+
const innerNodes = sourceNodes.value.map((node) => {
|
|
744
766
|
const box = getNodeAABB(node);
|
|
745
|
-
const left = layout.value!.offsetX + (box.left - layout.value!.minX) * layout.value!.
|
|
746
|
-
const top = layout.value!.offsetY + (box.top - layout.value!.minY) * layout.value!.
|
|
747
|
-
const width = Math.max(12, node.width * layout.value!.
|
|
748
|
-
const height = Math.max(10, node.height * layout.value!.
|
|
749
|
-
const boxWidth = Math.max(12, (box.right - box.left) * layout.value!.
|
|
750
|
-
const boxHeight = Math.max(10, (box.bottom - box.top) * layout.value!.
|
|
767
|
+
const left = layout.value!.offsetX + (box.left - layout.value!.minX) * layout.value!.scaleX;
|
|
768
|
+
const top = layout.value!.offsetY + (box.top - layout.value!.minY) * layout.value!.scaleY;
|
|
769
|
+
const width = Math.max(12, node.width * layout.value!.scaleX);
|
|
770
|
+
const height = Math.max(10, node.height * layout.value!.scaleY);
|
|
771
|
+
const boxWidth = Math.max(12, (box.right - box.left) * layout.value!.scaleX);
|
|
772
|
+
const boxHeight = Math.max(10, (box.bottom - box.top) * layout.value!.scaleY);
|
|
751
773
|
return h(
|
|
752
774
|
"div",
|
|
753
775
|
{
|
|
754
776
|
key: node.id,
|
|
755
777
|
class: "absolute overflow-visible",
|
|
778
|
+
"data-viewport-item": isViewportNode(p.element) ? "true" : undefined,
|
|
756
779
|
style: { left, top, width: boxWidth, height: boxHeight },
|
|
757
780
|
},
|
|
758
781
|
[
|
|
@@ -760,6 +783,7 @@ export const ReferenceNodeContent = defineComponent({
|
|
|
760
783
|
"div",
|
|
761
784
|
{
|
|
762
785
|
class: "absolute",
|
|
786
|
+
"data-viewport-item": isViewportNode(p.element) ? "true" : undefined,
|
|
763
787
|
style: {
|
|
764
788
|
left: (boxWidth - width) / 2,
|
|
765
789
|
top: (boxHeight - height) / 2,
|
|
@@ -777,7 +801,7 @@ export const ReferenceNodeContent = defineComponent({
|
|
|
777
801
|
previewLayoutKey: p.previewLayoutKey,
|
|
778
802
|
previewMode: p.previewMode,
|
|
779
803
|
})
|
|
780
|
-
: node.materialType === "reference"
|
|
804
|
+
: node.materialType === "reference" || node.materialType === "viewport"
|
|
781
805
|
? h(ReferenceNodeContent as Component, {
|
|
782
806
|
element: node,
|
|
783
807
|
allElements: p.allElements,
|
|
@@ -800,8 +824,42 @@ export const ReferenceNodeContent = defineComponent({
|
|
|
800
824
|
),
|
|
801
825
|
]
|
|
802
826
|
);
|
|
803
|
-
})
|
|
804
|
-
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
return h(
|
|
830
|
+
"div",
|
|
831
|
+
{
|
|
832
|
+
class: "relative h-full w-full",
|
|
833
|
+
style: {
|
|
834
|
+
overflowX: overflowCss.overflowX,
|
|
835
|
+
overflowY: overflowCss.overflowY,
|
|
836
|
+
pointerEvents: isViewportNode(p.element) ? "auto" : "none",
|
|
837
|
+
},
|
|
838
|
+
onWheel: canScroll
|
|
839
|
+
? (e: WheelEvent) => {
|
|
840
|
+
e.stopPropagation();
|
|
841
|
+
}
|
|
842
|
+
: undefined,
|
|
843
|
+
},
|
|
844
|
+
[
|
|
845
|
+
h(
|
|
846
|
+
"div",
|
|
847
|
+
{
|
|
848
|
+
class: "pointer-events-none relative",
|
|
849
|
+
"data-viewport-sizer": isViewportNode(p.element) ? "true" : undefined,
|
|
850
|
+
style: isViewportNode(p.element)
|
|
851
|
+
? {
|
|
852
|
+
width: layout.value.sourceWidth,
|
|
853
|
+
height: layout.value.sourceHeight,
|
|
854
|
+
minWidth: "100%",
|
|
855
|
+
minHeight: "100%",
|
|
856
|
+
}
|
|
857
|
+
: { width: "100%", height: "100%" },
|
|
858
|
+
},
|
|
859
|
+
innerNodes
|
|
860
|
+
),
|
|
861
|
+
]
|
|
862
|
+
);
|
|
805
863
|
};
|
|
806
864
|
},
|
|
807
865
|
}) as Component;
|
|
@@ -26,6 +26,7 @@ const MATERIAL_LABEL_KEYS: Record<string, string> = {
|
|
|
26
26
|
text: "panel.material.text",
|
|
27
27
|
rect: "panel.material.rect",
|
|
28
28
|
grid: "panel.material.grid",
|
|
29
|
+
viewport: "panel.material.viewport",
|
|
29
30
|
image: "panel.material.image",
|
|
30
31
|
video: "panel.material.video",
|
|
31
32
|
audio: "panel.material.audio",
|
|
@@ -68,6 +69,7 @@ export function getDefaultCategories(t: TranslateFn): MaterialCategory[] {
|
|
|
68
69
|
{ id: "geometry", title: t("panel.material.geometry") },
|
|
69
70
|
{ id: "scene3d", title: t("panel.material.scene3d") },
|
|
70
71
|
{ id: "grid", title: t("panel.material.grid") },
|
|
72
|
+
{ id: "viewport", title: t("panel.material.viewport") },
|
|
71
73
|
{ id: "image", title: t("panel.material.image") },
|
|
72
74
|
{ id: "reference", title: t("panel.material.reference") },
|
|
73
75
|
],
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
DEFAULT_LAYER_ID,
|
|
17
17
|
getDefaultChartConfig,
|
|
18
18
|
getDefaultGridConfig,
|
|
19
|
+
getDefaultViewportConfig,
|
|
19
20
|
getDefaultNodeName,
|
|
20
21
|
getDefaultScene3dConfigForMaterial,
|
|
21
22
|
getDefaultSizeByMaterial,
|
|
@@ -569,7 +570,10 @@ export function usePanelElements() {
|
|
|
569
570
|
.filter((n) => isPanelElementNode(n) && n.props)
|
|
570
571
|
.map((n) => n.props as PanelElement);
|
|
571
572
|
const desiredMode = options?.referenceCopyMode;
|
|
572
|
-
if (
|
|
573
|
+
if (
|
|
574
|
+
(nextProps.materialType === "reference" || nextProps.materialType === "viewport") &&
|
|
575
|
+
desiredMode
|
|
576
|
+
) {
|
|
573
577
|
if (desiredMode === "deep" && nextProps.refLayerId) {
|
|
574
578
|
nextProps.refCopyMode = "deep";
|
|
575
579
|
nextProps.refSnapshot = buildDeepReferenceSnapshot(all, nextProps.refLayerId);
|
|
@@ -611,7 +615,9 @@ export function usePanelElements() {
|
|
|
611
615
|
name: getDefaultNodeName(materialType, t),
|
|
612
616
|
...getDefaultTextContent(materialType, t),
|
|
613
617
|
...getDefaultGridConfig(materialType),
|
|
614
|
-
|
|
618
|
+
...getDefaultViewportConfig(materialType),
|
|
619
|
+
refCopyMode:
|
|
620
|
+
materialType === "reference" || materialType === "viewport" ? "shallow" : undefined,
|
|
615
621
|
chart: getDefaultChartConfig(materialType, t),
|
|
616
622
|
table: getDefaultTableConfig(materialType, t),
|
|
617
623
|
geometryShape: materialType === "geometry" ? "rect" : undefined,
|
|
@@ -712,7 +718,7 @@ export function usePanelElements() {
|
|
|
712
718
|
.filter((n) => isPanelElementNode(n) && n.props)
|
|
713
719
|
.map((n) => n.props as PanelElement);
|
|
714
720
|
const target = all.find((el) => el.id === id);
|
|
715
|
-
if (!target || target.materialType !== "reference") return;
|
|
721
|
+
if (!target || (target.materialType !== "reference" && target.materialType !== "viewport")) return;
|
|
716
722
|
const currentLayers =
|
|
717
723
|
(current.variables?.layers as PanelLayer[] | undefined) ?? [DEFAULT_LAYER];
|
|
718
724
|
const layer = currentLayers.find((l) => l.id === target.layerId);
|
|
@@ -904,7 +910,7 @@ export function usePanelElements() {
|
|
|
904
910
|
if (target.isMapping) return false;
|
|
905
911
|
const willBeDeleted = mode === "remove" && el.layerId === layerId;
|
|
906
912
|
if (willBeDeleted) return false;
|
|
907
|
-
if (el.materialType !== "reference") return false;
|
|
913
|
+
if (el.materialType !== "reference" && el.materialType !== "viewport") return false;
|
|
908
914
|
if (el.refLayerId !== layerId) return false;
|
|
909
915
|
return (el.refCopyMode ?? "shallow") !== "deep";
|
|
910
916
|
});
|
package/src/panel/types.ts
CHANGED
|
@@ -54,6 +54,8 @@ export type PanelElementStyle = {
|
|
|
54
54
|
|
|
55
55
|
export type ReferenceCopyMode = "shallow" | "deep";
|
|
56
56
|
|
|
57
|
+
export type ViewportOverflowMode = "scroll-x" | "scroll-y" | "scroll" | "clip";
|
|
58
|
+
|
|
57
59
|
export type { PanelTableConfig } from "@arronqzy/view-table";
|
|
58
60
|
import type { PanelTableConfig } from "@arronqzy/view-table";
|
|
59
61
|
import type { Scene3dConfig } from "@arronqzy/view-scene3d";
|
|
@@ -84,6 +86,8 @@ export type PanelElement = {
|
|
|
84
86
|
gridPadding?: number;
|
|
85
87
|
gridSnapThreshold?: number;
|
|
86
88
|
parentGridId?: string;
|
|
89
|
+
/** 视窗节点:只显示自身尺寸内的引用图层内容,超出部分滚动或裁剪 */
|
|
90
|
+
viewportOverflow?: ViewportOverflowMode;
|
|
87
91
|
gridSlotIndex?: number;
|
|
88
92
|
gridColSpan?: number;
|
|
89
93
|
gridRowSpan?: number;
|
|
@@ -15,6 +15,7 @@ const DEFAULT_NODE_NAME_KEYS: Record<string, string> = {
|
|
|
15
15
|
funnel: "panel.defaults.funnel",
|
|
16
16
|
text: "panel.defaults.text",
|
|
17
17
|
grid: "panel.defaults.grid",
|
|
18
|
+
viewport: "panel.defaults.viewport",
|
|
18
19
|
image: "panel.defaults.image",
|
|
19
20
|
video: "panel.defaults.video",
|
|
20
21
|
audio: "panel.defaults.audio",
|
|
@@ -71,6 +72,8 @@ export function getDefaultSizeByMaterial(materialType: string) {
|
|
|
71
72
|
return { width: 180, height: 120 };
|
|
72
73
|
case "grid":
|
|
73
74
|
return { width: 320, height: 220 };
|
|
75
|
+
case "viewport":
|
|
76
|
+
return { width: 360, height: 240 };
|
|
74
77
|
case "image":
|
|
75
78
|
return { width: 220, height: 140 };
|
|
76
79
|
case "video":
|
|
@@ -145,6 +148,13 @@ export function getDefaultGridConfig(materialType: string) {
|
|
|
145
148
|
} as const;
|
|
146
149
|
}
|
|
147
150
|
|
|
151
|
+
export function getDefaultViewportConfig(materialType: string) {
|
|
152
|
+
if (materialType !== "viewport") return {};
|
|
153
|
+
return {
|
|
154
|
+
viewportOverflow: "clip" as const,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
148
158
|
export function getDefaultChartConfig(
|
|
149
159
|
materialType: string,
|
|
150
160
|
t: TranslateFn = tForLocale("zh-CN")
|
|
@@ -26,9 +26,11 @@ export function buildDeepReferenceSnapshot(
|
|
|
26
26
|
const layerNodes = allElements.filter((el) => el.layerId === layerId);
|
|
27
27
|
return layerNodes.map((el) => {
|
|
28
28
|
const cloned = clonePanelElement(el);
|
|
29
|
-
if (cloned.materialType === "reference"
|
|
30
|
-
cloned.
|
|
31
|
-
|
|
29
|
+
if (cloned.materialType === "reference" || cloned.materialType === "viewport") {
|
|
30
|
+
if (cloned.refLayerId) {
|
|
31
|
+
cloned.refCopyMode = "deep";
|
|
32
|
+
cloned.refSnapshot = buildDeepReferenceSnapshot(allElements, cloned.refLayerId, nextVisited);
|
|
33
|
+
}
|
|
32
34
|
}
|
|
33
35
|
return cloned;
|
|
34
36
|
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export const VIEWPORT_OVERFLOW_MODES = [
|
|
2
|
+
"scroll-x",
|
|
3
|
+
"scroll-y",
|
|
4
|
+
"scroll",
|
|
5
|
+
"clip",
|
|
6
|
+
] as const;
|
|
7
|
+
|
|
8
|
+
export type ViewportOverflowMode = (typeof VIEWPORT_OVERFLOW_MODES)[number];
|
|
9
|
+
|
|
10
|
+
export const DEFAULT_VIEWPORT_OVERFLOW: ViewportOverflowMode = "clip";
|
|
11
|
+
|
|
12
|
+
export function isViewportOverflowMode(
|
|
13
|
+
value: unknown
|
|
14
|
+
): value is ViewportOverflowMode {
|
|
15
|
+
return (
|
|
16
|
+
value === "scroll-x" ||
|
|
17
|
+
value === "scroll-y" ||
|
|
18
|
+
value === "scroll" ||
|
|
19
|
+
value === "clip"
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function normalizeViewportOverflow(
|
|
24
|
+
value: unknown
|
|
25
|
+
): ViewportOverflowMode {
|
|
26
|
+
return isViewportOverflowMode(value) ? value : DEFAULT_VIEWPORT_OVERFLOW;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function viewportOverflowStyle(mode: ViewportOverflowMode): {
|
|
30
|
+
overflowX: "hidden" | "auto";
|
|
31
|
+
overflowY: "hidden" | "auto";
|
|
32
|
+
} {
|
|
33
|
+
switch (mode) {
|
|
34
|
+
case "scroll-x":
|
|
35
|
+
return { overflowX: "auto", overflowY: "hidden" };
|
|
36
|
+
case "scroll-y":
|
|
37
|
+
return { overflowX: "hidden", overflowY: "auto" };
|
|
38
|
+
case "scroll":
|
|
39
|
+
return { overflowX: "auto", overflowY: "auto" };
|
|
40
|
+
default:
|
|
41
|
+
return { overflowX: "hidden", overflowY: "hidden" };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function isViewportNode(
|
|
46
|
+
el: { materialType?: string } | undefined
|
|
47
|
+
): boolean {
|
|
48
|
+
return el?.materialType === "viewport";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** 引用节点与视窗节点都通过选择图层来预览内容 */
|
|
52
|
+
export function isLayerPreviewNode(
|
|
53
|
+
el: { materialType?: string } | undefined
|
|
54
|
+
): boolean {
|
|
55
|
+
return el?.materialType === "reference" || el?.materialType === "viewport";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 视窗按自身宽高开窗;预览里节点会被整体拉满视口,
|
|
60
|
+
* 内部引用内容必须用同一套缩放,滚动比例才和编辑态一致。
|
|
61
|
+
*/
|
|
62
|
+
export function viewportWindowContentScale(
|
|
63
|
+
current: { id: string; width: number; height: number },
|
|
64
|
+
originals: ReadonlyArray<{ id: string; width: number; height: number }>
|
|
65
|
+
): { scaleX: number; scaleY: number } {
|
|
66
|
+
const original = originals.find((n) => n.id === current.id);
|
|
67
|
+
const ow = Math.max(1, original?.width ?? current.width);
|
|
68
|
+
const oh = Math.max(1, original?.height ?? current.height);
|
|
69
|
+
return {
|
|
70
|
+
scaleX: Math.max(1, current.width) / ow,
|
|
71
|
+
scaleY: Math.max(1, current.height) / oh,
|
|
72
|
+
};
|
|
73
|
+
}
|