diff --git a/frontend/src/component/linedrawer/Linedrawer.tsx b/frontend/src/component/linedrawer/Linedrawer.tsx index 856743dc..ce2cce02 100644 --- a/frontend/src/component/linedrawer/Linedrawer.tsx +++ b/frontend/src/component/linedrawer/Linedrawer.tsx @@ -1,124 +1,124 @@ -import React, { useState, useRef } from 'react'; -import { useDrawing } from '@/hooks/useDrawing'; -import { usePanning } from '@/hooks/usePanning'; -import { useZoom } from '@/hooks/useZoom'; -import { MdArrowCircleLeft, MdArrowCircleRight } from 'react-icons/md'; -import { useUndoRedo } from '@/hooks/useUndoRedo'; -import { ButtonState } from '@/component/common/enums'; -import { useFloatingButton } from '@/hooks/useFloatingButton'; -import { FloatingButton } from '@/component/common/floatingbutton/FloatingButton'; - -interface IPoint { - x: number; - y: number; -} - -// 네이버 지도 기준 확대/축소 비율 단계 -const NAVER_STEP_SCALES = [ - 100, 100, 100, 100, 100, 100, 50, 30, 20, 10, 5, 3, 1, 0.5, 0.3, 0.1, 0.05, 0.03, 0.02, 0.01, - 0.005, -]; - -// 선의 굵기 상수 -const LINE_WIDTH = 2; -// 선의 색 상수 -const STROKE_STYLE = 'black'; -// 지도의 처음 확대/축소 비율 단계 index -const INITIAL_ZOOM_INDEX = 12; - -export const Linedrawer = () => { - const canvasRef = useRef(null); - const { points, addPoint, undo, redo, undoStack, redoStack } = useUndoRedo([]); - const [startPoint, setStartPoint] = useState(null); - const [endPoint, setEndPoint] = useState(null); - const { isMenuOpen, toolType, toggleMenu, handleMenuClick } = useFloatingButton(); - - const { draw, scaleRef, viewPosRef } = useDrawing({ - canvasRef, - points, - startPoint, - endPoint, - lineWidth: LINE_WIDTH, - strokeStyle: STROKE_STYLE, - initialScale: NAVER_STEP_SCALES[INITIAL_ZOOM_INDEX], - }); - - const { handleMouseMove, handleMouseDown, handleMouseUp } = usePanning({ viewPosRef, draw }); - const { handleWheel } = useZoom({ - scaleRef, - viewPosRef, - draw, - stepScales: NAVER_STEP_SCALES, - initialZoomIndex: INITIAL_ZOOM_INDEX, - }); - - const handleCanvasClick = (e: React.MouseEvent) => { - const rect = canvasRef.current?.getBoundingClientRect(); - if (!rect) return; - - const x = (e.clientX - rect.left - viewPosRef.current.x) / scaleRef.current; - const y = (e.clientY - rect.top - viewPosRef.current.y) / scaleRef.current; - - switch (toolType) { - case ButtonState.LINE_DRAWING: - addPoint({ x, y }); - break; - case ButtonState.START_MARKER: - setStartPoint({ x, y }); - break; - case ButtonState.DESTINATION_MARKER: - setEndPoint({ x, y }); - break; - default: - break; - } - - draw(); - }; - - return ( -
-
- - -
- -
- -
-
- ); -}; +// import React, { useState, useRef } from 'react'; +// import { useDrawing } from '@/hooks/useDrawing'; +// import { usePanning } from '@/hooks/usePanning'; +// import { useZoom } from '@/hooks/useZoom'; +// import { MdArrowCircleLeft, MdArrowCircleRight } from 'react-icons/md'; +// import { useUndoRedo } from '@/hooks/useUndoRedo'; +// import { ButtonState } from '@/component/common/enums'; +// import { useFloatingButton } from '@/hooks/useFloatingButton'; +// import { FloatingButton } from '@/component/common/floatingbutton/FloatingButton'; +// +// interface IPoint { +// x: number; +// y: number; +// } +// +// // 네이버 지도 기준 확대/축소 비율 단계 +// const NAVER_STEP_SCALES = [ +// 100, 100, 100, 100, 100, 100, 50, 30, 20, 10, 5, 3, 1, 0.5, 0.3, 0.1, 0.05, 0.03, 0.02, 0.01, +// 0.005, +// ]; +// +// // 선의 굵기 상수 +// const LINE_WIDTH = 2; +// // 선의 색 상수 +// const STROKE_STYLE = 'black'; +// // 지도의 처음 확대/축소 비율 단계 index +// const INITIAL_ZOOM_INDEX = 12; +// +// export const Linedrawer = () => { +// const canvasRef = useRef(null); +// const { points, addPoint, undo, redo, undoStack, redoStack } = useUndoRedo([]); +// const [startPoint, setStartPoint] = useState(null); +// const [endPoint, setEndPoint] = useState(null); +// const { isMenuOpen, toolType, toggleMenu, handleMenuClick } = useFloatingButton(); +// +// const { draw, scaleRef, viewPosRef } = useDrawing({ +// canvasRef, +// points, +// startPoint, +// endPoint, +// lineWidth: LINE_WIDTH, +// strokeStyle: STROKE_STYLE, +// initialScale: NAVER_STEP_SCALES[INITIAL_ZOOM_INDEX], +// }); +// +// const { handleMouseMove, handleMouseDown, handleMouseUp } = usePanning({ viewPosRef, draw }); +// const { handleWheel } = useZoom({ +// scaleRef, +// viewPosRef, +// draw, +// stepScales: NAVER_STEP_SCALES, +// initialZoomIndex: INITIAL_ZOOM_INDEX, +// }); +// +// const handleCanvasClick = (e: React.MouseEvent) => { +// const rect = canvasRef.current?.getBoundingClientRect(); +// if (!rect) return; +// +// const x = (e.clientX - rect.left - viewPosRef.current.x) / scaleRef.current; +// const y = (e.clientY - rect.top - viewPosRef.current.y) / scaleRef.current; +// +// switch (toolType) { +// case ButtonState.LINE_DRAWING: +// addPoint({ x, y }); +// break; +// case ButtonState.START_MARKER: +// setStartPoint({ x, y }); +// break; +// case ButtonState.DESTINATION_MARKER: +// setEndPoint({ x, y }); +// break; +// default: +// break; +// } +// +// draw(); +// }; +// +// return ( +//
+//
+// +// +//
+// +//
+// +//
+//
+// ); +// }; diff --git a/frontend/src/component/maps/Canvas.tsx b/frontend/src/component/maps/Canvas.tsx index 0d07fd8c..0c5cf884 100644 --- a/frontend/src/component/maps/Canvas.tsx +++ b/frontend/src/component/maps/Canvas.tsx @@ -1,18 +1,19 @@ import { useEffect, useState } from 'react'; import { MapCanvas } from '@/component/maps/MapCanvas.tsx'; import { DEFAULT_CENTER } from '@/lib/constants/mapConstants.ts'; +import { ICanvasScreenProps } from '@/lib/types/canvasInterface.ts'; -export const FullScreenMap = () => { +export const FullScreenMap = ({ width, height }: ICanvasScreenProps) => { const [windowSize, setWindowSize] = useState({ - width: window.innerWidth, - height: window.innerHeight, + width, + height, }); useEffect(() => { const handleResize = () => { setWindowSize({ - width: window.innerWidth, - height: window.innerHeight, + width, + height, }); }; @@ -43,13 +44,11 @@ export const FullScreenMap = () => { }, []); return ( -
- -
+ ); }; diff --git a/frontend/src/component/maps/MapCanvas.tsx b/frontend/src/component/maps/MapCanvas.tsx index 0d84bbe2..a4460fd7 100644 --- a/frontend/src/component/maps/MapCanvas.tsx +++ b/frontend/src/component/maps/MapCanvas.tsx @@ -11,6 +11,7 @@ import { STROKE_STYLE, } from '@/lib/constants/canvasConstants.ts'; import { ICanvasPoint, IMapCanvasProps, IPoint } from '@/lib/types/canvasInterface.ts'; +import { useUndoRedo } from '@/hooks/useUndoRedo.ts'; export const MapCanvas = ({ width, height, initialCenter, initialZoom }: IMapCanvasProps) => { const mapRef = useRef(null); @@ -21,7 +22,6 @@ export const MapCanvas = ({ width, height, initialCenter, initialZoom }: IMapCan const [startMarker, setStartMarker] = useState(null); const [endMarker, setEndMarker] = useState(null); - const [pathPoints, setPathPoints] = useState([]); const [isDragging, setIsDragging] = useState(false); const [dragStartPos, setDragStartPos] = useState<{ x: number; y: number }>({ x: 0, y: 0 }); @@ -32,10 +32,8 @@ export const MapCanvas = ({ width, height, initialCenter, initialZoom }: IMapCan const [touchStartDistance, setTouchStartDistance] = useState(null); const [touchCenter, setTouchCenter] = useState<{ x: number; y: number } | null>(null); - const [undoStack, setUndoStack] = useState([]); - const [redoStack, setRedoStack] = useState([]); - const { isMenuOpen, toolType, toggleMenu, handleMenuClick } = useFloatingButton(); + const { pathPoints, addPoint, undo, redo, undoStack, redoStack } = useUndoRedo([]); useEffect(() => { if (!mapRef.current) return; @@ -110,6 +108,7 @@ export const MapCanvas = ({ width, height, initialCenter, initialZoom }: IMapCan }; const redrawCanvas = () => { + console.log('Redraw'); if (!canvasRef.current || !map) return; const canvas = canvasRef.current; @@ -140,13 +139,13 @@ export const MapCanvas = ({ width, height, initialCenter, initialZoom }: IMapCan ctx.fill(); } } - if (pathPoints.length > 0) { + if (pathPoints?.length > 0) { ctx.beginPath(); const firstPoint = latLngToCanvasPoint(pathPoints[0]); if (firstPoint) { ctx.moveTo(firstPoint.x, firstPoint.y); - for (let i = 1; i < pathPoints.length; i++) { + for (let i = 1; i < pathPoints?.length; i++) { const point = latLngToCanvasPoint(pathPoints[i]); if (point) { ctx.lineTo(point.x, point.y); @@ -157,14 +156,6 @@ export const MapCanvas = ({ width, height, initialCenter, initialZoom }: IMapCan } }; - const addPoint = (point: IPoint) => { - setUndoStack(stack => [...stack, [...pathPoints, point]]); - setRedoStack([]); - setPathPoints(prev => { - return [...prev, point]; - }); - }; - const handleCanvasClick = (e: React.MouseEvent) => { if (!map || !canvasRef.current) return; const canvas = canvasRef.current; @@ -344,23 +335,23 @@ export const MapCanvas = ({ width, height, initialCenter, initialZoom }: IMapCan } }; - const undo = () => { - if (undoStack.length === 0) return; - const previousPoints = undoStack[undoStack.length - 2]; - setPathPoints(previousPoints); - setUndoStack(stack => stack.slice(0, -1)); - setRedoStack(stack => [...stack, pathPoints]); - redrawCanvas(); - }; - - const redo = () => { - if (redoStack.length === 0) return; - const nextPoints = redoStack[redoStack.length - 1]; - setPathPoints(nextPoints); - setRedoStack(stack => stack.slice(0, -1)); - setUndoStack(stack => [...stack, pathPoints]); - redrawCanvas(); - }; + // const undo = () => { + // if (undoStack.length === 0) return; + // const previousPoints = undoStack[undoStack.length - 2]; + // setPathPoints(previousPoints); + // setUndoStack(stack => stack.slice(0, -1)); + // setRedoStack(stack => [...stack, pathPoints]); + // redrawCanvas(); + // }; + // + // const redo = () => { + // if (redoStack.length === 0) return; + // const nextPoints = redoStack[redoStack.length - 1]; + // setPathPoints(nextPoints); + // setRedoStack(stack => stack.slice(0, -1)); + // setUndoStack(stack => [...stack, pathPoints]); + // redrawCanvas(); + // }; useEffect(() => { if (isDragging) { @@ -394,30 +385,32 @@ export const MapCanvas = ({ width, height, initialCenter, initialZoom }: IMapCan onTouchEnd={handleTouchEnd} >
-
- - -
+ {toolType === ButtonState.LINE_DRAWING ? ( +
+ + +
+ ) : null} -
+
{ - const [isMenuOpen, setIsMenuOpen] = useState(false); - const [toolType, setToolType] = useState(ButtonState.CLOSE); + const [isMenuOpen, setIsMenuOpen] = useState(true); + const [toolType, setToolType] = useState(ButtonState.OPEN); const toggleMenu = () => { setIsMenuOpen(prev => !prev); diff --git a/frontend/src/hooks/useUndoRedo.ts b/frontend/src/hooks/useUndoRedo.ts index a04caea7..4c110f30 100644 --- a/frontend/src/hooks/useUndoRedo.ts +++ b/frontend/src/hooks/useUndoRedo.ts @@ -1,34 +1,30 @@ import { useState } from 'react'; - -interface IPoint { - x: number; - y: number; -} +import { IPoint } from '@/lib/types/canvasInterface.ts'; export const useUndoRedo = (initialPoints: IPoint[]) => { - const [points, setPoints] = useState(initialPoints); + const [pathPoints, setPathPoints] = useState(initialPoints); const [undoStack, setUndoStack] = useState([]); const [redoStack, setRedoStack] = useState([]); const addPoint = (newPoint: IPoint) => { - setUndoStack(prev => [...prev, points]); - setPoints(prevPoints => [...prevPoints, newPoint]); + setUndoStack(prev => [...prev, pathPoints]); + setPathPoints(prevPoints => [...prevPoints, newPoint]); setRedoStack([]); }; const undo = () => { if (undoStack.length === 0) return; - setRedoStack(prev => [points, ...prev]); - setPoints(undoStack[undoStack.length - 1]); + setRedoStack(prev => [pathPoints, ...prev]); + setPathPoints(undoStack[undoStack.length - 1]); setUndoStack(undoStack.slice(0, -1)); }; const redo = () => { if (redoStack.length === 0) return; - setUndoStack(prev => [...prev, points]); - setPoints(redoStack[0]); + setUndoStack(prev => [...prev, pathPoints]); + setPathPoints(redoStack[0]); setRedoStack(redoStack.slice(1)); }; - return { points, addPoint, undo, redo, undoStack, redoStack }; + return { pathPoints, addPoint, undo, redo, undoStack, redoStack }; }; diff --git a/frontend/src/lib/types/canvasInterface.ts b/frontend/src/lib/types/canvasInterface.ts index 812cd04a..156d814e 100644 --- a/frontend/src/lib/types/canvasInterface.ts +++ b/frontend/src/lib/types/canvasInterface.ts @@ -14,3 +14,8 @@ export interface ICanvasPoint { x: number; y: number; } + +export interface ICanvasScreenProps { + width: number; + height: number; +} diff --git a/frontend/src/pages/DrawRoute.tsx b/frontend/src/pages/DrawRoute.tsx index 88b6781e..25a26183 100644 --- a/frontend/src/pages/DrawRoute.tsx +++ b/frontend/src/pages/DrawRoute.tsx @@ -1,8 +1,8 @@ -import { Linedrawer } from '@/component/linedrawer/Linedrawer'; import { useContext, useEffect } from 'react'; import { FooterContext } from '@/component/layout/footer/LayoutFooterProvider'; import { useParams } from 'react-router-dom'; import { UserContext } from '@/context/UserContext'; +import { FullScreenMap } from '@/component/maps/Canvas.tsx'; export const DrawRoute = () => { const { users, setUsers } = useContext(UserContext); @@ -64,7 +64,10 @@ export const DrawRoute = () => { Mock 데이터 초기화 - +
+ {/* TODO: 동율님 mock 데이터 관련 버튼 없애고 나서, height={window.innerHeight - 180} 으로 변경해주시면 됩니다! */} + +
); }; diff --git a/frontend/src/pages/Main.tsx b/frontend/src/pages/Main.tsx index 2c9f39ab..7e5f7639 100644 --- a/frontend/src/pages/Main.tsx +++ b/frontend/src/pages/Main.tsx @@ -1,72 +1,79 @@ -import { useContext, useEffect } from 'react'; +import { Fragment, useContext, useEffect, useState } from 'react'; import { MdFormatListBulleted } from 'react-icons/md'; import { FooterContext } from '@/component/layout/footer/LayoutFooterProvider'; import { useNavigate } from 'react-router-dom'; import { buttonActiveType } from '@/component/layout/enumTypes'; import { FullScreenMap } from '@/component/maps/Canvas.tsx'; +import { BottomSheet } from '@/component/bottomsheet/BottomSheet.tsx'; +import { Content } from '@/component/content/Content.tsx'; +import { loadLocalData, saveLocalData } from '@/utils/common/manageLocalData.ts'; +import { AppConfig } from '@/lib/constants/commonConstants.ts'; +import { v4 as uuidv4 } from 'uuid'; +import { getUserLocation } from '@/hooks/getUserLocation.ts'; -// const contentData = [ -// { -// id: '1', -// title: '아들네 집으로', -// time: '0시간 34분', -// person: 2, -// link: '/channel/123/guest/456', -// }, -// { -// id: '2', -// title: '손자네 집으로', -// time: '2시간 32분', -// person: 0, -// link: '/channel/123/guest/456', -// }, -// { -// id: '3', -// title: '마을회관으로', -// time: '0시간 12분', -// person: 1, -// link: '/channel/123/guest/456', -// }, -// ]; +const contentData = [ + { + id: '1', + title: '아들네 집으로', + time: '0시간 34분', + person: 2, + link: '/channel/123/guest/456', + }, + { + id: '2', + title: '손자네 집으로', + time: '2시간 32분', + person: 0, + link: '/channel/123/guest/456', + }, + { + id: '3', + title: '마을회관으로', + time: '0시간 12분', + person: 1, + link: '/channel/123/guest/456', + }, +]; export const Main = () => { const { setFooterTitle, setFooterTransparency, setFooterOnClick, setFooterActive } = useContext(FooterContext); + const { lat, lng, error } = getUserLocation(); + const [otherLocations, setOtherLocations] = useState([]); const navigate = useNavigate(); + const MIN_HEIGHT = 0.5; + const MAX_HEIGHT = 0.8; - // useEffect(() => { - // if (lat && lng) { - // if (!loadLocalData(AppConfig.KEYS.BROWSER_TOKEN)) { - // const token = uuidv4(); - // saveLocalData(AppConfig.KEYS.BROWSER_TOKEN, token); - // } - // const token = loadLocalData(AppConfig.KEYS.BROWSER_TOKEN); - // const ws = new WebSocket(`${AppConfig.SOCKET_SERVER}/?token=${token}`); - // - // // 초기 위치 전송 - // ws.onopen = () => { - // ws.send(JSON.stringify({ type: 'location', location: { lat, lng } })); - // }; - // - // ws.onmessage = event => { - // const data = JSON.parse(event.data); - // - // if (data.type === 'init') { - // // 기존 클라이언트들의 위치 초기화 - // setOtherLocations(data.clients); - // } else if (data.type === 'location' && data.token !== token) { - // // 새로 들어온 위치 업데이트 - // setOtherLocations(prev => - // prev.some(loc => loc.token === data.token) - // ? prev.map(loc => (loc.token === data.token ? data : loc)) - // : [...prev, data], - // ); - // } - // }; - // return () => ws.close(); - // } - // }, [lat, lng]); - + // eslint-disable-next-line consistent-return + useEffect(() => { + if (lat && lng) { + if (!loadLocalData(AppConfig.KEYS.BROWSER_TOKEN)) { + const token = uuidv4(); + saveLocalData(AppConfig.KEYS.BROWSER_TOKEN, token); + } + const token = loadLocalData(AppConfig.KEYS.BROWSER_TOKEN); + const ws = new WebSocket(`${AppConfig.SOCKET_SERVER}/?token=${token}`); + // 초기 위치 전송 + ws.onopen = () => { + ws.send(JSON.stringify({ type: 'location', location: { lat, lng } })); + }; + ws.onmessage = event => { + const data = JSON.parse(event.data); + if (data.type === 'init') { + // 기존 클라이언트들의 위치 초기화 + setOtherLocations(data.clients); + } else if (data.type === 'location' && data.token !== token) { + // 새로 들어온 위치 업데이트 + setOtherLocations(prev => + prev.some(loc => loc.token === data.token) + ? prev.map(loc => (loc.token === data.token ? data : loc)) + : [...prev, data], + ); + } + }; + return () => ws.close(); + } + }, [lat, lng]); const goToAddChannel = () => { navigate('/add-channel'); }; @@ -83,23 +90,37 @@ export const Main = () => { - - {/*
*/} +
+ {/* eslint-disable-next-line no-nested-ternary */} + {lat && lng ? ( + otherLocations ? ( + + ) : ( +
+ Loading map data... +
+ ) + ) : ( +
+ {error ? `Error: ${error}` : 'Loading'} +
+ )} +
- {/* */} - {/* {contentData.map(item => ( */} - {/* */} - {/* */} - {/*
*/} - {/*
*/} - {/* ))} */} - {/*
*/} + + {contentData.map(item => ( + + +
+
+ ))} +
); };