Skip to content

Commit 1baec8b

Browse files
authored
Merge branch 'develop' into feature-be-#309
2 parents 6033a74 + c366e05 commit 1baec8b

File tree

8 files changed

+121
-145
lines changed

8 files changed

+121
-145
lines changed

apps/backend/src/app.module.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import { RedLockModule } from './red-lock/red-lock.module';
3838
imports: [ConfigModule],
3939
inject: [ConfigService],
4040
useFactory: (configService: ConfigService) => ({
41+
4142
// type: 'sqlite',
4243
// database: 'db.sqlite',
4344
type: 'postgres',

apps/backend/src/yjs/yjs.service.ts

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ export class YjsService
6767
const editorDoc = doc.getXmlFragment('default');
6868
const customDoc = editorDoc.doc as CustomDoc;
6969

70+
if (customDoc.name === 'users') {
71+
return;
72+
}
73+
7074
// document name이 flow-room이라면 모든 노드들을 볼 수 있는 화면입니다.
7175
// 노드를 클릭해 페이지를 열었을 때만 해당 페이지 값을 가져와서 초기 데이터로 세팅해줍니다.
7276
if (customDoc.name?.startsWith('document-')) {
@@ -109,18 +113,19 @@ export class YjsService
109113
editorDoc.observeDeep(() => {
110114
const document = editorDoc.doc as CustomDoc;
111115
const pageId = parseInt(document.name.split('-')[1]);
112-
// this.pageService.updatePage(
113-
// pageId,
114-
// JSON.parse(
115-
// JSON.stringify(yXmlFragmentToProsemirrorJSON(editorDoc)),
116-
// ),
117-
// );
118-
116+
119117
this.redisService.setField(
120118
`page:${pageId.toString()}`,
121119
'content',
122120
JSON.stringify(yXmlFragmentToProsemirrorJSON(editorDoc)),
121+
123122
);
123+
124+
// this.redisService.setField(
125+
// pageId.toString(),
126+
// 'content',
127+
// JSON.stringify(yXmlFragmentToProsemirrorJSON(editorDoc)),
128+
// );
124129
// this.redisService.get(pageId.toString()).then((data) => {
125130
// console.log(data);
126131
// });
@@ -155,17 +160,17 @@ export class YjsService
155160
// path가 존재할 때만 페이지 갱신
156161

157162
event[0].path.toString().split('_')[1] &&
158-
// this.pageService.updatePage(
159-
// parseInt(event[0].path.toString().split('_')[1]),
160-
// {
161-
// title: event[0].target.toString(),
162-
// },
163-
// );
164163
this.redisService.setField(
165164
`page:${event[0].path.toString().split('_')[1]}`,
166165
'title',
167166
event[0].target.toString(),
167+
168168
);
169+
// this.redisService.setField(
170+
// event[0].path.toString().split('_')[1],
171+
// 'title',
172+
// event[0].target.toString(),
173+
// );
169174
});
170175
emoji.observeDeep((event) => {
171176
// path가 존재할 때만 페이지 갱신

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

Lines changed: 26 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,11 @@ import {
1212
} from "@xyflow/react";
1313
import "@xyflow/react/dist/style.css";
1414
import { SocketIOProvider } from "y-socket.io";
15-
import { useQueryClient } from "@tanstack/react-query";
1615

17-
import { usePages } from "@/features/pageSidebar/api/usePages";
1816
import useYDocStore from "@/shared/model/ydocStore";
1917
import { calculateBestHandles } from "@/features/canvas/model/calculateHandles";
2018
import { createSocketIOProvider } from "@/shared/api/socketProvider";
2119
import { useCollaborativeCursors } from "./useCollaborativeCursors";
22-
import { getSortedNodes } from "./sortNodes";
2320
import { usePageStore } from "@/features/pageSidebar/model/pageStore";
2421
import { useWorkspace } from "@/shared/lib/useWorkspace";
2522

@@ -31,8 +28,6 @@ export const useCanvas = () => {
3128
const [nodes, setNodes, onNodesChange] = useNodesState<Node>([]);
3229
const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>([]);
3330
const workspace = useWorkspace();
34-
const { pages } = usePages(workspace);
35-
const queryClient = useQueryClient();
3631
const { ydoc } = useYDocStore();
3732

3833
const { cursors, handleMouseMove, handleNodeDrag, handleMouseLeave } =
@@ -41,7 +36,6 @@ export const useCanvas = () => {
4136
});
4237

4338
const provider = useRef<SocketIOProvider>();
44-
const existingPageIds = useRef(new Set<string>());
4539
const holdingNodeRef = useRef<string | null>(null);
4640

4741
const currentPage = usePageStore((state) => state.currentPage);
@@ -68,7 +62,6 @@ export const useCanvas = () => {
6862
useEffect(() => {
6963
const yTitleMap = ydoc.getMap("title");
7064
const yEmojiMap = ydoc.getMap("emoji");
71-
7265
const nodesMap = ydoc.getMap("nodes");
7366

7467
yTitleMap.observeDeep((event) => {
@@ -78,6 +71,7 @@ export const useCanvas = () => {
7871
const value = event[0].target.toString();
7972

8073
const existingNode = nodesMap.get(pageId) as YNode;
74+
if (!existingNode) return;
8175

8276
const newNode: YNode = {
8377
id: existingNode.id,
@@ -98,6 +92,7 @@ export const useCanvas = () => {
9892
const value = event[0].target.toString();
9993

10094
const existingNode = nodesMap.get(pageId) as YNode;
95+
if (!existingNode) return;
10196

10297
const newNode: YNode = {
10398
id: pageId,
@@ -110,67 +105,50 @@ export const useCanvas = () => {
110105

111106
nodesMap.set(pageId, newNode);
112107
});
113-
}, []);
114-
108+
}, [ydoc]);
115109
useEffect(() => {
116110
if (!ydoc) return;
117111

118112
const wsProvider = createSocketIOProvider(`flow-room-${workspace}`, ydoc);
119-
120113
provider.current = wsProvider;
121114

122-
const nodesMap = ydoc.getMap("nodes");
123-
const edgesMap = ydoc.getMap("edges");
124-
125-
const yNodes = Array.from(nodesMap.values()) as YNode[];
126-
127-
const initialNodes = yNodes.map((yNode) => {
128-
const nodeEntries = Object.entries(yNode).filter(
129-
([key]) => key !== "isHolding",
130-
);
131-
return Object.fromEntries(nodeEntries) as Node;
115+
wsProvider.on("sync", (isSynced: boolean) => {
116+
if (isSynced) {
117+
const nodesMap = ydoc.getMap("nodes");
118+
const yNodes = Array.from(nodesMap.values()) as YNode[];
119+
setNodes(yNodes);
120+
}
132121
});
133122

134-
console.log(initialNodes);
135-
136-
setNodes(initialNodes);
137-
138-
let isInitialSync = true;
123+
const nodesMap = ydoc.getMap("nodes");
124+
const edgesMap = ydoc.getMap("edges");
125+
const yEdges = Array.from(edgesMap.values()) as Edge[];
126+
setEdges(yEdges);
139127

140128
nodesMap.observe((event) => {
141-
if (isInitialSync) {
142-
isInitialSync = false;
143-
return;
144-
}
145-
146129
event.changes.keys.forEach((change, key) => {
147130
const nodeId = key;
148131
if (change.action === "add" || change.action === "update") {
149-
const updatedYNode = nodesMap.get(nodeId) as YNode;
150-
const updatedNodeEntries = Object.entries(updatedYNode).filter(
151-
([key]) => key !== "isHolding",
152-
);
153-
const updatedNode = Object.fromEntries(updatedNodeEntries) as Node;
154-
155-
if (change.action === "add") {
156-
queryClient.invalidateQueries({ queryKey: ["pages"] });
157-
}
158-
132+
const updatedNode = nodesMap.get(nodeId) as Node;
159133
setNodes((nds) => {
160134
const index = nds.findIndex((n) => n.id === nodeId);
161135
if (index === -1) {
162136
return [...nds, updatedNode];
163137
}
164138
const newNodes = [...nds];
165-
newNodes[index] = {
166-
...updatedNode,
167-
selected: newNodes[index].selected,
168-
};
139+
newNodes[index] = updatedNode;
169140
return newNodes;
170141
});
171142
} else if (change.action === "delete") {
143+
// parseInt는 yjs.service.ts에서 타입 변환 로직 참고.
144+
const deletedNodeId = parseInt(nodeId);
145+
const currentPageValue = usePageStore.getState().currentPage;
146+
147+
if (currentPageValue === deletedNodeId) {
148+
usePageStore.setState({ currentPage: null, isPanelOpen: false });
149+
}
150+
172151
setNodes((nds) => nds.filter((n) => n.id !== nodeId));
173-
queryClient.invalidateQueries({ queryKey: ["pages"] });
174152
}
175153
});
176154
});
@@ -183,52 +161,7 @@ export const useCanvas = () => {
183161
return () => {
184162
wsProvider.destroy();
185163
};
186-
}, [ydoc, queryClient]);
187-
188-
useEffect(() => {
189-
if (!pages || !ydoc) return;
190-
191-
const nodesMap = ydoc.getMap("nodes");
192-
const currentPageIds = new Set(pages.map((page) => page.id.toString()));
193-
194-
existingPageIds.current.forEach((pageId) => {
195-
if (!currentPageIds.has(pageId)) {
196-
nodesMap.delete(pageId);
197-
existingPageIds.current.delete(pageId);
198-
}
199-
});
200-
201-
pages.forEach((page) => {
202-
const pageId = page.id.toString();
203-
const existingNode = nodesMap.get(pageId) as YNode | undefined;
204-
205-
const newNode: YNode = {
206-
id: pageId,
207-
type: "note",
208-
data: { title: page.title, id: page.id, emoji: page.emoji },
209-
position: existingNode?.position || {
210-
x: Math.random() * 500,
211-
y: Math.random() * 500,
212-
},
213-
selected: false,
214-
isHolding: false,
215-
};
216-
217-
nodesMap.set(pageId, newNode);
218-
existingPageIds.current.add(pageId);
219-
});
220-
}, [pages, ydoc]);
221-
222-
const sortNodes = async () => {
223-
const sortedNodes = await getSortedNodes(nodes, edges);
224-
const nodesMap = ydoc.getMap("nodes");
225-
226-
sortedNodes.forEach((updateNode) => {
227-
nodesMap.set(updateNode.id, updateNode);
228-
});
229-
230-
setNodes(sortedNodes);
231-
};
164+
}, [ydoc, setNodes, setEdges, workspace]);
232165

233166
const handleNodesChange = useCallback(
234167
(changes: NodeChange[]) => {
@@ -275,7 +208,7 @@ export const useCanvas = () => {
275208

276209
onNodesChange(changes);
277210
},
278-
[nodes, edges, onNodesChange],
211+
[nodes, edges, onNodesChange, ydoc],
279212
);
280213

281214
const handleEdgesChange = useCallback(
@@ -291,7 +224,7 @@ export const useCanvas = () => {
291224

292225
onEdgesChange(changes);
293226
},
294-
[onEdgesChange],
227+
[onEdgesChange, ydoc],
295228
);
296229

297230
const onConnect = useCallback(
@@ -360,7 +293,6 @@ export const useCanvas = () => {
360293
onNodeDragStart,
361294
onNodeDragStop,
362295
onConnect,
363-
sortNodes,
364296
cursors,
365297
};
366298
};

apps/frontend/src/features/canvas/ui/Canvas/index.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ export function Canvas({ className }: CanvasProps) {
3838
onNodeDragStart,
3939
onNodeDragStop,
4040
onConnect,
41-
sortNodes,
4241
cursors,
4342
} = useCanvas();
4443

@@ -63,7 +62,7 @@ export function Canvas({ className }: CanvasProps) {
6362
>
6463
<Controls />
6564
<div className="fixed bottom-5 left-16 z-30 h-4 w-4 text-neutral-50 hover:cursor-pointer">
66-
<button onClick={sortNodes}>Sort</button>
65+
{/* <button onClick={sortNodes}>Sort</button> */}
6766
</div>
6867
<MiniMap />
6968
<Background variant={BackgroundVariant.Dots} gap={12} size={1} />

apps/frontend/src/features/editor/model/useEditorTitle.ts

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { useState } from "react";
2-
32
import Emoji from "@/shared/ui/Emoji";
4-
53
import useYDocStore from "@/shared/model/ydocStore";
64
import { useYText } from "@/shared/model/useYText";
5+
import { YNode } from "@/features/canvas/model/useCanvas";
76

87
interface Emoji {
98
id: string;
@@ -22,19 +21,38 @@ export const useEditorTitle = (currentPage: number) => {
2221

2322
const handleTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
2423
setYTitle(e.target.value);
24+
25+
const nodesMap = ydoc.getMap("nodes");
26+
const existingNode = nodesMap.get(currentPage.toString()) as YNode;
27+
28+
if (existingNode) {
29+
const updatedNode = {
30+
...existingNode,
31+
data: {
32+
...existingNode.data,
33+
title: e.target.value,
34+
},
35+
};
36+
nodesMap.set(currentPage.toString(), updatedNode);
37+
}
2538
};
2639

2740
const handleEmojiClick = ({ native }: Emoji) => {
2841
setYEmoji(native);
29-
setIsEmojiPickerOpen(false);
30-
};
3142

32-
const handleTitleEmojiClick = () => {
33-
setIsEmojiPickerOpen(!isEmojiPickerOpen);
34-
};
43+
const nodesMap = ydoc.getMap("nodes");
44+
const existingNode = nodesMap.get(currentPage.toString()) as YNode;
3545

36-
const handleEmojiOutsideClick = () => {
37-
if (!isEmojiPickerOpen) return;
46+
if (existingNode) {
47+
const updatedNode = {
48+
...existingNode,
49+
data: {
50+
...existingNode.data,
51+
emoji: native,
52+
},
53+
};
54+
nodesMap.set(currentPage.toString(), updatedNode);
55+
}
3856

3957
setIsEmojiPickerOpen(false);
4058
};
@@ -43,6 +61,30 @@ export const useEditorTitle = (currentPage: number) => {
4361
e.stopPropagation();
4462

4563
setYEmoji("");
64+
65+
const nodesMap = ydoc.getMap("nodes");
66+
const existingNode = nodesMap.get(currentPage.toString()) as YNode;
67+
68+
if (existingNode) {
69+
const updatedNode = {
70+
...existingNode,
71+
data: {
72+
...existingNode.data,
73+
emoji: "",
74+
},
75+
};
76+
nodesMap.set(currentPage.toString(), updatedNode);
77+
}
78+
79+
setIsEmojiPickerOpen(false);
80+
};
81+
82+
const handleTitleEmojiClick = () => {
83+
setIsEmojiPickerOpen(!isEmojiPickerOpen);
84+
};
85+
86+
const handleEmojiOutsideClick = () => {
87+
if (!isEmojiPickerOpen) return;
4688
setIsEmojiPickerOpen(false);
4789
};
4890

0 commit comments

Comments
 (0)