@raclettejs/workbench 0.1.33-canary.3 → 0.1.34

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 (32) hide show
  1. package/CHANGELOG.md +12 -3
  2. package/i18n/de-DE.json +15 -0
  3. package/i18n/en-EU.json +15 -0
  4. package/i18n/sk-SK.json +15 -0
  5. package/package.json +5 -4
  6. package/plugins/pacifico__compositions/frontend/widgets/CompositionListWidget.vue +39 -21
  7. package/plugins/pacifico__interactionLinks/frontend/widgets/InteractionLinkListWidget.vue +39 -21
  8. package/plugins/pacifico__plugins/frontend/index.ts +3 -3
  9. package/plugins/pacifico__plugins/frontend/widgets/PluginListWidget.vue +11 -12
  10. package/plugins/pacifico__tags/frontend/widgets/TagListWidget.vue +39 -21
  11. package/plugins/pacifico__users/frontend/widgets/UserListWidget.vue +79 -57
  12. package/services/frontend/src/app/components/dynamicForm/DynamicForm.vue +139 -51
  13. package/services/frontend/src/app/components/dynamicForm/DynamicInput.vue +95 -5
  14. package/services/frontend/src/app/components/dynamicForm/ObjectListInput.vue +78 -41
  15. package/services/frontend/src/app/components/dynamicForm/TextDivider.vue +46 -6
  16. package/services/frontend/src/app/components/dynamicForm/typedInputs/ListInput.vue +118 -27
  17. package/services/frontend/src/app/components/dynamicForm/typedInputs/RecordInput.vue +131 -21
  18. package/services/frontend/src/app/components/dynamicForm/typedInputs/TagsInput.vue +1 -0
  19. package/services/frontend/src/app/components/dynamicForm/typedInputs/TriggerInput.vue +4 -4
  20. package/services/frontend/src/app/composables/useWorkbenchTableActions.ts +29 -0
  21. package/services/frontend/src/orchestrator/assets/styles/themes/light.ts +4 -3
  22. package/.raclette/backend/raclette.config.js +0 -516
  23. package/.raclette/backend/yarn.lock +0 -3629
  24. package/.raclette/frontend/raclette.config.js +0 -46
  25. package/.raclette/frontend/yarn.lock +0 -3333
  26. package/.raclette/virtual/backend/README.md +0 -172
  27. package/.raclette/virtual/backend/raclette.config.js +0 -516
  28. package/.raclette/virtual/backend/yarn.lock +0 -3629
  29. package/.raclette/virtual/frontend/README.md +0 -511
  30. package/.raclette/virtual/frontend/raclette.config.js +0 -46
  31. package/.raclette/virtual/frontend/src/core/lib/eggs/readme.md +0 -75
  32. package/.raclette/virtual/frontend/yarn.lock +0 -3333
@@ -1,52 +1,70 @@
1
1
  <template>
