@raclettejs/core 0.1.10 → 0.1.12

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 (96) hide show
  1. package/CHANGELOG.md +59 -2
  2. package/dist/cli.js +104 -104
  3. package/dist/cli.js.map +7 -0
  4. package/dist/index.js +4 -4
  5. package/dist/index.js.map +7 -0
  6. package/package.json +32 -31
  7. package/services/backend/package.json +35 -45
  8. package/services/backend/src/core/payload/payloadTypes.ts +7 -0
  9. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.get.ts +1 -1
  10. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.getAll.ts +1 -1
  11. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/composition.model.ts +5 -0
  12. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/composition.schema.ts +1 -0
  13. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/composition.service.ts +1 -1
  14. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.get.ts +1 -1
  15. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.getAll.ts +1 -1
  16. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/helpers/payload.ts +6 -2
  17. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.get.ts +1 -1
  18. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.getAll.ts +1 -1
  19. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.get.ts +1 -1
  20. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.getAll.ts +1 -1
  21. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/index.ts +2 -1
  22. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.get.ts +1 -1
  23. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.getAll.ts +6 -2
  24. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/tag.service.ts +22 -1
  25. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.get.ts +1 -1
  26. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.getAll.ts +1 -1
  27. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.remove.ts +3 -0
  28. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/user.service.ts +1 -2
  29. package/services/backend/src/corePlugins/raclette__core/frontend/generated-config.ts +12 -12
  30. package/services/backend/src/domains/index.ts +3 -0
  31. package/services/backend/src/domains/system/collections.service.ts +119 -0
  32. package/services/backend/src/domains/system/index.ts +8 -0
  33. package/services/backend/src/domains/system/routes/index.ts +5 -0
  34. package/services/backend/src/domains/system/routes/route.collection.getAll.ts +61 -0
  35. package/services/backend/src/domains/system/routes/route.collection.remove.ts +93 -0
  36. package/services/backend/src/types/custom-fastify.d.ts +1 -0
  37. package/services/backend/src/utils/request.utils.ts +28 -1
  38. package/services/backend/yarn.lock +431 -1180
  39. package/services/frontend/package.json +29 -40
  40. package/services/frontend/src/core/constants/ApiClient.ts +6 -1
  41. package/services/frontend/src/core/lib/data/dataApi.ts +8 -1
  42. package/services/frontend/src/core/lib/data/fetchDataHandler.ts +58 -27
  43. package/services/frontend/src/core/lib/data/queryApi.ts +29 -8
  44. package/services/frontend/src/core/lib/data/responseTypeHandler.ts +27 -4
  45. package/services/frontend/src/core/lib/data/writeDataHandler.ts +1 -1
  46. package/services/frontend/src/core/lib/dataHelper.ts +8 -1
  47. package/services/frontend/src/core/lib/easteregg.ts +1 -1
  48. package/services/frontend/src/core/lib/eventEmitter.ts +1 -0
  49. package/services/frontend/src/core/lib/eventWhitelist.ts +11 -1
  50. package/services/frontend/src/core/lib/logger.ts +11 -3
  51. package/services/frontend/src/core/lib/userNotifier.ts +17 -17
  52. package/services/frontend/src/core/main.ts +3 -3
  53. package/services/frontend/src/core/setup/plugin-system/api/wrappers.ts +18 -1
  54. package/services/frontend/src/core/setup/plugin-system/registration/index.ts +1 -1
  55. package/services/frontend/src/core/setup/socketClient.ts +2 -2
  56. package/services/frontend/src/core/setup/socketEvents.ts +2 -2
  57. package/services/frontend/src/core/store/reducers/compositions/selectors.ts +2 -2
  58. package/services/frontend/src/core/store/reducers/data/index.ts +1 -1
  59. package/services/frontend/src/core/store/reducers/data/reducers.ts +3 -3
  60. package/services/frontend/src/core/store/reducers/queries/effects.ts +20 -1
  61. package/services/frontend/src/core/store/reducers/queries/index.ts +1 -0
  62. package/services/frontend/src/core/store/reducers/queries/reducers.ts +28 -1
  63. package/services/frontend/src/core/store/reducers/queriesCache/queryCacheHelper.ts +5 -4
  64. package/services/frontend/src/core/store/reducers/queriesCache/reducers.ts +4 -3
  65. package/services/frontend/src/core/store/state.ts +1 -1
  66. package/services/frontend/src/core/types/ApiClient.ts +1 -1
  67. package/services/frontend/src/core/types/DataApi.ts +4 -0
  68. package/services/frontend/src/core/types/Queries.ts +1 -0
  69. package/services/frontend/src/orchestrator/ProductOrchestrator.vue +19 -7
  70. package/services/frontend/src/orchestrator/WelcomeScreen.vue +39 -41
  71. package/services/frontend/src/orchestrator/assets/styles/tailwindStyles.css +3 -0
  72. package/services/frontend/src/orchestrator/assets/styles/themes/dark.ts +2 -2
  73. package/services/frontend/src/orchestrator/assets/styles/vuetifyStyles.scss +0 -1
  74. package/services/frontend/src/orchestrator/components/composition/CompositionOverlay.vue +11 -2
  75. package/services/frontend/src/orchestrator/components/composition/WidgetsLayoutLoader.vue +167 -58
  76. package/services/frontend/src/orchestrator/components/menu/DevIndicator.vue +122 -7
  77. package/services/frontend/src/orchestrator/components/menu/UserMenu.vue +38 -20
  78. package/services/frontend/src/orchestrator/components/snackbar/SnackStack.vue +17 -5
  79. package/services/frontend/src/orchestrator/composables/useCurrentComposition.ts +32 -4
  80. package/services/frontend/src/orchestrator/composables/usePageNavigation.ts +5 -1
  81. package/services/frontend/src/orchestrator/composables/usePluginApi.ts +125 -26
  82. package/services/frontend/src/orchestrator/composables/useRouteState.ts +0 -1
  83. package/services/frontend/src/orchestrator/composables/useVueQueryObservableHelper.ts +2 -0
  84. package/services/frontend/src/orchestrator/composables/useVueWriteOperationHelper.ts +5 -3
  85. package/services/frontend/src/orchestrator/composables/useWidgets/helperFunctions.ts +4 -1
  86. package/services/frontend/src/orchestrator/helpers/loginUserNotifications.ts +1 -1
  87. package/services/frontend/src/orchestrator/helpers/uiHelper.ts +1 -1
  88. package/services/frontend/src/orchestrator/i18n/de-DE.json +2 -1
  89. package/services/frontend/src/orchestrator/i18n/en-EU.json +2 -1
  90. package/services/frontend/src/orchestrator/i18n/sk.json +2 -1
  91. package/services/frontend/src/orchestrator/i18n/tl-TL.json +1 -1
  92. package/services/frontend/src/orchestrator/router/routeParserHelper.ts +4 -1
  93. package/services/frontend/src/orchestrator/router/routeStore.ts +78 -11
  94. package/services/frontend/tsconfig.app.json +1 -0
  95. package/services/frontend/yarn.lock +796 -1872
  96. package/yarn.lock +435 -618
