@raclettejs/core 0.1.11 → 0.1.13

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 (94) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/cli.js +97 -97
  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 +4 -3
  7. package/services/backend/package.json +1 -0
  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 +11 -2
  39. package/services/frontend/package.json +1 -0
  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 +40 -42
  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/components/composition/CompositionOverlay.vue +11 -2
  74. package/services/frontend/src/orchestrator/components/composition/WidgetsLayoutLoader.vue +167 -58
  75. package/services/frontend/src/orchestrator/components/menu/DevIndicator.vue +122 -7
  76. package/services/frontend/src/orchestrator/components/menu/UserMenu.vue +38 -20
  77. package/services/frontend/src/orchestrator/components/snackbar/SnackStack.vue +17 -5
  78. package/services/frontend/src/orchestrator/composables/useCurrentComposition.ts +32 -4
  79. package/services/frontend/src/orchestrator/composables/usePageNavigation.ts +5 -1
  80. package/services/frontend/src/orchestrator/composables/usePluginApi.ts +113 -63
  81. package/services/frontend/src/orchestrator/composables/useRouteState.ts +0 -1
  82. package/services/frontend/src/orchestrator/composables/useVueQueryObservableHelper.ts +2 -0
  83. package/services/frontend/src/orchestrator/composables/useVueWriteOperationHelper.ts +5 -3
  84. package/services/frontend/src/orchestrator/composables/useWidgets/helperFunctions.ts +4 -1
  85. package/services/frontend/src/orchestrator/helpers/loginUserNotifications.ts +1 -1
  86. package/services/frontend/src/orchestrator/helpers/uiHelper.ts +1 -1
  87. package/services/frontend/src/orchestrator/i18n/de-DE.json +1 -0
  88. package/services/frontend/src/orchestrator/i18n/en-EU.json +1 -0
  89. package/services/frontend/src/orchestrator/i18n/sk.json +1 -0
  90. package/services/frontend/src/orchestrator/router/routeParserHelper.ts +4 -1
  91. package/services/frontend/src/orchestrator/router/routeStore.ts +78 -11
  92. package/services/frontend/tsconfig.app.json +1 -0
  93. package/services/frontend/yarn.lock +5 -0
  94. package/yarn.lock +4 -4
@@ -5,18 +5,42 @@ import { useVueWriteOperationHelper } from "@racletteOrchestrator/composables/us
5
5
  import { useVueStateObservableHelper } from "@racletteOrchestrator/composables/useVueStateObservableHelper"
6
6
  import { getCurrentInstance, onUnmounted } from "vue"
7
7
  import { clone } from "ramda"
8
- import { $store } from "@racletteCore"
9
- import type { actionId } from "@racletteCore"
10
- import type { PluginApi, PluginApiParams } from "@racletteOrchestrator"
8
+ import { $store, createStableHash } from "@racletteCore"
9
+ import type { pluginKey } from "@racletteCore"
10
+ import {
11
+ useRouteState,
12
+ type PluginApi,
13
+ type PluginApiParams,
14
+ } from "@racletteOrchestrator"
11
15
  import { useNamespacedI18n } from "@racletteOrchestrator/composables/useNamespacedI18n"
12
- import { fixDataTypesFromObject } from "@racletteCore/lib/dataHelper"
16
+ import { getCompositionByPath } from "@racletteCore/store/reducers/compositions/selectors"
17
+
18
+ //TODO move this into a $store api module
19
+ const findWidgetUUIDs = (obj: any): string[] => {
20
+ const uuids: string[] = []
21
+
22
+ const recurse = (value: any): void => {
23
+ if (Array.isArray(value)) {
24
+ value.forEach(recurse)
25
+ } else if (value && typeof value === "object") {
26
+ if (value.widget?.uuid) {
27
+ uuids.push(value.widget.uuid)
28
+ }
29
+ Object.values(value).forEach(recurse)
30
+ }
31
+ }
32
+
33
+ recurse(obj.widgetsLayout)
34
+ return uuids
35
+ }
13
36
 
