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
This commit is contained in:
muehe
2026-05-01 11:05:39 +02:00
parent 3d73e6c59d
commit 41d720c1d0
34 changed files with 1018 additions and 126 deletions

View File

@@ -68,7 +68,11 @@ update: pkg
build/templates.pug: $(TEMPLS)
mkdir -p build
cat $(TEMPLS) >$@
# Use awk to ensure each template is followed by a newline so the
# next file's first line never gets glued onto the previous file's
# last line (some templates ship without a trailing newline, which
# would produce subtle Pug parse failures).
awk 'FNR==1 && NR>1 {print ""} {print} END{print ""}' $(TEMPLS) >$@
node_modules: package.json
npm install && touch node_modules

View File

@@ -8,7 +8,7 @@
#
# Defaults:
# HOST=onefinity.local
# USER=bbmc
# REMOTE_USER=bbmc
# PASSWORD=onefinity (used for sudo on the Pi)
#
# Override:
@@ -20,46 +20,53 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$SCRIPT_DIR"
HOST="${HOST:-onefinity.local}"
# REMOTE_USER (not USER, which the shell pre-populates with the local
# logged-in account).
REMOTE_USER="${REMOTE_USER:-bbmc}"
PASSWORD="${PASSWORD:-onefinity}"
echo "🛠 Building UI bundle..."
echo "Building UI bundle (HTML + resources)..."
make build/http/index.html >/dev/null
# Copy src/resources/* into build/http/. The Makefile's "all" target
# also does this, but pulls in cross-compiled subprojects (avr/boot/
# pwr/jig) we don't have toolchains for on macOS. This rsync mirrors
# only the resource tree.
rsync -a src/resources/ build/http/
# Discover the on-Pi http path; the bbctrl egg version may change.
echo "🔍 Locating bbctrl http/ directory on $HOST..."
echo "Locating bbctrl http/ directory on $HOST..."
REMOTE_HTTP_DIR="$(ssh -o ConnectTimeout=5 "${REMOTE_USER}@${HOST}" \
"ls -d /usr/local/lib/python*/dist-packages/bbctrl-*-py*.egg/bbctrl/http 2>/dev/null | head -1")"
if [[ -z "$REMOTE_HTTP_DIR" ]]; then
echo "❌ Could not find bbctrl http/ directory on $HOST"
echo "ERROR: could not find bbctrl http/ directory on $HOST"
exit 1
fi
echo " $REMOTE_HTTP_DIR"
echo "🚚 Rsyncing build/http/ $HOST:$REMOTE_HTTP_DIR/"
# Stage to a tmp dir owned by $REMOTE_USER, then sudo-mv into place.
# This avoids needing root over rsync.
echo "Rsyncing build/http/ -> $HOST:$REMOTE_HTTP_DIR/"
# Stage to a tmp dir owned by $REMOTE_USER, then sudo-rsync into
# place. This avoids needing root over rsync. We do NOT use --delete
# anywhere -- the Pi's egg ships extra runtime files (config-template
# .json, default machine JSON, buildbotics.nc, etc.) that come with
# the bbctrl package and are not in this repo's src/resources. If
# they were deleted the controller's API would 500 because Python
# imports fail.
REMOTE_TMP="/tmp/onefin_ui_$$"
ssh -o ConnectTimeout=5 "${REMOTE_USER}@${HOST}" "mkdir -p '${REMOTE_TMP}'"
rsync -avz --delete \
rsync -avz \
--exclude='hostinfo.txt' \
-e "ssh -o ConnectTimeout=5" \
build/http/ "${REMOTE_USER}@${HOST}:${REMOTE_TMP}/"
echo "📦 Installing into ${REMOTE_HTTP_DIR}/ (sudo)..."
echo "Installing into ${REMOTE_HTTP_DIR}/ (sudo)..."
ssh -o ConnectTimeout=5 "${REMOTE_USER}@${HOST}" \
"echo '${PASSWORD}' | sudo -S bash -c '
rsync -a --delete --exclude=hostinfo.txt \"${REMOTE_TMP}/\" \"${REMOTE_HTTP_DIR}/\" \
rsync -a --exclude=hostinfo.txt \"${REMOTE_TMP}/\" \"${REMOTE_HTTP_DIR}/\" \
&& rm -rf \"${REMOTE_TMP}\"
'" 2>&1 | tail -3
echo "🔁 Restarting bbctrl service..."
echo "Restarting bbctrl service..."
ssh -o ConnectTimeout=5 "${REMOTE_USER}@${HOST}" \
"echo '${PASSWORD}' | sudo -S systemctl restart bbctrl" 2>&1 | tail -3
echo ""
echo "Deployed to http://${HOST}/"
echo "Deployed to http://${HOST}/"
echo " Logs: ssh ${REMOTE_USER}@${HOST} 'journalctl -u bbctrl -f'"
echo " Open: open -a 'Google Chrome' http://${HOST}/"

View File

