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:
2026-05-03 14:11:29 +02:00
parent c10f5c053a
commit 94072253d4
16 changed files with 4165 additions and 1876 deletions

125
src/js/console-view.js Normal file
View File

@@ -0,0 +1,125 @@
"use strict";
const api = require("./api");
// Console tab — MDI command input, message log and live indicators.
// Sub-tab state syncs with the URL hash (#console:mdi |
// #console:messages | #console:indicators) so deep links work.
module.exports = {
template: "#console-view-template",
props: ["config", "template", "state"],
data: function () {
return {
mdi: "",
history: [],
sub: "mdi",
// Local mirror of $root.messages_count so Vue 1 reactivity works.
unread_messages_local: 0,
};
},
watch: {
sub: function () {
// Switching to messages marks them as seen so the header badge
// clears.
if (this.sub === "messages") {
this.$root.messages_seen = this.$root.messages_log.length;
this.unread_messages_local = 0;
}
},
},
computed: {
unread_messages: function () {
return this.unread_messages_local;
},
mach_state: function () {
const cycle = this.state.cycle;
const xx = this.state.xx;
if (xx != "ESTOPPED" && (cycle == "jogging" || cycle == "homing")) {
return cycle.toUpperCase();
}
return xx || "";
},
is_idle: function () { return this.state.cycle == "idle"; },
can_mdi: function () {
return this.is_idle || this.state.cycle == "mdi";
},
mach_units: function () {
return this.$root.display_units;
},
},
ready: function () {
this._onHash = () => this.refresh_from_hash();
window.addEventListener("hashchange", this._onHash);
this.refresh_from_hash();
this._poll = setInterval(() => {
// Cheap re-poll for unread message count; Vue 1 cannot observe
// `$root.messages_count` directly so we mirror it here.
const c = this.$root && this.$root.messages_count;
if (typeof c === "number" && c !== this.unread_messages_local) {
this.unread_messages_local = c;
}
}, 500);
},
beforeDestroy: function () {
if (this._onHash) window.removeEventListener("hashchange", this._onHash);
if (this._poll) clearInterval(this._poll);
},
methods: {
refresh_from_hash: function () {
const hash = location.hash.substr(1);
const parts = hash.split(":");
const sub = parts[0] === "console" ? (parts[1] || "mdi") : "mdi";
this.sub = sub;
if (sub === "messages" && this.$root) {
this.$root.messages_seen = this.$root.messages_log.length;
this.unread_messages_local = 0;
}
},
select_sub: function (name) {
this.sub = name;
// Update URL hash for deep links / back-button.
const h = "#console" + (name && name !== "mdi" ? ":" + name : "");
if (location.hash !== h) {
history.replaceState(null, "", h);
}
if (name === "messages") {
this.$root.messages_seen = this.$root.messages_log.length;
this.unread_messages_local = 0;
}
},
prepend: function (token) {
this.mdi = token + this.mdi.trimStart();
},
append: function (token) {
const tail = this.mdi.endsWith(" ") || !this.mdi ? "" : " ";
this.mdi = this.mdi + tail + token;
},
submit_mdi: function () {
if (!this.mdi) return;
this.$dispatch("send", this.mdi);
if (!this.history.length || this.history[0] != this.mdi) {
this.history.unshift(this.mdi);
}
this.mdi = "";
},
load_history: function (index) {
this.mdi = this.history[index];
},
},
};