@raclettejs/workbench 0.1.18 → 0.1.20

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 (35) hide show
  1. package/.raclette/backend/raclette.config.js +519 -0
  2. package/{yarn.lock → .raclette/backend/yarn.lock} +1014 -796
  3. package/.raclette/frontend/raclette.config.js +47 -0
  4. package/.raclette/frontend/yarn.lock +3428 -0
  5. package/.raclette/raclette.config.js +643 -0
  6. package/.raclette/virtual/backend/README.md +172 -0
  7. package/.raclette/virtual/backend/raclette.config.js +519 -0
  8. package/.raclette/virtual/backend/yarn.lock +3773 -0
  9. package/.raclette/virtual/frontend/README.md +511 -0
  10. package/.raclette/virtual/frontend/raclette.config.js +47 -0
  11. package/.raclette/virtual/frontend/src/core/lib/eggs/readme.md +75 -0
  12. package/.raclette/virtual/frontend/yarn.lock +3428 -0
  13. package/CHANGELOG.md +11 -0
  14. package/i18n/de-DE.json +17 -1
  15. package/i18n/en-EU.json +17 -1
  16. package/i18n/sk-SK.json +17 -1
  17. package/package.json +6 -4
  18. package/packages.json +3 -1
  19. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/CompositionWidgetsLayout.vue +8 -3
  20. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/widgetsLayout/WidgetContextMenu.vue +60 -0
  21. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/widgetsLayout/WidgetIcon.vue +4 -7
  22. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/widgetsLayout/WidgetList.vue +34 -117
  23. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/widgetsLayout/WidgetListFlat.vue +20 -0
  24. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/widgetsLayout/WidgetListGrouped.vue +50 -0
  25. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/widgetsLayout/WidgetListHeader.vue +213 -0
  26. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/widgetsLayout/WidgetListItem.vue +56 -0
  27. package/plugins/pacifico__compositions/frontend/composables/useWidgetList.ts +288 -0
  28. package/plugins/pacifico__compositions/frontend/widgets/CompositionListWidget.vue +201 -57
  29. package/plugins/pacifico__interactionLinks/frontend/widgets/InteractionLinkListWidget.vue +219 -55
  30. package/plugins/pacifico__tags/frontend/widgets/TagListWidget.vue +198 -48
  31. package/raclette.config.js +4 -1
  32. package/services/frontend/src/app/components/FileUpload.vue +98 -0
  33. package/services/frontend/src/app/components/SelectionDialog.vue +182 -0
  34. package/services/frontend/src/app/components/SummaryDialog.vue +140 -0
  35. package/services/frontend/src/app/lib/jsonTools.ts +81 -0
@@ -1,70 +1,151 @@
1
1
  <template>
