Skip to content

Commit 61299fd

Browse files
committed
feat: child 노드를 움직일 때, group node 계산할 때 최단 거리 엣지 계산
1 parent 843bd59 commit 61299fd

File tree

3 files changed

+154
-108
lines changed

3 files changed

+154
-108
lines changed

apps/frontend/src/features/canvas/model/calculateHandles.ts

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,61 @@
1-
import { Position, Node } from "@xyflow/react";
1+
import { Position, Node, XYPosition } from "@xyflow/react";
22

3-
export const getHandlePosition = (node: Node, handleId: Position) => {
3+
const getAbsolutePosition = (node: Node, nodes: Node[]): XYPosition => {
4+
if (!node.parentId) {
5+
return node.position;
6+
}
7+
8+
const parentNode = nodes.find((n) => n.id === node.parentId);
9+
if (!parentNode) {
10+
return node.position;
11+
}
12+
13+
const parentPosition: XYPosition = getAbsolutePosition(parentNode, nodes);
14+
return {
15+
x: parentPosition.x + node.position.x,
16+
y: parentPosition.y + node.position.y,
17+
};
18+
};
19+
20+
export const getHandlePosition = (
21+
node: Node,
22+
handleId: Position,
23+
nodes: Node[],
24+
) => {
425
const nodeElement = document.querySelector(`[data-id="${node.id}"]`);
526
const nodeRect = nodeElement!.getBoundingClientRect();
627
const nodeWidth = nodeRect.width;
728
const nodeHeight = nodeRect.height;
829

30+
const absolutePosition = getAbsolutePosition(node, nodes);
31+
932
const positions = {
1033
[Position.Left]: {
11-
x: node.position.x,
12-
y: node.position.y + nodeHeight / 2,
34+
x: absolutePosition.x,
35+
y: absolutePosition.y + nodeHeight / 2,
1336
},
1437
[Position.Right]: {
15-
x: node.position.x + nodeWidth,
16-
y: node.position.y + nodeHeight / 2,
38+
x: absolutePosition.x + nodeWidth,
39+
y: absolutePosition.y + nodeHeight / 2,
1740
},
1841
[Position.Top]: {
19-
x: node.position.x + nodeWidth / 2,
20-
y: node.position.y,
42+
x: absolutePosition.x + nodeWidth / 2,
43+
y: absolutePosition.y,
2144
},
2245
[Position.Bottom]: {
23-
x: node.position.x + nodeWidth / 2,
24-
y: node.position.y + nodeHeight,
46+
x: absolutePosition.x + nodeWidth / 2,
47+
y: absolutePosition.y + nodeHeight,
2548
},
2649
};
2750

2851
return positions[handleId];
2952
};
3053

31-
export const calculateBestHandles = (sourceNode: Node, targetNode: Node) => {
54+
export const calculateBestHandles = (
55+
sourceNode: Node,
56+
targetNode: Node,
57+
nodes: Node[],
58+
) => {
3259
const handlePositions = [
3360
Position.Left,
3461
Position.Right,
@@ -42,9 +69,9 @@ export const calculateBestHandles = (sourceNode: Node, targetNode: Node) => {
4269
};
4370

4471
handlePositions.forEach((sourceHandle) => {
45-
const sourcePosition = getHandlePosition(sourceNode, sourceHandle);
72+
const sourcePosition = getHandlePosition(sourceNode, sourceHandle, nodes);
4673
handlePositions.forEach((targetHandle) => {
47-
const targetPosition = getHandlePosition(targetNode, targetHandle);
74+
const targetPosition = getHandlePosition(targetNode, targetHandle, nodes);
4875
const distance = Math.hypot(
4976
sourcePosition.x - targetPosition.x,
5077
sourcePosition.y - targetPosition.y,
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { type Node } from "@xyflow/react";
2+
3+
export const getRelativePosition = (node: Node, parentNode: Node) => ({
4+
x: node.position.x - parentNode.position.x,
5+
y: node.position.y - parentNode.position.y,
6+
});
7+
8+
export const getAbsolutePosition = (node: Node, parentNode: Node) => ({
9+
x: parentNode.position.x + node.position.x,
10+
y: parentNode.position.y + node.position.y,
11+
});

apps/frontend/src/features/canvas/model/useCanvas.ts

Lines changed: 103 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { useCollaborativeCursors } from "./useCollaborativeCursors";
2222
import { getSortedNodes } from "./sortNodes";
2323
import { usePageStore } from "@/features/pageSidebar/model/pageStore";
2424
import { useWorkspace } from "@/shared/lib/useWorkspace";
25+
import { getAbsolutePosition, getRelativePosition } from "./getPosition";
2526

2627
export interface YNode extends Node {
2728
isHolding: boolean;
@@ -271,6 +272,7 @@ export const useCanvas = () => {
271272
const bestHandles = calculateBestHandles(
272273
sourceNode,
273274
targetNode,
275+
nodes,
274276
);
275277
const updatedEdge = {
276278
...edge,
@@ -323,8 +325,7 @@ export const useCanvas = () => {
323325
const targetNode = nodes.find((n) => n.id === connection.target);
324326

325327
if (sourceNode && targetNode) {
326-
const bestHandles = calculateBestHandles(sourceNode, targetNode);
327-
328+
const bestHandles = calculateBestHandles(sourceNode, targetNode, nodes);
328329
const newEdge: Edge = {
329330
id: `e${connection.source}-${connection.target}`,
330331
source: connection.source,
@@ -349,117 +350,124 @@ export const useCanvas = () => {
349350

350351
const onNodeDragStop = useCallback(
351352
(_event: React.MouseEvent, node: Node) => {
352-
if (ydoc) {
353-
const nodesMap = ydoc.getMap("nodes");
354-
const yNode = nodesMap.get(node.id) as YNode | undefined;
353+
if (!ydoc) return;
354+
355+
const nodesMap = ydoc.getMap("nodes");
356+
const yNode = nodesMap.get(node.id) as YNode | undefined;
355357

356-
if (yNode) {
357-
const currentNode = nodes.find((n) => n.id === node.id);
358-
if (!currentNode) return;
358+
if (!yNode) return;
359359

360-
if (node.type === "group") {
361-
const intersectingNotes = getIntersectingNodes(currentNode).filter(
362-
(n) => n.type === "note" && !n.parentId,
363-
);
360+
const currentNode = nodes.find((n) => n.id === node.id);
361+
if (!currentNode) return;
362+
363+
if (node.type === "group") {
364+
const intersectingNotes = getIntersectingNodes(currentNode).filter(
365+
(n) => n.type === "note" && !n.parentId,
366+
);
367+
368+
intersectingNotes.forEach((noteNode) => {
369+
nodesMap.set(noteNode.id, {
370+
...noteNode,
371+
parentId: node.id,
372+
position: getRelativePosition(noteNode, currentNode),
373+
isHolding: false,
374+
});
375+
});
376+
377+
nodesMap.set(node.id, {
378+
...currentNode,
379+
isHolding: false,
380+
});
364381

365-
intersectingNotes.forEach((noteNode) => {
366-
const relativePosition = {
367-
x: noteNode.position.x - currentNode.position.x,
368-
y: noteNode.position.y - currentNode.position.y,
382+
const childNodes = nodes.filter((n) => n.parentId === node.id);
383+
const edgesMap = ydoc.getMap("edges");
384+
385+
childNodes.forEach((childNode) => {
386+
const connectedEdges = edges.filter(
387+
(edge) =>
388+
edge.source === childNode.id || edge.target === childNode.id,
389+
);
390+
391+
connectedEdges.forEach((edge) => {
392+
const sourceNode = nodes.find((n) => n.id === edge.source);
393+
const targetNode = nodes.find((n) => n.id === edge.target);
394+
395+
if (sourceNode && targetNode) {
396+
const bestHandles = calculateBestHandles(
397+
sourceNode,
398+
targetNode,
399+
nodes,
400+
);
401+
const updatedEdge = {
402+
...edge,
403+
sourceHandle: bestHandles.source,
404+
targetHandle: bestHandles.target,
369405
};
406+
edgesMap.set(edge.id, updatedEdge);
407+
}
408+
});
409+
});
410+
} else {
411+
const intersectingGroups = getIntersectingNodes(currentNode).filter(
412+
(n) => n.type === "group",
413+
);
370414

371-
nodesMap.set(noteNode.id, {
372-
...noteNode,
373-
parentId: node.id,
374-
position: relativePosition,
375-
isHolding: false,
376-
});
377-
});
415+
if (intersectingGroups.length > 0) {
416+
const parentNode = intersectingGroups[intersectingGroups.length - 1];
378417

418+
if (yNode.parentId === parentNode.id) {
379419
nodesMap.set(node.id, {
380-
...currentNode,
420+
...yNode,
421+
position: currentNode.position,
381422
isHolding: false,
382423
});
383424
} else {
384-
const intersectingGroups = getIntersectingNodes(currentNode).filter(
385-
(n) => n.type === "group",
386-
);
387-
388-
if (intersectingGroups.length > 0) {
389-
const parentNode =
390-
intersectingGroups[intersectingGroups.length - 1];
391-
392-
if (yNode.parentId === parentNode.id) {
393-
nodesMap.set(node.id, {
394-
...yNode,
395-
position: currentNode.position,
396-
isHolding: false,
397-
});
398-
} else {
399-
let absolutePosition = currentNode.position;
400-
if (yNode.parentId) {
401-
const oldParentNode = nodes.find(
402-
(n) => n.id === yNode.parentId,
403-
);
404-
if (oldParentNode) {
405-
absolutePosition = {
406-
x: oldParentNode.position.x + currentNode.position.x,
407-
y: oldParentNode.position.y + currentNode.position.y,
408-
};
409-
}
410-
}
411-
412-
const relativePosition = {
413-
x: absolutePosition.x - parentNode.position.x,
414-
y: absolutePosition.y - parentNode.position.y,
415-
};
425+
const absolutePosition = yNode.parentId
426+
? getAbsolutePosition(
427+
currentNode,
428+
nodes.find((n) => n.id === yNode.parentId)!,
429+
)
430+
: currentNode.position;
416431

417-
nodesMap.set(node.id, {
418-
...currentNode,
419-
parentId: parentNode.id,
420-
position: relativePosition,
421-
isHolding: false,
422-
});
423-
}
424-
} else {
425-
if (yNode.parentId) {
426-
const oldParentNode = nodes.find(
427-
(n) => n.id === yNode.parentId,
428-
);
429-
if (oldParentNode) {
430-
const absolutePosition = {
431-
x: oldParentNode.position.x + currentNode.position.x,
432-
y: oldParentNode.position.y + currentNode.position.y,
433-
};
434-
435-
nodesMap.set(node.id, {
436-
...currentNode,
437-
parentId: undefined,
438-
position: absolutePosition,
439-
isHolding: false,
440-
});
441-
}
442-
} else {
443-
nodesMap.set(node.id, {
444-
...currentNode,
445-
parentId: undefined,
446-
isHolding: false,
447-
});
448-
}
432+
nodesMap.set(node.id, {
433+
...currentNode,
434+
parentId: parentNode.id,
435+
position: getRelativePosition(
436+
{ ...currentNode, position: absolutePosition },
437+
parentNode,
438+
),
439+
isHolding: false,
440+
});
441+
}
442+
} else {
443+
if (yNode.parentId) {
444+
const oldParentNode = nodes.find((n) => n.id === yNode.parentId);
445+
if (oldParentNode) {
446+
nodesMap.set(node.id, {
447+
...currentNode,
448+
parentId: undefined,
449+
position: getAbsolutePosition(currentNode, oldParentNode),
450+
isHolding: false,
451+
});
449452
}
453+
} else {
454+
nodesMap.set(node.id, {
455+
...currentNode,
456+
parentId: undefined,
457+
isHolding: false,
458+
});
450459
}
451-
452-
setNodes((ns) => {
453-
const groups = ns.filter((n) => n.type === "group");
454-
const notes = ns.filter((n) => n.type !== "group");
455-
return [...groups, ...notes];
456-
});
457460
}
458461
}
462+
463+
setNodes((ns) => {
464+
const groups = ns.filter((n) => n.type === "group");
465+
const notes = ns.filter((n) => n.type !== "group");
466+
return [...groups, ...notes];
467+
});
459468
},
460469
[ydoc, getIntersectingNodes, nodes, setNodes],
461470
);
462-
463471
return {
464472
handleMouseMove,
465473
nodes,

0 commit comments

Comments
 (0)