@@ -1,97 +1,206 @@
1
1
  <template>
2
- <div>
3
- <div class="tw:m-0 tw:h-full tw:p-0 tw:max-w-full">
4
- <Transition
5
- enter-active-class="tw:transition-opacity tw:duration-200 tw:ease-in-out"
6
- leave-active-class="tw:transition-opacity tw:duration-200 tw:ease-in-out"
7
- enter-from-class="tw:opacity-0"
8
- leave-to-class="tw:opacity-0"
9
- mode="out-in"
10
- appear
2
+ <div
3
+ class="tw:h-full tw:max-w-full tw:bg-gray-50 tw:dark:bg-[#252525]"
4
+ :class="{
5
+ 'tw:p-4':
6
+ slotLayout !== 'default' &&
7
+ slotLayout !== 'inline' &&
8
+ slotLayout !== 'full',
9
+ }"
10
+ >
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
18
+ >
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' }"
11
24
  >
12
- <v-row :key="gridKey" no-gutters class="tw:max-w-full tw:self-start">
13
- <v-col
14
- v-for="(column, colIndex) in props.widgetsLayout"
15
- :key="colIndex"
16
- :cols="getGridCols(props.widgetsLayout.length)"
25
+ <v-col
26
+ v-for="(column, colIndex) in props.widgetsLayout"
27
+ :key="colIndex"
28
+ :cols="getGridCols(props.widgetsLayout.length)"
29
+ :class="{ 'tw:h-full': slotLayout === 'full' }"
30
+ >
31
+ <v-row
32
+ :no-gutters="slotLayout === 'default' || slotLayout === 'full'"
33
+ :class="{ 'tw:h-full': slotLayout === 'full', ...rowClasses }"
17
34
  >
