diff --git a/src/main/gapp.js b/src/main/gapp.js index 3962a46e..7d07a8b5 100644 --- a/src/main/gapp.js +++ b/src/main/gapp.js @@ -43,7 +43,9 @@ let dbug = self.dbug = self.dbug || { error: function() { if (dbug.level <= 3) return console.trace(since(),'[ERR]', ...arguments); - } + }, + + since }; /** empty function. countless possible uses **/ diff --git a/src/main/mesh.js b/src/main/mesh.js index 31c5432f..bd55f2b6 100644 --- a/src/main/mesh.js +++ b/src/main/mesh.js @@ -71,6 +71,7 @@ function init() { // restore space layout and view from previous session function restore_space() { + mesh.api.log.emit(`restoring workspace`); let space = moto.Space; mesh.db.admin.get("camera") .then(saved => { @@ -142,7 +143,7 @@ function space_init(data) { case 'KeyH': space.view.home(); break; - case 'KeyF': + case 'KeyR': api.tool.repair(); break; case 'KeyT': @@ -256,7 +257,7 @@ function space_init(data) { } function load_files(files) { - // show spinner + mesh.api.log.emit(`loading file...`); load.File.load([...files]) .then(data => { call.space_load(data); @@ -265,7 +266,7 @@ function load_files(files) { dbug.error(error); }) .finally(() => { - // hide spinner + mesh.api.log.hide(); }); } @@ -281,7 +282,7 @@ function space_load(data) { // on debug key press function space_debug() { for (let g of mesh.api.group.list()) { - let { center, size } = g.bounds(); + let { center, size } = g.bounds; console.group(g.id); console.log({ center, diff --git a/src/mesh/api.js b/src/mesh/api.js index faaab28d..d186c0c5 100644 --- a/src/mesh/api.js +++ b/src/mesh/api.js @@ -169,7 +169,7 @@ let selection = { }, focus() { - api.focus(selected); + api.focus(selection.list()); }, bounds() { @@ -228,7 +228,7 @@ let tool = { }, repair() { - api.modal.spin(true, 250); + api.log.emit('repairing mesh(es)...').pin(); for (let m of selection.models()) { moto.client.fn.model_heal(m.id).then(data => { if (data) { @@ -238,7 +238,7 @@ let tool = { data.normals ); } - api.modal.spin(false); + api.log.emit('repair complete').unpin(); }); } } diff --git a/src/mesh/build.js b/src/mesh/build.js index 695e45b9..ec705e3b 100644 --- a/src/mesh/build.js +++ b/src/mesh/build.js @@ -69,6 +69,59 @@ let modal = mesh.api.modal = { } }; +// transient logging window bottom/left +let log = mesh.api.log = { + age: 10000, // age out lines more than 10 seconds old + + data: [], // last n messages + + wait: 3000, // time before windows closes + + lines: 20, // max log lines before forced-age out + + emit(msg) { + let { age, data, lines, render } = log; + let now = Date.now(); + data.push({ + text: `${dbug.since()} | ${msg}`, + time: now + }); + while (data.length && (data.length > lines || now - data[0].time > age)) { + data.shift(); + } + return render(); + }, + + hide() { + $('logger').style.display = 'none'; + return log; + }, + + pin() { + return log.show(1 << 30); + }, + + unpin() { + return log.show(); + }, + + // show log window for `time` milliseconds or default + show(time) { + clearTimeout(log.timer); + log.timer = setTimeout(log.hide, time || log.wait); + return log; + }, + + // re-render and show current log messages + render() { + let area = $('logtext'); + h.bind(area, log.data.map(rec => h.div({ _: rec.text }))); + area.scrollTop = area.scrollHeight; + $('logger').style.display = 'flex'; + return log.show(); + }, +}; + // create html elements function ui_build() { // set app version @@ -97,11 +150,15 @@ function ui_build() { h.div({ id: 'actions' }), h.div({ id: 'grouplist'}), h.div({ id: 'selectlist'}), + h.div({ id: 'logger', onmouseover() { log.show() } }) ]); - let { actions, grouplist, selectlist } = bound; + let { actions, grouplist, selectlist, logger } = bound; let { api, util } = mesh; + // create slid in/out logging window + h.bind(logger, [ h.div({ id: 'logtext' }) ]); + // create hotkey/action menu (top/left) h.bind(actions, [ h.div([ diff --git a/src/mesh/model.js b/src/mesh/model.js index 2cf34cf2..a23cafee 100644 --- a/src/mesh/model.js +++ b/src/mesh/model.js @@ -84,7 +84,7 @@ mesh.model = class MeshModel extends mesh.object { // for debugging matrix ops return mesh.api.group.new([new mesh.model({ file: `synth-${this.name}`, - mesh: data[0] + mesh: data })]); }); } diff --git a/web/mesh/index.css b/web/mesh/index.css index cd6a9343..93ac607d 100644 --- a/web/mesh/index.css +++ b/web/mesh/index.css @@ -160,7 +160,7 @@ button:hover { /** modal dialogs **/ #modal_page { - background-color: rgba(0,0,0,0.5); + background-color: rgba(0,0,0,0.25); justify-content: center; align-items: center; position: absolute; @@ -231,7 +231,7 @@ button:hover { /** common child look & feel */ -#modal_frame, #actions > div, #grouplist > div { +#logger, #modal_frame, #actions > div, #grouplist > div { border: 1px solid gray; border-radius: 3px; margin: 0 0 2px 0; @@ -243,6 +243,43 @@ button:hover { border-bottom: 1px dashed gray; } +/** slide in/out logging window **/ + +#logger { + display: none; + color: #88ff88; + min-width: 300px; + flex-direction: column; + background-color: rgba(0,0,0,0.5); + position: absolute; + margin: 0 0 3px 3px; + bottom: 0; + left: 0; +} + +#logtext { + flex-direction: column; + font-family: monospace; + font-size: smaller; + max-height: 5em; + min-height: 1em; + overflow-y: auto; +} + +#logtext::-webkit-scrollbar { + width: 6px; +} + +#logtext::-webkit-scrollbar-track { + background: transparent; +} + +#logtext::-webkit-scrollbar-thumb { + border-radius: 3px; + background-color: #ddd; + border: 3px solid rgba(255,255,255,0.1); +} + /** action and keyboard menu **/ #actions {