added tree view
This commit is contained in:
@@ -28,6 +28,7 @@ import WidgetContainer from "./constants/containers"
|
||||
import { isSubClassOfWidget } from "../utils/widget"
|
||||
import { ButtonModal } from "../components/modals"
|
||||
import ResizeWidgetContainer from "./resizeContainer"
|
||||
import { WidgetContext, WidgetContextProvider } from "./context/widgetContext"
|
||||
|
||||
// const DotsBackground = require("../assets/background/dots.svg")
|
||||
|
||||
@@ -38,6 +39,7 @@ const CanvasModes = {
|
||||
MOVE_WIDGET: 2 // when the mode is move widget
|
||||
}
|
||||
|
||||
// TODO: replace this.widgetRefs with this.widgetRefs.current
|
||||
|
||||
const IS_PRODUCTION = process.env.NODE_ENV === "production"
|
||||
|
||||
@@ -64,20 +66,25 @@ class Canvas extends React.Component {
|
||||
y: 0
|
||||
}
|
||||
|
||||
this.setWidgets = null // a helper variable to help update widgets from widgetProviderContext
|
||||
this.widgetRefs = {} // a helper variable to store the widgetRef from widgetProviderContext (doesn't store a copy but just a reference pointer )
|
||||
this.widgets = [] // a helper variable to store the widgets from widgetProviderContext (doesn't store a copy but just a reference pointer )
|
||||
|
||||
this.selectedWidget = null // a helper variable that stores the reference to the selected widget fro widgetProvider
|
||||
this.setSelectedWidget = null // a helper variable that stores reference to setSelected fro widgetProvider context
|
||||
// this._contextMenuItems = []
|
||||
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: {}}]
|
||||
// widgets: [], // stores the mapping to widgetRefs, stores id and WidgetType, later used for rendering [{id: , widgetType: WidgetClass, children: [], parent: "", initialData: {}}]
|
||||
zoom: 1,
|
||||
isPanning: false,
|
||||
currentTranslate: { x: 0, y: 0 },
|
||||
canvasSize: { width: 500, height: 500 },
|
||||
|
||||
contextMenuItems: [],
|
||||
selectedWidget: null,
|
||||
// selectedWidget: null,
|
||||
|
||||
toolbarOpen: false,
|
||||
toolbarAttrs: null
|
||||
@@ -106,7 +113,7 @@ class Canvas extends React.Component {
|
||||
this.getCanvasContainerBoundingRect = this.getCanvasContainerBoundingRect.bind(this)
|
||||
this.getCanvasBoundingRect = this.getCanvasBoundingRect.bind(this)
|
||||
|
||||
this.setSelectedWidget = this.setSelectedWidget.bind(this)
|
||||
// this.setSelectedWidget = this.setSelectedWidget.bind(this)
|
||||
this.deleteSelectedWidgets = this.deleteSelectedWidgets.bind(this)
|
||||
this.removeWidget = this.removeWidget.bind(this)
|
||||
this.clearSelections = this.clearSelections.bind(this)
|
||||
@@ -128,6 +135,7 @@ class Canvas extends React.Component {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
console.log("unmounted")
|
||||
|
||||
this.canvasContainerRef.current.removeEventListener("mousedown", this.mouseDownEvent)
|
||||
this.canvasContainerRef.current.removeEventListener("mouseup", this.mouseUpEvent)
|
||||
@@ -173,7 +181,8 @@ class Canvas extends React.Component {
|
||||
*/
|
||||
getWidgets() {
|
||||
|
||||
return this.state.widgets
|
||||
// return this.widgets
|
||||
return this.widgets
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -181,7 +190,7 @@ class Canvas extends React.Component {
|
||||
* @returns Widget[]
|
||||
*/
|
||||
getActiveObjects() {
|
||||
return Object.values(this.widgetRefs).filter((widgetRef) => {
|
||||
return Object.values(this.widgetRefs.current).filter((widgetRef) => {
|
||||
return widgetRef.current?.isSelected()
|
||||
})
|
||||
}
|
||||
@@ -193,10 +202,10 @@ class Canvas extends React.Component {
|
||||
* @returns {Widget}
|
||||
*/
|
||||
getWidgetFromTarget(target) {
|
||||
// TODO: improve search, currently O(n), but can be improved via this.state.widgets or something
|
||||
// TODO: improve search, currently O(n), but can be improved via this.widgets or something
|
||||
|
||||
let innerWidget = null
|
||||
for (let [key, ref] of Object.entries(this.widgetRefs)) {
|
||||
for (let [key, ref] of Object.entries(this.widgetRefs.current)) {
|
||||
|
||||
if (ref.current === target) {
|
||||
innerWidget = ref.current
|
||||
@@ -249,17 +258,19 @@ class Canvas extends React.Component {
|
||||
if (!selectedWidget._disableSelection) {
|
||||
// console.log("selected widget2: ", selectedWidget.getId(), this.state.selectedWidget?.getId())
|
||||
|
||||
this.state.selectedWidget?.deSelect() // deselect the previous widget before adding the new one
|
||||
this.state.selectedWidget?.setZIndex(0)
|
||||
this.selectedWidget?.deSelect() // deselect the previous widget before adding the new one
|
||||
this.selectedWidget?.setZIndex(0)
|
||||
selectedWidget.setZIndex(1000)
|
||||
selectedWidget.select()
|
||||
// console.log("selected widget", selectedWidget.getToolbarAttrs(), selectedWidget, this.state.selectedWidget)
|
||||
this.setState({
|
||||
selectedWidget: selectedWidget,
|
||||
// selectedWidget: selectedWidget,
|
||||
toolbarAttrs: selectedWidget.getToolbarAttrs(),
|
||||
toolbarOpen: true
|
||||
})
|
||||
|
||||
this.setSelectedWidget(selectedWidget)
|
||||
|
||||
|
||||
// if (!this.state.selectedWidget || (selectedWidget.getId() !== this.state.selectedWidget?.getId())) {
|
||||
// this.state.selectedWidget?.deSelect() // deselect the previous widget before adding the new one
|
||||
@@ -296,14 +307,14 @@ class Canvas extends React.Component {
|
||||
} else if (event.button === 2) {
|
||||
//right click
|
||||
|
||||
if (this.state.selectedWidget && this.state.selectedWidget.__id !== selectedWidget.__id) {
|
||||
if (this.selectedWidget && this.selectedWidget.__id !== selectedWidget.__id) {
|
||||
this.clearSelections()
|
||||
}
|
||||
|
||||
if (selectedWidget) {
|
||||
|
||||
this.setSelectedWidget(selectedWidget)
|
||||
this.setState({
|
||||
selectedWidget: selectedWidget,
|
||||
// selectedWidget: selectedWidget,
|
||||
contextMenuItems: [
|
||||
{
|
||||
key: "rename",
|
||||
@@ -356,7 +367,7 @@ class Canvas extends React.Component {
|
||||
const deltaX = event.clientX - this.mousePos.x
|
||||
const deltaY = event.clientY - this.mousePos.y
|
||||
|
||||
if (!this.state.selectedWidget) {
|
||||
if (!this.selectedWidget) {
|
||||
// if there aren't any selected widgets, then pan the canvas
|
||||
this.setState(prevState => ({
|
||||
currentTranslate: {
|
||||
@@ -392,7 +403,7 @@ class Canvas extends React.Component {
|
||||
this.setState({ widgetResizing: "" })
|
||||
}
|
||||
|
||||
for (let [key, widget] of Object.entries(this.widgetRefs)) {
|
||||
for (let [key, widget] of Object.entries(this.widgetRefs.current)) {
|
||||
// since the mouseUp event is not triggered inside the widget once its outside,
|
||||
// we'll need a global mouse up event to re-enable drag
|
||||
widget.current?.enableDrag()
|
||||
@@ -414,7 +425,7 @@ class Canvas extends React.Component {
|
||||
handleResize = (event) => {
|
||||
if (this.state.resizing === "") return
|
||||
|
||||
const widget = this.state.selectedWidget
|
||||
const widget = this.selectedWidget
|
||||
|
||||
if (!widget) return
|
||||
|
||||
@@ -543,13 +554,13 @@ class Canvas extends React.Component {
|
||||
}, this.applyTransform)
|
||||
}
|
||||
|
||||
setSelectedWidget(selectedWidget) {
|
||||
this.setState({ selectedWidget: [selectedWidget] })
|
||||
}
|
||||
// setSelectedWidget(selectedWidget) {
|
||||
// this.setState({ selectedWidget: [selectedWidget] })
|
||||
// }
|
||||
|
||||
clearSelections() {
|
||||
|
||||
if (!this.state.selectedWidget)
|
||||
if (!this.selectedWidget)
|
||||
return
|
||||
|
||||
this.getActiveObjects().forEach(widget => {
|
||||
@@ -560,11 +571,13 @@ class Canvas extends React.Component {
|
||||
// this.context.updateToolAttrs({})
|
||||
|
||||
this.setState({
|
||||
selectedWidget: null,
|
||||
// selectedWidget: null,
|
||||
toolbarAttrs: null,
|
||||
toolbarOpen: false
|
||||
})
|
||||
|
||||
this.setSelectedWidget(null)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -579,7 +592,7 @@ class Canvas extends React.Component {
|
||||
let right = Number.NEGATIVE_INFINITY
|
||||
let bottom = Number.NEGATIVE_INFINITY
|
||||
|
||||
for (let widget of Object.values(this.widgetRefs)) {
|
||||
for (let widget of Object.values(this.widgetRefs.current)) {
|
||||
const rect = widget.current.getBoundingRect()
|
||||
// Update the top, left, right, and bottom coordinates
|
||||
if (rect.top < top) top = rect.top
|
||||
@@ -593,7 +606,7 @@ class Canvas extends React.Component {
|
||||
|
||||
|
||||
/**
|
||||
* finds widgets from the list of this.state.widgets, also checks the children to find the widgets
|
||||
* finds widgets from the list of this.widgets, also checks the children to find the widgets
|
||||
* @param {string} widgetId
|
||||
* @returns
|
||||
*/
|
||||
@@ -616,7 +629,7 @@ class Canvas extends React.Component {
|
||||
return null // Widget not found
|
||||
}
|
||||
|
||||
return searchWidgetById(this.state.widgets, widgetId)
|
||||
return searchWidgetById(this.widgets, widgetId)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -643,7 +656,7 @@ class Canvas extends React.Component {
|
||||
}
|
||||
|
||||
// Start the recursive removal from the top level
|
||||
return recursiveRemove(this.state.widgets)
|
||||
return recursiveRemove(this.widgets)
|
||||
|
||||
}
|
||||
|
||||
@@ -726,10 +739,10 @@ class Canvas extends React.Component {
|
||||
y: ((dragStartCursorPos.y - canvasBoundingRect.top) / this.state.zoom - this.state.currentTranslate.y) - initialPos.y
|
||||
}
|
||||
|
||||
finalPosition = {
|
||||
x: finalPosition.x - initialOffset.x - this.state.currentTranslate.x,
|
||||
y: finalPosition.y - initialOffset.y - this.state.currentTranslate.y
|
||||
}
|
||||
// finalPosition = {
|
||||
// x: finalPosition.x - initialOffset.x - this.state.currentTranslate.x,
|
||||
// y: finalPosition.y - initialOffset.y - this.state.currentTranslate.y
|
||||
// }
|
||||
|
||||
let widgetId = draggedElement.getAttribute("data-widget-id")
|
||||
|
||||
@@ -781,10 +794,10 @@ class Canvas extends React.Component {
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
this.setState({
|
||||
widgets: [...updatedWidgets, updatedChildWidget]
|
||||
})
|
||||
this.setWidgets([...updatedWidgets, updatedChildWidget])
|
||||
// this.setState({
|
||||
// widgets: [...updatedWidgets, updatedChildWidget]
|
||||
// })
|
||||
|
||||
}
|
||||
}
|
||||
@@ -811,7 +824,7 @@ class Canvas extends React.Component {
|
||||
// Only check the first level of children
|
||||
parentWidget.children.forEach((child, index) => {
|
||||
if (child.id !== dragElementID) {
|
||||
const childElement = this.widgetRefs[child.id]
|
||||
const childElement = this.widgetRefs.current[child.id]
|
||||
|
||||
if (!childElement) return
|
||||
|
||||
@@ -832,7 +845,7 @@ class Canvas extends React.Component {
|
||||
|
||||
if (closestChild && closestIndex === 1) {
|
||||
// by default the new index of the closest first element will be one and it will be zero if rect.left > dropX
|
||||
const childElement = this.widgetRefs[closestChild.id]
|
||||
const childElement = this.widgetRefs.current[closestChild.id]
|
||||
const rect = childElement.current.getBoundingRect()
|
||||
|
||||
// Closest is first child, check if dropped before or after
|
||||
@@ -855,9 +868,11 @@ class Canvas extends React.Component {
|
||||
childrenCopy.splice(targetIndex, 0, draggedItem) // Insert at the target index
|
||||
|
||||
const updatedParent = { ...parentWidget, children: childrenCopy }
|
||||
this.setState(prevState => ({
|
||||
widgets: this.updateWidgetRecursively(prevState.widgets, updatedParent, {})
|
||||
}))
|
||||
// this.setState(prevState => ({
|
||||
// widgets: this.updateWidgetRecursively(prevState.widgets, updatedParent, {})
|
||||
// }))
|
||||
|
||||
this.setWidgets(this.updateWidgetRecursively(this.widgets, updatedParent, {}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -880,11 +895,11 @@ class Canvas extends React.Component {
|
||||
// console.log("Drag widget obj: ", dragWidgetObj, dropWidgetObj)
|
||||
|
||||
if (dropWidgetObj && dragWidgetObj) {
|
||||
const dragWidget = this.widgetRefs[dragWidgetObj.id]
|
||||
const dragWidget = this.widgetRefs.current[dragWidgetObj.id]
|
||||
const dragData = dragWidget.current.serialize()
|
||||
|
||||
|
||||
const parentWidget = this.widgetRefs[parentWidgetId].current
|
||||
const parentWidget = this.widgetRefs.current[parentWidgetId].current
|
||||
const parentRect = parentWidget.getBoundingRect()
|
||||
const { clientX, clientY } = event
|
||||
|
||||
@@ -943,7 +958,7 @@ class Canvas extends React.Component {
|
||||
...dragData,
|
||||
positionType: parentLayout === Layouts.PLACE ? PosType.ABSOLUTE : PosType.NONE,
|
||||
parentLayout: parentWidget.getLayout() || null, // pass everything about the parent layout
|
||||
parentWidgetRef: this.widgetRefs[parentWidgetId],
|
||||
parentWidgetRef: this.widgetRefs.current[parentWidgetId],
|
||||
zIndex: 0,
|
||||
pos: { x: finalPosition.x, y: finalPosition.y },
|
||||
widgetContainer: WidgetContainer.WIDGET
|
||||
@@ -959,20 +974,37 @@ class Canvas extends React.Component {
|
||||
// Recursively update the widget structure
|
||||
updatedWidgets = this.updateWidgetRecursively(updatedWidgets, updatedDropWidget, updatedDragWidget)
|
||||
|
||||
// Update the state with the new widget hierarchy
|
||||
this.setState({
|
||||
widgets: updatedWidgets
|
||||
}, () => {
|
||||
|
||||
if (parentLayout !== Layouts.PLACE){
|
||||
this.setWidgets(updatedWidgets)
|
||||
|
||||
setTimeout(() => {
|
||||
if (parentLayout !== Layouts.PLACE) {
|
||||
// swap only for grid and flex placements
|
||||
const swapClosest = this.__checkClosestShiftElement({ event, parentWidgetId, dragElementID })
|
||||
|
||||
if (swapClosest.closestChild){
|
||||
const swapClosest = this.__checkClosestShiftElement({
|
||||
event,
|
||||
parentWidgetId,
|
||||
dragElementID,
|
||||
})
|
||||
|
||||
if (swapClosest.closestChild) {
|
||||
this.shiftWidgetPosition(parentWidgetId, dragElementID, swapClosest.closestIndex)
|
||||
}
|
||||
}
|
||||
})
|
||||
}, 1)
|
||||
|
||||
// Update the state with the new widget hierarchy
|
||||
// this.setState({
|
||||
// widgets: updatedWidgets
|
||||
// }, () => {
|
||||
|
||||
// if (parentLayout !== Layouts.PLACE){
|
||||
// // swap only for grid and flex placements
|
||||
// const swapClosest = this.__checkClosestShiftElement({ event, parentWidgetId, dragElementID })
|
||||
|
||||
// if (swapClosest.closestChild){
|
||||
// this.shiftWidgetPosition(parentWidgetId, dragElementID, swapClosest.closestIndex)
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
|
||||
}
|
||||
|
||||
@@ -995,7 +1027,11 @@ class Canvas extends React.Component {
|
||||
const id = `${widgetComponentType.widgetType}_${UID()}`
|
||||
|
||||
// Store the ref in the instance variable
|
||||
this.widgetRefs[id] = widgetRef
|
||||
this.widgetRefs.current[id] = widgetRef
|
||||
|
||||
console.log("widget ref: ", widgetRef)
|
||||
|
||||
// this.setWidgetRefs({...this.widgetRefs.current, [id]: widgetRef})
|
||||
|
||||
const newWidget = {
|
||||
id,
|
||||
@@ -1005,25 +1041,36 @@ class Canvas extends React.Component {
|
||||
initialData: {} // useful for serializing and deserializing (aka, saving and loading)
|
||||
}
|
||||
|
||||
const widgets = [...this.state.widgets, newWidget] // don't add the widget refs in the state
|
||||
const widgets = [...this.widgets, newWidget] // don't add the widget refs in the state
|
||||
|
||||
this.setWidgets(widgets)
|
||||
|
||||
// Update the state to include the new widget's type and ID
|
||||
this.setState({
|
||||
widgets: widgets
|
||||
}, () => {
|
||||
setTimeout(() => {
|
||||
if (callback)
|
||||
callback({ id, widgetRef })
|
||||
|
||||
if (this._onWidgetListUpdated)
|
||||
this._onWidgetListUpdated(widgets) // inform the parent container
|
||||
})
|
||||
}, 1)
|
||||
|
||||
// Update the state to include the new widget's type and ID
|
||||
// this.setState({
|
||||
// widgets: widgets
|
||||
// }, () => {
|
||||
// if (callback)
|
||||
// callback({ id, widgetRef })
|
||||
|
||||
// if (this._onWidgetListUpdated)
|
||||
// this._onWidgetListUpdated(widgets) // inform the parent container
|
||||
// })
|
||||
|
||||
return { id, widgetRef }
|
||||
}
|
||||
|
||||
getWidgetById(id) {
|
||||
console.log("ID: ", id, this.widgetRefs.current)
|
||||
|
||||
return this.widgetRefs[id]
|
||||
return this.widgetRefs.current[id]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1032,12 +1079,14 @@ class Canvas extends React.Component {
|
||||
*/
|
||||
deleteSelectedWidgets(widgets = []) {
|
||||
|
||||
let activeWidgets = removeDuplicateObjects([...widgets, this.state.selectedWidget], "__id")
|
||||
let activeWidgets = removeDuplicateObjects([...widgets, this.selectedWidget], "__id")
|
||||
|
||||
this.selectedWidget(null)
|
||||
|
||||
this.setState({
|
||||
toolbarAttrs: null,
|
||||
toolbarOpen: false,
|
||||
selectedWidget: null
|
||||
// selectedWidget: null
|
||||
})
|
||||
|
||||
const widgetIds = activeWidgets.map(widget => widget.__id)
|
||||
@@ -1055,10 +1104,12 @@ class Canvas extends React.Component {
|
||||
|
||||
// NOTE: Don't remove from it using remove() function since, it already removed from the DOM tree when its removed from widgets
|
||||
|
||||
this.widgetRefs = {}
|
||||
this.setState({
|
||||
widgets: []
|
||||
})
|
||||
this.widgetRefs.current = {}
|
||||
this.setWidgets([])
|
||||
|
||||
// this.setState({
|
||||
// widgets: []
|
||||
// })
|
||||
|
||||
if (this._onWidgetListUpdated)
|
||||
this._onWidgetListUpdated([])
|
||||
@@ -1083,18 +1134,19 @@ class Canvas extends React.Component {
|
||||
return null // Return null if not found
|
||||
}
|
||||
|
||||
return recursiveFind(this.state.widgets)
|
||||
return recursiveFind(this.widgets)
|
||||
}
|
||||
|
||||
removeWidget(widgetId) {
|
||||
|
||||
|
||||
delete this.widgetRefs[widgetId]
|
||||
delete this.widgetRefs.current[widgetId]
|
||||
|
||||
const widgets = this.removeWidgetFromCurrentList(widgetId)
|
||||
this.setState({
|
||||
widgets: widgets
|
||||
})
|
||||
// this.setState({
|
||||
// widgets: widgets
|
||||
// })
|
||||
this.setWidgets(widgets)
|
||||
|
||||
if (this._onWidgetListUpdated)
|
||||
this._onWidgetListUpdated(widgets)
|
||||
@@ -1102,7 +1154,7 @@ class Canvas extends React.Component {
|
||||
|
||||
onActiveWidgetUpdate(widgetId) {
|
||||
|
||||
if (!this.state.selectedWidget || widgetId !== this.state.selectedWidget.__id)
|
||||
if (!this.selectedWidget || widgetId !== this.selectedWidget.__id)
|
||||
return
|
||||
|
||||
// console.log("updating...", this.state.toolbarAttrs, this.state.selectedWidget.getToolbarAttrs())
|
||||
@@ -1110,7 +1162,7 @@ class Canvas extends React.Component {
|
||||
// console.log("attrs: ", this.state.selectedWidgets.at(0).getToolbarAttrs())
|
||||
|
||||
this.setState({
|
||||
toolbarAttrs: this.state.selectedWidget.getToolbarAttrs()
|
||||
toolbarAttrs: this.selectedWidget.getToolbarAttrs()
|
||||
})
|
||||
|
||||
}
|
||||
@@ -1126,7 +1178,7 @@ class Canvas extends React.Component {
|
||||
if (!parent) return
|
||||
|
||||
for (let child of parent.children) {
|
||||
this.widgetRefs[child.id].current.setParentLayout(parentLayout)
|
||||
this.widgetRefs.current[child.id].current.setParentLayout(parentLayout)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1134,7 +1186,6 @@ class Canvas extends React.Component {
|
||||
|
||||
renderWidget = (widget) => {
|
||||
|
||||
// FIXME: initial data parentWidgetRef is empty
|
||||
const { id, widgetType: ComponentType, children = [], parent, initialData = {} } = widget
|
||||
|
||||
const renderChildren = (childrenData) => {
|
||||
@@ -1153,7 +1204,7 @@ class Canvas extends React.Component {
|
||||
<ComponentType
|
||||
key={id}
|
||||
id={id}
|
||||
ref={this.widgetRefs[id]}
|
||||
ref={this.widgetRefs.current[id]}
|
||||
initialData={initialData}
|
||||
parentWidgetRef={initialData.parentWidgetRef || null}
|
||||
canvasRef={this.canvasContainerRef}
|
||||
@@ -1162,6 +1213,8 @@ class Canvas extends React.Component {
|
||||
zoom: this.state.zoom,
|
||||
pan: this.state.currentTranslate
|
||||
}}
|
||||
|
||||
onWidgetDeleteRequest={this.removeWidget}
|
||||
|
||||
onWidgetUpdate={this.onActiveWidgetUpdate}
|
||||
onAddChildWidget={this.handleAddWidgetChild}
|
||||
@@ -1180,76 +1233,86 @@ class Canvas extends React.Component {
|
||||
render() {
|
||||
|
||||
return (
|
||||
<div className="tw-relative tw-overflow-hidden tw-flex tw-w-full tw-h-full tw-max-h-[100vh]">
|
||||
<WidgetContext.Consumer>
|
||||
{
|
||||
({ widgets, setWidgets, widgetRefs, activeWidget, setActiveWidget }) => {
|
||||
|
||||
<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">
|
||||
this.widgets = widgets
|
||||
this.setWidgets = setWidgets
|
||||
// this.setWidgetRefs = setWidgetRefs
|
||||
this.widgetRefs = widgetRefs
|
||||
this.selectedWidget = activeWidget
|
||||
this.setSelectedWidget = setActiveWidget
|
||||
|
||||
<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"
|
||||
>
|
||||
<Tooltip title="Clear canvas">
|
||||
<Button danger icon={<DeleteOutlined />} />
|
||||
</Tooltip>
|
||||
</ButtonModal>
|
||||
</div>
|
||||
return (<div className="tw-relative tw-overflow-hidden tw-flex tw-w-full tw-h-full tw-max-h-[100vh]">
|
||||
|
||||
{/* <ActiveWidgetProvider> */}
|
||||
<DroppableWrapper id="canvas-droppable"
|
||||
droppableTags={{ exclude: ["image", "video"] }}
|
||||
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 ${!IS_PRODUCTION ? "tw-bg-red-300" : "tw-bg-transparent"} 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 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} />
|
||||
</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"
|
||||
>
|
||||
<Tooltip title="Clear canvas">
|
||||
<Button danger icon={<DeleteOutlined />} />
|
||||
</Tooltip>
|
||||
</ButtonModal>
|
||||
</div>
|
||||
|
||||
{/* { this.state.selectedWidget &&
|
||||
<ResizeWidgetContainer selectedWidget={this.state.selectedWidget}/>
|
||||
} */}
|
||||
{/* <ActiveWidgetProvider> */}
|
||||
<DroppableWrapper id="canvas-droppable"
|
||||
droppableTags={{ exclude: ["image", "video"] }}
|
||||
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 ${!IS_PRODUCTION ? "tw-bg-red-300" : "tw-bg-transparent"} tw-top-0 tw-select-none`}
|
||||
ref={this.canvasRef}>
|
||||
<div className="tw-relative tw-w-full tw-h-full">
|
||||
{
|
||||
this.widgets.map(this.renderWidget)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dropdown>
|
||||
{/* </DragWidgetProvider> */}
|
||||
</DroppableWrapper>
|
||||
|
||||
<CanvasToolBar isOpen={this.state.toolbarOpen}
|
||||
widgetType={this.selectedWidget?.getWidgetType() || ""}
|
||||
attrs={this.state.toolbarAttrs}
|
||||
/>
|
||||
{/* </ActiveWidgetProvider> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dropdown>
|
||||
{/* </DragWidgetProvider> */}
|
||||
</DroppableWrapper>
|
||||
|
||||
<CanvasToolBar isOpen={this.state.toolbarOpen}
|
||||
widgetType={this.state.selectedWidget?.getWidgetType() || ""}
|
||||
attrs={this.state.toolbarAttrs}
|
||||
/>
|
||||
{/* </ActiveWidgetProvider> */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
</WidgetContext.Consumer>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
import React, { createContext, useContext, useState } from 'react';
|
||||
import React, { createContext, useContext, useEffect, useRef, useState } from 'react'
|
||||
|
||||
const widgetContext = createContext()
|
||||
export const WidgetContext = createContext()
|
||||
|
||||
export const useSelectedWidgetContext = () => useContext(widgetContext)
|
||||
export const useWidgetContext = () => useContext(WidgetContext)
|
||||
|
||||
|
||||
export const WidgetContextProvider = ({ children }) => {
|
||||
const [widgets, setWidgets] = useState(null)
|
||||
|
||||
const [activeWidget, setActiveWidget] = useState(null)
|
||||
const [widgets, setWidgets] = useState([]) // stores the mapping to widgetRefs, stores id and WidgetType, later used for rendering [{id: , widgetType: WidgetClass, children: [], parent: "", initialData: {}}]
|
||||
|
||||
// don't useState here because the refs are changing often
|
||||
const widgetRefs = useRef({}) // stores the actual refs to the widgets inside the canvas {id: ref, id2, ref2...}
|
||||
|
||||
const [widgetRef, setWidgetRef] = useState({})
|
||||
// const []
|
||||
|
||||
return (
|
||||
<widgetContext.Provider value={{ widgets, setWidgets, widgetRef, setWidgetRef }}>
|
||||
<WidgetContext.Provider value={{ widgets, setWidgets, widgetRefs,
|
||||
activeWidget, setActiveWidget }}>
|
||||
{children}
|
||||
</widgetContext.Provider>
|
||||
</WidgetContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -75,6 +75,8 @@ class Widget extends React.Component {
|
||||
this.state = {
|
||||
zIndex: 0,
|
||||
selected: false,
|
||||
isWidgetVisible: true,
|
||||
|
||||
widgetName: widgetName || 'widget', // this will later be converted to variable name
|
||||
enableRename: false, // will open the widgets editable div for renaming
|
||||
|
||||
@@ -177,6 +179,7 @@ class Widget extends React.Component {
|
||||
|
||||
// this.openRenaming = this.openRenaming.bind(this)
|
||||
|
||||
// this.isWidgetVisible = true // widget is visible in viewport
|
||||
this.isSelected = this.isSelected.bind(this)
|
||||
|
||||
this.setPos = this.setPos.bind(this)
|
||||
@@ -334,6 +337,26 @@ class Widget extends React.Component {
|
||||
|
||||
}
|
||||
|
||||
deleteWidget = () => {
|
||||
this.props.onWidgetDeleteRequest(this.__id)
|
||||
}
|
||||
|
||||
isWidgetVisible = () => {
|
||||
return this.state.isWidgetVisible
|
||||
}
|
||||
|
||||
hideFromViewport = () => {
|
||||
this.setState({
|
||||
isWidgetVisible: false
|
||||
})
|
||||
}
|
||||
|
||||
unHideFromViewport = () => {
|
||||
this.setState({
|
||||
isWidgetVisible: true
|
||||
})
|
||||
}
|
||||
|
||||
getVariableName() {
|
||||
return toSnakeCase(this.state.widgetName)
|
||||
}
|
||||
@@ -997,7 +1020,6 @@ class Widget extends React.Component {
|
||||
const thisContainer = this.elementRef.current.getAttribute("data-container")
|
||||
// console.log("Dropped as swappable: ", e.target, this.swappableAreaRef.current.contains(e.target))
|
||||
// If swaparea is true, then it swaps instead of adding it as a child, also make sure that the parent widget(this widget) is on the widget and not on the canvas
|
||||
const swapArea = false // (this.swappableAreaRef.current.contains(e.target) && !this.innerAreaRef.current.contains(e.target) && thisContainer === WidgetContainer.WIDGET)
|
||||
|
||||
const dragEleType = draggedElement.getAttribute("data-draggable-type")
|
||||
|
||||
@@ -1006,8 +1028,8 @@ class Widget extends React.Component {
|
||||
(this.droppableTags.exclude?.length > 0 && !this.droppableTags.exclude?.includes(dragEleType))
|
||||
))
|
||||
|
||||
if (!allowDrop && !swapArea) {
|
||||
// only if both swap and drop is not allowed return, if swap is allowed continue
|
||||
if (!allowDrop) {
|
||||
// only if drop is not allowed return, if swap is allowed continue
|
||||
return
|
||||
}
|
||||
// TODO: check if the drop is allowed
|
||||
@@ -1203,14 +1225,13 @@ class Widget extends React.Component {
|
||||
|
||||
const {zoom: canvasZoom, pan: canvasPan} = this.canvasMetaData
|
||||
|
||||
|
||||
return (
|
||||
|
||||
<DragContext.Consumer>
|
||||
{
|
||||
({ draggedElement, widgetClass, onDragStart, onDragEnd, overElement, setOverElement, posMetaData, setPosMetaData }) => {
|
||||
|
||||
const canvasRect = this.canvas.getBoundingClientRect()
|
||||
// const canvasRect = this.canvas.getBoundingClientRect()
|
||||
const canvasRectInner = this.props.canvasInnerContainerRef?.current.getBoundingClientRect()
|
||||
|
||||
const elementRect = this.getBoundingRect()
|
||||
@@ -1222,7 +1243,7 @@ class Widget extends React.Component {
|
||||
return (
|
||||
<div data-widget-id={this.__id}
|
||||
ref={this.elementRef}
|
||||
className="tw-shadow-xl tw-w-fit tw-h-fit"
|
||||
className={`tw-shadow-xl tw-w-fit tw-h-fit ${!this.state.isWidgetVisible ? "tw-hidden" : ""}`}
|
||||
style={outerStyle}
|
||||
data-draggable-type={this.getWidgetType()} // helps with droppable
|
||||
data-container={this.state.widgetContainer} // indicates how the canvas should handle dragging, one is sidebar other is canvas
|
||||
@@ -1243,8 +1264,6 @@ class Widget extends React.Component {
|
||||
// onPointerDown={setInitialPos}
|
||||
onPointerDown={(e) => handleSetInitialPosition(e, setPosMetaData)}
|
||||
>
|
||||
{/* FIXME: Swappable when the parent layout is flex/grid and gap is more, this trick won't work, add bg color to check */}
|
||||
{/* FIXME: Swappable, when the parent layout is gap is 0, it doesn't work well */}
|
||||
<div className="tw-relative tw-w-full tw-h-full tw-top-0 tw-left-0"
|
||||
|
||||
>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useMemo, useRef } from "react"
|
||||
import { memo, useEffect, useMemo, useRef, useState } from "react"
|
||||
import Draggable from "./utils/draggableDnd"
|
||||
|
||||
import { Button } from "antd"
|
||||
@@ -7,7 +7,8 @@ import { GithubOutlined, GitlabOutlined, LinkOutlined,
|
||||
DeleteFilled,
|
||||
DeleteOutlined,
|
||||
GlobalOutlined,
|
||||
EyeOutlined} from "@ant-design/icons"
|
||||
EyeOutlined,
|
||||
EyeInvisibleOutlined} from "@ant-design/icons"
|
||||
|
||||
import DraggableWrapper from "./draggable/draggable"
|
||||
|
||||
@@ -153,19 +154,38 @@ export function DraggableAssetCard({file, onDelete}){
|
||||
}
|
||||
|
||||
|
||||
export function TreeViewCard({widgetId, onDelete, title}){
|
||||
export const TreeViewCard = memo(({widgetRef, title, isTopLevel}) => {
|
||||
|
||||
const [widgetVisible, setWidgetVisible] = useState(widgetRef.current.isWidgetVisible)
|
||||
|
||||
const onDelete = () => {
|
||||
widgetRef.current.deleteWidget()
|
||||
}
|
||||
|
||||
const toggleHideShowWidget = () => {
|
||||
|
||||
setWidgetVisible(!widgetRef.current.isWidgetVisible())
|
||||
|
||||
if (widgetRef.current.isWidgetVisible())
|
||||
widgetRef.current.hideFromViewport()
|
||||
else{
|
||||
widgetRef.current.unHideFromViewport()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="tw-flex tw-place-items-center tw-px-2 tw-p-1 tw-place-content-between
|
||||
tw-gap-4 tw-w-full" style={{width: "100%"}}>
|
||||
<div className="tw-text-sm">
|
||||
<div className={`tw-text-sm ${isTopLevel ? "tw-font-medium" : ""}`}>
|
||||
{title}
|
||||
</div>
|
||||
|
||||
<div className="tw-ml-auto tw-flex tw-gap-1">
|
||||
<Button color="danger" size="small" variant="text" danger icon={<DeleteOutlined />}></Button>
|
||||
<Button variant="text" size="small" icon={<EyeOutlined />}></Button>
|
||||
<Button color="danger" title="delete" onClick={onDelete} size="small" variant="text" danger
|
||||
icon={<DeleteOutlined />}></Button>
|
||||
<Button variant="text" title="hide" onClick={toggleHideShowWidget} size="small"
|
||||
icon={widgetVisible ? <EyeInvisibleOutlined/> : <EyeOutlined />}></Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -4,66 +4,45 @@ import { CloseCircleFilled, SearchOutlined } from "@ant-design/icons"
|
||||
|
||||
import { SidebarWidgetCard, TreeViewCard } from "../components/cards"
|
||||
|
||||
import ButtonWidget from "../assets/widgets/button.png"
|
||||
|
||||
import { filterObjectListStartingWith } from "../utils/filter"
|
||||
import Widget from "../canvas/widgets/base"
|
||||
import { SearchComponent } from "../components/inputs"
|
||||
import { Tree } from "antd"
|
||||
import { TreeNode } from "antd/es/tree-select"
|
||||
import { useWidgetContext } from "../canvas/context/widgetContext"
|
||||
|
||||
|
||||
|
||||
function transformWidgets(widgets, widgetRefs, isTopLevel=true) {
|
||||
// console.log("Wdiegts refs: ", widgetRefs)
|
||||
return widgets.map(widget => ({
|
||||
title: widget.widgetType.name, // Assuming widgetType is a class
|
||||
key: widget.id,
|
||||
isTopLevel: isTopLevel,
|
||||
widgetRef: widgetRefs.current[widget.id],
|
||||
children: widget.children.length > 0 ? transformWidgets(widget.children, widgetRefs, false) : []
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {function} onWidgetsUpdate - this is a callback that will be called once the sidebar is populated with widgets
|
||||
* @returns
|
||||
*/
|
||||
function TreeviewContainer({ sidebarContent, onWidgetsUpdate }) {
|
||||
function TreeviewContainer() {
|
||||
|
||||
|
||||
const [searchValue, setSearchValue] = useState("")
|
||||
const [widgetData, setWidgetData] = useState(sidebarContent)
|
||||
const {widgets, widgetRefs} = useWidgetContext()
|
||||
|
||||
const transformedContent = useMemo(() => {
|
||||
return (transformWidgets(widgets, widgetRefs))
|
||||
}, [widgets, widgetRefs])
|
||||
|
||||
const treeData = [
|
||||
{
|
||||
title: 'Parent',
|
||||
key: '0-0',
|
||||
children: [
|
||||
{ title: 'Child 1', key: '0-0-1' },
|
||||
{ title: 'Child 2', key: '0-0-2' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
setWidgetData(sidebarContent)
|
||||
// if (onWidgetsUpdate){
|
||||
// onWidgetsUpdate(widgets)
|
||||
// }
|
||||
|
||||
}, [sidebarContent])
|
||||
|
||||
|
||||
|
||||
// useEffect(() => {
|
||||
|
||||
// if (searchValue.length > 0) {
|
||||
// const searchData = filterObjectListStartingWith(sidebarContent, "name", searchValue)
|
||||
// setWidgetData(searchData)
|
||||
// } else {
|
||||
// setWidgetData(sidebarContent)
|
||||
// }
|
||||
|
||||
// }, [searchValue])
|
||||
|
||||
function onSearch(event) {
|
||||
|
||||
setSearchValue(event.target.value)
|
||||
const topLevelKeys = transformedContent.filter(cont => cont.isTopLevel).map(cont => cont.key)
|
||||
|
||||
const onDeleteRequest = (widgetId) => {
|
||||
widgetRefs.current[widgetId].current?.deleteWidget()
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className="tw-w-full tw-p-2 tw-gap-4 tw-flex tw-flex-col tw-overflow-x-hidden">
|
||||
|
||||
@@ -71,10 +50,14 @@ function TreeviewContainer({ sidebarContent, onWidgetsUpdate }) {
|
||||
onClear={() => setSearchValue("")} /> */}
|
||||
<div className="tw-flex tw-flex-col tw-gap-2 tw-w-full tw-h-full tw-p-1">
|
||||
|
||||
<Tree treeData={treeData}
|
||||
<Tree treeData={transformedContent}
|
||||
titleRender={(nodeData) =>
|
||||
<TreeViewCard title={nodeData.title}/>
|
||||
|
||||
<TreeViewCard widgetId={nodeData.id} title={nodeData.title}
|
||||
widgetRef={nodeData.widgetRef}
|
||||
isTopLevel={nodeData.isTopLevel}/>
|
||||
}
|
||||
defaultExpandedKeys={topLevelKeys}
|
||||
>
|
||||
|
||||
</Tree>
|
||||
|
||||
Reference in New Issue
Block a user