@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.
Files changed (31) hide show
  1. package/CHANGELOG.md +22 -9
  2. package/dist/cli.js +1 -1
  3. package/dist/cli.js.map +1 -1
  4. package/package.json +2 -2
  5. package/services/frontend/src/core/lib/scheduleTask.ts +35 -0
  6. package/services/frontend/src/core/store/index.ts +5 -1
  7. package/services/frontend/src/core/store/reducers/compositionSlots/reducers.ts +1 -5
  8. package/services/frontend/src/core/store/reducers/data/reducers.ts +101 -65
  9. package/services/frontend/src/core/store/reducers/metaReducer.ts +23 -0
  10. package/services/frontend/src/core/store/reducers/notifications/reducers.ts +39 -47
  11. package/services/frontend/src/core/store/reducers/queries/effects.ts +55 -2
  12. package/services/frontend/src/core/store/reducers/queries/reducers.ts +60 -62
  13. package/services/frontend/src/core/store/reducers/queriesCache/effects.ts +105 -0
  14. package/services/frontend/src/core/store/reducers/queriesCache/index.ts +2 -1
  15. package/services/frontend/src/core/store/reducers/queriesCache/queryCacheHelper.ts +79 -9
  16. package/services/frontend/src/core/store/reducers/queriesCache/reducers.ts +95 -100
  17. package/services/frontend/src/orchestrator/components/CompositionLoadingState.vue +28 -0
  18. package/services/frontend/src/orchestrator/components/composition/CompositionOverlay.vue +5 -7
  19. package/services/frontend/src/orchestrator/components/composition/WidgetsLayoutLoader.vue +171 -39
  20. package/services/frontend/src/orchestrator/components/dataTable/BaseDataTable.vue +311 -54
  21. package/services/frontend/src/orchestrator/components/dataTable/BaseDataTableConfirmDeleteBtn.vue +27 -0
  22. package/services/frontend/src/orchestrator/components/dataTable/BaseDataTableFilterDrawerShell.vue +72 -0
  23. package/services/frontend/src/orchestrator/components/index.ts +4 -0
  24. package/services/frontend/src/orchestrator/composables/index.ts +1 -0
  25. package/services/frontend/src/orchestrator/composables/useBaseDataTableDeleteConfirm.ts +23 -0
  26. package/services/frontend/src/orchestrator/composables/useWidgetLifecycle.ts +53 -23
  27. package/services/frontend/src/orchestrator/constants/widgetSlotType.ts +4 -0
  28. package/services/frontend/src/orchestrator/i18n/de-DE.json +7 -1
  29. package/services/frontend/src/orchestrator/i18n/en-EU.json +7 -1
  30. package/services/frontend/src/orchestrator/i18n/sk.json +7 -1
  31. 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:p-5 tw:h-full" ref="appCompositionOverlay">
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, useTemplateRef } from "vue"
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
- const appCompositionOverlayRef = useTemplateRef("appCompositionOverlay")
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
- <Transition
12
- enter-active-class="tw:transition-opacity tw:duration-200 tw:ease-in-out"
13
- leave-active-class="tw:transition-opacity tw:duration-200 tw:ease-in-out"
14
- enter-from-class="tw:opacity-0"
15
- leave-to-class="tw:opacity-0"
16
- mode="out-in"
17
- appear
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
- <v-row
20
- :key="gridKey"
21
- :no-gutters="slotLayout === 'default' || slotLayout === 'full'"
22
- class="tw:max-w-full tw:self-start"
23
- :class="{ 'tw:h-full': slotLayout === 'full' }"
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
- return props.widgetsLayout.reduce((sum, column) => sum + column.length, 0)
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(createWidgetLifecycleManager(totalWidgets.value))
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
- // Pass lifecycle hook to the component as a prop
190
- const enhancedProps = {
191
- ...props,
192
- onAllWidgetsReady,
193
- ref: (el: any) => {
194
- if (el) {
195
- // Element is mounted, observe it
196
- nextTick(() => {
197
- // Get the actual DOM element
198
- const element = el.$el || el
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
- // Create new lifecycle manager with updated widget count
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>