18
- <v-row no-gutters>
19
- <TransitionGroup
20
- name="grid-item"
21
- tag="div"
22
- class="tw:w-full tw:flex tw:flex-wrap"
23
- appear
35
+ <TransitionGroup
36
+ name="grid-item"
37
+ tag="div"
38
+ class="tw:w-full tw:flex tw:flex-wrap"
39
+ appear
40
+ >
41
+ <v-col
42
+ v-for="(slot, slotIndex) in column"
43
+ :key="`${gridKey}-${colIndex}-${slotIndex}`"
44
+ :cols="getSlotCols(slot)"
45
+ :style="{
46
+ 'transition-delay': `${slotIndex * 50}ms`,
47
+ 'max-height': `${maxWidgetHeight}px`,
48
+ ...(slotLayout === 'default' || slotLayout === 'full'
49
+ ? { padding: '0px' }
50
+ : {}),
51
+ }"
24
52
  >
25
- <v-col
26
- v-for="(slot, slotIndex) in column"
27
- :key="`${gridKey}-${colIndex}-${slotIndex}`"
28
- class="tw:overflow-auto"
29
- :cols="getSlotCols(slot)"
30
- :style="{ 'transition-delay': `${slotIndex * 50}ms` }"
53
+ <div
54
+ :class="widgetContainerClasses"
55
+ class="tw:h-full"
56
+ :style="{ 'max-height': `${maxWidgetHeight}px` }"
31
57
  >
32
58
  <component
33
59
  v-if="slot.widget"
34
60
  :is="getSlotWidget(slot.widget)"
35
61
  :uuid="slot.widget.uuid"
62
+ class="tw:h-full"
63
+ :class="[
64
+ hasSpacing &&
65
+ widgetStates[slot.widget.uuid].config
66
+ ?.allowPaddingOverride
67
+ ? 'tw:p-4'
68
+ : '',
69
+ ]"
36
70
  v-bind="{
37
71
  ...widgetStates[slot.widget.uuid].config,
38
72
  ...widgetStates[slot.widget.uuid].public,
39
- ...(slot?.widget?.props || {}),
40
- // TODO: handle route slot params here as well
73
+ ...routeParams,
74
+ compositionName: currentRouteConfig?.slotValue,
75
+ slotType,
41
76
  }"
42
77
  />
43
- </v-col>
44
- </TransitionGroup>
45
- </v-row>
46
- </v-col>
47
- </v-row>
48
- </Transition>
49
- </div>
78
+ </div>
79
+ </v-col>
80
+ </TransitionGroup>
81
+ </v-row>
82
+ </v-col>
83
+ </v-row>
84
+ </Transition>
50
85
  </div>
51
86
  </template>
52
87
 
53
88
  <script setup lang="ts">
54
- import { watch, ref, h } from "vue"
89
+ import type { Composition } from "@shared/types/core/Composition.types"
90
+ import type { WidgetBase } from "@racletteCore/store/types"
91
+ import { watch, ref, h, computed, type Component } from "vue"
55
92
  import { widgetLoader } from "@racletteOrchestrator/helpers/widgetLoader"
56
93
  import { useDisplay } from "vuetify"
94
+ import { useWindowSize } from "@vueuse/core"
57
95
  import useStateSubscriber from "@racletteOrchestrator/composables/useStateSubscriber"
58
- import type { Composition } from "@shared/types/core/Composition.types"
59
- import type { WidgetBase } from "@racletteCore/store/types"
96
+ import useRouteState from "@racletteOrchestrator/composables/useRouteState"
60
97
 
