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/#240 페이지 반응형 구현 #251

Merged
merged 10 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion client/src/components/button/IconButton.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { cva } from "@styled-system/css";
export const iconButtonContainer = cva({
base: {
display: "flex",

flexShrink: 1,
justifyContent: "center",
alignItems: "center",
borderRadius: "xs",
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/button/textButton.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const textButton = cva({
base: {
borderRadius: "md",
width: "50%",
height: "40px",
paddingY: "4px",
cursor: "pointer",
},
variants: {
Expand Down
1 change: 1 addition & 0 deletions client/src/components/sidebar/Sidebar.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const plusIconBox = css({
position: "absolute",
bottom: "0px",
gap: "md",
flexShrink: 1,
justifyContent: "space-between",
alignItems: "center",
width: "100%",
Expand Down
10 changes: 10 additions & 0 deletions client/src/features/auth/AuthButton.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { css } from "@styled-system/css";

export const container = css({
display: "flex",
gap: "md",
flexGrow: 1,
justifyContent: "end",
alignItems: "center",
width: "auto",
});
9 changes: 5 additions & 4 deletions client/src/features/auth/AuthButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { TextButton } from "@components/button/textButton";
import { Modal } from "@components/modal/modal";
import { useModal } from "@components/modal/useModal";
import { AuthModal } from "./AuthModal";
import { container } from "./AuthButton.style";

export const AuthButton = () => {
const isLogin = useCheckLogin();
Expand All @@ -23,13 +24,13 @@ export const AuthButton = () => {
const { mutate: logout } = useLogoutMutation(closeLogoutModal);

return (
<>
<div className={container}>
{isLogin ? (
<TextButton variant="secondary" onClick={openLogoutModal}>
<TextButton onClick={openLogoutModal} variant="secondary">
로그아웃
</TextButton>
) : (
<TextButton variant="secondary" onClick={openAuthModal}>
<TextButton onClick={openAuthModal} variant="secondary">
로그인
</TextButton>
)}
Expand All @@ -44,6 +45,6 @@ export const AuthButton = () => {
>
<p>로그아웃 하시겠습니까?</p>
</Modal>
</>
</div>
);
};
5 changes: 4 additions & 1 deletion client/src/features/page/Page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ export const Page = ({
handleTitleChange,
serializedEditorData,
}: PageProps) => {
const { position, size, pageDrag, pageResize, pageMinimize, pageMaximize } = usePage({ x, y });
const { position, size, isMaximized, pageDrag, pageResize, pageMinimize, pageMaximize } = usePage(
{ x, y },
);
const [serializedEditorDatas, setSerializedEditorDatas] =
useState<serializedEditorDataProps | null>(serializedEditorData);

Expand Down Expand Up @@ -74,6 +76,7 @@ export const Page = ({
<div className={pageHeader} onPointerDown={pageDrag} onClick={handlePageClick}>
<PageTitle title={title} icon={icon} />
<PageControlButton
isMaximized={isMaximized}
onPageClose={() => handlePageClose(id)}
onPageMaximize={pageMaximize}
onPageMinimize={pageMinimize}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ export const pageControlButton = cva({
width: "20px",
height: "20px",
cursor: "pointer",
"&:disabled": {
background: "gray.400",
opacity: 0.5,
cursor: "not-allowed",
},
},
variants: {
color: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,25 @@ import MinusIcon from "@assets/icons/minus.svg?react";
import { pageControlContainer, pageControlButton, iconBox } from "./PageControlButton.style";

interface PageControlButtonProps {
isMaximized: boolean;
onPageMinimize?: () => void;
onPageMaximize?: () => void;
onPageClose?: () => void;
}

export const PageControlButton = ({
isMaximized,
onPageMinimize,
onPageMaximize,
onPageClose,
}: PageControlButtonProps) => {
return (
<div className={pageControlContainer}>
<button className={pageControlButton({ color: "yellow" })} onClick={onPageMinimize}>
<button
className={pageControlButton({ color: "yellow" })}
onClick={onPageMinimize}
disabled={isMaximized}
>
<MinusIcon className={iconBox} />
</button>
<button className={pageControlButton({ color: "green" })} onClick={onPageMaximize}>
Expand Down
148 changes: 118 additions & 30 deletions client/src/features/page/hooks/usePage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,42 +18,24 @@ export const DIRECTIONS = [

type Direction = (typeof DIRECTIONS)[number];

// 만약 maximize 상태면, 화면이 커질때도 꽉 촤게 해줘야함.
export const usePage = ({ x, y }: Position) => {
const [position, setPosition] = useState<Position>({ x, y });
const [size, setSize] = useState<Size>({
width: PAGE.WIDTH,
height: PAGE.HEIGHT,
});
const [prevPosition, setPrevPosition] = useState<Position>({ x, y });
const [prevSize, setPrevSize] = useState<Size>({
width: PAGE.WIDTH,
height: PAGE.HEIGHT,
});

const [isMaximized, setIsMaximized] = useState(false);
const isSidebarOpen = useIsSidebarOpen();

const getSidebarWidth = () => (isSidebarOpen ? SIDE_BAR.WIDTH : SIDE_BAR.MIN_WIDTH);

useEffect(() => {
// x 범위 넘어가면 x 위치 조정
const sidebarWidth = getSidebarWidth();
if (position.x > window.innerWidth - size.width - sidebarWidth - PADDING) {
// 만약 최대화 상태라면(사이드바 열었을때, 사이드바가 화면을 가린다면), 포지션 0으로 바꾸고 width도 재조정
// 만약 최대화가 아니라면, 포지션만 조정하고, 사이즈는 그대로
if (size.width > window.innerWidth - sidebarWidth - PADDING) {
setPosition({ x: 0, y: position.y });
setSize({
width: window.innerWidth - sidebarWidth - PADDING,
height: size.height,
});
} else {
setPosition({
x: position.x - sidebarWidth + PADDING,
y: position.y,
});
setSize({
width: size.width,
height: size.height,
});
}
}
}, [isSidebarOpen]);

const pageDrag = (e: React.PointerEvent) => {
e.preventDefault();
const startX = e.clientX - position.x;
Expand Down Expand Up @@ -210,19 +192,125 @@ export const usePage = ({ x, y }: Position) => {
};

const pageMaximize = () => {
setPosition({ x: 0, y: 0 });
setSize({
width: window.innerWidth - getSidebarWidth() - PADDING,
height: window.innerHeight - PADDING,
});
if (isMaximized) {
// 최대화가 된 상태에서 다시 최대화 버튼을 누르면, 원래 위치, 크기로 돌아가야함.
setPosition(prevPosition);
setSize(prevSize);
setIsMaximized(false);
} else {
// 최대화할시, 추후 이전 상태로 돌아가기 위해 prev 위치,크기 저장
setPrevPosition({ ...position });
setPrevSize({ ...size });
setPosition({ x: 0, y: 0 });
setSize({
width: window.innerWidth - getSidebarWidth() - PADDING,
height: window.innerHeight - PADDING,
});
setIsMaximized(true);
}
};

useEffect(() => {
if (isMaximized) {
setSize({
width: window.innerWidth - getSidebarWidth() - PADDING,
height: window.innerHeight - PADDING,
});
}
}, [isSidebarOpen]);

const adjustPageToWindow = () => {
const maxWidth = window.innerWidth - getSidebarWidth() - PADDING;
const maxHeight = window.innerHeight - PADDING;

let newWidth = Math.min(size.width, maxWidth);
let newHeight = Math.min(size.height, maxHeight);

// 최소 크기 보장
newWidth = Math.max(PAGE.MIN_WIDTH, newWidth);
newHeight = Math.max(PAGE.MIN_HEIGHT, newHeight);

// 새로운 위치 계산
let newX = position.x;
let newY = position.y;

// 오른쪽 경계를 벗어나는 경우
if (newX + newWidth > maxWidth) {
newX = Math.max(0, maxWidth - newWidth);
}

// 아래쪽 경계를 벗어나는 경우
if (newY + newHeight > maxHeight) {
newY = Math.max(0, maxHeight - newHeight);
}

// 크기나 위치가 변경된 경우에만 상태 업데이트
if (
newWidth !== size.width ||
newHeight !== size.height ||
newX !== position.x ||
newY !== position.y
) {
setSize({ width: newWidth, height: newHeight });
setPosition({ x: newX, y: newY });
}
};

// maximize 상태일 때의 resize 처리
useEffect(() => {
if (!isMaximized) return;

let timeoutId: NodeJS.Timeout;
const handleMaximizedResize = () => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
const newWidth = window.innerWidth - getSidebarWidth() - PADDING;
const newHeight = window.innerHeight - PADDING;

// 실제로 크기가 변경될 때만 update
if (size.width !== newWidth || size.height !== newHeight) {
setSize({ width: newWidth, height: newHeight });
}
}, 100);
};

window.addEventListener("resize", handleMaximizedResize);
handleMaximizedResize();

return () => {
window.removeEventListener("resize", handleMaximizedResize);
clearTimeout(timeoutId);
};
}, [isMaximized, isSidebarOpen]); // maximize 상태와 sidebar 상태만 의존성

// 일반 상태일 때의 resize 처리
useEffect(() => {
if (isMaximized) return;

let timeoutId: NodeJS.Timeout;
const handleNormalResize = () => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
adjustPageToWindow();
}, 100);
};

window.addEventListener("resize", handleNormalResize);
handleNormalResize();

return () => {
window.removeEventListener("resize", handleNormalResize);
clearTimeout(timeoutId);
};
}, [position, size, isSidebarOpen]);

return {
position,
size,
pageDrag,
pageResize,
pageMinimize,
pageMaximize,
isMaximized,
};
};
3 changes: 3 additions & 0 deletions client/src/features/workSpace/WorkSpace.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ export const content = css({
export const workSpaceContainer = cva({
base: {
display: "flex",
width: "100vw",
height: "100vh",
overflow: "hidden",
transition: "opacity 0.3s ease-in-out",
},
variants: {
Expand Down
4 changes: 2 additions & 2 deletions client/src/features/workSpace/WorkSpace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const WorkSpace = () => {
closePage,
updatePage,
initPages,
initPagePosition,
// initPagePosition,
openPage,
} = usePagesManage(workspace, clientId);
const visiblePages = pages.filter((page) => page.isVisible && page.isLoaded);
Expand All @@ -36,7 +36,7 @@ export const WorkSpace = () => {
setWorkspace(newWorkspace);

initPages(newWorkspace.pageList);
initPagePosition();
// initPagePosition();
}
}, [workspaceMetadata]);

Expand Down
13 changes: 0 additions & 13 deletions client/src/features/workSpace/hooks/usePagesManage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,17 +212,6 @@ export const usePagesManage = (workspace: WorkSpace | null, clientId: number | n
}
};

// 서버에서 처음 불러올때는 좌표를 모르기에, 초기화 과정 필요
const initPagePosition = () => {
setPages((prevPages) =>
prevPages.map((page, index) => ({
...page,
x: PAGE_OFFSET * index,
y: PAGE_OFFSET * index,
})),
);
};

const initPages = (list: CRDTPage[]) => {
const pageList: Page[] = list.map(
(crdtPage, index) =>
Expand All @@ -244,7 +233,6 @@ export const usePagesManage = (workspace: WorkSpace | null, clientId: number | n

useEffect(() => {
initPages([]);
initPagePosition();
}, []);

return {
Expand All @@ -256,6 +244,5 @@ export const usePagesManage = (workspace: WorkSpace | null, clientId: number | n
updatePageData,
updatePage,
initPages,
initPagePosition,
};
};
1 change: 1 addition & 0 deletions client/src/styles/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const globalStyles = defineGlobalStyles({
backgroundImage: 'url("./assets/images/background.png")',
backgroundSize: "cover",
fontFamily: "Pretendard, sans-serif",
boxSizing: "border-box",
},
// 스크롤바 전체
"::-webkit-scrollbar": {
Expand Down
Loading