@raclettejs/workbench 0.1.31 → 0.1.32-nightly

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 (26) hide show
  1. package/.raclette/backend/raclette.config.js +515 -0
  2. package/.raclette/backend/yarn.lock +3636 -0
  3. package/.raclette/frontend/raclette.config.js +43 -0
  4. package/.raclette/frontend/yarn.lock +3388 -0
  5. package/.raclette/virtual/backend/README.md +172 -0
  6. package/.raclette/virtual/backend/raclette.config.js +515 -0
  7. package/.raclette/virtual/backend/yarn.lock +3636 -0
  8. package/.raclette/virtual/frontend/README.md +511 -0
  9. package/.raclette/virtual/frontend/raclette.config.js +43 -0
  10. package/.raclette/virtual/frontend/src/core/lib/eggs/readme.md +75 -0
  11. package/.raclette/virtual/frontend/yarn.lock +3388 -0
  12. package/CHANGELOG.md +6 -10
  13. package/i18n/de-DE.json +1 -10
  14. package/i18n/en-EU.json +1 -10
  15. package/i18n/sk-SK.json +1 -10
  16. package/package.json +5 -3
  17. package/plugins/pacifico__compositions/frontend/widgets/CompositionListWidget.vue +61 -105
  18. package/plugins/pacifico__interactionLinks/frontend/widgets/InteractionLinkListWidget.vue +64 -120
  19. package/plugins/pacifico__plugins/frontend/widgets/PluginListWidget.vue +2 -2
  20. package/plugins/pacifico__tags/frontend/widgets/TagListWidget.vue +45 -102
  21. package/plugins/pacifico__users/frontend/widgets/UserListWidget.vue +24 -16
  22. package/services/frontend/src/app/lib/jsonTools.ts +69 -3
  23. package/services/frontend/src/app/components/BaseDataTable.vue +0 -303
  24. package/services/frontend/src/app/components/FileUpload.vue +0 -98
  25. package/services/frontend/src/app/components/SelectionDialog.vue +0 -182
  26. package/services/frontend/src/app/components/SummaryDialog.vue +0 -140
package/CHANGELOG.md CHANGED
@@ -7,24 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
- ## [0.1.30] - 2026-04-27 <a href="https://gitlab.com/raclettejs/core-dev/-/compare/v0.1.30...v0.1.30" target="_blank" rel="noopener"><b>Overview of all changes</b></a>
10
+ - set eslint version to latest
11
11
 
12
- ## [0.1.30] - 2026-04-27 <a href="https://gitlab.com/raclettejs/core-dev/-/compare/v0.1.30...v0.1.30" target="_blank" rel="noopener"><b>Overview of all changes</b></a>
12
+ ## [0.1.32] - 2026-04-30 <a href="https://gitlab.com/raclettejs/core-dev/-/compare/v0.1.31...v0.1.32" target="_blank" rel="noopener"><b>Overview of all changes</b></a>
13
13
 
14
- ## [0.1.29] - 2026-04-27 <a href="https://gitlab.com/raclettejs/core-dev/-/compare/v0.1.29...v0.1.29" target="_blank" rel="noopener"><b>Overview of all changes</b></a>
14
+ ## [0.1.31] - 2026-04-27 <a href="https://gitlab.com/raclettejs/core-dev/-/compare/v0.1.30...v0.1.31" target="_blank" rel="noopener"><b>Overview of all changes</b></a>
15
15
 
16
- ## [0.1.28] - 2026-04-27 <a href="https://gitlab.com/raclettejs/core-dev/-/compare/v0.1.28...v0.1.28" target="_blank" rel="noopener"><b>Overview of all changes</b></a>
16
+ ## [0.1.30] - 2026-04-27 <a href="https://gitlab.com/raclettejs/core-dev/-/compare/v0.1.29...v0.1.30" target="_blank" rel="noopener"><b>Overview of all changes</b></a>
17
17
 
