diff --git a/src/hooks/useMode.js b/src/hooks/useMode.js index 345d6179..2a7e93b7 100644 --- a/src/hooks/useMode.js +++ b/src/hooks/useMode.js @@ -1,10 +1,10 @@ -import { useRef, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import QRect from '@/components/fabric/QRect' import QPolygon from '@/components/fabric/QPolygon' import { findTopTwoIndexesByDistance, getCenterPoint, getDirection, getStartIndex, rearrangeArray } from '@/util/canvas-util' import { useRecoilState } from 'recoil' -import { fontSizeState, roofState, sortedPolygonArray, roofPolygonPatternArrayState, wallState } from '@/store/canvasAtom' +import { fontSizeState, roofPolygonPatternArrayState, roofState, sortedPolygonArray, wallState } from '@/store/canvasAtom' import { QLine } from '@/components/fabric/QLine' export const Mode = { @@ -34,6 +34,19 @@ export function useMode() { const [roofPolygonPattern, setRoofPolygonPattern] = useRecoilState(roofPolygonPatternArrayState) + useEffect(() => { + // 이벤트 리스너 추가 + if (!canvas) { + return + } + document.addEventListener('keydown', handleKeyDown) + + // 컴포넌트가 언마운트될 때 이벤트 리스너 제거 + return () => { + document.removeEventListener('keydown', handleKeyDown) + } + }, [canvas]) // 빈 배열을 전달하여 컴포넌트가 마운트될 때만 실행되도록 함 + const addEvent = (mode) => { switch (mode) { case 'default': @@ -66,6 +79,131 @@ export function useMode() { } } + const keyValid = () => { + if (points.current.length === 0) { + alert('시작점을 선택해주세요') + return false + } + return true + } + + const drawCircleAndLine = (verticalLength, horizontalLength) => { + const circle = new fabric.Circle({ + radius: 5, + fill: 'transparent', // 원 안을 비웁니다. + stroke: 'black', // 원 테두리 색상을 검은색으로 설정합니다. + left: points.current[points.current.length - 1].left + horizontalLength - 5, + top: points.current[points.current.length - 1].top + verticalLength - 5, + originX: 'center', + originY: 'center', + selectable: false, + }) + historyPoints.current.push(circle) + points.current.push(circle) + canvas?.add(circle) + canvas?.renderAll() + + // length 값이 숫자가 아닌 경우 + if (isNaN(length) || Math.max(Math.abs(verticalLength), Math.abs(horizontalLength)) === 0) { + //마지막 추가 된 points 제거합니다. + + const lastPoint = historyPoints.current[historyPoints.current.length - 1] + + canvas?.remove(lastPoint) + + historyPoints.current.pop() + points.current.pop() + return + } + + const line = new QLine( + [points.current[0].left, points.current[0].top, points.current[0].left + horizontalLength, points.current[0].top + verticalLength], + { + stroke: 'black', + strokeWidth: 2, + selectable: false, + viewLengthText: true, + direction: getDirection(points.current[0], points.current[1]), + fontSize: fontSize, + }, + ) + + pushHistoryLine(line) + + // 라인의 끝에 점을 추가합니다. + const endPointCircle = new fabric.Circle({ + radius: 1, + fill: 'transparent', // 원 안을 비웁니다. + stroke: 'black', // 원 테두리 색상을 검은색으로 설정합니다. + left: points.current[0].left + horizontalLength, + top: points.current[0].top + verticalLength, + originX: 'center', + originY: 'center', + selectable: false, + }) + + canvas?.add(endPointCircle) + + historyPoints.current.push(endPointCircle) + + points.current.forEach((point) => { + canvas?.remove(point) + }) + points.current = [endPointCircle] + + canvas.renderAll() + } + + const handleKeyDown = (e) => { + switch (e.key) { + case 'ArrowDown': { + if (!keyValid()) { + return + } + const verticalLength = Number(prompt('길이를 입력하세요:')) + const horizontalLength = 0 + + drawCircleAndLine(verticalLength, horizontalLength) + + break + } + case 'ArrowUp': { + if (!keyValid()) { + return + } + const verticalLength = -Number(prompt('길이를 입력하세요:')) + const horizontalLength = 0 + + drawCircleAndLine(verticalLength, horizontalLength) + + break + } + case 'ArrowLeft': { + if (!keyValid()) { + return + } + const verticalLength = 0 + const horizontalLength = -Number(prompt('길이를 입력하세요:')) + + drawCircleAndLine(verticalLength, horizontalLength) + + break + } + case 'ArrowRight': { + if (!keyValid()) { + return + } + + const verticalLength = 0 + const horizontalLength = Number(prompt('길이를 입력하세요:')) + + drawCircleAndLine(verticalLength, horizontalLength) + + break + } + } + } + const changeMode = (canvas, mode) => { setMode(mode) // mode변경 시 이전 이벤트 제거 diff --git a/src/util/canvas-util.js b/src/util/canvas-util.js index 4ce81607..b2f00bf7 100644 --- a/src/util/canvas-util.js +++ b/src/util/canvas-util.js @@ -426,9 +426,58 @@ export function findOrthogonalPoint(line1, line2) { export const sortedPoints = (points) => { const copyPoints = [...points] //points를 x,y좌표를 기준으로 정렬합니다. + copyPoints.sort((a, b) => { + if (a.x === b.x) { + return a.y - b.y + } + return a.x - b.x + }) - const idx = getStartIndexPoint(copyPoints) - return rearrangeArray(copyPoints, idx) + // 이때 copyPoints를 순회하며 최초엔 x값을 비교하여 같은 점을 찾는다. 이때 이 점이 2번째 점이 된다. + // 그 다음점은 2번째 점과 y값이 같은 점이 된다. + // 또 그다음 점은 3번째 점과 x값이 같은 점이 된다. + // 이를 반복하여 copyPoints를 재배열한다. + const resultPoints = [copyPoints[0]] + let index = 1 + let currentPoint = { ...copyPoints[0] } + copyPoints.splice(0, 1) + while (index < points.length) { + if (index === points.length - 1) { + resultPoints.push(copyPoints[0]) + index++ + break + } else if (index % 2 === 0) { + // 짝수번째는 y값이 같은 점을 찾는다. + for (let i = 0; i < copyPoints.length; i++) { + // y값이 같은 point가 많은 경우 그 중 x값이 가장 큰걸 찾는다. + const temp = copyPoints.filter((point) => point.y === currentPoint.y) + // temp중 x값이 가장 가까운 값 + + const min = temp.reduce((prev, current) => (Math.abs(current.x - currentPoint.x) <= Math.abs(prev.x - currentPoint.x) ? current : prev)) + + resultPoints.push(min) + currentPoint = min + copyPoints.splice(copyPoints.indexOf(min), 1) + index++ + break + } + } else { + // 홀수번째는 x값이 같은 점을 찾는다. + for (let i = 0; i < copyPoints.length; i++) { + // x값이 같은 point가 많은 경우 그 중 y값이 가장 큰걸 찾는다. + const temp = copyPoints.filter((point) => point.x === currentPoint.x) + // temp중 y값이 가장 가까운 값 + const min = temp.reduce((prev, current) => (Math.abs(current.y - currentPoint.y) <= Math.abs(prev.y - currentPoint.y) ? current : prev)) + + resultPoints.push(min) + currentPoint = min + copyPoints.splice(copyPoints.indexOf(min), 1) + index++ + break + } + } + } + return resultPoints } /**