Files
signalk-plugin/plugin/tools/kiosk/layout-client.js
Giuseppe Raffa c2c1598226 feat: Implement rulesets and layout management for kiosk plugin
- Added rulesets manager to handle various data types and updates via HTTP and WebSocket.
- Introduced layout store for managing kiosk layouts with caching and server synchronization.
- Enhanced dashboard and data routes to support new layout and ruleset features.
- Updated kiosk HTML and JavaScript to utilize new layout rendering and data binding.
- Removed obsolete map route and integrated map functionality into the new tile renderer.
- Improved Telegram commands to reflect changes in data structure and logging.
- Refactored weather fetching intervals to prevent multiple instances.
- Added SSE stream for real-time layout updates in the kiosk.
2026-05-12 10:17:54 +02:00

99 lines
3.6 KiB
JavaScript

/**
* layout-client.js — apre l'EventSource /meb/kiosk/stream del plugin, riceve
* il layout corrente (e i successivi update), e ridisegna la griglia.
*
* Il plugin NON edita: si limita a mostrare. La logica di binding ai dati
* SignalK (local stream) e' in data-binder.js; il render dei tile e' in
* tile-renderer.js.
*/
(function () {
const grid = document.getElementById('grid');
const chip = document.getElementById('statusChip');
let activeUnbinds = []; // funzioni di unbind per i tile correnti
let currentLayout = null;
function setStatus(text, isErr = false) {
chip.textContent = text;
chip.classList.toggle('err', isErr);
}
function clearGrid() {
for (const off of activeUnbinds) { try { off(); } catch (_) {} }
activeUnbinds = [];
grid.innerHTML = '';
}
function applyLayout(layoutRow) {
const content = layoutRow?.content;
if (!content || !Array.isArray(content.tiles)) {
console.warn('[KIOSK] layout vuoto/non valido');
return;
}
clearGrid();
currentLayout = layoutRow;
// grid CSS variables
grid.style.setProperty('--cols', String(content.grid?.cols ?? 12));
grid.style.setProperty('--rows', String(content.grid?.rows ?? 8));
grid.style.setProperty('--gap', String(content.grid?.gap ?? 4) + 'px');
// theme (opzionale)
if (content.theme === 'light') {
document.body.style.background = '#f4f5f7';
document.body.style.color = '#111';
} else {
document.body.style.background = '#0b1220';
document.body.style.color = '#fff';
}
for (const tile of content.tiles) {
const el = document.createElement('div');
el.className = `tile tile-${tile.type}`;
el.style.gridColumn = `${tile.x + 1} / span ${tile.w}`;
el.style.gridRow = `${tile.y + 1} / span ${tile.h}`;
grid.appendChild(el);
const r = window.tileRenderer.get(tile.type);
if (!r) { el.innerHTML = `<div class="title">tipo sconosciuto: ${tile.type}</div>`; continue; }
try {
r.mount(el, tile);
const off = r.bind(el, tile, window.dataBinder);
if (off) activeUnbinds.push(off);
} catch (err) {
console.error('[KIOSK] render', tile.id, err);
el.innerHTML = `<div class="title">errore render ${tile.id}</div>`;
}
}
setStatus(`v${layoutRow.version || '?'} · ${content.tiles.length} tiles`);
}
// ====== Stream del plugin (SSE) ======
function openStream() {
const es = new EventSource('/meb/kiosk/stream');
es.addEventListener('layout', (ev) => {
try { applyLayout(JSON.parse(ev.data)); }
catch (err) { console.warn('[KIOSK] layout parse:', err); }
});
es.onerror = () => setStatus('stream disconnesso', true);
es.onopen = () => setStatus('stream ok');
}
// status connessione SignalK
if (window.dataBinder) {
window.dataBinder.onConnState(({ connected }) => {
if (!connected) setStatus('signalk disconnesso', true);
else if (currentLayout) setStatus(`v${currentLayout.version || '?'} · live`);
});
}
// bootstrap: prima fetch one-shot del layout (per non aspettare il primo evento SSE),
// poi apri lo stream per gli update successivi
fetch('/meb/kiosk/layout')
.then(r => r.ok ? r.json() : null)
.then(l => { if (l) applyLayout(l); })
.catch(() => {})
.finally(openStream);
})();