From a6ce45943731e79d114059c1a1bd1aa51e6d0e6c Mon Sep 17 00:00:00 2001 From: djk01281 Date: Tue, 19 Nov 2024 14:18:02 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EA=B0=80=EC=9E=A5=20=EA=B0=80=EA=B9=8C?= =?UTF-8?q?=EC=9A=B4=20=ED=95=B8=EB=93=A4=EC=97=90=20=EC=97=B0=EA=B2=B0,?= =?UTF-8?q?=20=EB=85=B8=EB=93=9C=20=EA=B0=84=201=EA=B0=9C=EC=9D=98=20?= =?UTF-8?q?=EC=97=A3=EC=A7=80=EB=A7=8C=20=EA=B0=80=EB=8A=A5=ED=95=98?= =?UTF-8?q?=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/frontend/src/components/canvas/index.tsx | 124 ++++++++++++++++-- apps/frontend/src/lib/getHandlePosition.ts | 28 ++++ 2 files changed, 140 insertions(+), 12 deletions(-) create mode 100644 apps/frontend/src/lib/getHandlePosition.ts diff --git a/apps/frontend/src/components/canvas/index.tsx b/apps/frontend/src/components/canvas/index.tsx index ebb47ef7..73ba9040 100644 --- a/apps/frontend/src/components/canvas/index.tsx +++ b/apps/frontend/src/components/canvas/index.tsx @@ -10,6 +10,7 @@ import { BackgroundVariant, ConnectionMode, type Node, + Position, NodeChange, Edge, EdgeChange, @@ -29,6 +30,8 @@ import useYDocStore from "@/store/useYDocStore"; import { useCollaborativeCursors } from "@/hooks/useCursor"; import { CollaborativeCursors } from "../CursorView"; +import { getHandlePosition } from "@/lib/getHandlePosition"; + const proOptions = { hideAttribution: true }; interface CanvasProps { @@ -176,6 +179,7 @@ function Flow({ className }: CanvasProps) { (changes: NodeChange[]) => { if (!ydoc) return; const nodesMap = ydoc.getMap("nodes"); + const edgesMap = ydoc.getMap("edges"); changes.forEach((change) => { if (change.type === "position" && change.position) { @@ -187,13 +191,66 @@ function Flow({ className }: CanvasProps) { selected: false, }; nodesMap.set(change.id, updatedNode); + + edges.forEach((edge) => { + if (edge.source === change.id || edge.target === change.id) { + const sourceNode = nodes.find((n) => n.id === edge.source); + const targetNode = nodes.find((n) => n.id === edge.target); + + if (sourceNode && targetNode) { + const handlePositions = [ + Position.Left, + Position.Right, + Position.Top, + Position.Bottom, + ]; + let shortestDistance = Infinity; + let bestHandles = { + source: edge.sourceHandle, + target: edge.targetHandle, + }; + + handlePositions.forEach((sourceHandle) => { + handlePositions.forEach((targetHandle) => { + const sourcePosition = getHandlePosition( + sourceNode, + sourceHandle, + ); + const targetPosition = getHandlePosition( + targetNode, + targetHandle, + ); + const distance = Math.sqrt( + Math.pow(sourcePosition.x - targetPosition.x, 2) + + Math.pow(sourcePosition.y - targetPosition.y, 2), + ); + + if (distance < shortestDistance) { + shortestDistance = distance; + bestHandles = { + source: sourceHandle, + target: targetHandle, + }; + } + }); + }); + + const updatedEdge = { + ...edge, + sourceHandle: bestHandles.source, + targetHandle: bestHandles.target, + }; + edgesMap.set(edge.id, updatedEdge); + } + } + }); } } }); onNodesChange(changes); }, - [nodes, onNodesChange], + [nodes, edges, onNodesChange], ); const handleEdgesChange = useCallback( @@ -216,18 +273,61 @@ function Flow({ className }: CanvasProps) { (connection: Connection) => { if (!connection.source || !connection.target || !ydoc) return; - const newEdge: Edge = { - id: `e${connection.source}-${connection.target}`, - source: connection.source, - target: connection.target, - sourceHandle: connection.sourceHandle || undefined, - targetHandle: connection.targetHandle || undefined, - }; - - ydoc.getMap("edges").set(newEdge.id, newEdge); - setEdges((eds) => addEdge(connection, eds)); + const isConnected = edges.some( + (edge) => + (edge.source === connection.source && + edge.target === connection.target) || + (edge.source === connection.target && + edge.target === connection.source), + ); + + if (isConnected) return; + + const sourceNode = nodes.find((n) => n.id === connection.source); + const targetNode = nodes.find((n) => n.id === connection.target); + + if (sourceNode && targetNode) { + const handlePositions = [ + Position.Left, + Position.Right, + Position.Top, + Position.Bottom, + ]; + let shortestDistance = Infinity; + let closestHandles = { + source: connection.sourceHandle, + target: connection.targetHandle, + }; + + handlePositions.forEach((sourceHandle) => { + handlePositions.forEach((targetHandle) => { + const sourcePosition = getHandlePosition(sourceNode, sourceHandle); + const targetPosition = getHandlePosition(targetNode, targetHandle); + const distance = Math.sqrt( + Math.pow(sourcePosition.x - targetPosition.x, 2) + + Math.pow(sourcePosition.y - targetPosition.y, 2), + ); + + if (distance < shortestDistance) { + shortestDistance = distance; + closestHandles = { source: sourceHandle, target: targetHandle }; + } + }); + }); + + const newEdge: Edge = { + id: `e${connection.source}-${connection.target}`, + source: connection.source, + target: connection.target, + sourceHandle: closestHandles.source, + targetHandle: closestHandles.target, + }; + + ydoc.getMap("edges").set(newEdge.id, newEdge); + setEdges((eds) => addEdge(newEdge, eds)); + } }, - [setEdges], + [setEdges, edges, nodes, ydoc], ); const nodeTypes = useMemo(() => ({ note: NoteNode }), []); diff --git a/apps/frontend/src/lib/getHandlePosition.ts b/apps/frontend/src/lib/getHandlePosition.ts new file mode 100644 index 00000000..77b92cd9 --- /dev/null +++ b/apps/frontend/src/lib/getHandlePosition.ts @@ -0,0 +1,28 @@ +import { Position, type Node } from "@xyflow/react"; +export function getHandlePosition(node: Node, handleId: Position) { + const nodeElement = document.querySelector(`[data-id="${node.id}"]`); + const nodeRect = nodeElement!.getBoundingClientRect(); + const nodeWidth = nodeRect.width; + const nodeHeight = nodeRect.height; + + const positions = { + [Position.Left]: { + x: node.position.x, + y: node.position.y + nodeHeight / 2, + }, + [Position.Right]: { + x: node.position.x + nodeWidth, + y: node.position.y + nodeHeight / 2, + }, + [Position.Top]: { + x: node.position.x + nodeWidth / 2, + y: node.position.y, + }, + [Position.Bottom]: { + x: node.position.x + nodeWidth / 2, + y: node.position.y + nodeHeight, + }, + }; + + return positions[handleId]; +}