fixed place layout manager. Place/absolute position now working inside the widgets
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user