Skip to content

Commit 987ef55

Browse files
authored
Merge pull request #132 from boostcampwm-2024/feature-fe-#131
기존 캔버스 뷰에 소켓 기능 추가
2 parents e675719 + 80b602c commit 987ef55

File tree

3 files changed

+154
-27
lines changed

3 files changed

+154
-27
lines changed

backend/db.sqlite

36 KB
Binary file not shown.

backend/src/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ async function bootstrap() {
88
const app = await NestFactory.create(AppModule);
99
app.useWebSocketAdapter(new WsAdapter(app));
1010
app.useGlobalFilters(new HttpExceptionFilter());
11-
11+
1212
const config = new DocumentBuilder()
1313
.setTitle('OctoDocs')
1414
.setDescription('OctoDocs API 명세서')

frontend/src/components/canvas/index.tsx

Lines changed: 153 additions & 26 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, useEffect } from "react";
42
import {
53
ReactFlow,
64
MiniMap,
@@ -11,15 +9,19 @@ 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";
17-
1818
import "@xyflow/react/dist/style.css";
19-
20-
import { useEffect } from "react";
2119
import { usePages } from "@/hooks/usePages";
2220
import { NoteNode } from "./NoteNode";
21+
import * as Y from "yjs";
22+
import { WebsocketProvider } from "y-websocket";
23+
import { cn } from "@/lib/utils";
24+
import { useQueryClient } from "@tanstack/react-query";
2325

2426
const proOptions = { hideAttribution: true };
2527

@@ -29,26 +31,151 @@ interface CanvasProps {
2931

3032
export default function Canvas({ className }: CanvasProps) {
3133
const [nodes, setNodes, onNodesChange] = useNodesState<Node>([]);
32-
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
33-
34+
const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>([]);
3435
const { pages } = usePages();
36+
const queryClient = useQueryClient();
37+
38+
const ydoc = useRef<Y.Doc>();
39+
const provider = useRef<WebsocketProvider>();
40+
const existingPageIds = useRef(new Set<string>());
41+
42+
useEffect(() => {
43+
const doc = new Y.Doc();
44+
const wsProvider = new WebsocketProvider(
45+
"ws://localhost:1234",
46+
"flow-room",
47+
doc,
48+
);
49+
50+
ydoc.current = doc;
51+
provider.current = wsProvider;
52+
53+
const nodesMap = doc.getMap("nodes");
54+
const edgesMap = doc.getMap("edges");
55+
56+
nodesMap.observe((event) => {
57+
event.changes.keys.forEach((change, key) => {
58+
const nodeId = key;
59+
if (change.action === "add" || change.action === "update") {
60+
const node = nodesMap.get(nodeId) as Node;
61+
62+
if (change.action === "add") {
63+
queryClient.invalidateQueries({ queryKey: ["pages"] });
64+
}
65+
66+
setNodes((nds) => {
67+
const index = nds.findIndex((n) => n.id === nodeId);
68+
if (index === -1) {
69+
return [...nds, node];
70+
}
71+
const newNodes = [...nds];
72+
newNodes[index] = node;
73+
return newNodes;
74+
});
75+
} else if (change.action === "delete") {
76+
setNodes((nds) => nds.filter((n) => n.id !== nodeId));
77+
queryClient.invalidateQueries({ queryKey: ["pages"] });
78+
}
79+
});
80+
});
81+
82+
edgesMap.observe(() => {
83+
const yEdges = Array.from(edgesMap.values()) as Edge[];
84+
setEdges(yEdges);
85+
});
86+
87+
return () => {
88+
wsProvider.destroy();
89+
doc.destroy();
90+
};
91+
}, [queryClient]);
3592

3693
useEffect(() => {
37-
if (!pages) {
38-
return;
39-
}
40-
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]);
49-
50-
const onConnect: OnConnect = useCallback(
51-
(params) => setEdges((eds) => addEdge(params, eds)),
94+
if (!pages || !ydoc.current) return;
95+
96+
const nodesMap = ydoc.current.getMap("nodes");
97+
const currentPageIds = new Set(pages.map((page) => page.id.toString()));
98+
99+
existingPageIds.current.forEach((pageId) => {
100+
if (!currentPageIds.has(pageId)) {
101+
nodesMap.delete(pageId);
102+
existingPageIds.current.delete(pageId);
103+
}
104+
});
105+
106+
pages.forEach((page) => {
107+
const pageId = page.id.toString();
108+
if (!existingPageIds.current.has(pageId)) {
109+
const newNode = {
110+
id: pageId,
111+
position: {
112+
x: Math.random() * 500,
113+
y: Math.random() * 500,
114+
},
115+
data: { title: page.title, id: page.id },
116+
type: "note",
117+
};
118+
119+
nodesMap.set(pageId, newNode);
120+
existingPageIds.current.add(pageId);
121+
}
122+
});
123+
}, [pages]);
124+
125+
const handleNodesChange = useCallback(
126+
(changes: NodeChange[]) => {
127+
if (!ydoc.current) return;
128+
const nodesMap = ydoc.current.getMap("nodes");
129+
130+
onNodesChange(changes);
131+
132+
changes.forEach((change) => {
133+
if (change.type === "position" && change.position) {
134+
const node = nodes.find((n) => n.id === change.id);
135+
if (node) {
136+
const updatedNode = {
137+
...node,
138+
position: change.position,
139+
};
140+
nodesMap.set(change.id, updatedNode);
141+
}
142+
}
143+
});
144+
},
145+
[nodes, onNodesChange],
146+
);
147+
148+
const handleEdgesChange = useCallback(
149+
(changes: EdgeChange[]) => {
150+
if (!ydoc.current) return;
151+
const edgesMap = ydoc.current.getMap("edges");
152+
153+
changes.forEach((change) => {
154+
if (change.type === "remove") {
155+
edgesMap.delete(change.id);
156+
}
157+
});
158+
159+
onEdgesChange(changes);
160+
},
161+
[onEdgesChange],
162+
);
163+
164+
const onConnect = useCallback(
165+
(connection: Connection) => {
166+
if (!connection.source || !connection.target || !ydoc.current) return;
167+
168+
const newEdge: Edge = {
169+
id: `e${connection.source}-${connection.target}`,
170+
source: connection.source,
171+
target: connection.target,
172+
sourceHandle: connection.sourceHandle || undefined,
173+
targetHandle: connection.targetHandle || undefined,
174+
};
175+
176+
ydoc.current.getMap("edges").set(newEdge.id, newEdge);
177+
setEdges((eds) => addEdge(connection, eds));
178+
},
52179
[setEdges],
53180
);
54181

@@ -59,8 +186,8 @@ export default function Canvas({ className }: CanvasProps) {
59186
<ReactFlow
60187
nodes={nodes}
61188
edges={edges}
62-
onNodesChange={onNodesChange}
63-
onEdgesChange={onEdgesChange}
189+
onNodesChange={handleNodesChange}
190+
onEdgesChange={handleEdgesChange}
64191
onConnect={onConnect}
65192
proOptions={proOptions}
66193
nodeTypes={nodeTypes}

0 commit comments

Comments
 (0)