15 Commits

Author SHA1 Message Date
paul
ee953107f3 working on sortable 2025-03-06 11:38:03 +05:30
paul
c9e12cccd4 minor updates 2025-03-05 21:09:40 +05:30
paul
3edf1a974c working on sortable 2025-03-05 21:07:20 +05:30
paul
910ff81342 fixed initial offset problem 2025-03-05 18:05:59 +05:30
paul
59a0c0b583 fixed pointer issue 2025-03-05 15:16:54 +05:30
paul
4d0ea22af6 fixed the offset issue 2025-03-05 14:41:56 +05:30
paul
b1c707a21d working on dnd aaaaa..... screammmmm 2025-03-04 19:15:09 +05:30
paul
1ee137085e bud fixes for dnd 2025-03-04 11:45:26 +05:30
paul
03a4984cbc working on dnd inside a container 2025-03-03 19:17:28 +05:30
paul
72aafd17fe working on drop 2025-03-03 11:56:41 +05:30
paul
4df0e664ba fixed position 2025-03-02 22:17:06 +05:30
paul
62caafcf23 working with dnd-kit/react 2025-03-02 19:20:45 +05:30
paul
cc84a9f2d7 fixed sidebar drop position using dnd-kit 2025-03-02 10:29:59 +05:30
paul
a47191ee72 fixed droppable 2025-03-01 22:21:51 +05:30
paul
6656d3f6c4 working on drag and drop using dndkit 2025-03-01 19:20:46 +05:30
22 changed files with 1133 additions and 297 deletions

View File

@@ -9,7 +9,6 @@ The source code for PyUIBuilder is Dual licensed, most parts of the code comes u
some parts under a different non-commercial use license. Read the second part after the definition of the AGPL license.
Sub-folders with non-commercial use license will contain the second license as well.
- Only the code inside src/canvas/ is kept under a non-commercial use license.
--------------------------
@@ -677,8 +676,7 @@ For more information on this, and how to apply and follow the GNU AGPL, see
--------------------------
Below is the license for code under src/canvas/
This license can be found in multiple folders
License for non-commercial use in short
* You are free fork, modify and redistribute it given the original or derived works is kept free and open-source and under the same licese.

103
package-lock.json generated
View File

@@ -9,8 +9,10 @@
"version": "0.1.0",
"dependencies": {
"@ant-design/icons": "^5.4.0",
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/dom": "^0.0.9",
"@dnd-kit/modifiers": "^7.0.0",
"@dnd-kit/react": "^0.0.9",
"@dnd-kit/utilities": "^3.2.2",
"@reduxjs/toolkit": "^2.2.7",
"@testing-library/jest-dom": "^5.17.0",
@@ -2548,10 +2550,22 @@
"node": ">=10"
}
},
"node_modules/@dnd-kit/abstract": {
"version": "0.0.9",
"resolved": "https://registry.npmjs.org/@dnd-kit/abstract/-/abstract-0.0.9.tgz",
"integrity": "sha512-4FjHPOREfJJfRu0clCAnYqYc8JNHQl6CL31FjLKR+1wER+YOGkb3s3JntsqvZost5Xm/G/Z4sQ3ouLdyYQ9KdQ==",
"license": "MIT",
"dependencies": {
"@dnd-kit/geometry": "^0.0.9",
"@dnd-kit/state": "^0.0.9",
"tslib": "^2.6.2"
}
},
"node_modules/@dnd-kit/accessibility": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.0.tgz",
"integrity": "sha512-ea7IkhKvlJUv9iSHJOnxinBcoOI3ppGnnL+VDJ75O45Nss6HtZd8IdN8touXPDtASfeI2T2LImb8VOZcL47wjQ==",
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz",
"integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==",
"license": "MIT",
"dependencies": {
"tslib": "^2.0.0"
},
@@ -2559,12 +2573,24 @@
"react": ">=16.8.0"
}
},
"node_modules/@dnd-kit/core": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.1.0.tgz",
"integrity": "sha512-J3cQBClB4TVxwGo3KEjssGEXNJqGVWx17aRTZ1ob0FliR5IjYgTxl5YJbKTzA6IzrtelotH19v6y7uoIRUZPSg==",
"node_modules/@dnd-kit/collision": {
"version": "0.0.9",
"resolved": "https://registry.npmjs.org/@dnd-kit/collision/-/collision-0.0.9.tgz",
"integrity": "sha512-8Brbcd0dM/y2yzg/6EI9O/SvsVAglx0fUDvqPSsXACewR+//OGno1GVKndh35GAVKqDxOgG3Q/fw+CVnMnFv6g==",
"license": "MIT",
"dependencies": {
"@dnd-kit/accessibility": "^3.1.0",
"@dnd-kit/abstract": "^0.0.9",
"@dnd-kit/geometry": "^0.0.9",
"tslib": "^2.6.2"
}
},
"node_modules/@dnd-kit/core": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
"license": "MIT",
"dependencies": {
"@dnd-kit/accessibility": "^3.1.1",
"@dnd-kit/utilities": "^3.2.2",
"tslib": "^2.0.0"
},
@@ -2573,6 +2599,29 @@
"react-dom": ">=16.8.0"
}
},
"node_modules/@dnd-kit/dom": {
"version": "0.0.9",
"resolved": "https://registry.npmjs.org/@dnd-kit/dom/-/dom-0.0.9.tgz",
"integrity": "sha512-nsDcL6MMdNfGOg0xp24c1xy/eNW7fO1uLU0AM2b1SFYJYsbXUMw6sQXq+2eHCQol/FixCoSbjMgjx9D8jQXb+w==",
"license": "MIT",
"dependencies": {
"@dnd-kit/abstract": "^0.0.9",
"@dnd-kit/collision": "^0.0.9",
"@dnd-kit/geometry": "^0.0.9",
"@dnd-kit/state": "^0.0.9",
"tslib": "^2.6.2"
}
},
"node_modules/@dnd-kit/geometry": {
"version": "0.0.9",
"resolved": "https://registry.npmjs.org/@dnd-kit/geometry/-/geometry-0.0.9.tgz",
"integrity": "sha512-zqSY7vva+LGs9Y/c37GhD67q+boS27nCmyYYAohlZ29MGz66KwjpbSH5HNHa261kuq90KaaIVQQVHiZGjNfmOw==",
"license": "MIT",
"dependencies": {
"@dnd-kit/state": "^0.0.9",
"tslib": "^2.6.2"
}
},
"node_modules/@dnd-kit/modifiers": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@dnd-kit/modifiers/-/modifiers-7.0.0.tgz",
@@ -2586,6 +2635,32 @@
"react": ">=16.8.0"
}
},
"node_modules/@dnd-kit/react": {
"version": "0.0.9",
"resolved": "https://registry.npmjs.org/@dnd-kit/react/-/react-0.0.9.tgz",
"integrity": "sha512-a2ZUbVVyJwSf5gf0l6I10VqEj8EEe+O1W/q+rSR0fpjbxjwhLT0Gt+udwKZsg3yIKxmxJC4W6eHSS6feNca9aw==",
"license": "MIT",
"dependencies": {
"@dnd-kit/abstract": "^0.0.9",
"@dnd-kit/dom": "^0.0.9",
"@dnd-kit/state": "^0.0.9",
"tslib": "^2.6.2"
},
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
}
},
"node_modules/@dnd-kit/state": {
"version": "0.0.9",
"resolved": "https://registry.npmjs.org/@dnd-kit/state/-/state-0.0.9.tgz",
"integrity": "sha512-F43bOum8k/Rh1tfO3DiAUs/zw6KSv5IMm4PGKSPoU2tz+LW+37Kp37NlgwtLLDzx+z6R+6gILnJzE6Q5eVx7OQ==",
"license": "MIT",
"dependencies": {
"@preact/signals-core": "^1.6.0",
"tslib": "^2.6.2"
}
},
"node_modules/@dnd-kit/utilities": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz",
@@ -3900,6 +3975,16 @@
"node": ">= 8"
}
},
"node_modules/@preact/signals-core": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.8.0.tgz",
"integrity": "sha512-OBvUsRZqNmjzCZXWLxkZfhcgT+Fk8DDcT/8vD6a1xhDemodyy87UJRJfASMuSD8FaAIeGgGm85ydXhm7lr4fyA==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
}
},
"node_modules/@rc-component/async-validator": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.0.4.tgz",

View File

@@ -7,8 +7,10 @@
},
"dependencies": {
"@ant-design/icons": "^5.4.0",
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/dom": "^0.0.9",
"@dnd-kit/modifiers": "^7.0.0",
"@dnd-kit/react": "^0.0.9",
"@dnd-kit/utilities": "^3.2.2",
"@reduxjs/toolkit": "^2.2.7",
"@testing-library/jest-dom": "^5.17.0",

View File

@@ -3,6 +3,7 @@
* Github: PaulleDemon
*/
import { useEffect, useRef, useState } from 'react'
import { createPortal } from "react-dom"
import { LayoutFilled, ProductFilled, CloudUploadOutlined, DatabaseFilled } from "@ant-design/icons"
// import { DndContext, useSensors, useSensor, PointerSensor, closestCorners, DragOverlay, rectIntersection } from '@dnd-kit/core'
@@ -14,7 +15,7 @@ import Sidebar from './sidebar/sidebar'
import UploadsContainer from './sidebar/uploadsContainer'
import WidgetsContainer from './sidebar/widgetsContainer'
import { DragProvider } from './components/draggable/draggableContext'
import { DragProvider, useDragContext } from './components/draggable/draggableContext'
import PluginsContainer from './sidebar/pluginsContainer'
import { FileUploadProvider, useFileUploadContext } from './contexts/fileUploadContext'
@@ -31,6 +32,9 @@ import generateTkinterCode from './frameworks/tkinter/engine/code'
import TkMainWindow from './frameworks/tkinter/widgets/mainWindow'
import CTkMainWindow from './frameworks/customtk/widgets/mainWindow'
import { DndContext, DragOverlay } from '@dnd-kit/core'
import { SidebarOverlayWidgetCard, SidebarWidgetCard } from './components/cards'
import { DragDropProvider } from '@dnd-kit/react'
function App() {
@@ -45,6 +49,8 @@ function App() {
// const [uploadedAssets, setUploadedAssets] = useState([]) // a global storage for assets, since redux can't store files(serialize files)
const { draggedElement, overElement, setOverElement, widgetClass, dragElementMetaData } = useDragContext()
const [sidebarWidgets, setSidebarWidgets] = useState(TkinterWidgets || [])
const [sidebarPlugins, setSidebarPlugins] = useState(TkinterPluginWidgets || [])
@@ -53,6 +59,7 @@ function App() {
// NOTE: the below reference is no longer required
const [canvasWidgets, setCanvasWidgets] = useState([]) // contains the reference to the widgets inside the canvas
const sidebarTabs = [
{
name: "Widgets",
@@ -196,6 +203,9 @@ function App() {
}
// console.log("dragged element: ", dragElementMetaData)
return (
<div className="tw-w-full tw-h-[100vh] tw-flex tw-flex-col tw-bg-primaryBg">
<Header className="tw-h-[6vh]" onExportClick={handleCodeGen}
@@ -208,7 +218,7 @@ function App() {
<p>Are you sure you want to change the framework? This will clear the canvas.</p>
</Modal> */}
<DragProvider>
<DragDropProvider onDragStart={(e) => {console.log("Drag start event: ", e)}}>
<div className="tw-w-full tw-h-[94vh] tw-flex">
<Sidebar tabs={sidebarTabs}/>
@@ -217,9 +227,26 @@ function App() {
{/* </ActiveWidgetProvider> */}
</div>
{/* dragOverlay (dnd-kit) helps move items from one container to another */}
</DragProvider>
{
createPortal(
<DragOverlay dropAnimation={null}>
{
draggedElement ?
<SidebarOverlayWidgetCard {...dragElementMetaData} />
:
null
}
</DragOverlay>,
document.body
)
}
</DragDropProvider>
</div>
)
}
export default App;
export default App

View File

@@ -9,7 +9,7 @@ some parts under a different non-commercial use license.
-----------------------
License in short
* You are free fork, modify and redistribute it given the original or derived works is kept free and open-source and under the same licese.
* You are free fork, modify and redistribute it given the original or derived works is kept free and open-source and under the same license.
* Another restriction is you can't redistribute in binary or executable formats without asking me first.
* If you plan on making money on this work or derived work you need permission first.

View File

@@ -28,6 +28,7 @@ import WidgetContainer from "./constants/containers"
import { isSubClassOfWidget } from "../utils/widget"
import { ButtonModal } from "../components/modals"
import ResizeWidgetContainer from "./resizeContainer"
import Droppable from "../components/draggable/dnd/droppableDnd"
// const DotsBackground = require("../assets/background/dots.svg")
@@ -197,10 +198,9 @@ class Canvas extends React.Component {
innerWidget = ref.current
break
}
// 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)) {
if (ref.current?.getElement()?.contains(target)) {
if (!innerWidget) {
innerWidget = ref.current
@@ -665,62 +665,80 @@ 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) => {
handleDropEvent = (e, draggedElement, widgetClass = null, posMetaData) => {
e.preventDefault()
// console.log("Drop event")
// 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
const { clientX, clientY } = e.nativeEvent
let finalPosition = {
x: (clientX - canvasRect.left) / this.state.zoom,
y: (clientY - canvasRect.top) / this.state.zoom,
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)
// widgetRef.current.setPos(10, 10)
})
} 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,
// }
const canvasBoundingRect = this.getCanvasBoundingRect()
const {dragStartCursorPos, initialPos} = posMetaData
// calculate the initial offset from the div to the cursor grab
const initialOffset = {
x: ((dragStartCursorPos.x - canvasBoundingRect.left) / this.state.zoom - this.state.currentTranslate.x) - initialPos.x,
y: ((dragStartCursorPos.y - canvasBoundingRect.top) / this.state.zoom - this.state.currentTranslate.y) - initialPos.y
}
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,
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")
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) {
console.log("removing")
// if the widget was inside another widget move it outside
let childWidgetObj = this.findWidgetFromListById(widgetObj.current.getId())
let parentWidgetObj = this.findWidgetFromListById(childWidgetObj.parent)
@@ -782,7 +800,7 @@ class Canvas extends React.Component {
// console.log("event: ", event)
// widgets data structure { id, widgetType: widgetComponentType, children: [], parent: "" }
const dropWidgetObj = this.findWidgetFromListById(parentWidgetId)
// Find the dragged widget object
// Find the dragged wihandleAddWidgetChilddget object
let dragWidgetObj = this.findWidgetFromListById(dragElementID)
// console.log("Drag widget obj: ", dragWidgetObj, dropWidgetObj)
@@ -1063,6 +1081,7 @@ class Canvas extends React.Component {
}
render() {
// FIXME: inner canvas not recognized as droppable or some thing
return (
<div className="tw-relative tw-overflow-hidden tw-flex tw-w-full tw-h-full tw-max-h-[100vh]">
@@ -1086,17 +1105,21 @@ class Canvas extends React.Component {
</ButtonModal>
</div>
<Droppable className={"tw-w-full tw-h-full"}
droppableTags={{ exclude: ["image", "video"] }}
onDrop={this.handleDropEvent}
id="canvas-droppable">
{/* <ActiveWidgetProvider> */}
<DroppableWrapper id="canvas-droppable"
{/* <DroppableWrapper id="canvas-droppable"
droppableTags={{ exclude: ["image", "video"] }}
className="tw-w-full tw-h-full"
onDrop={this.handleDropEvent}>
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",
// transition: " transform 0.3s ease-in-out",
backgroundImage: `url(${DotsBackground})`,
backgroundSize: 'cover', // Ensure proper sizing if needed
backgroundRepeat: 'no-repeat',
@@ -1111,7 +1134,7 @@ 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-bg-transparent tw-top-0 tw-select-none"
ref={this.canvasRef}>
<div className="tw-relative tw-w-full tw-h-full">
{
@@ -1127,8 +1150,8 @@ class Canvas extends React.Component {
</div>
</Dropdown>
{/* </DragWidgetProvider> */}
</DroppableWrapper>
{/* </DroppableWrapper> */}
</Droppable>
<CanvasToolBar isOpen={this.state.toolbarOpen}
widgetType={this.state.selectedWidget?.getWidgetType() || ""}
attrs={this.state.toolbarAttrs}

View File

@@ -12,7 +12,8 @@ import WidgetContainer from "../constants/containers"
import { DragContext } from "../../components/draggable/draggableContext"
import { isNumeric, removeKeyFromObject } from "../../utils/common"
import { info } from "autoprefixer"
import { message } from "antd"
import { Layout, message } from "antd"
import WidgetDnd from "./widgetDnd"
// TODO: make it possible to apply widgetInnerStyle on load
@@ -67,11 +68,11 @@ class Widget extends React.Component {
}
// This indicates if the draggable can be dropped on this widget, set this to null to disable drops
this.droppableTags = {}
this.droppableTags = {}
this.state = {
zIndex: 0,
zIndex: 1,
selected: false,
widgetName: widgetName || 'widget', // this will later be converted to variable name
enableRename: false, // will open the widgets editable div for renaming
@@ -90,7 +91,7 @@ class Widget extends React.Component {
pos: { x: 0, y: 0 },
size: { width: 100, height: 100 },
fitContent: {width: false, height: false},
fitContent: { width: false, height: false },
positionType: PosType.ABSOLUTE,
widgetOuterStyling: {
@@ -116,7 +117,7 @@ class Widget extends React.Component {
this.setAttrValue("styling.backgroundColor", value)
}
},
label: "Styling"
},
layout: {
@@ -180,7 +181,7 @@ class Widget extends React.Component {
this.setPos = this.setPos.bind(this)
this.setAttrValue = this.setAttrValue.bind(this)
this.setWidgetName = this.setWidgetName.bind(this)
this.setWidgetInnerStyle = this.setWidgetInnerStyle.bind(this)
this.setWidgetOuterStyle = this.setWidgetOuterStyle.bind(this)
@@ -200,16 +201,15 @@ class Widget extends React.Component {
componentDidMount() {
if (this.state.attrs.layout){
if (this.state.attrs.layout) {
this.setLayout(this.state.attrs.layout.value)
// console.log("prior layout: ", this.state.attrs.layout.value)
}
if (this.state.attrs.styling.backgroundColor)
this.setWidgetInnerStyle('backgroundColor', this.state.attrs.styling?.backgroundColor.value || "#fff")
this.load(this.props.initialData || {}) // load the initial data
this.load(this.props.initialData || {}, console.log("loaded")) // load the initial data
}
componentWillUnmount() {
@@ -275,9 +275,9 @@ class Widget extends React.Component {
label: "Fit width",
tool: Tools.CHECK_BUTTON,
value: this.state.fitContent.width,
onChange: (value) => {
onChange: (value) => {
this.updateState((prev) => ({
fitContent: {...prev.fitContent, width: value}
fitContent: { ...prev.fitContent, width: value }
}))
}
},
@@ -285,9 +285,9 @@ class Widget extends React.Component {
label: "Fit height",
tool: Tools.CHECK_BUTTON,
value: this.state.fitContent.height,
onChange: (value) => {
onChange: (value) => {
this.updateState((prev) => ({
fitContent: {...prev.fitContent, height: value}
fitContent: { ...prev.fitContent, height: value }
}))
}
},
@@ -315,15 +315,15 @@ class Widget extends React.Component {
return this.constructor.widgetType
}
getRequirements(){
getRequirements() {
return this.constructor.requirements
}
getImports(){
getImports() {
return this.constructor.requiredImports
}
generateCode(){
generateCode() {
throw new NotImplementedError("generateCode() must be implemented by the subclass")
}
@@ -365,6 +365,7 @@ class Widget extends React.Component {
}
setPos(x, y) {
console.log("position set: ", x, y)
this.setState({
pos: { x, y }
@@ -404,11 +405,11 @@ class Widget extends React.Component {
return this.elementRef.current
}
hideDroppableIndicator(){
hideDroppableIndicator() {
console.log("hide drop indicator")
this.setState({
showDroppableStyle: {
allow: false,
allow: false,
show: false
}
}, () => {
@@ -421,7 +422,7 @@ class Widget extends React.Component {
* @param {string} path - eg: styling.backgroundColor
* @returns
*/
removeAttr = (path) =>{
removeAttr = (path) => {
const newAttrs = removeKeyFromObject(path, this.state.attrs)
@@ -442,16 +443,16 @@ class Widget extends React.Component {
const keys = path.split('.')
const lastKey = keys.pop()
// Traverse the state and update the nested value immutably
let newAttrs = { ...this.state.attrs }
let nestedObject = newAttrs
keys.forEach(key => {
nestedObject[key] = { ...nestedObject[key] } // Ensure immutability
nestedObject = nestedObject[key]
})
nestedObject[lastKey].value = value
this.updateState({ attrs: newAttrs })
@@ -484,7 +485,7 @@ class Widget extends React.Component {
* this is a helper function to remove any non-serializable data associated with attrs
* eg: {"styling.backgroundColor": "#ffff", "layout": {layout: "flex", direction: "", grid: }}
*/
serializeAttrsValues(){
serializeAttrsValues() {
const serializeValues = (obj, currentPath = "") => {
const result = {}
@@ -532,28 +533,28 @@ class Widget extends React.Component {
* inform the child about the parent layout changes
* @param {Layouts} parentLayout
*/
setParentLayout(parentLayout){
setParentLayout(parentLayout) {
if (!parentLayout){
if (!parentLayout) {
// if parent layout is null (i,e the widget is on the canvas)
return {}
}
}
const {layout, direction, gap} = parentLayout
const { layout, direction, gap } = parentLayout
let updates = {
parentLayout: parentLayout,
}
if (layout === Layouts.FLEX || layout === Layouts.GRID){
if (layout === Layouts.FLEX || layout === Layouts.GRID) {
updates = {
...updates,
positionType: PosType.NONE
}
}else if (layout === Layouts.PLACE){
} else if (layout === Layouts.PLACE) {
updates = {
...updates,
positionType: PosType.ABSOLUTE
@@ -563,11 +564,11 @@ class Widget extends React.Component {
this.setState(updates)
}
getParentLayout(){
getParentLayout() {
return this.state.parentLayout
}
getLayout(){
getLayout() {
return this.state?.attrs?.layout?.value || Layouts.FLEX
}
@@ -584,18 +585,18 @@ class Widget extends React.Component {
gap: `${gap}px`,
flexWrap: "wrap",
gridTemplateColumns: "repeat(auto-fill, minmax(100px, 1fr))",
gridTemplateRows: "repeat(auto-fill, minmax(100px, 1fr))",
gridTemplateRows: "repeat(auto-fill, minmax(100px, 1fr))",
// gridAutoRows: 'minmax(100px, auto)', // Rows with minimum height of 100px, and grow to fit content
// gridAutoCols: 'minmax(100px, auto)', // Cols with minimum height of 100px, and grow to fit content
}
if (align === "start"){
if (align === "start") {
widgetStyle["alignItems"] = "flex-start"
}else if (align === "center"){
} else if (align === "center") {
widgetStyle["alignItems"] = "center"
}else if (align === "end"){
} else if (align === "end") {
widgetStyle["alignItems"] = "flex-end"
}else{
} else {
widgetStyle["alignItems"] = "unset"
}
@@ -604,7 +605,7 @@ class Widget extends React.Component {
})
this.setAttrValue("layout", value)
this.props.onLayoutUpdate({parentId: this.__id, parentLayout: value})// inform children about the layout update
this.props.onLayoutUpdate({ parentId: this.__id, parentLayout: value })// inform children about the layout update
}
@@ -621,7 +622,7 @@ class Widget extends React.Component {
* @param {string} key - The string in react Style format
* @param {string} value - Value of the style
*/
setWidgetOuterStyle(key, value){
setWidgetOuterStyle(key, value) {
const widgetStyle = {
...this.state.widgetOuterStyling,
[key]: value
@@ -639,7 +640,7 @@ class Widget extends React.Component {
* @param {string} value - Value of the style
*/
setWidgetInnerStyle(key, value) {
const widgetStyle = {
...this.state.widgetInnerStyling,
[key]: value
@@ -661,7 +662,7 @@ class Widget extends React.Component {
const fitWidth = this.state.fitContent?.width
const fitHeight = this.state.fitContent?.height
if (fitWidth && fitHeight){
if (fitWidth && fitHeight) {
message.warning("both width and height are set to fit-content, unset it to start resizing")
return
}
@@ -713,7 +714,7 @@ class Widget extends React.Component {
*
* serialize data for saving
*/
serialize(){
serialize() {
// NOTE: when serializing make sure, you are only passing serializable objects not functions or other
return ({
zIndex: this.state.zIndex,
@@ -735,27 +736,27 @@ class Widget extends React.Component {
* @param {object} data
* @param {() => void | undefined} callback - optional callback that will be called after load
*/
load(data, callback){
load(data, callback) {
if (Object.keys(data).length === 0) return // no data to load
data = {...data} // create a shallow copy
data = { ...data } // create a shallow copy
const {attrs, parentLayout, ...restData} = data
const { attrs, parentLayout, ...restData } = data
let layoutUpdates = {
parentLayout: parentLayout.layout || null
}
if (parentLayout?.layout === Layouts.FLEX || parentLayout?.layout === Layouts.GRID){
if (parentLayout?.layout === Layouts.FLEX || parentLayout?.layout === Layouts.GRID) {
layoutUpdates = {
...layoutUpdates,
positionType: PosType.NONE
}
}else if (parentLayout?.layout === Layouts.PLACE){
} else if (parentLayout?.layout === Layouts.PLACE) {
layoutUpdates = {
...layoutUpdates,
positionType: PosType.ABSOLUTE
@@ -767,7 +768,7 @@ class Widget extends React.Component {
...layoutUpdates
}
this.setState(newData, () => {
this.setState(newData, () => {
// Updates attrs
let newAttrs = { ...this.state.attrs }
@@ -789,75 +790,34 @@ class Widget extends React.Component {
nestedObject[lastKey].value = value
})
if (newAttrs?.styling?.backgroundColor){
if (newAttrs?.styling?.backgroundColor) {
// some widgets don't have background color
this.setWidgetInnerStyle("backgroundColor", newAttrs.styling.backgroundColor)
}
this.updateState({ attrs: newAttrs }, callback)
})
})
}
handleDragStart = (e, callback) => {
e.stopPropagation()
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
dragImage.style.position = 'absolute'
dragImage.style.top = '-9999px' // Move it out of view
document.body.appendChild(dragImage)
const rect = this.elementRef?.current.getBoundingClientRect()
// snap to mouse pos
// const offsetX = e.clientX - rect.left
// const offsetY = e.clientY - rect.top
// snap to middle
const offsetX = rect.width / 2
const offsetY = rect.height / 2
// Set the custom drag image with correct offset to avoid snapping to the top-left corner
e.dataTransfer.setDragImage(dragImage, offsetX, offsetY)
// Remove the custom drag image after some time to avoid leaving it in the DOM
setTimeout(() => {
document.body.removeChild(dragImage)
}, 0)
handleDragStart = (e, ) => {
// NOTE: this line will prevent problem's such as self-drop or dropping inside its own children
setTimeout(this.disablePointerEvents, 1)
// setTimeout(this.disablePointerEvents, 1)
this.setState({ isDragging: true })
}
handleDragEnter = (e, draggedElement, setOverElement) => {
handleDragEnter = (e, draggedElement) => {
if (!draggedElement || !draggedElement.getAttribute("data-drag-start-within")){
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 dragEleType = draggedElement.getAttribute("data-draggable-type")
// console.log("Drag entering...", dragEleType, draggedElement, this.droppableTags)
// FIXME: the outer widget shouldn't be swallowed by inner widget
if (draggedElement === this.elementRef.current) {
// prevent drop on itself, since the widget is invisible when dragging, if dropped on itself, it may consume itself
return
}
setOverElement(this.elementRef.current) // provide context to the provider
let showDrop = {
allow: true,
show: true
@@ -889,40 +849,18 @@ class Widget extends React.Component {
handleDragOver = (e, 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
}
if (draggedElement === this.elementRef.current) {
// prevent drop on itself, since the widget is invisible when dragging, if dropped on itself, it may consume itself
return
}
// console.log("Drag over: ", e.dataTransfer.getData("text/plain"), e.dataTransfer)
const dragEleType = draggedElement.getAttribute("data-draggable-type")
const allowDrop = (this.droppableTags && this.droppableTags !== null && (Object.keys(this.droppableTags).length === 0 ||
(this.droppableTags.include?.length > 0 && this.droppableTags.include?.includes(dragEleType)) ||
(this.droppableTags.exclude?.length > 0 && !this.droppableTags.exclude?.includes(dragEleType))
))
if (allowDrop) {
e.preventDefault() // NOTE: this is necessary to allow drop to take place
}
}
handleDropEvent = (e, draggedElement, widgetClass = null) => {
if (!draggedElement || !draggedElement.getAttribute("data-drag-start-within")){
console.log("Dropped: ", 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
}
e.preventDefault()
e.stopPropagation()
// FIXME: sometimes the elements showDroppableStyle is not gone, when dropping on the same widget
this.setState({
showDroppableStyle: {
@@ -932,9 +870,9 @@ class Widget extends React.Component {
})
if (draggedElement === this.elementRef.current){
if (draggedElement === this.elementRef.current) {
// prevent drop on itself, since the widget is invisible when dragging, if dropped on itself, it may consume itself
return
return
}
let currentElement = e.currentTarget
@@ -963,7 +901,7 @@ class Widget extends React.Component {
if (!allowDrop && !swapArea) {
// only if both swap and drop is not allowed return, if swap is allowed continue
return
return
}
// TODO: check if the drop is allowed
if ([WidgetContainer.CANVAS, WidgetContainer.WIDGET].includes(container)) {
@@ -981,10 +919,10 @@ class Widget extends React.Component {
// console.log("Dropped on Sidebar: ", this.__id)
this.props.onCreateWidgetRequest(widgetClass, ({ id, widgetRef }) => {
this.props.onAddChildWidget({
event: e,
parentWidgetId: this.__id,
dragElementID: id
}) // if dragged from the sidebar create the widget first
event: e,
parentWidgetId: this.__id,
dragElementID: id
}) // if dragged from the sidebar create the widget first
})
}
@@ -992,18 +930,15 @@ class Widget extends React.Component {
}
handleDragLeave = (e, draggedElement, overElement) => {
e.preventDefault()
e.stopPropagation()
handleDragLeave = (e) => {
const rect = this.getBoundingRect()
const {clientX, clientY} = e
const { clientX, clientY } = e
const isInBoundingBox = (clientX >= rect.left && clientX <= rect.right &&
clientY >= rect.top && clientY <= rect.bottom)
// if (!e.currentTarget.contains(draggedElement)) {
if (!isInBoundingBox) {
// FIXME: if the mouse pointer is over this widget's child, then droppable style should be invisible
@@ -1020,11 +955,10 @@ class Widget extends React.Component {
}
}
handleDragEnd = (callback) => {
callback()
handleDragEnd = () => {
this.setState({ isDragging: false })
this.enablePointerEvents()
// this.enablePointerEvents()
// console.log("enable pointer events: ")
// this.props.onWidgetDragEnd(this.elementRef?.current)
}
@@ -1039,40 +973,40 @@ class Widget extends React.Component {
this.elementRef.current.style.pointerEvents = "auto"
}
getInnerRenderStyling(){
const {width, height, minWidth, minHeight} = this.getRenderSize()
getInnerRenderStyling() {
const { width, height, minWidth, minHeight } = this.getRenderSize()
const styling = {
...this.state.widgetInnerStyling,
width,
width,
height,
minWidth,
minWidth,
minHeight
}
return styling
}
getRenderSize(){
getRenderSize() {
let width = isNumeric(this.state.size.width) ? `${this.state.size.width}px` : this.state.size.width
let height = isNumeric(this.state.size.height) ? `${this.state.size.height}px` : this.state.size.height
let fitWidth = this.state.fitContent.width
let fitHeight = this.state.fitContent.height
if (fitWidth){
if (fitWidth) {
width = "max-content"
}
if (fitHeight){
if (fitHeight) {
height = "max-content"
}
// if fit width is enabled then the minsize is the resizable size
let minWidth = fitWidth ? this.state.size.width : this.minSize.width
let minHeight = fitHeight ? this.state.size.height : this.minSize.height
return {width, height, minWidth, minHeight}
return { width, height, minWidth, minHeight }
}
@@ -1093,10 +1027,10 @@ class Widget extends React.Component {
* This is an internal methods don't override
* @returns {HTMLElement}
*/
render() {
render() {
const { width, height, minWidth, minHeight } = this.getRenderSize()
const {width, height, minWidth, minHeight} = this.getRenderSize()
// NOTE: first check tkinter behaviour with the width and height
let outerStyle = {
@@ -1108,74 +1042,92 @@ class Widget extends React.Component {
left: `${this.state.pos.x}px`,
width: width,
height: height,
minWidth: minWidth,
minWidth: minWidth,
minHeight: minHeight,
opacity: this.state.isDragging ? 0.3 : 1,
opacity: this.state.isDragging ? 0.6 : 1,
}
// const boundingRect = this.getBoundingRect
console.log("state conatainer: ", this.state.widgetContainer, this.state.widgetName)
const isSortable = this.getParentLayout() === Layouts.FLEX || this.getParentLayout() === Layouts.GRID
// const boundingRect = this.getBoundingRect
// FIXME: if the parent container has tw-overflow-none, then the resizable indicator are also hidden
// FIXME: re-enable pointer events
return (
// <DragContext.Consumer>
<DragContext.Consumer>
{
({ draggedElement, widgetClass, onDragStart, onDragEnd, overElement, setOverElement }) => {
({ draggedElement, widgetClass, setInitialOffset }) => {
return (
<div data-widget-id={this.__id}
ref={this.elementRef}
className="tw-shadow-xl tw-w-fit tw-h-fit"
style={outerStyle}
return (
<WidgetDnd widgetId={this.__id}
disabled={false && this.state.dragEnabled}
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
data-container={this.state.widgetContainer}
data-drag-start-within // this attribute indicates that the drag is occurring from within the project and not a outside file drop
dragElementType={this.getWidgetType()}
droppableTags={this.droppableTags}
className="tw-shadow-xl tw-w-full tw-h-full"
currentPos={{ ...this.state.pos }}
onDragStart={this.handleDragStart}
onDragOver={(e) => {this.handleDragOver(e, draggedElement)}}
onDragEnd={(e) => this.handleDragEnd()}
draggable={this.state.dragEnabled}
onDrop={(e) => {this.handleDropEvent(e, draggedElement, widgetClass)}} // handle by droppable
onDragEnter={(e) => this.handleDragEnter(e, draggedElement)} // this is by droppable
widgetRef={this.elementRef}
onDragOver={(e) => this.handleDragOver(e, draggedElement)}
onDrop={(e) => {this.handleDropEvent(e, draggedElement, widgetClass); onDragEnd()}}
isSortable={isSortable}
sortableIndex={0}
onDragEnter={(e) => this.handleDragEnter(e, draggedElement, setOverElement)}
onDragLeave={(e) => this.handleDragLeave(e, draggedElement, overElement)}
canvas={this.canvas}
// onDragOver={(e) => 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)}
style={outerStyle}
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 */}
<div className="tw-relative tw-w-full tw-h-full tw-top-0 tw-left-0"
>
>
<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-bg-red-400
tw-scale-[1.1] tw-opacity-1 tw-z-[-1] `}
tw-border-1 tw-opacity-0 tw-border-solid tw-border-black
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
// swapable area
>
{/* helps with swappable: if the mouse is in this area while hovering/dropping, then swap */}
</div>
<div className="tw-relative tw-top-0 tw-left-0 tw-w-full tw-h-full" ref={this.innerAreaRef}
>
>
{this.renderContent()}
</div>
{
// show drop style on drag hover
draggedElement && this.state.showDroppableStyle.show &&
<div className={`${this.state.showDroppableStyle.allow ? "tw-border-blue-600" : "tw-border-red-600"}
tw-absolute tw-top-[-5px] tw-left-[-5px] tw-w-full tw-h-full tw-z-[2]
tw-border-2 tw-border-dashed tw-rounded-lg tw-pointer-events-none
tw-absolute tw-top-[-5px] tw-left-[-5px] tw-w-full tw-h-full tw-z-[2]
tw-border-2 tw-border-dashed tw-rounded-lg tw-pointer-events-none
`}
`}
style={{
width: "calc(100% + 10px)",
height: "calc(100% + 10px)",
@@ -1185,8 +1137,8 @@ class Widget extends React.Component {
}
{/* 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'}`}
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)",
@@ -1199,7 +1151,7 @@ class Widget extends React.Component {
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"
tw-overflow-hidden tw-absolute tw--top-6 tw-h-6"
/>
<div
@@ -1253,12 +1205,14 @@ class Widget extends React.Component {
</div>
</div>
)
}
}
</DragContext.Consumer>
</WidgetDnd>
)
}
}
</DragContext.Consumer >
)
}

View File

@@ -0,0 +1,252 @@
import React, { useEffect, useRef, useState } from 'react'
import { useDragDropManager, useDroppable, useDraggable } from '@dnd-kit/react'
import { useDragContext } from '../../components/draggable/draggableContext'
import WidgetContainer from '../constants/containers'
import { useSortable } from '@dnd-kit/react/sortable'
import {
pointerIntersection,
closestCenter,
shapeIntersection
} from '@dnd-kit/collision'
function WidgetDnd({widgetId, canvas, widgetRef, droppableTags,onMousePress, onDrop, onDragStart,
onDragEnd, onDragEnter, onDragOver, currentPos={x: 0, y: 0},
dragElementType, isSortable=false, sortableIndex=0,
...props}) {
const dndRef = useRef(null)
const { draggedElement, setOverElement, widgetClass, setPosMetaData } = useDragContext()
const [isDropDisabled, setIsDropDisabled] = useState(false);
const { ref: dropRef, droppable} = useDroppable({
id: widgetId,
disabled: isDropDisabled,
collisionDetector: pointerIntersection,
// 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: widgetId,
feedback: "move",
type: dragElementType,
disabled: props.disabled,
// data: { title: props.children }
})
// const {ref: sortableRef} = useSortable({
// id: widgetId,
// index: sortableIndex,
// disabled: false && isSortable,
// feedback: 'move'
// })
const manager = useDragDropManager()
// const {}
const {onDragStart: onDragContextStart, onDragEnd: onDragContextEnd, disableStyle=false} = useDragContext()
const [allowDrop, setAllowDrop] = useState(false) // indicator if the draggable can be dropped on the droppable
useEffect(() => {
if (draggable.isDragging){
setIsDropDisabled(true)
}else{
setIsDropDisabled(false)
}
}, [draggable.isDragging])
useEffect(() => {
canvas?.addEventListener("pointerdown", handleInitialPosOffset)
canvas?.addEventListener("mousedown", handleInitialPosOffset)
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)
canvas?.removeEventListener("mousedown", handleInitialPosOffset)
canvas?.removeEventListener("pointerdown", handleInitialPosOffset)
}
}, [manager, draggedElement, widgetClass, canvas, currentPos])
const handleRef = (node) => {
dndRef.current = node
widgetRef.current = node
dropRef(node)
dragRef(node)
// sortableRef(node)
}
const handleInitialPosOffset = (e) => {
if (!widgetRef?.current.contains(e.target)){
return
}
console.log("canvas bounding rect: ", canvas.getBoundingClientRect(), widgetRef.current)
const {clientX, clientY} = e
const canvasBoundingRect = canvas.getBoundingClientRect()
const posMetaData = {
dragStartCursorPos: {x: clientX, y: clientY},
initialPos: currentPos
}
setPosMetaData(posMetaData)
console.log("initial position calc: ", posMetaData, currentPos, (clientX - canvasBoundingRect.left), (clientY - canvasBoundingRect.top), "client: ", clientX, clientY, canvasBoundingRect)
}
const handleDragEnter = (e) => {
if (draggable.isDragSource){
// if the current widget is being dragged
onDragContextStart(dndRef?.current, widgetClass, {})
dndRef.current.style.zIndex = 10
onDragStart(e)
return
}else if (droppable.isDropTarget){
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(dndRef.current)
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(dropAllowed)
}
}
const handleDragOver = (e) => {
if (droppable.isDropTarget){
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))
))
console.log("Drop allowed: ", dropAllowed, dragElementType )
setAllowDrop(dropAllowed)
onDragOver(e)
}
}
const handleDropEvent = (e) => {
if (draggable.isDragSource){
onDragContextEnd()
onDragEnd(e)
}else if (droppable.isDropTarget){
// setAllowDrop(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 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 && (droppable.isDropTarget && !draggable.isDragSource)) {
onDrop(e, draggedElement, widgetClass)
}
}
}
return (
<div ref={handleRef} data-drag-start-within
{...props}
data-widget-id={widgetId}
data-draggable-type={dragElementType}
className={`${props.className || ''} ${draggable.isDragging && "tw-pointer-events-none"} tw-outline
tw-border-2 tw-border-solid tw-border-red-400
tw-relative tw-outline-none`}
>
{props.children}
{/* <div className={`${allowDrop ? "tw-bg-[#82ff1c55]" : "tw-bg-[#eb5d3662]"}
tw-absolute tw-top-0 tw-left-[${rect.}] tw-w-full tw-h-full tw-z-[3]
tw-border-2 tw-border-dashed tw-rounded-lg tw-pointer-events-none
`}>
</div> */}
{
(droppable.isDropTarget && !draggable.isDragSource) &&
<div className={`${allowDrop ? "tw-bg-[#82ff1c55]" : "tw-bg-[#eb5d3662]"}
tw-absolute tw-top-0 tw-left-0 tw-w-full tw-h-full tw-z-[3]
tw-border-2 tw-border-dashed tw-rounded-lg tw-pointer-events-none
`}>
</div>
}
</div>
)
}
export default WidgetDnd

View File

@@ -33,7 +33,7 @@ const WidgetDraggable = memo(({ widgetRef, enableDrag=true, dragElementType="wid
onDragStart(widgetRef?.current || null)
console.log("Drag start: ", widgetRef.current)
// console.log("Drag start: ", widgetRef.current)
// Create custom drag image with full opacity, this will ensure the image isn't taken from part of the canvas
const dragImage = widgetRef?.current.cloneNode(true)

View File

@@ -0,0 +1,42 @@
License held by paul
Github username: PaulleDemon
Below is the License for source code of PyUIBuilder. For license regarding source code generated by PyUIBuilder Refer Readme.md file (it has basically no restrictions).
The source code for PyUIBuilder is Dual licensed, most parts of the code comes under AGPL,
some parts under a different non-commercial use license.
-----------------------
License in short
* You are free fork, modify and redistribute it given the original or derived works is kept free and open-source and under the same license.
* Another restriction is you can't redistribute in binary or executable formats without asking me first.
* If you plan on making money on this work or derived work you need permission first.
License was planned to be kept under one AGPL or less restrictive license, but since its difficult for me to continue providing free and open-source software without proper funding. It's under this license.
Once there enough money to fund this project and my upcoming FOSS projects, The license will be brought back to a single AGPL or much lesser restrictive license. The fastest way to make this happen is by purchasing a one-time license or fund me.
Read the full license below.
--------------------
1. License Grant
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to use, copy, modify, merge, publish, and distribute the source code, subject to the following conditions:
2. Derived Work License
Any modifications, derivative works, or copies of the Software must retain this license. All derivative works must be open-sourced under the same license terms. You are not permitted to relicense or sub-license any portion of the Software under a different license without explicit prior written permission from the original author.
3. Distribution Restriction on Executables
You are not permitted to distribute the Software in compiled, executable, or binary form without prior written consent from the original author. This applies to both original and derivative works.
4. Commercial Use Restriction
The Software, in its original or modified form, cannot be used for any commercial purpose without prior written permission from the original author. This includes selling, licensing, or offering the Software as part of a service.
5. Non-Commercial Use
You may freely use, copy, modify, and distribute the source code of the Software for non-commercial purposes, provided that the same terms of this license are maintained in all copies or derivative works.
6. Changes to the License
The original author reserves the right to modify, amend, or update this license at any time. Any changes will apply only to future versions of the Software. Any prior versions of the Software, including derivative works, will continue to be governed by the version of the license in effect at the time the work was created.
7. Disclaimer
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,5 +1,5 @@
import { useEffect, useMemo, useRef } from "react"
import Draggable from "./utils/draggableDnd"
import Draggable from "./draggable/dnd/draggableDnd"
import { GithubOutlined, GitlabOutlined, LinkOutlined,
AudioOutlined, FileTextOutlined,
@@ -29,13 +29,89 @@ export function SidebarWidgetCard({ name, img, url, license, widgetClass, innerR
return (
// <Draggable className="tw-cursor-pointer" id={name}>
<DraggableWrapper data-container={"sidebar"}
<Draggable id={name}
className="tw-cursor-pointer tw-w-fit tw-bg-white tw-h-fit"
data-container={"sidebar"}
dragElementType={widgetClass.widgetType}
dragWidgetClass={widgetClass}
draggableType={"clone"}
elementMetaData={{
name,
url,
img,
license,
widgetClass,
}}
>
<div ref={innerRef} className="tw-select-none tw-h-[200px] tw-w-[230px] tw-flex tw-flex-col
tw-rounded-md tw-overflow-hidden
tw-gap-2 tw-text-gray-600 tw-bg-[#ffffff44] tw-border-solid tw-border-[1px]
tw-border-blue-500 tw-shadow-md">
<div className="tw-h-[200px] tw-pointer-events-none tw-w-full tw-overflow-hidden">
<img src={img} alt={name} className="tw-object-contain tw-h-full tw-w-full tw-select-none" />
</div>
<span className="tw-text-center tw-text-black tw-text-lg">{name}</span>
<div className="tw-flex tw-text-lg tw-place tw-px-4">
<a href={url} className="tw-text-gray-600" target="_blank" rel="noopener noreferrer">
{urlIcon}
</a>
{license?.name &&
<div className="tw-ml-auto tw-text-sm">
{
license.url ?
<a href={license.url} target="_blank" rel="noreferrer noopener"
className="tw-p-[1px] tw-px-2 tw-text-blue-500 tw-border-[1px]
tw-border-solid tw-rounded-sm tw-border-blue-500
tw-shadow-md tw-text-center tw-no-underline">
{license.name}
</a>
:
<div className="tw-p-[1px] tw-px-2 tw-text-blue-500 tw-border-[1px]
tw-border-solid tw-rounded-sm tw-border-blue-500
tw-shadow-md tw-text-center">
{license.name}
</div>
}
</div>
}
</div>
</div>
</Draggable>
)
}
export function SidebarOverlayWidgetCard({ name, img, url, license, widgetClass, innerRef}){
const urlIcon = useMemo(() => {
if (url){
const host = new URL(url).hostname.toLowerCase()
if (host === "github.com"){
return <GithubOutlined />
}else if(host === "gitlab.com"){
return <GitlabOutlined />
}else{
return <GlobalOutlined />
}
}
}, [url])
return (
<>
{/* <DraggableWrapper data-container={"sidebar"}
dragElementType={widgetClass.widgetType}
dragWidgetClass={widgetClass}
className="tw-cursor-pointer tw-w-fit tw-bg-white tw-h-fit">
className="tw-cursor-pointer tw-w-fit tw-bg-white tw-h-fit"> */}
<div ref={innerRef} className="tw-select-none tw-h-[200px] tw-w-[230px] tw-flex tw-flex-col
<div ref={innerRef} className="tw-bg-white tw-select-none tw-h-[200px] tw-w-[230px] tw-flex tw-flex-col
tw-rounded-md tw-overflow-hidden
tw-gap-2 tw-text-gray-600 tw-bg-[#ffffff44] tw-border-solid tw-border-[1px]
tw-border-blue-500 tw-shadow-md">
@@ -72,8 +148,8 @@ export function SidebarWidgetCard({ name, img, url, license, widgetClass, innerR
</div>
</div>
</DraggableWrapper>
// </Draggable>
{/* </DraggableWrapper> */}
</>
)
}

View File

@@ -0,0 +1,36 @@
import React, { createContext, useContext, useState } from 'react'
import { isSubClassOfWidget } from '../../utils/widget'
// import Widget from '../../canvas/widgets/base'
export const DragContext = createContext()
export const useDragContext = () => useContext(DragContext)
// Provider component to wrap around parts of your app that need drag-and-drop functionality
export const DragProvider = ({ children }) => {
const [draggedElement, setDraggedElement] = useState(null)
const [overElement, setOverElement] = useState(null) // the element the dragged items is over
const [widgetClass, setWidgetClass] = useState(null) // helper to help pass the widget type from sidebar to canvas
const onDragStart = (element, widgetClass=null) => {
setDraggedElement(element)
if (widgetClass && !isSubClassOfWidget(widgetClass))
throw new Error("widgetClass must inherit from the Widget base class")
setWidgetClass(() => widgetClass) // store the class so later it can be passed to the canvas from sidebar
}
const onDragEnd = () => {
setDraggedElement(null)
setWidgetClass(null)
}
return (
<DragDropProvider value={{ draggedElement, overElement, setOverElement,
widgetClass, onDragStart, onDragEnd }}>
{children}
</DragDropProvider>
)
}

View File

@@ -0,0 +1,88 @@
import React, { useEffect, useRef } from "react"
import { useDragDropManager, useDraggable } from "@dnd-kit/react"
import { CSS } from "@dnd-kit/utilities"
import { useDragContext } from "../draggableContext"
function Draggable({dragElementType, draggableType, dragWidgetClass = null, elementMetaData, ...props}) {
const draggableRef = useRef(null);
const { ref, draggable } = useDraggable({
id: dragElementType,
feedback: draggableType || "default",
type: dragElementType
// data: { title: props.children }
})
const {onDragStart, onDragEnd, disableStyle=false} = useDragContext()
const manager = useDragDropManager()
useEffect(() => {
manager?.monitor?.addEventListener("dragstart", handleDragStart)
manager?.monitor?.addEventListener("dragend", handleDragEnd)
return () => {
manager?.monitor?.removeEventListener("dragstart", handleDragStart)
manager?.monitor?.removeEventListener("dragend", handleDragEnd)
}
}, [manager])
// const style = transform ? {
// transform: CSS.Translate.toString(transform),
// } : undefined
const handleRef = (node) => {
draggableRef.current = node
ref(node)
}
const handleDragStart = (event) => {
const {source} = event.operation
if (!draggable.isDragSource){
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'))
if (!draggable.isDragSource){
return
}
onDragEnd()
}
// TODO: remove element meta data from props
return (
<div
ref={handleRef}
className={`${props.className}`}
// style={!disableStyle ? style : null} //enable this to show like the original item is moving, if commented out the original item will not have css effects
data-drag-start-within // this attribute indicates that the drag is occurring from within the project and not a outside file drop
data-draggable-type={dragElementType}
{...props}
>
{props.children}
</div>
)
}
export default Draggable

View File

@@ -0,0 +1,189 @@
import React, { useEffect, useRef, useState } from 'react'
import { useDragDropManager, useDroppable } from '@dnd-kit/react'
import { useDragContext } from '../draggableContext'
import {
pointerIntersection,
closestCenter,
shapeIntersection
} from '@dnd-kit/collision'
function Droppable(props) {
const droppableRef = useRef(null)
const { droppableTags, onDrop } = props
const { draggedElement, setOverElement, widgetClass, posMetaData } = useDragContext()
const { ref, isDropTarget, droppable} = useDroppable({
id: props.id,
collisionDetector: pointerIntersection,
// 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 manager = useDragDropManager()
// const {}
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", handleDragLeave)
manager?.monitor?.addEventListener("dragmove", handleDragOver)
return () => {
manager?.monitor?.removeEventListener("dragstart", handleDragEnter)
manager?.monitor?.removeEventListener("dragend", handleDragLeave)
manager?.monitor?.removeEventListener("dragmove", handleDragOver)
}
}, [manager, draggedElement, widgetClass, posMetaData])
const handleRef = (node) => {
droppableRef.current = node
ref(node)
}
// TODO: handle Drop on Canvas
const handleDragEnter = (e) => {
const {target, source} = e.operation
if (!droppable.isDropTarget){
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))
))
setAllowDrop({allow: dropAllowed, show: true})
}
const handleDragOver = (e) => {
const {target} = e.operation
if (!droppable.isDropTarget){
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 (!droppable.isDropTarget){
return
}
console.log("dropping")
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))
))
console.log("initial POs: ", posMetaData)
if (onDrop && dropAllowed) {
onDrop(e, draggedElement, widgetClass, posMetaData)
console.log("Dropped")
}
setTimeout(() => setAllowDrop({allow: true, show: false}), 10)
}
const handleDragLeave = (e) => {
// const {target} = e.operation
if (droppable.isDropTarget){
handleDropEvent(e)
}else{
setAllowDrop({allow: false, show: false})
}
}
return (
<div ref={handleRef} className={`${props.className}` || ''}>
{props.children}
{
droppable.isDropTarget &&
<div className={`
tw-absolute tw-top-0 tw-left-0 tw-w-full tw-h-full tw-z-[0] tw-p-3
tw-border-2 tw-border-dashed tw-rounded-lg tw-pointer-events-none
`}>
<div className={`${allowDrop.allow ? "tw-bg-[#82ff1c31]" : "tw-bg-[#eb5d3646]"} tw-w-full tw-h-full tw-pointer-events-none`}>
</div>
</div>
}
</div>
)
}
export default Droppable

View File

@@ -1,5 +1,6 @@
import React, { createContext, useContext, useState } from 'react'
import { isSubClassOfWidget } from '../../utils/widget'
import { useDndContext } from '@dnd-kit/core'
// import Widget from '../../canvas/widgets/base'
export const DragContext = createContext()
@@ -11,25 +12,44 @@ export const DragProvider = ({ children }) => {
const [draggedElement, setDraggedElement] = useState(null)
const [overElement, setOverElement] = useState(null) // the element the dragged items is over
const [posMetaData, setPosMetaData] = useState({dragStartCursorPos: {x: 0, y: 0}, initialPos: {x: 0, y: 0}})
const [dragElementMetaData, setDragElementMetaData] = useState({})
const [widgetClass, setWidgetClass] = useState(null) // helper to help pass the widget type from sidebar to canvas
const onDragStart = (element, widgetClass=null) => {
const [isDragging, setIsDragging] = useState(false)
const onDragStart = (element, widgetClass=null, metaData={}) => {
setDraggedElement(element)
setIsDragging(true)
setDragElementMetaData(metaData)
if (widgetClass && !isSubClassOfWidget(widgetClass))
throw new Error("widgetClass must inherit from the Widget base class")
setWidgetClass(() => widgetClass) // store the class so later it can be passed to the canvas from sidebar
}
const onDragEnd = () => {
const onDragEnd = (pos) => {
setDraggedElement(null)
setWidgetClass(null)
setIsDragging(false)
// setInitialOffset({x: 0, y: 0})
setDragElementMetaData({})
}
return (
<DragContext.Provider value={{ draggedElement, overElement, setOverElement,
widgetClass, onDragStart, onDragEnd }}>
widgetClass, onDragStart, onDragEnd, isDragging,
dragElementMetaData, setDragElementMetaData,
posMetaData, setPosMetaData
}}>
{children}
</DragContext.Provider>
)

View File

@@ -1,28 +0,0 @@
import React from "react"
import {useDraggable} from "@dnd-kit/core"
import { CSS } from "@dnd-kit/utilities"
function Draggable(props) {
const {attributes, listeners, setNodeRef, transform} = useDraggable({
id: props.id,
data: {title: props.children}
})
const style = transform ? {
transform: CSS.Translate.toString(transform),
} : undefined
return (
<button className={`tw-bg-transparent tw-outline-none tw-border-none ${props.className}`}
ref={setNodeRef}
style={style}
{...listeners}
{...attributes}>
{props.children}
</button>
)
}
export default Draggable

View File

@@ -1,20 +0,0 @@
import React from 'react'
import {useDroppable} from '@dnd-kit/core'
function Droppable(props) {
const {isOver, setNodeRef} = useDroppable({
id: props.id,
})
const style = {
backgroundColor: isOver ? 'green' : '',
}
return (
<div ref={setNodeRef} style={style} className={props.className || ''}>
{props.children}
</div>
)
}
export default Droppable

View File

@@ -0,0 +1,42 @@
License held by paul
Github username: PaulleDemon
Below is the License for source code of PyUIBuilder. For license regarding source code generated by PyUIBuilder Refer Readme.md file (it has basically no restrictions).
The source code for PyUIBuilder is Dual licensed, most parts of the code comes under AGPL,
some parts under a different non-commercial use license.
-----------------------
License in short
* You are free fork, modify and redistribute it given the original or derived works is kept free and open-source and under the same license.
* Another restriction is you can't redistribute in binary or executable formats without asking me first.
* If you plan on making money on this work or derived work you need permission first.
License was planned to be kept under one AGPL or less restrictive license, but since its difficult for me to continue providing free and open-source software without proper funding. It's under this license.
Once there enough money to fund this project and my upcoming FOSS projects, The license will be brought back to a single AGPL or much lesser restrictive license. The fastest way to make this happen is by purchasing a one-time license or fund me.
Read the full license below.
--------------------
1. License Grant
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to use, copy, modify, merge, publish, and distribute the source code, subject to the following conditions:
2. Derived Work License
Any modifications, derivative works, or copies of the Software must retain this license. All derivative works must be open-sourced under the same license terms. You are not permitted to relicense or sub-license any portion of the Software under a different license without explicit prior written permission from the original author.
3. Distribution Restriction on Executables
You are not permitted to distribute the Software in compiled, executable, or binary form without prior written consent from the original author. This applies to both original and derivative works.
4. Commercial Use Restriction
The Software, in its original or modified form, cannot be used for any commercial purpose without prior written permission from the original author. This includes selling, licensing, or offering the Software as part of a service.
5. Non-Commercial Use
You may freely use, copy, modify, and distribute the source code of the Software for non-commercial purposes, provided that the same terms of this license are maintained in all copies or derivative works.
6. Changes to the License
The original author reserves the right to modify, amend, or update this license at any time. Any changes will apply only to future versions of the Software. Any prior versions of the Software, including derivative works, will continue to be governed by the version of the license in effect at the time the work was created.
7. Disclaimer
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -408,7 +408,6 @@ export class TkinterBase extends Widget {
}
this.updateState({ attrs: newAttrs }, callback)
})

View File

@@ -12,6 +12,8 @@ import { QueryClient, QueryClientProvider } from "react-query";
import "./styles/tailwind.css";
import "./styles/index.css";
import { FileUploadProvider } from "./contexts/fileUploadContext";
import { DndContext } from "@dnd-kit/core";
import { DragProvider } from "./components/draggable/draggableContext";
const originalSetItem = localStorage.setItem;
// triggers itemsChaned event whenever the item in localstorage is chanegd.
@@ -69,7 +71,9 @@ root.render(
<Provider store={store}>
<QueryClientProvider client={queryClient} >
<FileUploadProvider>
<App />
<DragProvider>
<App />
</DragProvider>
</FileUploadProvider>
</QueryClientProvider>
</Provider>

View File

@@ -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;