diff --git a/README.md b/README.md index 3f28b91..71037dc 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,16 @@ Join the free newsletter to know about upcoming updates, learn how I built this To keep up with the latest developments considering starting ⭐️ this repo +## Tested on + +Depending on whether your Browser supports native HTML drag and drop, it may work differently. +I haven't tested on Safari, since I don't have a Macbook, feel free to let me know if it works. + +- [x] Chrome +- [x] Edge +- [x] FireFox +- [ ] Safari (Not tested on safari) + ## FAQ diff --git a/package-lock.json b/package-lock.json index b8f3d14..6056503 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "dom-to-image-more": "^3.4.5", "fabric": "^6.1.0", "file-saver": "^2.0.5", + "jszip": "^3.10.1", "postcss-cli": "^11.0.0", "re-resizable": "^6.9.17", "react": "^18.3.1", @@ -10404,6 +10405,11 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, "node_modules/immer": { "version": "9.0.21", "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", @@ -13516,6 +13522,49 @@ "node": ">=4.0" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -13593,6 +13642,14 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lilconfig": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", @@ -14485,6 +14542,11 @@ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==" }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, "node_modules/param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", @@ -17957,6 +18019,11 @@ "node": ">= 0.4" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", diff --git a/package.json b/package.json index 3e50caa..4a6d1ad 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "dom-to-image-more": "^3.4.5", "fabric": "^6.1.0", "file-saver": "^2.0.5", + "jszip": "^3.10.1", "postcss-cli": "^11.0.0", "re-resizable": "^6.9.17", "react": "^18.3.1", diff --git a/src/canvas/widgets/base.js b/src/canvas/widgets/base.js index d274495..7f4f16f 100644 --- a/src/canvas/widgets/base.js +++ b/src/canvas/widgets/base.js @@ -171,6 +171,9 @@ class Widget extends React.Component { this.generateCode = this.generateCode.bind(this) + this.getImports = this.getImports.bind(this) + this.getRequirements = this.getRequirements.bind(this) + // this.openRenaming = this.openRenaming.bind(this) this.isSelected = this.isSelected.bind(this) @@ -334,11 +337,11 @@ class Widget extends React.Component { return this.constructor.widgetType } - getRequirements = () => { + getRequirements(){ return this.constructor.requirements } - getImports = () => { + getImports(){ return this.constructor.requiredImports } diff --git a/src/codeEngine/utils.js b/src/codeEngine/utils.js new file mode 100644 index 0000000..bc7f810 --- /dev/null +++ b/src/codeEngine/utils.js @@ -0,0 +1,34 @@ +import JSZip from "jszip" + + +/** + * + * @param {[]} fileDataArray - [{fileData: "", fileName: "", folder: ""}] + */ +export async function createFilesAndDownload(fileDataArray, zipName="untitled"){ + + const zip = new JSZip() + + fileDataArray.forEach(file => { + const { fileData, fileName, folder } = file + + // If a folder is specified, create it if it doesn't exist + if (folder) { + const folderRef = zip.folder(folder) + folderRef.file(fileName, fileData) + + } else { + // If no folder is specified, place the file in the root + zip.file(fileName, fileData) + } + }) + + // Generate the ZIP asynchronously + zip.generateAsync({ type: "blob" }).then(function(content) { + const link = document.createElement("a") + link.href = URL.createObjectURL(content) + link.download = `${zipName}.zip` + link.click() + }) + +} \ No newline at end of file diff --git a/src/frameworks/tkinter/engine/code.js b/src/frameworks/tkinter/engine/code.js index ee6c10a..8f1aef8 100644 --- a/src/frameworks/tkinter/engine/code.js +++ b/src/frameworks/tkinter/engine/code.js @@ -1,3 +1,4 @@ +import { createFilesAndDownload } from "../../../codeEngine/utils" import MainWindow from "../widgets/mainWindow" import { message } from "antd" @@ -23,8 +24,71 @@ async function generateTkinterCode(projectName, widgetList=[], widgetRefs=[]){ if (mainWindowCount === 0){ message.error("Aborting. No instances of Main window found. Add one and try again") + return } + let variableNames = [] // {widgetId: "", name: "", count: 1} + + let imports = new Set([]) + let requirements = new Set([]) + + let code = [`# This code is generated by PyUIbuilder: https://github.com/paulledemon \n`] + code.push("\n", "import tkinter as tk", "\n\n") + + + let mainVariable = "" // the main window variable + + 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()) + + 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}) + } + + const parentVariable = variableNames.find(val => val.widgetId === widget.id)?.name || null + + console.log("widget ref: ", widgetRef) + let widgetCode = widgetRef.generateCode(varName, parentVariable) + + if (!widgetCode instanceof Array){ + throw new Error("Generate code function should return array, each new line should be a new item") + } + // add \n after every line + widgetCode = widgetCode.flatMap((item, index) => index < code.length - 1 ? [item, "\n"] : [item]) + + code.push(...widgetCode) + code.push("\n") + + } + + code.push(`\n${mainVariable}.mainloop()`) // start the main loop for tkinter + + console.log("Code: ", code.join("")) + + message.info("starting zipping files, download will start in a few seconds") + + // createFilesAndDownload([], projectName).then(() => { + // message.success("Download complete") + // }).catch(() => { + // message.error("Error while downloading") + // }) } diff --git a/src/frameworks/tkinter/widgets/mainWindow.js b/src/frameworks/tkinter/widgets/mainWindow.js index 815c365..65186e0 100644 --- a/src/frameworks/tkinter/widgets/mainWindow.js +++ b/src/frameworks/tkinter/widgets/mainWindow.js @@ -36,11 +36,12 @@ class MainWindow extends Widget{ this.setWidgetName("main") } - generateCode(parent){ + generateCode(variableName, parent){ - return (` - ${this.getWidgetName()} = tk.Tk() - `) + return [ + `${variableName} = tk.Tk()`, + `${variableName}.title("${this.getAttrValue("title")}")` + ] } getToolbarAttrs(){ diff --git a/src/frameworks/tkinter/widgets/toplevel.js b/src/frameworks/tkinter/widgets/toplevel.js index 6e759c0..681d656 100644 --- a/src/frameworks/tkinter/widgets/toplevel.js +++ b/src/frameworks/tkinter/widgets/toplevel.js @@ -37,6 +37,14 @@ class TopLevel extends Widget{ this.setWidgetName("toplevel") } + generateCode(variableName, parent){ + + return [ + `${variableName} = tk.TopLevel(root=${parent})`, + `${variableName}.title("${this.getAttrValue("title")}")` + ] + } + getToolbarAttrs(){ const toolBarAttrs = super.getToolbarAttrs()