Dashboard Studio 1.6.1: show version, load fresh files after updates

- Version shown under About in the dashboard settings, with a link to
  the repository; /api/version reports it
- index.html is never cached and stamps the version on every asset URL,
  so browsers can't keep running old files after an update
- Open screens check the version after reconnecting and every 5 minutes
  and reload into the new version (editing only shows a message);
  pending saves are flushed first

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-25 22:36:53 +01:00
parent 4187c8f38d
commit fa3e58b747
8 changed files with 69 additions and 5 deletions
+31 -1
View File
@@ -4,6 +4,9 @@
const Widgets = window.Widgets;
const Icons = window.Icons;
const tr = window.I18n.t, LOCALE = window.I18n.locale;
// Filled in by the server; "dev" when index.html is opened some other way.
const APP_VERSION = (document.querySelector('meta[name="ds-version"]')?.content || "dev").replace("__VERSION__", "dev");
const REPO_URL = "https://git.melvanveen.nl/veenm/ha-dashboard-studio";
/** Option labels: a direct translation when there is one, else the capitalised value. */
const optLabel = (o) => (tr(o) !== o ? tr(o) : tr(cap(String(o).replace("-", " "))));
const $ = (s, el = document) => el.querySelector(s);
@@ -245,13 +248,15 @@
let metrics = { colW: 0, rh: 72, gap: 14, cols: 12, compact: false };
// ------------------------------------------------------------ persistence
let saveTimer = null;
let saveTimer = null, savePending = false;
function scheduleSave() {
savePending = true;
$("#save-status").textContent = tr("Saving…");
clearTimeout(saveTimer);
saveTimer = setTimeout(save, 700);
}
async function save() {
savePending = false;
try {
const r = await fetch(new URL("api/dashboards", BASE), { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(store) });
if (!r.ok) throw new Error(r.status);
@@ -677,6 +682,11 @@
<div class="section"><div class="section-title">${tr("Language")}</div>
${fieldHTML({ k: "lang", t: "select", l: "Language on this device", o: Object.keys(window.I18n.languages), labels: Object.fromEntries(Object.entries(window.I18n.languages).map(([c, n]) => [c, c === "auto" ? tr(n) : n])) }, window.I18n.preference(), "device")}
</div>
<div class="section"><div class="section-title">${tr("About")}</div>
<div class="about"><img src="static/favicon.png?v=${esc(APP_VERSION)}" alt="" width="40" height="40">
<div><div style="font-weight:600">Dashboard Studio</div><div class="muted small">${tr("Version {0}", esc(APP_VERSION))}</div></div></div>
<a class="btn" href="${REPO_URL}" target="_blank" rel="noopener" style="width:100%;margin-top:10px;text-decoration:none">${Icons.svg("globe")}${tr("Source code and changes")}</a>
</div>
<p class="hint">${tr("Select a widget to edit it.")}</p>`;
}
@@ -1369,7 +1379,27 @@
const c = $("#conn");
c.classList.toggle("ok", ok);
c.textContent = ok ? tr("Connected") : tr("Offline");
// An update restarts the app, which drops the connection: check right after reconnecting.
if (ok) checkVersion();
};
/** If the app was updated while this page was open, load the new version. */
let updateNoticeShown = false;
async function checkVersion() {
if (APP_VERSION === "dev") return;
let latest;
try { latest = (await api("api/version")).version; } catch { return; }
if (!latest || latest === APP_VERSION) return;
if (editMode) { // never yank the editor away; just say so once
if (!updateNoticeShown) toast(tr("Dashboard Studio was updated to {0}. Reload the page to use it.", latest));
updateNoticeShown = true;
return;
}
toast(tr("Dashboard Studio was updated to {0}. Reloading…", latest));
if (savePending) { clearTimeout(saveTimer); await save(); } // keep edits still waiting to be saved
setTimeout(() => location.reload(), 2500);
}
setInterval(checkVersion, 5 * 60 * 1000);
ha.onChange = onStateChange;
/** Translate the static page: elements marked data-i18n, plus every aria-label and title. */
+3
View File
@@ -242,6 +242,9 @@
"Weather (optional)": "Weer (optioneel)", "Show today’s high and low": "Toon de hoogste en laagste temperatuur van vandaag",
"High {0}° · Low {1}°": "Max {0}° · min {1}°", "High {0}°": "Max {0}°",
"Menu style": "Menustijl", "icons": "Alleen pictogrammen", "labels": "Met namen",
"About": "Over", "Version {0}": "Versie {0}", "Source code and changes": "Broncode en wijzigingen",
"Dashboard Studio was updated to {0}. Reload the page to use it.": "Dashboard Studio is bijgewerkt naar {0}. Herlaad de pagina om de nieuwe versie te gebruiken.",
"Dashboard Studio was updated to {0}. Reloading…": "Dashboard Studio is bijgewerkt naar {0}. Opnieuw laden…",
"Today": "Vandaag", "Tomorrow": "Morgen", "All day": "Hele dag", "Busy": "Bezet",
};
+1
View File
@@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="theme-color" content="#f2f2f7" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#0f0f11" media="(prefers-color-scheme: dark)">
<meta name="ds-version" content="__VERSION__">
<title>Dashboard Studio</title>
<link rel="icon" type="image/png" href="static/favicon.png">
<link rel="stylesheet" href="static/style.css">
+2
View File
@@ -439,6 +439,8 @@ body.kiosk #tabnav { top: 6px; max-height: calc(100vh - 20px); }
}
.surface-plain #tabnav { background: transparent; border-color: transparent; backdrop-filter: none; -webkit-backdrop-filter: none; }
.i.flip { transform: rotate(180deg); }
.about { display: flex; align-items: center; gap: 12px; }
.about img { border-radius: 10px; flex: none; }
.tab-list { display: flex; flex-direction: column; gap: 6px; margin-bottom: 4px; }
.tab-item { display: flex; gap: 4px; align-items: center; }
.tab-item .input { min-width: 0; }