fixed place layout manager. Place/absolute position now working inside the widgets

This commit is contained in:
paul
2024-09-24 21:49:26 +05:30
parent 22e0a0543d
commit 00277205f5
6 changed files with 412 additions and 152 deletions

View File

@@ -23,10 +23,11 @@ import { ReactComponent as DotsBackground } from "../assets/background/dots.svg"
import DroppableWrapper from "../components/draggable/droppable"
import { PosType } from "./constants/layouts"
import { Layouts, PosType } from "./constants/layouts"
import WidgetContainer from "./constants/containers"
import { isSubClassOfWidget } from "../utils/widget"
import { ButtonModal } from "../components/modals"
import ResizeWidgetContainer from "./resizeContainer"
// const DotsBackground = require("../assets/background/dots.svg")
@@ -65,6 +66,7 @@ class Canvas extends React.Component {
this.widgetRefs = {} // stores the actual refs to the widgets inside the canvas {id: ref, id2, ref2...}
this.state = {
isWidgetDragging: false,
widgetResizing: "", // set this to "nw", "sw" etc based on the side when widgets resizing handles are selected
widgets: [], // stores the mapping to widgetRefs, stores id and WidgetType, later used for rendering [{id: , widgetType: WidgetClass, children: [], parent: "", initialData: {}}]
zoom: 1,
@@ -181,8 +183,8 @@ class Canvas extends React.Component {
let innerWidget = null
for (let [key, ref] of Object.entries(this.widgetRefs)) {
if (ref.current === target){
if (ref.current === target) {
innerWidget = ref.current
break
}
@@ -235,11 +237,13 @@ class Canvas extends React.Component {
this.state.selectedWidget?.setZIndex(0)
selectedWidget.setZIndex(1000)
selectedWidget.select()
// console.log("selected widget", selectedWidget, this.state.selectedWidget)
this.setState({
selectedWidget: selectedWidget,
toolbarAttrs: selectedWidget.getToolbarAttrs()
})
// if (!this.state.selectedWidget || (selectedWidget.getId() !== this.state.selectedWidget?.getId())) {
// this.state.selectedWidget?.deSelect() // deselect the previous widget before adding the new one
// this.state.selectedWidget?.setZIndex(0)
@@ -301,16 +305,16 @@ class Canvas extends React.Component {
{
key: "snap",
label: (<div onClick={() => {
domtoimage.toPng(selectedWidget.getElement(), {
width: selectedWidget.getElement().offsetWidth * 2, // Multiply element's width by 2
height: selectedWidget.getElement().offsetHeight * 2 // Multiply element's height by 2
}).then((dataUrl) => {
saveAs(dataUrl, 'widget.png')
}).catch((error) => {
console.error('Error capturing widget as PNg:', error)
})
}}>
<FileImageOutlined /> Save as Image</div>),
domtoimage.toPng(selectedWidget.getElement(), {
width: selectedWidget.getElement().offsetWidth * 2, // Multiply element's width by 2
height: selectedWidget.getElement().offsetHeight * 2 // Multiply element's height by 2
}).then((dataUrl) => {
saveAs(dataUrl, 'widget.png')
}).catch((error) => {
console.error('Error capturing widget as PNg:', error)
})
}}>
<FileImageOutlined /> Save as Image</div>),
icons: <FileImageOutlined />,
}
]
@@ -638,6 +642,114 @@ class Canvas extends React.Component {
})
}
/**
* Handles drop event to canvas from the sidebar and on canvas widget movement
* @param {DragEvent} e
*/
handleDropEvent = (e, draggedElement, widgetClass = null) => {
e.preventDefault()
this.setState({ isWidgetDragging: false })
if (!draggedElement || !draggedElement.getAttribute("data-drag-start-within")) {
// if the drag is starting from outside (eg: file drop) or if drag doesn't exist
return
}
const container = draggedElement.getAttribute("data-container")
const canvasRect = this.canvasRef.current.getBoundingClientRect()
const draggedElementRect = draggedElement.getBoundingClientRect()
const elementWidth = draggedElementRect.width
const elementHeight = draggedElementRect.height
const { clientX, clientY } = e
let finalPosition = {
x: (clientX - canvasRect.left) / this.state.zoom,
y: (clientY - canvasRect.top) / this.state.zoom,
}
if (container === WidgetContainer.SIDEBAR) {
if (!widgetClass) {
throw new Error("WidgetClass has to be passed for widgets dropped from sidebar")
}
// if the widget is being dropped from the sidebar, use the info to create the widget first
this.createWidget(widgetClass, ({ id, widgetRef }) => {
widgetRef.current.setPos(finalPosition.x, finalPosition.y)
})
} else if ([WidgetContainer.CANVAS, WidgetContainer.WIDGET].includes(container)) {
// snaps to center
finalPosition = {
x: (clientX - canvasRect.left) / this.state.zoom - (elementWidth / 2) / this.state.zoom,
y: (clientY - canvasRect.top) / this.state.zoom - (elementHeight / 2) / this.state.zoom,
}
let widgetId = draggedElement.getAttribute("data-widget-id")
const widgetObj = this.getWidgetById(widgetId)
// console.log("WidgetObj: ", widgetObj)
if (container === WidgetContainer.CANVAS) {
widgetObj.current.setPos(finalPosition.x, finalPosition.y)
} else if (container === WidgetContainer.WIDGET) {
// if the widget was inside another widget move it outside
let childWidgetObj = this.findWidgetFromListById(widgetObj.current.getId())
let parentWidgetObj = this.findWidgetFromListById(childWidgetObj.parent)
const childData = widgetObj.current.serialize() // save the data and pass it the updated child object
// remove child from current position
const updatedChildWidget = {
...childWidgetObj,
parent: "",
initialData: {
...childData,
pos: { x: finalPosition.x, y: finalPosition.y },
positionType: PosType.ABSOLUTE, // makes sure that after dropping the position is set to absolute value
zIndex: 0,
widgetContainer: WidgetContainer.CANVAS
}
}
let updatedWidgets = this.removeWidgetFromCurrentList(widgetObj.current.getId())
// Create a new copy of the parent widget with the child added
const updatedParentWidget = {
...parentWidgetObj,
// children: parentWidgetObj.children.filter(child => child.id !== childWidgetObj.id)
}
updatedWidgets = updatedWidgets.map(widget => {
if (widget.id === parentWidgetObj.id) {
return updatedParentWidget // Update the parent widget with the child removed
} else {
return widget // Leave other widgets unchanged
}
})
this.setState({
widgets: [...updatedWidgets, updatedChildWidget]
})
}
}
}
/**
* Adds the child into the children attribute inside the this.widgets list of objects
* // widgets data structure { id, widgetType: widgetComponentType, children: [], parent: "" }
@@ -645,8 +757,9 @@ class Canvas extends React.Component {
* @param {object} dragElement
* @param {boolean} create - if create is set to true the widget will be created before adding to the child tree
*/
handleAddWidgetChild = ({ parentWidgetId, dragElementID, swap = false }) => {
handleAddWidgetChild = ({event, parentWidgetId, dragElementID, swap = false }) => {
console.log("event: ", event)
// widgets data structure { id, widgetType: widgetComponentType, children: [], parent: "" }
const dropWidgetObj = this.findWidgetFromListById(parentWidgetId)
// Find the dragged widget object
@@ -658,6 +771,18 @@ class Canvas extends React.Component {
const dragWidget = this.widgetRefs[dragWidgetObj.id]
const dragData = dragWidget.current.serialize()
const parentWidget = this.widgetRefs[parentWidgetId].current
const parentRect = parentWidget.getBoundingRect()
const canvasRect = this.canvasRef.current.getBoundingClientRect()
const { clientX, clientY } = event
let finalPosition = {
x: (clientX - parentRect.left) / this.state.zoom,
y: (clientY - parentRect.top) / this.state.zoom,
}
if (swap) {
// If swapping, we need to find the common parent
const grandParentWidgetObj = this.findWidgetFromListById(dropWidgetObj.parent)
@@ -690,17 +815,25 @@ class Canvas extends React.Component {
// Non-swap mode: Add the dragged widget as a child of the drop widget
let updatedWidgets = this.removeWidgetFromCurrentList(dragElementID)
const parentLayout = parentWidget.getLayout()?.layout
console.log("parent layout: ", parentLayout, parentWidget.getLayout(), parentWidget)
dragWidget.current.setPos(finalPosition.x, finalPosition.y)
const updatedDragWidget = {
...dragWidgetObj,
parent: dropWidgetObj.id, // Keep the parent reference
initialData: {
...dragData,
positionType: PosType.NONE,
positionType: parentLayout === Layouts.PLACE ? PosType.ABSOLUTE : PosType.NONE,
zIndex: 0,
pos: {x: finalPosition.x, y: finalPosition.y},
widgetContainer: WidgetContainer.WIDGET
}
}
console.log("updated widget: ", updatedDragWidget)
const updatedDropWidget = {
...dropWidgetObj,
children: [...dropWidgetObj.children, updatedDragWidget]
@@ -725,7 +858,7 @@ class Canvas extends React.Component {
*/
createWidget(widgetComponentType, callback) {
if (!isSubClassOfWidget(widgetComponentType)){
if (!isSubClassOfWidget(widgetComponentType)) {
throw new Error("widgetComponentType must be a subclass of Widget class")
}
@@ -804,6 +937,28 @@ class Canvas extends React.Component {
this._onWidgetListUpdated([])
}
getWidgetByIdFromWidgetList = (widgetId) => {
function recursiveFind(objects) {
for (const obj of objects) {
// Check if the current object has the matching ID
if (obj.id === widgetId) {
return obj // Return the object if found
}
// Recursively check children if they exist
if (obj.children && obj.children.length > 0) {
const found = recursiveFind(obj.children)
if (found) {
return found // Return the found object from children
}
}
}
return null // Return null if not found
}
return recursiveFind(this.state.widgets)
}
removeWidget(widgetId) {
@@ -833,114 +988,23 @@ class Canvas extends React.Component {
}
/**
* Handles drop event to canvas from the sidebar and on canvas widget movement
* @param {DragEvent} e
* informs the child about the parent layout
*/
handleDropEvent = (e, draggedElement, widgetClass=null) => {
updateChildLayouts = ({parentId, parentLayout}) => {
const parent = this.getWidgetByIdFromWidgetList(parentId)
e.preventDefault()
if (!parent) return
if (!draggedElement || !draggedElement.getAttribute("data-drag-start-within")){
// if the drag is starting from outside (eg: file drop) or if drag doesn't exist
return
}
const container = draggedElement.getAttribute("data-container")
const canvasRect = this.canvasRef.current.getBoundingClientRect()
const draggedElementRect = draggedElement.getBoundingClientRect()
const elementWidth = draggedElementRect.width
const elementHeight = draggedElementRect.height
const { clientX, clientY } = e
let finalPosition = {
x: (clientX - canvasRect.left) / this.state.zoom,
y: (clientY - canvasRect.top) / this.state.zoom,
}
if (container === WidgetContainer.SIDEBAR) {
if (!widgetClass){
throw new Error("WidgetClass has to be passed for widgets dropped from sidebar")
}
// if the widget is being dropped from the sidebar, use the info to create the widget first
this.createWidget(widgetClass, ({ id, widgetRef }) => {
widgetRef.current.setPos(finalPosition.x, finalPosition.y)
})
} else if ([WidgetContainer.CANVAS, WidgetContainer.WIDGET].includes(container)) {
// snaps to center
finalPosition = {
x: (clientX - canvasRect.left) / this.state.zoom - (elementWidth / 2) / this.state.zoom,
y: (clientY - canvasRect.top) / this.state.zoom - (elementHeight / 2) / this.state.zoom,
}
let widgetId = draggedElement.getAttribute("data-widget-id")
const widgetObj = this.getWidgetById(widgetId)
// console.log("WidgetObj: ", widgetObj)
if (container === WidgetContainer.CANVAS) {
widgetObj.current.setPos(finalPosition.x, finalPosition.y)
} else if (container === WidgetContainer.WIDGET) {
// if the widget was inside another widget move it outside
let childWidgetObj = this.findWidgetFromListById(widgetObj.current.getId())
let parentWidgetObj = this.findWidgetFromListById(childWidgetObj.parent)
const childData = widgetObj.current.serialize() // save the data and pass it the updated child object
// remove child from current position
const updatedChildWidget = {
...childWidgetObj,
parent: "",
initialData: {
...childData,
pos: { x: finalPosition.x, y: finalPosition.y },
positionType: PosType.ABSOLUTE, // makes sure that after dropping the position is set to absolute value
zIndex: 0,
widgetContainer: WidgetContainer.CANVAS
}
}
let updatedWidgets = this.removeWidgetFromCurrentList(widgetObj.current.getId())
// Create a new copy of the parent widget with the child added
const updatedParentWidget = {
...parentWidgetObj,
// children: parentWidgetObj.children.filter(child => child.id !== childWidgetObj.id)
}
updatedWidgets = updatedWidgets.map(widget => {
if (widget.id === parentWidgetObj.id) {
return updatedParentWidget // Update the parent widget with the child removed
} else {
return widget // Leave other widgets unchanged
}
})
this.setState({
widgets: [...updatedWidgets, updatedChildWidget]
})
}
for (let child of parent.children){
this.widgetRefs[child.id].current.setParentLayout(parentLayout)
}
}
renderWidget = (widget) => {
const { id, widgetType: ComponentType, children = [], parent, initialData = {} } = widget
@@ -959,6 +1023,7 @@ class Canvas extends React.Component {
return (
<ComponentType
key={id}
id={id}
@@ -969,6 +1034,9 @@ class Canvas extends React.Component {
onAddChildWidget={this.handleAddWidgetChild}
onCreateWidgetRequest={this.createWidget} // create widget when dropped from sidebar
onWidgetResizing={(resizeSide) => this.setState({ widgetResizing: resizeSide })}
// onWidgetDragStart={() => this.setState({isWidgetDragging: true})}
// onWidgetDragEnd={() => this.setState({isWidgetDragging: false})}
onLayoutUpdate={this.updateChildLayouts}
>
{/* Render children inside the parent with layout applied */}
{renderChildren(children)}
@@ -977,6 +1045,7 @@ class Canvas extends React.Component {
}
render() {
return (
<div className="tw-relative tw-flex tw-w-full tw-h-full tw-max-h-[100vh]">
@@ -986,13 +1055,13 @@ class Canvas extends React.Component {
<Tooltip title="Reset viewport">
<Button icon={<ReloadOutlined />} onClick={this.resetTransforms} />
</Tooltip>
<ButtonModal
message={"Are you sure you want to clear the canvas? This cannot be undone."}
title={"Clear canvas"}
onOk={this.clearCanvas}
okText="Yes"
okButtonType="danger"
>
<ButtonModal
message={"Are you sure you want to clear the canvas? This cannot be undone."}
title={"Clear canvas"}
onOk={this.clearCanvas}
okText="Yes"
okButtonType="danger"
>
<Tooltip title="Clear canvas">
<Button danger icon={<DeleteOutlined />} />
</Tooltip>
@@ -1001,7 +1070,7 @@ class Canvas extends React.Component {
{/* <ActiveWidgetProvider> */}
<DroppableWrapper id="canvas-droppable"
droppableTags={{exclude: ["image", "video"]}}
droppableTags={{ exclude: ["image", "video"] }}
className="tw-w-full tw-h-full"
onDrop={this.handleDropEvent}>
{/* <DragWidgetProvider> */}
@@ -1024,13 +1093,17 @@ class Canvas extends React.Component {
}}
/>
{/* Canvas */}
<div data-canvas className="tw-w-full tw-h-full tw-absolute tw-top-0 tw-select-none
"
<div data-canvas className="tw-w-full tw-h-full tw-absolute tw-top-0 tw-select-none"
ref={this.canvasRef}>
<div className="tw-relative tw-w-full tw-h-full">
{
this.state.widgets.map(this.renderWidget)
}
{/* { this.state.selectedWidget &&
<ResizeWidgetContainer selectedWidget={this.state.selectedWidget}/>
} */}
</div>
</div>
</div>