feat(ui): add power switch toggle to the Dashboard
User feedback: the power-control button for the smart-plug feature
was hard to find (only in the Printers tab, not the Dashboard where
users look first) and once found, easy to miss - a bare emoji icon
with no background/border, colored only via a muted gray/green text
color change.
Adds a "Steckdose"/Power Switch dashboard card with a segmented
An/Aus toggle styled like the print-speed selector (bordered pill,
accent-colored + tinted background when active) - much more visible
than a plain icon. Only rendered when the active printer has
power_on_url/power_off_url configured, mirroring the existing
has_power_control gating in the Printers tab. Reuses the existing
/kx/printers/{id}/power and power-status endpoints; the Printers-tab
button stays as-is for per-printer control in multi-printer setups.
Also handles the case where a user's saved GridStack dashboard layout
predates this card: initDashGrid() now appends any DASH_DEFAULT_LAYOUT
card missing from a saved layout instead of only updating matching
ids, so existing users get the new card without their custom layout
being wiped (which bumping DASH_LAYOUT_VERSION would otherwise do to
everyone).
This commit is contained in:
@ -277,8 +277,49 @@ 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
|
||||
// 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();
|
||||
}
|
||||
function _refreshDashboardPower(){
|
||||
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');
|
||||
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){
|
||||
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');
|
||||
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);
|
||||
}).catch(function(e){
|
||||
if(onBtn)onBtn.style.opacity='1';
|
||||
if(offBtn)offBtn.style.opacity='1';
|
||||
clog('Power-Fehler: '+e,'msg-err');
|
||||
});
|
||||
}
|
||||
function renderPrinterDropdown(){
|
||||
var wrap=document.getElementById('printer-dropdown-wrap');
|
||||
var single=document.getElementById('h-pname-single');
|
||||
@ -367,6 +408,9 @@ 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('d-card-cam',T.card_cam);
|
||||
setText('d-card-ams',T.panel_ams_title);
|
||||
setText('d-lbl-elapsed',T.lbl_elapsed);
|
||||
@ -2025,6 +2069,7 @@ var pollTimer;
|
||||
}).catch(function(){});
|
||||
poll();pollTimer=setInterval(poll,ms);
|
||||
setInterval(_loadSpoolmanStatus,30000);
|
||||
setInterval(_refreshDashboardPower,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).
|
||||
@ -2041,7 +2086,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','fan','ams'];
|
||||
var DASH_CARD_KEYS=['camera','progress','temps','motion','speed','power','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},
|
||||
@ -2049,8 +2094,9 @@ 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:'fan', x:6,y:20,w:6, h:3},
|
||||
{id:'ams', x:0,y:23,w:12,h:4},
|
||||
{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},
|
||||
];
|
||||
// "Wide desktop" preset (Blaim, Issue #89): big camera left, settings center,
|
||||
// motion right.
|
||||
@ -2128,6 +2174,20 @@ 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);
|
||||
}
|
||||
|
||||
@ -335,6 +335,21 @@
|
||||
</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>
|
||||
|
||||
@ -68,6 +68,7 @@
|
||||
"card_light_fan": "Lüfter",
|
||||
"card_progress": "Fortschritt",
|
||||
"card_speed": "Druckgeschwindigkeit",
|
||||
"card_power": "Steckdose",
|
||||
"card_temps": "Temperaturen",
|
||||
"confirm_cancel": "Druck wirklich abbrechen?",
|
||||
"dash_done": "Fertig",
|
||||
@ -228,6 +229,8 @@
|
||||
"printers_power_on": "Strom: An",
|
||||
"printers_power_off": "Strom: Aus",
|
||||
"printers_power_off_confirm": "Drucker-Strom ausschalten? Stelle sicher, dass kein Druck läuft.",
|
||||
"printers_power_on_short": "An",
|
||||
"printers_power_off_short": "Aus",
|
||||
"printers_remove_confirm": "Drucker \"{name}\" entfernen? Die Bridge startet neu.",
|
||||
"printers_switch": "Wechseln →",
|
||||
"progress_action_clear": "Leeren",
|
||||
|
||||
@ -68,6 +68,7 @@
|
||||
"card_light_fan": "Fan",
|
||||
"card_progress": "Progress",
|
||||
"card_speed": "Print Speed",
|
||||
"card_power": "Power Switch",
|
||||
"card_temps": "Temperatures",
|
||||
"confirm_cancel": "Really cancel the print?",
|
||||
"dash_done": "Done",
|
||||
@ -228,6 +229,8 @@
|
||||
"printers_power_on": "Power: On",
|
||||
"printers_power_off": "Power: Off",
|
||||
"printers_power_off_confirm": "Turn printer power off? Make sure no print is running.",
|
||||
"printers_power_on_short": "On",
|
||||
"printers_power_off_short": "Off",
|
||||
"printers_remove_confirm": "Remove printer \"{name}\"? The bridge will restart.",
|
||||
"printers_switch": "Switch →",
|
||||
"progress_action_clear": "Clear",
|
||||
|
||||
@ -68,6 +68,7 @@
|
||||
"card_light_fan": "Ventilador",
|
||||
"card_progress": "Progreso",
|
||||
"card_speed": "Velocidad de impresión",
|
||||
"card_power": "Enchufe",
|
||||
"card_temps": "Temperaturas",
|
||||
"confirm_cancel": "¿Realmente cancelar la impresión?",
|
||||
"dash_done": "Listo",
|
||||
@ -228,6 +229,8 @@
|
||||
"printers_power_on": "Alimentación: Encendida",
|
||||
"printers_power_off": "Alimentación: Apagada",
|
||||
"printers_power_off_confirm": "¿Apagar la alimentación de la impresora? Asegúrate de que no haya ninguna impresión en curso.",
|
||||
"printers_power_on_short": "Encendido",
|
||||
"printers_power_off_short": "Apagado",
|
||||
"printers_remove_confirm": "¿Eliminar impresora \"{name}\"? El bridge se reiniciará.",
|
||||
"printers_switch": "Cambiar →",
|
||||
"progress_action_clear": "Vaciar",
|
||||
|
||||
@ -68,6 +68,7 @@
|
||||
"card_light_fan": "Ventilateur",
|
||||
"card_progress": "Progression",
|
||||
"card_speed": "Vitesse d'impression",
|
||||
"card_power": "Prise électrique",
|
||||
"card_temps": "Températures",
|
||||
"confirm_cancel": "Vraiment annuler l'impression ?",
|
||||
"fd_cancel": "Annuler",
|
||||
@ -216,6 +217,8 @@
|
||||
"printers_power_on": "Alimentation : Allumée",
|
||||
"printers_power_off": "Alimentation : Éteinte",
|
||||
"printers_power_off_confirm": "Éteindre l'alimentation de l'imprimante ? Assurez-vous qu'aucune impression n'est en cours.",
|
||||
"printers_power_on_short": "Marche",
|
||||
"printers_power_off_short": "Arrêt",
|
||||
"printers_remove_confirm": "Supprimer l'imprimante \"{name}\" ? Le bridge va redémarrer.",
|
||||
"printers_switch": "Changer →",
|
||||
"progress_action_clear": "Vider",
|
||||
|
||||
@ -68,6 +68,7 @@
|
||||
"card_light_fan": "Ventola",
|
||||
"card_progress": "Avanzamento",
|
||||
"card_speed": "Velocità di stampa",
|
||||
"card_power": "Presa di corrente",
|
||||
"card_temps": "Temperature",
|
||||
"confirm_cancel": "Annullare davvero la stampa?",
|
||||
"fd_cancel": "Annulla",
|
||||
@ -216,6 +217,8 @@
|
||||
"printers_power_on": "Alimentazione: Accesa",
|
||||
"printers_power_off": "Alimentazione: Spenta",
|
||||
"printers_power_off_confirm": "Spegnere l'alimentazione della stampante? Assicurati che non sia in corso alcuna stampa.",
|
||||
"printers_power_on_short": "Acceso",
|
||||
"printers_power_off_short": "Spento",
|
||||
"printers_remove_confirm": "Rimuovere la stampante \"{name}\"? Il bridge si riavvierà.",
|
||||
"printers_switch": "Cambia →",
|
||||
"progress_action_clear": "Cancella",
|
||||
|
||||
@ -68,6 +68,7 @@
|
||||
"card_light_fan": "风扇",
|
||||
"card_progress": "进度",
|
||||
"card_speed": "打印速度",
|
||||
"card_power": "电源插座",
|
||||
"card_temps": "温度",
|
||||
"confirm_cancel": "确定要取消打印吗?",
|
||||
"dash_done": "完成",
|
||||
@ -228,6 +229,8 @@
|
||||
"printers_power_on": "电源:开",
|
||||
"printers_power_off": "电源:关",
|
||||
"printers_power_off_confirm": "关闭打印机电源?请确认当前没有正在进行的打印任务。",
|
||||
"printers_power_on_short": "开",
|
||||
"printers_power_off_short": "关",
|
||||
"printers_remove_confirm": "移除打印机 \"{name}\"? Bridge 将重启。",
|
||||
"printers_switch": "切换 →",
|
||||
"progress_action_clear": "清除",
|
||||
|
||||
Reference in New Issue
Block a user