Skip to content
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

Merged
merged 12 commits into from
Nov 25, 2024

Conversation

Ludovico7
Copy link
Collaborator

@Ludovico7 Ludovico7 commented Nov 22, 2024

📝 변경 사항

🔍 변경 사항 설명

멘토님 피드백 반영

멘토님 피드백 내용 중 현재 캐럿관리를 너무 많이 하고있다는 내용이 있었습니다. 불필요하게 모든 캐럿관리를 직접 하다보니 예상치 못한 에러가 발생하고, js로 캐럿을 관리하다보니 성능상 문제가 발생하고 있습니다. 이를 해결하기 위해 다음과 같이 구조를 변경했습니다.

  • editorState에서 currentBlock을 상태로 관리하지 않음
    • 블록을 클릭하면 editorCRDT의 currentBlock만 변경
    • 캐럿이 변경되면 직접 editorCRDTRef의 currentBlock.crdt.currentCaret값을 변경
    • useLayoutEffect과 currentCaret을 통해 캐럿 관리
  • BlockCRDT의 currentCaret을 사용하지 않고 브라우저 API를 사용해 캐럿 관리
    • currentCaret은 상태를 저장할 뿐 이 값을 통해 상태를 변경시키지 않음
  • 블록 내부에서 왼쪽 오른쪽 키보드 입력시 이벤트 발생시키지 않음

이를 통해 캐럿 관련된 기능은 최대한 브라우저가 지원하는 기능을 사용할 수 있도록 수정했습니다.


기존 PR 내용 병합

작업중에 서윤님이 작성해주신 애니메이션, 블록 옵션 기능 PR이 머지되어서 해당 내용 merge한 후 변경사항 반영했습니다.

  • 변경된 editorState 구조에 맞게 useBlockOption 훅 수정
    • 현재 editorCRDT에서 currentBlock을 관리하지 않고 currentBlock의 경우 직접 editorCRDTRef에 저장하기 때문에 해당 내용 반영
    • 삭제한 블록이 currentBlock인 경우 추가로 처리

추가 구현사항

기존에 임시로 되어있던 블록 추가 버튼에 스타일을 적용하고, edtiorCRDT에 Block 인스턴스가 없는 경우에만 렌더링되도록 변경했습니다.


🙏 질문 사항

  • 캐럿 관리방식과 키입력 핸들러 수정하는데 생각보다 시간이 엄청 오래 걸렸고 중간에 에러도 많이 났었습니다... 최대한 에러나는 부분 있는지 디버깅 해보고 잘 돌아간다고 판단해서 merge 했지만 혹시 실행해 보시고 문제되는 부분 있으면 말씀해주세요!
  • 코드도 많이 지저분합니다... editor에서 캐럿 이동하는 부분에서 분기마다 requestAnimationFrame을 사용했는데, 분기가 다 끝나고 적용하면 잘 안되는 문제가 있습니다. 원인을 찾아보려고 했는데 피곤하기도 하고 해서 그런지 잘 안보였습니다...

📷 스크린샷 (선택)

2024-11-23.4.59.04.mov

✅ 작성자 체크리스트

  • Self-review: 코드가 스스로 검토됨
  • Unit tests 추가 또는 수정
  • 로컬에서 모든 기능이 정상 작동함
  • 린터 및 포맷터로 코드 정리됨
  • 의존성 업데이트 확인
  • 문서 업데이트 또는 주석 추가 (필요 시)

@Ludovico7 Ludovico7 added Bug 기능 또는 UI에서 발생한 오류나 예상치 못한 동작을 해결 FE 프론트엔드 작업 Feat 새로운 기능 추가나 기존 기능 확장 작업 labels Nov 22, 2024
@Ludovico7 Ludovico7 self-assigned this Nov 22, 2024
@hyonun321
Copy link
Collaborator

기존에는 char별로 인덱스로 접근해서 드래그를 하자마자 풀리는 현상과 currentblock이 업데이트되면서 렌더가 일어났는데 이 현상이 해결되었군요! 좀더 자세히 알게되었습니다. 완성도를 다듬기위해 저도 더 작업해보겠습니다!

Copy link
Collaborator

@minjungw00 minjungw00 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고 많으셨습니다! 나중에 시간되실 때 중복된 로직만 제거해주셔도 좋을 것 같네요!

