@raclettejs/workbench 0.1.30 → 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 +7 -9
  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
@@ -1,303 +0,0 @@
1
- <template>
2
- <div>
3
- <!-- Search and Actions Bar -->
4
- <div class="tw:flex tw:gap-2 tw:items-center tw:mb-6">
5
- <v-text-field
6
- v-model="search"
7
- variant="outlined"
8
- hide-details
9
- prepend-inner-icon="mdi-magnify"
10
- clearable
11
- :placeholder="
12
- $t('workbench.baseDataTable.searchPlaceholder', {
13
- dataName,
14
- })
15
- "
16
- density="compact"
17
- />
18
-
19
- <AppButton
20
- v-if="showCreateButton"
21
- @click="handleCreate"
22
- variant="secondary"
23
- class="!tw:h-10"
24
- >
25
- <template #prepend-icon>
26
- <v-icon icon="mdi-plus-circle-outline" />
27
- </template>
28
- {{
29
- $t("workbench.baseDataTable.createDataButton", {
30
- dataName,
31
- })
32
- }}
33
- </AppButton>
34
-
35
- <!-- Additional action buttons slot -->
36
- <slot name="actions" />
37
- </div>
38
- <!-- Data Table -->
39
- <v-data-table
40
- v-model:search="search"
41
- density="comfortable"
42
- :items-per-page="itemsPerPage"
43
- sticky
44
- fixed-header
45
- :headers="computedHeaders"
46
- :items="items"
47
- :loading="loading"
48
- @click:row="handleRowClick"
49
- :class="{ itemsDeleted }"
50
- >
51
- <template
52
- #item.color="{ item }"
53
- v-if="!customSlots.includes('item.color')"
54
- >
55
- <div class="tw:flex tw:gap-2" v-if="item.color?.length">
56
- <v-chip :color="item.color" label variant="outlined" size="small">
57
- {{ item.color }}
58
- </v-chip>
59
- </div>
60
- </template>
61
- <!-- Built-in tags column -->
62
- <template #item.tags="{ item }" v-if="!customSlots.includes('item.tags')">
63
- <div class="tw:flex tw:gap-2" v-if="item.tags?.length">
64
- <v-chip
65
- v-for="tag in item.tags"
66
- :key="tag._id || tag.id || tag"
67
- :color="tag.color"
68
- label
69
- variant="outlined"
70
- size="small"
71
- >
72
- {{ tag.title || tag.name || tag }}
73
- </v-chip>
74
- </div>
75
- </template>
76
-
77
- <!-- Dynamic slots for custom column rendering -->
78
- <template v-for="slot in customSlots" :key="slot" #[slot]="slotProps">
79
- <slot :name="slot" v-bind="slotProps" />
80
- </template>
81
-
82
- <!-- Actions column template -->
83
- <template #item.actions="{ item }" v-if="showActionsColumn">
84
- <slot name="prepend-row-actions" :item="item"> </slot>
85
- <slot name="row-actions" :item="item">
86
- <!-- Default delete action -->
87
- <v-icon
88
- v-if="showDeleteAction && !isDeleteDisabled(item)"
89
- color="red"
90
- @click.stop.prevent="handleDelete(item)"
91
- class="mr-2"
92
- :title="
93
- $t('workbench.baseDataTable.deleteDataTitle', {
94
- dataName,
95
- })
96
- "
97
- icon="mdi-delete"
98
- />
99
- </slot>
100
- <slot name="append-row-actions" :item="item"> </slot>
101
- </template>
102
-
103
- <!-- Loading template -->
104
- <template #loader>
105
- <v-skeleton-loader
106
- class="mx-auto border"
107
- :height="500"
108
- width="100%"
109
- type="list-item"
110
- :loading="true"
111
- />
112
- </template>
113
-
114
- <!-- Additional table slots (this caused the table to be empty) -->
115
- <!-- <slot name="table-slots" /> -->
116
- </v-data-table>
117
-
118
- <!-- Delete Confirmation Dialog -->
119
- <v-dialog
120
- v-model="deleteDialog"
121
- max-width="400"
122
- v-if="showDeleteAction && itemToDelete"
123
- >
124
- <v-card>
125
- <v-card-title class="text-h6">
126
- {{
127
- $t("workbench.baseDataTable.deleteDataTitle", {
128
- dataName,
129
- })
130
- }}
131
- </v-card-title>
132
-
133
- <v-card-text
134
- v-html="
135
- $t('workbench.baseDataTable.deleteDataConfirmMessage', {
136
- article: dataArticle,
137
- dataName,
138
- value:
139
- itemToDelete.email ||
140
- itemToDelete.title ||
141
- itemToDelete.name ||
142
- itemToDelete._id,
143
- })
144
- "
145
- />
146
- <v-card-actions>
147
- <v-spacer />
148
- <v-btn variant="text" @click="deleteDialog = false">
149
- {{ $t("workbench.baseDataTable.deleteDataConfirmCancel") }}
150
- </v-btn>
151
- <v-btn color="red" variant="flat" @click="confirmDelete">
152
- {{ $t("workbench.baseDataTable.deleteDataConfirmButton") }}
153
- </v-btn>
154
- </v-card-actions>
155
- </v-card>
156
- </v-dialog>
157
-
158
- <!-- Additional dialogs slot -->
159
- <slot name="dialogs" />
160
- </div>
161
- </template>
162
-
163
- <script setup lang="ts" generic="T extends Record<string, any>">
164
- import { computed, ref, useSlots } from "vue"
165
- import { useRouteState } from "@raclettejs/core/orchestrator/composables"
166
- import AppButton from "../../orchestrator/components/layout/AppButton.vue"
167
-
168
- export interface BaseDataTableProps<T> {
169
- uuid?: string
170
- // Data props
171
- items?: T[]
172
- loading?: boolean
173
- itemsDeleted?: boolean
174
- // Table configuration
175
- headers: Array<{
176
- title: string
177
- key: keyof T | string
178
- align?: "start" | "center" | "end"
179
- sortable?: boolean
180
- width?: string | number
181
- }>
182
- itemsPerPage?: number
183
-
184
- // Create button
185
- showCreateButton?: boolean
186
- createInteractionLinkId?: string
187
-
188
- // Row actions
189
- showActionsColumn?: boolean
190
- showDeleteAction?: boolean
191
-
192
- // Navigation
193
- editInteractionLinkId?: string
194
-
195
- // Item processing
196
- deleteValidator?: (item: T) => boolean
197
- rowClickHandler?: (item: T) => void
198
-
199
- dataName: string
200
- dataArticle: string
201
- }
202
-
203
- const props = withDefaults(defineProps<BaseDataTableProps<T>>(), {
204
- uuid: undefined,
205
- items: () => [],
206
- itemsDeleted: false,
207
- itemsPerPage: 50,
208
- showCreateButton: true,
209
- showActionsColumn: true,
210
- showDeleteAction: true,
211
- })
212
-
213
- const emit = defineEmits<{
214
- create: []
215
- edit: [item: T]
216
- delete: [item: T]
217
- rowClick: [item: T]
218
- }>()
219
-
220
- const slots = useSlots()
221
- const { triggerInteractionLinkById } = useRouteState()
222
-
223
- // Reactive data
224
- const search = ref("")
225
- const deleteDialog = ref(false)
226
- const itemToDelete = ref<T | null>(null)
227
-
228
- const computedHeaders = computed(() => {
229
- if (!props.headers || !props.headers?.length) {
230
- return null
231
- }
232
-
233
- const headers = [...props.headers]
234
-
235
- // Add actions column if needed
236
- if (props.showActionsColumn) {
237
- headers.push({
238
- title: "Actions",
239
- key: "actions",
240
- align: "end" as const,
241
- sortable: false,
242
- })
243
- }
244
-
245
- return headers
246
- })
247
-
248
- const customSlots = computed(() => {
249
- return Object.keys(slots).filter(
250
- (slot) => slot.startsWith("item.") && slot !== "item.actions",
251
- )
252
- })
253
-
254
- // Methods
255
- const handleCreate = () => {
256
- if (props.createInteractionLinkId) {
257
- triggerInteractionLinkById(props.createInteractionLinkId)
258
- }
259
- emit("create")
260
- }
261
-
262
- const handleRowClick = (_: PointerEvent, row: { item: T }) => {
263
- if (props.rowClickHandler) {
264
- props.rowClickHandler(row.item)
265
- return
266
- }
267
-
268
- if (props.editInteractionLinkId) {
269
- const item = row.item as any
270
-
271
- triggerInteractionLinkById(props.editInteractionLinkId, {
272
- id: item._id,
273
- })
274
- }
275
-
276
- emit("edit", row.item)
277
- emit("rowClick", row.item)
278
- }
279
-
280
- const handleDelete = (item: T) => {
281
- itemToDelete.value = item
282
- deleteDialog.value = true
283
- }
284
-
285
- const confirmDelete = () => {
286
- if (itemToDelete.value) {
287
- emit("delete", itemToDelete.value)
288
- }
289
-
290
- itemToDelete.value = null
291
- deleteDialog.value = false
292
- }
293
-
294
- const isDeleteDisabled = (item: T): boolean => {
295
- return props.deleteValidator ? !props.deleteValidator(item) : false
296
- }
297
- </script>
298
- <style lang="css">
299
- .itemsDeleted .v-data-table__tr {
300
- background-color: rgb(233, 190, 190);
301
- color: #000;
302
- }
303
- </style>
@@ -1,98 +0,0 @@
1
- <template>
2
- <v-file-input
3
- v-model="files"
4
- :accept="accept"
5
- ref="fileInput"
6
- hide-input
7
- flat
8
- prepend-icon="mdi-database-import"
9
- @update:model-value="handleFileChange"
10
- >
11
- </v-file-input>
12
- </template>
13
-
14
- <script setup lang="ts">
15
- import { ref, useTemplateRef } from "vue"
16
- const fileInput = useTemplateRef("fileInput")
17
- const props = defineProps({
18
- /**
19
- * Function to call when file content is loaded.
20
- * Receives: (content: string | ArrayBuffer, file: File)
21
- */
22
- onFileLoaded: {
23
- type: Function,
24
- required: true,
25
- },
26
-
27
- /**
28
- * Optional: accept only certain file types
29
- */
30
- accept: {
31
- type: String,
32
- default: "*/*",
33
- },
34
-
35
- /**
36
- * How to read the file: "text" or "arrayBuffer"
37
- */
38
- readAs: {
39
- type: String,
40
- default: "text",
41
- validator: (v: string) => ["text", "arrayBuffer"].includes(v),
42
- },
43
- })
44
-
45
- const files = ref<File[]>([])
46
- const trigger = () => {
47
- fileInput.value?.click()
48
- }
49
- const handleFileChange = (newFile: File | null) => {
50
- console.log("File change triggered:", newFile)
51
- console.log(files.value)
52
-
53
- if (!newFile) {
54
- console.log("No file selected")
55
- return
56
- }
57
-
58
- const selectedFile = newFile
59
- if (!selectedFile) {
60
- console.log("Selected file is null")
61
- return
62
- }
63
-
64
- console.log(
65
- "Reading file:",
66
- selectedFile.name,
67
- selectedFile.type,
68
- selectedFile.size,
69
- )
70
-
71
- const reader = new FileReader()
72
-
73
- reader.onload = () => {
74
- console.log(
75
- "File loaded successfully, length:",
76
- (reader.result as string)?.length,
77
- )
78
- // Pass content first, then file
79
- props.onFileLoaded(reader.result, selectedFile)
80
- }
81
-
82
- reader.onerror = () => {
83
- console.error("Error reading file:", reader.error)
84
- }
85
-
86
- reader.onprogress = (e) => {
87
- console.log("Reading progress:", e.loaded, "/", e.total)
88
- }
89
-
90
- // Read the file from the parameter, not from refs
91
- if (props.readAs === "arrayBuffer") {
92
- reader.readAsArrayBuffer(selectedFile)
93
- } else {
94
- reader.readAsText(selectedFile)
95
- }
96
- }
97
- defineExpose({ trigger })
98
- </script>
@@ -1,182 +0,0 @@
1
- <template>
2
- <!-- Dialog overlay -->
3
- <div
4
- v-if="dialog"
5
- class="tw:fixed tw:inset-0 tw:flex tw:items-center tw:justify-center tw:bg-black/50 tw:z-50"
6
- >
7
- <!-- Dialog panel -->
8
- <div
9
- class="tw:bg-white tw:rounded-lg tw:shadow-xl tw:max-w-xl tw:w-full tw:overflow-auto"
10
- >
11
- <!-- Title bar -->
12
- <div
13
- class="tw:flex tw:justify-between tw:items-center tw:px-4 tw:py-3 tw:border-b tw:border-gray-200"
14
- >
15
- <span class="tw:text-lg tw:font-semibold">{{ title }}</span>
16
-
17
- <button
18
- @click="close"
19
- class="tw:text-gray-500 hover:tw:text-gray-700 tw:p-1 tw:rounded tw:hover:bg-gray-100"
20
- >
21
- <i class="mdi mdi-close tw:text-xl"></i>
22
- </button>
23
- </div>
24
-
25
- <!-- Content -->
26
- <div class="tw:px-4 tw:py-4">
27
- <!-- Header row -->
28
- <div class="tw:flex tw:justify-between tw:items-center tw:mb-4">
29
- <!-- Select all -->
30
- <label class="tw:flex tw:items-center tw:space-x-2 tw:cursor-pointer">
31
- <input
32
- type="checkbox"
33
- v-model="selectAll"
34
- @change="toggleSelectAll"
35
- class="tw:w-4 tw:h-4 tw:rounded tw:border-gray-300 tw:text-primary tw:focus:ring-primary"
36
- />
37
- <span class="tw:text-sm tw:text-gray-700">
38
- {{ $t("core.selectAll") }}
39
- </span>
40
- </label>
41
-
42
- <!-- Chip -->
43
- <div
44
- class="tw:px-3 tw:py-1 tw:bg-primary tw:text-white tw:rounded-full tw:text-sm tw:font-medium"
45
- >
46
- {{ selectedCount }} / {{ items.length }} {{ $t("core.selected") }}
47
- </div>
48
- </div>
49
-
50
- <!-- List -->
51
- <ul class="tw:max-h-96 tw:overflow-y-auto tw:space-y-1">
52
- <li
53
- v-for="item in items"
54
- :key="item._id"
55
- @click="toggleSelection(item._id)"
56
- class="tw:flex tw:items-center tw:space-x-3 tw:p-2 tw:rounded tw:cursor-pointer hover:tw:bg-gray-50"
57
- >
58
- <!-- Checkbox -->
59
- <input
60
- type="checkbox"
61
- :checked="selected.includes(item._id)"
62
- @click.stop="toggleSelection(item._id)"
63
- class="tw:w-4 tw:h-4 tw:rounded tw:border-gray-300 tw:text-primary tw:focus:ring-primary"
64
- />
65
-
66
- <!-- Label -->
67
- <span class="tw:text-sm tw:text-gray-800">
68
- {{ getDisplayValue(item) }}
69
- </span>
70
- </li>
71
- </ul>
72
-
73
- <!-- Warning for no selection -->
74
- <div
75
- v-if="selectedCount === 0"
76
- class="tw:mt-4 tw:bg-yellow-50 tw:text-yellow-700 tw:p-3 tw:rounded tw:text-sm"
77
- >
78
- {{ $t("core.noItemsSelected") }}
79
- </div>
80
- </div>
81
-
82
- <!-- Actions -->
83
- <div
84
- class="tw:flex tw:justify-end tw:space-x-2 tw:px-4 tw:py-3 tw:border-t tw:border-gray-200"
85
- >
86
- <button
87
- @click="close"
88
- class="tw:px-4 tw:py-2 tw:text-sm tw:text-gray-700 tw:rounded"
89
- >
90
- {{ $t("core.cancel") }}
91
- </button>
92
-
93
- <button
94
- v-if="selectedCount"
95
- @click="confirm"
96
- :disabled="selectedCount === 0"
97
- class="tw:px-4 tw:py-2 tw:text-sm tw:rounded tw:text-gray-700 tw:bg-primary"
98
- >
99
- {{ confirmText }}
100
- </button>
101
- </div>
102
- </div>
103
- </div>
104
- </template>
105
-
106
- <script setup lang="ts">
107
- import { ref, computed, watch } from "vue"
108
-
109
- interface Props {
110
- modelValue: boolean
111
- items: any[]
112
- displayAttribute: string
113
- title: string
114
- confirmText: string
115
- }
116
-
117
- const props = defineProps<Props>()
118
-
119
- const emit = defineEmits<{
120
- "update:modelValue": [value: boolean]
121
- confirm: [selectedIds: string[]]
122
- }>()
123
-
124
- const dialog = computed({
125
- get: () => props.modelValue,
126
- set: (value) => emit("update:modelValue", value),
127
- })
128
-
129
- const selected = ref<string[]>([])
130
- const selectAll = ref(false)
131
-
132
- const selectedCount = computed(() => selected.value.length)
133
-
134
- const getDisplayValue = (item: any) => {
135
- return item[props.displayAttribute] || item._id
136
- }
137
-
138
- const toggleSelection = (id: string) => {
139
- const index = selected.value.indexOf(id)
140
- if (index > -1) {
141
- selected.value.splice(index, 1)
142
- } else {
143
- selected.value.push(id)
144
- }
145
- updateSelectAllState()
146
- }
147
-
148
- const toggleSelectAll = (value: boolean) => {
149
- if (value) {
150
- selected.value = props.items.map((item) => item._id)
151
- } else {
152
- selected.value = []
153
- }
154
- }
155
-
156
- const updateSelectAllState = () => {
157
- selectAll.value =
158
- selected.value.length === props.items.length && props.items.length > 0
159
- }
160
-
161
- const close = () => {
162
- dialog.value = false
163
- selected.value = []
164
- selectAll.value = false
165
- }
166
-
167
- const confirm = () => {
168
- if (selectedCount.value > 0) {
169
- emit("confirm", selected.value)
170
- close()
171
- }
172
- }
173
-
174
- // Initialize selectAll state when items change
175
- watch(
176
- () => props.items,
177
- () => {
178
- updateSelectAllState()
179
- },
180
- { immediate: true },
181
- )
182
- </script>