working on widget-children dnd

This commit is contained in:
paul
2024-09-18 22:16:34 +05:30
parent c3759e0457
commit ad87e1cc53
5 changed files with 419 additions and 244 deletions

View File

@@ -1,6 +1,6 @@
import React from "react"
import {DndContext} from '@dnd-kit/core'
import { DndContext } from '@dnd-kit/core'
import { CloseOutlined, DeleteOutlined, EditOutlined, FullscreenOutlined, ReloadOutlined } from "@ant-design/icons"
import { Button, Tooltip, Dropdown } from "antd"
@@ -18,7 +18,7 @@ import { WidgetContext } from './context/widgetContext'
// import {ReactComponent as DotsBackground} from "../assets/background/dots.svg"
// import DotsBackground from "../assets/background/dots.svg"
import {ReactComponent as DotsBackground} from "../assets/background/dots.svg"
import { ReactComponent as DotsBackground } from "../assets/background/dots.svg"
import DroppableWrapper from "../components/draggable/droppable"
import { ActiveWidgetContext, ActiveWidgetProvider, withActiveWidget } from "./activeWidgetContext"
@@ -42,14 +42,14 @@ class Canvas extends React.Component {
super(props)
const { canvasWidgets, onWidgetListUpdated } = props
this.canvasRef = React.createRef()
this.canvasRef = React.createRef()
this.canvasContainerRef = React.createRef()
this.currentMode = CanvasModes.DEFAULT
this.minCanvasSize = {width: 500, height: 500}
this.minCanvasSize = { width: 500, height: 500 }
this.mousePressed = false
this.mousePos = {
@@ -62,16 +62,16 @@ class Canvas extends React.Component {
this.state = {
widgetResizing: "", // set this to "nw", "sw" etc based on the side when widgets resizing handles are selected
widgets: [], // stores the mapping to widgetRefs, stores id and WidgetType, later used for rendering [{id: , widgetType: WidgetClass, children: [], parent: ""}]
widgets: [], // stores the mapping to widgetRefs, stores id and WidgetType, later used for rendering [{id: , widgetType: WidgetClass, children: [], parent: "", layoutType: "flex"}]
zoom: 1,
isPanning: false,
currentTranslate: { x: 0, y: 0 },
canvasSize: { width: 500, height: 500 },
canvasSize: { width: 500, height: 500 },
contextMenuItems: [],
selectedWidget: null,
toolbarOpen: true,
toolbarOpen: true,
toolbarAttrs: null
}
@@ -79,7 +79,7 @@ class Canvas extends React.Component {
this.resetTransforms = this.resetTransforms.bind(this)
this.renderWidget = this.renderWidget.bind(this)
this.mouseDownEvent = this.mouseDownEvent.bind(this)
this.mouseMoveEvent = this.mouseMoveEvent.bind(this)
this.mouseUpEvent = this.mouseUpEvent.bind(this)
@@ -115,7 +115,7 @@ class Canvas extends React.Component {
}
componentWillUnmount() {
this.canvasContainerRef.current.removeEventListener("mousedown", this.mouseDownEvent)
this.canvasContainerRef.current.removeEventListener("mouseup", this.mouseUpEvent)
this.canvasContainerRef.current.removeEventListener("mousemove", this.mouseMoveEvent)
@@ -128,31 +128,31 @@ class Canvas extends React.Component {
this.clearCanvas()
}
initEvents(){
initEvents() {
this.canvasContainerRef.current.addEventListener("mousedown", this.mouseDownEvent)
this.canvasContainerRef.current.addEventListener("mouseup", this.mouseUpEvent)
this.canvasContainerRef.current.addEventListener("mousemove", this.mouseMoveEvent)
this.canvasContainerRef.current.addEventListener("wheel", this.wheelZoom)
this.canvasContainerRef.current.addEventListener("keydown", this.keyDownEvent, true)
// window.addEventListener("keydown", this.keyDownEvent, true)
}
applyTransform(){
applyTransform() {
const { currentTranslate, zoom } = this.state
this.canvasRef.current.style.transform = `translate(${currentTranslate.x}px, ${currentTranslate.y}px) scale(${zoom})`
}
/**
*
* @returns {import("./widgets/base").Widget[]}
*/
getWidgets(){
/**
*
* @returns {import("./widgets/base").Widget[]}
*/
getWidgets() {
return this.state.widgets
}
@@ -160,7 +160,7 @@ class Canvas extends React.Component {
* returns list of active objects / selected objects on the canvas
* @returns Widget[]
*/
getActiveObjects(){
getActiveObjects() {
return Object.values(this.widgetRefs).filter((widgetRef) => {
return widgetRef.current?.isSelected()
})
@@ -172,47 +172,47 @@ class Canvas extends React.Component {
* @param {HTMLElement} target
* @returns {Widget}
*/
getWidgetFromTarget(target){
getWidgetFromTarget(target) {
for (let [key, ref] of Object.entries(this.widgetRefs)){
// console.log("ref: ", ref)
if (ref.current.getElement().contains(target)){
for (let [key, ref] of Object.entries(this.widgetRefs)) {
console.log("ref: ", ref, key)
if (ref.current.getElement().contains(target)) {
return ref.current
}
}
}
keyDownEvent(event){
keyDownEvent(event) {
if (event.key === "Delete"){
if (event.key === "Delete") {
this.deleteSelectedWidgets()
}
if (event.key === "+"){
if (event.key === "+") {
this.setZoom(this.state.zoom + 0.1)
}
if (event.key === "-"){
if (event.key === "-") {
this.setZoom(this.state.zoom - 0.1)
}
}
mouseDownEvent(event){
mouseDownEvent(event) {
this.mousePos = { x: event.clientX, y: event.clientY }
let selectedWidget = this.getWidgetFromTarget(event.target)
if (event.button === 0){
if (event.button === 0) {
this.mousePressed = true
if (selectedWidget){
if (selectedWidget) {
// if the widget is selected don't pan, instead move the widget
if (!selectedWidget._disableSelection){
if (!selectedWidget._disableSelection) {
// console.log("selected widget: ", selectedWidget)
if (!this.state.selectedWidget || (selectedWidget.__id !== this.state.selectedWidget?.__id)){
if (!this.state.selectedWidget || (selectedWidget.__id !== this.state.selectedWidget?.__id)) {
this.state.selectedWidget?.deSelect() // deselect the previous widget before adding the new one
this.state.selectedWidget?.setZIndex(0)
@@ -233,7 +233,7 @@ class Canvas extends React.Component {
this.currentMode = CanvasModes.PAN
}else if (!selectedWidget){
} else if (!selectedWidget) {
// get the canvas ready to pan, if there are widgets on the canvas
this.clearSelections()
this.currentMode = CanvasModes.PAN
@@ -248,14 +248,14 @@ class Canvas extends React.Component {
// this.setState({
// showContextMenu: false
// })
}else if (event.button === 2){
} else if (event.button === 2) {
//right click
if (this.state.selectedWidget && this.state.selectedWidget.__id !== selectedWidget.__id){
if (this.state.selectedWidget && this.state.selectedWidget.__id !== selectedWidget.__id) {
this.clearSelections()
}
if (selectedWidget){
if (selectedWidget) {
this.setState({
selectedWidget: selectedWidget,
@@ -273,16 +273,16 @@ class Canvas extends React.Component {
}
]
})
}
}
}
mouseMoveEvent(event){
mouseMoveEvent(event) {
if (this.state.widgetResizing !== ""){
if (this.state.widgetResizing !== "") {
// if resizing is taking place don't do anything else
this.handleResize(event)
return
@@ -292,8 +292,8 @@ class Canvas extends React.Component {
if (this.mousePressed && [CanvasModes.PAN, CanvasModes.MOVE_WIDGET].includes(this.currentMode)) {
const deltaX = event.clientX - this.mousePos.x
const deltaY = event.clientY - this.mousePos.y
if (!this.state.selectedWidget){
if (!this.state.selectedWidget) {
// if there aren't any selected widgets, then pan the canvas
this.setState(prevState => ({
currentTranslate: {
@@ -302,7 +302,7 @@ class Canvas extends React.Component {
}
}), this.applyTransform)
}else{
} else {
// update the widgets position
// this.state.selectedWidgets.forEach(widget => {
// const {x, y} = widget.getPos()
@@ -314,13 +314,13 @@ class Canvas extends React.Component {
}
this.mousePos = { x: event.clientX, y: event.clientY }
this.mousePos = { x: event.clientX, y: event.clientY }
this.setCursor(Cursor.GRAB)
}
this.setCursor(Cursor.GRAB)
}
}
mouseUpEvent(event){
mouseUpEvent(event) {
this.mousePressed = false
this.currentMode = CanvasModes.DEFAULT
this.setCursor(Cursor.DEFAULT)
@@ -330,11 +330,11 @@ class Canvas extends React.Component {
}
}
wheelZoom(event){
wheelZoom(event) {
let delta = event.deltaY
let zoom = this.state.zoom * 0.999 ** delta
this.setZoom(zoom, {x: event.offsetX, y: event.offsetY})
this.setZoom(zoom, { x: event.offsetX, y: event.offsetY })
}
/**
@@ -385,27 +385,27 @@ class Canvas extends React.Component {
break
default:
break
}
}
widget.setResize(newPos, newSize)
}
getCanvasContainerBoundingRect(){
getCanvasContainerBoundingRect() {
return this.canvasContainerRef.current.getBoundingClientRect()
}
getCanvasBoundingRect(){
getCanvasBoundingRect() {
return this.canvasRef.current.getBoundingClientRect()
}
getCanvasTranslation(){
getCanvasTranslation() {
return this.state.currentTranslate
}
/**
* fits the canvas size to fit the widgets bounding box
*/
fitCanvasToBoundingBox(padding=0){
fitCanvasToBoundingBox(padding = 0) {
const { top, left, right, bottom } = this.getCanvasObjectsBoundingBox()
const width = right - left
@@ -425,21 +425,21 @@ class Canvas extends React.Component {
canvasStyle.top = `${top - padding}px`
}
setCursor(cursor){
setCursor(cursor) {
this.canvasContainerRef.current.style.cursor = cursor
}
setZoom(zoom, pos){
setZoom(zoom, pos) {
const { currentTranslate } = this.state
let newTranslate = currentTranslate
if (pos){
if (pos) {
// Calculate the new translation to zoom into the mouse position
const offsetX = pos.x - (this.canvasContainerRef.current.clientWidth / 2 + currentTranslate.x)
const offsetY = pos.y - (this.canvasContainerRef.current.clientHeight / 2 + currentTranslate.y)
const newTranslateX = currentTranslate.x - offsetX * (zoom - this.state.zoom)
const newTranslateY = currentTranslate.y - offsetY * (zoom - this.state.zoom)
newTranslate = {
@@ -456,10 +456,10 @@ class Canvas extends React.Component {
}
getZoom(){
getZoom() {
return this.state.zoom
}
resetTransforms() {
this.setState({
zoom: 1,
@@ -467,11 +467,11 @@ class Canvas extends React.Component {
}, this.applyTransform)
}
setSelectedWidget(selectedWidget){
setSelectedWidget(selectedWidget) {
this.setState({ selectedWidget: [selectedWidget] })
}
clearSelections(){
clearSelections() {
if (!this.state.selectedWidget)
return
@@ -482,7 +482,7 @@ class Canvas extends React.Component {
// this.context?.updateActiveWidget("")
// this.context.updateToolAttrs({})
this.setState({
selectedWidget: null,
toolbarAttrs: null,
@@ -495,7 +495,7 @@ class Canvas extends React.Component {
* returns tha combined bounding rect of all the widgets on the canvas
*
*/
getCanvasObjectsBoundingBox(){
getCanvasObjectsBoundingBox() {
// Initialize coordinates to opposite extremes
let top = Number.POSITIVE_INFINITY
@@ -511,15 +511,147 @@ class Canvas extends React.Component {
if (rect.right > right) right = rect.right
if (rect.bottom > bottom) bottom = rect.bottom
}
return { top, left, right, bottom }
}
/**
* finds widgets from the list of this.state.widgets, also checks the children to find the widgets
* @param {string} widgetId
* @returns
*/
findWidgetFromListById = (widgetId) => {
const searchWidgetById = (widgets, widgetId) => {
for (let widget of widgets) {
if (widget.id === widgetId) {
return widget
}
// Recursively search in children
if (widget.children.length > 0) {
const foundInChildren = searchWidgetById(widget.children, widgetId)
if (foundInChildren) {
return foundInChildren // Found in children
}
}
}
return null // Widget not found
}
return searchWidgetById(this.state.widgets, widgetId)
}
/**
* Finds the widget from the list and removes it from its current position, even if the widget is in the child position
* @param {Array} widgets - The current list of widgets
* @param {string} widgetId - The ID of the widget to remove
* @returns {Array} - The updated widgets list
*/
removeWidgetFromCurrentList = (widgetId) => {
// Helper function to recursively remove widget
const removeWidget = (widgets, widgetId) => {
// Process each widget
return widgets.reduce((acc, widget) => {
// If the widget is found at the top level, skip it
if (widget.id === widgetId) {
return acc
}
// Process children recursively
const updatedChildren = removeWidget(widget.children.map(childId =>
widgets.find(w => w.id === childId)
), widgetId)
// If the widget has children and the widgetId is not found, include it in the results
if (widget.children.length > 0) {
const updatedWidget = {
...widget,
children: updatedChildren.map(child => child.id) // Flatten children IDs
};
return [...acc, updatedWidget]
}
return [...acc, widget]
}, [])
}
// Perform the removal operation
return removeWidget(this.state.widgets, widgetId)
}
/**
* Adds the child into the children attribute inside the this.widgets list of objects
* // widgets data structure { id, widgetType: widgetComponentType, children: [], parent: "" }
* @param {string} parentWidgetId
* @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, create = false) => {
// widgets data structure { id, widgetType: widgetComponentType, children: [], parent: "" }
const parentWidgetObj = this.findWidgetFromListById(parentWidgetId)
let childWidgetObj = this.findWidgetFromListById(dragElementID)
console.log("WIdgets: ", parentWidgetObj, childWidgetObj)
if (parentWidgetObj && childWidgetObj) {
// remove child from current postion
let updatedWidgets = this.removeWidgetFromCurrentList(dragElementID)
console.log("pre updated widgets: ", updatedWidgets)
const updatedChildWidget = {
...childWidgetObj,
parent: parentWidgetId
}
// Create a new copy of the parent widget with the child added
const updatedParentWidget = {
...parentWidgetObj,
children: [...parentWidgetObj.children, updatedChildWidget]
}
// add parent id to the child widget
updatedWidgets = updatedWidgets.map(widget => {
if (widget.id === parentWidgetId) {
return updatedParentWidget // Update the parent widget
} else if (widget.id === updatedChildWidget.id) {
return updatedChildWidget // Update the child widget
} else {
return widget // Leave other widgets unchanged
}
})
console.log("updated widgets: ", updatedWidgets)
// once its mutated the original widget ref is lost so attach the new one
this.setState({
widgets: updatedWidgets
}, () => {
this.widgetRefs[dragElementID] = React.createRef()
// Optionally, force React to update and re-render the refs
this.forceUpdate()
})
}
}
/**
*
* @param {Widget} widgetComponentType - don't pass <Widget /> instead pass Widget object/class
*/
createWidget(widgetComponentType, callback){
createWidget(widgetComponentType, callback) {
const widgetRef = React.createRef()
const id = `${widgetComponentType.widgetType}_${UID()}`
@@ -527,25 +659,23 @@ class Canvas extends React.Component {
// Store the ref in the instance variable
this.widgetRefs[id] = widgetRef
const widgets = [...this.state.widgets, { id, widgetType: widgetComponentType, children: [], parent: "" }] // don't add the widget refs in the state
const widgets = [...this.state.widgets, { id, widgetType: widgetComponentType, children: [], parent: "", layoutType: "flex" }] // don't add the widget refs in the state
// Update the state to include the new widget's type and ID
this.setState({
widgets: widgets
}, () => {
if (callback)
callback({id, widgetRef})
callback({ id, widgetRef })
if (this._onWidgetListUpdated)
this._onWidgetListUpdated(widgets) // inform the parent container
})
return {id, widgetRef}
return { id, widgetRef }
}
getWidgetById(id){
getWidgetById(id) {
return this.widgetRefs[id]
}
@@ -554,14 +684,14 @@ class Canvas extends React.Component {
* delete's the selected widgets from the canvas
* @param {null|Widget} widgets - optional widgets that can be deleted along the selected widgets
*/
deleteSelectedWidgets(widgets=[]){
deleteSelectedWidgets(widgets = []) {
let activeWidgets = removeDuplicateObjects([...widgets, this.state.selectedWidget], "__id")
const widgetIds = activeWidgets.map(widget => widget.__id)
for (let widgetId of widgetIds){
for (let widgetId of widgetIds) {
// this.widgetRefs[widgetId]?.current.remove()
delete this.widgetRefs[widgetId]
@@ -576,14 +706,14 @@ class Canvas extends React.Component {
// value.current?.remove()
}
}
/**
* removes all the widgets from the canvas
*/
clearCanvas(){
clearCanvas() {
// NOTE: Don't remove from it using remove() function since, it already removed from the DOM tree when its removed from widgets
@@ -596,7 +726,10 @@ class Canvas extends React.Component {
this._onWidgetListUpdated([])
}
removeWidget(widgetId){
removeWidget(widgetId) {
// FIXME: need to delete the child widgets
// IDEA: find the widget first, check for the parent, if parent exist remove it from the parents children list
// this.widgetRefs[widgetId]?.current.remove()
delete this.widgetRefs[widgetId]
@@ -611,7 +744,7 @@ class Canvas extends React.Component {
this._onWidgetListUpdated(widgets)
}
onActiveWidgetUpdate(widgetId){
onActiveWidgetUpdate(widgetId) {
if (!this.state.selectedWidget || widgetId !== this.state.selectedWidget.__id)
return
@@ -634,7 +767,7 @@ class Canvas extends React.Component {
e.preventDefault()
const container = draggedElement.getAttribute("data-container")
// console.log("Dropped on canvas",)
@@ -642,17 +775,17 @@ class Canvas extends React.Component {
const canvasRect = this.canvasRef.current.getBoundingClientRect()
const { clientX, clientY } = e
const finalPosition = {
x: (clientX - canvasRect.left) / this.state.zoom,
y: (clientY - canvasRect.top) / this.state.zoom,
}
const finalPosition = {
x: (clientX - canvasRect.left) / this.state.zoom,
y: (clientY - canvasRect.top) / this.state.zoom,
}
if (container === "sidebar"){
if (container === "sidebar") {
// if the widget is being dropped from the sidebar, use the info to create the widget first
this.createWidget(Widget, ({id, widgetRef}) => {
this.createWidget(Widget, ({ id, widgetRef }) => {
widgetRef.current.setPos(finalPosition.x, finalPosition.y)
})
}else if (container === "canvas"){
} else if (container === "canvas") {
const widgetObj = this.getWidgetById(draggedElement.getAttribute("data-widget-id"))
// console.log("WidgetObj: ", widgetObj)
@@ -661,28 +794,75 @@ class Canvas extends React.Component {
}
renderWidget(widget){
const { id, widgetType: ComponentType, children=[], parent } = widget
// console.log("widet: ", this.widgetRefs, id)
// TODO: need to pass the widget ref for child elements as well
return <ComponentType key={id} id={id} ref={this.widgetRefs[id]}
canvasRef={this.canvasContainerRef}
onWidgetUpdate={this.onActiveWidgetUpdate}
childWidgets={children}
parent={parent}
onWidgetResizing={(resizeSide) => this.setState({widgetResizing: resizeSide })}
/>
getLayoutStyleForWidget = (widget) => {
const { layoutType } = widget // e.g., 'grid', 'flex', 'absolute'
switch (layoutType) {
case 'grid':
return { display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '10px' }
case 'flex':
return { display: 'flex', flexDirection: 'row', justifyContent: 'space-around' }
case 'absolute':
return { position: 'absolute', left: widget.left, top: widget.top } // Custom positioning
default:
return {}
}
}
renderWidget = (widget) => {
// FIXME: the child elements ref is not correct when drag and dropped into another
const { id, widgetType: ComponentType, children = [], parent } = widget
console.log("rendering: ", widget, id)
// Layout management for children inside the parent
const renderChildren = (childWidgets) => {
console.log("Found the child : ", childWidgets)
return childWidgets.map((child) => {
const childWidget = this.findWidgetFromListById(child.id)
// console.log("Found the child : ", childWidget)
if (childWidget) {
console.log("rendering the child", childWidget)
return this.renderWidget(childWidget) // Recursively render child widgets
}
return null
})
}
// Example of handling layout within the parent widget
const layoutStyle = this.getLayoutStyleForWidget(widget)
console.log("widget ref id: ", this.widgetRefs[id], this.widgetRefs)
return (
<ComponentType
key={id}
id={id}
ref={this.widgetRefs[id]}
canvasRef={this.canvasContainerRef}
onWidgetUpdate={this.onActiveWidgetUpdate}
childWidgets={children} // Pass the list of children (IDs)
parent={parent}
onAddChildWidget={this.handleAddWidgetChild}
onWidgetResizing={(resizeSide) => this.setState({ widgetResizing: resizeSide })}
style={layoutStyle} // Apply layout style (for position, size, etc.)
>
{/* Render children inside the parent with layout applied */}
{renderChildren(children)}
</ComponentType>
)
}
render() {
return (
<div className="tw-relative tw-flex tw-w-full tw-h-full tw-max-h-[100vh]">
<div className="tw-absolute tw-p-2 tw-bg-white tw-z-10 tw-min-w-[100px] tw-h-[50px] tw-gap-2
tw-top-4 tw-place-items-center tw-right-4 tw-shadow-md tw-rounded-md tw-flex">
<Tooltip title="Reset viewport">
<Button icon={<ReloadOutlined />} onClick={this.resetTransforms} />
<Button icon={<ReloadOutlined />} onClick={this.resetTransforms} />
</Tooltip>
<Tooltip title="Clear canvas">
<Button danger icon={<DeleteOutlined />} onClick={this.clearCanvas} />
@@ -690,47 +870,47 @@ class Canvas extends React.Component {
</div>
{/* <ActiveWidgetProvider> */}
<DroppableWrapper id="canvas-droppable"
className="tw-w-full tw-h-full"
onDrop={this.handleDropEvent}>
<DroppableWrapper id="canvas-droppable"
className="tw-w-full tw-h-full"
onDrop={this.handleDropEvent}>
{/* <DragWidgetProvider> */}
<Dropdown trigger={['contextMenu']} mouseLeaveDelay={0} menu={{items: this.state.contextMenuItems, }}>
<div className="tw-w-full tw-h-full tw-outline-none tw-flex tw-relative tw-bg-[#f2f2f2] tw-overflow-hidden"
ref={this.canvasContainerRef}
style={{
transition: " transform 0.3s ease-in-out",
backgroundImage: `url(${DotsBackground})`,
backgroundSize: 'cover', // Ensure proper sizing if needed
backgroundRepeat: 'no-repeat',
}}
tabIndex={0} // allow focus
>
<DotsBackground
style={{
width: '100%',
height: '100%',
backgroundSize: 'cover'
}}
/>
{/* Canvas */}
<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)
}
</div>
<Dropdown trigger={['contextMenu']} mouseLeaveDelay={0} menu={{ items: this.state.contextMenuItems, }}>
<div className="tw-w-full tw-h-full tw-outline-none tw-flex tw-relative tw-bg-[#f2f2f2] tw-overflow-hidden"
ref={this.canvasContainerRef}
style={{
transition: " transform 0.3s ease-in-out",
backgroundImage: `url(${DotsBackground})`,
backgroundSize: 'cover', // Ensure proper sizing if needed
backgroundRepeat: 'no-repeat',
}}
tabIndex={0} // allow focus
>
<DotsBackground
style={{
width: '100%',
height: '100%',
backgroundSize: 'cover'
}}
/>
{/* Canvas */}
<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)
}
</div>
</div>
</Dropdown>
</div>
</Dropdown>
{/* </DragWidgetProvider> */}
</DroppableWrapper>
<CanvasToolBar isOpen={this.state.toolbarOpen}
widgetType={this.state.selectedWidget?.getWidgetType() || ""}
attrs={this.state.toolbarAttrs}
/>
<CanvasToolBar isOpen={this.state.toolbarOpen}
widgetType={this.state.selectedWidget?.getWidgetType() || ""}
attrs={this.state.toolbarAttrs}
/>
{/* </ActiveWidgetProvider> */}
</div>
)

View File

@@ -1,5 +1,5 @@
const Layouts = {
PACK: "flex",
FLEX: "flex",
GRID: "grid",
PLACE: "absolute"
}

View File

@@ -5,6 +5,8 @@ const Tools = {
SELECT_DROPDOWN: "select_dropdown",
COLOR_PICKER: "color_picker",
EVENT_HANDLER: "event_handler", // shows a event handler with all the possible function in the dropdown
LAYOUT_MANAGER: "layout_manager"
}

View File

@@ -5,6 +5,7 @@ import { ColorPicker, Input, InputNumber, Select } from "antd"
import { capitalize } from "../utils/common"
import Tools from "./constants/tools.js"
import { useActiveWidget } from "./activeWidgetContext.js"
import Layouts from "./constants/layouts.js"
// FIXME: Maximum recursion error
@@ -15,7 +16,7 @@ import { useActiveWidget } from "./activeWidgetContext.js"
* @param {string} widgetType
* @param {object} attrs - widget attributes
*/
const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
// const { activeWidgetAttrs } = useActiveWidget()
@@ -28,16 +29,9 @@ const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
}, [isOpen])
useEffect(() => {
console.log("active widget: ", attrs)
setToolbarAttrs(attrs)
}, [attrs])
// useEffect(() => {
// console.log("active widget: ", activeWidgetAttrs)
// setToolbarAttrs(activeWidgetAttrs || {})
// }, [activeWidgetAttrs])
const handleChange = (value, callback) => {
console.log("changed...")
@@ -46,6 +40,69 @@ const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
}
}
const renderLayoutManager = (val) => {
return (
<div className="tw-flex tw-flex-col tw-gap-2">
<Select
options={[
{ value: Layouts.FLEX, label: "Flex" },
{ value: Layouts.GRID, label: "Grid" },
{ value: Layouts.PLACE, label: "Place" },
]}
showSearch
value={val.value?.layout || ""}
placeholder={`${val.label}`}
size="medium"
onChange={(value) => handleChange(value, val.onChange)}
/>
<div className="tw-flex tw-flex-col tw-gap-1">
<span className="tw-text-sm">Direction</span>
<Select
options={[
{ value: "vertical", label: "Vertical" },
{ value: "horizontal", label: "Horizontal" },
]}
showSearch
value={val.value?.direction || ""}
placeholder={`${val.label}`}
onChange={(value) => handleChange(value, val.onChange)}
/>
</div>
<div className="tw-flex tw-flex-col">
<span className="tw-text-sm tw-font-medium">Grids</span>
<div className="tw-flex tw-gap-2">
<div className="tw-flex tw-flex-col">
<span className="tw-text-sm">Rows</span>
<InputNumber
max={12}
min={1}
value={val.value?.grid.rows || 1}
size="small"
onChange={(value) => handleChange(value, val.onChange)}
/>
</div>
<div className="tw-flex tw-flex-col">
<span className="tw-text-sm">Columns</span>
<InputNumber
max={12}
min={1}
value={val.value?.grid.cols || 1}
size="small"
onChange={(value) => handleChange(value, val.onChange)}
/>
</div>
</div>
</div>
</div>
)
}
const renderWidgets = (obj, parentKey = "") => {
return Object.entries(obj).map(([key, val], i) => {
const keyName = parentKey ? `${parentKey}.${key}` : key
@@ -103,6 +160,13 @@ const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
onChange={(value) => handleChange(value, val.onChange)}
/>
)}
{
val.tool === Tools.LAYOUT_MANAGER && (
renderLayoutManager(val)
)
}
</div>
);
}
@@ -137,7 +201,6 @@ const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
{capitalize(`${widgetType || ""}`)}
</h3>
<hr />
<div className="tw-flex tw-flex-col tw-gap-4">{renderWidgets(toolbarAttrs || {})}</div>
</div>
)

View File

@@ -53,7 +53,7 @@ class Widget extends React.Component {
}
this.layout = Layouts.PACK
this.layout = Layouts.FLEX
this.boundingRect = {
x: 0,
y: 0,
@@ -66,8 +66,6 @@ class Widget extends React.Component {
selected: false,
widgetName: widgetName || 'widget', // this will later be converted to variable name
enableRename: false, // will open the widgets editable div for renaming
resizing: false,
resizeCorner: "",
dragEnabled: true,
showDroppableStyle: { // shows the droppable indicator
@@ -103,8 +101,15 @@ class Widget extends React.Component {
},
layout: {
label: "Layout",
tool: Tools.SELECT_DROPDOWN, // the tool to display, can be either HTML ELement or a constant string
value: "flex",
tool: Tools.LAYOUT_MANAGER, // the tool to display, can be either HTML ELement or a constant string
value: {
layout: "flex",
direction: "vertical",
grid: {
rows: 1,
cols: 1
}
},
options: [
{ value: "flex", label: "Flex" },
{ value: "grid", label: "Grid" },
@@ -141,25 +146,14 @@ class Widget extends React.Component {
this.setWidgetName = this.setWidgetName.bind(this)
this.setWidgetStyling = this.setWidgetStyling.bind(this)
// this.startResizing = this.startResizing.bind(this)
// this.handleResize = this.handleResize.bind(this)
// this.stopResizing = this.stopResizing.bind(this)
}
componentDidMount() {
this.elementRef.current?.addEventListener("click", this.mousePress)
// this.canvas.addEventListener("mousemove", this.handleResize)
// this.canvas.addEventListener("mouseup", this.stopResizing)
}
componentWillUnmount() {
this.elementRef.current?.removeEventListener("click", this.mousePress)
// this.canvas.addEventListener("mousemove", this.handleResize)
// this.canvas.addEventListener("mouseup", this.stopResizing)
}
componentDidUpdate(prevProps, prevState) {
@@ -287,11 +281,6 @@ class Widget extends React.Component {
setPos(x, y) {
// if (this.state.resizing) {
// // don't change position when resizing the widget
// return
// }
this.setState({
pos: { x, y }
})
@@ -410,63 +399,6 @@ class Widget extends React.Component {
})
}
// 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: ", deltaX, deltaY, event)
// 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 })
// this.updateState({
// size: newSize,
// pos: newPos
// })
// }
// stopResizing() {
// if (this.state.resizing) {
// this.setState({ resizing: false })
// }
// }
openRenaming() {
this.setState({
selected: true,
@@ -500,12 +432,14 @@ class Widget extends React.Component {
console.log("dragging event: ", event, dragElement)
const container = dragElement.getAttribute("data-container")
// TODO: check if the drop is allowed
if (container === "canvas"){
// this.canvas.getWidgetById
// this._children.push()
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
}
@@ -604,7 +538,6 @@ class Widget extends React.Component {
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)
this.props.onWidgetResizing("nw")
this.setState({dragEnabled: false})
}}
@@ -614,7 +547,6 @@ class Widget extends React.Component {
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)
this.props.onWidgetResizing("ne")
this.setState({dragEnabled: false})
}}
@@ -624,7 +556,6 @@ class Widget extends React.Component {
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)
this.props.onWidgetResizing("sw")
this.setState({dragEnabled: false})
}}
@@ -634,7 +565,6 @@ class Widget extends React.Component {
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)
this.props.onWidgetResizing("se")
this.setState({dragEnabled: false})
}}