import { useEffect, useState } from 'react' import { fabric } from 'fabric' import { actionHandler, anchorWrapper, polygonPositionHandler } from '@/util/canvas-util' import { useRecoilState, useSetRecoilState } from 'recoil' import { canvasSizeState, fontSizeState, guidePointModeState } 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 { post } from '@/lib/Axios' export function useCanvas(id) { const [canvas, setCanvas] = useState() const [isLocked, setIsLocked] = useState(false) const [history, setHistory] = useState([]) const [canvasSize] = useRecoilState(canvasSizeState) const [fontSize] = useRecoilState(fontSizeState) const { setCanvasForEvent, attachDefaultEventOnCanvas } = useCanvasEvent() const setGuidePointMode = useSetRecoilState(guidePointModeState) /** * 처음 셋팅 */ useEffect(() => { const c = new fabric.Canvas(id, { height: canvasSize.vertical, width: canvasSize.horizontal, backgroundColor: 'white', selection: false, }) setCanvas(c) setCanvasForEvent(c) return () => { c.dispose() } }, []) useEffect(() => { // canvas 사이즈가 변경되면 다시 attachDefaultEventOnCanvas() }, [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.cornerColor = '#2BEBC8' fabric.Object.prototype.cornerStyle = 'rect' fabric.Object.prototype.cornerStrokeColor = '#2BEBC8' fabric.Object.prototype.cornerSize = 6 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() setHistory((prev) => { if (prev === undefined) { return poppedObject ? [poppedObject] : [] } return poppedObject ? [...prev, poppedObject] : prev }) canvas?.renderAll() } } } const handleRedo = () => { if (canvas && history) { if (history.length > 0) { setIsLocked(true) 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) => { setGuidePointMode(true) // 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', ]) 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) } return { canvas, addShape, handleUndo, handleRedo, handleCopy, handleSave, handlePaste, handleRotate, attachCustomControlOnPolygon, saveImage, handleFlip, setCanvasBackgroundWithDots, addCanvas, removeMouseLines, } }