From 344d963018c06192d8f1d7ce437a0f86f2024c1a Mon Sep 17 00:00:00 2001 From: Ludovico7 Date: Thu, 21 Nov 2024 23:30:56 +0900 Subject: [PATCH 1/5] =?UTF-8?q?refactor:=20remoteReorder=20=EC=9D=B8?= =?UTF-8?q?=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- @noctaCrdt/Crdt.ts | 19 +++++++++++-------- @noctaCrdt/Interfaces.ts | 3 ++- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/@noctaCrdt/Crdt.ts b/@noctaCrdt/Crdt.ts index ae7686ca..34812d0c 100644 --- a/@noctaCrdt/Crdt.ts +++ b/@noctaCrdt/Crdt.ts @@ -7,7 +7,7 @@ import { RemoteBlockInsertOperation, RemoteCharInsertOperation, CRDTSerializedProps, - RemoteReorderOperation, + RemoteBlockReorderOperation, RemoteBlockUpdateOperation, } from "./Interfaces"; @@ -149,20 +149,25 @@ export class EditorCRDT extends CRDT { targetId: BlockId; beforeId: BlockId | null; afterId: BlockId | null; - }): RemoteReorderOperation { - const operation: RemoteReorderOperation = { + pageId: string; + }): RemoteBlockReorderOperation { + const operation: RemoteBlockReorderOperation = { ...params, clock: this.clock, client: this.client, }; - this.LinkedList.reorderNodes(params); + this.LinkedList.reorderNodes({ + targetId: params.targetId, + beforeId: params.beforeId, + afterId: params.afterId, + }); this.clock += 1; return operation; } - remoteReorder(operation: RemoteReorderOperation): void { + remoteReorder(operation: RemoteBlockReorderOperation): void { const { targetId, beforeId, afterId, clock } = operation; this.LinkedList.reorderNodes({ @@ -171,9 +176,7 @@ export class EditorCRDT extends CRDT { afterId, }); - if (this.clock <= clock) { - this.clock = clock + 1; - } + this.clock = Math.max(this.clock, clock) + 1; } serialize(): CRDTSerializedProps { diff --git a/@noctaCrdt/Interfaces.ts b/@noctaCrdt/Interfaces.ts index ecee570d..d8762ced 100644 --- a/@noctaCrdt/Interfaces.ts +++ b/@noctaCrdt/Interfaces.ts @@ -93,10 +93,11 @@ export interface WorkSpaceSerializedProps { pageList: Page[]; authUser: Map; } -export interface RemoteReorderOperation { +export interface RemoteBlockReorderOperation { targetId: BlockId; beforeId: BlockId | null; afterId: BlockId | null; clock: number; client: number; + pageId: string; } From f80740c692fd42493282ccd0f63595d4bac9db3d Mon Sep 17 00:00:00 2001 From: Ludovico7 Date: Thu, 21 Nov 2024 23:31:25 +0900 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20reorder=20=EC=97=B0=EC=82=B0=20sock?= =?UTF-8?q?et=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/stores/useSocketStore.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/client/src/stores/useSocketStore.ts b/client/src/stores/useSocketStore.ts index 62bd6989..ec5f8a03 100644 --- a/client/src/stores/useSocketStore.ts +++ b/client/src/stores/useSocketStore.ts @@ -5,6 +5,7 @@ import { RemoteCharInsertOperation, RemoteCharDeleteOperation, RemoteBlockUpdateOperation, + RemoteBlockReorderOperation, CursorPosition, WorkSpaceSerializedProps, } from "@noctaCrdt/Interfaces"; @@ -24,6 +25,7 @@ interface SocketStore { sendCharInsertOperation: (operation: RemoteCharInsertOperation) => void; sendBlockDeleteOperation: (operation: RemoteBlockDeleteOperation) => void; sendCharDeleteOperation: (operation: RemoteCharDeleteOperation) => void; + sendBlockReorderOperation: (operation: RemoteBlockReorderOperation) => void; sendCursorPosition: (position: CursorPosition) => void; subscribeToRemoteOperations: (handlers: RemoteOperationHandlers) => (() => void) | undefined; subscribeToPageOperations: (handlers: PageOperationsHandlers) => (() => void) | undefined; @@ -34,6 +36,7 @@ interface RemoteOperationHandlers { onRemoteBlockUpdate: (operation: RemoteBlockUpdateOperation) => void; onRemoteBlockInsert: (operation: RemoteBlockInsertOperation) => void; onRemoteBlockDelete: (operation: RemoteBlockDeleteOperation) => void; + onRemoteBlockReorder: (operation: RemoteBlockReorderOperation) => void; onRemoteCharInsert: (operation: RemoteCharInsertOperation) => void; onRemoteCharDelete: (operation: RemoteCharDeleteOperation) => void; onRemoteCursor: (position: CursorPosition) => void; @@ -146,6 +149,11 @@ export const useSocketStore = create((set, get) => ({ socket?.emit("cursor", position); }, + sendBlockReorderOperation: (operation: RemoteBlockReorderOperation) => { + const { socket } = get(); + socket?.emit("reorder/block", operation); + }, + subscribeToRemoteOperations: (handlers: RemoteOperationHandlers) => { const { socket } = get(); if (!socket) return; @@ -153,6 +161,7 @@ export const useSocketStore = create((set, get) => ({ socket.on("update/block", handlers.onRemoteBlockUpdate); socket.on("insert/block", handlers.onRemoteBlockInsert); socket.on("delete/block", handlers.onRemoteBlockDelete); + socket.on("reorder/block", handlers.onRemoteBlockReorder); socket.on("insert/char", handlers.onRemoteCharInsert); socket.on("delete/char", handlers.onRemoteCharDelete); socket.on("cursor", handlers.onRemoteCursor); @@ -161,6 +170,7 @@ export const useSocketStore = create((set, get) => ({ socket.off("update/block", handlers.onRemoteBlockUpdate); socket.off("insert/block", handlers.onRemoteBlockInsert); socket.off("delete/block", handlers.onRemoteBlockDelete); + socket.off("reorder/block", handlers.onRemoteBlockReorder); socket.off("insert/char", handlers.onRemoteCharInsert); socket.off("delete/char", handlers.onRemoteCharDelete); socket.off("cursor", handlers.onRemoteCursor); From f5c8906544404d7d97093a0bb1f2987d90b1db47 Mon Sep 17 00:00:00 2001 From: Ludovico7 Date: Thu, 21 Nov 2024 23:31:43 +0900 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20=EB=93=9C=EB=9E=98=EA=B7=B8=20?= =?UTF-8?q?=EC=95=A4=20=EB=93=9C=EB=9E=8D=EC=8B=9C=20=EC=84=9C=EB=B2=84?= =?UTF-8?q?=EB=A1=9C=20=EC=97=B0=EC=82=B0=20=EC=A0=84=EC=86=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/features/editor/Editor.tsx | 23 +++++++++++-------- .../editor/hooks/useBlockDragAndDrop.ts | 10 +++++++- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/client/src/features/editor/Editor.tsx b/client/src/features/editor/Editor.tsx index c9790bf5..f8424eb7 100644 --- a/client/src/features/editor/Editor.tsx +++ b/client/src/features/editor/Editor.tsx @@ -28,16 +28,6 @@ export interface EditorStateProps { } // TODO: pageId, editorCRDT를 props로 받아와야함 export const Editor = ({ onTitleChange, pageId, serializedEditorData }: EditorProps) => { - /* - const { - sendCharInsertOperation, - sendCharDeleteOperation, - subscribeToRemoteOperations, - sendBlockInsertOperation, - sendBlockDeleteOperation, - sendBlockUpdateOperation, - } = useSocket(); - */ const { sendCharInsertOperation, sendCharDeleteOperation, @@ -62,6 +52,7 @@ export const Editor = ({ onTitleChange, pageId, serializedEditorData }: EditorPr editorCRDT: editorCRDT.current, editorState, setEditorState, + pageId, }); const { handleKeyDown } = useMarkdownGrammer({ @@ -222,6 +213,18 @@ export const Editor = ({ onTitleChange, pageId, serializedEditorData }: EditorPr currentBlock: prev.currentBlock, })); }, + + onRemoteBlockReorder: (operation) => { + console.log(operation, "block : 재정렬 확인합니다이"); + if (!editorCRDT.current) return; + editorCRDT.current.remoteReorder(operation); + setEditorState((prev) => ({ + clock: editorCRDT.current.clock, + linkedList: editorCRDT.current.LinkedList, + currentBlock: prev.currentBlock, + })); + }, + onRemoteCursor: (position) => { console.log(position, "커서위치 수신"); }, diff --git a/client/src/features/editor/hooks/useBlockDragAndDrop.ts b/client/src/features/editor/hooks/useBlockDragAndDrop.ts index 04e4d45b..61c35eb9 100644 --- a/client/src/features/editor/hooks/useBlockDragAndDrop.ts +++ b/client/src/features/editor/hooks/useBlockDragAndDrop.ts @@ -1,18 +1,21 @@ // hooks/useBlockDragAndDrop.ts import { DragEndEvent, PointerSensor, useSensor, useSensors } from "@dnd-kit/core"; import { EditorCRDT } from "@noctaCrdt/Crdt"; +import { useSocketStore } from "@src/stores/useSocketStore.ts"; import { EditorStateProps } from "../Editor"; interface UseBlockDragAndDropProps { editorCRDT: EditorCRDT; editorState: EditorStateProps; setEditorState: React.Dispatch>; + pageId: string; } export const useBlockDragAndDrop = ({ editorCRDT, editorState, setEditorState, + pageId, }: UseBlockDragAndDropProps) => { const sensors = useSensors( useSensor(PointerSensor, { @@ -22,6 +25,8 @@ export const useBlockDragAndDrop = ({ }), ); + const { sendBlockReorderOperation } = useSocketStore(); + const handleDragEnd = (event: DragEndEvent) => { const { active, over } = event; @@ -73,12 +78,15 @@ export const useBlockDragAndDrop = ({ } // EditorCRDT의 현재 상태로 작업 - editorCRDT.localReorder({ + const operation = editorCRDT.localReorder({ targetId: targetNode.id, beforeId: beforeNode?.id || null, afterId: afterNode?.id || null, + pageId, }); + sendBlockReorderOperation(operation); + // EditorState 업데이트 setEditorState({ clock: editorCRDT.clock, From 5cf6b102af3f16e55faaa9b90449385e194ac03a Mon Sep 17 00:00:00 2001 From: Ludovico7 Date: Thu, 21 Nov 2024 23:32:12 +0900 Subject: [PATCH 4/5] =?UTF-8?q?feat:=20=EC=84=9C=EB=B2=84=EC=97=90?= =?UTF-8?q?=EC=84=9C=20reorder/block=20=EC=97=B0=EC=82=B0=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/crdt/crdt.gateway.ts | 38 +++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/server/src/crdt/crdt.gateway.ts b/server/src/crdt/crdt.gateway.ts index 23c354b1..a7600ff9 100644 --- a/server/src/crdt/crdt.gateway.ts +++ b/server/src/crdt/crdt.gateway.ts @@ -17,6 +17,7 @@ import { RemoteCharInsertOperation, RemoteBlockUpdateOperation, RemotePageCreateOperation, + RemoteBlockReorderOperation, CursorPosition, } from "@noctaCrdt/Interfaces"; import { Logger } from "@nestjs/common"; @@ -362,6 +363,43 @@ export class CrdtGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa } } + @SubscribeMessage("reorder/block") + async handleBlockReorder( + @MessageBody() data: RemoteBlockReorderOperation, + @ConnectedSocket() client: Socket, + ): Promise { + const clientInfo = this.clientMap.get(client.id); + try { + this.logger.debug( + `블록 Reorder 연산 수신 - Client ID: ${clientInfo?.clientId}, Data:`, + JSON.stringify(data), + ); + // 1. 워크스페이스 가져오기 + const workspace = this.workSpaceService.getWorkspace(); + + const currentPage = workspace.pageList.find((p) => p.id === data.pageId); + if (!currentPage) { + throw new Error(`Page with id ${data.pageId} not found`); + } + currentPage.crdt.remoteReorder(data); + + // 5. 다른 클라이언트들에게 업데이트된 블록 정보 브로드캐스트 + const operation = { + targetId: data.targetId, + beforeId: data.beforeId, + afterId: data.afterId, + pageId: data.pageId + } as RemoteBlockReorderOperation; + client.broadcast.emit("reorder/block", operation); + } catch (error) { + this.logger.error( + `블록 Reorder 연산 처리 중 오류 발생 - Client ID: ${clientInfo?.clientId}`, + error.stack, + ); + throw new WsException(`Update 연산 실패: ${error.message}`); + } + } + /** * 커서 위치 업데이트 처리 */ From bf8a7f45dc9b941cd3673980b2a4a2ea6e99034e Mon Sep 17 00:00:00 2001 From: Ludovico7 Date: Thu, 21 Nov 2024 23:36:20 +0900 Subject: [PATCH 5/5] =?UTF-8?q?chore:=20lint=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/crdt/crdt.gateway.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/crdt/crdt.gateway.ts b/server/src/crdt/crdt.gateway.ts index a7600ff9..e4e6d9ad 100644 --- a/server/src/crdt/crdt.gateway.ts +++ b/server/src/crdt/crdt.gateway.ts @@ -376,7 +376,7 @@ export class CrdtGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa ); // 1. 워크스페이스 가져오기 const workspace = this.workSpaceService.getWorkspace(); - + const currentPage = workspace.pageList.find((p) => p.id === data.pageId); if (!currentPage) { throw new Error(`Page with id ${data.pageId} not found`); @@ -388,7 +388,7 @@ export class CrdtGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa targetId: data.targetId, beforeId: data.beforeId, afterId: data.afterId, - pageId: data.pageId + pageId: data.pageId, } as RemoteBlockReorderOperation; client.broadcast.emit("reorder/block", operation); } catch (error) {