@revenexx/studio-integrations 2.0.0 → 2.2.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 +141 -0
- package/README.md +1 -0
- package/dist/module.json +1 -1
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +6 -6
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +134 -131
- package/dist/runtime/components/Integrations/CredentialLogo.vue +1 -1
- package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +1 -1
- package/dist/runtime/components/Integrations/CredentialTypePicker.vue +6 -6
- package/dist/runtime/components/Integrations/ExpressionInput.d.vue.ts +2 -0
- package/dist/runtime/components/Integrations/ExpressionInput.vue +90 -11
- package/dist/runtime/components/Integrations/ExpressionInput.vue.d.ts +2 -0
- package/dist/runtime/components/Integrations/GroupFrameView.vue +121 -121
- package/dist/runtime/components/Integrations/NodeConfigField.vue +28 -9
- package/dist/runtime/components/Integrations/NodeInspector.vue +44 -41
- package/dist/runtime/components/Integrations/NodePalette.vue +52 -37
- package/dist/runtime/components/Integrations/RunActionsMenu.vue +6 -5
- package/dist/runtime/components/Integrations/RunDetailPanel.d.vue.ts +7 -0
- package/dist/runtime/components/Integrations/RunDetailPanel.vue +14 -2
- package/dist/runtime/components/Integrations/RunDetailPanel.vue.d.ts +7 -0
- package/dist/runtime/components/Integrations/RunsTable.vue +7 -26
- package/dist/runtime/components/Integrations/StickyNoteView.vue +1 -0
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +10 -6
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +224 -225
- package/dist/runtime/components/Integrations/WorkflowExportDialog.d.vue.ts +6 -0
- package/dist/runtime/components/Integrations/WorkflowExportDialog.vue +71 -0
- package/dist/runtime/components/Integrations/WorkflowExportDialog.vue.d.ts +6 -0
- package/dist/runtime/components/Integrations/WorkflowImportFileStep.d.vue.ts +8 -0
- package/dist/runtime/components/Integrations/WorkflowImportFileStep.vue +95 -0
- package/dist/runtime/components/Integrations/WorkflowImportFileStep.vue.d.ts +8 -0
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +5 -4
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +0 -7
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +2 -2
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +2 -3
- package/dist/runtime/components/Integrations/WorkflowsTable.vue +14 -8
- package/dist/runtime/composables/useIntegrationsNodes.js +4 -1
- package/dist/runtime/composables/useIntegrationsWorkflows.d.ts +3 -1
- package/dist/runtime/composables/useIntegrationsWorkflows.js +5 -22
- package/dist/runtime/composables/useNow.d.ts +17 -0
- package/dist/runtime/composables/useNow.js +16 -0
- package/dist/runtime/composables/useWorkflowFile.d.ts +21 -0
- package/dist/runtime/composables/useWorkflowFile.js +63 -0
- package/dist/runtime/data/expressionEditor.d.ts +11 -1
- package/dist/runtime/data/expressionEditor.js +2 -1
- package/dist/runtime/data/nodeIcons.d.ts +14 -0
- package/dist/runtime/data/nodeIcons.js +57 -14
- package/dist/runtime/data/nodeRunData.js +1 -1
- package/dist/runtime/data/payloadForm.js +1 -1
- package/dist/runtime/i18n/messages/de.json +42 -0
- package/dist/runtime/i18n/messages/en.json +42 -0
- package/dist/runtime/pages/integrations/audit.vue +3 -3
- package/dist/runtime/pages/integrations/credentials/oauth-complete.vue +2 -2
- package/dist/runtime/pages/integrations/index.vue +5 -10
- package/dist/runtime/pages/integrations/schedules.vue +13 -1
- package/dist/runtime/pages/integrations/templates/index.vue +40 -40
- package/dist/runtime/pages/integrations/workflows/[id].vue +324 -244
- package/dist/runtime/types/integrations.d.ts +13 -0
- package/dist/runtime/utils/credentialState.d.ts +0 -7
- package/dist/runtime/utils/credentialState.js +0 -12
- package/dist/runtime/utils/localize.d.ts +12 -0
- package/dist/runtime/utils/localize.js +1 -1
- package/dist/runtime/utils/paletteOrder.d.ts +29 -0
- package/dist/runtime/utils/paletteOrder.js +46 -0
- package/dist/runtime/utils/relativeTime.d.ts +29 -0
- package/dist/runtime/utils/relativeTime.js +23 -0
- package/dist/runtime/utils/runFeedQuery.d.ts +12 -6
- package/dist/runtime/utils/runFeedQuery.js +3 -18
- package/dist/runtime/utils/settingValue.d.ts +33 -0
- package/dist/runtime/utils/settingValue.js +8 -0
- package/dist/runtime/utils/statusStyle.d.ts +8 -8
- package/dist/runtime/utils/statusStyle.js +4 -1
- package/dist/runtime/utils/workflowFile.d.ts +119 -0
- package/dist/runtime/utils/workflowFile.js +192 -0
- package/package.json +8 -3
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed, inject, onBeforeUnmount, onMounted, ref, useId, watch } from "vue";
|
|
3
3
|
import { Editor, EditorContent } from "@tiptap/vue-3";
|
|
4
|
-
import { Node } from "@tiptap/core";
|
|
4
|
+
import { Mark, Node } from "@tiptap/core";
|
|
5
|
+
import { NodeSelection, TextSelection } from "@tiptap/pm/state";
|
|
6
|
+
import { isHistoryTransaction } from "@tiptap/pm/history";
|
|
5
7
|
import Document from "@tiptap/extension-document";
|
|
6
8
|
import Paragraph from "@tiptap/extension-paragraph";
|
|
7
9
|
import Text from "@tiptap/extension-text";
|
|
8
10
|
import History from "@tiptap/extension-history";
|
|
9
11
|
import { resolveExpressionText } from "../../data/nodeRunData";
|
|
10
|
-
import { EXPR_TOKEN_NODE, docToText, hasRawTokenText, textToDoc } from "../../data/expressionEditor";
|
|
12
|
+
import { EXPR_EDITING_MARK, EXPR_TOKEN_NODE, docToText, hasRawTokenText, textToDoc, textToInlineContent } from "../../data/expressionEditor";
|
|
11
13
|
const props = defineProps({
|
|
12
14
|
modelValue: { type: null, required: true },
|
|
13
15
|
placeholder: { type: String, required: false },
|
|
@@ -17,7 +19,7 @@ const props = defineProps({
|
|
|
17
19
|
label: { type: String, required: false },
|
|
18
20
|
multiline: { type: Boolean, required: false }
|
|
19
21
|
});
|
|
20
|
-
const emit = defineEmits(["update:modelValue"]);
|
|
22
|
+
const emit = defineEmits(["update:modelValue", "blur"]);
|
|
21
23
|
const t = useIntegrationsT();
|
|
22
24
|
const text = computed(() => String(props.modelValue ?? ""));
|
|
23
25
|
const OPEN = "${{ ";
|
|
@@ -68,19 +70,33 @@ const ExprToken = Node.create({
|
|
|
68
70
|
return ["span", { "data-expr-token": String(node.attrs.token) }, String(node.attrs.token)];
|
|
69
71
|
},
|
|
70
72
|
addNodeView() {
|
|
71
|
-
return ({ node }) => {
|
|
73
|
+
return ({ node, getPos }) => {
|
|
72
74
|
const dom = document.createElement("span");
|
|
73
75
|
dom.className = "expr-pill";
|
|
74
76
|
dom.textContent = tokenLabel(String(node.attrs.token));
|
|
75
77
|
dom.title = String(node.attrs.token);
|
|
78
|
+
dom.addEventListener("mousedown", (e) => {
|
|
79
|
+
if (e.button !== 0 || e.shiftKey || e.ctrlKey || e.metaKey || e.altKey) return;
|
|
80
|
+
const pos = getPos();
|
|
81
|
+
if (pos !== void 0 && onPillMousedown(pos)) e.preventDefault();
|
|
82
|
+
});
|
|
76
83
|
return { dom };
|
|
77
84
|
};
|
|
78
85
|
}
|
|
79
86
|
});
|
|
87
|
+
const ExprEditing = Mark.create({
|
|
88
|
+
name: EXPR_EDITING_MARK,
|
|
89
|
+
inclusive: true,
|
|
90
|
+
// No `parseHTML`: the mark is only ever made by `openPill`. Parsed back, a copy
|
|
91
|
+
// of opened text would paste as text nobody opened, left unpilled.
|
|
92
|
+
renderHTML() {
|
|
93
|
+
return ["span", { "data-expr-editing": "", class: "expr-editing" }, 0];
|
|
94
|
+
}
|
|
95
|
+
});
|
|
80
96
|
const lineMode = () => ({ multiline: props.multiline });
|
|
81
97
|
function editableAttributes() {
|
|
82
98
|
return {
|
|
83
|
-
|
|
99
|
+
role: "textbox",
|
|
84
100
|
...props.multiline ? { "aria-multiline": "true" } : {},
|
|
85
101
|
...props.label ? { "aria-label": props.label } : {}
|
|
86
102
|
};
|
|
@@ -94,13 +110,66 @@ function endOfLine(line) {
|
|
|
94
110
|
});
|
|
95
111
|
return pos;
|
|
96
112
|
}
|
|
113
|
+
function openPill(pos, node) {
|
|
114
|
+
if (node.type.name !== EXPR_TOKEN_NODE) return false;
|
|
115
|
+
const { state, dispatch } = editor.view;
|
|
116
|
+
const token = String(node.attrs.token);
|
|
117
|
+
const textNode = state.schema.text(token, [state.schema.marks[EXPR_EDITING_MARK].create()]);
|
|
118
|
+
const tr = state.tr.replaceWith(pos, pos + node.nodeSize, textNode);
|
|
119
|
+
tr.setSelection(TextSelection.create(tr.doc, pos + token.length));
|
|
120
|
+
dispatch(tr);
|
|
121
|
+
editor.commands.focus();
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
function onPillMousedown(pos) {
|
|
125
|
+
const view = editor.view;
|
|
126
|
+
const sel = view.state.selection;
|
|
127
|
+
if (sel instanceof NodeSelection && sel.from === pos) return openPill(pos, sel.node);
|
|
128
|
+
view.focus();
|
|
129
|
+
view.dispatch(view.state.tr.setSelection(NodeSelection.create(view.state.doc, pos)));
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
function editingRanges() {
|
|
133
|
+
const ranges = [];
|
|
134
|
+
editor.state.doc.descendants((node, pos) => {
|
|
135
|
+
if (node.isText && node.marks.some((m) => m.type.name === EXPR_EDITING_MARK)) {
|
|
136
|
+
ranges.push({ from: pos, to: pos + node.nodeSize });
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
return ranges;
|
|
140
|
+
}
|
|
141
|
+
function closeEditing() {
|
|
142
|
+
if (editor.isDestroyed) return;
|
|
143
|
+
const ranges = editingRanges();
|
|
144
|
+
if (!ranges.length) return;
|
|
145
|
+
const { state, dispatch } = editor.view;
|
|
146
|
+
const tr = state.tr;
|
|
147
|
+
for (const r of ranges.reverse()) {
|
|
148
|
+
const raw = state.doc.textBetween(r.from, r.to);
|
|
149
|
+
tr.replaceWith(r.from, r.to, textToInlineContent(raw).map((n) => state.schema.nodeFromJSON(n)));
|
|
150
|
+
}
|
|
151
|
+
dispatch(tr);
|
|
152
|
+
}
|
|
97
153
|
const editor = new Editor({
|
|
98
|
-
extensions: [Document, Paragraph, Text, History, ExprToken],
|
|
154
|
+
extensions: [Document, Paragraph, Text, History, ExprToken, ExprEditing],
|
|
99
155
|
content: textToDoc(text.value, lineMode()),
|
|
100
156
|
editorProps: {
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
|
|
157
|
+
// A selected pill is not replaced by what is typed: a click on it selects it
|
|
158
|
+
// (`onPillMousedown`), and a click into the field followed by typing used to
|
|
159
|
+
// put text beside the value, never over it. Deleting it is Backspace's job.
|
|
160
|
+
handleTextInput: (view, _from, _to, input) => {
|
|
161
|
+
const sel = view.state.selection;
|
|
162
|
+
if (!(sel instanceof NodeSelection) || sel.node.type.name !== EXPR_TOKEN_NODE) return false;
|
|
163
|
+
const tr = view.state.tr.insertText(input, sel.to);
|
|
164
|
+
view.dispatch(tr.setSelection(TextSelection.create(tr.doc, sel.to + input.length)));
|
|
165
|
+
return true;
|
|
166
|
+
},
|
|
167
|
+
handleKeyDown: (view, event) => {
|
|
168
|
+
if (event.isComposing || event.key !== "Enter") return false;
|
|
169
|
+
const sel = view.state.selection;
|
|
170
|
+
if (sel instanceof NodeSelection && openPill(sel.from, sel.node)) return true;
|
|
171
|
+
return !props.multiline;
|
|
172
|
+
},
|
|
104
173
|
attributes: editableAttributes()
|
|
105
174
|
},
|
|
106
175
|
onUpdate({ editor: ed }) {
|
|
@@ -114,9 +183,19 @@ const editor = new Editor({
|
|
|
114
183
|
}
|
|
115
184
|
if (raw !== text.value) emit("update:modelValue", raw);
|
|
116
185
|
},
|
|
186
|
+
onSelectionUpdate({ editor: ed, transaction }) {
|
|
187
|
+
if (isHistoryTransaction(transaction)) return;
|
|
188
|
+
const { from, to } = ed.state.selection;
|
|
189
|
+
const ranges = editingRanges();
|
|
190
|
+
if (ranges.length && !ranges.some((r) => from >= r.from && to <= r.to)) closeEditing();
|
|
191
|
+
},
|
|
117
192
|
onFocus() {
|
|
118
193
|
if (insertHandler) insertHandler.value = insertToken;
|
|
119
194
|
if (hasSuggestions.value) open.value = true;
|
|
195
|
+
},
|
|
196
|
+
onBlur() {
|
|
197
|
+
closeEditing();
|
|
198
|
+
emit("blur");
|
|
120
199
|
}
|
|
121
200
|
});
|
|
122
201
|
onBeforeUnmount(() => {
|
|
@@ -280,9 +359,9 @@ function insert(expr) {
|
|
|
280
359
|
v-if="listOpen"
|
|
281
360
|
:id="pickerId"
|
|
282
361
|
role="group"
|
|
283
|
-
@keydown="onPickerKeydown"
|
|
284
362
|
:aria-label="t('integration.nodeConfig.expression.picker.aria')"
|
|
285
363
|
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"
|
|
364
|
+
@keydown="onPickerKeydown"
|
|
286
365
|
>
|
|
287
366
|
<div class="shrink-0 border-b border-black/[0.06] p-1">
|
|
288
367
|
<input
|
|
@@ -322,5 +401,5 @@ function insert(expr) {
|
|
|
322
401
|
</template>
|
|
323
402
|
|
|
324
403
|
<style>
|
|
325
|
-
.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-editor.expr-multiline{--expr-line:1.375rem;max-height:calc(var(--expr-line)*10 + .5rem);min-height:calc(var(--expr-line)*4 + .5rem);overflow-x:hidden;overflow-y:auto;padding-bottom:.25rem;padding-top:.25rem}.expr-editor.expr-multiline .ProseMirror{display:block;height:auto;overflow-x:visible}.expr-editor.expr-multiline .ProseMirror p{line-height:var(--expr-line);white-space:pre-wrap}.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)}
|
|
404
|
+
.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-editor.expr-multiline{--expr-line:1.375rem;max-height:calc(var(--expr-line)*10 + .5rem);min-height:calc(var(--expr-line)*4 + .5rem);overflow-x:hidden;overflow-y:auto;padding-bottom:.25rem;padding-top:.25rem}.expr-editor.expr-multiline .ProseMirror{display:block;height:auto;overflow-x:visible}.expr-editor.expr-multiline .ProseMirror p{line-height:var(--expr-line);white-space:pre-wrap}.expr-pill{align-items:center;background:rgba(16,185,129,.1);border:1px solid rgba(16,185,129,.25);border-radius:6px;color:#047857;cursor:text;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-editing{background:rgba(16,185,129,.08);border-radius:4px;color:#047857;font-family:ui-monospace,monospace}.expr-pill.ProseMirror-selectednode{outline:2px solid rgba(16,185,129,.5)}
|
|
326
405
|
</style>
|
|
@@ -21,7 +21,9 @@ type __VLS_Props = {
|
|
|
21
21
|
};
|
|
22
22
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
23
23
|
"update:modelValue": (value: string) => any;
|
|
24
|
+
blur: () => any;
|
|
24
25
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
25
26
|
"onUpdate:modelValue"?: ((value: string) => any) | undefined;
|
|
27
|
+
onBlur?: (() => any) | undefined;
|
|
26
28
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
27
29
|
export default _default;
|
|
@@ -91,143 +91,143 @@ function onResizeEnd() {
|
|
|
91
91
|
|
|
92
92
|
<template>
|
|
93
93
|
<div class="relative">
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
>
|
|
100
|
-
<button
|
|
101
|
-
v-for="(c, i) in GROUP_COLORS"
|
|
102
|
-
:key="i"
|
|
103
|
-
type="button"
|
|
104
|
-
class="size-6 rounded-full border transition hover:scale-110"
|
|
105
|
-
:class="(data.group.color ?? 4) === i ? 'ring-2 ring-black/25 ring-offset-1' : ''"
|
|
106
|
-
:style="{ backgroundColor: c.chip, borderColor: c.border }"
|
|
107
|
-
:title="t(c.nameKey)"
|
|
108
|
-
:aria-label="t(c.nameKey)"
|
|
109
|
-
@click="data.actions?.setColor(i)"
|
|
110
|
-
/>
|
|
111
|
-
<div class="mx-0.5 h-4 w-px bg-black/10" />
|
|
112
|
-
<button
|
|
113
|
-
type="button"
|
|
114
|
-
class="flex size-6 items-center justify-center rounded-md text-muted-foreground transition hover:bg-muted/50 hover:text-foreground"
|
|
115
|
-
:title="collapsed ? t('integration.canvas.group.action.expand') : t('integration.canvas.group.action.collapse')"
|
|
116
|
-
@click="data.actions?.toggleCollapse()"
|
|
117
|
-
>
|
|
118
|
-
<component :is="collapsed ? ChevronsUpDown : ChevronsDownUp" :size="13" />
|
|
119
|
-
</button>
|
|
120
|
-
<button
|
|
121
|
-
type="button"
|
|
122
|
-
class="flex size-6 items-center justify-center rounded-md text-muted-foreground transition hover:bg-muted/50 hover:text-foreground"
|
|
123
|
-
:title="t('integration.canvas.group.action.ungroup')"
|
|
124
|
-
@click="data.actions?.ungroup()"
|
|
94
|
+
<NodeToolbar :is-visible="toolbarVisible" :position="Position.Top" :offset="8">
|
|
95
|
+
<div
|
|
96
|
+
class="flex items-center gap-1 rounded-lg border border-black/[0.06] bg-white px-1.5 py-1 shadow-md"
|
|
97
|
+
@mouseenter="onHoverEnter"
|
|
98
|
+
@mouseleave="onHoverLeave"
|
|
125
99
|
>
|
|
126
|
-
<
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
100
|
+
<button
|
|
101
|
+
v-for="(c, i) in GROUP_COLORS"
|
|
102
|
+
:key="i"
|
|
103
|
+
type="button"
|
|
104
|
+
class="size-6 rounded-full border transition hover:scale-110"
|
|
105
|
+
:class="(data.group.color ?? 4) === i ? 'ring-2 ring-black/25 ring-offset-1' : ''"
|
|
106
|
+
:style="{ backgroundColor: c.chip, borderColor: c.border }"
|
|
107
|
+
:title="t(c.nameKey)"
|
|
108
|
+
:aria-label="t(c.nameKey)"
|
|
109
|
+
@click="data.actions?.setColor(i)"
|
|
110
|
+
/>
|
|
111
|
+
<div class="mx-0.5 h-4 w-px bg-black/10" />
|
|
112
|
+
<button
|
|
113
|
+
type="button"
|
|
114
|
+
class="flex size-6 items-center justify-center rounded-md text-muted-foreground transition hover:bg-muted/50 hover:text-foreground"
|
|
115
|
+
:title="collapsed ? t('integration.canvas.group.action.expand') : t('integration.canvas.group.action.collapse')"
|
|
116
|
+
@click="data.actions?.toggleCollapse()"
|
|
117
|
+
>
|
|
118
|
+
<component :is="collapsed ? ChevronsUpDown : ChevronsDownUp" :size="13" />
|
|
119
|
+
</button>
|
|
120
|
+
<button
|
|
121
|
+
type="button"
|
|
122
|
+
class="flex size-6 items-center justify-center rounded-md text-muted-foreground transition hover:bg-muted/50 hover:text-foreground"
|
|
123
|
+
:title="t('integration.canvas.group.action.ungroup')"
|
|
124
|
+
@click="data.actions?.ungroup()"
|
|
125
|
+
>
|
|
126
|
+
<Ungroup :size="13" />
|
|
127
|
+
</button>
|
|
128
|
+
<button
|
|
129
|
+
type="button"
|
|
130
|
+
class="flex size-6 items-center justify-center rounded-md text-muted-foreground transition hover:bg-rose-50 hover:text-rose-600"
|
|
131
|
+
:title="t('integration.canvas.group.action.removeFrame')"
|
|
132
|
+
@click="data.actions?.ungroup()"
|
|
133
|
+
>
|
|
134
|
+
<TrashBin :size="13" />
|
|
135
|
+
</button>
|
|
136
|
+
</div>
|
|
137
|
+
</NodeToolbar>
|
|
138
138
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
139
|
+
<!-- Collapsed: compact stand-in card with generic edge handles. -->
|
|
140
|
+
<template v-if="collapsed">
|
|
141
|
+
<Handle
|
|
142
|
+
:id="inputHandleId('top')"
|
|
143
|
+
type="target"
|
|
144
|
+
:position="Position.Top"
|
|
145
|
+
class="!size-3 !rounded-full !border-2 !border-white !bg-slate-500 !shadow-sm"
|
|
146
|
+
/>
|
|
147
|
+
<!-- The collapsed card stops its own double-click unconditionally, which is
|
|
148
148
|
safe: both read-only panels build every frame with `collapsed: false`, so
|
|
149
149
|
this surface is the editor's alone. -->
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
150
|
+
<div
|
|
151
|
+
class="w-[220px] rounded-lg border-2 bg-white transition-all duration-150"
|
|
152
|
+
:style="{ borderColor: color.border }"
|
|
153
|
+
:class="selected ? 'shadow-[0_0_0_2px_rgba(59,130,246,0.2)]' : 'shadow-sm hover:shadow-md'"
|
|
154
|
+
@mouseenter="onHoverEnter"
|
|
155
|
+
@mouseleave="onHoverLeave"
|
|
156
|
+
@dblclick.stop="data.actions?.toggleCollapse()"
|
|
157
|
+
>
|
|
158
|
+
<div class="flex items-center gap-2.5 px-3 py-2.5">
|
|
159
|
+
<span class="flex size-8 shrink-0 items-center justify-center rounded-md text-white" :style="{ backgroundColor: color.chip }">
|
|
160
|
+
<ChevronsUpDown :size="15" />
|
|
161
|
+
</span>
|
|
162
|
+
<div class="min-w-0 flex-1">
|
|
163
|
+
<div class="truncate text-sm font-semibold leading-tight text-foreground">{{ label }}</div>
|
|
164
|
+
<div class="mt-0.5 text-sm text-muted-foreground">{{ memberCountText }}</div>
|
|
165
|
+
</div>
|
|
165
166
|
</div>
|
|
166
167
|
</div>
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
</template>
|
|
168
|
+
<Handle
|
|
169
|
+
:id="outputHandleId('default', 'bottom')"
|
|
170
|
+
type="source"
|
|
171
|
+
:position="Position.Bottom"
|
|
172
|
+
class="!size-3 !rounded-full !border-2 !border-white !bg-slate-500 !shadow-sm"
|
|
173
|
+
/>
|
|
174
|
+
</template>
|
|
175
175
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
176
|
+
<!-- Expanded: the frame behind the members. -->
|
|
177
|
+
<div
|
|
178
|
+
v-else
|
|
179
|
+
class="pointer-events-none relative rounded-xl border-2 border-dashed transition-colors"
|
|
180
|
+
:class="selected ? 'shadow-[0_0_0_2px_rgba(59,130,246,0.15)]' : ''"
|
|
181
|
+
:style="{
|
|
182
182
|
width: `${frameW}px`,
|
|
183
183
|
height: `${frameH}px`,
|
|
184
184
|
backgroundColor: color.bg,
|
|
185
185
|
borderColor: color.border
|
|
186
186
|
}"
|
|
187
|
-
>
|
|
188
|
-
<div
|
|
189
|
-
class="pointer-events-auto flex cursor-grab items-center gap-1.5 px-3 pt-1.5 active:cursor-grabbing"
|
|
190
|
-
@mouseenter="onHoverEnter"
|
|
191
|
-
@mouseleave="onHoverLeave"
|
|
192
187
|
>
|
|
193
|
-
<
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
:aria-label="t('integration.canvas.group.input.nameAria')"
|
|
198
|
-
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"
|
|
199
|
-
@blur="commitRename"
|
|
200
|
-
@keydown.enter.prevent="commitRename"
|
|
201
|
-
@keydown.stop
|
|
202
|
-
@mousedown.stop
|
|
188
|
+
<div
|
|
189
|
+
class="pointer-events-auto flex cursor-grab items-center gap-1.5 px-3 pt-1.5 active:cursor-grabbing"
|
|
190
|
+
@mouseenter="onHoverEnter"
|
|
191
|
+
@mouseleave="onHoverLeave"
|
|
203
192
|
>
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
193
|
+
<input
|
|
194
|
+
v-if="editing"
|
|
195
|
+
ref="inputEl"
|
|
196
|
+
v-model="draft"
|
|
197
|
+
:aria-label="t('integration.canvas.group.input.nameAria')"
|
|
198
|
+
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"
|
|
199
|
+
@blur="commitRename"
|
|
200
|
+
@keydown.enter.prevent="commitRename"
|
|
201
|
+
@keydown.stop
|
|
202
|
+
@mousedown.stop
|
|
203
|
+
>
|
|
204
|
+
<span
|
|
205
|
+
v-else
|
|
206
|
+
class="select-none text-sm font-semibold"
|
|
207
|
+
:style="{ color: color.chip }"
|
|
208
|
+
@dblclick="onLabelDoubleClick"
|
|
209
|
+
>
|
|
210
|
+
{{ label }}
|
|
211
|
+
</span>
|
|
212
|
+
<span class="select-none text-xs text-muted-foreground">· {{ memberCountText }}</span>
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
<!-- Bottom-right resize handle (editable frames only). -->
|
|
216
|
+
<div
|
|
217
|
+
v-if="editable"
|
|
218
|
+
class="nodrag pointer-events-auto absolute bottom-0 right-0 flex size-5 cursor-se-resize items-end justify-end p-1"
|
|
207
219
|
:style="{ color: color.chip }"
|
|
208
|
-
|
|
220
|
+
:title="t('integration.canvas.group.action.resize')"
|
|
221
|
+
@pointerdown="onResizeStart"
|
|
222
|
+
@pointermove="onResizeMove"
|
|
223
|
+
@pointerup="onResizeEnd"
|
|
224
|
+
@pointercancel="onResizeEnd"
|
|
209
225
|
>
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
<!-- Bottom-right resize handle (editable frames only). -->
|
|
216
|
-
<div
|
|
217
|
-
v-if="editable"
|
|
218
|
-
class="nodrag pointer-events-auto absolute bottom-0 right-0 flex size-5 cursor-se-resize items-end justify-end p-1"
|
|
219
|
-
:style="{ color: color.chip }"
|
|
220
|
-
:title="t('integration.canvas.group.action.resize')"
|
|
221
|
-
@pointerdown="onResizeStart"
|
|
222
|
-
@pointermove="onResizeMove"
|
|
223
|
-
@pointerup="onResizeEnd"
|
|
224
|
-
@pointercancel="onResizeEnd"
|
|
225
|
-
>
|
|
226
|
-
<svg viewBox="0 0 8 8" class="size-2.5" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round">
|
|
227
|
-
<path d="M7 1 1 7" />
|
|
228
|
-
<path d="M7 4.5 4.5 7" />
|
|
229
|
-
</svg>
|
|
226
|
+
<svg viewBox="0 0 8 8" class="size-2.5" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round">
|
|
227
|
+
<path d="M7 1 1 7" />
|
|
228
|
+
<path d="M7 4.5 4.5 7" />
|
|
229
|
+
</svg>
|
|
230
|
+
</div>
|
|
230
231
|
</div>
|
|
231
232
|
</div>
|
|
232
|
-
</div>
|
|
233
233
|
</template>
|
|
@@ -19,6 +19,7 @@ import { fromModelValue, toItemValue, toModelValue } from "../../utils/selectOpt
|
|
|
19
19
|
import { credentialState } from "../../utils/credentialState";
|
|
20
20
|
import { defaultsFor } from "../../utils/configDefaults";
|
|
21
21
|
import { applyingFields, settingApplies, staleKeys } from "../../utils/settingCondition";
|
|
22
|
+
import { coerceSettingValue } from "../../utils/settingValue";
|
|
22
23
|
const props = defineProps({
|
|
23
24
|
field: { type: Object, required: true },
|
|
24
25
|
config: { type: Object, required: true },
|
|
@@ -38,8 +39,20 @@ const emit = defineEmits(["set", "declare-state"]);
|
|
|
38
39
|
const t = useIntegrationsT();
|
|
39
40
|
const value = computed(() => props.config[props.field.key]);
|
|
40
41
|
function write(v) {
|
|
42
|
+
emit("set", props.field.key, coerceSettingValue(props.field, v));
|
|
43
|
+
}
|
|
44
|
+
function writeTyped(v) {
|
|
41
45
|
emit("set", props.field.key, v);
|
|
42
46
|
}
|
|
47
|
+
function commitTyped() {
|
|
48
|
+
const committed = coerceSettingValue(props.field, value.value);
|
|
49
|
+
if (committed !== value.value) emit("set", props.field.key, committed);
|
|
50
|
+
}
|
|
51
|
+
const expressionText = computed(() => {
|
|
52
|
+
const v = value.value;
|
|
53
|
+
if (typeof v === "string") return v;
|
|
54
|
+
return typeof v === "number" ? String(v) : "";
|
|
55
|
+
});
|
|
43
56
|
const exprHint = "${{ \u2026 }}";
|
|
44
57
|
const descriptionText = computed(() => resolveLocalized(props.field.description));
|
|
45
58
|
const inRow = computed(() => props.rowIndex !== void 0);
|
|
@@ -65,9 +78,10 @@ const formatError = computed(() => {
|
|
|
65
78
|
}
|
|
66
79
|
}
|
|
67
80
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (val.
|
|
81
|
+
const n = coerceSettingValue(props.field, v);
|
|
82
|
+
if (typeof n === "number") {
|
|
83
|
+
if (val.min != null && n < val.min) return t("integration.nodeConfig.validation.min", { min: val.min });
|
|
84
|
+
if (val.max != null && n > val.max) return t("integration.nodeConfig.validation.max", { max: val.max });
|
|
71
85
|
}
|
|
72
86
|
return null;
|
|
73
87
|
});
|
|
@@ -166,11 +180,12 @@ function acceptsSftp(field) {
|
|
|
166
180
|
return t2 === "revenexx:sftp" || Array.isArray(t2) && t2.includes("revenexx:sftp");
|
|
167
181
|
}
|
|
168
182
|
const EXPR_TOKEN = "${{";
|
|
183
|
+
const TYPED_KINDS = ["number", "boolean", "select", "multiselect"];
|
|
184
|
+
const valueIsExpression = computed(() => typeof value.value === "string" && value.value.includes(EXPR_TOKEN));
|
|
169
185
|
const canToggleExpression = computed(
|
|
170
|
-
() => !!props.field.dynamic ||
|
|
186
|
+
() => (!!props.field.dynamic || TYPED_KINDS.includes(props.field.type)) && (!!props.field.expressionAllowed || valueIsExpression.value)
|
|
171
187
|
);
|
|
172
188
|
const exprMode = ref(false);
|
|
173
|
-
const valueIsExpression = computed(() => typeof value.value === "string" && value.value.includes(EXPR_TOKEN));
|
|
174
189
|
const showExpression = computed(() => canToggleExpression.value && (exprMode.value || valueIsExpression.value));
|
|
175
190
|
function toggleExpression() {
|
|
176
191
|
if (showExpression.value) {
|
|
@@ -433,14 +448,15 @@ function setArrayItemField(index, fieldKey, v) {
|
|
|
433
448
|
<!-- typed control flipped to expression mode -->
|
|
434
449
|
<IntegrationsExpressionInput
|
|
435
450
|
v-else-if="showExpression"
|
|
436
|
-
:model-value="
|
|
451
|
+
:model-value="expressionText"
|
|
437
452
|
:placeholder="resolveLocalized(field.placeholder)"
|
|
438
453
|
:variables="variables"
|
|
439
454
|
:upstream-outputs="upstreamOutputs"
|
|
440
455
|
:invalid="invalid"
|
|
441
456
|
:label="controlName"
|
|
442
457
|
:multiline="isMultiline"
|
|
443
|
-
@update:model-value="(v) =>
|
|
458
|
+
@update:model-value="(v) => writeTyped(v)"
|
|
459
|
+
@blur="commitTyped"
|
|
444
460
|
/>
|
|
445
461
|
|
|
446
462
|
<!-- dynamic options — resolved at author time via config:resolve. Ahead of
|
|
@@ -463,9 +479,12 @@ function setArrayItemField(index, fieldKey, v) {
|
|
|
463
479
|
@update:model-value="write"
|
|
464
480
|
/>
|
|
465
481
|
|
|
466
|
-
<!-- expression-capable string: token-autocomplete input
|
|
482
|
+
<!-- expression-capable string: token-autocomplete input. A typed control
|
|
483
|
+
(number, switch, choice) that allows an expression is not drawn here:
|
|
484
|
+
it keeps its own control, and the switch above turns it into this one
|
|
485
|
+
(showExpression, PO-548). -->
|
|
467
486
|
<IntegrationsExpressionInput
|
|
468
|
-
v-else-if="field.type === 'expression' || field.expressionAllowed"
|
|
487
|
+
v-else-if="field.type === 'expression' || field.expressionAllowed && !TYPED_KINDS.includes(field.type)"
|
|
469
488
|
:model-value="value"
|
|
470
489
|
:placeholder="resolveLocalized(field.placeholder)"
|
|
471
490
|
:variables="variables"
|