-
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/#213 하위요소 블록 드래그앤드랍 구현 #226
Merged
The head ref may contain hidden characters: "Feature/#213_\uD558\uC704\uC694\uC18C_\uBE14\uB85D_\uB4DC\uB798\uADF8\uC564\uB4DC\uB78D_\uAD6C\uD604"
+337
−112
Merged
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
878c480
feat: indicator 스타일 추가
pipisebastian 39a8e08
feat: ul 아이콘 스타일 변경
pipisebastian 6be2141
feat: tab의 경우 부모요소의 +1 까지만 가능하도록 수정
pipisebastian 34bf566
feat: indicator 구현
pipisebastian 7d9b68b
feat: blockId 배열대신 id string 배열로 관리
pipisebastian f10ae2b
feat: handleDragEnd 구현
pipisebastian 6daeb57
refactor: 변수명 변경 및 함수 분리
pipisebastian 8289631
refactor: const로 변경
pipisebastian 06798c2
feat: handleDragStart 함수 구현
pipisebastian 468a7b6
refactor: type string 변경
pipisebastian 9d3fcdc
refactor: type string 변경
pipisebastian ad2aa42
refactor: indicator 다자인 수정
pipisebastian c6c0ae6
feat: backspace/shift tab 누를시 indent 감소 구현
pipisebastian e0a2745
feat: indent별 ul 아이콘 변경
pipisebastian b3f2f94
Merge branch 'dev' of https://github.com/boostcampwm-2024/web33-Nocta…
pipisebastian baab9fd
feat: 백스페이스 내용있을때도 indent 줄어들도록
pipisebastian b64288f
fix: 드래그 부모,자식관계가 제대로 업데이트 안되는 문제 해결
pipisebastian d7646d6
fix: prevBlock 존재여부 검사 추가
pipisebastian f67999e
feat: 하위요소까지 드래그 기능 구현
pipisebastian 5f2c92a
refactor: 안쓰는 css import 제거
pipisebastian 9a9d7e4
refactor: 기존코드 복구
pipisebastian File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,4 @@ | ||
import { useSortable } from "@dnd-kit/sortable"; | ||
import { CSS } from "@dnd-kit/utilities"; | ||
import { | ||
AnimationType, | ||
ElementType, | ||
|
@@ -20,11 +19,17 @@ import { MenuBlock } from "../MenuBlock/MenuBlock"; | |
import { TextOptionModal } from "../TextOptionModal/TextOptionModal"; | ||
import { TypeOptionModal } from "../TypeOptionModal/TypeOptionModal"; | ||
import { blockAnimation } from "./Block.animation"; | ||
import { textContainerStyle, blockContainerStyle, contentWrapperStyle } from "./Block.style"; | ||
import { | ||
textContainerStyle, | ||
blockContainerStyle, | ||
contentWrapperStyle, | ||
dropIndicatorStyle, | ||
} from "./Block.style"; | ||
|
||
interface BlockProps { | ||
id: string; | ||
block: CRDTBlock; | ||
dragBlockList: string[]; | ||
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. string 으로 바뀌게된 이유가 있을까요? |
||
isActive: boolean; | ||
onInput: (e: React.FormEvent<HTMLDivElement>, block: CRDTBlock) => void; | ||
onCompositionEnd: (e: React.CompositionEvent<HTMLDivElement>, block: CRDTBlock) => void; | ||
|
@@ -64,6 +69,7 @@ export const Block: React.FC<BlockProps> = memo( | |
({ | ||
id, | ||
block, | ||
dragBlockList, | ||
isActive, | ||
onInput, | ||
onCompositionEnd, | ||
|
@@ -83,13 +89,23 @@ export const Block: React.FC<BlockProps> = memo( | |
const { isOpen, openModal, closeModal } = useModal(); | ||
const [selectedNodes, setSelectedNodes] = useState<Array<Char> | null>(null); | ||
const { isAnimationStart } = useBlockAnimation(blockRef); | ||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ | ||
id, | ||
data: { | ||
type: "block", | ||
block, | ||
}, | ||
}); | ||
const { attributes, listeners, setNodeRef, isDragging, isOver, activeIndex, overIndex, data } = | ||
useSortable({ | ||
id, | ||
data: { | ||
id, | ||
type: "block", | ||
block, | ||
}, | ||
}); | ||
|
||
// 현재 드래그 중인 부모 블록의 indent 확인 | ||
const isChildOfDragging = dragBlockList.some((item) => item === data.id); | ||
|
||
// NOTE 드롭 인디케이터 위치 계산 | ||
// 현재 over 중인 블럭 위치 + 위/아래로 모두 인디케이터 표시 + 부모요소는 자식요소 내부로는 이동하지 못함 | ||
const showTopIndicator = isOver && !isChildOfDragging && activeIndex >= overIndex; | ||
const showBottomIndicator = isOver && !isChildOfDragging && activeIndex < overIndex; | ||
|
||
const [slashModalOpen, setSlashModalOpen] = useState(false); | ||
const [slashModalPosition, setSlashModalPosition] = useState({ top: 0, left: 0 }); | ||
|
@@ -214,6 +230,14 @@ export const Block: React.FC<BlockProps> = memo( | |
} | ||
}; | ||
|
||
const Indicator = () => ( | ||
<div | ||
className={dropIndicatorStyle({ | ||
indent: block.indent === 0 ? "first" : block.indent === 1 ? "second" : "third", | ||
})} | ||
/> | ||
); | ||
|
||
useEffect(() => { | ||
if (blockRef.current) { | ||
setInnerHTML({ element: blockRef.current, block }); | ||
|
@@ -223,66 +247,66 @@ export const Block: React.FC<BlockProps> = memo( | |
return ( | ||
// TODO: eslint 규칙을 수정해야 할까? | ||
// TODO: ol일때 index 순서 처리 | ||
<motion.div | ||
ref={setNodeRef} | ||
className={blockContainerStyle({ isActive })} | ||
style={{ | ||
transform: CSS.Transform.toString(transform), | ||
transition, | ||
opacity: isDragging ? 0.5 : undefined, | ||
}} | ||
initial={blockAnimation[block.animation || "none"].initial} | ||
animate={isAnimationStart && blockAnimation[block.animation || "none"].animate} | ||
data-group | ||
> | ||
<div style={{ position: "relative" }}> | ||
{showTopIndicator && <Indicator />} | ||
<motion.div | ||
className={contentWrapperStyle()} | ||
style={{ paddingLeft: `${block.indent * 12}px` }} | ||
ref={setNodeRef} | ||
className={blockContainerStyle({ isActive })} | ||
style={{ opacity: isDragging || isChildOfDragging ? 0.3 : undefined }} | ||
initial={blockAnimation[block.animation || "none"].initial} | ||
animate={isAnimationStart && blockAnimation[block.animation || "none"].animate} | ||
data-group | ||
> | ||
<MenuBlock | ||
attributes={attributes} | ||
listeners={listeners} | ||
onAnimationSelect={handleAnimationSelect} | ||
onTypeSelect={handleTypeSelect} | ||
onCopySelect={handleCopySelect} | ||
onDeleteSelect={handleDeleteSelect} | ||
<motion.div | ||
className={contentWrapperStyle()} | ||
style={{ paddingLeft: `${block.indent * 12}px` }} | ||
> | ||
<MenuBlock | ||
attributes={attributes} | ||
listeners={listeners} | ||
onAnimationSelect={handleAnimationSelect} | ||
onTypeSelect={handleTypeSelect} | ||
onCopySelect={handleCopySelect} | ||
onDeleteSelect={handleDeleteSelect} | ||
/> | ||
<IconBlock type={block.type} index={block.listIndex} indent={block.indent} /> | ||
<div | ||
ref={blockRef} | ||
onKeyDown={(e) => onKeyDown(e, blockRef.current, block)} | ||
onInput={handleInput} | ||
onClick={(e) => onClick(block.id, e)} | ||
onCopy={(e) => onCopy(e, blockRef.current, block)} | ||
onPaste={(e) => onPaste(e, blockRef.current, block)} | ||
onMouseUp={handleMouseUp} | ||
onCompositionEnd={(e) => onCompositionEnd(e, block)} | ||
contentEditable={block.type !== "hr"} | ||
spellCheck={false} | ||
suppressContentEditableWarning | ||
className={textContainerStyle({ | ||
type: block.type, | ||
})} | ||
/> | ||
</motion.div> | ||
<TextOptionModal | ||
selectedNodes={selectedNodes} | ||
isOpen={isOpen} | ||
onClose={closeModal} | ||
onBoldSelect={() => handleStyleSelect("bold")} | ||
onItalicSelect={() => handleStyleSelect("italic")} | ||
onUnderlineSelect={() => handleStyleSelect("underline")} | ||
onStrikeSelect={() => handleStyleSelect("strikethrough")} | ||
onTextColorSelect={handleTextColorSelect} | ||
onTextBackgroundColorSelect={handleTextBackgroundColorSelect} | ||
/> | ||
<IconBlock type={block.type} index={block.listIndex} /> | ||
<div | ||
ref={blockRef} | ||
onKeyDown={(e) => onKeyDown(e, blockRef.current, block)} | ||
onInput={handleInput} | ||
onClick={(e) => onClick(block.id, e)} | ||
onCopy={(e) => onCopy(e, blockRef.current, block)} | ||
onPaste={(e) => onPaste(e, blockRef.current, block)} | ||
onMouseUp={handleMouseUp} | ||
onCompositionEnd={(e) => onCompositionEnd(e, block)} | ||
contentEditable={block.type !== "hr"} | ||
spellCheck={false} | ||
suppressContentEditableWarning | ||
className={textContainerStyle({ | ||
type: block.type, | ||
})} | ||
<TypeOptionModal | ||
isOpen={slashModalOpen} | ||
onClose={() => setSlashModalOpen(false)} | ||
onTypeSelect={(type) => handleTypeSelect(type)} | ||
position={slashModalPosition} | ||
/> | ||
</motion.div> | ||
<TextOptionModal | ||
selectedNodes={selectedNodes} | ||
isOpen={isOpen} | ||
onClose={closeModal} | ||
onBoldSelect={() => handleStyleSelect("bold")} | ||
onItalicSelect={() => handleStyleSelect("italic")} | ||
onUnderlineSelect={() => handleStyleSelect("underline")} | ||
onStrikeSelect={() => handleStyleSelect("strikethrough")} | ||
onTextColorSelect={handleTextColorSelect} | ||
onTextBackgroundColorSelect={handleTextBackgroundColorSelect} | ||
/> | ||
<TypeOptionModal | ||
isOpen={slashModalOpen} | ||
onClose={() => setSlashModalOpen(false)} | ||
onTypeSelect={(type) => handleTypeSelect(type)} | ||
position={slashModalPosition} | ||
/> | ||
</motion.div> | ||
{showBottomIndicator && <Indicator />} | ||
</div> | ||
); | ||
}, | ||
); | ||
|
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
이게 밑으로 드래그했을때, 영상처럼 위에게 다 날라가는 원인이었습니다!
2024-11-28.4.49.09.mov
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.
아 이부분 관련해서 질문글을 쓰고있었는데.. ㅋㅋ 바로 답이 올라왔군요! 감사합니다.
저부분이 헤드에있던 녀석이 마지막으로 이동할때, 나머지 요소들이 재정렬될때 누락이 있었나보군요.. 대단하십니다 !!
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.
영상을 보시면 첫번째 block을 밑으로 드래그할때만 버그가 발생했습니다,
2,3번째 block은 버그가 발생하지 않았습니다.
이유는 첫번째 block은 prev가 없는 상태인데, 그걸 밑으로 드래그합니다.
LinkedList의 head가 여전히 첫 번째 블록을 가리키고 있기에 연결이 망가집니다.
그래서 prev가 없는 경우(= head인 경우), 현재 head의 next(
targetNode.next
)를 새로운 head로 업데이트해줘야합니다.