qcast-front/src/hooks/useCanvasEvent.js
2025-12-24 09:50:47 +09:00

457 lines
12 KiB
JavaScript

import { useEffect, useState } from 'react'
import { useRecoilState, useRecoilValue } from 'recoil'
import { v4 as uuidv4 } from 'uuid'
import { canvasSizeState, canvasState, canvasZoomState, currentMenuState, currentObjectState } from '@/store/canvasAtom'
import { QPolygon } from '@/components/fabric/QPolygon'
import { fontSelector } from '@/store/fontAtom'
import { MENU, POLYGON_TYPE } from '@/common/common'
import { useText } from '@/hooks/useText'
import { usePolygon } from '@/hooks/usePolygon'
// 캔버스에 필요한 이벤트
export function useCanvasEvent() {
const canvas = useRecoilValue(canvasState)
const [canvasForEvent, setCanvasForEvent] = useState(null)
const [currentObject, setCurrentObject] = useRecoilState(currentObjectState)
const canvasSize = useRecoilValue(canvasSizeState)
const [canvasZoom, setCanvasZoom] = useRecoilState(canvasZoomState)
const lengthTextOption = useRecoilValue(fontSelector('lengthText'))
const currentMenu = useRecoilValue(currentMenuState)
const { changeCorridorDimensionText } = useText()
const { setPolygonLinesActualSize } = usePolygon()
useEffect(() => {
canvas?.setZoom(canvasZoom / 100)
}, [canvasZoom])
useEffect(() => {
attachDefaultEventOnCanvas()
}, [currentMenu])
// 기본적인 이벤트 필요시 추가
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)
// roof 이동 후 좌표 재계산
if (target.name === POLYGON_TYPE.ROOF && target.type === 'QPolygon') {
target.fire('polygonMoved')
}
}
},
addEvent: (e) => {
const target = e.target
if (!target.id) {
target.id = uuidv4()
}
if (!target.uuid) {
target.uuid = uuidv4()
}
if (target.type === 'QPolygon' || target.type === 'QLine') {
const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'lengthText')
textObjs.forEach((obj) => {
obj.bringToFront()
})
if (target.name === POLYGON_TYPE.ROOF) {
setTimeout(() => {
setPolygonLinesActualSize(target)
changeCorridorDimensionText()
}, 300)
}
}
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.name === 'lengthText' && target.type.toLowerCase().includes('text') > 0) {
const x = target.left
const y = target.top
target.lockMovementX = false
target.lockMovementY = false
target.fill = lengthTextOption.fontColor.value
target.fontFamily = lengthTextOption.fontFamily.value
target.fontSize = lengthTextOption.fontSize.value
target.fontStyle = lengthTextOption.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal'
target.fontWeight = lengthTextOption.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal'
// 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('moving', (e) => {
target.uuid = uuidv4()
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)
const { selected } = e
if (selected?.length > 0) {
selected.forEach((obj) => {
// if (obj.type === 'QPolygon' && currentMenu !== MENU.MODULE_CIRCUIT_SETTING.BASIC_SETTING) {
if (obj.type === 'QPolygon') {
const originStroke = obj.stroke
obj.set({ stroke: 'red' })
if (currentMenu === MENU.MODULE_CIRCUIT_SETTING.BASIC_SETTING) {
if (obj.name === POLYGON_TYPE.MODULE) {
obj.set({ strokeWidth: 3 })
}
if (obj.name === POLYGON_TYPE.ROOF) {
canvas.discardActiveObject()
obj.set({ stroke: originStroke })
}
}
}
})
canvas.renderAll()
}
},
cleared: (e) => {
setCurrentObject(null)
const { deselected } = e
if (deselected?.length > 0) {
deselected.forEach((obj) => {
if (obj.type === 'QPolygon') {
if (obj.name !== 'moduleSetupSurface') {
obj.set({ stroke: 'black' })
}
if (obj.name === POLYGON_TYPE.MODULE && currentMenu === MENU.MODULE_CIRCUIT_SETTING.BASIC_SETTING) {
obj.set({ strokeWidth: 0.3 })
}
}
})
}
canvas.renderAll()
},
updated: (e) => {
const target = e.selected[0]
setCurrentObject(target)
const { selected, deselected } = e
if (deselected?.length > 0) {
deselected.forEach((obj) => {
if (obj.type === 'QPolygon') {
obj.set({ stroke: 'black' })
if (obj.name === POLYGON_TYPE.MODULE && currentMenu === MENU.MODULE_CIRCUIT_SETTING.BASIC_SETTING) {
//모듈 미선택시 라인 두께 변경
obj.set({ strokeWidth: 0.3 })
}
}
})
}
if (selected?.length > 0) {
selected.forEach((obj) => {
if (obj.type === 'QPolygon') {
obj.set({ stroke: 'red' })
if (obj.name === POLYGON_TYPE.MODULE && currentMenu === MENU.MODULE_CIRCUIT_SETTING.BASIC_SETTING) {
//모듈 선택시 라인 두께 변경
obj.set({ strokeWidth: 3 })
}
}
})
}
canvas.renderAll()
},
}
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 handleZoom = (isZoom) => {
let zoom
if (isZoom) {
zoom = canvasZoom + 10
if (zoom > 500) {
return
}
} else {
zoom = canvasZoom - 10
if (zoom < 10) {
//50%->10%
return
}
}
setCanvasZoom(zoom)
}
const handleZoomClear = () => {
setCanvasZoom(100)
zoomToAllObjects()
canvas.renderAll()
}
const zoomToAllObjects = () => {
const objects = canvas.getObjects().filter((obj) => obj.visible)
if (objects.length === 0) return
let minX = Infinity,
minY = Infinity
let maxX = -Infinity,
maxY = -Infinity
objects.forEach((obj) => {
const bounds = obj.getBoundingRect()
minX = Math.min(minX, bounds.left)
minY = Math.min(minY, bounds.top)
maxX = Math.max(maxX, bounds.left + bounds.width)
maxY = Math.max(maxY, bounds.top + bounds.height)
})
const centerX = (minX + maxX) / 2
const centerY = (minY + maxY) / 2
const centerPoint = new fabric.Point(centerX, centerY)
canvas.zoomToPoint(centerPoint, 1)
canvas.renderAll()
}
return {
setCanvasForEvent,
attachDefaultEventOnCanvas,
handleZoomClear,
handleZoom,
}
}