-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/#175 캐럿 관리방식 변경 #176
The head ref may contain hidden characters: "Feature/#175_\uCE90\uB7FF_\uAD00\uB9AC\uBC29\uC2DD_\uBCC0\uACBD"
Changes from all commits
9073fea
2ba1b3d
f9b95b6
e6a9a31
ec34926
e62f1de
1af8fcd
16aa9c5
f5c9221
400be0a
8f493b4
5434d30
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -50,3 +50,16 @@ export const checkbox = css({ | |
backgroundColor: "blue.500", | ||
}, | ||
}); | ||
|
||
export const addNewBlockButton = css({ | ||
display: "flex", | ||
gap: "spacing.sm", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 후후 요거 그냥 "sm"해도 동작될겁니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 앗 수정하겠습니다! requestAnimationFrame 없어도 동작하는군요! 이곳저곳 수정하면서 디버깅 하고 되나 안되나 확인하는 식으로 구현해서 중복되는 코드들이 많네요... 이부분도 반영해서 수정하겠습니다! |
||
borderRadius: "4px", | ||
padding: "spacing.sm", | ||
color: "gray.900", | ||
opacity: 0.8, | ||
cursor: "pointer", | ||
"&:hover": { | ||
opacity: 1, | ||
}, | ||
}); |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -8,9 +8,15 @@ import { | |||||
RemoteCharInsertOperation, | ||||||
serializedEditorDataProps, | ||||||
} from "node_modules/@noctaCrdt/Interfaces.ts"; | ||||||
import { useRef, useState, useCallback, useEffect, useMemo } from "react"; | ||||||
import { useRef, useState, useCallback, useEffect, useMemo, useLayoutEffect } from "react"; | ||||||
import { useSocketStore } from "@src/stores/useSocketStore.ts"; | ||||||
import { editorContainer, editorTitleContainer, editorTitle } from "./Editor.style"; | ||||||
import { setCaretPosition } from "@src/utils/caretUtils.ts"; | ||||||
import { | ||||||
editorContainer, | ||||||
editorTitleContainer, | ||||||
editorTitle, | ||||||
addNewBlockButton, | ||||||
} from "./Editor.style"; | ||||||
import { Block } from "./components/block/Block.tsx"; | ||||||
import { useBlockDragAndDrop } from "./hooks/useBlockDragAndDrop"; | ||||||
import { useBlockOptionSelect } from "./hooks/useBlockOption.ts"; | ||||||
|
@@ -25,7 +31,6 @@ interface EditorProps { | |||||
export interface EditorStateProps { | ||||||
clock: number; | ||||||
linkedList: BlockLinkedList; | ||||||
currentBlock: BlockId | null; | ||||||
} | ||||||
// TODO: pageId, editorCRDT를 props로 받아와야함 | ||||||
export const Editor = ({ onTitleChange, pageId, serializedEditorData }: EditorProps) => { | ||||||
|
@@ -47,7 +52,6 @@ export const Editor = ({ onTitleChange, pageId, serializedEditorData }: EditorPr | |||||
const [editorState, setEditorState] = useState<EditorStateProps>({ | ||||||
clock: editorCRDT.current.clock, | ||||||
linkedList: editorCRDT.current.LinkedList, | ||||||
currentBlock: null as BlockId | null, | ||||||
}); | ||||||
const { sensors, handleDragEnd } = useBlockDragAndDrop({ | ||||||
editorCRDT: editorCRDT.current, | ||||||
|
@@ -84,37 +88,9 @@ export const Editor = ({ onTitleChange, pageId, serializedEditorData }: EditorPr | |||||
onTitleChange(e.target.value); | ||||||
}; | ||||||
|
||||||
const handleBlockClick = (blockId: BlockId, e: React.MouseEvent<HTMLDivElement>) => { | ||||||
try { | ||||||
const block = editorState.linkedList.getNode(blockId); | ||||||
if (!block) { | ||||||
console.warn("Block not found:", blockId); | ||||||
return; | ||||||
} | ||||||
|
||||||
const selection = window.getSelection(); | ||||||
const range = document.caretRangeFromPoint(e.clientX, e.clientY); | ||||||
|
||||||
if (!selection || !range) { | ||||||
console.warn("Selection or range not available"); | ||||||
return; | ||||||
} | ||||||
|
||||||
// 새로운 Range로 Selection 설정 | ||||||
selection.removeAllRanges(); | ||||||
selection.addRange(range); | ||||||
|
||||||
// 현재 캐럿 위치를 저장 | ||||||
const caretPosition = selection.focusOffset; | ||||||
block.crdt.currentCaret = caretPosition; | ||||||
|
||||||
setEditorState((prev) => ({ | ||||||
...prev, | ||||||
currentBlock: blockId, | ||||||
})); | ||||||
} catch (error) { | ||||||
console.error("Error handling block click:", error); | ||||||
} | ||||||
const handleBlockClick = (blockId: BlockId) => { | ||||||
editorCRDT.current.currentBlock = | ||||||
editorCRDT.current.LinkedList.nodeMap[JSON.stringify(blockId)]; | ||||||
}; | ||||||
|
||||||
const handleBlockInput = useCallback( | ||||||
|
@@ -136,29 +112,55 @@ export const Editor = ({ onTitleChange, pageId, serializedEditorData }: EditorPr | |||||
if (caretPosition === 0) { | ||||||
const [addedChar] = newContent; | ||||||
charNode = block.crdt.localInsert(0, addedChar, block.id, pageId); | ||||||
block.crdt.currentCaret = 1; | ||||||
editorCRDT.current.currentBlock!.crdt.currentCaret = caretPosition; | ||||||
requestAnimationFrame(() => { | ||||||
setCaretPosition({ | ||||||
blockId: block.id, | ||||||
linkedList: editorCRDT.current.LinkedList, | ||||||
position: caretPosition, | ||||||
}); | ||||||
}); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 수고하셨습니다!!! requestAnimationFram이 뭔가 보니, 이전의 프레임 렌더가 다 끝나고 실행하는 함수 같더라구요. 이 중복되는 코드는 const updateEditorState = () => {
editorCRDT.current.currentBlock!.crdt.currentCaret = caretPosition;
requestAnimationFrame(() => {
setCaretPosition({
blockId: block.id,
linkedList: editorCRDT.current.LinkedList,
position: caretPosition,
});
}); 로 줄여서 updateEditorState() 로 사용해도 되지 않을까.. 싶습니다 !
Comment on lines
+115
to
+122
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 로직이 중복해서 나오네요 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 혹시 해당 로직들이 필요한 이유가 있을까요? 이게 없으면 어떤 부분이 동작하지 않는걸까요?? requestAnimationFrame(() => {
setCaretPosition({
blockId: block.id,
linkedList: editorCRDT.current.LinkedList,
position: caretPosition,
});
}); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 확인해보니 이 부분의 문제가 아니었던 것 같습니다. 다른 부분 수정하면서 조금 헷갈렸네요... 이내용 현재 구현하고 있는 텍스트 옵션 PR 올릴때 수정하도록 하겠습니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 앗 서윤님 피드백 주신 내용을 보면 requestAnimationFrame 없이도 동작하는 것 같습니다. 이부분은 삭제하겠습니다! |
||||||
} else if (caretPosition > currentContent.length) { | ||||||
const addedChar = newContent[newContent.length - 1]; | ||||||
charNode = block.crdt.localInsert(currentContent.length, addedChar, block.id, pageId); | ||||||
block.crdt.currentCaret = caretPosition; | ||||||
editorCRDT.current.currentBlock!.crdt.currentCaret = caretPosition; | ||||||
requestAnimationFrame(() => { | ||||||
setCaretPosition({ | ||||||
blockId: block.id, | ||||||
linkedList: editorCRDT.current.LinkedList, | ||||||
position: caretPosition, | ||||||
}); | ||||||
}); | ||||||
} else { | ||||||
const addedChar = newContent[caretPosition - 1]; | ||||||
charNode = block.crdt.localInsert(caretPosition - 1, addedChar, block.id, pageId); | ||||||
block.crdt.currentCaret = caretPosition; | ||||||
editorCRDT.current.currentBlock!.crdt.currentCaret = caretPosition; | ||||||
requestAnimationFrame(() => { | ||||||
setCaretPosition({ | ||||||
blockId: block.id, | ||||||
linkedList: editorCRDT.current.LinkedList, | ||||||
position: caretPosition, | ||||||
}); | ||||||
}); | ||||||
} | ||||||
sendCharInsertOperation({ node: charNode.node, blockId: block.id, pageId }); | ||||||
} else if (newContent.length < currentContent.length) { | ||||||
// 문자가 삭제된 경우 | ||||||
operationNode = block.crdt.localDelete(caretPosition, block.id, pageId); | ||||||
block.crdt.currentCaret = caretPosition; | ||||||
sendCharDeleteOperation(operationNode); | ||||||
editorCRDT.current.currentBlock!.crdt.currentCaret = caretPosition; | ||||||
requestAnimationFrame(() => { | ||||||
setCaretPosition({ | ||||||
blockId: block.id, | ||||||
linkedList: editorCRDT.current.LinkedList, | ||||||
position: caretPosition, | ||||||
}); | ||||||
}); | ||||||
} | ||||||
|
||||||
setEditorState((prev) => ({ | ||||||
setEditorState({ | ||||||
clock: editorCRDT.current.clock, | ||||||
linkedList: editorCRDT.current.LinkedList, | ||||||
currentBlock: prev.currentBlock, | ||||||
})); | ||||||
}); | ||||||
}, | ||||||
[sendCharInsertOperation, sendCharDeleteOperation], | ||||||
); | ||||||
|
@@ -186,6 +188,15 @@ export const Editor = ({ onTitleChange, pageId, serializedEditorData }: EditorPr | |||||
|
||||||
const subscriptionRef = useRef(false); | ||||||
|
||||||
useLayoutEffect(() => { | ||||||
if (!editorCRDT.current.currentBlock) return; | ||||||
setCaretPosition({ | ||||||
blockId: editorCRDT.current.currentBlock.id, | ||||||
linkedList: editorCRDT.current.LinkedList, | ||||||
position: editorCRDT.current.currentBlock?.crdt.currentCaret, | ||||||
}); | ||||||
}, [editorCRDT.current.currentBlock?.crdt.read().length]); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 2. 옵션창 -> 삭제버튼 누를시, 캐럿이 제대로 이동하지 않는 문제
Suggested change
2024-11-23.5.50.39.movThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 제가 branch 만들때 블록 옵션 기능이 없었어서 이부분은 확인하지 못했던 것 같습니다. 현재 작업중인 텍스트 옵션 pr 올릴때 피드백 주신 부분 반영해서 올리겠습니다! |
||||||
|
||||||
useEffect(() => { | ||||||
if (subscriptionRef.current) return; | ||||||
subscriptionRef.current = true; | ||||||
|
@@ -195,22 +206,20 @@ export const Editor = ({ onTitleChange, pageId, serializedEditorData }: EditorPr | |||||
console.log(operation, "block : 입력 확인합니다이"); | ||||||
if (!editorCRDT.current) return; | ||||||
editorCRDT.current.remoteInsert(operation); | ||||||
setEditorState((prev) => ({ | ||||||
setEditorState({ | ||||||
clock: editorCRDT.current.clock, | ||||||
linkedList: editorCRDT.current.LinkedList, | ||||||
currentBlock: prev.currentBlock, | ||||||
})); | ||||||
}); | ||||||
Comment on lines
207
to
+212
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이쪽은 묶으려면 묶을 수도 있겠는데 투머치일 것 같다는 생각이 들어서 굳이 묶을 필요는 없어보이네요 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 묶는게 정확이 어떤 걸 묶는다는 걸까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 비슷한 로직이 아래에도 중복해서 나오는 것 같아서 함수로 묶을 수 있겠다고 생각했는데 해보니까 좀 어거지로 묶이는 것 같더라구요.. 코멘트를 달기는 했는데 이 코멘트는 그냥 거르셔도 될 것 같습니다! |
||||||
}, | ||||||
|
||||||
onRemoteBlockDelete: (operation) => { | ||||||
console.log(operation, "block : 삭제 확인합니다이"); | ||||||
if (!editorCRDT.current) return; | ||||||
editorCRDT.current.remoteDelete(operation); | ||||||
setEditorState((prev) => ({ | ||||||
setEditorState({ | ||||||
clock: editorCRDT.current.clock, | ||||||
linkedList: editorCRDT.current.LinkedList, | ||||||
currentBlock: prev.currentBlock, | ||||||
})); | ||||||
}); | ||||||
}, | ||||||
|
||||||
onRemoteCharInsert: (operation) => { | ||||||
|
@@ -219,11 +228,10 @@ export const Editor = ({ onTitleChange, pageId, serializedEditorData }: EditorPr | |||||
const targetBlock = | ||||||
editorCRDT.current.LinkedList.nodeMap[JSON.stringify(operation.blockId)]; | ||||||
targetBlock.crdt.remoteInsert(operation); | ||||||
setEditorState((prev) => ({ | ||||||
setEditorState({ | ||||||
clock: editorCRDT.current.clock, | ||||||
linkedList: editorCRDT.current.LinkedList, | ||||||
currentBlock: prev.currentBlock, | ||||||
})); | ||||||
}); | ||||||
}, | ||||||
|
||||||
onRemoteCharDelete: (operation) => { | ||||||
|
@@ -232,11 +240,10 @@ export const Editor = ({ onTitleChange, pageId, serializedEditorData }: EditorPr | |||||
const targetBlock = | ||||||
editorCRDT.current.LinkedList.nodeMap[JSON.stringify(operation.blockId)]; | ||||||
targetBlock.crdt.remoteDelete(operation); | ||||||
setEditorState((prev) => ({ | ||||||
setEditorState({ | ||||||
clock: editorCRDT.current.clock, | ||||||
linkedList: editorCRDT.current.LinkedList, | ||||||
currentBlock: prev.currentBlock, | ||||||
})); | ||||||
}); | ||||||
}, | ||||||
|
||||||
onRemoteBlockUpdate: (operation) => { | ||||||
|
@@ -245,22 +252,20 @@ export const Editor = ({ onTitleChange, pageId, serializedEditorData }: EditorPr | |||||
// ?? | ||||||
console.log("타입", operation.node); | ||||||
editorCRDT.current.remoteUpdate(operation.node, operation.pageId); | ||||||
setEditorState((prev) => ({ | ||||||
setEditorState({ | ||||||
clock: editorCRDT.current.clock, | ||||||
linkedList: editorCRDT.current.LinkedList, | ||||||
currentBlock: prev.currentBlock, | ||||||
})); | ||||||
}); | ||||||
}, | ||||||
|
||||||
onRemoteBlockReorder: (operation) => { | ||||||
console.log(operation, "block : 재정렬 확인합니다이"); | ||||||
if (!editorCRDT.current) return; | ||||||
editorCRDT.current.remoteReorder(operation); | ||||||
setEditorState((prev) => ({ | ||||||
setEditorState({ | ||||||
clock: editorCRDT.current.clock, | ||||||
linkedList: editorCRDT.current.LinkedList, | ||||||
currentBlock: prev.currentBlock, | ||||||
})); | ||||||
}); | ||||||
}, | ||||||
|
||||||
onRemoteCursor: (position) => { | ||||||
|
@@ -274,18 +279,16 @@ export const Editor = ({ onTitleChange, pageId, serializedEditorData }: EditorPr | |||||
}; | ||||||
}, []); | ||||||
|
||||||
const tempBlock = () => { | ||||||
const addNewBlock = () => { | ||||||
const index = editorCRDT.current.LinkedList.spread().length; | ||||||
|
||||||
// 로컬 삽입을 수행하고 연산 객체를 반환받음 | ||||||
const operation = editorCRDT.current.localInsert(index, ""); | ||||||
sendBlockInsertOperation({ node: operation.node, pageId }); | ||||||
console.log("operation clock", operation.node); | ||||||
setEditorState(() => ({ | ||||||
setEditorState({ | ||||||
clock: operation.node.id.clock, | ||||||
linkedList: editorCRDT.current.LinkedList, | ||||||
currentBlock: operation.node.id, | ||||||
})); | ||||||
}); | ||||||
}; | ||||||
|
||||||
return ( | ||||||
|
@@ -309,7 +312,7 @@ export const Editor = ({ onTitleChange, pageId, serializedEditorData }: EditorPr | |||||
key={`${block.id.client}-${block.id.clock}`} | ||||||
id={`${block.id.client}-${block.id.clock}`} | ||||||
block={block} | ||||||
isActive={block.id === editorState.currentBlock} | ||||||
isActive={block.id === editorCRDT.current.currentBlock?.id} | ||||||
onInput={handleBlockInput} | ||||||
onCompositionEnd={handleCompositionEnd} | ||||||
onKeyDown={handleKeyDown} | ||||||
|
@@ -322,7 +325,11 @@ export const Editor = ({ onTitleChange, pageId, serializedEditorData }: EditorPr | |||||
))} | ||||||
</SortableContext> | ||||||
</DndContext> | ||||||
<div onClick={tempBlock}>임시</div> | ||||||
{editorState.linkedList.spread().length === 0 && ( | ||||||
<div className={addNewBlockButton} onClick={addNewBlock}> | ||||||
클릭해서 새로운 블록을 추가하세요 | ||||||
</div> | ||||||
)} | ||||||
</div> | ||||||
</div> | ||||||
); | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
대신 해주셨군요!! 감사합니다!! 🙇♂️