From 740a5400f88c0dcb08ba041e068104589f82f608 Mon Sep 17 00:00:00 2001 From: paul Date: Sun, 22 Sep 2024 12:39:03 +0530 Subject: [PATCH] feat: added sidebar widget drop fix: corrected color picker value, drag and drop --- src/App.js | 5 +- src/canvas/canvas.js | 124 +++++++----------- src/canvas/toolbar.js | 4 +- src/canvas/widgets/base.js | 46 +++---- src/canvas/widgets/widgetDragDrop.js | 2 - src/components/cards.js | 8 +- src/components/draggable/draggable.js | 7 +- src/components/draggable/draggableContext.js | 15 ++- src/components/draggable/droppable.js | 4 +- .../tkinter/assets/widgets/button.png | Bin 0 -> 2041 bytes src/frameworks/tkinter/sidebarWidgets.js | 41 ++++++ src/sidebar/widgetsContainer.js | 50 ++----- src/utils/widget.js | 16 +++ 13 files changed, 164 insertions(+), 158 deletions(-) create mode 100644 src/frameworks/tkinter/assets/widgets/button.png create mode 100644 src/frameworks/tkinter/sidebarWidgets.js create mode 100644 src/utils/widget.js diff --git a/src/App.js b/src/App.js index aeeca5a..bd8d389 100644 --- a/src/App.js +++ b/src/App.js @@ -14,6 +14,7 @@ import Widget from './canvas/widgets/base' import { DraggableWidgetCard } from './components/cards' import { DragProvider } from './components/draggable/draggableContext' import { ActiveWidgetProvider } from './canvas/activeWidgetContext' +import TkinterSidebar from './frameworks/tkinter/sidebarWidgets' function App() { @@ -30,7 +31,7 @@ function App() { const [dropAnimation, setDropAnimation] = useState(null) - const [sidebarWidgets, setSidebarWidgets] = useState([]) + const [sidebarWidgets, setSidebarWidgets] = useState(TkinterSidebar || []) const [canvasWidgets, setCanvasWidgets] = useState([]) // contains the reference to the widgets inside the canvas const [activeSidebarWidget, setActiveSidebarWidget] = useState(null) // helps with the dnd overlay @@ -43,7 +44,7 @@ function App() { { name: "Widgets", icon: , - content: setSidebarWidgets(widgets)}/> + content: setSidebarWidgets(widgets)}/> }, { name: "Plugins", diff --git a/src/canvas/canvas.js b/src/canvas/canvas.js index fc3a4c7..09ffa5d 100644 --- a/src/canvas/canvas.js +++ b/src/canvas/canvas.js @@ -178,9 +178,13 @@ class Canvas extends React.Component { // TODO: improve search, currently O(n), but can be improved via this.state.widgets or something let innerWidget = null - // FIXME: target selection not accurate when there is inner child involved for (let [key, ref] of Object.entries(this.widgetRefs)) { - console.log("key: ", key, innerWidget) + + if (ref.current === target){ + innerWidget = ref.current + break + } + if (ref.current.getElement().contains(target)) { if (!innerWidget) { @@ -193,34 +197,6 @@ class Canvas extends React.Component { } return innerWidget - // for (let [key, ref] of Object.entries(this.widgetRefs)) { - // console.log("ref: ", ref, key) - // if (ref.current.getElement().contains(target)) { - // return ref.current - // } - // } - - - // const returnTargetWidget = (widgets) => { - // for (let x of widgets) { - // const widget = this.widgetRefs[x.id] - - // // Check if the widget contains the target - // if (widget && widget.current.getElement().contains(target)) { - // // If it has children, continue checking the children for the innermost match - // const childWidget = returnTargetWidget(x.children) - - // // Return the innermost child widget if found, otherwise return the current widget - // return childWidget || widget.current - // } - // } - // // If no matching widget is found, return null - // return null - // } - - - // return returnTargetWidget(this.state.widgets) - } keyDownEvent(event) { @@ -248,25 +224,32 @@ class Canvas extends React.Component { if (event.button === 0) { this.mousePressed = true - // FIXME: the inner widgets are not selected properly if (selectedWidget) { - console.log("selected widget: ", selectedWidget.getId(), selectedWidget.getElement()) // if the widget is selected don't pan, instead move the widget if (!selectedWidget._disableSelection) { - console.log("selected widget: ", selectedWidget.getId(), this.state.selectedWidget?.getId()) + // console.log("selected widget2: ", selectedWidget.getId(), this.state.selectedWidget?.getId()) - if (!this.state.selectedWidget || (selectedWidget.getId() !== this.state.selectedWidget?.getId())) { - this.state.selectedWidget?.deSelect() // deselect the previous widget before adding the new one - this.state.selectedWidget?.setZIndex(0) + this.state.selectedWidget?.deSelect() // deselect the previous widget before adding the new one + this.state.selectedWidget?.setZIndex(0) + selectedWidget.setZIndex(1000) + selectedWidget.select() + this.setState({ + selectedWidget: selectedWidget, + toolbarAttrs: selectedWidget.getToolbarAttrs() + }) - selectedWidget.setZIndex(1000) - selectedWidget.select() - console.log("widget selected") - this.setState({ - selectedWidget: selectedWidget, - toolbarAttrs: selectedWidget.getToolbarAttrs() - }) - } + // if (!this.state.selectedWidget || (selectedWidget.getId() !== this.state.selectedWidget?.getId())) { + // this.state.selectedWidget?.deSelect() // deselect the previous widget before adding the new one + // this.state.selectedWidget?.setZIndex(0) + // console.log("working: ", this.state.selectedWidget) + // selectedWidget.setZIndex(1000) + // selectedWidget.select() + // console.log("widget selected") + // this.setState({ + // selectedWidget: selectedWidget, + // toolbarAttrs: selectedWidget.getToolbarAttrs() + // }) + // } this.currentMode = CanvasModes.MOVE_WIDGET } @@ -277,7 +260,7 @@ class Canvas extends React.Component { this.clearSelections() this.currentMode = CanvasModes.PAN this.setCursor(Cursor.GRAB) - console.log("clear selection") + // console.log("clear selection") } this.setState({ @@ -763,28 +746,14 @@ class Canvas extends React.Component { */ deleteSelectedWidgets(widgets = []) { - let activeWidgets = removeDuplicateObjects([...widgets, this.state.selectedWidget], "__id") const widgetIds = activeWidgets.map(widget => widget.__id) for (let widgetId of widgetIds) { - - // this.widgetRefs[widgetId]?.current.remove() - delete this.widgetRefs[widgetId] - - this.setState((prevState) => ({ - widgets: prevState.widgets.filter(widget => widget.id !== widgetId) - }), () => { - - if (this._onWidgetListUpdated) - this._onWidgetListUpdated(this.state.widgets) - }) - // value.current?.remove() + this.removeWidget(widgetId) } - - } /** @@ -805,15 +774,10 @@ class Canvas extends React.Component { removeWidget(widgetId) { - // FIXME: need to delete the child widgets - // IDEA: find the widget first, check for the parent, if parent exist remove it from the parents children list - // this.widgetRefs[widgetId]?.current.remove() - //this.removeWidgetFromCurrentList(widgetID) <--- use this delete this.widgetRefs[widgetId] - const widgets = this.state.widgets.filter(widget => widget.id !== widgetId) - + const widgets = this.removeWidgetFromCurrentList(widgetId) this.setState({ widgets: widgets }) @@ -841,7 +805,7 @@ class Canvas extends React.Component { * Handles drop event to canvas from the sidebar and on canvas widget movement * @param {DragEvent} e */ - handleDropEvent = (e, draggedElement) => { + handleDropEvent = (e, draggedElement, widgetClass=null) => { e.preventDefault() @@ -854,18 +818,19 @@ class Canvas extends React.Component { const { clientX, clientY } = e - // const finalPosition = { - // x: (clientX - canvasRect.left) / this.state.zoom, - // y: (clientY - canvasRect.top) / this.state.zoom, - // } - - // snaps to center - const 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, + let finalPosition = { + x: (clientX - canvasRect.left) / this.state.zoom, + y: (clientY - canvasRect.top) / this.state.zoom, } + + if (container === WidgetContainer.SIDEBAR) { + + if (!widgetClass){ + throw new Error("WidgetClass has to be passed for widgets dropped from sidebar") + } + // TODO: handle drop from sidebar // if the widget is being dropped from the sidebar, use the info to create the widget first this.createWidget(Widget, ({ id, widgetRef }) => { @@ -874,6 +839,12 @@ class Canvas extends React.Component { } else if ([WidgetContainer.CANVAS, WidgetContainer.WIDGET].includes(container)) { + // snaps to center + finalPosition = { + x: (clientX - canvasRect.left) / this.state.zoom - (elementWidth / 2) / this.state.zoom, + y: (clientY - canvasRect.top) / this.state.zoom - (elementHeight / 2) / this.state.zoom, + } + let widgetId = draggedElement.getAttribute("data-widget-id") const widgetObj = this.getWidgetById(widgetId) @@ -945,7 +916,6 @@ class Canvas extends React.Component { // recursively render the child elements return childrenData.map((child) => { const childWidget = this.findWidgetFromListById(child.id) - // console.log("Found the child : ", childWidget) if (childWidget) { return this.renderWidget(childWidget) // Recursively render child widgets } diff --git a/src/canvas/toolbar.js b/src/canvas/toolbar.js index e8fb258..593b305 100644 --- a/src/canvas/toolbar.js +++ b/src/canvas/toolbar.js @@ -128,7 +128,6 @@ const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => { const renderWidgets = (obj, parentKey = "") => { - // FIXME: The color widget is not being updated on selection change return Object.entries(obj).map(([key, val], i) => { const keyName = parentKey ? `${parentKey}.${key}` : key @@ -164,7 +163,8 @@ const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => { {val.tool === Tools.COLOR_PICKER && ( { // FIXME: maximum recursion error when updating size @@ -450,7 +438,7 @@ class Widget extends React.Component { } setLayout(value){ - + // FIXME: when the parent layout is place, the child widgets should have position absolute const {layout, direction, grid={rows: 1, cols: 1}, gap=10} = value const widgetStyle = { @@ -585,7 +573,7 @@ class Widget extends React.Component { */ handleDrop = (event, dragElement) => { // THIS function is depreciated in favour of handleDropEvent() - console.log("dragging event: ", event, dragElement) + // console.log("dragging event: ", event, dragElement) const container = dragElement.getAttribute("data-container") // TODO: check if the drop is allowed if (container === "canvas"){ @@ -630,11 +618,8 @@ class Widget extends React.Component { document.body.removeChild(dragImage) }, 0) - setTimeout(() => { - // NOTE: this line will prevent problem's such as self-drop or dropping inside its own children - this.elementRef.current.style.pointerEvents = "none" - - }, 1) + // NOTE: this line will prevent problem's such as self-drop or dropping inside its own children + setTimeout(this.disablePointerEvents, 1) this.setState({isDragging: true}) @@ -644,7 +629,7 @@ class Widget extends React.Component { const dragEleType = draggedElement.getAttribute("data-draggable-type") - console.log("Drag entering...", dragEleType, draggedElement, this.droppableTags) + // 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 @@ -695,7 +680,7 @@ class Widget extends React.Component { handleDropEvent = (e, draggedElement) => { e.preventDefault() e.stopPropagation() - // FIXME: sometimes the elements shoDroppableStyle 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({ showDroppableStyle: { allow: false, @@ -771,9 +756,19 @@ class Widget extends React.Component { handleDragEnd = (callback) => { callback() this.setState({isDragging: false}) - this.elementRef.current.style.pointerEvents = "auto" + this.enablePointerEvents() } + disablePointerEvents = () => { + + if (this.elementRef.current) + this.elementRef.current.style.pointerEvents = "none" + } + + enablePointerEvents = () => { + if (this.elementRef.current) + this.elementRef.current.style.pointerEvents = "auto" + } // FIXME: children outside the bounding box, add tw-overflow-hidden renderContent() { @@ -803,7 +798,6 @@ class Widget extends React.Component { opacity: this.state.isDragging ? 0.3 : 1, } - // console.log("selected: ", this.state.dragEnabled) return ( @@ -865,7 +859,7 @@ class Widget extends React.Component { }
-
+
{/* ${this.state.isDragging ? "tw-pointer-events-none" : "tw-pointer-events-auto"} */}
{ e.stopPropagation() diff --git a/src/canvas/widgets/widgetDragDrop.js b/src/canvas/widgets/widgetDragDrop.js index 6571d53..58557a5 100644 --- a/src/canvas/widgets/widgetDragDrop.js +++ b/src/canvas/widgets/widgetDragDrop.js @@ -16,7 +16,6 @@ const WidgetDraggable = memo(({ widgetRef, enableDrag=true, dragElementType="wid onDragEnter, onDragLeave, onDrop, style={}, droppableTags = ["widget"], ...props }) => { - // FIXME: It's not possible to move the widget ~10px because, its considered as self drop, so fix it // const { draggedElement, onDragStart, onDragEnd } = useDragWidgetContext() const { draggedElement, onDragStart, onDragEnd, overElement, setOverElement } = useDragContext() @@ -61,7 +60,6 @@ const WidgetDraggable = memo(({ widgetRef, enableDrag=true, dragElementType="wid const dragEleType = draggedElement.getAttribute("data-draggable-type") // console.log("Drag entering...", overElement === e.currentTarget) - // FIXME: the outer widget shouldn't be swallowed by inner widget if (draggedElement === widgetRef.current){ // prevent drop on itself, since the widget is invisible when dragging, if dropped on itself, it may consume itself return diff --git a/src/components/cards.js b/src/components/cards.js index 8526c9a..38f18a4 100644 --- a/src/components/cards.js +++ b/src/components/cards.js @@ -5,9 +5,10 @@ import { FileImageOutlined, GithubOutlined, GitlabOutlined, LinkOutlined, AudioOutlined, VideoCameraOutlined, FileTextOutlined} from "@ant-design/icons" import DraggableWrapper from "./draggable/draggable" +import { useDragContext } from "./draggable/draggableContext" -export function DraggableWidgetCard({ name, img, url, innerRef}){ +export function SidebarWidgetCard({ name, img, url, widgetClass, innerRef}){ const urlIcon = useMemo(() => { if (url){ @@ -29,6 +30,7 @@ export function DraggableWidgetCard({ name, img, url, innerRef}){ //
-
{ file.fileType === "image" && @@ -111,7 +113,7 @@ export function DraggableAssetCard({file}){ }
- {file.name} + {file.name}
) diff --git a/src/components/draggable/draggable.js b/src/components/draggable/draggable.js index 13e7ad8..1050e7b 100644 --- a/src/components/draggable/draggable.js +++ b/src/components/draggable/draggable.js @@ -7,7 +7,7 @@ import { useDragContext } from "./draggableContext" * @param {string} - dragElementType - this will set the data-draggable-type which can be accessed on droppable to check if its allowed or not * @returns */ -const DraggableWrapper = memo(({dragElementType, className, children, ...props}) => { +const DraggableWrapper = memo(({dragElementType, dragWidgetClass=null, className, children, ...props}) => { const { onDragStart, onDragEnd } = useDragContext() @@ -20,10 +20,7 @@ const DraggableWrapper = memo(({dragElementType, className, children, ...props}) const handleDragStart = (event) => { // event.dataTransfer.setData("text/plain", "") - - if (onDragStart) - onDragStart(draggableRef?.current) - + onDragStart(draggableRef?.current, dragWidgetClass) } diff --git a/src/components/draggable/draggableContext.js b/src/components/draggable/draggableContext.js index b6f070d..4925b6d 100644 --- a/src/components/draggable/draggableContext.js +++ b/src/components/draggable/draggableContext.js @@ -1,4 +1,6 @@ import React, { createContext, useContext, useState } from 'react' +import { isSubClassOfWidget } from '../../utils/widget' +// import Widget from '../../canvas/widgets/base' export const DragContext = createContext() @@ -9,16 +11,25 @@ export const DragProvider = ({ children }) => { const [draggedElement, setDraggedElement] = useState(null) const [overElement, setOverElement] = useState(null) // the element the dragged items is over - const onDragStart = (element) => { + 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 ( - + {children} ) diff --git a/src/components/draggable/droppable.js b/src/components/draggable/droppable.js index 40765f7..eb7c9bc 100644 --- a/src/components/draggable/droppable.js +++ b/src/components/draggable/droppable.js @@ -5,7 +5,7 @@ import { useDragContext } from "./draggableContext" const DroppableWrapper = memo(({onDrop, droppableTags=["widget"], ...props}) => { - const { draggedElement, overElement, setOverElement } = useDragContext() + const { draggedElement, overElement, setOverElement, widgetClass } = useDragContext() const [showDroppable, setShowDroppable] = useState({ show: false, @@ -53,7 +53,7 @@ const DroppableWrapper = memo(({onDrop, droppableTags=["widget"], ...props}) => }) if(onDrop){ - onDrop(e, draggedElement) + onDrop(e, draggedElement, widgetClass) } } diff --git a/src/frameworks/tkinter/assets/widgets/button.png b/src/frameworks/tkinter/assets/widgets/button.png new file mode 100644 index 0000000000000000000000000000000000000000..2d4518271714fed43a4c07c32f0eefd99f0ff504 GIT binary patch literal 2041 zcmVZgXgFbngSdJ^%m!FLXs%bVG7w zVRUJ4ZXi@?ZDjycb#7!~c_1(}AVGC!b#rteGB7eUATT*PH8(mjHy}_=-G*kO000M2 zNkl&ReY#lp-6pzXT&2& zR3IS%Dgp`dP>^^62~s4)6Qv-fP?Z+cAV}P}Qa2S&t5{W1rA}QtXlcFc_3YfsgLmxl z^1b9aS=tu~O0NU)l$D){ z>J+adt##y1FT9rw;`Bs#!t~cGTIJ57pT0Sp`bH^*Wm%bdOGhZ}i2HL&QcX_?(-YY} zk9va+?vp6BRY}1F^GrKM$thVRSHBY1DZQ9mva+!)l(5k8j(Y)4(2gj!cT%D-6QwIjcD)UV-+Y4|^cBHvO=Ck<*lXi*% zFt(?d8Wj;5jm8ZHD5Y>+cPkMpr9!6-{R8vb^9F5M7Pf6~f$*Sr<28^-P=ZSb zLBl?tNtegpl#u(wzR@u7Ha1(Rve ztNi=4N(3e7G2XzvW7uAcQA%Ok_KwgAdwll~(zN*@`90MC-85BL3vD|uKE4H;r>U3qc)tcz%z?bFTmZyfV~Mv9C-e$@Zx*WHv(lF z-aQ20xf582Kh444SAf0n*iksH;i*?Nmz%Ko7tQKtY)P;!tq&)FC9vr za3g!lX5&I>KU`YTfab!oP`Xnnt;0eCXwoZlxr^_cLj+k{hdurw-s?+ZeyPx#6gZBFcmN=30S zV)OJ9ef;1PaQd8LK_~Z`*opSmF`;@*Q(f0A)Sx_JQTm8xVHs9d!5I|}T08T=n^&NC zL^w7g^bZR6f3yV};k9L$DG8-Pp*RXhM_XrVaQ2$;xfx-kPdIW1OxNLj?fU9f1f05N zsSabe+x+N3o2e906st}V5sKr&SVeR3D!e-?OxfT}2+uCVg_`EzEf$5qcj^udkNO7E z`Xd5$2gZh4c?J+#S$rL;P0h(aYfk#w8{gQr+JjS`wRyOr`1K1-Y6HTtyDT0bNpm!665gX3 z?iZeT#Nr;?*S`ofcGfv7Yf!%1=GeWk@{Z!CXB4+RVlmqvcXXSIk5h!H5l!WNDBsc= z$xXtP)>IrQ?eh(;b@1`SLiLiSvJSYK`Kx;CT=@XZFDNcIpmt4jW~KGLy+V0N^VbUe zPYKNr;g7FrmNZ~P0jRD6Xy(tk{Ng{Fx(GvqPz0Q0Jb;8{{^XcY_&_M#o>hGJ51V(s z*%^y50s4evhc&-^!Da2UHjjVOX6cN}lM61+fNZ~6GJ!QH=XsSZPwDOkA&tBJ5HPo(ZUa1P@@$A}9qD(y{k!p%(=M*n3Yn*=J zzOv0%Z { - return [ - { - name: "TopLevel", - img: ButtonWidget, - link: "https://github.com", - // widgetType: Widget - }, - { - name: "Frame", - img: ButtonWidget, - link: "https://github.com" - }, - { - name: "Button", - img: ButtonWidget, - link: "https://github.com" - }, - { - name: "Input", - img: ButtonWidget, - link: "https://github.com" - }, - ] - }, []) const [searchValue, setSearchValue] = useState("") - const [widgetData, setWidgetData] = useState(widgets) + const [widgetData, setWidgetData] = useState(sidebarContent) useEffect(() => { - setWidgetData(widgets) + setWidgetData(sidebarContent) + // if (onWidgetsUpdate){ + // onWidgetsUpdate(widgets) + // } - if (onWidgetsUpdate){ - onWidgetsUpdate(widgets) - } - - }, [widgets]) + }, [sidebarContent]) useEffect(() => { if (searchValue.length > 0){ - const searchData = filterObjectListStartingWith(widgets, "name", searchValue) + const searchData = filterObjectListStartingWith(sidebarContent, "name", searchValue) setWidgetData(searchData) }else{ - setWidgetData(widgets) + setWidgetData(sidebarContent) } }, [searchValue]) @@ -95,10 +70,11 @@ function WidgetsContainer({onWidgetsUpdate}){ { widgetData.map((widget, index) => { return ( - ) diff --git a/src/utils/widget.js b/src/utils/widget.js new file mode 100644 index 0000000..f839b39 --- /dev/null +++ b/src/utils/widget.js @@ -0,0 +1,16 @@ +import Widget from "../canvas/widgets/base" + +// checks if the object is instance is instance of widget class +export const isSubClassOfWidget = (_class) => { + + return Widget.isPrototypeOf(_class) || _class === Widget +} + + + +export const isInstanceOfWidget = (_class) => { + + console.log("Widget is instance of Object: ", _class) + + return _class instanceof Widget +}