import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil' import { adsorptionPointModeState, adsorptionRangeState, canvasState, globalPitchState, verticalHorizontalModeState } from '@/store/canvasAtom' import { useEvent } from '@/hooks/useEvent' import { useMouse } from '@/hooks/useMouse' import { useLine } from '@/hooks/useLine' import { useEffect, useRef } from 'react' import { fabric } from 'fabric' import { calculateAngle } from '@/util/qpolygon-utils' import { OUTER_LINE_TYPE, placementShapeDrawingAngle1State, placementShapeDrawingAngle2State, placementShapeDrawingArrow1State, placementShapeDrawingArrow2State, placementShapeDrawingDiagonalState, placementShapeDrawingFixState, placementShapeDrawingLength1State, placementShapeDrawingLength2State, placementShapeDrawingPointsState, placementShapeDrawingTypeState, } from '@/store/placementShapeDrawingAtom' import { usePolygon } from '@/hooks/usePolygon' import { POLYGON_TYPE } from '@/common/common' import { usePopup } from '@/hooks/usePopup' import { useSwal } from '@/hooks/useSwal' import { useMessage } from '@/hooks/useMessage' import { useSurfaceShapeBatch } from './useSurfaceShapeBatch' import { roofDisplaySelector } from '@/store/settingAtom' import { useRoofFn } from '@/hooks/common/useRoofFn' import { useObject } from '@/hooks/useObject' import { useUndoRedo } from '@/hooks/useUndoRedo' // 배치면 그리기 export function usePlacementShapeDrawing(id) { const canvas = useRecoilValue(canvasState) const roofDisplay = useRecoilValue(roofDisplaySelector) const { initEvent, addCanvasMouseEventListener, addDocumentEventListener, removeAllMouseEventListeners, removeAllDocumentEventListeners, removeMouseLine, } = useEvent() // const { addCanvasMouseEventListener, addDocumentEventListener, removeAllMouseEventListeners, removeAllDocumentEventListeners, removeMouseEvent } = // useContext(EventContext) const { getIntersectMousePoint } = useMouse() const { addLine, removeLine } = useLine() const { addPolygonByLines, drawDirectionArrow, addLengthText } = usePolygon() const { setSurfaceShapePattern } = useRoofFn() const { changeSurfaceLineType } = useSurfaceShapeBatch({}) const { handleSelectableObjects } = useObject() const { saveSnapshot, popLastSnapshot } = useUndoRedo() const { swalFire } = useSwal() const { getMessage } = useMessage() const verticalHorizontalMode = useRecoilValue(verticalHorizontalModeState) const adsorptionRange = useRecoilValue(adsorptionRangeState) const adsorptionPointMode = useRecoilValue(adsorptionPointModeState) const length1Ref = useRef(null) const length2Ref = useRef(null) const angle1Ref = useRef(null) const angle2Ref = useRef(null) const [length1, setLength1] = useRecoilState(placementShapeDrawingLength1State) const [length2, setLength2] = useRecoilState(placementShapeDrawingLength2State) const [arrow1, setArrow1] = useRecoilState(placementShapeDrawingArrow1State) const [arrow2, setArrow2] = useRecoilState(placementShapeDrawingArrow2State) const [points, setPoints] = useRecoilState(placementShapeDrawingPointsState) const [type, setType] = useRecoilState(placementShapeDrawingTypeState) const [angle1, setAngle1] = useRecoilState(placementShapeDrawingAngle1State) const [angle2, setAngle2] = useRecoilState(placementShapeDrawingAngle2State) const [outerLineDiagonalLength, setOuterLineDiagonalLength] = useRecoilState(placementShapeDrawingDiagonalState) const setOuterLineFix = useSetRecoilState(placementShapeDrawingFixState) const arrow1Ref = useRef(arrow1) const arrow2Ref = useRef(arrow2) const outerLineDiagonalLengthRef = useRef(null) const isFix = useRef(false) const { closePopup, addPopup } = usePopup() const globalPitch = useRecoilValue(globalPitchState) useEffect(() => { addCanvasMouseEventListener('mouse:down', mouseDown) addDocumentEventListener('contextmenu', document, (e) => { handleRollback() }) handleSelectableObjects(['lineGrid', 'tempGrid', 'adsorptionPoint'], false) clear() return () => { initEvent() handleSelectableObjects(['lineGrid', 'tempGrid', 'adsorptionPoint'], true) } }, [verticalHorizontalMode, points, adsorptionRange, adsorptionPointMode]) useEffect(() => { // 배치면 그리기 시작 시 스냅샷 저장 (undo 하면 그리기 전 상태로 복원) saveSnapshot() setPoints([]) setType(OUTER_LINE_TYPE.OUTER_LINE) return () => { const placementShapeDrawingStartPoint = canvas.getObjects().find((obj) => obj.name === 'placementShapeDrawingStartPoint') canvas.remove(placementShapeDrawingStartPoint) // 확정하지 않고 닫은 경우 스냅샷 제거 if (!isFix.current) { popLastSnapshot() } } }, []) useEffect(() => { arrow1Ref.current = arrow1 }, [arrow1]) useEffect(() => { arrow2Ref.current = arrow2 }, [arrow2]) useEffect(() => { clear() addDocumentEventListener('keydown', document, keydown[type]) }, [type]) const clear = () => { setLength1(0) setLength2(0) setArrow1('') setArrow2('') setAngle1(0) setAngle2(0) setOuterLineDiagonalLength(0) } const mouseDown = (e) => { let pointer = getIntersectMousePoint(e) if (points.length === 0) { setPoints((prev) => [...prev, pointer]) } else { const lastPoint = points[points.length - 1] let newPoint = { x: pointer.x, y: pointer.y } if (verticalHorizontalMode) { const vector = { x: pointer.x - lastPoint.x, y: pointer.y - lastPoint.y, } const slope = Math.abs(vector.y / vector.x) if (slope >= 1) { // 기울기가 1 이상이면 수직으로 제한 newPoint = { x: lastPoint.x, y: pointer.y, } } else { // 기울기가 1 미만이면 수평으로 제한 newPoint = { x: pointer.x, y: lastPoint.y, } } } setPoints((prev) => [...prev, newPoint]) } } useEffect(() => { canvas ?.getObjects() .filter((obj) => obj.name === 'placementShapeDrawingLine' || obj.name === 'helpGuideLine') .forEach((obj) => { removeLine(obj) }) canvas?.remove(canvas?.getObjects().find((obj) => obj.name === 'placementShapeDrawingStartPoint')) if (points.length === 0) { setOuterLineFix(true) removeAllDocumentEventListeners() return } addDocumentEventListener('keydown', document, keydown[type]) if (points.length === 1) { const point = new fabric.Circle({ radius: 5, fill: 'transparent', stroke: 'red', left: points[0].x - 5, top: points[0].y - 5, selectable: false, name: 'placementShapeDrawingStartPoint', }) canvas?.add(point) } else { setOuterLineFix(false) canvas .getObjects() .filter((obj) => obj.name === 'placementShapeDrawingPoint') .forEach((obj) => { canvas.remove(obj) }) points.forEach((point, idx) => { const circle = new fabric.Circle({ left: point.x, top: point.y, visible: false, name: 'placementShapeDrawingPoint', }) canvas.add(circle) }) points.forEach((point, idx) => { if (idx === 0) { return } drawLine(points[idx - 1], point, idx) }) const lastPoint = points[points.length - 1] const firstPoint = points[0] if (isFix.current) { removeAllMouseEventListeners() removeAllDocumentEventListeners() const lines = canvas?.getObjects().filter((obj) => obj.name === 'placementShapeDrawingLine') const roof = addPolygonByLines(lines, { stroke: 'black', strokeWidth: 3, selectable: true, name: POLYGON_TYPE.ROOF, originX: 'center', originY: 'center', direction: 'south', pitch: globalPitch, from: 'surface', }) // 기존 도형의 흡착점을 이용해 그린 경우, 기존 도형의 planeSize를 상속 inheritPlaneSizeFromExistingShapes(roof) setSurfaceShapePattern(roof, roofDisplay.column) drawDirectionArrow(roof) lines.forEach((line) => { removeLine(line) }) setPoints([]) canvas?.renderAll() // if (+canvasSetting?.roofSizeSet === 3) { // closePopup(id) // return // } // addPopup(id, 1, , false) changeSurfaceLineType(roof) closePopup(id) } if (points.length < 3) { return } if (Math.abs(lastPoint.x - firstPoint.x) < 1 && Math.abs(lastPoint.y - firstPoint.y) < 1) { return } if (Math.abs(lastPoint.x - firstPoint.x) < 1 || Math.abs(lastPoint.y - firstPoint.y) < 1) { let isAllRightAngle = true const firstPoint = points[0] points.forEach((point, idx) => { if (idx === 0 || !isAllRightAngle) { return } const angle = calculateAngle(point, firstPoint) if (angle % 90 !== 0) { isAllRightAngle = false } }) if (isAllRightAngle) { return } const line = addLine([lastPoint.x, lastPoint.y, firstPoint.x, firstPoint.y], { stroke: 'grey', strokeWidth: 1, selectable: false, name: 'helpGuideLine', }) } else { const guideLine1 = addLine([lastPoint.x, lastPoint.y, lastPoint.x, firstPoint.y], { stroke: 'grey', strokeWidth: 1, strokeDashArray: [1, 1, 1], name: 'helpGuideLine', }) const guideLine2 = addLine([guideLine1.x2, guideLine1.y2, firstPoint.x, firstPoint.y], { stroke: 'grey', strokeWidth: 1, strokeDashArray: [1, 1, 1], name: 'helpGuideLine', }) } } }, [points]) // 기존 도형의 변과 일치하는 경우 planeSize를 상속하는 함수 const inheritPlaneSizeFromExistingShapes = (newPolygon) => { const tolerance = 2 const existingPolygons = canvas .getObjects() .filter((obj) => (obj.name === POLYGON_TYPE.ROOF || obj.name === POLYGON_TYPE.WALL) && obj.id !== newPolygon.id && obj.lines) if (existingPolygons.length === 0) return const inheritedSet = new Set() // 1단계: 기존 도형의 변과 매칭하여 planeSize 상속 newPolygon.lines.forEach((line, lineIdx) => { const x1 = line.x1, y1 = line.y1, x2 = line.x2, y2 = line.y2 const isHorizontal = Math.abs(y1 - y2) < tolerance const isVertical = Math.abs(x1 - x2) < tolerance for (const polygon of existingPolygons) { for (const edge of polygon.lines) { if (!edge.attributes?.planeSize) continue const ex1 = edge.x1, ey1 = edge.y1, ex2 = edge.x2, ey2 = edge.y2 // 1순위: 양 끝점이 정확히 일치 const forwardMatch = Math.abs(x1 - ex1) < tolerance && Math.abs(y1 - ey1) < tolerance && Math.abs(x2 - ex2) < tolerance && Math.abs(y2 - ey2) < tolerance const reverseMatch = Math.abs(x1 - ex2) < tolerance && Math.abs(y1 - ey2) < tolerance && Math.abs(x2 - ex1) < tolerance && Math.abs(y2 - ey1) < tolerance if (forwardMatch || reverseMatch) { line.attributes = { ...line.attributes, planeSize: edge.attributes.planeSize } if (edge.attributes.actualSize) { line.attributes.actualSize = edge.attributes.actualSize } inheritedSet.add(lineIdx) return } // 2순위: 같은 방향 + 같은 좌표 차이 (끝점 공유 불필요) if (isHorizontal && Math.abs(ey1 - ey2) < tolerance && Math.abs(Math.abs(x2 - x1) - Math.abs(ex2 - ex1)) < tolerance) { line.attributes = { ...line.attributes, planeSize: edge.attributes.planeSize } if (edge.attributes.actualSize) { line.attributes.actualSize = edge.attributes.actualSize } inheritedSet.add(lineIdx) return } if (isVertical && Math.abs(ex1 - ex2) < tolerance && Math.abs(Math.abs(y2 - y1) - Math.abs(ey2 - ey1)) < tolerance) { line.attributes = { ...line.attributes, planeSize: edge.attributes.planeSize } if (edge.attributes.actualSize) { line.attributes.actualSize = edge.attributes.actualSize } inheritedSet.add(lineIdx) return } } } }) // 2단계: 상속받은 변과 평행하고 좌표 차이가 같은 변에 planeSize 전파 if (inheritedSet.size > 0) { newPolygon.lines.forEach((line, lineIdx) => { if (inheritedSet.has(lineIdx)) return const x1 = line.x1, y1 = line.y1, x2 = line.x2, y2 = line.y2 const isHorizontal = Math.abs(y1 - y2) < tolerance const isVertical = Math.abs(x1 - x2) < tolerance for (const idx of inheritedSet) { const inherited = newPolygon.lines[idx] const ix1 = inherited.x1, iy1 = inherited.y1, ix2 = inherited.x2, iy2 = inherited.y2 const iIsHorizontal = Math.abs(iy1 - iy2) < tolerance const iIsVertical = Math.abs(ix1 - ix2) < tolerance if (isHorizontal && iIsHorizontal) { if (Math.abs(Math.abs(x2 - x1) - Math.abs(ix2 - ix1)) < tolerance) { line.attributes = { ...line.attributes, planeSize: inherited.attributes.planeSize } if (inherited.attributes.actualSize) { line.attributes.actualSize = inherited.attributes.actualSize } inheritedSet.add(lineIdx) return } } else if (isVertical && iIsVertical) { if (Math.abs(Math.abs(y2 - y1) - Math.abs(iy2 - iy1)) < tolerance) { line.attributes = { ...line.attributes, planeSize: inherited.attributes.planeSize } if (inherited.attributes.actualSize) { line.attributes.actualSize = inherited.attributes.actualSize } inheritedSet.add(lineIdx) return } } } }) } // planeSize가 상속된 경우 길이 텍스트를 다시 렌더링 if (inheritedSet.size > 0) { addLengthText(newPolygon) } } // 기존 도형에서 매칭되는 변의 planeSize를 찾는 헬퍼 함수 const findMatchingEdgePlaneSize = (x1, y1, x2, y2) => { const tolerance = 2 const existingPolygons = canvas.getObjects().filter((obj) => (obj.name === POLYGON_TYPE.ROOF || obj.name === POLYGON_TYPE.WALL) && obj.lines) const isHorizontal = Math.abs(y1 - y2) < tolerance const isVertical = Math.abs(x1 - x2) < tolerance for (const polygon of existingPolygons) { for (const edge of polygon.lines) { if (!edge.attributes?.planeSize) continue const ex1 = edge.x1, ey1 = edge.y1, ex2 = edge.x2, ey2 = edge.y2 // 1순위: 양 끝점 일치 (정방향/역방향) const forwardMatch = Math.abs(x1 - ex1) < tolerance && Math.abs(y1 - ey1) < tolerance && Math.abs(x2 - ex2) < tolerance && Math.abs(y2 - ey2) < tolerance const reverseMatch = Math.abs(x1 - ex2) < tolerance && Math.abs(y1 - ey2) < tolerance && Math.abs(x2 - ex1) < tolerance && Math.abs(y2 - ey1) < tolerance if (forwardMatch || reverseMatch) { return edge.attributes.planeSize } // 2순위: 같은 방향 + 같은 좌표 차이 (끝점 공유 불필요) if (isHorizontal && Math.abs(ey1 - ey2) < tolerance && Math.abs(Math.abs(x2 - x1) - Math.abs(ex2 - ex1)) < tolerance) { return edge.attributes.planeSize } if (isVertical && Math.abs(ex1 - ex2) < tolerance && Math.abs(Math.abs(y2 - y1) - Math.abs(ey2 - ey1)) < tolerance) { return edge.attributes.planeSize } } } return null } const drawLine = (point1, point2, idx) => { const line = addLine([point1.x, point1.y, point2.x, point2.y], { stroke: 'black', strokeWidth: 3, idx: idx, selectable: true, name: 'placementShapeDrawingLine', x1: point1.x, y1: point1.y, x2: point2.x, y2: point2.y, }) // 기존 도형의 변과 일치하는 경우 planeSize 상속 if (line) { const matchedPlaneSize = findMatchingEdgePlaneSize(point1.x, point1.y, point2.x, point2.y) if (matchedPlaneSize !== null) { line.attributes = { ...line.attributes, planeSize: matchedPlaneSize } } } } // 직각 완료될 경우 확인 const checkRightAngle = (direction) => { const activeElem = document.activeElement const canDirection = direction === '↓' || direction === '↑' ? arrow1Ref.current === '←' || arrow1Ref.current === '→' : arrow1Ref.current === '↓' || arrow1Ref.current === '↑' if (activeElem === length1Ref.current || activeElem === angle1Ref.current) { setArrow1(direction) arrow1Ref.current = direction length2Ref.current.focus() } else if (activeElem === length2Ref.current || activeElem === angle2Ref.current) { if (!canDirection) { return } setArrow2(direction) arrow2Ref.current = direction } const length1Num = Number(length1Ref.current.value) / 10 const length2Num = Number(length2Ref.current.value) / 10 if (points.length === 0) { return } if (length1Num === 0 || length2Num === 0 || arrow1Ref.current === '' || arrow2Ref.current === '') { return } if (arrow1Ref.current === '↓' && arrow2Ref.current === '→') { setPoints((prev) => { if (prev.length === 0) { return [] } return [...prev, { x: prev[prev.length - 1].x + length2Num, y: prev[prev.length - 1].y + length1Num }] }) } else if (arrow1Ref.current === '↓' && arrow2Ref.current === '←') { setPoints((prev) => { if (prev.length === 0) { return [] } return [...prev, { x: prev[prev.length - 1].x - length2Num, y: prev[prev.length - 1].y + length1Num }] }) } else if (arrow1Ref.current === '↑' && arrow2Ref.current === '→') { setPoints((prev) => { if (prev.length === 0) { return [] } return [...prev, { x: prev[prev.length - 1].x + length2Num, y: prev[prev.length - 1].y - length1Num }] }) } else if (arrow1Ref.current === '↑' && arrow2Ref.current === '←') { setPoints((prev) => { if (prev.length === 0) { return [] } return [...prev, { x: prev[prev.length - 1].x - length2Num, y: prev[prev.length - 1].y - length1Num }] }) } else if (arrow1Ref.current === '→' && arrow2Ref.current === '↓') { setPoints((prev) => { if (prev.length === 0) { return [] } return [...prev, { x: prev[prev.length - 1].x + length1Num, y: prev[prev.length - 1].y + length2Num }] }) } else if (arrow1Ref.current === '→' && arrow2Ref.current === '↑') { setPoints((prev) => { if (prev.length === 0) { return [] } return [...prev, { x: prev[prev.length - 1].x + length1Num, y: prev[prev.length - 1].y - length2Num }] }) } else if (arrow1Ref.current === '←' && arrow2Ref.current === '↓') { setPoints((prev) => { if (prev.length === 0) { return [] } return [...prev, { x: prev[prev.length - 1].x - length1Num, y: prev[prev.length - 1].y + length2Num }] }) } else if (arrow1Ref.current === '←' && arrow2Ref.current === '↑') { setPoints((prev) => { if (prev.length === 0) { return [] } return [...prev, { x: prev[prev.length - 1].x - length1Num, y: prev[prev.length - 1].y - length2Num }] }) } } //이구배 완료될 경우 확인 ↓, ↑, ←, → const checkDoublePitch = (direction) => { const activeElem = document.activeElement const canDirection = direction === '↓' || direction === '↑' ? arrow1Ref.current === '←' || arrow1Ref.current === '→' : arrow1Ref.current === '↓' || arrow1Ref.current === '↑' if (activeElem === length1Ref.current || activeElem === angle1Ref.current) { setArrow1(direction) arrow1Ref.current = direction angle2Ref.current.focus() } else if (activeElem === length2Ref.current || activeElem === angle2Ref.current) { if (!canDirection) { return } setArrow2(direction) arrow2Ref.current = direction } const angle1Value = angle1Ref.current.value const angle2Value = angle2Ref.current.value const length1Value = length1Ref.current.value const length2Value = length2Ref.current.value const arrow1Value = arrow1Ref.current const arrow2Value = arrow2Ref.current if (angle1Value !== 0 && length1Value !== 0 && angle2Value !== 0 && arrow1Value !== '' && arrow2Value !== '') { if (arrow1Value === '↓' && arrow2Value === '→') { setPoints((prev) => { if (prev.length === 0) { return [] } return [ ...prev, { x: prev[prev.length - 1].x + length1Value / 10, y: prev[prev.length - 1].y + length2Value / 10, }, ] }) } else if (arrow1Value === '↓' && arrow2Value === '←') { setPoints((prev) => { if (prev.length === 0) { return [] } return [ ...prev, { x: prev[prev.length - 1].x - length1Value / 10, y: prev[prev.length - 1].y + length2Value / 10, }, ] }) } else if (arrow1Value === '↑' && arrow2Value === '→') { setPoints((prev) => { if (prev.length === 0) { return [] } return [ ...prev, { x: prev[prev.length - 1].x + length1Value / 10, y: prev[prev.length - 1].y - length2Value / 10, }, ] }) } else if (arrow1Value === '↑' && arrow2Value === '←') { setPoints((prev) => { if (prev.length === 0) { return [] } return [ ...prev, { x: prev[prev.length - 1].x - length1Value / 10, y: prev[prev.length - 1].y - length2Value / 10, }, ] }) } else if (arrow1Value === '→' && arrow2Value === '↓') { setPoints((prev) => { if (prev.length === 0) { return [] } return [ ...prev, { x: prev[prev.length - 1].x + length2Value / 10, y: prev[prev.length - 1].y + length1Value / 10, }, ] }) } else if (arrow1Value === '→' && arrow2Value === '↑') { setPoints((prev) => { if (prev.length === 0) { return [] } return [ ...prev, { x: prev[prev.length - 1].x + length2Value / 10, y: prev[prev.length - 1].y - length1Value / 10, }, ] }) } else if (arrow1Value === '←' && arrow2Value === '↓') { setPoints((prev) => { if (prev.length === 0) { return [] } return [ ...prev, { x: prev[prev.length - 1].x - length2Value / 10, y: prev[prev.length - 1].y + length1Value / 10, }, ] }) } else if (arrow1Value === '←' && arrow2Value === '↑') { setPoints((prev) => { if (prev.length === 0) { return [] } return [ ...prev, { x: prev[prev.length - 1].x - length2Value / 10, y: prev[prev.length - 1].y - length1Value / 10, }, ] }) } angle1Ref.current.focus() } } //대각선 완료될 경우 확인 const checkDiagonal = (direction) => { const activeElem = document.activeElement const diagonalLength = outerLineDiagonalLengthRef.current.value // 대각선 길이 const length1Value = length1Ref.current.value if (diagonalLength <= length1Value) { // alert('대각선 길이는 직선 길이보다 길어야 합니다.') return } const canDirection = direction === '↓' || direction === '↑' ? arrow1Ref.current === '←' || arrow1Ref.current === '→' : arrow1Ref.current === '↓' || arrow1Ref.current === '↑' if (activeElem === length1Ref.current) { setArrow1(direction) arrow1Ref.current = direction } else if (activeElem === length2Ref.current || activeElem === angle2Ref.current) { if (!canDirection) { return } setArrow2(direction) arrow2Ref.current = direction } const arrow1Value = arrow1Ref.current const arrow2Value = arrow2Ref.current const getLength2 = () => { return Math.floor(Math.sqrt(diagonalLength ** 2 - length1Value ** 2)) } const length2Value = getLength2() if (diagonalLength !== 0 && length1Value !== 0 && arrow1Value !== '') { setLength2(getLength2()) length2Ref.current.focus() } if (length1Value !== 0 && length2Value !== 0 && arrow1Value !== '' && arrow2Value !== '') { if (arrow1Value === '↓' && arrow2Value === '→') { setPoints((prev) => { if (prev.length === 0) { return [] } return [ ...prev, { x: prev[prev.length - 1].x + length2Value / 10, y: prev[prev.length - 1].y + length1Value / 10, }, ] }) } else if (arrow1Value === '↓' && arrow2Value === '←') { setPoints((prev) => { if (prev.length === 0) { return [] } return [ ...prev, { x: prev[prev.length - 1].x - length2Value / 10, y: prev[prev.length - 1].y + length1Value / 10, }, ] }) } else if (arrow1Value === '↑' && arrow2Value === '→') { setPoints((prev) => { if (prev.length === 0) { return [] } return [ ...prev, { x: prev[prev.length - 1].x + length2Value / 10, y: prev[prev.length - 1].y - length1Value / 10, }, ] }) } else if (arrow1Value === '↑' && arrow2Value === '←') { setPoints((prev) => { if (prev.length === 0) { return [] } return [ ...prev, { x: prev[prev.length - 1].x - length2Value / 10, y: prev[prev.length - 1].y - length1Value / 10, }, ] }) } else if (arrow1Value === '→' && arrow2Value === '↓') { setPoints((prev) => { if (prev.length === 0) { return [] } return [ ...prev, { x: prev[prev.length - 1].x + length1Value / 10, y: prev[prev.length - 1].y + length2Value / 10, }, ] }) } else if (arrow1Value === '→' && arrow2Value === '↑') { setPoints((prev) => { if (prev.length === 0) { return [] } return [ ...prev, { x: prev[prev.length - 1].x + length1Value / 10, y: prev[prev.length - 1].y - length2Value / 10, }, ] }) } else if (arrow1Value === '←' && arrow2Value === '↓') { setPoints((prev) => { if (prev.length === 0) { return [] } return [ ...prev, { x: prev[prev.length - 1].x - length1Value / 10, y: prev[prev.length - 1].y + length2Value / 10, }, ] }) } else if (arrow1Value === '←' && arrow2Value === '↑') { setPoints((prev) => { if (prev.length === 0) { return [] } return [ ...prev, { x: prev[prev.length - 1].x - length1Value / 10, y: prev[prev.length - 1].y - length2Value / 10, }, ] }) } } } const keydown = { outerLine: (e) => { if (points.length === 0) { return } enterCheck(e) // 포커스가 length1에 있지 않으면 length1에 포커스를 줌 const activeElem = document.activeElement if (activeElem !== length1Ref.current) { length1Ref.current?.focus() } const key = e.key if (!length1Ref.current) { return } const lengthNum = Number(length1Ref.current?.value) / 10 if (lengthNum === 0) { return } switch (key) { case 'Down': // IE/Edge에서 사용되는 값 case 'ArrowDown': { setArrow1('↓') setPoints((prev) => { if (prev.length === 0) { return [] } return [...prev, { x: prev[prev.length - 1].x, y: prev[prev.length - 1].y + lengthNum }] }) break } case 'Up': // IE/Edge에서 사용되는 값 case 'ArrowUp': setArrow1('↑') setPoints((prev) => { if (prev.length === 0) { return [] } return [...prev, { x: prev[prev.length - 1].x, y: prev[prev.length - 1].y - lengthNum }] }) break case 'Left': // IE/Edge에서 사용되는 값 case 'ArrowLeft': setArrow1('←') setPoints((prev) => { if (prev.length === 0) { return [] } return [...prev, { x: prev[prev.length - 1].x - lengthNum, y: prev[prev.length - 1].y }] }) break case 'Right': // IE/Edge에서 사용되는 값 case 'ArrowRight': setArrow1('→') setPoints((prev) => { if (prev.length === 0) { return [] } return [...prev, { x: prev[prev.length - 1].x + lengthNum, y: prev[prev.length - 1].y }] }) break } }, rightAngle: (e) => { if (points.length === 0) { return } enterCheck(e) const key = e.key const activeElem = document.activeElement if (activeElem !== length1Ref.current && activeElem !== length2Ref.current) { length1Ref.current.focus() } switch (key) { case 'Down': // IE/Edge에서 사용되는 값 case 'ArrowDown': { checkRightAngle('↓') break } case 'Up': // IE/Edge에서 사용되는 값 case 'ArrowUp': checkRightAngle('↑') break case 'Left': // IE/Edge에서 사용되는 값 case 'ArrowLeft': checkRightAngle('←') break case 'Right': // IE/Edge에서 사용되는 값 case 'ArrowRight': checkRightAngle('→') break case 'Enter': break } }, doublePitch: (e) => { if (points.length === 0) { return } enterCheck(e) const key = e.key switch (key) { case 'Down': // IE/Edge에서 사용되는 값 case 'ArrowDown': { checkDoublePitch('↓') break } case 'Up': // IE/Edge에서 사용되는 값 case 'ArrowUp': checkDoublePitch('↑') break case 'Left': // IE/Edge에서 사용되는 값 case 'ArrowLeft': checkDoublePitch('←') break case 'Right': // IE/Edge에서 사용되는 값 case 'ArrowRight': checkDoublePitch('→') break } }, angle: (e) => { if (points.length === 0) { return } // enterCheck(e) const key = e.key switch (key) { case 'Enter': { setPoints((prev) => { if (prev.length === 0) { return [] } const lastPoint = prev[prev.length - 1] const length = length1Ref.current.value / 10 const angle = angle1Ref.current.value //lastPoint로부터 angle1만큼의 각도로 length1만큼의 길이를 가지는 선을 그림 const radian = (angle * Math.PI) / 180 const x = lastPoint.x + length * Math.cos(radian) const y = lastPoint.y - length * Math.sin(radian) return [...prev, { x, y }] }) } } }, diagonalLine: (e) => { if (points.length === 0) { return } enterCheck(e) const key = e.key switch (key) { case 'Down': // IE/Edge에서 사용되는 값 case 'ArrowDown': { checkDiagonal('↓') break } case 'Up': // IE/Edge에서 사용되는 값 case 'ArrowUp': checkDiagonal('↑') break case 'Left': // IE/Edge에서 사용되는 값 case 'ArrowLeft': checkDiagonal('←') break case 'Right': // IE/Edge에서 사용되는 값 case 'ArrowRight': checkDiagonal('→') break } }, } /** * 일변전으로 돌아가기 */ const handleRollback = () => { //points의 마지막 요소를 제거 setPoints((prev) => prev.slice(0, prev.length - 1)) } const handleFix = () => { if (points.length < 3) { return } let isAllRightAngle = true const firstPoint = points[0] /*points.forEach((point, idx) => { if (idx === 0 || !isAllRightAngle) { return } const angle = calculateAngle(point, firstPoint) if (angle % 90 !== 0) { isAllRightAngle = false } }) if (isAllRightAngle) { // alert('부정확한 다각형입니다.') return }*/ setPoints((prev) => { return [...prev, { x: prev[0].x, y: prev[0].y }] }) isFix.current = true } const enterCheck = (e) => { if (e.key === 'Enter') { swalFire({ type: 'confirm', text: getMessage('modal.cover.outline.fix.confirm'), confirmFn: handleFix, }) } } return { points, setPoints, length1, setLength1, length2, setLength2, length1Ref, length2Ref, arrow1, setArrow1, arrow2, setArrow2, arrow1Ref, arrow2Ref, angle1, setAngle1, angle1Ref, angle2, setAngle2, angle2Ref, outerLineDiagonalLength, setOuterLineDiagonalLength, outerLineDiagonalLengthRef, type, setType, handleFix, handleRollback, } }