diff --git a/package-lock.json b/package-lock.json index 52fa908c..b3af31f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@types/react-dom": "^19.0.2", "antd": "^5.22.1", "blockly": "^11.1.1", + "jszip": "^3.10.1", "lucide-react": "^0.460.0", "re-resizable": "^6.10.1", "react": "^18.3.1", @@ -3207,6 +3208,12 @@ "toggle-selection": "^1.0.6" } }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz", @@ -3973,11 +3980,16 @@ } ] }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/is-alphabetical": { "version": "1.0.4", @@ -4134,6 +4146,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -4261,6 +4279,48 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "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==", + "license": "MIT", + "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==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -4270,6 +4330,15 @@ "node": ">=6" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lilconfig": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", @@ -4812,6 +4881,12 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/parse-entities": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", @@ -5166,6 +5241,12 @@ "node": ">=6" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -6081,6 +6162,12 @@ "compute-scroll-into-view": "^3.0.2" } }, + "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==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -6987,8 +7074,7 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/vite": { "version": "6.1.0", diff --git a/package.json b/package.json index 3b577046..f7acd64d 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "@types/react-dom": "^19.0.2", "antd": "^5.22.1", "blockly": "^11.1.1", + "jszip": "^3.10.1", "lucide-react": "^0.460.0", "re-resizable": "^6.10.1", "react": "^18.3.1", diff --git a/src/App.tsx b/src/App.tsx index ac5d39ad..546e1dab 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -14,10 +14,12 @@ import { Tooltip, Tree, Typography, - theme + theme, + Upload } from 'antd'; import type { InputRef } from 'antd'; import type { TreeDataNode, TreeProps } from 'antd'; +import type { UploadProps } from 'antd'; import { AppstoreAddOutlined as MechanismAddOutlined, AppstoreOutlined as MechanismOutlined, @@ -348,7 +350,7 @@ const App: React.FC = () => { if (errorMessage) { setAlertErrorMessage('Unable to load the list of modules: ' + errorMessage); setAlertErrorVisible(true); - return + return; } if (array != null) { setModules(array) @@ -894,17 +896,65 @@ const App: React.FC = () => { setOpenPopconfirm(true); }; - const handleUploadClicked = () => { - messageApi.open({ - type: 'success', - content: 'Not implemented yet .', - }); + const uploadProps: UploadProps = { + accept: commonStorage.UPLOAD_DOWNLOAD_FILE_EXTENSION, + beforeUpload: (file) => { + const isBlocks = file.name.endsWith(commonStorage.UPLOAD_DOWNLOAD_FILE_EXTENSION) + if (!isBlocks) { + setAlertErrorMessage(file.name + ' is not a blocks file'); + setAlertErrorVisible(true); + return false; + } + return isBlocks || Upload.LIST_IGNORE; + }, + onChange: (info) => { + }, + customRequest: ({ file, onSuccess, onError }) => { + const reader = new FileReader(); + reader.onload = (event) => { + const dataUrl = event.target.result; + const uploadProjectName = commonStorage.makeUploadProjectName(file.name, getProjectNames()); + storage.uploadProject( + uploadProjectName, dataUrl, + (success: boolean, errorMessage: string) => { + if (success) { + onSuccess('Upload successful'); + afterListModulesSuccess.current = () => { + const uploadProjectPath = commonStorage.makeProjectPath(uploadProjectName); + setCurrentModulePath(uploadProjectPath); + }; + setTriggerListModules(!triggerListModules); + } else { + onError(errorMessage); + setAlertErrorMessage('Unable to upload the project'); + setAlertErrorVisible(true); + } + }); + }; + reader.onerror = (error) => { + onError(error); + setAlertErrorMessage('Unable to upload the project'); + setAlertErrorVisible(true); + }; + reader.readAsDataURL(file); + }, }; const handleDownloadClicked = () => { - messageApi.open({ - type: 'success', - content: 'Not implemented yet .', + checkIfBlocksWereModified(() => { + storage.downloadProject( + currentModule.projectName, + (url: string | null, errorMessage: string) => { + if (errorMessage) { + setAlertErrorMessage('Unable to download the project: ' + errorMessage); + setAlertErrorVisible(true); + return; + } + const link = document.createElement('a'); + link.href = url; + link.download = currentModule.projectName + commonStorage.UPLOAD_DOWNLOAD_FILE_EXTENSION; + link.click(); + }); }); }; @@ -1083,16 +1133,20 @@ const App: React.FC = () => { > - - + + - +