qcast-front/src/hooks/useCanvas.js

566 lines
14 KiB
JavaScript

import { useEffect, useState } from 'react'
import { fabric } from 'fabric'
import { actionHandler, anchorWrapper, polygonPositionHandler } from '@/util/canvas-util'
import { useRecoilState } from 'recoil'
import { canvasSizeState, canvasState, fontSizeState } from '@/store/canvasAtom'
import { QLine } from '@/components/fabric/QLine'
import { QPolygon } from '@/components/fabric/QPolygon'
import { defineQLine } from '@/util/qline-utils'
import { defineQPloygon } from '@/util/qpolygon-utils'
import { writeImage } from '@/lib/canvas'
import { useCanvasEvent } from '@/hooks/useCanvasEvent'
import { useAxios } from '@/hooks/useAxios'
import { useFont } from '@/hooks/common/useFont'
import { OBJECT_PROTOTYPE, RELOAD_TYPE_PROTOTYPE, SAVE_KEY } from '@/common/common'
export function useCanvas(id) {
const [canvas, setCanvas] = useRecoilState(canvasState)
const [isLocked, setIsLocked] = useState(false)
const [history, setHistory] = useState([])
const [backImg, setBackImg] = useState()
const [canvasSize] = useRecoilState(canvasSizeState)
const [fontSize] = useRecoilState(fontSizeState)
const { setCanvasForEvent, attachDefaultEventOnCanvas } = useCanvasEvent()
const { post } = useAxios()
const {} = useFont()
/**
* 처음 셋팅
*/
useEffect(() => {
const c = new fabric.Canvas(id, {
height: canvasSize.vertical,
width: canvasSize.horizontal,
backgroundColor: 'white',
selection: false,
})
setCanvas(c)
setCanvasForEvent(c)
attachDefaultEventOnCanvas()
return () => {
// c.dispose()
c.clear()
}
}, [])
useEffect(() => {
// canvas 사이즈가 변경되면 다시
}, [canvasSize])
useEffect(() => {
canvas
?.getObjects()
?.filter((obj) => obj.type === 'textbox' || obj.type === 'text' || obj.type === 'i-text')
.forEach((obj) => {
obj.set({ fontSize: fontSize })
})
canvas
?.getObjects()
?.filter((obj) => obj.type === 'QLine' || obj.type === 'QPolygon' || obj.type === 'QRect')
.forEach((obj) => {
obj.setFontSize(fontSize)
})
canvas?.getObjects().length > 0 && canvas?.renderAll()
}, [fontSize])
/**
* 캔버스 초기화
*/
useEffect(() => {
if (canvas) {
initialize()
attachDefaultEventOnCanvas()
}
}, [canvas])
/**
* 마우스 포인터의 가이드라인을 제거합니다.
*/
const removeMouseLines = () => {
if (canvas?._objects.length > 0) {
const mouseLines = canvas?._objects.filter((obj) => obj.name === 'mouseLine')
mouseLines.forEach((item) => canvas?.remove(item))
}
canvas?.renderAll()
}
const initialize = () => {
canvas.getObjects().length > 0 && canvas?.clear()
// settings for all canvas in the app
fabric.Object.prototype.transparentCorners = false
fabric.Object.prototype.selectable = true
fabric.Object.prototype.lockMovementX = true
fabric.Object.prototype.lockMovementY = true
fabric.Object.prototype.lockRotation = true
fabric.Object.prototype.lockScalingX = true
fabric.Object.prototype.lockScalingY = true
fabric.Object.prototype.cornerColor = '#2BEBC8'
fabric.Object.prototype.cornerStyle = 'rect'
fabric.Object.prototype.cornerStrokeColor = '#2BEBC8'
fabric.Object.prototype.cornerSize = 6
// 해당 오브젝트 타입의 경우 저장한 값 그대로 불러와야함
OBJECT_PROTOTYPE.forEach((type) => {
type.toObject = function (propertiesToInclude) {
let source = {}
for (let key in this) {
if (typeof this[key] !== 'function' && SAVE_KEY.includes(key)) {
source.key = this[key]
}
}
return fabric.util.object.extend(this.callSuper('toObject', propertiesToInclude), source)
}
})
fabric.QLine = QLine
fabric.QPolygon = QPolygon
QPolygon.prototype.canvas = canvas
QLine.prototype.canvas = canvas
defineQLine()
defineQPloygon()
}
/**
* 캔버스에 도형을 추가한다. 도형은 사용하는 페이지에서 만들어서 파라미터로 넘겨주어야 한다.
*/
const addShape = (shape) => {
canvas?.add(shape)
canvas?.setActiveObject(shape)
canvas?.requestRenderAll()
}
const onChange = (e) => {
const target = e.target
if (target) {
// settleDown(target)
}
if (!isLocked) {
setHistory([])
}
setIsLocked(false)
}
/**
* 눈금 모양에 맞게 움직이도록 한다.
*/
const settleDown = (shape) => {
const left = Math.round(shape?.left / 10) * 10
const top = Math.round(shape?.top / 10) * 10
shape?.set({ left: left, top: top })
}
/**
* redo, undo가 필요한 곳에서 사용한다.
*/
const handleUndo = () => {
if (canvas) {
if (canvas?._objects.length > 0) {
const poppedObject = canvas?._objects.pop()
const group = []
group.push(poppedObject)
if (poppedObject.parent || poppedObject.parentId) {
canvas
?.getObjects()
.filter((obj) => obj.parent === poppedObject.parent || obj.parentId === poppedObject.parentId || obj === poppedObject.parent)
.forEach((obj) => {
group.push(obj)
canvas?.remove(obj)
})
}
setHistory((prev) => {
if (prev === undefined) {
return poppedObject ? [group] : []
}
return poppedObject ? [...prev, group] : prev
})
canvas?.renderAll()
}
}
}
const handleRedo = () => {
if (canvas && history) {
if (history.length > 0) {
setIsLocked(true)
if (Array.isArray(history[history.length - 1])) {
history[history.length - 1].forEach((obj) => {
canvas?.add(obj)
})
} else {
canvas?.add(history[history.length - 1])
}
const newHistory = history.slice(0, -1)
setHistory(newHistory)
}
}
}
/**
* 선택한 도형을 복사한다.
*/
const handleCopy = () => {
const activeObjects = canvas?.getActiveObjects()
if (activeObjects?.length === 0) {
return
}
activeObjects?.forEach((obj) => {
obj.clone((cloned) => {
cloned.set({ left: obj.left + 10, top: obj.top + 10 })
addShape(cloned)
})
})
}
/**
* 페이지 내 캔버스 저장
* todo : 현재는 localStorage에 그냥 저장하지만 나중에 변경해야함
*/
const handleSave = () => {
const objects = canvas?.getObjects()
if (objects?.length === 0) {
alert('저장할 대상이 없습니다.')
return
}
const jsonStr = JSON.stringify(canvas)
localStorage.setItem('canvas', jsonStr)
}
/**
* 페이지 내 캔버스에 저장한 내용 불러오기
* todo : 현재는 localStorage에 그냥 저장하지만 나중에 변경해야함
*/
const handlePaste = () => {
const jsonStr = localStorage.getItem('canvas')
if (!jsonStr) {
alert('붙여넣기 할 대상이 없습니다.')
return
}
canvas?.loadFromJSON(JSON.parse(jsonStr), () => {
localStorage.removeItem('canvas')
console.log('paste done')
})
}
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 })
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 })
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 })
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 })
canvas?.renderAll()
}
const handleRotate = (degree = 45) => {
const target = canvas?.getActiveObject()
if (!target) {
return
}
const currentAngle = target.angle
target.set({ angle: currentAngle + degree })
canvas?.renderAll()
}
/**
* Polygon 타입만 가능
* 생성한 polygon을 넘기면 해당 polygon은 꼭지점으로 컨트롤 가능한 polygon이 됨
*/
const attachCustomControlOnPolygon = (poly) => {
const lastControl = poly.points?.length - 1
poly.cornerStyle = 'rect'
poly.cornerColor = 'rgba(0,0,255,0.5)'
poly.objectCaching = false
poly.controls = poly.points.reduce(function (acc, point, index) {
acc['p' + index] = new fabric.Control({
positionHandler: polygonPositionHandler,
actionHandler: anchorWrapper(index > 0 ? index - 1 : lastControl, actionHandler),
actionName: 'modifyPolygon',
pointIndex: index,
})
return acc
}, {})
poly.hasBorders = !poly.edit
canvas?.requestRenderAll()
}
/**
* 이미지로 저장하는 함수
* @param {string} title - 저장할 이미지 이름
*/
const saveImage = async (title = 'canvas', userId, setThumbnails) => {
removeMouseLines()
await writeImage(title, canvas?.toDataURL('image/png').replace('data:image/png;base64,', ''))
.then((res) => {
console.log('success', res)
})
.catch((err) => {
console.log('err', err)
})
const canvasStatus = addCanvas()
const patternData = {
userId: userId,
imageName: title,
objectNo: 'test123240822001',
canvasStatus: JSON.stringify(canvasStatus).replace(/"/g, '##'),
}
await post({ url: '/api/canvas-management/canvas-statuses', data: patternData })
setThumbnails((prev) => [...prev, { imageName: `/canvasState/${title}.png`, userId, canvasStatus: JSON.stringify(canvasStatus) }])
}
const handleFlip = () => {
const target = canvas?.getActiveObject()
if (!target) {
return
}
// 현재 scaleX 및 scaleY 값을 가져옵니다.
const scaleX = target.scaleX
// const scaleY = target.scaleY;
// 도형을 반전시킵니다.
target.set({
scaleX: scaleX * -1,
// scaleY: scaleY * -1
})
// 캔버스를 다시 그립니다.
canvas?.renderAll()
}
function fillCanvasWithDots(canvas, gap) {
const width = canvas.getWidth()
const height = canvas.getHeight()
for (let x = 0; x < width; x += gap) {
for (let y = 0; y < height; y += gap) {
const circle = new fabric.Circle({
radius: 1,
fill: 'black',
left: x,
top: y,
selectable: false,
})
canvas.add(circle)
}
}
canvas?.renderAll()
}
const setCanvasBackgroundWithDots = (canvas, gap) => {
// Create a new canvas and fill it with dots
const tempCanvas = new fabric.StaticCanvas()
tempCanvas.setDimensions({
width: canvas.getWidth(),
height: canvas.getHeight(),
})
fillCanvasWithDots(tempCanvas, gap)
// Convert the dotted canvas to an image
const dataUrl = tempCanvas.toDataURL({ format: 'png' })
// Set the image as the background of the original canvas
fabric.Image.fromURL(dataUrl, function (img) {
canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas), {
scaleX: canvas.width / img.width,
scaleY: canvas.height / img.height,
})
})
}
const addCanvas = () => {
// const canvasState = canvas
const objs = canvas?.toJSON([
'selectable',
'name',
'parentId',
'id',
'length',
'idx',
'direction',
'lines',
'points',
'lockMovementX',
'lockMovementY',
'lockRotation',
'lockScalingX',
'lockScalingY',
'opacity',
'cells',
'maxX',
'maxY',
'minX',
'minY',
'x',
'y',
'stickeyPoint',
])
const str = JSON.stringify(objs)
canvas?.clear()
return str
// setTimeout(() => {
// // 역직렬화하여 캔버스에 객체를 다시 추가합니다.
// canvas?.loadFromJSON(JSON.parse(str), function () {
// // 모든 객체가 로드되고 캔버스에 추가된 후 호출됩니다.
// console.log(canvas?.getObjects().filter((obj) => obj.name === 'roof'))
// canvas?.renderAll() // 캔버스를 다시 그립니다.
// })
// }, 1000)
}
/**
* cad 파일 사용시 이미지 로딩 함수
*/
const handleBackImageLoadToCanvas = (url) => {
console.log('image load url: ', url)
fabric.Image.fromURL(url, function (img) {
img.set({
left: 0,
top: 0,
width: 1500,
height: 1500,
selectable: true,
})
canvas.add(img)
canvas.renderAll()
setBackImg(img)
})
}
const handleCadImageInit = () => {
canvas.clear()
}
const getCurrentCanvas = () => {
return canvas.toJSON([
'selectable',
'name',
'parentId',
'id',
'length',
'idx',
'direction',
'lines',
'points',
'lockMovementX',
'lockMovementY',
'lockRotation',
'lockScalingX',
'lockScalingY',
'opacity',
'cells',
'maxX',
'maxY',
'minX',
'minY',
'x',
'y',
'stickeyPoint',
])
}
return {
canvas,
addShape,
handleUndo,
handleRedo,
handleCopy,
handleSave,
handlePaste,
handleRotate,
attachCustomControlOnPolygon,
saveImage,
handleFlip,
setCanvasBackgroundWithDots,
addCanvas,
removeMouseLines,
handleBackImageLoadToCanvas,
handleCadImageInit,
backImg,
setBackImg,
}
}