Skip to content

Commit d50e7e8

Browse files
committed
refactor: 페이지 리사이징할때 스켈레톤 ui 제거
#36
1 parent 606644f commit d50e7e8

File tree

3 files changed

+136
-90
lines changed

3 files changed

+136
-90
lines changed

client/src/features/editor/Editor.tsx

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ export interface EditorStateProps {
2525

2626
interface EditorProps {
2727
testKey: string;
28-
isResizing: boolean;
2928
onTitleChange: (title: string, syncWithServer: boolean) => void;
3029
pageId: string;
3130
serializedEditorData: serializedEditorDataProps;
@@ -148,21 +147,6 @@ export const Editor = memo(
148147
isLocalChange,
149148
});
150149

151-
const handleTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
152-
const newTitle = e.target.value;
153-
setDisplayTitle(newTitle); // 로컬 상태 업데이트
154-
onTitleChange(newTitle, false); // 낙관적 업데이트
155-
};
156-
157-
const handleBlur = (e: React.ChangeEvent<HTMLInputElement>) => {
158-
const newTitle = e.target.value;
159-
if (newTitle === "") {
160-
setDisplayTitle(""); // 입력이 비어있으면 로컬상태는 빈 문자열로
161-
} else {
162-
onTitleChange(newTitle, true);
163-
}
164-
};
165-
166150
const handleCompositionStart = (
167151
e: React.CompositionEvent<HTMLDivElement>,
168152
block: CRDTBlock,
@@ -284,7 +268,7 @@ export const Editor = memo(
284268
count: editorState.linkedList.spread().length,
285269
getScrollElement: () => editorRef.current,
286270
estimateSize: () => 24,
287-
overscan: 5,
271+
overscan: 3,
288272
});
289273

