-
Notifications
You must be signed in to change notification settings - Fork 2
[FE] 화이트보드 캔버스 구현 #172
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
[FE] 화이트보드 캔버스 구현 #172
Conversation
- 15000x9000 드래그/줌 가능 캔버스 생성 - Zustand 상태 관리 - 마우스 휠 줌 인/아웃
- 현재 스케일 수준 표시 - 줌인 줌아웃 버튼 동작
- 캔버스 조작 및 윈도우 사이즈 파악 로직 훅으로 분리 - 캔버스 사이즈 변경(20000x20000) - 상수 분리
whiteboard/utils
- workspace 내부로 이동
- 텍스트, 화살표 타입 정의
- 캔버스 상태 관리 (zoom, position) - 텍스트, 화살표 추가 및 아이템 제거 - 선택/편집 상태 관리 - 레이어 순서 조정
- 크기 조절 및 회전 - 텍스트/도형별 앵커 설정 - 스케일 보정
- 더블클릭으로 편집 모드 진입 - ESC/외부 클릭으로 편집 종료 - 스타일 동기화 (폰트, 색상, 정렬 - 패널 버튼으로 화면 중앙에 생성
- 요소 렌더링 및 선택
- 중간 point 추출, point, 선분 거리 계산
- 양 끝 핸들로 위치 조작 - 중간 지점 더블클릭시 위치 이동 가능한 핸들 생성 및 클린하여 선택 후 백스페이스로 제거
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.
고생많으셨습니다 ~!! 잘 동작하네요.
캔버스 로직 깔끔하게 설명해주셔서 보는데 큰 도움이 되었습니다. konva의 달인이 되셨군요..ㅎㅎ
현재 화이트보드 도메인 내부에서만 사용되는 유틸 함수를 app/utils/에 위치시켜 두었는데, 도메인 한정 유틸의 경우 app/utils/ 와 app/src/components/whiteboard/utils/ 중 어느 위치가 더 적절한지에 대한 의견을 구하고 싶습니다. 마찬가지 도메인/컴포넌트 전용 hook / store는 어디에 위치하면 좋을지도 의견이 궁금합니다.
저는 작업중인 브랜치에선 app/utils/meeting/...ts => 이렇게 도메인별로 구분을 하긴 했습니다! hooks도 마찬가지로 /hooks/meeting/useHooks.ts 이런식으로 생각했어요.
개인적으로 src/utils/가 이미 있다면 이 안에 위치시키는 게 바람직하지 않나 생각이 들어요. 이것도 따로 논의한 적이 없어서 같이 얘기해봐도 좋을 것 같아요.
| import RenderItem from '@/components/whiteboard/items/RenderItem'; | ||
| import TextArea from '@/components/whiteboard/items/text/TextArea'; | ||
| import ItemTransformer from '@/components/whiteboard/controls/ItemTransformer'; | ||
| import ArrowHandles from '@/components/whiteboard/items/arrow/ArrowHandles'; |
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.
p5: 추후에 /items나 /controls에서 가져올 컴포넌트가 더 많아진다면 import line을 줄이기 위해 각 컴포넌트별로 export 모아두는 것도 좋을 것 같아요!
| if (selectedId && !isArrowSelected) { | ||
| const selectedNode = stage.findOne('#' + selectedId); | ||
| if (selectedNode) { | ||
| transformerRef.current.nodes([selectedNode]); | ||
| transformerRef.current.getLayer()?.batchDraw(); | ||
| } else { | ||
| transformerRef.current.nodes([]); | ||
| } | ||
| } else { | ||
| transformerRef.current.nodes([]); | ||
| } |
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.
p4: early-return 패턴을 시도해봐도 좋을 것 같아요!
| if (node.getClassName() === 'Text') { | ||
| node.width(node.width() * scaleX); | ||
| } |
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.
Text만 보정한 이유가 텍스트 배율에 따라 찌그러져서 그런 걸까요!? 일반 도형아이템은 해당 로직 없이도 잘 보정되나요?
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.
네 맞아요 리시가 언급한 대로 요소 박스(Transformer)를 늘려 크기 변화시 각 요쇼의 width, height가 변경되는게 아니라 그만큼의 scaleX / scaleY 확대 수준이 변화하게 되는데 텍스트의 경우는 박스를 늘려도 텍스트 자체 크기를 유지하도록 보정 로직을 추가하였습니다.
일반 도형 아이템은 해도 안해도 보이는 결과는 같긴하지만 높이 너비만 변화시키는게 바람직해보여 추후에 요소 추가시 보정 로직도 추가하면 좋지 않을까 생각하고 있어요.
| onDragEnd={(e) => { | ||
| const pos = e.target.position(); | ||
| const newPoints = arrowItem.points.map((p, i) => | ||
| i % 2 === 0 ? p + pos.x : p + pos.y | ||
| ); | ||
|
|
||
| e.target.position({ x: 0, y: 0 }); | ||
|
|
||
| onChange({ | ||
| points: newPoints, | ||
| }); | ||
|
|
||
| onDragEnd?.(); | ||
| }} |
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.
p4: 길어지는 로직은 함수로 따로 빼도 좋을 것 같아요~
| export default function ArrowHandles({ | ||
| arrow, | ||
| selectedHandleIndex, | ||
| onHandleClick, | ||
| onStartDrag, | ||
| onControlPointDrag, | ||
| onEndDrag, | ||
| }: ArrowHandlesProps) { |
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.
<ArrowHandles />안에서 useArrowHandles()를 불러 가져오지 않고, props를 받아 활용하는 방식을 선택하신 이유가 궁금합니다!
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.
일단 관련 상태나 로직을 Canvas에서 관리하고 있고 Canvas 내의 다른 기능(키보드 Delete 등)에서 접근해야 하는데 내부에서 훅을 호출하면 접근이 안돼서 props로 넘기는 방식을 사용했습니다.
seorang42
left a comment
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.
직접 사용해봤는데 생각보다 완성도 높게 구현되어 있네요!
사용해보며 느꼈던 몇 가지 의견 및 질문 드려요.
- 화살표의 클릭 범위를 조금 넓혀도 좋을 것 같아요. 화살표의 몸통 너비만큼만 클릭 범위로 설정되다보니, 화이트보드를 축소했을 경우 화살표를 클릭하여 옮기는게 조금 어려웠던 것 같아요. 투명한 범위를 설정해서 살짝 주변을 클릭해도 선택되게 하면 좋을 것 같아요.
- 캔버스 요소에 대한 복사/붙여넣기/잘라내기는 이 PR에서는 고려되지 않을 예정인지 궁금합니다!
- 커서 / 펜 / 지우개 등 사용자 커서 타입에 대한 활성화 여부 또한 이번 PR에서는 고려되지 않는 것인지 궁금합니다!
고생 많으셨습니다!
| // 텍스트 렌더링 | ||
| if (item.type === 'text') { | ||
| const textItem = item as TextItem; | ||
| return ( | ||
| <Text |
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.
P5: 나중에 타입이 많아지면 if문이 길어지지 않을까 생각이 드는데,
이후 함수로 분리 후, 객체를 사용해서 팩토리 패턴으로 컴포넌트를 렌더링해도 좋을 것 같아요!
| // 시작점과 끝점을 제외한 중간점들만 추출 | ||
| export function getControlPoints(points: number[]) { | ||
| const controlPoints = []; | ||
| for (let i = 2; i < points.length - 2; i += 2) { |
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.
P4: i = 2, i += 2가 어떤 의미를 가지는지 명확하지 않을 수 있겠다 생각이 들아요
의미를 가지는 상수로 정의해도 좋을 것 같다는 의견 드려요!
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.
넵 해당 부분 상수로 분리하였습니다!
- 렌더시 hitStrokeWidth={30} 추가
- COORDS_PER_POINT: 각 점을 표현하는데 필요한 좌표수 이차원이므로 (x,y) 두 개 - START_POINT_OFFSET: 건너뛸 시작점 인덱스 기준 0,1 건너 뜀 - END_POINT_OFFSET 건너뛸 끝 점 , 인덱스 기준 -1, -2 포함 안함
결론적으로 2,3 번의 경우 지금 pr 범위에서는 고려하지 않을 부분이었습니다!! |
폴리곤, 텍스트 패널 import 제거
🎯 이슈 번호
✅ 작업 내용
캔버스
텍스트
화살표
새로운 요소 추가 시 아래와 같은 방식으로 진행해 주시면 될 거 같습니다.
타입 정의 (types/whiteboard.ts)
스토어 액션 추가 (store/useCanvasStore.ts)
렌더링 로직 (items/RenderItem.tsx)
버튼 추가 및 좌표 변환 (sidebar/panels/*.tsx)
🤔 리뷰 요구사항
app/utils/와app/src/components/whiteboard/utils/중 어느 위치가 더 적절한지에 대한 의견을 구하고 싶습니다. 마찬가지 도메인/컴포넌트 전용 hook / store는 어디에 위치하면 좋을지도 의견이 궁금합니다.📸 스크린샷 (선택)