2024-08-08 16:21:19 +05:30
import React from "react"
import { NotImplementedError } from "../../utils/errors"
import Tools from "../constants/tools"
2024-09-22 22:24:35 +05:30
import { Layouts , PosType } from "../constants/layouts"
2024-08-08 16:21:19 +05:30
import Cursor from "../constants/cursor"
2024-09-09 19:06:03 +05:30
import { toSnakeCase } from "../utils/utils"
import EditableDiv from "../../components/editableDiv"
2024-09-16 22:04:24 +05:30
2024-09-28 11:29:37 +05:30
2024-09-19 19:26:10 +05:30
import WidgetContainer from "../constants/containers"
2024-09-21 18:37:28 +05:30
import { DragContext } from "../../components/draggable/draggableContext"
2024-09-25 23:29:50 +05:30
import { isNumeric , removeKeyFromObject } from "../../utils/common"
2024-09-29 17:51:42 +05:30
import { info } from "autoprefixer"
import { message } from "antd"
2024-09-19 19:26:10 +05:30
2024-09-25 23:29:50 +05:30
// TODO: make it possible to apply widgetInnerStyle on load
2024-09-19 19:26:10 +05:30
2024-09-26 11:59:24 +05:30
// FIXME: the drag drop indicator is not going invisible if the drop happens on the child
2024-09-27 16:04:03 +05:30
// FIXME: once the width and height is set to fit-content, it can no longer be resized
2024-10-03 05:28:39 +05:30
// FIXME: if the label, buttons are dropped directly on canvas, the background colors don't apply
2024-09-27 19:22:33 +05:30
const ATTRS _KEYS = [ 'value' , 'label' , 'tool' , 'onChange' , 'options' , 'toolProps' ] // these are attrs keywords, don't use these keywords as keys while defining the attrs property or serializing
2024-08-08 16:21:19 +05:30
/ * *
* Base class to be extended
* /
2024-09-15 12:08:29 +05:30
class Widget extends React . Component {
2024-08-08 16:21:19 +05:30
2024-08-08 22:49:14 +05:30
static widgetType = "widget"
2024-08-08 16:21:19 +05:30
2024-09-24 21:49:26 +05:30
static requirements = [ ] // requirements for the widgets (libraries) eg: tkvideoplayer, tktimepicker
static requiredImports = [ ] // import statements
2024-09-16 22:04:24 +05:30
// static contextType = ActiveWidgetContext
2024-09-15 12:08:29 +05:30
constructor ( props ) {
2024-08-08 16:21:19 +05:30
super ( props )
2024-08-08 22:49:14 +05:30
2024-09-15 12:08:29 +05:30
const { id , widgetName , canvasRef } = props
2024-09-12 19:20:46 +05:30
// console.log("Id: ", id)
2024-08-08 16:21:19 +05:30
// this id has to be unique inside the canvas, it will be set automatically and should never be changed
2024-08-08 22:49:14 +05:30
this . _ _id = id
2024-09-13 22:05:38 +05:30
this . canvas = canvasRef ? . current || null // canvasContainerRef, because some events work properly only if attached to the container
2024-09-09 19:06:03 +05:30
// this._selected = false
2024-08-08 16:21:19 +05:30
this . _disableResize = false
this . _disableSelection = false
2024-09-22 22:24:35 +05:30
this . minSize = { width : 10 , height : 10 } // disable resizing below this number
this . maxSize = { width : 2000 , height : 2000 } // disable resizing above this number
2024-09-10 21:34:05 +05:30
2024-08-08 16:21:19 +05:30
this . cursor = Cursor . POINTER
this . icon = "" // antd icon name representing this widget
2024-09-21 18:37:28 +05:30
this . elementRef = React . createRef ( ) // this is the outer ref for draggable area
this . swappableAreaRef = React . createRef ( ) // helps identify if the users intent is to swap or drop inside the widget
this . innerAreaRef = React . createRef ( ) // this is the inner area where swap is prevented and only drop is accepted
2024-08-08 16:21:19 +05:30
this . functions = {
2024-09-15 12:08:29 +05:30
"load" : { "args1" : "number" , "args2" : "string" }
2024-08-08 16:21:19 +05:30
}
2024-09-23 12:31:01 +05:30
// This indicates if the draggable can be dropped on this widget, set this to null to disable drops
this . droppableTags = { }
2024-08-08 16:21:19 +05:30
2024-08-08 22:49:14 +05:30
this . state = {
2024-09-09 19:06:03 +05:30
zIndex : 0 ,
selected : false ,
2024-09-15 12:08:29 +05:30
widgetName : widgetName || 'widget' , // this will later be converted to variable name
2024-09-13 12:28:32 +05:30
enableRename : false , // will open the widgets editable div for renaming
2024-09-22 22:24:35 +05:30
2024-09-24 21:49:26 +05:30
parentLayout : null , // depending on the parents layout the child will behave
2024-09-21 18:37:28 +05:30
isDragging : false , // tells if the widget is currently being dragged
2024-09-17 18:32:33 +05:30
dragEnabled : true ,
2024-09-19 19:26:10 +05:30
widgetContainer : WidgetContainer . CANVAS , // what is the parent of the widget
2024-09-17 18:32:33 +05:30
showDroppableStyle : { // shows the droppable indicator
2024-09-22 22:24:35 +05:30
allow : false ,
2024-09-17 18:32:33 +05:30
show : false ,
} ,
2024-09-13 19:24:03 +05:30
2024-09-15 12:08:29 +05:30
pos : { x : 0 , y : 0 } ,
size : { width : 100 , height : 100 } ,
2024-09-29 17:51:42 +05:30
fitContent : { width : false , height : false } ,
2024-09-19 19:26:10 +05:30
positionType : PosType . ABSOLUTE ,
2024-09-14 16:03:26 +05:30
2024-09-25 17:27:12 +05:30
widgetOuterStyling : {
// responsible for stuff like position, grid placement etc
} ,
widgetInnerStyling : {
2024-09-14 16:03:26 +05:30
// use for widget's inner styling
2024-09-20 19:17:29 +05:30
backgroundColor : "#fff" ,
display : "flex" ,
flexDirection : "row" ,
gap : 10 ,
flexWrap : "wrap"
2024-09-13 22:05:38 +05:30
} ,
2024-09-15 12:08:29 +05:30
attrs : {
2024-09-13 19:24:03 +05:30
styling : {
backgroundColor : {
2024-09-14 16:03:26 +05:30
label : "Background Color" ,
2024-09-13 19:24:03 +05:30
tool : Tools . COLOR _PICKER , // the tool to display, can be either HTML ELement or a constant string
2024-09-20 15:12:43 +05:30
value : "#fff" ,
2024-09-16 22:04:24 +05:30
onChange : ( value ) => {
2024-09-25 17:27:12 +05:30
this . setWidgetInnerStyle ( "backgroundColor" , value )
2024-09-16 22:04:24 +05:30
this . setAttrValue ( "styling.backgroundColor" , value )
}
2024-09-13 19:24:03 +05:30
} ,
2024-09-22 22:24:35 +05:30
2024-09-15 12:08:29 +05:30
label : "Styling"
2024-09-14 16:03:26 +05:30
} ,
layout : {
label : "Layout" ,
2024-09-18 22:16:34 +05:30
tool : Tools . LAYOUT _MANAGER , // the tool to display, can be either HTML ELement or a constant string
value : {
layout : "flex" ,
2024-09-20 15:12:43 +05:30
direction : "row" ,
2024-09-25 17:27:12 +05:30
// grid: {
// rows: 12,
// cols: 12
// },
2024-09-20 15:12:43 +05:30
gap : 10 ,
2024-09-18 22:16:34 +05:30
} ,
2024-09-19 19:26:10 +05:30
toolProps : {
options : [
{ value : "flex" , label : "Flex" } ,
{ value : "grid" , label : "Grid" } ,
{ value : "place" , label : "Place" } ,
] ,
} ,
2024-09-20 15:12:43 +05:30
onChange : ( value ) => {
// this.setAttrValue("layout", value)
this . setLayout ( value )
}
2024-09-13 19:24:03 +05:30
} ,
2024-09-24 23:27:52 +05:30
// events: {
// event1: {
// tool: Tools.EVENT_HANDLER,
// value: ""
// }
// }
2024-09-13 19:24:03 +05:30
} ,
2024-08-08 22:49:14 +05:30
}
2024-09-09 19:06:03 +05:30
this . getElement = this . getElement . bind ( this )
2024-09-20 15:12:43 +05:30
this . getId = this . getId . bind ( this )
2024-09-15 12:08:29 +05:30
this . getPos = this . getPos . bind ( this )
this . getSize = this . getSize . bind ( this )
2024-09-13 16:03:58 +05:30
this . getWidgetName = this . getWidgetName . bind ( this )
this . getWidgetType = this . getWidgetType . bind ( this )
2024-09-15 12:08:29 +05:30
this . getBoundingRect = this . getBoundingRect . bind ( this )
2024-09-26 11:59:24 +05:30
this . getLayout = this . getLayout . bind ( this )
this . getParentLayout = this . getParentLayout . bind ( this )
2024-09-22 22:24:35 +05:30
2024-09-26 11:59:24 +05:30
this . getAttrValue = this . getAttrValue . bind ( this )
2024-09-15 12:08:29 +05:30
this . getToolbarAttrs = this . getToolbarAttrs . bind ( this )
2024-09-26 11:59:24 +05:30
this . generateCode = this . generateCode . bind ( this )
2024-09-26 18:53:21 +05:30
this . getImports = this . getImports . bind ( this )
this . getRequirements = this . getRequirements . bind ( this )
2024-09-13 12:28:32 +05:30
// this.openRenaming = this.openRenaming.bind(this)
2024-09-09 19:06:03 +05:30
this . isSelected = this . isSelected . bind ( this )
this . setPos = this . setPos . bind ( this )
2024-09-15 12:08:29 +05:30
this . setAttrValue = this . setAttrValue . bind ( this )
this . setWidgetName = this . setWidgetName . bind ( this )
2024-09-25 17:27:12 +05:30
this . setWidgetInnerStyle = this . setWidgetInnerStyle . bind ( this )
this . setWidgetOuterStyle = this . setWidgetOuterStyle . bind ( this )
2024-09-13 22:05:38 +05:30
2024-09-25 17:27:12 +05:30
this . setPosType = this . setPosType . bind ( this )
this . setParentLayout = this . setParentLayout . bind ( this )
2024-09-25 23:29:50 +05:30
this . load = this . load . bind ( this )
this . serialize = this . serialize . bind ( this )
this . serializeAttrsValues = this . serializeAttrsValues . bind ( this )
2024-09-28 11:29:37 +05:30
this . hideDroppableIndicator = this . hideDroppableIndicator . bind ( this )
2024-09-29 17:51:42 +05:30
this . getRenderSize = this . getRenderSize . bind ( this )
this . getInnerRenderStyling = this . getInnerRenderStyling . bind ( this )
2024-09-15 12:08:29 +05:30
}
2024-08-08 16:21:19 +05:30
2024-09-15 12:08:29 +05:30
componentDidMount ( ) {
2024-09-19 19:26:10 +05:30
2024-09-23 12:31:01 +05:30
if ( this . state . attrs . layout ) {
this . setLayout ( this . state . attrs . layout . value )
2024-09-25 23:29:50 +05:30
// console.log("prior layout: ", this.state.attrs.layout.value)
2024-09-23 12:31:01 +05:30
}
if ( this . state . attrs . styling . backgroundColor )
2024-09-25 17:27:12 +05:30
this . setWidgetInnerStyle ( 'backgroundColor' , this . state . attrs . styling ? . backgroundColor . value || "#fff" )
2024-09-22 22:24:35 +05:30
2024-09-20 15:12:43 +05:30
this . load ( this . props . initialData || { } ) // load the initial data
2024-08-08 16:21:19 +05:30
}
2024-09-15 12:08:29 +05:30
componentWillUnmount ( ) {
2024-08-08 16:21:19 +05:30
}
2024-09-15 12:08:29 +05:30
updateState = ( newState , callback ) => {
2024-09-16 22:04:24 +05:30
2024-09-22 15:00:34 +05:30
// FIXME: maximum recursion error when updating size, color etc
2024-09-15 12:08:29 +05:30
this . setState ( newState , ( ) => {
2024-09-16 22:04:24 +05:30
2024-09-15 12:08:29 +05:30
const { onWidgetUpdate } = this . props
if ( onWidgetUpdate ) {
onWidgetUpdate ( this . _ _id )
}
2024-09-16 22:04:24 +05:30
// const { activeWidgetId, updateToolAttrs } = this.context
// if (activeWidgetId === this.__id)
// updateToolAttrs(this.getToolbarAttrs())
2024-09-15 12:08:29 +05:30
if ( callback ) callback ( )
2024-09-16 22:04:24 +05:30
2024-09-15 12:08:29 +05:30
} )
}
_getWidgetMethods = ( ) => {
return {
rename : this . setWidgetName ,
resize : this . setWidgetSize ,
setWidgetAttrs : this . setAttrValue ,
}
}
2024-09-22 22:24:35 +05:30
getToolbarAttrs ( ) {
2024-09-15 12:08:29 +05:30
return ( {
2024-09-22 22:24:35 +05:30
id : this . _ _id ,
widgetName : {
label : "Widget Name" ,
tool : Tools . INPUT , // the tool to display, can be either HTML ELement or a constant string
toolProps : { placeholder : "Widget name" , maxLength : 40 } ,
value : this . state . widgetName ,
onChange : ( value ) => this . setWidgetName ( value )
} ,
size : {
label : "Size" ,
display : "horizontal" ,
width : {
label : "Width" ,
tool : Tools . NUMBER _INPUT , // the tool to display, can be either HTML ELement or a constant string
toolProps : { placeholder : "width" , max : this . maxSize . width , min : this . minSize . width } ,
value : this . state . size . width || 100 ,
onChange : ( value ) => this . setWidgetSize ( value , null )
2024-09-15 12:08:29 +05:30
} ,
2024-09-22 22:24:35 +05:30
height : {
label : "Height" ,
tool : Tools . NUMBER _INPUT ,
toolProps : { placeholder : "height" , max : this . maxSize . height , min : this . minSize . height } ,
value : this . state . size . height || 100 ,
onChange : ( value ) => this . setWidgetSize ( null , value )
2024-09-15 12:08:29 +05:30
} ,
2024-09-25 23:29:50 +05:30
fitWidth : {
label : "Fit width" ,
tool : Tools . CHECK _BUTTON ,
2024-09-29 17:51:42 +05:30
value : this . state . fitContent . width ,
2024-09-25 23:29:50 +05:30
onChange : ( value ) => {
2024-09-29 17:51:42 +05:30
this . updateState ( ( prev ) => ( {
fitContent : { ... prev . fitContent , width : value }
} ) )
2024-09-25 23:29:50 +05:30
}
} ,
fitHeight : {
label : "Fit height" ,
tool : Tools . CHECK _BUTTON ,
2024-09-29 17:51:42 +05:30
value : this . state . fitContent . height ,
2024-09-25 23:29:50 +05:30
onChange : ( value ) => {
2024-09-29 17:51:42 +05:30
this . updateState ( ( prev ) => ( {
fitContent : { ... prev . fitContent , height : value }
} ) )
2024-09-25 23:29:50 +05:30
}
} ,
2024-09-22 22:24:35 +05:30
} ,
... this . state . attrs ,
2024-09-15 12:08:29 +05:30
} )
}
2024-09-09 19:06:03 +05:30
// TODO: add context menu items such as delete, add etc
2024-09-15 12:08:29 +05:30
contextMenu ( ) {
2024-09-09 19:06:03 +05:30
}
2024-09-15 12:08:29 +05:30
getVariableName ( ) {
2024-09-09 19:06:03 +05:30
return toSnakeCase ( this . state . widgetName )
}
2024-09-15 12:08:29 +05:30
getWidgetName ( ) {
2024-09-13 16:03:58 +05:30
return this . state . widgetName
}
2024-09-15 12:08:29 +05:30
getWidgetType ( ) {
2024-09-13 16:03:58 +05:30
return this . constructor . widgetType
}
2024-09-26 18:53:21 +05:30
getRequirements ( ) {
2024-09-24 21:49:26 +05:30
return this . constructor . requirements
}
2024-09-26 18:53:21 +05:30
getImports ( ) {
2024-09-24 21:49:26 +05:30
return this . constructor . requiredImports
}
2024-09-26 11:59:24 +05:30
generateCode ( ) {
2024-09-27 11:42:34 +05:30
throw new NotImplementedError ( "generateCode() must be implemented by the subclass" )
2024-09-24 21:49:26 +05:30
}
2024-09-15 12:08:29 +05:30
getAttributes ( ) {
2024-09-13 19:24:03 +05:30
return this . state . attrs
}
2024-09-22 22:24:35 +05:30
getId ( ) {
2024-09-20 15:12:43 +05:30
return this . _ _id
}
2024-09-15 12:08:29 +05:30
select ( ) {
2024-09-09 19:24:43 +05:30
this . setState ( {
2024-09-09 19:06:03 +05:30
selected : true
2024-09-09 19:24:43 +05:30
} )
2024-09-15 12:08:29 +05:30
2024-08-08 16:21:19 +05:30
}
2024-09-15 12:08:29 +05:30
deSelect ( ) {
2024-09-09 19:24:43 +05:30
this . setState ( {
2024-09-09 19:06:03 +05:30
selected : false
2024-09-09 19:24:43 +05:30
} )
2024-09-09 19:06:03 +05:30
}
2024-09-15 12:08:29 +05:30
isSelected ( ) {
2024-09-09 19:06:03 +05:30
return this . state . selected
}
2024-09-22 22:24:35 +05:30
setPosType ( positionType ) {
2024-09-19 19:26:10 +05:30
2024-09-22 22:24:35 +05:30
if ( ! Object . values ( PosType ) . includes ( positionType ) ) {
2024-09-19 19:26:10 +05:30
throw Error ( ` The Position type can only be among: ${ Object . values ( PosType ) . join ( ", " ) } ` )
}
this . setState ( {
positionType : positionType
} )
}
2024-09-15 12:08:29 +05:30
setPos ( x , y ) {
2025-03-02 10:29:59 +05:30
console . log ( "position set: " , x , y )
2024-09-10 21:34:05 +05:30
2024-09-16 22:04:24 +05:30
this . setState ( {
2024-09-15 12:08:29 +05:30
pos : { x , y }
} )
2024-09-16 22:04:24 +05:30
// this.updateState({
// pos: { x, y }
// })
2024-09-12 22:09:13 +05:30
}
2024-09-09 19:06:03 +05:30
2024-09-15 12:08:29 +05:30
getPos ( ) {
2024-09-14 16:03:26 +05:30
return this . state . pos
2024-08-08 16:21:19 +05:30
}
2024-09-15 12:08:29 +05:30
getProps ( ) {
2024-09-09 19:06:03 +05:30
return this . attrs
2024-08-08 16:21:19 +05:30
}
2024-09-15 12:08:29 +05:30
getBoundingRect ( ) {
2024-09-10 21:34:05 +05:30
return this . elementRef . current ? . getBoundingClientRect ( )
}
2024-09-15 12:08:29 +05:30
getSize ( ) {
return this . state . size
2024-09-10 21:34:05 +05:30
}
2024-09-15 12:08:29 +05:30
getWidgetFunctions ( ) {
2024-08-08 16:21:19 +05:30
return this . functions
}
2024-09-15 12:08:29 +05:30
getId ( ) {
2024-08-08 16:21:19 +05:30
return this . _ _id
}
2024-09-15 12:08:29 +05:30
getElement ( ) {
2024-09-09 19:06:03 +05:30
return this . elementRef . current
}
2024-09-28 11:29:37 +05:30
hideDroppableIndicator ( ) {
console . log ( "hide drop indicator" )
this . setState ( {
showDroppableStyle : {
allow : false ,
show : false
}
} , ( ) => {
console . log ( "hidden the drop indicator" )
} )
}
2024-09-19 19:26:10 +05:30
2024-09-25 17:27:12 +05:30
/ * *
*
* @ param { string } path - eg : styling . backgroundColor
* @ returns
* /
removeAttr = ( path ) => {
const newAttrs = removeKeyFromObject ( path , this . state . attrs )
this . setState ( {
attrs : newAttrs
} )
return newAttrs
}
2024-09-14 16:03:26 +05:30
/ * *
* Given the key as a path , sets the value for the widget attribute
* @ param { string } path - path to the key , eg : styling . backgroundColor
* @ param { any } value
* /
2024-09-15 12:08:29 +05:30
setAttrValue ( path , value ) {
2024-09-22 15:00:34 +05:30
const keys = path . split ( '.' )
const lastKey = keys . pop ( )
2024-10-01 11:22:45 +05:30
2024-09-22 15:00:34 +05:30
// Traverse the state and update the nested value immutably
let newAttrs = { ... this . state . attrs }
let nestedObject = newAttrs
2024-10-01 11:22:45 +05:30
2024-09-22 15:00:34 +05:30
keys . forEach ( key => {
nestedObject [ key ] = { ... nestedObject [ key ] } // Ensure immutability
nestedObject = nestedObject [ key ]
2024-09-14 16:03:26 +05:30
} )
2024-10-01 11:22:45 +05:30
2024-09-22 15:00:34 +05:30
nestedObject [ lastKey ] . value = value
2024-09-22 22:24:35 +05:30
this . updateState ( { attrs : newAttrs } )
}
/ * *
* Given the key as a path , retrieves the value for the widget attribute
* @ param { string } path - path to the key , eg : styling . backgroundColor
* @ returns { any } - the value at the given path
* /
getAttrValue ( path ) {
const keys = path . split ( '.' )
// Traverse the state and get the nested value
let nestedObject = this . state . attrs
for ( const key of keys ) {
if ( nestedObject [ key ] !== undefined ) {
nestedObject = nestedObject [ key ]
} else {
return undefined // Return undefined if the key doesn't exist
}
}
return nestedObject ? . value // Return the value (assuming it has a 'value' field)
2024-09-14 16:03:26 +05:30
}
2024-09-19 19:26:10 +05:30
/ * *
* 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 : } }
* /
2024-09-25 23:29:50 +05:30
serializeAttrsValues ( ) {
2024-09-19 19:26:10 +05:30
const serializeValues = ( obj , currentPath = "" ) => {
const result = { }
2024-09-22 22:24:35 +05:30
2024-09-19 19:26:10 +05:30
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 ;
2024-09-22 22:24:35 +05:30
2024-09-19 19:26:10 +05:30
// 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 ) )
}
}
2024-09-22 22:24:35 +05:30
2024-09-19 19:26:10 +05:30
return result
}
return serializeValues ( this . state . attrs )
}
2024-09-15 12:08:29 +05:30
setZIndex ( zIndex ) {
2024-09-13 12:28:32 +05:30
this . setState ( {
zIndex : zIndex
} )
}
2024-09-15 12:08:29 +05:30
setWidgetName ( name ) {
this . updateState ( {
widgetName : name . length > 0 ? name : this . state . widgetName
} )
2024-09-14 16:03:26 +05:30
}
2024-09-24 21:49:26 +05:30
/ * *
2024-09-25 17:27:12 +05:30
* inform the child about the parent layout changes
2024-09-26 11:59:24 +05:30
* @ param { Layouts } parentLayout
2024-09-24 21:49:26 +05:30
* /
2024-09-26 11:59:24 +05:30
setParentLayout ( parentLayout ) {
2024-09-27 16:04:03 +05:30
if ( ! parentLayout ) {
// if parent layout is null (i,e the widget is on the canvas)
return { }
}
2024-09-26 11:59:24 +05:30
const { layout , direction , gap } = parentLayout
2024-09-24 21:49:26 +05:30
let updates = {
2024-09-26 11:59:24 +05:30
parentLayout : parentLayout ,
2024-09-24 21:49:26 +05:30
}
if ( layout === Layouts . FLEX || layout === Layouts . GRID ) {
updates = {
... updates ,
positionType : PosType . NONE
}
} else if ( layout === Layouts . PLACE ) {
updates = {
... updates ,
positionType : PosType . ABSOLUTE
}
}
this . setState ( updates )
}
2024-09-26 11:59:24 +05:30
getParentLayout ( ) {
2024-09-25 17:27:12 +05:30
return this . state . parentLayout
}
2024-09-26 11:59:24 +05:30
getLayout ( ) {
2024-09-24 21:49:26 +05:30
return this . state ? . attrs ? . layout ? . value || Layouts . FLEX
}
2024-09-22 22:24:35 +05:30
setLayout ( value ) {
2024-09-30 15:30:46 +05:30
const { layout , direction , grid = { rows : 1 , cols : 1 } , gap = 10 , align } = value
2024-09-20 15:12:43 +05:30
2024-09-25 23:29:50 +05:30
// console.log("layout value: ", value)
// FIXME: In grid layout the layout doesn't adapt to the size of the child if resized
2024-09-25 17:27:12 +05:30
let widgetStyle = {
... this . state . widgetInnerStyling ,
2024-09-24 21:49:26 +05:30
display : layout !== Layouts . PLACE ? layout : "block" ,
2024-09-20 15:12:43 +05:30
flexDirection : direction ,
gap : ` ${ gap } px ` ,
2024-09-25 17:27:12 +05:30
flexWrap : "wrap" ,
gridTemplateColumns : "repeat(auto-fill, minmax(100px, 1fr))" ,
gridTemplateRows : "repeat(auto-fill, minmax(100px, 1fr))" ,
2024-09-25 23:29:50 +05:30
// gridAutoRows: 'minmax(100px, auto)', // Rows with minimum height of 100px, and grow to fit content
// gridAutoCols: 'minmax(100px, auto)', // Cols with minimum height of 100px, and grow to fit content
2024-09-20 15:12:43 +05:30
}
2024-09-30 15:30:46 +05:30
if ( align === "start" ) {
widgetStyle [ "alignItems" ] = "flex-start"
} else if ( align === "center" ) {
widgetStyle [ "alignItems" ] = "center"
} else if ( align === "end" ) {
widgetStyle [ "alignItems" ] = "flex-end"
} else {
widgetStyle [ "alignItems" ] = "unset"
}
2024-09-20 15:12:43 +05:30
this . updateState ( {
2024-09-25 17:27:12 +05:30
widgetInnerStyling : widgetStyle
2024-09-20 15:12:43 +05:30
} )
2024-09-24 21:49:26 +05:30
this . setAttrValue ( "layout" , value )
2024-09-26 23:16:43 +05:30
this . props . onLayoutUpdate ( { parentId : this . _ _id , parentLayout : value } ) // inform children about the layout update
2024-09-24 21:49:26 +05:30
2024-09-20 15:12:43 +05:30
}
2024-09-25 17:27:12 +05:30
getWidgetInnerStyle = ( key ) => {
return this . state . widgetInnerStyling [ key ]
}
getWidgetOuterStyle = ( key ) => {
return this . state . widgetOuterStyling [ key ]
}
/ * *
* sets outer styling like grid placement etc , don ' t use this for background color , foreground color etc
* @ param { string } key - The string in react Style format
* @ param { string } value - Value of the style
* /
setWidgetOuterStyle ( key , value ) {
const widgetStyle = {
... this . state . widgetOuterStyling ,
[ key ] : value
}
this . setState ( {
widgetOuterStyling : widgetStyle
} )
}
2024-09-15 12:08:29 +05:30
/ * *
*
* @ param { string } key - The string in react Style format
* @ param { string } value - Value of the style
* /
2024-09-25 17:27:12 +05:30
setWidgetInnerStyle ( key , value ) {
2024-09-24 21:49:26 +05:30
2024-09-15 12:08:29 +05:30
const widgetStyle = {
2024-09-25 17:27:12 +05:30
... this . state . widgetInnerStyling ,
2024-09-15 12:08:29 +05:30
[ key ] : value
}
this . setState ( {
2024-09-25 17:27:12 +05:30
widgetInnerStyling : widgetStyle
2024-09-15 12:08:29 +05:30
} )
}
/ * *
*
* @ param { number | null } width
* @ param { number | null } height
* /
2024-09-16 22:04:24 +05:30
setWidgetSize ( width , height ) {
2024-09-15 12:08:29 +05:30
2024-09-30 09:14:16 +05:30
const fitWidth = this . state . fitContent ? . width
const fitHeight = this . state . fitContent ? . height
2024-09-29 17:51:42 +05:30
if ( fitWidth && fitHeight ) {
message . warning ( "both width and height are set to fit-content, unset it to start resizing" )
return
}
2024-09-15 12:08:29 +05:30
const newSize = {
width : Math . max ( this . minSize . width , Math . min ( width || this . state . size . width , this . maxSize . width ) ) ,
height : Math . max ( this . minSize . height , Math . min ( height || this . state . size . height , this . maxSize . height ) ) ,
}
this . updateState ( {
size : newSize
} )
2024-09-14 16:03:26 +05:30
}
2024-09-22 22:24:35 +05:30
setResize ( pos , size ) {
2024-09-18 11:39:07 +05:30
// useful when resizing the widget relative to the canvas, sets all pos, and size
2024-09-15 12:08:29 +05:30
this . updateState ( {
2024-09-18 11:39:07 +05:30
size : size ,
pos : pos
2024-09-15 12:08:29 +05:30
} )
2024-09-10 21:34:05 +05:30
}
2024-09-15 12:08:29 +05:30
openRenaming ( ) {
2024-09-13 12:28:32 +05:30
this . setState ( {
selected : true ,
enableRename : true
} )
}
2024-09-15 12:08:29 +05:30
closeRenaming ( ) {
2024-09-13 12:28:32 +05:30
this . setState ( {
enableRename : false
} )
}
2024-09-20 22:07:22 +05:30
enableDrag = ( ) => {
this . setState ( {
dragEnabled : true
} )
}
disableDrag = ( ) => {
this . setState ( {
dragEnabled : false
} )
}
2024-09-15 15:31:04 +05:30
2024-09-19 19:26:10 +05:30
/ * *
*
* serialize data for saving
* /
2024-09-25 23:29:50 +05:30
serialize ( ) {
2024-09-19 19:26:10 +05:30
// 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 ,
2024-09-25 17:27:12 +05:30
widgetInnerStyling : this . state . widgetInnerStyling ,
widgetOuterStyling : this . state . widgetOuterStyling ,
2024-09-24 21:49:26 +05:30
parentLayout : this . state . parentLayout ,
2024-09-19 19:26:10 +05:30
positionType : this . state . positionType ,
attrs : this . serializeAttrsValues ( ) // makes sure that functions are not serialized
} )
}
/ * *
* loads the data
* @ param { object } data
2024-09-25 23:29:50 +05:30
* @ param { ( ) => void | undefined } callback - optional callback that will be called after load
2024-09-19 19:26:10 +05:30
* /
2024-09-25 23:29:50 +05:30
load ( data , callback ) {
2024-09-19 19:26:10 +05:30
2024-09-20 19:17:29 +05:30
if ( Object . keys ( data ) . length === 0 ) return // no data to load
2024-09-23 18:25:40 +05:30
data = { ... data } // create a shallow copy
2024-09-19 19:26:10 +05:30
2024-09-24 21:49:26 +05:30
const { attrs , parentLayout , ... restData } = data
2024-09-19 19:26:10 +05:30
2024-09-23 18:25:40 +05:30
2024-09-24 21:49:26 +05:30
let layoutUpdates = {
2024-09-26 11:59:24 +05:30
parentLayout : parentLayout . layout || null
2024-09-24 21:49:26 +05:30
}
2024-09-24 23:27:52 +05:30
2024-09-26 11:59:24 +05:30
if ( parentLayout ? . layout === Layouts . FLEX || parentLayout ? . layout === Layouts . GRID ) {
2024-09-24 21:49:26 +05:30
layoutUpdates = {
... layoutUpdates ,
positionType : PosType . NONE
}
2024-09-26 11:59:24 +05:30
} else if ( parentLayout ? . layout === Layouts . PLACE ) {
2024-09-24 21:49:26 +05:30
layoutUpdates = {
... layoutUpdates ,
positionType : PosType . ABSOLUTE
}
}
const newData = {
... restData ,
2024-09-25 17:27:12 +05:30
... layoutUpdates
2024-09-24 21:49:26 +05:30
}
this . setState ( newData , ( ) => {
2024-09-25 23:29:50 +05:30
// Updates attrs
2024-09-23 18:25:40 +05:30
let newAttrs = { ... this . state . attrs }
// Iterate over each path in the updates object
Object . entries ( attrs ) . forEach ( ( [ path , value ] ) => {
const keys = path . split ( '.' )
const lastKey = keys . pop ( )
// Traverse the nested object within attrs
let nestedObject = newAttrs
keys . forEach ( key => {
nestedObject [ key ] = { ... nestedObject [ key ] } // Ensure immutability for each nested level
nestedObject = nestedObject [ key ]
} )
// Set the value at the last key
2024-09-25 19:20:05 +05:30
if ( nestedObject [ lastKey ] ) // TODO: remove this check, else won't be able to catch buggy data
2024-09-25 17:27:12 +05:30
nestedObject [ lastKey ] . value = value
2024-09-23 18:25:40 +05:30
} )
2024-09-25 23:29:50 +05:30
if ( newAttrs ? . styling ? . backgroundColor ) {
// some widgets don't have background color
this . setWidgetInnerStyle ( "backgroundColor" , newAttrs . styling . backgroundColor )
}
this . updateState ( { attrs : newAttrs } , callback )
2024-09-23 18:25:40 +05:30
} )
2024-09-19 19:26:10 +05:30
}
2024-09-21 18:37:28 +05:30
handleDragStart = ( e , callback ) => {
e . stopPropagation ( )
callback ( this . elementRef ? . current || null )
2024-09-24 21:49:26 +05:30
// this.props.onWidgetDragStart(this.elementRef?.current)
2024-09-21 18:37:28 +05:30
// Create custom drag image with full opacity, this will ensure the image isn't taken from part of the canvas
const dragImage = this . elementRef ? . current . cloneNode ( true )
dragImage . style . opacity = '1' // Ensure full opacity
dragImage . style . position = 'absolute'
dragImage . style . top = '-9999px' // Move it out of view
document . body . appendChild ( dragImage )
const rect = this . elementRef ? . current . getBoundingClientRect ( )
2024-09-21 22:47:47 +05:30
// snap to mouse pos
// const offsetX = e.clientX - rect.left
// const offsetY = e.clientY - rect.top
// snap to middle
const offsetX = rect . width / 2
const offsetY = rect . height / 2
2024-09-21 18:37:28 +05:30
// Set the custom drag image with correct offset to avoid snapping to the top-left corner
e . dataTransfer . setDragImage ( dragImage , offsetX , offsetY )
2024-09-21 22:47:47 +05:30
2024-09-21 18:37:28 +05:30
// Remove the custom drag image after some time to avoid leaving it in the DOM
setTimeout ( ( ) => {
document . body . removeChild ( dragImage )
} , 0 )
2024-09-21 22:47:47 +05:30
2024-09-22 12:39:03 +05:30
// NOTE: this line will prevent problem's such as self-drop or dropping inside its own children
setTimeout ( this . disablePointerEvents , 1 )
2024-09-21 22:47:47 +05:30
2024-09-22 22:24:35 +05:30
this . setState ( { isDragging : true } )
2024-09-21 22:47:47 +05:30
2024-09-21 18:37:28 +05:30
}
handleDragEnter = ( e , draggedElement , setOverElement ) => {
2024-09-24 14:33:02 +05:30
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
}
2024-09-21 18:37:28 +05:30
const dragEleType = draggedElement . getAttribute ( "data-draggable-type" )
2024-09-22 12:39:03 +05:30
// console.log("Drag entering...", dragEleType, draggedElement, this.droppableTags)
2024-09-21 18:37:28 +05:30
// FIXME: the outer widget shouldn't be swallowed by inner widget
2024-09-22 22:24:35 +05:30
if ( draggedElement === this . elementRef . current ) {
2024-09-21 18:37:28 +05:30
// prevent drop on itself, since the widget is invisible when dragging, if dropped on itself, it may consume itself
2024-09-22 22:24:35 +05:30
return
2024-09-21 18:37:28 +05:30
}
2024-09-25 23:29:50 +05:30
setOverElement ( this . elementRef . current ) // provide context to the provider
2024-09-21 18:37:28 +05:30
let showDrop = {
2024-09-22 22:24:35 +05:30
allow : true ,
2024-09-21 18:37:28 +05:30
show : true
}
2024-09-23 12:31:01 +05:30
const allowDrop = ( this . droppableTags && this . droppableTags !== null && ( Object . keys ( this . droppableTags ) . length === 0 ||
2024-09-22 22:24:35 +05:30
( this . droppableTags . include ? . length > 0 && this . droppableTags . include ? . includes ( dragEleType ) ) ||
( this . droppableTags . exclude ? . length > 0 && ! this . droppableTags . exclude ? . includes ( dragEleType ) )
) )
2024-09-22 19:23:06 +05:30
if ( allowDrop ) {
2024-09-21 18:37:28 +05:30
showDrop = {
allow : true ,
show : true
}
} else {
showDrop = {
allow : false ,
show : true
}
}
this . setState ( {
showDroppableStyle : showDrop
} )
2024-09-22 22:24:35 +05:30
2024-09-21 18:37:28 +05:30
}
handleDragOver = ( e , draggedElement ) => {
2024-09-24 14:33:02 +05:30
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
}
2024-09-22 22:24:35 +05:30
if ( draggedElement === this . elementRef . current ) {
2024-09-21 18:37:28 +05:30
// prevent drop on itself, since the widget is invisible when dragging, if dropped on itself, it may consume itself
2024-09-22 22:24:35 +05:30
return
2024-09-21 18:37:28 +05:30
}
// console.log("Drag over: ", e.dataTransfer.getData("text/plain"), e.dataTransfer)
const dragEleType = draggedElement . getAttribute ( "data-draggable-type" )
2024-09-23 12:31:01 +05:30
const allowDrop = ( this . droppableTags && this . droppableTags !== null && ( Object . keys ( this . droppableTags ) . length === 0 ||
2024-09-22 22:24:35 +05:30
( this . droppableTags . include ? . length > 0 && this . droppableTags . include ? . includes ( dragEleType ) ) ||
( this . droppableTags . exclude ? . length > 0 && ! this . droppableTags . exclude ? . includes ( dragEleType ) )
) )
2024-09-22 19:23:06 +05:30
if ( allowDrop ) {
2024-09-21 18:37:28 +05:30
e . preventDefault ( ) // NOTE: this is necessary to allow drop to take place
}
}
2024-09-22 22:24:35 +05:30
handleDropEvent = ( e , draggedElement , widgetClass = null ) => {
2024-09-24 14:33:02 +05:30
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
}
2024-09-21 18:37:28 +05:30
e . preventDefault ( )
e . stopPropagation ( )
2024-09-24 14:33:02 +05:30
2024-09-22 12:39:03 +05:30
// FIXME: sometimes the elements showDroppableStyle is not gone, when dropping on the same widget
2024-09-21 18:37:28 +05:30
this . setState ( {
showDroppableStyle : {
2024-09-22 22:24:35 +05:30
allow : false ,
show : false
}
2024-09-21 18:37:28 +05:30
} )
2024-09-22 22:24:35 +05:30
if ( draggedElement === this . elementRef . 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 ) {
// if the parent is dropped accidentally into the child don't allow drop
// console.log("Dropped into a descendant element, ignoring drop")
return // Exit early to prevent the drop
}
currentElement = currentElement . parentElement // Traverse up to check ancestors
}
2024-09-21 18:37:28 +05:30
const container = draggedElement . getAttribute ( "data-container" )
const thisContainer = this . elementRef . current . getAttribute ( "data-container" )
// console.log("Dropped as swappable: ", e.target, this.swappableAreaRef.current.contains(e.target))
// If swaparea is true, then it swaps instead of adding it as a child, also make sure that the parent widget(this widget) is on the widget and not on the canvas
2024-09-22 22:24:35 +05:30
const swapArea = ( this . swappableAreaRef . current . contains ( e . target ) && ! this . innerAreaRef . current . contains ( e . target ) && thisContainer === WidgetContainer . WIDGET )
2024-09-21 18:37:28 +05:30
2024-09-23 18:25:40 +05:30
const dragEleType = draggedElement . getAttribute ( "data-draggable-type" )
const allowDrop = ( this . droppableTags && this . droppableTags !== null && ( Object . keys ( this . droppableTags ) . length === 0 ||
( this . droppableTags . include ? . length > 0 && this . droppableTags . include ? . includes ( dragEleType ) ) ||
( this . droppableTags . exclude ? . length > 0 && ! this . droppableTags . exclude ? . includes ( dragEleType ) )
) )
if ( ! allowDrop && ! swapArea ) {
// only if both swap and drop is not allowed return, if swap is allowed continue
return
}
2024-09-21 18:37:28 +05:30
// TODO: check if the drop is allowed
2024-09-22 22:24:35 +05:30
if ( [ WidgetContainer . CANVAS , WidgetContainer . WIDGET ] . includes ( container ) ) {
2024-09-21 18:37:28 +05:30
// console.log("Dropped on meee: ", swapArea, this.swappableAreaRef.current.contains(e.target), thisContainer)
2024-09-22 22:24:35 +05:30
this . props . onAddChildWidget ( {
2024-09-24 21:49:26 +05:30
event : e ,
2024-09-22 22:24:35 +05:30
parentWidgetId : this . _ _id ,
dragElementID : draggedElement . getAttribute ( "data-widget-id" ) ,
swap : swapArea || false
} )
} else if ( container === WidgetContainer . SIDEBAR ) {
2024-09-21 18:37:28 +05:30
// console.log("Dropped on Sidebar: ", this.__id)
2024-09-22 22:24:35 +05:30
this . props . onCreateWidgetRequest ( widgetClass , ( { id , widgetRef } ) => {
2024-09-24 21:49:26 +05:30
this . props . onAddChildWidget ( {
event : e ,
parentWidgetId : this . _ _id ,
dragElementID : id
} ) // if dragged from the sidebar create the widget first
2024-09-22 15:00:34 +05:30
} )
2024-09-21 18:37:28 +05:30
}
}
2024-09-25 23:29:50 +05:30
handleDragLeave = ( e , draggedElement , overElement ) => {
2024-09-21 18:37:28 +05:30
2024-09-25 23:29:50 +05:30
e . preventDefault ( )
e . stopPropagation ( )
2024-09-21 18:37:28 +05:30
2024-09-25 23:29:50 +05:30
const rect = this . getBoundingRect ( )
const { clientX , clientY } = e
const isInBoundingBox = ( clientX >= rect . left && clientX <= rect . right &&
clientY >= rect . top && clientY <= rect . bottom )
// if (!e.currentTarget.contains(draggedElement)) {
if ( ! isInBoundingBox ) {
2024-09-26 11:59:24 +05:30
// FIXME: if the mouse pointer is over this widget's child, then droppable style should be invisible
2024-09-25 23:29:50 +05:30
// only if the dragging element is not within the rect of this element remove the dragging rect
2024-09-21 18:37:28 +05:30
this . setState ( {
2024-09-22 22:24:35 +05:30
showDroppableStyle : {
allow : false ,
show : false
}
2024-09-25 23:29:50 +05:30
} , ( ) => {
// console.log("Drag left", this.state.showDroppableStyle)
2024-09-22 22:24:35 +05:30
} )
2024-09-21 18:37:28 +05:30
}
}
handleDragEnd = ( callback ) => {
callback ( )
2024-09-22 22:24:35 +05:30
this . setState ( { isDragging : false } )
2024-09-22 12:39:03 +05:30
this . enablePointerEvents ( )
2024-09-24 21:49:26 +05:30
// this.props.onWidgetDragEnd(this.elementRef?.current)
2024-09-22 12:39:03 +05:30
}
disablePointerEvents = ( ) => {
2024-09-22 22:24:35 +05:30
2024-09-22 12:39:03 +05:30
if ( this . elementRef . current )
this . elementRef . current . style . pointerEvents = "none"
2024-09-21 18:37:28 +05:30
}
2024-09-22 12:39:03 +05:30
enablePointerEvents = ( ) => {
if ( this . elementRef . current )
this . elementRef . current . style . pointerEvents = "auto"
}
2024-09-21 18:37:28 +05:30
2024-09-29 17:51:42 +05:30
getInnerRenderStyling ( ) {
const { width , height , minWidth , minHeight } = this . getRenderSize ( )
const styling = {
... this . state . widgetInnerStyling ,
width ,
height ,
minWidth ,
minHeight
}
return styling
}
getRenderSize ( ) {
let width = isNumeric ( this . state . size . width ) ? ` ${ this . state . size . width } px ` : this . state . size . width
let height = isNumeric ( this . state . size . height ) ? ` ${ this . state . size . height } px ` : this . state . size . height
let fitWidth = this . state . fitContent . width
let fitHeight = this . state . fitContent . height
if ( fitWidth ) {
width = "max-content"
}
if ( fitHeight ) {
height = "max-content"
}
// if fit width is enabled then the minsize is the resizable size
let minWidth = fitWidth ? this . state . size . width : this . minSize . width
let minHeight = fitHeight ? this . state . size . height : this . minSize . height
return { width , height , minWidth , minHeight }
}
2024-09-15 15:31:04 +05:30
2024-10-02 06:31:01 +05:30
/ * *
* Note : you must implement this method in subclass , if you want children make sure to pass
* { this . props . children } , to modify the style add this . state . widgetInnerStyling
* /
renderContent ( ) {
// throw new NotImplementedError("render method has to be implemented")
return (
< div className = "tw-w-full tw-h-full tw-p-2 tw-content-start tw-rounded-md tw-overflow-hidden" style = { this . state . widgetInnerStyling } >
{ this . props . children }
< / d i v >
)
}
2024-08-08 16:21:19 +05:30
/ * *
* This is an internal methods don ' t override
* @ returns { HTMLElement }
* /
2024-09-23 12:31:01 +05:30
render ( ) {
2024-09-15 12:08:29 +05:30
2024-09-29 17:51:42 +05:30
const { width , height , minWidth , minHeight } = this . getRenderSize ( )
// NOTE: first check tkinter behaviour with the width and height
2024-09-25 23:29:50 +05:30
2024-09-14 16:03:26 +05:30
let outerStyle = {
2024-09-25 17:27:12 +05:30
... this . state . widgetOuterStyling ,
2024-08-08 16:21:19 +05:30
cursor : this . cursor ,
2024-09-13 12:28:32 +05:30
zIndex : this . state . zIndex ,
2024-09-19 19:26:10 +05:30
position : this . state . positionType , // don't change this if it has to be movable on the canvas
2024-09-15 12:08:29 +05:30
top : ` ${ this . state . pos . y } px ` ,
left : ` ${ this . state . pos . x } px ` ,
2024-09-25 23:29:50 +05:30
width : width ,
height : height ,
2024-09-29 17:51:42 +05:30
minWidth : minWidth ,
minHeight : minHeight ,
2024-09-21 22:47:47 +05:30
opacity : this . state . isDragging ? 0.3 : 1 ,
2024-08-08 16:21:19 +05:30
}
2024-09-24 21:49:26 +05:30
// const boundingRect = this.getBoundingRect
2024-09-22 22:24:35 +05:30
// FIXME: if the parent container has tw-overflow-none, then the resizable indicator are also hidden
2024-08-08 16:21:19 +05:30
return (
2024-09-21 18:37:28 +05:30
2025-03-01 19:20:46 +05:30
// <DragContext.Consumer>
2025-03-02 10:29:59 +05:30
< DragContext . Consumer >
2024-09-21 18:37:28 +05:30
{
2024-09-28 11:29:37 +05:30
( { draggedElement , widgetClass , onDragStart , onDragEnd , overElement , setOverElement } ) => {
2024-09-22 22:24:35 +05:30
2024-09-28 11:29:37 +05:30
2024-09-29 17:51:42 +05:30
return (
< div data - widget - id = { this . _ _id }
2024-09-28 11:29:37 +05:30
ref = { this . elementRef }
className = "tw-shadow-xl tw-w-fit tw-h-fit"
style = { outerStyle }
data - draggable - type = { this . getWidgetType ( ) } // helps with droppable
data - container = { this . state . widgetContainer } // indicates how the canvas should handle dragging, one is sidebar other is canvas
2024-09-22 22:24:35 +05:30
2024-09-28 11:29:37 +05:30
data - drag - start - within // this attribute indicates that the drag is occurring from within the project and not a outside file drop
2024-09-24 14:33:02 +05:30
2024-09-28 11:29:37 +05:30
draggable = { this . state . dragEnabled }
2024-09-22 22:24:35 +05:30
2024-09-28 11:29:37 +05:30
onDragOver = { ( e ) => this . handleDragOver ( e , draggedElement ) }
onDrop = { ( e ) => { this . handleDropEvent ( e , draggedElement , widgetClass ) ; onDragEnd ( ) } }
2024-09-22 22:24:35 +05:30
2024-09-28 11:29:37 +05:30
onDragEnter = { ( e ) => this . handleDragEnter ( e , draggedElement , setOverElement ) }
onDragLeave = { ( e ) => this . handleDragLeave ( e , draggedElement , overElement ) }
2024-09-22 22:24:35 +05:30
2024-09-28 11:29:37 +05:30
onDragStart = { ( e ) => this . handleDragStart ( e , onDragStart ) }
onDragEnd = { ( e ) => this . handleDragEnd ( onDragEnd ) }
2024-09-22 22:24:35 +05:30
>
2024-09-28 11:29:37 +05:30
{ /* FIXME: Swappable when the parent layout is flex/grid and gap is more, this trick won't work, add bg color to check */ }
{ /* FIXME: Swappable, when the parent layout is gap is 0, it doesn't work well */ }
2024-09-29 19:17:11 +05:30
< div className = "tw-relative tw-w-full tw-h-full tw-top-0 tw-left-0"
2024-09-29 17:51:42 +05:30
>
2024-09-28 11:29:37 +05:30
< div className = { ` tw-absolute tw-top-[-5px] tw-left-[-5px]
tw - border - 1 tw - opacity - 0 tw - border - solid tw - border - black
tw - w - full tw - h - full tw - bg - red - 400
tw - scale - [ 1.1 ] tw - opacity - 1 tw - z - [ - 1 ] ` }
2024-09-22 22:24:35 +05:30
style = { {
width : "calc(100% + 10px)" ,
height : "calc(100% + 10px)" ,
} }
2024-09-28 11:29:37 +05:30
ref = { this . swappableAreaRef }
// swapable area
2024-09-22 22:24:35 +05:30
>
2024-09-28 11:29:37 +05:30
{ /* helps with swappable: if the mouse is in this area while hovering/dropping, then swap */ }
2024-09-22 22:24:35 +05:30
< / d i v >
2024-09-29 17:51:42 +05:30
2024-09-29 19:17:11 +05:30
< div className = "tw-relative tw-top-0 tw-left-0 tw-w-full tw-h-full" ref = { this . innerAreaRef }
2024-09-29 17:51:42 +05:30
>
2024-09-28 11:29:37 +05:30
{ this . renderContent ( ) }
< / d i v >
{
// show drop style on drag hover
draggedElement && this . state . showDroppableStyle . show &&
< div className = { ` ${ this . state . showDroppableStyle . allow ? "tw-border-blue-600" : "tw-border-red-600" }
tw - absolute tw - top - [ - 5 px ] tw - left - [ - 5 px ] 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)" ,
2024-09-21 18:37:28 +05:30
} }
2024-09-28 11:29:37 +05:30
>
< / d i v >
}
{ /* FIXME: the resize handles get clipped in parent container */ }
< div className = { ` tw-absolute tw-z-[-1] tw-bg-transparent tw-top-[-10px] tw-left-[-10px] tw-opacity-100
tw - w - full tw - h - full
$ { this . state . selected ? 'tw-border-2 tw-border-solid tw-border-blue-500' : 'tw-hidden' } ` }
style = { {
width : "calc(100% + 20px)" ,
height : "calc(100% + 20px)" ,
zIndex : - 1 ,
} }
>
2024-09-21 18:37:28 +05:30
2024-09-28 11:29:37 +05:30
< div className = { ` "tw-relative tw-w-full tw-h-full" ` } > { /* ${this.state.isDragging ? "tw-pointer-events-none" : "tw-pointer-events-auto"} */ }
< EditableDiv value = { this . state . widgetName } onChange = { this . setWidgetName }
maxLength = { 40 }
openEdit = { this . state . enableRename }
className = " tw - text - sm tw - w - fit tw - max - w - [ 160 px ] tw - text - clip tw - min - w - [ 150 px ]
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 ) => {
e . stopPropagation ( )
e . preventDefault ( )
this . props . onWidgetResizing ( "nw" )
this . setState ( { dragEnabled : false } )
} }
onMouseUp = { ( ) => 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 ) => {
e . stopPropagation ( )
e . preventDefault ( )
this . props . onWidgetResizing ( "ne" )
this . setState ( { dragEnabled : false } )
} }
onMouseUp = { ( ) => 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 ) => {
e . stopPropagation ( )
e . preventDefault ( )
this . props . onWidgetResizing ( "sw" )
this . setState ( { dragEnabled : false } )
} }
onMouseUp = { ( ) => 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 ) => {
e . stopPropagation ( )
e . preventDefault ( )
this . props . onWidgetResizing ( "se" )
this . setState ( { dragEnabled : false } )
} }
onMouseUp = { ( ) => this . setState ( { dragEnabled : true } ) }
/ >
< / d i v >
2024-09-21 18:37:28 +05:30
2024-09-28 11:29:37 +05:30
< / d i v >
2024-09-19 19:26:10 +05:30
2024-09-16 22:04:24 +05:30
2024-09-28 11:29:37 +05:30
< / d i v >
2024-09-21 18:37:28 +05:30
< / d i v >
2024-09-28 11:29:37 +05:30
)
}
2024-09-21 18:37:28 +05:30
}
2024-08-08 16:21:19 +05:30
2025-03-02 10:29:59 +05:30
< / D r a g C o n t e x t . C o n s u m e r >
2024-08-08 16:21:19 +05:30
)
}
2024-09-15 12:08:29 +05:30
}
2024-08-08 16:21:19 +05:30
export default Widget