From 1d53376fc7ff91b76c28a70f4abd01bdff5ec67d Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sun, 31 May 2020 21:34:04 -0400 Subject: [PATCH] separate kiri and meta css. remove web-server --- js/license.js | 2 +- js/web-server.js | 1427 ------------------------ notes.md | 1 - web/kiri/{style.css => index.css} | 460 +++++++- web/kiri/index.html | 3 +- web/{moto/style.css => meta/index.css} | 55 + web/meta/index.html | 3 +- web/meta/style.css | 54 - 8 files changed, 511 insertions(+), 1494 deletions(-) delete mode 100644 js/web-server.js rename web/kiri/{style.css => index.css} (62%) rename web/{moto/style.css => meta/index.css} (90%) delete mode 100644 web/meta/style.css diff --git a/js/license.js b/js/license.js index f5dd8e9e..cd7f6c4f 100644 --- a/js/license.js +++ b/js/license.js @@ -1,7 +1,7 @@ var exports = { COPYRIGHT:"Copyright (C) Stewart Allen - All Rights Reserved", LICENSE:"See the license.md file included with the source distribution", - VERSION:"2.0.5" + VERSION:"3.0.0-dev" }; if (!module) var module = {}; module.exports = exports; diff --git a/js/web-server.js b/js/web-server.js deleted file mode 100644 index f5cc08ba..00000000 --- a/js/web-server.js +++ /dev/null @@ -1,1427 +0,0 @@ -/** Copyright Stewart Allen -- All Rights Reserved */ - -Array.prototype.contains = function(v) { - return this.indexOf(v) >= 0; -}; - -Array.prototype.appendAll = function(a) { - this.push.apply(this,a); - return this; -}; - -const helper = { - log: function() { - console.log( - moment().format('YYMMDD.HHmmss'), - [...arguments] - .map(v => util.inspect(v, { - maxArrayLength: null, - breakLength: Infinity, - colors: debug, - compact: true, - sorted: true, - depth: null - })) - .join(' ') - ); - } -}; - -function log(o) { - helper.log(o); -} - -function promise(resolve, reject) { - return new Promise(resolve, reject); -} - -function rval() { - return (Math.round(Math.random()*0xffffffff)).toString(36); -} - -function guid() { - return time().toString(36)+rval()+rval()+rval(); -} - -function time() { - return Date.now(); -} - -/** - * @param {String[]} path - */ -function mkdirs(path) { - let root = ""; - path.forEach(seg => { - if (root) { - root = root + "/" + seg; - } else { - root = seg; - } - lastmod(root) || fs.mkdirSync(root); - }); -} - -/** - * @param {String} o - */ -function obj2string(o) { - return JSON.stringify(o); -} - -function string2obj(s) { - return JSON.parse(s); -} - -/** - * sync return mtime for file or 0 for no such file - * @param {String} path - * @returns {number} - */ -function lastmod(path) { - try { - return fs.statSync(path).mtime.getTime(); - } catch (e) { - return 0; - } -} - -/** - * @param {String} filePath - * @param {Function} fn - * @returns {*} - */ -function getCachedFile(filePath, cpath, fn) { - let cachePath = ".cache/" + cpath - .replace(/\//g,'_') - .replace(/\\/g,'_') - .replace(/:/g,'_'), - cached = fileCache[filePath], - now = time(); - - if (cached) { - if (now - cached.lastcheck > 60000) { - let smod = lastmod(filePath), - cmod = cached.mtime; - - if (!smod) throw "missing source file"; - if (smod > cmod) cached = null; - - cached.lastcheck = now; - } - } - - if (!cached) { - let smod = lastmod(filePath), - cmod = lastmod(cachePath), - cacheData; - - if (cmod >= smod) { - cacheData = fs.readFileSync(cachePath); - } else { - helper.log({update_cache:filePath}); - cacheData = fn(filePath); - fs.writeFileSync(cachePath, cacheData); - } - - cached = { - data: cacheData, - mtime: cmod || now, - lastcheck: now - }; - - fileCache[filePath] = cached; - } - - return cached.data; -} - -/** - * mangle, cache and concatenate scripts - */ -function prepareScripts() { - code.kiri = concatCode(script.kiri); - code.meta = concatCode(script.meta); - code.work = concatCode(script.work); - code.worker = concatCode(script.worker); - fs.readdir("./web/kiri/filter/FDM", function(err, files) { - filters_fdm = files || filters_fdm; - }); - fs.readdir("./web/kiri/filter/SLA", function(err, files) { - filters_sla = files || filters_sla; - }); - fs.readdir("./web/kiri/filter/CAM", function(err, files) { - filters_cam = files || filters_cam; - }); - fs.readdir("./web/kiri/filter/LASER", function(err, files) { - filters_laser = files || filters_laser; - }); -} - -/** - * @param {Array} array - * @returns {String} - */ -function concatCode(array) { - let code = [], - cached, - cachepath, - filepath; - - // in debug mode, the script should load dependent - // scripts instead of serving a complete bundle - if (debug) { - let code = [ `(function() { let load = [ `]; - array.forEach(file => { - if (file.indexOf(".js") < 0) { - file = `/js/${file}.js?${ver.VERSION}`; - } - if (file.indexOf(":\\") > 0) { - file = `/${file}`; - } - code.push(`"${file.replace(/\\/g,'/')}",`); - }); - code.push([ - ']; function load_next() {', - 'let file = load.shift();', - 'if (!file) return;', - 'if (!self.document) { importScripts(file); return load_next() }', - 'let s = document.createElement("script");', - 's.type = "text/javascript";', - 's.src = file;', - 's.onload = load_next;', - 'document.head.appendChild(s);', - '}; load_next(); })();', - 'self.debug=true;' - ].join('\n')); - return code.join('\n'); - } - - array.forEach((file, index) => { - if (file.charAt(0) === "/" || file.indexOf("\\") > 0) { - filepath = file; - cachepath = "js_mod" + file - .replace(/\//g,'_') - .replace(/\\/g,'_') - .replace(/:/g,'_'), - array[index] = cachepath.substring(3).replace('.js',''); - fileMap[cachepath.replace("js_","js/")] = filepath; - } else { - filepath = codePrefix + file + ".js"; - cachepath = filepath; - } - cached = getCachedFile(filepath, cachepath, function(path) { - return minify(filepath); - }); - code.push(cached); - }); - - return code.join(''); -} - -/** - * @param {String} cookie - * @param {String} [key] - * @returns {String | null} - */ -function getCookieValue(cookie,key) { - if (!cookie) return null; - key = (key || "key") + "="; - let kpos = cookie.lastIndexOf(key); - if (kpos >= 0) { - return cookie.substring(kpos+key.length).split(';')[0]; - } else { - return null; - } -} - -/** - * @param {String} ip - */ -function isNotLocal(ip) { - return ipLocal.contains(ip) ? null : ip; -} - -/** - * @param {Object} req - * @returns {String} - */ -function remoteIP(req) { - let fwd = req.headers['x-forwarded-for'], - sra = req.socket.remoteAddress, - cra = req.connection.remoteAddress, - ip = isNotLocal(fwd) || sra || cra || '', - ipa = ip.split(','); - if (ip === '' || ipa.length > 1) { - helper.log({remote:ipa, fwd, sra, cra}); - } - return ipa[0]; -} - -/** - * @param {Object} res - * @param {number} code - * @param {String} msg - */ -function quickReply(res, code, msg) { - res.writeHead(code); - res.end(msg+"\n"); -} - -/** - * @param {Object} req - * @param {Object} res - */ -function reply404(req, res) { - logger.emit([ - '404', - req.url, - req.socket.remoteAddress, - req.headers - ]); - res.writeHead(404); - res.end("[404]"); -} - -/** - * @param {Object} res - * @param {String} url - */ -function redirect(res, url) { - res.writeHead(302, { "Location": url }); - res.end(); -} - -/** - * @param {Array} array - * @param {number} length - * @param {number} timespan - * @returns {number} - */ -function limit(array, length, timespan, inc) { - let now = time(), - add = inc || 1, - limit = 0; - // age out entries older than timespan - while (array.length > 0 && now-array[0] > timespan) { - array.shift(); - } - // count elements over the limit - while (array.length > length) { - limit += add; - array.shift(); - } - return limit; -} - -function rewrite(req, res, next) { - if (req.url.indexOf(".html") > 0) { - let real_write = res.write; - let real_end = res.end; - - res.write = function() { - arguments[0] = arguments[0].toString().replace(/{{version}}/g,ver.VERSION); - real_write.apply(res, arguments); - }; - res.end = function() { - if (arguments[0]) { - arguments[0] = arguments[0].toString().replace(/{{version}}/g,ver.VERSION); - } - real_end.apply(res, arguments); - }; - } - - next(); -} - -/** - * @param {Object} req - * @param {Object} res - * @param {Function} next - */ -function setup(req, res, next) { - let host = req.headers['host'] || '', - beta = getCookieValue(req.headers['cookie'],'beta'); - - if (beta === 'true' && req.url.indexOf('?prod') > 0) { - res.setHeader('Set-Cookie', 'beta=false; path=/;'); - beta = 'false'; - } else if (beta !== 'true' && req.url.indexOf('?beta') > 0) { - res.setHeader('Set-Cookie', 'beta=true; path=/;'); - beta = 'true'; - } - - if (beta === 'true' && host.indexOf('beta.') !== 0) { - return redirect(res, `//beta.${host}${req.url}`); - } - - let parsed = url.parse(req.url, true), - ipaddr = remoteIP(req), - dbikey = db.key(["ip",ipaddr]), - path = parsed.pathname, - time = new Date().getTime(), - rec = ipCache[ipaddr] || { - saved : 0, - first: time, - last: [], - hits: 0, - api: [], - ip: ipaddr, - }, - ua = 'unknown'; - - try { - ua = agent.parse(req.headers['user-agent'] || ''); - } catch (e) { - helper.log("ua parse error on : "+req.headers['user-agent']); - } - - // cache it - if (rec.saved === 0) { - ipCache[ipaddr] = rec; - if (!rec.host) { - // prevent overlapping lookups for the same address - rec.host = 'unknown'; - try { - dns.reverse(ipaddr, (err,addr) => { - rec.host = addr; - }); - } catch (e) { - helper.log({dns_err: ipaddr}) - } - } - } - - // grid.space request state - req.gs = { - ua: ua, - ip: ipaddr, - iprec: rec, - local: ipLocal.contains(ipaddr), - port: req.socket.address().port, - url: parsed, - path: parsed.pathname, - query: parsed.query, - }; - - // fixup local addrs - if (req.gs.local) req.gs.ip = "::1"; - - // track clients & show first instance of IP - rec.last.push(time); - rec.hits++; - - // log request - logger.emit([ - req.method, - req.headers['host'] || '', - req.url, - req.socket.remoteAddress, - req.headers['origin'] || '', - req.headers['user-agent'] || '' - // m: req.method, - // u: req.url, - // i: ipaddr, - // h: req.headers['host'], - // o: req.headers['origin'], - // a: req.headers['user-agent'] - ]); - - // update db ip record - if (time - rec.saved > ipSaveDelay) db.get(dbikey) - .then(dbrec => { - // only on the first pull from disk - if (dbrec && rec.saved === 0) { - rec.first = dbrec.first; - rec.hits += dbrec.hits; - rec.last = dbrec.last.appendAll(rec.last); - rec.api = dbrec.api.appendAll(rec.api); - } - rec.saved = time; - return rec; - }) - .then(dbrec => { - if (rec.putTO) clearTimeout(rec.putTO); - rec.putTO = setTimeout(() => { - rec.putTO = null; - db.put(dbikey, dbrec); - }, ipSaveDelay); - }) - .catch(error => { - helper.log({dbikey, error}); - }); - - // absolute limit on client requests per minute - let rateinc = req.headers.host ? 1 : 50; - if (limit(rec.last, 300, 60000, rateinc) && !req.gs.local) { - res.writeHead(503); - res.end("rate limited"); - return log({rate_limit:ipaddr, len:rec.last.length}); - } - - if (req.method === 'OPTIONS') { - addCorsHeaders(req, res); - res.end(); - } else { - next(); - } -} - -function addCorsHeaders(req, res) { - res.setHeader('Access-Control-Allow-Credentials', 'true'); - res.setHeader('Access-Control-Allow-Headers', 'X-Moto-Ajax, Content-Type'); - res.setHeader("Access-Control-Allow-Origin", req.headers['origin'] || '*'); - res.setHeader("Allow", "GET,POST,OPTIONS"); -} - -/** - * in debug mode only, serve ANY file path beginning with / that exists - */ -function handleDebug(req, res, next) { - if (debug) { - let ext = req.url.split('.').pop(); - let type = { - 'html': 'text/html', - 'js': 'application/javascript' - }[ext] || 'application/unknown'; - if (lastmod(req.url)) { - res.setHeader('Content-Type',type); - return res.end(fs.readFileSync(req.url)); - } else if (lastmod(req.url.substring(1))) { - res.setHeader('Content-Type','application/javascript'); - return res.end(fs.readFileSync(req.url.substring(1))); - } - } - return next(); -} - -/** - * meta:moto data storage and retrieval url - * - * @param {Object} req - * @param {Object} res - * @param {Function} next - */ -function handleData(req, res, next) { - addCorsHeaders(req, res); - res.setHeader('Cache-Control', 'private, no-cache, max-age=0'); - - let tok = req.gs.url.path.split('/'), - muid = req.headers['x-moto-ajax'], - space = tok[2] || null, - version = tok[3], - valid = space && space.length >= 4 && space.length <= 8; - - function genKey() { - while (true) { - let k = Math.round(Math.random() * 9999999999).toString(36); - if (k.length >= 4 && k.length <= 8) return k; - } - } - - function countKey(space) { - return db.key(['meta/counter',space]); - } - - function ownerKey(space) { - return db.key(['meta/owner',muid,'space',space]); - } - - function recordKey(space, version) { - return db.key(["meta/space",space,version]); - } - - // retrieve latest space data - if (valid && req.method === 'GET' && valid) { - function send(rec, version) { - if (rec) { - res.write(obj2string({space:space,ver:version,rec:rec})); - res.end(); - } else { - res.end(); - } - } - - function retrieve(version) { - return db.get(recordKey(space,version)) - .then(record => { - send(record || null, version); - }) - } - - if (version) { - retrieve(version) - } else { - db.get(countKey(space)).then(version => retrieve(version)); - } - - return; - - } else if (valid && req.method === 'POST') { - - let dbOwner = null, - dbVersion = null, - postBody = null, - iprec = req.gs.iprec, - spacein = space, - version = 0, - body = ''; - - function checkDone() { - if (!(dbVersion && postBody)) return; - // if not owner, assign new space id - if (dbVersion > 1) { - if (!dbOwner) { - space = genKey(); - version = 1; - log({forked:space,from:spacein,by:muid}); - } - } - // log what we have - log({ - space: space, - ver: dbVersion, - uid: muid, - ip: iprec.ip, - hits: iprec.hits, - size: postBody.length - }); - if (muid && muid.length > 0) { - level.put(recordKey(space, dbVersion), body); - level.put(ownerKey(space), {ip: iprec.ip, time: time(), ver: dbVersion}); - level.put(countKey(space), dbVersion); - } - res.end(obj2string({space: space, ver: dbVersion})); - } - - // accumulate post body - req.on('data', data => { body += data }); - req.on('end', () => { - postBody = body; - checkDone(); - }); - - // fetch owner and version information - db.get(ownerKey(space)) - .then(owner => { - dbOwner = owner; - return db.get(countKey(space)); - }) - .then(version => { - dbVersion = parseInt(version || "0") + 1; - checkDone(); - }); - - return; - } -} - -function minify(path) { - let code = fs.readFileSync(path); - if (skip_minify.indexOf(path) >= 0) { - return code; - } - let mini = uglify.minify(code.toString()); - if (mini.error) { - console.trace(mini.error); - throw mini.error; - } - return mini.code; -} - -const clearok = [ - 'js/add-three.js', - 'js/ext-three.js', - 'js/ext-tween.js', - 'js/moto-load-stl.js', -]; - -/** - * @param {Object} req - * @param {Object} res - * @param {Function} next - */ -function handleJS(req, res, next) { - let spath = req.gs.path.substring(1), - jspos = spath.indexOf(".js"), - fpath = jspos > 0 ? spath.substring(0,jspos+3) : spath, - cached = fileCache[fpath]; - - if (!(req.gs.local || debug || clearok.indexOf(fpath) >= 0)) { - return reply404(req, res); - } - - if (fileMap[fpath]) { - fpath = fileMap[fpath]; - } - - fs.stat(fpath, (err, f) => { - if (err || !f) { - return reply404(req, res); - } - - let mtime = f.mtime.getTime(); - - if (!cached || cached.mtime != mtime) { - if (debug) { - fs.readFile(fpath, null, function(err, code) { - if (err) { - return reply404(req,res); - } - serveCode(req, res, fileCache[fpath] = { - clear: true, - mtime: mtime, - code: code - }); - }); - return; - } else { - let start = new Date().getTime(), - code = minify(fpath), - end = new Date().getTime(); - - log({minify:fpath, in:f.size, out:code.length, time:(end-start)}); - - cached = fileCache[fpath] = { - clear: false, - mtime: mtime, - code: code - }; - } - } - - serveCode(req, res, cached); - }); -} - -/** - * @param {Object} req - * @param {Object} res - * @param {Function} next - * - * expects paths in the format "/code/[token]/the/rest/is/ignored" - */ -function handleCode(req, res, next) { - let key = req.gs.path.split('/')[2].split('.')[0], - ck = code_src[key], - js = code[key]; - - if (!js) { - return reply404(req, res); - } - - if (ck) { - let mod = lastmod(ck.path); - if (mod > ck.mod) { - if (debug) { - js = code[ck.endpoint] = fs.readFileSync(ck.path); - } else { - js = code[ck.endpoint] = minify(ck.path); - } - ck.mod = mod; - } - } - - addCorsHeaders(req, res); - serveCode(req, res, { - code: js, - mtime: startTime - }); -} - -function handleWasm(req, res, next) { - let [root, file] = req.gs.path.split('/').slice(1); - let ext = (file || '').split('.')[1]; - let path = `wasm/${file}`; - let mod = lastmod(path); - - if (root === 'wasm' && ext === 'wasm' && mod) { - let imd = ifModifiedDate(req); - if (imd && mod <= imd) { - res.writeHead(304, "Not Modified"); - return res.end(); - } - res.writeHead(200, { - 'Content-Type': 'application/wasm', - 'Cache-Control': 'public, max-age=600', - 'Last-Modified': new Date(mod).toGMTString(), - }); - res.end(fs.readFileSync(path)); - } else { - next(); - } -} - -function ifModifiedDate(req) { - let ims = req.headers['if-modified-since']; - if (ims) { - // because sys time has a higher resolution than - // seconds converted from IMS header. so give it - // an extra second - return new Date(ims).getTime() + 1000; - } - return 0; -} - -/** - * @param {Object} req - * @param {Object} res - * @param {Obejct} code - */ -function serveCode(req, res, code) { - if (code.deny) { - return reply404(req, res); - } - - let imd = ifModifiedDate(req); - if (imd && code.mtime <= imd && !code.nocache) { - res.writeHead(304, "Not Modified"); - res.end(); - return; - } - - let cacheControl = code.nocache ? - 'private, max-age=0' : - 'public, max-age=600'; - - res.writeHead(200, { - 'Content-Type': 'application/javascript', - 'Cache-Control': cacheControl, - 'Last-Modified': new Date(code.mtime).toGMTString(), - }); - res.end(code.code); -} - -/* ********************************************* - * Setup / Global - ********************************************* */ - -let debug = false, - nolocal = false, - procexit = false, - port = 8080, - args = process.argv.slice(2); - -args.forEach((arg, index) => { - switch (arg) { - case 'nolocal': nolocal = true; break; - case 'debug': debug = true; break; - case 'port': port = process.argv[index+3]; break; - } -}); - -let ver = require('../js/license.js'), - fs = require('fs'), - url = require('url'), - dns = require('dns'), - util = require('util'), - path = require('path'), - valid = require('validator'), - agent = require('express-useragent'), - spawn = require('child_process').spawn, - level = require('level')('./persist', {valueEncoding:"json"}), - https = require('https'), - moment = require('moment'), - uglify = require('uglify-es'), - connect = require('connect'), - serveStatic = require('serve-static'), - compression = require('compression')(), - querystring = require('querystring'), - ipLocal = nolocal ? [] : ["127.0.0.1", "::1", "::ffff:127.0.0.1"], - currentDir = process.cwd(), - ipSaveDelay = 2000, - startTime = time(), - codePrefix = "js/", - fileCache = {}, - fileMap = {}, - filters_fdm = [], - filters_sla = [], - filters_cam = [], - filters_laser = [], - modPaths = [], - ipCache = {}, - skip_minify = [ - "js/ext-three.js" - ], - script = { - kiri : [ - "ext-three", - "license", - "ext-clip2", - "ext-tween", - "ext-fsave", - "ext-earcut", - "add-array", - "add-three", - "geo", - "geo-debug", - "geo-render", - "geo-point", - "geo-slope", - "geo-line", - "geo-bounds", - "geo-polygon", - "geo-polygons", - "geo-gyroid", - "moto-kv", - "moto-ajax", - "moto-ctrl", - "moto-space", - "moto-load-stl", - "moto-db", - "moto-ui", - "kiri-icons", - "kiri-lang", - "kiri-lang-en", - "kiri-fill", - "kiri-db", - "kiri-slice", - "kiri-slicer", - "kiri-driver-fdm", - "kiri-driver-sla", - "kiri-driver-cam", - "kiri-driver-laser", - "kiri-pack", - "kiri-layer", - "kiri-widget", - "kiri-print", - "kiri-codec", - "kiri-work", - "kiri-conf", - "kiri", - "kiri-init", - "kiri-export" - ], - meta : [ - "ext-three", - "license", - "ext-tween", - "ext-fsave", - "ext-earcut", - "add-array", - "add-three", - "geo", - "geo-debug", - "geo-render", - "geo-point", - "geo-slope", - "geo-line", - "geo-bounds", - "geo-polygon", - "geo-polygons", - "geo-gyroid", - "kiri-layer", - "moto-kv", - "moto-ajax", - "moto-ctrl", - "moto-space", - "moto-load-stl", - "moto-db", - "moto-ui", - "kiri-db", - "meta" - ], - work : [ - "ext-three", - "ext-pngjs", - "license", - "ext-clip2", - "add-array", - "add-three", - "add-class", - "geo", - // "geo-wasm", - "geo-debug", - "geo-point", - "geo-slope", - "geo-line", - "geo-bounds", - "geo-polygon", - "geo-polygons", - "geo-gyroid", - "kiri-fill", - "kiri-slice", - "kiri-slicer", - "kiri-driver-fdm", - "kiri-driver-sla", - "kiri-driver-cam", - "kiri-driver-laser", - "kiri-pack", - "kiri-widget", - "kiri-print", - "kiri-codec" - ], - worker : [ - "kiri-worker" - ] - }, - code_src = {}, - code = {}, - WS = require('ws'), - wss = new WS.Server({ noServer: true }), - wss_roots = {}, - loads = [], - exits = [], - mods = {}, - logger = open_logger({dir: ".log-main"}) - ; - -/* ********************************************* - * Promises-based leveldb interface - ********************************************* */ -const db = { - // -------- - key: arr => arr.join("/"), - // -------- - get: key => { - if (Array.isArray(key)) key = db.key(key); - return promise((resolve,reject) => { - level.get(key,(err,record) => { - resolve(record,err); - }); - }); - }, - - // -------- - put: (key, value) => { - if (Array.isArray(key)) key = db.key(key); - return promise((resolve,reject) => { - level.put(key,value,(err) => { - if (err) reject(err); - else resolve(); - }); - }); - }, - // -------- - del: key => { - return promise((resolve,reject) => { - level.del(key, (err) => { - if (err) reject(err); - else resolve(); - }); - }); - } -}; - -/* ********************************************* - * REST API (extensible in modules) - ********************************************* */ -const api = { - - rateLimit: (req, res, next) => { - req.gs.iprec.api.push(time()); - - // allow 60 calls in 60 seconds - if (limit(req.gs.iprec.api, 60, 60000) && !req.gs.local) { - quickReply(res, 503, "rate limited"); - try { log({rate_limit_api: req.gs.ip, url: req.gs.url}); } catch (e) { helper.log(e) } - } else { - next(); - } - }, - - "filters-fdm": (req, res, next) => { - res.setHeader("Content-Type", "application/javascript"); - res.end(obj2string(filters_fdm)); - }, - - "filters-sla": (req, res, next) => { - res.setHeader("Content-Type", "application/javascript"); - res.end(obj2string(filters_sla)); - }, - - "filters-cam": (req, res, next) => { - res.setHeader("Content-Type", "application/javascript"); - res.end(obj2string(filters_cam)); - }, - - "filters-laser": (req, res, next) => { - res.setHeader("Content-Type", "application/javascript"); - res.end(obj2string(filters_laser)); - } - -}; - -/* ********************************************* - * Dispatch Helpers - ********************************************* */ - -// dispatch for path prefixs -function prepath(pre) { - - function handle(req, res, next) { - pre.uid = pre.uid || guid(); - req.ppi = req.ppi || {}; - - let path = req.gs.path, - key, fn, i = req.ppi[pre.uid] || 0; - - while (i < pre.length) { - key = pre[i][0]; - fn = pre[i++][1]; - if (path.indexOf(key) === 0) { - return fn(req, res, () => { - req.ppi[pre.uid] = i; - handle(req, res, next); - }); - } - } - - next(); - } - - return handle; -} - -// dispatch full fixed paths -function fullpath(map) { - return (req, res, next) => { - let fn = map[req.gs.path]; - if (fn) fn(req, res, next); - else next(); - }; -} - -// dispatch full paths based on a prefix and a function map -function fixedmap(prefix, map) { - return (req, res, next) => { - let path = req.gs.path; - if (path.indexOf(prefix) != 0) return next(); - let fn = map[path.substring(prefix.length)]; - if (fn) fn(req, res, next); - else next(); - }; -} - -// HTTP 302 redirect -function redir(path) { - return (req, res, next) => redirect(res, path); -} - -// mangle request path -function remap(path) { - return (req, res, next) => { - req.url = req.gs.path = path; - next(); - } -} - -// load module and call returned function with helper object -function initModule(file, dir) { - helper.log({module:file}); - require(file)({ - api: api, - adm: { - reload: prepareScripts - }, - const: { - args: args, - debug: debug, - script: script, - moddir: dir, - rootdir: currentDir, - version: ver.VERSION - }, - pkg: { - agent, - moment - }, - mod: mods, - util: { - log: helper.log, - time: time, - guid: guid, - mkdirs: mkdirs, - lastmod: lastmod, - obj2string: obj2string, - string2obj: string2obj, - getCookieValue: getCookieValue, - logger: open_logger - }, - db: { - api: db, - level: level - }, - inject: (code, file, options) => { - let opt = options || {}; - if (opt.end) { - script[code].push(dir + "/" + file); - } else { - script[code].splice(0, 0, dir + "/" + file); - } - }, - path: { - any: arg => { modPaths.push(arg) }, - pre: arg => { modPaths.push(prepath(arg)) }, - map: arg => { modPaths.push(fixedmap(arg)) }, - full: arg => { modPaths.push(fullpath(arg)) }, - static: (root, pre) => { modPaths.push(handleStatic(root, pre)) }, - code: (endpoint, path) => { - if (debug) { - code[endpoint] = fs.readFileSync(path); - } else { - code[endpoint] = minify(path); - } - code_src[endpoint] = { - endpoint, - path, - mod: lastmod(path) - }; - }, - redir: redir, - remap: remap - }, - handler: { - addCORS: addCorsHeaders, - static: handleStatic, - redirect: redirect, - reply404: reply404, - reply: quickReply - }, - ws: { - register: ws_register_root - }, - onload: (fn) => { - loads.push(fn); - }, - onexit: (fn) => { - exits.push(fn) - } - }); -} - -function handleStatic(root, pre) { - let statServe = serveStatic(root); - return function(req, res, next) { - if (pre) { - if (req.url.indexOf(pre) === 0) { - if (req.url === pre) { - req.url = "/"; - } else { - req.url = req.url.substring(pre.length); - } - return statServe(req, res, next); - } - return next(); - } - statServe(req, res, next); - }; -} - -// add static assets to be served -function addStatic(dir, pre) { - helper.log({static:dir}); - modPaths.push(handleStatic(dir, pre)); -} - -// either add module assets to path or require(init.js) -function loadModule(dir) { - if (dir.indexOf('node_modules') >= 0) { - return; - } - if (lastmod(dir + "/.ignore")) { - return; - } - const modjs = dir + "/init.js"; - lastmod(modjs) ? initModule(modjs, dir) : addStatic(dir); -} - -function ws_delete_root(path) { - ws_register_root(path); -} - -function ws_register_root(path, handler) { - if (handler) { - wss_roots[path] = handler; - } else { - delete wss_roots[path]; - } -} - -function open_logger(options) { - let opt = options || { - dir: "logs" - } - let exiting = false; - let logfile = null; - let logstream = null; - let pattern = opt.pattern || 'YY-MM-DD-HH'; - let last_pattern; - let count_min = opt.min || 1000; - let count = 0; - - try { - fs.mkdirSync(opt.dir); - } catch (e) { } - - // create file write stream - function open_file_stream() { - close_file_stream(); - logfile = path.join(opt.dir, last_pattern = moment().format(pattern)); - logstream = fs.createWriteStream(logfile, {flags: 'a'}); - let cur = path.join(opt.dir, "current"); - try { fs.unlinkSync(cur) } catch (e) { } - try { fs.symlinkSync(last_pattern, cur) } catch (e) { } - count = 0; - } - - function close_file_stream() { - if (logstream) { - logstream.end(); - logstream = null; - } - } - - function emit_log(obj) { - helper.log(obj); - emit(obj); - } - - function emit(obj) { - if (exiting) { - // console.log({dir: opt.dir, exiting_skip_log: obj}) - return; - } - let next_pattern = moment().format(pattern); - if (++count > count_min && next_pattern !== last_pattern) { - open_file_stream(); - } - let output = [moment().format('YYMMDD-HHmmss'),' ',JSON.stringify(obj),'\n'].join(''); - logstream.write(output); - } - - function exit() { - exiting = true; - close_file_stream(); - } - - open_file_stream(); - - exits.push(exit); - - return { emit, log: emit_log, close: close_file_stream }; -} - -function processLoad() { - while (loads.length) { - try { - loads.shift()(); - } catch (e) { - log({on_load_fail: e}); - } - } -} - -function processExit(code) { - if (procexit) { - return; - } - procexit = true; - logger.log({proc_exit: code, registered: exits.length, uptime: time() - startTime}); - while (exits.length) { - try { - exits.shift()(code); - } catch (e) { - log({on_exit_fail: e}); - } - } - setTimeout(() => { - process.exit(); - }, 500); -} - -/* ********************************************* - * Start it up - ********************************************* */ - -// load modules -lastmod("mod") && fs.readdirSync(currentDir + "/mod").forEach(dir => { - const fullpath = currentDir + "/mod/" + dir; - if (dir.charAt(0) === '.') return; - const stats = fs.lstatSync(fullpath); - if (!(stats.isDirectory() || stats.isSymbolicLink())) return; - loadModule(fullpath); -}); - -// create cache dir if missing -lastmod(".cache") || mkdirs([".cache"]); - -// precache responses -prepareScripts(); - -// create web handler chain -let handler = connect().use(setup); - -// add path handlers registered by modules -modPaths.forEach(fn => { - handler = handler.use(fn); -}); - -// add the rest of the handler chain -handler.use(fullpath({ - "/kiri/index.html" : redir("/kiri/"), - "/kiri" : redir("/kiri/"), - "/kiri/" : remap("/kiri/index.html"), - "/meta/index.html" : redir("/meta/"), - "/meta" : redir("/meta/"), - "/meta/" : remap("/meta/index.html") - })) - .use(prepath([ - [ "/space", redir("/meta/")], - [ "/api/", api.rateLimit ], - [ "/data/", handleData ], - [ "/code/", handleCode ], - [ "/wasm/", handleWasm ], - [ "/js/", handleJS ] - ])) - .use(handleDebug) - .use(fixedmap("/api/", api)) - .use(compression) - .use(rewrite) - .use(handleStatic(currentDir + "/web/")) - .use(reply404) - .listen(port) - .on('upgrade', (request, socket, head) => { - let handler = wss_roots[request.url]; - if (handler) { - wss.handleUpgrade(request, socket, head, ws => { - try { - handler(ws, request); - } catch (err) { - log({wss_handler_error: err}); - ws_delete_root(request.url); - socket.destroy(); - } - }); - } else { - socket.destroy(); - } - }); - -helper.log("------------------------------------------"); -helper.log({port, debug, nolocal, version: ver.VERSION}); -logger.emit({port, debug, nolocal, version: ver.VERSION}); - -process.on('beforeExit', processExit); - -process.on('exit', processExit); - -process.on('SIGINT', function(sig, code) { - logger.log({exit: code, signal: sig}); - processExit(code); -}); - -process.on('SIGHUP', function(sig, code) { - logger.log({exit: code, signal: sig}); - processExit(code); -}); - -process.on('unhandledRejection', (reason, p) => { - logger.log({unhandled_rejection: reason, promise: p}); -}); - -process.on('uncaughtException', (err) => { - logger.log({uncaught_exception: err}); -}); - -processLoad(); diff --git a/notes.md b/notes.md index 36ed0fdc..08a63444 100644 --- a/notes.md +++ b/notes.md @@ -44,7 +44,6 @@ * `B` linear finishing should extend beyond part boundaries by tool radius * `B` outside cutting direction in roughing mode inverted * `F` z bounded slices (extension of z bottom offset feature) -* `F` do not mill out thru-pockets. cut outline only. * `F` use arcs to connect hard angles * `F` do not rough areas that go all the way through the part https://github.com/GridSpace/grid-apps/issues/20 diff --git a/web/kiri/style.css b/web/kiri/index.css similarity index 62% rename from web/kiri/style.css rename to web/kiri/index.css index 9d31cc2e..10360edd 100644 --- a/web/kiri/style.css +++ b/web/kiri/index.css @@ -1,3 +1,456 @@ +a, a:hover, a:visited { + border: none; + outline: none; + color: inherit; + text-decoration: none; +} +body { + overflow: hidden; +} +body,div { + margin: 0px; + padding: 0px; + border: 0px; + font-weight: normal; + font-family: sans-serif; +} +label { + font-size: 14px; + margin-right: 2px; +} +canvas { + width: 100%; + height: 100%; + overflow: hidden; +} +button { + outline: none; + max-width: 25ex; + border: 1px solid #ccc; + border-radius: 3px; + background-color: #eee; + padding: 2px 7px 3px 7px; + margin-bottom: 2px !important; +} +button:hover { + background-color: #aaa; + color: black; +} +button[load] { + width: 100%; + text-align: left; +} +button[del] { + margin-left: 5px; +} +select { + outline: none; +} +th, tr, td, span, div, label, button { + user-select: none; +} +.noselect { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.title { + text-align:center; + border-radius: 4px; + border-bottom: 2px; + color: #222; + padding: 4px 5px 3px 5px; + margin-bottom: 2px; + background-color: rgba(170,221,255,0.75); + border: 1px solid rgba(255,255,255,0.5); + font-size: 12pt !important; + font-weight: bold !important; +} +.tablerow { + -webkit-flex-direction: row; + -webkit-justify-content: center; + -webkit-align-items: center; + display: -webkit-flex; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; +} +.tablerow button { + -webkit-flex-basis: auto; + -webkit-flex-grow: 1; + flex-grow: 1; + flex-basis: auto; + width: 33.33%; +} +.grouphead:first-of-type { + margin-top: 0; +} +.dark .grouphead { + background-color: rgba(100,120,150,0.75); + border: 1px solid #555; + color: #eee; + font-weight: normal; +} +.grouphead { + color: black; + background-color: rgba(170,221,255,0.7); + border: 1px solid #ccc; + border-radius: 3px; + margin-top: 5px; + margin-bottom: 3px; + padding: 3px 2px 2px 3px; + font-size: 12px; + font-weight: bold; + text-align: center; +} +.grouphead > a { + text-transform: uppercase; +} +.grouphead:hover { + cursor: default; +} +.ck_catalog { + padding-top: 2px; +} +.ck_catalog div { + -webkit-flex-direction: row; + -webkit-justify-content: center; + -webkit-align-items: center; + display: -webkit-flex; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + margin-bottom: 1px; +} +.ck_catalog button { + margin: 0px 0px 1px 2px !important; +} +.buton { + font-weight: bold; + background-color: #bbb; +} +.flow-row { + position: relative; + -webkit-flex-direction: row; + -webkit-justify-content: flex-end; + display: -webkit-flex; + display: flex; + flex-direction: row; + justify-content: flex-end; +} +.flow-row label { + -webkit-flex-basis: auto; + -webkit-flex-grow: 1; + flex-basis: auto; + flex-grow: 1; +} +.flow-col { + position: relative; + -webkit-flex-direction: column; + -webkit-justify-content: flex-start; + display: -webkit-flex; + display: flex; + flex-direction: column; + justify-content: flex-start; +} +.flow-left { + -webkit-justify-content: flex-start; + justify-content: flex-start; +} +.flow-grow { + -webkit-flex-grow: 1; + flex-grow: 1 +} +.flow-space-between { + -webkit-justify-content: space-between; + justify-content: space-between; +} + +:-moz-any(#control-left) { + overflow-x: hidden !important; + overflow-y: hidden !important; +} +:-moz-any(#control-right) { + overflow-x: scroll !important; + overflow-y: scroll !important; + margin-right: -14px !important; + margin-bottom: -14px !important; +} +.dark .control { + border: 0 !important; + background-color: rgba(255,255,255,0.5); +} +.dark .control button { + border: 1px solid rgba(128,128,128,0.5); +} +.control { + background-color: rgba(255,255,255,0.75); +} +.control label { + padding: 2px; +} +.control input { + background-color: #eee; + margin-bottom: 1px; + text-align: right; + border: 1px solid #bbb; +} +.control input:focus { + background-color: #fec; + outline: none; +} +.control input[disabled] { + background-color: #ccc; + color: #000; + border-width: 1px; +} +.control input[type="range"] { + width: 85px; + background-color: transparent; +} +.control select { + border: 1px solid #bbb; + background-color: #eee; + margin-bottom: 1px !important; + border-radius: 3px; +} +.control button { + margin: 1px; +} +.control .flow-row { + align-items: center; + justify-content: center; +} +.dark #appid span { + border-color: #555; + background-color: rgba(100,120,150,0.75); + color: #eee; +} +.dark #appid a { + color: #eee; +} +.dark #appid a:hover { + color: #eee; +} +.dark #appid span:hover { + background-color: rgba(80,100,230,0.75); +} +.dark #langpop { + color: black; +} +#appid { + text-align: center; + position: fixed; + width: 100%; + top: 3px; +} +#appid span { + border: 1px solid #ccc; + border-radius: 5px; + padding: 5px 8px 3px 8px; + background-color: rgba(170,221,255,0.75); + color: rgba(0,0,0,0.5); +} +#appid span:hover { + background-color: rgba(170,221,255,0.5); +} +#appid a { + color: rgba(0,0,0,0.75); + font-weight: bold; +} +#appid a:hover { + color: rgba(0,50,50,0.75); +} +#apphelp { + margin-left: 5px; + font-weight: bold; +} +#applang { + position: relative; + margin-right: 5px; + font-weight: bold; +} +#langpop { + display: none; + position: absolute; + white-space: nowrap; + top: 105%; + left: 0; + border-radius: 5px; + border: 1px solid #ddd; + background-color: rgba(255,255,255,0.75); + padding: 5px; +} +#langpop > div { + padding: 5px; +} +#langpop > div:hover { + background-color: rgba(255,128,128,0.5); + border-radius: 5px; +} +#applang:hover #langpop { + display: block; +} +#container { + width: 100%; + height: 100%; +} +#control > div:hover label { + text-decoration: underline; + text-decoration-color: #aaa; +} +#control-left { + display: none; + position: fixed; + z-index: 10000; + color: black; + top: 5px; + left: 0px; + padding: 5px; + border-top: 2px solid #ddd; + border-right: 2px solid #ddd; + border-bottom: 2px solid #ddd; + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; + overflow-x: visible; + overflow-y: scroll; +} +#control-right { + display: none; + position: fixed; + z-index: 10000; + top: 5px; + right: 0px; + bottom: 5px; + padding: 5px; + border-top: 2px solid #ddd; + border-left: 2px solid #ddd; + border-bottom: 2px solid #ddd; + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; + color: #222; + overflow-y: scroll; + overflow-x: visible; +} +.dark .compact .grouphead { + background-color: rgba(100,120,150,0.75); + border: 1px solid #555; + color: #eee; +} +.dark .compact .grouphead:hover { + background-color: rgba(80,100,230,0.75); +} +.dark .compact.control { + background-color: rgba(128,128,128,0.2) !important; +} +.compact { + overflow: visible !important; + bottom: auto !important; + border: 0 !important; + background-color: rgba(255,255,255,0.5) !important; +} +.compact .grouphead { + padding: 10px 5px 10px 5px !important; + background-color: rgba(170,221,255,0.25); +} +.compact .grouphead:hover { + background-color: rgba(170,221,255,0.9); +} +.compact .grouphead a { + font-size: larger !important; + margin: 5px !important; +} +.compact .flow-row label { + font-size: 18px !important; +} +.compact .flow-row input { + font-size: 18px !important; +} +.compact .flow-row select { + font-size: 18px !important; +} +.compact .flow-row { + margin: 3px 0 3px 0; +} +.compact .asym { + width: auto !important; +} +.compact button { + font-size: larger !important; + padding: 5px 10px 5px 10px; +} +:-moz-any(#control-left.compact) { + overflow-x: display !important; + overflow-y: display !important; +} +:-moz-any(#control-right.compact) { + overflow-x: display !important; + overflow-y: display !important; + margin-right: auto !important; + margin-bottom: auto !important; +} +#control-left::-webkit-scrollbar, #control-right::-webkit-scrollbar { + display: none; +} +.dark #control-left:hover, .dark #control-right:hover { + background-color: rgba(255,255,255,0.6); +} +#control-left:hover, #control-right:hover { + background-color: #ffffff; +} +#loading { + position: fixed; + top: 5px; + left: 20%; + right: 20%; + border: 1px solid white; + margin: 2px; + background-color: #ddd; + text-align: center; + display: none; +} +#modal { + display: none; + position: fixed; + z-index: 20000; + top: 0px; + left: 0px; + right: 0px; + bottom: 0px; + background-color: rgba(0,0,0,0); +} +#progress { + position: relative; + width: 1%; + height: 100%; + background-color: #f00; +} +#welcome { + color: white; + padding: 10px; + border: 10px solid white; + background-color: rgba(20,20,20,0.75); + position: fixed; + top: 50px; + left: 0; + right: 0; + bottom: 100px; + margin: 0 auto; + width: 50%; + max-width: 800px; + min-width: 500px; + display: block; + unicode-bidi: embed; + font-family: monospace; + white-space: pre; + overflow-y: scroll +} + input[type=range]::-moz-focus-outer { border: 0; } @@ -648,9 +1101,6 @@ button.selected { overflow: hidden; font-family: monospace; } - -/** file catalog */ - #import { width: 100%; } @@ -690,7 +1140,6 @@ button[import="1"] { text-overflow: ellipsis; } -/** saved settings list */ #settings button { margin: 1px; } @@ -700,9 +1149,6 @@ button[import="1"] { padding-bottom: 10px; border: 0; } - -/** layer slider and controls */ - #layer-view { display: none; position: fixed; diff --git a/web/kiri/index.html b/web/kiri/index.html index 1e3daeaf..4239bcc0 100644 --- a/web/kiri/index.html +++ b/web/kiri/index.html @@ -14,8 +14,7 @@ Kiri:Moto - - + diff --git a/web/moto/style.css b/web/meta/index.css similarity index 90% rename from web/moto/style.css rename to web/meta/index.css index 2d574022..ef406e25 100644 --- a/web/moto/style.css +++ b/web/meta/index.css @@ -464,3 +464,58 @@ th, tr, td, span, div, label, button { white-space: pre; overflow-y: scroll } + +.ck_space button { + margin: 2px 0px 1px 2px !important; +} +.ck_spaces { + padding-top: 2px; +} +.ck_spaces div { + -webkit-flex-direction: row; + -webkit-justify-content: flex-start; + -webkit-align-items: center; + display: -webkit-flex; + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + margin-bottom: 1px; +} +.ck_spaces button { + margin: 0px 0px 1px 2px !important; +} +.ck_library { + padding-top: 2px; +} +.ck_library div { + -webkit-flex-direction: row; + -webkit-justify-content: flex-start; + -webkit-align-items: center; + display: -webkit-flex; + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + margin-bottom: 1px; +} +.ck_library button { + margin: 0px 0px 1px 2px !important; +} + +button.load { + -webkit-flex-basis: 1px; + -webkit-flex-grow: 1; + flex-basis: 1px; + flex-grow: 1; + text-align: left; +} +button.del { +} + +#dropbutton { + width: 100%; + height: 50px; + background-color: #ccc; + border-width: 1px; +} diff --git a/web/meta/index.html b/web/meta/index.html index c63e5baa..cd6491c9 100644 --- a/web/meta/index.html +++ b/web/meta/index.html @@ -10,8 +10,7 @@ Meta:Moto - - + diff --git a/web/meta/style.css b/web/meta/style.css deleted file mode 100644 index 9bf0869c..00000000 --- a/web/meta/style.css +++ /dev/null @@ -1,54 +0,0 @@ -.ck_space button { - margin: 2px 0px 1px 2px !important; -} -.ck_spaces { - padding-top: 2px; -} -.ck_spaces div { - -webkit-flex-direction: row; - -webkit-justify-content: flex-start; - -webkit-align-items: center; - display: -webkit-flex; - display: flex; - flex-direction: row; - justify-content: flex-start; - align-items: center; - margin-bottom: 1px; -} -.ck_spaces button { - margin: 0px 0px 1px 2px !important; -} -.ck_library { - padding-top: 2px; -} -.ck_library div { - -webkit-flex-direction: row; - -webkit-justify-content: flex-start; - -webkit-align-items: center; - display: -webkit-flex; - display: flex; - flex-direction: row; - justify-content: flex-start; - align-items: center; - margin-bottom: 1px; -} -.ck_library button { - margin: 0px 0px 1px 2px !important; -} - -button.load { - -webkit-flex-basis: 1px; - -webkit-flex-grow: 1; - flex-basis: 1px; - flex-grow: 1; - text-align: left; -} -button.del { -} - -#dropbutton { - width: 100%; - height: 50px; - background-color: #ccc; - border-width: 1px; -}