/** * 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 = `
tipo sconosciuto: ${tile.type}
`; 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 = `
errore render ${tile.id}
`; } } 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); })();