Files
PyUIBuilder/src/canvas/widgets/base.js

732 lines
24 KiB
JavaScript
Raw Normal View History

2024-08-08 16:21:19 +05:30
import React from "react"
import { NotImplementedError } from "../../utils/errors"
import Tools from "../constants/tools"
2024-09-19 19:26:10 +05:30
import { Layouts, PosType} from "../constants/layouts"
2024-08-08 16:21:19 +05:30
import Cursor from "../constants/cursor"
import { toSnakeCase } from "../utils/utils"
import EditableDiv from "../../components/editableDiv"
2024-09-16 22:04:24 +05:30
import DraggableWrapper from "../../components/draggable/draggable"
import DroppableWrapper from "../../components/draggable/droppable"
import { ActiveWidgetContext } from "../activeWidgetContext"
import { DragWidgetProvider } from "./draggableWidgetContext"
import WidgetDraggable from "./widgetDragDrop"
2024-09-19 19:26:10 +05:30
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
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-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
// 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
// this._selected = false
2024-08-08 16:21:19 +05:30
this._disableResize = false
this._disableSelection = false
2024-09-15 12:08:29 +05:30
this.minSize = { width: 50, height: 50 } // disable resizing below this number
this.maxSize = { width: 500, height: 500 } // 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-15 12:08:29 +05:30
this.elementRef = React.createRef()
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-18 22:16:34 +05:30
this.layout = Layouts.FLEX
2024-08-08 16:21:19 +05:30
this.boundingRect = {
x: 0,
y: 0,
height: 100,
width: 100
2024-09-15 12:08:29 +05:30
}
2024-08-08 16:21:19 +05:30
2024-08-08 22:49:14 +05:30
this.state = {
zIndex: 0,
selected: false,
2024-09-15 12:08:29 +05:30
widgetName: widgetName || 'widget', // this will later be converted to variable name
enableRename: false, // will open the widgets editable div for renaming
dragEnabled: true,
2024-09-19 19:26:10 +05:30
widgetContainer: WidgetContainer.CANVAS, // what is the parent of the widget
showDroppableStyle: { // shows the droppable indicator
allow: false,
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-19 19:26:10 +05:30
positionType: PosType.ABSOLUTE,
2024-09-14 16:03:26 +05:30
2024-09-13 22:05:38 +05:30
widgetStyling: {
2024-09-14 16:03:26 +05:30
// use for widget's inner styling
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
value: "#fff",
2024-09-16 22:04:24 +05:30
onChange: (value) => {
this.setWidgetStyling("backgroundColor", value)
this.setAttrValue("styling.backgroundColor", value)
}
2024-09-13 19:24:03 +05:30
},
foregroundColor: {
2024-09-14 16:03:26 +05:30
label: "Foreground Color",
2024-09-13 19:24:03 +05:30
tool: Tools.COLOR_PICKER,
2024-09-19 19:26:10 +05:30
value: "#000",
2024-09-14 16:03:26 +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",
direction: "row",
2024-09-18 22:16:34 +05:30
grid: {
rows: 1,
cols: 1
},
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" },
],
},
onChange: (value) => {
// this.setAttrValue("layout", value)
this.setLayout(value)
}
2024-09-13 19:24:03 +05:30
},
events: {
event1: {
tool: Tools.EVENT_HANDLER,
value: ""
}
}
},
2024-08-08 22:49:14 +05:30
}
this.mousePress = this.mousePress.bind(this)
this.getElement = this.getElement.bind(this)
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)
this.getToolbarAttrs = this.getToolbarAttrs.bind(this)
// this.openRenaming = this.openRenaming.bind(this)
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-13 22:05:38 +05:30
this.setWidgetStyling = this.setWidgetStyling.bind(this)
2024-09-19 19:26:10 +05:30
this.setPosType = this.setPosType.bind(this)
2024-09-13 22:05:38 +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
componentDidMount() {
2024-08-08 22:49:14 +05:30
this.elementRef.current?.addEventListener("click", this.mousePress)
2024-09-19 19:26:10 +05:30
// FIXME: initial layout is not set properly
console.log("prior layout: ", this.state.attrs.layout.value)
this.setLayout(this.state.attrs.layout.value)
this.setWidgetStyling('backgroundColor', this.state.attrs.styling?.backgroundColor.value || "#fff")
this.load(this.props.initialData || {}) // load the initial data
2024-09-19 19:26:10 +05:30
2024-08-08 16:21:19 +05:30
}
2024-09-15 12:08:29 +05:30
componentWillUnmount() {
2024-08-08 22:49:14 +05:30
this.elementRef.current?.removeEventListener("click", this.mousePress)
2024-08-08 16:21:19 +05:30
}
2024-09-16 22:04:24 +05:30
componentDidUpdate(prevProps, prevState) {
// if (prevState !== this.state) {
// // State has changed
// console.log('State has been updated')
// } else {
// // State has not changed
// console.log('State has not changed')
// }
}
2024-09-15 12:08:29 +05:30
updateState = (newState, callback) => {
2024-09-16 22:04:24 +05:30
// FIXME: maximum recursion error when updating size
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,
}
}
getToolbarAttrs(){
return ({
id: this.__id,
2024-09-15 12:08:29 +05:30
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: {
2024-09-16 22:04:24 +05:30
label: "Size",
2024-09-15 12:08:29 +05:30
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)
},
height: {
label: "Height",
tool: Tools.NUMBER_INPUT,
2024-09-15 15:31:04 +05:30
toolProps: {placeholder: "height", max: this.maxSize.height, min: this.minSize.height},
2024-09-15 12:08:29 +05:30
value: this.state.size.height || 100,
onChange: (value) => this.setWidgetSize(null, value)
},
},
...this.state.attrs,
})
}
// TODO: add context menu items such as delete, add etc
2024-09-15 12:08:29 +05:30
contextMenu() {
}
2024-09-15 12:08:29 +05:30
getVariableName() {
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-15 12:08:29 +05:30
getAttributes() {
2024-09-13 19:24:03 +05:30
return this.state.attrs
}
getId(){
return this.__id
}
2024-09-15 12:08:29 +05:30
mousePress(event) {
// event.preventDefault()
2024-09-15 12:08:29 +05:30
if (!this._disableSelection) {
2024-08-08 16:21:19 +05:30
}
}
2024-09-15 12:08:29 +05:30
select() {
this.setState({
selected: true
})
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() {
this.setState({
selected: false
})
}
2024-09-15 12:08:29 +05:30
isSelected() {
return this.state.selected
}
2024-09-19 19:26:10 +05:30
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
})
}
2024-09-15 12:08:29 +05:30
setPos(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-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() {
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() {
return this.elementRef.current
}
2024-09-19 19:26:10 +05:30
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 {}
}
}
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-14 16:03:26 +05:30
this.setState((prevState) => {
// Split the path to access the nested property (e.g., "styling.backgroundColor")
const keys = path.split('.')
const lastKey = keys.pop()
2024-09-15 12:08:29 +05:30
2024-09-14 16:03:26 +05:30
// Traverse the state and update the nested value immutably
let newAttrs = { ...prevState.attrs }
let nestedObject = newAttrs
2024-09-15 12:08:29 +05:30
2024-09-14 16:03:26 +05:30
keys.forEach(key => {
nestedObject[key] = { ...nestedObject[key] } // Ensure immutability
nestedObject = nestedObject[key]
})
nestedObject[lastKey].value = value
2024-09-15 12:08:29 +05:30
2024-09-14 16:03:26 +05:30
return { attrs: newAttrs }
})
}
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: }}
*/
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)
}
2024-09-15 12:08:29 +05:30
setZIndex(zIndex) {
this.setState({
zIndex: zIndex
})
}
2024-09-15 12:08:29 +05:30
setWidgetName(name) {
2024-09-14 16:03:26 +05:30
2024-09-15 12:08:29 +05:30
this.updateState({
widgetName: name.length > 0 ? name : this.state.widgetName
})
2024-09-14 16:03:26 +05:30
}
setLayout(value){
const {layout, direction, grid={rows: 1, cols: 1}, gap=10} = value
const widgetStyle = {
...this.state.widgetStyling,
display: layout,
flexDirection: direction,
gap: `${gap}px`,
flexWrap: "wrap"
// TODO: add grid rows and cols
}
this.setAttrValue("layout", value)
this.updateState({
widgetStyling: 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-16 22:04:24 +05:30
setWidgetStyling(key, value) {
2024-09-15 12:08:29 +05:30
const widgetStyle = {
...this.state.widgetStyling,
[key]: value
}
this.setState({
widgetStyling: widgetStyle
})
}
/**
*
* @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
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
}
setResize(pos, size){
// useful when resizing the widget relative to the canvas, sets all pos, and size
2024-09-15 12:08:29 +05:30
this.updateState({
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() {
this.setState({
selected: true,
enableRename: true
})
}
2024-09-15 12:08:29 +05:30
closeRenaming() {
this.setState({
enableRename: false
})
}
handleDrop = (event, dragElement) => {
console.log("dragging event: ", event, dragElement)
const container = dragElement.getAttribute("data-container")
2024-09-18 22:16:34 +05:30
// TODO: check if the drop is allowed
if (container === "canvas"){
2024-09-18 22:16:34 +05:30
this.props.onAddChildWidget(this.__id, dragElement.getAttribute("data-widget-id"))
}else if (container === "sidebar"){
this.props.onAddChildWidget(this.__id, null, true) // if dragged from the sidebar create the widget first
}
2024-09-15 15:31:04 +05:30
}
2024-09-19 19:26:10 +05:30
/**
*
* 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) => {
if (Object.keys(data).length === 0) return // no data to load
2024-09-19 19:26:10 +05:30
for (let [key, value] of Object.entries(data.attrs|{}))
this.setAttrValue(key, value)
delete data.attrs
2024-09-19 19:26:10 +05:30
/**
* 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
2024-09-15 12:08:29 +05:30
renderContent() {
2024-08-08 16:21:19 +05:30
// 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" style={this.state.widgetStyling}>
2024-09-19 19:26:10 +05:30
{this.props.children}
2024-08-08 16:21:19 +05:30
</div>
)
}
2024-09-15 15:31:04 +05:30
2024-08-08 16:21:19 +05:30
/**
* This is an internal methods don't override
* @returns {HTMLElement}
*/
2024-09-15 12:08:29 +05:30
render() {
2024-09-14 16:03:26 +05:30
let outerStyle = {
2024-08-08 16:21:19 +05:30
cursor: this.cursor,
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-14 16:03:26 +05:30
width: `${this.state.size.width}px`,
height: `${this.state.size.height}px`,
2024-08-08 16:21:19 +05:30
}
// console.log("selected: ", this.state.selected)
2024-08-08 16:21:19 +05:30
return (
<WidgetDraggable widgetRef={this.elementRef}
enableDrag={this.state.dragEnabled}
onDrop={this.handleDrop}
onDragEnter={({dragElement, showDrop}) => {
this.setState({
showDroppableStyle: showDrop
})
}
}
onDragLeave={ () => {
this.setState({
showDroppableStyle: {
allow: false,
show: false
}
})
}
}
>
<div data-widget-id={this.__id}
ref={this.elementRef}
className="tw-absolute tw-shadow-xl tw-w-fit tw-h-fit"
2024-09-16 22:04:24 +05:30
style={outerStyle}
data-draggable-type={this.getWidgetType()} // helps with droppable
2024-09-19 19:26:10 +05:30
data-container={this.state.widgetContainer} // indicates how the canvas should handle dragging, one is sidebar other is canvas
2024-09-16 22:04:24 +05:30
>
2024-09-19 19:26:10 +05:30
<div className="tw-relative tw-w-full tw-h-full tw-top-0 tw-left-0">
{this.renderContent()}
2024-09-19 19:26:10 +05:30
{
// 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)",
}
}
>
2024-09-19 19:26:10 +05:30
</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) => {
e.stopPropagation()
e.preventDefault()
2024-09-19 19:26:10 +05:30
this.props.onWidgetResizing("nw")
this.setState({dragEnabled: false})
}}
onMouseUp={() => this.setState({dragEnabled: true})}
2024-09-19 19:26:10 +05:30
/>
<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()
2024-09-19 19:26:10 +05:30
this.props.onWidgetResizing("ne")
this.setState({dragEnabled: false})
}}
onMouseUp={() => this.setState({dragEnabled: true})}
2024-09-19 19:26:10 +05:30
/>
<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()
2024-09-19 19:26:10 +05:30
this.props.onWidgetResizing("sw")
this.setState({dragEnabled: false})
}}
onMouseUp={() => this.setState({dragEnabled: true})}
2024-09-19 19:26:10 +05:30
/>
<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()
2024-09-19 19:26:10 +05:30
this.props.onWidgetResizing("se")
this.setState({dragEnabled: false})
}}
onMouseUp={() => this.setState({dragEnabled: true})}
2024-09-19 19:26:10 +05:30
/>
</div>
2024-09-16 22:04:24 +05:30
</div>
2024-08-08 16:21:19 +05:30
2024-09-16 22:04:24 +05:30
</div>
2024-08-08 16:21:19 +05:30
</div>
</WidgetDraggable>
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