2
- <BaseDataTable
3
- :items="parsedCompositions"
4
- :loading="compositionsLoading || tagsLoading"
5
- :headers="headers"
6
- create-interaction-link-id="compositionCreateInteractionLink"
7
- edit-interaction-link-id="compositionEditInteractionLink"
8
- :data-name="$t('workbench.compositionList.dataName')"
9
- :data-article="$t('workbench.compositionList.dataArticle')"
10
- @delete="deleteComposition"
11
- :itemsDeleted="showDeleted"
12
- >
13
- <template #actions>
14
- <v-tooltip
15
- :text="showDeleted ? $t('core.hideDeleted') : $t('core.showDeleted')"
16
- >
17
- <template v-slot:activator="{ props }">
18
- <v-btn-toggle
19
- density="compact"
20
- v-bind="props"
21
- v-model="showDeleted"
22
- mandatory
23
- :color="showDeleted ? 'error' : 'success'"
24
- >
25
- <v-btn :value="true">
26
- <v-icon>mdi-table-eye-off</v-icon>
27
- </v-btn>
28
- <v-btn :value="false"><v-icon>mdi-table-eye</v-icon></v-btn>
29
- </v-btn-toggle>
30
- </template>
31
- </v-tooltip>
32
- </template>
33
- <!-- Custom status column -->
34
- <template #item.status="{ item }">
35
- <v-icon
36
- :icon="item.statusIcon"
37
- :color="item.statusColor"
38
- size="x-large"
39
- />
40
- </template>
41
- <template #prepend-row-actions="{ item }">
42
- <v-icon
43
- v-if="showDeleted"
44
- color="green"
45
- class="mr-2"
46
- @click.stop.prevent="restore(item._id)"
47
- :title="
48
- $t('workbench.baseDataTable.restoreDataTitle', {
49
- dataName: item.title,
50
- })
51
- "
52
- icon="mdi-delete-restore"
53
- />
54
- </template>
55
- </BaseDataTable>
2
+ <div>
3
+ <BaseDataTable
4
+ :items="parsedCompositions"
5
+ :loading="compositionsLoading || tagsLoading"
6
+ :headers="headers"
7
+ create-interaction-link-id="compositionCreateInteractionLink"
8
+ edit-interaction-link-id="compositionEditInteractionLink"
9
+ :data-name="$t('workbench.compositionList.dataName')"
10
+ :data-article="$t('workbench.compositionList.dataArticle')"
11
+ @delete="deleteComposition"
12
+ :itemsDeleted="showDeleted"
13
+ >
14
+ <template #actions>
15
+ <v-tooltip
16
+ :text="showDeleted ? $t('core.hideDeleted') : $t('core.showDeleted')"
17
+ >
18
+ <template v-slot:activator="{ props }">
19
+ <v-btn-toggle
20
+ density="compact"
21
+ v-bind="props"
22
+ v-model="showDeleted"
23
+ mandatory
24
+ :color="showDeleted ? 'error' : 'success'"
25
+ >
26
+ <v-btn :value="true">
27
+ <v-icon>mdi-table-eye-off</v-icon>
28
+ </v-btn>
29
+ <v-btn :value="false"><v-icon>mdi-table-eye</v-icon></v-btn>
30
+ </v-btn-toggle>
31
+ </template>
32
+ </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
+ </template>
54
+ <!-- Custom status column -->
55
+ <template #item.status="{ item }">
56
+ <v-icon
57
+ :icon="item.statusIcon"
58
+ :color="item.statusColor"
59
+ size="x-large"
60
+ />
61
+ </template>
62
+ <template #prepend-row-actions="{ item }">
63
+ <v-icon
64
+ v-if="showDeleted"
65
+ color="green"
66
+ class="mr-2"
67
+ @click.stop.prevent="restore(item._id)"
68
+ :title="
69
+ $t('workbench.baseDataTable.restoreDataTitle', {
70
+ dataName: item.title,
71
+ })
72
+ "
73
+ icon="mdi-delete-restore"
74
+ />
75
+ </template>
76
+ </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
+ <!-- Import Selection Dialog -->
88
+ <SelectionDialog
89
+ v-model="showImportDialog"
90
+ :items="importableItems"
91
+ display-attribute="title"
92
+ :title="$t('core.selectItemsToImport')"
93
+ :confirm-text="$t('core.import')"
94
+ @confirm="handleImportConfirm"
95
+ />
96
+
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
+ <!-- Import Summary Dialog -->
108
+ <SummaryDialog
109
+ v-model="showImportSummary"
110
+ display-attribute="title"
111
+ :items="importedItems"
112
+ :title="$t('core.importSummary')"
113
+ :message="$t('core.importSuccessMessage')"
114
+ type="success"
115
+ :show-confirm="true"
116
+ :confirm-text="$t('core.saveImported')"
117
+ @confirm="finalizeImport"
118
+ />
119
+ </div>
56
120
  </template>
57
121
 
58
122
  <script setup lang="ts">
59
123
  import { usePluginApi } from "@raclettejs/core/orchestrator/composables"
60
- import { computed, watch, ref } from "vue"
124
+ import { computed, watch, ref, useTemplateRef } from "vue"
61
125
  import type { Composition } from "@raclettejs/core"
62
126
  import { formatDistance } from "date-fns"
63
127
  import BaseDataTable from "@app/components/BaseDataTable.vue"
64
128
  import { useI18n } from "vue-i18n"
65
- const showDeleted = ref(false)
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"
66
133
 
67
134
  const { t } = useI18n()
