diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 59b20bf4..32698c10 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: - name: Run Build run: pnpm build # pnpm을 사용하여 빌드 실행 - continue-on-error: false # 빌드 실패 시 워크플로우 실패로 처리 +# continue-on-error: false # 빌드 실패 시 워크플로우 실패로 처리 - name: Run Tests run: pnpm test # pnpm을 사용하여 테스트 실행 diff --git a/frontend/src/component/canvas/Canvas.tsx b/frontend/src/component/canvas/Canvas.tsx index f1406b3c..c8f13db4 100644 --- a/frontend/src/component/canvas/Canvas.tsx +++ b/frontend/src/component/canvas/Canvas.tsx @@ -44,7 +44,7 @@ export interface ICanvasRefMethods { } export const Canvas = forwardRef((props, ref) => { - const canvasRef = useRef(null); + const canvasRef = useRef(null!); const { points, addPoint, undo, redo, undoStack, redoStack } = useUndoRedo([]); const [startPoint, setStartPoint] = useState(null); const [endPoint, setEndPoint] = useState(null); diff --git a/frontend/src/component/canvas/CanvasWithMap.tsx b/frontend/src/component/canvas/CanvasWithMap.tsx index a4718071..50dcebae 100644 --- a/frontend/src/component/canvas/CanvasWithMap.tsx +++ b/frontend/src/component/canvas/CanvasWithMap.tsx @@ -1,9 +1,9 @@ -import { Canvas, ICanvasRefMethods } from '@/component/canvas/Canvas.tsx'; -import { Map, IMapRefMethods } from '@/component/maps/Map.tsx'; -import classNames from 'classnames'; -import { ICanvasVertex } from '@/utils/screen/canvasUtils.ts'; -import { INaverMapVertexPosition } from '@/component/maps/naverMapUtils.ts'; import { useRef, useEffect, useState } from 'react'; +import classNames from 'classnames'; +import { Canvas, ICanvasRefMethods } from '@/component/canvas/Canvas.tsx'; +import { Map } from '@/component/maps/Map.tsx'; +import { IMapObject, IMapRefMethods } from '@/component/maps/Map.types.ts'; +import { useEventHandlers } from '@/component/canvas/useEventHandlers.tsx'; interface ICanvasWithMapProps { className?: string; @@ -14,32 +14,13 @@ interface ICanvasWithMapProps { allowCanvas?: boolean; } -interface IMouseEventState { - isMouseDown: boolean; - mouseDownPosition: { x: number; y: number }; - // mouseMovePosition: { x: number; y: number }; - mouseDeltaPosition: { x: number; y: number }; -} - -export interface ILocationObject { - canvas: ICanvasVertex; - map: INaverMapVertexPosition; -} - -const MouseEventStateInitialValue = { - isMouseDown: false, - mouseDownPosition: { x: 0, y: 0 }, - // mouseMovePosition: { x: 0, y: 0 }, - mouseDeltaPosition: { x: 0, y: 0 }, -}; - export const CanvasWithMap = (props: ICanvasWithMapProps) => { const mapRefMethods = useRef(null); - const mapElement = useRef(null); const canvasRefMethods = useRef(null); + const mapElement = useRef(null); const canvasElement = useRef(null); - const mouseEventState = useRef({ ...MouseEventStateInitialValue }); - const [mapObject, setMapObject] = useState(null); + + const [mapObject, setMapObject] = useState(null); useEffect(() => { if (canvasRefMethods.current?.getCanvasElement) @@ -50,46 +31,16 @@ export const CanvasWithMap = (props: ICanvasWithMapProps) => { mapElement.current = mapRefMethods.current?.getMapContainer() ?? null; }, [mapObject]); - const initMap = (mapObj: naver.maps.Map | null) => { - setMapObject(mapObj); - }; - - const handleClick = (event: React.MouseEvent) => { - mapRefMethods.current?.onMouseClickHandler(event); - canvasRefMethods.current?.onMouseClickHandler(event); - }; - - const handleMouseDown = (event: React.MouseEvent) => { - if (!mapElement.current || !canvasElement.current) return; - mouseEventState.current.isMouseDown = true; - mouseEventState.current.mouseDownPosition = { x: event.clientX, y: event.clientY }; - canvasRefMethods.current?.onMouseDownHandler(event); - }; - - const handleMouseMove = (event: React.MouseEvent) => { - if (!mapElement.current || !canvasElement.current || !mouseEventState.current.isMouseDown) - return; - - // TODO: 쓰로틀링 걸기 - mouseEventState.current.mouseDeltaPosition = { - x: -(event.clientX - mouseEventState.current.mouseDownPosition.x), - y: -(event.clientY - mouseEventState.current.mouseDownPosition.y), - }; - - mapObject?.panBy( - new naver.maps.Point( - mouseEventState.current.mouseDeltaPosition.x, - mouseEventState.current.mouseDeltaPosition.y, - ), - ); - - canvasRefMethods.current?.onMouseMoveHandler(event); - }; + const { handleClick, handleMouseDown, handleMouseMove, handleMouseUp } = useEventHandlers( + canvasElement.current, + canvasRefMethods.current, + mapElement.current, + mapRefMethods.current, + mapObject, + ); - const handleMouseUp = (event: React.MouseEvent) => { - if (!mapElement.current || !canvasElement.current) return; - mouseEventState.current = { ...MouseEventStateInitialValue }; - canvasRefMethods.current?.onMouseUpHandler(event); + const initMap = (mapObj: IMapObject | null) => { + setMapObject(mapObj); }; return ( diff --git a/frontend/src/component/canvas/useEventHandlers.tsx b/frontend/src/component/canvas/useEventHandlers.tsx new file mode 100644 index 00000000..23191544 --- /dev/null +++ b/frontend/src/component/canvas/useEventHandlers.tsx @@ -0,0 +1,86 @@ +import { useRef } from 'react'; +import { ICanvasRefMethods } from '@/component/canvas/Canvas.tsx'; +import { IMapObject, IMapRefMethods } from '@/component/maps/Map.types.ts'; + +// TODO: 리팩토룅 시 null을 처리하기 +interface IUseEventHandlers { + ( + canvasElement: HTMLCanvasElement | null, + canvasRefMethods: ICanvasRefMethods | null, + mapElement: HTMLElement | null, + mapRefMethods: IMapRefMethods | null, + mapObject: IMapObject | null, // 비동기 로딩 시 null로 처리가 될 수 있어서 예외처리 필요 + ): { + handleClick: (event: React.MouseEvent) => void; + handleMouseDown: (event: React.MouseEvent) => void; + handleMouseMove: (event: React.MouseEvent) => void; + handleMouseUp: (event: React.MouseEvent) => void; + }; +} + +interface IMouseEventState { + isMouseDown: boolean; + mouseDownPosition: { x: number; y: number }; + // mouseMovePosition: { x: number; y: number }; + mouseDeltaPosition: { x: number; y: number }; +} + +const MouseEventStateInitialValue = { + isMouseDown: false, + mouseDownPosition: { x: 0, y: 0 }, + // mouseMovePosition: { x: 0, y: 0 }, + mouseDeltaPosition: { x: 0, y: 0 }, +}; + +export const useEventHandlers: IUseEventHandlers = ( + canvasElement, + canvasRefMethods, + mapElement, + mapRefMethods, + mapObject, +) => { + // if (!canvasElement || !canvasElement || !mapElement || !mapRefMethods || !mapObject) + // throw new Error('🚀 useEventHandler error : null 값이 포함되어 있습니다.'); + + const mouseEventState = useRef({ ...MouseEventStateInitialValue }); + + const handleClick = (event: React.MouseEvent) => { + mapRefMethods?.onMouseClickHandler(event); + canvasRefMethods?.onMouseClickHandler(event); + }; + + const handleMouseDown = (event: React.MouseEvent) => { + if (!mapElement || !canvasElement) return; + mouseEventState.current.isMouseDown = true; + mouseEventState.current.mouseDownPosition = { x: event.clientX, y: event.clientY }; + canvasRefMethods?.onMouseDownHandler(event); + }; + + const handleMouseMove = (event: React.MouseEvent) => { + if (!mapElement || !canvasElement || !mouseEventState.current.isMouseDown) return; + + // TODO: 쓰로틀링 걸기 + mouseEventState.current.mouseDeltaPosition = { + x: -(event.clientX - mouseEventState.current.mouseDownPosition.x), + y: -(event.clientY - mouseEventState.current.mouseDownPosition.y), + }; + + // TODO: 범용 지도에 따른 Refactoring 필요, 우선은 네이버 지도에 한해서만 수행 + mapObject?.panBy( + new naver.maps.Point( + mouseEventState.current.mouseDeltaPosition.x, + mouseEventState.current.mouseDeltaPosition.y, + ), + ); + + canvasRefMethods?.onMouseMoveHandler(event); + }; + + const handleMouseUp = (event: React.MouseEvent) => { + if (!mapElement || !canvasElement) return; + mouseEventState.current = { ...MouseEventStateInitialValue }; + canvasRefMethods?.onMouseUpHandler(event); + }; + + return { handleClick, handleMouseDown, handleMouseMove, handleMouseUp }; +};