2
- <BaseDataTable
3
- :items="parsedUsers"
4
- :exportable-items="exportableUsers"
5
- :loading="usersLoading || tagsLoading"
6
- :headers="headers"
7
- create-interaction-link-id="userCreateInteractionLink"
8
- edit-interaction-link-id="userEditInteractionLink"
9
- :data-name="$t('workbench.userList.dataName')"
10
- :data-article="$t('workbench.userList.dataArticle')"
11
- :delete-validator="(user) => user._id !== $user?._id"
12
- @delete="deleteUser"
13
- :items-deleted="showDeleted"
14
- >
15
- <template #toolbar-leading>
16
- <v-tooltip
17
- :text="showDeleted ? $t('core.hideDeleted') : $t('core.showDeleted')"
18
- >
19
- <template #activator="{ props: activatorProps }">
20
- <v-btn-toggle
21
- density="compact"
22
- v-bind="activatorProps"
23
- v-model="showDeleted"
24
- mandatory
25
- :color="showDeleted ? 'error' : 'success'"
26
- >
27
- <v-btn :value="true">
28
- <v-icon>mdi-table-eye-off</v-icon>
29
- </v-btn>
30
- <v-btn :value="false"><v-icon>mdi-table-eye</v-icon></v-btn>
31
- </v-btn-toggle>
32
- </template>
33
- </v-tooltip>
34
- </template>
35
- <template #prepend-row-actions="{ item }">
36
- <v-icon
37
- v-if="showDeleted"
38
- color="green"
39
- class="mr-2"
40
- @click.stop.prevent="restore(item._id)"
41
- :title="
42
- $t('workbench.baseDataTable.restoreDataTitle', {
43
- dataName: item.title,
44
- })
45
- "
46
- icon="mdi-delete-restore"
47
- />
48
- </template>
49
- </BaseDataTable>
2
+ <div class="tw:p-4">
3
+ <BaseDataTable
4
+ :items="parsedUsers"
5
+ :exportable-items="exportableUsers"
6
+ :loading="usersLoading || tagsLoading"
7
+ :headers="headers"
8
+ :row-click-handler="handleRowClick"
9
+ :data-name="$t('workbench.userList.dataName')"
10
+ :show-filters="true"
11
+ :items-deleted="showDeleted"
12
+ >
13
+ <template #toolbar-end>
14
+ <v-btn
15
+ prepend-icon="mdi-plus"
16
+ variant="flat"
17
+ color="success"
18
+ :text="
19
+ $t('core.baseDataTable.createDataButton', {
20
+ dataName: $t('workbench.userList.dataName'),
21
+ })
22
+ "
23
+ @click="goToCreate('userCreateInteractionLink')"
24
+ />
25
+ </template>
26
+ <template #toolbar-actions-menu>
27
+ <v-list-item
28
+ :title="showDeleted ? $t('core.hideDeleted') : $t('core.showDeleted')"
29
+ prepend-icon="mdi-delete-outline"
30
+ >
31
+ <template #append>
32
+ <v-switch
33
+ v-model="showDeleted"
34
+ color="error"
35
+ density="compact"
36
+ hide-details
37
+ inset
38
+ />
39
+ </template>
40
+ </v-list-item>
41
+ </template>
42
+ <template #prepend-row-actions="{ item }">
43
+ <v-icon
44
+ v-if="showDeleted"
45
+ color="green"
46
+ class="mr-2"
47
+ @click.stop.prevent="restore(item._id)"
48
+ :title="
49
+ $t('workbench.baseDataTable.restoreDataTitle', {
50
+ dataName: item.title,
51
+ })
52
+ "
53
+ icon="mdi-delete-restore"
54
+ />
55
+ </template>
56
+ <template #row-actions="{ item }">
57
+ <v-btn
58
+ v-if="item._id !== $user?._id"
59
+ icon="mdi-delete"
60
+ size="small"
61
+ variant="text"
62
+ color="error"
63
+ @click.stop.prevent="deleteUser(item)"
64
+ />
65
+ </template>
66
+ </BaseDataTable>
67
+ </div>
50
68
  </template>
51
69
 
52
70
  <script setup lang="ts">
@@ -56,6 +74,7 @@ import type { User } from "@raclettejs/core"
56
74
  import { formatDistance } from "date-fns"
57
75
  import { useI18n } from "vue-i18n"
58
76
  import BaseDataTable from "@raclettejs/core/orchestrator/components/dataTable/BaseDataTable.vue"
77
+ import { useWorkbenchTableActions } from "@app/composables/useWorkbenchTableActions"
59
78
  const showDeleted = ref(false)
60
79
 
