From 8cc6dc30fd79d5f26e34c145bfa4bdef323c7085 Mon Sep 17 00:00:00 2001 From: Ludovico7 Date: Tue, 26 Nov 2024 13:07:37 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EB=B3=91=ED=95=A9=EA=B3=BC=EC=A0=95?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=83=9D=EA=B8=B4=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- @noctaCrdt/Crdt.ts | 3 + client/src/features/editor/Editor.tsx | 132 +++++++++--------- .../editor/hooks/useMarkdownGrammer.ts | 10 +- client/src/features/page/Page.tsx | 1 + client/src/utils/caretUtils.ts | 10 +- server/src/crdt/crdt.gateway.ts | 3 +- 6 files changed, 79 insertions(+), 80 deletions(-) diff --git a/@noctaCrdt/Crdt.ts b/@noctaCrdt/Crdt.ts index d4b8e0e1..db62dd79 100644 --- a/@noctaCrdt/Crdt.ts +++ b/@noctaCrdt/Crdt.ts @@ -242,6 +242,8 @@ export class BlockCRDT extends CRDT { blockId, pageId, style: node.style || [], + color: node.color, + backgroundColor: node.backgroundColor, }; return operation; @@ -325,6 +327,7 @@ export class BlockCRDT extends CRDT { remoteUpdate(operation: RemoteCharUpdateOperation): void { const updatedChar = this.LinkedList.nodeMap[JSON.stringify(operation.node.id)]; + console.log("remoteUpdate", updatedChar); if (operation.node.style && operation.node.style.length > 0) { updatedChar.style = [...operation.node.style]; } diff --git a/client/src/features/editor/Editor.tsx b/client/src/features/editor/Editor.tsx index f73c0fd9..38860de9 100644 --- a/client/src/features/editor/Editor.tsx +++ b/client/src/features/editor/Editor.tsx @@ -10,7 +10,7 @@ import { TextColorType, BackgroundColorType, } from "node_modules/@noctaCrdt/Interfaces.ts"; -import { useRef, useState, useCallback, useEffect } from "react"; +import { useRef, useState, useCallback, useEffect, useMemo } from "react"; import { useSocketStore } from "@src/stores/useSocketStore.ts"; import { setCaretPosition, getAbsoluteCaretPosition } from "@src/utils/caretUtils.ts"; import { @@ -58,11 +58,8 @@ export const Editor = ({ sendBlockUpdateOperation, } = useSocketStore(); const { clientId } = useSocketStore(); - const editorRef = useRef(null); // Add ref for the editor - // editorCRDT를 useState로 관리하여 페이지별로 인스턴스를 분리 - const [editorCRDT, setEditorCRDT] = useState(() => new EditorCRDT(0)); - useEffect(() => { + const editorCRDTInstance = useMemo(() => { let newEditorCRDT; if (serializedEditorData) { newEditorCRDT = new EditorCRDT(serializedEditorData.client); @@ -70,26 +67,19 @@ export const Editor = ({ } else { newEditorCRDT = new EditorCRDT(clientId ? clientId : 0); } - setEditorCRDT(newEditorCRDT); + return newEditorCRDT; }, [serializedEditorData, clientId]); + const editorCRDT = useRef(editorCRDTInstance); + // editorState도 editorCRDT가 변경될 때마다 업데이트 const [editorState, setEditorState] = useState({ - clock: editorCRDT?.clock || 0, - linkedList: editorCRDT?.LinkedList || new BlockLinkedList(), + clock: editorCRDT.current.clock, + linkedList: editorCRDT.current.LinkedList, }); - useEffect(() => { - if (editorCRDT) { - setEditorState({ - clock: editorCRDT.clock, - linkedList: editorCRDT.LinkedList, - }); - } - }, [editorCRDT]); - const { sensors, handleDragEnd } = useBlockDragAndDrop({ - editorCRDT, + editorCRDT: editorCRDT.current, editorState, setEditorState, pageId, @@ -97,7 +87,7 @@ export const Editor = ({ const { handleTypeSelect, handleAnimationSelect, handleCopySelect, handleDeleteSelect } = useBlockOptionSelect({ - editorCRDT, + editorCRDT: editorCRDT.current, editorState, setEditorState, pageId, @@ -108,7 +98,7 @@ export const Editor = ({ }); const { handleKeyDown: onKeyDown } = useMarkdownGrammer({ - editorCRDT, + editorCRDT: editorCRDT.current, editorState, setEditorState, pageId, @@ -117,12 +107,11 @@ export const Editor = ({ sendBlockUpdateOperation, sendCharDeleteOperation, sendCharInsertOperation, - editorRef, }); const { onTextStyleUpdate, onTextColorUpdate, onTextBackgroundColorUpdate } = useTextOptionSelect( { - editorCRDT, + editorCRDT: editorCRDT.current, setEditorState, pageId, }, @@ -142,11 +131,12 @@ export const Editor = ({ ) as HTMLDivElement; if (!clickedElement) return; - editorCRDT.currentBlock = editorCRDT.LinkedList.nodeMap[JSON.stringify(blockId)]; + editorCRDT.current.currentBlock = + editorCRDT.current.LinkedList.nodeMap[JSON.stringify(blockId)]; const caretPosition = getAbsoluteCaretPosition(clickedElement); // 계산된 캐럿 위치 저장 - editorCRDT.currentBlock.crdt.currentCaret = caretPosition; + editorCRDT.current.currentBlock.crdt.currentCaret = caretPosition; } }; @@ -185,7 +175,7 @@ export const Editor = ({ const addedChar = newContent[validCaretPosition - 1]; charNode = block.crdt.localInsert(validCaretPosition - 1, addedChar, block.id, pageId); } - editorCRDT.currentBlock!.crdt.currentCaret = caretPosition; + editorCRDT.current.currentBlock!.crdt.currentCaret = caretPosition; sendCharInsertOperation({ node: charNode.node, blockId: block.id, pageId }); } else if (newContent.length < currentContent.length) { // 문자가 삭제된 경우 @@ -196,12 +186,12 @@ export const Editor = ({ sendCharDeleteOperation(operationNode); // 캐럿 위치 업데이트 - editorCRDT.currentBlock!.crdt.currentCaret = deletePosition; + editorCRDT.current.currentBlock!.crdt.currentCaret = deletePosition; } } setEditorState({ - clock: editorCRDT.clock, - linkedList: editorCRDT.LinkedList, + clock: editorCRDT.current.clock, + linkedList: editorCRDT.current.LinkedList, }); }, [sendCharInsertOperation, sendCharDeleteOperation, editorCRDT, pageId, updatePageData], @@ -237,8 +227,8 @@ export const Editor = ({ block.crdt.currentCaret = startOffset; setEditorState({ - clock: editorCRDT.clock, - linkedList: editorCRDT.LinkedList, + clock: editorCRDT.current.clock, + linkedList: editorCRDT.current.LinkedList, }); } else { onKeyDown(e); @@ -315,7 +305,7 @@ export const Editor = ({ }); }); - editorCRDT.currentBlock!.crdt.currentCaret = caretPosition + metadata.length; + editorCRDT.current.currentBlock!.crdt.currentCaret = caretPosition + metadata.length; } else { const text = e.clipboardData.getData("text/plain"); @@ -335,12 +325,12 @@ export const Editor = ({ }); // 캐럿 위치 업데이트 - editorCRDT.currentBlock!.crdt.currentCaret = caretPosition + text.length; + editorCRDT.current.currentBlock!.crdt.currentCaret = caretPosition + text.length; } setEditorState({ - clock: editorCRDT.clock, - linkedList: editorCRDT.LinkedList, + clock: editorCRDT.current.clock, + linkedList: editorCRDT.current.LinkedList, }); }; @@ -365,7 +355,7 @@ export const Editor = ({ }); block.crdt.currentCaret = caretPosition; - updatePageData(pageId, editorCRDT.serialize()); + updatePageData(pageId, editorCRDT.current.serialize()); }, [editorCRDT, pageId, sendCharInsertOperation, updatePageData], ); @@ -373,15 +363,15 @@ export const Editor = ({ const subscriptionRef = useRef(false); useEffect(() => { - if (!editorCRDT || !editorCRDT.currentBlock) return; + if (!editorCRDT || !editorCRDT.current.currentBlock) return; setCaretPosition({ - blockId: editorCRDT.currentBlock.id, - linkedList: editorCRDT.LinkedList, - position: editorCRDT.currentBlock?.crdt.currentCaret, - rootElement: editorRef.current, + blockId: editorCRDT.current.currentBlock.id, + linkedList: editorCRDT.current.LinkedList, + position: editorCRDT.current.currentBlock?.crdt.currentCaret, + pageId, }); // 서윤님 피드백 반영 - }, [editorCRDT, editorCRDT?.currentBlock?.id.serialize()]); + }, [editorCRDT.current.currentBlock?.id.serialize()]); useEffect(() => { if (!editorCRDT) return; @@ -392,32 +382,33 @@ export const Editor = ({ onRemoteBlockInsert: (operation) => { console.log(operation, "block : 입력 확인합니다이"); if (operation.pageId !== pageId) return; - editorCRDT.remoteInsert(operation); + editorCRDT.current.remoteInsert(operation); setEditorState({ - clock: editorCRDT.clock, - linkedList: editorCRDT.LinkedList, + clock: editorCRDT.current.clock, + linkedList: editorCRDT.current.LinkedList, }); }, onRemoteBlockDelete: (operation) => { console.log(operation, "block : 삭제 확인합니다이"); if (operation.pageId !== pageId) return; - editorCRDT.remoteDelete(operation); + editorCRDT.current.remoteDelete(operation); setEditorState({ - clock: editorCRDT.clock, - linkedList: editorCRDT.LinkedList, + clock: editorCRDT.current.clock, + linkedList: editorCRDT.current.LinkedList, }); }, onRemoteCharInsert: (operation) => { console.log(operation, "char : 입력 확인합니다이"); if (operation.pageId !== pageId) return; - const targetBlock = editorCRDT.LinkedList.nodeMap[JSON.stringify(operation.blockId)]; + const targetBlock = + editorCRDT.current.LinkedList.nodeMap[JSON.stringify(operation.blockId)]; if (targetBlock) { targetBlock.crdt.remoteInsert(operation); setEditorState({ - clock: editorCRDT.clock, - linkedList: editorCRDT.LinkedList, + clock: editorCRDT.current.clock, + linkedList: editorCRDT.current.LinkedList, }); } }, @@ -425,12 +416,13 @@ export const Editor = ({ onRemoteCharDelete: (operation) => { console.log(operation, "char : 삭제 확인합니다이"); if (operation.pageId !== pageId) return; - const targetBlock = editorCRDT.LinkedList.nodeMap[JSON.stringify(operation.blockId)]; + const targetBlock = + editorCRDT.current.LinkedList.nodeMap[JSON.stringify(operation.blockId)]; if (targetBlock) { targetBlock.crdt.remoteDelete(operation); setEditorState({ - clock: editorCRDT.clock, - linkedList: editorCRDT.LinkedList, + clock: editorCRDT.current.clock, + linkedList: editorCRDT.current.LinkedList, }); } }, @@ -438,31 +430,33 @@ export const Editor = ({ onRemoteBlockUpdate: (operation) => { console.log(operation, "block : 업데이트 확인합니다이"); if (operation.pageId !== pageId) return; - editorCRDT.remoteUpdate(operation.node, operation.pageId); + editorCRDT.current.remoteUpdate(operation.node, operation.pageId); setEditorState({ - clock: editorCRDT.clock, - linkedList: editorCRDT.LinkedList, + clock: editorCRDT.current.clock, + linkedList: editorCRDT.current.LinkedList, }); }, onRemoteBlockReorder: (operation) => { console.log(operation, "block : 재정렬 확인합니다이"); if (operation.pageId !== pageId) return; - editorCRDT.remoteReorder(operation); + editorCRDT.current.remoteReorder(operation); setEditorState({ - clock: editorCRDT.clock, - linkedList: editorCRDT.LinkedList, + clock: editorCRDT.current.clock, + linkedList: editorCRDT.current.LinkedList, }); }, onRemoteCharUpdate: (operation) => { console.log(operation, "char : 업데이트 확인합니다이"); if (!editorCRDT) return; - const targetBlock = editorCRDT.LinkedList.nodeMap[JSON.stringify(operation.blockId)]; + if (operation.pageId !== pageId) return; + const targetBlock = + editorCRDT.current.LinkedList.nodeMap[JSON.stringify(operation.blockId)]; targetBlock.crdt.remoteUpdate(operation); setEditorState({ - clock: editorCRDT.clock, - linkedList: editorCRDT.LinkedList, + clock: editorCRDT.current.clock, + linkedList: editorCRDT.current.LinkedList, }); }, @@ -479,15 +473,15 @@ export const Editor = ({ const addNewBlock = () => { if (!editorCRDT) return; - const index = editorCRDT.LinkedList.spread().length; - const operation = editorCRDT.localInsert(index, ""); - editorCRDT.currentBlock = operation.node; + const index = editorCRDT.current.LinkedList.spread().length; + const operation = editorCRDT.current.localInsert(index, ""); + editorCRDT.current.currentBlock = operation.node; sendBlockInsertOperation({ node: operation.node, pageId }); setEditorState(() => ({ - clock: editorCRDT.clock, - linkedList: editorCRDT.LinkedList, + clock: editorCRDT.current.clock, + linkedList: editorCRDT.current.LinkedList, })); - updatePageData(pageId, editorCRDT.serialize()); + updatePageData(pageId, editorCRDT.current.serialize()); }; // 로딩 상태 체크 @@ -516,7 +510,7 @@ export const Editor = ({ key={`${block.id.client}-${block.id.clock}`} id={`${block.id.client}-${block.id.clock}`} block={block} - isActive={block.id === editorCRDT.currentBlock?.id} + isActive={block.id === editorCRDT.current.currentBlock?.id} onInput={handleBlockInput} onCompositionEnd={handleCompositionEnd} onKeyDown={handleKeyDown} diff --git a/client/src/features/editor/hooks/useMarkdownGrammer.ts b/client/src/features/editor/hooks/useMarkdownGrammer.ts index 0e84f161..cf23ff73 100644 --- a/client/src/features/editor/hooks/useMarkdownGrammer.ts +++ b/client/src/features/editor/hooks/useMarkdownGrammer.ts @@ -14,8 +14,7 @@ import { setCaretPosition, getAbsoluteCaretPosition } from "@src/utils/caretUtil interface useMarkdownGrammerProps { editorCRDT: EditorCRDT; - editorState: EditorStateProps; - editorRef: React.RefObject; // Add editorRef + editorState: EditorStateProps; // Add editorRef setEditorState: React.Dispatch< React.SetStateAction<{ clock: number; @@ -40,7 +39,6 @@ export const useMarkdownGrammer = ({ sendCharDeleteOperation, sendCharInsertOperation, sendBlockUpdateOperation, - editorRef, }: useMarkdownGrammerProps) => { const handleKeyDown = useCallback( (e: React.KeyboardEvent) => { @@ -300,7 +298,7 @@ export const useMarkdownGrammer = ({ blockId: targetBlock.id, linkedList: editorCRDT.LinkedList, position: Math.min(caretPosition, targetBlock.crdt.read().length), - rootElement: editorRef.current, + pageId, }); break; } @@ -322,7 +320,7 @@ export const useMarkdownGrammer = ({ blockId: prevBlock.id, linkedList: editorCRDT.LinkedList, position: prevBlock.crdt.read().length, - rootElement: editorRef.current, + pageId, }); } break; @@ -341,7 +339,7 @@ export const useMarkdownGrammer = ({ blockId: nextBlock.id, linkedList: editorCRDT.LinkedList, position: 0, - rootElement: editorRef.current, + pageId, }); } break; diff --git a/client/src/features/page/Page.tsx b/client/src/features/page/Page.tsx index 006563d9..3434a811 100644 --- a/client/src/features/page/Page.tsx +++ b/client/src/features/page/Page.tsx @@ -54,6 +54,7 @@ export const Page = ({ return (
{ @@ -76,17 +76,19 @@ export const setCaretPosition = ({ blockId, linkedList, position, - rootElement, + pageId, }: SetCaretPositionProps): void => { try { - if (!rootElement) return; if (position === undefined) return; const selection = window.getSelection(); if (!selection) return; + const currentPage = document.getElementById(pageId); + const blockElements = Array.from( - document.querySelectorAll('.d_flex.pos_relative.w_full[data-group="true"]'), + currentPage?.querySelectorAll('.d_flex.pos_relative.w_full[data-group="true"]') || [], ); + const currentIndex = linkedList.spread().findIndex((b) => b.id === blockId); const targetElement = blockElements[currentIndex]; if (!targetElement) return; diff --git a/server/src/crdt/crdt.gateway.ts b/server/src/crdt/crdt.gateway.ts index ba351676..e3f8276d 100644 --- a/server/src/crdt/crdt.gateway.ts +++ b/server/src/crdt/crdt.gateway.ts @@ -307,7 +307,8 @@ export class CrdtGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa node: data.node, pageId: data.pageId, } as RemoteBlockUpdateOperation; - client.to(client.data.pageId).emit("update/block", operation); + // 여기서 문제가? + client.to(data.pageId).emit("update/block", operation); } catch (error) { this.logger.error( `블록 Update 연산 처리 중 오류 발생 - Client ID: ${clientInfo?.clientId}`,