'use client' import { useEffect, useRef } from 'react' import WithDraggable from '@/components/common/draggable/withDraggable' import { useRecoilState, useRecoilValue } from 'recoil' import { useMessage } from '@/hooks/useMessage' import { useEvent } from '@/hooks/useEvent' import { canvasState, verticalHorizontalModeState } from '@/store/canvasAtom' import { OUTER_LINE_TYPE, outerLineAngle1State, outerLineArrow1State, outerLineArrow2State, outerLineLength1State, outerLineLength2State, outerLinePointsState, outerLineTypeState, } from '@/store/outerLineAtom' import { useLine } from '@/hooks/useLine' import { distanceBetweenPoints } from '@/util/canvas-util' import { calculateAngle } from '@/util/qpolygon-utils' import { usePolygon } from '@/hooks/usePolygon' import { onlyNumberInputChange, onlyNumberWithDotInputChange } from '@/util/input-utils' export default function OuterLineWall(props) { const { setShowOutlineModal } = props const { getMessage } = useMessage() const { addCanvasMouseEventListener, addDocumentEventListener, removeAllMouseEventListeners, removeAllDocumentEventListeners, removeMouseEvent } = useEvent() const { addLine, removeLine } = useLine() const { addPolygonByLines } = usePolygon() const verticalHorizontalMode = useRecoilValue(verticalHorizontalModeState) const length1Ref = useRef(null) const length2Ref = useRef(null) const angle1Ref = useRef(null) const [length1, setLength1] = useRecoilState(outerLineLength1State) const [length2, setLength2] = useRecoilState(outerLineLength2State) const [arrow1, setArrow1] = useRecoilState(outerLineArrow1State) const [arrow2, setArrow2] = useRecoilState(outerLineArrow2State) const [points, setPoints] = useRecoilState(outerLinePointsState) const [type, setType] = useRecoilState(outerLineTypeState) const arrow1Ref = useRef(arrow1) const arrow2Ref = useRef(arrow2) const isFix = useRef(false) const [angle1, setAngle1] = useRecoilState(outerLineAngle1State) const canvas = useRecoilValue(canvasState) useEffect(() => { removeMouseEvent('mouse:down', mouseDown) addCanvasMouseEventListener('mouse:down', mouseDown) clear() return () => { removeAllMouseEventListeners() } }, [verticalHorizontalMode, points]) useEffect(() => { arrow1Ref.current = arrow1 }, [arrow1]) useEffect(() => { arrow2Ref.current = arrow2 }, [arrow2]) useEffect(() => { removeAllDocumentEventListeners() addDocumentEventListener('keydown', document, keydown[type]) clear() }, [type]) const clear = () => { setLength1(0) setLength2(0) setArrow1('') setArrow2('') setAngle1(0) } const mouseDown = (e) => { const pointer = canvas.getPointer(e.e) if (points.length === 0) { setPoints((prev) => [...prev, pointer]) } else { const lastPoint = points[points.length - 1] let newPoint = { x: pointer.x, y: pointer.y } const length = distanceBetweenPoints(lastPoint, newPoint) if (verticalHorizontalMode) { const vector = { x: pointer.x - points[points.length - 1].x, y: pointer.y - points[points.length - 1].y, } const slope = Math.abs(vector.y / vector.x) // 기울기 계산 let scaledVector if (slope >= 1) { // 기울기가 1 이상이면 x축 방향으로 그림 scaledVector = { x: 0, y: vector.y >= 0 ? Number(length) : -Number(length), } } else { // 기울기가 1 미만이면 y축 방향으로 그림 scaledVector = { x: vector.x >= 0 ? Number(length) : -Number(length), y: 0, } } const verticalLength = scaledVector.y const horizontalLength = scaledVector.x newPoint = { x: lastPoint.x + horizontalLength, y: lastPoint.y + verticalLength, } } setPoints((prev) => [...prev, newPoint]) } } useEffect(() => { canvas ?.getObjects() .filter((obj) => obj.name === 'outerLine' || obj.name === 'helpGuideLine') .forEach((obj) => { removeLine(obj) }) canvas?.remove(canvas?.getObjects().find((obj) => obj.name === 'startPoint')) // point가 변경 될때마다 이벤트 리스너를 제거하고 다시 등록 removeAllDocumentEventListeners() addDocumentEventListener('keydown', document, keydown[type]) if (points.length === 0) { return } 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: 'startPoint', }) canvas?.add(point) } else { 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 (points.length < 3) { return } /*if (lastPoint.x === firstPoint.x && lastPoint.y === firstPoint.y) { return } if (lastPoint.x === firstPoint.x || lastPoint.y === firstPoint.y) { 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 = new QLine([lastPoint.x, lastPoint.y, firstPoint.x, firstPoint.y], { stroke: 'grey', strokeWidth: 1, selectable: false, name: 'helpGuideLine', }) canvas?.add(line) addLineText(line) } else { const guideLine1 = new QLine([lastPoint.x, lastPoint.y, lastPoint.x, firstPoint.y], { stroke: 'grey', strokeWidth: 1, strokeDashArray: [1, 1, 1], name: 'helpGuideLine', }) const guideLine2 = new QLine([guideLine1.x2, guideLine1.y2, firstPoint.x, firstPoint.y], { stroke: 'grey', strokeWidth: 1, strokeDashArray: [1, 1, 1], name: 'helpGuideLine', }) if (guideLine1.length > 0) { canvas?.add(guideLine1) addLineText(guideLine1) } canvas?.add(guideLine2) addLineText(guideLine2) }*/ } }, [points]) const drawLine = (point1, point2, idx) => { addLine([point1.x, point1.y, point2.x, point2.y], { stroke: 'black', strokeWidth: 3, idx: idx, selectable: false, name: 'outerLine', }) } const settingLine = () => { const outerLines = canvas?.getObjects().filter((obj) => obj.name === 'outerLine') outerLines.forEach((line) => { removeLine(line) }) addPolygonByLines(outerLines, { fill: 'rgba(0,0,0,0)', stroke: 'black', strokeWidth: 3, }) setShowOutlineModal(false) } // 직각 완료될 경우 확인 const checkRightAngle = () => { 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 keydown = { outerLine: (e) => { if (points.length === 0) { return } 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 } const key = e.key const activeElem = document.activeElement switch (key) { case 'Down': // IE/Edge에서 사용되는 값 case 'ArrowDown': { if (activeElem === length1Ref.current) { setArrow1('↓') arrow1Ref.current = '↓' length2Ref.current.focus() } else if (activeElem === length2Ref.current) { if (arrow1Ref.current === '↓' || arrow1Ref.current === '↑') { break } setArrow2('↓') arrow2Ref.current = '↓' checkRightAngle() } break } case 'Up': // IE/Edge에서 사용되는 값 case 'ArrowUp': if (activeElem === length1Ref.current) { setArrow1('↑') arrow1Ref.current = '↑' length2Ref.current.focus() } else if (activeElem === length2Ref.current) { if (arrow1Ref.current === '↓' || arrow1Ref.current === '↑') { break } setArrow2('↑') arrow2Ref.current = '↑' checkRightAngle() } break case 'Left': // IE/Edge에서 사용되는 값 case 'ArrowLeft': if (activeElem === length1Ref.current) { setArrow1('←') arrow1Ref.current = '←' length2Ref.current.focus() } else if (activeElem === length2Ref.current) { if (arrow1Ref.current === '←' || arrow1Ref.current === '→') { break } setArrow2('←') arrow2Ref.current = '←' checkRightAngle() } break case 'Right': // IE/Edge에서 사용되는 값 case 'ArrowRight': if (activeElem === length1Ref.current) { setArrow1('→') arrow1Ref.current = '→' length2Ref.current.focus() } else if (activeElem === length2Ref.current) { if (arrow1Ref.current === '←' || arrow1Ref.current === '→') { break } setArrow2('→') arrow2Ref.current = '→' checkRightAngle() } break } }, leeGubae: (e) => { console.log('leegubae') }, angle: (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) => { console.log('diagonalLine') }, } /** * 일변전으로 돌아가기 */ 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 } return (

{getMessage('modal.cover.outline.drawing')}

{getMessage('modal.cover.outline.setting')}

{type === OUTER_LINE_TYPE.OUTER_LINE ? (
onlyNumberInputChange(e, setLength1)} placeholder="3000" />
) : type === OUTER_LINE_TYPE.RIGHT_ANGLE ? (
onlyNumberInputChange(e, setLength1)} placeholder="3000" />
onlyNumberInputChange(e, setLength2)} placeholder="3000" />
) : type === OUTER_LINE_TYPE.ANGLE ? (
onlyNumberInputChange(e, setLength1)} placeholder="3000" />
onlyNumberWithDotInputChange(e, setAngle1)} className="input-origin block" />
) : ( <> )}
) }