@raclettejs/workbench 0.1.31 → 0.1.32-nightly-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 +515 -0
- package/.raclette/backend/yarn.lock +3636 -0
- package/.raclette/frontend/raclette.config.js +43 -0
- package/.raclette/frontend/yarn.lock +3388 -0
- package/.raclette/virtual/backend/README.md +172 -0
- package/.raclette/virtual/backend/raclette.config.js +515 -0
- package/.raclette/virtual/backend/yarn.lock +3636 -0
- package/.raclette/virtual/frontend/README.md +511 -0
- package/.raclette/virtual/frontend/raclette.config.js +43 -0
- package/.raclette/virtual/frontend/src/core/lib/eggs/readme.md +75 -0
- package/.raclette/virtual/frontend/yarn.lock +3388 -0
- package/CHANGELOG.md +6 -10
- 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 +65 -122
- package/plugins/pacifico__plugins/frontend/widgets/PluginListWidget.vue +2 -2
- package/plugins/pacifico__tags/frontend/widgets/TagListWidget.vue +48 -104
- package/plugins/pacifico__users/frontend/widgets/UserListWidget.vue +22 -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
|
@@ -8,20 +8,20 @@
|
|
|
8
8
|
:show-delete-action="false"
|
|
9
9
|
:show-actions-column="false"
|
|
10
10
|
:row-click-handler="onRowClick"
|
|
11
|
+
:show-export="false"
|
|
11
12
|
>
|
|
12
13
|
</BaseDataTable>
|
|
13
14
|
</template>
|
|
14
15
|
|
|
15
16
|
<script setup lang="ts">
|
|
16
17
|
import useWorkbenchPlugins from "@app/composables/useWorkbenchPlugins"
|
|
17
|
-
import BaseDataTable from "@
|
|
18
|
+
import { BaseDataTable } from "@raclettejs/core/orchestrator/components"
|
|
18
19
|
import type { PluginMetadata } from "@raclettejs/core"
|
|
19
20
|
import { computed } from "vue"
|
|
20
21
|
import {
|
|
21
22
|
usePluginApi,
|
|
22
23
|
useRouteState,
|
|
23
24
|
} from "@raclettejs/core/orchestrator/composables"
|
|
24
|
-
import { DEFAULT_SLOT_NAME } from "@raclettejs/core/orchestrator/router/routeParserHelper"
|
|
25
25
|
|
|
26
26
|
const { pluginMetadataFiles, hasPluginSettingsWidget } = useWorkbenchPlugins()
|
|
27
27
|
|
|
@@ -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,27 @@
|
|
|
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"
|
|
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"
|
|
89
|
+
import { ImportSummaryDialog } from "@raclettejs/core/orchestrator/components"
|
|
90
|
+
|
|
125
91
|
const showDeleted = ref(false)
|
|
126
92
|
const { t } = useI18n()
|
|
127
|
-
// Dialog states
|
|
128
|
-
const showExportDialog = ref(false)
|
|
129
93
|
const showImportDialog = ref(false)
|
|
130
|
-
const showExportSummary = ref(false)
|
|
131
94
|
const showImportSummary = ref(false)
|
|
132
95
|
|
|
133
|
-
// Data for dialogs
|
|
134
|
-
const exportableItems = ref<any[]>([])
|
|
135
96
|
const importableItems = ref<any[]>([])
|
|
136
|
-
const exportedItems = ref<any[]>([])
|
|
137
97
|
const importedItems = ref<any[]>([])
|
|
138
|
-
const
|
|
139
|
-
const uploadRef = useTemplateRef("uploadRef")
|
|
98
|
+
const pendingImportById = ref<Record<string, any>>({})
|
|
140
99
|
const headers = computed<{ title: string; key: keyof Tag }[]>(() => [
|
|
141
100
|
{
|
|
142
101
|
title: t("workbench.tagList.tableHeaders.title"),
|
|
@@ -183,16 +142,23 @@ const { execute: executeHardDelete } = await $data.tag.deleteHard({
|
|
|
183
142
|
const { execute: executeRestore } = await $data.tag.updateRestore({
|
|
184
143
|
options: { cb: () => execute({ isDeleted: showDeleted.value }) },
|
|
185
144
|
})
|
|
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
|
-
}))
|
|
145
|
+
const tagTableRows = (relativeUpdatedAt: boolean) => {
|
|
146
|
+
if (!tags.value) {
|
|
147
|
+
return []
|
|
193
148
|
}
|
|
194
|
-
|
|
195
|
-
})
|
|
149
|
+
|
|
150
|
+
return Object.values(tags.value as { [key: string]: Tag }).map((tag) => ({
|
|
151
|
+
...tag,
|
|
152
|
+
tags: tag?.tags?.map((t) => tags.value?.[t]) ?? [],
|
|
153
|
+
...(relativeUpdatedAt
|
|
154
|
+
? { updatedAt: formatDistance(new Date(tag.updatedAt), new Date()) }
|
|
155
|
+
: {}),
|
|
156
|
+
}))
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const parsedTags = computed(() => tagTableRows(true))
|
|
160
|
+
|
|
161
|
+
const exportableTags = computed(() => tagTableRows(false))
|
|
196
162
|
|
|
197
163
|
const deleteTag = async (tag: Tag) => {
|
|
198
164
|
if (showDeleted.value) {
|
|
@@ -201,48 +167,26 @@ const deleteTag = async (tag: Tag) => {
|
|
|
201
167
|
await executeDelete({ _id: tag._id })
|
|
202
168
|
}
|
|
203
169
|
}
|
|
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
170
|
// Import functions
|
|
230
|
-
const handleFileUpload = (content: string
|
|
171
|
+
const handleFileUpload = (content: string | ArrayBuffer) => {
|
|
172
|
+
if (typeof content !== "string") {
|
|
173
|
+
return
|
|
174
|
+
}
|
|
231
175
|
try {
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
importableItems.value = itemsArray
|
|
236
|
-
pendingImportData.value = parsedData
|
|
176
|
+
const { items, byId } = parseAndNormalizeWorkbenchImportJson(content)
|
|
177
|
+
importableItems.value = items
|
|
178
|
+
pendingImportById.value = byId
|
|
237
179
|
showImportDialog.value = true
|
|
238
180
|
} catch (error) {
|
|
239
|
-
console.error("Failed to
|
|
181
|
+
console.error("Failed to prepare import:", error)
|
|
240
182
|
}
|
|
241
183
|
}
|
|
242
184
|
|
|
243
185
|
const handleImportConfirm = (selectedIds: string[]) => {
|
|
244
|
-
|
|
245
|
-
|
|
186
|
+
importedItems.value = resolveWorkbenchImportSelection(
|
|
187
|
+
pendingImportById.value,
|
|
188
|
+
selectedIds,
|
|
189
|
+
)
|
|
246
190
|
showImportSummary.value = true
|
|
247
191
|
}
|
|
248
192
|
|
|
@@ -264,7 +208,7 @@ const finalizeImport = async () => {
|
|
|
264
208
|
// e.g., call an API endpoint to save the imported compositions
|
|
265
209
|
|
|
266
210
|
// Reset states
|
|
267
|
-
|
|
211
|
+
pendingImportById.value = {}
|
|
268
212
|
importableItems.value = []
|
|
269
213
|
importedItems.value = []
|
|
270
214
|
}
|
|
@@ -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"
|
|
58
59
|
const showDeleted = ref(false)
|
|
59
60
|
|
|
60
61
|
const props = defineProps<{
|
|
@@ -102,18 +103,23 @@ 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((user) => ({
|
|
112
|
+
...user,
|
|
113
|
+
tags: user.tags.map((tag) => tags.value?.[tag]),
|
|
114
|
+
...(relativeUpdatedAt
|
|
115
|
+
? { updatedAt: formatDistance(new Date(user.updatedAt), new Date()) }
|
|
116
|
+
: {}),
|
|
117
|
+
}))
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const parsedUsers = computed(() => userTableRows(true))
|
|
121
|
+
|
|
122
|
+
const exportableUsers = computed(() => userTableRows(false))
|
|
117
123
|
const deleteUser = async (user: User) => {
|
|
118
124
|
if (showDeleted.value) {
|
|
119
125
|
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
|
}
|