@@ -251,6 +251,19 @@ module.exports = new Vue({
},
computed: {
// True when the UI is in kiosk mode — i.e. running on the
// controller's own onboard browser (Pi 3B at 1366x768) or
// explicitly forced via ?kiosk=1. Source-of-truth is the
// `kiosk-mode` class added to <html> by the inline script
// in index.pug, which already honors hostname + URL param +
// localStorage. The Pi's VideoCore IV is too slow for the
// three.js toolpath preview, so we suppress that panel in
// kiosk mode and let the gcode listing take the full width.
is_kiosk: function() {
return typeof document !== "undefined"
&& document.documentElement.classList.contains("kiosk-mode");
},
popupMessages: function() {
const msgs = [];
@@ -356,6 +369,8 @@ module.exports = new Vue({
ready: function() {
window.onhashchange = () => this.parse_hash();
// 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
@@ -365,9 +380,11 @@ module.exports = new Vue({
// motion.*, etc.) and would throw on first paint with the
// empty placeholder config.
const settingsFamily = [
"settings", "admin-general", "admin-network",
"settings", "probing", "gcode",
"admin-general", "admin-network",
"motor", "tool", "io", "macros",
"help", "cheat-sheet",
"w-axis",
];
const initialHead = (location.hash || "").replace(/^#/, "").split(":")[0];
if (settingsFamily.indexOf(initialHead) === -1) {
@@ -593,9 +610,11 @@ module.exports = new Vue({
// Settings tab while keeping their existing top-level
// hash. This preserves all existing deep links.
const settingsViews = [
"settings", "admin-general", "admin-network",
"settings", "probing", "gcode",
"admin-general", "admin-network",
"motor", "tool", "io", "macros",
"help", "cheat-sheet",
"w-axis",
];
if (head == "control") {

View File

@@ -49,14 +49,17 @@ module.exports = {
methods: {
get_io_state_class: function(active, state) {
if (typeof active == "undefined" || typeof state == "undefined") {
return "fa-exclamation-triangle warn";
return "fa-triangle-exclamation warn";
}
// Tristated: render as the regular (outline) circle to
// distinguish from active/inactive solid circles. Adding
// `far` switches to the FA6 regular family.
if (state == 2) {
return "fa-circle-o";
return "far fa-circle";
}
const icon = state ? "fa-plus-circle" : "fa-minus-circle";
const icon = state ? "fa-circle-plus" : "fa-circle-minus";
return `${icon} ${active ? "active" : "inactive"}`;
},

View File

@@ -101,6 +101,13 @@ module.exports = {
Vue.nextTick(this.update);
},
beforeDestroy: function() {
if (this._sizeWatcher) {
this._sizeWatcher.disconnect();
this._sizeWatcher = null;
}
},
methods: {
update: async function() {
if (!this.webglAvailable) {
@@ -201,6 +208,12 @@ module.exports = {
}
const dims = this.get_dims();
// Skip layouts where the target has no measurable size.
// The render loop guard below will not draw frames until
// a real size has been observed at least once.
if (!(dims.width > 0 && dims.height > 0)) {
return;
}
this.camera.aspect = dims.width / dims.height;
this.camera.updateProjectionMatrix();
@@ -274,12 +287,23 @@ module.exports = {
}
try {
// Renderer
this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
// Renderer. Use an opaque canvas with a clear color
// that matches the page-side gradient so the moment
// the canvas is appended (and before the first 3D
// frame is drawn) the user does not see a flash from
// the page background through transparency.
this.renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: false,
});
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setClearColor(0, 0);
this.renderer.setClearColor(0x222222, 1);
// Same color on the DOM element itself so the very
// first paint (before the WebGL context has cleared)
// is dark too.
this.renderer.domElement.style.background = "#222222";
this.renderer.domElement.style.display = "block";
this.target.appendChild(this.renderer.domElement);
} catch (e) {
console.log("WebGL not supported: ", e);
return;
@@ -333,8 +357,46 @@ module.exports = {
// Events
window.addEventListener("resize", this.update_view, false);
// Start it
// Start the render loop only after the target has a real,
// stable size. Without this, the first frame paints into
// a 0×0 / collapsed-flex canvas and a second frame paints
// again at the right size — visible as a flash on the
// very first mount of the Program tab.
const startRendering = () => {
if (this._rendering) return;
this._rendering = true;
this.update_view();
this.render();
};
const dims = this.get_dims();
if (dims.width > 0 && dims.height > 0) {
startRendering();
} else if (typeof ResizeObserver !== "undefined") {
this._sizeWatcher = new ResizeObserver(entries => {
for (const entry of entries) {
const r = entry.contentRect;
if (r.width > 0 && r.height > 0) {
this._sizeWatcher.disconnect();
this._sizeWatcher = null;
startRendering();
return;
}
}
});
this._sizeWatcher.observe(this.target);
} else {
// Old browser fallback: poll for a non-zero size.
const tick = () => {
const d = this.get_dims();
if (d.width > 0 && d.height > 0) {
startRendering();
} else {
requestAnimationFrame(tick);
}
};
requestAnimationFrame(tick);
}
},
create_surface_material: function() {
@@ -646,6 +708,14 @@ module.exports = {
return;
}
// Don't paint frames while the target has no size; this
// prevents an initial single-frame clear from painting
// before the layout has settled (visible as a dark flash).
const dims = this.get_dims();
if (!(dims.width > 0 && dims.height > 0)) {
return;
}
if (this.controls.update() || this.dirty) {
this.dirty = false;
this.renderer.render(this.scene, this.camera);

View File

@@ -77,6 +77,21 @@ module.exports = {
return this.state.cycle == "idle";
},
// True only while a loaded G-code program is actually being
// executed (running, paused/holding, or stopping). Excludes
// jogging, homing, MDI commands and other one-off motion that
// also leave state.cycle != 'idle' but should not bring up the
// "Now Running" panel on the Control tab.
is_program_executing: function () {
const xx = this.state && this.state.xx;
if (xx == "RUNNING" || xx == "HOLDING" || xx == "STOPPING") {
// Only count it as a program run if a file is selected.
// Otherwise an MDI submission also reads xx=RUNNING.
return !!(this.state && this.state.selected);
}
return false;
},
is_paused: function () {
return this.is_holding && (this.pause_reason == "User pause" || this.pause_reason == "Program pause");
},

View File

@@ -26,6 +26,8 @@ module.exports = {
},
computed: {
is_kiosk: function () { return !!this.$root.is_kiosk; },
display_units: {
cache: false,
get: function () { return this.$root.display_units; },

View File

@@ -24,6 +24,7 @@ module.exports = {
"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 () {
@@ -36,8 +37,17 @@ module.exports = {
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" },
@@ -47,13 +57,10 @@ module.exports = {
{ 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). Its config lives inside
// the main Settings page; we route to #settings and scroll to
// the #w-axis anchor on click.
{ sub: "w-axis", href: "#settings", anchor: "w-axis", icon: "fa-arrows-up-down", label: "W Axis" },
// 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: "macros", href: "#macros", icon: "fa-keyboard", label: "Macros" },
{ sub: "cheat-sheet", href: "#cheat-sheet", icon: "fa-book", label: "G-code Cheat Sheet" },
{ sub: "help", href: "#help", icon: "fa-circle-question", label: "Help" },
],
};
@@ -63,6 +70,12 @@ module.exports = {
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 () {
@@ -87,6 +100,7 @@ module.exports = {
if (this._onHash) {
window.removeEventListener("hashchange", this._onHash);
}
if (this._configPoll) clearInterval(this._configPoll);
},
methods: {
@@ -99,12 +113,6 @@ module.exports = {
is_active: function (item) {
if (!item || item.section) return false;
// The W Axis rail item is a soft-link into #settings; we mark
// it active only when the user is on Display & Units AND the
// last clicked rail item was W Axis.
if (item.sub === "w-axis") {
return this.sub === "settings" && this._w_axis_focus === true;
}
if (item.sub !== this.sub) return false;
if (item.sub === "motor") {
return "" + item.motor === "" + this.ridx;
@@ -113,19 +121,50 @@ module.exports = {
},
on_rail_click: function (item, ev) {
// Soft-link rail items use an anchor and a scrollIntoView call.
if (item && item.anchor) {
ev.preventDefault();
// Navigate to settings if not already there, then scroll.
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");
// Defer the scroll so Vue mounts the inner Svelte page first.
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);
if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
}, 250);
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;
}
},

View File

@@ -1,14 +1,60 @@
// V09 wraps the legacy Svelte SettingsView and filters its big page
// down to a single rail section so each rail item shows only the
// relevant controls. The Svelte component is left untouched (it is
// shared with the legacy UI) — we just hide the `<h2>` and `<fieldset>`
// elements whose `data-sec` does not match the active section.
module.exports = {
template: "#settings-view-template",
props: {
// "display" | "probing" | "gcode". Default is "display" which
// keeps the rail's "Display & Units" item working unchanged.
section: { default: "display" },
},
attached: function () {
this.svelteComponent = SvelteComponents.createComponent(
"SettingsView",
document.getElementById("settings")
);
// Defer one tick so Svelte has rendered the section markup.
setTimeout(() => this.apply_section_filter(), 0);
},
detached: function () {
this.svelteComponent.$destroy();
if (this.svelteComponent) this.svelteComponent.$destroy();
},
watch: {
section: function () {
this.apply_section_filter();
},
},
methods: {
apply_section_filter: function () {
const root = document.getElementById("settings");
if (!root) return;
const want = this.section || "display";
// Hide every section block that does not match.
root.querySelectorAll("[data-sec]").forEach(el => {
el.style.display = el.dataset.sec === want ? "" : "none";
});
// Hide the global <h1>Settings</h1> on subsections so the
// page reads as a focused panel.
const h1 = root.querySelector(".settings-view > h1");
if (h1) {
if (want === "display") {
h1.textContent = "Display & Units";
} else if (want === "probing") {
h1.textContent = "Probing";
} else if (want === "gcode") {
h1.textContent = "G-code & Motion";
} else {
h1.textContent = "Settings";
}
}
},
},
};

20
src/js/w-axis-view.js Normal file
View File

@@ -0,0 +1,20 @@
"use strict";
// V09 W-axis page \u2014 mounts the existing WAxisSettings Svelte component
// inside the settings shell so it gets a real top-level rail entry
// instead of being a soft-link anchor inside Display & Units.
module.exports = {
template: "#w-axis-view-template",
attached: function () {
this.svelteComponent = SvelteComponents.createComponent(
"WAxisSettings",
document.getElementById("w-axis-mount")
);
},
detached: function () {
if (this.svelteComponent) this.svelteComponent.$destroy();
},
};

View File

@@ -9,7 +9,7 @@ html(lang="en")
style: include ../static/css/pure-min.css
style: include ../static/css/font-awesome.min.css
style: include ../static/css/fa6.min.css
style: include ../static/css/Audiowide.css
style: include ../static/css/clusterize.css
style: include ../svelte-components/node_modules/svelte-material-ui/bare.css
@@ -18,6 +18,51 @@ html(lang="en")
style: include:stylus ../stylus/style.styl
body(v-cloak)
// Tablet (kiosk) mode — pins the .app-shell to 1920x1080 and
// scales it to fit the actual viewport so the UI always looks
// exactly like the 10.8" 1920x1080 portable monitor.
//
// Toggle: ?tablet=1 to enable
// ?tablet=0 to disable
// Sticky in localStorage; once set, no querystring is needed.
script.
(function () {
try {
var p = new URLSearchParams(location.search);
if (p.has("tablet")) {
var on = p.get("tablet") !== "0" && p.get("tablet") !== "false";
localStorage.setItem("ui-tablet-mode", on ? "1" : "0");
}
if (localStorage.getItem("ui-tablet-mode") === "1") {
document.documentElement.classList.add("tablet-mode");
}
function fit() {
if (!document.documentElement.classList.contains("tablet-mode")) return;
var s = Math.min(window.innerWidth / 1920, window.innerHeight / 1080);
document.documentElement.style.setProperty("--tablet-scale", s);
}
fit();
window.addEventListener("resize", fit);
// Kiosk mode: when the UI is loaded by the controller's
// own onboard browser (Chromium pointing at localhost on
// the Pi 3B at 1366x768), apply a tighter layout that
// packs the V09 UI into the smaller, slower display.
// Override with ?kiosk=0 to force the desktop layout.
if (p.has("kiosk")) {
var k = p.get("kiosk") !== "0" && p.get("kiosk") !== "false";
localStorage.setItem("ui-kiosk-mode", k ? "1" : "0");
}
var stored = localStorage.getItem("ui-kiosk-mode");
var auto = location.hostname === "localhost"
|| location.hostname === "127.0.0.1"
|| location.hostname === "::1";
if (stored === "1" || (stored !== "0" && auto)) {
document.documentElement.classList.add("kiosk-mode");
}
} catch (_e) {}
})();
#svelte-dialog-host
#overlay(v-if="status != 'connected'")
@@ -57,7 +102,7 @@ html(lang="en")
.pi-temp-warning(v-if="80 <= state.rpi_temp",
title="Raspberry Pi temperature too high.")
.fa.fa-thermometer-full
.fa.fa-temperature-full
span.state-badge(:class="state_class", :title="mach_state_full")
span.dot
@@ -75,7 +120,7 @@ html(lang="en")
.sp-val v{{config.full_version}}
a.sp-act(v-if="show_upgrade()", href="#admin-general")
| Upgrade to v{{latestVersion}}
.fa.fa-exclamation-circle.upgrade-attention
.fa.fa-circle-exclamation.upgrade-attention
.sp-row
.sp-icon: .fa.fa-network-wired
.sp-text
@@ -122,13 +167,22 @@ html(lang="en")
.fa.fa-save
| &nbsp;Save{{modified ? '*' : ''}}
// Routed view (no keep-alive: Vue 1 has issues re-evaluating
// dynamic :class / v-if bindings on cached components when the
// route changes within the same kept-alive tree)
// Routed view. We keep instances alive across tab swaps so:
// - The Program tab's WebGL <path-viewer> canvas does not
// get destroyed and recreated each time (which caused a
// dark flash as the GL context cleared the new canvas
// before its first frame).
// - The Program tab's clusterize.js gcode list does not
// re-virtualize from scratch on every visit.
// - The Settings shell's child Svelte components stay
// mounted, preserving any in-flight form state.
// The settings-shell handles its own inner v-if cascade so
// the Vue 1 reactivity quirk that motivated removing
// keep-alive earlier no longer applies here.
.app-body
component(:is="currentView + '-view'", :index="index",
:config="config", :template="template", :state="state",
:sub-tab="sub_tab")
:sub-tab="sub_tab", keep-alive)
message.error-message(:show.sync="errorShow")
div(slot="header")

View File

@@ -52,7 +52,7 @@ script#console-view-template(type="text/x-template")
// ----- Messages -----
.messages-pane(v-show="sub === 'messages'")
.msg-empty(v-if="!$root.messages_log.length")
.fa.fa-check-circle
.fa.fa-circle-check
| &nbsp;No messages.
.msg(v-for="m in $root.messages_log",
:class="m.level === 'warning' ? 'warn' : 'info'", track-by="$index")

View File

@@ -35,8 +35,9 @@ script#control-view-template(type="text/x-template")
button.pure-button.button-error(@click="GCodeNotFound=false") OK
message(:show.sync="show_probe_dialog")
h3(slot="header") Probe Rotary
h3(slot="header") Choose probe type
div(slot="body")
p Pick which probe routine to run.
button.pure-button(:class="state['pw'] ? '' : 'load-on'", @click="showProbeDialog('xyz')") Probe XYZ
button.pure-button(:class="state['pw'] ? '' : 'load-on'", @click="showProbeDialog('z')") Probe Z
div(slot="footer")
@@ -46,7 +47,9 @@ script#control-view-template(type="text/x-template")
.control-grid
// ===== JOG =====
.jog-card
// Hidden only while a G-code program is running / paused /
// stopping. Jogging / homing / MDI moves do not hide it.
.jog-card(v-if="!is_program_executing")
.jog-head
.jog-title
| Jog
@@ -89,21 +92,25 @@ 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 — W axis (auxcnc) when enabled
// Row 4 — W axis (auxcnc) when enabled.
// W- | W+ | Probe XYZ | Probe Z
// "Home W" lives in the DRO table's actions column on the
// right, so it doesn't need a tile here.
template(v-if="w.enabled")
button.jbtn(@click="aux_jog_incr(-1)", :disabled="!w.enabled")
.fa.fa-arrow-down.ico
span.lbl W
button.jbtn.ghost(@click="aux_home()", :disabled="!w.enabled")
span.lbl Home
span W
button.jbtn(@click="aux_jog_incr(+1)", :disabled="!w.enabled")
.fa.fa-arrow-up.ico
span.lbl W+
button.jbtn(@click="show_probe_dialog=true",
button.jbtn(@click="showProbeDialog('xyz')",
:class="{'load-on': !state['pw']}")
.fa.fa-bullseye.ico
span.lbl Probe
span.lbl Probe XYZ
button.jbtn(@click="showProbeDialog('z')",
:class="{'load-on': !state['pw']}")
.fa.fa-bullseye.ico
span.lbl Probe Z
// Row 4 — A axis (rotary) when no W and rotary is enabled
// (Vue 1 has no v-else-if; we negate w.enabled explicitly.)
@@ -129,16 +136,72 @@ script#control-view-template(type="text/x-template")
.fa.fa-bullseye.ico
span.lbl Probe XYZ
button.jbtn.ghost(@click="zero()", :disabled="!can_set_axis")
.fa.fa-map-marker.ico
.fa.fa-location-dot.ico
span.lbl Zero all
button.jbtn(@click="showProbeDialog('z')",
:class="{'load-on': !state['pw']}")
.fa.fa-bullseye.ico
span.lbl Probe Z
button.jbtn.ghost(@click="home()", :disabled="!is_idle")
button.jbtn.ghost(@click="home()")
.fa.fa-home.ico
span.lbl Home all
// ===== NOW RUNNING (replaces jog grid only while a G-code
// program is actually executing). Jogging is excluded.
.running-panel(v-if="is_program_executing")
.running-top
div
.running-file
.fa.fa-file-code
span(v-if="state.selected") &nbsp;{{state.selected}}
span(v-else) &nbsp;{{(mach_state || 'BUSY').toLowerCase()}}
.running-meta
span(v-if="is_running") {{ (mach_state || 'RUNNING').toLowerCase() }}
span(v-if="is_holding") paused
span(v-if="is_holding && pause_reason") · {{pause_reason}}
span(v-if="is_stopping") stopping
span(v-if="toolpath.lines") · line {{state.line || 0 | number}} / {{toolpath.lines | number}}
span(v-if="plan_time_remaining") · ETA {{plan_time_remaining | time}}
.running-pct
| {{((progress || 0) * 100) | fixed 0}}
span %
.running-progress
div(:style="'width:' + ((progress || 0) * 100) + '%'")
.running-stats
.running-stat
.lbl Velocity
.val
unit-value(:value="state.v", precision="2", unit="", iunit="", scale="0.0254")
| &nbsp;{{metric ? 'm/min' : 'IPM'}}
.running-stat
.lbl Feed
.val
unit-value(:value="state.feed", precision="0", unit="", iunit="")
| &nbsp;{{metric ? 'mm/min' : 'IPM'}}
.running-stat
.lbl Spindle
.val
| {{(state.speed || 0) | fixed 0}}
span(v-if="state.s != null && !isNaN(state.s)") ({{state.s | fixed 0}})
| &nbsp;RPM
.running-stat
.lbl Tool
.val T{{state.tool || 0}}
.running-row
// While RUNNING the primary action is Pause; while HOLDING / STOPPING it's Resume.
button.tx-btn.pause(v-if="is_running", @click="pause()")
.fa.fa-pause
span.lbl PAUSE
button.tx-btn.run(v-if="is_holding || is_stopping", @click="unpause()")
.fa.fa-play
span.lbl RESUME
button.tx-btn.stop(@click="stop()")
.fa.fa-stop
span.lbl STOP
button.tx-btn.step(v-if="is_holding", @click="step()")
.fa.fa-forward-step
span.lbl STEP
// ===== DRO + status strip =====
.right-col
@@ -174,11 +237,11 @@ script#control-view-template(type="text/x-template")
button.icon-btn(:disabled="!can_set_axis",
:title=`'Set ${axis.toUpperCase()} axis position.'`,
@click=`show_set_position('${axis}')`)
.fa.fa-cog
.fa.fa-gear
button.icon-btn(:disabled="!can_set_axis",
:title=`'Zero ${axis.toUpperCase()} axis offset.'`,
@click=`zero('${axis}')`)
.fa.fa-map-marker
.fa.fa-location-dot
button.icon-btn(:disabled="!is_idle",
:title=`'Home ${axis.toUpperCase()} axis.'`,
@click=`home('${axis}')`)
@@ -201,9 +264,9 @@ script#control-view-template(type="text/x-template")
| &nbsp;{{w.tstate}}
.actions-cell
button.icon-btn(disabled, style="visibility:hidden")
.fa.fa-cog
.fa.fa-gear
button.icon-btn(disabled, style="visibility:hidden")
.fa.fa-map-marker
.fa.fa-location-dot
button.icon-btn(:disabled="!w.enabled",
title="Home W axis.", @click="aux_home()")
.fa.fa-home

View File

@@ -6,11 +6,11 @@ script#indicators-template(type="text/x-template")
tr
td
.fa.fa-plus-circle.io
.fa.fa-circle-plus.io
th Hi/+3.3v
th.separator
td
.fa.fa-minus-circle.io
.fa.fa-circle-minus.io
th Lo/Gnd
th.separator
td
@@ -22,7 +22,7 @@ script#indicators-template(type="text/x-template")
th Inactive
th.separator
td
.fa.fa-circle-o.io
.far.fa-circle.io
th Tristated/Disabled
table.inputs
@@ -169,14 +169,14 @@ script#indicators-template(type="text/x-template")
tr
th Motor
th(title="Overtemperature fault"): .fa.fa-thermometer-full
th(title="Overtemperature fault"): .fa.fa-temperature-full
th(title="Overcurrent motor channel A") A #[.fa.fa-bolt]
th(title="Predriver fault motor channel A")
| A #[.fa.fa-exclamation-triangle]
| A #[.fa.fa-triangle-exclamation]
th(title="Overcurrent motor channel B") B #[.fa.fa-bolt]
th(title="Predriver fault motor channel B")
| B #[.fa.fa-exclamation-triangle]
th(title="Driver communication failure"): .fa.fa-handshake-o
| B #[.fa.fa-triangle-exclamation]
th(title="Driver communication failure"): .fa.fa-handshake
th(title="Reset all motor flags")
.fa.fa-eraser(@click="motor_reset()")

View File

@@ -3,7 +3,7 @@ script#path-viewer-template(type="text/x-template")
.path-viewer-toolbar
.tool-button(title="Toggle path view size.",
@click="small = !small", :class="{active: !small}")
.fa.fa-arrows-alt
.fa.fa-up-down-left-right
.tool-button(@click="showTool = !showTool", :class="{active: showTool}",
title="Show/hide tool.")

View File

@@ -82,7 +82,7 @@ script#program-view-template(type="text/x-template")
span STOP
button.action-btn(@click="open_folder", :disabled="!is_ready",
title="Upload a new GCode folder.")
.fa.fa-folder-arrow-up.ico
.fa.fa-folder-plus.ico
span UPLOAD FOLDER
form.gcode-folder-input.file-upload
input#folderInput(type="file", @change="upload_folder",
@@ -126,10 +126,15 @@ script#program-view-template(type="text/x-template")
.fa.fa-arrow-down-wide-short
| &nbsp;{{files_sortby}}
// Body: gcode listing on the left, 3D viewer on the right
.program-body
// Body: gcode listing on the left, 3D viewer on the right.
// The 3D path-viewer is suppressed when the UI is loaded by
// the Pi's onboard kiosk browser — the VideoCore IV cannot
// run three.js at a usable frame rate. Off-Pi clients still
// see the full split.
.program-body(:class="{'no-preview': is_kiosk}")
gcode-viewer
path-viewer(:toolpath="toolpath", :state="state", :config="config")
path-viewer(v-if="!is_kiosk", :toolpath="toolpath",
:state="state", :config="config")
.progress-bar(v-if="toolpath_progress && toolpath_progress < 1",
title="Simulating GCode to check for errors, calculate ETA and generate 3D view.")

View File

@@ -24,21 +24,35 @@ script#settings-shell-view-template(type="text/x-template")
// Explicit v-if cascade so the inner template swaps reactively
// when sub changes (Vue 1's `<component :is>` does not always
// re-evaluate dynamic strings inside a kept-alive parent).
settings-view-inner(v-if="sub === 'settings'",
// The Svelte settings views read many config keys eagerly on
// attach (settings.units, settings.easy-adapter, motion.*),
// so we gate the inner mount on config_ready.
settings-view-inner(v-if="sub === 'settings' && config_ready",
section="display",
:index="index", :config="config", :template="template", :state="state")
admin-general-view(v-if="sub === 'admin-general'",
settings-view-inner(v-if="sub === 'probing' && config_ready",
section="probing",
:index="index", :config="config", :template="template", :state="state")
admin-network-view(v-if="sub === 'admin-network'",
settings-view-inner(v-if="sub === 'gcode' && config_ready",
section="gcode",
:index="index", :config="config", :template="template", :state="state")
motor-view(v-if="sub === 'motor'",
admin-general-view(v-if="sub === 'admin-general' && config_ready",
:index="index", :config="config", :template="template", :state="state")
tool-view(v-if="sub === 'tool'",
admin-network-view(v-if="sub === 'admin-network' && config_ready",
:index="index", :config="config", :template="template", :state="state")
io-view(v-if="sub === 'io'",
motor-view(v-if="sub === 'motor' && config_ready",
:index="index", :config="config", :template="template", :state="state")
macros-view(v-if="sub === 'macros'",
tool-view(v-if="sub === 'tool' && config_ready",
:index="index", :config="config", :template="template", :state="state")
help-view(v-if="sub === 'help'",
io-view(v-if="sub === 'io' && config_ready",
:index="index", :config="config", :template="template", :state="state")
cheat-sheet-view(v-if="sub === 'cheat-sheet'",
w-axis-view(v-if="sub === 'w-axis' && config_ready",
:index="index", :config="config", :template="template", :state="state")
macros-view(v-if="sub === 'macros' && config_ready",
:index="index", :config="config", :template="template", :state="state")
help-view(v-if="sub === 'help' && config_ready",
:index="index", :config="config", :template="template", :state="state")
cheat-sheet-view(v-if="sub === 'cheat-sheet' && config_ready",
:index="index", :config="config", :template="template", :state="state")
.settings-loading(v-if="!config_ready")
| Loading configuration…

View File

@@ -0,0 +1,4 @@
script#w-axis-view-template(type="text/x-template")
#w-axis-page
h1 W Axis (auxcnc)
#w-axis-mount

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

9
src/static/css/fa6.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -30,6 +30,16 @@ $jog-ghost-hov = #9ba6bb
$jog-ink = #fff
$jog-ghost-ink = $ink
// Lock html + body so nothing other than the explicit .app-body or
// inner scroll containers can scroll. Without this, autofocus inside
// nested Svelte components (Settings, Admin Network, etc.) can call
// scrollIntoView() on the html element and push our fixed header off
// the top of the viewport.
html, body
height 100%
overflow hidden
overscroll-behavior none
body
margin 0
font-family 'Inter', system-ui, -apple-system, sans-serif
@@ -78,6 +88,66 @@ tt
width 100%
overflow hidden
background $body-bg
// Hint to the browser that this layer is a stable composited
// surface so tab swaps inside it don't invalidate the whole page.
contain layout paint
isolation isolate
// Program tab pre-warmer. Mounts the program-view at app start so
// the WebGL canvas is already initialized when the user first
// clicks the Program tab avoids the first-time dark flash that
// happens when WebGL is created on demand. We render the component
// at full size off-screen with the same width as the live tab so
// the canvas dims match.
.program-warmer
position absolute
left -10000px
top 0
width 1920px
height 980px
pointer-events none
visibility hidden
// =====================================================================
// Tablet / kiosk mode
//
// When <html class="tablet-mode"> is set (via ?tablet=1, sticky in
// localStorage), pin the app shell to a fixed 1920 x 1080 box and
// scale it to fit the real viewport. The scale ratio is published as
// the --tablet-scale CSS variable by the inline script in index.pug.
//
// On the actual 10.8" 1920x1080 portable monitor the scale is 1; on
// a desktop browser at e.g. 1440x900 you get a faithfully shrunk
// preview of exactly what the kiosk renders.
// =====================================================================
html.tablet-mode
background #0f172a
overflow hidden
html.tablet-mode body
margin 0
width 100vw
height 100vh
overflow hidden
display flex
align-items center
justify-content center
html.tablet-mode .app-shell
width 1920px
height 1080px
flex 0 0 auto
transform scale(var(--tablet-scale, 1))
transform-origin center center
box-shadow 0 30px 60px rgba(0, 0, 0, 0.45)
border-radius 14px
// Keep dialog hosts above the scaled shell.
html.tablet-mode #svelte-dialog-host
position fixed
top 0
left 0
z-index 10000
.app-body
flex 1
@@ -100,7 +170,10 @@ tt
padding 0 24px
background $bg
border-bottom 1px solid $line
position relative
// sticky so the header stays visible even if a nested scroll
// container manages to move under it.
position sticky
top 0
z-index 30
.brand-blk
@@ -644,8 +717,12 @@ span.unit
text-transform capitalize
.path-viewer-content
background-color #333
background linear-gradient(to bottom, #666 0%, #222 100%);
// Solid dark background matching the WebGL renderer's clear
// colour. We used to use a gradient (#666 -> #222) but the
// visible-during-mount color difference between the gradient
// top and the GL clear bottom caused a brief dark flash when
// the canvas was reattached on every tab switch.
background-color #222
margin-bottom 0.5em
&.small
@@ -1073,6 +1150,150 @@ tt.save
font-size 0.85em
margin-left 2px
// =====================================================================
// NOW RUNNING panel
// Replaces the jog grid (left column) while the machine is not idle.
// Same outer dimensions as .jog-card so the rest of the layout doesn't
// reflow.
// =====================================================================
.control-page .running-panel
flex 1 1 auto
min-height 0
display flex
flex-direction column
gap 18px
padding 24px
border-radius 18px
color #fff
background linear-gradient(160deg, #0f172a 0%, #1e293b 100%)
.running-top
display flex
align-items flex-start
justify-content space-between
gap 18px
.running-file
font-size 1.6rem
font-weight 900
line-height 1.1
display flex
align-items center
gap 4px
.fa
color $accent
.running-meta
font-size 0.9rem
color #cbd5e1
font-weight 600
margin-top 6px
text-transform capitalize
.running-pct
font-family 'JetBrains Mono', monospace
font-size 3.6rem
font-weight 900
line-height 1
color #fff
span
font-size 2rem
color $accent
font-weight 800
margin-left 4px
.running-progress
height 14px
background rgba(255, 255, 255, 0.18)
border-radius 9999px
overflow hidden
width 100%
> div
height 100%
background $accent
transition width 0.4s ease-out
.running-stats
display grid
grid-template-columns repeat(4, 1fr)
gap 14px
.running-stat
background rgba(255, 255, 255, 0.06)
border-radius 14px
padding 14px 16px
.lbl
font-size 0.7rem
letter-spacing 0.14em
text-transform uppercase
color #cbd5e1
font-weight 700
.val
font-family 'JetBrains Mono', monospace
font-weight 900
font-size 1.5rem
margin-top 4px
color #fff
.running-row
display grid
grid-template-columns repeat(auto-fit, minmax(140px, 1fr))
gap 14px
margin-top auto
.running-row .tx-btn
display inline-flex
flex-direction column
align-items center
justify-content center
gap 6px
height 120px
border-radius 18px
border none
font-weight 800
letter-spacing 0.05em
cursor pointer
background #1e293b
color #fff
transition transform 0.06s, background 0.15s
.running-row .tx-btn .fa
font-size 2.4rem
.running-row .tx-btn .lbl
font-size 0.85rem
opacity 0.9
.running-row .tx-btn:hover
background #334155
.running-row .tx-btn:active
transform translateY(2px)
.running-row .tx-btn.pause
background #f59e0b
color #0f172a
.running-row .tx-btn.pause:hover
background #d97706
.running-row .tx-btn.run
background #16a34a
.running-row .tx-btn.run:hover
background #15803d
.running-row .tx-btn.stop
background #dc2626
.running-row .tx-btn.stop:hover
background #b91c1c
// Step segmented control
.step-seg
display inline-flex
@@ -1627,6 +1848,15 @@ tt.save
min-height 0
overflow hidden
// On the Pi's onboard kiosk browser the 3D toolpath preview is
// suppressed (Pi 3B's VideoCore IV can't run three.js fast
// enough), so the gcode listing claims the full width.
&.no-preview
grid-template-columns 1fr
> .gcode
border-right none
> .gcode
border-right 1px solid $line-soft
background #fafafa
@@ -1643,22 +1873,28 @@ tt.save
> .path-viewer
overflow hidden
min-height 0
height 100%
background #222 // matches the WebGL clear colour so any
// first-frame mismatch reads as the same dark
// panel rather than a flash.
display flex
flex-direction column
.path-viewer-content
flex 1 1 auto
width 100% !important
height auto !important
height 100% !important
min-height 0
float none !important
margin 0 !important
background #222
&.small .path-viewer-content
width 100% !important
height auto !important
height 100% !important
float none !important
margin 0 !important
background #222
.progress-bar
height 28px
@@ -2054,3 +2290,283 @@ tt.save
h1, h2, h3
margin-top 0
.settings-loading
color $muted
font-style italic
padding 24px
// =====================================================================
// KIOSK MODE compact layout for the controller's own onboard browser
// (Pi 3B at 1366x768). Activated by `html.kiosk-mode` (auto-applied
// when location.hostname is localhost). All overrides target the V09
// shell so the desktop / portable touchscreen layout is unaffected.
// =====================================================================
html.kiosk-mode
font-size 13px
.app-head
flex 0 0 56px
height 56px
padding 0 12px
gap 10px
.brand-blk
display none
.estop
transform scale(0.6)
transform-origin right center
.tabs-host
height 56px
padding-left 0
.ktab
height 56px
padding 0 14px
font-size 14px
gap 6px
.fa
font-size 16px
.ktab .ktab-underline,
.ktab.active::after
bottom 0
.app-body
padding 8px
gap 8px
// Control page: tighten everything
.control-page
gap 8px
// Keep two columns at 1366x768 vertical space is the constraint.
// Shrink the jog column from 720px to 540px so the DRO has more
// breathing room.
.control-page .control-grid
grid-template-columns 540px 1fr
gap 8px
.control-page .right-col
grid-template-rows 1fr 110px
gap 8px
.control-page .jog-card
padding 10px
.control-page .jog-head
margin-bottom 8px
.control-page .jog-title
font-size 14px
.control-page .jog-grid
gap 6px
.jbtn
border-radius 10px
font-size 0.85rem
.ico
font-size 1.15rem
.lbl
font-size 0.65rem
.control-page .dro-head, .control-page .dro-row
grid-template-columns 56px 1fr 0.85fr 0.85fr 90px 90px 1fr
column-gap 0.4rem
padding 6px 10px
.control-page .dro-head
font-size 0.65rem
.control-page .dro-row
font-size 0.95rem
// Smaller axis-action buttons in DRO rows.
.control-page .dro-row .icon-btn
width 44px
height 44px
font-size 1rem
border-radius 9px
.control-page .status-strip
grid-template-columns repeat(2, 1fr)
gap 8px
.control-page .stat-card
padding 8px 12px
.stat-label
font-size 9px
.stat-val
font-size 18px
margin-top 2px
.stat-sub
font-size 11px
margin-top 0
// Macros: 8 -> 4 column grid; shorter buttons.
.control-page .macro-row
grid-template-columns repeat(4, 1fr)
gap 6px
.macro-btn
height 56px
font-size 0.85rem
border-radius 10px
// Now-running panel: tighten paddings, smaller percent text.
.control-page .running-panel
padding 12px
gap 10px
.running-file
font-size 1.15rem
.running-pct
font-size 2.2rem
span
font-size 1.2rem
.running-stats
grid-template-columns repeat(2, 1fr)
gap 6px
.running-stat
padding 8px 10px
.val
font-size 1rem
// Program page: gcode listing fills (path-viewer is hidden via JS).
.program-page
gap 8px
// Settings shell: tighter rail.
.settings-shell .rail
padding 8px
.settings-shell .rail .rail-item
padding 8px 10px
font-size 0.9rem
.settings-content
padding 10px
// System pill / sidebar headers smaller.
.system-pill, .sidebar-pill
font-size 0.8rem
// Inside system-pill, the icon + text need explicit spacing.
.system-pill > * + *,
.sidebar-pill > * + *
margin-left 6px
// Modal dialogs scaled down for the smaller viewport.
.modal-bg
font-size 13px
// =====================================================================
// LEGACY-CHROMIUM FLEX-GAP FALLBACK
// =====================================================================
// Chromium 72 (which ships on the controller's Pi 3B) does not support
// `gap` on flexbox containers it landed in Chrome 84 (2020-05). Grid
// `gap` IS supported (Chrome 57+) so anything `display: grid` is fine.
//
// We can't use @supports not (gap: 1px) because Chromium 72 reports
// it supports `gap` (the spec considers it a grid-only property in
// older snapshots). Instead, we add explicit margin-based spacing for
// every known flex container in the V09 shell that visibly breaks on
// the Pi kiosk. The modern CSS gap rule still applies in newer Chrome
// these rules are inert (margin-left:auto rules elsewhere keep
// their meaning) because the gap pushes children apart anyway.
// App header brand block, tabs, system pill, estop
.app-head > * + *
margin-left 18px
.app-head .brand-blk > * + *
margin-left 14px
// Tabs ribbon the tab button itself uses flex+gap to space its
// icon and label. Without flex-gap support those collapse.
.ktab > * + *
margin-left 0.55rem
// Header system pill (sys-btn) and machine state badge
.sys-btn > * + *
margin-left 0.55rem
.state-badge > * + *
margin-left 0.6rem
// Jog card title row
.control-page .jog-head > * + *
margin-left 12px
// Now-running panel top, file/meta, stats, row, transport
.control-page .running-panel > * + *
margin-top 18px
.running-top > * + *
margin-left 18px
.running-row > * + *
margin-left 14px
.transport-row > * + *
margin-left 14px
// Console card tabs
.console-card .ptab-bar > * + *
margin-left 6px
// Settings shell rail and content
.settings-shell > * + *
margin-left 18px
// Macro buttons (.macro-btn icon + label)
.macro-btn > * + *
margin-left 0.6rem
// Jog buttons (.jbtn ico + lbl) column flex, vertical gap
.jbtn > * + *
margin-top 4px
// DRO actions cell already uses gap; emulate via margin
.actions-cell > * + *
margin-left 10px
// Generic ".header"-style flex rows in older subpages
.app-body .pure-form > * + *
margin-top 4px
// =====================================================================
// KIOSK-MODE-SPECIFIC LEGACY FALLBACKS
// =====================================================================
html.kiosk-mode
.app-head > * + *
margin-left 10px
.control-page
> * + *
margin-top 8px
.control-page .control-grid
// grid-gap works on Chromium 72 so nothing here.
.control-page .right-col
// grid-gap works.
grid-template-rows 1fr 158px // tighter than the desktop 158px
.running-row > * + *
margin-left 6px
.control-page .jog-head > * + *
margin-left 8px

View File

@@ -2,7 +2,10 @@
import configTemplate from "../../../resources/config-template.json";
import ScreenRotationDialog from "$dialogs/ScreenRotationDialog.svelte";
import ConfigTemplatedInput from "./ConfigTemplatedInput.svelte";
import WAxisSettings from "./WAxisSettings.svelte";
// WAxisSettings is mounted directly by the V09 settings shell at
// #w-axis instead of being embedded here — see
// src/pug/templates/w-axis-view.pug.
// import WAxisSettings from "./WAxisSettings.svelte";
import SetTimeDialog from "$dialogs/SetTimeDialog.svelte";
import Button, { Label } from "@smui/button";
@@ -19,8 +22,8 @@
<h1>Settings</h1>
<div class="pure-form pure-form-aligned">
<h2>User Interface</h2>
<fieldset>
<h2 id="sec-display" data-sec="display">User Interface</h2>
<fieldset data-sec="display">
<div class="pure-control-group">
<label for="screen-rotation" />
<Button
@@ -46,8 +49,8 @@
</div> -->
</fieldset>
<h2>Units</h2>
<fieldset>
<h2 id="sec-units" data-sec="display">Units</h2>
<fieldset data-sec="display">
<ConfigTemplatedInput key={`settings.units`} />
<div class="tip">
Note, units sets both the machine default units and the units used in motor configuration. GCode program-start,
@@ -55,13 +58,13 @@
</div>
</fieldset>
<h2>Easy Adapter</h2>
<fieldset>
<h2 id="sec-easy-adapter" data-sec="display">Easy Adapter</h2>
<fieldset data-sec="display">
<ConfigTemplatedInput key={`settings.easy-adapter`} />
</fieldset>
<h2>Probing</h2>
<fieldset>
<h2 id="sec-probing" data-sec="probing">Probing</h2>
<fieldset data-sec="probing">
<ConfigTemplatedInput key={`settings.probing-prompts`} />
<div class="tip">
Onefinity highly recommends that you keep the safety prompts
@@ -88,20 +91,19 @@
{/each}
</fieldset>
<fieldset>
<h2>GCode</h2>
<fieldset data-sec="gcode">
<h2 id="sec-gcode" data-sec="gcode">GCode</h2>
{#each Object.keys(configTemplate.gcode) as key}
<ConfigTemplatedInput key={`gcode.${key}`} />
{/each}
</fieldset>
<h2 id="w-axis">W Axis (auxcnc)</h2>
<fieldset>
<WAxisSettings />
</fieldset>
<!-- W Axis (auxcnc) is now its own routed page in the V09
settings shell (#w-axis). Keep the SettingsView free of
that section so we don't render it twice. -->
<h2>Path Accuracy</h2>
<fieldset>
<h2 id="sec-path-accuracy" data-sec="gcode">Path Accuracy</h2>
<fieldset data-sec="gcode">
<ConfigTemplatedInput key={`settings.max-deviation`} />
<div class="tip">
@@ -124,8 +126,8 @@
</div>
</fieldset>
<h2>Cornering Speed (Advanced)</h2>
<fieldset>
<h2 id="sec-cornering" data-sec="gcode">Cornering Speed (Advanced)</h2>
<fieldset data-sec="gcode">
<ConfigTemplatedInput key={`settings.junction-accel`} />
<div class="tip">
Junction acceleration limits the cornering speed the planner

View File

@@ -51,7 +51,7 @@
>
<div slot="trailingIcon">
{#if valid}
<Icon class="fa fa-check-circle-o" style="color: green;" />
<Icon class="fa fa-circle-check" style="color: green;" />
{/if}
</div>
<HelperText persistent slot="helper">{helperText}</HelperText>

View File

@@ -6,6 +6,7 @@ matchAll.shim();
import AdminNetworkView from "$components/AdminNetworkView.svelte";
import SettingsView from "$components/SettingsView.svelte";
import HelpView from "$components/HelpView.svelte";
import WAxisSettings from "$components/WAxisSettings.svelte";
import DialogHost, { showDialog } from "$dialogs/DialogHost.svelte";
import { handleConfigUpdate, setDisplayUnits } from "$lib/ConfigStore";
import { handleControllerStateUpdate } from "$lib/ControllerState";
@@ -22,6 +23,9 @@ export function createComponent(component: string, target: HTMLElement, props: R
case "HelpView":
return new HelpView({ target, props });
case "WAxisSettings":
return new WAxisSettings({ target, props });
case "DialogHost":
return new DialogHost({ target, props });