Compare commits
15 Commits
code-edito
...
ui-fixes
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee953107f3 | ||
|
|
c9e12cccd4 | ||
|
|
3edf1a974c | ||
|
|
910ff81342 | ||
|
|
59a0c0b583 | ||
|
|
4d0ea22af6 | ||
|
|
b1c707a21d | ||
|
|
1ee137085e | ||
|
|
03a4984cbc | ||
|
|
72aafd17fe | ||
|
|
4df0e664ba | ||
|
|
62caafcf23 | ||
|
|
cc84a9f2d7 | ||
|
|
a47191ee72 | ||
|
|
6656d3f6c4 |
@@ -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
103
package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
35
src/App.js
35
src/App.js
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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 >
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
252
src/canvas/widgets/widgetDnd.js
Normal file
252
src/canvas/widgets/widgetDnd.js
Normal 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
|
||||||
@@ -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)
|
||||||
|
|||||||
42
src/components/License.txt
Normal file
42
src/components/License.txt
Normal 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.
|
||||||
@@ -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> */}
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
36
src/components/draggable/dnd/dndContext.js
Normal file
36
src/components/draggable/dnd/dndContext.js
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
88
src/components/draggable/dnd/draggableDnd.js
Normal file
88
src/components/draggable/dnd/draggableDnd.js
Normal 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
|
||||||
189
src/components/draggable/dnd/droppableDnd.js
Normal file
189
src/components/draggable/dnd/droppableDnd.js
Normal 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
|
||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
42
src/frameworks/License.txt
Normal file
42
src/frameworks/License.txt
Normal 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.
|
||||||
@@ -408,7 +408,6 @@ export class TkinterBase extends Widget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.updateState({ attrs: newAttrs }, callback)
|
this.updateState({ attrs: newAttrs }, callback)
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
47
src/utils/dndkit/plugins.js
Normal file
47
src/utils/dndkit/plugins.js
Normal 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;
|
||||||
|
|
||||||
Reference in New Issue
Block a user