From 03a4984cbca0810afdd0021055b168b8c7e2066e Mon Sep 17 00:00:00 2001 From: paul Date: Mon, 3 Mar 2025 19:17:28 +0530 Subject: [PATCH] working on dnd inside a container --- src/canvas/canvas.js | 5 - src/canvas/widgets/base.js | 247 ++++++++++--------- src/canvas/widgets/widgetDnd.js | 236 ++++++++++++++++++ src/components/cards.js | 1 + src/components/draggable/dnd/draggableDnd.js | 2 +- src/utils/dndkit/plugins.js | 47 ++++ 6 files changed, 417 insertions(+), 121 deletions(-) create mode 100644 src/canvas/widgets/widgetDnd.js create mode 100644 src/utils/dndkit/plugins.js diff --git a/src/canvas/canvas.js b/src/canvas/canvas.js index 03bc17a..903ff39 100644 --- a/src/canvas/canvas.js +++ b/src/canvas/canvas.js @@ -191,8 +191,6 @@ class Canvas extends React.Component { getWidgetFromTarget(target) { // TODO: improve search, currently O(n), but can be improved via this.state.widgets or something - console.log("Target: ", target, ) - let innerWidget = null for (let [key, ref] of Object.entries(this.widgetRefs)) { @@ -200,9 +198,6 @@ class Canvas extends React.Component { innerWidget = ref.current break } - - console.log("ref: ", ref.current) - // console.log("refs: ", ref) // TODO: remove the ref.current? if there are bugs it would become hard to debug if (ref.current?.getElement()?.contains(target)) { diff --git a/src/canvas/widgets/base.js b/src/canvas/widgets/base.js index 2231965..bf6c0a1 100644 --- a/src/canvas/widgets/base.js +++ b/src/canvas/widgets/base.js @@ -13,6 +13,7 @@ import { DragContext } from "../../components/draggable/draggableContext" import { isNumeric, removeKeyFromObject } from "../../utils/common" import { info } from "autoprefixer" import { message } from "antd" +import WidgetDnd from "./widgetDnd" // TODO: make it possible to apply widgetInnerStyle on load @@ -1104,9 +1105,9 @@ class Widget extends React.Component { ...this.state.widgetOuterStyling, cursor: this.cursor, zIndex: this.state.zIndex, - position: this.state.positionType, // don't change this if it has to be movable on the canvas - top: `${this.state.pos.y}px`, - left: `${this.state.pos.x}px`, + // position: this.state.positionType, // don't change this if it has to be movable on the canvas + // top: `${this.state.pos.y}px`, + // left: `${this.state.pos.x}px`, width: width, height: height, minWidth: minWidth, @@ -1115,7 +1116,7 @@ class Widget extends React.Component { } // const boundingRect = this.getBoundingRect - + // TODO: rewrite Drag and drop // FIXME: if the parent container has tw-overflow-none, then the resizable indicator are also hidden return ( @@ -1126,137 +1127,153 @@ class Widget extends React.Component { return ( -
this.handleDragOver(e, draggedElement)} + // onDrop={(e) => {this.handleDropEvent(e, draggedElement, widgetClass); onDragEnd()}} - draggable={this.state.dragEnabled} + // onDragEnter={(e) => this.handleDragEnter(e, draggedElement, setOverElement)} + // onDragLeave={(e) => this.handleDragLeave(e, draggedElement, overElement)} - onDragOver={(e) => this.handleDragOver(e, draggedElement)} - onDrop={(e) => {this.handleDropEvent(e, draggedElement, widgetClass); onDragEnd()}} + // onDragStart={(e) => this.handleDragStart(e, onDragStart)} + // onDragEnd={(e) => this.handleDragEnd(onDragEnd)} + // style={outerStyle} - onDragEnter={(e) => this.handleDragEnter(e, draggedElement, setOverElement)} - onDragLeave={(e) => this.handleDragLeave(e, draggedElement, overElement)} - - onDragStart={(e) => this.handleDragStart(e, onDragStart)} - onDragEnd={(e) => this.handleDragEnd(onDragEnd)} - > - {/* 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 */} -
- -
- {/* helps with swappable: if the mouse is in this area while hovering/dropping, then swap */} -
- -
- {this.renderContent()} -
- { - // show drop style on drag hover - draggedElement && this.state.showDroppableStyle.show && -
this.handleDragOver(e, draggedElement)} + // onDrop={(e) => {this.handleDropEvent(e, draggedElement, widgetClass); onDragEnd()}} + + // onDragEnter={(e) => this.handleDragEnter(e, draggedElement, setOverElement)} + // onDragLeave={(e) => this.handleDragLeave(e, draggedElement, overElement)} + + // onDragStart={(e) => this.handleDragStart(e, onDragStart)} + // onDragEnd={(e) => this.handleDragEnd(onDragEnd)} + > + {/* 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 */} +
+ +
+ {/* helps with swappable: if the mouse is in this area while hovering/dropping, then swap */}
- } - {/* FIXME: the resize handles get clipped in parent container */} -
-
{/* ${this.state.isDragging ? "tw-pointer-events-none" : "tw-pointer-events-auto"} */} - +
+ {this.renderContent()} +
+ { + // show drop style on drag hover + draggedElement && this.state.showDroppableStyle.show && +
{ - e.stopPropagation() - e.preventDefault() - this.props.onWidgetResizing("nw") - this.setState({ dragEnabled: false }) + `} + style={{ + width: "calc(100% + 10px)", + height: "calc(100% + 10px)", }} - onMouseUp={() => this.setState({ dragEnabled: true })} - /> -
{ - e.stopPropagation() - e.preventDefault() - this.props.onWidgetResizing("ne") - this.setState({ dragEnabled: false }) - }} - onMouseUp={() => this.setState({ dragEnabled: true })} - /> -
{ - e.stopPropagation() - e.preventDefault() - this.props.onWidgetResizing("sw") - this.setState({ dragEnabled: false }) - }} - onMouseUp={() => this.setState({ dragEnabled: true })} - /> -
{ - e.stopPropagation() - e.preventDefault() - this.props.onWidgetResizing("se") - this.setState({ dragEnabled: false }) - }} - onMouseUp={() => this.setState({ dragEnabled: true })} - /> + > +
+ } + {/* FIXME: the resize handles get clipped in parent container */} +
+ +
{/* ${this.state.isDragging ? "tw-pointer-events-none" : "tw-pointer-events-auto"} */} + + +
{ + e.stopPropagation() + e.preventDefault() + this.props.onWidgetResizing("nw") + this.setState({ dragEnabled: false }) + }} + onMouseUp={() => this.setState({ dragEnabled: true })} + /> +
{ + e.stopPropagation() + e.preventDefault() + this.props.onWidgetResizing("ne") + this.setState({ dragEnabled: false }) + }} + onMouseUp={() => this.setState({ dragEnabled: true })} + /> +
{ + e.stopPropagation() + e.preventDefault() + this.props.onWidgetResizing("sw") + this.setState({ dragEnabled: false }) + }} + onMouseUp={() => this.setState({ dragEnabled: true })} + /> +
{ + e.stopPropagation() + e.preventDefault() + this.props.onWidgetResizing("se") + this.setState({ dragEnabled: false }) + }} + onMouseUp={() => this.setState({ dragEnabled: true })} + /> + +
+
- -
-
- ) + + ) } } diff --git a/src/canvas/widgets/widgetDnd.js b/src/canvas/widgets/widgetDnd.js new file mode 100644 index 0000000..1462eb5 --- /dev/null +++ b/src/canvas/widgets/widgetDnd.js @@ -0,0 +1,236 @@ +import React, { useEffect, useRef, useState } from 'react' +import { useDragDropManager, useDroppable, useDraggable } from '@dnd-kit/react' +import { useDragContext } from '../../components/draggable/draggableContext' + + +// FIXME: fix the issue of self drop +function WidgetDnd({droppableTags, + ...props}) { + + const dndRef = useRef(null) + + const {dragElementType} = props + + const { draggedElement, setOverElement, widgetClass } = useDragContext() + + const { ref: dropRef, isDropTarget, droppable} = useDroppable({ + id: props.id, + // accept: (draggable) => { + + // const allowDrop = (droppableTags && droppableTags !== null && (Object.keys(droppableTags).length === 0 || + // (droppableTags.include?.length > 0 && droppableTags.include?.includes(draggable.type)) || + // (droppableTags.exclude?.length > 0 && !droppableTags.exclude?.includes(draggable.type)) + // )) + + // return allowDrop + // } + }) + + const { ref: dragRef, draggable } = useDraggable({ + id: dragElementType, + feedback: "default", + type: dragElementType, + disabled: props.disabled, + + // data: { title: props.children } + }) + + + const manager = useDragDropManager() + + // const {} + const {onDragStart, onDragEnd, disableStyle=false} = useDragContext() + + const [allowDrop, setAllowDrop] = useState({show: false, allow: false}) // indicator if the draggable can be dropped on the droppable + + useEffect(() => { + + manager?.monitor?.addEventListener("dragstart", handleDragEnter) + manager?.monitor?.addEventListener("dragend", handleDropEvent) + manager?.monitor?.addEventListener("dragmove", handleDragOver) + // manager?.monitor?.addEventListener("dragover", onDragEndhandleDragOver) + + + return () => { + manager?.monitor?.removeEventListener("dragstart", handleDragEnter) + manager?.monitor?.removeEventListener("dragend", handleDropEvent) + manager?.monitor?.removeEventListener("dragmove", handleDragOver) + } + }, [manager, draggedElement]) + + + const handleRef = (node) => { + dndRef.current = node + dropRef(node) + dragRef(node) + } + + const handleDragEnter = (e) => { + + const {target, source} = e.operation + + console.log("is drag source: ", props.id, draggable.isDragSource) + + + if (draggable.isDragSource){ + onDragStart(dndRef?.current, widgetClass) + + } + + if (target && target?.id !== props?.id){ + 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 dragElementType = draggedElement.getAttribute("data-draggable-type") + + setOverElement(document.getElementById(source.id)) + + const dropAllowed = (droppableTags && droppableTags !== null && (Object.keys(droppableTags).length === 0 || + (droppableTags.include?.length > 0 && droppableTags.include?.includes(dragElementType)) || + (droppableTags.exclude?.length > 0 && !droppableTags.exclude?.includes(dragElementType)) + )) + + console.log("droppable tags: ", dropAllowed) + + + setAllowDrop({allow: dropAllowed, show: true}) + + } + + const handleDragOver = (e) => { + + const {target} = e.operation + + if (target && target?.id !== props?.id){ + return + } + // console.log("Over sir1: ", draggedElement) + + + 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 + } + + // console.log("Drag over: ", e.dataTransfer.getData("text/plain"), e.dataTransfer) + const dragElementType = draggedElement.getAttribute("data-draggable-type") + + const dropAllowed = (droppableTags && droppableTags !== null && (Object.keys(droppableTags).length === 0 || + (droppableTags.include?.length > 0 && droppableTags.include?.includes(dragElementType)) || + (droppableTags.exclude?.length > 0 && !droppableTags.exclude?.includes(dragElementType)) + )) + + + setAllowDrop({allow: dropAllowed, show: true}) + + // if (allowDrop) { + // e.preventDefault() // this is necessary to allow drop to take place + // } + + } + + const handleDropEvent = (e) => { + + const {target} = e.operation + + if (target && target?.id !== props?.id){ + return + } + + setAllowDrop({allow: false, show: 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 + } + + // e.stopPropagation() + + const dragElementType = draggedElement.getAttribute("data-draggable-type") + + + const dropAllowed = (droppableTags && droppableTags !== null && (Object.keys(droppableTags).length === 0 || + (droppableTags.include?.length > 0 && droppableTags.include?.includes(dragElementType)) || + (droppableTags.exclude?.length > 0 && !droppableTags.exclude?.includes(dragElementType)) + )) + + // if (onDrop && dropAllowed) { + // onDrop(e, draggedElement, widgetClass) + // } + } + + + // const handleDragLeave = (e) => { + + // const {target} = e.operation + + // if (target && target.id === props.id){ + // handleDropEvent(e) + // }else{ + // setAllowDrop({allow: false, show: false}) + + // } + + // } + + // const handleDragStart = (event) => { + + // const {source} = event.operation + + // if (!source || (source && source.id !== props.dragElementType)){ + // return + // } if (!source || (source && source.id !== props.dragElementType)){ + // return + // } + // // event.dataTransfer.setData("text/plain", "") + // // onDragStart(draggableRef?.current, dragWidgetClass) + // onDragStart(draggableRef?.current, dragWidgetClass, elementMetaData) + + // } + + // const handleDragEnd = (event) => { + // // console.log("Drag end: ", e, e.target.closest('div')) + // const {source} = event.operation + + // if (!source || (source && source.id !== props.dragElementType)){ + // return + // } + + // // onDragEnd() + // } + + return ( +
+ {props.children} + {/* { + showDroppable.show && +
+
+ } */} + + { + droppable.isDropTarget && +
+
+ } +
+ ) +} + +export default WidgetDnd \ No newline at end of file diff --git a/src/components/cards.js b/src/components/cards.js index ebf3e7a..65b462a 100644 --- a/src/components/cards.js +++ b/src/components/cards.js @@ -34,6 +34,7 @@ export function SidebarWidgetCard({ name, img, url, license, widgetClass, innerR data-container={"sidebar"} dragElementType={widgetClass.widgetType} dragWidgetClass={widgetClass} + draggableType={"clone"} elementMetaData={{ name, url, diff --git a/src/components/draggable/dnd/draggableDnd.js b/src/components/draggable/dnd/draggableDnd.js index 61ece43..40c68f0 100644 --- a/src/components/draggable/dnd/draggableDnd.js +++ b/src/components/draggable/dnd/draggableDnd.js @@ -10,7 +10,7 @@ function Draggable(props) { const { ref } = useDraggable({ id: props.dragElementType, - feedback: "clone", + feedback: props.draggableType || "default", type: props.dragElementType // data: { title: props.children } }) diff --git a/src/utils/dndkit/plugins.js b/src/utils/dndkit/plugins.js new file mode 100644 index 0000000..5e3dc71 --- /dev/null +++ b/src/utils/dndkit/plugins.js @@ -0,0 +1,47 @@ +class ZIndexPlugin { + constructor(manager, options) { + this.manager = manager; + this.options = options || {}; + this.originalZIndexValues = new Map(); + } + + registerEffect() { + const handleDragStart = (event) => { + const { active } = event; + const draggableElement = document.getElementById(active.id); + + if (draggableElement) { + // Store the original z-index + this.originalZIndexValues.set(active.id, draggableElement.style.zIndex); + + // Apply the dragged z-index + draggableElement.style.zIndex = this.options.draggedZIndex || '9999'; + } + }; + + const handleDragEnd = (event) => { + const { active } = event; + const draggableElement = document.getElementById(active.id); + + if (draggableElement) { + // Restore the original z-index + const originalZIndex = this.originalZIndexValues.get(active.id) || ''; + draggableElement.style.zIndex = originalZIndex; + this.originalZIndexValues.delete(active.id); + } + }; + + // Listen for drag events + this.manager.addEventListener('dragstart', handleDragStart); + this.manager.addEventListener('dragend', handleDragEnd); + + // Return cleanup function + return () => { + this.manager.removeEventListener('dragstart', handleDragStart); + this.manager.removeEventListener('dragend', handleDragEnd); + }; + } + } + +export default ZIndexPlugin; + \ No newline at end of file