@feedmepos/mf-order-setting 0.0.90 → 0.0.91
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/DisplaySystemView-n7Kqdw-h.js +6564 -0
- package/dist/{KioskDevicesView-WuOTJ9Fi.js → KioskDevicesView-Bz6seroM.js} +1 -1
- package/dist/{KioskDevicesView.vue_vue_type_script_setup_true_lang-DdHfATyN.js → KioskDevicesView.vue_vue_type_script_setup_true_lang-I39sE8Pe.js} +3 -3
- package/dist/{KioskSettingView-Bsja2OqT.js → KioskSettingView-Cajjh4Zm.js} +3 -3
- package/dist/{KioskView-BH5qGTM_.js → KioskView-BPSbsMi5.js} +93 -89
- package/dist/{OrderSettingsView-D6V1ztiS.js → OrderSettingsView-jPxZ3JDM.js} +5 -5
- package/dist/{app-DsMk7W2h.js → app-DIhv5ssA.js} +266 -254
- package/dist/app.js +1 -1
- package/dist/frontend/mf-order/src/app.d.ts +18 -6
- package/dist/frontend/mf-order/src/main.d.ts +18 -6
- package/dist/frontend/mf-order/src/modules/display-system/interface.d.ts +6 -3
- package/dist/frontend/mf-order/src/modules/kiosk/bindDeviceError.d.ts +20 -0
- package/dist/frontend/mf-order/src/views/display-system/CanvasArea.vue.d.ts +48 -0
- package/dist/frontend/mf-order/src/views/display-system/DisplayLayoutCanvas.vue.d.ts +17 -14
- package/dist/frontend/mf-order/src/views/display-system/DisplayModulePickerDialog.vue.d.ts +2 -0
- package/dist/frontend/mf-order/tsconfig.app.tsbuildinfo +1 -1
- package/dist/{index-DGDiI0fr.js → index-Clp_YRJ2.js} +1 -1
- package/dist/{index-BUDM0Rqn.js → index-CmqNrkNl.js} +1 -1
- package/dist/{index-C2n1MV_1.js → index-vaSLX6r3.js} +2 -2
- package/dist/{order.dto-BRDWWgMV.js → order.dto-DV1qL3Q_.js} +21 -1
- package/dist/package/entity/alert/investigation.dto.d.ts +26 -0
- package/dist/package/entity/alert/lark-callback.dto.d.ts +19 -0
- package/dist/package/entity/alert/record-resolution.dto.d.ts +35 -0
- package/dist/package/entity/index.d.ts +1 -0
- package/dist/package/entity/kiosk/kiosk.do.d.ts +105 -0
- package/dist/package/entity/kiosk/kiosk.dto.d.ts +76 -0
- package/dist/style.css +1 -1
- package/package.json +3 -1
- package/src/locales/en-US.json +11 -4
- package/src/locales/zh-CN.json +9 -4
- package/src/modules/display-system/interface.spec.ts +90 -5
- package/src/modules/display-system/interface.ts +31 -21
- package/src/modules/kiosk/bindDeviceError.spec.ts +42 -0
- package/src/modules/kiosk/bindDeviceError.ts +40 -0
- package/src/views/display-system/CanvasArea.vue +355 -0
- package/src/views/display-system/DisplayLayoutCanvas.spec.ts +57 -0
- package/src/views/display-system/DisplayLayoutCanvas.vue +173 -298
- package/src/views/display-system/DisplayModulePickerDialog.spec.ts +71 -0
- package/src/views/display-system/DisplayModulePickerDialog.vue +107 -0
- package/src/views/display-system/DisplayTemplateEditor.spec.ts +151 -0
- package/src/views/display-system/DisplayTemplateEditor.vue +233 -193
- package/src/views/kiosk/KioskView.vue +2 -4
- package/vitest.config.ts +2 -0
- package/dist/DisplaySystemView-tosCa7Ej.js +0 -6485
- package/dist/frontend/mf-order/src/views/display-system/DisplayModulePalette.vue.d.ts +0 -13
- package/src/views/display-system/DisplayModulePalette.vue +0 -140
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, ref } from 'vue'
|
|
3
|
-
import
|
|
3
|
+
import CanvasArea from './CanvasArea.vue'
|
|
4
4
|
|
|
5
5
|
import type {
|
|
6
6
|
DisplayLayoutDraft,
|
|
@@ -8,7 +8,7 @@ import type {
|
|
|
8
8
|
DisplayZoneDraft
|
|
9
9
|
} from '@/modules/display-system/interface'
|
|
10
10
|
import {
|
|
11
|
-
|
|
11
|
+
DISPLAY_CONTAINER_CHILD_LIMIT,
|
|
12
12
|
crossDirection,
|
|
13
13
|
walkZones,
|
|
14
14
|
weightPercents
|
|
@@ -18,8 +18,7 @@ const props = defineProps<{
|
|
|
18
18
|
root: DisplayLayoutDraft
|
|
19
19
|
selectedZoneId: string
|
|
20
20
|
selectedBlockId: string
|
|
21
|
-
/** Set while
|
|
22
|
-
* themselves as drop targets. */
|
|
21
|
+
/** Set while an existing module is dragged between areas. */
|
|
23
22
|
dragging: boolean
|
|
24
23
|
/** Which scope the inspector is editing. Selecting a module also selects its
|
|
25
24
|
* area, so without this both would light up the same way and there would be
|
|
@@ -30,23 +29,34 @@ const props = defineProps<{
|
|
|
30
29
|
const emit = defineEmits<{
|
|
31
30
|
(event: 'select-zone', zoneId: string): void
|
|
32
31
|
(event: 'select-block', payload: { zoneId: string; blockId: string }): void
|
|
33
|
-
|
|
32
|
+
/** Clicking an empty area opens its module picker. */
|
|
33
|
+
(event: 'open-module-picker', zoneId: string): void
|
|
34
34
|
(event: 'move-block', payload: { fromZoneId: string; toZoneId: string }): void
|
|
35
35
|
/** `path` is empty for the top-level container and holds the slot index of
|
|
36
36
|
* the group when the divider is inside one. */
|
|
37
37
|
(event: 'resize', payload: { path: number[]; index: number; ratio: number }): void
|
|
38
|
+
(event: 'add-area', direction: DisplaySplitDraft['direction']): void
|
|
38
39
|
(event: 'split-area', zoneId: string): void
|
|
39
|
-
(event: '
|
|
40
|
+
(event: 'extend-area', zoneId: string): void
|
|
41
|
+
(event: 'remove-area', zoneId: string): void
|
|
40
42
|
(event: 'block-dragstart'): void
|
|
41
43
|
(event: 'block-dragend'): void
|
|
44
|
+
/** A divider drag can carry the pointer outside the preview frame the
|
|
45
|
+
* overlay is pinned to, so it needs its own signal to stay visible --
|
|
46
|
+
* hover alone would drop it mid-drag. */
|
|
47
|
+
(event: 'resize-start'): void
|
|
48
|
+
(event: 'resize-end'): void
|
|
42
49
|
}>()
|
|
43
50
|
|
|
44
|
-
const { t } = useI18n()
|
|
45
|
-
|
|
46
51
|
interface Slot {
|
|
47
52
|
zone?: DisplayZoneDraft
|
|
48
53
|
group?: DisplaySplitDraft
|
|
54
|
+
/** Rounded, for the "N%" label only. */
|
|
49
55
|
percent: number
|
|
56
|
+
/** Raw, for `flex-grow`: the live renderer uses the weight itself rather
|
|
57
|
+
* than a rounded percentage, so rendering from the rounded number would let
|
|
58
|
+
* the overlay's boundaries drift a few pixels from the real ones underneath. */
|
|
59
|
+
weight: number
|
|
50
60
|
}
|
|
51
61
|
|
|
52
62
|
const topSplit = computed(() => (props.root.kind === 'SPLIT' ? props.root : undefined))
|
|
@@ -54,12 +64,14 @@ const topDirection = computed(() => topSplit.value?.direction ?? 'ROW')
|
|
|
54
64
|
|
|
55
65
|
const slots = computed<Slot[]>(() => {
|
|
56
66
|
if (!topSplit.value) {
|
|
57
|
-
return props.root.kind === 'ZONE' ? [{ zone: props.root, percent: 100 }] : []
|
|
67
|
+
return props.root.kind === 'ZONE' ? [{ zone: props.root, percent: 100, weight: 1 }] : []
|
|
58
68
|
}
|
|
59
69
|
const percents = weightPercents(topSplit.value.children)
|
|
60
|
-
return topSplit.value.children.map((child, index) =>
|
|
61
|
-
|
|
62
|
-
|
|
70
|
+
return topSplit.value.children.map((child, index) =>
|
|
71
|
+
child.node.kind === 'ZONE'
|
|
72
|
+
? { zone: child.node, percent: percents[index] ?? 100, weight: child.weight }
|
|
73
|
+
: { group: child.node, percent: percents[index] ?? 100, weight: child.weight }
|
|
74
|
+
)
|
|
63
75
|
})
|
|
64
76
|
|
|
65
77
|
/** "Area N" is the render order across the whole screen, so the number a
|
|
@@ -70,10 +82,68 @@ const areaNumbers = computed(() => {
|
|
|
70
82
|
return map
|
|
71
83
|
})
|
|
72
84
|
|
|
85
|
+
/** Which border an area offers a "+" on, and what it does there. The border a
|
|
86
|
+
* direction sits on is always the same regardless of why it is offered, so
|
|
87
|
+
* placement and semantics never need to be chosen separately: ROW lives on
|
|
88
|
+
* the right edge, COLUMN on the bottom edge. */
|
|
89
|
+
interface EdgeAction {
|
|
90
|
+
direction: DisplaySplitDraft['direction']
|
|
91
|
+
kind: 'bootstrap' | 'split' | 'extend'
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** A lone, not-yet-split area offers both directions as the template's first
|
|
95
|
+
* split. Once split, every top-level area can still nest a group across its
|
|
96
|
+
* parent's direction, and only the last one can also extend the top-level
|
|
97
|
+
* container -- that is the only place a new sibling can land. */
|
|
98
|
+
function topEdgeActions(index: number): EdgeAction[] {
|
|
99
|
+
if (!topSplit.value)
|
|
100
|
+
return [
|
|
101
|
+
{ direction: 'ROW', kind: 'bootstrap' },
|
|
102
|
+
{ direction: 'COLUMN', kind: 'bootstrap' }
|
|
103
|
+
]
|
|
104
|
+
const actions: EdgeAction[] = [{ direction: crossDirection(topDirection.value), kind: 'split' }]
|
|
105
|
+
if (
|
|
106
|
+
index === slots.value.length - 1 &&
|
|
107
|
+
topSplit.value.children.length < DISPLAY_CONTAINER_CHILD_LIMIT
|
|
108
|
+
) {
|
|
109
|
+
actions.push({ direction: topDirection.value, kind: 'extend' })
|
|
110
|
+
}
|
|
111
|
+
return actions
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** A nested group has no further level to split into, so its own last area
|
|
115
|
+
* only ever offers to grow the group itself, same as the top level does. */
|
|
116
|
+
function groupEdgeActions(group: DisplaySplitDraft, position: number): EdgeAction[] {
|
|
117
|
+
if (
|
|
118
|
+
position !== group.children.length - 1 ||
|
|
119
|
+
group.children.length >= DISPLAY_CONTAINER_CHILD_LIMIT
|
|
120
|
+
)
|
|
121
|
+
return []
|
|
122
|
+
return [{ direction: group.direction, kind: 'extend' }]
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function onEdgeClick(action: EdgeAction, zoneId: string): void {
|
|
126
|
+
if (action.kind === 'bootstrap') emit('add-area', action.direction)
|
|
127
|
+
else if (action.kind === 'split') emit('split-area', zoneId)
|
|
128
|
+
else emit('extend-area', zoneId)
|
|
129
|
+
}
|
|
130
|
+
|
|
73
131
|
function groupPercents(group: DisplaySplitDraft): number[] {
|
|
74
132
|
return weightPercents(group.children)
|
|
75
133
|
}
|
|
76
134
|
|
|
135
|
+
/** Where a divider sits, as a percent of the container -- from the raw
|
|
136
|
+
* weights rather than the rounded per-child percentages, so it lands on
|
|
137
|
+
* exactly the same boundary the areas' own flex-grow renders, not a
|
|
138
|
+
* fraction of a percent off it. */
|
|
139
|
+
function dividerPercent(children: DisplaySplitDraft['children'], beforeIndex: number): number {
|
|
140
|
+
const total = children.reduce((sum, child) => sum + Math.max(1, child.weight), 0)
|
|
141
|
+
const before = children
|
|
142
|
+
.slice(0, beforeIndex)
|
|
143
|
+
.reduce((sum, child) => sum + Math.max(1, child.weight), 0)
|
|
144
|
+
return total > 0 ? (before / total) * 100 : 0
|
|
145
|
+
}
|
|
146
|
+
|
|
77
147
|
function groupZones(group: DisplaySplitDraft): DisplayZoneDraft[] {
|
|
78
148
|
return group.children
|
|
79
149
|
.map((child) => child.node)
|
|
@@ -86,15 +156,6 @@ const groupSurfaces = ref<Record<number, HTMLElement | undefined>>({})
|
|
|
86
156
|
let dragged: { fromZoneId: string } | undefined
|
|
87
157
|
let resizing: { path: number[]; index: number } | undefined
|
|
88
158
|
|
|
89
|
-
function blockLabel(type: string): string {
|
|
90
|
-
const definition = blockDefinition(type)
|
|
91
|
-
return definition ? t(definition.editor.labelKey) : type
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function blockIcon(type: string): string {
|
|
95
|
-
return blockDefinition(type)?.editor.icon ?? 'widgets'
|
|
96
|
-
}
|
|
97
|
-
|
|
98
159
|
function onBlockDragStart(event: DragEvent, zone: DisplayZoneDraft): void {
|
|
99
160
|
dragged = { fromZoneId: zone.id }
|
|
100
161
|
event.dataTransfer?.setData('application/x-display-block-id', zone.block?.id ?? '')
|
|
@@ -108,16 +169,8 @@ function onBlockDragEnd(): void {
|
|
|
108
169
|
emit('block-dragend')
|
|
109
170
|
}
|
|
110
171
|
|
|
111
|
-
function
|
|
112
|
-
|
|
113
|
-
dropZoneId.value = zone.id
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function onZoneDrop(event: DragEvent, zone: DisplayZoneDraft): void {
|
|
117
|
-
event.preventDefault()
|
|
118
|
-
const newType = event.dataTransfer?.getData('application/x-display-block-type')
|
|
119
|
-
if (newType) emit('drop-block', { zoneId: zone.id })
|
|
120
|
-
else if (dragged && dragged.fromZoneId !== zone.id) {
|
|
172
|
+
function onZoneDrop(zone: DisplayZoneDraft): void {
|
|
173
|
+
if (dragged && dragged.fromZoneId !== zone.id) {
|
|
121
174
|
emit('move-block', { fromZoneId: dragged.fromZoneId, toZoneId: zone.id })
|
|
122
175
|
}
|
|
123
176
|
dropZoneId.value = ''
|
|
@@ -131,25 +184,26 @@ function onZoneDragLeave(zone: DisplayZoneDraft): void {
|
|
|
131
184
|
function startResize(event: PointerEvent, path: number[], index: number): void {
|
|
132
185
|
resizing = { path, index }
|
|
133
186
|
;(event.currentTarget as HTMLElement).setPointerCapture(event.pointerId)
|
|
187
|
+
emit('resize-start')
|
|
134
188
|
}
|
|
135
189
|
|
|
136
190
|
function onResizeMove(event: PointerEvent): void {
|
|
137
191
|
if (!resizing) return
|
|
138
|
-
const group = resizing.path.length
|
|
139
|
-
? slots.value[resizing.path[0]!]?.group
|
|
140
|
-
: undefined
|
|
192
|
+
const group = resizing.path.length ? slots.value[resizing.path[0]!]?.group : undefined
|
|
141
193
|
const container = resizing.path.length ? group : topSplit.value
|
|
142
194
|
const element = resizing.path.length ? groupSurfaces.value[resizing.path[0]!] : surface.value
|
|
143
195
|
if (!container || !element) return
|
|
144
196
|
const bounds = element.getBoundingClientRect()
|
|
145
|
-
const
|
|
146
|
-
const
|
|
147
|
-
const
|
|
197
|
+
const weights = container.children.map((child) => Math.max(1, child.weight))
|
|
198
|
+
const total = weights.reduce((sum, value) => sum + value, 0)
|
|
199
|
+
const before = weights.slice(0, resizing.index).reduce((sum, value) => sum + value, 0)
|
|
200
|
+
const pair = (weights[resizing.index] ?? 0) + (weights[resizing.index + 1] ?? 0)
|
|
148
201
|
if (pair <= 0) return
|
|
149
|
-
const along =
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
202
|
+
const along =
|
|
203
|
+
container.direction === 'ROW'
|
|
204
|
+
? (event.clientX - bounds.left) / Math.max(1, bounds.width)
|
|
205
|
+
: (event.clientY - bounds.top) / Math.max(1, bounds.height)
|
|
206
|
+
const ratio = (along * total - before) / pair
|
|
153
207
|
if (container.children.length > resizing.index + 1) {
|
|
154
208
|
emit('resize', { path: resizing.path, index: resizing.index, ratio })
|
|
155
209
|
}
|
|
@@ -159,13 +213,12 @@ function endResize(event: PointerEvent): void {
|
|
|
159
213
|
if (!resizing) return
|
|
160
214
|
;(event.currentTarget as HTMLElement).releasePointerCapture(event.pointerId)
|
|
161
215
|
resizing = undefined
|
|
216
|
+
emit('resize-end')
|
|
162
217
|
}
|
|
163
218
|
|
|
164
219
|
function setGroupSurface(index: number, element: unknown): void {
|
|
165
220
|
groupSurfaces.value[index] = (element as HTMLElement | null) ?? undefined
|
|
166
221
|
}
|
|
167
|
-
|
|
168
|
-
const areaCount = computed(() => walkZones(props.root).length)
|
|
169
222
|
</script>
|
|
170
223
|
|
|
171
224
|
<template>
|
|
@@ -173,15 +226,21 @@ const areaCount = computed(() => walkZones(props.root).length)
|
|
|
173
226
|
ref="surface"
|
|
174
227
|
class="canvas"
|
|
175
228
|
:class="topDirection === 'COLUMN' ? 'canvas-column' : 'canvas-row'"
|
|
176
|
-
:style="{ '--area-count': areaCount }"
|
|
177
229
|
>
|
|
178
230
|
<template v-for="(slot, index) in slots" :key="slot.zone?.id ?? `group-${index}`">
|
|
179
|
-
<!--
|
|
180
|
-
|
|
231
|
+
<!-- Floats on the boundary rather than sitting in the flex flow: the
|
|
232
|
+
real areas touch with zero gap between them, so a divider that
|
|
233
|
+
consumed its own layout space would sit off-center from that
|
|
234
|
+
boundary by half its own width. -->
|
|
181
235
|
<div
|
|
182
236
|
v-if="index > 0 && topSplit"
|
|
183
237
|
class="canvas-divider"
|
|
184
238
|
:class="topDirection === 'COLUMN' ? 'divider-horizontal' : 'divider-vertical'"
|
|
239
|
+
:style="
|
|
240
|
+
topDirection === 'COLUMN'
|
|
241
|
+
? { top: `${dividerPercent(topSplit.children, index)}%` }
|
|
242
|
+
: { left: `${dividerPercent(topSplit.children, index)}%` }
|
|
243
|
+
"
|
|
185
244
|
role="separator"
|
|
186
245
|
@pointerdown="startResize($event, [], index - 1)"
|
|
187
246
|
@pointermove="onResizeMove"
|
|
@@ -191,78 +250,54 @@ const areaCount = computed(() => walkZones(props.root).length)
|
|
|
191
250
|
<span class="canvas-divider-grip" />
|
|
192
251
|
</div>
|
|
193
252
|
|
|
194
|
-
<!-- A top-level area.
|
|
195
|
-
|
|
253
|
+
<!-- A top-level area. The card that carries the border/padding is a
|
|
254
|
+
child of this, not this element itself: with border-box sizing,
|
|
255
|
+
flex-grow can't actually shrink a flex-basis: 0 item's border and
|
|
256
|
+
padding away, so a chrome-bearing item ends up a few pixels wider
|
|
257
|
+
than its weight says -- keeping this outer sizing box chrome-free
|
|
258
|
+
is what makes the divider land exactly on the true boundary. -->
|
|
259
|
+
<CanvasArea
|
|
196
260
|
v-if="slot.zone"
|
|
197
|
-
|
|
198
|
-
:
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
:
|
|
205
|
-
|
|
206
|
-
|
|
261
|
+
:zone="slot.zone"
|
|
262
|
+
:area-number="areaNumbers.get(slot.zone.id) ?? index + 1"
|
|
263
|
+
:percent="slot.percent"
|
|
264
|
+
:weight="slot.weight"
|
|
265
|
+
:removable="Boolean(topSplit)"
|
|
266
|
+
:edge-actions="topEdgeActions(index)"
|
|
267
|
+
:selected-zone-id="selectedZoneId"
|
|
268
|
+
:selected-block-id="selectedBlockId"
|
|
269
|
+
:scope="scope"
|
|
270
|
+
:dragging="dragging"
|
|
271
|
+
:drop-zone-id="dropZoneId"
|
|
272
|
+
@select-zone="emit('select-zone', $event)"
|
|
273
|
+
@select-block="emit('select-block', $event)"
|
|
274
|
+
@open-module-picker="emit('open-module-picker', $event)"
|
|
275
|
+
@remove-area="emit('remove-area', $event)"
|
|
276
|
+
@edge-click="onEdgeClick($event, slot.zone.id)"
|
|
277
|
+
@block-dragstart="onBlockDragStart($event, slot.zone)"
|
|
278
|
+
@block-dragend="onBlockDragEnd"
|
|
279
|
+
@dragover="dropZoneId = slot.zone.id"
|
|
207
280
|
@dragleave="onZoneDragLeave(slot.zone)"
|
|
208
|
-
@drop="onZoneDrop(
|
|
209
|
-
|
|
210
|
-
<header class="canvas-area-head">
|
|
211
|
-
<span class="canvas-area-name">
|
|
212
|
-
{{ t('order.displaySystem.editor.ui.area', { index: areaNumbers.get(slot.zone.id) ?? index + 1 }) }}
|
|
213
|
-
</span>
|
|
214
|
-
<span class="canvas-area-share">{{ slot.percent }}%</span>
|
|
215
|
-
<!-- Splitting is offered on the area itself: the nested container runs
|
|
216
|
-
across the parent, so there is nothing to choose. -->
|
|
217
|
-
<button
|
|
218
|
-
type="button"
|
|
219
|
-
class="canvas-area-action"
|
|
220
|
-
:title="t('order.displaySystem.editor.ui.splitAreaHint', {
|
|
221
|
-
direction: t(`order.displaySystem.editor.values.${crossDirection(topDirection).toLowerCase()}`)
|
|
222
|
-
})"
|
|
223
|
-
@click.stop="emit('split-area', slot.zone!.id)"
|
|
224
|
-
>
|
|
225
|
-
<FmIcon :name="crossDirection(topDirection) === 'COLUMN' ? 'splitscreen' : 'splitscreen_vertical'" size="sm" />
|
|
226
|
-
</button>
|
|
227
|
-
</header>
|
|
228
|
-
|
|
229
|
-
<div class="canvas-chips">
|
|
230
|
-
<button
|
|
231
|
-
v-if="slot.zone.block"
|
|
232
|
-
type="button"
|
|
233
|
-
class="canvas-chip"
|
|
234
|
-
:class="{
|
|
235
|
-
'canvas-chip-active': slot.zone.block.id === selectedBlockId && scope === 'module',
|
|
236
|
-
'canvas-chip-context': slot.zone.block.id === selectedBlockId && scope === 'area'
|
|
237
|
-
}"
|
|
238
|
-
draggable="true"
|
|
239
|
-
@click.stop="emit('select-block', { zoneId: slot.zone!.id, blockId: slot.zone!.block!.id })"
|
|
240
|
-
@dragstart="onBlockDragStart($event, slot.zone!)"
|
|
241
|
-
@dragend="onBlockDragEnd"
|
|
242
|
-
>
|
|
243
|
-
<FmIcon name="drag_indicator" size="sm" class="canvas-chip-grip" />
|
|
244
|
-
<FmIcon :name="blockIcon(slot.zone.block.type)" size="sm" />
|
|
245
|
-
<span class="canvas-chip-label">{{ blockLabel(slot.zone.block.type) }}</span>
|
|
246
|
-
</button>
|
|
247
|
-
<p v-else class="canvas-area-empty">
|
|
248
|
-
{{ t('order.displaySystem.editor.ui.emptyArea') }}
|
|
249
|
-
</p>
|
|
250
|
-
</div>
|
|
251
|
-
</section>
|
|
252
|
-
|
|
281
|
+
@drop="onZoneDrop(slot.zone)"
|
|
282
|
+
/>
|
|
253
283
|
<!-- A nested container, drawn as areas running across the parent. -->
|
|
254
284
|
<div
|
|
255
285
|
v-else-if="slot.group"
|
|
256
286
|
:ref="(element) => setGroupSurface(index, element)"
|
|
257
287
|
class="canvas-group"
|
|
258
288
|
:class="slot.group.direction === 'COLUMN' ? 'canvas-column' : 'canvas-row'"
|
|
259
|
-
:style="{ flexGrow: slot.
|
|
289
|
+
:style="{ flexGrow: slot.weight, flexBasis: '0' }"
|
|
260
290
|
>
|
|
261
291
|
<template v-for="(zone, position) in groupZones(slot.group)" :key="zone.id">
|
|
262
292
|
<div
|
|
263
293
|
v-if="position > 0"
|
|
264
294
|
class="canvas-divider"
|
|
265
295
|
:class="slot.group.direction === 'COLUMN' ? 'divider-horizontal' : 'divider-vertical'"
|
|
296
|
+
:style="
|
|
297
|
+
slot.group.direction === 'COLUMN'
|
|
298
|
+
? { top: `${dividerPercent(slot.group.children, position)}%` }
|
|
299
|
+
: { left: `${dividerPercent(slot.group.children, position)}%` }
|
|
300
|
+
"
|
|
266
301
|
role="separator"
|
|
267
302
|
@pointerdown="startResize($event, [index], position - 1)"
|
|
268
303
|
@pointermove="onResizeMove"
|
|
@@ -272,58 +307,29 @@ const areaCount = computed(() => walkZones(props.root).length)
|
|
|
272
307
|
<span class="canvas-divider-grip" />
|
|
273
308
|
</div>
|
|
274
309
|
|
|
275
|
-
<
|
|
276
|
-
|
|
277
|
-
:
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
:
|
|
284
|
-
|
|
285
|
-
|
|
310
|
+
<CanvasArea
|
|
311
|
+
:zone="zone"
|
|
312
|
+
:area-number="areaNumbers.get(zone.id) ?? position + 1"
|
|
313
|
+
:percent="groupPercents(slot.group)[position] ?? 100"
|
|
314
|
+
:weight="slot.group.children[position]?.weight ?? 1"
|
|
315
|
+
:removable="true"
|
|
316
|
+
:edge-actions="groupEdgeActions(slot.group, position)"
|
|
317
|
+
:selected-zone-id="selectedZoneId"
|
|
318
|
+
:selected-block-id="selectedBlockId"
|
|
319
|
+
:scope="scope"
|
|
320
|
+
:dragging="dragging"
|
|
321
|
+
:drop-zone-id="dropZoneId"
|
|
322
|
+
@select-zone="emit('select-zone', $event)"
|
|
323
|
+
@select-block="emit('select-block', $event)"
|
|
324
|
+
@open-module-picker="emit('open-module-picker', $event)"
|
|
325
|
+
@remove-area="emit('remove-area', $event)"
|
|
326
|
+
@edge-click="onEdgeClick($event, zone.id)"
|
|
327
|
+
@block-dragstart="onBlockDragStart($event, zone)"
|
|
328
|
+
@block-dragend="onBlockDragEnd"
|
|
329
|
+
@dragover="dropZoneId = zone.id"
|
|
286
330
|
@dragleave="onZoneDragLeave(zone)"
|
|
287
|
-
@drop="onZoneDrop(
|
|
288
|
-
|
|
289
|
-
<header class="canvas-area-head">
|
|
290
|
-
<span class="canvas-area-name">
|
|
291
|
-
{{ t('order.displaySystem.editor.ui.area', { index: areaNumbers.get(zone.id) ?? position + 1 }) }}
|
|
292
|
-
</span>
|
|
293
|
-
<span class="canvas-area-share">{{ groupPercents(slot.group)[position] ?? 100 }}%</span>
|
|
294
|
-
<button
|
|
295
|
-
type="button"
|
|
296
|
-
class="canvas-area-action"
|
|
297
|
-
:title="t('order.displaySystem.editor.ui.mergeGroupHint')"
|
|
298
|
-
@click.stop="emit('merge-group', zone.id)"
|
|
299
|
-
>
|
|
300
|
-
<FmIcon name="close_fullscreen" size="sm" />
|
|
301
|
-
</button>
|
|
302
|
-
</header>
|
|
303
|
-
|
|
304
|
-
<div class="canvas-chips">
|
|
305
|
-
<button
|
|
306
|
-
v-if="zone.block"
|
|
307
|
-
type="button"
|
|
308
|
-
class="canvas-chip"
|
|
309
|
-
:class="{
|
|
310
|
-
'canvas-chip-active': zone.block.id === selectedBlockId && scope === 'module',
|
|
311
|
-
'canvas-chip-context': zone.block.id === selectedBlockId && scope === 'area'
|
|
312
|
-
}"
|
|
313
|
-
draggable="true"
|
|
314
|
-
@click.stop="emit('select-block', { zoneId: zone.id, blockId: zone.block!.id })"
|
|
315
|
-
@dragstart="onBlockDragStart($event, zone)"
|
|
316
|
-
@dragend="onBlockDragEnd"
|
|
317
|
-
>
|
|
318
|
-
<FmIcon name="drag_indicator" size="sm" class="canvas-chip-grip" />
|
|
319
|
-
<FmIcon :name="blockIcon(zone.block.type)" size="sm" />
|
|
320
|
-
<span class="canvas-chip-label">{{ blockLabel(zone.block.type) }}</span>
|
|
321
|
-
</button>
|
|
322
|
-
<p v-else class="canvas-area-empty">
|
|
323
|
-
{{ t('order.displaySystem.editor.ui.emptyArea') }}
|
|
324
|
-
</p>
|
|
325
|
-
</div>
|
|
326
|
-
</section>
|
|
331
|
+
@drop="onZoneDrop(zone)"
|
|
332
|
+
/>
|
|
327
333
|
</template>
|
|
328
334
|
</div>
|
|
329
335
|
</template>
|
|
@@ -332,12 +338,10 @@ const areaCount = computed(() => walkZones(props.root).length)
|
|
|
332
338
|
|
|
333
339
|
<style scoped>
|
|
334
340
|
.canvas {
|
|
341
|
+
position: absolute;
|
|
342
|
+
inset: 0;
|
|
335
343
|
display: flex;
|
|
336
344
|
min-height: 0;
|
|
337
|
-
flex: 1;
|
|
338
|
-
border-radius: 12px;
|
|
339
|
-
background: #f3f4f6;
|
|
340
|
-
padding: 8px;
|
|
341
345
|
}
|
|
342
346
|
.canvas-row {
|
|
343
347
|
flex-direction: row;
|
|
@@ -345,160 +349,31 @@ const areaCount = computed(() => walkZones(props.root).length)
|
|
|
345
349
|
.canvas-column {
|
|
346
350
|
flex-direction: column;
|
|
347
351
|
}
|
|
348
|
-
/* A group is a container, not an area: it gets no border of its own so the
|
|
349
|
-
areas inside it read as siblings of the areas outside. */
|
|
350
352
|
.canvas-group {
|
|
353
|
+
position: relative;
|
|
351
354
|
display: flex;
|
|
352
355
|
min-width: 0;
|
|
353
356
|
min-height: 0;
|
|
354
|
-
gap: 0;
|
|
355
|
-
}
|
|
356
|
-
.canvas-area {
|
|
357
|
-
display: flex;
|
|
358
|
-
min-width: 0;
|
|
359
|
-
min-height: 78px;
|
|
360
|
-
flex-direction: column;
|
|
361
|
-
gap: 8px;
|
|
362
|
-
overflow: hidden;
|
|
363
|
-
border: 1.5px dashed #d1d5db;
|
|
364
|
-
border-radius: 10px;
|
|
365
|
-
padding: 10px;
|
|
366
|
-
background: white;
|
|
367
|
-
text-align: left;
|
|
368
|
-
}
|
|
369
|
-
.canvas-area-nested {
|
|
370
|
-
min-height: 62px;
|
|
371
|
-
}
|
|
372
|
-
.canvas-area:hover {
|
|
373
|
-
border-color: #fdba74;
|
|
374
|
-
}
|
|
375
|
-
/* Editing the area itself: the area carries the emphasis. */
|
|
376
|
-
.canvas-area-active {
|
|
377
|
-
border-style: solid;
|
|
378
|
-
border-color: #f97316;
|
|
379
|
-
background: #fffbf5;
|
|
380
|
-
box-shadow: 0 0 0 3px #f973161f;
|
|
381
|
-
}
|
|
382
|
-
/* Editing a module inside it: the area only says "you are in here". */
|
|
383
|
-
.canvas-area-context {
|
|
384
|
-
border-style: solid;
|
|
385
|
-
border-color: #fed7aa;
|
|
386
|
-
background: #fffdfa;
|
|
387
|
-
}
|
|
388
|
-
.canvas-area-droppable {
|
|
389
|
-
border-color: #f97316;
|
|
390
|
-
background: #fff7ed;
|
|
391
|
-
}
|
|
392
|
-
.canvas-area-drop-over {
|
|
393
|
-
border-style: solid;
|
|
394
|
-
box-shadow: 0 0 0 3px #f9731633;
|
|
395
|
-
}
|
|
396
|
-
.canvas-area-head {
|
|
397
|
-
display: flex;
|
|
398
|
-
align-items: center;
|
|
399
|
-
gap: 8px;
|
|
400
|
-
}
|
|
401
|
-
.canvas-area-name {
|
|
402
|
-
color: #6b7280;
|
|
403
|
-
font-size: 10px;
|
|
404
|
-
font-weight: 700;
|
|
405
|
-
letter-spacing: 0.06em;
|
|
406
|
-
text-transform: uppercase;
|
|
407
|
-
}
|
|
408
|
-
.canvas-area-share {
|
|
409
|
-
margin-left: auto;
|
|
410
|
-
color: #374151;
|
|
411
|
-
font-size: 12px;
|
|
412
|
-
font-variant-numeric: tabular-nums;
|
|
413
|
-
font-weight: 700;
|
|
414
|
-
}
|
|
415
|
-
.canvas-area-action {
|
|
416
|
-
display: grid;
|
|
417
|
-
flex: 0 0 auto;
|
|
418
|
-
place-items: center;
|
|
419
|
-
border-radius: 6px;
|
|
420
|
-
padding: 2px;
|
|
421
|
-
color: #9ca3af;
|
|
422
|
-
}
|
|
423
|
-
.canvas-area-action:hover {
|
|
424
|
-
background: #fff7ed;
|
|
425
|
-
color: #f97316;
|
|
426
|
-
}
|
|
427
|
-
.canvas-chips {
|
|
428
|
-
display: flex;
|
|
429
|
-
min-height: 0;
|
|
430
|
-
flex: 1;
|
|
431
|
-
flex-direction: column;
|
|
432
|
-
gap: 4px;
|
|
433
|
-
}
|
|
434
|
-
.canvas-chip {
|
|
435
|
-
display: flex;
|
|
436
|
-
align-items: center;
|
|
437
|
-
gap: 6px;
|
|
438
|
-
border: 1px solid #e5e7eb;
|
|
439
|
-
border-radius: 8px;
|
|
440
|
-
padding: 6px 8px;
|
|
441
|
-
background: white;
|
|
442
|
-
color: #374151;
|
|
443
|
-
font-size: 12px;
|
|
444
|
-
text-align: left;
|
|
445
|
-
cursor: grab;
|
|
446
|
-
}
|
|
447
|
-
.canvas-chip:hover {
|
|
448
|
-
border-color: #fdba74;
|
|
449
|
-
}
|
|
450
|
-
/* Editing this module: it carries the emphasis, with a solid accent edge that
|
|
451
|
-
the area's own highlight never uses. */
|
|
452
|
-
.canvas-chip-active {
|
|
453
|
-
border-color: #f97316;
|
|
454
|
-
background: #ffffff;
|
|
455
|
-
box-shadow: inset 4px 0 0 #f97316, 0 0 0 3px #f973161f;
|
|
456
|
-
font-weight: 600;
|
|
457
|
-
}
|
|
458
|
-
.canvas-chip-active .canvas-chip-grip {
|
|
459
|
-
color: #f97316;
|
|
460
|
-
}
|
|
461
|
-
/* Selected, but the panel is on the area — keep it findable, not shouting. */
|
|
462
|
-
.canvas-chip-context {
|
|
463
|
-
border-color: #fed7aa;
|
|
464
|
-
background: #fffdfa;
|
|
465
|
-
}
|
|
466
|
-
.canvas-chip:active {
|
|
467
|
-
cursor: grabbing;
|
|
468
|
-
}
|
|
469
|
-
.canvas-chip-grip {
|
|
470
|
-
flex: 0 0 auto;
|
|
471
|
-
color: #9ca3af;
|
|
472
|
-
}
|
|
473
|
-
.canvas-chip-label {
|
|
474
|
-
min-width: 0;
|
|
475
|
-
flex: 1;
|
|
476
|
-
overflow: hidden;
|
|
477
|
-
text-overflow: ellipsis;
|
|
478
|
-
white-space: nowrap;
|
|
479
|
-
}
|
|
480
|
-
/* An empty area is a legitimate editing state, so it should look like an
|
|
481
|
-
invitation rather than a rendering failure. */
|
|
482
|
-
.canvas-area-empty {
|
|
483
|
-
display: grid;
|
|
484
|
-
flex: 1;
|
|
485
|
-
place-items: center;
|
|
486
|
-
border: 1px dashed #d1d5db;
|
|
487
|
-
border-radius: 8px;
|
|
488
|
-
color: #9ca3af;
|
|
489
|
-
font-size: 11px;
|
|
490
357
|
}
|
|
491
358
|
.canvas-divider {
|
|
359
|
+
position: absolute;
|
|
360
|
+
z-index: 2;
|
|
492
361
|
display: grid;
|
|
493
|
-
flex: 0 0 auto;
|
|
494
362
|
place-items: center;
|
|
363
|
+
touch-action: none;
|
|
495
364
|
}
|
|
496
365
|
.divider-vertical {
|
|
366
|
+
top: 0;
|
|
367
|
+
bottom: 0;
|
|
497
368
|
width: 10px;
|
|
369
|
+
transform: translateX(-50%);
|
|
498
370
|
cursor: col-resize;
|
|
499
371
|
}
|
|
500
372
|
.divider-horizontal {
|
|
373
|
+
left: 0;
|
|
374
|
+
right: 0;
|
|
501
375
|
height: 10px;
|
|
376
|
+
transform: translateY(-50%);
|
|
502
377
|
cursor: row-resize;
|
|
503
378
|
}
|
|
504
379
|
.canvas-divider-grip {
|