Skip to content

Commit a549b0d

Browse files
committed
feat: 기존 캔버스 뷰에 소켓 기능 추가
1 parent 5816b42 commit a549b0d

File tree

1 file changed

+115
-21
lines changed

1 file changed

+115
-21
lines changed

frontend/src/components/canvas/index.tsx

Lines changed: 115 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import { cn } from "@/lib/utils";
2-
3-
import { useCallback, useMemo } from "react";
1+
import { useCallback, useMemo, useRef } from "react";
42
import {
53
ReactFlow,
64
MiniMap,
@@ -11,15 +9,21 @@ import {
119
addEdge,
1210
BackgroundVariant,
1311
ConnectionMode,
14-
type OnConnect,
1512
type Node,
13+
NodeChange,
14+
Edge,
15+
EdgeChange,
16+
Connection,
1617
} from "@xyflow/react";
1718

1819
import "@xyflow/react/dist/style.css";
1920

2021
import { useEffect } from "react";
2122
import { usePages } from "@/hooks/usePages";
2223
import { NoteNode } from "./NoteNode";
24+
import * as Y from "yjs";
25+
import { WebsocketProvider } from "y-websocket";
26+
import { cn } from "@/lib/utils";
2327

2428
const proOptions = { hideAttribution: true };
2529

@@ -29,38 +33,128 @@ interface CanvasProps {
2933

3034
export default function Canvas({ className }: CanvasProps) {
3135
const [nodes, setNodes, onNodesChange] = useNodesState<Node>([]);
32-
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
36+
const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>([]);
3337

3438
const { pages } = usePages();
3539

36-
useEffect(() => {
37-
if (!pages) {
38-
return;
39-
}
40+
const onConnect = useCallback(
41+
(connection: Connection) => {
42+
if (!connection.source || !connection.target) return;
4043

41-
const newNodes = pages.map((page, index) => ({
42-
id: page.id.toString(),
43-
position: { x: 100 * index, y: 100 },
44-
data: { title: page.title, id: page.id },
45-
type: "note",
46-
}));
47-
setNodes(newNodes);
48-
}, [pages, setNodes]);
44+
const newEdge: Edge = {
45+
id: `e${connection.source}-${connection.target}`,
46+
source: connection.source,
47+
target: connection.target,
48+
sourceHandle: connection.sourceHandle || undefined,
49+
targetHandle: connection.targetHandle || undefined,
50+
};
51+
52+
if (ydoc.current) {
53+
ydoc.current.getMap("edges").set(newEdge.id, newEdge);
54+
}
4955

50-
const onConnect: OnConnect = useCallback(
51-
(params) => setEdges((eds) => addEdge(params, eds)),
56+
setEdges((eds) => addEdge(connection, eds));
57+
},
5258
[setEdges],
5359
);
5460

61+
const handleEdgesChange = useCallback(
62+
(changes: EdgeChange[]) => {
63+
onEdgesChange(changes);
64+
65+
if (!ydoc.current) return;
66+
67+
changes.forEach((change) => {
68+
if (change.type === "remove") {
69+
ydoc.current?.getMap("edges").delete(change.id);
70+
}
71+
});
72+
},
73+
[onEdgesChange],
74+
);
75+
5576
const nodeTypes = useMemo(() => ({ note: NoteNode }), []);
5677

78+
const ydoc = useRef<Y.Doc>();
79+
const provider = useRef<WebsocketProvider>();
80+
81+
useEffect(() => {
82+
const doc = new Y.Doc();
83+
const wsProvider = new WebsocketProvider(
84+
"ws://localhost:1234",
85+
"flow-room",
86+
doc,
87+
);
88+
const nodesMap = doc.getMap("nodes");
89+
const edgesMap = doc.getMap("edges");
90+
91+
ydoc.current = doc;
92+
provider.current = wsProvider;
93+
94+
if (nodesMap.size === 0) {
95+
nodes.forEach((node) => {
96+
nodesMap.set(node.id, JSON.parse(JSON.stringify(node)));
97+
});
98+
}
99+
100+
nodesMap.observe(() => {
101+
const yNodes = Array.from(nodesMap.values()) as Node[];
102+
setNodes(yNodes);
103+
});
104+
105+
edgesMap.observe(() => {
106+
const yEdges = Array.from(edgesMap.values()) as Edge[];
107+
setEdges(yEdges);
108+
});
109+
110+
return () => {
111+
wsProvider.destroy();
112+
doc.destroy();
113+
};
114+
}, []);
115+
116+
useEffect(() => {
117+
if (pages) {
118+
const newNodes = pages.map((page, index) => ({
119+
id: page.id.toString(),
120+
position: { x: 100 * index, y: 100 },
121+
data: { title: page.title, id: page.id },
122+
type: "note",
123+
}));
124+
setNodes(newNodes);
125+
}
126+
}, [pages, setNodes]);
127+
128+
const handleNodesChange = useCallback(
129+
(changes: NodeChange[]) => {
130+
onNodesChange(changes);
131+
if (!ydoc.current) {
132+
return;
133+
}
134+
135+
changes.forEach((change) => {
136+
if (change.type === "position") {
137+
const node = nodes.find((n) => n.id === change.id);
138+
if (node) {
139+
const updatedNode = {
140+
...node,
141+
position: change.position || node.position,
142+
};
143+
ydoc.current?.getMap("nodes").set(change.id, updatedNode);
144+
}
145+
}
146+
});
147+
},
148+
[nodes, onNodesChange],
149+
);
150+
57151
return (
58152
<div className={cn("", className)}>
59153
<ReactFlow
60154
nodes={nodes}
61155
edges={edges}
62-
onNodesChange={onNodesChange}
63-
onEdgesChange={onEdgesChange}
156+
onNodesChange={handleNodesChange}
157+
onEdgesChange={handleEdgesChange}
64158
onConnect={onConnect}
65159
proOptions={proOptions}
66160
nodeTypes={nodeTypes}

0 commit comments

Comments
 (0)