working on dnd inside the widgets
This commit is contained in:
@@ -23,6 +23,8 @@ import { ReactComponent as DotsBackground } from "../assets/background/dots.svg"
|
||||
import DroppableWrapper from "../components/draggable/droppable"
|
||||
import { ActiveWidgetContext, ActiveWidgetProvider, withActiveWidget } from "./activeWidgetContext"
|
||||
import { DragWidgetProvider } from "./widgets/draggableWidgetContext"
|
||||
import { PosType } from "./constants/layouts"
|
||||
import WidgetContainer from "./constants/containers"
|
||||
|
||||
// const DotsBackground = require("../assets/background/dots.svg")
|
||||
|
||||
@@ -62,7 +64,7 @@ class Canvas extends React.Component {
|
||||
|
||||
this.state = {
|
||||
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: "", layoutType: "flex"}]
|
||||
widgets: [], // stores the mapping to widgetRefs, stores id and WidgetType, later used for rendering [{id: , widgetType: WidgetClass, children: [], parent: "", initialData: {}}]
|
||||
zoom: 1,
|
||||
isPanning: false,
|
||||
currentTranslate: { x: 0, y: 0 },
|
||||
@@ -173,14 +175,51 @@ class Canvas extends React.Component {
|
||||
* @returns {Widget}
|
||||
*/
|
||||
getWidgetFromTarget(target) {
|
||||
// TODO: improve search, currently O(n), but can be improved via this.state.widgets or something
|
||||
|
||||
let innerWidget = null
|
||||
|
||||
for (let [key, ref] of Object.entries(this.widgetRefs)) {
|
||||
console.log("ref: ", ref, key)
|
||||
if (ref.current.getElement().contains(target)) {
|
||||
return ref.current
|
||||
|
||||
if (!innerWidget) {
|
||||
innerWidget = ref.current;
|
||||
} else if (innerWidget.getElement().contains(ref.current.getElement())) {
|
||||
// If the current widget is deeper than the existing innermost widget, update innerWidget
|
||||
innerWidget = ref.current;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return innerWidget
|
||||
// for (let [key, ref] of Object.entries(this.widgetRefs)) {
|
||||
// console.log("ref: ", ref, key)
|
||||
// if (ref.current.getElement().contains(target)) {
|
||||
// return ref.current
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// const returnTargetWidget = (widgets) => {
|
||||
// for (let x of widgets) {
|
||||
// const widget = this.widgetRefs[x.id]
|
||||
|
||||
// // Check if the widget contains the target
|
||||
// if (widget && widget.current.getElement().contains(target)) {
|
||||
// // If it has children, continue checking the children for the innermost match
|
||||
// const childWidget = returnTargetWidget(x.children)
|
||||
|
||||
// // Return the innermost child widget if found, otherwise return the current widget
|
||||
// return childWidget || widget.current
|
||||
// }
|
||||
// }
|
||||
// // If no matching widget is found, return null
|
||||
// return null
|
||||
// }
|
||||
|
||||
|
||||
// return returnTargetWidget(this.state.widgets)
|
||||
|
||||
}
|
||||
|
||||
keyDownEvent(event) {
|
||||
@@ -204,6 +243,7 @@ class Canvas extends React.Component {
|
||||
this.mousePos = { x: event.clientX, y: event.clientY }
|
||||
|
||||
let selectedWidget = this.getWidgetFromTarget(event.target)
|
||||
// console.log("selected widget: ", selectedWidget)
|
||||
if (event.button === 0) {
|
||||
this.mousePressed = true
|
||||
|
||||
@@ -590,23 +630,28 @@ class Canvas extends React.Component {
|
||||
* @param {boolean} create - if create is set to true the widget will be created before adding to the child tree
|
||||
*/
|
||||
handleAddWidgetChild = (parentWidgetId, dragElementID, create = false) => {
|
||||
|
||||
// TODO: creation of the child widget if its not created
|
||||
// widgets data structure { id, widgetType: widgetComponentType, children: [], parent: "" }
|
||||
const parentWidgetObj = this.findWidgetFromListById(parentWidgetId)
|
||||
let childWidgetObj = this.findWidgetFromListById(dragElementID)
|
||||
|
||||
console.log("WIdgets: ", parentWidgetObj, childWidgetObj)
|
||||
// console.log("WIdgets: ", parentWidgetObj, childWidgetObj)
|
||||
|
||||
if (parentWidgetObj && childWidgetObj) {
|
||||
|
||||
// remove child from current postion
|
||||
const childWidget = this.widgetRefs[childWidgetObj.id]
|
||||
// childWidget.current.setPosType(PosType.RELATIVE) // set state needs to rerender so serialize will return absolute always
|
||||
const childData = childWidget.current.serialize() // save the data and pass it the updated child object
|
||||
|
||||
// remove child from current position
|
||||
let updatedWidgets = this.removeWidgetFromCurrentList(dragElementID)
|
||||
|
||||
console.log("pre updated widgets: ", updatedWidgets)
|
||||
|
||||
const updatedChildWidget = {
|
||||
...childWidgetObj,
|
||||
parent: parentWidgetId
|
||||
parent: parentWidgetId,
|
||||
initialData: {...childData, positionType: PosType.NONE, zIndex: 0, widgetContainer: WidgetContainer.WIDGET} // makes sure that after dropping the position is set to non absolute value
|
||||
}
|
||||
|
||||
// Create a new copy of the parent widget with the child added
|
||||
@@ -637,10 +682,10 @@ class Canvas extends React.Component {
|
||||
widgets: updatedWidgets
|
||||
}, () => {
|
||||
|
||||
this.widgetRefs[dragElementID] = React.createRef()
|
||||
// this.widgetRefs[dragElementID] = React.createRef()
|
||||
|
||||
// Optionally, force React to update and re-render the refs
|
||||
this.forceUpdate()
|
||||
// // Optionally, force React to update and re-render the refs
|
||||
// this.forceUpdate()
|
||||
})
|
||||
|
||||
}
|
||||
@@ -659,7 +704,15 @@ class Canvas extends React.Component {
|
||||
// Store the ref in the instance variable
|
||||
this.widgetRefs[id] = widgetRef
|
||||
|
||||
const widgets = [...this.state.widgets, { id, widgetType: widgetComponentType, children: [], parent: "", layoutType: "flex" }] // don't add the widget refs in the state
|
||||
const newWidget = {
|
||||
id,
|
||||
widgetType: widgetComponentType,
|
||||
children: [],
|
||||
parent: "",
|
||||
initialData: {} // useful for serializing and deserializing (aka, saving and loading)
|
||||
}
|
||||
|
||||
const widgets = [...this.state.widgets, newWidget] // don't add the widget refs in the state
|
||||
|
||||
// Update the state to include the new widget's type and ID
|
||||
this.setState({
|
||||
@@ -730,8 +783,9 @@ class Canvas extends React.Component {
|
||||
|
||||
// FIXME: need to delete the child widgets
|
||||
// IDEA: find the widget first, check for the parent, if parent exist remove it from the parents children list
|
||||
|
||||
// this.widgetRefs[widgetId]?.current.remove()
|
||||
|
||||
//this.removeWidgetFromCurrentList(widgetID) <--- use this
|
||||
delete this.widgetRefs[widgetId]
|
||||
|
||||
const widgets = this.state.widgets.filter(widget => widget.id !== widgetId)
|
||||
@@ -787,66 +841,97 @@ class Canvas extends React.Component {
|
||||
})
|
||||
} else if (container === "canvas") {
|
||||
|
||||
const widgetObj = this.getWidgetById(draggedElement.getAttribute("data-widget-id"))
|
||||
let widgetId = draggedElement.getAttribute("data-widget-id")
|
||||
let widgetContainer = draggedElement.getAttribute("data-container")
|
||||
|
||||
|
||||
const widgetObj = this.getWidgetById(widgetId)
|
||||
// console.log("WidgetObj: ", widgetObj)
|
||||
widgetObj.current.setPos(finalPosition.x, finalPosition.y)
|
||||
if (widgetContainer === WidgetContainer.CANVAS){
|
||||
|
||||
widgetObj.current.setPos(finalPosition.x, finalPosition.y)
|
||||
|
||||
}else if (widgetContainer === WidgetContainer.WIDGET){
|
||||
|
||||
// FIXME: move the widget out of the widget
|
||||
// if the widget was inside another widget move it outside
|
||||
let childWidgetObj = this.findWidgetFromListById(widgetObj.id)
|
||||
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
|
||||
|
||||
console.log("pre updated widgets: ", updatedWidgets)
|
||||
|
||||
const updatedChildWidget = {
|
||||
...childWidgetObj,
|
||||
parent: "",
|
||||
initialData: {...childData,
|
||||
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.id)
|
||||
|
||||
|
||||
// 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
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
getLayoutStyleForWidget = (widget) => {
|
||||
const { layoutType } = widget // e.g., 'grid', 'flex', 'absolute'
|
||||
|
||||
switch (layoutType) {
|
||||
case 'grid':
|
||||
return { display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '10px' }
|
||||
case 'flex':
|
||||
return { display: 'flex', flexDirection: 'row', justifyContent: 'space-around' }
|
||||
case 'absolute':
|
||||
return { position: 'absolute', left: widget.left, top: widget.top } // Custom positioning
|
||||
default:
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
renderWidget = (widget) => {
|
||||
|
||||
// FIXME: the child elements ref is not correct when drag and dropped into another
|
||||
const { id, widgetType: ComponentType, children = [], parent } = widget
|
||||
// FIXME: the child elements are being recreated instead of using the same object
|
||||
const { id, widgetType: ComponentType, children = [], parent, initialData={} } = widget
|
||||
|
||||
console.log("rendering: ", widget, id)
|
||||
|
||||
// Layout management for children inside the parent
|
||||
const renderChildren = (childWidgets) => {
|
||||
console.log("Found the child : ", childWidgets)
|
||||
return childWidgets.map((child) => {
|
||||
const renderChildren = (childrenData) => {
|
||||
// recursively render the child elements
|
||||
return childrenData.map((child) => {
|
||||
const childWidget = this.findWidgetFromListById(child.id)
|
||||
// console.log("Found the child : ", childWidget)
|
||||
if (childWidget) {
|
||||
console.log("rendering the child", childWidget)
|
||||
return this.renderWidget(childWidget) // Recursively render child widgets
|
||||
}
|
||||
return null
|
||||
})
|
||||
}
|
||||
|
||||
// Example of handling layout within the parent widget
|
||||
const layoutStyle = this.getLayoutStyleForWidget(widget)
|
||||
|
||||
console.log("widget ref id: ", this.widgetRefs[id], this.widgetRefs)
|
||||
|
||||
return (
|
||||
<ComponentType
|
||||
key={id}
|
||||
id={id}
|
||||
ref={this.widgetRefs[id]}
|
||||
initialData={initialData}
|
||||
canvasRef={this.canvasContainerRef}
|
||||
onWidgetUpdate={this.onActiveWidgetUpdate}
|
||||
childWidgets={children} // Pass the list of children (IDs)
|
||||
parent={parent}
|
||||
onAddChildWidget={this.handleAddWidgetChild}
|
||||
onWidgetResizing={(resizeSide) => this.setState({ widgetResizing: resizeSide })}
|
||||
style={layoutStyle} // Apply layout style (for position, size, etc.)
|
||||
>
|
||||
{/* Render children inside the parent with layout applied */}
|
||||
{renderChildren(children)}
|
||||
|
||||
12
src/canvas/constants/containers.js
Normal file
12
src/canvas/constants/containers.js
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
|
||||
const WidgetContainer = {
|
||||
|
||||
CANVAS: "canvas", // widget is on the canvas
|
||||
SIDEBAR: "sidebar", // widget is contained inside sidebar
|
||||
WIDGET: "widget", // widget is contained inside another widget
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default WidgetContainer
|
||||
@@ -1,7 +1,12 @@
|
||||
const Layouts = {
|
||||
export const Layouts = {
|
||||
FLEX: "flex",
|
||||
GRID: "grid",
|
||||
PLACE: "absolute"
|
||||
}
|
||||
|
||||
export default Layouts
|
||||
|
||||
export const PosType = {
|
||||
ABSOLUTE: "absolute",
|
||||
RELATIVE: "relative",
|
||||
NONE: "unset"
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ColorPicker, Input, InputNumber, Select } from "antd"
|
||||
import { capitalize } from "../utils/common"
|
||||
import Tools from "./constants/tools.js"
|
||||
import { useActiveWidget } from "./activeWidgetContext.js"
|
||||
import Layouts from "./constants/layouts.js"
|
||||
import { Layouts } from "./constants/layouts.js"
|
||||
|
||||
|
||||
// FIXME: Maximum recursion error
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from "react"
|
||||
import { NotImplementedError } from "../../utils/errors"
|
||||
|
||||
import Tools from "../constants/tools"
|
||||
import Layouts from "../constants/layouts"
|
||||
import { Layouts, PosType} from "../constants/layouts"
|
||||
import Cursor from "../constants/cursor"
|
||||
import { toSnakeCase } from "../utils/utils"
|
||||
import EditableDiv from "../../components/editableDiv"
|
||||
@@ -12,6 +12,11 @@ import DroppableWrapper from "../../components/draggable/droppable"
|
||||
import { ActiveWidgetContext } from "../activeWidgetContext"
|
||||
import { DragWidgetProvider } from "./draggableWidgetContext"
|
||||
import WidgetDraggable from "./widgetDragDrop"
|
||||
import WidgetContainer from "../constants/containers"
|
||||
|
||||
|
||||
|
||||
const ATTRS_KEYS = ['value', 'label', 'tool', 'onChange', 'toolProps'] // these are attrs keywords, don't use these keywords as keys while defining the attrs property
|
||||
|
||||
|
||||
/**
|
||||
@@ -36,9 +41,6 @@ class Widget extends React.Component {
|
||||
this._disableResize = false
|
||||
this._disableSelection = false
|
||||
|
||||
this._parent = "" // id of the parent widget, default empty string
|
||||
this._children = [] // id's of all the child widgets
|
||||
|
||||
this.minSize = { width: 50, height: 50 } // disable resizing below this number
|
||||
this.maxSize = { width: 500, height: 500 } // disable resizing above this number
|
||||
|
||||
@@ -68,6 +70,8 @@ class Widget extends React.Component {
|
||||
enableRename: false, // will open the widgets editable div for renaming
|
||||
dragEnabled: true,
|
||||
|
||||
widgetContainer: WidgetContainer.CANVAS, // what is the parent of the widget
|
||||
|
||||
showDroppableStyle: { // shows the droppable indicator
|
||||
allow: false,
|
||||
show: false,
|
||||
@@ -75,7 +79,7 @@ class Widget extends React.Component {
|
||||
|
||||
pos: { x: 0, y: 0 },
|
||||
size: { width: 100, height: 100 },
|
||||
position: "absolute",
|
||||
positionType: PosType.ABSOLUTE,
|
||||
|
||||
widgetStyling: {
|
||||
// use for widget's inner styling
|
||||
@@ -95,7 +99,7 @@ class Widget extends React.Component {
|
||||
foregroundColor: {
|
||||
label: "Foreground Color",
|
||||
tool: Tools.COLOR_PICKER,
|
||||
value: "",
|
||||
value: "#000",
|
||||
},
|
||||
label: "Styling"
|
||||
},
|
||||
@@ -110,11 +114,13 @@ class Widget extends React.Component {
|
||||
cols: 1
|
||||
}
|
||||
},
|
||||
options: [
|
||||
{ value: "flex", label: "Flex" },
|
||||
{ value: "grid", label: "Grid" },
|
||||
{ value: "place", label: "Place" },
|
||||
],
|
||||
toolProps: {
|
||||
options: [
|
||||
{ value: "flex", label: "Flex" },
|
||||
{ value: "grid", label: "Grid" },
|
||||
{ value: "place", label: "Place" },
|
||||
],
|
||||
},
|
||||
onChange: (value) => this.setWidgetStyling("backgroundColor", value)
|
||||
},
|
||||
events: {
|
||||
@@ -145,11 +151,15 @@ class Widget extends React.Component {
|
||||
this.setAttrValue = this.setAttrValue.bind(this)
|
||||
this.setWidgetName = this.setWidgetName.bind(this)
|
||||
this.setWidgetStyling = this.setWidgetStyling.bind(this)
|
||||
this.setPosType = this.setPosType.bind(this)
|
||||
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.elementRef.current?.addEventListener("click", this.mousePress)
|
||||
|
||||
this.load(this.props.initialData || {})
|
||||
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@@ -249,13 +259,6 @@ class Widget extends React.Component {
|
||||
return this.state.attrs
|
||||
}
|
||||
|
||||
/**
|
||||
* removes the element/widget
|
||||
*/
|
||||
remove() {
|
||||
this.canvas.removeWidget(this.__id)
|
||||
}
|
||||
|
||||
mousePress(event) {
|
||||
// event.preventDefault()
|
||||
if (!this._disableSelection) {
|
||||
@@ -279,6 +282,18 @@ class Widget extends React.Component {
|
||||
return this.state.selected
|
||||
}
|
||||
|
||||
setPosType(positionType){
|
||||
|
||||
if (!Object.values(PosType).includes(positionType)){
|
||||
throw Error(`The Position type can only be among: ${Object.values(PosType).join(", ")}`)
|
||||
}
|
||||
|
||||
this.setState({
|
||||
positionType: positionType
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
setPos(x, y) {
|
||||
|
||||
this.setState({
|
||||
@@ -319,6 +334,20 @@ class Widget extends React.Component {
|
||||
return this.elementRef.current
|
||||
}
|
||||
|
||||
getLayoutStyleForWidget = () => {
|
||||
|
||||
switch (this.state.attrs.layout) {
|
||||
case 'grid':
|
||||
return { display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '10px' }
|
||||
case 'flex':
|
||||
return { display: 'flex', flexDirection: 'row', justifyContent: 'space-around' }
|
||||
case 'absolute':
|
||||
return { position: 'absolute', left: "0", top: "0" } // Custom positioning
|
||||
default:
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the key as a path, sets the value for the widget attribute
|
||||
* @param {string} path - path to the key, eg: styling.backgroundColor
|
||||
@@ -344,6 +373,43 @@ class Widget extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the path from the serialized attrs values,
|
||||
* this is a helper function to remove any non-serializable data associated with attrs
|
||||
* eg: {"styling.backgroundColor": "#ffff", "layout": {layout: "flex", direction: "", grid: }}
|
||||
*/
|
||||
serializeAttrsValues = () => {
|
||||
|
||||
const serializeValues = (obj, currentPath = "") => {
|
||||
const result = {}
|
||||
|
||||
for (let key in obj) {
|
||||
|
||||
if (ATTRS_KEYS.includes(key)) continue // don't serialize these as separate keys
|
||||
|
||||
if (typeof obj[key] === 'object' && obj[key] !== null) {
|
||||
// If the key contains a value property
|
||||
if (obj[key].hasOwnProperty('value')) {
|
||||
const path = currentPath ? `${currentPath}.${key}` : key;
|
||||
|
||||
// If the value is an object, retain the entire value object
|
||||
if (typeof obj[key].value === 'object' && obj[key].value !== null) {
|
||||
result[path] = obj[key].value
|
||||
} else {
|
||||
result[`${path}`] = obj[key].value
|
||||
}
|
||||
}
|
||||
// Continue recursion for nested objects
|
||||
Object.assign(result, serializeValues(obj[key], currentPath ? `${currentPath}.${key}` : key))
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
return serializeValues(this.state.attrs)
|
||||
}
|
||||
|
||||
setZIndex(zIndex) {
|
||||
this.setState({
|
||||
zIndex: zIndex
|
||||
@@ -412,22 +478,6 @@ class Widget extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
setParent(parentId) {
|
||||
this._parent = parentId
|
||||
}
|
||||
|
||||
addChild(childWidget) {
|
||||
|
||||
childWidget.setParent(this.__id)
|
||||
this._children.push(childWidget)
|
||||
}
|
||||
|
||||
removeChild(childId) {
|
||||
this._children = this._children.filter(function (item) {
|
||||
return item !== childId
|
||||
})
|
||||
}
|
||||
|
||||
handleDrop = (event, dragElement) => {
|
||||
console.log("dragging event: ", event, dragElement)
|
||||
|
||||
@@ -445,11 +495,53 @@ class Widget extends React.Component {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* serialize data for saving
|
||||
*/
|
||||
serialize = () => {
|
||||
// NOTE: when serializing make sure, you are only passing serializable objects not functions or other
|
||||
return ({
|
||||
zIndex: this.state.zIndex,
|
||||
widgetName: this.state.widgetName,
|
||||
pos: this.state.pos,
|
||||
size: this.state.size,
|
||||
widgetContainer: this.state.widgetContainer,
|
||||
widgetStyling: this.state.widgetStyling,
|
||||
positionType: this.state.positionType,
|
||||
attrs: this.serializeAttrsValues() // makes sure that functions are not serialized
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* loads the data
|
||||
* @param {object} data
|
||||
*/
|
||||
load = (data) => {
|
||||
|
||||
for (let [key, value] of Object.entries(data.attrs|{}))
|
||||
this.setAttrValue(key, value)
|
||||
|
||||
delete data.attrs // think of immutable way to modify
|
||||
|
||||
/**
|
||||
* const obj = { a: 1, b: 2, c: 3 }
|
||||
* const { b, ...newObj } = obj
|
||||
* console.log(newObj) // { a: 1, c: 3 }
|
||||
*/
|
||||
|
||||
this.setState(data)
|
||||
|
||||
}
|
||||
|
||||
// FIXME: children outside the bounding box
|
||||
renderContent() {
|
||||
// console.log("Children: ", this.props.children)
|
||||
// throw new NotImplementedError("render method has to be implemented")
|
||||
return (
|
||||
<div className="tw-w-full tw-h-full tw-rounded-md tw-bg-red-500" style={this.state.widgetStyling}>
|
||||
{/* {this.props.children} */}
|
||||
<div className="tw-w-full tw-h-full tw-p-2 tw-rounded-md tw-bg-red-500" style={this.state.widgetStyling}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -464,7 +556,7 @@ class Widget extends React.Component {
|
||||
let outerStyle = {
|
||||
cursor: this.cursor,
|
||||
zIndex: this.state.zIndex,
|
||||
position: "absolute", // don't change this if it has to be movable on the canvas
|
||||
position: this.state.positionType, // don't change this if it has to be movable on the canvas
|
||||
top: `${this.state.pos.y}px`,
|
||||
left: `${this.state.pos.x}px`,
|
||||
width: `${this.state.size.width}px`,
|
||||
@@ -499,81 +591,83 @@ class Widget extends React.Component {
|
||||
className="tw-absolute tw-shadow-xl tw-w-fit tw-h-fit"
|
||||
style={outerStyle}
|
||||
data-draggable-type={this.getWidgetType()} // helps with droppable
|
||||
data-container={"canvas"} // indicates how the canvas should handle dragging, one is sidebar other is canvas
|
||||
data-container={this.state.widgetContainer} // indicates how the canvas should handle dragging, one is sidebar other is canvas
|
||||
>
|
||||
|
||||
{this.renderContent()}
|
||||
<div className="tw-relative tw-w-full tw-h-full tw-top-0 tw-left-0">
|
||||
|
||||
{
|
||||
// show drop style on drag hover
|
||||
this.state.showDroppableStyle.show &&
|
||||
<div className={`${this.state.showDroppableStyle.allow ? "tw-border-blue-600" : "tw-border-red-600"}
|
||||
tw-absolute tw-top-[-5px] tw-left-[-5px] tw-w-full tw-h-full tw-z-[2]
|
||||
tw-border-2 tw-border-dashed tw-rounded-lg tw-pointer-events-none
|
||||
{
|
||||
// show drop style on drag hover
|
||||
this.state.showDroppableStyle.show &&
|
||||
<div className={`${this.state.showDroppableStyle.allow ? "tw-border-blue-600" : "tw-border-red-600"}
|
||||
tw-absolute tw-top-[-5px] tw-left-[-5px] tw-w-full tw-h-full tw-z-[2]
|
||||
tw-border-2 tw-border-dashed tw-rounded-lg tw-pointer-events-none
|
||||
|
||||
`}
|
||||
style={
|
||||
{
|
||||
width: "calc(100% + 10px)",
|
||||
height: "calc(100% + 10px)",
|
||||
`}
|
||||
style={
|
||||
{
|
||||
width: "calc(100% + 10px)",
|
||||
height: "calc(100% + 10px)",
|
||||
}
|
||||
}
|
||||
}
|
||||
>
|
||||
>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div className={`tw-absolute tw-bg-transparent tw-scale-[1.1] tw-opacity-100
|
||||
tw-w-full tw-h-full tw-top-0
|
||||
${this.state.selected ? 'tw-border-2 tw-border-solid tw-border-blue-500' : 'tw-hidden'}`}>
|
||||
|
||||
<div className="tw-relative tw-w-full tw-h-full">
|
||||
<EditableDiv value={this.state.widgetName} onChange={this.setWidgetName}
|
||||
maxLength={40}
|
||||
openEdit={this.state.enableRename}
|
||||
className="tw-text-sm tw-w-fit tw-max-w-[160px] tw-text-clip tw-min-w-[150px]
|
||||
tw-overflow-hidden tw-absolute tw--top-6 tw-h-6"
|
||||
/>
|
||||
|
||||
<div
|
||||
className="tw-w-2 tw-h-2 tw-absolute tw--left-1 tw--top-1 tw-bg-blue-500"
|
||||
style={{ cursor: Cursor.NW_RESIZE }}
|
||||
onMouseDown={(e) => {
|
||||
this.props.onWidgetResizing("nw")
|
||||
this.setState({dragEnabled: false})
|
||||
}}
|
||||
onMouseLeave={() => this.setState({dragEnabled: true})}
|
||||
/>
|
||||
<div
|
||||
className="tw-w-2 tw-h-2 tw-absolute tw--right-1 tw--top-1 tw-bg-blue-500"
|
||||
style={{ cursor: Cursor.SW_RESIZE }}
|
||||
onMouseDown={(e) => {
|
||||
this.props.onWidgetResizing("ne")
|
||||
this.setState({dragEnabled: false})
|
||||
}}
|
||||
onMouseLeave={() => this.setState({dragEnabled: true})}
|
||||
/>
|
||||
<div
|
||||
className="tw-w-2 tw-h-2 tw-absolute tw--left-1 tw--bottom-1 tw-bg-blue-500"
|
||||
style={{ cursor: Cursor.SW_RESIZE }}
|
||||
onMouseDown={(e) => {
|
||||
this.props.onWidgetResizing("sw")
|
||||
this.setState({dragEnabled: false})
|
||||
}}
|
||||
onMouseLeave={() => this.setState({dragEnabled: true})}
|
||||
/>
|
||||
<div
|
||||
className="tw-w-2 tw-h-2 tw-absolute tw--right-1 tw--bottom-1 tw-bg-blue-500"
|
||||
style={{ cursor: Cursor.SE_RESIZE }}
|
||||
onMouseDown={(e) => {
|
||||
this.props.onWidgetResizing("se")
|
||||
this.setState({dragEnabled: false})
|
||||
}}
|
||||
onMouseLeave={() => this.setState({dragEnabled: true})}
|
||||
/>
|
||||
|
||||
</div>
|
||||
}
|
||||
|
||||
<div className={`tw-absolute tw-bg-transparent tw-scale-[1.1] tw-opacity-100
|
||||
tw-w-full tw-h-full tw-top-0
|
||||
${this.state.selected ? 'tw-border-2 tw-border-solid tw-border-blue-500' : 'tw-hidden'}`}>
|
||||
|
||||
<div className="tw-relative tw-w-full tw-h-full">
|
||||
<EditableDiv value={this.state.widgetName} onChange={this.setWidgetName}
|
||||
maxLength={40}
|
||||
openEdit={this.state.enableRename}
|
||||
className="tw-text-sm tw-w-fit tw-max-w-[160px] tw-text-clip tw-min-w-[150px]
|
||||
tw-overflow-hidden tw-absolute tw--top-6 tw-h-6"
|
||||
/>
|
||||
|
||||
<div
|
||||
className="tw-w-2 tw-h-2 tw-absolute tw--left-1 tw--top-1 tw-bg-blue-500"
|
||||
style={{ cursor: Cursor.NW_RESIZE }}
|
||||
onMouseDown={(e) => {
|
||||
this.props.onWidgetResizing("nw")
|
||||
this.setState({dragEnabled: false})
|
||||
}}
|
||||
onMouseLeave={() => this.setState({dragEnabled: true})}
|
||||
/>
|
||||
<div
|
||||
className="tw-w-2 tw-h-2 tw-absolute tw--right-1 tw--top-1 tw-bg-blue-500"
|
||||
style={{ cursor: Cursor.SW_RESIZE }}
|
||||
onMouseDown={(e) => {
|
||||
this.props.onWidgetResizing("ne")
|
||||
this.setState({dragEnabled: false})
|
||||
}}
|
||||
onMouseLeave={() => this.setState({dragEnabled: true})}
|
||||
/>
|
||||
<div
|
||||
className="tw-w-2 tw-h-2 tw-absolute tw--left-1 tw--bottom-1 tw-bg-blue-500"
|
||||
style={{ cursor: Cursor.SW_RESIZE }}
|
||||
onMouseDown={(e) => {
|
||||
this.props.onWidgetResizing("sw")
|
||||
this.setState({dragEnabled: false})
|
||||
}}
|
||||
onMouseLeave={() => this.setState({dragEnabled: true})}
|
||||
/>
|
||||
<div
|
||||
className="tw-w-2 tw-h-2 tw-absolute tw--right-1 tw--bottom-1 tw-bg-blue-500"
|
||||
style={{ cursor: Cursor.SE_RESIZE }}
|
||||
onMouseDown={(e) => {
|
||||
this.props.onWidgetResizing("se")
|
||||
this.setState({dragEnabled: false})
|
||||
}}
|
||||
onMouseLeave={() => this.setState({dragEnabled: true})}
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{this.renderContent()}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { memo, useEffect, useState } from "react"
|
||||
import { memo, useEffect, useRef, useState } from "react"
|
||||
import { useDragWidgetContext } from "./draggableWidgetContext"
|
||||
import { useDragContext } from "../../components/draggable/draggableContext"
|
||||
|
||||
@@ -29,10 +29,13 @@ const WidgetDraggable = memo(({ widgetRef, enableDrag=true, dragElementType="wid
|
||||
})
|
||||
|
||||
const handleDragStart = (e) => {
|
||||
e.stopPropagation()
|
||||
setIsDragging(true)
|
||||
|
||||
onDragStart(widgetRef?.current || null)
|
||||
|
||||
console.log("Drag start: ", widgetRef.current)
|
||||
|
||||
// Create custom drag image with full opacity, this will ensure the image isn't taken from part of the canvas
|
||||
const dragImage = widgetRef?.current.cloneNode(true)
|
||||
dragImage.style.opacity = '1' // Ensure full opacity
|
||||
@@ -58,6 +61,11 @@ const WidgetDraggable = memo(({ widgetRef, enableDrag=true, dragElementType="wid
|
||||
const dragEleType = draggedElement.getAttribute("data-draggable-type")
|
||||
|
||||
// console.log("Drag entering...", overElement === e.currentTarget)
|
||||
// FIXME: the outer widget shouldn't be swallowed by inner widget
|
||||
if (draggedElement === widgetRef.current){
|
||||
// prevent drop on itself, since the widget is invisible when dragging, if dropped on itself, it may consume itself
|
||||
return
|
||||
}
|
||||
|
||||
setOverElement(e.currentTarget)
|
||||
|
||||
@@ -86,6 +94,10 @@ const WidgetDraggable = memo(({ widgetRef, enableDrag=true, dragElementType="wid
|
||||
}
|
||||
|
||||
const handleDragOver = (e) => {
|
||||
if (draggedElement === widgetRef.current){
|
||||
// prevent drop on itself, since the widget is invisible when dragging, if dropped on itself, it may consume itself
|
||||
return
|
||||
}
|
||||
// console.log("Drag over: ", e.dataTransfer.getData("text/plain"), e.dataTransfer)
|
||||
const dragEleType = draggedElement.getAttribute("data-draggable-type")
|
||||
|
||||
@@ -98,7 +110,21 @@ const WidgetDraggable = memo(({ widgetRef, enableDrag=true, dragElementType="wid
|
||||
const handleDropEvent = (e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
// console.log("Dropped")
|
||||
console.log("Dropped: ", draggedElement, props.children)
|
||||
|
||||
if (draggedElement === widgetRef.current){
|
||||
// prevent drop on itself, since the widget is invisible when dragging, if dropped on itself, it may consume itself
|
||||
return
|
||||
}
|
||||
|
||||
let currentElement = e.currentTarget
|
||||
while (currentElement) {
|
||||
if (currentElement === draggedElement) {
|
||||
console.log("Dropped into a descendant element, ignoring drop")
|
||||
return // Exit early to prevent the drop
|
||||
}
|
||||
currentElement = currentElement.parentElement // Traverse up to check ancestors
|
||||
}
|
||||
|
||||
setShowDroppable({
|
||||
allow: false,
|
||||
@@ -130,7 +156,7 @@ const WidgetDraggable = memo(({ widgetRef, enableDrag=true, dragElementType="wid
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`${props.className} tw-w-fit tw-h-fit tw-bg-blue`}
|
||||
<div className={`${props.className || ""} tw-w-fit tw-h-fit tw-bg-blue`}
|
||||
onDragOver={handleDragOver}
|
||||
onDrop={handleDropEvent}
|
||||
onDragEnter={handleDragEnter}
|
||||
@@ -138,7 +164,7 @@ const WidgetDraggable = memo(({ widgetRef, enableDrag=true, dragElementType="wid
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnd={handleDragEnd}
|
||||
draggable={enableDrag}
|
||||
style={{ opacity: isDragging ? 0 : 1}} // hide the initial position when dragging
|
||||
style={{ opacity: isDragging ? 0.3 : 1}} // hide the initial position when dragging
|
||||
>
|
||||
{props.children}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user