@raclettejs/workbench 0.1.35 → 0.1.36-canary.0
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/CHANGELOG.md +10 -2
- package/config/compositions.js +18 -0
- package/config/interactionLinks.js +19 -0
- package/i18n/de-DE.json +15 -1
- package/i18n/en-EU.json +52 -1
- package/i18n/sk-SK.json +15 -1
- package/package.json +3 -4
- package/packages.json +3 -1
- package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/CompositionConfiguration.vue +96 -14
- package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/CompositionIntegration.vue +29 -1
- package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/widgetsLayout/WidgetRenderer.vue +1 -6
- package/plugins/pacifico__compositions/frontend/composables/prefillInteractionLinkFromComposition.ts +54 -0
- package/plugins/pacifico__compositions/frontend/widgets/CompositionCreateWidget.vue +145 -9
- package/plugins/pacifico__compositions/frontend/widgets/CompositionEditWidget.vue +11 -2
- package/plugins/pacifico__compositions/frontend/widgets/CompositionListWidget.vue +25 -5
- package/plugins/pacifico__core/frontend/generated-config.ts +22 -0
- package/plugins/pacifico__interactionLinks/frontend/components/InteractionLinkConfiguration.vue +3 -65
- package/plugins/pacifico__plugins/frontend/widgets/PluginListWidget.vue +20 -3
- package/plugins/raclette__cache/backend/helpers/cacheEntryBuilder.ts +94 -0
- package/plugins/raclette__cache/backend/helpers/cacheEntrySanitizer.ts +86 -0
- package/plugins/raclette__cache/backend/helpers/valkeyExplorer.ts +89 -0
- package/plugins/raclette__cache/backend/index.ts +21 -0
- package/plugins/raclette__cache/backend/routes/route.cache.cacheEntries.get.ts +118 -0
- package/plugins/raclette__cache/backend/routes/route.cache.cacheEntry.get.ts +91 -0
- package/plugins/raclette__cache/backend/routes.ts +8 -0
- package/plugins/raclette__cache/frontend/components/TableColumnConfigDialog.vue +95 -0
- package/plugins/raclette__cache/frontend/composables/useTableColumnConfig.ts +66 -0
- package/plugins/raclette__cache/frontend/generated-config.ts +33 -0
- package/plugins/raclette__cache/frontend/utils/cacheValueTableRows.ts +340 -0
- package/plugins/raclette__cache/frontend/widgets/CacheListWidget.vue +742 -0
- package/plugins/raclette__cache/raclette.plugin.ts +8 -0
- package/services/frontend/src/app/components/dynamicForm/DynamicForm.vue +3 -1
- package/services/frontend/src/app/components/interactionLink/InteractionLinkBehaviorForm.vue +99 -0
- package/services/frontend/src/app/components/stepNavigator/StepNavigator.vue +175 -24
- package/services/frontend/src/app/components/stepNavigator/StepNavigatorTypes.ts +18 -0
- package/services/frontend/src/app/composables/useInteractionLinkBehaviorFields.ts +83 -0
- package/services/frontend/src/app/composables/useRacletteUserIsAdmin.ts +25 -0
- package/services/frontend/src/orchestrator/assets/styles/themes/light.ts +1 -1
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
>
|
|
7
7
|
<div class="tw:flex tw:min-h-0 tw:w-full tw:flex-col">
|
|
8
8
|
<header
|
|
9
|
-
v-if="title || routeBackInteractionLinkId"
|
|
9
|
+
v-if="!hideTitle && (title || routeBackInteractionLinkId)"
|
|
10
10
|
class="tw:flex tw:items-center tw:justify-between tw:gap-3 tw:pt-3"
|
|
11
11
|
>
|
|
12
12
|
<div class="tw:flex tw:min-w-0 tw:items-center tw:gap-2">
|
|
@@ -86,6 +86,8 @@ const props = withDefaults(
|
|
|
86
86
|
/** When false, constrains width (previous default). Default true fills the layout. */
|
|
87
87
|
fluid?: boolean
|
|
88
88
|
routeBackInteractionLinkId?: string
|
|
89
|
+
/** Hides title and inline back (e.g. when StepNavigator provides exit back) */
|
|
90
|
+
hideTitle?: boolean
|
|
89
91
|
density?: "compact" | "comfortable" | "spacious"
|
|
90
92
|
}>(),
|
|
91
93
|
{
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="tw:box-border tw:flex tw:w-full tw:min-w-0 tw:flex-col">
|
|
3
|
+
<div v-if="intro" class="tw:grid tw:grid-cols-12 tw:px-4 tw:mdpx-5 tw:pt-3">
|
|
4
|
+
<div class="tw:col-span-12 tw:mb-2">
|
|
5
|
+
<v-alert type="info" variant="tonal" density="comfortable">
|
|
6
|
+
{{ intro }}
|
|
7
|
+
</v-alert>
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<div
|
|
12
|
+
v-if="showLinkedComposition"
|
|
13
|
+
class="tw:grid tw:grid-cols-12 tw:rounded-md tw:p-2 tw:px-4 tw:mdpx-5"
|
|
14
|
+
>
|
|
15
|
+
<label class="tw:col-span-5 tw:py-2">
|
|
16
|
+
<div
|
|
17
|
+
class="tw:font-semibold tw:leading-5 tw:text-[color:rgb(var(--v-theme-on-surface))]"
|
|
18
|
+
>
|
|
19
|
+
{{ $t("workbench.interactionLinkConfiguration.composition.title") }}
|
|
20
|
+
</div>
|
|
21
|
+
<div
|
|
22
|
+
class="tw:mt-1 tw:max-w-prose tw:leading-snug tw:text-[color:rgb(var(--v-theme-on-surface))] tw:opacity-80 tw:text-sm"
|
|
23
|
+
>
|
|
24
|
+
{{
|
|
25
|
+
$t(
|
|
26
|
+
"workbench.compositionConfiguration.integration.linkedCompositionDescription",
|
|
27
|
+
)
|
|
28
|
+
}}
|
|
29
|
+
</div>
|
|
30
|
+
</label>
|
|
31
|
+
<div class="tw:col-span-7 tw:flex tw:items-center">
|
|
32
|
+
<v-text-field
|
|
33
|
+
:model-value="linkedCompositionTitle"
|
|
34
|
+
readonly
|
|
35
|
+
variant="outlined"
|
|
36
|
+
density="comfortable"
|
|
37
|
+
hide-details
|
|
38
|
+
prepend-inner-icon="mdi-file-document-outline"
|
|
39
|
+
class="tw:w-full"
|
|
40
|
+
/>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<!-- @vue-generic {InteractionLinkCreate | InteractionLinkUpdate} -->
|
|
45
|
+
<DynamicForm
|
|
46
|
+
ref="dynamicFormRef"
|
|
47
|
+
v-model="interactionLink"
|
|
48
|
+
:data="behaviorFields"
|
|
49
|
+
:disabled
|
|
50
|
+
:fluid="fluid"
|
|
51
|
+
:validate-on-change="false"
|
|
52
|
+
:title="title"
|
|
53
|
+
/>
|
|
54
|
+
</div>
|
|
55
|
+
</template>
|
|
56
|
+
|
|
57
|
+
<script setup lang="ts">
|
|
58
|
+
import type {
|
|
59
|
+
InteractionLinkCreate,
|
|
60
|
+
InteractionLinkUpdate,
|
|
61
|
+
} from "@raclettejs/core"
|
|
62
|
+
import DynamicForm from "@app/components/dynamicForm/DynamicForm.vue"
|
|
63
|
+
import { useTemplateRef } from "vue"
|
|
64
|
+
import { useInteractionLinkBehaviorFields } from "@app/composables/useInteractionLinkBehaviorFields"
|
|
65
|
+
|
|
66
|
+
withDefaults(
|
|
67
|
+
defineProps<{
|
|
68
|
+
disabled?: boolean
|
|
69
|
+
title?: string
|
|
70
|
+
fluid?: boolean
|
|
71
|
+
intro?: string
|
|
72
|
+
showLinkedComposition?: boolean
|
|
73
|
+
linkedCompositionTitle?: string
|
|
74
|
+
}>(),
|
|
75
|
+
{
|
|
76
|
+
fluid: true,
|
|
77
|
+
showLinkedComposition: false,
|
|
78
|
+
},
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
const interactionLink = defineModel<
|
|
82
|
+
InteractionLinkCreate | InteractionLinkUpdate
|
|
83
|
+
>({ required: true })
|
|
84
|
+
|
|
85
|
+
const dynamicFormRef = useTemplateRef("dynamicFormRef")
|
|
86
|
+
const { behaviorFields } = useInteractionLinkBehaviorFields()
|
|
87
|
+
|
|
88
|
+
const validate = (): boolean => {
|
|
89
|
+
const validateAllFields = dynamicFormRef.value?.validateAllFields
|
|
90
|
+
|
|
91
|
+
if (validateAllFields) {
|
|
92
|
+
return !!validateAllFields()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return true
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
defineExpose({ validate })
|
|
99
|
+
</script>
|
|
@@ -7,8 +7,25 @@
|
|
|
7
7
|
permanent
|
|
8
8
|
:width="NAVIGATION_HEIGHT"
|
|
9
9
|
>
|
|
10
|
-
<div
|
|
11
|
-
|
|
10
|
+
<div
|
|
11
|
+
class="tw:flex tw:size-full tw:w-full tw:items-center"
|
|
12
|
+
:class="showExitBack ? 'tw:gap-1 tw:px-2' : ''"
|
|
13
|
+
>
|
|
14
|
+
<div
|
|
15
|
+
v-if="showExitBack"
|
|
16
|
+
class="tw:flex tw:size-12 tw:flex-shrink-0 tw:items-center tw:justify-center"
|
|
17
|
+
>
|
|
18
|
+
<v-btn
|
|
19
|
+
v-if="isFirstStep"
|
|
20
|
+
@click="onExitBack"
|
|
21
|
+
:disabled="isSaving"
|
|
22
|
+
icon="mdi-chevron-left"
|
|
23
|
+
variant="text"
|
|
24
|
+
:aria-label="$t('workbench.global.back')"
|
|
25
|
+
/>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<div class="tw:min-w-0 tw:flex-1 tw:overflow-x-auto">
|
|
12
29
|
<div
|
|
13
30
|
class="tw:flex tw:items-center tw:justify-center tw:min-w-max tw:px-4"
|
|
14
31
|
>
|
|
@@ -19,8 +36,10 @@
|
|
|
19
36
|
@click="
|
|
20
37
|
allowFreeNavigation ? setCurrentStep(index + 1) : null
|
|
21
38
|
"
|
|
22
|
-
class="tw:flex tw:items-center tw:
|
|
39
|
+
class="tw:flex tw:items-center tw:transition-all tw:duration-200 tw:group tw:flex-shrink-0"
|
|
23
40
|
:class="{
|
|
41
|
+
'tw:cursor-pointer': allowFreeNavigation,
|
|
42
|
+
'tw:cursor-default': !allowFreeNavigation,
|
|
24
43
|
'tw:opacity-100': currentStepIndex >= index + 1,
|
|
25
44
|
'tw:opacity-60':
|
|
26
45
|
currentStepIndex < index + 1 &&
|
|
@@ -31,11 +50,15 @@
|
|
|
31
50
|
<div
|
|
32
51
|
class="tw:w-10 tw:h-10 tw:rounded-full tw:flex tw:items-center tw:justify-center tw:transition-all tw:duration-200 tw:border-2"
|
|
33
52
|
:class="{
|
|
34
|
-
'tw:bg-[
|
|
53
|
+
'tw:bg-[color:rgb(var(--v-theme-primary))] tw:border-[color:rgb(var(--v-theme-primary-darken-1))] tw:text-white':
|
|
35
54
|
currentStepIndex === index + 1,
|
|
36
55
|
'tw:bg-green-500 tw:border-green-500 tw:text-white':
|
|
37
56
|
currentStepIndex > index + 1,
|
|
38
|
-
'tw:bg-white tw:border-gray-300 tw:text-gray-500
|
|
57
|
+
'tw:bg-white tw:border-gray-300 tw:text-gray-500':
|
|
58
|
+
currentStepIndex !== index + 1 &&
|
|
59
|
+
currentStepIndex < index + 1,
|
|
60
|
+
'group-hover:tw:border-blue-300':
|
|
61
|
+
allowFreeNavigation &&
|
|
39
62
|
currentStepIndex !== index + 1 &&
|
|
40
63
|
currentStepIndex < index + 1,
|
|
41
64
|
}"
|
|
@@ -56,17 +79,20 @@
|
|
|
56
79
|
v-if="step.title"
|
|
57
80
|
class="tw:text-sm tw:font-medium tw:transition-colors tw:duration-200 tw:whitespace-nowrap"
|
|
58
81
|
:class="{
|
|
59
|
-
'tw:text-[
|
|
82
|
+
'tw:text-[color:rgb(var(--v-theme-primary-darken-2))] tw:dark:text-[color:rgb(var(--v-theme-primary))]':
|
|
83
|
+
currentStepIndex === index + 1,
|
|
60
84
|
'tw:text-green-600': currentStepIndex > index + 1,
|
|
61
|
-
'tw:text-gray-
|
|
85
|
+
'tw:text-gray-500 tw:dark:text-gray-300':
|
|
62
86
|
currentStepIndex !== index + 1,
|
|
87
|
+
'group-hover:tw:text-blue-600':
|
|
88
|
+
allowFreeNavigation && currentStepIndex !== index + 1,
|
|
63
89
|
}"
|
|
64
90
|
>
|
|
65
91
|
{{ step.title }}
|
|
66
92
|
</div>
|
|
67
93
|
<div
|
|
68
94
|
v-if="step.subtitle"
|
|
69
|
-
class="tw:text-xs tw:text-gray-500 tw:whitespace-nowrap"
|
|
95
|
+
class="tw:text-xs tw:text-gray-500 tw:dark:text-gray-400 tw:whitespace-nowrap"
|
|
70
96
|
>
|
|
71
97
|
{{ step.subtitle }}
|
|
72
98
|
</div>
|
|
@@ -86,22 +112,27 @@
|
|
|
86
112
|
</div>
|
|
87
113
|
</div>
|
|
88
114
|
</div>
|
|
115
|
+
|
|
116
|
+
<!-- Spacer to make sure content is still centered -->
|
|
117
|
+
<div
|
|
118
|
+
v-if="showExitBack"
|
|
119
|
+
class="tw:w-12 tw:flex-shrink-0"
|
|
120
|
+
aria-hidden="true"
|
|
121
|
+
/>
|
|
89
122
|
</div>
|
|
90
123
|
</v-navigation-drawer>
|
|
91
124
|
|
|
92
|
-
<!-- Step Content Container -->
|
|
93
|
-
<!-- <div class="tw:min-h-full"> -->
|
|
94
125
|
<slot v-bind="{ currentStep, currentStepIndex }" />
|
|
95
126
|
<!-- </div> -->
|
|
96
127
|
|
|
97
128
|
<v-navigation-drawer location="bottom" permanent :width="84">
|
|
98
129
|
<div
|
|
99
|
-
class="tw:flex tw:h-full tw:items-center tw:justify-between tw:
|
|
130
|
+
class="tw:flex tw:h-full tw:items-center tw:justify-between tw:mx-auto tw:px-4"
|
|
100
131
|
>
|
|
101
132
|
<!-- Left side - Back button -->
|
|
102
133
|
<div>
|
|
103
134
|
<v-btn
|
|
104
|
-
v-if="
|
|
135
|
+
v-if="showFooterBackButton"
|
|
105
136
|
@click="previousStep"
|
|
106
137
|
:disabled="isFirstStep || isSaving"
|
|
107
138
|
color="primary"
|
|
@@ -122,8 +153,22 @@
|
|
|
122
153
|
:text="$t('workbench.global.save')"
|
|
123
154
|
/>
|
|
124
155
|
|
|
156
|
+
<template v-if="currentSecondaryActions.length">
|
|
157
|
+
<v-btn
|
|
158
|
+
v-for="action in currentSecondaryActions"
|
|
159
|
+
:key="action.id"
|
|
160
|
+
@click="onCustomAction(action.id, action.skipValidation)"
|
|
161
|
+
:disabled="isSaving"
|
|
162
|
+
:color="action.color ?? 'primary'"
|
|
163
|
+
:variant="action.variant ?? 'outlined'"
|
|
164
|
+
:prepend-icon="action.prependIcon"
|
|
165
|
+
:append-icon="action.appendIcon"
|
|
166
|
+
:text="$t(action.labelKey)"
|
|
167
|
+
/>
|
|
168
|
+
</template>
|
|
169
|
+
|
|
125
170
|
<v-btn
|
|
126
|
-
v-if="
|
|
171
|
+
v-if="showDefaultNextButton"
|
|
127
172
|
@click="nextStep()"
|
|
128
173
|
:disabled="isSaving"
|
|
129
174
|
color="primary"
|
|
@@ -132,7 +177,19 @@
|
|
|
132
177
|
/>
|
|
133
178
|
|
|
134
179
|
<v-btn
|
|
135
|
-
v-else-if="
|
|
180
|
+
v-else-if="showCustomPrimaryButton"
|
|
181
|
+
@click="onCustomAction(currentStep!.primaryActionId!)"
|
|
182
|
+
:disabled="isSaving"
|
|
183
|
+
:loading="isSaving"
|
|
184
|
+
:color="currentStepPrimaryColor"
|
|
185
|
+
variant="flat"
|
|
186
|
+
:prepend-icon="currentStepPrimaryPrependIcon"
|
|
187
|
+
:append-icon="currentStepPrimaryAppendIcon"
|
|
188
|
+
:text="currentStepPrimaryLabel"
|
|
189
|
+
/>
|
|
190
|
+
|
|
191
|
+
<v-btn
|
|
192
|
+
v-else-if="showDefaultFinishButton"
|
|
136
193
|
@click="save(true)"
|
|
137
194
|
:disabled="isSaving"
|
|
138
195
|
:loading="isSaving"
|
|
@@ -148,7 +205,9 @@
|
|
|
148
205
|
|
|
149
206
|
<script setup lang="ts">
|
|
150
207
|
import { ref, computed, watch } from "vue"
|
|
151
|
-
import {
|
|
208
|
+
import { useI18n } from "vue-i18n"
|
|
209
|
+
import { useRouteState } from "@raclettejs/core/orchestrator/composables"
|
|
210
|
+
import { Step, StepChangeType, StepFooterAction } from "./StepNavigatorTypes"
|
|
152
211
|
|
|
153
212
|
const NAVIGATION_HEIGHT = 84
|
|
154
213
|
|
|
@@ -161,6 +220,14 @@ const props = withDefaults(
|
|
|
161
220
|
canFinish?: boolean
|
|
162
221
|
isSaving?: boolean
|
|
163
222
|
showSaveButton?: boolean
|
|
223
|
+
/** When true, Next is shown on non-last steps even if `showSaveButton` is enabled */
|
|
224
|
+
showNextWithSave?: boolean
|
|
225
|
+
/** When true with `showSaveButton`, last step shows Save & Finish in addition to Save */
|
|
226
|
+
showFinishOnLastStep?: boolean
|
|
227
|
+
/** Back + title above content on the first step (e.g. return to list) */
|
|
228
|
+
showExitBack?: boolean
|
|
229
|
+
headerTitle?: string
|
|
230
|
+
exitBackInteractionLinkId?: string
|
|
164
231
|
paddingTop?: boolean
|
|
165
232
|
paddingBottom?: boolean
|
|
166
233
|
}>(),
|
|
@@ -177,14 +244,79 @@ const emit = defineEmits<{
|
|
|
177
244
|
"validation-failed": [{ step: Step; stepIndex: number }]
|
|
178
245
|
save: [void]
|
|
179
246
|
"save-and-finish": [void]
|
|
247
|
+
"custom-action": [string]
|
|
248
|
+
"exit-back": [void]
|
|
180
249
|
}>()
|
|
181
250
|
|
|
251
|
+
const { t } = useI18n()
|
|
252
|
+
const { triggerInteractionLinkById } = useRouteState()
|
|
253
|
+
|
|
182
254
|
const currentStepIndex = ref(props.initialStep)
|
|
183
255
|
|
|
184
256
|
const currentStep = computed(() => props.steps[currentStepIndex.value - 1])
|
|
185
257
|
const isFirstStep = computed(() => currentStepIndex.value === 1)
|
|
186
258
|
const isLastStep = computed(() => currentStepIndex.value === props.steps.length)
|
|
187
259
|
|
|
260
|
+
const showFooterBackButton = computed(
|
|
261
|
+
() => props.steps.length > 1 && !currentStep.value?.disableFooterBack,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
const showDefaultNextButton = computed(() => {
|
|
265
|
+
if (isLastStep.value || currentStep.value?.primaryAction === "custom") {
|
|
266
|
+
return false
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (props.showNextWithSave) {
|
|
270
|
+
return true
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return !props.showSaveButton
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
const showCustomPrimaryButton = computed(
|
|
277
|
+
() => currentStep.value?.primaryAction === "custom",
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
const showDefaultFinishButton = computed(() => {
|
|
281
|
+
if (!isLastStep.value || currentStep.value?.primaryAction === "custom") {
|
|
282
|
+
return false
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (props.showFinishOnLastStep) {
|
|
286
|
+
return true
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return !props.showSaveButton
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
const currentStepPrimaryLabel = computed(() =>
|
|
293
|
+
currentStep.value?.primaryLabelKey
|
|
294
|
+
? t(currentStep.value.primaryLabelKey)
|
|
295
|
+
: "",
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
const currentStepPrimaryColor = computed(() =>
|
|
299
|
+
isLastStep.value && currentStep.value?.primaryAction === "custom"
|
|
300
|
+
? "success"
|
|
301
|
+
: "primary",
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
const currentStepPrimaryPrependIcon = computed(() =>
|
|
305
|
+
isLastStep.value && currentStep.value?.primaryAction === "custom"
|
|
306
|
+
? "mdi-check"
|
|
307
|
+
: undefined,
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
const currentStepPrimaryAppendIcon = computed(() =>
|
|
311
|
+
!isLastStep.value && currentStep.value?.primaryAction === "custom"
|
|
312
|
+
? "mdi-chevron-right"
|
|
313
|
+
: undefined,
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
const currentSecondaryActions = computed<StepFooterAction[]>(
|
|
317
|
+
() => currentStep.value?.secondaryActions ?? [],
|
|
318
|
+
)
|
|
319
|
+
|
|
188
320
|
const isStepAccessible = (stepNumber: number) => {
|
|
189
321
|
if (props.allowFreeNavigation) return true
|
|
190
322
|
|
|
@@ -208,7 +340,10 @@ const validateCurrentStep = () => {
|
|
|
208
340
|
}
|
|
209
341
|
}
|
|
210
342
|
|
|
211
|
-
const setCurrentStep = (
|
|
343
|
+
const setCurrentStep = (
|
|
344
|
+
step: number,
|
|
345
|
+
options?: { skipValidation?: boolean },
|
|
346
|
+
) => {
|
|
212
347
|
if (currentStepIndex.value === step) {
|
|
213
348
|
// Step is already active
|
|
214
349
|
return
|
|
@@ -219,13 +354,8 @@ const setCurrentStep = (step: number) => {
|
|
|
219
354
|
return
|
|
220
355
|
}
|
|
221
356
|
|
|
222
|
-
// // Validate step access
|
|
223
|
-
// if (!isStepAccessible(step)) {
|
|
224
|
-
// return
|
|
225
|
-
// }
|
|
226
|
-
|
|
227
357
|
// If moving forward (not backward), validate current step first
|
|
228
|
-
if (step > currentStepIndex.value) {
|
|
358
|
+
if (!options?.skipValidation && step > currentStepIndex.value) {
|
|
229
359
|
const isValid = validateCurrentStep()
|
|
230
360
|
if (!isValid) {
|
|
231
361
|
emit("validation-failed", {
|
|
@@ -249,6 +379,18 @@ const setCurrentStep = (step: number) => {
|
|
|
249
379
|
})
|
|
250
380
|
}
|
|
251
381
|
|
|
382
|
+
const onCustomAction = (actionId: string, skipValidation = false) => {
|
|
383
|
+
if (!skipValidation && !validateCurrentStep()) {
|
|
384
|
+
emit("validation-failed", {
|
|
385
|
+
step: currentStep.value,
|
|
386
|
+
stepIndex: currentStepIndex.value - 1,
|
|
387
|
+
})
|
|
388
|
+
return
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
emit("custom-action", actionId)
|
|
392
|
+
}
|
|
393
|
+
|
|
252
394
|
const save = (finish = false) => {
|
|
253
395
|
if (validateCurrentStep()) {
|
|
254
396
|
if (finish) {
|
|
@@ -259,9 +401,18 @@ const save = (finish = false) => {
|
|
|
259
401
|
}
|
|
260
402
|
}
|
|
261
403
|
|
|
404
|
+
const onExitBack = () => {
|
|
405
|
+
if (props.exitBackInteractionLinkId) {
|
|
406
|
+
triggerInteractionLinkById(props.exitBackInteractionLinkId)
|
|
407
|
+
return
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
emit("exit-back")
|
|
411
|
+
}
|
|
412
|
+
|
|
262
413
|
// Public methods (exposed to parent)
|
|
263
|
-
const goToStep = (step) => {
|
|
264
|
-
setCurrentStep(step)
|
|
414
|
+
const goToStep = (step: number, options?: { skipValidation?: boolean }) => {
|
|
415
|
+
setCurrentStep(step, options)
|
|
265
416
|
}
|
|
266
417
|
|
|
267
418
|
const nextStep = () => {
|
|
@@ -1,8 +1,26 @@
|
|
|
1
|
+
export type StepFooterAction = {
|
|
2
|
+
id: string
|
|
3
|
+
labelKey: string
|
|
4
|
+
color?: string
|
|
5
|
+
variant?: "flat" | "outlined" | "text" | "elevated" | "tonal" | "plain"
|
|
6
|
+
prependIcon?: string
|
|
7
|
+
appendIcon?: string
|
|
8
|
+
/** When true, the action does not run the step validator before emitting */
|
|
9
|
+
skipValidation?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
1
12
|
export type Step<T = string> = {
|
|
2
13
|
id: T
|
|
3
14
|
title?: string
|
|
4
15
|
subtitle?: string
|
|
5
16
|
validator?: () => boolean
|
|
17
|
+
/** Primary footer button: `next` (default for non-last steps) or `custom` */
|
|
18
|
+
primaryAction?: "next" | "custom"
|
|
19
|
+
primaryActionId?: string
|
|
20
|
+
primaryLabelKey?: string
|
|
21
|
+
secondaryActions?: StepFooterAction[]
|
|
22
|
+
/** When true, the footer Back button is hidden on this step */
|
|
23
|
+
disableFooterBack?: boolean
|
|
6
24
|
}
|
|
7
25
|
|
|
8
26
|
export type StepChangeType<T = string> = {
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
InteractionLinkCreate,
|
|
3
|
+
InteractionLinkUpdate,
|
|
4
|
+
} from "@raclettejs/core"
|
|
5
|
+
import type { DynamicFormItem } from "@app/components/dynamicForm/DynamicFormTypes"
|
|
6
|
+
import {
|
|
7
|
+
DEFAULT_SLOT_NAME,
|
|
8
|
+
DEFAULT_INLINE_SLOT_NAME,
|
|
9
|
+
DEFAULT_MODAL_SLOT_NAME,
|
|
10
|
+
} from "@raclettejs/core/orchestrator/router/routeParserHelper"
|
|
11
|
+
import { computed } from "vue"
|
|
12
|
+
import { useI18n } from "vue-i18n"
|
|
13
|
+
|
|
14
|
+
export const useInteractionLinkBehaviorFields = () => {
|
|
15
|
+
const { t } = useI18n()
|
|
16
|
+
|
|
17
|
+
const behaviorFields = computed<
|
|
18
|
+
DynamicFormItem<InteractionLinkCreate | InteractionLinkUpdate>[]
|
|
19
|
+
>(() => [
|
|
20
|
+
{
|
|
21
|
+
title: t("workbench.interactionLinkConfiguration.description.title"),
|
|
22
|
+
description: t(
|
|
23
|
+
"workbench.interactionLinkConfiguration.description.description",
|
|
24
|
+
),
|
|
25
|
+
inputType: "textarea",
|
|
26
|
+
field: "description",
|
|
27
|
+
prependIcon: "text",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
title: t("workbench.interactionLinkConfiguration.tags.title"),
|
|
31
|
+
description: t("workbench.interactionLinkConfiguration.tags.description"),
|
|
32
|
+
inputType: "tags",
|
|
33
|
+
field: "tags",
|
|
34
|
+
prependIcon: "tag-multiple",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
divider: t("workbench.interactionLinkConfiguration.dividers.behavior"),
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
title: t("workbench.interactionLinkConfiguration.slotType.title"),
|
|
41
|
+
description: t(
|
|
42
|
+
"workbench.interactionLinkConfiguration.slotType.description",
|
|
43
|
+
),
|
|
44
|
+
inputType: "select",
|
|
45
|
+
field: "slotType",
|
|
46
|
+
selectItems: [
|
|
47
|
+
DEFAULT_SLOT_NAME,
|
|
48
|
+
DEFAULT_MODAL_SLOT_NAME,
|
|
49
|
+
DEFAULT_INLINE_SLOT_NAME,
|
|
50
|
+
],
|
|
51
|
+
prependIcon: "dock-window",
|
|
52
|
+
validation: {
|
|
53
|
+
required: true,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
title: t("workbench.interactionLinkConfiguration.triggers.title"),
|
|
58
|
+
description: t(
|
|
59
|
+
"workbench.interactionLinkConfiguration.triggers.description",
|
|
60
|
+
),
|
|
61
|
+
inputType: "triggers",
|
|
62
|
+
field: "triggers",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
title: t("workbench.interactionLinkConfiguration.queryParameters.title"),
|
|
66
|
+
description: t(
|
|
67
|
+
"workbench.interactionLinkConfiguration.queryParameters.description",
|
|
68
|
+
),
|
|
69
|
+
inputType: "list",
|
|
70
|
+
field: "queryParameters",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
title: t("workbench.interactionLinkConfiguration.isLandingPage.title"),
|
|
74
|
+
description: t(
|
|
75
|
+
"workbench.interactionLinkConfiguration.isLandingPage.description",
|
|
76
|
+
),
|
|
77
|
+
inputType: "switch",
|
|
78
|
+
field: "isLandingPage",
|
|
79
|
+
},
|
|
80
|
+
])
|
|
81
|
+
|
|
82
|
+
return { behaviorFields }
|
|
83
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { User } from "@raclettejs/core"
|
|
2
|
+
import {
|
|
3
|
+
isSystemAdminTag,
|
|
4
|
+
userHasAdminTag,
|
|
5
|
+
} from "@shared/helper/adminTag"
|
|
6
|
+
import { usePluginApi } from "@raclettejs/core/orchestrator/composables"
|
|
7
|
+
import { computed } from "vue"
|
|
8
|
+
|
|
9
|
+
export const useRacletteUserIsAdmin = () => {
|
|
10
|
+
const { $data } = usePluginApi("raclette__core")
|
|
11
|
+
|
|
12
|
+
const { data: tags, isLoading: tagsLoading } = $data.tag.getAll({
|
|
13
|
+
options: { immediate: true },
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const adminTag = computed(() => {
|
|
17
|
+
if (!tags.value) return undefined
|
|
18
|
+
return Object.values(tags.value).find(isSystemAdminTag)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const isRacletteUserAdmin = (user: User | null | undefined): boolean =>
|
|
22
|
+
userHasAdminTag(user, adminTag.value)
|
|
23
|
+
|
|
24
|
+
return { isRacletteUserAdmin, adminTag, tagsLoading }
|
|
25
|
+
}
|