Allow nested loop blocks (#1977)
This commit is contained in:
@@ -73,6 +73,7 @@ import {
|
||||
convertEchoParameters,
|
||||
createNode,
|
||||
defaultEdge,
|
||||
descendants,
|
||||
generateNodeLabel,
|
||||
getAdditionalParametersForEmailBlock,
|
||||
getOutputParameterKey,
|
||||
@@ -408,12 +409,22 @@ function FlowRenderer({
|
||||
if (!node || !isWorkflowBlockNode(node)) {
|
||||
return;
|
||||
}
|
||||
const nodesToDelete = descendants(nodes, id);
|
||||
const deletedNodeLabel = node.data.label;
|
||||
const newNodes = nodes.filter((node) => node.id !== id);
|
||||
const newNodes = nodes.filter(
|
||||
(node) => !nodesToDelete.includes(node) && node.id !== id,
|
||||
);
|
||||
const newEdges = edges.flatMap((edge) => {
|
||||
if (edge.source === id) {
|
||||
return [];
|
||||
}
|
||||
if (
|
||||
nodesToDelete.some(
|
||||
(node) => node.id === edge.source || node.id === edge.target,
|
||||
)
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
if (edge.target === id) {
|
||||
const nextEdge = edges.find((edge) => edge.source === id);
|
||||
if (nextEdge) {
|
||||
|
||||
@@ -56,7 +56,6 @@ function EdgeWithAddButton({
|
||||
size="icon"
|
||||
className="h-4 w-4 rounded-full transition-all hover:scale-150"
|
||||
onClick={() => {
|
||||
const disableLoop = Boolean(sourceNode?.parentId);
|
||||
setWorkflowPanelState({
|
||||
active: true,
|
||||
content: "nodeLibrary",
|
||||
@@ -64,7 +63,6 @@ function EdgeWithAddButton({
|
||||
previous: source,
|
||||
next: target,
|
||||
parent: sourceNode?.parentId,
|
||||
disableLoop,
|
||||
},
|
||||
});
|
||||
}}
|
||||
|
||||
@@ -21,10 +21,15 @@ import type { LoopNode } from "./types";
|
||||
import { useState } from "react";
|
||||
import { useIsFirstBlockInWorkflow } from "../../hooks/useIsFirstNodeInWorkflow";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { getLoopNodeWidth } from "../../workflowEditorUtils";
|
||||
|
||||
function LoopNode({ id, data }: NodeProps<LoopNode>) {
|
||||
const { updateNodeData } = useReactFlow();
|
||||
const nodes = useNodes<AppNode>();
|
||||
const node = nodes.find((n) => n.id === id);
|
||||
if (!node) {
|
||||
throw new Error("Node not found"); // not possible
|
||||
}
|
||||
const [label, setLabel] = useNodeLabelChangeHandler({
|
||||
id,
|
||||
initialValue: data.label,
|
||||
@@ -55,6 +60,7 @@ function LoopNode({ id, data }: NodeProps<LoopNode>) {
|
||||
(furthestDownChild?.position.y ?? 0) +
|
||||
24;
|
||||
|
||||
const loopNodeWidth = getLoopNodeWidth(node, nodes);
|
||||
function handleChange(key: string, value: unknown) {
|
||||
if (!data.editable) {
|
||||
return;
|
||||
@@ -78,8 +84,9 @@ function LoopNode({ id, data }: NodeProps<LoopNode>) {
|
||||
className="opacity-0"
|
||||
/>
|
||||
<div
|
||||
className="w-[600px] rounded-xl border-2 border-dashed border-slate-600 p-2"
|
||||
className="rounded-xl border-2 border-dashed border-slate-600 p-2"
|
||||
style={{
|
||||
width: loopNodeWidth,
|
||||
height: childrenHeightExtent,
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -27,7 +27,6 @@ function NodeAdderNode({ id, parentId }: NodeProps<NodeAdderNode>) {
|
||||
className="rounded-full bg-slate-50 p-2"
|
||||
onClick={() => {
|
||||
const previous = edges.find((edge) => edge.target === id)?.source;
|
||||
const disableLoop = Boolean(parentId);
|
||||
setWorkflowPanelState({
|
||||
active: true,
|
||||
content: "nodeLibrary",
|
||||
@@ -36,7 +35,6 @@ function NodeAdderNode({ id, parentId }: NodeProps<NodeAdderNode>) {
|
||||
next: id,
|
||||
parent: parentId,
|
||||
connectingEdgeType: "default",
|
||||
disableLoop,
|
||||
},
|
||||
});
|
||||
}}
|
||||
|
||||
@@ -132,13 +132,36 @@ function layoutUtil(
|
||||
};
|
||||
}
|
||||
|
||||
export function descendants(nodes: Array<AppNode>, id: string): Array<AppNode> {
|
||||
const children = nodes.filter((n) => n.parentId === id);
|
||||
return children.concat(...children.map((c) => descendants(nodes, c.id)));
|
||||
}
|
||||
|
||||
export function getLoopNodeWidth(node: AppNode, nodes: Array<AppNode>): number {
|
||||
const maxNesting = maxNestingLevel(nodes);
|
||||
const nestingLevel = getNestingLevel(node, nodes);
|
||||
return 600 + (maxNesting - nestingLevel) * 50;
|
||||
}
|
||||
|
||||
function maxNestingLevel(nodes: Array<AppNode>): number {
|
||||
return Math.max(...nodes.map((node) => getNestingLevel(node, nodes)));
|
||||
}
|
||||
|
||||
function getNestingLevel(node: AppNode, nodes: Array<AppNode>): number {
|
||||
let level = 0;
|
||||
let current = nodes.find((n) => n.id === node.parentId);
|
||||
while (current) {
|
||||
level++;
|
||||
current = nodes.find((n) => n.id === current?.parentId);
|
||||
}
|
||||
return level;
|
||||
}
|
||||
|
||||
function layout(
|
||||
nodes: Array<AppNode>,
|
||||
edges: Array<Edge>,
|
||||
): { nodes: Array<AppNode>; edges: Array<Edge> } {
|
||||
const loopNodes = nodes.filter(
|
||||
(node) => node.type === "loop" && !node.parentId,
|
||||
);
|
||||
const loopNodes = nodes.filter((node) => node.type === "loop");
|
||||
const loopNodeChildren: Array<Array<AppNode>> = loopNodes.map(() => []);
|
||||
|
||||
loopNodes.forEach((node, index) => {
|
||||
@@ -151,7 +174,7 @@ function layout(
|
||||
const maxChildWidth = Math.max(
|
||||
...childNodes.map((node) => node.measured?.width ?? 0),
|
||||
);
|
||||
const loopNodeWidth = 600; // 600 px
|
||||
const loopNodeWidth = getLoopNodeWidth(node, nodes);
|
||||
const layouted = layoutUtil(childNodes, childEdges, {
|
||||
marginx: (loopNodeWidth - maxChildWidth) / 2,
|
||||
marginy: 225,
|
||||
@@ -1169,7 +1192,23 @@ function getOrderedChildrenBlocks(
|
||||
const children: Array<BlockYAML> = [];
|
||||
let currentNode: WorkflowBlockNode | undefined = firstChild;
|
||||
while (currentNode) {
|
||||
children.push(getWorkflowBlock(currentNode));
|
||||
if (currentNode.type === "loop") {
|
||||
const loopChildren = getOrderedChildrenBlocks(
|
||||
nodes,
|
||||
edges,
|
||||
currentNode.id,
|
||||
);
|
||||
children.push({
|
||||
block_type: "for_loop",
|
||||
label: currentNode.data.label,
|
||||
continue_on_failure: currentNode.data.continueOnFailure,
|
||||
loop_blocks: loopChildren,
|
||||
loop_variable_reference: currentNode.data.loopVariableReference,
|
||||
complete_if_empty: currentNode.data.completeIfEmpty,
|
||||
});
|
||||
} else {
|
||||
children.push(getWorkflowBlock(currentNode));
|
||||
}
|
||||
const nextId = edges.find(
|
||||
(edge) => edge.source === currentNode?.id,
|
||||
)?.target;
|
||||
@@ -1991,12 +2030,14 @@ export {
|
||||
createNode,
|
||||
generateNodeData,
|
||||
generateNodeLabel,
|
||||
getNestingLevel,
|
||||
getAdditionalParametersForEmailBlock,
|
||||
getAvailableOutputParameterKeys,
|
||||
getBlockNameOfOutputParameterKey,
|
||||
getDefaultValueForParameterType,
|
||||
getElements,
|
||||
getLabelForWorkflowParameterType,
|
||||
maxNestingLevel,
|
||||
getWorkflowSettings,
|
||||
getOutputParameterKey,
|
||||
getPreviousNodeIds,
|
||||
|
||||
Reference in New Issue
Block a user