18
- ### Updated
18
+ ## [0.1.29] - 2026-04-27 <a href="https://gitlab.com/raclettejs/core-dev/-/compare/v0.1.28...v0.1.29" target="_blank" rel="noopener"><b>Overview of all changes</b></a>
19
19
 
20
- - core release
20
+ ## [0.1.28] - 2026-04-27 <a href="https://gitlab.com/raclettejs/core-dev/-/compare/v0.1.27...v0.1.28" target="_blank" rel="noopener"><b>Overview of all changes</b></a>
21
21
 
22
22
  ## [0.1.27] - 2026-04-27 <a href="https://gitlab.com/raclettejs/core-dev/-/compare/v0.1.26...v0.1.27" target="_blank" rel="noopener"><b>Overview of all changes</b></a>
23
23
 
24
- ### Updated
25
-
26
- - core release
27
-
28
24
  ## [0.1.26] - 2026-04-27 <a href="https://gitlab.com/raclettejs/core-dev/-/compare/v0.1.25...v0.1.26" target="_blank" rel="noopener"><b>Overview of all changes</b></a>
29
25
 
30
26
  ### Updated
package/i18n/de-DE.json CHANGED
@@ -38,15 +38,6 @@
38
38
  "continue": "Weiter"
39
39
  }
40
40
  },
41
- "baseDataTable": {
42
- "searchPlaceholder": "{dataName} durchsuchen",
43
- "createDataButton": "{dataName} erstellen",
44
- "deleteDataTitle": "{dataName} löschen",
45
- "restoreDataTitle": "{dataName} widerherstellen",
46
- "deleteDataConfirmMessage": "Bist du sicher, dass du {article} {dataName} <strong>{value}</strong> löschen willst?",
47
- "deleteDataConfirmButton": "Löschen",
48
- "deleteDataConfirmCancel": "Abbrechen"
49
- },
50
41
  "compositionList": {
51
42
  "dataName": "Komposition",
52
43
  "dataArticle": "die",
@@ -290,4 +281,4 @@
290
281
  }
291
282
  }
292
283
  }
293
- }
284
+ }
package/i18n/en-EU.json CHANGED
@@ -38,15 +38,6 @@
38
38
  "continue": "Continue"
39
39
  }
40
40
  },
41
- "baseDataTable": {
42
- "searchPlaceholder": "Search {dataName}",
43
- "createDataButton": "Create {dataName}",
44
- "deleteDataTitle": "Delete {dataName}",
45
- "restoreDataTitle": "Restore {dataName}",
46
- "deleteDataConfirmMessage": "Are you sure you want to delete {article} {dataName} <strong>{value}</strong>?",
47
- "deleteDataConfirmButton": "Delete",
48
- "deleteDataConfirmCancel": "Cancel"
49
- },
50
41
  "compositionList": {
51
42
  "dataName": "Composition",
52
43
  "dataArticle": "the",
@@ -290,4 +281,4 @@
290
281
  }
291
282
  }
292
283
  }
293
- }
284
+ }
package/i18n/sk-SK.json CHANGED
@@ -38,15 +38,6 @@
38
38
  "continue": "Pokračovať"
39
39
  }
40
40
  },
41
- "baseDataTable": {
42
- "searchPlaceholder": "Hľadať {dataName}",
43
- "createDataButton": "Vytvoriť {dataName}",
44
- "deleteDataTitle": "Odstrániť {dataName}",
45
- "restoreDataTitle": "Obnoviť {dataName}",
46
- "deleteDataConfirmMessage": "Naozaj chcete odstrániť {article} {dataName} <strong>{value}</strong>?",
47
- "deleteDataConfirmButton": "Odstrániť",
48
- "deleteDataConfirmCancel": "Zrušiť"
49
- },
50
41
  "compositionList": {
51
42
  "dataName": "Kompozícia",
52
43
  "dataArticle": "",
@@ -285,4 +276,4 @@
285
276
  }
286
277
  }
287
278
  }
288
- }
279
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raclettejs/workbench",
3
- "version": "0.1.31",
3
+ "version": "0.1.32-nightly",
4
4
  "license": "MIT",