290274
useEffect(() => {
@@ -376,16 +360,6 @@ export const Editor = memo(
376360
}
377361
return (
378362
<div data-testid={`editor-${testKey}`} className={editorContainer} ref={editorRef}>
379-
<input
380-
data-testid={`editorTitle-${testKey}`}
381-
type="text"
382-
placeholder="제목을 입력하세요..."
383-
onChange={handleTitleChange}
384-
onBlur={handleBlur}
385-
value={displayTitle}
386-
className={editorTitle}
387-
/>
388-
<div style={{ height: "36px" }}></div>
389363
<div
390364
style={{
391365
height: virtualizer.getTotalSize(),
@@ -454,13 +428,6 @@ export const Editor = memo(
454428
</div>
455429
);
456430
},
457-
(prev, next) => {
458-
// 리사이징 중에는 항상 리렌더링을 방지
459-
if (prev.isResizing) return true;
460-
461-
// 일반적인 상황에서는 serializedEditorData가 변경될 때만 리렌더링
462-
return false;
463-
},
464431
);
465432

466433
Editor.displayName = "Editor";

client/src/features/page/Page.tsx

Lines changed: 11 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@ import { PageIconType, serializedEditorDataProps } from "@noctaCrdt/types/Interf
22
import { motion, AnimatePresence } from "framer-motion";
33
import { useEffect, useState } from "react";
44
import { Editor } from "@features/editor/Editor";
5-
import { Page as PageType, DIRECTIONS, Direction } from "@src/types/page";
5+
import { Page as PageType, DIRECTIONS } from "@src/types/page";
66
import { pageContainer, pageHeader, resizeHandles } from "./Page.style";
77
import { PageControlButton } from "./components/PageControlButton/PageControlButton";
8-
import { PageSkeletonUI } from "./components/PageSkeletonUI/PageSkeletonUI";
9-
import { useSkeletonPage } from "./components/PageSkeletonUI/useSkeletonPage";
108
import { PageTitle } from "./components/PageTitle/PageTitle";
119
import { usePage } from "./hooks/usePage";
1210

@@ -36,24 +34,8 @@ export const Page = ({
3634
handleTitleChange,
3735
serializedEditorData,
3836
}: PageProps) => {
39-
const {
40-
position,
41-
size,
42-
isMaximized,
43-
pageDrag,
44-
pageMinimize,
45-
pageMaximize,
46-
isSidebarOpen,
47-
handleResizeComplete,
48-
} = usePage({ x, y });
49-
50-
const { isResizing, skeletonPosition, skeletonSize, handleSkeletonResizeStart } = useSkeletonPage(
51-
{
52-
initialPosition: position,
53-
initialSize: size,
54-
isSidebarOpen,
55-
onApply: handleResizeComplete,
56-
},
37+
const { position, size, isMaximized, pageResize, pageDrag, pageMinimize, pageMaximize } = usePage(
38+
{ x, y },
5739
);
5840

5941
const [serializedEditorDatas, setSerializedEditorDatas] =
@@ -73,11 +55,6 @@ export const Page = ({
7355
}
7456
};
7557

76-
const handleResizeStart = (e: React.MouseEvent, direction: Direction) => {
77-
e.preventDefault();
78-
// 스켈레톤 UI의 리사이징을 시작
79-
handleSkeletonResizeStart(e, direction);
80-
};
8158
// serializedEditorData prop이 변경되면 local state도 업데이트
8259
useEffect(() => {
8360
setSerializedEditorDatas(serializedEditorData);
@@ -96,16 +73,18 @@ export const Page = ({
9673
style={{
9774
width: `${size.width}px`,
9875
height: `${size.height}px`,
99-
transform: isResizing
100-
? "scale(0)"
101-
: `scale(1) translate(${position.x}px, ${position.y}px)`,
76+
transform: `translate(${position.x}px, ${position.y}px)`,
10277
zIndex,
103-
visibility: isResizing ? "hidden" : "visible",
10478
}}
10579
onPointerDown={handlePageClick}
10680
>
10781
<div className={pageHeader} onPointerDown={pageDrag} onClick={handlePageClick}>
108-
<PageTitle testKey={testKey.split("-")[1]} title={title} icon={icon} />
82+
<PageTitle
83+
testKey={testKey.split("-")[1]}
84+
title={title}
85+
icon={icon}
86+
onTitleChange={onTitleChange}
87+
/>
10988
<PageControlButton
11089
testKey={testKey.split("-")[1]}
11190
isMaximized={isMaximized}
@@ -115,7 +94,6 @@ export const Page = ({
11594
/>
11695
</div>
11796
<Editor
118-
isResizing={isResizing}
11997
testKey={`${testKey.split("-")[1]}`}
12098
onTitleChange={onTitleChange}
12199
pageId={id}
@@ -126,26 +104,11 @@ export const Page = ({
126104
<motion.div
127105
key={direction}
128106
className={resizeHandles[direction]}
129-
onMouseDown={(e) => handleResizeStart(e, direction)}
107+
onMouseDown={(e) => pageResize(e, direction)}
130108
/>
131109
))}
132110
</div>
133111
</motion.div>
134-
{isResizing && (
135-
<motion.div key={`skeleton-${id}`}>
136-
<PageSkeletonUI
137-
id={`${id}-skeleton`}
138-
title={title}
139-
icon={icon}
140-
testKey={`${testKey}-skeleton`}
141-
position={skeletonPosition}
142-
size={skeletonSize}
143-
zIndex={zIndex}
144-
onDragStart={pageDrag}
145-
onResizeStart={handleSkeletonResizeStart}
146-
/>
147-
</motion.div>
148-
)}
149112
</AnimatePresence>
150113
);
151114
};

client/src/features/page/hooks/usePage.ts

Lines changed: 124 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useEffect, useState } from "react";
22
import { PAGE, SIDE_BAR } from "@constants/size";
33
import { SPACING } from "@constants/spacing";
4-
import { Position, Size } from "@src/types/page";
4+
import { Position, Size, Direction } from "@src/types/page";
55
import { useIsSidebarOpen } from "@stores/useSidebarStore";
66

77
const PADDING = SPACING.MEDIUM * 2;
@@ -50,6 +50,128 @@ export const usePage = ({ x, y }: Position) => {
5050
document.addEventListener("pointerup", handleDragEnd);
5151
};
5252

53+
const pageResize = (e: React.MouseEvent, direction: Direction) => {
54+
e.preventDefault();
55+
const startX = e.clientX;
56+
const startY = e.clientY;
57+
const startWidth = size.width;
58+
const startHeight = size.height;
59+
const startPosition = { x: position.x, y: position.y };
60+
61+
const resize = (e: MouseEvent) => {
62+
const deltaX = e.clientX - startX;
63+
const deltaY = e.clientY - startY;
64+
65+
let newWidth = startWidth;
66+
let newHeight = startHeight;
67+
let newX = startPosition.x;
68+
let newY = startPosition.y;
69+
70+
switch (direction) {
71+
case "right": {
72+
newWidth = Math.min(
73+
window.innerWidth - startPosition.x - getSidebarWidth() - PADDING,
74+
Math.max(PAGE.MIN_WIDTH, startWidth + deltaX),
75+
);
76+
break;
77+
}
78+
79+
case "left": {
80+
newWidth = Math.min(
81+
startPosition.x + startWidth,
82+
Math.max(PAGE.MIN_WIDTH, startWidth - deltaX),
83+
);
84+
newX = Math.max(0, startPosition.x + startWidth - newWidth);
85+
break;
86+
}
87+
88+
case "bottom": {
89+
newHeight = Math.min(
90+
window.innerHeight - startPosition.y - PADDING,
91+
Math.max(PAGE.MIN_HEIGHT, startHeight + deltaY),
92+
);
93+
break;
94+
}
95+
96+
case "top": {
97+
newHeight = Math.min(
98+
startPosition.y + startHeight,
99+
Math.max(PAGE.MIN_HEIGHT, startHeight - deltaY),
100+
);
101+
newY = Math.max(0, startPosition.y + startHeight - newHeight);
102+
break;
103+
}
104+
105+
case "topLeft": {
106+
newHeight = Math.min(
107+
startPosition.y + startHeight,
108+
Math.max(PAGE.MIN_HEIGHT, startHeight - deltaY),
109+
);
110+
newY = Math.max(0, startPosition.y + startHeight - newHeight);
111+
112+
newWidth = Math.min(
113+
startPosition.x + startWidth,
114+
Math.max(PAGE.MIN_WIDTH, startWidth - deltaX),
115+
);
116+
newX = Math.max(0, startPosition.x + startWidth - newWidth);
117+
break;
118+
}
119+
120+
case "topRight": {
121+
newHeight = Math.min(
122+
startPosition.y + startHeight,
123+
Math.max(PAGE.MIN_HEIGHT, startHeight - deltaY),
124+
);
125+
newY = Math.max(0, startPosition.y + startHeight - newHeight);
126+
127+
newWidth = Math.min(
128+
window.innerWidth - startPosition.x - getSidebarWidth() - PADDING,
129+
Math.max(PAGE.MIN_WIDTH, startWidth + deltaX),
130+
);
131+
break;
132+
}
133+
134+
case "bottomLeft": {
135+
newHeight = Math.min(
136+
window.innerHeight - startPosition.y - PADDING,
137+
Math.max(PAGE.MIN_HEIGHT, startHeight + deltaY),
138+
);
139+
140+
newWidth = Math.min(
141+
startPosition.x + startWidth,
142+
Math.max(PAGE.MIN_WIDTH, startWidth - deltaX),
143+
);
144+
newX = Math.max(0, startPosition.x + startWidth - newWidth);
145+
break;
146+
}
147+
148+
case "bottomRight": {
149+
newHeight = Math.min(
150+
window.innerHeight - startPosition.y - PADDING,
151+
Math.max(PAGE.MIN_HEIGHT, startHeight + deltaY),
152+
);
153+
154+
newWidth = Math.min(
155+
window.innerWidth - startPosition.x - getSidebarWidth() - PADDING,
156+
Math.max(PAGE.MIN_WIDTH, startWidth + deltaX),
157+
);
158+
break;
159+
}
160+
}
161+
162+
setSize({ width: newWidth, height: newHeight });
163+
setPosition({ x: newX, y: newY });
164+
};
165+
166+
const stopResize = () => {
167+
document.removeEventListener("mousemove", resize);
168+
document.removeEventListener("mouseup", stopResize);
169+
};
170+
171+
document.addEventListener("mousemove", resize);
172+
document.addEventListener("mouseup", stopResize);
173+
};
174+
53175
const pageMinimize = () => {
54176
setSize({
55177
width: PAGE.MIN_WIDTH,
@@ -149,11 +271,6 @@ export const usePage = ({ x, y }: Position) => {
149271
};
150272
}, [isMaximized, isSidebarOpen]); // maximize 상태와 sidebar 상태만 의존성
151273

152-
const handleResizeComplete = (newSize: Size, newPosition: Position) => {
153-
setSize(newSize);
154-
setPosition(newPosition);
155-
};
156-
157274
// 일반 상태일 때의 resize 처리
158275
useEffect(() => {
159276
if (isMaximized) return;
@@ -178,11 +295,10 @@ export const usePage = ({ x, y }: Position) => {
178295
return {
179296
position,
180297
size,
298+
pageResize,
181299
pageDrag,
182300
pageMinimize,
183301
pageMaximize,
184302
isMaximized,
185-
isSidebarOpen,
186-
handleResizeComplete,
187303
};
188304
};

0 commit comments

Comments
 (0)