diff --git a/README.md b/README.md index f90eb7a..16eb231 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,9 @@ If you want to discuss jscad or jscadui you can also join us on discord: https:/ Most of the things are work in progres, but some parts are pretty ready to be used -- [packages/html-gizmo](./packages/html-gizmo) - a gizmo to display current camera direction +- [packages/html-gizmo](./packages/html-gizmo) - [![npm version](https://badge.fury.io/js/@jscadui%2Fhtml-gizmo.svg)](https://www.npmjs.com/package/@jscadui%2Fhtml-gizmo) a gizmo to display current camera direction +- [file-format/3mf-export](./file-format/3mf-export) - [![npm version](https://badge.fury.io/js/@jscadui%2F3mf-export.svg)](https://www.npmjs.com/package/@jscadui%2F3mf-export) 3mf-export (also used by mynifold) +- [packages/postmessage](./packages/postmessage) - [![npm version](https://badge.fury.io/js/@jscadui%2Fpostmessage.svg)](https://www.npmjs.com/package/@jscadui%2Fpostmessage) postMessage quality of life improvement # demo [jscad.app](https://jscad.app) is a nice demo and our attempt at making a an improved version of [openjscad.xyz](https://openjscad.xyz). diff --git a/apps/cardboard-cutter/main.js b/apps/cardboard-cutter/main.js index 30533b5..8706ff5 100644 --- a/apps/cardboard-cutter/main.js +++ b/apps/cardboard-cutter/main.js @@ -111,7 +111,7 @@ function save(blob, filename) { } function exportModel(format) { - sendCmd('exportData', { format }).then(({ data }) => { + sendCmd('exportData', [{ format }]).then(({ data }) => { console.log('save', fileToRun + '.stl', data) save(new Blob([data], { type: 'text/plain' }), fileToRun + '.stl') }) @@ -123,12 +123,12 @@ const { sendCmd, sendNotify } = initMessaging(worker, handlers) const paramChangeCallback = params => { console.log('params', params) - sendCmd('runMain', { params }) + sendCmd('runMain', [{ params }]) } export const runScript = file => { fileToRun = file.replace(/.*\//, '').replace(/\..*/, '') - sendCmd('runScript', { url: file }).then(result => { + sendCmd('runScript', [{ url: file }]).then(result => { console.log('result', result) genParams({ target: byId('paramsDiv'), params: result.def || {}, callback: paramChangeCallback }) }) @@ -141,5 +141,5 @@ export const initEngine = async (THREE, elem, workerOptions) => { updateFromCtrl(ctrl) setTheme(theme) - await sendCmd('init', workerOptions) + await sendCmd('init', [workerOptions]) } diff --git a/apps/engine-test/main.js b/apps/engine-test/main.js index bf02090..0edc827 100644 --- a/apps/engine-test/main.js +++ b/apps/engine-test/main.js @@ -3,7 +3,7 @@ import { JscadToCommon } from '@jscadui/format-jscad' import { Gizmo } from '@jscadui/html-gizmo' import { OrbitControl, OrbitState, closerAngle, getCommonRotCombined } from '@jscadui/orbit' import { genParams } from '@jscadui/params' -import { initMessaging } from '@jscadui/postmessage' +import { initMessaging, messageProxy } from '@jscadui/postmessage' import { makeAxes, makeGrid } from '@jscadui/scene' import * as themes from '@jscadui/themes' @@ -16,6 +16,8 @@ import { availableEngines, availableEnginesList } from './src/availableEngines' import { CurrentUrl } from './src/currentUrl' import { EngineState } from './src/engineState' +/** @typedef {import('@jscadui/worker').JscadWorker} JscadWorker*/ + const theme = themes.light const { subtract } = booleans const { translate } = transforms @@ -122,11 +124,11 @@ document.body.ondrop = async ev => { if (!sw) await initFs() showDrop(false) - sendCmd('clearTempCache', {}) + workerApi.clearTempCache() const { alias, script } = await fileDropped(sw, files) projectName = sw.projectName if (alias.length) { - sendNotify('init', { alias }) + workerApi.init({ alias }) } runScript({ url: sw.fileToRun, base: sw.base }) } catch (error) { @@ -167,14 +169,29 @@ function save(blob, filename) { } function exportModel(format) { - sendCmd('exportData', { format }).then(({ data }) => { + workerApi.exportData({ format }).then(({ data }) => { console.log('save', fileToRun + '.stl', data) save(new Blob([data], { type: 'text/plain' }), fileToRun + '.stl') }).catch(setError) } window.exportModel = exportModel +const paramChangeCallback = async params => { + console.log('params changed', params) + let result = await workerApi.runMain({ params }) + handlers.entities(result) +} + +const runScript = async ({script, url = './index.js', base, root}) => { + const result = await workerApi.runScript({ script, url, base, root }) + console.log('result', result) + genParams({ target: byId('paramsDiv'), params: result.def || {}, callback: paramChangeCallback }) + handlers.entities(result) +} + +/** @type {JscadWorker} */ const worker = new Worker('./build/bundle.worker.js') +const workerApi = messageProxy(worker, handlers, { onJobCount: trackJobs }) const handlers = { entities: ({ entities }) => { if (!(entities instanceof Array)) entities = [entities] @@ -182,27 +199,30 @@ const handlers = { setError(undefined) }, } -const { sendCmd, sendNotify } = initMessaging(worker, handlers) const spinner = byId('spinner') -async function sendCmdAndSpin(method, params){ - spinner.style.display = 'block' - try{ - return await sendCmd(method, params) - }catch(error){ - setError(error) - throw error - }finally{ +let firstJobTimer + +function trackJobs(jobs) { + if (jobs === 1) { + // do not show spinner for fast renders + firstJobTimer = setTimeout(() => { + spinner.style.display = 'block' + }, 300) + } + if (jobs === 0) { + clearTimeout(firstJobTimer) spinner.style.display = 'none' } } -sendCmdAndSpin('init', { +await workerApi.init({ bundles: { '@jscad/modeling': toUrl('./build/bundle.jscad_modeling.js'), }, -}).then(()=>{ - runScript({script:`const { sphere, geodesicSphere } = require('@jscad/modeling').primitives +}) + +runScript({script:`const { sphere, geodesicSphere } = require('@jscad/modeling').primitives const { translate, scale } = require('@jscad/modeling').transforms const main = () => [ @@ -220,27 +240,13 @@ sendCmdAndSpin('init', { module.exports = { main }` }) -}) - -const paramChangeCallback = async params => { - console.log('params changed', params) - let result = await sendCmdAndSpin('runMain', { params }) - handlers.entities(result) -} - -const runScript = async ({script, url = './index.js', base, root}) => { - const result = await sendCmdAndSpin('runScript', { script, url, base, root }) - console.log('result', result) - genParams({ target: byId('paramsDiv'), params: result.def || {}, callback: paramChangeCallback }) - handlers.entities(result) -} let sw async function initFs() { sw = await registerServiceWorker('bundle.fs-serviceworker.js?prefix=/swfs/') sw.defProjectName = 'jscad' sw.onfileschange = files => { - sendNotify('clearFileCache', { files }) + workerApi.clearFileCache({ files }) if (sw.fileToRun) runScript({ url: sw.fileToRun, base: sw.base }) } } diff --git a/apps/engine-test/tsconfig.json b/apps/engine-test/tsconfig.json index 4c9d069..9e48a45 100644 --- a/apps/engine-test/tsconfig.json +++ b/apps/engine-test/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "allowJs": true, + "skipLibCheck": true, "moduleResolution": "node16" }, "exclude": ["node_modules", "build_dev", "build", "dist"] diff --git a/apps/jscad-web/README.md b/apps/jscad-web/README.md index 59ee669..7aaced3 100644 --- a/apps/jscad-web/README.md +++ b/apps/jscad-web/README.md @@ -20,6 +20,15 @@ To start the local development server, go to the `apps/jscad-web` directory and npm start ``` +this will start the dev server without generating jscad docs, which is like ok 99% of time. + +to start dev server that also has docs run + +``` +npm run start:full +``` + + ## Deployment To start the production server run: diff --git a/apps/jscad-web/build.js b/apps/jscad-web/build.js index 2b6d1c3..38e1635 100644 --- a/apps/jscad-web/build.js +++ b/apps/jscad-web/build.js @@ -7,12 +7,12 @@ import {serve} from './serve.js' import { buildBundle, buildOne } from './src_build/esbuildUtil.js' // *************** read parameters ********************** -const { dev, port = 5120, serve:serveBuild=false } = parseArgs() +const { dev, port = 5120, serve:serveBuild=false, skipDocs=false } = parseArgs() const watch = dev const outDir = dev ? 'build_dev' : 'build' const docsDir = 'jscad/docs' // if docs dir does not exist, then clone jscad and run `npm run docs` to generate it -if (!existsSync(docsDir)) { +if (!skipDocs &&!existsSync(docsDir)) { console.log('generating docs') if (!existsSync('jscad')) { // TODO: faster to fetch https://github.com/jscad/OpenJSCAD.org/archive/refs/heads/master.zip @@ -29,16 +29,16 @@ mkdirSync(outDir, { recursive: true }) copyTask('static', outDir, { include: [], exclude: [], watch, filters: [] }) copyTask('examples', outDir+'/examples', { include: [], exclude: [], watch, filters: [] }) //in dev mode dont try to sync docs, just copy the first time -if(!(dev & existsSync(outDir + "/docs"))){ +if(!skipDocs && !(dev & existsSync(outDir + "/docs"))){ // this task is heavy copyTask(docsDir, outDir + "/docs", { include: [], exclude: [], watch:false, filters: [] }) } /**************************** BUILD JS that is static *************/ -await buildBundle(outDir + '/build', 'bundle.threejs.js', { globalName: 'THREE' }) -await buildBundle(outDir + '/build', 'bundle.jscad_modeling.js', { format: 'cjs' }) -await buildBundle(outDir + '/build', 'bundle.jscad_io.js', { format:'cjs' }) -await buildBundle(outDir + '/build', 'bundle.jscadui.transform-babel.js', { globalName: 'jscadui_transform_babel' }) +await buildBundle(outDir + '/build', 'bundle.threejs.js', { globalName: 'THREE', skipExisting: dev }) +await buildBundle(outDir + '/build', 'bundle.jscad_modeling.js', { format: 'cjs', skipExisting: dev }) +await buildBundle(outDir + '/build', 'bundle.jscad_io.js', { format:'cjs', skipExisting: dev }) +await buildBundle(outDir + '/build', 'bundle.jscadui.transform-babel.js', { globalName: 'jscadui_transform_babel', skipExisting: dev }) /**************************** BUILD JS THAT can change and watch if in dev mode *************/ await buildOne('src_bundle', outDir + '/build', 'bundle.worker.js', watch, { format: 'iife' }) diff --git a/apps/jscad-web/main.js b/apps/jscad-web/main.js index 4ea81e1..9dab9fb 100644 --- a/apps/jscad-web/main.js +++ b/apps/jscad-web/main.js @@ -2,6 +2,7 @@ import { addToCache, clearFs, extractEntries, + analyzeProject, fileDropped, getFile, getFileContent, @@ -10,7 +11,7 @@ import { import { Gizmo } from '@jscadui/html-gizmo' import { OrbitControl } from '@jscadui/orbit' import { genParams } from '@jscadui/params' -import { initMessaging } from '@jscadui/postmessage' +import { initMessaging, messageProxy } from '@jscadui/postmessage' import defaultCode from './examples/jscad.example.js' import * as editor from './src/editor.js' @@ -21,15 +22,17 @@ import * as remote from './src/remote.js' import { formatStacktrace } from './src/stacktrace.js' import { ViewState } from './src/viewState.js' import * as welcome from './src/welcome.js' -import { runMain } from '../../packages/worker/worker.js' export const byId = id => document.getElementById(id) + +/** @typedef {import('@jscadui/worker').JscadWorker} JscadWorker*/ + const appBase = document.baseURI let currentBase = appBase const toUrl = path => new URL(path, appBase).toString() const viewState = new ViewState() -viewState.onRequireReRender = ()=>paramChangeCallback(lastRunParams) +viewState.onRequireReRender = () => paramChangeCallback(lastRunParams) const gizmo = (window.gizmo = new Gizmo()) byId('overlay').parentNode.appendChild(gizmo) @@ -55,10 +58,10 @@ ctrl.oninput = state => updateFromCtrl(state) gizmo.oncam = ({ cam }) => ctrl.animateToCommonCamera(cam) let sw -async function resetFileRefs(){ +async function resetFileRefs() { editor.setFiles([]) saveMap = {} - if(sw){ + if (sw) { delete sw.fileToRun await clearFs(sw) } @@ -78,8 +81,13 @@ async function initFs() { }) sw.defProjectName = 'jscad' sw.onfileschange = files => { - sendNotify('clearFileCache', { files }) - editor.filesChanged(files) + console.log('files', files) + if(files.includes('/package.json')){ + reloadProject() + }else{ + workerApi.clearFileCache({ files }) + editor.filesChanged(files) + } if (sw.fileToRun) runScript({ url: sw.fileToRun, base: sw.base }) } sw.getFile = path => getFile(path, sw) @@ -97,23 +105,31 @@ document.body.ondrop = async ev => { await resetFileRefs() if (!sw) await initFs() showDrop(false) - sendCmd('clearTempCache', {}) - saveMap = {} - const { alias, script } = await fileDropped(sw, files) - projectName = sw.projectName - if (alias.length) { - sendNotify('init', { alias }) - } - let url = sw.fileToRun - runScript({ url, base: sw.base }) - editor.setSource(script, url) - editor.setFiles(sw.filesToCheck) + workerApi.clearTempCache() + + await fileDropped(sw, files) + + reloadProject() + } catch (error) { setError(error) console.error(error) } } +async function reloadProject(){ + saveMap = {} + const { alias, script } = await analyzeProject(sw) + projectName = sw.projectName + if (alias.length) { + workerApi.init({ alias }) + } + let url = sw.fileToRun + runScript({ url, base: sw.base }) + editor.setSource(script, url) + editor.setFiles(sw.filesToCheck) +} + document.body.ondragover = ev => { ev.preventDefault() showDrop(true) @@ -128,7 +144,6 @@ document.body.ondragleave = document.body.ondragend = ev => { const setError = error => { const errorBar = byId('error-bar') if (error) { - console.error(error) const name = (error.name || 'Error') + ': ' byId('error-name').innerText = name const message = formatStacktrace(error) @@ -150,7 +165,7 @@ function save(blob, filename) { } const exportModel = async (format, extension) => { - const { data } = (await sendCmdAndSpin('exportData', { format })) || {} + const { data } = (await workerApi.exportData({ format })) || {} if (data) { save(new Blob([data], { type: 'text/plain' }), `${projectName}.${extension}`) console.log('save', `${projectName}.${extension}`, data) @@ -177,44 +192,52 @@ const handlers = { }, onProgress, } -const { sendCmd, sendNotify } = initMessaging(worker, handlers) + +/** @type {JscadWorker} */ +const workerApi = messageProxy(worker, handlers, { onJobCount: trackJobs }) const progress = byId('progress') let jobs = 0 let firstJobTimer -async function sendCmdAndSpin(method, params) { - jobs++ + +function trackJobs(jobs) { if (jobs === 1) { // do not show progress for fast renders + clearTimeout(firstJobTimer) firstJobTimer = setTimeout(() => { onProgress([]) progress.style.display = 'block' }, 300) } - try { - return await sendCmd(method, params) - } catch (error) { - setError(error) - throw error - } finally { - if (--jobs === 0) { - clearTimeout(firstJobTimer) - progress.style.display = 'none' - } + if (jobs === 0) { + clearTimeout(firstJobTimer) + progress.style.display = 'none' } } -sendCmdAndSpin('init', { - bundles: { - // local bundled alias for common libs. - '@jscad/modeling': toUrl('./build/bundle.jscad_modeling.js'), - '@jscad/io': toUrl('./build/bundle.jscad_io.js'), - }, -}).then(() => { - if (loadDefault) { - runScript({ script: defaultCode, smooth: viewState.smoothRender }) +const runScript = async ({ script, url = './jscad.model.js', base = currentBase, root }) => { + currentBase = base + loadDefault = false // don't load default model if something else was loaded + try{ + const result = await workerApi.runScript({ script, url, base, root, smooth: viewState.smoothRender }) + genParams({ target: byId('paramsDiv'), params: result.def || {}, callback: paramChangeCallback }) + lastRunParams = result.params + handlers.entities(result) + }catch(err){ + setError(err) } -}) +} + +const bundles = { + // local bundled alias for common libs. + '@jscad/modeling': toUrl('./build/bundle.jscad_modeling.js'), + '@jscad/io': toUrl('./build/bundle.jscad_io.js'), +} + +await workerApi.init({ bundles }) +if (loadDefault) { + runScript({ script: defaultCode, smooth: viewState.smoothRender }) +} let working let lastParams @@ -228,23 +251,14 @@ const paramChangeCallback = async params => { } working = true let result - try{ - result = await sendCmdAndSpin('runMain', { params, smooth: viewState.smoothRender }) + try { + result = await workerApi.runMain({ params, smooth: viewState.smoothRender }) lastRunParams = params - } finally{ + } finally { working = false } - handlers.entities(result, {smooth: viewState.smoothRender}) - if(lastParams && lastParams != params) paramChangeCallback(lastParams) -} - -const runScript = async ({ script, url = './jscad.model.js', base = currentBase, root }) => { - currentBase = base - loadDefault = false // don't load default model if something else was loaded - const result = await sendCmdAndSpin('runScript', { script, url, base, root, smooth: viewState.smoothRender }) - genParams({ target: byId('paramsDiv'), params: result.def || {}, callback: paramChangeCallback }) - lastRunParams = result.params - handlers.entities(result) + handlers.entities(result, { smooth: viewState.smoothRender }) + if (lastParams && lastParams != params) paramChangeCallback(lastParams) } const loadExample = async (source, base = appBase) => { @@ -254,21 +268,19 @@ const loadExample = async (source, base = appBase) => { } // Initialize three engine -engine.init().then(viewer => { - viewState.setEngine(viewer) -}) +viewState.setEngine(await engine.init()) let saveMap = {} -setInterval(async ()=>{ - for(let p in saveMap){ +setInterval(async () => { + for (let p in saveMap) { let handle = saveMap[p] let file = await handle.getFile() - if(file.lastModified > handle.lastMod){ + if (file.lastModified > handle.lastMod) { handle.lastMod = file.lastModified editor.filesChanged([file]) } } -},500) +}, 500) editor.init( defaultCode, @@ -279,7 +291,7 @@ editor.init( // it is expected if multiple files require same file/module that first time it is loaded // but for others resolved module is returned // if not cleared by calling clearFileCache, require will not try to reload the file - await sendCmd('clearFileCache', { files: [path] }) + await workerApi.clearFileCache({ files: [path] }) if (sw.fileToRun) runScript({ url: sw.fileToRun, base: sw.base }) } else { runScript({ script }) @@ -289,7 +301,7 @@ editor.init( console.log('save file', path) let pathArr = path.split('/') let fileHandle = (await sw?.getFile(path))?.fileHandle - if(!fileHandle) fileHandle = saveMap[path] + if (!fileHandle) fileHandle = saveMap[path] if (!fileHandle) { const opts = { suggestedName: pathArr[pathArr.length - 1], @@ -308,10 +320,10 @@ editor.init( await writable.write(script) await writable.close() saveMap[path] = fileHandle - fileHandle.lastMod = Date.now()+500 + fileHandle.lastMod = Date.now() + 500 } }, - path=>sw?.getFile(path) + path => sw?.getFile(path), ) menu.init(loadExample) welcome.init() diff --git a/apps/jscad-web/online.editor.md b/apps/jscad-web/online.editor.md new file mode 100644 index 0000000..f3e6b7c --- /dev/null +++ b/apps/jscad-web/online.editor.md @@ -0,0 +1,49 @@ + +## Easy: edit online + + +## Advanced: edit in your fav code editor + +Any text or code editor will work for this (vi, vim, neovim, notepad++, VSCodium , VSCode, Atom, SublimeText...). + +- create a file `someScript.js` +- copy exmaple code form jscad.app +- save the file +- drag&drop the file to jscad.app browser window +- put your editor and jscad.app side by side +- edit the file, save the changes, jscad.app will automatically re-run the script + +A chromium based browser is required, as other browser do not allow JS to see changes to the file. +Firefox plays dumb and will indefinitely give the initial file version, even after it changes on your drive. + +## Enthusiast: a multifile project + +## Expert: project with package.json and workspaces for internal libs + + +Bonus: if you change `main` in package.json, project will be reloaded +and that file will be the new starting point for the project without you needing +D&D again. + +https://github.com/hrgdavor/jscadui/assets/2480762/63821c2d-3b2c-45cb-816a-b99f2f0e24fe + + +better option is to download a nice editor that can syntax highlight JS code +edit with the said editor (you may try VScode if it works well on your computer) +save the fil on yor drive, and keep the openjscad window open, then drag and drop the file there +it will render the script, and check for changes, so you continue editing it the text editor on your computer +as soon as you save it will pick-up the changes +... another simpler option that was made available recently on https://jscad.app/ is to copy the script text from openjscad + +paste there, and then just use CTRL+S it will aks the first time permission to save the file, and further saving will save the file, and render changes +jscad.app is not official yet, but works rather well +I personally prefer to use VSCode and drag/drop my file onto jscad.app to see it rendered there, and export in the end for 3d printing + + +any editor +drag and dropping file to openjscad or jscad.app will cause it to be given to the browser with permission to read it. +You must use Chrome for jscad to be able to check few times a second to see if it changed +Only Chrome can get change info and read new content ... + + + diff --git a/apps/jscad-web/package.json b/apps/jscad-web/package.json index e3a6db7..08af3e7 100644 --- a/apps/jscad-web/package.json +++ b/apps/jscad-web/package.json @@ -3,15 +3,16 @@ "version": "0.0.0", "type": "module", "scripts": { - "start": "node build.js --dev", + "start": "node build.js --dev --skipDocs", + "start:full": "node build.js --dev", "build": "node build.js", "serve": "node build.js --serve", "test": "ava" }, "dependencies": { "@codemirror/lang-javascript": "^6.1.9", - "@jscad/io": "2.4.4", - "@jscad/modeling": "2.12.0", + "@jscad/io": "2.4.7", + "@jscad/modeling": "2.12.1", "@jscadui/format-jscad": "*", "@jscadui/format-threejs": "*", "@jscadui/fs-provider": "*", diff --git a/apps/jscad-web/src/editor.js b/apps/jscad-web/src/editor.js index 2d09e31..3e636ba 100644 --- a/apps/jscad-web/src/editor.js +++ b/apps/jscad-web/src/editor.js @@ -70,6 +70,9 @@ export const init = (defaultCode, fn, _saveFn, _getFileFn) => { document.getElementById('editor-hint').addEventListener('click', () => { compile(view.state.doc.toString(), currentFile) }) + document.getElementById('editor-hint2').addEventListener('click', () => { + save(view.state.doc.toString(), currentFile) + }) // Setup file selector editorFile.addEventListener('click', () => { diff --git a/apps/jscad-web/src/stacktrace.js b/apps/jscad-web/src/stacktrace.js index a406701..5c4be76 100644 --- a/apps/jscad-web/src/stacktrace.js +++ b/apps/jscad-web/src/stacktrace.js @@ -7,21 +7,20 @@ */ export const formatStacktrace = (error) => { // error.stack is not standard but works on chrome and firefox - if (!error.stack) return error.toString() + const stack = error.stack + if (!stack) return error.toString() // chrome stacktrace (script error, syntax error): - // at main (eval at (eval at (require.js:24:69)), :13:3) - // at eval (eval at (require.js:24:69), :3:12) - // firefox stacktrace: - // main@http://localhost:5120/build/bundle.worker.js line 14 > eval line 1 > eval:13:3 - // @http://localhost:5120/build/bundle.worker.js line 14 > eval:1:37 - const cleaned = error.stack + // ReferenceError: gggggg is not defined + // at causeErr (./jscad.model.js:51:3) + // at main (./jscad.model.js:46:27) + // at ve (http://localhost:5120/build/bundle.worker.js:28:2964) + // at Pt (http://localhost:5120/build/bundle.worker.js:28:3731) + // at async http://localhost:5120/build/bundle.worker.js:14:3218 + const cleaned = stack .split('\n') - .filter(line => line.includes('eval')) - .map(line => line.replace(/eval at \(.*?\), /, '')) // chrome - .map(line => line.replace(/^@/, '@')) // firefox - .map(line => line.replace(/@http.*?bundle.worker.js.* > eval:/, ' ')) // firefox - .map(line => line.replace(/^\s*(at )?/, ' at ')) // indent + .filter(line => !line.includes('bundle.worker.js')) - return [error.message, ...cleaned].join('\n') -} + if(!stack.includes(error.message)) cleaned.unshift(error.message) + return cleaned.join('\n') +} \ No newline at end of file diff --git a/apps/jscad-web/src_build/esbuildUtil.js b/apps/jscad-web/src_build/esbuildUtil.js index 350cb72..0da9e69 100644 --- a/apps/jscad-web/src_build/esbuildUtil.js +++ b/apps/jscad-web/src_build/esbuildUtil.js @@ -21,10 +21,10 @@ const bundleDef = { format: 'iife', } -export const buildBundle = (outDir, bundle, {srcDir='src_bundle', ...options})=>{ +export const buildBundle = (outDir, bundle, {srcDir='src_bundle', skipExisting = true, ...options})=>{ let file = `${srcDir}/${bundle}` let outfile = `${outDir}/${bundle}` - return runEsbuild(esbuild,{...bundleDef, ...options, skipExisting: true, entryPoints:[file], outfile}) + return runEsbuild(esbuild,{...bundleDef, ...options, skipExisting, entryPoints:[file], outfile}) } export const buildOneIfNeeded = (outDir, file, options={})=>{ diff --git a/apps/jscad-web/static/index.html b/apps/jscad-web/static/index.html index 6286ac2..c47dfee 100644 --- a/apps/jscad-web/static/index.html +++ b/apps/jscad-web/static/index.html @@ -73,6 +73,7 @@

JSCAD

Shift-enter to update + CTRL+S to save and update
diff --git a/apps/jscad-web/static/main.css b/apps/jscad-web/static/main.css index 08cc7c8..9f424f5 100644 --- a/apps/jscad-web/static/main.css +++ b/apps/jscad-web/static/main.css @@ -190,7 +190,7 @@ p { height: calc(100vh - 60px); flex: 1; } -#editor-hint { +#editor-hint, #editor-hint2 { position: absolute; bottom: 10px; right: calc(min(10px, 100% - 180px)); /* push right when editor is small */ @@ -199,6 +199,9 @@ p { font-size: 10pt; user-select: none; } +#editor-hint { + bottom: 30px; +} .dark #editor-hint { color: #dddddd88; } diff --git a/apps/jscad-web/tsconfig.json b/apps/jscad-web/tsconfig.json index 9e23180..9c45611 100644 --- a/apps/jscad-web/tsconfig.json +++ b/apps/jscad-web/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "allowJs": true, + "skipLibCheck": true, "outDir": "build_dev", "moduleResolution": "node16" }, diff --git a/apps/model-page/main.js b/apps/model-page/main.js index 30533b5..8706ff5 100644 --- a/apps/model-page/main.js +++ b/apps/model-page/main.js @@ -111,7 +111,7 @@ function save(blob, filename) { } function exportModel(format) { - sendCmd('exportData', { format }).then(({ data }) => { + sendCmd('exportData', [{ format }]).then(({ data }) => { console.log('save', fileToRun + '.stl', data) save(new Blob([data], { type: 'text/plain' }), fileToRun + '.stl') }) @@ -123,12 +123,12 @@ const { sendCmd, sendNotify } = initMessaging(worker, handlers) const paramChangeCallback = params => { console.log('params', params) - sendCmd('runMain', { params }) + sendCmd('runMain', [{ params }]) } export const runScript = file => { fileToRun = file.replace(/.*\//, '').replace(/\..*/, '') - sendCmd('runScript', { url: file }).then(result => { + sendCmd('runScript', [{ url: file }]).then(result => { console.log('result', result) genParams({ target: byId('paramsDiv'), params: result.def || {}, callback: paramChangeCallback }) }) @@ -141,5 +141,5 @@ export const initEngine = async (THREE, elem, workerOptions) => { updateFromCtrl(ctrl) setTheme(theme) - await sendCmd('init', workerOptions) + await sendCmd('init', [workerOptions]) } diff --git a/apps/vue3-jscad/package.json b/apps/vue3-jscad/package.json index b881156..c0a83a1 100644 --- a/apps/vue3-jscad/package.json +++ b/apps/vue3-jscad/package.json @@ -13,7 +13,7 @@ "@jscadui/html-gizmo": "^0.1.0", "@jscadui/orbit": "^0.1.0", "@jscadui/params": "^0.1.0", - "@jscadui/postmessage": "^0.1.0", + "@jscadui/postmessage": "^0.2.0", "vue": "^3.3.11" }, "devDependencies": { diff --git a/file-format/3mf-export/README.md b/file-format/3mf-export/README.md index 18a4b3b..95369be 100644 --- a/file-format/3mf-export/README.md +++ b/file-format/3mf-export/README.md @@ -1,4 +1,5 @@ # 3mf export MVP +[![npm version](https://badge.fury.io/js/@jscadui%2F3mf-export.svg)](https://www.npmjs.com/package/@jscadui%2F3mf-export) This is a set of functions to generate 3mf content for exporting 3d models and optionally embeding a thumbnail. diff --git a/notes.md b/notes.md index 0a07ee3..1d9a5f7 100644 --- a/notes.md +++ b/notes.md @@ -1,3 +1,9 @@ +# refresh badge after publishing + +```sh +curl -X PURGE BADGE_URL +``` + # libs source for easier integrationd with IDE Make `index.js` that exports all from `src` to have errors in console or `console.log` display fine lanem taht is not `index.js`. In case where I had multiple libs separated, and because they are simple, code was directly in `index.js`. This caused visibility issues because many trace lines had `index.js` as source and then you need to look closely at full path to find out what lib is causing it. diff --git a/package-lock.json b/package-lock.json index 93cb891..f89684e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -155,8 +155,8 @@ "version": "0.0.0", "dependencies": { "@codemirror/lang-javascript": "^6.1.9", - "@jscad/io": "2.4.4", - "@jscad/modeling": "2.12.0", + "@jscad/io": "2.4.7", + "@jscad/modeling": "2.12.1", "@jscadui/format-jscad": "*", "@jscadui/format-threejs": "*", "@jscadui/fs-provider": "*", @@ -169,7 +169,6 @@ "@jscadui/scene": "*", "@jscadui/transform-babel": "*", "@jscadui/worker": "*", - "base64-arraybuffer": "^1.0.2", "codemirror": "^6.0.1", "fflate": "0.8.1", "gl-matrix": "^3.4.0", @@ -534,9 +533,206 @@ "node": ">=12" } }, + "apps/jscad-web/node_modules/@jscad/3mf-serializer": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@jscad/3mf-serializer/-/3mf-serializer-2.1.10.tgz", + "integrity": "sha512-jVmTQYi8xQarsrbZ66jlxcioNQGJbp9T9QQLTgfHC/XshaGuzGB3nOb4ELb+bGvNXljTYd0Eh0w0rPVAA3YaoQ==", + "dependencies": { + "@jscad/array-utils": "2.1.4", + "@jscad/modeling": "2.12.1", + "fflate": "0.7.3", + "onml": "1.2.0" + } + }, + "apps/jscad-web/node_modules/@jscad/3mf-serializer/node_modules/fflate": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.3.tgz", + "integrity": "sha512-0Zz1jOzJWERhyhsimS54VTqOteCNwRtIlh8isdL0AXLo0g7xNTfTL7oWrkmCnPhZGocKIkWHBistBrrpoNH3aw==" + }, + "apps/jscad-web/node_modules/@jscad/amf-deserializer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/@jscad/amf-deserializer/-/amf-deserializer-2.3.6.tgz", + "integrity": "sha512-V+Q/Kr1ga2oe+9fBY7k+MyUWUTX3JZccQKZMaxapEoySBy6VSar+hfaBWBb2SXmNKnvL/he8MGJGnpbcykLZ+g==", + "dependencies": { + "@jscad/modeling": "2.12.1", + "saxes": "5.0.1" + } + }, + "apps/jscad-web/node_modules/@jscad/amf-serializer": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@jscad/amf-serializer/-/amf-serializer-2.1.16.tgz", + "integrity": "sha512-+0aNeZUGKF0AsQb3v0Kdivl3Ln21cFlkyzoHVC1QF2f3Dg0TTNy89n63nrGCOS3Ly/2DPqfLJim26jXgyB+8/Q==", + "dependencies": { + "@jscad/array-utils": "2.1.4", + "@jscad/modeling": "2.12.1", + "onml": "1.3.0" + } + }, + "apps/jscad-web/node_modules/@jscad/amf-serializer/node_modules/onml": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/onml/-/onml-1.3.0.tgz", + "integrity": "sha512-RhGUsC6Im2A5vAdIvxE3auRKTqrqUZQl/AYLn8+9lM3SO4da5bwhcI5TcM+hfQxNCSLLOVErsl9p0ZPjKKmz+g==", + "dependencies": { + "sax": "^1.2.1" + } + }, + "apps/jscad-web/node_modules/@jscad/dxf-deserializer": { + "version": "2.3.23", + "resolved": "https://registry.npmjs.org/@jscad/dxf-deserializer/-/dxf-deserializer-2.3.23.tgz", + "integrity": "sha512-veUS4mxkWAli5UihzAoz1rDuunoc5ff6SjmdKEtiYfyK25Hp2NcQ5EFedo/DE8SjCNIyEkIaJULfd3jC+XnckA==", + "dependencies": { + "@jscad/modeling": "2.12.1" + } + }, + "apps/jscad-web/node_modules/@jscad/dxf-serializer": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@jscad/dxf-serializer/-/dxf-serializer-2.1.16.tgz", + "integrity": "sha512-zS1E1vd/4i7S1ZG4vwZzlXr4qseMVnhnb6IzwgDJFth8a9Z/W2+VXR0TLLhYPYAcRKGHkGsCVpGcLHjigWFkDA==", + "dependencies": { + "@jscad/array-utils": "2.1.4", + "@jscad/modeling": "2.12.1" + } + }, + "apps/jscad-web/node_modules/@jscad/io": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@jscad/io/-/io-2.4.7.tgz", + "integrity": "sha512-ikt79RsjhD7Odm7pS5zS0ez7KF/QiDAVzsX1Wm65NOMlN/NEtDnVmEQCxtoOS5h+iZkmmtVsfaJYDOCvlbwxxQ==", + "dependencies": { + "@jscad/3mf-serializer": "2.1.10", + "@jscad/amf-deserializer": "2.3.6", + "@jscad/amf-serializer": "2.1.16", + "@jscad/array-utils": "2.1.4", + "@jscad/dxf-deserializer": "2.3.23", + "@jscad/dxf-serializer": "2.1.16", + "@jscad/io-utils": "2.0.26", + "@jscad/json-deserializer": "2.0.27", + "@jscad/json-serializer": "2.0.26", + "@jscad/modeling": "2.12.1", + "@jscad/obj-deserializer": "2.0.26", + "@jscad/obj-serializer": "2.1.16", + "@jscad/stl-deserializer": "2.1.23", + "@jscad/stl-serializer": "2.1.16", + "@jscad/svg-deserializer": "2.5.7", + "@jscad/svg-serializer": "2.3.14", + "@jscad/x3d-deserializer": "2.2.6", + "@jscad/x3d-serializer": "2.4.6" + } + }, + "apps/jscad-web/node_modules/@jscad/io-utils": { + "version": "2.0.26", + "resolved": "https://registry.npmjs.org/@jscad/io-utils/-/io-utils-2.0.26.tgz", + "integrity": "sha512-I4Egbw7c5yAZFJpaTDdTh9harNLghyU7R3a0s/4v2f2Rv5bZg6GsAFvPjFHl8e3gTZA2qdKhR2X/DTjN38em3g==" + }, + "apps/jscad-web/node_modules/@jscad/json-deserializer": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/@jscad/json-deserializer/-/json-deserializer-2.0.27.tgz", + "integrity": "sha512-tZuP1O5QiGtdZU0RlG/08Ftbf6OIp/8gTe+xkbzeq7gcd+WpWLcf19Fv9ka0lQKkUz7W9VSnNprP6PJqzA0LFg==", + "dependencies": { + "@jscad/array-utils": "2.1.4" + } + }, + "apps/jscad-web/node_modules/@jscad/json-serializer": { + "version": "2.0.26", + "resolved": "https://registry.npmjs.org/@jscad/json-serializer/-/json-serializer-2.0.26.tgz", + "integrity": "sha512-PoXg4nkiwvwDAZsAgzWK1VWe+tVZNBcf97H9EAskE882rQuW+ad7gDaBis3TLm78jjcPE+jgnTboifdEGR5HdQ==", + "dependencies": { + "@jscad/modeling": "2.12.1" + } + }, "apps/jscad-web/node_modules/@jscad/modeling": { - "version": "2.12.0", - "license": "MIT" + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/@jscad/modeling/-/modeling-2.12.1.tgz", + "integrity": "sha512-8DOOXmrMuzHgoCXkuBodBidY8K1NNRFQCsHuAiOVyZWoHql45+PG5KQYgZUj3MF2HmtsFD16pZOcMVsJjglhcQ==" + }, + "apps/jscad-web/node_modules/@jscad/obj-deserializer": { + "version": "2.0.26", + "resolved": "https://registry.npmjs.org/@jscad/obj-deserializer/-/obj-deserializer-2.0.26.tgz", + "integrity": "sha512-CORWS9spR3UT/cYxakVLdEGwl+CV5wEroZbgyJuOqQMpeDKJKnI8uRWMalyy/9jjdKMFt/DNG8cNKGZ/mN3SMw==", + "dependencies": { + "@jscad/modeling": "2.12.1" + } + }, + "apps/jscad-web/node_modules/@jscad/obj-serializer": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@jscad/obj-serializer/-/obj-serializer-2.1.16.tgz", + "integrity": "sha512-gRTTB2NY0S/99P83BhMj8SknAIPbhSUivoNuvdNKqR4PM6lp/6PFYYteutpQdmdIVbkRcSbZNQgQW0ZIqRue3w==", + "dependencies": { + "@jscad/array-utils": "2.1.4", + "@jscad/modeling": "2.12.1" + } + }, + "apps/jscad-web/node_modules/@jscad/stl-deserializer": { + "version": "2.1.23", + "resolved": "https://registry.npmjs.org/@jscad/stl-deserializer/-/stl-deserializer-2.1.23.tgz", + "integrity": "sha512-Ttg3r78wgE+fi9PMa1Jil57v++B2ztpuB/PrkyU2x25FrgJyrXgyG5wtILN7dYEO81/ylR26TKVrYyMPofrWHA==", + "dependencies": { + "@jscad/io-utils": "2.0.26", + "@jscad/modeling": "2.12.1" + } + }, + "apps/jscad-web/node_modules/@jscad/stl-serializer": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@jscad/stl-serializer/-/stl-serializer-2.1.16.tgz", + "integrity": "sha512-d2GYMwHyfX7i5M3WRvfzF3IdQMrP9FYIKMi0Xa2GPysGGWHT3+cA2+9paAPOMzUNFDwWUObSV7Jr9mYDTHiKow==", + "dependencies": { + "@jscad/array-utils": "2.1.4", + "@jscad/modeling": "2.12.1" + } + }, + "apps/jscad-web/node_modules/@jscad/svg-deserializer": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/@jscad/svg-deserializer/-/svg-deserializer-2.5.7.tgz", + "integrity": "sha512-CbECgTkvjV6mwK94m8T5QE1D7Qz4tXHUNNL0WO3N30t842SrJkkJWKgBKhSdA0xru3URQ70f0x05vZ2yT+iEOQ==", + "dependencies": { + "@jscad/array-utils": "2.1.4", + "@jscad/modeling": "2.12.1", + "saxes": "5.0.1" + } + }, + "apps/jscad-web/node_modules/@jscad/svg-serializer": { + "version": "2.3.14", + "resolved": "https://registry.npmjs.org/@jscad/svg-serializer/-/svg-serializer-2.3.14.tgz", + "integrity": "sha512-A2OsmaVunYQ4RnPbpqOPzT9aD88d7MlxhoGSJ981TNdyDEOs3O6Tpu6gdank66JPJ0dmSVEtDiHxdKPTN+Tneg==", + "dependencies": { + "@jscad/modeling": "2.12.1", + "onml": "1.3.0" + } + }, + "apps/jscad-web/node_modules/@jscad/svg-serializer/node_modules/onml": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/onml/-/onml-1.3.0.tgz", + "integrity": "sha512-RhGUsC6Im2A5vAdIvxE3auRKTqrqUZQl/AYLn8+9lM3SO4da5bwhcI5TcM+hfQxNCSLLOVErsl9p0ZPjKKmz+g==", + "dependencies": { + "sax": "^1.2.1" + } + }, + "apps/jscad-web/node_modules/@jscad/x3d-deserializer": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@jscad/x3d-deserializer/-/x3d-deserializer-2.2.6.tgz", + "integrity": "sha512-6emVP+AEW+BoIyEdPtklfzDM54pJCmzdV+D8WpL7GkxERmmZemsK1sqefRaFCa5gYAHum2AcNFrRndKrPwqPyQ==", + "dependencies": { + "@jscad/array-utils": "2.1.4", + "@jscad/modeling": "2.12.1", + "saxes": "5.0.1" + } + }, + "apps/jscad-web/node_modules/@jscad/x3d-serializer": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@jscad/x3d-serializer/-/x3d-serializer-2.4.6.tgz", + "integrity": "sha512-6Kjf25c+sQEOR4Aq6Uo0PBET2s+AdVhTnCXppMWwD7ABIDycExKTVeHII80R9/PY6cPmMsmaooAQ1lPZMoObPw==", + "dependencies": { + "@jscad/array-utils": "2.1.4", + "@jscad/modeling": "2.12.1", + "onml": "1.3.0" + } + }, + "apps/jscad-web/node_modules/@jscad/x3d-serializer/node_modules/onml": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/onml/-/onml-1.3.0.tgz", + "integrity": "sha512-RhGUsC6Im2A5vAdIvxE3auRKTqrqUZQl/AYLn8+9lM3SO4da5bwhcI5TcM+hfQxNCSLLOVErsl9p0ZPjKKmz+g==", + "dependencies": { + "sax": "^1.2.1" + } }, "apps/jscad-web/node_modules/esbuild": { "version": "0.17.19", @@ -651,7 +847,7 @@ "@jscadui/html-gizmo": "^0.1.0", "@jscadui/orbit": "^0.1.0", "@jscadui/params": "^0.1.0", - "@jscadui/postmessage": "^0.1.0", + "@jscadui/postmessage": "^0.2.0", "vue": "^3.3.11" }, "devDependencies": { @@ -4379,14 +4575,6 @@ "node": ">=0.10.0" } }, - "node_modules/base64-arraybuffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", - "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", - "engines": { - "node": ">= 0.6.0" - } - }, "node_modules/basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", @@ -15348,7 +15536,7 @@ }, "packages/postmessage": { "name": "@jscadui/postmessage", - "version": "0.1.0", + "version": "0.2.0", "license": "MIT", "devDependencies": { "@trivago/prettier-plugin-sort-imports": "~3.3.0", @@ -20237,8 +20425,8 @@ "version": "file:apps/jscad-web", "requires": { "@codemirror/lang-javascript": "^6.1.9", - "@jscad/io": "2.4.4", - "@jscad/modeling": "2.12.0", + "@jscad/io": "2.4.7", + "@jscad/modeling": "2.12.1", "@jscadui/format-jscad": "*", "@jscadui/format-threejs": "*", "@jscadui/fs-provider": "*", @@ -20253,7 +20441,6 @@ "@jscadui/worker": "*", "@jsx6/build": "0.2.0", "@trivago/prettier-plugin-sort-imports": "~3.3.0", - "base64-arraybuffer": "^1.0.2", "codemirror": "^6.0.1", "esbuild": "^0.17.11", "fflate": "0.8.1", @@ -20415,8 +20602,214 @@ "dev": true, "optional": true }, + "@jscad/3mf-serializer": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@jscad/3mf-serializer/-/3mf-serializer-2.1.10.tgz", + "integrity": "sha512-jVmTQYi8xQarsrbZ66jlxcioNQGJbp9T9QQLTgfHC/XshaGuzGB3nOb4ELb+bGvNXljTYd0Eh0w0rPVAA3YaoQ==", + "requires": { + "@jscad/array-utils": "2.1.4", + "@jscad/modeling": "2.12.1", + "fflate": "0.7.3", + "onml": "1.2.0" + }, + "dependencies": { + "fflate": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.3.tgz", + "integrity": "sha512-0Zz1jOzJWERhyhsimS54VTqOteCNwRtIlh8isdL0AXLo0g7xNTfTL7oWrkmCnPhZGocKIkWHBistBrrpoNH3aw==" + } + } + }, + "@jscad/amf-deserializer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/@jscad/amf-deserializer/-/amf-deserializer-2.3.6.tgz", + "integrity": "sha512-V+Q/Kr1ga2oe+9fBY7k+MyUWUTX3JZccQKZMaxapEoySBy6VSar+hfaBWBb2SXmNKnvL/he8MGJGnpbcykLZ+g==", + "requires": { + "@jscad/modeling": "2.12.1", + "saxes": "5.0.1" + } + }, + "@jscad/amf-serializer": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@jscad/amf-serializer/-/amf-serializer-2.1.16.tgz", + "integrity": "sha512-+0aNeZUGKF0AsQb3v0Kdivl3Ln21cFlkyzoHVC1QF2f3Dg0TTNy89n63nrGCOS3Ly/2DPqfLJim26jXgyB+8/Q==", + "requires": { + "@jscad/array-utils": "2.1.4", + "@jscad/modeling": "2.12.1", + "onml": "1.3.0" + }, + "dependencies": { + "onml": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/onml/-/onml-1.3.0.tgz", + "integrity": "sha512-RhGUsC6Im2A5vAdIvxE3auRKTqrqUZQl/AYLn8+9lM3SO4da5bwhcI5TcM+hfQxNCSLLOVErsl9p0ZPjKKmz+g==", + "requires": { + "sax": "^1.2.1" + } + } + } + }, + "@jscad/dxf-deserializer": { + "version": "2.3.23", + "resolved": "https://registry.npmjs.org/@jscad/dxf-deserializer/-/dxf-deserializer-2.3.23.tgz", + "integrity": "sha512-veUS4mxkWAli5UihzAoz1rDuunoc5ff6SjmdKEtiYfyK25Hp2NcQ5EFedo/DE8SjCNIyEkIaJULfd3jC+XnckA==", + "requires": { + "@jscad/modeling": "2.12.1" + } + }, + "@jscad/dxf-serializer": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@jscad/dxf-serializer/-/dxf-serializer-2.1.16.tgz", + "integrity": "sha512-zS1E1vd/4i7S1ZG4vwZzlXr4qseMVnhnb6IzwgDJFth8a9Z/W2+VXR0TLLhYPYAcRKGHkGsCVpGcLHjigWFkDA==", + "requires": { + "@jscad/array-utils": "2.1.4", + "@jscad/modeling": "2.12.1" + } + }, + "@jscad/io": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@jscad/io/-/io-2.4.7.tgz", + "integrity": "sha512-ikt79RsjhD7Odm7pS5zS0ez7KF/QiDAVzsX1Wm65NOMlN/NEtDnVmEQCxtoOS5h+iZkmmtVsfaJYDOCvlbwxxQ==", + "requires": { + "@jscad/3mf-serializer": "2.1.10", + "@jscad/amf-deserializer": "2.3.6", + "@jscad/amf-serializer": "2.1.16", + "@jscad/array-utils": "2.1.4", + "@jscad/dxf-deserializer": "2.3.23", + "@jscad/dxf-serializer": "2.1.16", + "@jscad/io-utils": "2.0.26", + "@jscad/json-deserializer": "2.0.27", + "@jscad/json-serializer": "2.0.26", + "@jscad/modeling": "2.12.1", + "@jscad/obj-deserializer": "2.0.26", + "@jscad/obj-serializer": "2.1.16", + "@jscad/stl-deserializer": "2.1.23", + "@jscad/stl-serializer": "2.1.16", + "@jscad/svg-deserializer": "2.5.7", + "@jscad/svg-serializer": "2.3.14", + "@jscad/x3d-deserializer": "2.2.6", + "@jscad/x3d-serializer": "2.4.6" + } + }, + "@jscad/io-utils": { + "version": "2.0.26", + "resolved": "https://registry.npmjs.org/@jscad/io-utils/-/io-utils-2.0.26.tgz", + "integrity": "sha512-I4Egbw7c5yAZFJpaTDdTh9harNLghyU7R3a0s/4v2f2Rv5bZg6GsAFvPjFHl8e3gTZA2qdKhR2X/DTjN38em3g==" + }, + "@jscad/json-deserializer": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/@jscad/json-deserializer/-/json-deserializer-2.0.27.tgz", + "integrity": "sha512-tZuP1O5QiGtdZU0RlG/08Ftbf6OIp/8gTe+xkbzeq7gcd+WpWLcf19Fv9ka0lQKkUz7W9VSnNprP6PJqzA0LFg==", + "requires": { + "@jscad/array-utils": "2.1.4" + } + }, + "@jscad/json-serializer": { + "version": "2.0.26", + "resolved": "https://registry.npmjs.org/@jscad/json-serializer/-/json-serializer-2.0.26.tgz", + "integrity": "sha512-PoXg4nkiwvwDAZsAgzWK1VWe+tVZNBcf97H9EAskE882rQuW+ad7gDaBis3TLm78jjcPE+jgnTboifdEGR5HdQ==", + "requires": { + "@jscad/modeling": "2.12.1" + } + }, "@jscad/modeling": { - "version": "2.12.0" + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/@jscad/modeling/-/modeling-2.12.1.tgz", + "integrity": "sha512-8DOOXmrMuzHgoCXkuBodBidY8K1NNRFQCsHuAiOVyZWoHql45+PG5KQYgZUj3MF2HmtsFD16pZOcMVsJjglhcQ==" + }, + "@jscad/obj-deserializer": { + "version": "2.0.26", + "resolved": "https://registry.npmjs.org/@jscad/obj-deserializer/-/obj-deserializer-2.0.26.tgz", + "integrity": "sha512-CORWS9spR3UT/cYxakVLdEGwl+CV5wEroZbgyJuOqQMpeDKJKnI8uRWMalyy/9jjdKMFt/DNG8cNKGZ/mN3SMw==", + "requires": { + "@jscad/modeling": "2.12.1" + } + }, + "@jscad/obj-serializer": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@jscad/obj-serializer/-/obj-serializer-2.1.16.tgz", + "integrity": "sha512-gRTTB2NY0S/99P83BhMj8SknAIPbhSUivoNuvdNKqR4PM6lp/6PFYYteutpQdmdIVbkRcSbZNQgQW0ZIqRue3w==", + "requires": { + "@jscad/array-utils": "2.1.4", + "@jscad/modeling": "2.12.1" + } + }, + "@jscad/stl-deserializer": { + "version": "2.1.23", + "resolved": "https://registry.npmjs.org/@jscad/stl-deserializer/-/stl-deserializer-2.1.23.tgz", + "integrity": "sha512-Ttg3r78wgE+fi9PMa1Jil57v++B2ztpuB/PrkyU2x25FrgJyrXgyG5wtILN7dYEO81/ylR26TKVrYyMPofrWHA==", + "requires": { + "@jscad/io-utils": "2.0.26", + "@jscad/modeling": "2.12.1" + } + }, + "@jscad/stl-serializer": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@jscad/stl-serializer/-/stl-serializer-2.1.16.tgz", + "integrity": "sha512-d2GYMwHyfX7i5M3WRvfzF3IdQMrP9FYIKMi0Xa2GPysGGWHT3+cA2+9paAPOMzUNFDwWUObSV7Jr9mYDTHiKow==", + "requires": { + "@jscad/array-utils": "2.1.4", + "@jscad/modeling": "2.12.1" + } + }, + "@jscad/svg-deserializer": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/@jscad/svg-deserializer/-/svg-deserializer-2.5.7.tgz", + "integrity": "sha512-CbECgTkvjV6mwK94m8T5QE1D7Qz4tXHUNNL0WO3N30t842SrJkkJWKgBKhSdA0xru3URQ70f0x05vZ2yT+iEOQ==", + "requires": { + "@jscad/array-utils": "2.1.4", + "@jscad/modeling": "2.12.1", + "saxes": "5.0.1" + } + }, + "@jscad/svg-serializer": { + "version": "2.3.14", + "resolved": "https://registry.npmjs.org/@jscad/svg-serializer/-/svg-serializer-2.3.14.tgz", + "integrity": "sha512-A2OsmaVunYQ4RnPbpqOPzT9aD88d7MlxhoGSJ981TNdyDEOs3O6Tpu6gdank66JPJ0dmSVEtDiHxdKPTN+Tneg==", + "requires": { + "@jscad/modeling": "2.12.1", + "onml": "1.3.0" + }, + "dependencies": { + "onml": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/onml/-/onml-1.3.0.tgz", + "integrity": "sha512-RhGUsC6Im2A5vAdIvxE3auRKTqrqUZQl/AYLn8+9lM3SO4da5bwhcI5TcM+hfQxNCSLLOVErsl9p0ZPjKKmz+g==", + "requires": { + "sax": "^1.2.1" + } + } + } + }, + "@jscad/x3d-deserializer": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@jscad/x3d-deserializer/-/x3d-deserializer-2.2.6.tgz", + "integrity": "sha512-6emVP+AEW+BoIyEdPtklfzDM54pJCmzdV+D8WpL7GkxERmmZemsK1sqefRaFCa5gYAHum2AcNFrRndKrPwqPyQ==", + "requires": { + "@jscad/array-utils": "2.1.4", + "@jscad/modeling": "2.12.1", + "saxes": "5.0.1" + } + }, + "@jscad/x3d-serializer": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@jscad/x3d-serializer/-/x3d-serializer-2.4.6.tgz", + "integrity": "sha512-6Kjf25c+sQEOR4Aq6Uo0PBET2s+AdVhTnCXppMWwD7ABIDycExKTVeHII80R9/PY6cPmMsmaooAQ1lPZMoObPw==", + "requires": { + "@jscad/array-utils": "2.1.4", + "@jscad/modeling": "2.12.1", + "onml": "1.3.0" + }, + "dependencies": { + "onml": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/onml/-/onml-1.3.0.tgz", + "integrity": "sha512-RhGUsC6Im2A5vAdIvxE3auRKTqrqUZQl/AYLn8+9lM3SO4da5bwhcI5TcM+hfQxNCSLLOVErsl9p0ZPjKKmz+g==", + "requires": { + "sax": "^1.2.1" + } + } + } }, "esbuild": { "version": "0.17.19", @@ -22876,11 +23269,6 @@ } } }, - "base64-arraybuffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", - "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==" - }, "basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", @@ -27777,7 +28165,7 @@ "@jscadui/html-gizmo": "^0.1.0", "@jscadui/orbit": "^0.1.0", "@jscadui/params": "^0.1.0", - "@jscadui/postmessage": "^0.1.0", + "@jscadui/postmessage": "^0.2.0", "@vitejs/plugin-vue": "^4.5.2", "sass": "^1.69.6", "vite": "^5.0.10", diff --git a/packages/fs-provider/fs-provider.js b/packages/fs-provider/fs-provider.js index 296a1c2..0592e77 100644 --- a/packages/fs-provider/fs-provider.js +++ b/packages/fs-provider/fs-provider.js @@ -225,13 +225,12 @@ export const checkFiles = sw => { export async function fileDropped(sw, files) { sw.filesToCheck.length = 0 sw.fileToRun = 'index.js' - let folderName clearFs(sw) let rootFiles = [] if (files.length === 1) { const file = files[0] if (file.isDirectory) { - folderName = file.name + sw.folderName = file.name file.fsDir = '/' rootFiles = await readDir(file) } else { @@ -243,16 +242,17 @@ export async function fileDropped(sw, files) { } rootFiles = rootFiles.map(e => fileToFsEntry(e, '/')) sw.roots.push(rootFiles) +} +export async function analyzeProject(sw){ const alias = await getWorkspaceAliases(sw) - let time = Date.now() const preLoad = ['/' + sw.fileToRun, '/package.json'] const loaded = await addPreLoadAll(sw, preLoad, true) sw.projectName = sw.defProjectName if (sw.fileToRun !== 'index.js') sw.projectName = sw.fileToRun.replace(/\.js$/, '') - if (folderName) sw.projectName = folderName + if (sw.folderName) sw.projectName = sw.folderName let script = '' if (sw.fileToRun) { @@ -266,6 +266,7 @@ export async function fileDropped(sw, files) { } } return { alias, script } + } /** @@ -280,6 +281,7 @@ const getWorkspaceAliases = async sw => { let pkgFile = await findFileInRoots(sw.roots, 'package.json') if (pkgFile) { try { + sw.filesToCheck.push(pkgFile) const pack = JSON.parse(await readAsText(pkgFile)) if (pack.main) sw.fileToRun = pack.main if (pack.workspaces) diff --git a/packages/fs-serviceworker/fs-serviceworker.js b/packages/fs-serviceworker/fs-serviceworker.js index 8637a54..100a792 100644 --- a/packages/fs-serviceworker/fs-serviceworker.js +++ b/packages/fs-serviceworker/fs-serviceworker.js @@ -63,7 +63,7 @@ self.addEventListener('fetch', async event => { return (done = true) } - let resp = await clientWrapper.sendCmd('getFile', { path: path }) + let resp = await clientWrapper.sendCmd('getFile', [{ path: path }]) rCached = await clientWrapper.cache.match(fileReq) done = true resolve(rCached || new Response(path + ' not in cache', { status: rCached ? 200 : 404 })) diff --git a/packages/html-gizmo/README.md b/packages/html-gizmo/README.md index 8bf3709..905467b 100644 --- a/packages/html-gizmo/README.md +++ b/packages/html-gizmo/README.md @@ -1,5 +1,7 @@ # Cube camera gizmo Web Component +[![npm version](https://badge.fury.io/js/@jscadui%2Fhtml-gizmo.svg)](https://www.npmjs.com/package/@jscadui%2Fhtml-gizmo) + | ![gizmo in action](docs/gizmo.gif) | Check the demo at: https://hrgdavor.github.io/jscadui/html-gizmo/ | | ---- | ---- | diff --git a/packages/postmessage/README.md b/packages/postmessage/README.md index beb0925..ddc701e 100644 --- a/packages/postmessage/README.md +++ b/packages/postmessage/README.md @@ -1,15 +1,135 @@ # postMessage utility +[![npm version](https://badge.fury.io/js/@jscadui%2Fpostmessage.svg)](https://www.npmjs.com/package/@jscadui%2Fpostmessage) -Allows for simpler usage of [postMessage](https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage) by defining a kind of RPC protocol that can send notifications or call methods. Calling methods is handled with Promises because the postMessage is async by definition. +Allows for simpler usage of [postMessage](https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage) by defining a RPC protocol that can send notifications or call methods. +Calling methods is handled with Promises because the postMessage is async by definition. -If you use this utility in your main thread and in the worker, it will (in my opinion) simplify +If you use this utility both in your main thread and in the worker you will get the most benefits. -## transferable +Consider these steps depending on the complexity: + +1. **postMessage**: if you have only few messages pushing some data, you do not even need this +2. **RPC:** if you have messages, and some of them are response to a request(functionally) you should try formalizing a protocol and this RPC here can be a good starting point +3. **Proxy+methods:** as soon as you are considering RPC, I would recommend going the step further and formalize the communication with documented method names and parameter types (TS or JSDoc) and use the Proxy object to have nice code hints in your IDE. + +# RPC protocol + +The RPC protocol is inspired by JSONRPC, as I personally found it useful in multiple projects. + +**Request:** Structure the top level object like this: + + - `method` - name of the method called + - `params` - array of parameters + - `id` - id of the request. Optional, not used for one-way notifications. Used to match response with the original request. + +**Response:** Structure the top level object like this: + + - `response` - value returned by the method + - `error` - `{code,message,stack}` if method fails + - `id` - id of the request to match this response to + +# Proxy object and method definitions + +With modern JavaScript we can go few steps further and make communication with worker be as simple +as calling methods(all of them async ofc.). + +If you create a class that handles incomming rpc calls, NOTICE THAT unless all of your methods are actually async, you can not just use the declaration +of your class for the side that is takling to your class via postMessage. You must create an interface that has all the methods declared async. + +Here is partial sample of jsdoc definitions for jscad worker +```ts +/** +@typedef InitOptions +@prop {String} baseURI - to resolve inital relative path +@prop {Array} alias - +@prop {Array} bundles - bundle alias {name:path} + +@typedef RunScriptOptions +@prop {string} script - script source +@prop {string} url - script url/name +@prop {string} base - base url + +@typedef ScriptResponse +@prop {Array} entities +@prop {number} mainTime - script run time +@prop {number} convertTime - tim converting script output to gl data + +@typedef JscadWorker +@prop {String} name +@prop {(options:InitOptions)=>Promise} init +@prop {(options:RunScriptOptions)=>Promise} runScript +*/ +``` -It is simple to send transferable objects when sending a message to the worker by adding a parameter. +For me, TypeScript is nicer to write declarations, but I still prefer [JSDoc](https://alexharri.com/blog/jsdoc-as-an-alternative-typescript-syntax) as it requires less tooling and also works in IDE. -It is however more tricky to support transferable for return values without complicating simple use -cases that do not need transferable. +You can do it either way you prefer, so here is also typescript version of the sample: + +```ts +export interface InitOptions { + /** to resolve inital relative path */ + baseURI:String, + alias: Array, + /** bundle alias {name:path} */ + bundles: Array>, +} + +export interface RunScriptOptions { + /** script source */ + script: string, + /** script url/name */ + url: string, + /** base url */ + base: string, +} + +export interface ScriptResponse { + entities: Array, + /** script run time */ + mainTime: number, +} + +export interface JscadWorker { + async init(options:InitOptions):void, + async init(options:RunScriptOptions):ScriptResponse, +} +``` + +Manuall calling `init` worker method documented above, requires sending +`worker.postMessage({method:'init', params:[{bundles}]})` just to trigger the method. +Then you would also need to handle response, and wrap it all into a promise + +If you do not want to be fancy with typed code you can just use `initMessaging` and call worker methods using `sendCmd`. + +```js +const {sendCmd } = initMessaging(worker, handlers, { onJobCount: trackJobs }) +await sendCmd('init',[{ bundles }]) +const result = await sendCmd('runScript', [{ script}]) +// IDE does not know type of the result +``` + +You can then import and use the definition + +```ts +/** @typedef {import('@jscadui/worker').JscadWorker} JscadWorker*/ + +/** @type {JscadWorker} */ +const workerApi = messageProxy(worker, handlers, { onJobCount: trackJobs }) +await workerApi.init({ bundles }) +const result = await workerApi.runScript({ script}) +// result is now known to be ScriptResponse and you get autocomplete +``` + +## transferable + +It is simple to send transferable objects when sending a message to the worker by adding a parameter to `postMessage`. +It is however more tricky to support transferable for return values without complicating simple use cases that do not need transferable. If you have a method that can be called and it needs to return transferable then you must use object as a return value. -When returning such object, include `__transferable` key in the return value. It will not be in the data at the receiving end but will be taken out and passed to postMessage as a transferable parameter. +When returning such object in call to `withTransferable` before returning the value. +It will not be in the data at the receiving end, but will be taken out and passed to postMessage as the transferable parameter. + +Exmaple from jscad worker +```js + return withTransferable({ entities, mainTime }, transferable) +``` diff --git a/packages/postmessage/cjs/index.js b/packages/postmessage/cjs/index.js new file mode 100644 index 0000000..17fc127 --- /dev/null +++ b/packages/postmessage/cjs/index.js @@ -0,0 +1,2 @@ +var w=Object.defineProperty;var p=Object.getOwnPropertyDescriptor;var N=Object.getOwnPropertyNames;var P=Object.prototype.hasOwnProperty;var k=(e,t)=>{for(var d in t)w(e,d,{get:t[d],enumerable:!0})},T=(e,t,d,f)=>{if(t&&typeof t=="object"||typeof t=="function")for(let a of N(t))!P.call(e,a)&&a!==d&&w(e,a,{get:()=>t[a],enumerable:!(f=p(t,a))||f.enumerable});return e};var q=e=>T(w({},"__esModule",{value:!0}),e);var A={};k(A,{initMessaging:()=>_,messageProxy:()=>C,withTransferable:()=>z});module.exports=q(A);var v=1,m=new Map,E="__RESPONSE__",x=Symbol.for("__transferable__"),z=(e,t)=>(e[x]=t,e),M=e=>e?e.map(t=>t.buffer||t):[],_=(e,t,{onJobCount:d}={})=>{let f=e.postMessage?e:e.controller,a=(o,s)=>{let r=o?.[x];r&&delete o[x];try{f.postMessage({method:E,params:o,id:s},M(r))}catch(n){throw console.error("failed to send ",o,r),n}},g=(o,s)=>{try{let r=o.stack;f.postMessage({method:E,error:o,stack:r,id:s})}catch(r){throw console.error("failed to send ",r),r}},y=(o,s=[],r=[])=>{f.postMessage({method:o,params:s},M(r))},u=(o,s=[],r=[],n)=>{let l=v++;return f.postMessage({method:o,params:s,id:l},M(r)),new Promise((h,c)=>{m.set(l,[h,c]),d?.(m.size),n&&setTimeout(()=>{c("timeout")},n)})},i=async o=>{let{method:s,params:r,id:n,error:l,stack:R}=o.data;if(n&&s===E){let c=m.get(n);if(!c)return console.error(`req ${n} not found`,n,o.data,o);m.delete(n),d?.(m.size);let[S,b]=c;l?(l.stack=R,b(l)):S(r);return}let h=t[s];if(!h){let c="no handler for type: "+s;throw console.error(c,o),new Error(c)}try{let c=await h(...r);n&&a(c,n)}catch(c){console.error(`error executing command ${s}`,r,c),g(c,n)}};return e.addEventListener?.("message",i),{sendCmd:u,sendNotify:y,sendResponse:a,sendError:g,listener:i,self:e,getRpcJobCount:()=>m.size}},C=(e,t,{sender:d,onJobCount:f})=>{let{sendCmd:a,sendNotify:g,getRpcJobCount:y}=d||_(e,t,{onJobCount:f});return new Proxy({getRpcJobCount:y},{get(u,i,o){return i in u?u[i]:i.startsWith("on")?u[i]=function(...s){g(i,s)}:u[i]=function(...s){return a(i,s)}}})}; +//# sourceMappingURL=index.js.map diff --git a/packages/postmessage/cjs/index.js.map b/packages/postmessage/cjs/index.js.map new file mode 100644 index 0000000..b7b7274 --- /dev/null +++ b/packages/postmessage/cjs/index.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["../index.js"], + "sourcesContent": ["let seq = 1\r\nlet reqMap = new Map()\r\nconst RESPONSE = '__RESPONSE__'\r\nconst TRANSFERABLE = Symbol.for('__transferable__')\r\n\r\nexport const withTransferable = (params,trans)=>{\r\n params[TRANSFERABLE] = trans\r\n return params\r\n}\r\n\r\nconst fixTransfer = trans => (trans ? trans.map(a => a.buffer || a) : [])\r\n\r\n/**\r\n *\r\n * @param {*} _self reference to self of the main window (self) or reference to a worker\r\n * @param {*} handlers - object where key if method name, and value ih handler\r\n * @returns\r\n */\r\nexport const initMessaging = (_self, handlers, {onJobCount}={}) => {\r\n // on service worker, postMessage is on the controller\r\n const ___self = _self.postMessage ? _self : _self.controller\r\n const sendResponse = (result, id) => {\r\n let trans = result?.[TRANSFERABLE]\r\n if (trans) {\r\n delete result[TRANSFERABLE]\r\n }\r\n try {\r\n ___self.postMessage({ method: RESPONSE, params: result, id }, fixTransfer(trans))\r\n \r\n } catch (error) {\r\n console.error('failed to send ', result, trans)\r\n throw error\r\n }\r\n }\r\n\r\n const sendError = (error, id) => {\r\n try {\r\n // serialize stacktrace so it isn't lost in transit\r\n const stack = error.stack\r\n ___self.postMessage({ method: RESPONSE, error, stack, id })\r\n } catch (error) {\r\n console.error('failed to send ', error)\r\n throw error\r\n }\r\n }\r\n\r\n /**\r\n * Send a message with no response\r\n *\r\n * @param {string} method\r\n * @param {object} params\r\n * @param {Array} trans\r\n */\r\n const sendNotify = (method, params = [], trans = []) => {\r\n ___self.postMessage({ method, params }, fixTransfer(trans))\r\n }\r\n\r\n /**\r\n * Send a message with response expected\r\n *\r\n * @param {string} method\r\n * @param {object} params\r\n * @param {Array} trans\r\n * @param {number?} timeout\r\n * @returns {Promise} resolves when response is received\r\n */\r\n const sendCmd = (method, params = [], trans = [], timeout) => {\r\n const id = seq++\r\n ___self.postMessage({ method, params, id }, fixTransfer(trans))\r\n\r\n const out = new Promise((resolve, reject) => {\r\n reqMap.set(id, [resolve, reject])\r\n onJobCount?.(reqMap.size)\r\n if (timeout) {\r\n setTimeout(() => {\r\n reject('timeout')\r\n }, timeout)\r\n }\r\n })\r\n return out\r\n }\r\n\r\n const listener = async (e) => {\r\n const { method, params, id, error, stack } = e.data\r\n if (id && method === RESPONSE) {\r\n const p = reqMap.get(id)\r\n\r\n if (!p) return console.error(`req ${id} not found`,id, e.data, e)\r\n reqMap.delete(id)\r\n onJobCount?.(reqMap.size)\r\n\r\n const [resolve, reject] = p\r\n if (error) {\r\n // restore stacktrace\r\n error.stack = stack\r\n reject(error)\r\n } else {\r\n resolve(params)\r\n }\r\n\r\n return\r\n }\r\n\r\n const fn = handlers[method]\r\n if (!fn) {\r\n const msg = 'no handler for type: ' + method\r\n console.error(msg, e)\r\n throw new Error(msg)\r\n }\r\n try {\r\n const out = await fn(...params)\r\n if (id) {\r\n sendResponse(out, id)\r\n }\r\n } catch (error) {\r\n console.error(`error executing command ${method}`, params, error)\r\n sendError(error, id)\r\n }\r\n }\r\n \r\n _self.addEventListener?.('message', listener)\r\n\r\n return { \r\n sendCmd, \r\n sendNotify, \r\n sendResponse, \r\n sendError, \r\n listener, \r\n self:_self, \r\n getRpcJobCount:()=>reqMap.size \r\n }\r\n}\r\n\r\n/**\r\n * \r\n * @param {*} _self \r\n * @param {*} handlers \r\n * @returns {object}\r\n*/\r\nexport const messageProxy = (_self, handlers, {sender, onJobCount}) => {\r\n const { sendCmd, sendNotify, getRpcJobCount} = sender || initMessaging(_self, handlers,{onJobCount})\r\n\r\n return new Proxy({\r\n getRpcJobCount\r\n },{\r\n get(target, prop, receiver) {\r\n if(prop in target) return target[prop]\r\n if(prop.startsWith('on')){\r\n return target[prop] = function(...params){\r\n sendNotify(prop, params)\r\n } \r\n }\r\n return target[prop] = function(...params){\r\n return sendCmd(prop, params)\r\n }\r\n },\r\n })\r\n}\r\n"], + "mappings": "4ZAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,mBAAAE,EAAA,iBAAAC,EAAA,qBAAAC,IAAA,eAAAC,EAAAL,GAAA,IAAIM,EAAM,EACNC,EAAS,IAAI,IACXC,EAAW,eACXC,EAAe,OAAO,IAAI,kBAAkB,EAErCL,EAAmB,CAACM,EAAOC,KACtCD,EAAOD,GAAgBE,EAChBD,GAGHE,EAAcD,GAAUA,EAAQA,EAAM,IAAIE,GAAKA,EAAE,QAAUA,CAAC,EAAI,CAAC,EAQ1DX,EAAgB,CAACY,EAAOC,EAAU,CAAC,WAAAC,CAAU,EAAE,CAAC,IAAM,CAEjE,IAAMC,EAAUH,EAAM,YAAcA,EAAQA,EAAM,WAC5CI,EAAe,CAACC,EAAQC,IAAO,CACnC,IAAIT,EAAQQ,IAASV,GACjBE,GACF,OAAOQ,EAAOV,GAEhB,GAAI,CACFQ,EAAQ,YAAY,CAAE,OAAQT,EAAU,OAAQW,EAAQ,GAAAC,CAAG,EAAGR,EAAYD,CAAK,CAAC,CAElF,OAASU,EAAP,CACA,cAAQ,MAAM,kBAAmBF,EAAQR,CAAK,EACxCU,CACR,CACF,EAEMC,EAAY,CAACD,EAAOD,IAAO,CAC/B,GAAI,CAEF,IAAMG,EAAQF,EAAM,MACpBJ,EAAQ,YAAY,CAAE,OAAQT,EAAU,MAAAa,EAAO,MAAAE,EAAO,GAAAH,CAAG,CAAC,CAC5D,OAASC,EAAP,CACA,cAAQ,MAAM,kBAAmBA,CAAK,EAChCA,CACR,CACF,EASMG,EAAa,CAACC,EAAQf,EAAS,CAAC,EAAGC,EAAQ,CAAC,IAAM,CACtDM,EAAQ,YAAY,CAAE,OAAAQ,EAAQ,OAAAf,CAAO,EAAGE,EAAYD,CAAK,CAAC,CAC5D,EAWMe,EAAU,CAACD,EAAQf,EAAS,CAAC,EAAGC,EAAQ,CAAC,EAAGgB,IAAY,CAC5D,IAAMP,EAAKd,IACX,OAAAW,EAAQ,YAAY,CAAE,OAAAQ,EAAQ,OAAAf,EAAQ,GAAAU,CAAG,EAAGR,EAAYD,CAAK,CAAC,EAElD,IAAI,QAAQ,CAACiB,EAASC,IAAW,CAC3CtB,EAAO,IAAIa,EAAI,CAACQ,EAASC,CAAM,CAAC,EAChCb,IAAaT,EAAO,IAAI,EACpBoB,GACF,WAAW,IAAM,CACfE,EAAO,SAAS,CAClB,EAAGF,CAAO,CAEd,CAAC,CAEH,EAEMG,EAAW,MAAOC,GAAM,CAC5B,GAAM,CAAE,OAAAN,EAAQ,OAAAf,EAAQ,GAAAU,EAAI,MAAAC,EAAO,MAAAE,CAAM,EAAIQ,EAAE,KAC/C,GAAIX,GAAMK,IAAWjB,EAAU,CAC7B,IAAMwB,EAAIzB,EAAO,IAAIa,CAAE,EAEvB,GAAI,CAACY,EAAG,OAAO,QAAQ,MAAM,OAAOZ,cAAeA,EAAIW,EAAE,KAAMA,CAAC,EAChExB,EAAO,OAAOa,CAAE,EAChBJ,IAAaT,EAAO,IAAI,EAExB,GAAM,CAACqB,EAASC,CAAM,EAAIG,EACtBX,GAEFA,EAAM,MAAQE,EACdM,EAAOR,CAAK,GAEZO,EAAQlB,CAAM,EAGhB,MACF,CAEA,IAAMuB,EAAKlB,EAASU,GACpB,GAAI,CAACQ,EAAI,CACP,IAAMC,EAAM,wBAA0BT,EACtC,cAAQ,MAAMS,EAAKH,CAAC,EACd,IAAI,MAAMG,CAAG,CACrB,CACA,GAAI,CACF,IAAMC,EAAM,MAAMF,EAAG,GAAGvB,CAAM,EAC1BU,GACFF,EAAaiB,EAAKf,CAAE,CAExB,OAASC,EAAP,CACA,QAAQ,MAAM,2BAA2BI,IAAUf,EAAQW,CAAK,EAChEC,EAAUD,EAAOD,CAAE,CACrB,CACF,EAEA,OAAAN,EAAM,mBAAmB,UAAWgB,CAAQ,EAErC,CACL,QAAAJ,EACA,WAAAF,EACA,aAAAN,EACA,UAAAI,EACA,SAAAQ,EACA,KAAKhB,EACL,eAAe,IAAIP,EAAO,IAC5B,CACF,EAQaJ,EAAe,CAACW,EAAOC,EAAU,CAAC,OAAAqB,EAAQ,WAAApB,CAAU,IAAM,CACrE,GAAM,CAAE,QAAAU,EAAS,WAAAF,EAAY,eAAAa,CAAc,EAAID,GAAUlC,EAAcY,EAAOC,EAAS,CAAC,WAAAC,CAAU,CAAC,EAEnG,OAAO,IAAI,MAAM,CACf,eAAAqB,CACF,EAAE,CACA,IAAIC,EAAQC,EAAMC,EAAU,CAC1B,OAAGD,KAAQD,EAAgBA,EAAOC,GAC/BA,EAAK,WAAW,IAAI,EACdD,EAAOC,GAAQ,YAAY7B,EAAO,CACvCc,EAAWe,EAAM7B,CAAM,CACzB,EAEK4B,EAAOC,GAAQ,YAAY7B,EAAO,CACvC,OAAOgB,EAAQa,EAAM7B,CAAM,CAC7B,CACF,CACF,CAAC,CACH", + "names": ["postmessage_exports", "__export", "initMessaging", "messageProxy", "withTransferable", "__toCommonJS", "seq", "reqMap", "RESPONSE", "TRANSFERABLE", "params", "trans", "fixTransfer", "a", "_self", "handlers", "onJobCount", "___self", "sendResponse", "result", "id", "error", "sendError", "stack", "sendNotify", "method", "sendCmd", "timeout", "resolve", "reject", "listener", "e", "p", "fn", "msg", "out", "sender", "getRpcJobCount", "target", "prop", "receiver"] +} diff --git a/packages/postmessage/esm/index.js b/packages/postmessage/esm/index.js new file mode 100644 index 0000000..a3d9bb0 --- /dev/null +++ b/packages/postmessage/esm/index.js @@ -0,0 +1,2 @@ +var S=1,u=new Map,w="__RESPONSE__",M=Symbol.for("__transferable__"),p=(s,a)=>(s[M]=a,s),E=s=>s?s.map(a=>a.buffer||a):[],b=(s,a,{onJobCount:l}={})=>{let i=s.postMessage?s:s.controller,m=(e,t)=>{let o=e?.[M];o&&delete e[M];try{i.postMessage({method:w,params:e,id:t},E(o))}catch(r){throw console.error("failed to send ",e,o),r}},g=(e,t)=>{try{let o=e.stack;i.postMessage({method:w,error:e,stack:o,id:t})}catch(o){throw console.error("failed to send ",o),o}},y=(e,t=[],o=[])=>{i.postMessage({method:e,params:t},E(o))},d=(e,t=[],o=[],r)=>{let f=S++;return i.postMessage({method:e,params:t,id:f},E(o)),new Promise((h,n)=>{u.set(f,[h,n]),l?.(u.size),r&&setTimeout(()=>{n("timeout")},r)})},c=async e=>{let{method:t,params:o,id:r,error:f,stack:x}=e.data;if(r&&t===w){let n=u.get(r);if(!n)return console.error(`req ${r} not found`,r,e.data,e);u.delete(r),l?.(u.size);let[R,_]=n;f?(f.stack=x,_(f)):R(o);return}let h=a[t];if(!h){let n="no handler for type: "+t;throw console.error(n,e),new Error(n)}try{let n=await h(...o);r&&m(n,r)}catch(n){console.error(`error executing command ${t}`,o,n),g(n,r)}};return s.addEventListener?.("message",c),{sendCmd:d,sendNotify:y,sendResponse:m,sendError:g,listener:c,self:s,getRpcJobCount:()=>u.size}},N=(s,a,{sender:l,onJobCount:i})=>{let{sendCmd:m,sendNotify:g,getRpcJobCount:y}=l||b(s,a,{onJobCount:i});return new Proxy({getRpcJobCount:y},{get(d,c,e){return c in d?d[c]:c.startsWith("on")?d[c]=function(...t){g(c,t)}:d[c]=function(...t){return m(c,t)}}})};export{b as initMessaging,N as messageProxy,p as withTransferable}; +//# sourceMappingURL=index.js.map diff --git a/packages/postmessage/esm/index.js.map b/packages/postmessage/esm/index.js.map new file mode 100644 index 0000000..6ea897c --- /dev/null +++ b/packages/postmessage/esm/index.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["../index.js"], + "sourcesContent": ["let seq = 1\r\nlet reqMap = new Map()\r\nconst RESPONSE = '__RESPONSE__'\r\nconst TRANSFERABLE = Symbol.for('__transferable__')\r\n\r\nexport const withTransferable = (params,trans)=>{\r\n params[TRANSFERABLE] = trans\r\n return params\r\n}\r\n\r\nconst fixTransfer = trans => (trans ? trans.map(a => a.buffer || a) : [])\r\n\r\n/**\r\n *\r\n * @param {*} _self reference to self of the main window (self) or reference to a worker\r\n * @param {*} handlers - object where key if method name, and value ih handler\r\n * @returns\r\n */\r\nexport const initMessaging = (_self, handlers, {onJobCount}={}) => {\r\n // on service worker, postMessage is on the controller\r\n const ___self = _self.postMessage ? _self : _self.controller\r\n const sendResponse = (result, id) => {\r\n let trans = result?.[TRANSFERABLE]\r\n if (trans) {\r\n delete result[TRANSFERABLE]\r\n }\r\n try {\r\n ___self.postMessage({ method: RESPONSE, params: result, id }, fixTransfer(trans))\r\n \r\n } catch (error) {\r\n console.error('failed to send ', result, trans)\r\n throw error\r\n }\r\n }\r\n\r\n const sendError = (error, id) => {\r\n try {\r\n // serialize stacktrace so it isn't lost in transit\r\n const stack = error.stack\r\n ___self.postMessage({ method: RESPONSE, error, stack, id })\r\n } catch (error) {\r\n console.error('failed to send ', error)\r\n throw error\r\n }\r\n }\r\n\r\n /**\r\n * Send a message with no response\r\n *\r\n * @param {string} method\r\n * @param {object} params\r\n * @param {Array} trans\r\n */\r\n const sendNotify = (method, params = [], trans = []) => {\r\n ___self.postMessage({ method, params }, fixTransfer(trans))\r\n }\r\n\r\n /**\r\n * Send a message with response expected\r\n *\r\n * @param {string} method\r\n * @param {object} params\r\n * @param {Array} trans\r\n * @param {number?} timeout\r\n * @returns {Promise} resolves when response is received\r\n */\r\n const sendCmd = (method, params = [], trans = [], timeout) => {\r\n const id = seq++\r\n ___self.postMessage({ method, params, id }, fixTransfer(trans))\r\n\r\n const out = new Promise((resolve, reject) => {\r\n reqMap.set(id, [resolve, reject])\r\n onJobCount?.(reqMap.size)\r\n if (timeout) {\r\n setTimeout(() => {\r\n reject('timeout')\r\n }, timeout)\r\n }\r\n })\r\n return out\r\n }\r\n\r\n const listener = async (e) => {\r\n const { method, params, id, error, stack } = e.data\r\n if (id && method === RESPONSE) {\r\n const p = reqMap.get(id)\r\n\r\n if (!p) return console.error(`req ${id} not found`,id, e.data, e)\r\n reqMap.delete(id)\r\n onJobCount?.(reqMap.size)\r\n\r\n const [resolve, reject] = p\r\n if (error) {\r\n // restore stacktrace\r\n error.stack = stack\r\n reject(error)\r\n } else {\r\n resolve(params)\r\n }\r\n\r\n return\r\n }\r\n\r\n const fn = handlers[method]\r\n if (!fn) {\r\n const msg = 'no handler for type: ' + method\r\n console.error(msg, e)\r\n throw new Error(msg)\r\n }\r\n try {\r\n const out = await fn(...params)\r\n if (id) {\r\n sendResponse(out, id)\r\n }\r\n } catch (error) {\r\n console.error(`error executing command ${method}`, params, error)\r\n sendError(error, id)\r\n }\r\n }\r\n \r\n _self.addEventListener?.('message', listener)\r\n\r\n return { \r\n sendCmd, \r\n sendNotify, \r\n sendResponse, \r\n sendError, \r\n listener, \r\n self:_self, \r\n getRpcJobCount:()=>reqMap.size \r\n }\r\n}\r\n\r\n/**\r\n * \r\n * @param {*} _self \r\n * @param {*} handlers \r\n * @returns {object}\r\n*/\r\nexport const messageProxy = (_self, handlers, {sender, onJobCount}) => {\r\n const { sendCmd, sendNotify, getRpcJobCount} = sender || initMessaging(_self, handlers,{onJobCount})\r\n\r\n return new Proxy({\r\n getRpcJobCount\r\n },{\r\n get(target, prop, receiver) {\r\n if(prop in target) return target[prop]\r\n if(prop.startsWith('on')){\r\n return target[prop] = function(...params){\r\n sendNotify(prop, params)\r\n } \r\n }\r\n return target[prop] = function(...params){\r\n return sendCmd(prop, params)\r\n }\r\n },\r\n })\r\n}\r\n"], + "mappings": "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", + "names": ["seq", "reqMap", "RESPONSE", "TRANSFERABLE", "withTransferable", "params", "trans", "fixTransfer", "initMessaging", "_self", "handlers", "onJobCount", "___self", "sendResponse", "result", "id", "error", "sendError", "stack", "sendNotify", "method", "sendCmd", "timeout", "resolve", "reject", "listener", "p", "fn", "msg", "out", "messageProxy", "sender", "getRpcJobCount", "target", "prop", "receiver"] +} diff --git a/packages/postmessage/index.js b/packages/postmessage/index.js index 757ad59..045145c 100644 --- a/packages/postmessage/index.js +++ b/packages/postmessage/index.js @@ -8,58 +8,27 @@ export const withTransferable = (params,trans)=>{ return params } -const messageHandler = (handlers, sendResponse, sendError) => { - return async (e) => { - const { method, params, id, error, stack } = e.data - if (id && method === RESPONSE) { - const p = reqMap.get(id) - - if (!p) return console.error(`req ${id} not found`) - reqMap.delete(id) - - const [resolve, reject] = p - if (error) { - // restore stacktrace - error.stack = stack - reject(error) - } else { - resolve(params) - } - - return - } - - const fn = handlers[method] - if (!fn) { - const msg = 'no handler for type: ' + method - console.error(msg, e) - throw new Error(msg) - } - try { - const out = await fn(params) - if (id) { - sendResponse(out, id) - } - } catch (error) { - console.error(`error executing command ${method}`, params, error) - sendError(error, id) - } - } -} - const fixTransfer = trans => (trans ? trans.map(a => a.buffer || a) : []) -const messageSender = _self => { - const sendResponse = (params, id) => { - let trans = params?.[TRANSFERABLE] +/** + * + * @param {*} _self reference to self of the main window (self) or reference to a worker + * @param {*} handlers - object where key if method name, and value ih handler + * @returns + */ +export const initMessaging = (_self, handlers, {onJobCount}={}) => { + // on service worker, postMessage is on the controller + const ___self = _self.postMessage ? _self : _self.controller + const sendResponse = (result, id) => { + let trans = result?.[TRANSFERABLE] if (trans) { - delete params[TRANSFERABLE] + delete result[TRANSFERABLE] } try { - _self.postMessage({ method: RESPONSE, params, id }, fixTransfer(trans)) + ___self.postMessage({ method: RESPONSE, params: result, id }, fixTransfer(trans)) } catch (error) { - console.error('failed to send ', params, trans) + console.error('failed to send ', result, trans) throw error } } @@ -68,7 +37,7 @@ const messageSender = _self => { try { // serialize stacktrace so it isn't lost in transit const stack = error.stack - _self.postMessage({ method: RESPONSE, error, stack, id }) + ___self.postMessage({ method: RESPONSE, error: {message: error.message, name:error.name, stack}, id }) } catch (error) { console.error('failed to send ', error) throw error @@ -82,8 +51,8 @@ const messageSender = _self => { * @param {object} params * @param {Array} trans */ - const sendNotify = (method, params = {}, trans = []) => { - _self.postMessage({ method, params }, fixTransfer(trans)) + const sendNotify = (method, params = [], trans = []) => { + ___self.postMessage({ method, params }, fixTransfer(trans)) } /** @@ -95,12 +64,13 @@ const messageSender = _self => { * @param {number?} timeout * @returns {Promise} resolves when response is received */ - const sendCmd = (method, params = {}, trans = [], timeout) => { + const sendCmd = (method, params = [], trans = [], timeout) => { const id = seq++ - _self.postMessage({ method, params, id }, fixTransfer(trans)) + ___self.postMessage({ method, params, id }, fixTransfer(trans)) const out = new Promise((resolve, reject) => { reqMap.set(id, [resolve, reject]) + onJobCount?.(reqMap.size) if (timeout) { setTimeout(() => { reject('timeout') @@ -110,21 +80,82 @@ const messageSender = _self => { return out } - return { sendCmd, sendNotify, sendResponse, sendError } + const listener = async (e) => { + const { method, params, id, error } = e.data + if (id && method === RESPONSE) { + const p = reqMap.get(id) + + if (!p) return console.error(`req ${id} not found`,id, e.data, e) + reqMap.delete(id) + onJobCount?.(reqMap.size) + + const [resolve, reject] = p + if (error) { + // restore stacktrace + // if(typeof error === 'string') + const _error = new Error(error.message) + _error.stack = error.stack + _error.name = error.name + reject(_error) + } else { + resolve(params) + } + + return + } + + const fn = handlers[method] + if (!fn) { + const msg = 'no handler for type: ' + method + console.error(msg, e) + throw new Error(msg) + } + try { + const out = await fn(...params) + if (id) { + sendResponse(out, id) + } + } catch (error) { + console.error(`error executing command ${method}`, params, error) + sendError(error, id) + } + } + + _self.addEventListener?.('message', listener) + + return { + sendCmd, + sendNotify, + sendResponse, + sendError, + listener, + self:_self, + getRpcJobCount:()=>reqMap.size + } } /** - * - * @param {*} _self reference to self of the main window (self) or reference to a worker - * @param {*} handlers - object where key if method name, and value ih handler - * @returns - */ -export const initMessaging = (_self, handlers) => { - // service worker has the postMessage on the .controller, it is not na same object as addEventListener - const out = messageSender(_self.postMessage ? _self : _self.controller) - out.listener = messageHandler(handlers, out.sendResponse, out.sendError) - out.self = _self - _self.addEventListener?.('message', out.listener) + * + * @param {*} _self + * @param {*} handlers + * @returns {object} +*/ +export const messageProxy = (_self, handlers, {sender, onJobCount}={}) => { + const { sendCmd, sendNotify, getRpcJobCount} = sender || initMessaging(_self, handlers,{onJobCount}) - return out + return new Proxy({ + getRpcJobCount + },{ + get(target, prop, receiver) { + if(prop in target) return target[prop] + if(prop.startsWith('on')){ + return target[prop] = function(...params){ + sendNotify(prop, params) + } + } + return target[prop] = function(...params){ + return sendCmd(prop, params) + } + }, + }) } diff --git a/packages/postmessage/package.json b/packages/postmessage/package.json index f12857e..308e93f 100644 --- a/packages/postmessage/package.json +++ b/packages/postmessage/package.json @@ -2,7 +2,7 @@ "type": "module", "sideEffects": false, "name": "@jscadui/postmessage", - "version": "0.1.0", + "version": "0.2.0", "description": "postMessage utility for worker and main window", "keywords": ["postMessage","rpc","async","promise","worker"], "main": "index.js", diff --git a/packages/require/README.md b/packages/require/README.md index 347e0e1..3bac3aa 100644 --- a/packages/require/README.md +++ b/packages/require/README.md @@ -1,31 +1 @@ -# fs-provider - -Provider that fills cache for `fs-service-worker` that you need in your main script to fill files data. - -Use case is for creating a virtual fodler taht can be accessed by javascript uring fetch or sync/async XMLHttpRequest. - -## temp caches - -This require wrapper will be used for running jscad scripts that might be whole project folders. In that case it can have local libraries in workspaces, and also will have multiple files. It is important to separate global cache (that caches possibly things like '@jscad/modeling' or some other library, versus modules or files from uploaded folder). When new folder is uploaded we should be able to purge only files cached from previous folder, and not others. - -Maybe this use case for temp caches will prove to be not so useful, so we also need to support full cache purging. - -# URL - useful to resolve relative paths - -Regardless if url is something `http://` or some fake prefix `fs:/` the `URL` class can be used to resolve paths relative to a file for us. We do not even need to cut the file name out, it will use the folder as basis like you would expect for html resources. - -```js -new URL('/index.js','fs:/bb/aaa/ccc/bla.js') -// .pathname: /index.js -new URL('./index.js','fs:/bb/aaa/ccc/bla.js') -// .pathname: /bb/aaa/ccc/index.js -new URL('../index.js','fs:/bb/aaa/ccc/bla.js') -// .pathname: /bb/aaa/index.js -``` - -Another also useful behaviour is that if url is not relative, secont param is ignored - -```js -new URL('http://google.com','fs:/bb/aaa/ccc/bla.js') -// http://google.com -``` +# require \ No newline at end of file diff --git a/packages/require/src/require.js b/packages/require/src/require.js index 1c45f1c..2ea587d 100644 --- a/packages/require/src/require.js +++ b/packages/require/src/require.js @@ -122,10 +122,11 @@ const requireModule = (id, url, source, _require) => { const exports = {} const module = { id, uri: url, exports, source } // according to node.js modules //module.require = _require + source += '\n//# sourceURL=' + url runModule(_require, exports, module, source) return module } catch (err) { - err.message = `failed loading module ${id}\n ${err}` + err.message += ` / failed loading module ${id}` throw err } } diff --git a/packages/worker/worker.d.ts b/packages/worker/worker.d.ts new file mode 100644 index 0000000..8fa1d18 --- /dev/null +++ b/packages/worker/worker.d.ts @@ -0,0 +1,47 @@ +/** +@typedef InitOptions +@prop {String} baseURI - to resolve inital relative path +@prop {Array} alias - +@prop {Array} bundles - bundle alias {name:path} + +@typedef RunScriptOptions +@prop {string} script - script source +@prop {string} url - script url/name +@prop {string} base - base url + +@typedef ScriptResponse +@prop {Array} entities +@prop {number} mainTime - script run time + +@typedef JscadWorker +@prop {(options:InitOptions)=>Promise} init +@prop {(options:RunScriptOptions)=>Promise} runScript +*/ + +export interface InitOptions { + /** to resolve inital relative path */ + baseURI:String, + alias: Array, + /** bundle alias {name:path} */ + bundles: Array>, +} + +export interface RunScriptOptions { + /** script source */ + script: string, + /** script url/name */ + url: string, + /** base url */ + base: string, +} + +export interface ScriptResponse { + entities: Array, + /** script run time */ + mainTime: number, +} + +export interface JscadWorker { + async init(options:InitOptions):void, + async init(options:RunScriptOptions):ScriptResponse, +} diff --git a/packages/worker/worker.js b/packages/worker/worker.js index 3ae0319..7304b32 100644 --- a/packages/worker/worker.js +++ b/packages/worker/worker.js @@ -7,6 +7,48 @@ import { combineParameterDefinitions, getParameterDefinitionsFromSource } from ' import { extractDefaults } from './src/extractDefaults.js' import { extractPathInfo, readAsArrayBuffer, readAsText } from '../fs-provider/fs-provider.js' +/** +@typedef Alias + @prop {String} name + @prop {String} path + +@typedef RunScriptOptions + @prop {string} script - script source + @prop {string} url - script url/name + @prop {string} base - base url + @prop {string} base - root (do not allow paths below that root) + + @typedef ExportDataOptions + @prop {string} format + + @typedef ClearFileCacheOptions + @prop {Array} files + + @typedef RunMainOptions + @prop {Object} params + + @typedef InitOptions + @prop {String} baseURI - to resolve inital relative path + @prop {Array} alias - + @prop {Array} bundle - bundle alias {name:path} + + @typedef ScriptResponse + @prop {Array} entities + @prop {number} mainTime - script run time + @prop {number} convertTime - tim converting script output to gl data + + +@typedef JscadWorker +@prop {String} name +@prop {(options:InitOptions)=>Promise} init +@prop {(options:RunMainOptions)=>Promise} runMain +@prop {(options:RunScriptOptions)=>Promise} runScript +@prop {(options:ExportDataOptions)=>Promise} exportData +@prop {(options:ClearFileCacheOptions)=>Promise} clearFileCache +@prop {()=>Promise} clearTempCache + +*/ + let main self.JSCAD_WORKER_ENV = {} let transformFunc = x => x @@ -29,8 +71,8 @@ export const flatten = arr=>{ return out } -export const init = params => { - let { baseURI, alias = [], bundles = {} } = params +export const init = options => { + let { baseURI, alias = [], bundles = {} } = options if (baseURI) globalBase = baseURI if (bundles) Object.assign(requireCache.bundleAlias, bundles) @@ -39,7 +81,7 @@ export const init = params => { requireCache.alias[name] = path }) console.log('init alias', alias, 'bundles',bundles) - userInstances = params.userInstances + userInstances = options.userInstances } async function readFileFile(file, {bin=false}={}){ @@ -78,14 +120,23 @@ export async function runMain({ params } = {}) { const importReg = /import(?:(?:(?:[ \n\t]+([^ *\n\t\{\},]+)[ \n\t]*(?:,|[ \n\t]+))?([ \n\t]*\{(?:[ \n\t]*[^ \n\t"'\{\}]+[ \n\t]*,?)+\})?[ \n\t]*)|[ \n\t]*\*[ \n\t]*as[ \n\t]+([^ \n\t\{\}]+)[ \n\t]+)from[ \n\t]*(?:['"])([^'"\n]+)(['"])/ const exportReg = /export.*from/ -const runScript = async ({ script, url, base=globalBase, root=base }) => { +const runScript = async ({ script, url='jscad.js', base=globalBase, root=base }) => { console.log('run script with base:', base) if(!script) script = readFileWeb(resolveUrl(url, base, root).url) const shouldTransform = url.endsWith('.ts') || script.includes('import') && (importReg.test(script) || exportReg.test(script)) let def = [] - const scriptModule = require({url,script}, shouldTransform ? transformFunc : undefined, readFileWeb, base, root, importData) + let scriptModule + try{ + scriptModule = require({url,script}, shouldTransform ? transformFunc : undefined, readFileWeb, base, root, importData) + }catch(e){ + // with syntax error in browser we do not get nice stack trace + // we then try to parse the script to let transform function generate nice error with nice trace + if(e.name === 'SyntaxError') transformFunc(script, url) + // if error is not SyntaxError or if transform func does not find sysntax err (very unlikely) + throw e + } const fromSource = getParameterDefinitionsFromSource(script) def = combineParameterDefinitions(fromSource, await scriptModule.getParameterDefinitions?.()) main = scriptModule.main @@ -128,3 +179,5 @@ export const initWorker = (transform, exportData, _importData) => { client = initMessaging(self, handlers) } + +