fix(ui): move power switch from Dashboard card to header

Wrong placement: a standalone Dashboard card was awkward and easy to
miss depending on layout/scroll position, and didn't fit the
multi-printer case as naturally as the header does - the header
already tracks the active printer via renderPrinterDropdown() (single
name display + dropdown when >1 printer configured), so hooking the
power toggle in there means it always reflects whichever printer is
currently active, including after switching via the dropdown.

Replaces the removed card-power dashboard card with a compact
segmented An/Aus toggle in the header, next to the printer name and
before the status badge - same has_power_control gating and
/kx/printers/{id}/power + power-status endpoints as before, just
relocated and restyled for the header's smaller scale (.h-power-btn
instead of the dashboard-sized .spd-btn, same spd-active highlight
convention for the current state).

Also reverts the DASH_CARD_KEYS/DASH_DEFAULT_LAYOUT change and the
initDashGrid() backward-compat migration for old saved layouts, since
there's no longer a "power" grid card to add.
This commit is contained in:
2026-08-24 19:05:25 +02:00
parent 8f0db9d16c
commit 13f84979fe
3 changed files with 32 additions and 51 deletions

View File

@ -329,43 +329,46 @@ function initPrinters(){
var idx=window._printerIndex||1;
_activePrinter=_printers.find(function(p){return String(p.id)===String(idx)})||_printers[0]||null;
renderPrinterDropdown();
_initDashboardPowerCard();
}).catch(function(){});
}
// ── Dashboard Power Switch (Issue: hard to find + poorly visible in the
// ── Header Power Switch (Issue: hard to find + poorly visible in the
// Printers tab; mirrors togglePrinterPower()/_refreshPrinterPowerIcon() for
// the active printer, on a second, more prominent toggle on the Dashboard) ──
function _initDashboardPowerCard(){
var card=document.getElementById('card-power');
if(!card)return;
if(!_activePrinter||!_activePrinter.has_power_control){card.style.display='none';return;}
card.style.display='';
_refreshDashboardPower();
// the currently active printer, right next to its name/status in the header
// so it works the same in single- and multi-printer setups - switching the
// active printer via the dropdown re-evaluates has_power_control for the
// newly active one instead of a fixed dashboard card tied to whichever
// printer happened to be active on page load) ──
function _initHeaderPower(){
var wrap=document.getElementById('h-power-wrap');
if(!wrap)return;
if(!_activePrinter||!_activePrinter.has_power_control){wrap.style.display='none';return;}
wrap.style.display='flex';
_refreshHeaderPower();
}
function _refreshDashboardPower(){
function _refreshHeaderPower(){
if(!_activePrinter||!_activePrinter.has_power_control)return;
var bridgeUrl=(_activePrinter.bridge_url||'').replace(/\/+$/,'');
fetch(bridgeUrl+'/kx/printers/'+encodeURIComponent(_activePrinter.id)+'/power-status',{signal:AbortSignal.timeout(5000)})
.then(function(r){return r.json()})
.then(function(d){
var onBtn=document.getElementById('d-power-on'), offBtn=document.getElementById('d-power-off');
var onBtn=document.getElementById('h-power-on'), offBtn=document.getElementById('h-power-off');
if(!onBtn||!offBtn)return;
onBtn.classList.toggle('spd-active',d.state==='on');
offBtn.classList.toggle('spd-active',d.state==='off');
})
.catch(function(){/* status endpoint optional - toggle just stays neutral */});
}
function setDashboardPower(action){
function setHeaderPower(action){
if(!_activePrinter)return;
if(action==='off'&&!confirm(T.printers_power_off_confirm||'Turn printer power off? Make sure no print is running.'))return;
var onBtn=document.getElementById('d-power-on'), offBtn=document.getElementById('d-power-off');
var onBtn=document.getElementById('h-power-on'), offBtn=document.getElementById('h-power-off');
if(onBtn)onBtn.style.opacity='0.5';
if(offBtn)offBtn.style.opacity='0.5';
post('/kx/printers/'+encodeURIComponent(_activePrinter.id)+'/power',{action:action}).then(function(){
if(onBtn)onBtn.style.opacity='1';
if(offBtn)offBtn.style.opacity='1';
setTimeout(_refreshDashboardPower,1500);
setTimeout(_refreshHeaderPower,1500);
}).catch(function(e){
if(onBtn)onBtn.style.opacity='1';
if(offBtn)offBtn.style.opacity='1';
@ -395,6 +398,7 @@ function renderPrinterDropdown(){
if(wrap)wrap.style.display='none';
if(single)single.style.display='';
}
_initHeaderPower();
}
function togglePrinterDropdown(){
var menu=document.getElementById('printer-dropdown-menu');
@ -460,9 +464,8 @@ function applyLang(){
setText('d-card-temps',T.card_temps);
setText('d-card-lightfan',T.card_light_fan);
setText('d-card-speed',T.card_speed);
setText('d-card-power',T.card_power);
setText('d-power-lbl-on',T.printers_power_on_short||'An');
setText('d-power-lbl-off',T.printers_power_off_short||'Aus');
setText('h-power-lbl-on',T.printers_power_on_short||'An');
setText('h-power-lbl-off',T.printers_power_off_short||'Aus');
setText('d-card-cam',T.card_cam);
setText('d-card-ams',T.panel_ams_title);
setText('d-lbl-elapsed',T.lbl_elapsed);
@ -2137,7 +2140,7 @@ var pollTimer;
}).catch(function(){});
poll();pollTimer=setInterval(poll,ms);
setInterval(_loadSpoolmanStatus,30000);
setInterval(_refreshDashboardPower,30000);
setInterval(_refreshHeaderPower,30000);
// initDashGrid() is called at the very end of the file, after all the
// DASH_* var declarations below have executed (they are hoisted but not yet
// assigned at this point in the IIFE).
@ -2154,7 +2157,7 @@ var pollTimer;
// - columnOpts.breakpoints -> 1-column below 700px GRID width (sidebar-aware)
var DASH_STORAGE_KEY='dashLayout';
var DASH_LAYOUT_VERSION=3; // v1/v2 = older editors; discard those states
var DASH_CARD_KEYS=['camera','progress','temps','motion','speed','power','fan','ams'];
var DASH_CARD_KEYS=['camera','progress','temps','motion','speed','fan','ams'];
// Layout arrays in GridStack save()/load() format. cellHeight=60px.
var DASH_DEFAULT_LAYOUT=[
{id:'camera', x:0,y:0, w:12,h:7},
@ -2162,9 +2165,8 @@ var DASH_DEFAULT_LAYOUT=[
{id:'temps', x:0,y:13,w:6, h:7},
{id:'motion', x:6,y:13,w:6, h:7},
{id:'speed', x:0,y:20,w:6, h:3},
{id:'power', x:6,y:20,w:6, h:3},
{id:'fan', x:0,y:23,w:6, h:3},
{id:'ams', x:0,y:26,w:12,h:4},
{id:'fan', x:6,y:20,w:6, h:3},
{id:'ams', x:0,y:23,w:12,h:4},
];
// "Wide desktop" preset (Blaim, Issue #89): big camera left, settings center,
// motion right.
@ -2242,20 +2244,6 @@ function initDashGrid(){
var item=_dashItem(n.id);
if(item){_dashGrid.removeWidget(item,false);item.style.display='none';}
});
// Cards added after a user's layout was already saved (e.g. the "power"
// card) never appear otherwise: load(...,false) only updates ids already
// present in the saved layout, it never adds new ones. Append any missing
// known card next to its default-layout neighbour instead of silently
// dropping it - the alternative (bumping DASH_LAYOUT_VERSION) would wipe
// every user's custom layout just to surface one new card.
var known={};
state.layout.forEach(function(n){known[n.id]=true;});
_dashHidden.forEach(function(n){known[n.id]=true;});
DASH_DEFAULT_LAYOUT.forEach(function(def){
if(known[def.id])return;
var item=_dashItem(def.id);
if(item)_dashGrid.addWidget(item,{x:def.x,y:def.y,w:def.w,h:def.h});
});
}else{
_dashGrid.load(DASH_DEFAULT_LAYOUT,false);
}

View File

@ -35,6 +35,10 @@
</div>
<div id="h-pname-single" class="hname">Anycubic Kobra X</div>
<span id="h-version" style="font-size:11px;opacity:.5;margin-left:6px"></span>
<div id="h-power-wrap" style="display:none;margin-left:10px;gap:2px;background:var(--raised);border:1px solid var(--border);border-radius:6px;padding:2px" title="Steckdose">
<button id="h-power-on" class="h-power-btn" onclick="setHeaderPower('on')">🟢 <span id="h-power-lbl-on">An</span></button>
<button id="h-power-off" class="h-power-btn" onclick="setHeaderPower('off')">🔴 <span id="h-power-lbl-off">Aus</span></button>
</div>
<div class="hbadge" id="h-badge"><span class="dot"></span><span id="h-state">Standby</span></div>
<button class="theme-btn" onclick="toggleTheme()">☀ / ☾</button>
<button class="theme-btn" onclick="showPanel('settings')" id="settings-btn" title="Einstellungen"></button>
@ -335,21 +339,6 @@
</div>
</div>
<!-- Steckdose (Power Switch) -->
<div class="card" id="card-power" data-card="power" gs-w="6" gs-h="3" style="display:none">
<div class="card-title"><span>🔌</span> <span id="d-card-power">Steckdose</span></div>
<div style="display:flex;gap:8px;margin-top:4px">
<button class="spd-btn" id="d-power-on" onclick="setDashboardPower('on')">
<span class="spd-icon">🟢</span>
<span id="d-power-lbl-on">An</span>
</button>
<button class="spd-btn" id="d-power-off" onclick="setDashboardPower('off')">
<span class="spd-icon">🔴</span>
<span id="d-power-lbl-off">Aus</span>
</button>
</div>
</div>
<!-- Lüfter -->
<div class="card" id="card-fan" data-card="fan" gs-w="6" gs-h="3">
<div class="card-title"><span>🌀</span> <span id="d-card-lightfan">Lüfter</span></div>

View File

@ -202,6 +202,10 @@ main{flex:1;overflow-y:auto;padding:20px}
.spd-btn .spd-icon{font-size:22px}
.spd-bar{height:4px;border-radius:2px;background:var(--border);margin-top:10px;overflow:hidden}
.spd-bar-fill{height:100%;border-radius:2px;background:linear-gradient(90deg,var(--accent2),var(--accent));transition:width .3s}
.h-power-btn{border:1.5px solid transparent;background:none;color:var(--txt2);
border-radius:5px;padding:3px 8px;font-size:12px;font-weight:600;cursor:pointer;transition:all .15s}
.h-power-btn:hover{color:var(--txt)}
.h-power-btn.spd-active{border-color:var(--accent);background:rgba(0,200,255,.12);color:var(--accent)}
/* ── TIME CARDS ── */
.time-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;margin-top:8px}