diff --git a/README.md b/README.md index cb6842f..d03e6c1 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,11 @@ Licenses: see [LICENSES](./LICENSE). - Proper PWA w/ File opening / association to *.scad files - Animation rendering (And other formats than STL) - Compress URL fragment -- Customizer support. Probably by adding --export-json or --export-format=customizer-json to OpenSCAD. +- Customizer support. Probably by adding --export-json or --export-format=customizer-json to OpenSCAD. And use React Hook Forms maybe? https://react-hook-form.com/ - Mobile (iOS) editing support: switch to https://www.npmjs.com/package/react-codemirror ? - Proper Preview rendering: have OpenSCAD export the preview scene to a rich format (e.g. glTF, with some parts being translucent when prefixed w/ % modifier) and display it using https://modelviewer.dev/ maybe) +- Detect which bundled libraries are included / used in the sources and only download these rather than wait for all of the zips. Means the file explorer would need to be more lazy or have some prebuilt hierarchy. +- Preparse builtin libraries definitions at compile time, ship the JSON. ## Building diff --git a/src/components/FilePicker.tsx b/src/components/FilePicker.tsx index 83fdcf7..f627ba6 100644 --- a/src/components/FilePicker.tsx +++ b/src/components/FilePicker.tsx @@ -7,6 +7,7 @@ import { ModelContext, FSContext } from './contexts'; // import { isFileWritable } from '../state/model'; import { join } from '../fs/filesystem'; import { defaultSourcePath } from '../state/initial-state'; +import { zipArchives } from '../fs/zip-archives'; function listFilesAsNodes(fs: FS, path: string, accept?: (path: string) => boolean): TreeNode[] { const files: [string, string][] = [] @@ -15,7 +16,7 @@ function listFilesAsNodes(fs: FS, path: string, accept?: (path: string) => boole if (name.startsWith('.')) { continue; } - const childPath = join(path, name);//`${path}/${name}`; + const childPath = join(path, name); if (accept && !accept(childPath)) { continue; } @@ -31,19 +32,47 @@ function listFilesAsNodes(fs: FS, path: string, accept?: (path: string) => boole const nodes: TreeNode[] = [] for (const [arr, isDirectory] of [[files, false], [dirs, true]] as [[string, string][], boolean][]) { for (const [name, path] of arr) { - const children = isDirectory ? listFilesAsNodes(fs, path) : undefined; - if (isDirectory && children!.length == 0) { - continue; + let children: TreeNode[] = []; + let label = name; + if (path.lastIndexOf('/') === 0) { + const config = zipArchives[name]; + if (config && config.gitOrigin) { + const repoUrl = config.gitOrigin.repoUrl; + if (!children) children = []; + + children.push({ + icon: 'pi pi-github', + label: repoUrl, + key: repoUrl, + selectable: true, + }); + + for (const [label, link] of Object.entries(config.docs ?? [])) { + children.push({ + icon: 'pi pi-book', + label, + key: link, + selectable: true, + }); + } + } } + + if (isDirectory) { + children = [...children, ...listFilesAsNodes(fs, path, accept)]; + if (children.length == 0) { + continue; + } + } + nodes.push({ - // icon: path == '/home' ? 'pi-home' : ... // icon: isDirectory ? 'pi pi-folder' : isFileWritable(path) ? 'pi pi-file' : 'pi pi-lock', icon: isDirectory ? 'pi pi-folder' : path === defaultSourcePath ? 'pi pi-home' : 'pi pi-file', - label: name, + label, data: path, key: path, children, - selectable: !isDirectory // && (name == 'LICENSE' || name.endsWith('.scad') || name.endsWith('.scad') + selectable: !isDirectory }); } } @@ -58,34 +87,26 @@ export default function FilePicker({className, style}: {className?: string, styl const fs = useContext(FSContext); const fsItems = fs && listFilesAsNodes(fs, '/') - // [ - // { - // icon: 'pi pi-home', - // label: 'User files', - // key: '/', - // children: listFilesAsNodes(fs, '/'),// - // // children: listFilesAsNodes(fs, '/', f => f != librariesFolder && !f.startsWith(`${librariesFolder}/`)), - // selectable: false - // }, - // { - // icon: 'pi pi-database', - // label: 'Builtin libraries', - // key: '/libraries', - // children: listFilesAsNodes(fs, '/libraries'), - // selectable: false - // }, - // ] || []; return ( model.openFile(String(e.value))} - // dropdownIcon="pi pi-folder-open" + resetFilterOnHide={true} + filterBy="key" + onChange={e => { + const key = e.value; + if (typeof key === 'string') { + if (key.startsWith('https://')) { + window.open(key, '_blank') + } else { + model.openFile(key); + } + } + }} filter style={style} - // style={{style}} options={fsItems} /> ) } diff --git a/src/fs/filesystem.ts b/src/fs/filesystem.ts index 7093761..c7fb4de 100644 --- a/src/fs/filesystem.ts +++ b/src/fs/filesystem.ts @@ -1,6 +1,6 @@ // Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING. -import { zipArchives } from "./zip-archives"; +import { deployedArchiveNames, zipArchives } from "./zip-archives"; declare var BrowserFS: BrowserFSInterface @@ -56,7 +56,7 @@ export async function symlinkLibraries(archiveNames: string[], fs: FS, prefix='/ }; await Promise.all(archiveNames.map(n => (async () => { - if (!(n in zipArchives)) throw new Error(`Archive named ${n} invalid (valid ones: ${Object.keys(zipArchives).join(', ')})`); + if (!(n in zipArchives)) throw new Error(`Archive named ${n} invalid (valid ones: ${deployedArchiveNames.join(', ')})`); const {symlinks} = (zipArchives)[n]; if (symlinks) { for (const from in symlinks) { @@ -87,7 +87,7 @@ function configureAndInstallFS(windowOrSelf: Window, options: any) { } export async function createEditorFS(prefix: string): Promise { - const archiveNames = Object.keys(zipArchives); + const archiveNames = deployedArchiveNames; const librariesMounts = await getBrowserFSLibrariesMounts(archiveNames); const allMounts: FSMounts = {}; for (const n in librariesMounts) { diff --git a/src/fs/zip-archives.ts b/src/fs/zip-archives.ts index d2e0dc7..a7a2bea 100644 --- a/src/fs/zip-archives.ts +++ b/src/fs/zip-archives.ts @@ -4,47 +4,152 @@ import { Symlinks } from "./filesystem"; export type ZipArchives = { [name: string]: { - symlinks?: Symlinks + deployed?: boolean, + description?: string, + gitOrigin?: { + repoUrl: string, + branch: string, + include: { + glob: string | string[], + ignore?: string | string[], + replacePrefix?: {[path: string]: string}, + }[] + } + symlinks?: Symlinks, + docs?: {[name: string]: string} } }; export const zipArchives: ZipArchives = { 'fonts': {}, - // @openscad - 'MCAD': {}, - // @revarbat - 'BOSL': {}, - 'BOSL2': { - // "includes": { - // "BOSL2/std.scad": "The Belfry OpenScad Library, v2.0. An OpenSCAD library of shapes, masks, and manipulators to make working with OpenSCAD easier. BETA" - // } + 'MCAD': { + description: 'OpenSCAD Parametric CAD Library', + gitOrigin: { + branch: 'master', + repoUrl: 'https://github.com/openscad/MCAD', + include: [{glob: ['*.scad', 'bitmap/*.scad', 'LICENSE']}], + }, + }, + 'BOSL': { + description: 'The Belfry OpenScad Library', + gitOrigin: { + branch: 'master', + repoUrl: 'https://github.com/revarbat/BOSL', + include: [{glob: ['**/*.scad', 'LICENSE']}], + }, + }, + 'BOSL2': { + description: 'The Belfry OpenScad Library, v2.0', + gitOrigin: { + branch: 'master', + repoUrl: 'https://github.com/revarbat/BOSL2', + include: [{glob: ['**/*.scad', 'LICENSE']}], + }, + docs: { + 'CheatSheet': 'https://github.com/revarbat/BOSL2/wiki/CheatSheet', + 'Wiki': 'https://github.com/revarbat/BOSL2/wiki', + }, + }, + 'NopSCADlib': { + gitOrigin: { + branch: 'master', + repoUrl: 'https://github.com/nophead/NopSCADlib', + include: [{ + glob: '**/*.scad', + ignore: 'test/**', + }], + }, + }, + 'FunctionalOpenSCAD': { + description: 'Implementing OpenSCAD in OpenSCAD', + gitOrigin: { + branch: 'master', + repoUrl: 'https://github.com/thehans/FunctionalOpenSCAD', + include: [{glob: ['**/*.scad', 'LICENSE']}], + }, + }, + 'funcutils': { + description: 'OpenSCAD collection of functional programming utilities, making use of function-literals.', + gitOrigin: { + branch: 'master', + repoUrl: 'https://github.com/thehans/funcutils', + include: [{glob: '**/*.scad'}], + }, }, - // @nophead - 'NopSCADlib': {}, - // @thehans - 'FunctionalOpenSCAD': {}, - 'funcutils': {}, - // @colyer 'smooth-prim': { + description: 'OpenSCAD smooth primitives library', + gitOrigin: { + branch: 'master', + repoUrl: 'https://github.com/rcolyer/smooth-prim', + include: [{glob: ['**/*.scad', 'LICENSE.txt']}], + }, symlinks: {'smooth_prim.scad': 'smooth_prim.scad'}, }, 'closepoints': { + description: 'OpenSCAD ClosePoints Library', + gitOrigin: { + branch: 'master', + repoUrl: 'https://github.com/rcolyer/closepoints', + include: [{glob: ['**/*.scad', 'LICENSE.txt']}], + }, symlinks: {'closepoints.scad': 'closepoints.scad'}, }, 'plot-function': { + description: 'OpenSCAD Function Plotting Library', + gitOrigin: { + branch: 'master', + repoUrl: 'https://github.com/colyer/plot-function', + include: [{glob: ['**/*.scad', 'LICENSE.txt']}], + }, symlinks: {'plot_function.scad': 'plot_function.scad'}, }, - // 'threads': {}, - // @sofian + // 'threads': { + // deployed: false, + // gitOrigin: { + // branch: 'master', + // repoUrl: 'https://github.com/colyer/threads', + // include: [{glob: ['**/*.scad', 'LICENSE.txt']}], + // }, + // }, 'openscad-tray': { + description: 'OpenSCAD library to create rounded rectangular trays with optional subdividers.', + gitOrigin: { + branch: 'main', + repoUrl: 'https://github.com/sofian/openscad-tray', + include: [{glob: ['**/*.scad', 'LICENSE']}], + }, symlinks: {'tray.scad': 'tray.scad'}, }, - // @mrWheel - 'YAPP_Box': {}, - // @Cantareus - 'Stemfie_OpenSCAD': {}, - // @UBaer21 + 'YAPP_Box': { + description: 'Yet Another Parametric Projectbox Box', + gitOrigin: { + branch: 'main', + repoUrl: 'https://github.com/mrWheel/YAPP_Box', + include: [{glob: ['**/*.scad', 'LICENSE']}], + }, + }, + 'Stemfie_OpenSCAD': { + description: 'OpenSCAD Stemfie Library', + gitOrigin: { + branch: 'main', + repoUrl: 'https://github.com/Cantareus/Stemfie_OpenSCAD', + include: [{glob: ['**/*.scad', 'LICENSE']}], + }, + }, 'UB.scad': { - symlinks: {"ub.scad": "libraries/ub.scad"}, + gitOrigin: { + branch: 'main', + repoUrl: 'https://github.com/UBaer21/UB.scad', + include: [{glob: ['libraries/*.scad', 'LICENSE', 'examples/UBexamples/*.scad'], replacePrefix: { + 'libraries/': '', + 'examples/UBexamples/': 'examples/', + }}], + }, + symlinks: {"ub.scad": "libraries/ub.scad"}, // TODO change this after the replaces work }, }; + +export const deployedArchiveNames = + Object.entries(zipArchives) + .filter(([_, {deployed}]) => deployed == null || deployed) + .map(([n]) => n); diff --git a/src/language/openscad-completions.ts b/src/language/openscad-completions.ts index 930975c..2c0d2d0 100644 --- a/src/language/openscad-completions.ts +++ b/src/language/openscad-completions.ts @@ -76,11 +76,13 @@ export async function buildOpenSCADCompletionItemProvider(fs: FS, workingDir: st const toAbsolutePath = (path: string) => path.startsWith('/') ? path : `${workingDir}/${path}`; const allSymlinks: Symlinks = {}; - for (const n of Object.keys(zipArchives)) { + for (const [n, {deployed, symlinks}] of Object.entries(zipArchives)) { if (n == 'fonts') { continue; } - const { symlinks } = zipArchives[n]; + if (deployed === false) { + continue; + } for (const s in symlinks) { allSymlinks[s] = `${n}/${symlinks[s]}`; }