@raclettejs/workbench 0.1.17 → 0.1.19

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.
@@ -1,53 +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
- </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>
41
119
  </template>
42
120
 
43
121
  <script setup lang="ts">
122
+ import { clone } from "ramda"
44
123
  import BaseDataTable from "@app/components/BaseDataTable.vue"
45
124
  import { InteractionLink } from "@raclettejs/core"
46
125
  import { usePluginApi } from "@raclettejs/core/orchestrator/composables"
47
126
  import { formatDistance } from "date-fns"
48
- import { computed, ref, watch } from "vue"
127
+ import { computed, ref, watch, useTemplateRef } from "vue"
49
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"
50
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")
51
147
 
52
148
  const { t } = useI18n()
53
149
 
@@ -88,7 +184,7 @@ const props = defineProps<{
88
184
  uuid: string
89
185
  }>()
90
186
 
91
- const { $data, $socket } = usePluginApi("raclette__core")
187
+ const { $data, $socket, $user } = usePluginApi("raclette__core")
92
188
 
93
189
  const {
94
190
  data: interactionLinks,
@@ -105,15 +201,22 @@ const { data: users } = $data.user.getAll({
105
201
  })
106
202
 
107
203
  const { data: compositions } = $data.composition.getAll({
204
+ params: { isDeleted: false },
108
205
  options: { immediate: true },
109
206
  })
110
207
 
208
+ const { execute: executeBulkCreate } = await $data.interactionLink.createBulk({
209
+ options: { notify: false, cb: () => execute() },
210
+ })
111
211
  const { execute: executeDelete } = $data.interactionLink.delete({
112
212
  options: { cb: () => execute({ isDeleted: showDeleted.value }) },
113
213
  })
114
214
  const { execute: executeHardDelete } = await $data.interactionLink.deleteHard({
115
215
  options: { cb: () => execute({ isDeleted: showDeleted.value }) },
116
216
  })
217
+ const { execute: executeRestore } = await $data.interactionLink.updateRestore({
218
+ options: { cb: () => execute({ isDeleted: showDeleted.value }) },
219
+ })
117
220
  const parsedInteractionLinks = computed(() => {
118
221
  if (interactionLinks.value && tags.value) {
119
222
  return Object.values(
@@ -140,7 +243,9 @@ const parsedInteractionLinks = computed(() => {
140
243
 
141
244
  return []
142
245
  })
143
-
246
+ const restore = async (_id: string) => {
247
+ executeRestore({ _id })
248
+ }
144
249
  const deleteInteractionLink = async (interactionLink: InteractionLink) => {
145
250
  if (showDeleted.value) {
146
251
  await executeHardDelete({ _id: interactionLink._id })
@@ -152,6 +257,85 @@ const deleteInteractionLink = async (interactionLink: InteractionLink) => {
152
257
  $socket.on("interactionLinkCreated", () =>
153
258
  execute({ isDeleted: showDeleted.value }),
154
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
+ }
155
339
  watch(
156
340
  showDeleted,
157
341
  () => {
@@ -0,0 +1,107 @@
1
+ <template>
2
+ <StepNavigator
3
+ :steps
4
+ :is-saving="disabled"
5
+ :show-save-button="isEditing"
6
+ @save="$emit('save')"
7
+ @save-and-finish="$emit('save-and-finish')"
8
+ >
9
+ <!-- @vue-generic {ProjectUpdate} -->
10
+ <DynamicForm
11
+ ref="dynamicFormRef"
12
+ v-model="project"
13
+ :data="projectInputFields"
14
+ :disabled
15
+ :title="$t('workbench.project.formtitle')"
16
+ />
17
+ <DynamicForm
18
+ ref="dynamicFormRef"
19
+ v-model="project.config"
20
+ :data="configInputFields"
21
+ :disabled
22
+ />
23
+ </StepNavigator>
24
+ </template>
25
+
26
+ <script setup lang="ts">
27
+ import type { ProjectUpdate } from "@raclettejs/core"
28
+ import DynamicForm from "@app/components/dynamicForm/DynamicForm.vue"
29
+ import type { DynamicFormItem } from "@app/components/dynamicForm/DynamicFormTypes"
30
+ import { useI18n } from "vue-i18n"
31
+ import { computed, useTemplateRef } from "vue"
32
+ import { Step } from "@app/components/stepNavigator/StepNavigatorTypes"
33
+ import StepNavigator from "@app/components/stepNavigator/StepNavigator.vue"
34
+
35
+ defineProps<{ disabled?: boolean; isEditing?: boolean }>()
36
+
37
+ defineEmits<{
38
+ (e: "save"): void
39
+ (e: "save-and-finish"): void
40
+ }>()
41
+
42
+ const project = defineModel<ProjectUpdate>({ required: true })
43
+
44
+ const dynamicFormRef = useTemplateRef("dynamicFormRef")
45
+
46
+ const { t } = useI18n()
47
+
48
+ const projectInputFields = computed<DynamicFormItem<ProjectUpdate>[]>(() => [
49
+ { divider: t("workbench.project.dividers.general") },
50
+ {
51
+ title: t("workbench.project.title.title"),
52
+ description: t("workbench.project.title.description"),
53
+ inputType: "textarea",
54
+ field: "title",
55
+ prependIcon: "text",
56
+ },
57
+ {
58
+ title: t("workbench.project.description.title"),
59
+ description: t("workbench.project.description.description"),
60
+ inputType: "textarea",
61
+ field: "description",
62
+ prependIcon: "text",
63
+ },
64
+ /* {
65
+ title: t("workbench.interactionLinkConfiguration.tags.title"),
66
+ description: t("workbench.project.tags.description"),
67
+ inputType: "tags",
68
+ field: "tags",
69
+ prependIcon: "tag-multiple",
70
+ },*/
71
+ ])
72
+ const configInputFields = computed<DynamicFormItem<ProjectUpdate["config"]>[]>(
73
+ () => [
74
+ { divider: t("workbench.project.dividers.config") },
75
+ {
76
+ title: t("workbench.project.railPageNavigation.title"),
77
+ description: t("workbench.project.railPageNavigation.description"),
78
+ inputType: "switch",
79
+ field: "railPageNavigation",
80
+ },
81
+ /*{
82
+ title: t("workbench.project.tags.title"),
83
+ description: t("workbench.project.tags.description"),
84
+ inputType: "tags",
85
+ field: "tags",
86
+ prependIcon: "tag-multiple",
87
+ },*/
88
+ ],
89
+ )
90
+ const validateForm = (): boolean => {
91
+ const validateAllFields = dynamicFormRef.value?.validateAllFields
92
+
93
+ if (validateAllFields) {
94
+ const isValid = !!validateAllFields()
95
+
96
+ return isValid
97
+ }
98
+ return true
99
+ }
100
+
101
+ const steps = computed<Step[]>(() => [
102
+ {
103
+ id: "config",
104
+ validator: validateForm,
105
+ },
106
+ ])
107
+ </script>
@@ -0,0 +1,34 @@
1
+ <template>
2
+ <ProjectConfiguration
3
+ v-if="editableProject"
4
+ v-model="editableProject"
5
+ :disabled="isLoading"
6
+ is-editing
7
+ @save="onSubmit"
8
+ />
9
+ </template>
10
+
11
+ <script setup lang="ts">
12
+ import { clone } from "ramda"
13
+ import type { Project } from "@raclettejs/core"
14
+ import { usePluginApi } from "@raclettejs/core/orchestrator/composables"
15
+ import { onMounted, ref, toRaw } from "vue"
16
+ import ProjectConfiguration from "../components/ProjectConfiguration.vue"
17
+
18
+ const props = defineProps<{
19
+ uuid: string
20
+ }>()
21
+
22
+ const { $data } = usePluginApi("raclette__core")
23
+ const { $store } = usePluginApi()
24
+ const project = $store.get(["project"])
25
+
26
+ const { execute: executeUpdate, isLoading } = $data.project.update()
27
+
28
+ const editableProject = ref<Project>()
29
+
30
+ const onSubmit = async () => await executeUpdate(toRaw(editableProject.value))
31
+ onMounted(() => {
32
+ editableProject.value = clone({ config: {}, ...project.value })
33
+ })
34
+ </script>
@@ -0,0 +1,8 @@
1
+ export default {
2
+ name: "project",
3
+ author: "pacifico",
4
+ version: "0.0.1",
5
+ description: "A plugin for project configuration",
6
+ // Specify where the main frontend and backend entry points are
7
+ frontendDir: "./frontend",
8
+ }