@revenexx/studio-integrations 2.8.1 → 2.9.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 +22 -0
- package/dist/module.json +1 -1
- package/dist/runtime/components/Integrations/ExpressionInput.vue +31 -0
- package/dist/runtime/components/Integrations/NodeConfigField.vue +5 -1
- package/dist/runtime/components/Integrations/NodePalette.vue +72 -64
- package/dist/runtime/components/Integrations/RunDetailPanel.vue +11 -5
- package/dist/runtime/components/Integrations/TemplateInstallStep.vue +2 -2
- package/dist/runtime/components/Integrations/WorkflowEdgeView.d.vue.ts +6 -0
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +4 -2
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue.d.ts +6 -0
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +147 -27
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +36 -12
- package/dist/runtime/components/Integrations/WorkflowNodeView.d.vue.ts +14 -0
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +162 -13
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue.d.ts +14 -0
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +34 -12
- package/dist/runtime/components/Integrations/WorkflowTriggerView.d.vue.ts +8 -1
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +3 -1
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue.d.ts +8 -1
- package/dist/runtime/components/Integrations/canvasSides.d.ts +27 -0
- package/dist/runtime/components/Integrations/canvasSides.js +22 -0
- package/dist/runtime/composables/useIntegrationsCredentials.d.ts +4 -1
- package/dist/runtime/composables/useIntegrationsCredentials.js +8 -3
- package/dist/runtime/data/canvasShapes.d.ts +139 -0
- package/dist/runtime/data/canvasShapes.js +130 -0
- package/dist/runtime/data/connectionRules.d.ts +110 -0
- package/dist/runtime/data/connectionRules.js +110 -0
- package/dist/runtime/data/graphShapes.d.ts +175 -0
- package/dist/runtime/data/graphShapes.js +359 -0
- package/dist/runtime/data/handleId.d.ts +12 -0
- package/dist/runtime/data/handleId.js +3 -0
- package/dist/runtime/data/refusalMessage.d.ts +32 -0
- package/dist/runtime/data/refusalMessage.js +42 -0
- package/dist/runtime/data/saveRefusal.d.ts +31 -0
- package/dist/runtime/data/saveRefusal.js +25 -0
- package/dist/runtime/i18n/messages/de.json +36 -1
- package/dist/runtime/i18n/messages/en.json +36 -1
- package/dist/runtime/pages/integrations/schedules.vue +1 -1
- package/dist/runtime/pages/integrations/workflows/[id].vue +38 -3
- package/dist/runtime/types/integrations.d.ts +13 -4
- package/dist/runtime/utils/skippedStep.d.ts +39 -0
- package/dist/runtime/utils/skippedStep.js +22 -0
- package/package.json +1 -1
|
@@ -49,16 +49,19 @@ import { toast } from "vue-sonner";
|
|
|
49
49
|
import WorkflowEdgeView from "./WorkflowEdgeView.vue";
|
|
50
50
|
import { layoutWorkflowNodes } from "./workflowLayout";
|
|
51
51
|
import { defaultsFor } from "../../utils/configDefaults";
|
|
52
|
-
import { TRIGGER_TEMPLATES, findManifest } from "../../data/integrationNodeCatalog";
|
|
52
|
+
import { TRIGGER_TEMPLATES, findManifest, triggerTypeLabel } from "../../data/integrationNodeCatalog";
|
|
53
53
|
import { collectUpstreamNodes, isTerminalManifest, portsForNode } from "../../data/nodeInputData";
|
|
54
54
|
import { computeLoopBodies, iteratorNodeIds, nodeLoopData } from "../../data/loopBody";
|
|
55
|
+
import { canvasShapes } from "../../data/canvasShapes";
|
|
56
|
+
import { connectionRefusal, lastingCrossings, refusalJudge, refusalOfChange } from "../../data/connectionRules";
|
|
57
|
+
import { copiedRefusalText, joinRefusalText, refusalText } from "../../data/refusalMessage";
|
|
55
58
|
import { inputHandleId, outputHandleId, portFromHandleId } from "../../data/handleId";
|
|
56
59
|
import { OPPOSITE_SIDE } from "./canvasSides";
|
|
57
60
|
import { deepClone } from "../../utils/deepClone";
|
|
58
61
|
import { copyToClipboard } from "../../utils/clipboard";
|
|
59
|
-
import { groupFindingsByNode, findingsByNodeField } from "../../utils/validationFindings";
|
|
62
|
+
import { groupFindingsByNode, findingsByNodeField, nodeDisplayLabel } from "../../utils/validationFindings";
|
|
60
63
|
import { reorderBranchSubtrees } from "../../data/branchReorder";
|
|
61
|
-
import { resolveLocalized } from "../../utils/localize";
|
|
64
|
+
import { resolveDisplayName, resolveLocalized } from "../../utils/localize";
|
|
62
65
|
import { nodeDisplayName } from "../../utils/nodeDisplayName";
|
|
63
66
|
import { keepOpenBeside } from "../../utils/dialog";
|
|
64
67
|
const props = defineProps({
|
|
@@ -79,6 +82,7 @@ watch(() => props.workflow.id, () => {
|
|
|
79
82
|
const vfNodes = ref([]);
|
|
80
83
|
const vfEdges = ref([]);
|
|
81
84
|
const flow = useVueFlow();
|
|
85
|
+
let joinRefusals = [];
|
|
82
86
|
const invalidNodeMessages = computed(() => findingsByNodeField(props.workflow.warnings ?? [], props.workflow.blob.nodes));
|
|
83
87
|
const dynamicOutputs = ref({});
|
|
84
88
|
const invalidNodeFields = computed(() => {
|
|
@@ -255,6 +259,8 @@ function bornClass(id) {
|
|
|
255
259
|
const NODE_FALLBACK_POSITION = { x: 240, y: 80 };
|
|
256
260
|
function syncFromBlob() {
|
|
257
261
|
const loops = nodeLoopData(blob.value.nodes, blob.value.edges);
|
|
262
|
+
const shapes = canvasShapes(blob.value.nodes, blob.value.edges, blob.value.triggers);
|
|
263
|
+
joinRefusals = shapes.refusals;
|
|
258
264
|
const stickies = [...blob.value.stickies ?? []].sort((a, b) => stickyArea(b) - stickyArea(a));
|
|
259
265
|
const collapsedBy = collapsedGroupOf();
|
|
260
266
|
const groupNodes = (blob.value.groups ?? []).map((g, i) => {
|
|
@@ -300,7 +306,7 @@ function syncFromBlob() {
|
|
|
300
306
|
type: "trigger",
|
|
301
307
|
position: triggerPosition(t2.id, i),
|
|
302
308
|
class: bornClass(t2.id),
|
|
303
|
-
data: t2
|
|
309
|
+
data: { ...t2, __split: shapes.splitTriggers.has(t2.id) }
|
|
304
310
|
})),
|
|
305
311
|
...blob.value.nodes.filter((n) => !collapsedBy.has(n.id)).map((n) => ({
|
|
306
312
|
id: n.id,
|
|
@@ -311,7 +317,8 @@ function syncFromBlob() {
|
|
|
311
317
|
...n,
|
|
312
318
|
__resolvedOutputs: dynamicOutputs.value[n.id],
|
|
313
319
|
__invalidFields: invalidNodeFields.value[n.id],
|
|
314
|
-
__loop: loops.get(n.id)
|
|
320
|
+
__loop: loops.get(n.id),
|
|
321
|
+
__shape: shapes.byNode.get(n.id)
|
|
315
322
|
}
|
|
316
323
|
}))
|
|
317
324
|
];
|
|
@@ -329,10 +336,14 @@ function syncFromBlob() {
|
|
|
329
336
|
const vf = makeVfEdge(e.id, e.from.nodeId, e.to.nodeId, e.from.output);
|
|
330
337
|
mapped.push({
|
|
331
338
|
...vf,
|
|
339
|
+
// Where branches of a split cross, drawn as the connection to change.
|
|
340
|
+
data: { ...vf.data, crossing: shapes.crossingEdges.has(e.id) },
|
|
332
341
|
source,
|
|
333
342
|
target,
|
|
334
343
|
sourceHandle: srcGroup ? outputHandleId("default", "bottom") : vf.sourceHandle,
|
|
335
|
-
|
|
344
|
+
// A connection of a nested merge's level arrives at the virtual merger
|
|
345
|
+
// drawn in front of the input, not at the input dot (PO-750).
|
|
346
|
+
targetHandle: tgtGroup ? inputHandleId("top") : shapes.arrivalHandle(e) ?? vf.targetHandle
|
|
336
347
|
});
|
|
337
348
|
}
|
|
338
349
|
const bySource = /* @__PURE__ */ new Map();
|
|
@@ -350,6 +361,13 @@ function syncFromBlob() {
|
|
|
350
361
|
vfEdges.value = mapped;
|
|
351
362
|
void nextTick(() => flow.updateNodeInternals(vfNodes.value.map((n) => n.id)));
|
|
352
363
|
}
|
|
364
|
+
watch(
|
|
365
|
+
() => (blob.value.groups ?? []).flatMap((g) => g.nodeIds.map((id) => {
|
|
366
|
+
const { width, height } = memberDims(id);
|
|
367
|
+
return `${id}:${width}x${height}`;
|
|
368
|
+
})).join("|"),
|
|
369
|
+
() => syncFromBlob()
|
|
370
|
+
);
|
|
353
371
|
function makeVfEdge(id, sourceId, targetId, output) {
|
|
354
372
|
const kind = edgeKindFromPort(sourceId, output);
|
|
355
373
|
const wn = blob.value.nodes.find((n) => n.id === sourceId);
|
|
@@ -483,6 +501,7 @@ flow.onConnect((conn) => {
|
|
|
483
501
|
if (!conn.source || !conn.target) return;
|
|
484
502
|
const id = `e-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
|
|
485
503
|
const output = portFromHandleId(conn.sourceHandle) ?? "default";
|
|
504
|
+
const before = joinRefusals;
|
|
486
505
|
blob.value.edges.push({
|
|
487
506
|
id,
|
|
488
507
|
from: { nodeId: conn.source, output },
|
|
@@ -490,17 +509,26 @@ flow.onConnect((conn) => {
|
|
|
490
509
|
});
|
|
491
510
|
syncFromBlob();
|
|
492
511
|
push();
|
|
512
|
+
warnNewJoinRefusal(before);
|
|
493
513
|
});
|
|
494
514
|
const pendingConnect = ref(null);
|
|
495
515
|
let connectStart = null;
|
|
496
516
|
let connectCompleted = false;
|
|
497
517
|
flow.onConnectStart(({ nodeId, handleId, handleType }) => {
|
|
498
518
|
connectCompleted = false;
|
|
519
|
+
lastRefusal = null;
|
|
520
|
+
prepareDragJudge();
|
|
499
521
|
connectStart = handleType === "source" && nodeId ? { sourceId: nodeId, output: portFromHandleId(handleId) ?? "default" } : null;
|
|
500
522
|
});
|
|
501
523
|
flow.onConnectEnd((event) => {
|
|
502
524
|
const start = connectStart;
|
|
503
525
|
connectStart = null;
|
|
526
|
+
dragJudge = null;
|
|
527
|
+
const refused = connectCompleted ? null : droppedOnRefusal(event);
|
|
528
|
+
if (refused) {
|
|
529
|
+
sayRefused(refused);
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
504
532
|
if (!start || connectCompleted) return;
|
|
505
533
|
if (!(event instanceof MouseEvent)) return;
|
|
506
534
|
const target = event.target;
|
|
@@ -511,15 +539,42 @@ flow.onConnectEnd((event) => {
|
|
|
511
539
|
void focusPalette();
|
|
512
540
|
toast.info(t("integration.editor.quickConnect.toast"));
|
|
513
541
|
});
|
|
542
|
+
flow.onClickConnectStart(() => {
|
|
543
|
+
lastRefusal = null;
|
|
544
|
+
prepareDragJudge();
|
|
545
|
+
});
|
|
546
|
+
flow.onClickConnectEnd((event) => {
|
|
547
|
+
dragJudge = null;
|
|
548
|
+
const refused = droppedOnRefusal(event);
|
|
549
|
+
if (refused) sayRefused(refused);
|
|
550
|
+
});
|
|
551
|
+
flow.onEdgeUpdateStart(({ edge }) => {
|
|
552
|
+
updatingEdgeId = edge.id;
|
|
553
|
+
lastRefusal = null;
|
|
554
|
+
prepareDragJudge();
|
|
555
|
+
});
|
|
556
|
+
flow.onEdgeUpdateEnd(({ event }) => {
|
|
557
|
+
updatingEdgeId = null;
|
|
558
|
+
dragJudge = null;
|
|
559
|
+
const refused = droppedOnRefusal(event);
|
|
560
|
+
if (refused) sayRefused(refused);
|
|
561
|
+
});
|
|
514
562
|
flow.onEdgeUpdate(({ edge, connection }) => {
|
|
515
563
|
if (!connection.source || !connection.target) return;
|
|
516
564
|
if (connection.source === connection.target) return;
|
|
517
565
|
const be = blob.value.edges.find((e) => e.id === edge.id);
|
|
518
566
|
if (!be) return;
|
|
567
|
+
const prior = updatingEdgeId;
|
|
568
|
+
updatingEdgeId = edge.id;
|
|
569
|
+
const refused = refusalOf(connection.source, connection.sourceHandle ?? null, connection.target);
|
|
570
|
+
updatingEdgeId = prior;
|
|
571
|
+
if (refused) return;
|
|
572
|
+
const before = joinRefusals;
|
|
519
573
|
be.from = { nodeId: connection.source, output: portFromHandleId(connection.sourceHandle) ?? be.from.output };
|
|
520
574
|
be.to = { nodeId: connection.target, input: be.to.input };
|
|
521
575
|
syncFromBlob();
|
|
522
576
|
push();
|
|
577
|
+
warnNewJoinRefusal(before);
|
|
523
578
|
});
|
|
524
579
|
let pendingEdgeRemovals = [];
|
|
525
580
|
let pendingNodeRemovals = [];
|
|
@@ -531,6 +586,7 @@ function queueRemovalFlush() {
|
|
|
531
586
|
}
|
|
532
587
|
function flushRemovals() {
|
|
533
588
|
removalFlushQueued = false;
|
|
589
|
+
const before = joinRefusals;
|
|
534
590
|
const edges = pendingEdgeRemovals;
|
|
535
591
|
const nodes = pendingNodeRemovals;
|
|
536
592
|
pendingEdgeRemovals = [];
|
|
@@ -549,6 +605,7 @@ function flushRemovals() {
|
|
|
549
605
|
if (mutated) {
|
|
550
606
|
syncFromBlob();
|
|
551
607
|
push();
|
|
608
|
+
warnNewJoinRefusal(before);
|
|
552
609
|
}
|
|
553
610
|
}
|
|
554
611
|
flow.onEdgesChange((changes) => {
|
|
@@ -575,6 +632,7 @@ function removeBlobNode(id) {
|
|
|
575
632
|
ungroup(id);
|
|
576
633
|
return;
|
|
577
634
|
}
|
|
635
|
+
const before = joinRefusals;
|
|
578
636
|
stripFromGroups(/* @__PURE__ */ new Set([id]));
|
|
579
637
|
blob.value.nodes = blob.value.nodes.filter((n) => n.id !== id);
|
|
580
638
|
if (blob.value.stickies?.length) blob.value.stickies = blob.value.stickies.filter((s) => s.id !== id);
|
|
@@ -584,6 +642,7 @@ function removeBlobNode(id) {
|
|
|
584
642
|
if (selectedId.value === id) selectedId.value = null;
|
|
585
643
|
syncFromBlob();
|
|
586
644
|
push();
|
|
645
|
+
warnNewJoinRefusal(before);
|
|
587
646
|
}
|
|
588
647
|
function push() {
|
|
589
648
|
emit("change", deepClone(blob.value));
|
|
@@ -647,13 +706,16 @@ function dropFromBlob(ids) {
|
|
|
647
706
|
}
|
|
648
707
|
function removeBlobNodes(ids) {
|
|
649
708
|
if (!ids.length) return;
|
|
709
|
+
const before = joinRefusals;
|
|
650
710
|
dropFromBlob(ids);
|
|
651
711
|
syncFromBlob();
|
|
652
712
|
push();
|
|
713
|
+
warnNewJoinRefusal(before);
|
|
653
714
|
}
|
|
654
715
|
function duplicateBlobNodes(ids) {
|
|
655
716
|
const src = blob.value.nodes.filter((n) => ids.includes(n.id));
|
|
656
717
|
if (!src.length) return;
|
|
718
|
+
const before = joinRefusals;
|
|
657
719
|
const idMap = /* @__PURE__ */ new Map();
|
|
658
720
|
const stamp = Date.now();
|
|
659
721
|
src.forEach((n, i) => idMap.set(n.id, `n-${stamp}${i}`));
|
|
@@ -664,21 +726,29 @@ function duplicateBlobNodes(ids) {
|
|
|
664
726
|
position: { x: (n.position?.x ?? 0) + offset, y: (n.position?.y ?? 0) + offset }
|
|
665
727
|
}));
|
|
666
728
|
blob.value.nodes.push(...copies);
|
|
667
|
-
|
|
668
|
-
blob.value.edges.push({
|
|
669
|
-
id: `e-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`,
|
|
670
|
-
from: { nodeId: idMap.get(e.from.nodeId), output: e.from.output },
|
|
671
|
-
to: { nodeId: idMap.get(e.to.nodeId), input: e.to.input }
|
|
672
|
-
});
|
|
673
|
-
}
|
|
729
|
+
addCopiedEdges(blob.value.edges.filter((e) => idMap.has(e.from.nodeId) && idMap.has(e.to.nodeId)), idMap);
|
|
674
730
|
markBorn(...idMap.values());
|
|
675
731
|
syncFromBlob();
|
|
676
732
|
push();
|
|
733
|
+
warnNewJoinRefusal(before);
|
|
677
734
|
void nextTick(() => {
|
|
678
735
|
flow.removeSelectedElements();
|
|
679
736
|
flow.addSelectedNodes(flow.getNodes.value.filter((n) => [...idMap.values()].includes(n.id)));
|
|
680
737
|
});
|
|
681
738
|
}
|
|
739
|
+
function addCopiedEdges(edges, idMap) {
|
|
740
|
+
const iterators = iteratorNodeIds(blob.value.nodes);
|
|
741
|
+
const copies = edges.map((e) => ({
|
|
742
|
+
id: `e-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`,
|
|
743
|
+
from: { nodeId: idMap.get(e.from.nodeId), output: e.from.output },
|
|
744
|
+
to: { nodeId: idMap.get(e.to.nodeId), input: e.to.input }
|
|
745
|
+
}));
|
|
746
|
+
const lasting = lastingCrossings({ nodes: blob.value.nodes, edges: [...blob.value.edges, ...copies] }, iterators);
|
|
747
|
+
const kept = copies.filter((e) => !lasting.has(e.id));
|
|
748
|
+
blob.value.edges.push(...kept);
|
|
749
|
+
const refused = copies.length - kept.length;
|
|
750
|
+
if (refused) showRefusal(copiedRefusalText(refused), refused);
|
|
751
|
+
}
|
|
682
752
|
provide("workflow-node-actions", {
|
|
683
753
|
configure: (id) => {
|
|
684
754
|
renameIntent.value = false;
|
|
@@ -722,6 +792,7 @@ async function pasteNodes(at) {
|
|
|
722
792
|
toast.error(t("integration.editor.clipboard.error.empty"));
|
|
723
793
|
return;
|
|
724
794
|
}
|
|
795
|
+
const before = joinRefusals;
|
|
725
796
|
const idMap = /* @__PURE__ */ new Map();
|
|
726
797
|
const stamp = Date.now();
|
|
727
798
|
payload.nodes.forEach((n, i) => idMap.set(n.id, `n-${stamp}${i}`));
|
|
@@ -734,17 +805,11 @@ async function pasteNodes(at) {
|
|
|
734
805
|
id: idMap.get(n.id),
|
|
735
806
|
position: { x: (n.position?.x ?? 0) + dx, y: (n.position?.y ?? 0) + dy }
|
|
736
807
|
})));
|
|
737
|
-
|
|
738
|
-
if (!idMap.has(e.from.nodeId) || !idMap.has(e.to.nodeId)) continue;
|
|
739
|
-
blob.value.edges.push({
|
|
740
|
-
id: `e-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`,
|
|
741
|
-
from: { nodeId: idMap.get(e.from.nodeId), output: e.from.output },
|
|
742
|
-
to: { nodeId: idMap.get(e.to.nodeId), input: e.to.input }
|
|
743
|
-
});
|
|
744
|
-
}
|
|
808
|
+
addCopiedEdges((payload.edges ?? []).filter((e) => idMap.has(e.from.nodeId) && idMap.has(e.to.nodeId)), idMap);
|
|
745
809
|
markBorn(...idMap.values());
|
|
746
810
|
syncFromBlob();
|
|
747
811
|
push();
|
|
812
|
+
warnNewJoinRefusal(before);
|
|
748
813
|
void nextTick(() => {
|
|
749
814
|
flow.removeSelectedElements();
|
|
750
815
|
flow.addSelectedNodes(flow.getNodes.value.filter((n) => [...idMap.values()].includes(n.id)));
|
|
@@ -827,10 +892,55 @@ function onResolvedOutputs(nodeId, outputs) {
|
|
|
827
892
|
syncFromBlob();
|
|
828
893
|
nextTick(() => flow.updateNodeInternals([nodeId]));
|
|
829
894
|
}
|
|
895
|
+
let updatingEdgeId = null;
|
|
896
|
+
let lastRefusal = null;
|
|
897
|
+
let dragJudge = null;
|
|
898
|
+
function prepareDragJudge() {
|
|
899
|
+
dragJudge = { replacing: updatingEdgeId, judge: refusalJudge(blob.value, iteratorNodeIds(blob.value.nodes), updatingEdgeId) };
|
|
900
|
+
}
|
|
901
|
+
function refusalOf(source, sourceHandle, target) {
|
|
902
|
+
const candidate = { from: { nodeId: source, output: portFromHandleId(sourceHandle) ?? "default" }, to: { nodeId: target } };
|
|
903
|
+
if (dragJudge && dragJudge.replacing === updatingEdgeId) return dragJudge.judge(candidate);
|
|
904
|
+
return connectionRefusal(blob.value, iteratorNodeIds(blob.value.nodes), candidate, updatingEdgeId);
|
|
905
|
+
}
|
|
830
906
|
function isValidConnection(conn) {
|
|
831
907
|
if (!conn.source || !conn.target) return false;
|
|
832
908
|
if (conn.source === conn.target) return false;
|
|
833
|
-
return true;
|
|
909
|
+
if ("id" in conn && conn.id) return true;
|
|
910
|
+
const reason = refusalOf(conn.source, conn.sourceHandle, conn.target);
|
|
911
|
+
lastRefusal = reason ? { reason, dots: [`${conn.source}|${conn.sourceHandle ?? ""}`, `${conn.target}|${conn.targetHandle ?? ""}`] } : null;
|
|
912
|
+
return reason === null;
|
|
913
|
+
}
|
|
914
|
+
function canvasLabel(id) {
|
|
915
|
+
const node = blob.value.nodes.find((n) => n.id === id);
|
|
916
|
+
if (node) return nodeDisplayLabel(node);
|
|
917
|
+
const trigger = blob.value.triggers.find((tr) => tr.id === id);
|
|
918
|
+
return trigger ? resolveDisplayName(trigger.name) || triggerTypeLabel(trigger.type) : id;
|
|
919
|
+
}
|
|
920
|
+
function showRefusal(text, count) {
|
|
921
|
+
const description = count === void 0 ? t(text.description, text.params) : t(text.description, count, text.params);
|
|
922
|
+
toast.error(t(text.title), { description, duration: 8e3 });
|
|
923
|
+
}
|
|
924
|
+
function warnNewJoinRefusal(before) {
|
|
925
|
+
const key = (r) => `${r.nodeId}|${r.rule}`;
|
|
926
|
+
const known = new Set(before.map(key));
|
|
927
|
+
const added = joinRefusals.find((r) => !known.has(key(r)));
|
|
928
|
+
if (!added) return;
|
|
929
|
+
const text = joinRefusalText(added, canvasLabel(added.nodeId));
|
|
930
|
+
toast.warning(t(text.title), { description: t(text.description, text.params), duration: 8e3 });
|
|
931
|
+
}
|
|
932
|
+
function sayRefused(refusal) {
|
|
933
|
+
showRefusal(refusalText(refusal, canvasLabel));
|
|
934
|
+
}
|
|
935
|
+
function droppedOnRefusal(event) {
|
|
936
|
+
const refusal = lastRefusal;
|
|
937
|
+
lastRefusal = null;
|
|
938
|
+
if (!refusal || !event) return null;
|
|
939
|
+
const point = "changedTouches" in event ? event.changedTouches[0] : event;
|
|
940
|
+
const under = point ? document.elementFromPoint(point.clientX, point.clientY) : null;
|
|
941
|
+
const dot = (under ?? event.target)?.closest(".vue-flow__handle");
|
|
942
|
+
if (!dot) return null;
|
|
943
|
+
return refusal.dots.includes(`${dot.dataset.nodeid}|${dot.dataset.handleid ?? ""}`) ? refusal.reason : null;
|
|
834
944
|
}
|
|
835
945
|
const selectedId = ref(null);
|
|
836
946
|
const selection = computed(() => {
|
|
@@ -1095,22 +1205,32 @@ function spliceIntoEdge(edgeId, payload) {
|
|
|
1095
1205
|
const a = nodePosition(edge.from.nodeId);
|
|
1096
1206
|
const b = nodePosition(edge.to.nodeId);
|
|
1097
1207
|
const id = `n-${Date.now()}`;
|
|
1098
|
-
|
|
1208
|
+
const inserted = {
|
|
1099
1209
|
id,
|
|
1100
1210
|
nodeSlug: manifest.slug,
|
|
1101
1211
|
nodeVersion: manifest.version,
|
|
1102
1212
|
config: defaultsFor(manifest.config ?? []),
|
|
1103
1213
|
position: { x: snap((a.x + b.x) / 2), y: snap((a.y + b.y) / 2) }
|
|
1104
|
-
}
|
|
1105
|
-
blob.value.edges = blob.value.edges.filter((e) => e.id !== edgeId);
|
|
1214
|
+
};
|
|
1106
1215
|
const stamp = Date.now().toString(36);
|
|
1107
|
-
|
|
1216
|
+
const edges = [
|
|
1217
|
+
...blob.value.edges.filter((e) => e.id !== edgeId),
|
|
1108
1218
|
{ id: `e-${stamp}a${Math.random().toString(36).slice(2, 6)}`, from: { nodeId: edge.from.nodeId, output: edge.from.output }, to: { nodeId: id, input: void 0 } },
|
|
1109
1219
|
{ id: `e-${stamp}b${Math.random().toString(36).slice(2, 6)}`, from: { nodeId: id, output: primaryOutputName(payload.slug, payload.version) }, to: { nodeId: edge.to.nodeId, input: edge.to.input } }
|
|
1110
|
-
|
|
1220
|
+
];
|
|
1221
|
+
const nodes = [...blob.value.nodes, inserted];
|
|
1222
|
+
const refused = refusalOfChange({ nodes, edges: blob.value.edges }, { nodes, edges }, iteratorNodeIds(nodes));
|
|
1223
|
+
if (refused) {
|
|
1224
|
+
sayRefused(refused);
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
const before = joinRefusals;
|
|
1228
|
+
blob.value.nodes.push(inserted);
|
|
1229
|
+
blob.value.edges = edges;
|
|
1111
1230
|
markBorn(id);
|
|
1112
1231
|
syncFromBlob();
|
|
1113
1232
|
push();
|
|
1233
|
+
warnNewJoinRefusal(before);
|
|
1114
1234
|
}
|
|
1115
1235
|
function nodeDims(id) {
|
|
1116
1236
|
const d = flow.findNode(id)?.dimensions;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { computed, markRaw, nextTick, onMounted, provide, ref, watch } from "vue";
|
|
2
|
+
import { computed, markRaw, nextTick, onMounted, provide, ref, shallowRef, watch } from "vue";
|
|
3
3
|
import {
|
|
4
4
|
MarkerType,
|
|
5
5
|
VueFlow,
|
|
@@ -22,6 +22,7 @@ import { findManifest } from "../../data/integrationNodeCatalog";
|
|
|
22
22
|
import { OPPOSITE_SIDE } from "./canvasSides";
|
|
23
23
|
import { inputHandleId, outputHandleId } from "../../data/handleId";
|
|
24
24
|
import { iteratorNodeIds, nodeLoopData } from "../../data/loopBody";
|
|
25
|
+
import { canvasShapes } from "../../data/canvasShapes";
|
|
25
26
|
import { useIntegrationsRevisions } from "../../composables/useIntegrationsRevisions";
|
|
26
27
|
import { errorReason } from "../../utils/apiError";
|
|
27
28
|
import { personLabel } from "../../utils/personLabel";
|
|
@@ -81,7 +82,7 @@ function edgeColorByKind(kind) {
|
|
|
81
82
|
}
|
|
82
83
|
const GROUP_PAD = 24;
|
|
83
84
|
const GROUP_LABEL_ROOM = 26;
|
|
84
|
-
function previewGroupNodes(blob, nodePos) {
|
|
85
|
+
function previewGroupNodes(blob, nodePos, nodeSize) {
|
|
85
86
|
return (blob.groups ?? []).map((g, i) => {
|
|
86
87
|
let minX = Number.POSITIVE_INFINITY;
|
|
87
88
|
let minY = Number.POSITIVE_INFINITY;
|
|
@@ -89,10 +90,11 @@ function previewGroupNodes(blob, nodePos) {
|
|
|
89
90
|
let maxY = Number.NEGATIVE_INFINITY;
|
|
90
91
|
for (const id of g.nodeIds) {
|
|
91
92
|
const pos = nodePos(id);
|
|
93
|
+
const size = nodeSize(id);
|
|
92
94
|
minX = Math.min(minX, pos.x);
|
|
93
95
|
minY = Math.min(minY, pos.y);
|
|
94
|
-
maxX = Math.max(maxX, pos.x +
|
|
95
|
-
maxY = Math.max(maxY, pos.y +
|
|
96
|
+
maxX = Math.max(maxX, pos.x + size.width);
|
|
97
|
+
maxY = Math.max(maxY, pos.y + size.height);
|
|
96
98
|
}
|
|
97
99
|
if (!Number.isFinite(minX)) return null;
|
|
98
100
|
return {
|
|
@@ -116,15 +118,36 @@ function previewGroupNodes(blob, nodePos) {
|
|
|
116
118
|
function stickyArea(s) {
|
|
117
119
|
return (s.size?.width ?? 240) * (s.size?.height ?? 160);
|
|
118
120
|
}
|
|
119
|
-
function
|
|
121
|
+
function snapshotPos(blob) {
|
|
120
122
|
const triggerPos = (id, i) => blob.triggerPositions?.[id] ?? { x: -40, y: 60 + i * 140 };
|
|
121
|
-
|
|
123
|
+
return (id) => {
|
|
122
124
|
const wn = blob.nodes.find((n) => n.id === id);
|
|
123
125
|
if (wn) return { x: wn.position?.x ?? 0, y: wn.position?.y ?? 0 };
|
|
124
126
|
const ti = blob.triggers.findIndex((t2) => t2.id === id);
|
|
125
127
|
if (ti >= 0) return triggerPos(id, ti);
|
|
126
128
|
return { x: 0, y: 0 };
|
|
127
129
|
};
|
|
130
|
+
}
|
|
131
|
+
function memberSize(id) {
|
|
132
|
+
const d = preview.findNode(id)?.dimensions;
|
|
133
|
+
return d && d.width > 0 ? d : { width: 220, height: 64 };
|
|
134
|
+
}
|
|
135
|
+
const builtBlob = shallowRef(null);
|
|
136
|
+
watch(
|
|
137
|
+
() => (builtBlob.value?.groups ?? []).flatMap((g) => g.nodeIds.map((id) => {
|
|
138
|
+
const { width, height } = memberSize(id);
|
|
139
|
+
return `${id}:${width}x${height}`;
|
|
140
|
+
})).join("|"),
|
|
141
|
+
() => {
|
|
142
|
+
const blob = builtBlob.value;
|
|
143
|
+
if (!blob) return;
|
|
144
|
+
const frames = new Map(previewGroupNodes(blob, snapshotPos(blob), memberSize).map((n) => [n.id, n]));
|
|
145
|
+
if (frames.size) previewNodes.value = previewNodes.value.map((n) => frames.get(n.id) ?? n);
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
function buildPreview(blob) {
|
|
149
|
+
builtBlob.value = blob;
|
|
150
|
+
const nodePos = snapshotPos(blob);
|
|
128
151
|
const pickSourceSide = (_sourceId, _targetId) => "bottom";
|
|
129
152
|
const edgeKind = (sourceNodeId, output) => {
|
|
130
153
|
const wn = blob.nodes.find((n) => n.id === sourceNodeId);
|
|
@@ -151,9 +174,10 @@ function buildPreview(blob) {
|
|
|
151
174
|
return "default";
|
|
152
175
|
};
|
|
153
176
|
const loops = nodeLoopData(blob.nodes, blob.edges);
|
|
177
|
+
const shapes = canvasShapes(blob.nodes, blob.edges, blob.triggers);
|
|
154
178
|
const stickies = [...blob.stickies ?? []].sort((a, b) => stickyArea(b) - stickyArea(a));
|
|
155
179
|
previewNodes.value = [
|
|
156
|
-
...previewGroupNodes(blob, nodePos),
|
|
180
|
+
...previewGroupNodes(blob, nodePos, memberSize),
|
|
157
181
|
...stickies.map((s, i) => ({
|
|
158
182
|
id: s.id,
|
|
159
183
|
type: "sticky",
|
|
@@ -164,19 +188,19 @@ function buildPreview(blob) {
|
|
|
164
188
|
// draws no toolbar, no resize handle and does not open for typing (PO-284).
|
|
165
189
|
data: { sticky: s }
|
|
166
190
|
})),
|
|
167
|
-
...blob.triggers.map((t2
|
|
191
|
+
...blob.triggers.map((t2) => ({
|
|
168
192
|
id: t2.id,
|
|
169
193
|
type: "trigger",
|
|
170
|
-
position:
|
|
194
|
+
position: nodePos(t2.id),
|
|
171
195
|
draggable: false,
|
|
172
|
-
data: t2
|
|
196
|
+
data: { ...t2, __split: shapes.splitTriggers.has(t2.id) }
|
|
173
197
|
})),
|
|
174
198
|
...blob.nodes.map((n) => ({
|
|
175
199
|
id: n.id,
|
|
176
200
|
type: "workflow",
|
|
177
201
|
position: n.position ?? { x: 240, y: 80 },
|
|
178
202
|
draggable: false,
|
|
179
|
-
data: { ...n, __resolvedOutputs: dynamicOutputs.value[n.id], __loop: loops.get(n.id) }
|
|
203
|
+
data: { ...n, __resolvedOutputs: dynamicOutputs.value[n.id], __loop: loops.get(n.id), __shape: shapes.byNode.get(n.id) }
|
|
180
204
|
}))
|
|
181
205
|
];
|
|
182
206
|
previewEdges.value = blob.edges.map((e) => {
|
|
@@ -190,7 +214,7 @@ function buildPreview(blob) {
|
|
|
190
214
|
source: e.from.nodeId,
|
|
191
215
|
target: e.to.nodeId,
|
|
192
216
|
sourceHandle: outputHandleId(e.from.output, srcSide),
|
|
193
|
-
targetHandle: inputHandleId(OPPOSITE_SIDE[srcSide]),
|
|
217
|
+
targetHandle: shapes.arrivalHandle(e) ?? inputHandleId(OPPOSITE_SIDE[srcSide]),
|
|
194
218
|
type: "flow",
|
|
195
219
|
selectable: false,
|
|
196
220
|
data: { kind, label: isBranchPort ? e.from.output : void 0 },
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { NodeShapeData } from '../../data/canvasShapes.js';
|
|
1
2
|
import type { NodeManifestOutput, WorkflowNode } from '../../types/integrations.js';
|
|
2
3
|
interface NodeData extends WorkflowNode {
|
|
3
4
|
__selected?: boolean;
|
|
@@ -22,6 +23,19 @@ interface NodeData extends WorkflowNode {
|
|
|
22
23
|
/** Set on an iterator itself: how many nodes hang off its `item` port. */
|
|
23
24
|
bodySize?: number;
|
|
24
25
|
};
|
|
26
|
+
/**
|
|
27
|
+
* Run replay only (PO-750): the node's step was skipped, and this says why —
|
|
28
|
+
* "Skipped: dead end in branch 2", or a plain "Skipped" for a reason the studio
|
|
29
|
+
* does not name. Injected like the markers above; no other canvas passes it.
|
|
30
|
+
*/
|
|
31
|
+
__skipped?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Where the workflow splits and merges at this node (PO-750), computed per sync
|
|
34
|
+
* from the blob's edges (`data/canvasShapes.ts`) and injected into all three
|
|
35
|
+
* canvases like `__loop`: a split port and a merging input are drawn as boxes,
|
|
36
|
+
* and the levels of a nested merge as small boxes in front of the input.
|
|
37
|
+
*/
|
|
38
|
+
__shape?: NodeShapeData;
|
|
25
39
|
}
|
|
26
40
|
type __VLS_Props = {
|
|
27
41
|
id: string;
|