Comment on lines +111 to +118
editorCRDT.current.currentBlock!.crdt.currentCaret = caretPosition;
requestAnimationFrame(() => {
setCaretPosition({
blockId: block.id,
linkedList: editorCRDT.current.LinkedList,
position: caretPosition,
});
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 로직이 중복해서 나오네요

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 해당 로직들이 필요한 이유가 있을까요? 이게 없으면 어떤 부분이 동작하지 않는걸까요??
이 부분을 다 지워봤을때, 특별히 동작이 안되던 부분이 없어서 질문드립니당!

          requestAnimationFrame(() => {
            setCaretPosition({
              blockId: block.id,
              linkedList: editorCRDT.current.LinkedList,
              position: caretPosition,
            });
          });

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인해보니 이 부분의 문제가 아니었던 것 같습니다. 다른 부분 수정하면서 조금 헷갈렸네요... 이내용 현재 구현하고 있는 텍스트 옵션 PR 올릴때 수정하도록 하겠습니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

앗 서윤님 피드백 주신 내용을 보면 requestAnimationFrame 없이도 동작하는 것 같습니다. 이부분은 삭제하겠습니다!

Comment on lines 182 to +187
if (!editorCRDT.current) return;
editorCRDT.current.remoteInsert(operation);
setEditorState((prev) => ({
setEditorState({
clock: editorCRDT.current.clock,
linkedList: editorCRDT.current.LinkedList,
currentBlock: prev.currentBlock,
}));
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이쪽은 묶으려면 묶을 수도 있겠는데 투머치일 것 같다는 생각이 들어서 굳이 묶을 필요는 없어보이네요

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

묶는게 정확이 어떤 걸 묶는다는 걸까요?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

비슷한 로직이 아래에도 중복해서 나오는 것 같아서 함수로 묶을 수 있겠다고 생각했는데 해보니까 좀 어거지로 묶이는 것 같더라구요.. 코멘트를 달기는 했는데 이 코멘트는 그냥 거르셔도 될 것 같습니다!

Copy link
Member

@pipisebastian pipisebastian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

디버깅과정,, 고생하셨씁니당..!!

{ id: "ul", label: "리스트" },
{ id: "ol", label: "순서 리스트" },
{ id: "checkbox", label: "체크박스" },
{ id: "blockquote", label: "인용문" },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

대신 해주셨군요!! 감사합니다!! 🙇‍♂️

clock: editorCRDT.clock,
linkedList: editorCRDT.LinkedList,
currentBlock: prev.currentBlock,
}));
});
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1. 옵션창 -> 삭제버튼 누를시, 캐럿이 제대로 이동하지 않는 문제

  • 맨 처음 블록을 삭제할때 findByIndex 메서드에서 에러를 띄워주더라구요!
  • findByIndex 자체에서 null을 반환할지, 혹은 지금 코드상에서 예외처리를 할까 고민했는데, 지금 코드상에서 한번더 예외처리를 해주는게 좋을 것 같아 findBlock 메서드를 추가했습니다!
  • 또한,local, remote 모두 삭제한 후에는, current Block은 currentIndex 값 그대로를 가야하더라구요. 그래서 index 부분도 수정했습니다.
  • 그리고 이것만 수정하면 실제 캐럿이 제대로 랜더링되지 않던데, 이 문제는 다음 코멘트에서 설명드리겠습니다! (여기 메서드 자체에서 setCaretPosition 유틸함수를 쓰더라도 실제로 렌더링이 제대로 되지 않더라구요! )
