diff --git a/app.js b/app.js index 4c27cc73..f5e629f4 100644 --- a/app.js +++ b/app.js @@ -122,6 +122,7 @@ function init(mod) { mod.add(serveWasm); mod.add(serveCode); mod.add(fullpath({ + "/boot" : redir((pre??"") + "/boot/", 301), "/kiri" : redir((pre??"") + "/kiri/", 301), "/mesh" : redir((pre??"") + "/mesh/", 301), "/meta" : redir((pre??"") + "/meta/", 301), @@ -147,6 +148,7 @@ function init(mod) { mod.static("/mesh/", "web/mesh"); mod.static("/moto/", "web/moto"); mod.static("/kiri/", "web/kiri"); + mod.static("/boot/", "web/boot"); // module loader function load_modules(root, force) { diff --git a/bin/bundle.config.json b/bin/bundle.config.json new file mode 100644 index 00000000..655fa67e --- /dev/null +++ b/bin/bundle.config.json @@ -0,0 +1,11 @@ +{ + "outputs": { + "bundle": "./web/kiri/bundle.bin" + }, + "inputs": [ + { "root": "./web", "prefix": "" }, + { "root": "./src", "prefix": "lib" }, + { "root": "./src/wasm", "prefix": "wasm" }, + { "root": "./src/wasm", "prefix": "lib/kiri/wasm" } + ] +} diff --git a/bin/bundler.mjs b/bin/bundler.mjs new file mode 100644 index 00000000..21cec2bd --- /dev/null +++ b/bin/bundler.mjs @@ -0,0 +1,106 @@ +import fs from 'fs'; +import path from 'path'; +import uglify from 'uglify-js'; +import { Buffer } from 'buffer'; + +const cfg = JSON.parse(fs.readFileSync(process.argv[2] || './bundle.config.json', 'utf8')); +const compress = process.env.UGLIFY ? true : false + +// --- recursive directory walker (follows symlinks safely) --- +function* walk(dir, seen = new Set()) { + const real = fs.realpathSync(dir); + if (seen.has(real)) { + return; + } + seen.add(real); + + for (const name of fs.readdirSync(dir)) { + const p = path.join(dir, name); + let st; + try { st = fs.statSync(p); } catch { continue; } + if (st.isDirectory()) yield* walk(p, seen); + else yield p; + } +} + +// --- collect all files according to config --- +let entries = []; +for (const input of cfg.inputs) { + const root = input.root; + const prefix = input.prefix || ''; + for (const file of walk(root)) { + const rel = path.relative(root, file).replace(/\\/g, '/'); + const virt = prefix ? `${prefix}/${rel}` : rel; + entries.push({ file, virt }); + } +} + +// --- build header and data --- +const dataParts = []; +const entryMeta = []; +let offset = 0; +let cache = new Map(); + +for (const { file, virt } of entries) { + let record = cache.get(file); + if (record) { + // console.log({ dup: file }); + entryMeta.push({ ...record, virt }); + } else { + let data = fs.readFileSync(file); + if (compress && file.endsWith("js")) { + console.log('compress', file); + data = uglify.minify(data.toString(), { + compress: { + merge_vars: false, + unused: false + } + }); + // console.log({ to: typeof(data), data }); + if (!data.code) { + console.log({ skip: file }); + continue; + } + data = Buffer.from(data.code); + } + record = { virt, offset, length: data.length }; + cache.set(file, record); + entryMeta.push(record); + dataParts.push(data); + offset += data.length; + } +} + +// temporary header without offsets to measure total size +let tempHeaderParts = []; +for (const { virt, offset, length } of entryMeta) { + const name = Buffer.from(virt, 'utf8'); + const nlen = Buffer.alloc(2); nlen.writeUInt16LE(name.length); + const offb = Buffer.alloc(4); offb.writeUInt32LE(0); // placeholder + const lenb = Buffer.alloc(4); lenb.writeUInt32LE(length); + tempHeaderParts.push(nlen, name, offb, lenb); +} +const count = Buffer.alloc(4); count.writeUInt32LE(entries.length); +const tempHeader = Buffer.concat([count, ...tempHeaderParts]); +const headerSize = tempHeader.length; // true data offset base + +// now rebuild header with correct absolute offsets +let headerParts = []; +for (const { virt, offset, length } of entryMeta) { + const name = Buffer.from(virt, 'utf8'); + const nlen = Buffer.alloc(2); nlen.writeUInt16LE(name.length); + const offb = Buffer.alloc(4); offb.writeUInt32LE(headerSize + offset); + const lenb = Buffer.alloc(4); lenb.writeUInt32LE(length); + headerParts.push(nlen, name, offb, lenb); + // console.log(virt); +} + +const header = Buffer.concat([count, ...headerParts]); +const full = Buffer.concat([header, ...dataParts]); + +// --- write final bundle --- +const outPath = cfg.outputs.bundle || './bundle.bin'; +fs.mkdirSync(path.dirname(outPath), { recursive: true }); +fs.writeFileSync(outPath, full); // ⚡ write raw binary + +console.log(`✅ Bundled ${entries.length} files → ${outPath} (${full.length} bytes)`); diff --git a/notes.md b/notes.md index 7cf2186c..56c74577 100644 --- a/notes.md +++ b/notes.md @@ -15,6 +15,7 @@ * `P` move all persisted / workspace settings/data to IndexedDB (LS limitations) * `P` faster ray intersect https://github.com/gkjohnson/three-mesh-bvh/ * `P` try material clipping planes for slice range selection +* `P` switch png lib to https://github.com/photopea/UPNG.js * `F` edit in Mesh:Tool * `F` custom device vars for profiles / ranges / gcode diff --git a/package.json b/package.json index 8ac0748a..b0880a91 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "grid-apps", - "version": "4.3.2", + "version": "4.3.3", "description": "grid.space 3d slicing & modeling tools", "author": "Stewart Allen ", "license": "MIT", @@ -82,7 +82,8 @@ "webpack-cli": "^5.1.4" }, "scripts": { - "setup": "npm i && npm run clean && npm run dev -- --dryrun && npm run webpack-ext", + "setup": "npm run setup-mods && npm i && npm run clean && npm run dev -- --dryrun && npm run webpack-ext", + "setup-mods": "cd mods && npm i", "build": "npm run prebuild && electron-builder", "build-nopublish": "npm run prebuild && electron-builder --publish never", "build-debug": "npm run prebuild && DEBUG=electron-builder electron-builder", diff --git a/src/data/index.js b/src/data/index.js index e28217be..ee631f26 100644 --- a/src/data/index.js +++ b/src/data/index.js @@ -168,7 +168,7 @@ SP.init = function(options = {}) { request = IDB.open(name, this.version); request.onupgradeneeded = function(event) { - let db = storage.db = request.result; + let db = request.result; let current = [...db.objectStoreNames]; let stores = storage.stores; // add missing stores @@ -184,13 +184,12 @@ SP.init = function(options = {}) { // remove obsolete stores for (let curr of current) { if (stores.indexOf(curr) < 0) { - storage.db.deleteObjectStore(curr); + db.deleteObjectStore(curr); console.log({index_dropped: curr}); } } - event.target.transaction.oncomplete = function(event) { - storage.runQueue(); - }; + // allow transactions + storage.db = db; }; request.onsuccess = function(event) { @@ -217,6 +216,7 @@ SP.runQueue = function() { let i = 0, q = this.queue, e; while (i < q.length) { e = q[i++]; + // console.log('runQ', i, ...e); switch (e.shift()) { case 'iterate': this.iterate(...e); break; case 'keys': this.keys(...e); break; @@ -252,7 +252,11 @@ SP.put = function(key, value, callback, store = this.current) { req.onerror = function(event) { callback(false) }; } } catch (e) { - console.log(e); + if (e.name === 'InvalidStateError') { + console.log({ putRetry: key }); + return this.queue.push(['put', key, value, callback, store]); + } + console.log(e.name, e); if (callback) callback(false); } }; @@ -275,6 +279,10 @@ SP.get = function(key, callback, store = this.current) { req.onerror = function(event) { callback(null) }; } } catch (e) { + if (e.name === 'InvalidStateError') { + console.log({ getRetry: key }); + return this.queue.push(['get', key, callback, store]); + } console.log(e); if (callback) callback(null); } diff --git a/src/ext/pngjs.esm.js b/src/ext/pngjs.esm.js index 1da89a23..fd8cbbb7 100644 --- a/src/ext/pngjs.esm.js +++ b/src/ext/pngjs.esm.js @@ -4,4 +4,4 @@ import './pngjs.js'; // Re-export the PNG object -export const PNG = self.png ? self.png.PNG : null; \ No newline at end of file +export const PNG = globalThis.png ? globalThis.png.PNG : null; \ No newline at end of file diff --git a/src/ext/pngjs.js b/src/ext/pngjs.js index 561ca443..da7ca622 100644 --- a/src/ext/pngjs.js +++ b/src/ext/pngjs.js @@ -1,4 +1,4 @@ -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.png = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i