fix: Center "Add Step" nodes below parent nodes and update positions on drag
This commit is contained in:
@@ -128,7 +128,29 @@ function getLayoutedElements(nodes: Node[], edges: Edge[]) {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return {nodes: layoutedNodes, edges};
|
// Center "Add Step" nodes directly below their parent nodes
|
||||||
|
const adjustedNodes = layoutedNodes.map(node => {
|
||||||
|
if (node.type === 'addStep') {
|
||||||
|
// Find the parent node (the source of the edge connecting to this add node)
|
||||||
|
const parentEdge = edges.find(edge => edge.target === node.id);
|
||||||
|
if (parentEdge) {
|
||||||
|
const parentNode = layoutedNodes.find(n => n.id === parentEdge.source);
|
||||||
|
if (parentNode) {
|
||||||
|
// Center the add node below the parent node
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
position: {
|
||||||
|
x: parentNode.position.x + nodeWidth / 2 - 32, // 32 is half of the 64px (w-16) width of the add button
|
||||||
|
y: node.position.y,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {nodes: adjustedNodes, edges};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add Step Node - appears at the end of flow paths
|
// Add Step Node - appears at the end of flow paths
|
||||||
@@ -412,6 +434,7 @@ export function WorkflowBuilder({workflowId, steps, onUpdate}: WorkflowBuilderPr
|
|||||||
id: `${step.id}-add-yes`,
|
id: `${step.id}-add-yes`,
|
||||||
type: 'addStep',
|
type: 'addStep',
|
||||||
position: {x: 0, y: 0},
|
position: {x: 0, y: 0},
|
||||||
|
draggable: false,
|
||||||
data: {
|
data: {
|
||||||
label: 'Yes',
|
label: 'Yes',
|
||||||
onClick: () => setAddStepContext({fromStepId: step.id, branch: 'yes'}),
|
onClick: () => setAddStepContext({fromStepId: step.id, branch: 'yes'}),
|
||||||
@@ -424,6 +447,7 @@ export function WorkflowBuilder({workflowId, steps, onUpdate}: WorkflowBuilderPr
|
|||||||
id: `${step.id}-add-no`,
|
id: `${step.id}-add-no`,
|
||||||
type: 'addStep',
|
type: 'addStep',
|
||||||
position: {x: 0, y: 0},
|
position: {x: 0, y: 0},
|
||||||
|
draggable: false,
|
||||||
data: {
|
data: {
|
||||||
label: 'No',
|
label: 'No',
|
||||||
onClick: () => setAddStepContext({fromStepId: step.id, branch: 'no'}),
|
onClick: () => setAddStepContext({fromStepId: step.id, branch: 'no'}),
|
||||||
@@ -437,6 +461,7 @@ export function WorkflowBuilder({workflowId, steps, onUpdate}: WorkflowBuilderPr
|
|||||||
id: `${step.id}-add`,
|
id: `${step.id}-add`,
|
||||||
type: 'addStep',
|
type: 'addStep',
|
||||||
position: {x: 0, y: 0},
|
position: {x: 0, y: 0},
|
||||||
|
draggable: false,
|
||||||
data: {
|
data: {
|
||||||
label: '',
|
label: '',
|
||||||
onClick: () => setAddStepContext({fromStepId: step.id}),
|
onClick: () => setAddStepContext({fromStepId: step.id}),
|
||||||
@@ -510,7 +535,7 @@ export function WorkflowBuilder({workflowId, steps, onUpdate}: WorkflowBuilderPr
|
|||||||
id: `${step.id}-add-yes-edge`,
|
id: `${step.id}-add-yes-edge`,
|
||||||
source: step.id,
|
source: step.id,
|
||||||
target: `${step.id}-add-yes`,
|
target: `${step.id}-add-yes`,
|
||||||
type: 'smoothstep',
|
type: 'straight',
|
||||||
animated: false,
|
animated: false,
|
||||||
label: 'Yes',
|
label: 'Yes',
|
||||||
labelStyle: {fill: '#16a34a', fontWeight: 600, fontSize: 12},
|
labelStyle: {fill: '#16a34a', fontWeight: 600, fontSize: 12},
|
||||||
@@ -527,7 +552,7 @@ export function WorkflowBuilder({workflowId, steps, onUpdate}: WorkflowBuilderPr
|
|||||||
id: `${step.id}-add-no-edge`,
|
id: `${step.id}-add-no-edge`,
|
||||||
source: step.id,
|
source: step.id,
|
||||||
target: `${step.id}-add-no`,
|
target: `${step.id}-add-no`,
|
||||||
type: 'smoothstep',
|
type: 'straight',
|
||||||
animated: false,
|
animated: false,
|
||||||
label: 'No',
|
label: 'No',
|
||||||
labelStyle: {fill: '#dc2626', fontWeight: 600, fontSize: 12},
|
labelStyle: {fill: '#dc2626', fontWeight: 600, fontSize: 12},
|
||||||
@@ -544,7 +569,7 @@ export function WorkflowBuilder({workflowId, steps, onUpdate}: WorkflowBuilderPr
|
|||||||
id: `${step.id}-add-edge`,
|
id: `${step.id}-add-edge`,
|
||||||
source: step.id,
|
source: step.id,
|
||||||
target: `${step.id}-add`,
|
target: `${step.id}-add`,
|
||||||
type: 'smoothstep',
|
type: 'straight',
|
||||||
animated: false,
|
animated: false,
|
||||||
style: {stroke: '#94a3b8', strokeWidth: 2.5, strokeDasharray: '5,5'},
|
style: {stroke: '#94a3b8', strokeWidth: 2.5, strokeDasharray: '5,5'},
|
||||||
markerEnd: {type: MarkerType.ArrowClosed, color: '#94a3b8', width: 22, height: 22},
|
markerEnd: {type: MarkerType.ArrowClosed, color: '#94a3b8', width: 22, height: 22},
|
||||||
@@ -565,6 +590,56 @@ export function WorkflowBuilder({workflowId, steps, onUpdate}: WorkflowBuilderPr
|
|||||||
const [nodes, setNodes, onNodesChange] = useNodesState(layoutedNodes);
|
const [nodes, setNodes, onNodesChange] = useNodesState(layoutedNodes);
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState(layoutedEdges);
|
const [edges, setEdges, onEdgesChange] = useEdgesState(layoutedEdges);
|
||||||
|
|
||||||
|
// Custom nodes change handler that updates "+" node positions when parent nodes move
|
||||||
|
const handleNodesChange = useCallback(
|
||||||
|
(changes: any[]) => {
|
||||||
|
onNodesChange(changes);
|
||||||
|
|
||||||
|
// Check if any position changes occurred
|
||||||
|
const positionChanges = changes.filter(change => change.type === 'position' && change.dragging);
|
||||||
|
if (positionChanges.length > 0) {
|
||||||
|
setNodes(currentNodes => {
|
||||||
|
const updatedNodes = [...currentNodes];
|
||||||
|
const nodeWidth = 280;
|
||||||
|
|
||||||
|
positionChanges.forEach((change: any) => {
|
||||||
|
const movedNode = updatedNodes.find(n => n.id === change.id);
|
||||||
|
if (movedNode && movedNode.type === 'custom' && change.position) {
|
||||||
|
// Find all "+" nodes connected to this parent node
|
||||||
|
const connectedAddNodes = updatedNodes.filter(node => {
|
||||||
|
if (node.type !== 'addStep') return false;
|
||||||
|
// Check if this add node is connected to the moved node
|
||||||
|
return (
|
||||||
|
node.id === `${movedNode.id}-add` ||
|
||||||
|
node.id === `${movedNode.id}-add-yes` ||
|
||||||
|
node.id === `${movedNode.id}-add-no`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update positions of connected "+" nodes to stay centered below parent
|
||||||
|
connectedAddNodes.forEach(addNode => {
|
||||||
|
const addNodeIndex = updatedNodes.findIndex(n => n.id === addNode.id);
|
||||||
|
if (addNodeIndex !== -1 && updatedNodes[addNodeIndex]) {
|
||||||
|
const existingNode = updatedNodes[addNodeIndex];
|
||||||
|
updatedNodes[addNodeIndex] = {
|
||||||
|
...existingNode,
|
||||||
|
position: {
|
||||||
|
x: change.position.x + nodeWidth / 2 - 32, // Center below parent
|
||||||
|
y: existingNode.position.y,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return updatedNodes;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onNodesChange, setNodes],
|
||||||
|
);
|
||||||
|
|
||||||
// Update nodes/edges when layout changes
|
// Update nodes/edges when layout changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setNodes(layoutedNodes);
|
setNodes(layoutedNodes);
|
||||||
@@ -727,7 +802,7 @@ export function WorkflowBuilder({workflowId, steps, onUpdate}: WorkflowBuilderPr
|
|||||||
<ReactFlow
|
<ReactFlow
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
edges={edges}
|
edges={edges}
|
||||||
onNodesChange={onNodesChange}
|
onNodesChange={handleNodesChange}
|
||||||
onEdgesChange={onEdgesChange}
|
onEdgesChange={onEdgesChange}
|
||||||
nodeTypes={nodeTypes}
|
nodeTypes={nodeTypes}
|
||||||
fitView
|
fitView
|
||||||
|
|||||||
Reference in New Issue
Block a user