Files
onefinity-firmware/src/js/settings-shell-view.js
muehe 41d720c1d0 kiosk: pi-friendly compact mode + chromium 72 fallbacks
- Detect kiosk mode (localhost / ?kiosk=1) and add html.kiosk-mode
- Suppress 3D path-viewer in kiosk mode (Pi 3B too slow)
- Compact 1366x768 layout: 56px header, smaller jog grid, 4-col macros
  2-col status, 540px jog column
- Flex-gap fallbacks for Chromium 72 (header tabs, sys-btn, state-badge,
  ktab, sp-row, etc.) using "> * + *" margin-* rules
- Path-viewer: opaque WebGL canvas, ResizeObserver-gated render loop,
  no first-frame size flash
- Path-viewer renderer cleared properly on component teardown
- W axis row: W- | W+ | Probe XYZ | Probe Z (was W-|HomeW|W+|Probe)
- Running panel only for actual program execution (not jogging)
- Settings sectioned (Display+Units / Probing / G-code+Motion)
- Routed component now keep-alive across tab swaps
- FA4 -> FA6 webfonts
2026-05-01 11:05:39 +02:00

176 lines
7.7 KiB
JavaScript

"use strict";
// Wrapper that adds a left-rail navigator around the settings family
// of views (Settings, Admin General, Admin Network, Tool, IO, Motor,
// Macros, Help, Cheat Sheet). The inner view is selected by the URL
// hash (parsed in app.js) and exposed as $root.sub_tab.
// Vue 1 has trouble making child components reactive to `$root.sub_tab`
// changes (whether via computed, watch, or prop binding through
// `<component :is>`). The shell instead listens to `hashchange`
// directly and parses the hash itself, mirroring app.js's logic, then
// keeps a local data prop `sub` that the template binds to. This is
// the only path that updates the rail's `:class` reactively.
module.exports = {
template: "#settings-shell-view-template",
props: ["config", "template", "state", "index"],
components: {
"settings-view-inner": require("./settings-view"),
"admin-general-view": require("./admin-general-view"),
"admin-network-view": require("./admin-network-view"),
"motor-view": require("./motor-view"),
"tool-view": require("./tool-view"),
"io-view": require("./io-view"),
"macros-view": require("./macros"),
"help-view": require("./help-view"),
"w-axis-view": require("./w-axis-view"),
"cheat-sheet-view": {
template: "#cheat-sheet-view-template",
data: function () {
return { showUnimplemented: false };
},
},
},
data: function () {
return {
sub: this.$root.sub_tab || "settings",
ridx: this.$root.index, // local copy of the motor index
// Whether the controller config has streamed in. The Svelte
// settings views crash on first paint with the placeholder
// config (settings.units / settings.easy-adapter / motion.*
// are all undefined). Gate the inner mount on this flag.
config_ready: false,
rail_items: [
{ sub: "settings", href: "#settings", icon: "fa-display", label: "Display & Units" },
{ sub: "probing", href: "#probing", icon: "fa-bullseye", label: "Probing" },
{ sub: "gcode", href: "#gcode", icon: "fa-code", label: "G-code & Motion" },
{ sub: "macros", href: "#macros", icon: "fa-keyboard", label: "Macros" },
{ sub: "cheat-sheet", href: "#cheat-sheet", icon: "fa-book", label: "G-code Cheat Sheet" },
{ sub: "admin-network", href: "#admin-network", icon: "fa-network-wired", label: "Network" },
{ sub: "admin-general", href: "#admin-general", icon: "fa-shield-halved", label: "General / Firmware" },
{ sub: "tool", href: "#tool", icon: "fa-bolt", label: "Spindle & Tool" },
{ sub: "io", href: "#io", icon: "fa-plug", label: "I/O" },
{ section: "Motors" },
{ sub: "motor", motor: 0, href: "#motor:0", icon: "fa-arrows-up-down-left-right", label: "Motor 0" },
{ sub: "motor", motor: 1, href: "#motor:1", icon: "fa-arrows-up-down-left-right", label: "Motor 1" },
{ sub: "motor", motor: 2, href: "#motor:2", icon: "fa-arrows-up-down-left-right", label: "Motor 2" },
{ sub: "motor", motor: 3, href: "#motor:3", icon: "fa-arrows-up-down-left-right", label: "Motor 3" },
// W axis is auxiliary (auxcnc ESP32). It mounts the existing
// WAxisSettings Svelte component on its own page.
{ sub: "w-axis", href: "#w-axis", icon: "fa-arrows-up-down", label: "W Axis" },
{ section: " " },
{ sub: "help", href: "#help", icon: "fa-circle-question", label: "Help" },
],
};
},
ready: function () {
this._onHash = () => this.refresh_from_hash();
window.addEventListener("hashchange", this._onHash);
this.refresh_from_hash();
this._configPoll = setInterval(() => {
const c = this.$root && this.$root.config;
const ready = !!(c && c.full_version && c.full_version !== "<loading>"
&& c.settings && typeof c.settings === "object");
if (ready !== this.config_ready) this.config_ready = ready;
}, 200);
},
attached: function () {
// Vue 1 fires `attached` whenever the component is inserted into
// the DOM (which happens on every route change because the outer
// <component :is> recreates the instance). Re-bind the listener
// here so it works even after detach/attach cycles.
if (!this._onHash) {
this._onHash = () => this.refresh_from_hash();
}
window.addEventListener("hashchange", this._onHash);
this.refresh_from_hash();
},
detached: function () {
if (this._onHash) {
window.removeEventListener("hashchange", this._onHash);
}
},
beforeDestroy: function () {
if (this._onHash) {
window.removeEventListener("hashchange", this._onHash);
}
if (this._configPoll) clearInterval(this._configPoll);
},
methods: {
refresh_from_hash: function () {
const hash = location.hash.substr(1) || "settings";
const parts = hash.split(":");
this.sub = parts[0] || "settings";
this.ridx = parts[1] !== undefined ? parts[1] : -1;
},
is_active: function (item) {
if (!item || item.section) return false;
if (item.sub !== this.sub) return false;
if (item.sub === "motor") {
return "" + item.motor === "" + this.ridx;
}
return true;
},
on_rail_click: function (item, ev) {
if (!item) return;
// Always preventDefault on rail clicks. Letting the browser
// anchor-scroll to <div id="settings"> etc. inside .app-body
// can pull the .app-head out of view; we drive navigation
// ourselves through location.hash and our hashchange handler.
if (ev && ev.preventDefault) ev.preventDefault();
if (item.anchor) {
// Soft-link rail items use a #settings hash plus an in-page
// anchor scroll once the Svelte page has mounted. We scroll
// ONLY the .settings-content overflow container by setting
// its scrollTop directly — element.scrollIntoView() walks all
// ancestor scroll containers and can tug the .app-body / html
// layout, which under tablet mode pulls the fixed header out
// of view.
if (location.hash !== item.href) location.hash = item.href;
this._w_axis_focus = (item.sub === "w-axis");
const reset = () => {
// Force any inadvertent ancestor scroll back to 0 before
// we move .settings-content explicitly.
window.scrollTo(0, 0);
const body = document.querySelector(".app-body");
if (body) body.scrollTop = 0;
document.documentElement.scrollTop = 0;
};
setTimeout(() => {
reset();
const el = document.getElementById(item.anchor);
const scroller = document.querySelector(".settings-content");
if (el && scroller) {
const elTop = el.getBoundingClientRect().top;
const scTop = scroller.getBoundingClientRect().top;
scroller.scrollTop = scroller.scrollTop + (elTop - scTop) - 12;
}
// Re-assert ancestor scroll = 0 in case the assignment above
// moved things.
requestAnimationFrame(reset);
}, 320);
} else {
this._w_axis_focus = false;
if (location.hash !== item.href) location.hash = item.href;
// Reset .app-body scroll so each route starts at the top.
const body = document.querySelector(".app-body");
if (body) body.scrollTop = 0;
}
},
showShutdownDialog: function () {
SvelteComponents.showDialog("Shutdown");
},
},
};