Themes: five presets, chosen in admin, applied live to every dashboard
Server: one THEMES list; `theme` is read from the data directory's proddash.json like port/adminPasscode; GET /api/theme is public (dashboards need it before anyone logs in) and returns { theme, themes: [{ id, name, colors: { bg, panel, accent } }] }; POST /api/admin/theme { theme } validates against the list, writes the key into proddash.json keeping every other key, updates the in-memory value and broadcasts a `theme` shell event over /api/events.
style.css: four html[data-theme] variable sets appended at the end — Harbor (navy/cyan), Graphite (charcoal/blue), Ember (warm black/orange) and Daylight (light, for daytime FOH) — each swapping exactly the eight palette variables; :root stays Booth, the default.
shell.js: a self-contained theme section — the last-known theme (localStorage proddash:theme) goes on synchronously, then the server's answer, then live changes via the SSE event; one listener line added in subscribeEvents. Tile-configured colours are set on the tile and still win.
README: a Themes subsection under Configure and the `theme` row in the proddash.json key table.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
5d55609fbe
commit
f79e8abd92
@@ -1381,6 +1381,7 @@ function subscribeEvents() {
|
||||
try { eventsSource?.close(); } catch { /* not open */ }
|
||||
eventsSource = new EventSource('/api/events');
|
||||
eventsSource.addEventListener('modules-changed', () => handleModulesChanged());
|
||||
eventsSource.addEventListener('theme', handleThemeEvent);
|
||||
eventsSource.onerror = () => {
|
||||
if (eventsSource.readyState === EventSource.CLOSED) {
|
||||
clearTimeout(eventsRetry);
|
||||
@@ -1417,6 +1418,43 @@ async function handleModulesChanged() {
|
||||
}
|
||||
}
|
||||
|
||||
/* ── theme ──────────────────────────────────────────────────────────── */
|
||||
|
||||
/* The dashboard's palette is a server-wide choice (Admin → Theme). Five
|
||||
preset variable sets live in style.css as html[data-theme="…"]; applying
|
||||
one is setting that attribute. The last-known theme goes on synchronously
|
||||
from localStorage so a reload never flashes the default palette, then the
|
||||
server's answer (GET /api/theme) wins, and a `theme` shell event on
|
||||
/api/events switches every open dashboard the moment an admin picks
|
||||
another. Modules style with the shell variables, so they follow; a colour
|
||||
a tile is configured with is set on the tile itself and stays as chosen. */
|
||||
|
||||
const LS_THEME = 'proddash:theme';
|
||||
|
||||
function applyTheme(id) {
|
||||
const theme = String(id || '');
|
||||
if (!/^[a-z][a-z0-9-]{0,31}$/.test(theme)) return;
|
||||
document.documentElement.dataset.theme = theme;
|
||||
try { localStorage.setItem(LS_THEME, theme); } catch { /* private mode — fine */ }
|
||||
}
|
||||
|
||||
/** `theme` shell event from /api/events (wired in subscribeEvents). */
|
||||
function handleThemeEvent(e) {
|
||||
try {
|
||||
applyTheme(JSON.parse(e.data).theme);
|
||||
} catch { /* a malformed frame changes nothing */ }
|
||||
}
|
||||
|
||||
function initTheme() {
|
||||
try { applyTheme(localStorage.getItem(LS_THEME)); } catch { /* fine */ }
|
||||
fetch('/api/theme', { cache: 'no-store' })
|
||||
.then((res) => (res.ok ? res.json() : null))
|
||||
.then((body) => { if (body?.theme) applyTheme(body.theme); })
|
||||
.catch(() => { /* offline: the remembered theme stands */ });
|
||||
}
|
||||
|
||||
initTheme();
|
||||
|
||||
/* ── boot ───────────────────────────────────────────────────────────── */
|
||||
|
||||
async function boot() {
|
||||
|
||||
Reference in New Issue
Block a user