@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
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import { computed, markRaw, nextTick, provide, ref, watch } from "vue";
|
|
3
3
|
import {
|
|
4
4
|
ConnectionLineType,
|
|
5
|
+
ConnectionMode,
|
|
5
6
|
MarkerType,
|
|
6
|
-
PanOnScrollMode,
|
|
7
7
|
SelectionMode,
|
|
8
8
|
VueFlow,
|
|
9
9
|
useVueFlow
|
|
@@ -48,12 +48,20 @@ import { toast } from "vue-sonner";
|
|
|
48
48
|
import WorkflowEdgeView from "./WorkflowEdgeView.vue";
|
|
49
49
|
import { layoutWorkflowNodes } from "./workflowLayout";
|
|
50
50
|
import { TRIGGER_TEMPLATES, findManifest } from "../../data/integrationNodeCatalog";
|
|
51
|
+
import { isTerminalManifest, portsForNode } from "../../data/nodeInputData";
|
|
52
|
+
import { portFromHandleId } from "../../data/handleId";
|
|
53
|
+
import { groupFindingsByNode } from "../../utils/validationFindings";
|
|
54
|
+
import { reorderBranchSubtrees } from "../../data/branchReorder";
|
|
55
|
+
import { findingsByNodeField } from "../../utils/validationFindings";
|
|
56
|
+
import { resolveDisplayName, resolveLocalized } from "../../utils/localize";
|
|
51
57
|
const props = defineProps({
|
|
52
58
|
workflow: { type: Object, required: true },
|
|
53
59
|
runBusy: { type: Boolean, required: false },
|
|
54
|
-
runWaiting: { type: Boolean, required: false }
|
|
60
|
+
runWaiting: { type: Boolean, required: false },
|
|
61
|
+
overlayOpen: { type: Boolean, required: false }
|
|
55
62
|
});
|
|
56
63
|
const emit = defineEmits(["change", "run", "toggleActive", "openSettings"]);
|
|
64
|
+
const t = useIntegrationsT();
|
|
57
65
|
const { keys: secretKeys } = useIntegrationsSecrets();
|
|
58
66
|
const blob = ref(clone(props.workflow.blob));
|
|
59
67
|
watch(() => props.workflow.id, () => {
|
|
@@ -66,7 +74,20 @@ function clone(v) {
|
|
|
66
74
|
const vfNodes = ref([]);
|
|
67
75
|
const vfEdges = ref([]);
|
|
68
76
|
const flow = useVueFlow();
|
|
77
|
+
const invalidNodeMessages = computed(() => findingsByNodeField(props.workflow.warnings ?? [], props.workflow.blob.nodes));
|
|
69
78
|
const dynamicOutputs = ref({});
|
|
79
|
+
const invalidNodeFields = computed(() => {
|
|
80
|
+
const out = {};
|
|
81
|
+
for (const g of groupFindingsByNode(props.workflow.warnings ?? [], props.workflow.blob.nodes, () => "")) {
|
|
82
|
+
if (g.nodeId) out[g.nodeId] = g.fields;
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
});
|
|
86
|
+
let dragInFlight = false;
|
|
87
|
+
watch(() => JSON.stringify(invalidNodeFields.value), () => {
|
|
88
|
+
if (dragInFlight) return;
|
|
89
|
+
syncFromBlob();
|
|
90
|
+
});
|
|
70
91
|
const dynamicOutputNodes = computed(() => blob.value.nodes.filter((n) => {
|
|
71
92
|
const m = findManifest(n.nodeSlug, n.nodeVersion);
|
|
72
93
|
return (m?.outputs ?? []).some((o) => o.resolveOutputs === true);
|
|
@@ -79,6 +100,7 @@ function edgeKindFromPort(sourceNodeId, output) {
|
|
|
79
100
|
if (output === "true") return "branch-true";
|
|
80
101
|
if (output === "false") return "branch-false";
|
|
81
102
|
if (output === "error") return "error";
|
|
103
|
+
if (output === "done") return "success";
|
|
82
104
|
const portDef = (manifest.outputs ?? []).find((o) => o.name === output);
|
|
83
105
|
if (portDef) {
|
|
84
106
|
if (portDef.kind === "error") return "error";
|
|
@@ -93,10 +115,10 @@ function edgeKindFromPort(sourceNodeId, output) {
|
|
|
93
115
|
}
|
|
94
116
|
return "default";
|
|
95
117
|
}
|
|
96
|
-
function
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
return
|
|
118
|
+
function orderedOutputPorts(nodeId) {
|
|
119
|
+
const wn = blob.value.nodes.find((n) => n.id === nodeId);
|
|
120
|
+
if (!wn) return ["default"];
|
|
121
|
+
return portsForNode(wn, findManifest(wn.nodeSlug, wn.nodeVersion), dynamicOutputs.value[nodeId]).map((p) => p.name);
|
|
100
122
|
}
|
|
101
123
|
function triggerPosition(id, index) {
|
|
102
124
|
const saved = blob.value.triggerPositions?.[id];
|
|
@@ -106,7 +128,7 @@ function triggerPosition(id, index) {
|
|
|
106
128
|
function nodePosition(id) {
|
|
107
129
|
const wn = blob.value.nodes.find((n) => n.id === id);
|
|
108
130
|
if (wn) return { x: wn.position?.x ?? 0, y: wn.position?.y ?? 0 };
|
|
109
|
-
const ti = blob.value.triggers.findIndex((
|
|
131
|
+
const ti = blob.value.triggers.findIndex((t2) => t2.id === id);
|
|
110
132
|
if (ti >= 0) return triggerPosition(id, ti);
|
|
111
133
|
return { x: 0, y: 0 };
|
|
112
134
|
}
|
|
@@ -121,10 +143,6 @@ function stickyArea(s) {
|
|
|
121
143
|
}
|
|
122
144
|
const GROUP_PAD = 24;
|
|
123
145
|
const GROUP_LABEL_ROOM = 26;
|
|
124
|
-
function locText(v) {
|
|
125
|
-
if (!v) return "";
|
|
126
|
-
return typeof v === "string" ? v : v.en ?? Object.values(v)[0] ?? "";
|
|
127
|
-
}
|
|
128
146
|
function memberDims(id) {
|
|
129
147
|
const d = flow.findNode(id)?.dimensions;
|
|
130
148
|
return d && d.width > 0 ? d : { width: 220, height: 64 };
|
|
@@ -180,7 +198,7 @@ function ungroup(id) {
|
|
|
180
198
|
push();
|
|
181
199
|
}
|
|
182
200
|
function createGroupFromSelection(ids) {
|
|
183
|
-
const valid = ids.filter((id2) => blob.value.nodes.some((n) => n.id === id2) || blob.value.triggers.some((
|
|
201
|
+
const valid = ids.filter((id2) => blob.value.nodes.some((n) => n.id === id2) || blob.value.triggers.some((t2) => t2.id === id2));
|
|
184
202
|
if (valid.length < 1) return;
|
|
185
203
|
for (const g of blob.value.groups ?? []) {
|
|
186
204
|
g.nodeIds = g.nodeIds.filter((x) => !valid.includes(x));
|
|
@@ -189,7 +207,7 @@ function createGroupFromSelection(ids) {
|
|
|
189
207
|
const id = `g-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`;
|
|
190
208
|
blob.value.groups = [...blob.value.groups ?? [], {
|
|
191
209
|
id,
|
|
192
|
-
label:
|
|
210
|
+
label: t("integration.editor.group.defaultLabel", { n: (blob.value.groups?.length ?? 0) + 1 }),
|
|
193
211
|
nodeIds: valid
|
|
194
212
|
}];
|
|
195
213
|
syncFromBlob();
|
|
@@ -250,19 +268,23 @@ function syncFromBlob() {
|
|
|
250
268
|
onRemove: () => removeBlobNode(s.id)
|
|
251
269
|
}
|
|
252
270
|
})),
|
|
253
|
-
...blob.value.triggers.filter((
|
|
254
|
-
id:
|
|
271
|
+
...blob.value.triggers.filter((t2) => !collapsedBy.has(t2.id)).map((t2, i) => ({
|
|
272
|
+
id: t2.id,
|
|
255
273
|
type: "trigger",
|
|
256
|
-
position: triggerPosition(
|
|
257
|
-
class: bornClass(
|
|
258
|
-
data:
|
|
274
|
+
position: triggerPosition(t2.id, i),
|
|
275
|
+
class: bornClass(t2.id),
|
|
276
|
+
data: t2
|
|
259
277
|
})),
|
|
260
278
|
...blob.value.nodes.filter((n) => !collapsedBy.has(n.id)).map((n) => ({
|
|
261
279
|
id: n.id,
|
|
262
280
|
type: "workflow",
|
|
263
281
|
position: n.position ?? { x: 240, y: 80 },
|
|
264
282
|
class: bornClass(n.id),
|
|
265
|
-
data: {
|
|
283
|
+
data: {
|
|
284
|
+
...n,
|
|
285
|
+
__resolvedOutputs: dynamicOutputs.value[n.id],
|
|
286
|
+
__invalidFields: invalidNodeFields.value[n.id]
|
|
287
|
+
}
|
|
266
288
|
}))
|
|
267
289
|
];
|
|
268
290
|
const seenEdge = /* @__PURE__ */ new Set();
|
|
@@ -285,6 +307,18 @@ function syncFromBlob() {
|
|
|
285
307
|
targetHandle: tgtGroup ? "__in__top" : vf.targetHandle
|
|
286
308
|
});
|
|
287
309
|
}
|
|
310
|
+
const bySource = /* @__PURE__ */ new Map();
|
|
311
|
+
for (const e of mapped) {
|
|
312
|
+
const list = bySource.get(e.source) ?? [];
|
|
313
|
+
list.push(e);
|
|
314
|
+
bySource.set(e.source, list);
|
|
315
|
+
}
|
|
316
|
+
for (const list of bySource.values()) {
|
|
317
|
+
list.sort((a, b) => (a.data?.portIndex ?? 0) - (b.data?.portIndex ?? 0));
|
|
318
|
+
list.forEach((e, i) => {
|
|
319
|
+
e.data = { ...e.data, laneIndex: i, laneCount: list.length };
|
|
320
|
+
});
|
|
321
|
+
}
|
|
288
322
|
vfEdges.value = mapped;
|
|
289
323
|
void nextTick(() => flow.updateNodeInternals(vfNodes.value.map((n) => n.id)));
|
|
290
324
|
}
|
|
@@ -292,9 +326,11 @@ function makeVfEdge(id, sourceId, targetId, output) {
|
|
|
292
326
|
const kind = edgeKindFromPort(sourceId, output);
|
|
293
327
|
const wn = blob.value.nodes.find((n) => n.id === sourceId);
|
|
294
328
|
const manifest = wn ? findManifest(wn.nodeSlug, wn.nodeVersion) : null;
|
|
295
|
-
const isBranchPort = (manifest?.outputs ?? []).some((o) => o.sourceFromConfig) || ["true", "false"
|
|
329
|
+
const isBranchPort = (manifest?.outputs ?? []).some((o) => o.sourceFromConfig) || ["true", "false"].includes(output);
|
|
296
330
|
const srcSide = pickSourceSide(sourceId, targetId);
|
|
297
331
|
const tgtSide = oppositeSide(srcSide);
|
|
332
|
+
const order = orderedOutputPorts(sourceId);
|
|
333
|
+
const portIndex = order.indexOf(output);
|
|
298
334
|
return {
|
|
299
335
|
id,
|
|
300
336
|
source: sourceId,
|
|
@@ -304,7 +340,8 @@ function makeVfEdge(id, sourceId, targetId, output) {
|
|
|
304
340
|
type: "flow",
|
|
305
341
|
data: {
|
|
306
342
|
kind,
|
|
307
|
-
label: isBranchPort ? output : void 0
|
|
343
|
+
label: isBranchPort ? output : void 0,
|
|
344
|
+
portIndex: portIndex < 0 ? 0 : portIndex
|
|
308
345
|
},
|
|
309
346
|
markerEnd: {
|
|
310
347
|
type: MarkerType.ArrowClosed,
|
|
@@ -315,24 +352,21 @@ function makeVfEdge(id, sourceId, targetId, output) {
|
|
|
315
352
|
};
|
|
316
353
|
}
|
|
317
354
|
function edgeColorByKind(kind) {
|
|
318
|
-
|
|
319
|
-
case "success":
|
|
320
|
-
return "#10b981";
|
|
321
|
-
case "error":
|
|
322
|
-
return "#f43f5e";
|
|
323
|
-
case "branch-true":
|
|
324
|
-
return "#10b981";
|
|
325
|
-
case "branch-false":
|
|
326
|
-
return "#f59e0b";
|
|
327
|
-
case "branch":
|
|
328
|
-
return "#8b5cf6";
|
|
329
|
-
default:
|
|
330
|
-
return "#94a3b8";
|
|
331
|
-
}
|
|
355
|
+
return kind === "error" ? "#cbd5e1" : "#94a3b8";
|
|
332
356
|
}
|
|
333
357
|
syncFromBlob();
|
|
334
|
-
|
|
358
|
+
const paneReady = ref(false);
|
|
359
|
+
const pendingReveal = ref(null);
|
|
360
|
+
flow.onPaneReady(() => {
|
|
361
|
+
flow.fitView({ padding: 0.2, maxZoom: 1 });
|
|
362
|
+
paneReady.value = true;
|
|
363
|
+
if (pendingReveal.value) {
|
|
364
|
+
centerOnNode(pendingReveal.value);
|
|
365
|
+
pendingReveal.value = null;
|
|
366
|
+
}
|
|
367
|
+
});
|
|
335
368
|
flow.onNodeDragStart(({ node }) => {
|
|
369
|
+
dragInFlight = true;
|
|
336
370
|
if (node.type !== "group") return;
|
|
337
371
|
const g = blob.value.groups?.find((x) => x.id === node.id);
|
|
338
372
|
if (!g) return;
|
|
@@ -350,6 +384,7 @@ flow.onNodeDrag(({ node }) => {
|
|
|
350
384
|
}
|
|
351
385
|
});
|
|
352
386
|
function persistDraggedNodes(evt) {
|
|
387
|
+
dragInFlight = false;
|
|
353
388
|
const moved = evt.nodes?.length ? evt.nodes : [evt.node];
|
|
354
389
|
for (const n of moved) {
|
|
355
390
|
const pos = { x: Math.round(n.position.x), y: Math.round(n.position.y) };
|
|
@@ -360,7 +395,7 @@ function persistDraggedNodes(evt) {
|
|
|
360
395
|
const dy = Math.round(n.position.y - origin.frame.y);
|
|
361
396
|
for (const [id, memberPos] of Object.entries(origin.members)) {
|
|
362
397
|
const target = { x: Math.round(memberPos.x + dx), y: Math.round(memberPos.y + dy) };
|
|
363
|
-
if (blob.value.triggers.some((
|
|
398
|
+
if (blob.value.triggers.some((t2) => t2.id === id)) {
|
|
364
399
|
blob.value.triggerPositions = { ...blob.value.triggerPositions ?? {}, [id]: target };
|
|
365
400
|
} else {
|
|
366
401
|
const wn = blob.value.nodes.find((b) => b.id === id);
|
|
@@ -373,7 +408,7 @@ function persistDraggedNodes(evt) {
|
|
|
373
408
|
}
|
|
374
409
|
continue;
|
|
375
410
|
}
|
|
376
|
-
if (blob.value.triggers.some((
|
|
411
|
+
if (blob.value.triggers.some((t2) => t2.id === n.id)) {
|
|
377
412
|
blob.value.triggerPositions = { ...blob.value.triggerPositions ?? {}, [n.id]: pos };
|
|
378
413
|
} else if (blob.value.stickies?.some((s) => s.id === n.id)) {
|
|
379
414
|
const st = blob.value.stickies.find((s) => s.id === n.id);
|
|
@@ -395,7 +430,7 @@ flow.onConnect((conn) => {
|
|
|
395
430
|
connectCompleted = true;
|
|
396
431
|
if (!conn.source || !conn.target) return;
|
|
397
432
|
const id = `e-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
|
|
398
|
-
const output =
|
|
433
|
+
const output = portFromHandleId(conn.sourceHandle) ?? "default";
|
|
399
434
|
blob.value.edges.push({
|
|
400
435
|
id,
|
|
401
436
|
from: { nodeId: conn.source, output },
|
|
@@ -409,7 +444,7 @@ let connectStart = null;
|
|
|
409
444
|
let connectCompleted = false;
|
|
410
445
|
flow.onConnectStart(({ nodeId, handleId, handleType }) => {
|
|
411
446
|
connectCompleted = false;
|
|
412
|
-
connectStart = handleType === "source" && nodeId ? { sourceId: nodeId, output:
|
|
447
|
+
connectStart = handleType === "source" && nodeId ? { sourceId: nodeId, output: portFromHandleId(handleId) ?? "default" } : null;
|
|
413
448
|
});
|
|
414
449
|
flow.onConnectEnd((event) => {
|
|
415
450
|
const start = connectStart;
|
|
@@ -422,14 +457,14 @@ flow.onConnectEnd((event) => {
|
|
|
422
457
|
pendingConnect.value = { sourceId: start.sourceId, output: start.output, pos: { x: snap(pos.x), y: snap(pos.y) } };
|
|
423
458
|
pendingSpliceEdgeId.value = null;
|
|
424
459
|
void focusPalette();
|
|
425
|
-
toast.info("
|
|
460
|
+
toast.info(t("integration.editor.quickConnect.toast"));
|
|
426
461
|
});
|
|
427
462
|
flow.onEdgeUpdate(({ edge, connection }) => {
|
|
428
463
|
if (!connection.source || !connection.target) return;
|
|
429
464
|
if (connection.source === connection.target) return;
|
|
430
465
|
const be = blob.value.edges.find((e) => e.id === edge.id);
|
|
431
466
|
if (!be) return;
|
|
432
|
-
be.from = { nodeId: connection.source, output:
|
|
467
|
+
be.from = { nodeId: connection.source, output: portFromHandleId(connection.sourceHandle) ?? be.from.output };
|
|
433
468
|
be.to = { nodeId: connection.target, input: be.to.input };
|
|
434
469
|
syncFromBlob();
|
|
435
470
|
push();
|
|
@@ -445,9 +480,11 @@ flow.onEdgesChange((changes) => {
|
|
|
445
480
|
if (mutated) push();
|
|
446
481
|
});
|
|
447
482
|
flow.onNodesChange((changes) => {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
483
|
+
const removed = changes.filter((c) => c.type === "remove").map((c) => c.id);
|
|
484
|
+
if (!removed.length) return;
|
|
485
|
+
void nextTick(() => {
|
|
486
|
+
for (const id of removed) removeBlobNode(id);
|
|
487
|
+
});
|
|
451
488
|
});
|
|
452
489
|
function stripFromGroups(drop) {
|
|
453
490
|
if (!blob.value.groups?.length) return;
|
|
@@ -462,7 +499,7 @@ function removeBlobNode(id) {
|
|
|
462
499
|
stripFromGroups(/* @__PURE__ */ new Set([id]));
|
|
463
500
|
blob.value.nodes = blob.value.nodes.filter((n) => n.id !== id);
|
|
464
501
|
if (blob.value.stickies?.length) blob.value.stickies = blob.value.stickies.filter((s) => s.id !== id);
|
|
465
|
-
blob.value.triggers = blob.value.triggers.filter((
|
|
502
|
+
blob.value.triggers = blob.value.triggers.filter((t2) => t2.id !== id);
|
|
466
503
|
blob.value.edges = blob.value.edges.filter((e) => e.from.nodeId !== id && e.to.nodeId !== id);
|
|
467
504
|
if (blob.value.triggerPositions?.[id]) delete blob.value.triggerPositions[id];
|
|
468
505
|
if (selectedId.value === id) selectedId.value = null;
|
|
@@ -493,7 +530,7 @@ function addSticky(at) {
|
|
|
493
530
|
id: `s-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`,
|
|
494
531
|
// n8n-style starter content: teaches the edit gesture and the supported
|
|
495
532
|
// markdown by example.
|
|
496
|
-
content: "
|
|
533
|
+
content: t("integration.editor.sticky.starterContent"),
|
|
497
534
|
position: at ? { x: Math.round(at.x), y: Math.round(at.y) } : {
|
|
498
535
|
x: Math.round(center.x - STICKY_DEFAULT_SIZE.width / 2),
|
|
499
536
|
y: Math.round(center.y - STICKY_DEFAULT_SIZE.height / 2)
|
|
@@ -509,7 +546,7 @@ function removeBlobNodes(ids) {
|
|
|
509
546
|
const drop = new Set(ids);
|
|
510
547
|
stripFromGroups(drop);
|
|
511
548
|
blob.value.nodes = blob.value.nodes.filter((n) => !drop.has(n.id));
|
|
512
|
-
blob.value.triggers = blob.value.triggers.filter((
|
|
549
|
+
blob.value.triggers = blob.value.triggers.filter((t2) => !drop.has(t2.id));
|
|
513
550
|
blob.value.edges = blob.value.edges.filter((e) => !drop.has(e.from.nodeId) && !drop.has(e.to.nodeId));
|
|
514
551
|
if (blob.value.stickies?.length) blob.value.stickies = blob.value.stickies.filter((s) => !drop.has(s.id));
|
|
515
552
|
for (const id of ids) {
|
|
@@ -548,9 +585,18 @@ function duplicateBlobNodes(ids) {
|
|
|
548
585
|
});
|
|
549
586
|
}
|
|
550
587
|
provide("workflow-node-actions", {
|
|
588
|
+
configure: (id) => {
|
|
589
|
+
renameIntent.value = false;
|
|
590
|
+
selectedId.value = id;
|
|
591
|
+
},
|
|
551
592
|
duplicate: (id) => duplicateBlobNodes([id]),
|
|
552
593
|
remove: (id) => removeBlobNode(id)
|
|
553
594
|
});
|
|
595
|
+
const controlBar = ref(null);
|
|
596
|
+
provide(
|
|
597
|
+
"workflow-canvas-bottom-inset",
|
|
598
|
+
() => controlBar.value ? controlBar.value.offsetHeight + 12 : 0
|
|
599
|
+
);
|
|
554
600
|
const CLIP_KIND = "revenexx.workflow.nodes";
|
|
555
601
|
async function copyNodes(ids) {
|
|
556
602
|
const nodes = blob.value.nodes.filter((n) => ids.includes(n.id));
|
|
@@ -559,9 +605,9 @@ async function copyNodes(ids) {
|
|
|
559
605
|
const edges = blob.value.edges.filter((e) => inner.has(e.from.nodeId) && inner.has(e.to.nodeId));
|
|
560
606
|
try {
|
|
561
607
|
await navigator.clipboard.writeText(JSON.stringify({ kind: CLIP_KIND, version: 1, nodes, edges }, null, 2));
|
|
562
|
-
toast.success(
|
|
608
|
+
toast.success(t("integration.editor.clipboard.toast.copied", nodes.length));
|
|
563
609
|
} catch {
|
|
564
|
-
toast.error("
|
|
610
|
+
toast.error(t("integration.editor.clipboard.error.write"));
|
|
565
611
|
}
|
|
566
612
|
}
|
|
567
613
|
async function pasteNodes(at) {
|
|
@@ -569,18 +615,18 @@ async function pasteNodes(at) {
|
|
|
569
615
|
try {
|
|
570
616
|
text = await navigator.clipboard.readText();
|
|
571
617
|
} catch {
|
|
572
|
-
toast.error("
|
|
618
|
+
toast.error(t("integration.editor.clipboard.error.read"));
|
|
573
619
|
return;
|
|
574
620
|
}
|
|
575
621
|
let payload;
|
|
576
622
|
try {
|
|
577
623
|
payload = JSON.parse(text);
|
|
578
624
|
} catch {
|
|
579
|
-
toast.error("
|
|
625
|
+
toast.error(t("integration.editor.clipboard.error.empty"));
|
|
580
626
|
return;
|
|
581
627
|
}
|
|
582
628
|
if (payload?.kind !== CLIP_KIND || !Array.isArray(payload.nodes) || !payload.nodes.length) {
|
|
583
|
-
toast.error("
|
|
629
|
+
toast.error(t("integration.editor.clipboard.error.empty"));
|
|
584
630
|
return;
|
|
585
631
|
}
|
|
586
632
|
const idMap = /* @__PURE__ */ new Map();
|
|
@@ -696,19 +742,35 @@ function isValidConnection(conn) {
|
|
|
696
742
|
const selectedId = ref(null);
|
|
697
743
|
const selection = computed(() => {
|
|
698
744
|
if (!selectedId.value) return null;
|
|
699
|
-
return blob.value.nodes.find((n) => n.id === selectedId.value) ?? blob.value.triggers.find((
|
|
745
|
+
return blob.value.nodes.find((n) => n.id === selectedId.value) ?? blob.value.triggers.find((t2) => t2.id === selectedId.value) ?? null;
|
|
700
746
|
});
|
|
701
|
-
|
|
747
|
+
const selectionIsTrigger = computed(() => !!selectedId.value && blob.value.triggers.some((t2) => t2.id === selectedId.value));
|
|
748
|
+
flow.onNodeDoubleClick((evt) => {
|
|
702
749
|
if (evt.node.type === "sticky" || evt.node.type === "group") return;
|
|
703
750
|
selectedId.value = evt.node.id;
|
|
704
751
|
});
|
|
705
752
|
flow.onPaneClick(() => {
|
|
706
753
|
selectedId.value = null;
|
|
707
754
|
});
|
|
708
|
-
function
|
|
709
|
-
|
|
710
|
-
|
|
755
|
+
function centerOnNode(id) {
|
|
756
|
+
const vf = vfNodes.value.find((n) => n.id === id);
|
|
757
|
+
if (!vf) return;
|
|
758
|
+
const size = flow.findNode(id)?.dimensions;
|
|
759
|
+
void flow.setCenter(
|
|
760
|
+
vf.position.x + (size?.width ?? 220) / 2,
|
|
761
|
+
vf.position.y + (size?.height ?? 76) / 2,
|
|
762
|
+
{ zoom: Math.max(flow.viewport.value?.zoom ?? 1, 0.75), duration: 300 }
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
function revealNode(id) {
|
|
766
|
+
if (paneReady.value) centerOnNode(id);
|
|
767
|
+
else pendingReveal.value = id;
|
|
768
|
+
if (blob.value.nodes.some((n) => n.id === id) || blob.value.triggers.some((t2) => t2.id === id)) {
|
|
769
|
+
renameIntent.value = false;
|
|
770
|
+
selectedId.value = id;
|
|
771
|
+
}
|
|
711
772
|
}
|
|
773
|
+
defineExpose({ revealNode });
|
|
712
774
|
const selectedUpstreamOutputs = computed(() => {
|
|
713
775
|
const id = selectedId.value;
|
|
714
776
|
if (!id) return [];
|
|
@@ -720,7 +782,9 @@ const selectedUpstreamOutputs = computed(() => {
|
|
|
720
782
|
const portDef = (findManifest(source.nodeSlug, source.nodeVersion)?.outputs ?? []).find((o) => o.name === e.from.output);
|
|
721
783
|
return [{
|
|
722
784
|
nodeId: source.id,
|
|
723
|
-
|
|
785
|
+
// A node's own name is text the user typed — never trimmed, never read
|
|
786
|
+
// as JSON. The manifest strings below it get the normalising resolver.
|
|
787
|
+
nodeName: resolveDisplayName(source.name) || source.nodeSlug,
|
|
724
788
|
output: e.from.output,
|
|
725
789
|
fields: portDef?.fields
|
|
726
790
|
}];
|
|
@@ -728,7 +792,7 @@ const selectedUpstreamOutputs = computed(() => {
|
|
|
728
792
|
});
|
|
729
793
|
function applyInspectorUpdate(next) {
|
|
730
794
|
const id = next.id;
|
|
731
|
-
const triggerIdx = blob.value.triggers.findIndex((
|
|
795
|
+
const triggerIdx = blob.value.triggers.findIndex((t2) => t2.id === id);
|
|
732
796
|
if (triggerIdx >= 0) {
|
|
733
797
|
blob.value.triggers[triggerIdx] = next;
|
|
734
798
|
} else {
|
|
@@ -801,7 +865,7 @@ function addFromPayload(payload, pos) {
|
|
|
801
865
|
const id2 = `g-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`;
|
|
802
866
|
blob.value.groups = [...blob.value.groups ?? [], {
|
|
803
867
|
id: id2,
|
|
804
|
-
label:
|
|
868
|
+
label: t("integration.editor.group.defaultLabel", { n: (blob.value.groups?.length ?? 0) + 1 }),
|
|
805
869
|
nodeIds: [],
|
|
806
870
|
rect: { x: at.x, y: at.y, ...EMPTY_GROUP_SIZE }
|
|
807
871
|
}];
|
|
@@ -811,7 +875,7 @@ function addFromPayload(payload, pos) {
|
|
|
811
875
|
return;
|
|
812
876
|
}
|
|
813
877
|
if (payload.kind === "trigger") {
|
|
814
|
-
const tpl = TRIGGER_TEMPLATES.find((
|
|
878
|
+
const tpl = TRIGGER_TEMPLATES.find((t2) => t2.type === payload.type);
|
|
815
879
|
if (!tpl) return;
|
|
816
880
|
const id2 = crypto.randomUUID();
|
|
817
881
|
blob.value.triggers.push({ id: id2, type: tpl.type, config: { ...tpl.defaultConfig } });
|
|
@@ -819,7 +883,6 @@ function addFromPayload(payload, pos) {
|
|
|
819
883
|
adoptIntoGroupAt(id2, at);
|
|
820
884
|
markBorn(id2);
|
|
821
885
|
syncFromBlob();
|
|
822
|
-
selectedId.value = id2;
|
|
823
886
|
push();
|
|
824
887
|
return;
|
|
825
888
|
}
|
|
@@ -836,7 +899,6 @@ function addFromPayload(payload, pos) {
|
|
|
836
899
|
adoptIntoGroupAt(id, at);
|
|
837
900
|
markBorn(id);
|
|
838
901
|
syncFromBlob();
|
|
839
|
-
selectedId.value = id;
|
|
840
902
|
push();
|
|
841
903
|
}
|
|
842
904
|
function onDrop(ev) {
|
|
@@ -871,7 +933,6 @@ function onPaletteInsert(payload) {
|
|
|
871
933
|
});
|
|
872
934
|
markBorn(id);
|
|
873
935
|
syncFromBlob();
|
|
874
|
-
selectedId.value = id;
|
|
875
936
|
push();
|
|
876
937
|
return;
|
|
877
938
|
}
|
|
@@ -892,7 +953,7 @@ provide("workflow-edge-actions", {
|
|
|
892
953
|
if (!blob.value.edges.some((e) => e.id === edgeId)) return;
|
|
893
954
|
pendingSpliceEdgeId.value = edgeId;
|
|
894
955
|
void focusPalette();
|
|
895
|
-
toast.info("
|
|
956
|
+
toast.info(t("integration.editor.splice.toast"));
|
|
896
957
|
}
|
|
897
958
|
});
|
|
898
959
|
function primaryOutputName(slug, version) {
|
|
@@ -903,6 +964,10 @@ function spliceIntoEdge(edgeId, payload) {
|
|
|
903
964
|
const edge = blob.value.edges.find((e) => e.id === edgeId);
|
|
904
965
|
const manifest = findManifest(payload.slug, payload.version);
|
|
905
966
|
if (!edge || !manifest) return;
|
|
967
|
+
if (isTerminalManifest(manifest)) {
|
|
968
|
+
toast.error(t("integration.editor.splice.error.terminal", { name: resolveLocalized(manifest.name) || manifest.slug }));
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
906
971
|
const a = nodePosition(edge.from.nodeId);
|
|
907
972
|
const b = nodePosition(edge.to.nodeId);
|
|
908
973
|
const id = `n-${Date.now()}`;
|
|
@@ -921,7 +986,6 @@ function spliceIntoEdge(edgeId, payload) {
|
|
|
921
986
|
);
|
|
922
987
|
markBorn(id);
|
|
923
988
|
syncFromBlob();
|
|
924
|
-
selectedId.value = id;
|
|
925
989
|
push();
|
|
926
990
|
}
|
|
927
991
|
function defaultConfigFor(fields = []) {
|
|
@@ -935,6 +999,34 @@ function nodeDims(id) {
|
|
|
935
999
|
const d = flow.findNode(id)?.dimensions;
|
|
936
1000
|
return d && d.width > 0 ? d : { width: 220, height: 64 };
|
|
937
1001
|
}
|
|
1002
|
+
function reorderBranchesByHandle(laid, memberToGroup) {
|
|
1003
|
+
const posById = new Map(laid.map((n) => [n.id, n]));
|
|
1004
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
1005
|
+
for (const n of laid) {
|
|
1006
|
+
if (!memberToGroup.has(n.id)) nodes.set(n.id, { x: n.position.x });
|
|
1007
|
+
}
|
|
1008
|
+
const portOrderCache = /* @__PURE__ */ new Map();
|
|
1009
|
+
const portsFor = (id) => {
|
|
1010
|
+
let o = portOrderCache.get(id);
|
|
1011
|
+
if (!o) {
|
|
1012
|
+
o = orderedOutputPorts(id);
|
|
1013
|
+
portOrderCache.set(id, o);
|
|
1014
|
+
}
|
|
1015
|
+
return o;
|
|
1016
|
+
};
|
|
1017
|
+
const edges = [];
|
|
1018
|
+
for (const e of vfEdges.value) {
|
|
1019
|
+
if (!nodes.has(e.source) || !nodes.has(e.target)) continue;
|
|
1020
|
+
const port = portFromHandleId(e.sourceHandle) ?? "default";
|
|
1021
|
+
const order = portsFor(e.source);
|
|
1022
|
+
const idx = order.indexOf(port);
|
|
1023
|
+
edges.push({ source: e.source, target: e.target, portIndex: idx < 0 ? order.length : idx });
|
|
1024
|
+
}
|
|
1025
|
+
for (const [id, x] of reorderBranchSubtrees(nodes, edges)) {
|
|
1026
|
+
const n = posById.get(id);
|
|
1027
|
+
n.position = { x, y: n.position.y };
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
938
1030
|
async function tidyUp() {
|
|
939
1031
|
const containment = /* @__PURE__ */ new Map();
|
|
940
1032
|
for (const s of blob.value.stickies ?? []) {
|
|
@@ -1014,6 +1106,7 @@ async function tidyUp() {
|
|
|
1014
1106
|
if (!inner) return n;
|
|
1015
1107
|
return { ...n, position: { x: anchor.x + GROUP_PAD + inner.position.x, y: anchor.y + GROUP_PAD + GROUP_LABEL_ROOM + inner.position.y } };
|
|
1016
1108
|
});
|
|
1109
|
+
reorderBranchesByHandle(laid, memberToGroup);
|
|
1017
1110
|
for (const n of laid) {
|
|
1018
1111
|
const pos = { x: Math.round(n.position.x), y: Math.round(n.position.y) };
|
|
1019
1112
|
if (n.type === "workflow") {
|
|
@@ -1120,6 +1213,7 @@ async function focusPalette() {
|
|
|
1120
1213
|
paletteRef.value?.focusSearch();
|
|
1121
1214
|
}
|
|
1122
1215
|
const lightboxOpen = computed(() => !!selection.value);
|
|
1216
|
+
const canvasHasKeyboard = computed(() => !lightboxOpen.value && !variablesOpen.value && !props.overlayOpen);
|
|
1123
1217
|
const singleSelected = computed(() => flow.getSelectedNodes.value.length === 1 ? flow.getSelectedNodes.value[0] : null);
|
|
1124
1218
|
useCanvasShortcuts(() => ({
|
|
1125
1219
|
"enter": {
|
|
@@ -1181,7 +1275,7 @@ useCanvasShortcuts(() => ({
|
|
|
1181
1275
|
pendingConnect.value = null;
|
|
1182
1276
|
flow.removeSelectedElements();
|
|
1183
1277
|
}
|
|
1184
|
-
}), { disabled:
|
|
1278
|
+
}), { disabled: () => !canvasHasKeyboard.value });
|
|
1185
1279
|
const nodeTypes = {
|
|
1186
1280
|
workflow: markRaw(WorkflowNodeView),
|
|
1187
1281
|
trigger: markRaw(WorkflowTriggerView),
|
|
@@ -1207,7 +1301,7 @@ const edgeTypes = {
|
|
|
1207
1301
|
:kind="menu.kind"
|
|
1208
1302
|
:selection-count="flow.getSelectedNodes.value.length"
|
|
1209
1303
|
:total-nodes="vfNodes.length"
|
|
1210
|
-
:target-is-trigger="!!menu.targetId && blob.triggers.some((
|
|
1304
|
+
:target-is-trigger="!!menu.targetId && blob.triggers.some((t2) => t2.id === menu.targetId)"
|
|
1211
1305
|
:run-busy="runBusy"
|
|
1212
1306
|
@action="onMenuAction"
|
|
1213
1307
|
>
|
|
@@ -1219,15 +1313,15 @@ const edgeTypes = {
|
|
|
1219
1313
|
:default-edge-options="{ type: 'flow' }"
|
|
1220
1314
|
:connection-line-type="ConnectionLineType.Step"
|
|
1221
1315
|
:connection-line-style="{ stroke: '#94a3b8', strokeWidth: 1.75, strokeDasharray: '4 4' }"
|
|
1316
|
+
:connection-mode="ConnectionMode.Strict"
|
|
1222
1317
|
:is-valid-connection="isValidConnection"
|
|
1223
1318
|
:snap-to-grid="true"
|
|
1224
1319
|
:snap-grid="[20, 20]"
|
|
1225
1320
|
:selection-mode="SelectionMode.Partial"
|
|
1226
1321
|
:selection-key-code="pointerMode === 'select' ? true : 'Shift'"
|
|
1227
1322
|
:pan-on-drag="pointerMode === 'hand' ? true : [1]"
|
|
1228
|
-
:
|
|
1229
|
-
:
|
|
1230
|
-
:delete-key-code="['Backspace', 'Delete']"
|
|
1323
|
+
:zoom-on-double-click="false"
|
|
1324
|
+
:delete-key-code="canvasHasKeyboard ? ['Backspace', 'Delete'] : null"
|
|
1231
1325
|
:edges-updatable="true"
|
|
1232
1326
|
:min-zoom="0.25"
|
|
1233
1327
|
:max-zoom="4"
|
|
@@ -1260,7 +1354,7 @@ const edgeTypes = {
|
|
|
1260
1354
|
class="absolute -top-0.5 left-1/2 -translate-x-1/2 -translate-y-full rounded-md px-1.5 py-0.5 text-xs font-medium text-white shadow-sm"
|
|
1261
1355
|
:class="nodeDrag.dragging.value?.kind === 'trigger' ? 'bg-emerald-500' : nodeDrag.dragging.value?.kind === 'group' ? 'bg-slate-500' : 'bg-violet-500'"
|
|
1262
1356
|
>
|
|
1263
|
-
|
|
1357
|
+
{{ t("integration.editor.dropGhost.label") }}
|
|
1264
1358
|
</span>
|
|
1265
1359
|
</div>
|
|
1266
1360
|
</Transition>
|
|
@@ -1313,11 +1407,11 @@ const edgeTypes = {
|
|
|
1313
1407
|
<button
|
|
1314
1408
|
type="button"
|
|
1315
1409
|
class="flex h-9 items-center gap-1.5 rounded-lg bg-white px-3 text-sm font-medium transition hover:bg-muted/75 hover:cursor-pointer"
|
|
1316
|
-
title="
|
|
1410
|
+
:title="t('integration.editor.palette.action.expand.title')"
|
|
1317
1411
|
@click="void focusPalette()"
|
|
1318
1412
|
>
|
|
1319
1413
|
<Plus :size="14" class="text-emerald-600" />
|
|
1320
|
-
|
|
1414
|
+
{{ t("integration.editor.palette.title") }}
|
|
1321
1415
|
</button>
|
|
1322
1416
|
</div>
|
|
1323
1417
|
</div>
|
|
@@ -1336,29 +1430,29 @@ const edgeTypes = {
|
|
|
1336
1430
|
@pointerdown="palette.startDrag($event, paletteShellRef)"
|
|
1337
1431
|
>
|
|
1338
1432
|
<GripVertical :size="14" class="shrink-0 text-muted-foreground/60" />
|
|
1339
|
-
<span class="min-w-0 flex-1 truncate text-sm font-medium">
|
|
1433
|
+
<span class="min-w-0 flex-1 truncate text-sm font-medium">{{ t("integration.editor.palette.title") }}</span>
|
|
1340
1434
|
<DropdownMenu>
|
|
1341
1435
|
<DropdownMenuTrigger as-child>
|
|
1342
1436
|
<button
|
|
1343
1437
|
type="button"
|
|
1344
1438
|
class="flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground transition hover:bg-muted/50 hover:text-foreground"
|
|
1345
|
-
title="
|
|
1439
|
+
:title="t('integration.editor.palette.action.placement.title')"
|
|
1346
1440
|
>
|
|
1347
1441
|
<EllipsisVertical :size="14" />
|
|
1348
1442
|
</button>
|
|
1349
1443
|
</DropdownMenuTrigger>
|
|
1350
1444
|
<DropdownMenuContent align="end" class="w-36 rounded-lg border border-black/[0.06] bg-white p-1 shadow-lg">
|
|
1351
|
-
<DropdownMenuItem class="rounded-md text-sm" @select="palette.dock('left')">
|
|
1352
|
-
<DropdownMenuItem class="rounded-md text-sm" @select="palette.dock('right')">
|
|
1353
|
-
<DropdownMenuItem class="rounded-md text-sm" @select="palette.floatHere(paletteShellRef)">
|
|
1445
|
+
<DropdownMenuItem class="rounded-md text-sm" @select="palette.dock('left')">{{ t("integration.editor.palette.action.dockLeft") }}</DropdownMenuItem>
|
|
1446
|
+
<DropdownMenuItem class="rounded-md text-sm" @select="palette.dock('right')">{{ t("integration.editor.palette.action.dockRight") }}</DropdownMenuItem>
|
|
1447
|
+
<DropdownMenuItem class="rounded-md text-sm" @select="palette.floatHere(paletteShellRef)">{{ t("integration.editor.panel.action.floatHere") }}</DropdownMenuItem>
|
|
1354
1448
|
<DropdownMenuSeparator class="my-1 h-px bg-black/[0.06]" />
|
|
1355
|
-
<DropdownMenuItem class="rounded-md text-sm" @select="palette.setCollapsed(true)">
|
|
1449
|
+
<DropdownMenuItem class="rounded-md text-sm" @select="palette.setCollapsed(true)">{{ t("integration.editor.panel.action.collapse") }}</DropdownMenuItem>
|
|
1356
1450
|
</DropdownMenuContent>
|
|
1357
1451
|
</DropdownMenu>
|
|
1358
1452
|
<button
|
|
1359
1453
|
type="button"
|
|
1360
1454
|
class="flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground transition hover:bg-muted/50 hover:text-foreground"
|
|
1361
|
-
title="
|
|
1455
|
+
:title="t('integration.editor.palette.action.collapse.title')"
|
|
1362
1456
|
@click="palette.setCollapsed(true)"
|
|
1363
1457
|
>
|
|
1364
1458
|
<Minus :size="14" />
|
|
@@ -1400,11 +1494,11 @@ const edgeTypes = {
|
|
|
1400
1494
|
<button
|
|
1401
1495
|
type="button"
|
|
1402
1496
|
class="flex h-9 items-center gap-1.5 rounded-lg bg-white px-3 text-sm font-medium transition hover:bg-muted/75 hover:cursor-pointer"
|
|
1403
|
-
title="
|
|
1497
|
+
:title="t('integration.editor.minimap.action.expand.title')"
|
|
1404
1498
|
@click="minimapLayout.setCollapsed(false)"
|
|
1405
1499
|
>
|
|
1406
1500
|
<MapIcon :size="14" class="text-emerald-600" />
|
|
1407
|
-
|
|
1501
|
+
{{ t("integration.editor.minimap.title") }}
|
|
1408
1502
|
</button>
|
|
1409
1503
|
</div>
|
|
1410
1504
|
</div>
|
|
@@ -1423,31 +1517,31 @@ const edgeTypes = {
|
|
|
1423
1517
|
@pointerdown="minimapLayout.startDrag($event, minimapShellRef)"
|
|
1424
1518
|
>
|
|
1425
1519
|
<GripVertical :size="14" class="shrink-0 text-muted-foreground/60" />
|
|
1426
|
-
<span class="min-w-0 flex-1 truncate text-sm font-medium">
|
|
1520
|
+
<span class="min-w-0 flex-1 truncate text-sm font-medium">{{ t("integration.editor.minimap.title") }}</span>
|
|
1427
1521
|
<DropdownMenu>
|
|
1428
1522
|
<DropdownMenuTrigger as-child>
|
|
1429
1523
|
<button
|
|
1430
1524
|
type="button"
|
|
1431
1525
|
class="flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground transition hover:bg-muted/50 hover:text-foreground"
|
|
1432
|
-
title="
|
|
1526
|
+
:title="t('integration.editor.minimap.action.placement.title')"
|
|
1433
1527
|
>
|
|
1434
1528
|
<EllipsisVertical :size="14" />
|
|
1435
1529
|
</button>
|
|
1436
1530
|
</DropdownMenuTrigger>
|
|
1437
1531
|
<DropdownMenuContent align="end" class="w-40 rounded-lg border border-black/[0.06] bg-white p-1 shadow-lg">
|
|
1438
|
-
<DropdownMenuItem class="rounded-md text-sm" @select="minimapLayout.dock('top-left')">
|
|
1439
|
-
<DropdownMenuItem class="rounded-md text-sm" @select="minimapLayout.dock('top-right')">
|
|
1440
|
-
<DropdownMenuItem class="rounded-md text-sm" @select="minimapLayout.dock('bottom-left')">
|
|
1441
|
-
<DropdownMenuItem class="rounded-md text-sm" @select="minimapLayout.dock('bottom-right')">
|
|
1442
|
-
<DropdownMenuItem class="rounded-md text-sm" @select="minimapLayout.floatHere(minimapShellRef)">
|
|
1532
|
+
<DropdownMenuItem class="rounded-md text-sm" @select="minimapLayout.dock('top-left')">{{ t("integration.editor.minimap.action.topLeft") }}</DropdownMenuItem>
|
|
1533
|
+
<DropdownMenuItem class="rounded-md text-sm" @select="minimapLayout.dock('top-right')">{{ t("integration.editor.minimap.action.topRight") }}</DropdownMenuItem>
|
|
1534
|
+
<DropdownMenuItem class="rounded-md text-sm" @select="minimapLayout.dock('bottom-left')">{{ t("integration.editor.minimap.action.bottomLeft") }}</DropdownMenuItem>
|
|
1535
|
+
<DropdownMenuItem class="rounded-md text-sm" @select="minimapLayout.dock('bottom-right')">{{ t("integration.editor.minimap.action.bottomRight") }}</DropdownMenuItem>
|
|
1536
|
+
<DropdownMenuItem class="rounded-md text-sm" @select="minimapLayout.floatHere(minimapShellRef)">{{ t("integration.editor.panel.action.floatHere") }}</DropdownMenuItem>
|
|
1443
1537
|
<DropdownMenuSeparator class="my-1 h-px bg-black/[0.06]" />
|
|
1444
|
-
<DropdownMenuItem class="rounded-md text-sm" @select="minimapLayout.setCollapsed(true)">
|
|
1538
|
+
<DropdownMenuItem class="rounded-md text-sm" @select="minimapLayout.setCollapsed(true)">{{ t("integration.editor.panel.action.collapse") }}</DropdownMenuItem>
|
|
1445
1539
|
</DropdownMenuContent>
|
|
1446
1540
|
</DropdownMenu>
|
|
1447
1541
|
<button
|
|
1448
1542
|
type="button"
|
|
1449
1543
|
class="flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground transition hover:bg-muted/50 hover:text-foreground"
|
|
1450
|
-
title="
|
|
1544
|
+
:title="t('integration.editor.minimap.action.collapse.title')"
|
|
1451
1545
|
@click="minimapLayout.setCollapsed(true)"
|
|
1452
1546
|
>
|
|
1453
1547
|
<Minus :size="14" />
|
|
@@ -1466,14 +1560,14 @@ const edgeTypes = {
|
|
|
1466
1560
|
</Transition>
|
|
1467
1561
|
|
|
1468
1562
|
<!-- Floating control pane — bottom center. -->
|
|
1469
|
-
<div class="absolute bottom-3 left-1/2 z-10 -translate-x-1/2">
|
|
1563
|
+
<div ref="controlBar" class="absolute bottom-3 left-1/2 z-10 -translate-x-1/2">
|
|
1470
1564
|
<TooltipProvider :delay-duration="250">
|
|
1471
1565
|
<div class="rounded-lg border border-gray-400/10 outline-1 outline-white/30 backdrop-blur-sm">
|
|
1472
1566
|
<div class="flex items-center gap-0.5 whitespace-nowrap border border-white bg-white rounded-lg bg-gradient-to-br from-white via-violet-50/40 to-white px-2 py-1">
|
|
1473
1567
|
<!-- Smart Run: the page decides what "run" means — workflows that
|
|
1474
1568
|
consume trigger.payload open the payload dialog, payload-free
|
|
1475
1569
|
ones fire immediately. One button either way. -->
|
|
1476
|
-
<IntegrationsEditorTip :label="runWaiting ? '
|
|
1570
|
+
<IntegrationsEditorTip :label="runWaiting ? t('integration.editor.tool.run.tooltipWaiting') : t('integration.editor.tool.run.tooltip')" shortcut="mod+enter">
|
|
1477
1571
|
<button
|
|
1478
1572
|
type="button"
|
|
1479
1573
|
class="relative flex h-8 items-center gap-1.5 rounded-lg bg-gradient-to-b from-emerald-500 to-emerald-600 px-3.5 text-sm font-medium text-white transition after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:shadow-[inset_0_1px_0_rgba(255,255,255,0.3),inset_0_-2px_0_rgba(0,0,0,0.25)] hover:from-emerald-600 hover:to-emerald-600 active:translate-y-px disabled:opacity-60"
|
|
@@ -1482,16 +1576,20 @@ const edgeTypes = {
|
|
|
1482
1576
|
>
|
|
1483
1577
|
<Loader2 v-if="runBusy" :size="14" class="animate-spin" />
|
|
1484
1578
|
<Play v-else :size="14" />
|
|
1485
|
-
{{ runWaiting ? "
|
|
1579
|
+
{{ runWaiting ? t("integration.editor.run.waitingForBuild") : t("integration.editor.run.button") }}
|
|
1486
1580
|
</button>
|
|
1487
1581
|
</IntegrationsEditorTip>
|
|
1488
1582
|
|
|
1489
1583
|
<div class="mx-1 h-5 w-px bg-black/10" />
|
|
1490
1584
|
|
|
1491
1585
|
<div class="flex items-center gap-0.5 rounded-lg bg-black/[0.05] p-0.5 shadow-[inset_0_1px_2px_rgba(15,23,42,0.12)]">
|
|
1492
|
-
|
|
1586
|
+
<!-- Icon-only, and the tooltip is not an accessible name: each one
|
|
1587
|
+
says what it does itself, and whether it is the active mode. -->
|
|
1588
|
+
<IntegrationsEditorTip :label="t('integration.editor.tool.select')" shortcut="v">
|
|
1493
1589
|
<button
|
|
1494
1590
|
type="button"
|
|
1591
|
+
:aria-label="t('integration.editor.tool.select')"
|
|
1592
|
+
:aria-pressed="pointerMode === 'select'"
|
|
1495
1593
|
class="flex size-7 items-center justify-center rounded-lg transition"
|
|
1496
1594
|
:class="pointerMode === 'select' ? 'bg-gradient-to-b from-white to-gray-50 text-foreground shadow-[0_1px_2px_rgba(15,23,42,0.18),inset_0_1px_0_rgba(255,255,255,0.9)]' : 'text-muted-foreground hover:text-foreground'"
|
|
1497
1595
|
@click="setPointerMode('select')"
|
|
@@ -1499,9 +1597,11 @@ const edgeTypes = {
|
|
|
1499
1597
|
<MousePointer2 :size="14" />
|
|
1500
1598
|
</button>
|
|
1501
1599
|
</IntegrationsEditorTip>
|
|
1502
|
-
<IntegrationsEditorTip label="
|
|
1600
|
+
<IntegrationsEditorTip :label="t('integration.editor.tool.hand')" shortcut="h">
|
|
1503
1601
|
<button
|
|
1504
1602
|
type="button"
|
|
1603
|
+
:aria-label="t('integration.editor.tool.hand')"
|
|
1604
|
+
:aria-pressed="pointerMode === 'hand'"
|
|
1505
1605
|
class="flex size-7 items-center justify-center rounded-lg transition"
|
|
1506
1606
|
:class="pointerMode === 'hand' ? 'bg-gradient-to-b from-white to-gray-50 text-foreground shadow-[0_1px_2px_rgba(15,23,42,0.18),inset_0_1px_0_rgba(255,255,255,0.9)]' : 'text-muted-foreground hover:text-foreground'"
|
|
1507
1607
|
@click="setPointerMode('hand')"
|
|
@@ -1511,73 +1611,74 @@ const edgeTypes = {
|
|
|
1511
1611
|
</IntegrationsEditorTip>
|
|
1512
1612
|
</div>
|
|
1513
1613
|
|
|
1514
|
-
<IntegrationsEditorTip label="
|
|
1614
|
+
<IntegrationsEditorTip :label="t('integration.editor.tool.tidyUp')" shortcut="shift+alt+t">
|
|
1515
1615
|
<button
|
|
1516
1616
|
type="button"
|
|
1517
1617
|
class="flex h-8 items-center gap-1.5 rounded-lg px-2.5 text-sm font-medium text-foreground transition hover:bg-black/[0.04]"
|
|
1518
1618
|
@click="tidyUp"
|
|
1519
1619
|
>
|
|
1520
1620
|
<LayoutGrid :size="13" />
|
|
1521
|
-
|
|
1621
|
+
{{ t("integration.editor.tool.tidyUp") }}
|
|
1522
1622
|
</button>
|
|
1523
1623
|
</IntegrationsEditorTip>
|
|
1524
|
-
<IntegrationsEditorTip label="
|
|
1624
|
+
<IntegrationsEditorTip :label="t('integration.editor.tool.fitView.tooltip')" shortcut="1">
|
|
1525
1625
|
<button
|
|
1526
1626
|
type="button"
|
|
1527
1627
|
class="flex h-8 items-center gap-1.5 rounded-lg px-2.5 text-sm font-medium text-foreground transition hover:bg-black/[0.04]"
|
|
1528
1628
|
@click="flow.fitView({ padding: 0.2, duration: 400, maxZoom: 1 })"
|
|
1529
1629
|
>
|
|
1530
1630
|
<Sparkles :size="13" />
|
|
1531
|
-
|
|
1631
|
+
{{ t("integration.editor.tool.fitView.label") }}
|
|
1532
1632
|
</button>
|
|
1533
1633
|
</IntegrationsEditorTip>
|
|
1534
|
-
<IntegrationsEditorTip label="
|
|
1634
|
+
<IntegrationsEditorTip :label="t('integration.editor.tool.addSticky.tooltip')" shortcut="shift+s">
|
|
1535
1635
|
<button
|
|
1536
1636
|
type="button"
|
|
1537
1637
|
class="flex h-8 items-center gap-1.5 rounded-lg px-2.5 text-sm font-medium text-foreground transition hover:bg-black/[0.04]"
|
|
1538
1638
|
@click="addSticky()"
|
|
1539
1639
|
>
|
|
1540
1640
|
<StickyNote :size="13" />
|
|
1541
|
-
|
|
1641
|
+
{{ t("integration.editor.tool.addSticky.label") }}
|
|
1542
1642
|
</button>
|
|
1543
1643
|
</IntegrationsEditorTip>
|
|
1544
|
-
<IntegrationsEditorTip label="
|
|
1644
|
+
<IntegrationsEditorTip :label="t('integration.editor.tool.addGroup.tooltip')">
|
|
1545
1645
|
<button
|
|
1546
1646
|
type="button"
|
|
1547
1647
|
class="flex h-8 items-center gap-1.5 rounded-lg px-2.5 text-sm font-medium text-foreground transition hover:bg-black/[0.04]"
|
|
1548
1648
|
@click="addGroupFrame"
|
|
1549
1649
|
>
|
|
1550
1650
|
<GroupIcon :size="13" />
|
|
1551
|
-
|
|
1651
|
+
{{ t("integration.editor.tool.addGroup.label") }}
|
|
1552
1652
|
</button>
|
|
1553
1653
|
</IntegrationsEditorTip>
|
|
1554
|
-
<IntegrationsEditorTip label="
|
|
1654
|
+
<IntegrationsEditorTip :label="t('integration.editor.tool.variables.tooltip')" shortcut="shift+v">
|
|
1555
1655
|
<button
|
|
1556
1656
|
type="button"
|
|
1557
1657
|
class="flex h-8 items-center gap-1.5 rounded-lg px-2.5 text-sm font-medium text-foreground transition hover:bg-black/[0.04]"
|
|
1558
1658
|
@click="variablesOpen = true"
|
|
1559
1659
|
>
|
|
1560
1660
|
<Braces :size="13" />
|
|
1561
|
-
|
|
1661
|
+
{{ t("integration.editor.tool.variables.label") }}
|
|
1562
1662
|
<span v-if="variableCount" class="rounded-lg bg-muted px-1.5 text-xs tabular-nums text-muted-foreground">{{ variableCount }}</span>
|
|
1563
1663
|
</button>
|
|
1564
1664
|
</IntegrationsEditorTip>
|
|
1565
|
-
<IntegrationsEditorTip label="
|
|
1665
|
+
<IntegrationsEditorTip :label="t('integration.editor.tool.settings.tooltip')" shortcut="shift+alt+s">
|
|
1566
1666
|
<button
|
|
1567
1667
|
type="button"
|
|
1568
1668
|
class="flex h-8 items-center gap-1.5 rounded-lg px-2.5 text-sm font-medium text-foreground transition hover:bg-black/[0.04]"
|
|
1569
1669
|
@click="emit('openSettings')"
|
|
1570
1670
|
>
|
|
1571
1671
|
<Settings2 :size="13" />
|
|
1572
|
-
|
|
1672
|
+
{{ t("integration.editor.tool.settings.label") }}
|
|
1573
1673
|
</button>
|
|
1574
1674
|
</IntegrationsEditorTip>
|
|
1575
1675
|
|
|
1576
1676
|
<div class="mx-1 h-5 w-px bg-black/10" />
|
|
1577
1677
|
|
|
1578
|
-
<IntegrationsEditorTip label="
|
|
1678
|
+
<IntegrationsEditorTip :label="t('integration.editor.tool.zoomOut')" shortcut="-">
|
|
1579
1679
|
<button
|
|
1580
1680
|
type="button"
|
|
1681
|
+
:aria-label="t('integration.editor.tool.zoomOut')"
|
|
1581
1682
|
class="flex size-8 items-center justify-center rounded-lg text-foreground transition hover:bg-black/[0.04]"
|
|
1582
1683
|
@click="flow.zoomOut({ duration: 150 })"
|
|
1583
1684
|
>
|
|
@@ -1587,12 +1688,17 @@ const edgeTypes = {
|
|
|
1587
1688
|
<!-- Tooltip sits on a wrapper span so its as-child merge never
|
|
1588
1689
|
touches the dropdown trigger (merging both onto one button
|
|
1589
1690
|
swallowed the trigger's pointerdown and the menu never opened). -->
|
|
1590
|
-
<IntegrationsEditorTip label="
|
|
1691
|
+
<IntegrationsEditorTip :label="t('integration.editor.tool.zoomLevel.tooltip')" shortcut="0">
|
|
1591
1692
|
<span class="inline-flex">
|
|
1592
1693
|
<DropdownMenu v-model:open="zoomMenuOpen">
|
|
1593
1694
|
<DropdownMenuTrigger as-child>
|
|
1695
|
+
<!-- The label carries the percentage because it replaces the
|
|
1696
|
+
button's text as its accessible name: a bare "Zoom level"
|
|
1697
|
+
would leave screen reader users with the one thing sighted
|
|
1698
|
+
users do not need told and without the value they do. -->
|
|
1594
1699
|
<button
|
|
1595
1700
|
type="button"
|
|
1701
|
+
:aria-label="t('integration.editor.tool.zoomLevel.aria', { pct: zoomPct })"
|
|
1596
1702
|
class="w-12 rounded-lg py-1 text-center text-sm tabular-nums text-muted-foreground transition hover:bg-black/[0.04] hover:text-foreground"
|
|
1597
1703
|
>
|
|
1598
1704
|
{{ zoomPct }}
|
|
@@ -1604,6 +1710,7 @@ const edgeTypes = {
|
|
|
1604
1710
|
v-model="zoomInputText"
|
|
1605
1711
|
type="text"
|
|
1606
1712
|
inputmode="numeric"
|
|
1713
|
+
:aria-label="t('integration.editor.tool.zoomLevel.inputAria')"
|
|
1607
1714
|
class="h-6 w-full rounded-md border border-black/[0.08] bg-white px-1 text-center text-sm tabular-nums focus:outline-none focus:ring-2 focus:ring-primary/40"
|
|
1608
1715
|
placeholder="%"
|
|
1609
1716
|
@keydown.stop
|
|
@@ -1623,9 +1730,10 @@ const edgeTypes = {
|
|
|
1623
1730
|
</DropdownMenu>
|
|
1624
1731
|
</span>
|
|
1625
1732
|
</IntegrationsEditorTip>
|
|
1626
|
-
<IntegrationsEditorTip label="
|
|
1733
|
+
<IntegrationsEditorTip :label="t('integration.editor.tool.zoomIn')" shortcut="=">
|
|
1627
1734
|
<button
|
|
1628
1735
|
type="button"
|
|
1736
|
+
:aria-label="t('integration.editor.tool.zoomIn')"
|
|
1629
1737
|
class="flex size-8 items-center justify-center rounded-lg text-foreground transition hover:bg-black/[0.04]"
|
|
1630
1738
|
@click="flow.zoomIn({ duration: 150 })"
|
|
1631
1739
|
>
|
|
@@ -1644,10 +1752,10 @@ const edgeTypes = {
|
|
|
1644
1752
|
>
|
|
1645
1753
|
<div class="rounded-lg border border-dashed border-black/[0.12] bg-white/80 px-5 py-4 text-center text-sm text-muted-foreground shadow-sm backdrop-blur-sm">
|
|
1646
1754
|
<div class="font-medium text-foreground mb-1">
|
|
1647
|
-
|
|
1755
|
+
{{ t("integration.editor.emptyState.title") }}
|
|
1648
1756
|
</div>
|
|
1649
1757
|
<p>
|
|
1650
|
-
|
|
1758
|
+
{{ t("integration.editor.emptyState.hint") }}
|
|
1651
1759
|
</p>
|
|
1652
1760
|
</div>
|
|
1653
1761
|
</div>
|
|
@@ -1669,7 +1777,22 @@ const edgeTypes = {
|
|
|
1669
1777
|
renameIntent = false;
|
|
1670
1778
|
}
|
|
1671
1779
|
}">
|
|
1672
|
-
|
|
1780
|
+
<!-- Sized for the form, not for three columns. 1100px leaves the 1024px
|
|
1781
|
+
reading width a small gutter instead of floating it in the middle,
|
|
1782
|
+
and still gives the form ~770px with the Input panel open. Fixed
|
|
1783
|
+
rather than fitting the content: the tabs hold very different
|
|
1784
|
+
amounts, and a dialog that resizes under the pointer as you switch
|
|
1785
|
+
between them is worse than one with some room to spare.
|
|
1786
|
+
|
|
1787
|
+
A trigger is the exception, and gets a box its own size. It has one
|
|
1788
|
+
tab, no output to test and a handful of controls — a schedule is a
|
|
1789
|
+
frequency, a time and its days — so the node's width would leave the
|
|
1790
|
+
form as a strip down the left of an empty workbench. -->
|
|
1791
|
+
<DialogContent
|
|
1792
|
+
:show-close-button="false"
|
|
1793
|
+
class="flex max-w-none flex-col gap-0 overflow-hidden rounded-lg p-0 sm:max-w-none"
|
|
1794
|
+
:class="selectionIsTrigger ? 'h-[min(520px,86vh)] w-[min(600px,94vw)]' : 'h-[min(680px,86vh)] w-[min(1100px,94vw)]'"
|
|
1795
|
+
>
|
|
1673
1796
|
<NodeInspector
|
|
1674
1797
|
v-if="selection"
|
|
1675
1798
|
:selection="selection"
|
|
@@ -1682,6 +1805,7 @@ const edgeTypes = {
|
|
|
1682
1805
|
:triggers="blob.triggers"
|
|
1683
1806
|
:dynamic-outputs="dynamicOutputs"
|
|
1684
1807
|
:workflow-id="workflow.id"
|
|
1808
|
+
:refused-fields="selectedId ? invalidNodeMessages[selectedId] : void 0"
|
|
1685
1809
|
@update="applyInspectorUpdate"
|
|
1686
1810
|
@close="selectedId = null"
|
|
1687
1811
|
@remove="selectedId && removeBlobNode(selectedId)"
|