Suggested change
};
// ✅ 추가
const findBlock = (linkedList: BlockLinkedList, index: number) => {
if (index < 0) return null;
if (index >= linkedList.spread().length) return null;
return linkedList.findByIndex(index);
};
const handleDeleteSelect = (blockId: BlockId) => {
const currentIndex = editorCRDT.LinkedList.spread().findIndex((block) =>
block.id.equals(blockId),
);
sendBlockDeleteOperation(editorCRDT.localDelete(currentIndex, undefined, pageId));
// 삭제할 블록이 현재 활성화된 블록인 경우
if (editorCRDT.currentBlock?.id.equals(blockId)) {
// 다음 블록이나 이전 블록으로 currentBlock 설정
const nextBlock = findBlock(editorCRDT.LinkedList, currentIndex); // ✅ 이미 삭제한 후라, next는 currentIndex
const prevBlock = findBlock(editorCRDT.LinkedList, currentIndex - 1); // ✅ 이미 삭제한 후라, prev는 currentIndex - 1
editorCRDT.currentBlock = nextBlock || prevBlock || null;
}
setEditorState({
clock: editorCRDT.clock,
linkedList: editorCRDT.LinkedList,
});
};```

linkedList: editorCRDT.current.LinkedList,
position: editorCRDT.current.currentBlock?.crdt.currentCaret,
});
}, [editorCRDT.current.currentBlock?.crdt.read().length]);
Copy link
Member

@pipisebastian pipisebastian Nov 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2. 옵션창 -> 삭제버튼 누를시, 캐럿이 제대로 이동하지 않는 문제

  • 다음과 같이 변경해줘야합니다. 해당 useLayoutEffect가 도는 조건은, length가 바뀔때라서, 만약 1번째 블록이 aa, 2번째 블록이 bb면 length가 같다고해서 삭제버튼을 누르더라도 캐럿이 이동하지 않더라구요!
  • 그렇다고 crdt의 value값을 의존성으로 넣는다고 해도, 1번째 블록이 aa, 2번째 블록이 aa면 동작하지 않더라구요..
  • 그래서 id 자체를 의존성으로 넣어줬더니 잘 동작했습니다.
Suggested change
}, [editorCRDT.current.currentBlock?.crdt.read().length]);
}, [editorCRDT.current.currentBlock?.id.serialize()]);
2024-11-23.5.50.39.mov

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제가 branch 만들때 블록 옵션 기능이 없었어서 이부분은 확인하지 못했던 것 같습니다. 현재 작업중인 텍스트 옵션 pr 올릴때 피드백 주신 부분 반영해서 올리겠습니다!

Comment on lines +111 to +118
editorCRDT.current.currentBlock!.crdt.currentCaret = caretPosition;
requestAnimationFrame(() => {
setCaretPosition({
blockId: block.id,
linkedList: editorCRDT.current.LinkedList,
position: caretPosition,
});
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 해당 로직들이 필요한 이유가 있을까요? 이게 없으면 어떤 부분이 동작하지 않는걸까요??
이 부분을 다 지워봤을때, 특별히 동작이 안되던 부분이 없어서 질문드립니당!

          requestAnimationFrame(() => {
            setCaretPosition({
              blockId: block.id,
              linkedList: editorCRDT.current.LinkedList,
              position: caretPosition,
            });
          });


export const addNewBlockButton = css({
display: "flex",
gap: "spacing.sm",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

후후 요거 그냥 "sm"해도 동작될겁니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

앗 수정하겠습니다!

requestAnimationFrame 없어도 동작하는군요! 이곳저곳 수정하면서 디버깅 하고 되나 안되나 확인하는 식으로 구현해서 중복되는 코드들이 많네요... 이부분도 반영해서 수정하겠습니다!

Copy link
Collaborator

@hyonun321 hyonun321 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고하셨습니다 !

linkedList: editorCRDT.current.LinkedList,
position: caretPosition,
});
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고하셨습니다!!!

requestAnimationFram이 뭔가 보니, 이전의 프레임 렌더가 다 끝나고 실행하는 함수 같더라구요.
그렇다면 캐럿 연산이 다 끝나고 저 setCaretPosition이 실행된다고 볼 수 있겠네요!

이 중복되는 코드는

const updateEditorState = () => {
        editorCRDT.current.currentBlock!.crdt.currentCaret = caretPosition;
        requestAnimationFrame(() => {
          setCaretPosition({
            blockId: block.id,
            linkedList: editorCRDT.current.LinkedList,
            position: caretPosition,
          });
        });

로 줄여서

updateEditorState()

로 사용해도 되지 않을까.. 싶습니다 !

if (!selection) return false;

const blockElements = Array.from(
document.querySelectorAll('.d_flex.pos_relative.w_full[data-group="true"]'),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이부분이 브라우저의 에디터 요소를 찾는 구문이군요..
기존에 key입력마다 처리하는 방법에서 브라우저를 활용한다면 더욱 편하게 관리할 수 있을 듯 합니다.
다만 몇개 버그를 찾은 것 같습니다!

12321.mp4

위와같은 현상은 아직 page별로 캐럿이 제대로 관리되지 않아서 생기는 현상일까요?

아마 이전에 focus된 캐럿으로 다른 page를 눌러도 새로운 page로 캐럿위치가 업데이트 되지 않는것 같군요..

currentBlock.crdt.currentCaret -= 1;
} else {
currentBlock.crdt.currentCaret += 1;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이전에 오른쪽에 요소가 없을때 오른쪽 화살표키를 누르면 에러가 났는데 이젠 해결이 됐군요..! 😊

@github-actions github-actions bot merged commit 8e2f567 into dev Nov 25, 2024
4 checks passed
Copy link
Member

@pipisebastian pipisebastian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨씁니다!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug 기능 또는 UI에서 발생한 오류나 예상치 못한 동작을 해결 FE 프론트엔드 작업 Feat 새로운 기능 추가나 기존 기능 확장 작업
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants