16 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
paul
ed333d6ee6 fixed Relief string 2025-01-01 16:43:25 +05:30
22 changed files with 1134 additions and 298 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. 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. 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
-------------------------- --------------------------
This license can be found in multiple folders
Below is the license for code under src/canvas/
License for non-commercial use in short 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. * 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", "version": "0.1.0",
"dependencies": { "dependencies": {
"@ant-design/icons": "^5.4.0", "@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/modifiers": "^7.0.0",
"@dnd-kit/react": "^0.0.9",
"@dnd-kit/utilities": "^3.2.2", "@dnd-kit/utilities": "^3.2.2",
"@reduxjs/toolkit": "^2.2.7", "@reduxjs/toolkit": "^2.2.7",
"@testing-library/jest-dom": "^5.17.0", "@testing-library/jest-dom": "^5.17.0",
@@ -2548,10 +2550,22 @@
"node": ">=10" "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": { "node_modules/@dnd-kit/accessibility": {
"version": "3.1.0", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.0.tgz", "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz",
"integrity": "sha512-ea7IkhKvlJUv9iSHJOnxinBcoOI3ppGnnL+VDJ75O45Nss6HtZd8IdN8touXPDtASfeI2T2LImb8VOZcL47wjQ==", "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==",
"license": "MIT",
"dependencies": { "dependencies": {
"tslib": "^2.0.0" "tslib": "^2.0.0"
}, },
@@ -2559,12 +2573,24 @@
"react": ">=16.8.0" "react": ">=16.8.0"
} }
}, },
"node_modules/@dnd-kit/core": { "node_modules/@dnd-kit/collision": {
"version": "6.1.0", "version": "0.0.9",
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.1.0.tgz", "resolved": "https://registry.npmjs.org/@dnd-kit/collision/-/collision-0.0.9.tgz",
"integrity": "sha512-J3cQBClB4TVxwGo3KEjssGEXNJqGVWx17aRTZ1ob0FliR5IjYgTxl5YJbKTzA6IzrtelotH19v6y7uoIRUZPSg==", "integrity": "sha512-8Brbcd0dM/y2yzg/6EI9O/SvsVAglx0fUDvqPSsXACewR+//OGno1GVKndh35GAVKqDxOgG3Q/fw+CVnMnFv6g==",
"license": "MIT",
"dependencies": { "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", "@dnd-kit/utilities": "^3.2.2",
"tslib": "^2.0.0" "tslib": "^2.0.0"
}, },
@@ -2573,6 +2599,29 @@
"react-dom": ">=16.8.0" "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": { "node_modules/@dnd-kit/modifiers": {
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/@dnd-kit/modifiers/-/modifiers-7.0.0.tgz", "resolved": "https://registry.npmjs.org/@dnd-kit/modifiers/-/modifiers-7.0.0.tgz",
@@ -2586,6 +2635,32 @@
"react": ">=16.8.0" "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": { "node_modules/@dnd-kit/utilities": {
"version": "3.2.2", "version": "3.2.2",
"resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz",
@@ -3900,6 +3975,16 @@
"node": ">= 8" "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": { "node_modules/@rc-component/async-validator": {
"version": "5.0.4", "version": "5.0.4",
"resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.0.4.tgz", "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.0.4.tgz",

View File

@@ -7,8 +7,10 @@
}, },
"dependencies": { "dependencies": {
"@ant-design/icons": "^5.4.0", "@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/modifiers": "^7.0.0",
"@dnd-kit/react": "^0.0.9",
"@dnd-kit/utilities": "^3.2.2", "@dnd-kit/utilities": "^3.2.2",
"@reduxjs/toolkit": "^2.2.7", "@reduxjs/toolkit": "^2.2.7",
"@testing-library/jest-dom": "^5.17.0", "@testing-library/jest-dom": "^5.17.0",

View File

@@ -3,6 +3,7 @@
* Github: PaulleDemon * Github: PaulleDemon
*/ */
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { createPortal } from "react-dom"
import { LayoutFilled, ProductFilled, CloudUploadOutlined, DatabaseFilled } from "@ant-design/icons" import { LayoutFilled, ProductFilled, CloudUploadOutlined, DatabaseFilled } from "@ant-design/icons"
// import { DndContext, useSensors, useSensor, PointerSensor, closestCorners, DragOverlay, rectIntersection } from '@dnd-kit/core' // 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 UploadsContainer from './sidebar/uploadsContainer'
import WidgetsContainer from './sidebar/widgetsContainer' import WidgetsContainer from './sidebar/widgetsContainer'
import { DragProvider } from './components/draggable/draggableContext' import { DragProvider, useDragContext } from './components/draggable/draggableContext'
import PluginsContainer from './sidebar/pluginsContainer' import PluginsContainer from './sidebar/pluginsContainer'
import { FileUploadProvider, useFileUploadContext } from './contexts/fileUploadContext' 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 TkMainWindow from './frameworks/tkinter/widgets/mainWindow'
import CTkMainWindow from './frameworks/customtk/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() { 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 [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 [sidebarWidgets, setSidebarWidgets] = useState(TkinterWidgets || [])
const [sidebarPlugins, setSidebarPlugins] = useState(TkinterPluginWidgets || []) const [sidebarPlugins, setSidebarPlugins] = useState(TkinterPluginWidgets || [])
@@ -53,6 +59,7 @@ function App() {
// NOTE: the below reference is no longer required // NOTE: the below reference is no longer required
const [canvasWidgets, setCanvasWidgets] = useState([]) // contains the reference to the widgets inside the canvas const [canvasWidgets, setCanvasWidgets] = useState([]) // contains the reference to the widgets inside the canvas
const sidebarTabs = [ const sidebarTabs = [
{ {
name: "Widgets", name: "Widgets",
@@ -196,6 +203,9 @@ function App() {
} }
// console.log("dragged element: ", dragElementMetaData)
return ( return (
<div className="tw-w-full tw-h-[100vh] tw-flex tw-flex-col tw-bg-primaryBg"> <div className="tw-w-full tw-h-[100vh] tw-flex tw-flex-col tw-bg-primaryBg">
<Header className="tw-h-[6vh]" onExportClick={handleCodeGen} <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> <p>Are you sure you want to change the framework? This will clear the canvas.</p>
</Modal> */} </Modal> */}
<DragProvider> <DragDropProvider onDragStart={(e) => {console.log("Drag start event: ", e)}}>
<div className="tw-w-full tw-h-[94vh] tw-flex"> <div className="tw-w-full tw-h-[94vh] tw-flex">
<Sidebar tabs={sidebarTabs}/> <Sidebar tabs={sidebarTabs}/>
@@ -217,9 +227,26 @@ function App() {
{/* </ActiveWidgetProvider> */} {/* </ActiveWidgetProvider> */}
</div> </div>
{/* dragOverlay (dnd-kit) helps move items from one container to another */} {/* 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> </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 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. * 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. * 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 { isSubClassOfWidget } from "../utils/widget"
import { ButtonModal } from "../components/modals" import { ButtonModal } from "../components/modals"
import ResizeWidgetContainer from "./resizeContainer" import ResizeWidgetContainer from "./resizeContainer"
import Droppable from "../components/draggable/dnd/droppableDnd"
// const DotsBackground = require("../assets/background/dots.svg") // const DotsBackground = require("../assets/background/dots.svg")
@@ -197,10 +198,9 @@ class Canvas extends React.Component {
innerWidget = ref.current innerWidget = ref.current
break break
} }
// console.log("refs: ", ref) // console.log("refs: ", ref)
// TODO: remove the ref.current? if there are bugs it would become hard to debug // 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) { if (!innerWidget) {
innerWidget = ref.current 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 * Handles drop event to canvas from the sidebar and on canvas widget movement
* @param {DragEvent} e * @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 }) this.setState({ isWidgetDragging: false })
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 // if the drag is starting from outside (eg: file drop) or if drag doesn't exist
return return
} }
const container = draggedElement.getAttribute("data-container") const container = draggedElement.getAttribute("data-container")
const canvasRect = this.canvasRef.current.getBoundingClientRect() const canvasRect = this.canvasRef.current.getBoundingClientRect()
const draggedElementRect = draggedElement.getBoundingClientRect() const { clientX, clientY } = e.nativeEvent
const elementWidth = draggedElementRect.width
const elementHeight = draggedElementRect.height
const { clientX, clientY } = e
let finalPosition = { let finalPosition = {
x: (clientX - canvasRect.left) / this.state.zoom, x: ((clientX - canvasRect.left) / this.state.zoom),
y: (clientY - canvasRect.top) / this.state.zoom, y: ((clientY - canvasRect.top) / this.state.zoom),
} }
if (container === WidgetContainer.SIDEBAR) { if (container === WidgetContainer.SIDEBAR) {
if (!widgetClass) { if (!widgetClass) {
throw new Error("WidgetClass has to be passed for widgets dropped from sidebar") 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 // if the widget is being dropped from the sidebar, use the info to create the widget first
this.createWidget(widgetClass, ({ id, widgetRef }) => { this.createWidget(widgetClass, ({ id, widgetRef }) => {
widgetRef.current.setPos(finalPosition.x, finalPosition.y) widgetRef.current.setPos(finalPosition.x, finalPosition.y)
// widgetRef.current.setPos(10, 10)
}) })
} else if ([WidgetContainer.CANVAS, WidgetContainer.WIDGET].includes(container)) { } else if ([WidgetContainer.CANVAS, WidgetContainer.WIDGET].includes(container)) {
// snaps to center // 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 = { finalPosition = {
x: (clientX - canvasRect.left) / this.state.zoom - (elementWidth / 2) / this.state.zoom, x: finalPosition.x - initialOffset.x - this.state.currentTranslate.x,
y: (clientY - canvasRect.top) / this.state.zoom - (elementHeight / 2) / this.state.zoom, y: finalPosition.y - initialOffset.y - this.state.currentTranslate.y
} }
let widgetId = draggedElement.getAttribute("data-widget-id") let widgetId = draggedElement.getAttribute("data-widget-id")
const widgetObj = this.getWidgetById(widgetId) const widgetObj = this.getWidgetById(widgetId)
// console.log("WidgetObj: ", widgetObj) // console.log("WidgetObj: ", widgetObj)
if (container === WidgetContainer.CANVAS) { if (container === WidgetContainer.CANVAS) {
widgetObj.current.setPos(finalPosition.x, finalPosition.y) widgetObj.current.setPos(finalPosition.x, finalPosition.y)
} else if (container === WidgetContainer.WIDGET) { } else if (container === WidgetContainer.WIDGET) {
console.log("removing")
// if the widget was inside another widget move it outside // if the widget was inside another widget move it outside
let childWidgetObj = this.findWidgetFromListById(widgetObj.current.getId()) let childWidgetObj = this.findWidgetFromListById(widgetObj.current.getId())
let parentWidgetObj = this.findWidgetFromListById(childWidgetObj.parent) let parentWidgetObj = this.findWidgetFromListById(childWidgetObj.parent)
@@ -782,7 +800,7 @@ class Canvas extends React.Component {
// console.log("event: ", event) // console.log("event: ", event)
// widgets data structure { id, widgetType: widgetComponentType, children: [], parent: "" } // widgets data structure { id, widgetType: widgetComponentType, children: [], parent: "" }
const dropWidgetObj = this.findWidgetFromListById(parentWidgetId) const dropWidgetObj = this.findWidgetFromListById(parentWidgetId)
// Find the dragged widget object // Find the dragged wihandleAddWidgetChilddget object
let dragWidgetObj = this.findWidgetFromListById(dragElementID) let dragWidgetObj = this.findWidgetFromListById(dragElementID)
// console.log("Drag widget obj: ", dragWidgetObj, dropWidgetObj) // console.log("Drag widget obj: ", dragWidgetObj, dropWidgetObj)
@@ -1063,6 +1081,7 @@ class Canvas extends React.Component {
} }
render() { render() {
// FIXME: inner canvas not recognized as droppable or some thing
return ( return (
<div className="tw-relative tw-overflow-hidden tw-flex tw-w-full tw-h-full tw-max-h-[100vh]"> <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> </ButtonModal>
</div> </div>
<Droppable className={"tw-w-full tw-h-full"}
droppableTags={{ exclude: ["image", "video"] }}
onDrop={this.handleDropEvent}
id="canvas-droppable">
{/* <ActiveWidgetProvider> */} {/* <ActiveWidgetProvider> */}
<DroppableWrapper id="canvas-droppable" {/* <DroppableWrapper id="canvas-droppable"
droppableTags={{ exclude: ["image", "video"] }} droppableTags={{ exclude: ["image", "video"] }}
className="tw-w-full tw-h-full" className="tw-w-full tw-h-full"
onDrop={this.handleDropEvent}> onDrop={this.handleDropEvent}> */}
{/* <DragWidgetProvider> */} {/* <DragWidgetProvider> */}
<Dropdown trigger={['contextMenu']} mouseLeaveDelay={0} menu={{ items: this.state.contextMenuItems, }}> <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" <div className="tw-w-full tw-h-full tw-outline-none tw-flex tw-relative tw-bg-[#f2f2f2] tw-overflow-hidden"
ref={this.canvasContainerRef} ref={this.canvasContainerRef}
style={{ style={{
transition: " transform 0.3s ease-in-out", // transition: " transform 0.3s ease-in-out",
backgroundImage: `url(${DotsBackground})`, backgroundImage: `url(${DotsBackground})`,
backgroundSize: 'cover', // Ensure proper sizing if needed backgroundSize: 'cover', // Ensure proper sizing if needed
backgroundRepeat: 'no-repeat', backgroundRepeat: 'no-repeat',
@@ -1111,7 +1134,7 @@ class Canvas extends React.Component {
}} }}
/> />
{/* Canvas */} {/* 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}> ref={this.canvasRef}>
<div className="tw-relative tw-w-full tw-h-full"> <div className="tw-relative tw-w-full tw-h-full">
{ {
@@ -1127,8 +1150,8 @@ class Canvas extends React.Component {
</div> </div>
</Dropdown> </Dropdown>
{/* </DragWidgetProvider> */} {/* </DragWidgetProvider> */}
</DroppableWrapper> {/* </DroppableWrapper> */}
</Droppable>
<CanvasToolBar isOpen={this.state.toolbarOpen} <CanvasToolBar isOpen={this.state.toolbarOpen}
widgetType={this.state.selectedWidget?.getWidgetType() || ""} widgetType={this.state.selectedWidget?.getWidgetType() || ""}
attrs={this.state.toolbarAttrs} attrs={this.state.toolbarAttrs}

View File

@@ -12,7 +12,8 @@ import WidgetContainer from "../constants/containers"
import { DragContext } from "../../components/draggable/draggableContext" import { DragContext } from "../../components/draggable/draggableContext"
import { isNumeric, removeKeyFromObject } from "../../utils/common" import { isNumeric, removeKeyFromObject } from "../../utils/common"
import { info } from "autoprefixer" 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 // TODO: make it possible to apply widgetInnerStyle on load
@@ -71,7 +72,7 @@ class Widget extends React.Component {
this.state = { this.state = {
zIndex: 0, zIndex: 1,
selected: false, selected: false,
widgetName: widgetName || 'widget', // this will later be converted to variable name widgetName: widgetName || 'widget', // this will later be converted to variable name
enableRename: false, // will open the widgets editable div for renaming enableRename: false, // will open the widgets editable div for renaming
@@ -90,7 +91,7 @@ class Widget extends React.Component {
pos: { x: 0, y: 0 }, pos: { x: 0, y: 0 },
size: { width: 100, height: 100 }, size: { width: 100, height: 100 },
fitContent: {width: false, height: false}, fitContent: { width: false, height: false },
positionType: PosType.ABSOLUTE, positionType: PosType.ABSOLUTE,
widgetOuterStyling: { widgetOuterStyling: {
@@ -200,7 +201,7 @@ class Widget extends React.Component {
componentDidMount() { componentDidMount() {
if (this.state.attrs.layout){ if (this.state.attrs.layout) {
this.setLayout(this.state.attrs.layout.value) this.setLayout(this.state.attrs.layout.value)
// console.log("prior layout: ", this.state.attrs.layout.value) // console.log("prior layout: ", this.state.attrs.layout.value)
} }
@@ -208,8 +209,7 @@ class Widget extends React.Component {
if (this.state.attrs.styling.backgroundColor) if (this.state.attrs.styling.backgroundColor)
this.setWidgetInnerStyle('backgroundColor', this.state.attrs.styling?.backgroundColor.value || "#fff") 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() { componentWillUnmount() {
@@ -277,7 +277,7 @@ class Widget extends React.Component {
value: this.state.fitContent.width, value: this.state.fitContent.width,
onChange: (value) => { onChange: (value) => {
this.updateState((prev) => ({ this.updateState((prev) => ({
fitContent: {...prev.fitContent, width: value} fitContent: { ...prev.fitContent, width: value }
})) }))
} }
}, },
@@ -287,7 +287,7 @@ class Widget extends React.Component {
value: this.state.fitContent.height, value: this.state.fitContent.height,
onChange: (value) => { onChange: (value) => {
this.updateState((prev) => ({ 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 return this.constructor.widgetType
} }
getRequirements(){ getRequirements() {
return this.constructor.requirements return this.constructor.requirements
} }
getImports(){ getImports() {
return this.constructor.requiredImports return this.constructor.requiredImports
} }
generateCode(){ generateCode() {
throw new NotImplementedError("generateCode() must be implemented by the subclass") throw new NotImplementedError("generateCode() must be implemented by the subclass")
} }
@@ -365,6 +365,7 @@ class Widget extends React.Component {
} }
setPos(x, y) { setPos(x, y) {
console.log("position set: ", x, y)
this.setState({ this.setState({
pos: { x, y } pos: { x, y }
@@ -404,7 +405,7 @@ class Widget extends React.Component {
return this.elementRef.current return this.elementRef.current
} }
hideDroppableIndicator(){ hideDroppableIndicator() {
console.log("hide drop indicator") console.log("hide drop indicator")
this.setState({ this.setState({
showDroppableStyle: { showDroppableStyle: {
@@ -421,7 +422,7 @@ class Widget extends React.Component {
* @param {string} path - eg: styling.backgroundColor * @param {string} path - eg: styling.backgroundColor
* @returns * @returns
*/ */
removeAttr = (path) =>{ removeAttr = (path) => {
const newAttrs = removeKeyFromObject(path, this.state.attrs) const newAttrs = removeKeyFromObject(path, this.state.attrs)
@@ -484,7 +485,7 @@ class Widget extends React.Component {
* this is a helper function to remove any non-serializable data associated with attrs * this is a helper function to remove any non-serializable data associated with attrs
* eg: {"styling.backgroundColor": "#ffff", "layout": {layout: "flex", direction: "", grid: }} * eg: {"styling.backgroundColor": "#ffff", "layout": {layout: "flex", direction: "", grid: }}
*/ */
serializeAttrsValues(){ serializeAttrsValues() {
const serializeValues = (obj, currentPath = "") => { const serializeValues = (obj, currentPath = "") => {
const result = {} const result = {}
@@ -532,28 +533,28 @@ class Widget extends React.Component {
* inform the child about the parent layout changes * inform the child about the parent layout changes
* @param {Layouts} parentLayout * @param {Layouts} parentLayout
*/ */
setParentLayout(parentLayout){ setParentLayout(parentLayout) {
if (!parentLayout){ if (!parentLayout) {
// if parent layout is null (i,e the widget is on the canvas) // if parent layout is null (i,e the widget is on the canvas)
return {} return {}
} }
const {layout, direction, gap} = parentLayout const { layout, direction, gap } = parentLayout
let updates = { let updates = {
parentLayout: parentLayout, parentLayout: parentLayout,
} }
if (layout === Layouts.FLEX || layout === Layouts.GRID){ if (layout === Layouts.FLEX || layout === Layouts.GRID) {
updates = { updates = {
...updates, ...updates,
positionType: PosType.NONE positionType: PosType.NONE
} }
}else if (layout === Layouts.PLACE){ } else if (layout === Layouts.PLACE) {
updates = { updates = {
...updates, ...updates,
positionType: PosType.ABSOLUTE positionType: PosType.ABSOLUTE
@@ -563,11 +564,11 @@ class Widget extends React.Component {
this.setState(updates) this.setState(updates)
} }
getParentLayout(){ getParentLayout() {
return this.state.parentLayout return this.state.parentLayout
} }
getLayout(){ getLayout() {
return this.state?.attrs?.layout?.value || Layouts.FLEX return this.state?.attrs?.layout?.value || Layouts.FLEX
} }
@@ -589,13 +590,13 @@ class Widget extends React.Component {
// gridAutoCols: 'minmax(100px, auto)', // Cols 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" widgetStyle["alignItems"] = "flex-start"
}else if (align === "center"){ } else if (align === "center") {
widgetStyle["alignItems"] = "center" widgetStyle["alignItems"] = "center"
}else if (align === "end"){ } else if (align === "end") {
widgetStyle["alignItems"] = "flex-end" widgetStyle["alignItems"] = "flex-end"
}else{ } else {
widgetStyle["alignItems"] = "unset" widgetStyle["alignItems"] = "unset"
} }
@@ -604,7 +605,7 @@ class Widget extends React.Component {
}) })
this.setAttrValue("layout", value) 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} key - The string in react Style format
* @param {string} value - Value of the style * @param {string} value - Value of the style
*/ */
setWidgetOuterStyle(key, value){ setWidgetOuterStyle(key, value) {
const widgetStyle = { const widgetStyle = {
...this.state.widgetOuterStyling, ...this.state.widgetOuterStyling,
[key]: value [key]: value
@@ -661,7 +662,7 @@ class Widget extends React.Component {
const fitWidth = this.state.fitContent?.width const fitWidth = this.state.fitContent?.width
const fitHeight = this.state.fitContent?.height 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") message.warning("both width and height are set to fit-content, unset it to start resizing")
return return
} }
@@ -713,7 +714,7 @@ class Widget extends React.Component {
* *
* serialize data for saving * serialize data for saving
*/ */
serialize(){ serialize() {
// NOTE: when serializing make sure, you are only passing serializable objects not functions or other // NOTE: when serializing make sure, you are only passing serializable objects not functions or other
return ({ return ({
zIndex: this.state.zIndex, zIndex: this.state.zIndex,
@@ -735,27 +736,27 @@ class Widget extends React.Component {
* @param {object} data * @param {object} data
* @param {() => void | undefined} callback - optional callback that will be called after load * @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 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 = { let layoutUpdates = {
parentLayout: parentLayout.layout || null parentLayout: parentLayout.layout || null
} }
if (parentLayout?.layout === Layouts.FLEX || parentLayout?.layout === Layouts.GRID){ if (parentLayout?.layout === Layouts.FLEX || parentLayout?.layout === Layouts.GRID) {
layoutUpdates = { layoutUpdates = {
...layoutUpdates, ...layoutUpdates,
positionType: PosType.NONE positionType: PosType.NONE
} }
}else if (parentLayout?.layout === Layouts.PLACE){ } else if (parentLayout?.layout === Layouts.PLACE) {
layoutUpdates = { layoutUpdates = {
...layoutUpdates, ...layoutUpdates,
positionType: PosType.ABSOLUTE positionType: PosType.ABSOLUTE
@@ -789,75 +790,34 @@ class Widget extends React.Component {
nestedObject[lastKey].value = value nestedObject[lastKey].value = value
}) })
if (newAttrs?.styling?.backgroundColor){ if (newAttrs?.styling?.backgroundColor) {
// some widgets don't have background color // some widgets don't have background color
this.setWidgetInnerStyle("backgroundColor", newAttrs.styling.backgroundColor) this.setWidgetInnerStyle("backgroundColor", newAttrs.styling.backgroundColor)
} }
this.updateState({ attrs: newAttrs }, callback) this.updateState({ attrs: newAttrs }, callback)
}) })
} }
handleDragStart = (e, callback) => { handleDragStart = (e, ) => {
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)
// NOTE: this line will prevent problem's such as self-drop or dropping inside its own children // 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 }) 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 // if the drag is starting from outside (eg: file drop) or if drag doesn't exist
return return
} }
const dragEleType = draggedElement.getAttribute("data-draggable-type") 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 = { let showDrop = {
allow: true, allow: true,
show: true show: true
@@ -889,39 +849,17 @@ class Widget extends React.Component {
handleDragOver = (e, draggedElement) => { 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) => { 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 // if the drag is starting from outside (eg: file drop) or if drag doesn't exist
return return
} }
e.preventDefault()
e.stopPropagation()
// FIXME: sometimes the elements showDroppableStyle is not gone, when dropping on the same widget // FIXME: sometimes the elements showDroppableStyle is not gone, when dropping on the same widget
this.setState({ this.setState({
@@ -932,7 +870,7 @@ 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 // prevent drop on itself, since the widget is invisible when dragging, if dropped on itself, it may consume itself
return return
} }
@@ -992,14 +930,11 @@ class Widget extends React.Component {
} }
handleDragLeave = (e, draggedElement, overElement) => { handleDragLeave = (e) => {
e.preventDefault()
e.stopPropagation()
const rect = this.getBoundingRect() const rect = this.getBoundingRect()
const {clientX, clientY} = e const { clientX, clientY } = e
const isInBoundingBox = (clientX >= rect.left && clientX <= rect.right && const isInBoundingBox = (clientX >= rect.left && clientX <= rect.right &&
clientY >= rect.top && clientY <= rect.bottom) clientY >= rect.top && clientY <= rect.bottom)
@@ -1020,11 +955,10 @@ class Widget extends React.Component {
} }
} }
handleDragEnd = (callback) => { handleDragEnd = () => {
callback()
this.setState({ isDragging: false }) this.setState({ isDragging: false })
this.enablePointerEvents() // this.enablePointerEvents()
// console.log("enable pointer events: ")
// this.props.onWidgetDragEnd(this.elementRef?.current) // this.props.onWidgetDragEnd(this.elementRef?.current)
} }
@@ -1039,8 +973,8 @@ class Widget extends React.Component {
this.elementRef.current.style.pointerEvents = "auto" this.elementRef.current.style.pointerEvents = "auto"
} }
getInnerRenderStyling(){ getInnerRenderStyling() {
const {width, height, minWidth, minHeight} = this.getRenderSize() const { width, height, minWidth, minHeight } = this.getRenderSize()
const styling = { const styling = {
...this.state.widgetInnerStyling, ...this.state.widgetInnerStyling,
@@ -1052,7 +986,7 @@ class Widget extends React.Component {
return styling return styling
} }
getRenderSize(){ getRenderSize() {
let width = isNumeric(this.state.size.width) ? `${this.state.size.width}px` : this.state.size.width 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 height = isNumeric(this.state.size.height) ? `${this.state.size.height}px` : this.state.size.height
@@ -1060,11 +994,11 @@ class Widget extends React.Component {
let fitWidth = this.state.fitContent.width let fitWidth = this.state.fitContent.width
let fitHeight = this.state.fitContent.height let fitHeight = this.state.fitContent.height
if (fitWidth){ if (fitWidth) {
width = "max-content" width = "max-content"
} }
if (fitHeight){ if (fitHeight) {
height = "max-content" height = "max-content"
} }
@@ -1072,7 +1006,7 @@ class Widget extends React.Component {
let minWidth = fitWidth ? this.state.size.width : this.minSize.width let minWidth = fitWidth ? this.state.size.width : this.minSize.width
let minHeight = fitHeight ? this.state.size.height : this.minSize.height let minHeight = fitHeight ? this.state.size.height : this.minSize.height
return {width, height, minWidth, minHeight} return { width, height, minWidth, minHeight }
} }
@@ -1095,7 +1029,7 @@ class Widget extends React.Component {
*/ */
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 // NOTE: first check tkinter behaviour with the width and height
@@ -1110,42 +1044,60 @@ class Widget extends React.Component {
height: height, height: height,
minWidth: minWidth, minWidth: minWidth,
minHeight: minHeight, 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: if the parent container has tw-overflow-none, then the resizable indicator are also hidden
// FIXME: re-enable pointer events
return ( return (
// <DragContext.Consumer>
<DragContext.Consumer> <DragContext.Consumer>
{ {
({ draggedElement, widgetClass, onDragStart, onDragEnd, overElement, setOverElement }) => { ({ draggedElement, widgetClass, setInitialOffset }) => {
return ( return (
<div data-widget-id={this.__id}
ref={this.elementRef} <WidgetDnd widgetId={this.__id}
className="tw-shadow-xl tw-w-fit tw-h-fit" disabled={false && this.state.dragEnabled}
style={outerStyle}
data-draggable-type={this.getWidgetType()} // helps with droppable 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)} isSortable={isSortable}
onDrop={(e) => {this.handleDropEvent(e, draggedElement, widgetClass); onDragEnd()}} sortableIndex={0}
onDragEnter={(e) => this.handleDragEnter(e, draggedElement, setOverElement)} canvas={this.canvas}
onDragLeave={(e) => this.handleDragLeave(e, draggedElement, overElement)}
// 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-relative tw-w-full tw-h-full tw-top-0 tw-left-0"
> >
@@ -1253,12 +1205,14 @@ class Widget extends React.Component {
</div> </div>
</div>
</WidgetDnd>
) )
} }
} }
</DragContext.Consumer> </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) 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 // 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) 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 { useEffect, useMemo, useRef } from "react"
import Draggable from "./utils/draggableDnd" import Draggable from "./draggable/dnd/draggableDnd"
import { GithubOutlined, GitlabOutlined, LinkOutlined, import { GithubOutlined, GitlabOutlined, LinkOutlined,
AudioOutlined, FileTextOutlined, AudioOutlined, FileTextOutlined,
@@ -29,11 +29,20 @@ export function SidebarWidgetCard({ name, img, url, license, widgetClass, innerR
return ( return (
// <Draggable className="tw-cursor-pointer" id={name}> <Draggable id={name}
<DraggableWrapper data-container={"sidebar"} className="tw-cursor-pointer tw-w-fit tw-bg-white tw-h-fit"
data-container={"sidebar"}
dragElementType={widgetClass.widgetType} dragElementType={widgetClass.widgetType}
dragWidgetClass={widgetClass} dragWidgetClass={widgetClass}
className="tw-cursor-pointer tw-w-fit tw-bg-white tw-h-fit"> 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 <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-rounded-md tw-overflow-hidden
@@ -72,8 +81,75 @@ export function SidebarWidgetCard({ name, img, url, license, widgetClass, innerR
</div> </div>
</div> </div>
</DraggableWrapper> </Draggable>
// </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"> */}
<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">
<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>
{/* </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 React, { createContext, useContext, useState } from 'react'
import { isSubClassOfWidget } from '../../utils/widget' import { isSubClassOfWidget } from '../../utils/widget'
import { useDndContext } from '@dnd-kit/core'
// import Widget from '../../canvas/widgets/base' // import Widget from '../../canvas/widgets/base'
export const DragContext = createContext() export const DragContext = createContext()
@@ -11,25 +12,44 @@ export const DragProvider = ({ children }) => {
const [draggedElement, setDraggedElement] = useState(null) const [draggedElement, setDraggedElement] = useState(null)
const [overElement, setOverElement] = useState(null) // the element the dragged items is over 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 [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) setDraggedElement(element)
setIsDragging(true)
setDragElementMetaData(metaData)
if (widgetClass && !isSubClassOfWidget(widgetClass)) if (widgetClass && !isSubClassOfWidget(widgetClass))
throw new Error("widgetClass must inherit from the Widget base class") 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 setWidgetClass(() => widgetClass) // store the class so later it can be passed to the canvas from sidebar
} }
const onDragEnd = () => { const onDragEnd = (pos) => {
setDraggedElement(null) setDraggedElement(null)
setWidgetClass(null) setWidgetClass(null)
setIsDragging(false)
// setInitialOffset({x: 0, y: 0})
setDragElementMetaData({})
} }
return ( return (
<DragContext.Provider value={{ draggedElement, overElement, setOverElement, <DragContext.Provider value={{ draggedElement, overElement, setOverElement,
widgetClass, onDragStart, onDragEnd }}> widgetClass, onDragStart, onDragEnd, isDragging,
dragElementMetaData, setDragElementMetaData,
posMetaData, setPosMetaData
}}>
{children} {children}
</DragContext.Provider> </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) this.updateState({ attrs: newAttrs }, callback)
}) })
@@ -569,7 +568,7 @@ export class TkinterWidgetBase extends TkinterBase{
config["bd"] = this.getAttrValue("styling.borderWidth") config["bd"] = this.getAttrValue("styling.borderWidth")
if (this.getAttrValue("styling.relief")) if (this.getAttrValue("styling.relief"))
config["relief"] = `"${this.getAttrValue("styling.relief")}"` config["relief"] = `tk.${this.getAttrValue("styling.relief")}`
if (this.getAttrValue("font.fontFamily") || this.getAttrValue("font.fontSize")){ if (this.getAttrValue("font.fontFamily") || this.getAttrValue("font.fontSize")){
config["font"] = `("${this.getAttrValue("font.fontFamily")}", ${this.getAttrValue("font.fontSize") || 12}, )` config["font"] = `("${this.getAttrValue("font.fontFamily")}", ${this.getAttrValue("font.fontSize") || 12}, )`

View File

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