working on drag and drop using dndkit

This commit is contained in:
paul
2025-03-01 19:20:46 +05:30
parent ed333d6ee6
commit 6656d3f6c4
18 changed files with 591 additions and 69 deletions

83
package-lock.json generated
View File

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

View File

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

View File

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

View File

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

View File

@@ -28,6 +28,7 @@ import WidgetContainer from "./constants/containers"
import { isSubClassOfWidget } from "../utils/widget"
import { ButtonModal } from "../components/modals"
import ResizeWidgetContainer from "./resizeContainer"
import Droppable from "../components/draggable/dnd/droppableDnd"
// const DotsBackground = require("../assets/background/dots.svg")
@@ -1086,11 +1087,12 @@ class Canvas extends React.Component {
</ButtonModal>
</div>
<Droppable className={"tw-w-full tw-h-full"} id="canvas-droppable">
{/* <ActiveWidgetProvider> */}
<DroppableWrapper id="canvas-droppable"
{/* <DroppableWrapper id="canvas-droppable"
droppableTags={{ exclude: ["image", "video"] }}
className="tw-w-full tw-h-full"
onDrop={this.handleDropEvent}>
onDrop={this.handleDropEvent}> */}
{/* <DragWidgetProvider> */}
<Dropdown trigger={['contextMenu']} mouseLeaveDelay={0} menu={{ items: this.state.contextMenuItems, }}>
<div className="tw-w-full tw-h-full tw-outline-none tw-flex tw-relative tw-bg-[#f2f2f2] tw-overflow-hidden"
@@ -1127,8 +1129,8 @@ class Canvas extends React.Component {
</div>
</Dropdown>
{/* </DragWidgetProvider> */}
</DroppableWrapper>
{/* </DroppableWrapper> */}
</Droppable>
<CanvasToolBar isOpen={this.state.toolbarOpen}
widgetType={this.state.selectedWidget?.getWidgetType() || ""}
attrs={this.state.toolbarAttrs}

View File

@@ -1118,7 +1118,8 @@ class Widget extends React.Component {
// FIXME: if the parent container has tw-overflow-none, then the resizable indicator are also hidden
return (
<DragContext.Consumer>
// <DragContext.Consumer>
<>
{
({ draggedElement, widgetClass, onDragStart, onDragEnd, overElement, setOverElement }) => {
@@ -1258,7 +1259,7 @@ class Widget extends React.Component {
}
}
</DragContext.Consumer>
</>
)
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,78 @@
import React, { useEffect, useRef } from "react"
import { useDndMonitor, useDraggable } from "@dnd-kit/core"
import { CSS } from "@dnd-kit/utilities"
import { useDragContext } from "../draggableContext"
function Draggable(props) {
const draggableRef = useRef(null)
const { attributes, listeners, setNodeRef, transform } = useDraggable({
id: props.id,
data: { title: props.children }
})
const {onDragStart, onDragEnd, disableStyle=false} = useDragContext()
useDndMonitor({
onDragStart(event){
if (event.active.id === props.id) { // Ensure only this element triggers it
handleDragStart()
}
},
onDragEnd(event){
if (event.active.id === props.id) { // Ensure only this element triggers it
handleDragEnd()
}
},
})
useEffect(() => {
if (draggableRef.current)
setNodeRef(draggableRef.current)
}, [draggableRef.current, setNodeRef])
const { dragElementType, dragWidgetClass = null, elementMetaData } = props
const style = transform ? {
transform: CSS.Translate.toString(transform),
} : undefined
const handleDragStart = (event) => {
console.log("Drag start1: ", elementMetaData)
// event.dataTransfer.setData("text/plain", "")
// onDragStart(draggableRef?.current, dragWidgetClass)
onDragStart(draggableRef?.current, dragWidgetClass, elementMetaData)
}
const handleDragEnd = (e) => {
// console.log("Drag end: ", e, e.target.closest('div'))
onDragEnd()
}
return (
<div className={`${props.className}`}
ref={draggableRef}
// 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
draggable
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}
{...listeners}
{...attributes}
{...props}
>
{props.children}
</div>
)
}
export default Draggable

View File

@@ -0,0 +1,163 @@
import React, { useEffect, useRef, useState } from 'react'
import { useDroppable } from '@dnd-kit/core'
import { useDragContext } from '../draggableContext'
function Droppable(props) {
const droppableRef = useRef(null)
const { isOver, setNodeRef } = useDroppable({
id: props.id,
})
console.log("IS over: ", isOver)
const style = {
backgroundColor: isOver ? 'green' : '',
}
const {droppableTags, onDrop} = props
const { draggedElement, overElement, setOverElement, widgetClass } = useDragContext()
const [showDroppable, setShowDroppable] = useState({
show: false,
allow: false
})
useEffect(() => {
if (droppableRef.current)
setNodeRef(droppableRef.current)
}, [droppableRef.current, setNodeRef])
useEffect(() => {
if (draggedElement === null){
setShowDroppable({
show: false,
allow: false
})
}
}, [draggedElement])
const handleDragEnter = (e) => {
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(e.currentTarget)
const allowDrop = (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 (allowDrop){
setShowDroppable({
allow: true,
show: true
})
}else{
setShowDroppable({
allow: false,
show: true
})
}
}
const handleDragOver = (e) => {
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 allowDrop = (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 (allowDrop){
e.preventDefault() // this is necessary to allow drop to take place
}
}
const handleDropEvent = (e) => {
setShowDroppable({
allow: false,
show: false
})
if (!draggedElement || !draggedElement.getAttribute("data-drag-start-within")){
// if the drag is starting from outside (eg: file drop) or if drag doesn't exist
return
}
e.stopPropagation()
const dragElementType = draggedElement.getAttribute("data-draggable-type")
const allowDrop = (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 && allowDrop){
onDrop(e, draggedElement, widgetClass)
}
}
const handleDragLeave = (e) => {
if (!e.currentTarget.contains(e.relatedTarget)) {
setShowDroppable({
allow: false,
show: false
})
}
}
// TODO: from here
return (
<div ref={droppableRef} style={style} className={props.className || ''}>
{props.children}
{/* {
showDroppable.show &&
<div className={`${showDroppable.allow ? "tw-border-green-600" : "tw-border-red-600"}
tw-absolute tw-top-0 tw-left-0 tw-w-full tw-h-full tw-z-[2]
tw-border-2 tw-border-dashed tw-rounded-lg tw-pointer-events-none
`}>
</div>
} */}
{
isOver &&
<div className={`${showDroppable.allow ? "tw-bg-[#82ff1c6e]" : "tw-bg-[#eb5d366e]"}
tw-absolute tw-top-0 tw-left-0 tw-w-full tw-h-full tw-z-[999]
tw-border-2 tw-border-dashed tw-rounded-lg tw-pointer-events-none
`}>
</div>
}
</div>
)
}
export default Droppable

View File

@@ -11,10 +11,16 @@ export const DragProvider = ({ children }) => {
const [draggedElement, setDraggedElement] = useState(null)
const [overElement, setOverElement] = useState(null) // the element the dragged items is over
const [dragElementMetaData, setDragElementMetaData] = useState({})
const [widgetClass, setWidgetClass] = useState(null) // helper to help pass the widget type from sidebar to canvas
const onDragStart = (element, widgetClass=null) => {
const [isDragging, setIsDragging] = useState(false)
const onDragStart = (element, widgetClass=null, metaData={}) => {
setDraggedElement(element)
setIsDragging(true)
setDragElementMetaData(metaData)
if (widgetClass && !isSubClassOfWidget(widgetClass))
throw new Error("widgetClass must inherit from the Widget base class")
@@ -25,11 +31,17 @@ export const DragProvider = ({ children }) => {
const onDragEnd = () => {
setDraggedElement(null)
setWidgetClass(null)
setIsDragging(false)
setDragElementMetaData({})
}
return (
<DragContext.Provider value={{ draggedElement, overElement, setOverElement,
widgetClass, onDragStart, onDragEnd }}>
widgetClass, onDragStart, onDragEnd, isDragging,
dragElementMetaData, setDragElementMetaData
}}>
{children}
</DragContext.Provider>
)

View File

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

View File

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

View File

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

View File

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