670 lines
21 KiB
JavaScript

'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 (
<WithDraggable isShow={true} pos={{ x: 50, y: -1000 + 50 }}>
<div className={`modal-pop-wrap ssm`}>
<div className="modal-head">
<h1 className="title">{getMessage('modal.cover.outline.drawing')}</h1>
<button className="modal-close" onClick={() => setShowOutlineModal(false)}>
닫기
</button>
</div>
<div className="modal-body">
<div className="modal-btn-wrap">
<button
className={`btn-frame modal ${type === OUTER_LINE_TYPE.OUTER_LINE ? 'act' : ''}`}
onClick={() => setType(OUTER_LINE_TYPE.OUTER_LINE)}
>
{getMessage('modal.cover.outline')}
</button>
<button
className={`btn-frame modal ${type === OUTER_LINE_TYPE.RIGHT_ANGLE ? 'act' : ''}`}
onClick={() => setType(OUTER_LINE_TYPE.RIGHT_ANGLE)}
>
{getMessage('modal.cover.outline.right.angle')}
</button>
<button
className={`btn-frame modal ${type === OUTER_LINE_TYPE.LEE_GUBAE ? 'act' : ''}`}
onClick={() => setType(OUTER_LINE_TYPE.LEE_GUBAE)}
>
{getMessage('modal.cover.outline2')}
</button>
<button className={`btn-frame modal ${type === OUTER_LINE_TYPE.ANGLE ? 'act' : ''}`} onClick={() => setType(OUTER_LINE_TYPE.ANGLE)}>
{getMessage('modal.cover.outline.angle')}
</button>
<button
className={`btn-frame modal ${type === OUTER_LINE_TYPE.DIAGONAL_LINE ? 'act' : ''}`}
onClick={() => setType(OUTER_LINE_TYPE.DIAGONAL_LINE)}
>
{getMessage('modal.cover.outline.diagonal')}
</button>
</div>
<div className="modal-check-btn-wrap">
<h3 className="check-wrap-title">{getMessage('modal.cover.outline.setting')}</h3>
{type === OUTER_LINE_TYPE.OUTER_LINE ? (
<div className="outer-line-wrap">
<div className="form-input">
<label htmlFor="">{getMessage('modal.cover.outline.length')}</label>
<input
type="text"
className="input-origin block"
value={length1}
ref={length1Ref}
onChange={(e) => onlyNumberInputChange(e, setLength1)}
placeholder="3000"
/>
</div>
<div className="form-input">
<label htmlFor="">{getMessage('modal.cover.outline.arrow')}</label>
<input type="text" readOnly={true} value={arrow1} className="input-origin block" />
</div>
</div>
) : type === OUTER_LINE_TYPE.RIGHT_ANGLE ? (
<div className="outer-line-wrap">
<div className="form-input">
<label htmlFor="">{getMessage('modal.cover.outline.length')}</label>
<input
type="text"
className="input-origin block"
value={length1}
ref={length1Ref}
onChange={(e) => onlyNumberInputChange(e, setLength1)}
placeholder="3000"
/>
</div>
<div className="form-input">
<label htmlFor="">{getMessage('modal.cover.outline.arrow')}</label>
<input type="text" readOnly={true} value={arrow1} className="input-origin block" />
</div>
<div className="form-input">
<label htmlFor="">{getMessage('modal.cover.outline.length')}</label>
<input
type="text"
className="input-origin block"
value={length2}
ref={length2Ref}
onChange={(e) => onlyNumberInputChange(e, setLength2)}
placeholder="3000"
/>
</div>
<div className="form-input">
<label htmlFor="">{getMessage('modal.cover.outline.arrow')}</label>
<input type="text" readOnly={true} value={arrow2} className="input-origin block" />
</div>
</div>
) : type === OUTER_LINE_TYPE.ANGLE ? (
<div className="outer-line-wrap">
<div className="form-input">
<label htmlFor="">{getMessage('modal.cover.outline.length')}</label>
<input
type="text"
className="input-origin block"
value={length1}
ref={length1Ref}
onChange={(e) => onlyNumberInputChange(e, setLength1)}
placeholder="3000"
/>
</div>
<div className="form-input">
<label htmlFor="">{getMessage('modal.cover.outline.angle')}</label>
<input
type="text"
value={angle1}
ref={angle1Ref}
onChange={(e) => onlyNumberWithDotInputChange(e, setAngle1)}
className="input-origin block"
/>
</div>
</div>
) : (
<></>
)}
<div className="flex-check-box for2 btn">
<button className={`arr-btn dark ${points.length >= 3 ? 'act' : ''}`} onClick={handleFix}>
<span>{getMessage('modal.cover.outline.fix')}</span>
</button>
<button className={`arr-btn dark ${points.length >= 1 ? 'act' : ''}`} onClick={handleRollback}>
<span>{getMessage('modal.cover.outline.rollback')}</span>
</button>
<button className="arr-btn dark">
<span>{getMessage('modal.cover.outline.remove')}</span>
</button>
<button className="arr-btn dark">
<span>{getMessage('modal.cover.outline.select.move')}</span>
</button>
</div>
</div>
</div>
</div>
</WithDraggable>
)
}