433 lines
12 KiB
JavaScript
433 lines
12 KiB
JavaScript
import { useEffect, useState } from 'react'
|
|
import { useRecoilState, useRecoilValue } from 'recoil'
|
|
import { v4 as uuidv4 } from 'uuid'
|
|
import { canvasSizeState, canvasState, canvasZoomState, currentObjectState } from '@/store/canvasAtom'
|
|
import { QPolygon } from '@/components/fabric/QPolygon'
|
|
import { usePlan } from '@/hooks/usePlan'
|
|
import { fontSelector } from '@/store/fontAtom'
|
|
|
|
// 캔버스에 필요한 이벤트
|
|
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 { modifiedPlanFlag, setModifiedPlanFlag } = usePlan()
|
|
|
|
useEffect(() => {
|
|
canvas?.setZoom(canvasZoom / 100)
|
|
}, [canvasZoom])
|
|
|
|
// 기본적인 이벤트 필요시 추가
|
|
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.name !== 'mouseLine' && !modifiedPlanFlag) {
|
|
setModifiedPlanFlag((prev) => !prev)
|
|
}
|
|
|
|
if (target) {
|
|
// settleDown(target)
|
|
}
|
|
},
|
|
addEvent: (e) => {
|
|
const target = e.target
|
|
|
|
if (!target.id) {
|
|
target.id = uuidv4()
|
|
}
|
|
if (!target.uuid) {
|
|
target.uuid = uuidv4()
|
|
}
|
|
|
|
if (target.name !== 'mouseLine' && !modifiedPlanFlag) {
|
|
setModifiedPlanFlag((prev) => !prev)
|
|
}
|
|
|
|
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.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('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) => {
|
|
target.uuid = uuidv4()
|
|
if (!modifiedPlanFlag) {
|
|
setModifiedPlanFlag((prev) => !prev)
|
|
}
|
|
|
|
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') {
|
|
obj.set({ stroke: 'red' })
|
|
}
|
|
})
|
|
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' })
|
|
}
|
|
}
|
|
})
|
|
}
|
|
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 (selected?.length > 0) {
|
|
selected.forEach((obj) => {
|
|
if (obj.type === 'QPolygon') {
|
|
obj.set({ stroke: 'red' })
|
|
}
|
|
})
|
|
}
|
|
|
|
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) => {
|
|
if (isZoom) {
|
|
setCanvasZoom(canvasZoom + 10)
|
|
} else {
|
|
setCanvasZoom(canvasZoom - 10)
|
|
}
|
|
}
|
|
|
|
const handleZoomClear = () => {
|
|
setCanvasZoom(100)
|
|
canvas.set({ zoom: 1 })
|
|
canvas.viewportTransform = [1, 0, 0, 1, 0, 0]
|
|
canvas.renderAll()
|
|
}
|
|
|
|
return {
|
|
setCanvasForEvent,
|
|
attachDefaultEventOnCanvas,
|
|
handleZoomClear,
|
|
handleZoom,
|
|
}
|
|
}
|