ui: V09 redesign - Control/Program/Console/Settings shell
Replaces the legacy side-menu chrome with a 4-tab top header. - index.pug: tablet/kiosk fit-to-viewport script, header tab nav, estop/state badges in header. - app.js: route hash to (control|program|console|<settings-family>), multi-section settings shell. - control-view: header DRO, jog grid, MDI/probe/macros panels. - program-view + program-mixin: file browser + toolpath preview + run/pause/stop, replaces the legacy 'macros' tab content. - console-view: MDI shell, message log, indicators. - settings-shell-view: rail-driven inner pages (Display & Units, Probing, G-code & Motion, Macros, Network, etc.). - settings-view: filter Svelte SettingsView to one rail section. - SettingsView.svelte: tag every section with data-sec=… so the filter above can hide non-matching ones. - style.styl: ~2700 lines of V09 layout, DRO, jog grid, status strip, and tablet/kiosk variants. No A-axis / auxiliary-axis content lives on this branch.
This commit is contained in:
169
src/js/settings-shell-view.js
Normal file
169
src/js/settings-shell-view.js
Normal file
@@ -0,0 +1,169 @@
|
||||
"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"),
|
||||
"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" },
|
||||
{ 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;
|
||||
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 {
|
||||
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");
|
||||
},
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user