@raclettejs/core 0.1.39 → 0.1.41
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/CHANGELOG.md +65 -1
- package/dist/cli.js +104 -104
- package/dist/cli.js.map +4 -4
- package/dist/index.js.map +2 -2
- package/package.json +2 -2
- package/services/backend/package.json +1 -1
- package/services/frontend/eslint.config.js +31 -9
- package/services/frontend/src/core/setup/socketBootstrap.ts +4 -3
- package/services/frontend/src/core/store/state.ts +2 -7
- package/services/frontend/src/core/store/types/index.ts +2 -0
- package/services/frontend/src/orchestrator/ProductOrchestrator.vue +33 -22
- package/services/frontend/src/orchestrator/components/LoadingWidgetState.vue +7 -3
- package/services/frontend/src/orchestrator/components/composition/WidgetsLayoutLoader.vue +179 -195
- package/services/frontend/src/orchestrator/components/dataExport/DataExporter.vue +79 -24
- package/services/frontend/src/orchestrator/components/dataTable/BaseDataTable.vue +27 -14
- package/services/frontend/src/orchestrator/components/dataTable/BaseDataTableGroupSelectCheckbox.vue +42 -0
- package/services/frontend/src/orchestrator/components/index.ts +2 -0
- package/services/frontend/src/orchestrator/components/menu/ServerStatus.vue +2 -4
- package/services/frontend/src/orchestrator/components/menu/UserMenu.vue +1 -1
- package/services/frontend/src/orchestrator/components/welcomeScreen/AdminWelcomeScreen.vue +1 -1
- package/services/frontend/src/orchestrator/composables/index.ts +1 -0
- package/services/frontend/src/orchestrator/composables/useAppLoadingCoordinator.ts +77 -0
- package/services/frontend/src/orchestrator/composables/usePageNavigation.ts +1 -1
- package/services/frontend/src/orchestrator/composables/useUiState.ts +2 -0
- package/services/frontend/src/orchestrator/composables/useWidgetLifecycle.ts +65 -76
- package/services/frontend/src/orchestrator/composables/useWidgets/helperFunctions.ts +7 -4
- package/services/frontend/src/orchestrator/constants/index.ts +2 -0
- package/services/frontend/src/orchestrator/constants/widgetSlot.ts +7 -0
- package/services/frontend/src/orchestrator/exports.ts +1 -0
- package/services/frontend/src/orchestrator/helpers/staticWidgetRegistry.ts +9 -44
- package/services/frontend/src/orchestrator/helpers/widgetLayoutHelper.ts +37 -0
- package/services/frontend/src/orchestrator/helpers/widgetLoader.ts +0 -1
- package/services/frontend/src/orchestrator/helpers/widgetSlotHelper.ts +20 -0
- package/services/frontend/src/orchestrator/helpers/widgetVisuals.ts +11 -12
- package/services/frontend/src/orchestrator/helpers/wrapWidgetWithLifecycle.ts +32 -0
- package/services/frontend/src/orchestrator/router/routerHooks/afterEach.ts +1 -20
- package/services/frontend/src/orchestrator/types/Widgets.ts +12 -6
- package/src/types.ts +7 -1
|
@@ -26,14 +26,23 @@
|
|
|
26
26
|
<v-card rounded="lg" elevation="8" :style="{ width: '360px' }">
|
|
27
27
|
<!-- Header -->
|
|
28
28
|
<slot name="header" v-bind="headerSlotProps">
|
|
29
|
-
<div
|
|
29
|
+
<div
|
|
30
|
+
class="tw:flex tw:items-center tw:justify-between tw:px-4 tw:pt-4 tw:pb-2"
|
|
31
|
+
>
|
|
30
32
|
<div class="tw:flex tw:items-center tw:gap-2">
|
|
31
33
|
<v-icon icon="mdi-export-variant" size="18" color="primary" />
|
|
32
|
-
<span
|
|
34
|
+
<span
|
|
35
|
+
class="tw:text-sm tw:font-semibold tw:tracking-wide tw:uppercase tw:opacity-70"
|
|
36
|
+
>
|
|
33
37
|
{{ $t("core.dataExporter.title") }}
|
|
34
38
|
</span>
|
|
35
39
|
</div>
|
|
36
|
-
<v-btn
|
|
40
|
+
<v-btn
|
|
41
|
+
icon="mdi-close"
|
|
42
|
+
variant="text"
|
|
43
|
+
size="x-small"
|
|
44
|
+
@click="isOpen = false"
|
|
45
|
+
/>
|
|
37
46
|
</div>
|
|
38
47
|
</slot>
|
|
39
48
|
|
|
@@ -71,7 +80,12 @@
|
|
|
71
80
|
<span class="tw:text-xs tw:opacity-60 tw:uppercase tw:tracking-wide">
|
|
72
81
|
{{ $t("core.dataExporter.scopeLabel") }}
|
|
73
82
|
</span>
|
|
74
|
-
<v-btn-toggle
|
|
83
|
+
<v-btn-toggle
|
|
84
|
+
v-model="selectedScope"
|
|
85
|
+
divided
|
|
86
|
+
density="compact"
|
|
87
|
+
mandatory
|
|
88
|
+
>
|
|
75
89
|
<v-btn value="combined" size="x-small">
|
|
76
90
|
{{ $t("core.dataExporter.scopeCombined") }}
|
|
77
91
|
</v-btn>
|
|
@@ -86,9 +100,18 @@
|
|
|
86
100
|
</div>
|
|
87
101
|
|
|
88
102
|
<!-- No formats warning -->
|
|
89
|
-
<div
|
|
90
|
-
|
|
91
|
-
|
|
103
|
+
<div
|
|
104
|
+
v-if="resolvedFormats.length === 0"
|
|
105
|
+
class="tw:px-4 tw:py-6 tw:text-center"
|
|
106
|
+
>
|
|
107
|
+
<v-icon
|
|
108
|
+
icon="mdi-alert-circle-outline"
|
|
109
|
+
color="warning"
|
|
110
|
+
class="tw:mb-2"
|
|
111
|
+
/>
|
|
112
|
+
<p class="tw:text-sm tw:opacity-60">
|
|
113
|
+
{{ $t("core.dataExporter.noValidFormats") }}
|
|
114
|
+
</p>
|
|
92
115
|
</div>
|
|
93
116
|
|
|
94
117
|
<!-- Preview pane -->
|
|
@@ -98,7 +121,7 @@
|
|
|
98
121
|
<v-btn
|
|
99
122
|
variant="text"
|
|
100
123
|
density="compact"
|
|
101
|
-
class="tw:mb-1.5 tw:w-full tw:justify-start tw:normal-case tw:text-xs tw:opacity-60 hover:
|
|
124
|
+
class="tw:mb-1.5 tw:w-full tw:justify-start tw:normal-case tw:text-xs tw:opacity-60 tw:hover:opacity-90"
|
|
102
125
|
@click="previewOpen = !previewOpen"
|
|
103
126
|
>
|
|
104
127
|
<template #prepend>
|
|
@@ -112,7 +135,13 @@
|
|
|
112
135
|
</slot>
|
|
113
136
|
|
|
114
137
|
<!-- Fixed-height container prevents width reflow when toggling -->
|
|
115
|
-
<div
|
|
138
|
+
<div
|
|
139
|
+
class="tw:overflow-hidden"
|
|
140
|
+
:style="{
|
|
141
|
+
height: previewOpen ? 'auto' : '0px',
|
|
142
|
+
minHeight: previewOpen ? '0px' : undefined,
|
|
143
|
+
}"
|
|
144
|
+
>
|
|
116
145
|
<v-expand-transition>
|
|
117
146
|
<div v-show="previewOpen">
|
|
118
147
|
<slot name="preview-content" v-bind="previewContentSlotProps">
|
|
@@ -124,7 +153,9 @@
|
|
|
124
153
|
density="compact"
|
|
125
154
|
class="tw:mb-2 tw:font-mono tw:text-xs"
|
|
126
155
|
>
|
|
127
|
-
<pre class="tw:whitespace-pre-wrap tw:break-all">{{
|
|
156
|
+
<pre class="tw:whitespace-pre-wrap tw:break-all">{{
|
|
157
|
+
serializeResult.error
|
|
158
|
+
}}</pre>
|
|
128
159
|
</v-alert>
|
|
129
160
|
|
|
130
161
|
<!-- Preview code block -->
|
|
@@ -136,7 +167,9 @@
|
|
|
136
167
|
<div
|
|
137
168
|
class="tw:flex tw:items-center tw:justify-between tw:px-3 tw:py-1.5 tw:bg-black/5 dark:tw:bg-white/5 tw:border-b tw:border-black/10 dark:tw:border-white/10"
|
|
138
169
|
>
|
|
139
|
-
<span
|
|
170
|
+
<span
|
|
171
|
+
class="tw:font-mono tw:text-[10px] tw:uppercase tw:tracking-widest tw:opacity-50"
|
|
172
|
+
>
|
|
140
173
|
{{ activeFormatId }}
|
|
141
174
|
</span>
|
|
142
175
|
<span class="tw:font-mono tw:text-[10px] tw:opacity-40">
|
|
@@ -148,14 +181,19 @@
|
|
|
148
181
|
<!-- Code content -->
|
|
149
182
|
<pre
|
|
150
183
|
class="tw:text-xs tw:font-mono tw:p-3 tw:max-h-52 tw:overflow-auto tw:leading-relaxed tw:bg-black/[0.03] dark:tw:bg-white/[0.03] tw:m-0"
|
|
151
|
-
|
|
184
|
+
>{{ previewLines.text }}</pre
|
|
185
|
+
>
|
|
152
186
|
|
|
153
187
|
<!-- Truncation notice -->
|
|
154
188
|
<div
|
|
155
189
|
v-if="previewLines.truncated"
|
|
156
190
|
class="tw:text-[10px] tw:text-center tw:py-1 tw:opacity-40 tw:bg-black/5 dark:tw:bg-white/5 tw:border-t tw:border-black/10 dark:tw:border-white/10"
|
|
157
191
|
>
|
|
158
|
-
{{
|
|
192
|
+
{{
|
|
193
|
+
$t("core.dataExporter.previewTruncatedAt", {
|
|
194
|
+
count: PREVIEW_LINE_LIMIT,
|
|
195
|
+
})
|
|
196
|
+
}}
|
|
159
197
|
</div>
|
|
160
198
|
</div>
|
|
161
199
|
</slot>
|
|
@@ -169,21 +207,31 @@
|
|
|
169
207
|
<v-btn
|
|
170
208
|
v-if="clipboardSupported"
|
|
171
209
|
:disabled="!serializeResult.ok"
|
|
172
|
-
:color="
|
|
210
|
+
:color="
|
|
211
|
+
copyState === 'success'
|
|
212
|
+
? 'success'
|
|
213
|
+
: copyState === 'error'
|
|
214
|
+
? 'error'
|
|
215
|
+
: 'primary'
|
|
216
|
+
"
|
|
173
217
|
variant="tonal"
|
|
174
218
|
size="small"
|
|
175
219
|
class="tw:flex-1 tw:font-medium"
|
|
176
220
|
:prepend-icon="
|
|
177
|
-
copyState === 'success'
|
|
178
|
-
|
|
179
|
-
|
|
221
|
+
copyState === 'success'
|
|
222
|
+
? 'mdi-check'
|
|
223
|
+
: copyState === 'error'
|
|
224
|
+
? 'mdi-alert-circle'
|
|
225
|
+
: 'mdi-content-copy'
|
|
180
226
|
"
|
|
181
227
|
@click="copyToClipboard"
|
|
182
228
|
>
|
|
183
229
|
{{
|
|
184
|
-
copyState === "success"
|
|
185
|
-
|
|
186
|
-
|
|
230
|
+
copyState === "success"
|
|
231
|
+
? $t("core.dataExporter.copied")
|
|
232
|
+
: copyState === "error"
|
|
233
|
+
? $t("core.dataExporter.copyFailed")
|
|
234
|
+
: $t("core.dataExporter.copy")
|
|
187
235
|
}}
|
|
188
236
|
</v-btn>
|
|
189
237
|
<v-btn
|
|
@@ -340,14 +388,21 @@ const actionsSlotProps = computed(() => ({
|
|
|
340
388
|
|
|
341
389
|
const PREVIEW_LINE_LIMIT = 200
|
|
342
390
|
|
|
343
|
-
const previewLines = computed<{
|
|
344
|
-
|
|
391
|
+
const previewLines = computed<{
|
|
392
|
+
text: string
|
|
393
|
+
lineCount: number
|
|
394
|
+
truncated: boolean
|
|
395
|
+
}>(() => {
|
|
396
|
+
if (!serializeResult.value.ok)
|
|
397
|
+
return { text: "", lineCount: 0, truncated: false }
|
|
345
398
|
const lines = serializeResult.value.value.split("\n")
|
|
346
399
|
const truncated = lines.length > PREVIEW_LINE_LIMIT
|
|
347
400
|
return {
|
|
348
|
-
text: truncated
|
|
401
|
+
text: truncated
|
|
402
|
+
? lines.slice(0, PREVIEW_LINE_LIMIT).join("\n")
|
|
403
|
+
: serializeResult.value.value,
|
|
349
404
|
lineCount: lines.length,
|
|
350
405
|
truncated,
|
|
351
406
|
}
|
|
352
407
|
})
|
|
353
|
-
</script>
|
|
408
|
+
</script>
|
|
@@ -217,7 +217,7 @@
|
|
|
217
217
|
|
|
218
218
|
<!-- Dynamic slots for custom column rendering -->
|
|
219
219
|
<template v-for="slot in customSlots" :key="slot" #[slot]="slotProps">
|
|
220
|
-
<slot :name="slot" v-bind="slotProps" />
|
|
220
|
+
<slot :name="slot" v-bind="slotProps ?? {}" />
|
|
221
221
|
</template>
|
|
222
222
|
|
|
223
223
|
<!-- Actions column template -->
|
|
@@ -249,6 +249,16 @@
|
|
|
249
249
|
<slot name="tfoot" v-bind="slotProps ?? {}" />
|
|
250
250
|
</template>
|
|
251
251
|
|
|
252
|
+
<template v-if="slots['group-header']" #group-header="slotProps">
|
|
253
|
+
<slot
|
|
254
|
+
name="group-header"
|
|
255
|
+
v-bind="{
|
|
256
|
+
...(slotProps ?? {}),
|
|
257
|
+
selectionActive: showSelectColumn,
|
|
258
|
+
}"
|
|
259
|
+
/>
|
|
260
|
+
</template>
|
|
261
|
+
|
|
252
262
|
<!-- Forward remaining v-data-table slots (e.g. bottom, thead). -->
|
|
253
263
|
<template
|
|
254
264
|
v-for="slotName in vDataTablePassthroughSlots"
|
|
@@ -694,20 +704,9 @@ const V_DATA_TABLE_BRIDGED_SLOTS = new Set([
|
|
|
694
704
|
"body.prepend",
|
|
695
705
|
"body.append",
|
|
696
706
|
"tfoot",
|
|
707
|
+
"group-header",
|
|
697
708
|
])
|
|
698
709
|
|
|
699
|
-
/** v-data-table slots supplied by the parent (e.g. bottom), excluding built-in handling. */
|
|
700
|
-
const vDataTablePassthroughSlots = computed(() =>
|
|
701
|
-
Object.keys(slots).filter(
|
|
702
|
-
(name) =>
|
|
703
|
-
!BASE_DATA_TABLE_RESERVED_SLOTS.has(name) &&
|
|
704
|
-
!V_DATA_TABLE_BRIDGED_SLOTS.has(name) &&
|
|
705
|
-
!name.startsWith("item.") &&
|
|
706
|
-
name !== "loader" &&
|
|
707
|
-
name !== "top",
|
|
708
|
-
),
|
|
709
|
-
)
|
|
710
|
-
|
|
711
710
|
/** Hide ⋮ when the menu would be empty (no export toggle, import, or slot content). */
|
|
712
711
|
const showActionsOverflowMenu = computed(
|
|
713
712
|
() =>
|
|
@@ -721,7 +720,8 @@ const showActionsOverflowMenu = computed(
|
|
|
721
720
|
const hasSlotContent = (name: string) => {
|
|
722
721
|
const slot = slots[name]
|
|
723
722
|
if (!slot) return false
|
|
724
|
-
|
|
723
|
+
// Scoped slots may destructure props; never probe-call them without a scope object.
|
|
724
|
+
return slot({}).some((vnode) => {
|
|
725
725
|
if (vnode.type === Comment) return false
|
|
726
726
|
if (typeof vnode.children === "string")
|
|
727
727
|
return vnode.children.trim().length > 0
|
|
@@ -757,6 +757,19 @@ const isExportMode = ref(false)
|
|
|
757
757
|
const showSelectColumn = computed(
|
|
758
758
|
() => props.showSelect || (props.showExport && isExportMode.value),
|
|
759
759
|
)
|
|
760
|
+
|
|
761
|
+
/** v-data-table slots supplied by the parent (e.g. bottom), excluding built-in handling. */
|
|
762
|
+
const vDataTablePassthroughSlots = computed(() =>
|
|
763
|
+
Object.keys(slots).filter(
|
|
764
|
+
(name) =>
|
|
765
|
+
!BASE_DATA_TABLE_RESERVED_SLOTS.has(name) &&
|
|
766
|
+
!V_DATA_TABLE_BRIDGED_SLOTS.has(name) &&
|
|
767
|
+
!name.startsWith("item.") &&
|
|
768
|
+
name !== "loader" &&
|
|
769
|
+
name !== "top",
|
|
770
|
+
),
|
|
771
|
+
)
|
|
772
|
+
|
|
760
773
|
const isActionsMenuOpen = ref(false)
|
|
761
774
|
const isFilterDrawerOpen = ref(false)
|
|
762
775
|
/** Draft values edited in the filter drawer (applied only on Apply). */
|
package/services/frontend/src/orchestrator/components/dataTable/BaseDataTableGroupSelectCheckbox.vue
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Group-row select checkbox for BaseDataTable when using a custom `#group-header` slot.
|
|
4
|
+
*
|
|
5
|
+
* Vuetify does not publicly export `useSelection` / `useGroupBy`, updates can break this
|
|
6
|
+
* TODO: we should find a further solution for this!
|
|
7
|
+
* the built-in group checkbox only appears on the default group header row. This component mirrors
|
|
8
|
+
* VDataTableGroupHeaderRow and must run inside `v-data-table` (inject context).
|
|
9
|
+
*
|
|
10
|
+
*/
|
|
11
|
+
import { computed } from "vue"
|
|
12
|
+
import { useGroupBy } from "vuetify/lib/components/VDataTable/composables/group.js"
|
|
13
|
+
import { useSelection } from "vuetify/lib/components/VDataTable/composables/select.js"
|
|
14
|
+
|
|
15
|
+
const props = defineProps<{
|
|
16
|
+
group: { items?: readonly unknown[] }
|
|
17
|
+
}>()
|
|
18
|
+
|
|
19
|
+
const { isSelected, isSomeSelected, select } = useSelection()
|
|
20
|
+
const { extractRows } = useGroupBy()
|
|
21
|
+
|
|
22
|
+
const selectableRows = computed(() =>
|
|
23
|
+
extractRows([props.group]).filter((row) => row.selectable),
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
const modelValue = computed(
|
|
27
|
+
() => selectableRows.value.length > 0 && isSelected(selectableRows.value),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
const indeterminate = computed(
|
|
31
|
+
() => isSomeSelected(selectableRows.value) && !modelValue.value,
|
|
32
|
+
)
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<template>
|
|
36
|
+
<v-checkbox-btn
|
|
37
|
+
:disabled="selectableRows.length === 0"
|
|
38
|
+
:model-value="modelValue"
|
|
39
|
+
:indeterminate="indeterminate"
|
|
40
|
+
@update:model-value="select(selectableRows, !!$event)"
|
|
41
|
+
/>
|
|
42
|
+
</template>
|
|
@@ -20,6 +20,7 @@ import ImportSelectionDialog from "./dataImport/ImportSelectionDialog.vue"
|
|
|
20
20
|
import ImportSummaryDialog from "./dataImport/ImportSummaryDialog.vue"
|
|
21
21
|
import BaseDataTable from "./dataTable/BaseDataTable.vue"
|
|
22
22
|
import BaseDataTableConfirmDeleteBtn from "./dataTable/BaseDataTableConfirmDeleteBtn.vue"
|
|
23
|
+
import BaseDataTableGroupSelectCheckbox from "./dataTable/BaseDataTableGroupSelectCheckbox.vue"
|
|
23
24
|
import FileUpload from "./input/FileUpload.vue"
|
|
24
25
|
|
|
25
26
|
export {
|
|
@@ -27,6 +28,7 @@ export {
|
|
|
27
28
|
ImportSummaryDialog,
|
|
28
29
|
BaseDataTable,
|
|
29
30
|
BaseDataTableConfirmDeleteBtn,
|
|
31
|
+
BaseDataTableGroupSelectCheckbox,
|
|
30
32
|
FileUpload,
|
|
31
33
|
CompositionLoadingState,
|
|
32
34
|
CompositionOverlay,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<v-tooltip
|
|
3
|
-
content-class="
|
|
3
|
+
content-class="tw:!p-1"
|
|
4
4
|
location="bottom"
|
|
5
5
|
max-width="400px"
|
|
6
6
|
v-model="show"
|
|
@@ -65,9 +65,7 @@ const theme = computed(() =>
|
|
|
65
65
|
vuetifyTheme.global.name.value === "light" ? "dark" : "light",
|
|
66
66
|
)
|
|
67
67
|
|
|
68
|
-
const socketStatus = computed(
|
|
69
|
-
() => backend.value?.socket?.status ?? 2,
|
|
70
|
-
)
|
|
68
|
+
const socketStatus = computed(() => backend.value?.socket?.status ?? 2)
|
|
71
69
|
|
|
72
70
|
const serverStatusLabel = computed(() =>
|
|
73
71
|
i18n.t(`core.socket_status_${socketStatus.value || 0}`),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<v-menu :close-on-content-click="false" v-model="isOpen">
|
|
3
3
|
<template #activator="{ props }">
|
|
4
|
-
<v-btn class="
|
|
4
|
+
<v-btn class="tw:!me-0" v-bind="props">
|
|
5
5
|
<v-avatar density="compact" color="primary" class="tw:mr-2">
|
|
6
6
|
{{ avatar }}
|
|
7
7
|
</v-avatar>
|
|
@@ -4,5 +4,6 @@ export * from "./useCurrentComposition"
|
|
|
4
4
|
export * from "./useWidgets"
|
|
5
5
|
export * from "./useInteractionLinks"
|
|
6
6
|
export * from "./useWidgetLifecycle"
|
|
7
|
+
export * from "./useAppLoadingCoordinator"
|
|
7
8
|
export * from "./useExport"
|
|
8
9
|
export * from "./useBaseDataTableDeleteConfirm"
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { computed, ref, watch, type Ref } from "vue"
|
|
2
|
+
import { scheduleAfterPaint } from "@racletteCore/lib/scheduleTask"
|
|
3
|
+
import useUiState from "./useUiState"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Gates global `ui.loading` until socket bootstrap and page widget slots are ready.
|
|
7
|
+
* Owns page-loader remount session so readiness re-reports stay in sync with loading.
|
|
8
|
+
* Used only from `ProductOrchestrator`.
|
|
9
|
+
*/
|
|
10
|
+
export const useAppLoadingCoordinator = (
|
|
11
|
+
compositionId: Ref<string | undefined | null>,
|
|
12
|
+
) => {
|
|
13
|
+
const { loading, bootstrapComplete } = useUiState()
|
|
14
|
+
const pageWidgetsReady = ref(false)
|
|
15
|
+
const pageLoaderSession = ref(0)
|
|
16
|
+
|
|
17
|
+
const armPageLoadingGate = () => {
|
|
18
|
+
pageLoaderSession.value++
|
|
19
|
+
pageWidgetsReady.value = false
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const pageLoaderKey = computed(
|
|
23
|
+
() => `${compositionId.value ?? "none"}-s${pageLoaderSession.value}`,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
const tryClearLoading = () => {
|
|
27
|
+
if (!loading.value) {
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
if (!bootstrapComplete.value || !pageWidgetsReady.value) {
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
scheduleAfterPaint(() => {
|
|
35
|
+
if (loading.value && bootstrapComplete.value && pageWidgetsReady.value) {
|
|
36
|
+
loading.value = false
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const onPageWidgetsReady = (ready: boolean) => {
|
|
42
|
+
pageWidgetsReady.value = ready
|
|
43
|
+
tryClearLoading()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Welcome screen, 404, or layouts with nothing to wait for. */
|
|
47
|
+
const markPageReadyWithoutWidgets = () => {
|
|
48
|
+
onPageWidgetsReady(true)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
watch(
|
|
52
|
+
bootstrapComplete,
|
|
53
|
+
(complete, wasComplete) => {
|
|
54
|
+
if (!complete) {
|
|
55
|
+
if (wasComplete) {
|
|
56
|
+
pageLoaderSession.value++
|
|
57
|
+
}
|
|
58
|
+
pageWidgetsReady.value = false
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
tryClearLoading()
|
|
62
|
+
},
|
|
63
|
+
{ immediate: true },
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
watch(loading, (isLoading, wasLoading) => {
|
|
67
|
+
if (isLoading && wasLoading === false) {
|
|
68
|
+
armPageLoadingGate()
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
pageLoaderKey,
|
|
74
|
+
onPageWidgetsReady,
|
|
75
|
+
markPageReadyWithoutWidgets,
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -41,7 +41,7 @@ export default () => {
|
|
|
41
41
|
interactionLink.triggers
|
|
42
42
|
|
|
43
43
|
.sort(
|
|
44
|
-
(a, b) => (
|
|
44
|
+
(a, b) => (b.settings.sortOrder ?? 0) - (a.settings.sortOrder ?? 0),
|
|
45
45
|
)
|
|
46
46
|
.forEach((trigger) => {
|
|
47
47
|
if (trigger.settings) {
|
|
@@ -11,6 +11,7 @@ export default () => {
|
|
|
11
11
|
const theme = createUiStateBinding<"light" | "dark">("theme")
|
|
12
12
|
const mobile = createUiStateBinding<boolean>("mobile")
|
|
13
13
|
const loading = createUiStateBinding<boolean>("loading")
|
|
14
|
+
const bootstrapComplete = createUiStateBinding<boolean>("bootstrapComplete")
|
|
14
15
|
const landingPage = createUiStateBinding<string>("landingPage")
|
|
15
16
|
const availableInterfaces = createUiStateBinding<
|
|
16
17
|
UIState["availableInterfaces"]
|
|
@@ -22,6 +23,7 @@ export default () => {
|
|
|
22
23
|
theme,
|
|
23
24
|
mobile,
|
|
24
25
|
loading,
|
|
26
|
+
bootstrapComplete,
|
|
25
27
|
landingPage,
|
|
26
28
|
availableInterfaces,
|
|
27
29
|
}
|