5
5
  "description": "racletteJS Workbench - used to configure your application, for dev and prod",
6
6
  "repository": "https://gitlab.com/raclettejs/workbench",
@@ -13,6 +13,8 @@
13
13
  "update": "raclette update",
14
14
  "restart": "raclette restart",
15
15
  "add-package": "raclette add-package",
16
+ "format:check": "prettier --check .",
17
+ "format:fix": "prettier --write .",
16
18
  "lint": "eslint",
17
19
  "lint:fix": "eslint --fix"
18
20
  },
@@ -31,7 +33,7 @@
31
33
  ".": "./index.ts"
32
34
  },
33
35
  "dependencies": {
34
- "@raclettejs/core": "^0.1.31",
36
+ "@raclettejs/core": "^0.1.32-nightly",
35
37
  "@vueuse/integrations": "14.2.1",
36
38
  "fuse.js": "7.1.0",
37
39
  "three": "0.183.2",
@@ -41,7 +43,7 @@
41
43
  "@emnapi/core": "1.9.2",
42
44
  "@emnapi/runtime": "1.9.2",
43
45
  "@eslint/js": "9.35.0",
44
- "@raclettejs/types": "^0.1.31",
46
+ "@raclettejs/types": "^0.1.32-nightly",
45
47
  "@sinclair/typebox": "0.34.48",
46
48
  "@types/ramda": "0.31.1",
47
49
  "@vueuse/core": "14.2.1",
@@ -2,6 +2,7 @@
2
2
  <div>
3
3
  <BaseDataTable
4
4
  :items="parsedCompositions"
5
+ :exportable-items="exportableCompositions"
5
6
  :loading="compositionsLoading || tagsLoading"
6
7
  :headers="headers"
7
8
  create-interaction-link-id="compositionCreateInteractionLink"
@@ -9,16 +10,17 @@
9
10
  :data-name="$t('workbench.compositionList.dataName')"
10
11
  :data-article="$t('workbench.compositionList.dataArticle')"
11
12
  @delete="deleteComposition"
12
- :itemsDeleted="showDeleted"
13
+ :items-deleted="showDeleted"
14
+ :on-file-loaded="handleFileUpload"
13
15
  >
14
- <template #actions>
16
+ <template #toolbar-leading>
15
17
  <v-tooltip
16
18
  :text="showDeleted ? $t('core.hideDeleted') : $t('core.showDeleted')"
17
19
  >
18
- <template v-slot:activator="{ props }">
20
+ <template #activator="{ props: activatorProps }">
19
21
  <v-btn-toggle
20
22
  density="compact"
21
- v-bind="props"
23
+ v-bind="activatorProps"
22
24
  v-model="showDeleted"
23
25
  mandatory
24
26
  :color="showDeleted ? 'error' : 'success'"
@@ -30,29 +32,9 @@
30
32
  </v-btn-toggle>
31
33
  </template>
32
34
  </v-tooltip>
33
- <v-btn-group density="compact">
34
- <v-tooltip :text="$t('core.export')">
35
- <template v-slot:activator="{ props }">
36
- <v-btn color="info" v-bind="props" flat @click="openExportDialog">
37
- <v-icon>mdi-database-export</v-icon>
38
- </v-btn>
39
- </template>
40
- </v-tooltip>
41
- <v-tooltip :text="$t('core.import')">
42
- <template v-slot:activator="{ props }">
43
- <v-btn v-bind="props" color="warning">
44
- <FileUpload
45
- ref="uploadRef"
46
- :onFileLoaded="handleFileUpload"
47
- accept=".json"
48
- readAs="text"
49
- /></v-btn>
50
- </template>
51
- </v-tooltip>
52
- </v-btn-group>
53
35
  </template>
54
36
  <!-- Custom status column -->
55
- <template #item.status="{ item }">
37
+ <template #[`item.status`]="{ item }">
56
38
  <v-icon
57
39
  :icon="item.statusIcon"
58
40
  :color="item.statusColor"
@@ -74,16 +56,6 @@
74
56
  />
75
57
  </template>
76
58
  </BaseDataTable>
77
- <!-- Export Selection Dialog -->
78
- <SelectionDialog
79
- v-model="showExportDialog"
80
- :items="exportableItems"
81
- display-attribute="title"
82
- :title="$t('core.selectItemsToExport')"
83
- :confirm-text="$t('core.export')"
84
- @confirm="handleExportConfirm"
85
- />
86
-
87
59
  <!-- Import Selection Dialog -->
88
60
  <SelectionDialog
89
61
  v-model="showImportDialog"
@@ -94,16 +66,6 @@
94
66
  @confirm="handleImportConfirm"
95
67
  />
96
68
 
97
- <!-- Export Summary Dialog -->
98
- <SummaryDialog
99
- v-model="showExportSummary"
100
- display-attribute="title"
101
- :items="exportedItems"
102
- :title="$t('core.exportSummary')"
103
- :message="$t('core.exportSuccessMessage')"
104
- type="success"
105
- />
106
-
107
69
  <!-- Import Summary Dialog -->
108
70
  <SummaryDialog
109
71
  v-model="showImportSummary"
@@ -121,31 +83,27 @@
121
83
 
122
84
  <script setup lang="ts">
123
85
  import { usePluginApi } from "@raclettejs/core/orchestrator/composables"
124
- import { computed, watch, ref, useTemplateRef } from "vue"
86
+ import { computed, watch, ref } from "vue"
125
87
  import type { Composition } from "@raclettejs/core"
126
88
  import { formatDistance } from "date-fns"
127
- import BaseDataTable from "@app/components/BaseDataTable.vue"
89
+ import BaseDataTable from "@raclettejs/core/orchestrator/components/dataTable/BaseDataTable.vue"
128
90
  import { useI18n } from "vue-i18n"
129
- import { downloadJson, mapJsonValues } from "@app/lib/jsonTools"
130
- import FileUpload from "@app/components/FileUpload.vue"
131
- import SelectionDialog from "@app/components/SelectionDialog.vue"
132
- import SummaryDialog from "@app/components/SummaryDialog.vue"
91
+ import {
92
+ mapJsonValues,
93
+ parseAndNormalizeWorkbenchImportJson,
94
+ resolveWorkbenchImportSelection,
95
+ } from "@app/lib/jsonTools"
96
+ import SelectionDialog from "@raclettejs/core/orchestrator/components/dataTable/SelectionDialog.vue"
97
+ import SummaryDialog from "@raclettejs/core/orchestrator/components/dataTable/SummaryDialog.vue"
133
98
 
134
99
  const { t } = useI18n()
135
100
  const showDeleted = ref(false)
136
- // Dialog states
137
- const showExportDialog = ref(false)
138
101
  const showImportDialog = ref(false)
139
- const showExportSummary = ref(false)
140
102
  const showImportSummary = ref(false)
141
103
 
142
- // Data for dialogs
143
- const exportableItems = ref<any[]>([])
144
104
  const importableItems = ref<any[]>([])
145
- const exportedItems = ref<any[]>([])
146
105
  const importedItems = ref<any[]>([])
147
- const pendingImportData = ref<any>(null)
148
- const uploadRef = useTemplateRef("uploadRef")
106
+ const pendingImportById = ref<Record<string, any>>({})
149
107
 
150
108
  const headers = computed<
151
109
  { title: string; key: keyof Composition | "actions" }[]
@@ -204,25 +162,39 @@ const { execute: executeRestore } = await $data.composition.updateRestore({
204
162
  const { execute: executeHardDelete } = await $data.composition.deleteHard({
205
163
  options: { cb: () => execute({ isDeleted: showDeleted.value }) },
206
164
  })
207
- const parsedCompositions = computed(() => {
208
- if (compositions.value && tags.value) {
209
- return Object.values(
210
- compositions.value as { [key: string]: Composition },
211
- ).map((composition) => ({
212
- ...composition,
213
- tags: composition.tags.map((tag) => tags.value?.[tag]),
214
- lastEditor: users.value?.[composition.author]?.email,
215
- author: users.value?.[composition.author]?.email,
216
- status: composition.status, // TODO: parse status
217
- statusIcon: getStatusIcon(composition.status),
218
- statusColor: getStatusColor(composition.status),
219
- createdAt: formatDistance(new Date(composition.createdAt), new Date()),
220
- updatedAt: formatDistance(new Date(composition.updatedAt), new Date()),
221
- }))
165
+ const compositionTableRows = (relativeDates: boolean) => {
166
+ if (!compositions.value || !tags.value) {
167
+ return []
222
168
  }
223
169
 
224
- return []
225
- })
170
+ return Object.values(
171
+ compositions.value as { [key: string]: Composition },
172
+ ).map((composition) => ({
173
+ ...composition,
174
+ tags: composition.tags.map((tag) => tags.value?.[tag]),
175
+ lastEditor: users.value?.[composition.author]?.email,
176
+ author: users.value?.[composition.author]?.email,
177
+ status: composition.status, // TODO: parse status
178
+ statusIcon: getStatusIcon(composition.status),
179
+ statusColor: getStatusColor(composition.status),
180
+ ...(relativeDates
181
+ ? {
182
+ createdAt: formatDistance(
183
+ new Date(composition.createdAt),
184
+ new Date(),
185
+ ),
186
+ updatedAt: formatDistance(
187
+ new Date(composition.updatedAt),
188
+ new Date(),
189
+ ),
190
+ }
191
+ : {}),
192
+ }))
193
+ }
194
+
195
+ const parsedCompositions = computed(() => compositionTableRows(true))
196
+
197
+ const exportableCompositions = computed(() => compositionTableRows(false))
226
198
 
227
199
  // TODO: replace with composition status type
228
200
  const getStatusIcon = (status: string) => {
@@ -246,42 +218,26 @@ const getStatusColor = (status: string) => {
246
218
  return "#B66218"
247
219
  }
248
220
  }
249
- // Export functions
250
- const openExportDialog = () => {
251
- exportableItems.value = Object.values(compositions.value || {})
252
- showExportDialog.value = true
253
- }
254
-
255
- const handleExportConfirm = (selectedIds: string[]) => {
256
- const dataToExport = selectedIds.reduce((acc, id) => {
257
- if (compositions.value?.[id]) {
258
- acc[id] = compositions.value[id]
259
- }
260
- return acc
261
- }, {} as any)
262
-
263
- exportedItems.value = Object.values(dataToExport)
264
- downloadJson(dataToExport, "compositions")
265
- showExportSummary.value = true
266
- }
267
-
268
221
  // Import functions
269
- const handleFileUpload = (content: string, file: File) => {
222
+ const handleFileUpload = (content: string | ArrayBuffer) => {
223
+ if (typeof content !== "string") {
224
+ return
225
+ }
270
226
  try {
271
- const parsedData = JSON.parse(content)
272
- const itemsArray = Object.values(parsedData)
273
-
274
- importableItems.value = itemsArray
275
- pendingImportData.value = parsedData
227
+ const { items, byId } = parseAndNormalizeWorkbenchImportJson(content)
228
+ importableItems.value = items
229
+ pendingImportById.value = byId
276
230
  showImportDialog.value = true
277
231
  } catch (error) {
278
- console.error("Failed to parse JSON:", error)
232
+ console.error("Failed to prepare import:", error)
279
233
  }
280
234
  }
281
235
 
282
236
  const handleImportConfirm = (selectedIds: string[]) => {
283
- const selectedItems = selectedIds.map((id) => pendingImportData.value[id])
284
- importedItems.value = selectedItems
237
+ importedItems.value = resolveWorkbenchImportSelection(
238
+ pendingImportById.value,
239
+ selectedIds,
240
+ )
285
241
  showImportSummary.value = true
286
242
  }
287
243
 
@@ -303,7 +259,7 @@ const finalizeImport = async () => {
303
259
  // e.g., call an API endpoint to save the imported compositions
304
260
 
305
261
  // Reset states
306
- pendingImportData.value = null
262
+ pendingImportById.value = {}
307
263
  importableItems.value = []
308
264
  importedItems.value = []
309
265
  }
@@ -2,6 +2,7 @@
2
2
  <div>
3
3
  <BaseDataTable
4
4
  :items="parsedInteractionLinks"
5
+ :exportable-items="exportableInteractionLinks"
5
6
  :loading="interactionLinksLoading || tagsLoading"
6
7
  :headers="headers"
7
8
  create-interaction-link-id="interactionLinkCreateInteractionLink"
@@ -9,16 +10,17 @@
9
10
  :data-name="$t('workbench.interactionLinkList.dataName')"
10
11
  :data-article="$t('workbench.interactionLinkList.dataArticle')"
11
12
  @delete="deleteInteractionLink"
12
- :itemsDeleted="showDeleted"
13
+ :items-deleted="showDeleted"
14
+ :on-file-loaded="handleFileUpload"
13
15
  >
14
- <template #actions>
16
+ <template #toolbar-leading>
15
17
  <v-tooltip
16
18
  :text="showDeleted ? $t('core.hideDeleted') : $t('core.showDeleted')"
17
19
  >
18
- <template v-slot:activator="{ props }">
20
+ <template #activator="{ props: activatorProps }">
19
21
  <v-btn-toggle
20
22
  density="compact"
21
- v-bind="props"
23
+ v-bind="activatorProps"
22
24
  v-model="showDeleted"
23
25
  mandatory
24
26
  :color="showDeleted ? 'error' : 'success'"
@@ -30,29 +32,9 @@
30
32
  </v-btn-toggle>
31
33
  </template>
32
34
  </v-tooltip>
33
- <v-btn-group density="compact">
34
- <v-tooltip :text="$t('core.export')">
35
- <template v-slot:activator="{ props }">
36
- <v-btn color="info" v-bind="props" flat @click="openExportDialog">
37
- <v-icon>mdi-database-export</v-icon>
38
- </v-btn>
39
- </template>
40
- </v-tooltip>
41
- <v-tooltip :text="$t('core.import')">
42
- <template v-slot:activator="{ props }">
43
- <v-btn v-bind="props" color="warning">
44
- <FileUpload
45
- ref="uploadRef"
46
- :onFileLoaded="handleFileUpload"
47
- accept=".json"
48
- readAs="text"
49
- /></v-btn>
50
- </template>
51
- </v-tooltip>
52
- </v-btn-group>
53
35
  </template>
54
36
  <!-- Custom triggers column -->
55
- <template #item.triggers="{ item }">
37
+ <template #[`item.triggers`]="{ item }">
56
38
  <span class="tw:flex tw:gap-2">
57
39
  <span>{{ item.triggers }}</span>
58
40
  <v-icon icon="mdi-link" />
@@ -73,16 +55,6 @@
73
55
  />
74
56
  </template>
75
57
  </BaseDataTable>
76
- <!-- Export Selection Dialog -->
77
- <SelectionDialog
78
- v-model="showExportDialog"
79
- :items="exportableItems"
80
- display-attribute="description"
81
- :title="$t('core.selectItemsToExport')"
82
- :confirm-text="$t('core.export')"
83
- @confirm="handleExportConfirm"
84
- />
85
-
86
58
  <!-- Import Selection Dialog -->
87
59
  <SelectionDialog
88
60
  v-model="showImportDialog"
@@ -93,16 +65,6 @@
93
65
  @confirm="handleImportConfirm"
94
66
  />
95
67
 
96
- <!-- Export Summary Dialog -->
97
- <SummaryDialog
98
- v-model="showExportSummary"
99
- display-attribute="description"
100
- :items="exportedItems"
101
- :title="$t('core.exportSummary')"
102
- :message="$t('core.exportSuccessMessage')"
103
- type="success"
104
- />
105
-
106
68
  <!-- Import Summary Dialog -->
107
69
  <SummaryDialog
108
70
  v-model="showImportSummary"
@@ -119,31 +81,26 @@
119
81
  </template>
120
82
 
121
83
  <script setup lang="ts">
122
- import { clone } from "ramda"
123
- import BaseDataTable from "@app/components/BaseDataTable.vue"
84
+ import BaseDataTable from "@raclettejs/core/orchestrator/components/dataTable/BaseDataTable.vue"
124
85
  import { InteractionLink } from "@raclettejs/core"
125
86
  import { usePluginApi } from "@raclettejs/core/orchestrator/composables"
126
87
  import { formatDistance } from "date-fns"
127
- import { computed, ref, watch, useTemplateRef } from "vue"
88
+ import { computed, ref, watch } from "vue"
128
89
  import { useI18n } from "vue-i18n"
129
- import { downloadJson, mapJsonValues } from "@app/lib/jsonTools"
130
- import FileUpload from "@app/components/FileUpload.vue"
131
- import SelectionDialog from "@app/components/SelectionDialog.vue"
132
- import SummaryDialog from "@app/components/SummaryDialog.vue"
90
+ import {
91
+ mapJsonValues,
92
+ parseAndNormalizeWorkbenchImportJson,
93
+ resolveWorkbenchImportSelection,
94
+ } from "@app/lib/jsonTools"
95
+ import SelectionDialog from "@raclettejs/core/orchestrator/components/dataTable/SelectionDialog.vue"
96
+ import SummaryDialog from "@raclettejs/core/orchestrator/components/dataTable/SummaryDialog.vue"
133
97
  const showDeleted = ref(false)
134
- // Dialog states
135
- const showExportDialog = ref(false)
136
98
  const showImportDialog = ref(false)
137
- const showExportSummary = ref(false)
138
99
  const showImportSummary = ref(false)
139
100
 
140
- // Data for dialogs
141
- const exportableItems = ref<any[]>([])
142
101
  const importableItems = ref<any[]>([])
143
- const exportedItems = ref<any[]>([])
144
102
  const importedItems = ref<any[]>([])
145
- const pendingImportData = ref<any>(null)
146
- const uploadRef = useTemplateRef("uploadRef")
103
+ const pendingImportById = ref<Record<string, any>>({})
147
104
 
148
105
  const { t } = useI18n()
149
106
 
@@ -217,32 +174,42 @@ const { execute: executeHardDelete } = await $data.interactionLink.deleteHard({
217
174
  const { execute: executeRestore } = await $data.interactionLink.updateRestore({
218
175
  options: { cb: () => execute({ isDeleted: showDeleted.value }) },
219
176
  })
220
- const parsedInteractionLinks = computed(() => {
221
- if (interactionLinks.value && tags.value) {
222
- return Object.values(
223
- interactionLinks.value as { [key: string]: InteractionLink },
224
- ).map((interactionLink) => {
225
- return {
226
- ...interactionLink,
227
- composition: compositions.value?.[interactionLink.composition]?.title,
228
- tags: interactionLink.tags.map((tag) => tags.value?.[tag]),
229
- lastEditor: users.value?.[interactionLink.author]?.email,
230
- author: users.value?.[interactionLink.author]?.email,
231
- triggers: interactionLink.triggers.length,
232
- createdAt: formatDistance(
233
- new Date(interactionLink.createdAt),
234
- new Date(),
235
- ),
236
- updatedAt: formatDistance(
237
- new Date(interactionLink.updatedAt),
238
- new Date(),
239
- ),
240
- }
241
- })
177
+ const interactionLinkTableRows = (relativeDates: boolean) => {
178
+ if (!interactionLinks.value || !tags.value) {
179
+ return []
242
180
  }
243
181
 
244
- return []
245
- })
182
+ return Object.values(
183
+ interactionLinks.value as { [key: string]: InteractionLink },
184
+ ).map((interactionLink) => ({
185
+ ...interactionLink,
186
+ composition: compositions.value?.[interactionLink.composition]?.title,
187
+ tags: interactionLink.tags.map((tag) => tags.value?.[tag]),
188
+ lastEditor: users.value?.[interactionLink.author]?.email,
189
+ author: users.value?.[interactionLink.author]?.email,
190
+ triggers: interactionLink.triggers.length,
191
+ ...(relativeDates
192
+ ? {
193
+ createdAt: formatDistance(
194
+ new Date(interactionLink.createdAt),
195
+ new Date(),
196
+ ),
197
+ updatedAt: formatDistance(
198
+ new Date(interactionLink.updatedAt),
199
+ new Date(),
200
+ ),
201
+ }
202
+ : {}),
203
+ }))
204
+ }
205
+
206
+ const parsedInteractionLinks = computed(() =>
207
+ interactionLinkTableRows(true),
208
+ )
209
+
210
+ const exportableInteractionLinks = computed(() =>
211
+ interactionLinkTableRows(false),
212
+ )
246
213
  const restore = async (_id: string) => {
247
214
  executeRestore({ _id })
248
215
  }
@@ -267,50 +234,27 @@ const createTitleString = (link) => {
267
234
  link.description = linkTitleString + link.description
268
235
  return link
269
236
  }
270
- const createLinkDescriptions = (linkArr) => {
271
- linkArr.forEach((link) => {
272
- link = createTitleString(link)
273
- })
274
- return linkArr
275
- }
276
- // Export functions
277
- const openExportDialog = () => {
278
- exportableItems.value = createLinkDescriptions(
279
- Object.values(clone(interactionLinks.value) || {}),
280
- )
281
- showExportDialog.value = true
282
- }
283
-
284
- const handleExportConfirm = (selectedIds: string[]) => {
285
- const _interactionLinks = clone(interactionLinks.value)
286
- const dataToExport = clone(selectedIds).reduce((acc, id) => {
287
- if (_interactionLinks?.[id]) {
288
- acc[id] = createTitleString(_interactionLinks[id])
289
- }
290
- return acc
291
- }, {} as any)
292
- exportedItems.value = Object.values(dataToExport)
293
- downloadJson(dataToExport, "interactionLinks")
294
- showExportSummary.value = true
295
- }
296
237
 
297
238
  // Import functions
298
- const handleFileUpload = (content: string, file: File) => {
239
+ const handleFileUpload = (content: string | ArrayBuffer) => {
240
+ if (typeof content !== "string") {
241
+ return
242
+ }
299
243
  try {
300
- const parsedData = JSON.parse(content)
301
- const itemsArray = Object.values(parsedData)
302
-
303
- importableItems.value = itemsArray
304
- pendingImportData.value = parsedData
244
+ const { items, byId } = parseAndNormalizeWorkbenchImportJson(content)
245
+ importableItems.value = items
246
+ pendingImportById.value = byId
305
247
  showImportDialog.value = true
306
248
  } catch (error) {
307
- console.error("Failed to parse JSON:", error)
249
+ console.error("Failed to prepare import:", error)
308
250
  }
309
251
  }
310
252
 
311
253
  const handleImportConfirm = (selectedIds: string[]) => {
312
- const selectedItems = selectedIds.map((id) => pendingImportData.value[id])
313
- importedItems.value = selectedItems
254
+ importedItems.value = resolveWorkbenchImportSelection(
255
+ pendingImportById.value,
256
+ selectedIds,
257
+ )
314
258
  showImportSummary.value = true
315
259
  }
316
260
 
@@ -332,7 +276,7 @@ const finalizeImport = async () => {
332
276
  // e.g., call an API endpoint to save the imported compositions
333
277
 
334
278
  // Reset states
335
- pendingImportData.value = null
279
+ pendingImportById.value = {}
336
280
  importableItems.value = []
337
281
  importedItems.value = []
338
282
  }