import { useEffect } from 'react' import { useRecoilValue } from 'recoil' import { wordDisplaySelector } from '@/store/settingAtom' import { useEvent } from '@/hooks/useEvent' import { checkLineOrientation, getDistance } from '@/util/canvas-util' import { dimensionLineSettingsState } from '@/store/commonUtilsAtom' import { fontSelector } from '@/store/fontAtom' import { canvasState } from '@/store/canvasAtom' import { v4 as uuidv4 } from 'uuid' import { usePopup } from '@/hooks/usePopup' import Distance from '@/components/floor-plan/modal/distance/Distance' export function useCommonUtils({ commonFunctionState, setCommonFunctionState }) { const canvas = useRecoilValue(canvasState) const wordDisplay = useRecoilValue(wordDisplaySelector) const { addCanvasMouseEventListener, addDocumentEventListener, initEvent } = useEvent() const dimensionSettings = useRecoilValue(dimensionLineSettingsState) const dimensionLineTextFont = useRecoilValue(fontSelector('dimensionLineText')) const commonTextFont = useRecoilValue(fontSelector('commonText')) const { addPopup } = usePopup() useEffect(() => { initEvent() if (commonFunctionState.text) { commonTextMode() } else if (commonFunctionState.dimension) { commonDimensionMode() } else if (commonFunctionState.distance) { commonDistanceMode() } }, [commonFunctionState, dimensionSettings, commonTextFont, dimensionLineTextFont]) const commonTextMode = () => { let textbox if (commonFunctionState.text) { addCanvasMouseEventListener('mouse:down', (event) => { const pointer = canvas?.getPointer(event.e) textbox = new fabric.Textbox('', { left: pointer.x, top: pointer.y, width: 200, editable: true, name: 'commonText', visible: wordDisplay, fill: commonTextFont.fontColor.value, fontFamily: commonTextFont.fontFamily.value, fontSize: commonTextFont.fontSize.value, fontStyle: commonTextFont.fontWeight.value, }) canvas?.add(textbox) canvas.setActiveObject(textbox) textbox.enterEditing() textbox.selectAll() }) addDocumentEventListener('keydown', document, (e) => { if (e.key === 'Enter') { if (commonFunctionState.text) { const activeObject = canvas.getActiveObject() if (activeObject && activeObject.isEditing) { if (activeObject.text === '') { canvas?.remove(activeObject) } else { activeObject.exitEditing() } //정책 협의 const texts = canvas.getObjects().filter((obj) => obj.name === 'commonText') texts.forEach((text) => { text.set({ editable: false }) }) canvas.renderAll() } } } }) } } const commonDimensionMode = () => { if (commonFunctionState.dimension) { let points = [] let distanceText = null let minX, minY, maxX, maxY // 화살표를 생성하는 함수 function createArrow(x, y, angle) { return new fabric.Triangle({ left: x, top: y, originX: 'center', originY: 'center', angle: angle, width: 15, height: 15, fill: dimensionSettings.color, selectable: false, }) } const circleOptions = { radius: 5, strokeWidth: 2, stroke: 'red', fill: 'white', selectable: false, } const lineOptions = { stroke: dimensionSettings.color, strokeWidth: dimensionSettings.pixel, name: 'dimensionLine', selectable: true, } // 캔버스에 클릭 이벤트 추가 addCanvasMouseEventListener('mouse:down', (e) => { const pointer = canvas.getPointer(e.e) let point if (points.length === 0) { // 첫 번째 포인트는 그대로 클릭한 위치에 추가 point = new fabric.Circle({ left: pointer.x - 5, // 반지름 반영 top: pointer.y - 5, // 반지름 반영 ...circleOptions, }) points.push(point) canvas.add(point) } else if (points.length === 1) { // 두 번째 포인트는 첫 번째 포인트를 기준으로 수평 또는 수직으로만 배치 const p1 = points[0] const deltaX = Math.abs(pointer.x - (p1.left + p1.radius)) const deltaY = Math.abs(pointer.y - (p1.top + p1.radius)) if (deltaX > deltaY) { // 수평선 상에만 배치 (y 좌표 고정) point = new fabric.Circle({ left: pointer.x - 5, // 반지름 반영 top: p1.top, // y 좌표 고정 ...circleOptions, }) } else { // 수직선 상에만 배치 (x 좌표 고정) point = new fabric.Circle({ left: p1.left, // x 좌표 고정 top: pointer.y - 5, // 반지름 반영 ...circleOptions, }) } points.push(point) canvas.add(point) // 두 포인트의 중심 좌표 계산 const p2 = points[1] const p1CenterX = p1.left + p1.radius const p1CenterY = p1.top + p1.radius const p2CenterX = p2.left + p2.radius const p2CenterY = p2.top + p2.radius points.forEach((point) => { canvas?.remove(point) }) // 두 포인트 간에 직선을 그림 (중심을 기준으로) const line = new fabric.Line([p1CenterX, p1CenterY, p2CenterX, p2CenterY], lineOptions) canvas.add(line) const distance = getDistance(p1CenterX, p1CenterY, p2CenterX, p2CenterY) const lineDirection = checkLineOrientation(line) let extendLine = [] if (lineDirection === 'horizontal') { extendLine = [ [line.x1, line.y1 - 20, line.x1, line.y1 + 20], [line.x2, line.y2 - 20, line.x2, line.y2 + 20], ] } else { extendLine = [ [line.x1 - 20, line.y1, line.x1 + 20, line.y1], [line.x2 - 20, line.y2, line.x2 + 20, line.y2], ] } extendLine.forEach((line) => { const extendLine = new fabric.Line(line, lineOptions) canvas.add(extendLine) }) // 첫 번째 포인트에 화살표 추가 const paddingX = lineDirection === 'horizontal' ? p1CenterX + 7.5 : p1CenterX + 1 const paddingX2 = lineDirection === 'horizontal' ? p2CenterX - 6.5 : p2CenterX + 1 const paddingY = lineDirection === 'horizontal' ? p1CenterY + 1 : p1CenterY + 8 const paddingY2 = lineDirection === 'horizontal' ? p2CenterY + 1 : p2CenterY - 8 const arrow1 = createArrow(paddingX, paddingY, lineDirection === 'horizontal' ? -90 : 0) // 반대 방향 화살표 const arrow2 = createArrow(paddingX2, paddingY2, lineDirection === 'horizontal' ? 90 : 180) // 정방향 화살표 canvas.add(arrow1) canvas.add(arrow2) distanceText = new fabric.Text(`${distance * 10} `, { left: (p1CenterX + p2CenterX) / 2 + (lineDirection === 'horizontal' ? 0 : -15), top: (p1CenterY + p2CenterY) / 2 + (lineDirection === 'horizontal' ? +15 : 0), fill: dimensionLineTextFont.fontColor.value, fontSize: dimensionLineTextFont.fontSize.value, fontFamily: dimensionLineTextFont.fontFamily.value, fontStyle: dimensionLineTextFont.fontWeight.value, selectable: true, textAlign: 'center', originX: 'center', originY: 'center', angle: lineDirection === 'horizontal' ? 0 : 270, name: 'dimensionLineText', // lockMovementX: false, // lockMovementY: false, }) canvas.add(distanceText) // minX = p1CenterX // maxX = p2CenterX // minY = p1CenterY // maxY = p2CenterY // 거리 계산 후, 다음 측정을 위해 초기화 points = [] } // 캔버스 다시 그리기 canvas.renderAll() }) // addCanvasMouseEventListener('object:moving', function (e) { // const obj = e.target // if (obj.left < minX) { // obj.left = minX // } // if (obj.left + obj.width > maxX) { // obj.left = maxX - obj.width // } // if (obj.top < minY) { // obj.top = minY // } // if (obj.top + obj.height > maxY) { // obj.top = maxY - obj.height // } // }) } } const commonDistanceMode = () => { if (commonFunctionState.distance) { let points = [] let distanceText = null const circleOptions = { radius: 5, strokeWidth: 2, stroke: 'red', fill: 'white', selectable: false, } const lineOptions = { stroke: 'black', strokeWidth: 2, selectable: false, strokeDashArray: [9, 5], } const textOptions = { fill: 'black', fontSize: 16, selectable: true, textAlign: 'center', originX: 'center', originY: 'center', } // 캔버스에 클릭 이벤트 추가 addCanvasMouseEventListener('mouse:down', function (options) { const pointer = canvas.getPointer(options.e) let point let cross = {} if (points.length === 0) { point = new fabric.Line([pointer.x - 10, pointer.y, pointer.x + 10, pointer.y], { stroke: 'black', strokeWidth: 1, originX: 'center', originY: 'center', }) canvas.add(point) cross['x'] = parseInt(point.left.toFixed(0)) // 세로 선 생성 (십자 모양의 다른 축) point = new fabric.Line([pointer.x, pointer.y - 10, pointer.x, pointer.y + 10], { stroke: 'black', strokeWidth: 1, originX: 'center', originY: 'center', }) cross['y'] = parseInt(point.top.toFixed(0)) canvas.add(point) points.push(cross) } else if (points.length === 1) { // 두 번째 포인트는 첫 번째 포인트를 기준으로 수평 또는 수직으로만 배치 const p1 = points[0] point = new fabric.Line([pointer.x - 10, pointer.y, pointer.x + 10, pointer.y], { stroke: 'black', strokeWidth: 1, originX: 'center', originY: 'center', }) canvas.add(point) cross['x'] = parseInt(point.left.toFixed(0)) // 세로 선 생성 (십자 모양의 다른 축) point = new fabric.Line([pointer.x, pointer.y - 10, pointer.x, pointer.y + 10], { stroke: 'black', strokeWidth: 1, originX: 'center', originY: 'center', }) canvas.add(point) cross['y'] = parseInt(point.top.toFixed(0)) points.push(cross) let isParallel = false if (points[0].x === points[1].x || points[0].y === points[1].y) { isParallel = true } // 두 포인트의 중심 좌표 계산 const p2 = points[1] const p1CenterX = p1.x const p1CenterY = p1.y const p2CenterX = p2.x const p2CenterY = p2.y // 두 포인트 간에 직선을 그림 (중심을 기준으로) const line = new fabric.Line([p1CenterX, p1CenterY, p2CenterX, p2CenterY], lineOptions) canvas.add(line) const distance1 = getDistance(p1CenterX, p1CenterY, p2CenterX, p2CenterY) // 거리 텍스트가 이미 있으면 업데이트하고, 없으면 새로 생성 distanceText = new fabric.Text(`${distance1 * 10}`, { left: (p1CenterX + p2CenterX) / 2, top: (p1CenterY + p2CenterY) / 2, ...textOptions, }) canvas.add(distanceText) if (!isParallel) { const p3 = new fabric.Point(p2CenterX, p1CenterY) const line2 = new fabric.Line([p2CenterX, p2CenterY, p3.x, p3.y], lineOptions) const line3 = new fabric.Line([p3.x, p3.y, p1CenterX, p1CenterY], lineOptions) canvas.add(line2) canvas.add(line3) const distance2 = getDistance(p2CenterX, p2CenterY, p3.x, p3.y) const distance3 = getDistance(p3.x, p3.y, p1CenterX, p1CenterY) distanceText = new fabric.Text(`${distance2 * 10}`, { left: (p2CenterX + p3.x) / 2, top: (p2CenterY + p3.y) / 2, ...textOptions, }) canvas.add(distanceText) distanceText = new fabric.Text(`${distance3 * 10}`, { left: (p3.x + p1CenterX) / 2, top: (p3.y + p1CenterY) / 2, ...textOptions, }) canvas.add(distanceText) const id = uuidv4() addPopup( id, 1, , ) } // 거리 계산 후, 다음 측정을 위해 초기화 points = [] } // 캔버스 다시 그리기 canvas.renderAll() }) } } const commonFunctions = (mode) => { let tempStates = { ...commonFunctionState } if (tempStates[mode]) { tempStates[mode] = false } else { Object.keys(tempStates).forEach((key) => { tempStates[key] = false }) if (mode !== undefined) { tempStates[mode] = true } } if (setCommonFunctionState) setCommonFunctionState(tempStates) } return { commonFunctions, dimensionSettings, } }