2024-08-08 16:21:19 +05:30
|
|
|
import React from "react"
|
|
|
|
|
import { NotImplementedError } from "../../utils/errors"
|
|
|
|
|
|
|
|
|
|
import Tools from "../constants/tools"
|
|
|
|
|
import Layouts from "../constants/layouts"
|
|
|
|
|
import Cursor from "../constants/cursor"
|
2024-09-09 19:06:03 +05:30
|
|
|
import { toSnakeCase } from "../utils/utils"
|
|
|
|
|
import EditableDiv from "../../components/editableDiv"
|
2024-08-08 16:21:19 +05:30
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Base class to be extended
|
|
|
|
|
*/
|
|
|
|
|
class Widget extends React.Component{
|
|
|
|
|
|
2024-08-08 22:49:14 +05:30
|
|
|
static widgetType = "widget"
|
2024-08-08 16:21:19 +05:30
|
|
|
|
2024-08-08 22:49:14 +05:30
|
|
|
constructor(props){
|
2024-08-08 16:21:19 +05:30
|
|
|
super(props)
|
2024-08-08 22:49:14 +05:30
|
|
|
|
2024-09-09 19:06:03 +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-08-08 16:21:19 +05:30
|
|
|
this._zIndex = 0
|
|
|
|
|
|
2024-09-09 19:06:03 +05:30
|
|
|
this.canvas = canvasRef?.current || null
|
|
|
|
|
|
|
|
|
|
// this._selected = false
|
2024-08-08 16:21:19 +05:30
|
|
|
this._disableResize = false
|
|
|
|
|
this._disableSelection = false
|
|
|
|
|
|
2024-09-12 19:20:46 +05:30
|
|
|
this._parent = "" // id of the parent widget, default empty string
|
|
|
|
|
this._children = [] // id's of all the child widgets
|
|
|
|
|
|
2024-09-10 21:34:05 +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-08-08 16:21:19 +05:30
|
|
|
this.cursor = Cursor.POINTER
|
|
|
|
|
|
|
|
|
|
this.icon = "" // antd icon name representing this widget
|
|
|
|
|
|
|
|
|
|
this.elementRef = React.createRef()
|
|
|
|
|
|
2024-09-09 19:06:03 +05:30
|
|
|
this.attrs = {
|
2024-08-08 16:21:19 +05:30
|
|
|
styling: {
|
|
|
|
|
backgroundColor: {
|
|
|
|
|
tool: Tools.COLOR_PICKER, // the tool to display, can be either HTML ELement or a constant string
|
|
|
|
|
value: ""
|
|
|
|
|
},
|
|
|
|
|
foregroundColor: {
|
|
|
|
|
tool: Tools.COLOR_PICKER,
|
|
|
|
|
value: ""
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
layout: "show", // enables layout use "hide" to hide layout dropdown, takes the layout from this.layout
|
|
|
|
|
events: {
|
|
|
|
|
event1: {
|
|
|
|
|
tool: Tools.EVENT_HANDLER,
|
|
|
|
|
value: ""
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.functions = {
|
|
|
|
|
"load": {"args1": "number", "args2": "string"}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.layout = Layouts.PACK
|
|
|
|
|
this.boundingRect = {
|
|
|
|
|
x: 0,
|
|
|
|
|
y: 0,
|
|
|
|
|
height: 100,
|
|
|
|
|
width: 100
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-08 22:49:14 +05:30
|
|
|
this.state = {
|
|
|
|
|
attrs: { // attributes
|
|
|
|
|
// replace this with this.props
|
|
|
|
|
},
|
2024-09-09 19:06:03 +05:30
|
|
|
zIndex: 0,
|
|
|
|
|
pos: {x: 0, y: 0},
|
2024-09-10 21:34:05 +05:30
|
|
|
size: { width: 100, height: 100 },
|
2024-09-09 19:06:03 +05:30
|
|
|
selected: false,
|
2024-09-10 21:34:05 +05:30
|
|
|
widgetName: widgetName || 'unnamed widget', // this will later be converted to variable name
|
|
|
|
|
resizing: false,
|
|
|
|
|
resizeCorner: ""
|
2024-08-08 22:49:14 +05:30
|
|
|
}
|
|
|
|
|
|
2024-09-09 19:06:03 +05:30
|
|
|
this.mousePress = this.mousePress.bind(this)
|
|
|
|
|
this.getElement = this.getElement.bind(this)
|
2024-09-10 21:34:05 +05:30
|
|
|
this.getBoundingRect = this.getBoundingRect.bind(this)
|
2024-09-09 19:06:03 +05:30
|
|
|
|
|
|
|
|
this.isSelected = this.isSelected.bind(this)
|
|
|
|
|
|
|
|
|
|
this.getPos = this.getPos.bind(this)
|
|
|
|
|
this.setPos = this.setPos.bind(this)
|
|
|
|
|
|
2024-09-10 21:34:05 +05:30
|
|
|
this.startResizing = this.startResizing.bind(this)
|
|
|
|
|
this.handleResize = this.handleResize.bind(this)
|
|
|
|
|
this.stopResizing = this.stopResizing.bind(this)
|
|
|
|
|
|
2024-09-09 19:06:03 +05:30
|
|
|
}
|
2024-08-08 16:21:19 +05:30
|
|
|
|
|
|
|
|
componentDidMount(){
|
2024-08-08 22:49:14 +05:30
|
|
|
console.log("mounted: ")
|
|
|
|
|
this.elementRef.current?.addEventListener("click", this.mousePress)
|
2024-09-10 21:34:05 +05:30
|
|
|
|
|
|
|
|
this.canvas.addEventListener("mousemove", this.handleResize);
|
|
|
|
|
this.canvas.addEventListener("mouseup", this.stopResizing)
|
2024-08-08 16:21:19 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
componentWillUnmount(){
|
2024-08-08 22:49:14 +05:30
|
|
|
this.elementRef.current?.removeEventListener("click", this.mousePress)
|
2024-09-10 21:34:05 +05:30
|
|
|
|
|
|
|
|
this.canvas.addEventListener("mousemove", this.handleResize);
|
|
|
|
|
this.canvas.addEventListener("mouseup", this.stopResizing)
|
2024-08-08 16:21:19 +05:30
|
|
|
}
|
|
|
|
|
|
2024-09-09 19:06:03 +05:30
|
|
|
// TODO: add context menu items such as delete, add etc
|
|
|
|
|
contextMenu(){
|
2024-09-12 22:09:13 +05:30
|
|
|
|
2024-09-09 19:06:03 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getVariableName(){
|
|
|
|
|
return toSnakeCase(this.state.widgetName)
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-12 19:20:46 +05:30
|
|
|
/**
|
|
|
|
|
* removes the element/widget
|
|
|
|
|
*/
|
|
|
|
|
remove(){
|
|
|
|
|
this.elementRef.current.remove()
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-09 19:06:03 +05:30
|
|
|
mousePress(event){
|
|
|
|
|
// event.preventDefault()
|
2024-08-08 16:21:19 +05:30
|
|
|
if (!this._disableSelection){
|
|
|
|
|
|
2024-09-09 19:24:43 +05:30
|
|
|
// const widgetSelected = new CustomEvent("selection:created", {
|
|
|
|
|
// detail: {
|
|
|
|
|
// event,
|
|
|
|
|
// id: this.__id,
|
|
|
|
|
// element: this
|
|
|
|
|
// },
|
|
|
|
|
// // bubbles: true // Allow the event to bubble up the DOM tree
|
|
|
|
|
// })
|
|
|
|
|
// this.canvas.dispatchEvent(widgetSelected)
|
2024-08-08 16:21:19 +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
|
|
|
})
|
|
|
|
|
console.log("selected")
|
2024-08-08 16:21:19 +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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
isSelected(){
|
|
|
|
|
return this.state.selected
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setPos(x, y){
|
2024-09-10 21:34:05 +05:30
|
|
|
|
|
|
|
|
if (this.state.resizing){
|
|
|
|
|
// don't change position when resizing the widget
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-09 19:06:03 +05:30
|
|
|
this.setState({
|
|
|
|
|
pos: {x: x, y: y}
|
|
|
|
|
})
|
2024-09-12 19:20:46 +05:30
|
|
|
|
2024-09-12 22:09:13 +05:30
|
|
|
// console.log("POs: ", x, y)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setParent(parentId){
|
|
|
|
|
this._parent = parentId
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addChild(childId){
|
|
|
|
|
this._children.push(childId)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
removeChild(childId){
|
|
|
|
|
this._children = this._children.filter(function(item) {
|
|
|
|
|
return item !== childId
|
|
|
|
|
})
|
2024-09-09 19:06:03 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getPos(){
|
|
|
|
|
return this.state.pos
|
2024-08-08 16:21:19 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getProps(){
|
2024-09-09 19:06:03 +05:30
|
|
|
return this.attrs
|
2024-08-08 16:21:19 +05:30
|
|
|
}
|
|
|
|
|
|
2024-09-10 21:34:05 +05:30
|
|
|
getBoundingRect(){
|
|
|
|
|
return this.elementRef.current?.getBoundingClientRect()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getSize(){
|
|
|
|
|
const boundingRect = this.getBoundingRect()
|
|
|
|
|
|
|
|
|
|
return {width: boundingRect.width, height: boundingRect.height}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getScaleAwareDimensions() {
|
|
|
|
|
// Get the bounding rectangle
|
|
|
|
|
const rect = this.elementRef.current.getBoundingClientRect()
|
|
|
|
|
|
|
|
|
|
// Get the computed style of the element
|
|
|
|
|
const style = window.getComputedStyle(this.elementRef.current)
|
|
|
|
|
|
|
|
|
|
// Get the transform matrix
|
|
|
|
|
const transform = style.transform
|
|
|
|
|
|
|
|
|
|
// Extract scale factors from the matrix
|
|
|
|
|
let scaleX = 1
|
|
|
|
|
let scaleY = 1
|
|
|
|
|
|
|
|
|
|
if (transform && transform !== 'none') {
|
|
|
|
|
// For 2D transforms (a, c, b, d)
|
|
|
|
|
const matrix = transform.match(/^matrix\(([^,]+),[^,]+,([^,]+),[^,]+,[^,]+,[^,]+\)$/);
|
|
|
|
|
|
|
|
|
|
if (matrix) {
|
|
|
|
|
scaleX = parseFloat(matrix[1])
|
|
|
|
|
scaleY = parseFloat(matrix[2])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Return scaled width and height
|
|
|
|
|
return {
|
|
|
|
|
width: rect.width / scaleX,
|
|
|
|
|
height: rect.height / scaleY
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2024-08-08 16:21:19 +05:30
|
|
|
getWidgetFunctions(){
|
|
|
|
|
return this.functions
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getId(){
|
|
|
|
|
return this.__id
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-09 19:06:03 +05:30
|
|
|
getElement(){
|
|
|
|
|
return this.elementRef.current
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-10 21:34:05 +05:30
|
|
|
startResizing(corner, event) {
|
|
|
|
|
event.stopPropagation()
|
|
|
|
|
this.setState({ resizing: true, resizeCorner: corner })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
handleResize(event) {
|
|
|
|
|
if (!this.state.resizing) return
|
|
|
|
|
|
|
|
|
|
const { resizeCorner, size, pos } = this.state
|
|
|
|
|
const deltaX = event.movementX
|
|
|
|
|
const deltaY = event.movementY
|
|
|
|
|
|
|
|
|
|
let newSize = { ...size }
|
|
|
|
|
let newPos = { ...pos }
|
|
|
|
|
|
|
|
|
|
const {width: minWidth, height: minHeight} = this.minSize
|
|
|
|
|
const {width: maxWidth, height: maxHeight} = this.maxSize
|
|
|
|
|
console.log("resizing: ", minHeight, minHeight)
|
|
|
|
|
|
|
|
|
|
switch (resizeCorner) {
|
|
|
|
|
case "nw":
|
|
|
|
|
newSize.width = Math.max(minWidth, Math.min(maxWidth, newSize.width - deltaX))
|
|
|
|
|
newSize.height = Math.max(minHeight, Math.min(maxHeight, newSize.height - deltaY))
|
|
|
|
|
newPos.x += (newSize.width !== size.width) ? deltaX : 0
|
|
|
|
|
newPos.y += (newSize.height !== size.height) ? deltaY : 0
|
|
|
|
|
break
|
|
|
|
|
case "ne":
|
|
|
|
|
newSize.width = Math.max(minWidth, Math.min(maxWidth, newSize.width + deltaX))
|
|
|
|
|
newSize.height = Math.max(minHeight, Math.min(maxHeight, newSize.height - deltaY))
|
|
|
|
|
newPos.y += (newSize.height !== size.height) ? deltaY : 0
|
|
|
|
|
break
|
|
|
|
|
case "sw":
|
|
|
|
|
newSize.width = Math.max(minWidth, Math.min(maxWidth, newSize.width - deltaX))
|
|
|
|
|
newSize.height = Math.max(minHeight, Math.min(maxHeight, newSize.height + deltaY))
|
|
|
|
|
newPos.x += (newSize.width !== size.width) ? deltaX : 0
|
|
|
|
|
break
|
|
|
|
|
case "se":
|
|
|
|
|
newSize.width = Math.max(minWidth, Math.min(maxWidth, newSize.width + deltaX))
|
|
|
|
|
newSize.height = Math.max(minHeight, Math.min(maxHeight, newSize.height + deltaY))
|
|
|
|
|
break
|
|
|
|
|
default:
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.setState({ size: newSize, pos: newPos })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stopResizing() {
|
|
|
|
|
if (this.state.resizing) {
|
|
|
|
|
this.setState({ resizing: false })
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-08 16:21:19 +05:30
|
|
|
renderContent(){
|
|
|
|
|
// throw new NotImplementedError("render method has to be implemented")
|
|
|
|
|
return (
|
|
|
|
|
<div className="tw-w-full tw-h-full tw-bg-red-400">
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* This is an internal methods don't override
|
|
|
|
|
* @returns {HTMLElement}
|
|
|
|
|
*/
|
|
|
|
|
render(){
|
|
|
|
|
|
|
|
|
|
let style = {
|
|
|
|
|
cursor: this.cursor,
|
2024-09-09 19:06:03 +05:30
|
|
|
top: `${this.state.pos.y}px`,
|
|
|
|
|
left: `${this.state.pos.x}px`,
|
2024-09-10 21:34:05 +05:30
|
|
|
width: `${this.state.size.width}px`,
|
|
|
|
|
height: `${this.state.size.height}px`,
|
2024-09-12 19:20:46 +05:30
|
|
|
position: "absolute" // don't change this
|
2024-08-08 16:21:19 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let selectionStyle = {
|
|
|
|
|
x: "-5px",
|
|
|
|
|
y: "-5px",
|
|
|
|
|
width: this.boundingRect.width + 5,
|
|
|
|
|
height: this.boundingRect.height + 5
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-09 19:06:03 +05:30
|
|
|
const onWidgetNameChange = (value) => {
|
|
|
|
|
|
|
|
|
|
this.setState((prev) => ({
|
|
|
|
|
...prev,
|
|
|
|
|
widgetName: value.length > 0 ? value : prev.widgetName
|
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-08 16:21:19 +05:30
|
|
|
return (
|
|
|
|
|
|
2024-09-12 19:20:46 +05:30
|
|
|
<div data-id={this.__id} ref={this.elementRef} className="tw-absolute tw-w-fit tw-h-fit"
|
2024-09-09 19:06:03 +05:30
|
|
|
style={style}
|
2024-08-08 16:21:19 +05:30
|
|
|
>
|
|
|
|
|
|
2024-08-08 22:49:14 +05:30
|
|
|
{this.renderContent()}
|
2024-09-09 19:06:03 +05:30
|
|
|
<div className={`tw-absolute tw-bg-transparent tw-scale-[1.1] tw-opacity-100
|
|
|
|
|
tw-w-full tw-h-full tw-top-0
|
2024-09-10 21:34:05 +05:30
|
|
|
${this.state.selected ? 'tw-border-2 tw-border-solid tw-border-blue-500' : 'tw-hidden'}`}>
|
2024-08-08 16:21:19 +05:30
|
|
|
|
2024-09-09 19:06:03 +05:30
|
|
|
<div className="tw-relative tw-w-full tw-h-full">
|
2024-09-10 21:34:05 +05:30
|
|
|
<EditableDiv value={this.state.widgetName} onChange={onWidgetNameChange}
|
2024-09-09 19:06:03 +05:30
|
|
|
maxLength={40}
|
|
|
|
|
className="tw-text-sm tw-w-fit tw-max-w-[160px] tw-text-clip tw-min-w-[150px]
|
2024-09-10 21:34:05 +05:30
|
|
|
tw-overflow-hidden tw-absolute tw--top-6 tw-h-6"
|
2024-09-09 19:06:03 +05:30
|
|
|
/>
|
2024-09-10 21:34:05 +05:30
|
|
|
|
|
|
|
|
<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.startResizing("nw", e)}
|
|
|
|
|
/>
|
|
|
|
|
<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.startResizing("ne", e)}
|
|
|
|
|
/>
|
|
|
|
|
<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.startResizing("sw", e)}
|
|
|
|
|
/>
|
|
|
|
|
<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.startResizing("se", e)}
|
|
|
|
|
/>
|
|
|
|
|
|
2024-08-08 16:21:19 +05:30
|
|
|
</div>
|
|
|
|
|
|
2024-09-09 19:06:03 +05:30
|
|
|
|
|
|
|
|
|
2024-08-08 16:21:19 +05:30
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export default Widget
|