61
98
  const props = defineProps<{
62
99
  widgetsLayout: Composition["widgetsLayout"]
100
+ slotType: String
101
+ slotLayout: String
63
102
  }>()
64
103
 
104
+ const { compositionSlots } = useRouteState()
105
+
106
+ const currentRouteConfig = computed(() =>
107
+ compositionSlots.value?.find((slot) => slot.slotName === props.slotType),
108
+ )
109
+
110
+ const widgetContainerClasses = computed(() => {
111
+ const baseClasses = "tw:h-auto tw:overflow-auto"
112
+
113
+ switch (props.slotLayout) {
114
+ case "spaced":
115
+ return `${baseClasses} tw:rounded-lg`
116
+ case "card":
117
+ return `${baseClasses} tw:rounded-lg tw:shadow-lg hover:tw:shadow-xl tw:transition-shadow tw:duration-200`
118
+ case "border":
119
+ return `${baseClasses} tw:rounded-lg tw:border tw:border-gray-300 tw:dark:border-gray-700`
120
+ default:
121
+ return baseClasses
122
+ }
123
+ })
124
+
125
+ const hasSpacing = computed(() =>
126
+ ["spaced", "card", "border"].includes(props.slotLayout),
127
+ )
128
+
129
+ const rowClasses = computed(() => {
130
+ return hasSpacing.value ? "tw:gap-2" : ""
131
+ })
132
+
65
133
  const { mdAndDown } = useDisplay()
134
+ const { height: windowHeight } = useWindowSize()
66
135
 
67
136
  const { state: widgetStates } = useStateSubscriber("widgets")
137
+ const routeParams = computed(() => {
138
+ const cleanedParams = {}
139
+ if (currentRouteConfig.value?.slotParams) {
140
+ for (const [key, value] of Object.entries(
141
+ currentRouteConfig.value.slotParams,
142
+ )) {
143
+ if (value && value !== "undefined") {
144
+ cleanedParams[key] = value
145
+ }
146
+ }
147
+ }
148
+
149
+ return cleanedParams
150
+ })
68
151
 
69
152
  // Create a reactive key that changes when widgetsLayout changes
70
153
  const gridKey = ref(0)
71
154
 
