diff --git a/notes.md b/notes.md index 91d9fab..e735851 100644 --- a/notes.md +++ b/notes.md @@ -6,4 +6,4 @@ -## F\*CK Javascript, why the fu\*k, this.bind(this), class sucks in js, you can also use arrow functions, but you won't be able to override it in the subclass, because arrow functions, inherits from the surrounding context. \ No newline at end of file +### F\*CK Javascript, why the fu\*k, this.bind(this), classes sucks in js, you can also use arrow functions, but you won't be able to override it in the subclass, because arrow functions, inherits from the surrounding context. \ No newline at end of file diff --git a/src/App.js b/src/App.js index f941e99..1506574 100644 --- a/src/App.js +++ b/src/App.js @@ -33,7 +33,9 @@ function App() { // const [uploadedAssets, setUploadedAssets] = useState([]) // a global storage for assets, since redux can't store files(serialize files) const [sidebarWidgets, setSidebarWidgets] = useState(TkinterWidgets || []) - + + const {uploadedAssets} = useFileUploadContext() + // NOTE: the below reference is no longer required const [canvasWidgets, setCanvasWidgets] = useState([]) // contains the reference to the widgets inside the canvas @@ -140,7 +142,7 @@ function App() { const handleCodeGen = () => { if (UIFramework === FrameWorks.TKINTER){ - generateTkinterCode(projectName, canvasRef.current.getWidgets() || [], canvasRef.current.widgetRefs || []) + generateTkinterCode(projectName, canvasRef.current.getWidgets() || [], canvasRef.current.widgetRefs || [], uploadedAssets) } } diff --git a/src/canvas/activeWidgetContext.js b/src/canvas/activeWidgetContext.js index f9f4600..a1844f8 100644 --- a/src/canvas/activeWidgetContext.js +++ b/src/canvas/activeWidgetContext.js @@ -1,6 +1,6 @@ import React, { createContext, Component, useContext, useState, createRef, useMemo } from 'react' - +// NOT IN USE // NOTE: using this context provider causes many re-rendering when the canvas is panned the toolbar updates unnecessarily // use draggable widgetcontext diff --git a/src/canvas/toolbar.js b/src/canvas/toolbar.js index 96de668..30c09f0 100644 --- a/src/canvas/toolbar.js +++ b/src/canvas/toolbar.js @@ -15,7 +15,7 @@ import { AudioOutlined, FileImageOutlined, FileTextOutlined, VideoCameraOutlined // FIXME: Maximum recursion error - +// FIXME: Every time the parent attrs are changed a remount happens, which causes input cursor to go to the end /** * * @param {boolean} isOpen diff --git a/src/canvas/widgets/base.js b/src/canvas/widgets/base.js index c07176b..352b115 100644 --- a/src/canvas/widgets/base.js +++ b/src/canvas/widgets/base.js @@ -7,9 +7,7 @@ import Cursor from "../constants/cursor" import { toSnakeCase } from "../utils/utils" import EditableDiv from "../../components/editableDiv" -import { ActiveWidgetContext } from "../activeWidgetContext" -import { DragWidgetProvider } from "./draggableWidgetContext" -import WidgetDraggable from "./widgetDragDrop" + import WidgetContainer from "../constants/containers" import { DragContext } from "../../components/draggable/draggableContext" import { isNumeric, removeKeyFromObject } from "../../utils/common" @@ -66,12 +64,6 @@ class Widget extends React.Component { // This indicates if the draggable can be dropped on this widget, set this to null to disable drops this.droppableTags = {} - // this.boundingRect = { - // x: 0, - // y: 0, - // height: 100, - // width: 100 - // } this.state = { zIndex: 0, @@ -192,6 +184,9 @@ class Widget extends React.Component { this.load = this.load.bind(this) this.serialize = this.serialize.bind(this) this.serializeAttrsValues = this.serializeAttrsValues.bind(this) + + this.hideDroppableIndicator = this.hideDroppableIndicator.bind(this) + } componentDidMount() { @@ -206,7 +201,6 @@ class Widget extends React.Component { this.load(this.props.initialData || {}) // load the initial data - } componentWillUnmount() { @@ -425,6 +419,17 @@ class Widget extends React.Component { return this.elementRef.current } + hideDroppableIndicator(){ + console.log("hide drop indicator") + this.setState({ + showDroppableStyle: { + allow: false, + show: false + } + }, () => { + console.log("hidden the drop indicator") + }) + } /** * @@ -1073,135 +1078,137 @@ class Widget extends React.Component { { - ({ draggedElement, widgetClass, onDragStart, onDragEnd, overElement, setOverElement }) => ( + ({ draggedElement, widgetClass, onDragStart, onDragEnd, overElement, setOverElement }) => { -
this.handleDragOver(e, draggedElement)} - onDrop={(e) => this.handleDropEvent(e, draggedElement, widgetClass)} + 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)} + 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)} - > - {/* 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 */} -
this.handleDragStart(e, onDragStart)} + onDragEnd={(e) => this.handleDragEnd(onDragEnd)} > -
- {/* helps with swappable: if the mouse is in this area while hovering/dropping, then swap */} -
-
- {this.renderContent()} -
- { - // show drop style on drag hover - this.state.showDroppableStyle.show && -
+ {/* helps with swappable: if the mouse is in this area while hovering/dropping, then swap */}
- } - {/* FIXME: the resize handles get clipped in parent container */} -
+
+ {this.renderContent()} +
+ { + // show drop style on drag hover + draggedElement && this.state.showDroppableStyle.show && +
{/* ${this.state.isDragging ? "tw-pointer-events-none" : "tw-pointer-events-auto"} */} - + `} + style={{ + width: "calc(100% + 10px)", + height: "calc(100% + 10px)", + }} + > +
+ } + {/* FIXME: the resize handles get clipped in parent container */} +
-
{ - e.stopPropagation() - e.preventDefault() - this.props.onWidgetResizing("nw") - this.setState({ dragEnabled: false }) - }} - onMouseUp={() => this.setState({ dragEnabled: true })} - /> -
{ - e.stopPropagation() - e.preventDefault() - this.props.onWidgetResizing("ne") - this.setState({ dragEnabled: false }) - }} - onMouseUp={() => this.setState({ dragEnabled: true })} - /> -
{ - e.stopPropagation() - e.preventDefault() - this.props.onWidgetResizing("sw") - this.setState({ dragEnabled: false }) - }} - onMouseUp={() => this.setState({ dragEnabled: true })} - /> -
{ - e.stopPropagation() - e.preventDefault() - this.props.onWidgetResizing("se") - this.setState({ dragEnabled: false }) - }} - onMouseUp={() => this.setState({ dragEnabled: true })} - /> +
{/* ${this.state.isDragging ? "tw-pointer-events-none" : "tw-pointer-events-auto"} */} + + +
{ + e.stopPropagation() + e.preventDefault() + this.props.onWidgetResizing("nw") + this.setState({ dragEnabled: false }) + }} + onMouseUp={() => this.setState({ dragEnabled: true })} + /> +
{ + e.stopPropagation() + e.preventDefault() + this.props.onWidgetResizing("ne") + this.setState({ dragEnabled: false }) + }} + onMouseUp={() => this.setState({ dragEnabled: true })} + /> +
{ + e.stopPropagation() + e.preventDefault() + this.props.onWidgetResizing("sw") + this.setState({ dragEnabled: false }) + }} + onMouseUp={() => this.setState({ dragEnabled: true })} + /> +
{ + e.stopPropagation() + e.preventDefault() + this.props.onWidgetResizing("se") + this.setState({ dragEnabled: false }) + }} + onMouseUp={() => this.setState({ dragEnabled: true })} + /> + +
+
- -
-
- ) + ) + } } diff --git a/src/components/cards.js b/src/components/cards.js index a6665ed..9598e9d 100644 --- a/src/components/cards.js +++ b/src/components/cards.js @@ -109,7 +109,7 @@ export function DraggableAssetCard({file, onDelete}){ return ( -
diff --git a/src/components/draggable/droppable.js b/src/components/draggable/droppable.js index 704163f..3e17b08 100644 --- a/src/components/draggable/droppable.js +++ b/src/components/draggable/droppable.js @@ -1,4 +1,4 @@ -import { memo, useState } from "react" +import { memo, useEffect, useState } from "react" import { useDragContext } from "./draggableContext" @@ -15,11 +15,19 @@ const DroppableWrapper = memo(({onDrop, droppableTags={}, ...props}) => { allow: false }) + useEffect(() => { + + if (draggedElement === null){ + setShowDroppable({ + show: false, + allow: false + }) + } + + }, [draggedElement]) const handleDragEnter = (e) => { - console.log("Drag enter") - 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 @@ -71,6 +79,11 @@ const DroppableWrapper = memo(({onDrop, droppableTags={}, ...props}) => { 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 @@ -78,10 +91,7 @@ const DroppableWrapper = memo(({onDrop, droppableTags={}, ...props}) => { e.stopPropagation() - setShowDroppable({ - allow: false, - show: false - }) + const dragElementType = draggedElement.getAttribute("data-draggable-type") diff --git a/src/frameworks/tkinter/engine/code.js b/src/frameworks/tkinter/engine/code.js index 4980a81..9abe2f3 100644 --- a/src/frameworks/tkinter/engine/code.js +++ b/src/frameworks/tkinter/engine/code.js @@ -7,39 +7,46 @@ import TopLevel from "../widgets/toplevel" // FIXME: if the toplevel comes first, before the MainWindow in widgetlist the root may become null // Recursive function to generate the code list, imports, requirements, and track mainVariable -function generateTkinterCodeList(widgetList = [], widgetRefs = [], parentVariable = null, mainVariable = "") { - let variableNames = [] // {widgetId: "", name: "", count: 1} +function generateTkinterCodeList(widgetList = [], widgetRefs = [], parentVariable = null, mainVariable = "", usedVariableNames = new Set()) { + let variableMapping = new Map() // Map widget to variable { widgetId: variableName } let imports = new Set([]) let requirements = new Set([]) - let code = [] for (let widget of widgetList) { - // console.log("Key: ", widget) const widgetRef = widgetRefs[widget.id].current - let varName = widgetRef.getVariableName() - imports.add(widgetRef.getImports()) - requirements.add(widgetRef.getRequirements()) + // Add imports and requirements to sets + widgetRef.getImports().forEach(importItem => imports.add(importItem)); + widgetRef.getRequirements().forEach(requirementItem => requirements.add(requirementItem)); // Set main variable if the widget is MainWindow if (widget.widgetType === MainWindow) { mainVariable = varName } - if (!variableNames.find((val) => val.variable === varName)) { - variableNames.push({ widgetId: widgetRef.id, name: varName, count: 1 }) - } else { - // Avoid duplicate names - const existingVariable = variableNames.find((val) => val.variable === varName) - const existingCount = existingVariable.count - existingVariable.count += 1 - varName = `${existingVariable.name}${existingCount}` - variableNames.push({ widgetId: widgetRef.id, name: varName, count: 1 }) + // Ensure unique variable names across recursion + let originalVarName = varName + let count = 1; + + // Check for uniqueness and update varName + while (usedVariableNames.has(varName)) { + varName = `${originalVarName}${count}` + count++ } - const currentParentVariable = variableNames.find(val => val.widgetId === widget.id)?.name || parentVariable + usedVariableNames.add(varName) + variableMapping.set(widget.id, varName) // Store the variable name by widget ID + + // Determine the current parent variable from variableNames or fallback to parentVariable + let currentParentVariable = parentVariable || (variableMapping.get(widget.id) || null) + + if (widget.widgetType === TopLevel){ + // for top level set it to the main variable + // TODO: the toplevels parent should be determined other ways, suppose the top level has another toplevel + currentParentVariable = mainVariable + } let widgetCode = widgetRef.generateCode(varName, currentParentVariable) @@ -53,32 +60,32 @@ function generateTkinterCodeList(widgetList = [], widgetRefs = [], parentVariabl code.push(...widgetCode) code.push("\n\n") - // Recursively handle child widgets, which are full widget objects + // Recursively handle child widgets if (widget.children && widget.children.length > 0) { - const childResult = generateTkinterCodeList(widget.children, widgetRefs, varName, mainVariable) + // Pass down the unique names for children to prevent duplication + const childResult = generateTkinterCodeList(widget.children, widgetRefs, varName, mainVariable, usedVariableNames) // Merge child imports, requirements, and code imports = new Set([...imports, ...childResult.imports]) requirements = new Set([...requirements, ...childResult.requirements]) code.push(...childResult.code) - // Track the main variable returned by the child - mainVariable = childResult.mainVariable || mainVariable + mainVariable = childResult.mainVariable || mainVariable // the main variable is the main window variable } } return { - imports: Array.from(imports), // Convert Set to Array + imports: Array.from(imports), code: code, - requirements: Array.from(requirements), // Convert Set to Array + requirements: Array.from(requirements), mainVariable } } -async function generateTkinterCode(projectName, widgetList=[], widgetRefs=[]){ +async function generateTkinterCode(projectName, widgetList=[], widgetRefs=[], assetFiles){ - console.log("widgetList and refs", projectName, widgetList, widgetRefs) + console.log("widgetList and refs", projectName, widgetList, widgetRefs, assetFiles) let mainWindowCount = 0 @@ -102,38 +109,37 @@ async function generateTkinterCode(projectName, widgetList=[], widgetRefs=[]){ return } - // let code = [`# This code is generated by PyUIbuilder: https://github.com/paulledemon \n`] - // code.push("\n", "import tkinter as tk", "\n\n") - // widget - {id, widgetType: widgetComponentType, children: [], parent: "", initialData: {}} const generatedObject = generateTkinterCodeList(filteredWidgetList, widgetRefs, "", "") const {code: codeLines, imports, requirements, mainVariable} = generatedObject + // TODO: avoid adding \n inside the list instead rewrite using code.join("\n") const code = [ "# This code is generated by PyUIbuilder: https://github.com/paulledemon", "\n\n", - ...imports, + ...imports.flatMap((item, index) => index < imports.length - 1 ? [item, "\n"] : [item]), //add \n to every line "\n\n", ...codeLines, "\n", `${mainVariable}.mainloop()`, ] - // TODO: create requirements.txt file - console.log("Code: ", code.join("")) + console.log("Code: ", code.join(""), "\n", requirements.join("\n")) message.info("starting zipping files, download will start in a few seconds") const createFileList = [ { - fileData: code.join(""), + fileData: code.join("\n"), fileName: "main.py", folder: "" } ] + // TODO: Zip asset files + if (requirements.length > 0){ createFileList.push({ fileData: requirements.join("\n"), @@ -142,6 +148,36 @@ async function generateTkinterCode(projectName, widgetList=[], widgetRefs=[]){ }) } + for (let asset of assetFiles){ + + if (asset.fileType === "image"){ + createFileList.push({ + fileData: asset.originFileObj, + fileName: asset.name, + folder: "assets/images" + }) + }else if (asset.fileType === "video"){ + createFileList.push({ + fileData: asset.originFileObj, + fileName: asset.name, + folder: "assets/videos" + }) + }else if (asset.fileType === "audio"){ + createFileList.push({ + fileData: asset.originFileObj, + fileName: asset.name, + folder: "assets/audio" + }) + }else{ + createFileList.push({ + fileData: asset.originFileObj, + fileName: asset.name, + folder: "assets/media" + }) + } + + } + // createFilesAndDownload(createFileList, projectName).then(() => { // message.success("Download complete") // }).catch(() => { diff --git a/src/frameworks/tkinter/widgets/base.js b/src/frameworks/tkinter/widgets/base.js index e21b914..5374d86 100644 --- a/src/frameworks/tkinter/widgets/base.js +++ b/src/frameworks/tkinter/widgets/base.js @@ -255,8 +255,6 @@ export class TkinterWidgetBase extends TkinterBase{ const newAttrs = removeKeyFromObject("layout", this.state.attrs) - console.log("new attrs: ", newAttrs) - this.state = { ...this.state, attrs: { diff --git a/src/frameworks/tkinter/widgets/mainWindow.js b/src/frameworks/tkinter/widgets/mainWindow.js index 2051695..4b75860 100644 --- a/src/frameworks/tkinter/widgets/mainWindow.js +++ b/src/frameworks/tkinter/widgets/mainWindow.js @@ -17,6 +17,7 @@ class MainWindow extends TkinterBase{ this.state = { ...this.state, size: { width: 700, height: 400 }, + widgetName: "main", attrs: { ...this.state.attrs, widgetName: "main", diff --git a/src/sidebar/uploadsContainer.js b/src/sidebar/uploadsContainer.js index f6c64ae..0c6cea5 100644 --- a/src/sidebar/uploadsContainer.js +++ b/src/sidebar/uploadsContainer.js @@ -92,7 +92,7 @@ function UploadsContainer() { setSearchValue("")} /> -
+