@raclettejs/workbench 0.1.33-canary.2 → 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 +3 -3
- package/.raclette/backend/yarn.lock +4 -11
- package/.raclette/frontend/raclette.config.js +6 -4
- package/.raclette/frontend/yarn.lock +451 -489
- package/.raclette/virtual/backend/raclette.config.js +3 -3
- package/.raclette/virtual/backend/yarn.lock +4 -11
- package/.raclette/virtual/frontend/raclette.config.js +6 -4
- package/.raclette/virtual/frontend/yarn.lock +451 -489
- 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,75 +1,110 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="tw:
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
<div class="tw:flex tw:flex-col tw:gap-4">
|
|
3
|
+
<div
|
|
4
|
+
class="tw:rounded-lg tw:border tw:border-solid tw:border-[color:rgb(var(--v-theme-border-default))] tw:bg-[rgb(var(--v-theme-primary)/0.04)] tw:pb-2 tw:pl-2 tw:pr-2 tw:pt-2"
|
|
5
|
+
>
|
|
6
|
+
<v-expansion-panels
|
|
7
|
+
v-model="openPanels"
|
|
8
|
+
class="dynamic-object-list-panels tw:rounded-lg tw:bg-transparent"
|
|
9
|
+
flat
|
|
10
|
+
multiple
|
|
11
|
+
rounded="lg"
|
|
8
12
|
>
|
|
9
|
-
<v-expansion-panel
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
:
|
|
29
|
-
:value="field"
|
|
13
|
+
<v-expansion-panel
|
|
14
|
+
v-for="(item, index) in internalItems"
|
|
15
|
+
:key="index"
|
|
16
|
+
:value="index"
|
|
17
|
+
class="tw:bg-[rgb(var(--v-theme-primary)/0.02)] [&:first-child]:tw:rounded-t-lg [&:last-child]:tw:rounded-b-lg"
|
|
18
|
+
>
|
|
19
|
+
<v-expansion-panel-title
|
|
20
|
+
class="tw:flex tw:min-h-14 tw:items-center tw:gap-2 tw:py-4 tw:text-[color:rgb(var(--v-theme-on-surface))]"
|
|
21
|
+
>
|
|
22
|
+
<span class="tw:flex-1 tw:truncate tw:font-semibold tw:text-sm">
|
|
23
|
+
{{ getPanelTitle(item, index) }}
|
|
24
|
+
</span>
|
|
25
|
+
<v-btn
|
|
26
|
+
icon="mdi-minus"
|
|
27
|
+
size="small"
|
|
28
|
+
variant="text"
|
|
29
|
+
color="error"
|
|
30
|
+
class="tw:shrink-0"
|
|
31
|
+
type="button"
|
|
32
|
+
:density="panelButtonDensity"
|
|
30
33
|
:disabled="disabled"
|
|
31
|
-
|
|
32
|
-
@
|
|
34
|
+
:aria-label="t('workbench.dynamicInputs.objectList.removeEntry')"
|
|
35
|
+
@click.stop="removeItem(index)"
|
|
33
36
|
/>
|
|
34
|
-
</
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
</v-expansion-panel-title>
|
|
38
|
+
|
|
39
|
+
<v-expansion-panel-text
|
|
40
|
+
class="tw:border-t tw:border-solid tw:border-[color:rgb(var(--v-theme-border-default))]"
|
|
41
|
+
>
|
|
42
|
+
<div class="tw:grid tw:gap-4 tw:pb-4 tw:pt-4">
|
|
43
|
+
<DynamicInput
|
|
44
|
+
v-for="field in fields"
|
|
45
|
+
:key="String(field.field)"
|
|
46
|
+
v-model="internalItems[index][field.field]"
|
|
47
|
+
:input-type="field.inputType"
|
|
48
|
+
:value="field"
|
|
49
|
+
:disabled="disabled"
|
|
50
|
+
:density="density"
|
|
51
|
+
@update:model-value="onItemChange(index)"
|
|
52
|
+
@validate="$emit('validate')"
|
|
53
|
+
/>
|
|
54
|
+
</div>
|
|
55
|
+
</v-expansion-panel-text>
|
|
56
|
+
</v-expansion-panel>
|
|
57
|
+
</v-expansion-panels>
|
|
58
|
+
</div>
|
|
38
59
|
|
|
39
60
|
<v-btn
|
|
40
61
|
v-if="canAddNewItem"
|
|
41
|
-
|
|
42
|
-
|
|
62
|
+
variant="flat"
|
|
63
|
+
color="secondary"
|
|
43
64
|
prepend-icon="mdi-plus"
|
|
65
|
+
class="tw:self-start tw:!rounded-lg"
|
|
66
|
+
size="small"
|
|
67
|
+
type="button"
|
|
68
|
+
:density="panelButtonDensity"
|
|
44
69
|
:disabled="disabled"
|
|
45
70
|
@click="addNewItem"
|
|
46
71
|
>
|
|
47
|
-
|
|
72
|
+
{{ t("workbench.dynamicInputs.objectList.addEntry") }}
|
|
48
73
|
</v-btn>
|
|
49
74
|
</div>
|
|
50
75
|
</template>
|
|
51
76
|
|
|
52
77
|
<script setup lang="ts" generic="T extends Record<string, any>">
|
|
53
|
-
import { ref, watch
|
|
78
|
+
import { computed, ref, watch } from "vue"
|
|
79
|
+
import { useI18n } from "vue-i18n"
|
|
54
80
|
import DynamicInput from "./DynamicInput.vue"
|
|
55
81
|
import type { DynamicFormField } from "./DynamicFormTypes"
|
|
56
82
|
|
|
83
|
+
const { t } = useI18n()
|
|
84
|
+
|
|
57
85
|
const props = defineProps<{
|
|
58
86
|
fields?: DynamicFormField<T>[]
|
|
59
87
|
disabled?: boolean
|
|
88
|
+
density?: "compact" | "comfortable" | "spacious"
|
|
60
89
|
}>()
|
|
61
90
|
|
|
62
91
|
defineEmits<{
|
|
63
92
|
validate: []
|
|
64
93
|
}>()
|
|
65
94
|
|
|
95
|
+
const density = computed(() => props.density ?? "comfortable")
|
|
96
|
+
|
|
97
|
+
const panelButtonDensity = computed(() =>
|
|
98
|
+
density.value === "compact" ? "compact" : "comfortable",
|
|
99
|
+
)
|
|
100
|
+
|
|
66
101
|
const modelValue = defineModel<T[]>({ required: true, default: () => [] })
|
|
67
102
|
|
|
68
103
|
const internalItems = ref<T[]>([])
|
|
69
104
|
const openPanels = ref<number[]>([0])
|
|
70
105
|
|
|
71
106
|
const initializeEmptyItem = (): T => {
|
|
72
|
-
const item:
|
|
107
|
+
const item: Record<string, unknown> = {}
|
|
73
108
|
props.fields?.forEach((field) => {
|
|
74
109
|
const fieldKey = field.field as string
|
|
75
110
|
|
|
@@ -127,7 +162,9 @@ const isItemEmpty = (item: T): boolean => {
|
|
|
127
162
|
|
|
128
163
|
const getPanelTitle = (item: T, index: number): string => {
|
|
129
164
|
if (!props.fields || props.fields.length === 0) {
|
|
130
|
-
return
|
|
165
|
+
return t("workbench.dynamicInputs.objectList.unnamedEntry", {
|
|
166
|
+
n: index + 1,
|
|
167
|
+
})
|
|
131
168
|
}
|
|
132
169
|
|
|
133
170
|
const firstField = props.fields[0]
|
|
@@ -137,7 +174,7 @@ const getPanelTitle = (item: T, index: number): string => {
|
|
|
137
174
|
return String(firstValue)
|
|
138
175
|
}
|
|
139
176
|
|
|
140
|
-
return
|
|
177
|
+
return t("workbench.dynamicInputs.objectList.unnamedEntry", { n: index + 1 })
|
|
141
178
|
}
|
|
142
179
|
|
|
143
180
|
const syncToModelValue = () => {
|
|
@@ -145,7 +182,7 @@ const syncToModelValue = () => {
|
|
|
145
182
|
modelValue.value = nonEmptyItems
|
|
146
183
|
}
|
|
147
184
|
|
|
148
|
-
const onItemChange = (
|
|
185
|
+
const onItemChange = (_index: number) => {
|
|
149
186
|
syncToModelValue()
|
|
150
187
|
}
|
|
151
188
|
|
|
@@ -1,13 +1,53 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
<div
|
|
3
|
+
class="tw:flex tw:w-full tw:items-center"
|
|
4
|
+
:class="containerClasses"
|
|
5
|
+
role="separator"
|
|
6
|
+
:aria-label="label"
|
|
7
|
+
>
|
|
8
|
+
<v-divider
|
|
9
|
+
class="tw:opacity-80"
|
|
10
|
+
color="border-default"
|
|
11
|
+
thickness="1"
|
|
12
|
+
/>
|
|
13
|
+
<div
|
|
14
|
+
class="tw:text-nowrap tw:font-medium tw:tracking-wide tw:text-[color:rgb(var(--v-theme-on-surface))]"
|
|
15
|
+
:class="labelClasses"
|
|
16
|
+
>
|
|
17
|
+
{{ label }}
|
|
18
|
+
</div>
|
|
19
|
+
<v-divider
|
|
20
|
+
class="tw:opacity-80"
|
|
21
|
+
color="border-default"
|
|
22
|
+
thickness="1"
|
|
23
|
+
/>
|
|
6
24
|
</div>
|
|
7
25
|
</template>
|
|
8
26
|
|
|
9
27
|
<script setup lang="ts">
|
|
10
|
-
|
|
11
|
-
|
|
28
|
+
import { computed } from "vue"
|
|
29
|
+
|
|
30
|
+
const props = defineProps<{
|
|
31
|
+
label: string
|
|
32
|
+
density?: "compact" | "comfortable" | "spacious"
|
|
33
|
+
}>()
|
|
34
|
+
|
|
35
|
+
const density = computed(() => props.density ?? "comfortable")
|
|
36
|
+
|
|
37
|
+
const containerClasses = computed(() => {
|
|
38
|
+
if (density.value === "compact") {
|
|
39
|
+
return "tw:gap-2 tw:py-2"
|
|
40
|
+
}
|
|
41
|
+
if (density.value === "spacious") {
|
|
42
|
+
return "tw:gap-4 tw:py-4"
|
|
43
|
+
}
|
|
44
|
+
return "tw:gap-3 tw:py-3"
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const labelClasses = computed(() => {
|
|
48
|
+
if (density.value === "compact") {
|
|
49
|
+
return "tw:text-xs"
|
|
50
|
+
}
|
|
51
|
+
return "tw:text-sm"
|
|
12
52
|
})
|
|
13
53
|
</script>
|
|
@@ -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
|