@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
|
-
import { computed, ref, watch } from 'vue'
|
|
3
|
-
import type
|
|
2
|
+
import { computed, nextTick, ref, watch } from 'vue'
|
|
3
|
+
import { useDialog, type ISelectItem } from '@feedmepos/ui-library'
|
|
4
4
|
import { useCoreStore, useI18n } from '@feedmepos/mf-common'
|
|
5
5
|
import { MediaPickerModal, type MediaSelection } from '@feedmepos/media-picker'
|
|
6
6
|
import type { BlockEditorControl } from '@entity'
|
|
@@ -18,26 +18,26 @@ import type {
|
|
|
18
18
|
DisplayTemplatePreset
|
|
19
19
|
} from '@/modules/display-system/interface'
|
|
20
20
|
import {
|
|
21
|
-
DISPLAY_CONTAINER_CHILD_LIMIT,
|
|
22
21
|
addArea,
|
|
22
|
+
addAreaBeside,
|
|
23
23
|
applyDividerRatio,
|
|
24
24
|
blockDefinition,
|
|
25
25
|
clearBlock,
|
|
26
|
+
DISPLAY_CONTAINER_CHILD_LIMIT,
|
|
26
27
|
defaultBlock,
|
|
27
28
|
defaultDisplayTemplateDraft,
|
|
28
29
|
displayTemplateToDraft,
|
|
29
|
-
mergeGroup,
|
|
30
30
|
removeArea,
|
|
31
31
|
setBlock,
|
|
32
|
+
setLayoutDirection,
|
|
32
33
|
splitArea,
|
|
33
34
|
swapBlocks,
|
|
34
35
|
walkZones,
|
|
35
|
-
zoneFitsMinimum
|
|
36
|
-
zoneParent
|
|
36
|
+
zoneFitsMinimum
|
|
37
37
|
} from '@/modules/display-system/interface'
|
|
38
38
|
import DisplayPreview from './DisplayPreview.vue'
|
|
39
39
|
import DisplayLayoutCanvas from './DisplayLayoutCanvas.vue'
|
|
40
|
-
import
|
|
40
|
+
import DisplayModulePickerDialog from './DisplayModulePickerDialog.vue'
|
|
41
41
|
import DisplayInspector from './DisplayInspector.vue'
|
|
42
42
|
|
|
43
43
|
const props = defineProps<{
|
|
@@ -58,6 +58,7 @@ const emit = defineEmits<{
|
|
|
58
58
|
|
|
59
59
|
const { t } = useI18n()
|
|
60
60
|
const { currentBusiness, currentCountry, namespace, restaurants } = useCoreStore()
|
|
61
|
+
const Dialog = useDialog()
|
|
61
62
|
|
|
62
63
|
const draft = ref(defaultDisplayTemplateDraft())
|
|
63
64
|
const previewBrandKit = ref<RestaurantDisplayBrandKit | null>()
|
|
@@ -67,8 +68,9 @@ const inspectorScope = ref<'area' | 'module'>('module')
|
|
|
67
68
|
const presetsOpen = ref(false)
|
|
68
69
|
const error = ref('')
|
|
69
70
|
const dragging = ref(false)
|
|
70
|
-
/**
|
|
71
|
-
|
|
71
|
+
/** A divider drag, kept separate from `dragging`: that flag also lights up
|
|
72
|
+
* every area as a drop target, which a resize must not do. */
|
|
73
|
+
const resizingLayout = ref(false)
|
|
72
74
|
const mediaPickerOpen = ref(false)
|
|
73
75
|
const mediaTarget = ref<DisplayMediaTarget>({ kind: 'background' })
|
|
74
76
|
const brokenImages = ref(new Set<string>())
|
|
@@ -115,14 +117,24 @@ const PRESETS: PresetOption[] = [
|
|
|
115
117
|
{ key: 'media', labelKey: 'presetMedia', direction: 'ROW', cells: [100] }
|
|
116
118
|
]
|
|
117
119
|
|
|
120
|
+
/** Mirrors `syncDisplayScale` in display-system-v3's App.vue: the brand
|
|
121
|
+
* footer is a fixed 3.6rem there, and that root font-size is
|
|
122
|
+
* `16 * iframeWidth * scale / baseWidth` (1920 landscape, 1080 portrait). As a
|
|
123
|
+
* share of the FRAME's own height rather than its width, that only depends on
|
|
124
|
+
* scale and orientation, never on how large the frame itself renders here --
|
|
125
|
+
* so the overlay can reserve the same footer strip without asking the iframe
|
|
126
|
+
* for it. */
|
|
127
|
+
function previewFooterHeightPercent(orientation: DisplayTemplateDraft['orientation'], scale: number): number {
|
|
128
|
+
const baseWidth = orientation === 'PORTRAIT' ? 1080 : 1920
|
|
129
|
+
const aspect = orientation === 'PORTRAIT' ? 9 / 16 : 16 / 9
|
|
130
|
+
return ((3.6 * 16 * scale) / baseWidth) * aspect * 100
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const previewFooterPercent = computed(() => draft.value.hideFooter
|
|
134
|
+
? 0
|
|
135
|
+
: previewFooterHeightPercent(draft.value.orientation, draft.value.scale ?? 1))
|
|
136
|
+
|
|
118
137
|
const splitRoot = computed(() => (draft.value.root.kind === 'SPLIT' ? draft.value.root : undefined))
|
|
119
|
-
const canAddArea = computed(
|
|
120
|
-
() => draft.value.root.kind === 'ZONE'
|
|
121
|
-
|| (splitRoot.value?.children.length ?? 0) < DISPLAY_CONTAINER_CHILD_LIMIT
|
|
122
|
-
)
|
|
123
|
-
const canRemoveArea = computed(() => Boolean(
|
|
124
|
-
selectedZone.value && zoneParent(draft.value.root, selectedZone.value.id)
|
|
125
|
-
))
|
|
126
138
|
const isColumn = computed(() => splitRoot.value?.direction === 'COLUMN')
|
|
127
139
|
|
|
128
140
|
const tooSmallFor = computed(() => {
|
|
@@ -213,13 +225,44 @@ function selectBlock(payload: { zoneId: string; blockId: string }): void {
|
|
|
213
225
|
inspectorScope.value = 'module'
|
|
214
226
|
}
|
|
215
227
|
|
|
216
|
-
/** An area holds one module, so adding one replaces what is there
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
228
|
+
/** An area holds one module, so adding one replaces what is there -- silently
|
|
229
|
+
* for an empty area, but only after confirming when it would discard one
|
|
230
|
+
* that is already set up. */
|
|
231
|
+
function addModule(type: string, zoneId: string): void {
|
|
232
|
+
const existing = zones.value.find((zone) => zone.id === zoneId)?.block
|
|
233
|
+
const apply = () => {
|
|
234
|
+
const block = defaultBlock(type)
|
|
235
|
+
if (!setBlock(draft.value, zoneId, block)) return
|
|
236
|
+
selectBlock({ zoneId, blockId: block.id })
|
|
237
|
+
}
|
|
238
|
+
if (!existing) {
|
|
239
|
+
apply()
|
|
240
|
+
return
|
|
241
|
+
}
|
|
242
|
+
Dialog.open({
|
|
243
|
+
title: t('order.displaySystem.editor.ui.replaceModuleConfirmTitle'),
|
|
244
|
+
message: t('order.displaySystem.editor.ui.replaceModuleConfirmMessage', {
|
|
245
|
+
current: t(blockDefinition(existing.type)?.editor.labelKey ?? existing.type)
|
|
246
|
+
}),
|
|
247
|
+
primaryActions: { text: t('order.displaySystem.editor.ui.replaceModuleConfirmAction'), variant: 'destructive', close: true },
|
|
248
|
+
secondaryActions: { text: t('order.displaySystem.common.cancel'), variant: 'tertiary', close: true },
|
|
249
|
+
// Without this it falls back to Tailwind's z-50, behind this editor's own
|
|
250
|
+
// z-[60] overlay -- the same reason MediaPickerModal below needs its :z-index.
|
|
251
|
+
zIndex: 70
|
|
252
|
+
}).onPrimary(apply)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** The full grouped picker, opened right on the area it will fill -- an empty
|
|
256
|
+
* area is never more than one click from getting its module. */
|
|
257
|
+
function openModulePicker(zoneId: string): void {
|
|
258
|
+
Dialog.open({
|
|
259
|
+
title: t('order.displaySystem.editor.ui.pickModuleTitle'),
|
|
260
|
+
contentComponent: DisplayModulePickerDialog,
|
|
261
|
+
zIndex: 70
|
|
262
|
+
}).onPrimary((type: string) => {
|
|
263
|
+
Dialog.close()
|
|
264
|
+
void nextTick(() => addModule(type, zoneId))
|
|
265
|
+
})
|
|
223
266
|
}
|
|
224
267
|
|
|
225
268
|
function onMoveBlock(payload: { fromZoneId: string; toZoneId: string }): void {
|
|
@@ -241,31 +284,44 @@ function onResize(payload: { path: number[]; index: number; ratio: number }): vo
|
|
|
241
284
|
if (container) applyDividerRatio(container.children, payload.index, payload.ratio)
|
|
242
285
|
}
|
|
243
286
|
|
|
244
|
-
function onAddArea(): void {
|
|
245
|
-
// A new area starts empty
|
|
246
|
-
//
|
|
247
|
-
const created = addArea(draft.value,
|
|
248
|
-
if (created) selectZone(created)
|
|
287
|
+
function onAddArea(direction: DisplaySplitDraft['direction']): void {
|
|
288
|
+
// A new area starts empty, so the picker opens on it right away instead of
|
|
289
|
+
// leaving a filler nobody asked for until the merchant notices it.
|
|
290
|
+
const created = addArea(draft.value, direction)
|
|
291
|
+
if (created) { selectZone(created); openModulePicker(created) }
|
|
249
292
|
}
|
|
250
293
|
|
|
251
294
|
function onSplitArea(zoneId: string): void {
|
|
252
295
|
const created = splitArea(draft.value, zoneId)
|
|
253
|
-
if (created) selectZone(created)
|
|
296
|
+
if (created) { selectZone(created); openModulePicker(created) }
|
|
254
297
|
else error.value = t('order.displaySystem.editor.ui.splitLimit')
|
|
255
298
|
}
|
|
256
299
|
|
|
257
|
-
function
|
|
258
|
-
const
|
|
259
|
-
if (
|
|
300
|
+
function onExtendArea(zoneId: string): void {
|
|
301
|
+
const created = addAreaBeside(draft.value, zoneId)
|
|
302
|
+
if (created) { selectZone(created); openModulePicker(created) }
|
|
260
303
|
}
|
|
261
304
|
|
|
262
|
-
function onRemoveArea(): void {
|
|
263
|
-
const zone =
|
|
264
|
-
|
|
305
|
+
function onRemoveArea(zoneId: string): void {
|
|
306
|
+
const zone = zones.value.find((item) => item.id === zoneId)
|
|
307
|
+
const apply = () => {
|
|
308
|
+
if (removeArea(draft.value, zoneId)) selectFirst()
|
|
309
|
+
}
|
|
310
|
+
if (!zone?.block) {
|
|
311
|
+
apply()
|
|
312
|
+
return
|
|
313
|
+
}
|
|
314
|
+
Dialog.open({
|
|
315
|
+
title: t('order.displaySystem.editor.ui.removeAreaConfirmTitle'),
|
|
316
|
+
message: t('order.displaySystem.editor.ui.removeAreaConfirmMessage'),
|
|
317
|
+
primaryActions: { text: t('order.displaySystem.editor.ui.removeArea'), variant: 'destructive', close: true },
|
|
318
|
+
secondaryActions: { text: t('order.displaySystem.common.cancel'), variant: 'tertiary', close: true },
|
|
319
|
+
zIndex: 70
|
|
320
|
+
}).onPrimary(apply)
|
|
265
321
|
}
|
|
266
322
|
|
|
267
323
|
function toggleDirection(): void {
|
|
268
|
-
if (splitRoot.value) splitRoot.value
|
|
324
|
+
if (splitRoot.value) setLayoutDirection(splitRoot.value, isColumn.value ? 'ROW' : 'COLUMN')
|
|
269
325
|
}
|
|
270
326
|
|
|
271
327
|
function removeModule(): void {
|
|
@@ -517,113 +573,112 @@ function save(): void {
|
|
|
517
573
|
/>
|
|
518
574
|
<p v-if="footerWarning" class="rail-scale-warning">{{ footerWarning }}</p>
|
|
519
575
|
</div>
|
|
520
|
-
<DisplayModulePalette
|
|
521
|
-
:can-add="Boolean(selectedZone)"
|
|
522
|
-
@add="addModule($event)"
|
|
523
|
-
@dragstart="(type) => { draggedType = type; dragging = true }"
|
|
524
|
-
@dragend="dragging = false; draggedType = ''"
|
|
525
|
-
/>
|
|
526
576
|
</aside>
|
|
527
577
|
|
|
528
|
-
<main class="editor-stage"
|
|
529
|
-
<
|
|
530
|
-
<div
|
|
531
|
-
class="
|
|
532
|
-
|
|
533
|
-
>
|
|
534
|
-
<DisplayPreview :config="draft" :restaurant="previewRestaurant" />
|
|
578
|
+
<main class="editor-stage">
|
|
579
|
+
<div class="layout-bar">
|
|
580
|
+
<div class="layout-bar-title">
|
|
581
|
+
<h3 class="layout-title">{{ t('order.displaySystem.editor.ui.layoutTitle') }}</h3>
|
|
582
|
+
<p class="layout-hint">{{ t('order.displaySystem.editor.ui.layoutHint') }}</p>
|
|
535
583
|
</div>
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
? t('order.displaySystem.editor.ui.directionRow')
|
|
550
|
-
: t('order.displaySystem.editor.ui.directionColumn')"
|
|
551
|
-
>
|
|
552
|
-
<FmButton
|
|
553
|
-
v-if="splitRoot"
|
|
554
|
-
variant="secondary"
|
|
555
|
-
:prepend-icon="isColumn ? 'view_column' : 'view_agenda'"
|
|
556
|
-
:label="isColumn
|
|
557
|
-
? t('order.displaySystem.editor.ui.directionColumn')
|
|
558
|
-
: t('order.displaySystem.editor.ui.directionRow')"
|
|
559
|
-
@click="toggleDirection"
|
|
560
|
-
/>
|
|
561
|
-
</FmTooltip>
|
|
584
|
+
<div class="layout-actions">
|
|
585
|
+
<FmButton
|
|
586
|
+
variant="secondary"
|
|
587
|
+
prepend-icon="add"
|
|
588
|
+
:label="t('order.displaySystem.editor.ui.addArea')"
|
|
589
|
+
:disabled="Boolean(splitRoot && splitRoot.children.length >= DISPLAY_CONTAINER_CHILD_LIMIT)"
|
|
590
|
+
@click="onAddArea(splitRoot?.direction ?? 'ROW')"
|
|
591
|
+
/>
|
|
592
|
+
<FmTooltip
|
|
593
|
+
:text="isColumn
|
|
594
|
+
? t('order.displaySystem.editor.ui.directionRow')
|
|
595
|
+
: t('order.displaySystem.editor.ui.directionColumn')"
|
|
596
|
+
>
|
|
562
597
|
<FmButton
|
|
598
|
+
v-if="splitRoot"
|
|
563
599
|
variant="secondary"
|
|
564
|
-
prepend-icon="
|
|
565
|
-
:label="
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
<FmTooltip :text="t('order.displaySystem.editor.ui.removeAreaTooltip')">
|
|
570
|
-
<FmButton
|
|
571
|
-
variant="plain"
|
|
572
|
-
prepend-icon="delete"
|
|
573
|
-
:disabled="!canRemoveArea"
|
|
574
|
-
@click="onRemoveArea"
|
|
575
|
-
/>
|
|
576
|
-
</FmTooltip>
|
|
577
|
-
<FmButton
|
|
578
|
-
variant="plain"
|
|
579
|
-
prepend-icon="dashboard_customize"
|
|
580
|
-
:label="t('order.displaySystem.editor.ui.resetLayout')"
|
|
581
|
-
@click="presetsOpen = !presetsOpen"
|
|
600
|
+
:prepend-icon="isColumn ? 'view_column' : 'view_agenda'"
|
|
601
|
+
:label="isColumn
|
|
602
|
+
? t('order.displaySystem.editor.ui.directionColumn')
|
|
603
|
+
: t('order.displaySystem.editor.ui.directionRow')"
|
|
604
|
+
@click="toggleDirection"
|
|
582
605
|
/>
|
|
583
|
-
</
|
|
606
|
+
</FmTooltip>
|
|
607
|
+
<FmButton
|
|
608
|
+
variant="plain"
|
|
609
|
+
prepend-icon="dashboard_customize"
|
|
610
|
+
:label="t('order.displaySystem.editor.ui.resetLayout')"
|
|
611
|
+
@click="presetsOpen = !presetsOpen"
|
|
612
|
+
/>
|
|
584
613
|
</div>
|
|
614
|
+
</div>
|
|
585
615
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
616
|
+
<div v-if="presetsOpen" class="preset-row">
|
|
617
|
+
<button
|
|
618
|
+
v-for="preset in PRESETS"
|
|
619
|
+
:key="preset.key"
|
|
620
|
+
type="button"
|
|
621
|
+
class="preset-card"
|
|
622
|
+
@click="applyPreset(preset.key)"
|
|
623
|
+
>
|
|
624
|
+
<span
|
|
625
|
+
class="preset-diagram"
|
|
626
|
+
:class="preset.direction === 'ROW' ? 'preset-row-dir' : 'preset-column-dir'"
|
|
593
627
|
>
|
|
594
628
|
<span
|
|
595
|
-
|
|
596
|
-
:
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
{{ t(`order.displaySystem.editor.ui.${preset.labelKey}`) }}
|
|
607
|
-
</span>
|
|
608
|
-
</button>
|
|
609
|
-
</div>
|
|
629
|
+
v-for="(cell, index) in preset.cells"
|
|
630
|
+
:key="index"
|
|
631
|
+
class="preset-cell"
|
|
632
|
+
:style="{ flexGrow: cell }"
|
|
633
|
+
/>
|
|
634
|
+
</span>
|
|
635
|
+
<span class="preset-label">
|
|
636
|
+
{{ t(`order.displaySystem.editor.ui.${preset.labelKey}`) }}
|
|
637
|
+
</span>
|
|
638
|
+
</button>
|
|
639
|
+
</div>
|
|
610
640
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
641
|
+
<!-- The editor lives directly on the preview it edits: a translucent
|
|
642
|
+
layer that surfaces on hover or stays visible on touch devices.
|
|
643
|
+
At rest, a mouse user sees what the screen will actually show. It sits beside the clipped
|
|
644
|
+
preview frame rather than inside it, so the frame keeps rounding
|
|
645
|
+
off the real content while the overlay's own border controls
|
|
646
|
+
stay free to straddle an area's edge without being cut off. -->
|
|
647
|
+
<section class="stage-preview">
|
|
648
|
+
<div
|
|
649
|
+
class="preview-box"
|
|
650
|
+
:class="draft.orientation === 'PORTRAIT' ? 'preview-portrait' : 'preview-landscape'"
|
|
651
|
+
>
|
|
652
|
+
<div class="preview-frame">
|
|
653
|
+
<DisplayPreview :config="draft" :restaurant="previewRestaurant" />
|
|
654
|
+
</div>
|
|
655
|
+
<div
|
|
656
|
+
class="preview-edit-overlay"
|
|
657
|
+
:class="{ 'preview-edit-overlay-active': dragging || resizingLayout }"
|
|
658
|
+
:style="{ bottom: `${previewFooterPercent}%` }"
|
|
659
|
+
>
|
|
660
|
+
<DisplayLayoutCanvas
|
|
661
|
+
:root="draft.root"
|
|
662
|
+
:selected-zone-id="selectedZone?.id ?? ''"
|
|
663
|
+
:selected-block-id="selectedBlockId"
|
|
664
|
+
:scope="inspectorScope"
|
|
665
|
+
:dragging="dragging"
|
|
666
|
+
@select-zone="selectZone"
|
|
667
|
+
@select-block="selectBlock"
|
|
668
|
+
@open-module-picker="openModulePicker"
|
|
669
|
+
@move-block="onMoveBlock"
|
|
670
|
+
@resize="onResize"
|
|
671
|
+
@add-area="onAddArea"
|
|
672
|
+
@split-area="onSplitArea"
|
|
673
|
+
@extend-area="onExtendArea"
|
|
674
|
+
@remove-area="onRemoveArea"
|
|
675
|
+
@block-dragstart="dragging = true"
|
|
676
|
+
@block-dragend="dragging = false"
|
|
677
|
+
@resize-start="resizingLayout = true"
|
|
678
|
+
@resize-end="resizingLayout = false"
|
|
679
|
+
/>
|
|
680
|
+
</div>
|
|
681
|
+
</div>
|
|
627
682
|
</section>
|
|
628
683
|
</main>
|
|
629
684
|
|
|
@@ -753,85 +808,70 @@ function save(): void {
|
|
|
753
808
|
overflow: auto;
|
|
754
809
|
padding: 16px;
|
|
755
810
|
}
|
|
756
|
-
/* A portrait preview is narrow, so stacking the canvas under it wastes the
|
|
757
|
-
width beside it. Sit them side by side instead. */
|
|
758
|
-
.editor-stage-portrait {
|
|
759
|
-
flex-direction: row;
|
|
760
|
-
align-items: flex-start;
|
|
761
|
-
gap: 16px;
|
|
762
|
-
}
|
|
763
|
-
.editor-stage-portrait {
|
|
764
|
-
/* Side by side, so the preview is free to use the full stage height. */
|
|
765
|
-
height: 100%;
|
|
766
|
-
}
|
|
767
|
-
.editor-stage-portrait .stage-preview {
|
|
768
|
-
min-height: 0;
|
|
769
|
-
flex: 0 0 auto;
|
|
770
|
-
align-self: stretch;
|
|
771
|
-
}
|
|
772
|
-
.editor-stage-portrait .preview-portrait {
|
|
773
|
-
height: 100%;
|
|
774
|
-
max-height: none;
|
|
775
|
-
}
|
|
776
|
-
.editor-stage-portrait .stage-layout {
|
|
777
|
-
min-width: 0;
|
|
778
|
-
flex: 1;
|
|
779
|
-
}
|
|
780
|
-
/* The column is narrower than in landscape, so the title and the actions no
|
|
781
|
-
longer fit on one line without crushing the hint. */
|
|
782
|
-
.editor-stage-portrait .layout-bar {
|
|
783
|
-
flex-direction: column;
|
|
784
|
-
align-items: stretch;
|
|
785
|
-
gap: 8px;
|
|
786
|
-
}
|
|
787
|
-
.editor-stage-portrait .layout-actions {
|
|
788
|
-
flex-wrap: wrap;
|
|
789
|
-
}
|
|
790
811
|
.stage-preview {
|
|
791
812
|
display: flex;
|
|
792
|
-
|
|
813
|
+
min-height: 0;
|
|
814
|
+
flex: 1;
|
|
815
|
+
align-items: center;
|
|
816
|
+
justify-content: center;
|
|
793
817
|
}
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
818
|
+
/* The sizing box: the frame and the overlay both sit inside this at
|
|
819
|
+
`inset: 0`, so they always share exactly the same bounds without either
|
|
820
|
+
one clipping the other. */
|
|
821
|
+
.preview-box {
|
|
822
|
+
position: relative;
|
|
799
823
|
margin: 0 auto;
|
|
800
824
|
}
|
|
801
825
|
/* Landscape is width-driven; portrait must be height-driven. Giving portrait
|
|
802
826
|
`width: 100%` too would pin the width and let max-height clamp the height,
|
|
803
827
|
which drops the aspect ratio entirely and leaves the frame looking landscape. */
|
|
804
|
-
.preview-landscape {
|
|
828
|
+
.preview-box.preview-landscape {
|
|
805
829
|
width: 100%;
|
|
806
|
-
max-height:
|
|
830
|
+
max-height: 72vh;
|
|
807
831
|
aspect-ratio: 16 / 9;
|
|
808
832
|
}
|
|
809
|
-
.preview-portrait {
|
|
810
|
-
height:
|
|
833
|
+
.preview-box.preview-portrait {
|
|
834
|
+
height: 72vh;
|
|
811
835
|
max-width: 100%;
|
|
812
836
|
aspect-ratio: 9 / 16;
|
|
813
837
|
}
|
|
838
|
+
/* Clips the real preview to its rounded corners. The editor overlay lives
|
|
839
|
+
outside this box (a sibling, not a child) precisely so it is never subject
|
|
840
|
+
to this clipping -- its own border controls need to straddle an area's
|
|
841
|
+
edge without being cut off, which content inside the frame never does. */
|
|
842
|
+
.preview-frame {
|
|
843
|
+
position: absolute;
|
|
844
|
+
inset: 0;
|
|
845
|
+
overflow: hidden;
|
|
846
|
+
border: 1px solid #e5e7eb;
|
|
847
|
+
border-radius: 12px;
|
|
848
|
+
background: #101828;
|
|
849
|
+
}
|
|
814
850
|
.preview-frame :deep(> *) {
|
|
815
851
|
width: 100%;
|
|
816
852
|
height: 100%;
|
|
817
853
|
}
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
.
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
854
|
+
/* The translucent editing layer: invisible until the merchant is actually
|
|
855
|
+
pointing at the preview (or mid-drag), so idle the screen reads as a plain
|
|
856
|
+
preview rather than a diagram. */
|
|
857
|
+
.preview-edit-overlay {
|
|
858
|
+
position: absolute;
|
|
859
|
+
inset: 0;
|
|
860
|
+
opacity: 0;
|
|
861
|
+
pointer-events: none;
|
|
862
|
+
transition: opacity 0.15s ease;
|
|
863
|
+
}
|
|
864
|
+
.preview-box:hover .preview-edit-overlay,
|
|
865
|
+
.preview-box:focus-within .preview-edit-overlay,
|
|
866
|
+
.preview-edit-overlay.preview-edit-overlay-active {
|
|
867
|
+
opacity: 1;
|
|
868
|
+
pointer-events: auto;
|
|
869
|
+
}
|
|
870
|
+
@media (hover: none), (pointer: coarse) {
|
|
871
|
+
.preview-edit-overlay {
|
|
872
|
+
opacity: 1;
|
|
873
|
+
pointer-events: auto;
|
|
874
|
+
}
|
|
835
875
|
}
|
|
836
876
|
.layout-bar {
|
|
837
877
|
display: flex;
|
|
@@ -61,6 +61,7 @@ import { useKioskStore } from '@/stores/kiosk'
|
|
|
61
61
|
import type { MfKioskDevice } from '@/modules/kiosk/interface'
|
|
62
62
|
import KioskOtpDialog from '@/views/kiosk/devices/KioskOtpDialog.vue'
|
|
63
63
|
import { SvcCompanyApiError } from '@/api'
|
|
64
|
+
import { bindDeviceErrorKey } from '@/modules/kiosk/bindDeviceError'
|
|
64
65
|
import { useCoreStore, useI18n } from '@feedmepos/mf-common'
|
|
65
66
|
import { useOrderSettings } from '@/stores/order-setting'
|
|
66
67
|
import { usePaymentStore } from '@/stores/payment'
|
|
@@ -185,10 +186,7 @@ async function requestOtp() {
|
|
|
185
186
|
await KioskStore.requestOtp()
|
|
186
187
|
} catch (e) {
|
|
187
188
|
SnackBar.open({
|
|
188
|
-
title:
|
|
189
|
-
e instanceof SvcCompanyApiError && e.status === 403
|
|
190
|
-
? t('order.bindDevicePermissionDenied')
|
|
191
|
-
: t('order.bindDeviceFailed'),
|
|
189
|
+
title: t(bindDeviceErrorKey(e instanceof SvcCompanyApiError ? e : null)),
|
|
192
190
|
type: 'error'
|
|
193
191
|
})
|
|
194
192
|
return
|
package/vitest.config.ts
CHANGED