@raclettejs/core 0.1.37 → 0.1.38
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 +22 -9
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
- package/services/frontend/src/core/lib/scheduleTask.ts +35 -0
- package/services/frontend/src/core/store/index.ts +5 -1
- package/services/frontend/src/core/store/reducers/compositionSlots/reducers.ts +1 -5
- package/services/frontend/src/core/store/reducers/data/reducers.ts +101 -65
- package/services/frontend/src/core/store/reducers/metaReducer.ts +23 -0
- package/services/frontend/src/core/store/reducers/notifications/reducers.ts +39 -47
- package/services/frontend/src/core/store/reducers/queries/effects.ts +55 -2
- package/services/frontend/src/core/store/reducers/queries/reducers.ts +60 -62
- package/services/frontend/src/core/store/reducers/queriesCache/effects.ts +105 -0
- package/services/frontend/src/core/store/reducers/queriesCache/index.ts +2 -1
- package/services/frontend/src/core/store/reducers/queriesCache/queryCacheHelper.ts +79 -9
- package/services/frontend/src/core/store/reducers/queriesCache/reducers.ts +95 -100
- package/services/frontend/src/orchestrator/components/CompositionLoadingState.vue +28 -0
- package/services/frontend/src/orchestrator/components/composition/CompositionOverlay.vue +5 -7
- package/services/frontend/src/orchestrator/components/composition/WidgetsLayoutLoader.vue +171 -39
- package/services/frontend/src/orchestrator/components/dataTable/BaseDataTable.vue +311 -54
- package/services/frontend/src/orchestrator/components/dataTable/BaseDataTableConfirmDeleteBtn.vue +27 -0
- package/services/frontend/src/orchestrator/components/dataTable/BaseDataTableFilterDrawerShell.vue +72 -0
- package/services/frontend/src/orchestrator/components/index.ts +4 -0
- package/services/frontend/src/orchestrator/composables/index.ts +1 -0
- package/services/frontend/src/orchestrator/composables/useBaseDataTableDeleteConfirm.ts +23 -0
- package/services/frontend/src/orchestrator/composables/useWidgetLifecycle.ts +53 -23
- package/services/frontend/src/orchestrator/constants/widgetSlotType.ts +4 -0
- package/services/frontend/src/orchestrator/i18n/de-DE.json +7 -1
- package/services/frontend/src/orchestrator/i18n/en-EU.json +7 -1
- package/services/frontend/src/orchestrator/i18n/sk.json +7 -1
- package/services/frontend/src/orchestrator/router/routerHooks/afterEach.ts +15 -9
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="tw:flex tw:min-h-[160px] tw:w-full tw:items-center tw:justify-center"
|
|
4
|
+
role="status"
|
|
5
|
+
:aria-label="$t('core.app_loading')"
|
|
6
|
+
>
|
|
7
|
+
<v-progress-circular
|
|
8
|
+
indeterminate
|
|
9
|
+
color="primary"
|
|
10
|
+
:size="size"
|
|
11
|
+
:width="strokeWidth"
|
|
12
|
+
/>
|
|
13
|
+
<span class="tw:sr-only">{{ $t("core.app_loading") }}</span>
|
|
14
|
+
</div>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script setup lang="ts">
|
|
18
|
+
withDefaults(
|
|
19
|
+
defineProps<{
|
|
20
|
+
size?: number | string
|
|
21
|
+
strokeWidth?: number | string
|
|
22
|
+
}>(),
|
|
23
|
+
{
|
|
24
|
+
size: 32,
|
|
25
|
+
strokeWidth: 3,
|
|
26
|
+
},
|
|
27
|
+
)
|
|
28
|
+
</script>
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<DefaultModal v-model="showModal">
|
|
3
|
-
<div class="tw:
|
|
3
|
+
<div class="tw:h-full tw:p-5">
|
|
4
4
|
<WidgetsLayoutLoader
|
|
5
5
|
v-if="widgetsLayout"
|
|
6
|
+
reveal-when-ready
|
|
7
|
+
:loading-size="40"
|
|
6
8
|
:widgets-layout="widgetsLayout"
|
|
7
9
|
:slot-layout="slotLayout"
|
|
8
10
|
slot-type="modal"
|
|
9
|
-
:max-height="availableComponentHeight"
|
|
10
11
|
/>
|
|
11
12
|
</div>
|
|
12
13
|
</DefaultModal>
|
|
@@ -15,13 +16,10 @@
|
|
|
15
16
|
<script setup lang="ts">
|
|
16
17
|
import { DefaultModal, WidgetsLayoutLoader } from "@racletteOrchestrator"
|
|
17
18
|
import useCurrentComposition from "@racletteOrchestrator/composables/useCurrentComposition"
|
|
18
|
-
import { computed
|
|
19
|
+
import { computed } from "vue"
|
|
19
20
|
import useRouteState from "@racletteOrchestrator/composables/useRouteState"
|
|
20
21
|
import { DEFAULT_MODAL_SLOT_NAME } from "@racletteOrchestrator/router/routeParserHelper"
|
|
21
|
-
|
|
22
|
-
const availableComponentHeight = computed(
|
|
23
|
-
() => appCompositionOverlayRef?.value?.offsetHeight || 400,
|
|
24
|
-
)
|
|
22
|
+
|
|
25
23
|
const { widgetsLayout, slotLayout } = useCurrentComposition(
|
|
26
24
|
DEFAULT_MODAL_SLOT_NAME,
|
|
27
25
|
)
|
|
@@ -1,27 +1,37 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
|
-
class="tw:max-w-full tw:min-h-fit"
|
|
3
|
+
class="tw:relative tw:max-w-full tw:min-h-fit"
|
|
4
4
|
:class="{
|
|
5
|
+
'tw:min-h-[200px]': showLoadingOverlay,
|
|
5
6
|
'tw:p-4':
|
|
6
7
|
slotLayout !== 'default' &&
|
|
7
8
|
slotLayout !== 'inline' &&
|
|
8
9
|
slotLayout !== 'full',
|
|
9
10
|
}"
|
|
11
|
+
:aria-busy="showLoadingOverlay"
|
|
10
12
|
>
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
<div
|
|
14
|
+
class="tw:w-full"
|
|
15
|
+
:class="{
|
|
16
|
+
'tw:pointer-events-none tw:absolute tw:inset-0 tw:overflow-hidden tw:opacity-0':
|
|
17
|
+
showLoadingOverlay,
|
|
18
|
+
}"
|
|
19
|
+
:aria-hidden="showLoadingOverlay"
|
|
18
20
|
>
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class="tw:
|
|
23
|
-
|
|
21
|
+
<Transition
|
|
22
|
+
enter-active-class="tw:transition-opacity tw:duration-200 tw:ease-in-out"
|
|
23
|
+
leave-active-class="tw:transition-opacity tw:duration-200 tw:ease-in-out"
|
|
24
|
+
enter-from-class="tw:opacity-0"
|
|
25
|
+
leave-to-class="tw:opacity-0"
|
|
26
|
+
mode="out-in"
|
|
27
|
+
appear
|
|
24
28
|
>
|
|
29
|
+
<v-row
|
|
30
|
+
:key="gridKey"
|
|
31
|
+
:no-gutters="slotLayout === 'default' || slotLayout === 'full'"
|
|
32
|
+
class="tw:max-w-full tw:self-start"
|
|
33
|
+
:class="{ 'tw:h-full': slotLayout === 'full' }"
|
|
34
|
+
>
|
|
25
35
|
<v-col
|
|
26
36
|
v-for="(column, colIndex) in props.widgetsLayout"
|
|
27
37
|
:key="colIndex"
|
|
@@ -75,6 +85,25 @@
|
|
|
75
85
|
</v-row>
|
|
76
86
|
</v-col>
|
|
77
87
|
</v-row>
|
|
88
|
+
</Transition>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<Transition
|
|
92
|
+
enter-active-class="tw:transition-opacity tw:duration-200 tw:ease-in-out"
|
|
93
|
+
leave-active-class="tw:transition-opacity tw:duration-200 tw:ease-in-out"
|
|
94
|
+
enter-from-class="tw:opacity-0"
|
|
95
|
+
leave-to-class="tw:opacity-0"
|
|
96
|
+
>
|
|
97
|
+
<div
|
|
98
|
+
v-if="showLoadingOverlay"
|
|
99
|
+
class="tw:absolute tw:inset-0 tw:z-10 tw:flex tw:min-h-[200px] tw:items-center tw:justify-center"
|
|
100
|
+
:style="{ background: 'rgb(var(--v-theme-surface))' }"
|
|
101
|
+
>
|
|
102
|
+
<CompositionLoadingState
|
|
103
|
+
:size="loadingSize"
|
|
104
|
+
:stroke-width="loadingStrokeWidth"
|
|
105
|
+
/>
|
|
106
|
+
</div>
|
|
78
107
|
</Transition>
|
|
79
108
|
</div>
|
|
80
109
|
</template>
|
|
@@ -87,26 +116,41 @@ import {
|
|
|
87
116
|
ref,
|
|
88
117
|
h,
|
|
89
118
|
computed,
|
|
119
|
+
onMounted,
|
|
90
120
|
onUnmounted,
|
|
91
121
|
nextTick,
|
|
122
|
+
provide,
|
|
123
|
+
toRef,
|
|
92
124
|
type Component,
|
|
125
|
+
type ComponentPublicInstance,
|
|
93
126
|
} from "vue"
|
|
94
127
|
import { widgetLoader } from "@racletteOrchestrator/helpers/widgetLoader"
|
|
128
|
+
import { WIDGET_SLOT_TYPE_KEY } from "@racletteOrchestrator/constants/widgetSlotType"
|
|
95
129
|
import { useDisplay } from "vuetify"
|
|
96
130
|
import useStateSubscriber from "@racletteOrchestrator/composables/useStateSubscriber"
|
|
97
131
|
import useRouteState from "@racletteOrchestrator/composables/useRouteState"
|
|
98
132
|
import { createWidgetLifecycleManager } from "@racletteOrchestrator/composables/useWidgetLifecycle"
|
|
99
133
|
import { isDefined } from "@vueuse/core"
|
|
134
|
+
import CompositionLoadingState from "@racletteOrchestrator/components/CompositionLoadingState.vue"
|
|
100
135
|
|
|
101
136
|
const props = withDefaults(
|
|
102
137
|
defineProps<{
|
|
103
138
|
widgetsLayout: Composition["widgetsLayout"]
|
|
104
139
|
slotType: string
|
|
105
140
|
slotLayout: string
|
|
141
|
+
revealWhenReady?: boolean
|
|
142
|
+
loadingSize?: number | string
|
|
143
|
+
loadingStrokeWidth?: number | string
|
|
106
144
|
}>(),
|
|
107
|
-
{
|
|
145
|
+
{
|
|
146
|
+
revealWhenReady: false,
|
|
147
|
+
loadingSize: 32,
|
|
148
|
+
loadingStrokeWidth: 3,
|
|
149
|
+
},
|
|
108
150
|
)
|
|
109
151
|
|
|
152
|
+
provide(WIDGET_SLOT_TYPE_KEY, toRef(() => props.slotType))
|
|
153
|
+
|
|
110
154
|
const { compositionSlots } = useRouteState()
|
|
111
155
|
|
|
112
156
|
const currentRouteConfig = computed(() =>
|
|
@@ -157,13 +201,84 @@ const routeParams = computed(() => {
|
|
|
157
201
|
// Create a reactive key that changes when widgetsLayout changes
|
|
158
202
|
const gridKey = ref(0)
|
|
159
203
|
|
|
160
|
-
// Calculate total number of widgets
|
|
161
|
-
const totalWidgets = computed(() =>
|
|
162
|
-
|
|
163
|
-
|
|
204
|
+
// Calculate total number of widgets (only slots that actually contain a widget)
|
|
205
|
+
const totalWidgets = computed(() =>
|
|
206
|
+
props.widgetsLayout.reduce(
|
|
207
|
+
(sum, column) => sum + column.filter((slot) => slot.widget).length,
|
|
208
|
+
0,
|
|
209
|
+
),
|
|
210
|
+
)
|
|
164
211
|
|
|
165
212
|
// Widget lifecycle manager
|
|
166
|
-
const lifecycleManager = ref(
|
|
213
|
+
const lifecycleManager = ref(
|
|
214
|
+
createWidgetLifecycleManager(totalWidgets.value),
|
|
215
|
+
)
|
|
216
|
+
const allWidgetsReady = ref(false)
|
|
217
|
+
let stopReadyWatch: (() => void) | null = null
|
|
218
|
+
|
|
219
|
+
const attachLifecycleManager = (widgetCount: number) => {
|
|
220
|
+
stopReadyWatch?.()
|
|
221
|
+
const manager = createWidgetLifecycleManager(widgetCount)
|
|
222
|
+
stopReadyWatch = watch(
|
|
223
|
+
manager.allWidgetsReady,
|
|
224
|
+
(ready) => {
|
|
225
|
+
allWidgetsReady.value = ready
|
|
226
|
+
},
|
|
227
|
+
{ immediate: true },
|
|
228
|
+
)
|
|
229
|
+
lifecycleManager.value = manager
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
attachLifecycleManager(totalWidgets.value)
|
|
233
|
+
|
|
234
|
+
const isContentReady = computed(
|
|
235
|
+
() =>
|
|
236
|
+
!props.revealWhenReady ||
|
|
237
|
+
totalWidgets.value === 0 ||
|
|
238
|
+
allWidgetsReady.value,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
const showLoadingOverlay = computed(
|
|
242
|
+
() => props.revealWhenReady && !isContentReady.value,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
const resolveWidgetElement = (
|
|
246
|
+
target: Element | ComponentPublicInstance | null,
|
|
247
|
+
): HTMLElement | null => {
|
|
248
|
+
if (!target) {
|
|
249
|
+
return null
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (target instanceof HTMLElement) {
|
|
253
|
+
return target
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const root = target.$el
|
|
257
|
+
if (root instanceof HTMLElement) {
|
|
258
|
+
return root
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (root instanceof Text && root.parentElement instanceof HTMLElement) {
|
|
262
|
+
return root.parentElement
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return null
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const isWidgetLoadingPlaceholder = (
|
|
269
|
+
target: Element | ComponentPublicInstance | null,
|
|
270
|
+
): boolean => {
|
|
271
|
+
if (!target || target instanceof HTMLElement) {
|
|
272
|
+
return false
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const componentName =
|
|
276
|
+
target.$.type?.name ?? (target.$options?.name as string | undefined)
|
|
277
|
+
|
|
278
|
+
return (
|
|
279
|
+
componentName === "LoadingWidgetState" || componentName === "ErrorWidgetState"
|
|
280
|
+
)
|
|
281
|
+
}
|
|
167
282
|
|
|
168
283
|
// Widget component cache
|
|
169
284
|
const widgetComponentCache = new Map<string, Component>()
|
|
@@ -182,29 +297,43 @@ const getSlotWidget = (widget: WidgetBase) => {
|
|
|
182
297
|
const { onAllWidgetsReady, onWidgetRendered, onWidgetUnmounted } =
|
|
183
298
|
lifecycleManager.value.createLifecycleHooks(widget.uuid)
|
|
184
299
|
|
|
300
|
+
const widgetRoot = ref<Element | ComponentPublicInstance | null>(null)
|
|
301
|
+
|
|
302
|
+
const reportRendered = () => {
|
|
303
|
+
nextTick(() => {
|
|
304
|
+
const instance = widgetRoot.value
|
|
305
|
+
if (isWidgetLoadingPlaceholder(instance)) {
|
|
306
|
+
return
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const element = resolveWidgetElement(instance)
|
|
310
|
+
if (element) {
|
|
311
|
+
onWidgetRendered(element)
|
|
312
|
+
}
|
|
313
|
+
})
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
onMounted(reportRendered)
|
|
317
|
+
|
|
185
318
|
onUnmounted(() => {
|
|
186
319
|
onWidgetUnmounted()
|
|
187
320
|
})
|
|
188
321
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
if (element instanceof HTMLElement) {
|
|
200
|
-
onWidgetRendered(element)
|
|
322
|
+
return () =>
|
|
323
|
+
h(
|
|
324
|
+
originalComponent,
|
|
325
|
+
{
|
|
326
|
+
...props,
|
|
327
|
+
onAllWidgetsReady,
|
|
328
|
+
ref: (instance: Element | ComponentPublicInstance | null) => {
|
|
329
|
+
widgetRoot.value = instance
|
|
330
|
+
if (instance) {
|
|
331
|
+
reportRendered()
|
|
201
332
|
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
return () => h(originalComponent, enhancedProps, context.slots)
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
context.slots,
|
|
336
|
+
)
|
|
208
337
|
},
|
|
209
338
|
}
|
|
210
339
|
|
|
@@ -243,12 +372,15 @@ watch(
|
|
|
243
372
|
// Clear component cache to force re-wrapping with new lifecycle manager
|
|
244
373
|
widgetComponentCache.clear()
|
|
245
374
|
|
|
246
|
-
|
|
247
|
-
lifecycleManager.value = createWidgetLifecycleManager(totalWidgets.value)
|
|
375
|
+
attachLifecycleManager(totalWidgets.value)
|
|
248
376
|
|
|
249
377
|
// Trigger grid transition
|
|
250
378
|
gridKey.value++
|
|
251
379
|
},
|
|
252
380
|
{ deep: true },
|
|
253
381
|
)
|
|
382
|
+
|
|
383
|
+
onUnmounted(() => {
|
|
384
|
+
stopReadyWatch?.()
|
|
385
|
+
})
|
|
254
386
|
</script>
|