2024-08-08 16:21:19 +05:30
|
|
|
import React from "react"
|
2024-09-11 19:06:04 +05:30
|
|
|
|
|
|
|
|
import {DndContext} from '@dnd-kit/core'
|
|
|
|
|
|
2024-09-13 12:28:32 +05:30
|
|
|
import { CloseOutlined, DeleteOutlined, EditOutlined, FullscreenOutlined, ReloadOutlined } from "@ant-design/icons"
|
2024-09-12 22:09:13 +05:30
|
|
|
import { Button, Tooltip, Dropdown } from "antd"
|
2024-08-08 16:21:19 +05:30
|
|
|
|
2024-09-15 19:22:32 +05:30
|
|
|
import Droppable from "../components/utils/droppableDnd"
|
2024-08-08 16:21:19 +05:30
|
|
|
import Widget from "./widgets/base"
|
|
|
|
|
import Cursor from "./constants/cursor"
|
2024-09-14 16:03:26 +05:30
|
|
|
|
|
|
|
|
import CanvasToolBar from "./toolbar"
|
|
|
|
|
|
2024-08-08 22:49:14 +05:30
|
|
|
import { UID } from "../utils/uid"
|
2024-09-13 12:28:32 +05:30
|
|
|
import { removeDuplicateObjects } from "../utils/common"
|
2024-08-08 16:21:19 +05:30
|
|
|
|
2024-09-15 12:08:29 +05:30
|
|
|
import { WidgetContext } from './context/widgetContext'
|
2024-09-14 16:03:26 +05:30
|
|
|
// import {ReactComponent as DotsBackground} from "../assets/background/dots.svg"
|
|
|
|
|
|
2024-09-17 21:23:01 +05:30
|
|
|
// import DotsBackground from "../assets/background/dots.svg"
|
|
|
|
|
import {ReactComponent as DotsBackground} from "../assets/background/dots.svg"
|
|
|
|
|
|
2024-09-16 12:23:15 +05:30
|
|
|
import DroppableWrapper from "../components/draggable/droppable"
|
2024-09-16 22:04:24 +05:30
|
|
|
import { ActiveWidgetContext, ActiveWidgetProvider, withActiveWidget } from "./activeWidgetContext"
|
2024-09-17 11:55:21 +05:30
|
|
|
import { DragWidgetProvider } from "./widgets/draggableWidgetContext"
|
2024-09-14 16:03:26 +05:30
|
|
|
|
|
|
|
|
// const DotsBackground = require("../assets/background/dots.svg")
|
2024-08-08 16:21:19 +05:30
|
|
|
|
2024-09-17 21:23:01 +05:30
|
|
|
|
2024-09-10 21:34:05 +05:30
|
|
|
const CanvasModes = {
|
|
|
|
|
DEFAULT: 0,
|
|
|
|
|
PAN: 1,
|
|
|
|
|
MOVE_WIDGET: 2 // when the mode is move widget
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2024-08-08 16:21:19 +05:30
|
|
|
class Canvas extends React.Component {
|
|
|
|
|
|
2024-09-16 22:04:24 +05:30
|
|
|
// static contextType = ActiveWidgetContext
|
|
|
|
|
|
2024-08-08 16:21:19 +05:30
|
|
|
constructor(props) {
|
|
|
|
|
super(props)
|
2024-09-12 19:20:46 +05:30
|
|
|
|
|
|
|
|
const { canvasWidgets, onWidgetListUpdated } = props
|
2024-08-08 16:21:19 +05:30
|
|
|
|
|
|
|
|
this.canvasRef = React.createRef()
|
|
|
|
|
this.canvasContainerRef = React.createRef()
|
|
|
|
|
|
2024-09-10 21:34:05 +05:30
|
|
|
|
|
|
|
|
this.currentMode = CanvasModes.DEFAULT
|
|
|
|
|
|
|
|
|
|
this.minCanvasSize = {width: 500, height: 500}
|
2024-08-08 16:21:19 +05:30
|
|
|
|
|
|
|
|
this.mousePressed = false
|
|
|
|
|
this.mousePos = {
|
|
|
|
|
x: 0,
|
|
|
|
|
y: 0
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-12 22:09:13 +05:30
|
|
|
// this._contextMenuItems = []
|
2024-09-17 21:23:01 +05:30
|
|
|
this.widgetRefs = {} // stores the actual refs to the widgets inside the canvas {id: ref, id2, ref2...}
|
2024-09-12 22:09:13 +05:30
|
|
|
|
2024-08-08 16:21:19 +05:30
|
|
|
this.state = {
|
2024-09-17 21:23:01 +05:30
|
|
|
widgets: [], // stores the mapping to widgetRefs, stores id and WidgetType, later used for rendering [{id: , widgetType: WidgetClass}]
|
2024-08-08 16:21:19 +05:30
|
|
|
zoom: 1,
|
|
|
|
|
isPanning: false,
|
|
|
|
|
currentTranslate: { x: 0, y: 0 },
|
2024-09-10 21:34:05 +05:30
|
|
|
canvasSize: { width: 500, height: 500 },
|
2024-09-15 12:08:29 +05:30
|
|
|
|
2024-09-13 16:03:58 +05:30
|
|
|
contextMenuItems: [],
|
2024-09-15 12:08:29 +05:30
|
|
|
selectedWidgets: [],
|
|
|
|
|
|
|
|
|
|
toolbarOpen: true,
|
|
|
|
|
toolbarAttrs: null
|
2024-09-13 16:03:58 +05:30
|
|
|
}
|
2024-09-09 19:06:03 +05:30
|
|
|
|
2024-09-12 19:20:46 +05:30
|
|
|
this._onWidgetListUpdated = onWidgetListUpdated // a function callback when the widget is added to the canvas
|
|
|
|
|
|
2024-08-08 16:21:19 +05:30
|
|
|
this.resetTransforms = this.resetTransforms.bind(this)
|
2024-08-08 22:49:14 +05:30
|
|
|
this.renderWidget = this.renderWidget.bind(this)
|
2024-09-09 19:06:03 +05:30
|
|
|
|
|
|
|
|
this.mouseDownEvent = this.mouseDownEvent.bind(this)
|
|
|
|
|
this.mouseMoveEvent = this.mouseMoveEvent.bind(this)
|
|
|
|
|
this.mouseUpEvent = this.mouseUpEvent.bind(this)
|
2024-09-16 22:04:24 +05:30
|
|
|
this.keyDownEvent = this.keyDownEvent.bind(this)
|
|
|
|
|
this.wheelZoom = this.wheelZoom.bind(this)
|
2024-09-15 12:08:29 +05:30
|
|
|
|
|
|
|
|
this.onActiveWidgetUpdate = this.onActiveWidgetUpdate.bind(this)
|
|
|
|
|
|
2024-09-09 19:06:03 +05:30
|
|
|
this.getWidgets = this.getWidgets.bind(this)
|
|
|
|
|
this.getActiveObjects = this.getActiveObjects.bind(this)
|
|
|
|
|
this.getWidgetFromTarget = this.getWidgetFromTarget.bind(this)
|
|
|
|
|
|
2024-09-10 21:34:05 +05:30
|
|
|
this.getCanvasObjectsBoundingBox = this.getCanvasObjectsBoundingBox.bind(this)
|
|
|
|
|
this.fitCanvasToBoundingBox = this.fitCanvasToBoundingBox.bind(this)
|
2024-09-15 22:54:53 +05:30
|
|
|
|
|
|
|
|
this.getCanvasContainerBoundingRect = this.getCanvasContainerBoundingRect.bind(this)
|
|
|
|
|
this.getCanvasBoundingRect = this.getCanvasBoundingRect.bind(this)
|
2024-09-10 21:34:05 +05:30
|
|
|
|
2024-09-13 19:24:03 +05:30
|
|
|
this.setSelectedWidget = this.setSelectedWidget.bind(this)
|
2024-09-13 12:28:32 +05:30
|
|
|
this.deleteSelectedWidgets = this.deleteSelectedWidgets.bind(this)
|
|
|
|
|
this.removeWidget = this.removeWidget.bind(this)
|
2024-09-09 19:06:03 +05:30
|
|
|
this.clearSelections = this.clearSelections.bind(this)
|
|
|
|
|
this.clearCanvas = this.clearCanvas.bind(this)
|
2024-08-08 16:21:19 +05:30
|
|
|
|
|
|
|
|
// this.updateCanvasDimensions = this.updateCanvasDimensions.bind(this)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
componentDidMount() {
|
|
|
|
|
this.initEvents()
|
|
|
|
|
|
2024-09-17 21:23:01 +05:30
|
|
|
this.createWidget(Widget)
|
2024-08-08 16:21:19 +05:30
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
componentWillUnmount() {
|
2024-09-09 19:06:03 +05:30
|
|
|
|
2024-09-16 22:04:24 +05:30
|
|
|
this.canvasContainerRef.current.removeEventListener("mousedown", this.mouseDownEvent)
|
|
|
|
|
this.canvasContainerRef.current.removeEventListener("mouseup", this.mouseUpEvent)
|
|
|
|
|
this.canvasContainerRef.current.removeEventListener("mousemove", this.mouseMoveEvent)
|
|
|
|
|
this.canvasContainerRef.current.removeEventListener("wheel", this.wheelZoom)
|
|
|
|
|
|
|
|
|
|
this.canvasContainerRef.current.removeEventListener("keydown", this.keyDownEvent)
|
|
|
|
|
|
|
|
|
|
|
2024-09-09 19:06:03 +05:30
|
|
|
// NOTE: this will clear the canvas
|
|
|
|
|
this.clearCanvas()
|
2024-08-08 16:21:19 +05:30
|
|
|
}
|
|
|
|
|
|
2024-09-16 22:04:24 +05:30
|
|
|
initEvents(){
|
|
|
|
|
|
|
|
|
|
this.canvasContainerRef.current.addEventListener("mousedown", this.mouseDownEvent)
|
|
|
|
|
this.canvasContainerRef.current.addEventListener("mouseup", this.mouseUpEvent)
|
|
|
|
|
this.canvasContainerRef.current.addEventListener("mousemove", this.mouseMoveEvent)
|
|
|
|
|
this.canvasContainerRef.current.addEventListener("wheel", this.wheelZoom)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.canvasContainerRef.current.addEventListener("keydown", this.keyDownEvent, true)
|
|
|
|
|
// window.addEventListener("keydown", this.keyDownEvent, true)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
applyTransform(){
|
|
|
|
|
const { currentTranslate, zoom } = this.state
|
|
|
|
|
this.canvasRef.current.style.transform = `translate(${currentTranslate.x}px, ${currentTranslate.y}px) scale(${zoom})`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2024-08-08 16:21:19 +05:30
|
|
|
*
|
|
|
|
|
* @returns {import("./widgets/base").Widget[]}
|
|
|
|
|
*/
|
2024-09-16 22:04:24 +05:30
|
|
|
getWidgets(){
|
2024-08-08 16:21:19 +05:30
|
|
|
|
|
|
|
|
return this.state.widgets
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2024-09-09 19:06:03 +05:30
|
|
|
* returns list of active objects / selected objects on the canvas
|
2024-08-08 16:21:19 +05:30
|
|
|
* @returns Widget[]
|
|
|
|
|
*/
|
|
|
|
|
getActiveObjects(){
|
2024-09-09 19:06:03 +05:30
|
|
|
return Object.values(this.widgetRefs).filter((widgetRef) => {
|
|
|
|
|
return widgetRef.current?.isSelected()
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-08 16:21:19 +05:30
|
|
|
|
2024-09-09 19:06:03 +05:30
|
|
|
/**
|
|
|
|
|
* returns the widget that contains the target
|
|
|
|
|
* @param {HTMLElement} target
|
|
|
|
|
* @returns {Widget}
|
|
|
|
|
*/
|
|
|
|
|
getWidgetFromTarget(target){
|
2024-08-08 16:21:19 +05:30
|
|
|
|
2024-09-09 19:06:03 +05:30
|
|
|
for (let [key, ref] of Object.entries(this.widgetRefs)){
|
2024-09-16 22:04:24 +05:30
|
|
|
// console.log("ref: ", ref)
|
2024-09-09 19:06:03 +05:30
|
|
|
if (ref.current.getElement().contains(target)){
|
|
|
|
|
return ref.current
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-08-08 16:21:19 +05:30
|
|
|
|
2024-09-09 19:06:03 +05:30
|
|
|
}
|
2024-08-08 16:21:19 +05:30
|
|
|
|
2024-09-16 22:04:24 +05:30
|
|
|
keyDownEvent(event){
|
|
|
|
|
|
|
|
|
|
if (event.key === "Delete"){
|
|
|
|
|
this.deleteSelectedWidgets()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (event.key === "+"){
|
|
|
|
|
this.setZoom(this.state.zoom + 0.1)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (event.key === "-"){
|
|
|
|
|
this.setZoom(this.state.zoom - 0.1)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
2024-08-08 16:21:19 +05:30
|
|
|
|
2024-09-09 19:06:03 +05:30
|
|
|
mouseDownEvent(event){
|
|
|
|
|
|
2024-09-12 22:09:13 +05:30
|
|
|
this.mousePos = { x: event.clientX, y: event.clientY }
|
|
|
|
|
|
2024-09-09 19:06:03 +05:30
|
|
|
let selectedWidget = this.getWidgetFromTarget(event.target)
|
2024-09-12 22:09:13 +05:30
|
|
|
if (event.button === 0){
|
|
|
|
|
this.mousePressed = true
|
|
|
|
|
|
|
|
|
|
if (selectedWidget){
|
|
|
|
|
// if the widget is selected don't pan, instead move the widget
|
|
|
|
|
if (!selectedWidget._disableSelection){
|
2024-09-09 19:06:03 +05:30
|
|
|
|
2024-09-13 16:03:58 +05:30
|
|
|
const selectedLength = this.state.selectedWidgets.length
|
2024-09-13 12:28:32 +05:30
|
|
|
|
2024-09-15 15:31:04 +05:30
|
|
|
// console.log("selected widget: ", selectedWidget)
|
2024-09-15 12:08:29 +05:30
|
|
|
|
2024-09-13 16:03:58 +05:30
|
|
|
if (selectedLength === 0 || (selectedLength === 1 && selectedWidget.__id !== this.state.selectedWidgets[0].__id)){
|
|
|
|
|
this.state.selectedWidgets[0]?.deSelect() // deselect the previous widget before adding the new one
|
|
|
|
|
this.state.selectedWidgets[0]?.setZIndex(0)
|
2024-08-08 16:21:19 +05:30
|
|
|
|
2024-09-13 12:28:32 +05:30
|
|
|
selectedWidget.setZIndex(1000)
|
|
|
|
|
selectedWidget.select()
|
2024-09-13 16:03:58 +05:30
|
|
|
|
|
|
|
|
this.setState({
|
2024-09-15 12:08:29 +05:30
|
|
|
selectedWidgets: [selectedWidget],
|
|
|
|
|
toolbarAttrs: selectedWidget.getToolbarAttrs()
|
2024-09-13 16:03:58 +05:30
|
|
|
})
|
2024-09-16 22:04:24 +05:30
|
|
|
|
2024-09-17 09:13:54 +05:30
|
|
|
// this.context.updateActiveWidget(selectedWidget.__id)
|
|
|
|
|
// this.context.updateToolAttrs(selectedWidget.getToolbarAttrs())
|
2024-09-16 22:04:24 +05:30
|
|
|
// this.props.updateActiveWidget(selectedWidget)
|
2024-09-13 12:28:32 +05:30
|
|
|
}
|
2024-09-12 22:09:13 +05:30
|
|
|
this.currentMode = CanvasModes.MOVE_WIDGET
|
|
|
|
|
}
|
2024-08-08 16:21:19 +05:30
|
|
|
|
2024-09-12 22:09:13 +05:30
|
|
|
this.currentMode = CanvasModes.PAN
|
2024-09-09 19:06:03 +05:30
|
|
|
|
2024-09-13 12:28:32 +05:30
|
|
|
}else if (!selectedWidget){
|
2024-09-12 22:09:13 +05:30
|
|
|
// get the canvas ready to pan, if there are widgets on the canvas
|
|
|
|
|
this.clearSelections()
|
|
|
|
|
this.currentMode = CanvasModes.PAN
|
|
|
|
|
this.setCursor(Cursor.GRAB)
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.setState({
|
2024-09-13 16:03:58 +05:30
|
|
|
contextMenuItems: [],
|
|
|
|
|
toolbarOpen: true
|
2024-09-12 22:09:13 +05:30
|
|
|
})
|
|
|
|
|
// this.setState({
|
|
|
|
|
// showContextMenu: false
|
|
|
|
|
// })
|
|
|
|
|
}else if (event.button === 2){
|
|
|
|
|
//right click
|
2024-09-13 12:28:32 +05:30
|
|
|
|
2024-09-13 16:03:58 +05:30
|
|
|
if (this.state.selectedWidgets.length > 0 && this.state.selectedWidgets[0].__id !== selectedWidget.__id){
|
2024-09-13 12:28:32 +05:30
|
|
|
this.clearSelections()
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-14 16:03:26 +05:30
|
|
|
if (selectedWidget){
|
2024-09-13 16:03:58 +05:30
|
|
|
|
2024-09-12 22:09:13 +05:30
|
|
|
this.setState({
|
2024-09-14 16:03:26 +05:30
|
|
|
selectedWidget: [selectedWidget],
|
2024-09-13 12:28:32 +05:30
|
|
|
contextMenuItems: [
|
|
|
|
|
{
|
|
|
|
|
key: "rename",
|
|
|
|
|
label: (<div onClick={() => selectedWidget.openRenaming()}><EditOutlined /> Rename</div>),
|
|
|
|
|
icons: <EditOutlined />,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: "delete",
|
|
|
|
|
label: (<div onClick={() => this.deleteSelectedWidgets([selectedWidget])}><DeleteOutlined /> Delete</div>),
|
|
|
|
|
icons: <DeleteOutlined />,
|
|
|
|
|
danger: true
|
|
|
|
|
}
|
|
|
|
|
]
|
2024-09-12 22:09:13 +05:30
|
|
|
})
|
2024-09-14 16:03:26 +05:30
|
|
|
|
|
|
|
|
}
|
2024-09-12 22:09:13 +05:30
|
|
|
|
2024-09-09 19:06:03 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
2024-08-08 16:21:19 +05:30
|
|
|
|
2024-09-09 19:06:03 +05:30
|
|
|
mouseMoveEvent(event){
|
2024-09-13 12:28:32 +05:30
|
|
|
|
2024-09-09 19:06:03 +05:30
|
|
|
// console.log("mode: ", this.currentMode, this.getActiveObjects())
|
2024-09-10 21:34:05 +05:30
|
|
|
if (this.mousePressed && [CanvasModes.PAN, CanvasModes.MOVE_WIDGET].includes(this.currentMode)) {
|
2024-09-09 19:06:03 +05:30
|
|
|
const deltaX = event.clientX - this.mousePos.x
|
|
|
|
|
const deltaY = event.clientY - this.mousePos.y
|
|
|
|
|
|
2024-09-13 16:03:58 +05:30
|
|
|
if (this.state.selectedWidgets.length === 0){
|
2024-09-10 21:34:05 +05:30
|
|
|
// if there aren't any selected widgets, then pan the canvas
|
2024-08-08 16:21:19 +05:30
|
|
|
this.setState(prevState => ({
|
|
|
|
|
currentTranslate: {
|
|
|
|
|
x: prevState.currentTranslate.x + deltaX,
|
|
|
|
|
y: prevState.currentTranslate.y + deltaY,
|
|
|
|
|
}
|
|
|
|
|
}), this.applyTransform)
|
2024-09-10 21:34:05 +05:30
|
|
|
|
2024-09-09 19:06:03 +05:30
|
|
|
}else{
|
|
|
|
|
// update the widgets position
|
2024-09-17 21:23:01 +05:30
|
|
|
// this.state.selectedWidgets.forEach(widget => {
|
|
|
|
|
// const {x, y} = widget.getPos()
|
2024-08-08 16:21:19 +05:30
|
|
|
|
2024-09-17 21:23:01 +05:30
|
|
|
// const newPosX = x + (deltaX/this.state.zoom) // account for the zoom, since the widget is relative to canvas
|
|
|
|
|
// const newPosY = y + (deltaY/this.state.zoom) // account for the zoom, since the widget is relative to canvas
|
|
|
|
|
// widget.setPos(newPosX, newPosY)
|
|
|
|
|
// })
|
2024-08-08 16:21:19 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2024-09-09 19:06:03 +05:30
|
|
|
this.mousePos = { x: event.clientX, y: event.clientY }
|
2024-08-08 16:21:19 +05:30
|
|
|
|
2024-09-09 19:06:03 +05:30
|
|
|
this.setCursor(Cursor.GRAB)
|
|
|
|
|
}
|
2024-08-08 16:21:19 +05:30
|
|
|
}
|
|
|
|
|
|
2024-09-09 19:06:03 +05:30
|
|
|
mouseUpEvent(event){
|
|
|
|
|
this.mousePressed = false
|
2024-09-10 21:34:05 +05:30
|
|
|
this.currentMode = CanvasModes.DEFAULT
|
2024-09-09 19:06:03 +05:30
|
|
|
this.setCursor(Cursor.DEFAULT)
|
2024-08-08 16:21:19 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wheelZoom(event){
|
|
|
|
|
let delta = event.deltaY
|
|
|
|
|
let zoom = this.state.zoom * 0.999 ** delta
|
2024-09-10 21:34:05 +05:30
|
|
|
|
2024-09-11 19:06:04 +05:30
|
|
|
this.setZoom(zoom, {x: event.offsetX, y: event.offsetY})
|
2024-09-10 21:34:05 +05:30
|
|
|
}
|
|
|
|
|
|
2024-09-12 19:20:46 +05:30
|
|
|
getCanvasContainerBoundingRect(){
|
|
|
|
|
return this.canvasContainerRef.current.getBoundingClientRect()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getCanvasBoundingRect(){
|
|
|
|
|
return this.canvasRef.current.getBoundingClientRect()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getCanvasTranslation(){
|
|
|
|
|
return this.state.currentTranslate
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-08 16:21:19 +05:30
|
|
|
/**
|
|
|
|
|
* fits the canvas size to fit the widgets bounding box
|
|
|
|
|
*/
|
2024-09-10 21:34:05 +05:30
|
|
|
fitCanvasToBoundingBox(padding=0){
|
|
|
|
|
const { top, left, right, bottom } = this.getCanvasObjectsBoundingBox()
|
|
|
|
|
|
|
|
|
|
const width = right - left
|
|
|
|
|
const height = bottom - top
|
|
|
|
|
|
|
|
|
|
const newWidth = Math.max(width + padding, this.minCanvasSize.width)
|
|
|
|
|
const newHeight = Math.max(height + padding, this.minCanvasSize.height)
|
|
|
|
|
|
|
|
|
|
const canvasStyle = this.canvasRef.current.style
|
|
|
|
|
|
|
|
|
|
// Adjust the canvas dimensions
|
|
|
|
|
canvasStyle.width = `${newWidth}px`
|
|
|
|
|
canvasStyle.height = `${newHeight}px`
|
|
|
|
|
|
|
|
|
|
// Adjust the canvas position if needed
|
|
|
|
|
canvasStyle.left = `${left - padding}px`
|
|
|
|
|
canvasStyle.top = `${top - padding}px`
|
2024-08-08 16:21:19 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setCursor(cursor){
|
|
|
|
|
this.canvasContainerRef.current.style.cursor = cursor
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-16 22:04:24 +05:30
|
|
|
setZoom(zoom, pos){
|
2024-08-08 16:21:19 +05:30
|
|
|
|
|
|
|
|
const { currentTranslate } = this.state
|
|
|
|
|
|
2024-09-16 22:04:24 +05:30
|
|
|
let newTranslate = currentTranslate
|
|
|
|
|
|
|
|
|
|
if (pos){
|
|
|
|
|
// Calculate the new translation to zoom into the mouse position
|
|
|
|
|
const offsetX = pos.x - (this.canvasContainerRef.current.clientWidth / 2 + currentTranslate.x)
|
|
|
|
|
const offsetY = pos.y - (this.canvasContainerRef.current.clientHeight / 2 + currentTranslate.y)
|
2024-09-14 16:03:26 +05:30
|
|
|
|
2024-09-16 22:04:24 +05:30
|
|
|
const newTranslateX = currentTranslate.x - offsetX * (zoom - this.state.zoom)
|
|
|
|
|
const newTranslateY = currentTranslate.y - offsetY * (zoom - this.state.zoom)
|
|
|
|
|
newTranslate = {
|
2024-08-08 16:21:19 +05:30
|
|
|
x: newTranslateX,
|
|
|
|
|
y: newTranslateY
|
|
|
|
|
}
|
2024-09-16 22:04:24 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
|
zoom: Math.max(0.5, Math.min(zoom, 1.5)), // clamp between 0.5 and 1.5
|
|
|
|
|
currentTranslate: newTranslate
|
2024-08-08 16:21:19 +05:30
|
|
|
}, this.applyTransform)
|
|
|
|
|
|
2024-09-11 19:06:04 +05:30
|
|
|
|
2024-08-08 16:21:19 +05:30
|
|
|
}
|
2024-09-12 19:20:46 +05:30
|
|
|
|
|
|
|
|
getZoom(){
|
|
|
|
|
return this.state.zoom
|
|
|
|
|
}
|
2024-08-08 16:21:19 +05:30
|
|
|
|
|
|
|
|
resetTransforms() {
|
|
|
|
|
this.setState({
|
|
|
|
|
zoom: 1,
|
|
|
|
|
currentTranslate: { x: 0, y: 0 }
|
|
|
|
|
}, this.applyTransform)
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-13 19:24:03 +05:30
|
|
|
setSelectedWidget(selectedWidget){
|
|
|
|
|
this.setState({ selectedWidget: [selectedWidget] })
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-09 19:06:03 +05:30
|
|
|
clearSelections(){
|
|
|
|
|
this.getActiveObjects().forEach(widget => {
|
|
|
|
|
widget.current?.deSelect()
|
|
|
|
|
})
|
2024-09-13 16:03:58 +05:30
|
|
|
|
2024-09-17 09:13:54 +05:30
|
|
|
// this.context?.updateActiveWidget("")
|
|
|
|
|
// this.context.updateToolAttrs({})
|
2024-09-16 22:04:24 +05:30
|
|
|
|
2024-09-13 16:03:58 +05:30
|
|
|
this.setState({
|
2024-09-15 12:08:29 +05:30
|
|
|
selectedWidgets: [],
|
2024-09-15 15:31:04 +05:30
|
|
|
toolbarAttrs: null,
|
2024-09-15 12:08:29 +05:30
|
|
|
// toolbarOpen:
|
2024-09-13 16:03:58 +05:30
|
|
|
})
|
|
|
|
|
|
2024-09-09 19:06:03 +05:30
|
|
|
}
|
|
|
|
|
|
2024-09-10 21:34:05 +05:30
|
|
|
/**
|
|
|
|
|
* returns tha combined bounding rect of all the widgets on the canvas
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
getCanvasObjectsBoundingBox(){
|
|
|
|
|
|
|
|
|
|
// Initialize coordinates to opposite extremes
|
|
|
|
|
let top = Number.POSITIVE_INFINITY
|
|
|
|
|
let left = Number.POSITIVE_INFINITY
|
|
|
|
|
let right = Number.NEGATIVE_INFINITY
|
|
|
|
|
let bottom = Number.NEGATIVE_INFINITY
|
|
|
|
|
|
|
|
|
|
for (let widget of Object.values(this.widgetRefs)) {
|
|
|
|
|
const rect = widget.current.getBoundingRect()
|
|
|
|
|
// Update the top, left, right, and bottom coordinates
|
|
|
|
|
if (rect.top < top) top = rect.top
|
|
|
|
|
if (rect.left < left) left = rect.left
|
|
|
|
|
if (rect.right > right) right = rect.right
|
|
|
|
|
if (rect.bottom > bottom) bottom = rect.bottom
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { top, left, right, bottom }
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-08 22:49:14 +05:30
|
|
|
/**
|
|
|
|
|
*
|
2024-09-17 21:23:01 +05:30
|
|
|
* @param {Widget} widgetComponentType - don't pass <Widget /> instead pass Widget object/class
|
2024-08-08 22:49:14 +05:30
|
|
|
*/
|
2024-09-17 21:23:01 +05:30
|
|
|
createWidget(widgetComponentType, callback){
|
2024-08-08 22:49:14 +05:30
|
|
|
const widgetRef = React.createRef()
|
|
|
|
|
|
|
|
|
|
const id = `${widgetComponentType.widgetType}_${UID()}`
|
|
|
|
|
|
|
|
|
|
// Store the ref in the instance variable
|
|
|
|
|
this.widgetRefs[id] = widgetRef
|
2024-09-12 19:20:46 +05:30
|
|
|
|
|
|
|
|
const widgets = [...this.state.widgets, { id, widgetType: widgetComponentType }] // don't add the widget refs in the state
|
2024-08-08 22:49:14 +05:30
|
|
|
// Update the state to include the new widget's type and ID
|
2024-09-12 19:20:46 +05:30
|
|
|
this.setState({
|
|
|
|
|
widgets: widgets
|
|
|
|
|
}, () => {
|
|
|
|
|
if (callback)
|
|
|
|
|
callback({id, widgetRef})
|
|
|
|
|
|
|
|
|
|
if (this._onWidgetListUpdated)
|
2024-09-17 11:55:21 +05:30
|
|
|
this._onWidgetListUpdated(widgets) // inform the parent container
|
2024-09-12 19:20:46 +05:30
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return {id, widgetRef}
|
2024-08-08 22:49:14 +05:30
|
|
|
}
|
|
|
|
|
|
2024-09-17 11:55:21 +05:30
|
|
|
getWidgetById(id){
|
|
|
|
|
|
|
|
|
|
return this.widgetRefs[id]
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-16 22:04:24 +05:30
|
|
|
/**
|
|
|
|
|
* delete's the selected widgets from the canvas
|
|
|
|
|
* @param {null|Widget} widgets - optional widgets that can be deleted along the selected widgets
|
|
|
|
|
*/
|
2024-09-13 16:03:58 +05:30
|
|
|
deleteSelectedWidgets(widgets=[]){
|
2024-09-13 12:28:32 +05:30
|
|
|
|
|
|
|
|
|
2024-09-13 16:03:58 +05:30
|
|
|
let activeWidgets = removeDuplicateObjects([...widgets, ...this.state.selectedWidgets], "__id")
|
|
|
|
|
|
2024-09-13 12:28:32 +05:30
|
|
|
const widgetIds = activeWidgets.map(widget => widget.__id)
|
|
|
|
|
|
|
|
|
|
for (let widgetId of widgetIds){
|
|
|
|
|
|
|
|
|
|
// this.widgetRefs[widgetId]?.current.remove()
|
|
|
|
|
delete this.widgetRefs[widgetId]
|
|
|
|
|
|
|
|
|
|
this.setState((prevState) => ({
|
|
|
|
|
widgets: prevState.widgets.filter(widget => widget.id !== widgetId)
|
2024-09-14 16:03:26 +05:30
|
|
|
}), () => {
|
|
|
|
|
|
|
|
|
|
if (this._onWidgetListUpdated)
|
|
|
|
|
this._onWidgetListUpdated(this.state.widgets)
|
|
|
|
|
})
|
2024-09-13 12:28:32 +05:30
|
|
|
// value.current?.remove()
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-14 16:03:26 +05:30
|
|
|
|
|
|
|
|
|
2024-09-13 12:28:32 +05:30
|
|
|
}
|
|
|
|
|
|
2024-09-09 19:06:03 +05:30
|
|
|
/**
|
|
|
|
|
* removes all the widgets from the canvas
|
|
|
|
|
*/
|
|
|
|
|
clearCanvas(){
|
|
|
|
|
|
2024-09-13 12:28:32 +05:30
|
|
|
// NOTE: Don't remove from it using remove() function since, it already removed from the DOM tree when its removed from widgets
|
|
|
|
|
// for (let [key, value] of Object.entries(this.widgetRefs)){
|
|
|
|
|
// console.log("removed: ", value, value.current?.getElement())
|
2024-09-12 19:20:46 +05:30
|
|
|
|
2024-09-13 12:28:32 +05:30
|
|
|
// value.current?.remove()
|
|
|
|
|
// }
|
2024-09-09 19:06:03 +05:30
|
|
|
|
|
|
|
|
this.widgetRefs = {}
|
2024-09-15 12:08:29 +05:30
|
|
|
this.setState({
|
2024-09-09 19:06:03 +05:30
|
|
|
widgets: []
|
2024-09-12 19:20:46 +05:30
|
|
|
})
|
2024-09-13 19:24:03 +05:30
|
|
|
|
|
|
|
|
if (this._onWidgetListUpdated)
|
|
|
|
|
this._onWidgetListUpdated([])
|
2024-09-09 19:06:03 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
removeWidget(widgetId){
|
|
|
|
|
|
2024-09-13 12:28:32 +05:30
|
|
|
// this.widgetRefs[widgetId]?.current.remove()
|
2024-09-09 19:06:03 +05:30
|
|
|
delete this.widgetRefs[widgetId]
|
|
|
|
|
|
2024-09-13 19:24:03 +05:30
|
|
|
const widgets = this.state.widgets.filter(widget => widget.id !== widgetId)
|
|
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
|
widgets: widgets
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (this._onWidgetListUpdated)
|
|
|
|
|
this._onWidgetListUpdated(widgets)
|
2024-09-09 19:06:03 +05:30
|
|
|
}
|
|
|
|
|
|
2024-09-15 12:08:29 +05:30
|
|
|
onActiveWidgetUpdate(widgetId){
|
|
|
|
|
|
|
|
|
|
if (this.state.selectedWidgets.length === 0 || widgetId !== this.state.selectedWidgets[0].__id)
|
|
|
|
|
return
|
|
|
|
|
|
2024-09-16 22:04:24 +05:30
|
|
|
console.log("updating...", this.state.toolbarAttrs, this.state.selectedWidgets.at(0).getToolbarAttrs())
|
|
|
|
|
|
|
|
|
|
// console.log("attrs: ", this.state.selectedWidgets.at(0).getToolbarAttrs())
|
2024-09-15 15:31:04 +05:30
|
|
|
|
2024-09-15 12:08:29 +05:30
|
|
|
this.setState({
|
|
|
|
|
toolbarAttrs: this.state.selectedWidgets.at(0).getToolbarAttrs()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-16 12:23:15 +05:30
|
|
|
/**
|
2024-09-17 11:55:21 +05:30
|
|
|
* Handles drop event to canvas from the sidebar and on canvas widget movement
|
2024-09-16 12:23:15 +05:30
|
|
|
* @param {DragEvent} e
|
|
|
|
|
*/
|
2024-09-17 11:55:21 +05:30
|
|
|
handleDropEvent = (e, draggedElement) => {
|
2024-09-15 19:22:32 +05:30
|
|
|
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
|
2024-09-17 11:55:21 +05:30
|
|
|
|
|
|
|
|
const container = draggedElement.getAttribute("data-container")
|
2024-09-17 21:23:01 +05:30
|
|
|
// console.log("Dropped on canvas",)
|
2024-09-17 11:55:21 +05:30
|
|
|
|
2024-09-15 22:54:53 +05:30
|
|
|
// const canvasContainerRect = this.getCanvasContainerBoundingRect()
|
|
|
|
|
const canvasRect = this.canvasRef.current.getBoundingClientRect()
|
2024-09-15 19:22:32 +05:30
|
|
|
const { clientX, clientY } = e
|
|
|
|
|
|
2024-09-16 12:23:15 +05:30
|
|
|
const finalPosition = {
|
|
|
|
|
x: (clientX - canvasRect.left) / this.state.zoom,
|
|
|
|
|
y: (clientY - canvasRect.top) / this.state.zoom,
|
2024-09-17 11:55:21 +05:30
|
|
|
}
|
2024-09-15 19:22:32 +05:30
|
|
|
|
2024-09-17 11:55:21 +05:30
|
|
|
if (container === "sidebar"){
|
2024-09-17 18:32:33 +05:30
|
|
|
// if the widget is being dropped from the sidebar, use the info to create the widget first
|
2024-09-17 21:23:01 +05:30
|
|
|
this.createWidget(Widget, ({id, widgetRef}) => {
|
2024-09-17 11:55:21 +05:30
|
|
|
widgetRef.current.setPos(finalPosition.x, finalPosition.y)
|
|
|
|
|
})
|
|
|
|
|
}else if (container === "canvas"){
|
|
|
|
|
|
|
|
|
|
const widgetObj = this.getWidgetById(draggedElement.getAttribute("data-widget-id"))
|
|
|
|
|
// console.log("WidgetObj: ", widgetObj)
|
|
|
|
|
widgetObj.current.setPos(finalPosition.x, finalPosition.y)
|
|
|
|
|
}
|
2024-09-15 19:22:32 +05:30
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-08 22:49:14 +05:30
|
|
|
renderWidget(widget){
|
2024-09-12 19:20:46 +05:30
|
|
|
const { id, widgetType: ComponentType } = widget
|
2024-09-09 19:06:03 +05:30
|
|
|
// console.log("widet: ", this.widgetRefs, id)
|
2024-08-08 22:49:14 +05:30
|
|
|
|
2024-09-12 19:20:46 +05:30
|
|
|
return <ComponentType key={id} id={id} ref={this.widgetRefs[id]}
|
2024-09-15 12:08:29 +05:30
|
|
|
canvasRef={this.canvasContainerRef}
|
|
|
|
|
onWidgetUpdate={this.onActiveWidgetUpdate}
|
|
|
|
|
/>
|
2024-08-08 22:49:14 +05:30
|
|
|
}
|
|
|
|
|
|
2024-08-08 16:21:19 +05:30
|
|
|
render() {
|
|
|
|
|
return (
|
|
|
|
|
<div className="tw-relative tw-flex tw-w-full tw-h-full tw-max-h-[100vh]">
|
|
|
|
|
|
|
|
|
|
<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 viewport">
|
|
|
|
|
<Button icon={<ReloadOutlined />} onClick={this.resetTransforms} />
|
|
|
|
|
</Tooltip>
|
2024-09-13 16:03:58 +05:30
|
|
|
<Tooltip title="Clear canvas">
|
2024-09-14 16:03:26 +05:30
|
|
|
<Button danger icon={<DeleteOutlined />} onClick={this.clearCanvas} />
|
2024-09-12 19:20:46 +05:30
|
|
|
</Tooltip>
|
2024-08-08 16:21:19 +05:30
|
|
|
</div>
|
|
|
|
|
|
2024-09-16 22:04:24 +05:30
|
|
|
{/* <ActiveWidgetProvider> */}
|
2024-09-15 19:22:32 +05:30
|
|
|
<DroppableWrapper id="canvas-droppable"
|
2024-09-17 11:55:21 +05:30
|
|
|
className="tw-w-full tw-h-full"
|
|
|
|
|
onDrop={this.handleDropEvent}>
|
|
|
|
|
{/* <DragWidgetProvider> */}
|
|
|
|
|
<Dropdown trigger={['contextMenu']} mouseLeaveDelay={0} menu={{items: this.state.contextMenuItems, }}>
|
|
|
|
|
<div className="dots-bg tw-w-full tw-h-full tw-outline-none tw-flex tw-relative tw-bg-[#f2f2f2] tw-overflow-hidden"
|
|
|
|
|
ref={this.canvasContainerRef}
|
|
|
|
|
style={{
|
|
|
|
|
transition: " transform 0.3s ease-in-out",
|
2024-09-17 21:23:01 +05:30
|
|
|
backgroundImage: `url(${DotsBackground})`,
|
2024-09-17 11:55:21 +05:30
|
|
|
backgroundSize: 'cover', // Ensure proper sizing if needed
|
|
|
|
|
backgroundRepeat: 'no-repeat',
|
|
|
|
|
}}
|
|
|
|
|
tabIndex={0} // allow focus
|
|
|
|
|
>
|
2024-09-17 21:23:01 +05:30
|
|
|
<DotsBackground
|
|
|
|
|
style={{
|
|
|
|
|
width: '100%',
|
|
|
|
|
height: '100%',
|
|
|
|
|
backgroundSize: 'cover'
|
|
|
|
|
}}
|
|
|
|
|
/>
|
2024-09-17 11:55:21 +05:30
|
|
|
{/* Canvas */}
|
|
|
|
|
<div data-canvas className="tw-w-full tw-h-full tw-absolute tw-top-0 tw-select-none
|
2024-09-17 18:32:33 +05:30
|
|
|
"
|
2024-09-17 11:55:21 +05:30
|
|
|
ref={this.canvasRef}>
|
|
|
|
|
<div className="tw-relative tw-w-full tw-h-full">
|
|
|
|
|
{
|
|
|
|
|
this.state.widgets.map(this.renderWidget)
|
|
|
|
|
}
|
|
|
|
|
</div>
|
2024-09-11 19:06:04 +05:30
|
|
|
</div>
|
2024-09-13 16:03:58 +05:30
|
|
|
</div>
|
2024-09-17 11:55:21 +05:30
|
|
|
</Dropdown>
|
|
|
|
|
{/* </DragWidgetProvider> */}
|
2024-09-15 19:22:32 +05:30
|
|
|
</DroppableWrapper>
|
2024-09-13 16:03:58 +05:30
|
|
|
|
2024-09-16 12:23:15 +05:30
|
|
|
<CanvasToolBar isOpen={this.state.toolbarOpen}
|
2024-09-15 12:08:29 +05:30
|
|
|
widgetType={this.state.selectedWidgets?.at(0)?.getWidgetType() || ""}
|
|
|
|
|
attrs={this.state.toolbarAttrs}
|
2024-09-16 12:23:15 +05:30
|
|
|
/>
|
2024-09-16 22:04:24 +05:30
|
|
|
{/* </ActiveWidgetProvider> */}
|
2024-08-08 16:21:19 +05:30
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default Canvas
|