@raclettejs/workbench 0.1.32 → 0.1.33-canary.1
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.
- package/.raclette/backend/raclette.config.js +2 -2
- package/.raclette/backend/yarn.lock +71 -80
- package/.raclette/frontend/raclette.config.js +2 -2
- package/.raclette/frontend/yarn.lock +517 -471
- package/.raclette/virtual/backend/raclette.config.js +2 -2
- package/.raclette/virtual/backend/yarn.lock +71 -80
- package/.raclette/virtual/frontend/raclette.config.js +2 -2
- package/.raclette/virtual/frontend/yarn.lock +517 -471
- package/CHANGELOG.md +2 -0
- package/i18n/de-DE.json +1 -10
- package/i18n/en-EU.json +1 -10
- package/i18n/sk-SK.json +1 -10
- package/package.json +5 -3
- package/plugins/pacifico__compositions/frontend/widgets/CompositionListWidget.vue +63 -107
- package/plugins/pacifico__interactionLinks/frontend/widgets/InteractionLinkListWidget.vue +64 -122
- package/plugins/pacifico__plugins/frontend/widgets/PluginListWidget.vue +2 -2
- package/plugins/pacifico__tags/frontend/widgets/TagListWidget.vue +47 -104
- package/plugins/pacifico__users/frontend/widgets/UserListWidget.vue +24 -16
- package/services/frontend/src/app/lib/jsonTools.ts +69 -3
- package/services/frontend/src/app/components/BaseDataTable.vue +0 -303
- package/services/frontend/src/app/components/FileUpload.vue +0 -98
- package/services/frontend/src/app/components/SelectionDialog.vue +0 -182
- package/services/frontend/src/app/components/SummaryDialog.vue +0 -140
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
<div>
|
|
3
3
|
<BaseDataTable
|
|
4
4
|
:items="parsedTags"
|
|
5
|
+
:exportable-items="exportableTags"
|
|
5
6
|
:loading="tagsLoading"
|
|
6
7
|
:headers="headers"
|
|
7
8
|
create-interaction-link-id="tagCreateInteractionLink"
|
|
@@ -9,16 +10,17 @@
|
|
|
9
10
|
:data-name="$t('workbench.tagList.dataName')"
|
|
10
11
|
:data-article="$t('workbench.tagList.dataArticle')"
|
|
11
12
|
@delete="deleteTag"
|
|
12
|
-
:
|
|
13
|
+
:items-deleted="showDeleted"
|
|
14
|
+
:on-file-loaded="handleFileUpload"
|
|
13
15
|
>
|
|
14
|
-
<template #
|
|
16
|
+
<template #toolbar-leading>
|
|
15
17
|
<v-tooltip
|
|
16
18
|
:text="showDeleted ? $t('core.hideDeleted') : $t('core.showDeleted')"
|
|
17
19
|
>
|
|
18
|
-
<template
|
|
20
|
+
<template #activator="{ props: activatorProps }">
|
|
19
21
|
<v-btn-toggle
|
|
20
22
|
density="compact"
|
|
21
|
-
v-bind="
|
|
23
|
+
v-bind="activatorProps"
|
|
22
24
|
v-model="showDeleted"
|
|
23
25
|
mandatory
|
|
24
26
|
:color="showDeleted ? 'error' : 'success'"
|
|
@@ -30,26 +32,6 @@
|
|
|
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
|
<template #prepend-row-actions="{ item }">
|
|
55
37
|
<v-icon
|
|
@@ -66,18 +48,8 @@
|
|
|
66
48
|
/>
|
|
67
49
|
</template>
|
|
68
50
|
</BaseDataTable>
|
|
69
|
-
<!-- Export Selection Dialog -->
|
|
70
|
-
<SelectionDialog
|
|
71
|
-
v-model="showExportDialog"
|
|
72
|
-
:items="exportableItems"
|
|
73
|
-
display-attribute="title"
|
|
74
|
-
:title="$t('core.selectItemsToExport')"
|
|
75
|
-
:confirm-text="$t('core.export')"
|
|
76
|
-
@confirm="handleExportConfirm"
|
|
77
|
-
/>
|
|
78
|
-
|
|
79
51
|
<!-- Import Selection Dialog -->
|
|
80
|
-
<
|
|
52
|
+
<ImportSelectionDialog
|
|
81
53
|
v-model="showImportDialog"
|
|
82
54
|
:items="importableItems"
|
|
83
55
|
display-attribute="title"
|
|
@@ -86,18 +58,8 @@
|
|
|
86
58
|
@confirm="handleImportConfirm"
|
|
87
59
|
/>
|
|
88
60
|
|
|
89
|
-
<!-- Export Summary Dialog -->
|
|
90
|
-
<SummaryDialog
|
|
91
|
-
v-model="showExportSummary"
|
|
92
|
-
display-attribute="title"
|
|
93
|
-
:items="exportedItems"
|
|
94
|
-
:title="$t('core.exportSummary')"
|
|
95
|
-
:message="$t('core.exportSuccessMessage')"
|
|
96
|
-
type="success"
|
|
97
|
-
/>
|
|
98
|
-
|
|
99
61
|
<!-- Import Summary Dialog -->
|
|
100
|
-
<
|
|
62
|
+
<ImportSummaryDialog
|
|
101
63
|
v-model="showImportSummary"
|
|
102
64
|
display-attribute="title"
|
|
103
65
|
:items="importedItems"
|
|
@@ -113,30 +75,26 @@
|
|
|
113
75
|
|
|
114
76
|
<script setup lang="ts">
|
|
115
77
|
import { usePluginApi } from "@raclettejs/core/orchestrator/composables"
|
|
116
|
-
import { computed, ref, watch
|
|
78
|
+
import { computed, ref, watch } from "vue"
|
|
117
79
|
import type { Tag } from "@raclettejs/core"
|
|
118
80
|
import { useI18n } from "vue-i18n"
|
|
119
|
-
import BaseDataTable from "@
|
|
81
|
+
import BaseDataTable from "@raclettejs/core/orchestrator/components/dataTable/BaseDataTable.vue"
|
|
120
82
|
import { formatDistance } from "date-fns"
|
|
121
|
-
import {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
83
|
+
import {
|
|
84
|
+
mapJsonValues,
|
|
85
|
+
parseAndNormalizeWorkbenchImportJson,
|
|
86
|
+
resolveWorkbenchImportSelection,
|
|
87
|
+
} from "@app/lib/jsonTools"
|
|
88
|
+
import ImportSelectionDialog from "@raclettejs/core/orchestrator/components/dataImport/ImportSelectionDialog.vue"
|
|
89
|
+
import ImportSummaryDialog from "@raclettejs/core/orchestrator/components/dataImport/SummaryDImportSummaryDialogialog.vue"
|
|
125
90
|
const showDeleted = ref(false)
|
|
126
91
|
const { t } = useI18n()
|
|
127
|
-
// Dialog states
|
|
128
|
-
const showExportDialog = ref(false)
|
|
129
92
|
const showImportDialog = ref(false)
|
|
130
|
-
const showExportSummary = ref(false)
|
|
131
93
|
const showImportSummary = ref(false)
|
|
132
94
|
|
|
133
|
-
// Data for dialogs
|
|
134
|
-
const exportableItems = ref<any[]>([])
|
|
135
95
|
const importableItems = ref<any[]>([])
|
|
136
|
-
const exportedItems = ref<any[]>([])
|
|
137
96
|
const importedItems = ref<any[]>([])
|
|
138
|
-
const
|
|
139
|
-
const uploadRef = useTemplateRef("uploadRef")
|
|
97
|
+
const pendingImportById = ref<Record<string, any>>({})
|
|
140
98
|
const headers = computed<{ title: string; key: keyof Tag }[]>(() => [
|
|
141
99
|
{
|
|
142
100
|
title: t("workbench.tagList.tableHeaders.title"),
|
|
@@ -183,16 +141,23 @@ const { execute: executeHardDelete } = await $data.tag.deleteHard({
|
|
|
183
141
|
const { execute: executeRestore } = await $data.tag.updateRestore({
|
|
184
142
|
options: { cb: () => execute({ isDeleted: showDeleted.value }) },
|
|
185
143
|
})
|
|
186
|
-
const
|
|
187
|
-
if (tags.value) {
|
|
188
|
-
return
|
|
189
|
-
...tag,
|
|
190
|
-
tags: tag?.tags?.map((t) => tags.value?.[t]) ?? [],
|
|
191
|
-
updatedAt: formatDistance(new Date(tag.updatedAt), new Date()),
|
|
192
|
-
}))
|
|
144
|
+
const tagTableRows = (relativeUpdatedAt: boolean) => {
|
|
145
|
+
if (!tags.value) {
|
|
146
|
+
return []
|
|
193
147
|
}
|
|
194
|
-
|
|
195
|
-
})
|
|
148
|
+
|
|
149
|
+
return Object.values(tags.value as { [key: string]: Tag }).map((tag) => ({
|
|
150
|
+
...tag,
|
|
151
|
+
tags: tag?.tags?.map((t) => tags.value?.[t]) ?? [],
|
|
152
|
+
...(relativeUpdatedAt
|
|
153
|
+
? { updatedAt: formatDistance(new Date(tag.updatedAt), new Date()) }
|
|
154
|
+
: {}),
|
|
155
|
+
}))
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const parsedTags = computed(() => tagTableRows(true))
|
|
159
|
+
|
|
160
|
+
const exportableTags = computed(() => tagTableRows(false))
|
|
196
161
|
|
|
197
162
|
const deleteTag = async (tag: Tag) => {
|
|
198
163
|
if (showDeleted.value) {
|
|
@@ -201,48 +166,26 @@ const deleteTag = async (tag: Tag) => {
|
|
|
201
166
|
await executeDelete({ _id: tag._id })
|
|
202
167
|
}
|
|
203
168
|
}
|
|
204
|
-
const openExportDialog = () => {
|
|
205
|
-
const allTags = Object.values(tags.value || {})
|
|
206
|
-
const exportableTags = []
|
|
207
|
-
allTags.forEach((tag) => {
|
|
208
|
-
if (!tag.locked) {
|
|
209
|
-
exportableTags.push(tag)
|
|
210
|
-
}
|
|
211
|
-
})
|
|
212
|
-
exportableItems.value = exportableTags
|
|
213
|
-
showExportDialog.value = true
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const handleExportConfirm = (selectedIds: string[]) => {
|
|
217
|
-
const dataToExport = selectedIds.reduce((acc, id) => {
|
|
218
|
-
if (tags.value?.[id]) {
|
|
219
|
-
acc[id] = tags.value[id]
|
|
220
|
-
}
|
|
221
|
-
return acc
|
|
222
|
-
}, {} as any)
|
|
223
|
-
|
|
224
|
-
exportedItems.value = Object.values(dataToExport)
|
|
225
|
-
downloadJson(dataToExport, "tags")
|
|
226
|
-
showExportSummary.value = true
|
|
227
|
-
}
|
|
228
|
-
|
|
229
169
|
// Import functions
|
|
230
|
-
const handleFileUpload = (content: string
|
|
170
|
+
const handleFileUpload = (content: string | ArrayBuffer) => {
|
|
171
|
+
if (typeof content !== "string") {
|
|
172
|
+
return
|
|
173
|
+
}
|
|
231
174
|
try {
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
importableItems.value = itemsArray
|
|
236
|
-
pendingImportData.value = parsedData
|
|
175
|
+
const { items, byId } = parseAndNormalizeWorkbenchImportJson(content)
|
|
176
|
+
importableItems.value = items
|
|
177
|
+
pendingImportById.value = byId
|
|
237
178
|
showImportDialog.value = true
|
|
238
179
|
} catch (error) {
|
|
239
|
-
console.error("Failed to
|
|
180
|
+
console.error("Failed to prepare import:", error)
|
|
240
181
|
}
|
|
241
182
|
}
|
|
242
183
|
|
|
243
184
|
const handleImportConfirm = (selectedIds: string[]) => {
|
|
244
|
-
|
|
245
|
-
|
|
185
|
+
importedItems.value = resolveWorkbenchImportSelection(
|
|
186
|
+
pendingImportById.value,
|
|
187
|
+
selectedIds,
|
|
188
|
+
)
|
|
246
189
|
showImportSummary.value = true
|
|
247
190
|
}
|
|
248
191
|
|
|
@@ -264,7 +207,7 @@ const finalizeImport = async () => {
|
|
|
264
207
|
// e.g., call an API endpoint to save the imported compositions
|
|
265
208
|
|
|
266
209
|
// Reset states
|
|
267
|
-
|
|
210
|
+
pendingImportById.value = {}
|
|
268
211
|
importableItems.value = []
|
|
269
212
|
importedItems.value = []
|
|
270
213
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<BaseDataTable
|
|
3
3
|
:items="parsedUsers"
|
|
4
|
+
:exportable-items="exportableUsers"
|
|
4
5
|
:loading="usersLoading || tagsLoading"
|
|
5
6
|
:headers="headers"
|
|
6
7
|
create-interaction-link-id="userCreateInteractionLink"
|
|
@@ -9,16 +10,16 @@
|
|
|
9
10
|
:data-article="$t('workbench.userList.dataArticle')"
|
|
10
11
|
:delete-validator="(user) => user._id !== $user?._id"
|
|
11
12
|
@delete="deleteUser"
|
|
12
|
-
:
|
|
13
|
+
:items-deleted="showDeleted"
|
|
13
14
|
>
|
|
14
|
-
<template #
|
|
15
|
+
<template #toolbar-leading>
|
|
15
16
|
<v-tooltip
|
|
16
17
|
:text="showDeleted ? $t('core.hideDeleted') : $t('core.showDeleted')"
|
|
17
18
|
>
|
|
18
|
-
<template
|
|
19
|
+
<template #activator="{ props: activatorProps }">
|
|
19
20
|
<v-btn-toggle
|
|
20
21
|
density="compact"
|
|
21
|
-
v-bind="
|
|
22
|
+
v-bind="activatorProps"
|
|
22
23
|
v-model="showDeleted"
|
|
23
24
|
mandatory
|
|
24
25
|
:color="showDeleted ? 'error' : 'success'"
|
|
@@ -54,7 +55,7 @@ import { computed, watch, ref } from "vue"
|
|
|
54
55
|
import type { User } from "@raclettejs/core"
|
|
55
56
|
import { formatDistance } from "date-fns"
|
|
56
57
|
import { useI18n } from "vue-i18n"
|
|
57
|
-
import BaseDataTable from "@
|
|
58
|
+
import BaseDataTable from "@raclettejs/core/orchestrator/components/dataTable/BaseDataTable.vue"
|
|
58
59
|
const showDeleted = ref(false)
|
|
59
60
|
|
|
60
61
|
const props = defineProps<{
|
|
@@ -102,18 +103,25 @@ const { execute: executeHardDelete } = await $data.user.deleteHard({
|
|
|
102
103
|
const { execute: executeRestore } = await $data.user.updateRestore({
|
|
103
104
|
options: { cb: () => execute({ isDeleted: showDeleted.value }) },
|
|
104
105
|
})
|
|
105
|
-
const
|
|
106
|
-
if (users.value
|
|
107
|
-
return
|
|
108
|
-
(user) => ({
|
|
109
|
-
...user,
|
|
110
|
-
tags: user.tags.map((tag) => tags.value?.[tag]),
|
|
111
|
-
updatedAt: formatDistance(new Date(user.updatedAt), new Date()),
|
|
112
|
-
}),
|
|
113
|
-
)
|
|
106
|
+
const userTableRows = (relativeUpdatedAt: boolean) => {
|
|
107
|
+
if (!users.value || !tags.value) {
|
|
108
|
+
return []
|
|
114
109
|
}
|
|
115
|
-
|
|
116
|
-
})
|
|
110
|
+
|
|
111
|
+
return Object.values(users.value as { [key: string]: User }).map(
|
|
112
|
+
(user) => ({
|
|
113
|
+
...user,
|
|
114
|
+
tags: user.tags.map((tag) => tags.value?.[tag]),
|
|
115
|
+
...(relativeUpdatedAt
|
|
116
|
+
? { updatedAt: formatDistance(new Date(user.updatedAt), new Date()) }
|
|
117
|
+
: {}),
|
|
118
|
+
}),
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const parsedUsers = computed(() => userTableRows(true))
|
|
123
|
+
|
|
124
|
+
const exportableUsers = computed(() => userTableRows(false))
|
|
117
125
|
const deleteUser = async (user: User) => {
|
|
118
126
|
if (showDeleted.value) {
|
|
119
127
|
await executeHardDelete({ _id: user._id })
|
|
@@ -1,10 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parsed workbench list import: rows for {@link SelectionDialog} and a map by `_id` for confirm.
|
|
3
|
+
*/
|
|
4
|
+
export interface WorkbenchImportNormalization {
|
|
5
|
+
items: Record<string, unknown>[]
|
|
6
|
+
byId: Record<string, Record<string, unknown>>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Parses JSON from a table export (array of records with `_id`) into dialog state.
|
|
11
|
+
* Rejects non-arrays so imports match the current export format only.
|
|
12
|
+
*/
|
|
13
|
+
export const parseAndNormalizeWorkbenchImportJson = (
|
|
14
|
+
content: string,
|
|
15
|
+
): WorkbenchImportNormalization => {
|
|
16
|
+
let parsed: unknown
|
|
17
|
+
try {
|
|
18
|
+
parsed = JSON.parse(content)
|
|
19
|
+
} catch {
|
|
20
|
+
throw new Error("Invalid JSON file.")
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!Array.isArray(parsed)) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
"Imported JSON must be an array of records (export from the data table).",
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const byId: Record<string, Record<string, unknown>> = {}
|
|
30
|
+
const items: Record<string, unknown>[] = []
|
|
31
|
+
|
|
32
|
+
for (const entry of parsed) {
|
|
33
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
34
|
+
continue
|
|
35
|
+
}
|
|
36
|
+
const row = entry as Record<string, unknown>
|
|
37
|
+
const id = row._id
|
|
38
|
+
if (id === undefined || id === null || id === "") {
|
|
39
|
+
continue
|
|
40
|
+
}
|
|
41
|
+
const sid = String(id)
|
|
42
|
+
byId[sid] = row
|
|
43
|
+
items.push(row)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!items.length) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
"No importable records found. Each entry must be an object with an _id.",
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return { items, byId }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Resolve selected `_id`s using the `byId` map from {@link parseAndNormalizeWorkbenchImportJson}. */
|
|
56
|
+
export const resolveWorkbenchImportSelection = <
|
|
57
|
+
T extends Record<string, unknown>,
|
|
58
|
+
>(
|
|
59
|
+
byId: Record<string, T>,
|
|
60
|
+
selectedIds: string[],
|
|
61
|
+
): T[] =>
|
|
62
|
+
selectedIds
|
|
63
|
+
.map((id) => byId[id])
|
|
64
|
+
.filter((item): item is T => item != null)
|
|
65
|
+
|
|
1
66
|
/**
|
|
2
67
|
* Downloads a JSON object as a .json file
|
|
3
68
|
* @param {Object|Array} data - The data to be downloaded as JSON
|
|
4
69
|
* @param {string} filename - The name of the file (without .json extension)
|
|
5
70
|
* @throws {Error} If data cannot be serialized to JSON
|
|
6
71
|
*/
|
|
7
|
-
export
|
|
72
|
+
export const downloadJson = (data, filename = "download") => {
|
|
8
73
|
try {
|
|
9
74
|
// Convert data to JSON string with pretty formatting
|
|
10
75
|
const jsonString = JSON.stringify(data, null, 2)
|
|
@@ -33,7 +98,8 @@ export function downloadJson(data, filename = "download") {
|
|
|
33
98
|
URL.revokeObjectURL(url)
|
|
34
99
|
} catch (error) {
|
|
35
100
|
console.error("Failed to download JSON:", error)
|
|
36
|
-
|
|
101
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
102
|
+
throw new Error(`Unable to download JSON file: ${message}`)
|
|
37
103
|
}
|
|
38
104
|
}
|
|
39
105
|
/**
|
|
@@ -49,7 +115,7 @@ export function downloadJson(data, filename = "download") {
|
|
|
49
115
|
* const result = mapJsonValues(data, mapping);
|
|
50
116
|
* // Result: { owner: 'user456', name: 'Project A', lastEditor: 'user789' }
|
|
51
117
|
*/
|
|
52
|
-
export
|
|
118
|
+
export const mapJsonValues = (data, mapping = {}) => {
|
|
53
119
|
if (!data || typeof data !== "object") {
|
|
54
120
|
return data
|
|
55
121
|
}
|