155
+ // Calculate available height for widgets
156
+ const availableHeight = computed(() => {
157
+ // this is really just a simplified approach for now
158
+
159
+ // TODO: this needs to be more dynamic
160
+ const appBarHeight = 48 // Vuetify compact app bar height
161
+ const progressBarHeight = 2 // Progress bar height
162
+ const borderAndPadding = 8 // Border and padding buffer
163
+
164
+ const calculated =
165
+ windowHeight.value - appBarHeight - progressBarHeight - borderAndPadding
166
+ return calculated
167
+ })
168
+
169
+ // Calculate max height per widget based on grid layout
170
+ const maxWidgetHeight = computed(() => {
171
+ // Always set max height to the calculated available screen height (with buffer)
172
+ // Later we should determine a max value based on other widgets in each row, so this value will be different across rows and columns
173
+
174
+ return Math.min(availableHeight.value, 2400)
175
+ })
176
+
177
+ const widgetComponentCache = new Map<string, Component>()
178
+
72
179
  const getSlotWidget = (widget: WidgetBase) => {
73
- try {
74
- // Use enhanced widgetLoader that handles loading/error states internally
75
- return widgetLoader(widget)
76
- } catch (error) {
77
- console.error("🚨 Error creating widget component:", error)
78
-
79
- // Enhanced widgetLoader handles errors internally, so this shouldn't happen
80
- // But keep as fallback
81
- return {
82
- name: "ComponentCreationError",
83
- setup() {
84
- return () =>
85
- h(
86
- "div",
87
- {
88
- class: "p-4 text-red-500 border border-red-300 rounded",
89
- },
90
- "Error creating component",
91
- )
92
- },
180
+ const cacheKey = `${widget.pluginKey}-${widget.name}-${widget.uuid}`
181
+
182
+ if (!widgetComponentCache.has(cacheKey)) {
183
+ try {
184
+ const component = widgetLoader(widget)
185
+ widgetComponentCache.set(cacheKey, component)
186
+ } catch (error) {
187
+ console.error("🚨 Error creating widget component:", error)
188
+ // cache error component too
189
+ widgetComponentCache.set(cacheKey, {
190
+ name: "ComponentCreationError",
191
+ setup() {
192
+ return () =>
193
+ h(
194
+ "div",
195
+ { class: "p-4 text-red-500 border border-red-300 rounded" },
196
+ "Error creating component",
197
+ )
198
+ },
199
+ })
93
200
  }
94
201
  }
202
+
203
+ return widgetComponentCache.get(cacheKey)
95
204
  }
96
205
 
97
206
  const getGridCols = (rowLength) => (mdAndDown.value ? 12 : 12 / rowLength)
@@ -6,38 +6,114 @@
6
6
  <v-btn v-bind="props" icon="mdi-dev-to" color="#ffc400" />
7
7
  </div>
8
8
  </template>
9
-
10
- <v-list>
9
+ <v-list
10
+ v-model:selected="dropCollections"
11
+ v-model:opened="openGroups"
12
+ select-strategy="leaf"
13
+ >
11
14
  <v-list-subheader>Development mode</v-list-subheader>
12
-
13
15
  <v-list-item
14
16
  :title="toggleDevIndicatorMessage"
15
17
  prepend-icon="mdi-border-outside"
16
18
  @click="toggleShowDevIndicator"
17
19
  />
20
+ <v-list-group value="Users">
21
+ <template v-slot:activator="{ props }">
22
+ <v-list-item v-bind="props" prepend-icon="mdi-delete-sweep">
23
+ Drop Collections
24
+ </v-list-item>
25
+ </template>
26
+ <div class="tw:relative">
27
+ <v-btn
28
+ v-if="dropCollections.length"
29
+ color="error"
30
+ class="tw:absolute! tw:top-5! tw:right-5! tw:z-9999!"
31
+ @click="showConfirmation = true"
32
+ icon="mdi-delete-circle-outline"
33
+ >
34
+ </v-btn>
35
+
36
+ <v-list-item
37
+ v-for="collection in allCollections"
38
+ :key="collection.name"
39
+ :title="collection.name"
40
+ :subtitle="`Item Count: ${collection.count}`"
41
+ :value="collection.name"
42
+ :color="getColor(collection.name)"
43
+ :base-color="getColor(collection.name)"
44
+ >
45
+ <template v-slot:prepend="{ isSelected, select }">
46
+ <v-list-item-action start>
47
+ <v-checkbox-btn
48
+ :model-value="isSelected"
49
+ @update:model-value="select"
50
+ ></v-checkbox-btn>
51
+ </v-list-item-action>
52
+ </template>
53
+ <template v-slot:append>
54
+ <v-list-item-action
55
+ v-if="isCoreCollection(collection.name)"
56
+ end
57
+ >
58
+ <v-icon color="warning">mdi-alert</v-icon>
59
+ </v-list-item-action>
60
+ </template>
61
+ </v-list-item>
62
+ </div>
63
+ </v-list-group>
18
64
  </v-list>
19
65
  </v-bottom-sheet>
66
+ <v-dialog v-model="showConfirmation" max-width="400">
67
+ <v-card>
68
+ <v-card-title class="tw:text-lg tw:font-semibold tw:text-red-600">
69
+ Delete Database Collections
70
+ </v-card-title>
71
+ <v-card-text>
72
+ Are you sure you want to delete the selected Collections?
73
+ </v-card-text>
74
+ <v-card-actions>
75
+ <v-spacer />
76
+ <v-btn @click="showConfirmation = false" variant="text"> Nope </v-btn>
77
+ <v-btn
78
+ @click="handleDropCollections"
79
+ color="error"
80
+ variant="elevated"
81
+ >
82
+ Aigh
83
+ </v-btn>
84
+ </v-card-actions>
85
+ </v-card>
86
+ </v-dialog>
20
87
  </div>
21
88
  </template>
22
89
 
23
90
  <script setup lang="ts">
24
91
  import { useLocalStorage } from "@vueuse/core"
25
- import { computed, ref, watch } from "vue"
92
+ import { computed, ref, watch, onMounted } from "vue"
93
+ import { $api, $eventbus } from "@racletteCore"
94
+ import { useRouter } from "vue-router"
26
95
 
96
+ const router = useRouter()
97
+ const openGroups = ref([])
27
98
  const showMenu = ref(false)
28
99
  const showDevIndicator = useLocalStorage<boolean>("show-dev-indicator", true)
29
-
100
+ const showConfirmation = ref(false)
101
+ const allCollections = ref([])
30
102
  const toggleShowDevIndicator = () => {
31
103
  showDevIndicator.value = !showDevIndicator.value
32
104
  showMenu.value = false
33
105
  }
34
-
106
+ const dropCollections = ref([])
35
107
  const toggleDevIndicatorMessage = computed(() => {
36
108
  const word = showDevIndicator.value ? "Hide" : "Show"
37
109
 
38
110
  return `${word} dev indication borders`
39
111
  })
40
-
112
+ watch(showMenu, (value) => {
113
+ if (!value) {
114
+ dropCollections.value = []
115
+ }
116
+ })
41
117
  watch(
42
118
  showDevIndicator,
43
119
  (value) => {
@@ -49,6 +125,45 @@ watch(
49
125
  },
50
126
  { immediate: true },
51
127
  )
128
+ onMounted(async () => {
129
+ const res = await $api.axios
130
+ .get("/core/admin/collections/all", {})
131
+ .catch((e) => {
132
+ $eventbus.emit("ui/addToSnackbar", {
133
+ message: e.response.data.message,
134
+ color: "error",
135
+ })
136
+ throw e
137
+ })
138
+ const data = res?.data?.data || []
139
+ allCollections.value = data.sort((a, b) => a.name.localeCompare(b.name))
140
+ })
141
+ const handleDropCollections = async () => {
142
+ if (dropCollections.value.length) {
143
+ const res = await $api.axios
144
+ .delete("/core/admin/collections", {
145
+ data: {
146
+ collections: dropCollections.value,
147
+ },
148
+ })
149
+ .catch((e) => {
150
+ $eventbus.emit("ui/addToSnackbar", {
151
+ message: e.response.data.message,
152
+ color: "error",
153
+ })
154
+ throw e
155
+ })
156
+ router.go()
157
+ }
158
+ }
159
+ const isCoreCollection = (name) => {
160
+ return name.indexOf("raclette__core") > -1
161
+ }
162
+ const getColor = (name) => {
163
+ if (isCoreCollection(name)) {
164
+ return "error"
165
+ }
166
+ }
52
167
  </script>
53
168
 
54
169
  <style lang="scss">
@@ -43,25 +43,42 @@
43
43
  </template>
44
44
  <v-list-item-title>{{ $t("core.toggle_snow") }}</v-list-item-title>
45
45
  </v-list-item>
46
- <v-list-item
47
- v-for="item in pageNavigation.userMenuItems"
48
- :key="item.route"
49
- :disabled="activeRouteName === item.route || !item.route"
50
- :active="activeRouteName === item.route"
51
- :value="item.route || ''"
52
- color="primary"
53
- class="nav-link"
54
- link
55
- @click="navigate(item)"
56
- >
57
- <!-- item prepend icon -->
58
- <template v-if="item.icon" #prepend>
59
- <v-icon :icon="item.icon" />
60
- </template>
61
- <v-list-item-title v-if="item.title">{{
62
- item.title
63
- }}</v-list-item-title>
64
- </v-list-item>
46
+ <template v-for="item in pageNavigation.userMenuItems" :key="item.route">
47
+ <v-list-item
48
+ :key="locale"
49
+ v-if="item.slotType !== 'inline'"
50
+ :disabled="activeRouteName === item.route || !item.route"
51
+ :active="activeRouteName === item.route"
52
+ :value="item.route || ''"
53
+ color="primary"
54
+ class="nav-link"
55
+ link
56
+ @click="navigate(item)"
57
+ >
58
+ <!-- item prepend icon -->
59
+ <template v-if="item.icon" #prepend>
60
+ <v-icon :icon="item.icon" />
61
+ </template>
62
+ <v-list-item-title v-if="item.title">{{
63
+ item.title
64
+ }}</v-list-item-title>
65
+ </v-list-item>
66
+ <v-list-group v-else>
67
+ <template v-slot:activator="{ props }">
68
+ <v-list-item
69
+ v-bind="props"
70
+ :prepend-icon="item.icon"
71
+ :title="item.title"
72
+ ></v-list-item>
73
+ </template>
74
+ <WidgetsLayoutLoader
75
+ v-if="compositions[item.compositionId]"
76
+ :widgets-layout="compositions[item.compositionId].widgetsLayout"
77
+ slot-type="inline"
78
+ :slot-layout="compositions[item.compositionId].slotLayout"
79
+ />
80
+ </v-list-group>
81
+ </template>
65
82
 
66
83
  <v-divider />
67
84
 
@@ -87,7 +104,7 @@ import { awaitTime, debounceHelper } from "@racletteCore/lib/dataHelper"
87
104
  import useRouteState from "@racletteOrchestrator/composables/useRouteState"
88
105
  import { DEFAULT_SLOT_NAME } from "@racletteOrchestrator/router/routeParserHelper"
89
106
  import { useLocalStorage } from "@vueuse/core"
90
-
107
+ import WidgetsLayoutLoader from "../composition/WidgetsLayoutLoader.vue"
91
108
  import {
92
109
  debounceHelper as asteroidsDebounceHelper,
93
110
  eggMe,
@@ -101,6 +118,7 @@ const router = useRouter()
101
118
  const asteroidsDebounce = asteroidsDebounceHelper(3)
102
119
  const debounce = debounceHelper(1000)
103
120
  const { state: user } = useStateSubscriber("user")
121
+ const { state: compositions } = useStateSubscriber("compositions")
104
122
  const { locale, theme } = useUiState()
105
123
  const { getCompositionLinkForSlotName } = useRouteState()
106
124
 
@@ -41,8 +41,18 @@ export type SnackbarMessage = {
41
41
  i18n?: string
42
42
  color?: string
43
43
  timeout?: number
44
+ sender?: string
45
+ }
46
+ export type ScopedSnackbarMessage = {
47
+ sender: string
48
+ data: {
49
+ message?: string
50
+ content?: Record<string, any>
51
+ i18n?: string
52
+ color?: string
53
+ timeout?: number
54
+ }
44
55
  }
45
-
46
56
  const snackbars = ref<(SnackbarMessage & { show: boolean })[]>([])
47
57
 
48
58
  const defaultTimeout = 4000
@@ -54,8 +64,10 @@ const calcMargin = (index: number) => `${index * 60 + 10}px`
54
64
  // the close icon will only be shown if the bar has no timeout
55
65
  const handleSnackbarClick = (index: number) => snackbars.value.splice(index, 1)
56
66
 
57
- const addToSnackBar = (data: SnackbarMessage) => {
58
- const snackbar = { ...data, show: true }
67
+ const addToSnackBar = (data: SnackbarMessage | ScopedSnackbarMessage) => {
68
+ // TODO implement proper handling of sender attribute
69
+ const _data = data?.sender ? data.data : data
70
+ const snackbar = { ..._data, show: true }
59
71
 
60
72
  // Prevent duplicate messages (check last two)
61
73
  const lastMessages = snackbars.value.slice(-2)
@@ -80,7 +92,7 @@ const addToSnackBar = (data: SnackbarMessage) => {
80
92
  }
81
93
  }
82
94
 
83
- onMounted(() => $eventbus.on("ui_addToSnackBar", addToSnackBar))
95
+ onMounted(() => $eventbus.on("ui/addToSnackbar", addToSnackBar))
84
96
 
85
- onUnmounted(() => $eventbus.removeListener("ui_addToSnackBar", addToSnackBar))
97
+ onUnmounted(() => $eventbus.removeListener("ui/addToSnackbar", addToSnackBar))
86
98
  </script>
@@ -4,15 +4,21 @@ import { getCompositionPathById } from "@racletteCore/store/reducers/composition
4
4
  import { computed } from "vue"
5
5
  import { DEFAULT_SLOT_NAME } from "@racletteOrchestrator/router/routeParserHelper"
6
6
 
7
+ // Simple reference cache to prevent unnecessary re-renders
8
+ let cachedCompositionId: string | null = null
9
+ let cachedTitle: string | null = null
10
+ let cachedWidgetsLayout: any = null
11
+
7
12
  export const useCurrentComposition = (key: string = DEFAULT_SLOT_NAME) => {
8
13
  const { state: currentComposableId } = useStateSubscriber(["ui", key])
9
14
  const { state: compositions } = useStateSubscriber("compositions")
10
15
 
11
- const state = computed<Composition | undefined>(
12
- () => compositions.value[currentComposableId.value],
13
- )
16
+ const state = computed<Composition | undefined>(() => {
17
+ return compositions.value[currentComposableId.value]
18
+ })
14
19
 
15
20
  const id = computed(() => state.value?._id)
21
+ const slotLayout = computed(() => state.value?.slotLayout || "default")
16
22
 
17
23
  const localizedPath = computed(() =>
18
24
  id.value ? getCompositionPathById(id.value) : undefined,
@@ -25,7 +31,28 @@ export const useCurrentComposition = (key: string = DEFAULT_SLOT_NAME) => {
25
31
  const pathname = computed(() => state.value?.pathname)
26
32
  const status = computed(() => state.value?.status)
27
33
 
28
- const widgetsLayout = computed(() => state.value?.widgetsLayout)
34
+ // Cached widgetsLayout that only updates when core composition changes
35
+ const widgetsLayout = computed(() => {
36
+ const currentComposition = state.value
37
+ if (!currentComposition || !currentComposition._id) {
38
+ return undefined
39
+ }
40
+
41
+ const compositionId = currentComposition._id
42
+ const currentTitle = currentComposition.title
43
+ const currentWidgetsLayout = currentComposition.widgetsLayout
44
+
45
+ // If composition ID or title changed, it's a different composition - update cache
46
+ if (cachedCompositionId !== compositionId || cachedTitle !== currentTitle) {
47
+ cachedCompositionId = compositionId
48
+ cachedTitle = currentTitle
49
+ cachedWidgetsLayout = currentWidgetsLayout
50
+ return currentWidgetsLayout
51
+ }
52
+
53
+ // Same composition, return cached reference to prevent re-renders
54
+ return cachedWidgetsLayout
55
+ })
29
56
 
30
57
  return {
31
58
  state,
@@ -38,6 +65,7 @@ export const useCurrentComposition = (key: string = DEFAULT_SLOT_NAME) => {
38
65
  pathname,
39
66
  status,
40
67
  widgetsLayout,
68
+ slotLayout,
41
69
  }
42
70
  }
43
71
 
@@ -5,17 +5,20 @@ import {
5
5
  import useInteractionLinks from "@racletteOrchestrator/composables/useInteractionLinks"
6
6
  import { computed } from "vue"
7
7
  import useRouteState from "./useRouteState"
8
+ import useUiState from "@racletteOrchestrator/composables/useUiState"
8
9
 
9
10
  export type NavigationItem = {
10
11
  icon: string
11
12
  route?: string
12
13
  sortOrder: number
14
+ compositionId: string
13
15
  title: string
14
16
  slotType: string
15
17
  navigateTo: () => void
16
18
  }
17
19
 
18
20
  export default () => {
21
+ const { locale } = useUiState()
19
22
  const { state } = useInteractionLinks(["page-navigation", "user-menu"])
20
23
  const { triggerInteractionLink } = useRouteState()
21
24
 
@@ -27,7 +30,7 @@ export default () => {
27
30
  const navigationItems: NavigationItem[] = []
28
31
  const footerItems: NavigationItem[] = []
29
32
  const userMenuItems: NavigationItem[] = []
30
-
33
+ locale.value
31
34
  state.value.forEach((interactionLink) =>
32
35
  interactionLink.triggers
33
36
 
@@ -42,6 +45,7 @@ export default () => {
42
45
  const item: NavigationItem = {
43
46
  icon,
44
47
  sortOrder,
48
+ compositionId: interactionLink.composition,
45
49
  title: getLocalizedName(title),
46
50
  route: getCompositionPathById(interactionLink.composition),
47
51
  slotType: interactionLink.slotType,