/* ┌──────────────────────────────────────────────────────────────────────────────┐ │ @author: Davidson Gomes │ │ @file: /app/agents/workflows/utils.ts │ │ Developed by: Davidson Gomes │ │ Creation date: May 13, 2025 │ │ Contact: contato@evolution-api.com │ ├──────────────────────────────────────────────────────────────────────────────┤ │ @copyright © Evolution API 2025. All rights reserved. │ │ Licensed under the Apache License, Version 2.0 │ │ │ │ You may not use this file except in compliance with the License. │ │ You may obtain a copy of the License at │ │ │ │ http://www.apache.org/licenses/LICENSE-2.0 │ │ │ │ Unless required by applicable law or agreed to in writing, software │ │ distributed under the License is distributed on an "AS IS" BASIS, │ │ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │ │ See the License for the specific language governing permissions and │ │ limitations under the License. │ ├──────────────────────────────────────────────────────────────────────────────┤ │ @important │ │ For any future changes to the code in this file, it is recommended to │ │ include, together with the modification, the information of the developer │ │ who changed it and the date of modification. │ └──────────────────────────────────────────────────────────────────────────────┘ */ import { Node, NodePositionChange, XYPosition } from "@xyflow/react"; type GetHelperLinesResult = { horizontal?: number; vertical?: number; snapPosition: Partial; }; // this utility function can be called with a position change (inside onNodesChange) // it checks all other nodes and calculated the helper line positions and the position where the current node should snap to export function getHelperLines( change: NodePositionChange, nodes: Node[], distance = 5, ): GetHelperLinesResult { const defaultResult = { horizontal: undefined, vertical: undefined, snapPosition: { x: undefined, y: undefined }, }; const nodeA = nodes.find((node) => node.id === change.id); if (!nodeA || !change.position) { return defaultResult; } const nodeABounds = { left: change.position.x, right: change.position.x + (nodeA.measured?.width ?? 0), top: change.position.y, bottom: change.position.y + (nodeA.measured?.height ?? 0), width: nodeA.measured?.width ?? 0, height: nodeA.measured?.height ?? 0, }; let horizontalDistance = distance; let verticalDistance = distance; return nodes .filter((node) => node.id !== nodeA.id) .reduce((result, nodeB) => { const nodeBBounds = { left: nodeB.position.x, right: nodeB.position.x + (nodeB.measured?.width ?? 0), top: nodeB.position.y, bottom: nodeB.position.y + (nodeB.measured?.height ?? 0), width: nodeB.measured?.width ?? 0, height: nodeB.measured?.height ?? 0, }; // |‾‾‾‾‾‾‾‾‾‾‾| // | A | // |___________| // | // | // |‾‾‾‾‾‾‾‾‾‾‾| // | B | // |___________| const distanceLeftLeft = Math.abs(nodeABounds.left - nodeBBounds.left); if (distanceLeftLeft < verticalDistance) { result.snapPosition.x = nodeBBounds.left; result.vertical = nodeBBounds.left; verticalDistance = distanceLeftLeft; } // |‾‾‾‾‾‾‾‾‾‾‾| // | A | // |___________| // | // | // |‾‾‾‾‾‾‾‾‾‾‾| // | B | // |___________| const distanceRightRight = Math.abs( nodeABounds.right - nodeBBounds.right, ); if (distanceRightRight < verticalDistance) { result.snapPosition.x = nodeBBounds.right - nodeABounds.width; result.vertical = nodeBBounds.right; verticalDistance = distanceRightRight; } // |‾‾‾‾‾‾‾‾‾‾‾| // | A | // |___________| // | // | // |‾‾‾‾‾‾‾‾‾‾‾| // | B | // |___________| const distanceLeftRight = Math.abs(nodeABounds.left - nodeBBounds.right); if (distanceLeftRight < verticalDistance) { result.snapPosition.x = nodeBBounds.right; result.vertical = nodeBBounds.right; verticalDistance = distanceLeftRight; } // |‾‾‾‾‾‾‾‾‾‾‾| // | A | // |___________| // | // | // |‾‾‾‾‾‾‾‾‾‾‾| // | B | // |___________| const distanceRightLeft = Math.abs(nodeABounds.right - nodeBBounds.left); if (distanceRightLeft < verticalDistance) { result.snapPosition.x = nodeBBounds.left - nodeABounds.width; result.vertical = nodeBBounds.left; verticalDistance = distanceRightLeft; } // |‾‾‾‾‾‾‾‾‾‾‾|‾‾‾‾‾|‾‾‾‾‾‾‾‾‾‾‾| // | A | | B | // |___________| |___________| const distanceTopTop = Math.abs(nodeABounds.top - nodeBBounds.top); if (distanceTopTop < horizontalDistance) { result.snapPosition.y = nodeBBounds.top; result.horizontal = nodeBBounds.top; horizontalDistance = distanceTopTop; } // |‾‾‾‾‾‾‾‾‾‾‾| // | A | // |___________|_________________ // | | // | B | // |___________| const distanceBottomTop = Math.abs(nodeABounds.bottom - nodeBBounds.top); if (distanceBottomTop < horizontalDistance) { result.snapPosition.y = nodeBBounds.top - nodeABounds.height; result.horizontal = nodeBBounds.top; horizontalDistance = distanceBottomTop; } // |‾‾‾‾‾‾‾‾‾‾‾| |‾‾‾‾‾‾‾‾‾‾‾| // | A | | B | // |___________|_____|___________| const distanceBottomBottom = Math.abs( nodeABounds.bottom - nodeBBounds.bottom, ); if (distanceBottomBottom < horizontalDistance) { result.snapPosition.y = nodeBBounds.bottom - nodeABounds.height; result.horizontal = nodeBBounds.bottom; horizontalDistance = distanceBottomBottom; } // |‾‾‾‾‾‾‾‾‾‾‾| // | B | // | | // |‾‾‾‾‾‾‾‾‾‾‾|‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ // | A | // |___________| const distanceTopBottom = Math.abs(nodeABounds.top - nodeBBounds.bottom); if (distanceTopBottom < horizontalDistance) { result.snapPosition.y = nodeBBounds.bottom; result.horizontal = nodeBBounds.bottom; horizontalDistance = distanceTopBottom; } return result; }, defaultResult); }