14
37
  export const usePluginApi = (
15
- params: actionId | PluginApiParams = {},
38
+ params: pluginKey | PluginApiParams = {},
16
39
  ): PluginApi => {
17
- const subscribers$ = {}
18
40
  const instance = getCurrentInstance()
19
41
  const getState = useVueStateObservableHelper()
42
+
43
+ const routeState = useRouteState()
20
44
  let pluginKeyOverride: string | undefined
21
45
  let actionIdOverride: string | undefined
22
46
 
@@ -80,36 +104,66 @@ export const usePluginApi = (
80
104
  }
81
105
  $instanceApi.$i18n = useNamespacedI18n(pluginKeyOverride ?? pluginKey)
82
106
  if (!pluginKeyOverride) {
107
+ const getSiblingWidgetStates = (compositionId: string = "byRoute") => {
108
+ const currentSlotType = instance.props?.slotType
109
+ let siblingWidgetIds = []
110
+ const activeCompositionPaths = []
111
+
112
+ if (compositionId === "byRoute") {
113
+ const { compositionSlots } = routeState
114
+ compositionSlots.value.forEach((slot) => {
115
+ if (currentSlotType) {
116
+ if (slot.slotName === currentSlotType) {
117
+ activeCompositionPaths.push(slot.slotValue)
118
+ }
119
+ } else {
120
+ activeCompositionPaths.push(slot.slotValue)
121
+ }
122
+ })
123
+ activeCompositionPaths?.forEach((path) => {
124
+ const composition = getCompositionByPath(path)
125
+ siblingWidgetIds = siblingWidgetIds.concat(
126
+ findWidgetUUIDs(composition),
127
+ )
128
+ })
129
+ } else {
130
+ // TODO implement widget retrieval from state via compositionId
131
+ console.log("not implemented yet")
132
+ }
133
+
134
+ if (!siblingWidgetIds.length) {
135
+ console.error("There is no sibling widgets")
136
+ return {}
137
+ } else {
138
+ const getResult = () => {
139
+ const { state } = getState(["widgets", siblingWidgetIds])
140
+ return state
141
+ }
142
+ return getResult()
143
+ }
144
+ }
145
+ const getWidgetId = () => {
146
+ return instance?.props?.widgetId || instance?.props?.uuid || ""
147
+ }
83
148
  $instanceApi.$store = {
149
+ hasChanges: $store.hasChanges,
150
+ getSiblingWidgetStates,
84
151
  /* Store getter */
85
- getWidgetState: (widgetId: string = instance?.props?.uuid || "") => {
152
+ getWidgetState: (widgetId: string = getWidgetId()) => {
86
153
  if (!widgetId.length) {
87
154
  console.error("No widgetId was provided")
88
155
  return {}
89
156
  } else {
90
- let lastSubscriber$ = subscribers$["$store_getWidgetState"]
91
157
  const getResult = () => {
92
- const { state, observable$ } = getState(["widgets", widgetId])
93
- if (lastSubscriber$) {
94
- lastSubscriber$.unsubscribe()
95
- }
96
- lastSubscriber$ = observable$
97
- subscribers$["$store_getWidgetState"] = lastSubscriber$
98
-
158
+ const { state } = getState(["widgets", widgetId])
99
159
  return state
100
160
  }
101
161
  return getResult()
102
162
  }
103
163
  },
104
164
  getUiState: () => {
105
- let lastSubscriber$ = subscribers$["$store_getUiState"]
106
165
  const getResult = () => {
107
- const { state, observable$ } = getState(["ui"])
108
- if (lastSubscriber$) {
109
- lastSubscriber$.unsubscribe()
110
- }
111
- lastSubscriber$ = observable$
112
- subscribers$["$store_getUiState"] = lastSubscriber$
166
+ const { state } = getState(["ui"])
113
167
 
114
168
  return state
115
169
  }
@@ -117,14 +171,9 @@ export const usePluginApi = (
117
171
  },
118
172
 
119
173
  getQueryState: (actionId) => {
120
- let lastSubscriber$ = subscribers$["$store_getQueryState"]
174
+ if (!actionId) return {}
121
175
  const getResult = () => {
122
- const { state, observable$ } = getState(["queries", actionId])
123
- if (lastSubscriber$) {
124
- lastSubscriber$.unsubscribe()
125
- }
126
- lastSubscriber$ = observable$
127
- subscribers$["$store_store_getQueryStateget"] = lastSubscriber$
176
+ const { state } = getState(["queries", actionId])
128
177
 
129
178
  return state
130
179
  }
@@ -132,17 +181,13 @@ export const usePluginApi = (
132
181
  },
133
182
 
134
183
  getQueryData: (actionId) => {
184
+ if (!actionId) return {}
135
185
  console.error("not implemented yet")
136
186
  },
137
187
  get: (pathArray, filter) => {
138
- let lastSubscriber$ = subscribers$["$store_get"]
188
+ const cacheString = createStableHash({ pathArray, filter })
139
189
  const getResult = () => {
140
- const { state, observable$ } = getState(pathArray, filter)
141
- if (lastSubscriber$) {
142
- lastSubscriber$.unsubscribe()
143
- }
144
- lastSubscriber$ = observable$
145
- subscribers$["$store_get"] = lastSubscriber$
190
+ const { state } = getState(pathArray, filter)
146
191
  return state
147
192
  }
148
193
  return getResult()
@@ -153,20 +198,18 @@ export const usePluginApi = (
153
198
  type: "ui/update",
154
199
  payload,
155
200
  }),
156
- setWidgetState: (payload, widgetId = "") => {
157
- const _widgetId =
158
- widgetId.length > 0 ? widgetId : instance?.props?.uuid || false
159
-
201
+ setWidgetState: (payload, widgetId = getWidgetId()) => {
160
202
  $store.dispatch({
161
203
  type: "widgets/update",
162
- uuid: _widgetId,
163
- payload: fixDataTypesFromObject(payload),
204
+ uuid: widgetId,
205
+ override: true,
206
+ payload,
164
207
  })
165
208
  },
166
209
 
167
210
  updateWidgetState: (
168
211
  payload: object,
169
- widgetId: string = instance?.props?.uuid || "",
212
+ widgetId: string = getWidgetId(),
170
213
  ) => {
171
214
  if (!widgetId) return false
172
215
  $store.dispatch({
@@ -175,10 +218,7 @@ export const usePluginApi = (
175
218
  payload: payload,
176
219
  })
177
220
  },
178
- updateConfig: (
179
- payload: object,
180
- widgetId: string = instance?.props?.uuid || "",
181
- ) => {
221
+ updateConfig: (payload: object, widgetId: string = getWidgetId()) => {
182
222
  if (!widgetId) return false
183
223
  $store.dispatch({
184
224
  type: "widgets/update",
@@ -188,29 +228,45 @@ export const usePluginApi = (
188
228
  },
189
229
  })
190
230
  },
191
- updatePublic: (
231
+ updatePublic: (payload: object, widgetId: string = getWidgetId()) => {
232
+ if (!widgetId) return false
233
+ $store.dispatch({
234
+ type: "widgets/update",
235
+ uuid: widgetId,
236
+ payload: {
237
+ public: payload,
238
+ },
239
+ })
240
+ },
241
+ publishWorkingState: (
192
242
  payload: object,
193
- widgetId: string = instance?.props?.uuid || "",
243
+ widgetId: string = getWidgetId(),
194
244
  ) => {
195
245
  if (!widgetId) return false
196
246
  $store.dispatch({
197
247
  type: "widgets/update",
198
248
  uuid: widgetId,
199
249
  payload: {
200
- public: payload,
250
+ workingState: payload,
201
251
  },
202
252
  })
203
253
  },
254
+ updateDataItem: (
255
+ uuid: string,
256
+ payload: object,
257
+ override: boolean = false,
258
+ ) => {
259
+ $store.dispatch({
260
+ type: "data/update",
261
+ docId: uuid,
262
+ override,
263
+ payload: payload,
264
+ })
265
+ },
204
266
  }
205
267
  }
206
268
  const getUser = () => {
207
- let lastSubscriber$ = subscribers$["$user"]
208
- const { state, observable$ } = getState("user")
209
- if (lastSubscriber$) {
210
- lastSubscriber$.unsubscribe()
211
- }
212
- lastSubscriber$ = observable$
213
- subscribers$["$user"] = lastSubscriber$
269
+ const { state } = getState("user")
214
270
  return state
215
271
  }
216
272
  if (plugin.initEventbus) {
@@ -220,12 +276,6 @@ export const usePluginApi = (
220
276
  // Retrieve the user for the api
221
277
  $instanceApi.$user = getUser()
222
278
  onUnmounted(() => {
223
- if (subscribers$.length) {
224
- subscribers$.forEach((subscriber) => {
225
- subscriber.unsubscribe()
226
- })
227
- }
228
-
229
279
  if ($instanceApi.$socket?.registeredListeners?.length) {
230
280
  // Clean up socket events
231
281
  $instanceApi.$socket.registeredListeners.forEach((listener) => {
@@ -84,7 +84,6 @@ export const useRouteState = () => {
84
84
  const currentSlotParams =
85
85
  compositionSlots.value.find((slot) => slot.slotName === targetSlotName)
86
86
  ?.slotParams ?? {}
87
-
88
87
  // Merge current and requested parameters, then filter to only allowed ones
89
88
  const allowedParams = interactionLink.queryParameters ?? []
90
89
  const mergedParams = mergeDeepRight(currentSlotParams, requestedParams)
@@ -79,6 +79,8 @@ export const useVueQueryObservableHelper = (
79
79
  response: responseRef,
80
80
  execute: coreQuery.execute,
81
81
  uuids: coreQuery.uuids$,
82
+ remove: coreQuery.remove,
83
+ clear: coreQuery.clear,
82
84
  error: computed(() => {
83
85
  return errorObservable.value
84
86
  }),
@@ -66,13 +66,13 @@ export const useVueWriteOperationHelper = (
66
66
  subscriberCounter[instanceActionId] -= 1
67
67
  if (subscriberCounter[instanceActionId] === 0) {
68
68
  // Clean up the observable subject
69
- if (subscriber$[instanceActionId]?.destroy) {
70
- subscriber$[instanceActionId].destroy()
69
+ if (subscriber$[instanceActionId]?.remove) {
70
+ subscriber$[instanceActionId].remove()
71
71
  }
72
72
  delete subscriber$[instanceActionId]
73
73
  delete subscriberCounter[instanceActionId]
74
74
  }
75
- writeOperation.destroy()
75
+ writeOperation.remove()
76
76
  })
77
77
 
78
78
  const operationObject = {
@@ -85,6 +85,8 @@ export const useVueWriteOperationHelper = (
85
85
  }),
86
86
  response: responseRef,
87
87
  execute: writeOperation.execute,
88
+ remove: writeOperation.remove,
89
+ clear: writeOperation.clear,
88
90
  error: computed(() => {
89
91
  return errorObservable.value
90
92
  }),
@@ -19,7 +19,10 @@ const WIDGET_COMPONENT_FILES = import.meta.glob(
19
19
  [
20
20
  "@racletteCore/../../external-app-plugins/**/frontend/widgets/**/*Widget.vue",
21
21
  ],
22
- { eager: false },
22
+ {
23
+ eager: false,
24
+ as: "url",
25
+ },
23
26
  )
24
27
 
25
28
  // Discover widget setup files for metadata
@@ -10,7 +10,7 @@ export default () => {
10
10
  )
11
11
 
12
12
  if (checkTokenNotifications.length) {
13
- $eventbus.emit("ui_addToSnackBar", checkTokenNotifications[0].message)
13
+ $eventbus.emit("ui/addToSnackbar", checkTokenNotifications[0].message)
14
14
  $store.dispatch({
15
15
  type: "notifications/markAllAsRead",
16
16
  })
@@ -2,7 +2,7 @@ import { $eventbus } from "@racletteCore/main"
2
2
 
3
3
  const copyToClipboard = (name: string, value: string) => {
4
4
  navigator.clipboard.writeText(value)
5
- $eventbus.emit("ui_addToSnackBar", {
5
+ $eventbus.emit("ui/addToSnackbar", {
6
6
  i18n: "core.copiedToClipboard$name",
7
7
  content: { name },
8
8
  color: "info",
@@ -198,6 +198,7 @@
198
198
  "yi-IL": "Jiddisch (Israel)",
199
199
  "zu-ZA": "Zulu (Südafrika)",
200
200
  "sk": "Slowakisch (Slowakei)",
201
+ "sk-SK": "Slowakisch (Slowakei)",
201
202
  "sl-SL": "Slowenisch (Slowenien)"
202
203
  }
203
204
  }
@@ -198,6 +198,7 @@
198
198
  "yi-IL": "Yiddish (Israel)",
199
199
  "zu-ZA": "Zulu (South Africa)",
200
200
  "sk": "Slovak (Slovakia)",
201
+ "sk-SK": "Slovak (Slovakia)",
201
202
  "sl-SL": "Slovenian (Slovenia)"
202
203
  }
203
204
  }
@@ -198,6 +198,7 @@
198
198
  "yi-IL": "Jidiš (Izrael)",
199
199
  "zu-ZA": "Zulu (Južná Afrika)",
200
200
  "sk": "Slovenčina (Slovensko)",
201
+ "sk-SK": "Slovenčina (Slovensko)",
201
202
  "sl-SL": "Slovinčina (Slovinsko)"
202
203
  }
203
204
  }
@@ -1,15 +1,19 @@
1
1
  import { compose, type ComposedRouteState } from "./routeParser"
2
2
  import { $store } from "@racletteCore"
3
3
 
4
+ export type WidgetLayoutType = "default" | "spaced" | "card" | "border" | "full"
5
+
4
6
  export interface CompositionSlot {
5
7
  slotName: string
6
8
  slotValue: string
7
9
  slotParams?: Record<string, string>
10
+ slotLayout?: WidgetLayoutType
8
11
  }
9
12
 
10
13
  // TODO: transform this to an array
11
14
  export const DEFAULT_SLOT_NAME = "page"
12
15
  export const DEFAULT_MODAL_SLOT_NAME = "modal"
16
+ export const DEFAULT_INLINE_SLOT_NAME = "inline"
13
17
 
14
18
  export const parseRouteToCompositionSlots = (
15
19
  route: string,
@@ -17,7 +21,6 @@ export const parseRouteToCompositionSlots = (
17
21
  addDefaultSlotIfMissing = false,
18
22
  ) => {
19
23
  const composed = compose(route)
20
-
21
24
  return parseComposedToCompositionSlots(
22
25
  composed,
23
26
  defaultSlotName,
@@ -3,8 +3,8 @@ import type { RouteLocationNormalized } from "vue-router"
3
3
  import {
4
4
  DEFAULT_SLOT_NAME,
5
5
  parseRouteToCompositionSlots,
6
+ type CompositionSlot,
6
7
  } from "@racletteOrchestrator/router/routeParserHelper"
7
- import { difference } from "ramda"
8
8
  import { getCompositionIdByPath } from "@racletteCore/store/reducers/compositions/selectors"
9
9
 
10
10
  // TODO: find better filename
@@ -46,6 +46,59 @@ export const dispatchSetCompositionAs = (
46
46
  }
47
47
  }
48
48
 
49
+ /**
50
+ * Compares two arrays of composition slots and returns the differences
51
+ * Returns an object with three arrays: toRemove, toAdd, toUpdate
52
+ */
53
+ const getCompositionSlotChanges = (
54
+ oldSlots: CompositionSlot[],
55
+ newSlots: CompositionSlot[],
56
+ ) => {
57
+ // Helper: Create a comparison key for slots (ignoring slotParams)
58
+ const getSlotKey = (slot: CompositionSlot) =>
59
+ `${slot.slotName}:${slot.slotValue}`
60
+
61
+ // Helper: Check if slotParams are different
62
+ const hasDifferentSlotParams = (
63
+ slotA: CompositionSlot,
64
+ slotB: CompositionSlot,
65
+ ) => {
66
+ const paramsA = slotA.slotParams || {}
67
+ const paramsB = slotB.slotParams || {}
68
+ return JSON.stringify(paramsA) !== JSON.stringify(paramsB)
69
+ }
70
+
71
+ const oldSlotMap = new Map(oldSlots.map((slot) => [getSlotKey(slot), slot]))
72
+ const newSlotMap = new Map(newSlots.map((slot) => [getSlotKey(slot), slot]))
73
+
74
+ const toRemove: CompositionSlot[] = []
75
+ const toAdd: CompositionSlot[] = []
76
+ const toUpdate: CompositionSlot[] = []
77
+
78
+ // Find slots to remove (exist in old but not in new)
79
+ for (const [key, oldSlot] of oldSlotMap) {
80
+ if (!newSlotMap.has(key)) {
81
+ toRemove.push(oldSlot)
82
+ }
83
+ }
84
+
85
+ // Find slots to add or update (exist in new)
86
+ for (const [key, newSlot] of newSlotMap) {
87
+ if (!oldSlotMap.has(key)) {
88
+ // Completely new slot
89
+ toAdd.push(newSlot)
90
+ } else {
91
+ // Slot exists in both, check if slotParams changed
92
+ const oldSlot = oldSlotMap.get(key)!
93
+ if (hasDifferentSlotParams(oldSlot, newSlot)) {
94
+ toUpdate.push(newSlot)
95
+ }
96
+ }
97
+ }
98
+
99
+ return { toRemove, toAdd, toUpdate }
100
+ }
101
+
49
102
  export const updateActiveCompositions = (
50
103
  to: RouteLocationNormalized,
51
104
  from: RouteLocationNormalized,
@@ -54,7 +107,6 @@ export const updateActiveCompositions = (
54
107
  const hasToPageIdParameter = Boolean(to.params.pageId?.length)
55
108
  const hasFromPageIdParameter = Boolean(from.params.pageId?.length)
56
109
 
57
- // TODO: write helper method to combine compose & parseRouteToCompositionSlots
58
110
  const compositionSlots = parseRouteToCompositionSlots(
59
111
  to.fullPath,
60
112
  undefined,
@@ -67,21 +119,36 @@ export const updateActiveCompositions = (
67
119
  !isInitialRouting && !hasFromPageIdParameter,
68
120
  )
69
121
 
70
- const compositionSlotsToRemove = difference(
122
+ const { toRemove, toAdd, toUpdate } = getCompositionSlotChanges(
71
123
  beforeCompositionSlots,
72
124
  compositionSlots,
73
125
  )
74
- const compositionSlotsToAdd = difference(
75
- compositionSlots,
76
- beforeCompositionSlots,
77
- )
78
126
 
79
- compositionSlotsToRemove.forEach((slot) =>
80
- dispatchSetCompositionAs(slot.slotName),
81
- )
82
- compositionSlotsToAdd.forEach((slot) =>
127
+ // Remove slots that are no longer needed
128
+ toRemove.forEach((slot) => dispatchSetCompositionAs(slot.slotName))
129
+
130
+ // Add completely new slots
131
+ toAdd.forEach((slot) =>
83
132
  dispatchSetCompositionAs(slot.slotName, slot.slotValue, slot.slotParams),
84
133
  )
134
+
135
+ // Update existing slots with new slotParams (without removing/adding)
136
+ toUpdate.forEach((slot) => {
137
+ const compositionId = getCompositionIdByPath(slot.slotValue)
138
+ if (
139
+ compositionId &&
140
+ slot.slotParams &&
141
+ Object.keys(slot.slotParams).length
142
+ ) {
143
+ $store.dispatch({
144
+ type: "compositions/update",
145
+ payload: {
146
+ compositionId,
147
+ compositionParams: slot.slotParams,
148
+ },
149
+ })
150
+ }
151
+ })
85
152
  }
86
153
 
87
154
  export const handleRouteChange = (
@@ -8,6 +8,7 @@
8
8
  "paths": {
9
9
  "@shared/*": ["../backend/src/shared/*"],
10
10
  "@core": ["../.."],
11
+ "@app/*": ["./src/app/*"],
11
12
  "@racletteCore/*": ["./src/core/*"],
12
13
  "@racletteCore": ["./src/core"],
13
14
  "@racletteOrchestrator": ["./src/orchestrator"],
@@ -596,6 +596,11 @@
596
596
  resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8"
597
597
  integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==
598
598
 
599
+ "@sinclair/typebox@0.34.41":
600
+ version "0.34.41"
601
+ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.34.41.tgz#aa51a6c1946df2c5a11494a2cdb9318e026db16c"
602
+ integrity sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==
603
+
599
604
  "@socket.io/component-emitter@~3.1.0":
600
605
  version "3.1.2"
601
606
  resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz#821f8442f4175d8f0467b9daf26e3a18e2d02af2"
package/yarn.lock CHANGED
@@ -295,10 +295,10 @@
295
295
  resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.2.9.tgz#d229a7b7f9dac167a156992ef23c7f023653f53b"
296
296
  integrity sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==
297
297
 
298
- "@raclettejs/types@0.1.11":
299
- version "0.1.11"
300
- resolved "https://registry.yarnpkg.com/@raclettejs/types/-/types-0.1.11.tgz#5149b4d3cfe4518f3a22558afb1e71ca34e9d7c1"
301
- integrity sha512-tbwy+fZVUFcrC8fmQEZuWYWooin6+TV3gbqsbVenj5RMmNtqBqcScootFcC4xz5w32wgN17Ay9wwifSwzdC6pw==
298
+ "@raclettejs/types@^0.1.13":
299
+ version "0.1.13"
300
+ resolved "https://registry.yarnpkg.com/@raclettejs/types/-/types-0.1.13.tgz#16fbe78f3e9d6e1ea4ae3c5e1537ba5f7335f9a6"
301
+ integrity sha512-AbYVlo0AI+hqgIxEY6rb5UYq8tX+yQvr/yeOvT0rhgITgXGXwUoaJXIKXAVtoSrHkX9zBeCWaTSq5iroCTXBwg==
302
302
  dependencies:
303
303
  "@types/node" "24.5.2"
304
304
  fastify "5.6.0"