fixed place layout manager. Place/absolute position now working inside the widgets
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,6 +5,8 @@
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
python-tests/
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
|
||||
@@ -23,10 +23,11 @@ import { ReactComponent as DotsBackground } from "../assets/background/dots.svg"
|
||||
|
||||
import DroppableWrapper from "../components/draggable/droppable"
|
||||
|
||||
import { PosType } from "./constants/layouts"
|
||||
import { Layouts, PosType } from "./constants/layouts"
|
||||
import WidgetContainer from "./constants/containers"
|
||||
import { isSubClassOfWidget } from "../utils/widget"
|
||||
import { ButtonModal } from "../components/modals"
|
||||
import ResizeWidgetContainer from "./resizeContainer"
|
||||
|
||||
// const DotsBackground = require("../assets/background/dots.svg")
|
||||
|
||||
@@ -65,6 +66,7 @@ class Canvas extends React.Component {
|
||||
this.widgetRefs = {} // stores the actual refs to the widgets inside the canvas {id: ref, id2, ref2...}
|
||||
|
||||
this.state = {
|
||||
isWidgetDragging: false,
|
||||
widgetResizing: "", // set this to "nw", "sw" etc based on the side when widgets resizing handles are selected
|
||||
widgets: [], // stores the mapping to widgetRefs, stores id and WidgetType, later used for rendering [{id: , widgetType: WidgetClass, children: [], parent: "", initialData: {}}]
|
||||
zoom: 1,
|
||||
@@ -181,8 +183,8 @@ class Canvas extends React.Component {
|
||||
|
||||
let innerWidget = null
|
||||
for (let [key, ref] of Object.entries(this.widgetRefs)) {
|
||||
|
||||
if (ref.current === target){
|
||||
|
||||
if (ref.current === target) {
|
||||
innerWidget = ref.current
|
||||
break
|
||||
}
|
||||
@@ -235,11 +237,13 @@ class Canvas extends React.Component {
|
||||
this.state.selectedWidget?.setZIndex(0)
|
||||
selectedWidget.setZIndex(1000)
|
||||
selectedWidget.select()
|
||||
// console.log("selected widget", selectedWidget, this.state.selectedWidget)
|
||||
this.setState({
|
||||
selectedWidget: selectedWidget,
|
||||
toolbarAttrs: selectedWidget.getToolbarAttrs()
|
||||
})
|
||||
|
||||
|
||||
// if (!this.state.selectedWidget || (selectedWidget.getId() !== this.state.selectedWidget?.getId())) {
|
||||
// this.state.selectedWidget?.deSelect() // deselect the previous widget before adding the new one
|
||||
// this.state.selectedWidget?.setZIndex(0)
|
||||
@@ -301,16 +305,16 @@ class Canvas extends React.Component {
|
||||
{
|
||||
key: "snap",
|
||||
label: (<div onClick={() => {
|
||||
domtoimage.toPng(selectedWidget.getElement(), {
|
||||
width: selectedWidget.getElement().offsetWidth * 2, // Multiply element's width by 2
|
||||
height: selectedWidget.getElement().offsetHeight * 2 // Multiply element's height by 2
|
||||
}).then((dataUrl) => {
|
||||
saveAs(dataUrl, 'widget.png')
|
||||
}).catch((error) => {
|
||||
console.error('Error capturing widget as PNg:', error)
|
||||
})
|
||||
}}>
|
||||
<FileImageOutlined /> Save as Image</div>),
|
||||
domtoimage.toPng(selectedWidget.getElement(), {
|
||||
width: selectedWidget.getElement().offsetWidth * 2, // Multiply element's width by 2
|
||||
height: selectedWidget.getElement().offsetHeight * 2 // Multiply element's height by 2
|
||||
}).then((dataUrl) => {
|
||||
saveAs(dataUrl, 'widget.png')
|
||||
}).catch((error) => {
|
||||
console.error('Error capturing widget as PNg:', error)
|
||||
})
|
||||
}}>
|
||||
<FileImageOutlined /> Save as Image</div>),
|
||||
icons: <FileImageOutlined />,
|
||||
}
|
||||
]
|
||||
@@ -638,6 +642,114 @@ class Canvas extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles drop event to canvas from the sidebar and on canvas widget movement
|
||||
* @param {DragEvent} e
|
||||
*/
|
||||
handleDropEvent = (e, draggedElement, widgetClass = null) => {
|
||||
|
||||
e.preventDefault()
|
||||
|
||||
this.setState({ isWidgetDragging: false })
|
||||
|
||||
if (!draggedElement || !draggedElement.getAttribute("data-drag-start-within")) {
|
||||
// if the drag is starting from outside (eg: file drop) or if drag doesn't exist
|
||||
return
|
||||
}
|
||||
|
||||
const container = draggedElement.getAttribute("data-container")
|
||||
const canvasRect = this.canvasRef.current.getBoundingClientRect()
|
||||
|
||||
const draggedElementRect = draggedElement.getBoundingClientRect()
|
||||
const elementWidth = draggedElementRect.width
|
||||
const elementHeight = draggedElementRect.height
|
||||
|
||||
const { clientX, clientY } = e
|
||||
|
||||
let finalPosition = {
|
||||
x: (clientX - canvasRect.left) / this.state.zoom,
|
||||
y: (clientY - canvasRect.top) / this.state.zoom,
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (container === WidgetContainer.SIDEBAR) {
|
||||
|
||||
if (!widgetClass) {
|
||||
throw new Error("WidgetClass has to be passed for widgets dropped from sidebar")
|
||||
}
|
||||
|
||||
// if the widget is being dropped from the sidebar, use the info to create the widget first
|
||||
this.createWidget(widgetClass, ({ id, widgetRef }) => {
|
||||
widgetRef.current.setPos(finalPosition.x, finalPosition.y)
|
||||
})
|
||||
|
||||
} else if ([WidgetContainer.CANVAS, WidgetContainer.WIDGET].includes(container)) {
|
||||
|
||||
// snaps to center
|
||||
finalPosition = {
|
||||
x: (clientX - canvasRect.left) / this.state.zoom - (elementWidth / 2) / this.state.zoom,
|
||||
y: (clientY - canvasRect.top) / this.state.zoom - (elementHeight / 2) / this.state.zoom,
|
||||
}
|
||||
|
||||
let widgetId = draggedElement.getAttribute("data-widget-id")
|
||||
|
||||
const widgetObj = this.getWidgetById(widgetId)
|
||||
// console.log("WidgetObj: ", widgetObj)
|
||||
if (container === WidgetContainer.CANVAS) {
|
||||
|
||||
widgetObj.current.setPos(finalPosition.x, finalPosition.y)
|
||||
|
||||
} else if (container === WidgetContainer.WIDGET) {
|
||||
|
||||
// if the widget was inside another widget move it outside
|
||||
let childWidgetObj = this.findWidgetFromListById(widgetObj.current.getId())
|
||||
let parentWidgetObj = this.findWidgetFromListById(childWidgetObj.parent)
|
||||
|
||||
const childData = widgetObj.current.serialize() // save the data and pass it the updated child object
|
||||
|
||||
// remove child from current position
|
||||
|
||||
const updatedChildWidget = {
|
||||
...childWidgetObj,
|
||||
parent: "",
|
||||
initialData: {
|
||||
...childData,
|
||||
pos: { x: finalPosition.x, y: finalPosition.y },
|
||||
positionType: PosType.ABSOLUTE, // makes sure that after dropping the position is set to absolute value
|
||||
zIndex: 0,
|
||||
widgetContainer: WidgetContainer.CANVAS
|
||||
}
|
||||
}
|
||||
|
||||
let updatedWidgets = this.removeWidgetFromCurrentList(widgetObj.current.getId())
|
||||
|
||||
|
||||
// Create a new copy of the parent widget with the child added
|
||||
const updatedParentWidget = {
|
||||
...parentWidgetObj,
|
||||
// children: parentWidgetObj.children.filter(child => child.id !== childWidgetObj.id)
|
||||
}
|
||||
|
||||
|
||||
updatedWidgets = updatedWidgets.map(widget => {
|
||||
if (widget.id === parentWidgetObj.id) {
|
||||
return updatedParentWidget // Update the parent widget with the child removed
|
||||
} else {
|
||||
return widget // Leave other widgets unchanged
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
this.setState({
|
||||
widgets: [...updatedWidgets, updatedChildWidget]
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the child into the children attribute inside the this.widgets list of objects
|
||||
* // widgets data structure { id, widgetType: widgetComponentType, children: [], parent: "" }
|
||||
@@ -645,8 +757,9 @@ class Canvas extends React.Component {
|
||||
* @param {object} dragElement
|
||||
* @param {boolean} create - if create is set to true the widget will be created before adding to the child tree
|
||||
*/
|
||||
handleAddWidgetChild = ({ parentWidgetId, dragElementID, swap = false }) => {
|
||||
|
||||
handleAddWidgetChild = ({event, parentWidgetId, dragElementID, swap = false }) => {
|
||||
|
||||
console.log("event: ", event)
|
||||
// widgets data structure { id, widgetType: widgetComponentType, children: [], parent: "" }
|
||||
const dropWidgetObj = this.findWidgetFromListById(parentWidgetId)
|
||||
// Find the dragged widget object
|
||||
@@ -658,6 +771,18 @@ class Canvas extends React.Component {
|
||||
const dragWidget = this.widgetRefs[dragWidgetObj.id]
|
||||
const dragData = dragWidget.current.serialize()
|
||||
|
||||
|
||||
const parentWidget = this.widgetRefs[parentWidgetId].current
|
||||
const parentRect = parentWidget.getBoundingRect()
|
||||
const canvasRect = this.canvasRef.current.getBoundingClientRect()
|
||||
const { clientX, clientY } = event
|
||||
|
||||
|
||||
let finalPosition = {
|
||||
x: (clientX - parentRect.left) / this.state.zoom,
|
||||
y: (clientY - parentRect.top) / this.state.zoom,
|
||||
}
|
||||
|
||||
if (swap) {
|
||||
// If swapping, we need to find the common parent
|
||||
const grandParentWidgetObj = this.findWidgetFromListById(dropWidgetObj.parent)
|
||||
@@ -690,17 +815,25 @@ class Canvas extends React.Component {
|
||||
// Non-swap mode: Add the dragged widget as a child of the drop widget
|
||||
let updatedWidgets = this.removeWidgetFromCurrentList(dragElementID)
|
||||
|
||||
const parentLayout = parentWidget.getLayout()?.layout
|
||||
|
||||
console.log("parent layout: ", parentLayout, parentWidget.getLayout(), parentWidget)
|
||||
dragWidget.current.setPos(finalPosition.x, finalPosition.y)
|
||||
const updatedDragWidget = {
|
||||
...dragWidgetObj,
|
||||
parent: dropWidgetObj.id, // Keep the parent reference
|
||||
initialData: {
|
||||
...dragData,
|
||||
positionType: PosType.NONE,
|
||||
positionType: parentLayout === Layouts.PLACE ? PosType.ABSOLUTE : PosType.NONE,
|
||||
zIndex: 0,
|
||||
pos: {x: finalPosition.x, y: finalPosition.y},
|
||||
widgetContainer: WidgetContainer.WIDGET
|
||||
}
|
||||
}
|
||||
|
||||
console.log("updated widget: ", updatedDragWidget)
|
||||
|
||||
|
||||
const updatedDropWidget = {
|
||||
...dropWidgetObj,
|
||||
children: [...dropWidgetObj.children, updatedDragWidget]
|
||||
@@ -725,7 +858,7 @@ class Canvas extends React.Component {
|
||||
*/
|
||||
createWidget(widgetComponentType, callback) {
|
||||
|
||||
if (!isSubClassOfWidget(widgetComponentType)){
|
||||
if (!isSubClassOfWidget(widgetComponentType)) {
|
||||
throw new Error("widgetComponentType must be a subclass of Widget class")
|
||||
}
|
||||
|
||||
@@ -804,6 +937,28 @@ class Canvas extends React.Component {
|
||||
this._onWidgetListUpdated([])
|
||||
}
|
||||
|
||||
getWidgetByIdFromWidgetList = (widgetId) => {
|
||||
|
||||
function recursiveFind(objects) {
|
||||
for (const obj of objects) {
|
||||
// Check if the current object has the matching ID
|
||||
if (obj.id === widgetId) {
|
||||
return obj // Return the object if found
|
||||
}
|
||||
// Recursively check children if they exist
|
||||
if (obj.children && obj.children.length > 0) {
|
||||
const found = recursiveFind(obj.children)
|
||||
if (found) {
|
||||
return found // Return the found object from children
|
||||
}
|
||||
}
|
||||
}
|
||||
return null // Return null if not found
|
||||
}
|
||||
|
||||
return recursiveFind(this.state.widgets)
|
||||
}
|
||||
|
||||
removeWidget(widgetId) {
|
||||
|
||||
|
||||
@@ -833,114 +988,23 @@ class Canvas extends React.Component {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles drop event to canvas from the sidebar and on canvas widget movement
|
||||
* @param {DragEvent} e
|
||||
* informs the child about the parent layout
|
||||
*/
|
||||
handleDropEvent = (e, draggedElement, widgetClass=null) => {
|
||||
updateChildLayouts = ({parentId, parentLayout}) => {
|
||||
|
||||
const parent = this.getWidgetByIdFromWidgetList(parentId)
|
||||
|
||||
e.preventDefault()
|
||||
if (!parent) return
|
||||
|
||||
if (!draggedElement || !draggedElement.getAttribute("data-drag-start-within")){
|
||||
// if the drag is starting from outside (eg: file drop) or if drag doesn't exist
|
||||
return
|
||||
}
|
||||
|
||||
const container = draggedElement.getAttribute("data-container")
|
||||
const canvasRect = this.canvasRef.current.getBoundingClientRect()
|
||||
|
||||
const draggedElementRect = draggedElement.getBoundingClientRect()
|
||||
const elementWidth = draggedElementRect.width
|
||||
const elementHeight = draggedElementRect.height
|
||||
|
||||
const { clientX, clientY } = e
|
||||
|
||||
let finalPosition = {
|
||||
x: (clientX - canvasRect.left) / this.state.zoom,
|
||||
y: (clientY - canvasRect.top) / this.state.zoom,
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (container === WidgetContainer.SIDEBAR) {
|
||||
|
||||
if (!widgetClass){
|
||||
throw new Error("WidgetClass has to be passed for widgets dropped from sidebar")
|
||||
}
|
||||
|
||||
// if the widget is being dropped from the sidebar, use the info to create the widget first
|
||||
this.createWidget(widgetClass, ({ id, widgetRef }) => {
|
||||
widgetRef.current.setPos(finalPosition.x, finalPosition.y)
|
||||
})
|
||||
|
||||
} else if ([WidgetContainer.CANVAS, WidgetContainer.WIDGET].includes(container)) {
|
||||
|
||||
// snaps to center
|
||||
finalPosition = {
|
||||
x: (clientX - canvasRect.left) / this.state.zoom - (elementWidth / 2) / this.state.zoom,
|
||||
y: (clientY - canvasRect.top) / this.state.zoom - (elementHeight / 2) / this.state.zoom,
|
||||
}
|
||||
|
||||
let widgetId = draggedElement.getAttribute("data-widget-id")
|
||||
|
||||
const widgetObj = this.getWidgetById(widgetId)
|
||||
// console.log("WidgetObj: ", widgetObj)
|
||||
if (container === WidgetContainer.CANVAS) {
|
||||
|
||||
widgetObj.current.setPos(finalPosition.x, finalPosition.y)
|
||||
|
||||
} else if (container === WidgetContainer.WIDGET) {
|
||||
|
||||
// if the widget was inside another widget move it outside
|
||||
let childWidgetObj = this.findWidgetFromListById(widgetObj.current.getId())
|
||||
let parentWidgetObj = this.findWidgetFromListById(childWidgetObj.parent)
|
||||
|
||||
const childData = widgetObj.current.serialize() // save the data and pass it the updated child object
|
||||
|
||||
// remove child from current position
|
||||
|
||||
const updatedChildWidget = {
|
||||
...childWidgetObj,
|
||||
parent: "",
|
||||
initialData: {
|
||||
...childData,
|
||||
pos: { x: finalPosition.x, y: finalPosition.y },
|
||||
positionType: PosType.ABSOLUTE, // makes sure that after dropping the position is set to absolute value
|
||||
zIndex: 0,
|
||||
widgetContainer: WidgetContainer.CANVAS
|
||||
}
|
||||
}
|
||||
|
||||
let updatedWidgets = this.removeWidgetFromCurrentList(widgetObj.current.getId())
|
||||
|
||||
|
||||
// Create a new copy of the parent widget with the child added
|
||||
const updatedParentWidget = {
|
||||
...parentWidgetObj,
|
||||
// children: parentWidgetObj.children.filter(child => child.id !== childWidgetObj.id)
|
||||
}
|
||||
|
||||
|
||||
updatedWidgets = updatedWidgets.map(widget => {
|
||||
if (widget.id === parentWidgetObj.id) {
|
||||
return updatedParentWidget // Update the parent widget with the child removed
|
||||
} else {
|
||||
return widget // Leave other widgets unchanged
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
this.setState({
|
||||
widgets: [...updatedWidgets, updatedChildWidget]
|
||||
})
|
||||
|
||||
}
|
||||
for (let child of parent.children){
|
||||
this.widgetRefs[child.id].current.setParentLayout(parentLayout)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
renderWidget = (widget) => {
|
||||
|
||||
const { id, widgetType: ComponentType, children = [], parent, initialData = {} } = widget
|
||||
@@ -959,6 +1023,7 @@ class Canvas extends React.Component {
|
||||
|
||||
|
||||
return (
|
||||
|
||||
<ComponentType
|
||||
key={id}
|
||||
id={id}
|
||||
@@ -969,6 +1034,9 @@ class Canvas extends React.Component {
|
||||
onAddChildWidget={this.handleAddWidgetChild}
|
||||
onCreateWidgetRequest={this.createWidget} // create widget when dropped from sidebar
|
||||
onWidgetResizing={(resizeSide) => this.setState({ widgetResizing: resizeSide })}
|
||||
// onWidgetDragStart={() => this.setState({isWidgetDragging: true})}
|
||||
// onWidgetDragEnd={() => this.setState({isWidgetDragging: false})}
|
||||
onLayoutUpdate={this.updateChildLayouts}
|
||||
>
|
||||
{/* Render children inside the parent with layout applied */}
|
||||
{renderChildren(children)}
|
||||
@@ -977,6 +1045,7 @@ class Canvas extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
return (
|
||||
<div className="tw-relative tw-flex tw-w-full tw-h-full tw-max-h-[100vh]">
|
||||
|
||||
@@ -986,13 +1055,13 @@ class Canvas extends React.Component {
|
||||
<Tooltip title="Reset viewport">
|
||||
<Button icon={<ReloadOutlined />} onClick={this.resetTransforms} />
|
||||
</Tooltip>
|
||||
<ButtonModal
|
||||
message={"Are you sure you want to clear the canvas? This cannot be undone."}
|
||||
title={"Clear canvas"}
|
||||
onOk={this.clearCanvas}
|
||||
okText="Yes"
|
||||
okButtonType="danger"
|
||||
>
|
||||
<ButtonModal
|
||||
message={"Are you sure you want to clear the canvas? This cannot be undone."}
|
||||
title={"Clear canvas"}
|
||||
onOk={this.clearCanvas}
|
||||
okText="Yes"
|
||||
okButtonType="danger"
|
||||
>
|
||||
<Tooltip title="Clear canvas">
|
||||
<Button danger icon={<DeleteOutlined />} />
|
||||
</Tooltip>
|
||||
@@ -1001,7 +1070,7 @@ class Canvas extends React.Component {
|
||||
|
||||
{/* <ActiveWidgetProvider> */}
|
||||
<DroppableWrapper id="canvas-droppable"
|
||||
droppableTags={{exclude: ["image", "video"]}}
|
||||
droppableTags={{ exclude: ["image", "video"] }}
|
||||
className="tw-w-full tw-h-full"
|
||||
onDrop={this.handleDropEvent}>
|
||||
{/* <DragWidgetProvider> */}
|
||||
@@ -1024,13 +1093,17 @@ class Canvas extends React.Component {
|
||||
}}
|
||||
/>
|
||||
{/* Canvas */}
|
||||
<div data-canvas className="tw-w-full tw-h-full tw-absolute tw-top-0 tw-select-none
|
||||
"
|
||||
<div data-canvas className="tw-w-full tw-h-full tw-absolute tw-top-0 tw-select-none"
|
||||
ref={this.canvasRef}>
|
||||
<div className="tw-relative tw-w-full tw-h-full">
|
||||
{
|
||||
this.state.widgets.map(this.renderWidget)
|
||||
}
|
||||
|
||||
|
||||
{/* { this.state.selectedWidget &&
|
||||
<ResizeWidgetContainer selectedWidget={this.state.selectedWidget}/>
|
||||
} */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
102
src/canvas/resizeContainer.js
Normal file
102
src/canvas/resizeContainer.js
Normal file
@@ -0,0 +1,102 @@
|
||||
import Cursor from "./constants/cursor"
|
||||
import Widget from "./widgets/base"
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
// FIXME: when using this if the widhet has invisible swappable area, this won't work
|
||||
/**
|
||||
*
|
||||
* @param {Widget} - selectedWidget
|
||||
* @returns
|
||||
*/
|
||||
const ResizeWidgetContainer = ({selectedWidget, onResize}) => {
|
||||
|
||||
const [pos, setPos] = useState({x: 0, y: 0})
|
||||
const [size, setSize] = useState({width: 0, height: 0})
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
if (selectedWidget){
|
||||
setPos(selectedWidget.getPos())
|
||||
setSize(selectedWidget.getSize())
|
||||
}
|
||||
|
||||
console.log("selected widget resizable: ", selectedWidget)
|
||||
|
||||
}, [selectedWidget, selectedWidget?.getPos(), selectedWidget?.getSize()])
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className={`tw-absolute tw-bg-transparent tw-top-[-20px] tw-left-[-20px] tw-opacity-100
|
||||
tw-w-full tw-h-full tw-z-[-1] tw-border-2 tw-border-solid tw-border-blue-500`}
|
||||
style={{
|
||||
top: `${pos.y - 40}px`,
|
||||
left: `${pos.x - 20}px`,
|
||||
width: `${size.width + 40}px`,
|
||||
height: `${size.height + 40}px`,
|
||||
}}
|
||||
>
|
||||
|
||||
<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()
|
||||
onResize("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()
|
||||
onResize("nw")
|
||||
// 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()
|
||||
onResize("nw")
|
||||
// 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()
|
||||
onResize("nw")
|
||||
// this.props.onWidgetResizing("se")
|
||||
// this.setState({ dragEnabled: false })
|
||||
}}
|
||||
// onMouseUp={() => this.setState({ dragEnabled: true })}
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default ResizeWidgetContainer
|
||||
@@ -25,6 +25,9 @@ class Widget extends React.Component {
|
||||
|
||||
static widgetType = "widget"
|
||||
|
||||
static requirements = [] // requirements for the widgets (libraries) eg: tkvideoplayer, tktimepicker
|
||||
static requiredImports = [] // import statements
|
||||
|
||||
// static contextType = ActiveWidgetContext
|
||||
|
||||
constructor(props) {
|
||||
@@ -71,6 +74,8 @@ class Widget extends React.Component {
|
||||
widgetName: widgetName || 'widget', // this will later be converted to variable name
|
||||
enableRename: false, // will open the widgets editable div for renaming
|
||||
|
||||
parentLayout: null, // depending on the parents layout the child will behave
|
||||
|
||||
isDragging: false, // tells if the widget is currently being dragged
|
||||
dragEnabled: true,
|
||||
|
||||
@@ -128,6 +133,7 @@ class Widget extends React.Component {
|
||||
],
|
||||
},
|
||||
onChange: (value) => {
|
||||
console.log("changed: ", value)
|
||||
// this.setAttrValue("layout", value)
|
||||
this.setLayout(value)
|
||||
}
|
||||
@@ -267,6 +273,18 @@ class Widget extends React.Component {
|
||||
return this.constructor.widgetType
|
||||
}
|
||||
|
||||
getRequirements = () => {
|
||||
return this.constructor.requirements
|
||||
}
|
||||
|
||||
getImports = () => {
|
||||
return this.constructor.requiredImports
|
||||
}
|
||||
|
||||
getCode = () => {
|
||||
throw new NotImplementedError("Get Code must be implemented by the subclass")
|
||||
}
|
||||
|
||||
getAttributes() {
|
||||
return this.state.attrs
|
||||
}
|
||||
@@ -344,19 +362,6 @@ 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
|
||||
@@ -454,24 +459,62 @@ class Widget extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Layouts} layout
|
||||
*/
|
||||
setParentLayout = (layout) => {
|
||||
|
||||
let updates = {
|
||||
parentLayout: layout,
|
||||
}
|
||||
|
||||
if (layout === Layouts.FLEX || layout === Layouts.GRID){
|
||||
|
||||
updates = {
|
||||
...updates,
|
||||
positionType: PosType.NONE
|
||||
}
|
||||
|
||||
}else if (layout === Layouts.PLACE){
|
||||
updates = {
|
||||
...updates,
|
||||
positionType: PosType.ABSOLUTE
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Parent layout updated: ", updates)
|
||||
|
||||
this.setState(updates)
|
||||
}
|
||||
|
||||
getLayout = () => {
|
||||
|
||||
return this.state?.attrs?.layout?.value || Layouts.FLEX
|
||||
}
|
||||
|
||||
setLayout(value) {
|
||||
// FIXME: when the parent layout is place, the child widgets should have position absolute
|
||||
const { layout, direction, grid = { rows: 1, cols: 1 }, gap = 10 } = value
|
||||
|
||||
console.log("layout value: ", value)
|
||||
|
||||
const widgetStyle = {
|
||||
...this.state.widgetStyling,
|
||||
display: layout,
|
||||
display: layout !== Layouts.PLACE ? layout : "block",
|
||||
flexDirection: direction,
|
||||
gap: `${gap}px`,
|
||||
flexWrap: "wrap"
|
||||
// TODO: add grid rows and cols
|
||||
}
|
||||
|
||||
this.setAttrValue("layout", value)
|
||||
this.updateState({
|
||||
widgetStyling: widgetStyle
|
||||
})
|
||||
|
||||
this.setAttrValue("layout", value)
|
||||
this.props.onLayoutUpdate({parentId: this.__id, parentLayout: layout})// inform children about the layout update
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -480,7 +523,7 @@ class Widget extends React.Component {
|
||||
* @param {string} value - Value of the style
|
||||
*/
|
||||
setWidgetStyling(key, value) {
|
||||
|
||||
|
||||
const widgetStyle = {
|
||||
...this.state.widgetStyling,
|
||||
[key]: value
|
||||
@@ -555,6 +598,7 @@ class Widget extends React.Component {
|
||||
size: this.state.size,
|
||||
widgetContainer: this.state.widgetContainer,
|
||||
widgetStyling: this.state.widgetStyling,
|
||||
parentLayout: this.state.parentLayout,
|
||||
positionType: this.state.positionType,
|
||||
attrs: this.serializeAttrsValues() // makes sure that functions are not serialized
|
||||
})
|
||||
@@ -571,14 +615,40 @@ class Widget extends React.Component {
|
||||
|
||||
data = {...data} // create a shallow copy
|
||||
|
||||
const {attrs, ...restData} = data
|
||||
const {attrs, parentLayout, ...restData} = data
|
||||
|
||||
// for (let [key, value] of Object.entries(attrs | {}))
|
||||
// this.setAttrValue(key, value)
|
||||
|
||||
// delete data.attrs
|
||||
|
||||
this.setState(restData, () => {
|
||||
let layoutUpdates = {
|
||||
parentLayout: parentLayout
|
||||
}
|
||||
// FIXME: Need to load the data properly
|
||||
if (parentLayout === Layouts.FLEX || parentLayout === Layouts.GRID){
|
||||
|
||||
layoutUpdates = {
|
||||
...layoutUpdates,
|
||||
positionType: PosType.NONE
|
||||
}
|
||||
|
||||
}else if (parentLayout === Layouts.PLACE){
|
||||
layoutUpdates = {
|
||||
...layoutUpdates,
|
||||
positionType: PosType.ABSOLUTE
|
||||
}
|
||||
}
|
||||
|
||||
console.log("loaded layout: ", layoutUpdates)
|
||||
|
||||
const newData = {
|
||||
...restData,
|
||||
layoutUpdates
|
||||
}
|
||||
console.log("loaded layout2: ", newData)
|
||||
|
||||
this.setState(newData, () => {
|
||||
// UPdates attrs
|
||||
let newAttrs = { ...this.state.attrs }
|
||||
|
||||
@@ -611,6 +681,8 @@ class Widget extends React.Component {
|
||||
|
||||
callback(this.elementRef?.current || null)
|
||||
|
||||
// this.props.onWidgetDragStart(this.elementRef?.current)
|
||||
|
||||
// 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
|
||||
@@ -775,6 +847,7 @@ class Widget extends React.Component {
|
||||
// console.log("Dropped on meee: ", swapArea, this.swappableAreaRef.current.contains(e.target), thisContainer)
|
||||
|
||||
this.props.onAddChildWidget({
|
||||
event: e,
|
||||
parentWidgetId: this.__id,
|
||||
dragElementID: draggedElement.getAttribute("data-widget-id"),
|
||||
swap: swapArea || false
|
||||
@@ -784,7 +857,11 @@ class Widget extends React.Component {
|
||||
|
||||
// console.log("Dropped on Sidebar: ", this.__id)
|
||||
this.props.onCreateWidgetRequest(widgetClass, ({ id, widgetRef }) => {
|
||||
this.props.onAddChildWidget({ parentWidgetId: this.__id, dragElementID: id }) // if dragged from the sidebar create the widget first
|
||||
this.props.onAddChildWidget({
|
||||
event: e,
|
||||
parentWidgetId: this.__id,
|
||||
dragElementID: id
|
||||
}) // if dragged from the sidebar create the widget first
|
||||
})
|
||||
|
||||
}
|
||||
@@ -811,6 +888,8 @@ class Widget extends React.Component {
|
||||
callback()
|
||||
this.setState({ isDragging: false })
|
||||
this.enablePointerEvents()
|
||||
|
||||
// this.props.onWidgetDragEnd(this.elementRef?.current)
|
||||
}
|
||||
|
||||
disablePointerEvents = () => {
|
||||
@@ -854,6 +933,9 @@ class Widget extends React.Component {
|
||||
height: `${this.state.size.height}px`,
|
||||
opacity: this.state.isDragging ? 0.3 : 1,
|
||||
}
|
||||
|
||||
// const boundingRect = this.getBoundingRect
|
||||
|
||||
// FIXME: if the parent container has tw-overflow-none, then the resizable indicator are also hidden
|
||||
return (
|
||||
|
||||
@@ -887,14 +969,14 @@ class Widget extends React.Component {
|
||||
>
|
||||
<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-w-full tw-h-full tw-bg-red-400
|
||||
tw-scale-[1.1] tw-opacity-1 tw-z-[-1] `}
|
||||
style={{
|
||||
width: "calc(100% + 10px)",
|
||||
height: "calc(100% + 10px)",
|
||||
}}
|
||||
ref={this.swappableAreaRef}
|
||||
|
||||
// swapable area
|
||||
>
|
||||
{/* helps with swappable: if the mouse is in this area while hovering/dropping, then swap */}
|
||||
</div>
|
||||
@@ -916,13 +998,14 @@ class Widget extends React.Component {
|
||||
>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div className={`tw-absolute tw-bg-transparent tw-top-[-10px] tw-left-[-10px] tw-opacity-100
|
||||
tw-w-full tw-h-full tw-z-[-1]
|
||||
{/* 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,
|
||||
}}
|
||||
>
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ class Frame extends Widget{
|
||||
|
||||
renderContent(){
|
||||
return (
|
||||
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden">
|
||||
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-relative tw-rounded-md tw-overflow-hidden">
|
||||
<div className="tw-p-2 tw-w-full tw-h-full tw-content-start" style={this.state.widgetStyling}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
|
||||
@@ -65,7 +65,7 @@ class MainWindow extends Widget{
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="tw-p-2 tw-w-full tw-h-full tw-content-start" style={this.state.widgetStyling}>
|
||||
<div className="tw-p-2 tw-w-full tw-relative tw-h-full tw-overflow-hidden tw-content-start" style={this.state.widgetStyling}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user