"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 // ``). 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"), "a-axis-view": require("./a-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" }, // Auxiliary axis (auxcnc ESP32 - exposed to gplan as A). // Mounts the AAxisSettings Svelte component on its own page. { sub: "a-axis", href: "#a-axis", icon: "fa-arrows-up-down", label: "A 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 !== "" && 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 // 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
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._a_axis_focus = (item.sub === "a-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._a_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"); }, }, };