UX redesign V09: replace shell, split Program/Console/Settings
Implements the V09 mock end-to-end (per plans/2026-04-30_ux_redesign.md):
Top shell
- index.pug rebuilt around .app-shell with a slim 96px header.
- Underline-ribbon tab bar (Control / Program / Console / Settings)
replaces the old side menu and the inline #tab1..#tab4 system.
- Single 'All systems' pill collapses the legacy WiFi/Camera/Rotary/
IP/Version chip-soup into one popover (sys-popover) anchored to the
header; rotary toggle, camera feed and shutdown live there.
- Octagonal 88x88px STOP button wraps the existing <estop> SVG; STATE
pill with pulse-dot honors prefers-reduced-motion.
Routing
- app.js parse_hash maps every existing hash:
#control -> Control
#program / #program:auto -> Program
#console / #console:mdi|messages|indicators -> Console
#settings, #admin-general,
#admin-network, #motor:N, #tool, #io, #macros, #help,
#cheat-sheet -> Settings (rail picks inner)
- All deep links are preserved.
Control panel (control-view.pug + .js)
- 720px jog grid + 4-axis DRO + 4 KPI cards + 8-macro row.
- Jog tiles use V09 flat slate (#3f4b63) with diagonal helpers and
a ghost row for XY/Z origin shortcuts.
- Per-axis Settings/Set-zero/Home buttons grow to 72x72px.
- Status strip cards: State / Velocity-Feed / Spindle / Job. Tapping
the Spindle card opens the new override-drawer with feed + spindle
range inputs (resolved decision in plans/...).
- Macro row binds to state.macros.slice(0, 8); >8 lives in Settings.
- Drops the old <table> control-buttons, .info, .override and .tabs
blocks entirely.
Program panel (program-view.pug + .js)
- Extracts the Auto bar, file selectors, gcode-viewer and path-viewer
out of control-view.
- Action buttons (RUN/STOP/UPLOAD-FOLDER/UPLOAD-FILE/DOWNLOAD-FILE/
DELETE) at 84px with explicit color affordances.
- Reuses control-view's existing methods via the new program-mixin.
Console panel (console-view.pug + .js)
- Three sub-tabs: MDI / Messages / Indicators. Sub-tab persists in the
URL fragment (#console:messages etc.).
- MDI: terminal-style prompt + SEND, plus an 8-wide on-screen keypad
(G0/G1/G2/G3/G28/G92/M3/M5 + axis letters + CLEAR/SEND).
- Messages: pulls from .messages_log (mirrored from
state.messages); badge in the header tab counts unread.
- Indicators: mounts the existing <indicators> component.
Settings shell (settings-shell.pug + .js)
- New left rail navigator listing Display, Network, General/Firmware,
Spindle&Tool, IO, Motors 0..3, Macros, Cheat Sheet, Help.
- Inner area mounts the existing settings family templates via an
explicit v-if cascade (avoiding a Vue 1 :is reactivity quirk).
- Shutdown / Save buttons relocated from the dropped side menu.
JS plumbing
- main.js: Vue.config.async = false to keep dependent watchers in
sync when reactive data is mutated outside Vue's normal event loop
(e.g. from a hashchange listener).
- program-mixin.js extracted so control-view.js no longer carries the
file/macro/gcode methods that are now Program-only.
- control-view.js trimmed to jog/DRO/probe/home logic.
- console-view.js / settings-shell-view.js use a hashchange listener
+ local data props because Vue 1 cannot reliably observe
.sub_tab from a child component.
Stylus rewrite
- Removes the old .header (140px), .nav-header, .brand subtree, #menu,
#main, .control-view block, .info, .override, .toolbar, .macros-div,
.macros-button, the .tabs > input radio-tab system and the .control-
view #control media-query overrides. None of these are referenced
any more.
- Adds V09 tokens (jog/macro palette + accent + line/card colors) at
the top, the new shell rules, .ktab / .sys-btn / .state-badge /
.estop chrome, the .control-page grid, status strip + override
drawer, .program-page action / file bars and program body,
.console-page MDI keypad / messages / indicators panes, and the
.settings-shell rail.
- Adds a 1820px breakpoint that stacks the right column under the jog
on smaller portable monitors.
Hard cut: no config.ui.layout flag, the old shell is removed in this
single commit. side-menu.css is no longer included from index.pug.
Tested locally with agent-browser (1920x1080) on every top tab and
every settings sub-route; routing, active tab highlighting and inner
view selection all work without a controller connection.
This commit is contained in:
@@ -8,7 +8,6 @@ html(lang="en")
|
||||
|
||||
|
||||
style: include ../static/css/pure-min.css
|
||||
style: include ../static/css/side-menu.css
|
||||
|
||||
style: include ../static/css/font-awesome.min.css
|
||||
style: include ../static/css/Audiowide.css
|
||||
@@ -23,99 +22,113 @@ html(lang="en")
|
||||
|
||||
#overlay(v-if="status != 'connected'")
|
||||
span {{status}}
|
||||
|
||||
#layout
|
||||
a#menuLink.menu-link(href="#menu"): span
|
||||
|
||||
#menu
|
||||
button.save.pure-button.button-success(:disabled="!modified",
|
||||
@click="save") Save
|
||||
.app-shell
|
||||
header.app-head
|
||||
.brand-blk
|
||||
.brand-logo
|
||||
.brand-name ONEFINITY
|
||||
|
||||
.pure-menu
|
||||
ul.pure-menu-list
|
||||
li.pure-menu-heading
|
||||
a.pure-menu-link(href="#control") Control
|
||||
nav.tabs-host(role="tablist")
|
||||
a.ktab(:class="{active: top_tab === 'control'}", href="#control",
|
||||
title="Jog, DRO, macros")
|
||||
.fa.fa-gamepad
|
||||
span Control
|
||||
a.ktab(:class="{active: top_tab === 'program'}", href="#program",
|
||||
title="Run programs, files, toolpath preview")
|
||||
.fa.fa-list-ol
|
||||
span Program
|
||||
a.ktab(:class="{active: top_tab === 'console'}", href="#console",
|
||||
title="MDI, messages, indicators")
|
||||
.fa.fa-terminal
|
||||
span Console
|
||||
span.ktab-badge(v-if="messages_count") {{messages_count}}
|
||||
a.ktab(:class="{active: top_tab === 'settings'}", href="#settings",
|
||||
title="Configuration, network, macros")
|
||||
.fa.fa-sliders
|
||||
span Settings
|
||||
|
||||
li.pure-menu-heading
|
||||
a.pure-menu-link(href="#macros") Macros
|
||||
.head-spacer
|
||||
|
||||
li.pure-menu-heading
|
||||
a.pure-menu-link(href="#settings") Settings
|
||||
.sys-btn(@click.stop="toggle_sys_popover", :class="{open: sys_open}")
|
||||
span.pip(:class="sys_class")
|
||||
span.sys-text {{sys_summary}}
|
||||
.fa.fa-chevron-down
|
||||
|
||||
li.pure-menu-heading
|
||||
a.pure-menu-link(href="#motor:0") Motors
|
||||
.pi-temp-warning(v-if="80 <= state.rpi_temp",
|
||||
title="Raspberry Pi temperature too high.")
|
||||
.fa.fa-thermometer-full
|
||||
|
||||
li.pure-menu-item(v-for="motor in config.motors")
|
||||
a.pure-menu-link(:href="'#motor:' + $index") Motor {{$index}}
|
||||
span.state-badge(:class="state_class", :title="mach_state_full")
|
||||
span.dot
|
||||
span {{state_label}}
|
||||
|
||||
li.pure-menu-heading
|
||||
a.pure-menu-link(href="#tool") Tool
|
||||
.estop(:class="{active: state.es}")
|
||||
estop(@click="estop")
|
||||
|
||||
li.pure-menu-heading
|
||||
a.pure-menu-link(href="#io") I/O
|
||||
|
||||
li.pure-menu-heading
|
||||
a.pure-menu-link(href="#admin-general") Admin
|
||||
|
||||
li.pure-menu-item
|
||||
a.pure-menu-link(href="#admin-general") General
|
||||
|
||||
li.pure-menu-item
|
||||
a.pure-menu-link(href="#admin-network") Network
|
||||
|
||||
li.pure-menu-heading
|
||||
a.pure-menu-link(href="#cheat-sheet") Cheat Sheet
|
||||
|
||||
li.pure-menu-heading
|
||||
a.pure-menu-link(href="#help") Help
|
||||
|
||||
button.pure-button.pure-button-primary(@click="showShutdownDialog", style="width: 100%")
|
||||
.fa.fa-power-off
|
||||
|
||||
#main
|
||||
.nav-header
|
||||
.brand
|
||||
img(src="/images/onefinity_logo.png")
|
||||
.version
|
||||
div Version: v{{config.full_version}}
|
||||
div IP Address: {{config.ip}}
|
||||
div WiFi: {{config.wifiName}}
|
||||
a.upgrade-link(v-if="show_upgrade()", href="#admin-general")
|
||||
| Upgrade to v{{latestVersion}}
|
||||
.fa.fa-exclamation-circle.upgrade-attention(v-if="show_upgrade()")
|
||||
|
||||
.pi-temp-warning
|
||||
.fa.fa-thermometer-full(class="error",
|
||||
v-if="80 <= state.rpi_temp",
|
||||
title="Raspberry Pi temperature too high.")
|
||||
|
||||
.easy-adapter(v-if="is_easy_adapter_active")
|
||||
.round-dot
|
||||
div.easy-adapter-text Easy Adapter
|
||||
|
||||
.whitespace
|
||||
|
||||
div
|
||||
button.rotary-button(:disabled="!enable_rotary", :class="is_rotary_active && 'active'", @click="showSwitchRotaryModeDialog")
|
||||
img(src="/images/rotary.svg", alt="rotary", :style="is_rotary_active ? 'width:90%;' : 'width:85%;'")
|
||||
div.rotary-text Rotary
|
||||
|
||||
.video(title="Plug camera into USB.\n" +
|
||||
"Left click to toggle video size.\n" +
|
||||
"Right click to toggle crosshair.", @click="toggle_video",
|
||||
@contextmenu="toggle_crosshair", :class="video_size")
|
||||
// System popover (chip-soup destination)
|
||||
.sys-popover(v-if="sys_open", @click.stop="")
|
||||
.sp-row
|
||||
.sp-icon: .fa.fa-microchip
|
||||
.sp-text
|
||||
.sp-label Firmware
|
||||
.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
|
||||
.sp-row
|
||||
.sp-icon: .fa.fa-network-wired
|
||||
.sp-text
|
||||
.sp-label IP Address
|
||||
.sp-val {{config.ip}}
|
||||
.sp-row
|
||||
.sp-icon: .fa.fa-wifi(:class="{'sp-warn': config.wifiName === 'not connected'}")
|
||||
.sp-text
|
||||
.sp-label WiFi
|
||||
.sp-val {{config.wifiName}}
|
||||
a.sp-act(href="#admin-network", @click="sys_open=false") Configure
|
||||
.sp-row(v-if="enable_rotary")
|
||||
.sp-icon: img(src="/images/rotary.svg", alt="rotary")
|
||||
.sp-text
|
||||
.sp-label Rotary
|
||||
.sp-val {{is_rotary_active ? 'Active' : 'Inactive'}}
|
||||
button.sp-act(@click="showSwitchRotaryModeDialog")
|
||||
| {{is_rotary_active ? 'Disable' : 'Enable'}}
|
||||
.sp-row(v-if="is_easy_adapter_active")
|
||||
.sp-icon: .fa.fa-puzzle-piece
|
||||
.sp-text
|
||||
.sp-label Easy Adapter
|
||||
.sp-val Active
|
||||
.sp-row.video-row
|
||||
.sp-icon: .fa.fa-video
|
||||
.sp-text
|
||||
.sp-label Camera
|
||||
.sp-val {{has_camera ? 'Live' : 'Plug camera into USB'}}
|
||||
.sp-act(v-if="has_camera", @click="toggle_video")
|
||||
| {{video_size === 'small' ? 'Enlarge' : 'Shrink'}}
|
||||
.video(v-if="sys_open && has_camera", title="Camera feed",
|
||||
@click="toggle_video", @contextmenu="toggle_crosshair",
|
||||
:class="video_size")
|
||||
.crosshair(v-if="crosshair")
|
||||
.vertical
|
||||
.horizontal
|
||||
.box
|
||||
img(src="/api/video")
|
||||
img(src="/api/video", @error="has_camera=false")
|
||||
.sp-foot
|
||||
button.sp-shutdown(@click="showShutdownDialog")
|
||||
.fa.fa-power-off
|
||||
| Shutdown
|
||||
button.sp-save(:disabled="!modified", @click="save")
|
||||
.fa.fa-save
|
||||
| Save{{modified ? '*' : ''}}
|
||||
|
||||
.estop(:class="{active: state.es}")
|
||||
estop(@click="estop")
|
||||
|
||||
.content(class="{{currentView}}-view")
|
||||
component(:is="currentView + '-view'", :index="index",
|
||||
:config="config", :template="template", :state="state", keep-alive)
|
||||
// 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)
|
||||
.app-body
|
||||
component(:is="currentView + '-view'", :index="index",
|
||||
:config="config", :template="template", :state="state",
|
||||
:sub-tab="sub_tab")
|
||||
|
||||
message.error-message(:show.sync="errorShow")
|
||||
div(slot="header")
|
||||
|
||||
Reference in New Issue
Block a user