diff --git a/README.md b/README.md index f94f088..084ed32 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,17 @@ This project uses [`openscad-wasm`](https://github.com/openscad/openscad-wasm) under the hood to build an editor for OpenSCAD that is capable of loading `.scad` files from other servers. -It is capable of rendering and exporting your creations. There is also a naive implementation of a -visual customizer. +## Features + +- It is capable of rendering OpenSCAD scripts +- You can import scripts by URL +- There are adapters for Printables and Thingiverse. Those platform do not expose the + links to their files in a way the user can copy the URL to the files. Instead this + application imports it from the model URL (i.e. https://www.thingiverse.com/thing:1234 + or https://www.printables.com/model/123456-model-name) +- You can import your own libraries or chose from a list of common libraries +- You can add your own fonts or choose from a list of common fonts +- You can export your customized model to STL, OFF, AMF, CSG, DXF and SVG # Dev diff --git a/package-lock.json b/package-lock.json index 6d84bad..e396ddf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,10 +14,13 @@ "@fontsource/roboto": "^5.0.8", "@mui/icons-material": "^5.15.6", "@mui/material": "^5.15.6", + "@mui/x-tree-view": "^6.17.0", + "@zip.js/zip.js": "^2.7.33", "mui-chips-input": "^2.1.3", "notistack": "^3.0.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-dropzone": "^14.2.3", "react-stl-viewer": "^2.5.0" }, "devDependencies": { @@ -2438,6 +2441,35 @@ } } }, + "node_modules/@mui/x-tree-view": { + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@mui/x-tree-view/-/x-tree-view-6.17.0.tgz", + "integrity": "sha512-09dc2D+Rjg2z8KOaxbUXyPi0aw7fm2jurEtV8Xw48xJ00joLWd5QJm1/v4CarEvaiyhTQzHImNqdgeJW8ZQB6g==", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@mui/base": "^5.0.0-beta.20", + "@mui/utils": "^5.14.14", + "@types/react-transition-group": "^4.4.8", + "clsx": "^2.0.0", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.8.6", + "@mui/system": "^5.8.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -3397,6 +3429,16 @@ "resolved": "https://registry.npmjs.org/@webgpu/glslang/-/glslang-0.0.15.tgz", "integrity": "sha512-niT+Prh3Aff8Uf1MVBVUsaNjFj9rJAKDXuoHIKiQbB+6IUP/3J3JIhBNyZ7lDhytvXxw6ppgnwKZdDJ08UMj4Q==" }, + "node_modules/@zip.js/zip.js": { + "version": "2.7.33", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.33.tgz", + "integrity": "sha512-3Ct8eoIkvkflNEyL3wD3GwJ2ORMobGTGO4v9giq6a4vCBcVmk1qFXtfDN2P9hJ9Eibd4zPKMiRCca4lVu+56Ig==", + "engines": { + "bun": ">=0.7.0", + "deno": ">=1.0.0", + "node": ">=16.5.0" + } + }, "node_modules/acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", @@ -3702,6 +3744,14 @@ "node": ">= 4.0.0" } }, + "node_modules/attr-accept": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", + "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==", + "engines": { + "node": ">=4" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -6186,6 +6236,17 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/file-selector": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz", + "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==", + "dependencies": { + "tslib": "^2.4.0" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -10741,6 +10802,22 @@ "loose-envify": "^1.1.0" } }, + "node_modules/react-dropzone": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz", + "integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==", + "dependencies": { + "attr-accept": "^2.2.2", + "file-selector": "^0.6.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "react": ">= 16.8 || 18.0.0" + } + }, "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -11817,8 +11894,7 @@ "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/tunnel-agent": { "version": "0.6.0", @@ -14121,6 +14197,20 @@ "react-is": "^18.2.0" } }, + "@mui/x-tree-view": { + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@mui/x-tree-view/-/x-tree-view-6.17.0.tgz", + "integrity": "sha512-09dc2D+Rjg2z8KOaxbUXyPi0aw7fm2jurEtV8Xw48xJ00joLWd5QJm1/v4CarEvaiyhTQzHImNqdgeJW8ZQB6g==", + "requires": { + "@babel/runtime": "^7.23.2", + "@mui/base": "^5.0.0-beta.20", + "@mui/utils": "^5.14.14", + "@types/react-transition-group": "^4.4.8", + "clsx": "^2.0.0", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -14812,6 +14902,11 @@ "resolved": "https://registry.npmjs.org/@webgpu/glslang/-/glslang-0.0.15.tgz", "integrity": "sha512-niT+Prh3Aff8Uf1MVBVUsaNjFj9rJAKDXuoHIKiQbB+6IUP/3J3JIhBNyZ7lDhytvXxw6ppgnwKZdDJ08UMj4Q==" }, + "@zip.js/zip.js": { + "version": "2.7.33", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.33.tgz", + "integrity": "sha512-3Ct8eoIkvkflNEyL3wD3GwJ2ORMobGTGO4v9giq6a4vCBcVmk1qFXtfDN2P9hJ9Eibd4zPKMiRCca4lVu+56Ig==" + }, "acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", @@ -15031,6 +15126,11 @@ "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", "dev": true }, + "attr-accept": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", + "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==" + }, "available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -16890,6 +16990,14 @@ "flat-cache": "^3.0.4" } }, + "file-selector": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz", + "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==", + "requires": { + "tslib": "^2.4.0" + } + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -20277,6 +20385,16 @@ } } }, + "react-dropzone": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz", + "integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==", + "requires": { + "attr-accept": "^2.2.2", + "file-selector": "^0.6.0", + "prop-types": "^15.8.1" + } + }, "react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -21083,8 +21201,7 @@ "tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "tunnel-agent": { "version": "0.6.0", diff --git a/package.json b/package.json index 493d1f7..c146d8d 100644 --- a/package.json +++ b/package.json @@ -39,10 +39,13 @@ "@fontsource/roboto": "^5.0.8", "@mui/icons-material": "^5.15.6", "@mui/material": "^5.15.6", + "@mui/x-tree-view": "^6.17.0", + "@zip.js/zip.js": "^2.7.33", "mui-chips-input": "^2.1.3", "notistack": "^3.0.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-dropzone": "^14.2.3", "react-stl-viewer": "^2.5.0" } } diff --git a/src/App.tsx b/src/App.tsx index a398201..94d9ed5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,9 +1,11 @@ -import { Box, CircularProgress, Paper, styled } from '@mui/material'; +import { styled } from '@mui/material'; +import Box from '@mui/material/Box'; +import CircularProgress from '@mui/material/CircularProgress'; import React from 'react'; -import Editor from './components/Editor'; import ErrorBox from './components/ErrorBox'; -import ImportFileSelector from './components/ImportFileSelector'; +import { useFileSystemProvider } from './components/FileSystemProvider'; +import Workspace from './components/Workspace'; import useImport from './hooks/useImport'; const MyBox = styled(Box)(({ theme }) => ({ @@ -19,9 +21,8 @@ const MyBox = styled(Box)(({ theme }) => ({ export default function App() { const importUrl = getImportUrl(); - const { error, files, isLoading } = useImport(importUrl); - const [selectedIndex, setSelectedIndex] = React.useState(); - let file: string | undefined; + const { error, isLoading } = useImport(importUrl); + const { files } = useFileSystemProvider(); // Show a loading indicator during the import. if (isLoading) { @@ -49,27 +50,7 @@ export default function App() { ); } - // If there are multiple files, let the user select the one to import. - if (files.length > 1 && selectedIndex === undefined) { - return ( - - - - - - ); - } - - // If there is only one file, we can directly import it. - if (files.length === 1) { - file = files[0].url; - - // If the user has selected a file, we can import it. - } else if (selectedIndex !== undefined && files.length > selectedIndex) { - file = files[selectedIndex].url; - } - - return ; + return ; } function getImportUrl(): string | undefined { diff --git a/src/components/CodeEditor.tsx b/src/components/CodeEditor.tsx deleted file mode 100644 index f01dae8..0000000 --- a/src/components/CodeEditor.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; - -interface CodeEditorProps { - code: string; - disabled?: boolean; - onChange: (newCode: string) => void; -} - -export default function CodeEditor({ - code, - disabled, - onChange, -}: CodeEditorProps) { - const handleCodeChange = (event: React.ChangeEvent) => { - onChange(event.target.value); - }; - - return ( -
-