61
80
  const props = defineProps<{
@@ -63,6 +82,7 @@ const props = defineProps<{
63
82
  }>()
64
83
 
65
84
  const { t } = useI18n()
85
+ const { goToCreate, goToEditById } = useWorkbenchTableActions()
66
86
 
67
87
  const headers = computed<{ title: string; key: keyof User }[]>(() => [
68
88
  {
@@ -108,15 +128,13 @@ const userTableRows = (relativeUpdatedAt: boolean) => {
108
128
  return []
109
129
  }
110
130
 
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
- )
131
+ return Object.values(users.value as { [key: string]: User }).map((user) => ({
132
+ ...user,
133
+ tags: user.tags.map((tag) => tags.value?.[tag]),
134
+ ...(relativeUpdatedAt
135
+ ? { updatedAt: formatDistance(new Date(user.updatedAt), new Date()) }
136
+ : {}),
137
+ }))
120
138
  }
121
139
 
122
140
  const parsedUsers = computed(() => userTableRows(true))
@@ -132,6 +150,10 @@ const deleteUser = async (user: User) => {
132
150
  const restore = async (_id: string) => {
133
151
  executeRestore({ _id })
134
152
  }
153
+
154
+ const handleRowClick = (item: User) =>
155
+ goToEditById("userEditInteractionLink", item._id)
156
+
135
157
  $socket.on("userCreated", () => execute({ isDeleted: showDeleted.value }))
136
158
  watch(
137
159
  showDeleted,
@@ -1,69 +1,97 @@
1
1
  <template>
2
- <div class="tw:p-6" :class="{ 'tw:max-w-6xl': !fluid }">
3
- <div v-if="title" class="tw:mb-6 tw:flex tw:gap-1 tw:items-center">
4
- <v-btn
5
- v-if="routeBackInteractionLinkId"
6
- @click="navigateBack"
7
- density="comfortable"
8
- icon="mdi-chevron-left"
9
- flat
10
- />
11
- <h1 v-if="title" class="tw:text-2xl tw:font-bold">
12
- {{ title }}
13
- </h1>
14
- </div>
15
-
16
- <v-form @submit.prevent="onFormSubmit">
17
- <!-- body -->
18
- <div class="tw:grid tw:gap-6">
19
- <template v-for="(value, key) in props.data" :key="key">
20
- <TextDivider v-if="'divider' in value" :label="value.divider" />
21
- <DynamicInput
22
- v-else
23
- v-model="modelProxy[value.field]"
24
- :input-type="value.inputType"
25
- :value="value"
26
- :disabled="!!disabled"
27
- :error-message="getValidationError(value)"
28
- @validate="validateField(value)"
29
- />
30
- </template>
31
- </div>
32
-
33
- <!-- button area -->
34
- <slot name="button-area-override">
35
- <v-btn type="submit" class="tw:hidden!" />
36
- <div
37
- class="tw:mt-8 tw:flex tw:gap-4 tw:justify-end"
38
- v-if="$slots?.['button-area']"
2
+ <div :class="wrapperClasses">
3
+ <v-form
4
+ class="tw:flex tw:min-h-0 tw:w-full tw:flex-col"
5
+ @submit.prevent="onFormSubmit"
6
+ >
7
+ <div class="tw:flex tw:min-h-0 tw:w-full tw:flex-col">
8
+ <header
9
+ v-if="title || routeBackInteractionLinkId"
10
+ class="tw:flex tw:items-center tw:justify-between tw:gap-3 tw:pt-3"
39
11
  >
40
- <slot name="button-area" v-bind="{ isValid, validateAllFields }" />
41
- <!-- <v-btn color="primary" variant="outlined"> Validate Form </v-btn>
12
+ <div class="tw:flex tw:min-w-0 tw:items-center tw:gap-2">
13
+ <v-btn
14
+ v-if="routeBackInteractionLinkId"
15
+ @click="navigateBack"
16
+ :density="vuetifyDensity"
17
+ icon="mdi-chevron-left"
18
+ variant="text"
19
+ aria-label="Go back"
20
+ />
21
+ <h1
22
+ v-if="title"
23
+ class="tw:min-w-0 tw:truncate tw:font-semibold tw:tracking-tight tw:text-[color:rgb(var(--v-theme-on-surface))]"
24
+ :class="titleClasses"
25
+ >
26
+ {{ title }}
27
+ </h1>
28
+ </div>
29
+ </header>
30
+
31
+ <!-- body -->
32
+ <div class="tw:grid" :class="bodyClasses">
33
+ <template v-for="(value, key) in props.data" :key="key">
34
+ <TextDivider
35
+ v-if="'divider' in value"
36
+ :label="value.divider"
37
+ :density="density"
38
+ />
39
+ <DynamicInput
40
+ v-else
41
+ v-model="modelProxy[value.field]"
42
+ :input-type="value.inputType"
43
+ :value="value"
44
+ :disabled="!!disabled"
45
+ :error-message="getValidationError(value)"
46
+ :density="density"
47
+ @validate="validateField(value)"
48
+ />
49
+ </template>
50
+ </div>
51
+
52
+ <!-- button area -->
53
+ <slot name="button-area-override">
54
+ <v-btn type="submit" class="tw:hidden!" />
55
+ <footer
56
+ v-if="$slots?.['button-area']"
57
+ class="tw:flex tw:flex-wrap tw:items-center tw:justify-end"
58
+ :class="actionClasses"
59
+ >
60
+ <slot name="button-area" v-bind="{ isValid, validateAllFields }" />
61
+ <!-- <v-btn color="primary" variant="outlined"> Validate Form </v-btn>
42
62
  <v-btn color="secondary" variant="outlined"> Reset Form </v-btn>
43
63
  <v-btn color="info" variant="outlined"> Show Form Data </v-btn> -->
44
- </div>
45
- </slot>
64
+ </footer>
65
+ </slot>
66
+ </div>
46
67
  </v-form>
47
68
  </div>
48
69
  </template>
49
70
 
50
71
  <script setup lang="ts" generic="T">
51
- import { computed, ref, watch, watchEffect } from "vue"
72
+ import { computed, ref, watch, watchEffect, withDefaults } from "vue"
52
73
  import DynamicInput from "./DynamicInput.vue"
53
74
  import TextDivider from "./TextDivider.vue"
54
75
  import { DynamicFormItem, DynamicFormField } from "./DynamicFormTypes"
55
76
  import { useI18n } from "vue-i18n"
56
77
  import { useRouteState } from "@raclettejs/core/orchestrator/composables"
57
78
 
58
- const props = defineProps<{
59
- title?: string
60
- data: DynamicFormItem<T>[]
61
- modelValue: T
62
- disabled?: Boolean
63
- validateOnChange?: boolean
64
- fluid?: boolean
65
- routeBackInteractionLinkId?: string
66
- }>()
79
+ const props = withDefaults(
80
+ defineProps<{
81
+ title?: string
82
+ data: DynamicFormItem<T>[]
83
+ modelValue: T
84
+ disabled?: Boolean
85
+ validateOnChange?: boolean
86
+ /** When false, constrains width (previous default). Default true fills the layout. */
87
+ fluid?: boolean
88
+ routeBackInteractionLinkId?: string
89
+ density?: "compact" | "comfortable" | "spacious"
90
+ }>(),
91
+ {
92
+ fluid: true,
93
+ },
94
+ )
67
95
 
68
96
  const emit = defineEmits<{
69
97
  (e: "update:modelValue", value: T): void
@@ -75,6 +103,66 @@ const { t } = useI18n()
75
103
  const { triggerInteractionLinkById } = useRouteState()
76
104
 
77
105
  const validationErrors = ref<Record<string, string>>({})
106
+ const density = computed(() => props.density ?? "comfortable")
107
+
108
+ const vuetifyDensity = computed(() => {
109
+ if (density.value === "spacious") {
110
+ return "default"
111
+ }
112
+ return density.value
113
+ })
114
+
115
+ const horizontalInsetClasses = computed(() => {
116
+ if (density.value === "compact") {
117
+ return "tw:px-3 sm:tw:px-4"
118
+ }
119
+ if (density.value === "spacious") {
120
+ return "tw:px-5 tw:mdpx-6"
121
+ }
122
+ return "tw:px-4 tw:mdpx-5"
123
+ })
124
+
125
+ const wrapperClasses = computed(() => [
126
+ "tw:box-border tw:flex tw:min-h-0 tw:w-full tw:flex-col",
127
+ horizontalInsetClasses.value,
128
+ !props.fluid ? "tw:mx-auto tw:max-w-6xl" : "",
129
+ ])
130
+
131
+ const titleClasses = computed(() => {
132
+ if (density.value === "compact") {
133
+ return "tw:text-lg"
134
+ }
135
+
136
+ if (density.value === "spacious") {
137
+ return "tw:text-2xl"
138
+ }
139
+
140
+ return "tw:text-xl"
141
+ })
142
+
143
+ const bodyClasses = computed(() => {
144
+ if (density.value === "compact") {
145
+ return "tw:gap-0"
146
+ }
147
+
148
+ if (density.value === "spacious") {
149
+ return "tw:gap-4"
150
+ }
151
+
152
+ return "tw:gap-2"
153
+ })
154
+
155
+ const actionClasses = computed(() => {
156
+ if (density.value === "compact") {
157
+ return "tw:gap-2 tw:py-3"
158
+ }
159
+
160
+ if (density.value === "spacious") {
161
+ return "tw:gap-4 tw:py-5"
162
+ }
163
+
164
+ return "tw:gap-3 tw:py-4"
165
+ })
78
166
 
79
167
  const isValid = computed(() => Object.keys(validationErrors.value).length === 0)
80
168
 
@@ -1,11 +1,33 @@
1
1
  <template>
2
- <div class="tw:grid tw:grid-cols-12 tw:gap-6">
3
- <label class="tw:col-span-5" :for="inputId" v-if="hasTitleOrDescription">
4
- <div class="tw:text-sm tw:font-bold">
2
+ <div
3
+ class="tw:grid tw:grid-cols-1 tw:rounded-md tw:p-2 tw:grid-cols-12"
4
+ :class="rowGapClasses"
5
+ >
6
+ <label
7
+ class="tw:col-span-5"
8
+ :class="labelSpacingClasses"
9
+ :for="inputId"
10
+ v-if="hasTitleOrDescription"
11
+ >
12
+ <div
13
+ class="tw:font-semibold tw:leading-5 tw:text-[color:rgb(var(--v-theme-on-surface))]"
14
+ :class="labelTextClasses"
15
+ >
5
16
  {{ value.title }}
6
- <span v-if="value.validation?.required" class="tw:text-red-500">*</span>
17
+ <span
18
+ v-if="value.validation?.required"
19
+ class="tw:ml-0.5 tw:text-[color:rgb(var(--v-theme-error))]"
20
+ aria-hidden="true"
21
+ >
22
+ *
23
+ </span>
7
24
  </div>
8
- <div class="tw:text-xs">
25
+ <div
26
+ v-if="value.description"
27
+ :id="descriptionHtmlId ?? undefined"
28
+ class="tw:mt-1 tw:max-w-prose tw:leading-snug tw:text-[color:rgb(var(--v-theme-on-surface))]"
29
+ :class="descriptionTextClasses"
30
+ >
9
31
  {{ value.description }}
10
32
  </div>
11
33
  </label>
@@ -66,6 +88,7 @@
66
88
  <RecordInput
67
89
  v-model="recordModel"
68
90
  :input-id="inputId"
91
+ v-bind="additionalInputBindings"
69
92
  @blur="$emit('validate')"
70
93
  />
71
94
  </template>
@@ -87,6 +110,7 @@
87
110
  v-model="objectArrayModel"
88
111
  :fields="value.fields"
89
112
  :disabled="!!disabled"
113
+ :density="density"
90
114
  @validate="$emit('validate')"
91
115
  />
92
116
  </template>
@@ -239,6 +263,7 @@ const props = defineProps<{
239
263
  disabled?: boolean
240
264
  errorMessage?: string
241
265
  hideDetails?: boolean
266
+ density?: "compact" | "comfortable" | "spacious"
242
267
  }>()
243
268
 
244
269
  defineEmits<{
@@ -262,6 +287,69 @@ const id = useId()
262
287
  const attrs = useAttrs()
263
288
 
264
289
  const inputId = computed(() => `dynamic-input-${id}`)
290
+ const descriptionHtmlId = computed(() =>
291
+ props.value.description ? `${inputId.value}-description` : undefined,
292
+ )
293
+
294
+ const density = computed(() => props.density ?? "comfortable")
295
+
296
+ const vuetifyDensity = computed(() => {
297
+ if (density.value === "spacious") {
298
+ return "default"
299
+ }
300
+ return density.value
301
+ })
302
+
303
+ const rowGapClasses = computed(() => {
304
+ if (density.value === "compact") {
305
+ return "tw:gap-3 tw:md:gap-4"
306
+ }
307
+ if (density.value === "spacious") {
308
+ return "tw:gap-6"
309
+ }
310
+ return "tw:gap-4 tw:md:gap-5"
311
+ })
312
+
313
+ const labelSpacingClasses = computed(() => {
314
+ if (density.value === "spacious") {
315
+ return "tw:pt-1"
316
+ }
317
+ return ""
318
+ })
319
+
320
+ const labelTextClasses = computed(() => {
321
+ if (density.value === "compact") {
322
+ return "tw:text-sm"
323
+ }
324
+ if (density.value === "spacious") {
325
+ return "tw:text-base"
326
+ }
327
+ return "tw:text-sm"
328
+ })
329
+
330
+ const descriptionTextClasses = computed(() => {
331
+ if (density.value === "compact") {
332
+ return "tw:text-xs"
333
+ }
334
+ return "tw:text-sm"
335
+ })
336
+
337
+ const accessibilityFieldBindings = computed(() => {
338
+ const o: Record<string, unknown> = {
339
+ color: "primary",
340
+ }
341
+ const descId = descriptionHtmlId.value
342
+ if (descId) {
343
+ o["aria-describedby"] = descId
344
+ }
345
+ if (props.value.validation?.required) {
346
+ o["aria-required"] = true
347
+ }
348
+ if (props.errorMessage) {
349
+ o["aria-invalid"] = true
350
+ }
351
+ return o
352
+ })
265
353
 
266
354
  const createTypedModel = <T extends DynamicModelType>(
267
355
  types: DynamicInputType[],
@@ -333,8 +421,10 @@ const additionalInputBindings = computed(() => {
333
421
  } = props.value
334
422
 
335
423
  const result: Record<string, unknown> = {
424
+ ...accessibilityFieldBindings.value,
336
425
  prefix,
337
426
  suffix,
427
+ density: vuetifyDensity.value,
338
428
  prependIcon: prependIcon ? `mdi-${prependIcon}` : undefined,
339
429
  prependInnerIcon: prependInnerIcon ? `mdi-${prependInnerIcon}` : undefined,
340
430
  appendIcon: appendIcon ? `mdi-${appendIcon}` : undefined,