215 lines
6.3 KiB
JavaScript
215 lines
6.3 KiB
JavaScript
import React from "react"
|
|
import * as fabric from 'fabric'
|
|
import { FullscreenOutlined } from "@ant-design/icons"
|
|
import { Button, Tooltip } from "antd"
|
|
|
|
|
|
/**
|
|
* @deprecated - Fabric.js cannot be used as its limited to drawing, elements cannot be added to canvas
|
|
*/
|
|
|
|
class Canvas extends React.Component {
|
|
|
|
constructor(props) {
|
|
super(props)
|
|
|
|
/** @type {fabric.Canvas | null} */
|
|
this.fabricCanvas = null
|
|
this.canvasRef = React.createRef()
|
|
|
|
this.mousePressed = false
|
|
this.mousePos = {
|
|
startX: 0,
|
|
startY: 0
|
|
}
|
|
|
|
this.modes = {
|
|
DEFAULT: '',
|
|
PAN: 'pan',
|
|
}
|
|
this.currentMode = this.modes.DEFAULT
|
|
|
|
this.updateCanvasDimensions = this.updateCanvasDimensions.bind(this)
|
|
}
|
|
|
|
componentDidMount() {
|
|
this.initCanvas()
|
|
window.addEventListener("resize", this.updateCanvasDimensions)
|
|
this.updateCanvasDimensions()
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
this.fabricCanvas.clear()
|
|
this.fabricCanvas.dispose()
|
|
window.removeEventListener("resize", this.updateCanvasDimensions)
|
|
}
|
|
|
|
updateCanvasDimensions() {
|
|
if (!this.canvasRef.current || !this.fabricCanvas) return
|
|
|
|
const parent = this.canvasRef.current.parentNode.parentNode // Get the outer container
|
|
|
|
this.fabricCanvas.setDimensions({
|
|
width: parent.clientWidth,
|
|
height: parent.clientHeight
|
|
})
|
|
|
|
this.fabricCanvas.calcOffset()
|
|
this.fabricCanvas.renderAll()
|
|
}
|
|
|
|
initCanvas(){
|
|
this.fabricCanvas = new fabric.Canvas(this.canvasRef.current, {
|
|
selection: false,
|
|
})
|
|
|
|
this.fabricCanvas.hoverCursor = this.fabricCanvas.moveCursor = 'pointer'
|
|
|
|
const rect = new fabric.Rect({
|
|
left: 100,
|
|
top: 100,
|
|
fill: 'red',
|
|
width: 200,
|
|
height: 100,
|
|
angle: 0
|
|
})
|
|
this.fabricCanvas.add(rect)
|
|
fabric.FabricImage.fromURL("https://images.unsplash.com/photo-1722898614949-c9a315e35209?q=80&w=1974&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D").then((img) => {
|
|
|
|
img.scale(0.2)
|
|
this.fabricCanvas.add(img)
|
|
})
|
|
|
|
// this.fabricCanvas.renderAll()
|
|
this.initEvents()
|
|
|
|
}
|
|
|
|
initEvents(){
|
|
|
|
this.fabricCanvas.on("mouse:down", (event) => {
|
|
this.mousePressed = true
|
|
|
|
if (this.fabricCanvas.getActiveObjects().length === 0){
|
|
this.currentMode = this.modes.PAN
|
|
}
|
|
|
|
this.mousePos.startX = event.e.clientX
|
|
this.mousePos.startY = event.e.clientY
|
|
|
|
this.fabricCanvas.setCursor("grab")
|
|
|
|
})
|
|
|
|
this.fabricCanvas.on("mouse:up", () => {
|
|
this.mousePressed = false
|
|
this.fabricCanvas.setCursor("default")
|
|
|
|
})
|
|
|
|
this.fabricCanvas.on("mouse:move", (event) => {
|
|
|
|
if (event.e.buttons === 1 && this.currentMode === this.modes.PAN){
|
|
|
|
this.fabricCanvas.setCursor("grab")
|
|
|
|
// TODO: add panning limits
|
|
|
|
const deltaX = event.e.clientX - this.mousePos.startX
|
|
const deltaY = event.e.clientY - this.mousePos.startY
|
|
|
|
this.fabricCanvas.relativePan({ x: deltaX, y: deltaY })
|
|
|
|
this.mousePos.startX = event.e.clientX
|
|
this.mousePos.startY = event.e.clientY
|
|
|
|
}
|
|
|
|
})
|
|
|
|
this.fabricCanvas.on("selection:created", () => {
|
|
console.log("selected")
|
|
this.currentMode = this.modes.DEFAULT
|
|
})
|
|
|
|
this.fabricCanvas.on('mouse:wheel', (opt) => {
|
|
// console.log("opt: ", opt)
|
|
let delta = opt.e.deltaY
|
|
let zoom = this.fabricCanvas.getZoom()
|
|
zoom *= 0.999 ** delta
|
|
|
|
if (zoom > 20) zoom = 20
|
|
if (zoom < 0.01) zoom = 0.01
|
|
|
|
this.fabricCanvas.zoomToPoint({ x: opt.e.offsetX, y: opt.e.offsetY }, zoom)
|
|
|
|
opt.e.preventDefault()
|
|
opt.e.stopPropagation()
|
|
})
|
|
|
|
|
|
}
|
|
|
|
getCanvasObjectsBoundingBox(padding = 0) {
|
|
const objects = this.fabricCanvas.getObjects()
|
|
if (objects.length === 0) {
|
|
return { left: 0, top: 0, width: this.fabricCanvas.width, height: this.fabricCanvas.height }
|
|
}
|
|
|
|
const boundingBox = objects.reduce((acc, obj) => {
|
|
const objBoundingBox = obj.getBoundingRect(true)
|
|
acc.left = Math.min(acc.left, objBoundingBox.left)
|
|
acc.top = Math.min(acc.top, objBoundingBox.top)
|
|
acc.right = Math.max(acc.right, objBoundingBox.left + objBoundingBox.width)
|
|
acc.bottom = Math.max(acc.bottom, objBoundingBox.top + objBoundingBox.height)
|
|
return acc
|
|
}, {
|
|
left: Infinity,
|
|
top: Infinity,
|
|
right: -Infinity,
|
|
bottom: -Infinity
|
|
})
|
|
|
|
// Adding padding
|
|
boundingBox.left -= padding
|
|
boundingBox.top -= padding
|
|
boundingBox.right += padding
|
|
boundingBox.bottom += padding
|
|
|
|
return {
|
|
left: boundingBox.left,
|
|
top: boundingBox.top,
|
|
width: boundingBox.right - boundingBox.left,
|
|
height: boundingBox.bottom - boundingBox.top
|
|
}
|
|
}
|
|
|
|
|
|
resetZoom(){
|
|
this.fabricCanvas.setViewportTransform([1,0,0,1,0,0])
|
|
}
|
|
|
|
resetPan(){
|
|
this.fabricCanvas.setViewportTransform([1,0,0,1,0,0])
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<div className="tw-relative tw-flex tw-w-full tw-h-full tw-max-h-[100vh] tw-overflow-auto">
|
|
|
|
<div className="tw-absolute tw-p-2 tw-bg-white tw-z-10 tw-min-w-[100px] tw-h-[50px] tw-gap-2
|
|
tw-top-4 tw-place-items-center tw-right-4 tw-shadow-md tw-rounded-md tw-flex">
|
|
|
|
<Tooltip title="Reset zoom">
|
|
<Button icon={<FullscreenOutlined />} />
|
|
</Tooltip>
|
|
</div>
|
|
|
|
<canvas className="tw-w-full tw-h-full" ref={this.canvasRef}/> {/* Don't add any bg color here */}
|
|
</div>
|
|
)
|
|
}
|
|
}
|
|
|
|
export default Canvas
|