diff --git a/package.json b/package.json index 6dced782..65e77421 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "grid-apps", - "version": "4.5.0", + "version": "4.6.0", "description": "grid.space 3d slicing & modeling tools", "author": "Stewart Allen ", "license": "MIT", diff --git a/src/kiri/core/init.js b/src/kiri/core/init.js index ce3ea20f..0ac072b5 100644 --- a/src/kiri/core/init.js +++ b/src/kiri/core/init.js @@ -4,6 +4,7 @@ import { $, $c } from '../../moto/webui.js'; import { api } from './api.js'; import { base } from '../../geo/base.js'; import { local as sdb } from '../../data/local.js'; +import { slider } from './slider.js'; import { space } from '../../moto/space.js'; import { LOCAL, SETUP } from './main.js'; import { VIEWS, MODES, SEED } from './consts.js'; @@ -20,6 +21,7 @@ import { menu as menuCAM } from '../mode/cam/init-menu.js'; import { menu as menuFDM } from '../mode/fdm/init-menu.js'; import { menu as menuLaser } from '../mode/laser/init-menu.js'; import { menu as menuSLA } from '../mode/sla/init-menu.js'; +import STACKS from './stacks.js'; import './tools.js'; let { CAM, SLA, FDM, LASER, DRAG, WJET, WEDM } = MODES, @@ -304,14 +306,20 @@ function keyDownHandler(evt) { break; case 38: // up arrow if (inputHasFocus()) return false; - if (evt.metaKey) return api.show.layer(api.var.layer_at+1); + if (evt.metaKey) { + const { hi } = slider.getRange(); + return api.show.layer(hi + 1); + } if (deg) selection.rotate(deg, 0, 0); if (move > 0) selection.move(0, move, 0); evt.preventDefault(); break; case 40: // down arrow if (inputHasFocus()) return false; - if (evt.metaKey) return api.show.layer(api.var.layer_at-1); + if (evt.metaKey) { + const { hi } = slider.getRange(); + return api.show.layer(hi - 1); + } if (deg) selection.rotate(-deg, 0, 0); if (move > 0) selection.move(0, -move, 0); evt.preventDefault(); @@ -364,16 +372,56 @@ function keyHandler(evt) { } switch (evt.charCode) { case cca('`'): api.show.slices(0); break; - case cca('0'): api.show.slices(api.var.layer_max); break; - case cca('1'): api.show.slices(api.var.layer_max/10); break; - case cca('2'): api.show.slices(api.var.layer_max*2/10); break; - case cca('3'): api.show.slices(api.var.layer_max*3/10); break; - case cca('4'): api.show.slices(api.var.layer_max*4/10); break; - case cca('5'): api.show.slices(api.var.layer_max*5/10); break; - case cca('6'): api.show.slices(api.var.layer_max*6/10); break; - case cca('7'): api.show.slices(api.var.layer_max*7/10); break; - case cca('8'): api.show.slices(api.var.layer_max*8/10); break; - case cca('9'): api.show.slices(api.var.layer_max*9/10); break; + case cca('0'): { + const { max } = slider.getRange(); + api.show.slices(max); + break; + } + case cca('1'): { + const { max } = slider.getRange(); + api.show.slices(max/10); + break; + } + case cca('2'): { + const { max } = slider.getRange(); + api.show.slices(max*2/10); + break; + } + case cca('3'): { + const { max } = slider.getRange(); + api.show.slices(max*3/10); + break; + } + case cca('4'): { + const { max } = slider.getRange(); + api.show.slices(max*4/10); + break; + } + case cca('5'): { + const { max } = slider.getRange(); + api.show.slices(max*5/10); + break; + } + case cca('6'): { + const { max } = slider.getRange(); + api.show.slices(max*6/10); + break; + } + case cca('7'): { + const { max } = slider.getRange(); + api.show.slices(max*7/10); + break; + } + case cca('8'): { + const { max } = slider.getRange(); + api.show.slices(max*8/10); + break; + } + case cca('9'): { + const { max } = slider.getRange(); + api.show.slices(max*9/10); + break; + } case cca('?'): api.help.show(); break; @@ -434,10 +482,11 @@ function keyHandler(evt) { if (api.view.get() === VIEWS.ARRANGE) { api.space.set_focus(selection.widgets()); } - if (api.var.layer_hi == api.var.layer_lo) { - api.var.layer_lo = 0; + const { lo, hi } = slider.getRange(); + if (hi === lo) { + slider.setRange(0, hi); } else { - api.var.layer_lo = api.var.layer_hi; + slider.setRange(hi, hi); } api.show.slices(); break; @@ -804,25 +853,28 @@ function init_one() { space.sky.setColor(controller.dark ? 0 : 0xffffff); space.setAntiAlias(controller.antiAlias); space.init(container, function (delta) { - let vars = api.var; - if (vars.layer_max === 0 || !delta) return; + const { lo, hi, max } = slider.getRange(); + if (max === 0 || !delta) return; if (controller.reverseZoom) delta = -delta; - let same = vars.layer_hi === vars.layer_lo; - let track = vars.layer_lo > 0; + let same = hi === lo; + let track = lo > 0; + let newHi = hi; + let newLo = lo; if (delta > 0) { - vars.layer_hi = Math.max(same ? 0 : vars.layer_lo, vars.layer_hi - 1); + newHi = Math.max(same ? 0 : lo, hi - 1); if (track) { - vars.layer_lo = Math.max(0, vars.layer_lo - 1); + newLo = Math.max(0, lo - 1); } } else if (delta < 0) { - vars.layer_hi = Math.min(vars.layer_max, vars.layer_hi + 1); + newHi = Math.min(max, hi + 1); if (track) { - vars.layer_lo = Math.min(vars.layer_hi, vars.layer_lo + 1); + newLo = Math.min(newHi, lo + 1); } } if (same) { - vars.layer_lo = vars.layer_hi; + newLo = newHi; } + slider.setRange(newLo, newHi); view.update_slider(); show.slices(); }, controller.ortho); @@ -1086,148 +1138,45 @@ function init_one() { }; // slider setup - const mobile = space.info.mob; - const slbar = mobile ? 80 : 30; - const slbar2 = slbar * 2; - const slider = ui.sliderRange; - const drag = { }; + slider.init({ + ui: { + layers: ui.layers, + slider: ui.slider, + sliderRange: ui.sliderRange, + sliderHold: ui.sliderHold, + sliderMid: ui.sliderMid, + sliderLo: ui.sliderLo, + sliderHi: ui.sliderHi, + sliderMin: ui.sliderMin, + sliderMax: ui.sliderMax, + sliderZero: $('slider-zero'), + }, + tracker: tracker, + mobile: space.info.mob, + onLayerChange: (hi, lo) => { + api.show.layer(hi, lo); + }, + onStackUpdate: (lo, hi) => { + STACKS.setRange(lo, hi); + }, + onSceneUpdate: () => { + space.scene.active(); + } + }); - if (mobile) { - ui.slider.classList.add('slider-mobile'); - ui.sliderLo.classList.add('slider-mobile'); - ui.sliderHi.classList.add('slider-mobile'); - // add css style for mobile devices + // expose slider to API + api.slider = slider; + + // add mobile class to body if needed + if (space.info.mob) { DOC.body.classList.add('mobile'); } - function pxToInt(txt) { - return txt ? parseInt(txt.substring(0,txt.length-2)) : 0; - } - - function sliderUpdate() { - let start = drag.low / drag.maxval; - let end = (drag.low + drag.mid - slbar) / drag.maxval; - api.event.emit('slider.pos', { start, end }); - api.var.layer_lo = Math.round(start * api.var.layer_max); - api.var.layer_hi = Math.round(end * api.var.layer_max); - api.show.layer(); - space.scene.active(); - } - - function dragit(el, delta) { - el.ontouchstart = el.onmousedown = (ev) => { - // el.classList.add('sli-drag-el'); - tracker.style.display = 'block'; - ev.stopPropagation(); - let obj = (ev.touches ? ev.touches[0] : ev); - drag.width = slider.clientWidth; - drag.maxval = drag.width - slbar2; - drag.start = obj.screenX; - drag.loat = drag.low = pxToInt(ui.sliderHold.style.marginLeft); - drag.mdat = drag.mid = ui.sliderMid.clientWidth; - drag.hiat = pxToInt(ui.sliderHold.style.marginRight); - drag.mdmax = drag.width - slbar - drag.loat; - drag.himax = drag.width - slbar - drag.mdat; - let cancel_drag = tracker.ontouchend = tracker.onmouseup = (ev) => { - // el.classList.remove('sli-drag-el'); - if (ev) { - ev.stopPropagation(); - ev.preventDefault(); - } - slider.onmousemove = undefined; - tracker.style.display = 'none'; - }; - el.ontouchend = cancel_drag; - el.ontouchmove = tracker.ontouchmove = tracker.onmousemove = (ev) => { - ev.stopPropagation(); - ev.preventDefault(); - if (ev.buttons === 0) { - return cancel_drag(); - } - if (delta) { - let obj = (ev.touches ? ev.touches[0] : ev); - delta(obj.screenX - drag.start); - } - }; - }; - } - - dragit(ui.sliderLo, (delta) => { - let midval = drag.mdat - delta; - let lowval = drag.loat + delta; - if (midval < slbar || lowval < 0) { - return; - } - ui.sliderHold.style.marginLeft = `${lowval}px`; - ui.sliderMid.style.width = `${midval}px`; - drag.low = lowval; - drag.mid = midval; - sliderUpdate(); - }); - dragit(ui.sliderMid, (delta) => { - let loval = drag.loat + delta; - let hival = drag.hiat - delta; - if (loval < 0 || hival < 0) return; - ui.sliderHold.style.marginLeft = `${loval}px`; - ui.sliderHold.style.marginRight = `${hival}px`; - drag.low = loval; - sliderUpdate(); - }); - dragit(ui.sliderHi, (delta) => { - let midval = drag.mdat + delta; - let hival = drag.hiat - delta; - if (midval < slbar || midval > drag.mdmax || hival < 0) return; - ui.sliderMid.style.width = `${midval}px`; - ui.sliderHold.style.marginRight = `${hival}px`; - drag.mid = midval; - sliderUpdate(); - }); - ui.load.onchange = function(event) { api.platform.load_files(event.target.files); ui.load.value = ''; // reset so you can re-import the same filee }; - ui.sliderMin.onclick = () => { - api.show.layer(0,0); - } - - ui.sliderMax.onclick = () => { - api.show.layer(api.var.layer_max,0); - } - - ui.slider.onmouseover = (ev) => { - api.event.emit('slider.label'); - }; - - ui.slider.onmouseleave = (ev) => { - if (!ev.buttons) api.event.emit('slider.unlabel'); - }; - - api.event.on('slider.unlabel', (values) => { - }); - - api.event.on('slider.label', (values) => { - let digits = api.var.layer_max.toString().length; - $('slider-zero').style.width = `${digits}em`; - $('slider-max').style.width = `${digits}em`; - $('slider-zero').innerText = api.var.layer_lo; - $('slider-max').innerText = api.var.layer_hi; - }); - - api.event.on('slider.set', (values) => { - let width = slider.clientWidth; - let maxval = width - slbar2; - let start = Math.max(0, Math.min(1, values.start)); - let end = Math.max(start, Math.min(1, values.end)); - let lowval = start * maxval; - let midval = ((end - start) * maxval) + slbar; - let hival = maxval - end * maxval; - ui.sliderHold.style.marginLeft = `${lowval}px`; - ui.sliderMid.style.width = `${midval}px`; - ui.sliderHold.style.marginRight = `${hival}px`; - }); - // store layer preferences api.event.on('stack.show', label => { let mode = api.mode.get(); diff --git a/src/kiri/core/main.js b/src/kiri/core/main.js index 8c01ec0e..d222094e 100644 --- a/src/kiri/core/main.js +++ b/src/kiri/core/main.js @@ -314,20 +314,21 @@ function updateSpeeds(maxSpeed, minSpeed) { } function updateSlider() { - api.event.emit("slider.set", { - start: (api.var.layer_lo / api.var.layer_max), - end: (api.var.layer_hi / api.var.layer_max) - }); + const { lo, hi, max } = api.slider.getRange(); + if (max > 0) { + api.slider.updatePosition(lo / max, hi / max); + } api.conf.update_fields_from_range(); } function setVisibleLayer(h, l) { - h = h >= 0 ? h : api.var.layer_hi; - l = l >= 0 ? l : api.var.layer_lo; - api.var.layer_hi = bound(h, 0, api.var.layer_max); - api.var.layer_lo = bound(l, 0, h); - api.event.emit("slider.label"); - updateSlider(); + const { lo, hi, max } = api.slider.getRange(); + h = h >= 0 ? h : hi; + l = l >= 0 ? l : lo; + const newHi = bound(h, 0, max); + const newLo = bound(l, 0, newHi); + api.slider.setRange(newLo, newHi); + api.slider.showLabels(); showSlices(); } @@ -349,11 +350,11 @@ function setEdges(bool) { function updateSliderMax(set) { let max = STACKS.getRange().tallest - 1; - api.var.layer_max = api.ui.sliderMax.innerText = max; - if (set || max < api.var.layer_hi) { - api.var.layer_hi = api.var.layer_max; - api.event.emit("slider.label"); - updateSlider(); + api.slider.setMax(max); + const { hi } = api.slider.getRange(); + if (set || max < hi) { + api.slider.setRange(max, max); + api.slider.showLabels(); } } @@ -387,31 +388,31 @@ function showSlices(layer) { showSlider(); + const { lo, hi, max } = api.slider.getRange(); + if (typeof(layer) === 'string' || typeof(layer) === 'number') { layer = parseInt(layer); } else { - layer = api.var.layer_hi; + layer = hi; } - layer = bound(layer, 0, api.var.layer_max); - if (layer < api.var.layer_lo) api.var.layer_lo = layer; - api.var.layer_hi = layer; - api.event.emit("slider.label"); + layer = bound(layer, 0, max); + const newLo = layer < lo ? layer : lo; + api.slider.setRange(newLo, layer); + api.slider.showLabels(); updateSlider(); - STACKS.setRange(api.var.layer_lo, api.var.layer_hi); + // STACKS.setRange is called by slider callback SPACE.update(); } function showSlider() { - api.ui.layers.style.display = 'flex'; - api.ui.slider.style.display = 'flex'; + api.slider.show(); } function hideSlider() { - api.ui.layers.style.display = 'none'; - api.ui.slider.style.display = 'none'; + api.slider.hide(); api.ui.speeds.style.display = 'none'; } diff --git a/src/kiri/core/settings.js b/src/kiri/core/settings.js index 7630bad7..51bb27e2 100644 --- a/src/kiri/core/settings.js +++ b/src/kiri/core/settings.js @@ -115,7 +115,7 @@ function updateSettings(opt = {}) { if (settings.mode === 'FDM' && api.view.is_slice()) { let changes = {}; let values = process; - let { layer_lo, layer_hi, layer_max } = api.var; + const { lo: layer_lo, hi: layer_hi, max: layer_max } = api.slider.getRange(); let range = { lo: layer_lo, hi: layer_hi }; let add = false; if (layer_lo > 0 || layer_hi < layer_max) { @@ -450,8 +450,8 @@ function updateFieldsFromRange() { let match = 0; let values = {}; let restores = Object.clone(overrides); - let { layer_lo, layer_hi } = api.var; - for (let range of getOverlappingRanges(api.var.layer_lo, api.var.layer_hi)) { + const { lo: layer_lo, hi: layer_hi } = api.slider.getRange(); + for (let range of getOverlappingRanges(layer_lo, layer_hi)) { for (let key of Object.keys(range.fields)) { values[key] = range.fields[key]; overrides[key] = settings.process[key]; diff --git a/src/kiri/core/slider.js b/src/kiri/core/slider.js new file mode 100644 index 00000000..d7db613f --- /dev/null +++ b/src/kiri/core/slider.js @@ -0,0 +1,302 @@ +/** Copyright Stewart Allen -- All Rights Reserved */ + +/** + * Layer Slider Controller (Singleton) + * Manages layer range selection and visualization for slice/preview views + */ + +class SliderControl { + #layerLo = 0; + #layerHi = 0; + #layerMax = 0; + #ui; + #tracker; + #mobile; + #sliderBar; + #sliderBar2; + #drag = {}; + + // Callbacks to replace event emissions + #onLayerChange; + #onStackUpdate; + #onSceneUpdate; + + #initialized = false; + + /** + * Initialize the slider with configuration + * @param {Object} config + * @param {Object} config.ui - UI element references + * @param {HTMLElement} config.tracker - Drag tracker element + * @param {boolean} config.mobile - Mobile device flag + * @param {Function} config.onLayerChange - Called when layer range changes + * @param {Function} config.onStackUpdate - Called to update stack visibility + * @param {Function} config.onSceneUpdate - Called to refresh scene + */ + init({ ui, tracker, mobile, onLayerChange, onStackUpdate, onSceneUpdate }) { + if (this.#initialized) { + throw new Error('Slider already initialized'); + } + + this.#ui = ui; + this.#tracker = tracker; + this.#mobile = mobile; + this.#sliderBar = mobile ? 80 : 30; + this.#sliderBar2 = this.#sliderBar * 2; + this.#onLayerChange = onLayerChange; + this.#onStackUpdate = onStackUpdate; + this.#onSceneUpdate = onSceneUpdate; + + this.#setupUI(); + this.#bindEvents(); + this.#initialized = true; + } + + /** + * Get current layer range + * @returns {{lo: number, hi: number, max: number}} + */ + getRange() { + return { + lo: this.#layerLo, + hi: this.#layerHi, + max: this.#layerMax + }; + } + + /** + * Set layer range (bounds are enforced) + * @param {number} lo - Lower layer + * @param {number} hi - Upper layer + */ + setRange(lo, hi) { + this.#layerLo = Math.max(0, Math.min(hi, lo)); + this.#layerHi = Math.max(this.#layerLo, Math.min(this.#layerMax, hi)); + this.#updateVisuals(); + this.showLabels(); + this.#notifyChange(); + } + + /** + * Set maximum layer count + * @param {number} max - Maximum layer value + */ + setMax(max) { + this.#layerMax = max; + this.#ui.sliderMax.innerText = max; + if (max < this.#layerHi) { + this.#layerHi = max; + this.showLabels(); + this.#updateVisuals(); + } + } + + /** + * Update slider visual position from normalized values + * @param {number} startPercent - Start position (0-1) + * @param {number} endPercent - End position (0-1) + */ + updatePosition(startPercent, endPercent) { + const width = this.#ui.sliderRange.clientWidth; + const maxval = width - this.#sliderBar2; + const start = Math.max(0, Math.min(1, startPercent)); + const end = Math.max(start, Math.min(1, endPercent)); + + const lowval = start * maxval; + const midval = ((end - start) * maxval) + this.#sliderBar; + const hival = maxval - end * maxval; + + this.#ui.sliderHold.style.marginLeft = `${lowval}px`; + this.#ui.sliderMid.style.width = `${midval}px`; + this.#ui.sliderHold.style.marginRight = `${hival}px`; + } + + /** + * Show layer number labels + */ + showLabels() { + const digits = this.#layerMax.toString().length; + this.#ui.sliderZero.style.width = `${digits}em`; + this.#ui.sliderMax.style.width = `${digits}em`; + this.#ui.sliderZero.innerText = this.#layerLo; + this.#ui.sliderMax.innerText = this.#layerHi; + } + + /** + * Show slider UI + */ + show() { + this.#ui.layers.style.display = 'flex'; + this.#ui.slider.style.display = 'flex'; + } + + /** + * Hide slider UI + */ + hide() { + this.#ui.layers.style.display = 'none'; + this.#ui.slider.style.display = 'none'; + } + + #setupUI() { + if (this.#mobile) { + this.#ui.slider.classList.add('slider-mobile'); + this.#ui.sliderLo.classList.add('slider-mobile'); + this.#ui.sliderHi.classList.add('slider-mobile'); + } + } + + #bindEvents() { + // Min/Max buttons + this.#ui.sliderMin.onclick = () => { + this.#layerLo = 0; + this.#layerHi = 0; + this.showLabels(); + this.#updateVisuals(); + this.#notifyChange(); + }; + + this.#ui.sliderMax.onclick = () => { + this.#layerLo = this.#layerMax; + this.#layerHi = this.#layerMax; + this.showLabels(); + this.#updateVisuals(); + this.#notifyChange(); + }; + + // Mouse hover for labels + this.#ui.slider.onmouseover = () => { + this.showLabels(); + }; + + this.#ui.slider.onmouseleave = (ev) => { + if (!ev.buttons) { + this.hideLabels(); + } + }; + + // Drag handlers for the three slider sections + this.#bindDrag(this.#ui.sliderLo, this.#handleLoDrag.bind(this)); + this.#bindDrag(this.#ui.sliderMid, this.#handleMidDrag.bind(this)); + this.#bindDrag(this.#ui.sliderHi, this.#handleHiDrag.bind(this)); + } + + #bindDrag(el, deltaHandler) { + const slider = this.#ui.sliderRange; + + el.ontouchstart = el.onmousedown = (ev) => { + this.#tracker.style.display = 'block'; + ev.stopPropagation(); + + let obj = (ev.touches ? ev.touches[0] : ev); + this.#drag.width = slider.clientWidth; + this.#drag.maxval = this.#drag.width - this.#sliderBar2; + this.#drag.start = obj.screenX; + this.#drag.loat = this.#drag.low = this.#pxToInt(this.#ui.sliderHold.style.marginLeft); + this.#drag.mdat = this.#drag.mid = this.#ui.sliderMid.clientWidth; + this.#drag.hiat = this.#pxToInt(this.#ui.sliderHold.style.marginRight); + this.#drag.mdmax = this.#drag.width - this.#sliderBar - this.#drag.loat; + this.#drag.himax = this.#drag.width - this.#sliderBar - this.#drag.mdat; + + const cancelDrag = this.#tracker.ontouchend = this.#tracker.onmouseup = (ev) => { + if (ev) { + ev.stopPropagation(); + ev.preventDefault(); + } + slider.onmousemove = undefined; + this.#tracker.style.display = 'none'; + }; + + el.ontouchend = cancelDrag; + el.ontouchmove = this.#tracker.ontouchmove = this.#tracker.onmousemove = (ev) => { + ev.stopPropagation(); + ev.preventDefault(); + if (ev.buttons === 0) { + return cancelDrag(); + } + if (deltaHandler) { + let obj = (ev.touches ? ev.touches[0] : ev); + deltaHandler(obj.screenX - this.#drag.start); + } + }; + }; + } + + #handleLoDrag(delta) { + let midval = this.#drag.mdat - delta; + let lowval = this.#drag.loat + delta; + + if (midval < this.#sliderBar || lowval < 0) { + return; + } + + this.#ui.sliderHold.style.marginLeft = `${lowval}px`; + this.#ui.sliderMid.style.width = `${midval}px`; + this.#drag.low = lowval; + this.#drag.mid = midval; + this.#updateFromDrag(); + } + + #handleMidDrag(delta) { + let loval = this.#drag.loat + delta; + let hival = this.#drag.hiat - delta; + + if (loval < 0 || hival < 0) { + return; + } + + this.#ui.sliderHold.style.marginLeft = `${loval}px`; + this.#ui.sliderHold.style.marginRight = `${hival}px`; + this.#drag.low = loval; + this.#updateFromDrag(); + } + + #handleHiDrag(delta) { + let midval = this.#drag.mdat + delta; + let hival = this.#drag.hiat - delta; + + if (midval < this.#sliderBar || midval > this.#drag.mdmax || hival < 0) { + return; + } + + this.#ui.sliderMid.style.width = `${midval}px`; + this.#ui.sliderHold.style.marginRight = `${hival}px`; + this.#drag.mid = midval; + this.#updateFromDrag(); + } + + #updateFromDrag() { + const start = this.#drag.low / this.#drag.maxval; + const end = (this.#drag.low + this.#drag.mid - this.#sliderBar) / this.#drag.maxval; + + this.#layerLo = Math.round(start * this.#layerMax); + this.#layerHi = Math.round(end * this.#layerMax); + + this.#notifyChange(); + } + + #updateVisuals() { + if (this.#layerMax === 0) return; + const start = this.#layerLo / this.#layerMax; + const end = this.#layerHi / this.#layerMax; + this.updatePosition(start, end); + } + + #notifyChange() { + // Notify callbacks of layer change + this.#onLayerChange?.(this.#layerHi, this.#layerLo); + this.#onStackUpdate?.(this.#layerLo, this.#layerHi); + this.#onSceneUpdate?.(); + } + + #pxToInt(txt) { + return txt ? parseInt(txt.substring(0, txt.length - 2)) : 0; + } + + hideLabels() { + // Labels hide themselves on mouse leave, but can be called explicitly + } +} + +// Export singleton instance +export const slider = new SliderControl(); diff --git a/src/moto/license.js b/src/moto/license.js index 33e5ebca..83c6af45 100644 --- a/src/moto/license.js +++ b/src/moto/license.js @@ -3,7 +3,7 @@ const terms = { COPYRIGHT: "Copyright (C) Stewart Allen - All Rights Reserved", LICENSE: "See the license.md file included with the source distribution", - VERSION: "4.5.0" + VERSION: "4.6.0" }; export const beta = 0; diff --git a/web/kiri/manifest.json b/web/kiri/manifest.json index 09fe05cc..b7ba182c 100644 --- a/web/kiri/manifest.json +++ b/web/kiri/manifest.json @@ -1,5 +1,5 @@ { - "name": "Kiri:Moto 4.5.0", + "name": "Kiri:Moto 4.6.0", "short_name": "Kiri:Moto", "description": "Slicer for 3D printers, CNC mills, laser cutters and more", "start_url": "/boot/index.html",