135
+ const showDeleted = ref(false)
136
+ // Dialog states
137
+ const showExportDialog = ref(false)
138
+ const showImportDialog = ref(false)
139
+ const showExportSummary = ref(false)
140
+ const showImportSummary = ref(false)
141
+
142
+ // Data for dialogs
143
+ const exportableItems = ref<any[]>([])
144
+ const importableItems = ref<any[]>([])
145
+ const exportedItems = ref<any[]>([])
146
+ const importedItems = ref<any[]>([])
147
+ const pendingImportData = ref<any>(null)
148
+ const uploadRef = useTemplateRef("uploadRef")
68
149
 
69
150
  const headers = computed<
70
151
  { title: string; key: keyof Composition | "actions" }[]
@@ -99,7 +180,7 @@ const props = defineProps<{
99
180
  uuid: string
100
181
  }>()
101
182
 
102
- const { $data, $socket } = usePluginApi("raclette__core")
183
+ const { $data, $socket, $user } = usePluginApi("raclette__core")
103
184
  const {
104
185
  data: compositions,
105
186
  execute,
@@ -114,6 +195,9 @@ const { data: users } = $data.user.getAll({
114
195
  const { execute: executeDelete } = await $data.composition.delete({
115
196
  options: { cb: () => execute({ isDeleted: showDeleted.value }) },
116
197
  })
198
+ const { execute: executeBulkCreate } = await $data.composition.createBulk({
199
+ options: { notify: false, cb: () => execute() },
200
+ })
117
201
  const { execute: executeRestore } = await $data.composition.updateRestore({
118
202
  options: { cb: () => execute({ isDeleted: showDeleted.value }) },
119
203
  })
@@ -162,7 +246,67 @@ const getStatusColor = (status: string) => {
162
246
  return "#B66218"
163
247
  }
164
248
  }
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
+ // Import functions
269
+ const handleFileUpload = (content: string, file: File) => {
270
+ try {
271
+ const parsedData = JSON.parse(content)
272
+ const itemsArray = Object.values(parsedData)
273
+
274
+ importableItems.value = itemsArray
275
+ pendingImportData.value = parsedData
276
+ showImportDialog.value = true
277
+ } catch (error) {
278
+ console.error("Failed to parse JSON:", error)
279
+ }
280
+ }
281
+
282
+ const handleImportConfirm = (selectedIds: string[]) => {
283
+ const selectedItems = selectedIds.map((id) => pendingImportData.value[id])
284
+ importedItems.value = selectedItems
285
+ showImportSummary.value = true
286
+ }
165
287
 
288
+ const finalizeImport = async () => {
289
+ const mapping = {
290
+ owner: $user.value._id,
291
+ lastEditor: $user.value._id,
292
+ createdBy: $user.value._id,
293
+ }
294
+
295
+ const dataToImport = importedItems.value.reduce((acc, item) => {
296
+ const mappedItem = mapJsonValues(item, mapping)
297
+ acc[item._id] = mappedItem
298
+ return acc
299
+ }, {} as any)
300
+
301
+ await executeBulkCreate(Object.values(dataToImport))
302
+ // TODO: Add your actual import logic here
303
+ // e.g., call an API endpoint to save the imported compositions
304
+
305
+ // Reset states
306
+ pendingImportData.value = null
307
+ importableItems.value = []
308
+ importedItems.value = []
309
+ }
166
310
  const deleteComposition = async (composition: Composition) => {
167
311
  if (showDeleted.value) {
168
312
  await executeHardDelete({ _id: composition._id })
@@ -1,67 +1,149 @@
1
1
  <template>
2
- <BaseDataTable
3
- :items="parsedInteractionLinks"
4
- :loading="interactionLinksLoading || tagsLoading"
5
- :headers="headers"
6
- create-interaction-link-id="interactionLinkCreateInteractionLink"
7
- edit-interaction-link-id="interactionLinkEditInteractionLink"
8
- :data-name="$t('workbench.interactionLinkList.dataName')"
9
- :data-article="$t('workbench.interactionLinkList.dataArticle')"
10
- @delete="deleteInteractionLink"
11
- :itemsDeleted="showDeleted"
12
- >
13
- <template #actions>
14
- <v-tooltip
15
- :text="showDeleted ? $t('core.hideDeleted') : $t('core.showDeleted')"
16
- >
17
- <template v-slot:activator="{ props }">
18
- <v-btn-toggle
19
- density="compact"
20
- v-bind="props"
21
- v-model="showDeleted"
22
- mandatory
23
- :color="showDeleted ? 'error' : 'success'"
24
- >
25
- <v-btn :value="true">
26
- <v-icon>mdi-table-eye-off</v-icon>
27
- </v-btn>
28
- <v-btn :value="false"><v-icon>mdi-table-eye</v-icon></v-btn>
29
- </v-btn-toggle>
30
- </template>
31
- </v-tooltip>
32
- </template>
33
- <!-- Custom triggers column -->
34
- <template #item.triggers="{ item }">
35
- <span class="tw:flex tw:gap-2">
36
- <span>{{ item.triggers }}</span>
37
- <v-icon icon="mdi-link" />
38
- </span>
39
- </template>
40
- <template #prepend-row-actions="{ item }">
41
- <v-icon
42
- v-if="showDeleted"
43
- color="green"
44
- class="mr-2"
45
- @click="restore(item._id)"
46
- :title="
47
- $t('workbench.baseDataTable.restoreDataTitle', {
48
- dataName: item.title,
49
- })
50
- "
51
- icon="mdi-delete-restore"
52
- />
53
- </template>
54
- </BaseDataTable>
2
+ <div>
3
+ <BaseDataTable
4
+ :items="parsedInteractionLinks"
5
+ :loading="interactionLinksLoading || tagsLoading"
6
+ :headers="headers"
7
+ create-interaction-link-id="interactionLinkCreateInteractionLink"
8
+ edit-interaction-link-id="interactionLinkEditInteractionLink"
9
+ :data-name="$t('workbench.interactionLinkList.dataName')"
10
+ :data-article="$t('workbench.interactionLinkList.dataArticle')"
11
+ @delete="deleteInteractionLink"
12
+ :itemsDeleted="showDeleted"
13
+ >
14
+ <template #actions>
15
+ <v-tooltip
16
+ :text="showDeleted ? $t('core.hideDeleted') : $t('core.showDeleted')"
17
+ >
18
+ <template v-slot:activator="{ props }">
19
+ <v-btn-toggle
20
+ density="compact"
21
+ v-bind="props"
22
+ v-model="showDeleted"
23
+ mandatory
24
+ :color="showDeleted ? 'error' : 'success'"
25
+ >
26
+ <v-btn :value="true">
27
+ <v-icon>mdi-table-eye-off</v-icon>
28
+ </v-btn>
29
+ <v-btn :value="false"><v-icon>mdi-table-eye</v-icon></v-btn>
30
+ </v-btn-toggle>
31
+ </template>
32
+ </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
+ </template>
54
+ <!-- Custom triggers column -->
55
+ <template #item.triggers="{ item }">
56
+ <span class="tw:flex tw:gap-2">
57
+ <span>{{ item.triggers }}</span>
58
+ <v-icon icon="mdi-link" />
59
+ </span>
60
+ </template>
61
+ <template #prepend-row-actions="{ item }">
62
+ <v-icon
63
+ v-if="showDeleted"
64
+ color="green"
65
+ class="mr-2"
66
+ @click="restore(item._id)"
67
+ :title="
68
+ $t('workbench.baseDataTable.restoreDataTitle', {
69
+ dataName: item.title,
70
+ })
71
+ "
72
+ icon="mdi-delete-restore"
73
+ />
74
+ </template>
75
+ </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
+ <!-- Import Selection Dialog -->
87
+ <SelectionDialog
88
+ v-model="showImportDialog"
89
+ :items="importableItems"
90
+ display-attribute="description"
91
+ :title="$t('core.selectItemsToImport')"
92
+ :confirm-text="$t('core.import')"
93
+ @confirm="handleImportConfirm"
94
+ />
95
+
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
+ <!-- Import Summary Dialog -->
107
+ <SummaryDialog
108
+ v-model="showImportSummary"
109
+ :items="importedItems"
110
+ display-attribute="description"
111
+ :title="$t('core.importSummary')"
112
+ :message="$t('core.importSuccessMessage')"
113
+ type="success"
114
+ :show-confirm="true"
115
+ :confirm-text="$t('core.saveImported')"
116
+ @confirm="finalizeImport"
117
+ />
118
+ </div>
55
119
  </template>
56
120
 
57
121
  <script setup lang="ts">
122
+ import { clone } from "ramda"
58
123
  import BaseDataTable from "@app/components/BaseDataTable.vue"
59
124
  import { InteractionLink } from "@raclettejs/core"
60
125
  import { usePluginApi } from "@raclettejs/core/orchestrator/composables"
61
126
  import { formatDistance } from "date-fns"
62
- import { computed, ref, watch } from "vue"
127
+ import { computed, ref, watch, useTemplateRef } from "vue"
63
128
  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"
64
133
  const showDeleted = ref(false)
134
+ // Dialog states
135
+ const showExportDialog = ref(false)
136
+ const showImportDialog = ref(false)
137
+ const showExportSummary = ref(false)
138
+ const showImportSummary = ref(false)
139
+
140
+ // Data for dialogs
141
+ const exportableItems = ref<any[]>([])
142
+ const importableItems = ref<any[]>([])
143
+ const exportedItems = ref<any[]>([])
144
+ const importedItems = ref<any[]>([])
145
+ const pendingImportData = ref<any>(null)
146
+ const uploadRef = useTemplateRef("uploadRef")
65
147
 
66
148
  const { t } = useI18n()
67
149
 
@@ -102,7 +184,7 @@ const props = defineProps<{
102
184
  uuid: string
103
185
  }>()
104
186
 
105
- const { $data, $socket } = usePluginApi("raclette__core")
187
+ const { $data, $socket, $user } = usePluginApi("raclette__core")
106
188
 
107
189
  const {
108
190
  data: interactionLinks,
@@ -123,6 +205,9 @@ const { data: compositions } = $data.composition.getAll({
123
205
  options: { immediate: true },
124
206
  })
125
207
 
208
+ const { execute: executeBulkCreate } = await $data.interactionLink.createBulk({
209
+ options: { notify: false, cb: () => execute() },
210
+ })
126
211
  const { execute: executeDelete } = $data.interactionLink.delete({
127
212
  options: { cb: () => execute({ isDeleted: showDeleted.value }) },
128
213
  })
@@ -172,6 +257,85 @@ const deleteInteractionLink = async (interactionLink: InteractionLink) => {
172
257
  $socket.on("interactionLinkCreated", () =>
173
258
  execute({ isDeleted: showDeleted.value }),
174
259
  )
260
+ const createTitleString = (link) => {
261
+ let linkTitleString = `${link.slotType}`
262
+ if (link.triggers) {
263
+ link.triggers.forEach((trigger) => {
264
+ linkTitleString += `${trigger.type} - ${trigger.settings?.title?.default} `
265
+ })
266
+ }
267
+ link.description = linkTitleString + link.description
268
+ return link
269
+ }
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
+
297
+ // Import functions
298
+ const handleFileUpload = (content: string, file: File) => {
299
+ try {
300
+ const parsedData = JSON.parse(content)
301
+ const itemsArray = Object.values(parsedData)
302
+
303
+ importableItems.value = itemsArray
304
+ pendingImportData.value = parsedData
305
+ showImportDialog.value = true
306
+ } catch (error) {
307
+ console.error("Failed to parse JSON:", error)
308
+ }
309
+ }
310
+
311
+ const handleImportConfirm = (selectedIds: string[]) => {
312
+ const selectedItems = selectedIds.map((id) => pendingImportData.value[id])
313
+ importedItems.value = selectedItems
314
+ showImportSummary.value = true
315
+ }
316
+
317
+ const finalizeImport = async () => {
318
+ const mapping = {
319
+ owner: $user.value._id,
320
+ lastEditor: $user.value._id,
321
+ createdBy: $user.value._id,
322
+ }
323
+
324
+ const dataToImport = importedItems.value.reduce((acc, item) => {
325
+ const mappedItem = mapJsonValues(item, mapping)
326
+ acc[item._id] = createTitleString(mappedItem)
327
+ return acc
328
+ }, {} as any)
329
+
330
+ await executeBulkCreate(Object.values(dataToImport))
331
+ // TODO: Add your actual import logic here
332
+ // e.g., call an API endpoint to save the imported compositions
333
+
334
+ // Reset states
335
+ pendingImportData.value = null
336
+ importableItems.value = []
337
+ importedItems.value = []
338
+ }
175
339
  watch(
176
340
  showDeleted,
177
341
  () => {