@raclettejs/workbench 0.1.33-canary.3 → 0.1.33
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/frontend/raclette.config.js +2 -2
- package/.raclette/frontend/yarn.lock +143 -126
- package/.raclette/virtual/backend/raclette.config.js +2 -2
- package/.raclette/virtual/frontend/raclette.config.js +2 -2
- package/.raclette/virtual/frontend/yarn.lock +143 -126
- package/CHANGELOG.md +19 -3
- package/i18n/de-DE.json +15 -0
- package/i18n/en-EU.json +15 -0
- package/i18n/sk-SK.json +15 -0
- package/package.json +5 -4
- package/plugins/pacifico__compositions/frontend/widgets/CompositionListWidget.vue +39 -21
- package/plugins/pacifico__interactionLinks/frontend/widgets/InteractionLinkListWidget.vue +39 -21
- package/plugins/pacifico__plugins/frontend/index.ts +3 -3
- package/plugins/pacifico__plugins/frontend/widgets/PluginListWidget.vue +11 -12
- package/plugins/pacifico__tags/frontend/widgets/TagListWidget.vue +39 -21
- package/plugins/pacifico__users/frontend/widgets/UserListWidget.vue +79 -57
- package/services/frontend/src/app/components/dynamicForm/DynamicForm.vue +139 -51
- package/services/frontend/src/app/components/dynamicForm/DynamicInput.vue +95 -5
- package/services/frontend/src/app/components/dynamicForm/ObjectListInput.vue +78 -41
- package/services/frontend/src/app/components/dynamicForm/TextDivider.vue +46 -6
- package/services/frontend/src/app/components/dynamicForm/typedInputs/ListInput.vue +118 -27
- package/services/frontend/src/app/components/dynamicForm/typedInputs/RecordInput.vue +131 -21
- package/services/frontend/src/app/components/dynamicForm/typedInputs/TagsInput.vue +1 -0
- package/services/frontend/src/app/components/dynamicForm/typedInputs/TriggerInput.vue +4 -4
- package/services/frontend/src/app/composables/useWorkbenchTableActions.ts +29 -0
- package/services/frontend/src/orchestrator/assets/styles/themes/light.ts +4 -3
|
@@ -1,50 +1,141 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="tw:grid
|
|
3
|
-
|
|
4
|
-
<v-text-field
|
|
2
|
+
<div class="tw:grid" :class="listGapClasses" role="list">
|
|
3
|
+
<div
|
|
5
4
|
v-for="(_, index) in modelValue"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
5
|
+
:key="`row-${index}`"
|
|
6
|
+
class="tw:flex tw:min-w-0 tw:items-start tw:gap-2"
|
|
7
|
+
role="listitem"
|
|
8
|
+
>
|
|
9
|
+
<v-text-field
|
|
10
|
+
:model-value="modelValue[index]"
|
|
11
|
+
v-bind="passthroughAttrs(index)"
|
|
12
|
+
class="tw:min-w-0 tw:flex-1"
|
|
13
|
+
:id="fieldId(index)"
|
|
14
|
+
variant="outlined"
|
|
15
|
+
color="primary"
|
|
16
|
+
:density="effectiveDensity"
|
|
17
|
+
append-inner-icon="mdi-minus"
|
|
18
|
+
:aria-label="rowLabel(index)"
|
|
19
|
+
@click:append-inner="removeAt(index)"
|
|
20
|
+
@update:model-value="updateAt(index, String($event ?? ''))"
|
|
21
|
+
@blur="onFieldBlur"
|
|
22
|
+
/>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div role="listitem">
|
|
26
|
+
<v-text-field
|
|
27
|
+
v-model="virtualInput"
|
|
28
|
+
v-bind="passthroughAttrs(-1)"
|
|
29
|
+
class="tw:w-full"
|
|
30
|
+
:id="addFieldDomId"
|
|
31
|
+
variant="outlined"
|
|
32
|
+
color="primary"
|
|
33
|
+
:density="effectiveDensity"
|
|
34
|
+
hide-details
|
|
35
|
+
append-inner-icon="mdi-plus"
|
|
36
|
+
:aria-label="t('workbench.dynamicInputs.list.addNewLabel')"
|
|
37
|
+
@click:append-inner="addVirtualInput"
|
|
38
|
+
@blur="addAndBlur"
|
|
39
|
+
/>
|
|
40
|
+
</div>
|
|
26
41
|
</div>
|
|
27
42
|
</template>
|
|
28
43
|
|
|
29
44
|
<script setup lang="ts">
|
|
30
|
-
import { ref } from "vue"
|
|
45
|
+
import { computed, ref, useAttrs } from "vue"
|
|
46
|
+
import { useI18n } from "vue-i18n"
|
|
31
47
|
|
|
32
48
|
defineOptions({
|
|
33
49
|
inheritAttrs: false,
|
|
34
50
|
})
|
|
35
51
|
|
|
36
|
-
defineProps<{
|
|
52
|
+
const props = defineProps<{
|
|
37
53
|
inputId?: string
|
|
38
54
|
}>()
|
|
39
55
|
|
|
40
|
-
const
|
|
56
|
+
const emit = defineEmits<{
|
|
57
|
+
blur: []
|
|
58
|
+
}>()
|
|
59
|
+
|
|
60
|
+
const { t } = useI18n()
|
|
61
|
+
const attrs = useAttrs()
|
|
41
62
|
|
|
63
|
+
const modelValue = defineModel<string[]>({ required: true })
|
|
42
64
|
const virtualInput = ref("")
|
|
43
65
|
|
|
66
|
+
const baseId = computed(() => props.inputId ?? "dynamic-list")
|
|
67
|
+
|
|
68
|
+
const densityToken = computed(
|
|
69
|
+
() => (attrs["density"] as string | undefined) ?? "comfortable",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
const effectiveDensity = computed(() =>
|
|
73
|
+
densityToken.value === "spacious" ? "default" : densityToken.value,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
const listGapClasses = computed(() => {
|
|
77
|
+
if (densityToken.value === "compact") return "tw:gap-2"
|
|
78
|
+
if (
|
|
79
|
+
densityToken.value === "spacious" ||
|
|
80
|
+
densityToken.value === "default"
|
|
81
|
+
) {
|
|
82
|
+
return "tw:gap-3"
|
|
83
|
+
}
|
|
84
|
+
return "tw:gap-2 tw:md:gap-3"
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
const passthroughAttrs = (index: number) => {
|
|
88
|
+
const r = { ...(attrs as Record<string, unknown>) }
|
|
89
|
+
if (index > 0 || index === -1) {
|
|
90
|
+
delete r.errorMessages
|
|
91
|
+
delete r["error-messages"]
|
|
92
|
+
delete r.errorMessage
|
|
93
|
+
delete r["error-message"]
|
|
94
|
+
}
|
|
95
|
+
if (index === -1) {
|
|
96
|
+
r.hideDetails = true
|
|
97
|
+
}
|
|
98
|
+
return r
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function fieldId(index: number): string {
|
|
102
|
+
if (index === 0 && props.inputId) return props.inputId
|
|
103
|
+
return `${baseId.value}-${index}`
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const addFieldDomId = computed(() =>
|
|
107
|
+
modelValue.value.length === 0 && props.inputId
|
|
108
|
+
? props.inputId
|
|
109
|
+
: `${baseId.value}-new`,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
const rowLabel = (index: number) =>
|
|
113
|
+
t("workbench.dynamicInputs.list.rowLabel", { n: index + 1 })
|
|
114
|
+
|
|
115
|
+
const onFieldBlur = () => {
|
|
116
|
+
emit("blur")
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const addAndBlur = () => {
|
|
120
|
+
addVirtualInput()
|
|
121
|
+
emit("blur")
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const updateAt = (index: number, value: string) => {
|
|
125
|
+
const next = [...modelValue.value]
|
|
126
|
+
next[index] = value
|
|
127
|
+
modelValue.value = next
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const removeAt = (index: number) => {
|
|
131
|
+
if (modelValue.value.length <= 1) return
|
|
132
|
+
modelValue.value = modelValue.value.filter((_, i) => i !== index)
|
|
133
|
+
}
|
|
134
|
+
|
|
44
135
|
const addVirtualInput = () => {
|
|
45
136
|
const val = virtualInput.value.trim()
|
|
46
137
|
if (val !== "") {
|
|
47
|
-
modelValue.value.
|
|
138
|
+
modelValue.value = [...modelValue.value, val]
|
|
48
139
|
virtualInput.value = ""
|
|
49
140
|
}
|
|
50
141
|
}
|
|
@@ -1,49 +1,154 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
2
|
+
<div
|
|
3
|
+
role="group"
|
|
4
|
+
class="tw:grid tw:gap-3"
|
|
5
|
+
v-bind="rootA11yAttrs"
|
|
6
|
+
>
|
|
7
|
+
<div
|
|
8
|
+
v-if="errorSummary"
|
|
9
|
+
role="alert"
|
|
10
|
+
class="tw:text-sm tw:leading-snug tw:text-[color:rgb(var(--v-theme-error))]"
|
|
11
|
+
>
|
|
12
|
+
{{ errorSummary }}
|
|
13
|
+
</div>
|
|
14
|
+
|
|
3
15
|
<div
|
|
4
16
|
v-for="(entry, index) in entries"
|
|
5
17
|
:key="index"
|
|
6
|
-
class="tw:grid tw:grid-cols-12 tw:gap-2"
|
|
18
|
+
class="tw:grid tw:grid-cols-1 tw:gap-2 sm:tw:grid-cols-12 sm:tw:items-center sm:tw:gap-2"
|
|
7
19
|
>
|
|
8
20
|
<v-text-field
|
|
9
21
|
:model-value="entry.key"
|
|
22
|
+
v-bind="sharedFieldBindings"
|
|
10
23
|
@update:model-value="entry.key = $event"
|
|
11
|
-
@blur="emitEntries"
|
|
12
|
-
label="Key"
|
|
13
|
-
class="tw:col-span-5"
|
|
14
24
|
hide-details
|
|
15
25
|
variant="outlined"
|
|
26
|
+
color="primary"
|
|
27
|
+
:density="effectiveDensity"
|
|
28
|
+
class="tw:sm:col-span-5"
|
|
29
|
+
:aria-label="
|
|
30
|
+
t('workbench.dynamicInputs.record.keyLabel', { n: index + 1 })
|
|
31
|
+
"
|
|
32
|
+
@blur="emitEntriesAndBlur"
|
|
16
33
|
/>
|
|
17
34
|
|
|
18
35
|
<v-text-field
|
|
19
36
|
:model-value="entry.value"
|
|
37
|
+
v-bind="sharedFieldBindings"
|
|
20
38
|
@update:model-value="entry.value = $event"
|
|
21
|
-
@blur="emitEntries"
|
|
22
|
-
label="Value"
|
|
23
|
-
class="tw:col-span-6"
|
|
24
39
|
hide-details
|
|
25
40
|
variant="outlined"
|
|
41
|
+
color="primary"
|
|
42
|
+
:density="effectiveDensity"
|
|
43
|
+
class="tw:sm:col-span-6"
|
|
44
|
+
:aria-label="
|
|
45
|
+
t('workbench.dynamicInputs.record.valueLabel', { n: index + 1 })
|
|
46
|
+
"
|
|
47
|
+
@blur="emitEntriesAndBlur"
|
|
26
48
|
/>
|
|
49
|
+
<div class="tw:flex tw:justify-end sm:tw:col-span-1 sm:tw:justify-center">
|
|
50
|
+
<v-btn
|
|
51
|
+
icon="mdi-minus"
|
|
52
|
+
variant="text"
|
|
53
|
+
color="error"
|
|
54
|
+
size="small"
|
|
55
|
+
:density="effectiveDensity"
|
|
56
|
+
class="tw:shrink-0"
|
|
57
|
+
:aria-label="t('workbench.dynamicInputs.record.removeRow')"
|
|
58
|
+
type="button"
|
|
59
|
+
@click="remove(index)"
|
|
60
|
+
/>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<div class="tw:flex tw:justify-end">
|
|
27
65
|
<v-btn
|
|
28
|
-
icon="mdi-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
66
|
+
icon="mdi-plus"
|
|
67
|
+
variant="tonal"
|
|
68
|
+
color="secondary"
|
|
69
|
+
size="small"
|
|
70
|
+
:density="effectiveDensity"
|
|
71
|
+
:disabled="hasEmptyKeyEntry"
|
|
72
|
+
type="button"
|
|
73
|
+
:aria-label="t('workbench.dynamicInputs.record.addRow')"
|
|
74
|
+
@click="add"
|
|
32
75
|
/>
|
|
33
76
|
</div>
|
|
34
|
-
|
|
35
|
-
<v-btn
|
|
36
|
-
icon="mdi-plus"
|
|
37
|
-
color="green"
|
|
38
|
-
@click="add"
|
|
39
|
-
:disabled="hasEmptyKeyEntry"
|
|
40
|
-
/>
|
|
41
77
|
</div>
|
|
42
78
|
</template>
|
|
43
79
|
|
|
44
80
|
<script setup lang="ts">
|
|
45
|
-
import { computed, ref, watch } from "vue"
|
|
81
|
+
import { computed, ref, useAttrs, watch } from "vue"
|
|
46
82
|
import { equals } from "ramda"
|
|
83
|
+
import { useI18n } from "vue-i18n"
|
|
84
|
+
|
|
85
|
+
defineOptions({
|
|
86
|
+
inheritAttrs: false,
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
const { t } = useI18n()
|
|
90
|
+
const attrs = useAttrs()
|
|
91
|
+
const emit = defineEmits<{ blur: [] }>()
|
|
92
|
+
|
|
93
|
+
const densityToken = computed(
|
|
94
|
+
() => (attrs["density"] as string | undefined) ?? "comfortable",
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
const effectiveDensity = computed(() =>
|
|
98
|
+
densityToken.value === "spacious" ? "default" : densityToken.value,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
const pickErrorMessages = (raw: Record<string, unknown>): string | undefined => {
|
|
102
|
+
const m =
|
|
103
|
+
raw["errorMessages"] ?? raw["error-messages"] ?? raw["errorMessage"]
|
|
104
|
+
if (m == null) return undefined
|
|
105
|
+
if (Array.isArray(m)) return m.filter(Boolean)[0]?.toString()
|
|
106
|
+
return String(m)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const errorSummary = computed(() => pickErrorMessages(attrs))
|
|
110
|
+
|
|
111
|
+
const omitKeys = (
|
|
112
|
+
raw: Record<string, unknown>,
|
|
113
|
+
keys: readonly string[],
|
|
114
|
+
): Record<string, unknown> => {
|
|
115
|
+
const o = { ...raw }
|
|
116
|
+
for (const k of keys) delete o[k]
|
|
117
|
+
return o
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const SHARED_OMIT: readonly string[] = [
|
|
121
|
+
"errorMessages",
|
|
122
|
+
"error-messages",
|
|
123
|
+
"errorMessage",
|
|
124
|
+
"hideDetails",
|
|
125
|
+
"hide-details",
|
|
126
|
+
"disabled",
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
const ROOT_A11Y_KEYS: readonly string[] = [
|
|
130
|
+
"aria-describedby",
|
|
131
|
+
"aria-required",
|
|
132
|
+
"aria-invalid",
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
const rootA11yAttrs = computed(() => {
|
|
136
|
+
const raw = attrs as Record<string, unknown>
|
|
137
|
+
const pick: Record<string, unknown> = {}
|
|
138
|
+
for (const k of ROOT_A11Y_KEYS) {
|
|
139
|
+
if (raw[k] !== undefined) pick[k] = raw[k]
|
|
140
|
+
}
|
|
141
|
+
return pick
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
const sharedFieldBindings = computed(() => ({
|
|
145
|
+
...omitKeys(attrs as Record<string, unknown>, [
|
|
146
|
+
...SHARED_OMIT,
|
|
147
|
+
...ROOT_A11Y_KEYS,
|
|
148
|
+
]),
|
|
149
|
+
disabled: attrs.disabled ?? false,
|
|
150
|
+
density: effectiveDensity.value,
|
|
151
|
+
}))
|
|
47
152
|
|
|
48
153
|
const modelValue = defineModel<Record<string, string>>({ required: true })
|
|
49
154
|
|
|
@@ -64,15 +169,20 @@ const emitEntries = () => {
|
|
|
64
169
|
modelValue.value = obj
|
|
65
170
|
}
|
|
66
171
|
|
|
172
|
+
const emitEntriesAndBlur = () => {
|
|
173
|
+
emitEntries()
|
|
174
|
+
emit("blur")
|
|
175
|
+
}
|
|
176
|
+
|
|
67
177
|
const add = () => {
|
|
68
178
|
entries.value.push({ key: "", value: "" })
|
|
69
179
|
}
|
|
70
180
|
|
|
71
181
|
const remove = (index: number) => {
|
|
72
182
|
entries.value.splice(index, 1)
|
|
183
|
+
emitEntriesAndBlur()
|
|
73
184
|
}
|
|
74
185
|
|
|
75
|
-
// synchronize local clone of the modelValue
|
|
76
186
|
watch(
|
|
77
187
|
() => modelValue.value,
|
|
78
188
|
(newVal, oldVal) => {
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
<div class="tw:grid tw:gap-2">
|
|
3
3
|
<!-- existing triggers -->
|
|
4
4
|
<div
|
|
5
|
-
class="tw:py-2 tw:border-solid tw:rounded"
|
|
6
5
|
v-for="(trigger, index) in modelValue"
|
|
7
6
|
:key="index"
|
|
7
|
+
class="tw:grid tw:gap-3 tw:rounded-md tw:pb-4"
|
|
8
8
|
>
|
|
9
9
|
<v-select
|
|
10
10
|
v-model="trigger.type"
|
|
@@ -17,10 +17,9 @@
|
|
|
17
17
|
@update:model-value="updateTriggerType($event, index)"
|
|
18
18
|
@click:append="modelValue.splice(index, 1)"
|
|
19
19
|
:label="$t('workbench.dynamicInputs.triggers.triggerTypeLabel')"
|
|
20
|
-
:disabled="true"
|
|
21
20
|
/>
|
|
22
21
|
|
|
23
|
-
<div class="tw:grid tw:gap-2 tw:
|
|
22
|
+
<div class="tw:grid tw:gap-2 tw:pl-4">
|
|
24
23
|
<template
|
|
25
24
|
v-if="
|
|
26
25
|
['app-bar', 'page-navigation', 'user-menu'].includes(trigger.type)
|
|
@@ -33,7 +32,8 @@
|
|
|
33
32
|
v-bind="$attrs"
|
|
34
33
|
:input-id="`title-${index}`"
|
|
35
34
|
:title="$t('workbench.dynamicInputs.triggers.titleLabel')"
|
|
36
|
-
|
|
35
|
+
hide-details
|
|
36
|
+
/>
|
|
37
37
|
|
|
38
38
|
<v-number-input
|
|
39
39
|
v-model="trigger.settings.sortOrder"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useRouteState } from "@raclettejs/core/orchestrator/composables"
|
|
2
|
+
|
|
3
|
+
export const useWorkbenchTableActions = () => {
|
|
4
|
+
const routeState = useRouteState()
|
|
5
|
+
|
|
6
|
+
const goToCreate = (interactionLinkId: string) => {
|
|
7
|
+
routeState.triggerInteractionLinkById(interactionLinkId)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const goToEditById = (
|
|
11
|
+
interactionLinkId: string,
|
|
12
|
+
id: string | number | undefined | null,
|
|
13
|
+
) => {
|
|
14
|
+
if (id === undefined || id === null) {
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
routeState.triggerInteractionLinkById(interactionLinkId, {
|
|
18
|
+
id: String(id),
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
routeState,
|
|
24
|
+
goToCreate,
|
|
25
|
+
goToEditById,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default useWorkbenchTableActions
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* light theme
|
|
3
3
|
*/
|
|
4
4
|
export default {
|
|
5
|
-
background: "#
|
|
6
|
-
surface: "#
|
|
5
|
+
background: "#ffffff",
|
|
6
|
+
surface: "#ffffff",
|
|
7
|
+
"surface-light": "#ffffff",
|
|
7
8
|
primary: "#DD8B41",
|
|
8
9
|
"primary-darken-1": "#B26F34",
|
|
9
10
|
"primary-darken-2": "#9D612E",
|
|
@@ -23,7 +24,7 @@ export default {
|
|
|
23
24
|
"secondary-darken-1": "#247DA1",
|
|
24
25
|
"secondary-darken-2": "#1B5D78",
|
|
25
26
|
error: "#B00020",
|
|
26
|
-
info: "#
|
|
27
|
+
info: "#1973BE",
|
|
27
28
|
success: "#3A823D",
|
|
28
29
|
warning: "#FB8C00",
|
|
29
30
|
"surface-2": "#F4F4F4",
|