@revenexx/studio-integrations 0.5.1 → 0.7.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 +428 -0
- package/README.md +48 -4
- package/dist/module.d.mts +17 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +10 -2
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +43 -41
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +123 -50
- package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +108 -71
- package/dist/runtime/components/Integrations/CredentialTypePicker.vue +12 -15
- package/dist/runtime/components/Integrations/DeadLettersPanel.vue +18 -17
- package/dist/runtime/components/Integrations/EventTypeCombobox.d.vue.ts +1 -0
- package/dist/runtime/components/Integrations/EventTypeCombobox.vue +7 -4
- package/dist/runtime/components/Integrations/EventTypeCombobox.vue.d.ts +1 -0
- package/dist/runtime/components/Integrations/ExpressionInput.d.vue.ts +4 -0
- package/dist/runtime/components/Integrations/ExpressionInput.vue +67 -37
- package/dist/runtime/components/Integrations/ExpressionInput.vue.d.ts +4 -0
- package/dist/runtime/components/Integrations/GroupFrameView.vue +30 -20
- package/dist/runtime/components/Integrations/Layout.vue +48 -4
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +23 -23
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +11 -13
- package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +2 -0
- package/dist/runtime/components/Integrations/NodeConfigField.vue +187 -93
- package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +2 -0
- package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +6 -0
- package/dist/runtime/components/Integrations/NodeInspector.vue +1157 -376
- package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +6 -0
- package/dist/runtime/components/Integrations/NodeLogo.d.vue.ts +12 -0
- package/dist/runtime/components/Integrations/NodeLogo.vue +2 -30
- package/dist/runtime/components/Integrations/NodeLogo.vue.d.ts +12 -0
- package/dist/runtime/components/Integrations/NodeMark.d.vue.ts +15 -0
- package/dist/runtime/components/Integrations/NodeMark.vue +79 -0
- package/dist/runtime/components/Integrations/NodeMark.vue.d.ts +15 -0
- package/dist/runtime/components/Integrations/NodeOutputSchema.d.vue.ts +21 -0
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue +43 -0
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue.d.ts +21 -0
- package/dist/runtime/components/Integrations/NodePalette.vue +122 -55
- package/dist/runtime/components/Integrations/QueryBuilder.vue +46 -38
- package/dist/runtime/components/Integrations/RunActionsMenu.vue +38 -12
- package/dist/runtime/components/Integrations/RunDetailPanel.vue +114 -62
- package/dist/runtime/components/Integrations/RunPayloadInput.vue +27 -13
- package/dist/runtime/components/Integrations/RunResponseModal.vue +11 -10
- package/dist/runtime/components/Integrations/RunsTable.d.vue.ts +0 -1
- package/dist/runtime/components/Integrations/RunsTable.vue +38 -16
- package/dist/runtime/components/Integrations/RunsTable.vue.d.ts +0 -1
- package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue +8 -7
- package/dist/runtime/components/Integrations/Sidebar.vue +14 -10
- package/dist/runtime/components/Integrations/StickyNoteView.d.vue.ts +14 -4
- package/dist/runtime/components/Integrations/StickyNoteView.vue +38 -23
- package/dist/runtime/components/Integrations/StickyNoteView.vue.d.ts +14 -4
- package/dist/runtime/components/Integrations/TemplateCard.d.vue.ts +18 -0
- package/dist/runtime/components/Integrations/TemplateCard.vue +119 -0
- package/dist/runtime/components/Integrations/TemplateCard.vue.d.ts +18 -0
- package/dist/runtime/components/Integrations/TemplateFlowPreview.d.vue.ts +2 -2
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +16 -18
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue.d.ts +2 -2
- package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +34 -33
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +52 -67
- package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +8 -7
- package/dist/runtime/components/Integrations/WorkflowEdgeView.d.vue.ts +18 -4
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +29 -11
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue.d.ts +18 -4
- package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +20 -1
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +240 -116
- package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +20 -1
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +28 -43
- package/dist/runtime/components/Integrations/WorkflowIconPicker.d.vue.ts +19 -1
- package/dist/runtime/components/Integrations/WorkflowIconPicker.vue +17 -7
- package/dist/runtime/components/Integrations/WorkflowIconPicker.vue.d.ts +19 -1
- package/dist/runtime/components/Integrations/WorkflowNodeView.d.vue.ts +4 -0
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +198 -56
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue.d.ts +4 -0
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +56 -59
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +28 -21
- package/dist/runtime/components/Integrations/WorkflowTagsInput.d.vue.ts +1 -0
- package/dist/runtime/components/Integrations/WorkflowTagsInput.vue +15 -9
- package/dist/runtime/components/Integrations/WorkflowTagsInput.vue.d.ts +1 -0
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +15 -10
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +50 -36
- package/dist/runtime/components/Integrations/WorkflowsTable.vue +126 -48
- package/dist/runtime/composables/integrationsApi.js +4 -2
- package/dist/runtime/composables/useCredentialTypes.js +8 -6
- package/dist/runtime/composables/useIntegrationsAudit.js +4 -3
- package/dist/runtime/composables/useIntegrationsCredentials.js +10 -8
- package/dist/runtime/composables/useIntegrationsDeadLetters.js +7 -6
- package/dist/runtime/composables/useIntegrationsFocus.d.ts +12 -0
- package/dist/runtime/composables/useIntegrationsLocale.d.ts +26 -0
- package/dist/runtime/composables/useIntegrationsLocale.js +8 -0
- package/dist/runtime/composables/useIntegrationsNodes.js +7 -5
- package/dist/runtime/composables/useIntegrationsRealtime.d.ts +149 -0
- package/dist/runtime/composables/useIntegrationsRealtime.js +80 -0
- package/dist/runtime/composables/useIntegrationsRevisions.js +9 -7
- package/dist/runtime/composables/useIntegrationsRuns.d.ts +1 -1
- package/dist/runtime/composables/useIntegrationsRuns.js +14 -7
- package/dist/runtime/composables/useIntegrationsSchedules.d.ts +4 -0
- package/dist/runtime/composables/useIntegrationsSchedules.js +10 -3
- package/dist/runtime/composables/useIntegrationsSecrets.js +14 -9
- package/dist/runtime/composables/useIntegrationsT.d.ts +35 -0
- package/dist/runtime/composables/useIntegrationsT.js +9 -0
- package/dist/runtime/composables/useIntegrationsTemplates.d.ts +3 -3
- package/dist/runtime/composables/useIntegrationsTemplates.js +28 -16
- package/dist/runtime/composables/useIntegrationsTriggers.js +10 -8
- package/dist/runtime/composables/useIntegrationsWorkflows.js +13 -7
- package/dist/runtime/composables/useLoadGate.d.ts +17 -0
- package/dist/runtime/composables/useLoadGate.js +8 -0
- package/dist/runtime/composables/useNodeConfigResolve.js +2 -1
- package/dist/runtime/data/branchReorder.d.ts +37 -0
- package/dist/runtime/data/branchReorder.js +71 -0
- package/dist/runtime/data/handleId.d.ts +23 -0
- package/dist/runtime/data/handleId.js +6 -0
- package/dist/runtime/data/integrationNodeCatalog.d.ts +41 -0
- package/dist/runtime/data/integrationNodeCatalog.js +14 -0
- package/dist/runtime/data/integrationTemplates.d.ts +48 -0
- package/dist/runtime/data/integrationTemplates.js +21 -0
- package/dist/runtime/data/integrationWorkflowSamples.js +1 -1
- package/dist/runtime/data/nodeIcons.d.ts +2 -0
- package/dist/runtime/data/nodeIcons.js +73 -0
- package/dist/runtime/data/nodeInputData.d.ts +16 -4
- package/dist/runtime/data/nodeInputData.js +4 -0
- package/dist/runtime/data/payloadForm.d.ts +9 -2
- package/dist/runtime/data/payloadForm.js +29 -4
- package/dist/runtime/data/portTone.d.ts +17 -0
- package/dist/runtime/data/portTone.js +7 -0
- package/dist/runtime/data/webhookAuth.d.ts +18 -0
- package/dist/runtime/data/webhookAuth.js +19 -0
- package/dist/runtime/data/workflowIconCatalog.js +4 -6
- package/dist/runtime/i18n/messages/de.json +1207 -0
- package/dist/runtime/i18n/messages/en.json +1207 -0
- package/dist/runtime/i18n/translate.d.ts +83 -0
- package/dist/runtime/i18n/translate.js +41 -0
- package/dist/runtime/pages/integrations/audit.vue +140 -37
- package/dist/runtime/pages/integrations/credentials/oauth-complete.vue +9 -6
- package/dist/runtime/pages/integrations/credentials.vue +180 -64
- package/dist/runtime/pages/integrations/index.vue +91 -92
- package/dist/runtime/pages/integrations/runs.vue +33 -21
- package/dist/runtime/pages/integrations/schedules.vue +88 -23
- package/dist/runtime/pages/integrations/secrets.vue +57 -35
- package/dist/runtime/pages/integrations/templates/[slug].vue +119 -88
- package/dist/runtime/pages/integrations/templates/index.vue +82 -103
- package/dist/runtime/pages/integrations/workflows/[id].vue +344 -91
- package/dist/runtime/pages/integrations/workflows/index.vue +23 -5
- package/dist/runtime/plugins/locale.d.ts +10 -0
- package/dist/runtime/plugins/locale.js +6 -0
- package/dist/runtime/plugins/realtime.client.d.ts +32 -0
- package/dist/runtime/plugins/realtime.client.js +4 -0
- package/dist/runtime/types/integrations.d.ts +65 -6
- package/dist/runtime/utils/apiError.d.ts +47 -0
- package/dist/runtime/utils/apiError.js +32 -0
- package/dist/runtime/utils/cockpitSources.d.ts +153 -0
- package/dist/runtime/utils/cockpitSources.js +14 -0
- package/dist/runtime/utils/credentialState.d.ts +24 -1
- package/dist/runtime/utils/credentialState.js +26 -11
- package/dist/runtime/utils/loadGate.d.ts +61 -0
- package/dist/runtime/utils/loadGate.js +22 -0
- package/dist/runtime/utils/localize.d.ts +46 -4
- package/dist/runtime/utils/localize.js +34 -7
- package/dist/runtime/utils/notify.d.ts +59 -0
- package/dist/runtime/utils/notify.js +21 -0
- package/dist/runtime/utils/pollBudget.d.ts +72 -0
- package/dist/runtime/utils/pollBudget.js +17 -0
- package/dist/runtime/utils/revenexxCredentialSource.d.ts +26 -0
- package/dist/runtime/utils/revenexxCredentialSource.js +8 -0
- package/dist/runtime/utils/schedule.d.ts +116 -0
- package/dist/runtime/utils/schedule.js +219 -0
- package/dist/runtime/utils/statusStyle.d.ts +26 -2
- package/dist/runtime/utils/statusStyle.js +9 -1
- package/dist/runtime/utils/validationFindings.d.ts +101 -0
- package/dist/runtime/utils/validationFindings.js +54 -0
- package/dist/runtime/utils/whenIdle.d.ts +42 -0
- package/dist/runtime/utils/whenIdle.js +12 -0
- package/dist/types.d.mts +2 -6
- package/package.json +10 -3
|
@@ -12,9 +12,12 @@ const props = defineProps({
|
|
|
12
12
|
modelValue: { type: null, required: true },
|
|
13
13
|
placeholder: { type: String, required: false },
|
|
14
14
|
variables: { type: Object, required: false },
|
|
15
|
-
upstreamOutputs: { type: Array, required: false }
|
|
15
|
+
upstreamOutputs: { type: Array, required: false },
|
|
16
|
+
invalid: { type: Boolean, required: false },
|
|
17
|
+
label: { type: String, required: false }
|
|
16
18
|
});
|
|
17
19
|
const emit = defineEmits(["update:modelValue"]);
|
|
20
|
+
const t = useIntegrationsT();
|
|
18
21
|
const text = computed(() => String(props.modelValue ?? ""));
|
|
19
22
|
const OPEN = "${{ ";
|
|
20
23
|
const CLOSE = " }}";
|
|
@@ -32,12 +35,12 @@ function pillLabel(expr) {
|
|
|
32
35
|
}
|
|
33
36
|
if (path[0] === "trigger" && path[1] === "payload") {
|
|
34
37
|
const rest = path.slice(2).join(".");
|
|
35
|
-
return rest ? "
|
|
38
|
+
return rest ? t("integration.nodeConfig.expression.pill.triggerPath", { rest }) : t("integration.nodeConfig.expression.pill.triggerPayload");
|
|
36
39
|
}
|
|
37
|
-
if (path[0] === "vars") return "
|
|
40
|
+
if (path[0] === "vars") return t("integration.nodeConfig.expression.pill.variable", { path: path.slice(1).join(".") });
|
|
38
41
|
if (path[0] === "in") {
|
|
39
42
|
const rest = path.slice(1).join(".");
|
|
40
|
-
return rest ? "
|
|
43
|
+
return rest ? t("integration.nodeConfig.expression.pill.inputPath", { rest }) : t("integration.nodeConfig.expression.pill.inputPayload");
|
|
41
44
|
}
|
|
42
45
|
return expr;
|
|
43
46
|
}
|
|
@@ -78,7 +81,11 @@ const editor = new Editor({
|
|
|
78
81
|
content: textToDoc(text.value),
|
|
79
82
|
editorProps: {
|
|
80
83
|
// Single line: swallow Enter (paragraph splits would break the field).
|
|
81
|
-
handleKeyDown: (_view, event) => event.key === "Enter" && !event.isComposing
|
|
84
|
+
handleKeyDown: (_view, event) => event.key === "Enter" && !event.isComposing,
|
|
85
|
+
// A contenteditable region is not a text box to assistive technology
|
|
86
|
+
// unless it says so, and its label sits in a sibling element — so it would
|
|
87
|
+
// otherwise be an anonymous blob to a screen reader.
|
|
88
|
+
attributes: { role: "textbox", ...props.label ? { "aria-label": props.label } : {} }
|
|
82
89
|
},
|
|
83
90
|
onUpdate({ editor: ed }) {
|
|
84
91
|
const json = ed.getJSON();
|
|
@@ -98,9 +105,9 @@ onBeforeUnmount(() => {
|
|
|
98
105
|
if (insertHandler?.value === insertToken) insertHandler.value = null;
|
|
99
106
|
editor.destroy();
|
|
100
107
|
});
|
|
101
|
-
watch(text, (
|
|
102
|
-
if (docToText(editor.getJSON()) !==
|
|
103
|
-
editor.commands.setContent(textToDoc(
|
|
108
|
+
watch(text, (t2) => {
|
|
109
|
+
if (docToText(editor.getJSON()) !== t2) {
|
|
110
|
+
editor.commands.setContent(textToDoc(t2));
|
|
104
111
|
}
|
|
105
112
|
});
|
|
106
113
|
function insertToken(expr) {
|
|
@@ -120,7 +127,7 @@ const sections = computed(() => {
|
|
|
120
127
|
label: key,
|
|
121
128
|
expr: tok("vars." + key)
|
|
122
129
|
}));
|
|
123
|
-
if (varItems.length) out.push({ heading: "
|
|
130
|
+
if (varItems.length) out.push({ heading: t("integration.nodeConfig.expression.suggestion.heading.variables"), items: varItems });
|
|
124
131
|
const upstream = props.upstreamOutputs ?? [];
|
|
125
132
|
const nodeItems = upstream.flatMap((u) => {
|
|
126
133
|
const base = "nodes." + u.nodeId + ".outputs." + u.output;
|
|
@@ -133,16 +140,22 @@ const sections = computed(() => {
|
|
|
133
140
|
}
|
|
134
141
|
return rows;
|
|
135
142
|
});
|
|
136
|
-
if (nodeItems.length) out.push({ heading: "
|
|
143
|
+
if (nodeItems.length) out.push({ heading: t("integration.nodeConfig.expression.suggestion.heading.nodeOutputs"), items: nodeItems });
|
|
137
144
|
const primary = upstream[0];
|
|
138
|
-
const inItems = [{ label: "
|
|
145
|
+
const inItems = [{ label: t("integration.nodeConfig.expression.suggestion.inputPayload"), expr: tok("in") }];
|
|
139
146
|
for (const f of primary?.fields ?? []) {
|
|
140
147
|
inItems.push({ label: "input." + f, expr: tok("in." + f) });
|
|
141
148
|
}
|
|
142
|
-
out.push({ heading: "
|
|
149
|
+
out.push({ heading: t("integration.nodeConfig.expression.suggestion.heading.input"), items: inItems });
|
|
143
150
|
return out;
|
|
144
151
|
});
|
|
145
152
|
const hasSuggestions = computed(() => sections.value.some((s) => s.items.length > 0));
|
|
153
|
+
const query = ref("");
|
|
154
|
+
const filteredSections = computed(() => {
|
|
155
|
+
const q = query.value.trim().toLowerCase();
|
|
156
|
+
if (!q) return sections.value;
|
|
157
|
+
return sections.value.map((s) => ({ ...s, items: s.items.filter((i) => i.label.toLowerCase().includes(q) || i.expr.toLowerCase().includes(q)) })).filter((s) => s.items.length > 0);
|
|
158
|
+
});
|
|
146
159
|
const open = ref(false);
|
|
147
160
|
const wrapRef = ref(null);
|
|
148
161
|
function toggle() {
|
|
@@ -163,6 +176,9 @@ function onKeydown(e) {
|
|
|
163
176
|
function onDocMousedown(e) {
|
|
164
177
|
if (wrapRef.value && !wrapRef.value.contains(e.target)) open.value = false;
|
|
165
178
|
}
|
|
179
|
+
watch(open, (isOpen) => {
|
|
180
|
+
if (!isOpen) query.value = "";
|
|
181
|
+
});
|
|
166
182
|
onMounted(() => document.addEventListener("mousedown", onDocMousedown));
|
|
167
183
|
onBeforeUnmount(() => document.removeEventListener("mousedown", onDocMousedown));
|
|
168
184
|
function insert(expr) {
|
|
@@ -174,7 +190,10 @@ function insert(expr) {
|
|
|
174
190
|
<template>
|
|
175
191
|
<div ref="wrapRef" class="relative" @focusout="onFocusOut">
|
|
176
192
|
<div class="flex items-center gap-1">
|
|
177
|
-
<div
|
|
193
|
+
<div
|
|
194
|
+
class="expr-editor relative h-8 min-w-0 flex-1 overflow-hidden rounded-md border bg-white px-2 text-sm transition focus-within:outline-2 focus-within:-outline-offset-1 focus-within:outline-ring"
|
|
195
|
+
:class="invalid ? 'border-rose-400' : 'border-black/[0.08]'"
|
|
196
|
+
>
|
|
178
197
|
<span
|
|
179
198
|
v-if="!text"
|
|
180
199
|
class="pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 truncate text-muted-foreground/60"
|
|
@@ -183,7 +202,7 @@ function insert(expr) {
|
|
|
183
202
|
</div>
|
|
184
203
|
<button
|
|
185
204
|
type="button"
|
|
186
|
-
title="
|
|
205
|
+
:title="t('integration.nodeConfig.expression.insertTitle')"
|
|
187
206
|
class="flex h-8 shrink-0 items-center rounded-md border border-black/[0.08] bg-white px-1.5 font-mono text-sm text-muted-foreground transition hover:text-foreground hover:bg-muted/30"
|
|
188
207
|
:class="open ? 'ring-2 ring-emerald-500/40' : ''"
|
|
189
208
|
@mousedown.prevent="toggle"
|
|
@@ -195,7 +214,7 @@ function insert(expr) {
|
|
|
195
214
|
<p
|
|
196
215
|
v-if="preview"
|
|
197
216
|
class="mt-1 flex min-w-0 items-baseline gap-1 text-[11px]"
|
|
198
|
-
:title="preview.misses ? '
|
|
217
|
+
:title="preview.misses ? t('integration.nodeConfig.expression.preview.missTitle') : preview.resolved"
|
|
199
218
|
>
|
|
200
219
|
<span class="shrink-0 text-muted-foreground">→</span>
|
|
201
220
|
<span class="min-w-0 truncate font-mono" :class="preview.misses ? 'text-amber-600' : 'text-emerald-700'">{{ preview.resolved }}</span>
|
|
@@ -203,34 +222,45 @@ function insert(expr) {
|
|
|
203
222
|
|
|
204
223
|
<div
|
|
205
224
|
v-if="open && hasSuggestions"
|
|
206
|
-
class="absolute right-0 top-full z-50 mt-1 max-h-
|
|
225
|
+
class="absolute right-0 top-full z-50 mt-1 flex max-h-72 w-80 flex-col rounded-md border border-black/[0.08] bg-white text-sm shadow-lg"
|
|
207
226
|
>
|
|
208
|
-
<
|
|
209
|
-
<
|
|
210
|
-
v-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
<button
|
|
216
|
-
v-for="item in section.items"
|
|
217
|
-
:key="item.expr"
|
|
218
|
-
type="button"
|
|
219
|
-
class="block w-full px-2 py-1 text-left transition hover:bg-muted/50"
|
|
220
|
-
@mousedown.prevent="insert(item.expr)"
|
|
227
|
+
<div class="shrink-0 border-b border-black/[0.06] p-1">
|
|
228
|
+
<input
|
|
229
|
+
v-model="query"
|
|
230
|
+
:aria-label="t('integration.nodeConfig.expression.picker.filterAria')"
|
|
231
|
+
:placeholder="t('integration.nodeConfig.expression.picker.filterPlaceholder')"
|
|
232
|
+
class="w-full rounded px-1.5 py-1 text-sm outline-none placeholder:text-muted-foreground/60 focus:ring-2 focus:ring-primary/40"
|
|
233
|
+
@mousedown.stop
|
|
221
234
|
>
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
235
|
+
</div>
|
|
236
|
+
<div class="min-h-0 flex-1 overflow-y-auto py-1">
|
|
237
|
+
<p v-if="!filteredSections.length" class="px-2 py-1.5 text-muted-foreground">
|
|
238
|
+
{{ t("integration.nodeConfig.expression.picker.empty", { query }) }}
|
|
239
|
+
</p>
|
|
240
|
+
<template v-for="section in filteredSections" :key="section.heading">
|
|
241
|
+
<div class="px-2 pb-0.5 pt-1.5 text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
|
|
242
|
+
{{ section.heading }}
|
|
227
243
|
</div>
|
|
228
|
-
|
|
229
|
-
|
|
244
|
+
<button
|
|
245
|
+
v-for="item in section.items"
|
|
246
|
+
:key="item.expr"
|
|
247
|
+
type="button"
|
|
248
|
+
class="block w-full px-2 py-1 text-left transition hover:bg-muted/50"
|
|
249
|
+
@mousedown.prevent="insert(item.expr)"
|
|
250
|
+
>
|
|
251
|
+
<div class="truncate text-muted-foreground">
|
|
252
|
+
{{ item.label }}
|
|
253
|
+
</div>
|
|
254
|
+
<div class="truncate font-mono text-emerald-700">
|
|
255
|
+
{{ item.expr }}
|
|
256
|
+
</div>
|
|
257
|
+
</button>
|
|
258
|
+
</template>
|
|
259
|
+
</div>
|
|
230
260
|
</div>
|
|
231
261
|
</div>
|
|
232
262
|
</template>
|
|
233
263
|
|
|
234
264
|
<style>
|
|
235
|
-
.expr-editor .ProseMirror{align-items:center;display:flex;height:100%;outline:none;overflow-x:auto;scrollbar-width:none}.expr-editor .ProseMirror p{line-height:30px;margin:0;white-space:pre}.expr-pill{background:rgba(16,185,129,.1);border:1px solid rgba(16,185,129,.25);border-radius:6px;color:#047857;display:inline-
|
|
265
|
+
.expr-editor .ProseMirror{align-items:center;display:flex;height:100%;outline:none;overflow-x:auto;scrollbar-width:none}.expr-editor .ProseMirror p{line-height:30px;margin:0;white-space:pre}.expr-pill{align-items:center;background:rgba(16,185,129,.1);border:1px solid rgba(16,185,129,.25);border-radius:6px;color:#047857;display:inline-flex;font-family:ui-sans-serif,system-ui,sans-serif;font-size:11px;height:20px;line-height:1;margin:0 2px;padding:0 7px;-webkit-user-select:none;-moz-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.expr-pill.ProseMirror-selectednode{outline:2px solid rgba(16,185,129,.5)}
|
|
236
266
|
</style>
|
|
@@ -10,6 +10,10 @@ type __VLS_Props = {
|
|
|
10
10
|
placeholder?: string;
|
|
11
11
|
variables?: Record<string, WorkflowVariable>;
|
|
12
12
|
upstreamOutputs?: UpstreamOutput[];
|
|
13
|
+
/** Draw the editor as at fault — the field above owns the reason. */
|
|
14
|
+
invalid?: boolean;
|
|
15
|
+
/** The setting's label, so the editable region has an accessible name. */
|
|
16
|
+
label?: string;
|
|
13
17
|
};
|
|
14
18
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
15
19
|
"update:modelValue": (value: string) => any;
|
|
@@ -2,33 +2,37 @@
|
|
|
2
2
|
import { computed, nextTick, ref } from "vue";
|
|
3
3
|
import { Handle, Position, useVueFlow } from "@vue-flow/core";
|
|
4
4
|
import { NodeToolbar } from "@vue-flow/node-toolbar";
|
|
5
|
-
import { ChevronsDownUp, ChevronsUpDown,
|
|
5
|
+
import { ChevronsDownUp, ChevronsUpDown, Ungroup } from "lucide-vue-next";
|
|
6
|
+
import { TrashBinMinimalistic as TrashBin } from "#solar-icons";
|
|
7
|
+
import { resolveDisplayName } from "../../utils/localize";
|
|
6
8
|
const props = defineProps({
|
|
7
9
|
id: { type: String, required: true },
|
|
8
10
|
data: { type: Object, required: true },
|
|
9
11
|
selected: { type: Boolean, required: false }
|
|
10
12
|
});
|
|
11
13
|
defineEmits(["updateNodeInternals"]);
|
|
14
|
+
const t = useIntegrationsT();
|
|
12
15
|
const GROUP_COLORS = [
|
|
13
|
-
{ bg: "rgba(254,243,199,0.45)", border: "#fcd34d", chip: "#f59e0b" },
|
|
14
|
-
{ bg: "rgba(255,237,213,0.45)", border: "#fdba74", chip: "#f97316" },
|
|
15
|
-
{ bg: "rgba(255,228,230,0.45)", border: "#fda4af", chip: "#f43f5e" },
|
|
16
|
-
{ bg: "rgba(220,252,231,0.45)", border: "#86efac", chip: "#10b981" },
|
|
17
|
-
{ bg: "rgba(219,234,254,0.45)", border: "#93c5fd", chip: "#3b82f6" },
|
|
18
|
-
{ bg: "rgba(237,233,254,0.45)", border: "#c4b5fd", chip: "#8b5cf6" },
|
|
19
|
-
{ bg: "rgba(245,245,244,0.6)", border: "#d6d3d1", chip: "#78716c" }
|
|
16
|
+
{ nameKey: "integration.canvas.color.amber", bg: "rgba(254,243,199,0.45)", border: "#fcd34d", chip: "#f59e0b" },
|
|
17
|
+
{ nameKey: "integration.canvas.color.orange", bg: "rgba(255,237,213,0.45)", border: "#fdba74", chip: "#f97316" },
|
|
18
|
+
{ nameKey: "integration.canvas.color.rose", bg: "rgba(255,228,230,0.45)", border: "#fda4af", chip: "#f43f5e" },
|
|
19
|
+
{ nameKey: "integration.canvas.color.green", bg: "rgba(220,252,231,0.45)", border: "#86efac", chip: "#10b981" },
|
|
20
|
+
{ nameKey: "integration.canvas.color.blue", bg: "rgba(219,234,254,0.45)", border: "#93c5fd", chip: "#3b82f6" },
|
|
21
|
+
{ nameKey: "integration.canvas.color.violet", bg: "rgba(237,233,254,0.45)", border: "#c4b5fd", chip: "#8b5cf6" },
|
|
22
|
+
{ nameKey: "integration.canvas.color.gray", bg: "rgba(245,245,244,0.6)", border: "#d6d3d1", chip: "#78716c" }
|
|
20
23
|
];
|
|
21
24
|
const color = computed(() => GROUP_COLORS[props.data.group.color ?? 4] ?? GROUP_COLORS[4]);
|
|
22
|
-
|
|
23
|
-
if (!v) return "";
|
|
24
|
-
return typeof v === "string" ? v : v.en ?? Object.values(v)[0] ?? "";
|
|
25
|
-
}
|
|
26
|
-
const label = computed(() => loc(props.data.group.label) || "Group");
|
|
25
|
+
const label = computed(() => resolveDisplayName(props.data.group.label) || t("integration.canvas.group.defaultLabel"));
|
|
27
26
|
const collapsed = computed(() => props.data.group.collapsed === true);
|
|
28
27
|
const editable = computed(() => !!props.data.actions);
|
|
29
28
|
const editing = ref(false);
|
|
30
29
|
const draft = ref("");
|
|
31
30
|
const inputEl = ref(null);
|
|
31
|
+
function onLabelDoubleClick(event) {
|
|
32
|
+
if (!editable.value) return;
|
|
33
|
+
event.stopPropagation();
|
|
34
|
+
startRename();
|
|
35
|
+
}
|
|
32
36
|
function startRename() {
|
|
33
37
|
if (!editable.value) return;
|
|
34
38
|
draft.value = label.value;
|
|
@@ -54,7 +58,7 @@ function onHoverLeave() {
|
|
|
54
58
|
}, 250);
|
|
55
59
|
}
|
|
56
60
|
const toolbarVisible = computed(() => editable.value && !editing.value && (hovered.value || !!props.selected));
|
|
57
|
-
const memberCountText = computed(() =>
|
|
61
|
+
const memberCountText = computed(() => t("integration.canvas.group.memberCount", props.data.memberCount));
|
|
58
62
|
const flow = useVueFlow();
|
|
59
63
|
const resizing = ref(false);
|
|
60
64
|
const liveW = ref(0);
|
|
@@ -99,13 +103,15 @@ function onResizeEnd() {
|
|
|
99
103
|
class="size-4.5 rounded-full border transition hover:scale-110"
|
|
100
104
|
:class="(data.group.color ?? 4) === i ? 'ring-2 ring-black/25 ring-offset-1' : ''"
|
|
101
105
|
:style="{ backgroundColor: c.chip, borderColor: c.border, width: '16px', height: '16px' }"
|
|
106
|
+
:title="t(c.nameKey)"
|
|
107
|
+
:aria-label="t(c.nameKey)"
|
|
102
108
|
@click="data.actions?.setColor(i)"
|
|
103
109
|
/>
|
|
104
110
|
<div class="mx-0.5 h-4 w-px bg-black/10" />
|
|
105
111
|
<button
|
|
106
112
|
type="button"
|
|
107
113
|
class="flex size-6 items-center justify-center rounded-md text-muted-foreground transition hover:bg-muted/50 hover:text-foreground"
|
|
108
|
-
:title="collapsed ? '
|
|
114
|
+
:title="collapsed ? t('integration.canvas.group.action.expand') : t('integration.canvas.group.action.collapse')"
|
|
109
115
|
@click="data.actions?.toggleCollapse()"
|
|
110
116
|
>
|
|
111
117
|
<component :is="collapsed ? ChevronsUpDown : ChevronsDownUp" :size="13" />
|
|
@@ -113,7 +119,7 @@ function onResizeEnd() {
|
|
|
113
119
|
<button
|
|
114
120
|
type="button"
|
|
115
121
|
class="flex size-6 items-center justify-center rounded-md text-muted-foreground transition hover:bg-muted/50 hover:text-foreground"
|
|
116
|
-
title="
|
|
122
|
+
:title="t('integration.canvas.group.action.ungroup')"
|
|
117
123
|
@click="data.actions?.ungroup()"
|
|
118
124
|
>
|
|
119
125
|
<Ungroup :size="13" />
|
|
@@ -121,10 +127,10 @@ function onResizeEnd() {
|
|
|
121
127
|
<button
|
|
122
128
|
type="button"
|
|
123
129
|
class="flex size-6 items-center justify-center rounded-md text-muted-foreground transition hover:bg-rose-50 hover:text-rose-600"
|
|
124
|
-
title="
|
|
130
|
+
:title="t('integration.canvas.group.action.removeFrame')"
|
|
125
131
|
@click="data.actions?.ungroup()"
|
|
126
132
|
>
|
|
127
|
-
<
|
|
133
|
+
<TrashBin :size="13" />
|
|
128
134
|
</button>
|
|
129
135
|
</div>
|
|
130
136
|
</NodeToolbar>
|
|
@@ -137,6 +143,9 @@ function onResizeEnd() {
|
|
|
137
143
|
:position="Position.Top"
|
|
138
144
|
class="!size-3 !rounded-full !border-2 !border-white !bg-slate-500 !shadow-sm"
|
|
139
145
|
/>
|
|
146
|
+
<!-- The collapsed card stops its own double-click unconditionally, which is
|
|
147
|
+
safe: both read-only panels build every frame with `collapsed: false`, so
|
|
148
|
+
this surface is the editor's alone. -->
|
|
140
149
|
<div
|
|
141
150
|
class="w-[220px] rounded-lg border-2 bg-white transition-all duration-150"
|
|
142
151
|
:style="{ borderColor: color.border }"
|
|
@@ -184,6 +193,7 @@ function onResizeEnd() {
|
|
|
184
193
|
v-if="editing"
|
|
185
194
|
ref="inputEl"
|
|
186
195
|
v-model="draft"
|
|
196
|
+
:aria-label="t('integration.canvas.group.input.nameAria')"
|
|
187
197
|
class="nodrag w-40 rounded border border-black/[0.1] bg-white px-1 py-0.5 text-sm font-semibold focus:outline-none focus:ring-2 focus:ring-primary/40"
|
|
188
198
|
@blur="commitRename"
|
|
189
199
|
@keydown.enter.prevent="commitRename"
|
|
@@ -194,7 +204,7 @@ function onResizeEnd() {
|
|
|
194
204
|
v-else
|
|
195
205
|
class="select-none text-sm font-semibold"
|
|
196
206
|
:style="{ color: color.chip }"
|
|
197
|
-
@dblclick
|
|
207
|
+
@dblclick="onLabelDoubleClick"
|
|
198
208
|
>
|
|
199
209
|
{{ label }}
|
|
200
210
|
</span>
|
|
@@ -206,7 +216,7 @@ function onResizeEnd() {
|
|
|
206
216
|
v-if="editable"
|
|
207
217
|
class="nodrag pointer-events-auto absolute bottom-0 right-0 flex size-5 cursor-se-resize items-end justify-end p-1"
|
|
208
218
|
:style="{ color: color.chip }"
|
|
209
|
-
title="
|
|
219
|
+
:title="t('integration.canvas.group.action.resize')"
|
|
210
220
|
@pointerdown="onResizeStart"
|
|
211
221
|
@pointermove="onResizeMove"
|
|
212
222
|
@pointerup="onResizeEnd"
|
|
@@ -1,20 +1,64 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import { nextTick, onMounted, ref } from "vue";
|
|
2
3
|
import Sidebar from "./Sidebar.vue";
|
|
4
|
+
const t = useIntegrationsT();
|
|
3
5
|
const { focused } = useIntegrationsFocus();
|
|
6
|
+
const animate = ref(false);
|
|
7
|
+
onMounted(() => nextTick(() => {
|
|
8
|
+
animate.value = true;
|
|
9
|
+
}));
|
|
4
10
|
</script>
|
|
5
11
|
|
|
6
12
|
<template>
|
|
7
13
|
<div class="flex h-[calc(100%-0.75rem)] mb-3 mr-3 ml-1 overflow-hidden rounded-lg border border-gray-400/10 outline-1 outline-white/30">
|
|
14
|
+
<!-- First thing in the tab order: jumps past the sidebar into the page. -->
|
|
15
|
+
<a
|
|
16
|
+
href="#integrations-main"
|
|
17
|
+
class="sr-only focus:not-sr-only focus:absolute focus:left-3 focus:top-3 focus:z-50 focus:rounded-md focus:bg-white focus:px-3 focus:py-1.5 focus:text-sm focus:font-medium focus:ring-2 focus:ring-primary/40"
|
|
18
|
+
>
|
|
19
|
+
{{ t("integration.shell.action.skipToContent") }}
|
|
20
|
+
</a>
|
|
21
|
+
<!-- `inert` + `aria-hidden` are what make the fold real. The inner wrapper
|
|
22
|
+
below keeps its own `w-64` so the slide-out animates, so a folded rail
|
|
23
|
+
is merely clipped by `overflow-hidden`: its links keep their size and
|
|
24
|
+
stay focusable at a negative x, and a keyboard walks into a rail nobody
|
|
25
|
+
can see (PO-273). `display: none` would fix that too and take the
|
|
26
|
+
animation with it.
|
|
27
|
+
|
|
28
|
+
Both attributes are written to be *absent* rather than false when the
|
|
29
|
+
rail stands: `inert` is presence-based, so a renderer that emitted
|
|
30
|
+
`inert="false"` as an attribute would hold the standing rail back
|
|
31
|
+
instead. That the standing rail really does take focus is asserted in
|
|
32
|
+
its own right — specs/studio-navigation.md AC-1 is the control for AC-5.
|
|
33
|
+
|
|
34
|
+
The id is how a test reaches the rail while it is folded: out of the
|
|
35
|
+
accessibility tree, it can no longer be addressed by its role, and
|
|
36
|
+
`nav` alone would catch any navigation the page grows later. -->
|
|
8
37
|
<div
|
|
9
|
-
|
|
10
|
-
|
|
38
|
+
id="integrations-nav"
|
|
39
|
+
class="shrink-0 overflow-hidden border-r border-black/[0.06] bg-white"
|
|
40
|
+
:class="[focused ? 'w-0 !border-r-0' : 'w-64', animate ? 'transition-[width] duration-200 ease-out' : '']"
|
|
41
|
+
:inert="focused || void 0"
|
|
42
|
+
:aria-hidden="focused ? 'true' : void 0"
|
|
11
43
|
>
|
|
12
44
|
<div class="h-full w-64 overflow-y-auto">
|
|
13
45
|
<Sidebar />
|
|
14
46
|
</div>
|
|
15
47
|
</div>
|
|
16
|
-
|
|
48
|
+
<!-- `tabindex="-1"` is what makes the skip link actually skip: a <main> is
|
|
49
|
+
not focusable on its own, so the browser would move the fragment but
|
|
50
|
+
leave focus on the link — and the next Tab would walk into the sidebar
|
|
51
|
+
the user just asked to be past. It stays out of the tab order (-1, not
|
|
52
|
+
0); only the fragment and a script can put focus here — which is also
|
|
53
|
+
why it draws no focus ring: a ring around the whole page would be noise
|
|
54
|
+
on a region nobody tabs to, and the promise of a visible mark is about
|
|
55
|
+
the fields, buttons and links inside it. -->
|
|
56
|
+
<main
|
|
57
|
+
id="integrations-main"
|
|
58
|
+
tabindex="-1"
|
|
59
|
+
class="flex-1 min-w-0 overflow-y-auto bg-white/40 backdrop-blur-sm outline-none"
|
|
60
|
+
>
|
|
17
61
|
<slot />
|
|
18
|
-
</
|
|
62
|
+
</main>
|
|
19
63
|
</div>
|
|
20
64
|
</template>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { computed, ref } from "vue";
|
|
3
3
|
import { ChevronDown } from "lucide-vue-next";
|
|
4
4
|
import { Input } from "@revenexx/studio";
|
|
5
|
+
import { resolveLocalized } from "../../utils/localize";
|
|
5
6
|
const props = defineProps({
|
|
6
7
|
slug: { type: String, required: true },
|
|
7
8
|
version: { type: String, required: true },
|
|
@@ -10,10 +11,7 @@ const props = defineProps({
|
|
|
10
11
|
modelValue: { type: null, required: true }
|
|
11
12
|
});
|
|
12
13
|
const emit = defineEmits(["update:modelValue"]);
|
|
13
|
-
|
|
14
|
-
if (!v) return "";
|
|
15
|
-
return typeof v === "string" ? v : v.en ?? Object.values(v)[0] ?? "";
|
|
16
|
-
}
|
|
14
|
+
const t = useIntegrationsT();
|
|
17
15
|
const search = ref("");
|
|
18
16
|
const focused = ref(false);
|
|
19
17
|
const q = useNodeConfigResolve({
|
|
@@ -33,14 +31,14 @@ const currentKnown = computed(
|
|
|
33
31
|
);
|
|
34
32
|
const currentLabel = computed(() => {
|
|
35
33
|
const hit = options.value.find((o) => String(o.value) === currentValue.value);
|
|
36
|
-
return hit ?
|
|
34
|
+
return hit ? resolveLocalized(hit.label) || String(hit.value) : currentValue.value;
|
|
37
35
|
});
|
|
38
36
|
const listOpen = computed(() => focused.value && !q.gated.value);
|
|
39
37
|
const visibleOptions = computed(() => {
|
|
40
38
|
const term = search.value.trim().toLowerCase();
|
|
41
39
|
if (!focused.value || !term) return options.value;
|
|
42
40
|
const hits = options.value.filter(
|
|
43
|
-
(o) => (
|
|
41
|
+
(o) => (resolveLocalized(o.label) || "").toLowerCase().includes(term) || String(o.value).toLowerCase().includes(term)
|
|
44
42
|
);
|
|
45
43
|
return hits.length ? hits : options.value;
|
|
46
44
|
});
|
|
@@ -66,7 +64,7 @@ const visibleMultiOptions = computed(() => {
|
|
|
66
64
|
const f = multiFilter.value.trim().toLowerCase();
|
|
67
65
|
if (!f) return options.value;
|
|
68
66
|
return options.value.filter(
|
|
69
|
-
(o) => (
|
|
67
|
+
(o) => (resolveLocalized(o.label) || String(o.value)).toLowerCase().includes(f) || String(o.value).toLowerCase().includes(f)
|
|
70
68
|
);
|
|
71
69
|
});
|
|
72
70
|
function toggleMulti(optValue) {
|
|
@@ -81,25 +79,25 @@ function toggleMulti(optValue) {
|
|
|
81
79
|
<!-- gated: required dependencies still unset -->
|
|
82
80
|
<template v-if="q.gated.value">
|
|
83
81
|
<div :class="disabledControlClass">—</div>
|
|
84
|
-
<p class="mt-1 text-sm text-amber-600">
|
|
82
|
+
<p class="mt-1 text-sm text-amber-600">{{ t("integration.nodeConfig.dynamicField.gated", { keys: q.unsatisfied.value.join(", ") }) }}</p>
|
|
85
83
|
</template>
|
|
86
84
|
|
|
87
85
|
<!-- first load: no data yet -->
|
|
88
86
|
<template v-else-if="q.isFetching.value && options.length === 0">
|
|
89
|
-
<div :class="disabledControlClass">resolving
|
|
87
|
+
<div :class="disabledControlClass">{{ t("integration.nodeConfig.dynamicField.resolving") }}</div>
|
|
90
88
|
</template>
|
|
91
89
|
|
|
92
90
|
<!-- error: build/runtime/etc -->
|
|
93
91
|
<template v-else-if="q.error.value">
|
|
94
92
|
<div :class="disabledControlClass">—</div>
|
|
95
93
|
<div class="mt-1 flex items-center gap-2 text-sm text-amber-600">
|
|
96
|
-
<span>{{ q.error.value.kind === "building" ? "
|
|
94
|
+
<span>{{ q.error.value.kind === "building" ? t("integration.nodeConfig.resolve.error.building") : q.error.value.message }}</span>
|
|
97
95
|
<button
|
|
98
96
|
type="button"
|
|
99
97
|
class="rounded border border-black/[0.08] px-1.5 py-0.5 text-mono text-muted-foreground transition hover:text-foreground"
|
|
100
98
|
@click="q.refetch()"
|
|
101
99
|
>
|
|
102
|
-
|
|
100
|
+
{{ t("integration.nodeConfig.resolve.action.retry") }}
|
|
103
101
|
</button>
|
|
104
102
|
</div>
|
|
105
103
|
</template>
|
|
@@ -109,12 +107,13 @@ function toggleMulti(optValue) {
|
|
|
109
107
|
<Input
|
|
110
108
|
v-if="options.length > 10"
|
|
111
109
|
v-model="multiFilter"
|
|
112
|
-
|
|
110
|
+
:aria-label="t('integration.nodeConfig.dynamicField.multiselect.filterAria', { label: resolveLocalized(field.label) })"
|
|
111
|
+
:placeholder="t('integration.nodeConfig.dynamicField.multiselect.filterPlaceholder')"
|
|
113
112
|
class="mb-1.5 h-7 bg-white text-sm"
|
|
114
113
|
/>
|
|
115
|
-
<div class="flex flex-wrap gap-1">
|
|
114
|
+
<div role="group" :aria-label="resolveLocalized(field.label)" class="flex flex-wrap gap-1">
|
|
116
115
|
<span v-if="visibleMultiOptions.length === 0" class="text-sm text-muted-foreground">
|
|
117
|
-
{{ options.length === 0 ? "
|
|
116
|
+
{{ options.length === 0 ? t("integration.nodeConfig.dynamicField.empty") : t("integration.nodeConfig.dynamicField.emptyFiltered") }}
|
|
118
117
|
</span>
|
|
119
118
|
<button
|
|
120
119
|
v-for="opt in visibleMultiOptions"
|
|
@@ -125,7 +124,7 @@ function toggleMulti(optValue) {
|
|
|
125
124
|
:class="selectedArray.includes(String(opt.value)) ? 'bg-emerald-500/15 text-emerald-700 border-emerald-500/30' : 'bg-white text-muted-foreground hover:text-foreground'"
|
|
126
125
|
@click="toggleMulti(String(opt.value))"
|
|
127
126
|
>
|
|
128
|
-
{{
|
|
127
|
+
{{ resolveLocalized(opt.label) || String(opt.value) }}
|
|
129
128
|
</button>
|
|
130
129
|
<!-- stored values no longer offered — shown read-only so they stay visible -->
|
|
131
130
|
<span
|
|
@@ -133,10 +132,10 @@ function toggleMulti(optValue) {
|
|
|
133
132
|
:key="m"
|
|
134
133
|
class="rounded-md border border-black/[0.08] bg-white px-1.5 py-0.5 text-sm text-muted-foreground opacity-60"
|
|
135
134
|
>
|
|
136
|
-
{{
|
|
135
|
+
{{ t("integration.nodeConfig.dynamicField.notInList", { value: m }) }}
|
|
137
136
|
</span>
|
|
138
137
|
</div>
|
|
139
|
-
<p v-if="q.isFetching.value && q.hasData.value" class="mt-1 text-sm text-muted-foreground">reloading
|
|
138
|
+
<p v-if="q.isFetching.value && q.hasData.value" class="mt-1 text-sm text-muted-foreground">{{ t("integration.nodeConfig.dynamicField.reloading") }}</p>
|
|
140
139
|
</template>
|
|
141
140
|
|
|
142
141
|
<!-- single select: searchable combobox; typing re-resolves with the term -->
|
|
@@ -144,7 +143,8 @@ function toggleMulti(optValue) {
|
|
|
144
143
|
<div class="relative">
|
|
145
144
|
<Input
|
|
146
145
|
:model-value="focused ? search : currentLabel"
|
|
147
|
-
:
|
|
146
|
+
:aria-label="resolveLocalized(field.label)"
|
|
147
|
+
:placeholder="resolveLocalized(field.placeholder) || t('integration.nodeConfig.select.placeholder')"
|
|
148
148
|
class="h-8 bg-white pr-7 text-sm"
|
|
149
149
|
@update:model-value="(v) => {
|
|
150
150
|
search = String(v);
|
|
@@ -158,7 +158,7 @@ function toggleMulti(optValue) {
|
|
|
158
158
|
class="absolute left-0 right-0 top-full z-30 mt-1 max-h-56 overflow-y-auto rounded-md border border-black/[0.08] bg-white py-1 shadow-md"
|
|
159
159
|
>
|
|
160
160
|
<div v-if="q.isFetching.value" class="px-2.5 py-1.5 text-sm text-muted-foreground">
|
|
161
|
-
{{ search ? "searching
|
|
161
|
+
{{ search ? t("integration.nodeConfig.dynamicField.searching") : t("integration.nodeConfig.dynamicField.resolving") }}
|
|
162
162
|
</div>
|
|
163
163
|
<button
|
|
164
164
|
v-for="opt in visibleOptions"
|
|
@@ -168,17 +168,17 @@ function toggleMulti(optValue) {
|
|
|
168
168
|
:class="String(opt.value) === currentValue ? 'bg-emerald-500/10 text-emerald-800' : ''"
|
|
169
169
|
@mousedown.prevent="pick(String(opt.value))"
|
|
170
170
|
>
|
|
171
|
-
<span class="min-w-0 truncate">{{
|
|
171
|
+
<span class="min-w-0 truncate">{{ resolveLocalized(opt.label) || String(opt.value) }}</span>
|
|
172
172
|
</button>
|
|
173
173
|
<div v-if="!q.isFetching.value && visibleOptions.length === 0" class="px-2.5 py-1.5 text-sm text-muted-foreground">
|
|
174
|
-
{{ search ?
|
|
174
|
+
{{ search ? t("integration.nodeConfig.dynamicField.noMatch", { query: search }) : t("integration.nodeConfig.dynamicField.empty") }}
|
|
175
175
|
</div>
|
|
176
176
|
</div>
|
|
177
177
|
</div>
|
|
178
178
|
<p v-if="currentValue !== '' && !currentKnown" class="mt-1 text-sm text-amber-600">
|
|
179
|
-
|
|
179
|
+
{{ t("integration.nodeConfig.dynamicField.currentNotResolved") }}
|
|
180
180
|
</p>
|
|
181
|
-
<p v-if="q.isFetching.value && q.hasData.value && !focused" class="mt-1 text-sm text-muted-foreground">reloading
|
|
181
|
+
<p v-if="q.isFetching.value && q.hasData.value && !focused" class="mt-1 text-sm text-muted-foreground">{{ t("integration.nodeConfig.dynamicField.reloading") }}</p>
|
|
182
182
|
</template>
|
|
183
183
|
</div>
|
|
184
184
|
</template>
|