import { useState } from 'react' import { useRecoilState, useRecoilValue } from 'recoil' import { canvasSizeState, canvasState, canvasZoomState, currentObjectState, fontFamilyState, fontSizeState } from '@/store/canvasAtom' import { QPolygon } from '@/components/fabric/QPolygon' // 캔버스에 필요한 이벤트 export function useCanvasEvent() { const canvas = useRecoilValue(canvasState) const [canvasForEvent, setCanvasForEvent] = useState(null) const [currentObject, setCurrentObject] = useRecoilState(currentObjectState) const canvasSize = useRecoilValue(canvasSizeState) const fontSize = useRecoilValue(fontSizeState) const fontFamily = useRecoilValue(fontFamilyState) const [canvasZoom, setCanvasZoom] = useRecoilState(canvasZoomState) // 기본적인 이벤트 필요시 추가 const attachDefaultEventOnCanvas = () => { removeEventOnCanvas() canvas?.on('object:added', objectEvent.onChange) canvas?.on('object:added', objectEvent.addEvent) canvas?.on('object:modified', objectEvent.onChange) canvas?.on('object:removed', objectEvent.onChange) canvas?.on('selection:cleared', selectionEvent.cleared) canvas?.on('selection:created', selectionEvent.created) canvas?.on('selection:updated', selectionEvent.updated) /*canvas?.on('object:added', () => { document.addEventListener('keydown', handleKeyDown) })*/ canvas?.on('object:removed', objectEvent.removed) } const objectEvent = { onChange: (e) => { const target = e.target if (target) { // settleDown(target) } }, addEvent: (e) => { const target = e.target if (target.type === 'QPolygon' || target.type === 'QLine') { const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'lengthText') textObjs.forEach((obj) => { obj.bringToFront() }) } if (target.name === 'cell') { target.on('mousedown', () => { if (target.get('selected')) { target.set({ selected: false }) target.set({ fill: '#BFFD9F' }) } else { target.set({ selected: true }) target.set({ fill: 'red' }) } canvas?.renderAll() }) } if (target.name === 'trestle') { target.on('mousedown', () => { if (target.defense === 'north') { alert('북쪽은 선택 불가합니다.') return } if (target.get('selected')) { target.set({ strokeWidth: 1 }) target.set({ strokeDashArray: [5, 5] }) target.set({ selected: false }) } else { target.set({ strokeWidth: 5 }) target.set({ strokeDashArray: [0, 0] }) target.set({ selected: true }) } canvas?.renderAll() }) } if (target.type.toLowerCase().includes('text')) { target.set({ fontSize }) target.set({ fontFamily }) } if (target.name === 'lengthText') { const x = target.left const y = target.top target.lockMovementX = false target.lockMovementY = false // Add a property to store the previous value const previousValue = target.text target.on('selected', (e) => { Object.keys(target.controls).forEach((controlKey) => { target.setControlVisible(controlKey, false) }) }) /*target.on('editing:exited', () => { if (isNaN(target.text.trim())) { target.set({ text: previousValue }) canvas?.renderAll() return } const updatedValue = parseFloat(target.text.trim()) const targetParent = target.parent const points = targetParent.getCurrentPoints() const i = target.idx // Assuming target.index gives the index of the point const startPoint = points[i] const endPoint = points[(i + 1) % points.length] const dx = endPoint.x - startPoint.x const dy = endPoint.y - startPoint.y const currentLength = Math.sqrt(dx * dx + dy * dy) const scaleFactor = updatedValue / currentLength const newEndPoint = { x: startPoint.x + dx * scaleFactor, y: startPoint.y + dy * scaleFactor, } const newPoints = [...points] newPoints[(i + 1) % points.length] = newEndPoint for (let idx = i + 1; idx < points.length; idx++) { if (newPoints[idx].x === endPoint.x) { newPoints[idx].x = newEndPoint.x } else if (newPoints[idx].y === endPoint.y) { newPoints[idx].y = newEndPoint.y } } const newPolygon = new QPolygon(newPoints, targetParent.initOptions) canvas?.add(newPolygon) canvas?.remove(targetParent) canvas?.renderAll() })*/ target.on('moving', (e) => { if (target.parentDirection === 'left' || target.parentDirection === 'right') { const minX = target.minX const maxX = target.maxX if (target.left <= minX) { target.set({ left: minX, top: y }) } else if (target.left >= maxX) { target.set({ left: maxX, top: y }) } else { target.set({ top: y }) } } else if (target.parentDirection === 'top' || target.parentDirection === 'bottom') { const minY = target.minY const maxY = target.maxY if (target.top <= minY) { target.set({ left: x, top: minY }) } else if (target.top >= maxY) { target.set({ left: x, top: maxY }) } else { target.set({ left: x }) } } canvas?.renderAll() }) } }, removed: (e) => { const whiteList = ['mouseLine', 'guideLine'] if (whiteList.includes(e.target.name)) { } }, } const selectionEvent = { created: (e) => { const target = e.selected[0] setCurrentObject(target) }, cleared: (e) => { setCurrentObject(null) }, updated: (e) => { const target = e.selected[0] setCurrentObject(target) }, } const removeEventOnCanvas = () => { canvas?.off('object:added') canvas?.off('object:modified') canvas?.off('object:removed') canvas?.off('selection:cleared') canvas?.off('selection:created') canvas?.off('selection:updated') } /** * 각종 키보드 이벤트 * https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values */ const handleKeyDown = (e) => { const key = e.key switch (key) { case 'Delete': case 'Backspace': handleDelete() break case 'Down': // IE/Edge에서 사용되는 값 case 'ArrowDown': // "아래 화살표" 키가 눌렸을 때의 동작입니다. moveDown() break case 'Up': // IE/Edge에서 사용되는 값 case 'ArrowUp': // "위 화살표" 키가 눌렸을 때의 동작입니다. moveUp() break case 'Left': // IE/Edge에서 사용되는 값 case 'ArrowLeft': // "왼쪽 화살표" 키가 눌렸을 때의 동작입니다. moveLeft() break case 'Right': // IE/Edge에서 사용되는 값 case 'ArrowRight': // "오른쪽 화살표" 키가 눌렸을 때의 동작입니다. moveRight() break case 'Enter': // "enter" 또는 "return" 키가 눌렸을 때의 동작입니다. break case 'Esc': // IE/Edge에서 사용되는 값 case 'Escape': break case 'z': if (!e.ctrlKey) { return } console.log('뒤로가기') break default: return // 키 이벤트를 처리하지 않는다면 종료합니다. } e.preventDefault() } const moveDown = () => { const targetObj = canvas?.getActiveObject() if (!targetObj) { return } let top = targetObj.top + 10 if (top > canvasSize.vertical) { top = canvasSize.vertical } targetObj.set({ top: top }) targetObj.fire('modified') canvas?.renderAll() } const moveUp = () => { const targetObj = canvas?.getActiveObject() if (!targetObj) { return } let top = targetObj.top - 10 if (top < 0) { top = 0 } targetObj.set({ top: top }) targetObj.fire('modified') canvas?.renderAll() } const moveLeft = () => { const targetObj = canvas?.getActiveObject() if (!targetObj) { return } let left = targetObj.left - 10 if (left < 0) { left = 0 } targetObj.set({ left: left }) targetObj.fire('modified') canvas?.renderAll() } const moveRight = () => { const targetObj = canvas?.getActiveObject() if (!targetObj) { return } let left = targetObj.left + 10 if (left > canvasSize.horizontal) { left = canvasSize.horizontal } targetObj.set({ left: left }) targetObj.fire('modified') canvas?.renderAll() } /** * 선택한 도형을 삭제한다. */ const handleDelete = () => { const targets = canvas?.getActiveObjects() if (targets?.length === 0) { alert('삭제할 대상을 선택해주세요.') return } if (!confirm('정말로 삭제하시겠습니까?')) { return } targets?.forEach((target) => { canvas?.remove(target) }) } const handleZoomClear = () => { setCanvasZoom(100) canvas.set({ zoom: 1 }) canvas.viewportTransform = [1, 0, 0, 1, 0, 0] canvas.renderAll() } return { setCanvasForEvent, attachDefaultEventOnCanvas, handleZoomClear, } }