diff --git a/src/js/app.js b/src/js/app.js index c8cd5ce..85dbcd7 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -358,8 +358,22 @@ module.exports = new Vue({ // Resolve the initial route before the websocket connects so // the shell shows the right view even on a slow / offline // controller. update() will call parse_hash() again once the - // first config is in. - this.parse_hash(); + // first config is in. Skip routing into the Svelte settings + // family before config has loaded — those components read + // many config keys (settings.units, settings.probing-prompts, + // motion.*, etc.) and would throw on first paint with the + // empty placeholder config. + const settingsFamily = [ + "settings", "admin-general", "admin-network", + "motor", "tool", "io", "macros", + "help", "cheat-sheet", + ]; + const initialHead = (location.hash || "").replace(/^#/, "").split(":")[0]; + if (settingsFamily.indexOf(initialHead) === -1) { + this.parse_hash(); + } + // else: stay on "loading" until update() completes and calls + // parse_hash() itself. this.connect(); diff --git a/src/js/axis-vars.js b/src/js/axis-vars.js index abe96c0..2d39f16 100644 --- a/src/js/axis-vars.js +++ b/src/js/axis-vars.js @@ -189,7 +189,11 @@ module.exports = { _get_motor_id: function(axis) { for (let i = 0; i < this.config.motors.length; i++) { const motor = this.config.motors[i]; - if (motor.axis.toLowerCase() == axis) { + // motor.axis can be undefined on initial load before + // config has streamed in. Guard so the computed does + // not throw and bubble a Vue warning into the console. + if (motor && typeof motor.axis === "string" && + motor.axis.toLowerCase() == axis) { return i; } } @@ -198,10 +202,28 @@ module.exports = { }, _check_is_enabled: function(axis){ + // Prefer config.motors[i].axis (always present once the + // config has loaded). Fall back to the per-motor state + // `Nan` field, which is what the legacy UI used. This + // avoids hiding axis rows during the brief window after + // config has loaded but before the controller has pushed + // its first state delta. const axes = { x: 0, y: 1, z: 2, a: 3 }; - for(let i = 0; i < this.config.motors.length; i++){ - if(this.state[`${i}an`] == axes[axis]){ - return true; + const wanted = axes[axis]; + for (let i = 0; i < this.config.motors.length; i++) { + const motor = this.config.motors[i] || {}; + if (typeof motor.axis === "string" && + motor.axis.toLowerCase() == axis) { + return motor.enabled !== false; + } + // Only use the state Nan fallback for axes we know + // about (x/y/z/a). Otherwise undefined == undefined + // would mistakenly match every axis (b, c, ...). + if (typeof wanted === "number") { + const an = this.state[`${i}an`]; + if (typeof an === "number" && an === wanted) { + return true; + } } } return false; diff --git a/src/js/orbit.js b/src/js/orbit.js index ef7a4fc..efeb8e9 100644 --- a/src/js/orbit.js +++ b/src/js/orbit.js @@ -683,12 +683,16 @@ const OrbitControls = function(object, domElement) { event.preventDefault(); } + // Chrome treats touch/wheel listeners as passive by default, + // which prevents OrbitControls.preventDefault() from suppressing + // page panning while interacting with the 3D viewer. Pass + // {passive: false} on the events that need to call preventDefault. scope.domElement.addEventListener("contextmenu", onContextMenu, false); scope.domElement.addEventListener("mousedown", onMouseDown, false); - scope.domElement.addEventListener("wheel", onMouseWheel, false); - scope.domElement.addEventListener("touchstart", onTouchStart, false); + scope.domElement.addEventListener("wheel", onMouseWheel, { passive: false }); + scope.domElement.addEventListener("touchstart", onTouchStart, { passive: false }); scope.domElement.addEventListener("touchend", onTouchEnd, false); - scope.domElement.addEventListener("touchmove", onTouchMove, false); + scope.domElement.addEventListener("touchmove", onTouchMove, { passive: false }); window.addEventListener("keydown", onKeyDown, false); this.update(); // force an update at start diff --git a/src/js/program-mixin.js b/src/js/program-mixin.js index b3b5ae4..f14c139 100644 --- a/src/js/program-mixin.js +++ b/src/js/program-mixin.js @@ -120,10 +120,12 @@ module.exports = { gcode_files: function () { if (!this.state.folder) return []; - const folder = this.state.gcode_list.find(item => item.name == this.state.folder); + const list = Array.isArray(this.state.gcode_list) ? this.state.gcode_list : []; + const folder = list.find(item => item.name == this.state.folder); if (!folder) return []; - const files = folder.files - .filter(item => this.state.files.includes(item.file_name)) + const stateFiles = Array.isArray(this.state.files) ? this.state.files : []; + const files = (folder.files || []) + .filter(item => stateFiles.includes(item.file_name)) .map(item => item.file_name); if (this.files_sortby == "A-Z") return files.sort(); if (this.files_sortby == "Z-A") return files.sort().reverse(); @@ -136,7 +138,8 @@ module.exports = { }, gcode_folders: function () { - return this.state.gcode_list + const list = Array.isArray(this.state.gcode_list) ? this.state.gcode_list : []; + return list .map(item => item.name) .filter(element => element !== "default") .sort(); @@ -174,7 +177,11 @@ module.exports = { const file = this.state.selected; if (this.last_file == file && this.last_file_time == file_time) return; - if (this.state.selected && !this.state.files.includes(this.state.selected)) { + // state.files can be undefined briefly after connect, before the + // controller has pushed its file list. Skip the existence check + // until we have a list to consult. + const files = Array.isArray(this.state.files) ? this.state.files : null; + if (this.state.selected && files && !files.includes(this.state.selected)) { this.GCodeNotFound = true; return; } diff --git a/src/pug/templates/control-view.pug b/src/pug/templates/control-view.pug index f88d0d5..e7a6df3 100644 --- a/src/pug/templates/control-view.pug +++ b/src/pug/templates/control-view.pug @@ -89,7 +89,7 @@ script#control-view-template(type="text/x-template") .fa.fa-arrow-down.ico(style="transform: rotate(-45deg)") button.jbtn(@click="jog_fn(0, 0, -1, 0)") Z− - // Row 4 — auxiliary axis (W or A) or probe shortcuts + // Row 4 — W axis (auxcnc) when enabled template(v-if="w.enabled") button.jbtn(@click="aux_jog_incr(-1)", :disabled="!w.enabled") .fa.fa-arrow-down.ico @@ -104,7 +104,10 @@ script#control-view-template(type="text/x-template") :class="{'load-on': !state['pw']}") .fa.fa-bullseye.ico span.lbl Probe - template(v-else-if="state['2an'] == 3") + + // Row 4 — A axis (rotary) when no W and rotary is enabled + // (Vue 1 has no v-else-if; we negate w.enabled explicitly.) + template(v-if="!w.enabled && state['2an'] == 3") button.jbtn.dir(@click="jog_fn(0, 0, 0, -1)") .fa.fa-rotate-left.ico span.lbl A− @@ -118,7 +121,9 @@ script#control-view-template(type="text/x-template") :class="{'load-on': !state['pw']}") .fa.fa-bullseye.ico span.lbl Probe - template(v-else) + + // Row 4 — fallback probe / zero / home shortcuts + template(v-if="!w.enabled && state['2an'] != 3") button.jbtn(@click="showProbeDialog('xyz')", :class="{'load-on': !state['pw']}") .fa.fa-bullseye.ico @@ -246,9 +251,9 @@ script#control-view-template(type="text/x-template") title="Click to run macro", @click="run_macro(index)", :disabled="!is_ready", - :style="{ borderLeftColor: macros.color || '#fde047' }") + :class="{'has-color': macros.color && macros.color !== '#ffffff' && macros.color !== '#fff'}", + :style="macros.color && macros.color !== '#ffffff' && macros.color !== '#fff' ? {borderLeftColor: macros.color} : {}") span.mnum {{index + 1}} - .fa.fa-circle-play.micon span.mname {{macros.name || ('Macro ' + (index + 1))}} // ----- Override drawer (anchored to bottom; toggled by Spindle KPI tile) ----- diff --git a/src/pug/templates/estop.pug b/src/pug/templates/estop.pug index 393fa88..5a01e3b 100644 --- a/src/pug/templates/estop.pug +++ b/src/pug/templates/estop.pug @@ -2,6 +2,8 @@ script#estop-template(type="text/x-template") svg(version="1.1", xmlns:svg="http://www.w3.org/2000/svg", xmlns="http://www.w3.org/2000/svg", xmlns:xlink="http://www.w3.org/1999/xlink", + viewBox="0 0 130 130", + preserveAspectRatio="xMidYMid meet", width="130", height="130") defs path#text-path-1(d="m 73.735,673.129 c 0,55.107 44.673,99.780 99.780,99.780 55.107,0 99.780,-44.673 99.780,-99.780 0,-55.107 -44.673,-99.780 -99.780,-99.780 -55.107,0 -99.780,44.673 -99.780,99.780 z") diff --git a/src/static/css/Audiowide.css b/src/static/css/Audiowide.css index a73be84..9ad61bf 100644 --- a/src/static/css/Audiowide.css +++ b/src/static/css/Audiowide.css @@ -2,5 +2,5 @@ font-family: 'Audiowide'; font-style: normal; font-weight: 400; - src: local('Audiowide'), local('Audiowide-Regular'), url(http://fonts.gstatic.com/s/audiowide/v4/8XtYtNKEyyZh481XVWfVOqCWcynf_cDxXwCLxiixG1c.ttf) format('truetype'); + src: local('Audiowide'), local('Audiowide-Regular'), url(https://fonts.gstatic.com/s/audiowide/v4/8XtYtNKEyyZh481XVWfVOqCWcynf_cDxXwCLxiixG1c.ttf) format('truetype'); } diff --git a/src/static/js/ui.js b/src/static/js/ui.js index acc38a0..13238c9 100644 --- a/src/static/js/ui.js +++ b/src/static/js/ui.js @@ -1,35 +1,37 @@ +// V09 redesign: the legacy side menu was removed. Keep this file +// shipped in case anything still references it, but no-op the click +// handler that used to wire up the burger menu so it does not throw +// "Cannot set properties of null" on the Settings tab. (function (window, document) { + var menuLink = document.getElementById("menuLink"); + if (!menuLink) { + return; + } - var layout = document.getElementById('layout'), - menu = document.getElementById('menu'), - menuLink = document.getElementById('menuLink'); + var layout = document.getElementById("layout"); + var menu = document.getElementById("menu"); function toggleClass(element, className) { - var classes = element.className.split(/\s+/), - length = classes.length, - i = 0; - - for(; i < length; i++) { - if (classes[i] === className) { - classes.splice(i, 1); - break; - } + if (!element) return; + var classes = element.className.split(/\s+/); + var i; + for (i = 0; i < classes.length; i++) { + if (classes[i] === className) { + classes.splice(i, 1); + break; + } } - // The className is not found - if (length === classes.length) { + if (i === classes.length) { classes.push(className); } - - element.className = classes.join(' '); + element.className = classes.join(" "); } menuLink.onclick = function (e) { - var active = 'active'; - + var active = "active"; e.preventDefault(); toggleClass(layout, active); toggleClass(menu, active); toggleClass(menuLink, active); }; - }(this, this.document)); diff --git a/src/stylus/style.styl b/src/stylus/style.styl index 007c7da..b8dec52 100644 --- a/src/stylus/style.styl +++ b/src/stylus/style.styl @@ -74,7 +74,9 @@ tt .app-shell display flex flex-direction column - min-height 100vh + height 100vh // cap at viewport so children that ask for 1fr/flex:1 + width 100% + overflow hidden background $body-bg .app-body @@ -83,9 +85,10 @@ tt display flex flex-direction column padding 18px + overflow auto // settings/motor pages can scroll inside the body > * - flex 1 + flex 1 1 auto min-height 0 .app-head @@ -496,28 +499,34 @@ span.unit 50% fill #ff9d00 -// Octagonal STOP wrapper around the existing SVG. The SVG -// rules below (`.button`, `.ring`, etc.) keep working unchanged. +// E-Stop in the header — wraps the legacy SVG component. +// Sized to fit the 96px header with breathing room. The SVG carries +// its own yellow safety ring and EMERGENCY/STOP text; we only frame +// it with a soft drop shadow and a hover/active hit target. .app-head .estop - width 88px - height 88px - background #dc2626 - clip-path polygon(30% 0, 70% 0, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0 70%, 0 30%) - display flex + width 80px + height 80px + display inline-flex align-items center justify-content center - border 3px solid #fff - box-shadow 0 0 0 3px #b91c1c, 0 8px 20px rgba(220, 38, 38, 0.35) + border-radius 9999px cursor pointer - transition transform 0.06s + transition transform 0.06s, filter 0.15s + flex 0 0 auto + // Make sure the SVG's internal coordinate space scales correctly + overflow visible + + &:hover + filter brightness(1.05) &:active transform scale(0.96) svg - width 56px - height 56px + width 80px + height 80px cursor pointer + display block .button:hover filter brightness(120%) @@ -1009,11 +1018,12 @@ tt.save // CONTROL page (V09) // ===================================================================== .control-page - flex 1 + flex 1 1 auto min-height 0 display flex flex-direction column gap 14px + overflow hidden .control-page .control-grid display grid @@ -1362,7 +1372,6 @@ tt.save height 84px border-radius 14px border 1px solid transparent - border-left 6px solid $accent color #fff background $jog-bg font-weight 800 @@ -1386,6 +1395,12 @@ tt.save opacity 0.45 cursor not-allowed + // Per-macro color stripe is opt-in via :class="has-color" set by + // the template only when state.macros[i].color is configured. + &.has-color + border-left-width 6px + border-left-style solid + .mnum display inline-flex align-items center @@ -1398,16 +1413,12 @@ tt.save font-size 0.85rem font-weight 900 text-shadow none - - .micon - font-size 1.1rem - opacity 0.75 + flex 0 0 auto .mname white-space nowrap overflow hidden text-overflow ellipsis - max-width 9em // Override drawer .override-drawer @@ -1487,10 +1498,11 @@ tt.save // PROGRAM page (V09) // ===================================================================== .program-page - flex 1 + flex 1 1 auto min-height 0 display flex flex-direction column + overflow hidden .program-card flex 1 @@ -1609,7 +1621,7 @@ tt.save min-width 300px .program-body - flex 1 + flex 1 1 auto display grid grid-template-columns 1fr 600px min-height 0 @@ -1617,13 +1629,36 @@ tt.save > .gcode border-right 1px solid $line-soft - overflow auto background #fafafa padding 0 margin 0 + overflow hidden + min-height 0 + display flex + flex-direction column + // 3D toolpath preview — fill the entire 600px column. Override the + // legacy `.path-viewer.small` rule which would clamp the canvas to + // 340x150 and float it into the corner. > .path-viewer overflow hidden + min-height 0 + display flex + flex-direction column + + .path-viewer-content + flex 1 1 auto + width 100% !important + height auto !important + min-height 0 + float none !important + margin 0 !important + + &.small .path-viewer-content + width 100% !important + height auto !important + float none !important + margin 0 !important .progress-bar height 28px @@ -1645,8 +1680,20 @@ tt.save font-size 13px line-height 1.55 + .clusterize + flex 1 1 0 + min-height 0 + overflow hidden + display flex + flex-direction column + height 100% + .clusterize-scroll - max-height 100% + flex 1 1 0 + min-height 0 + height 100% + max-height none // override clusterize.css default of 200px + width 100% overflow auto ul @@ -1675,9 +1722,10 @@ tt.save // CONSOLE page (V09) // ===================================================================== .console-page - flex 1 + flex 1 1 auto display flex min-height 0 + overflow hidden .console-card flex 1