Dashboard Studio 1.14.0: new cards, kiosk announcements, guest mode, templates, sharing and phone app
- Seven new cards: parcels, air quality, water and gas usage, solar forecast, garage and gate, sports, Formula One - Kiosk: announcements from automations, tab rotation, guest mode, click sound - Editor: templates, widget presets, undo history, alignment guides, Lovelace import, share link and QR code, theme variants, suggestions, touch and hold actions - Phones: installable app with shortcuts, offline states, tab swipe, pull to refresh, bottom sheets, haptics, phone/large visibility, landscape layout, editing on the phone, away tab - Fixed: the weather forecast and to-do list could stay on Loading… Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -147,7 +147,9 @@ def record_history(old, new):
|
||||
async def index(request):
|
||||
# Stamp the version on every asset URL, so an update can never be served from a stale cache.
|
||||
html = (WEB_DIR / "index.html").read_text()
|
||||
html = re.sub(r'(static/[\w.-]+\.(?:js|css|png))"', rf'\1?v={VERSION}"', html)
|
||||
# Locally, files change without a new version number: stamp them with their last change instead.
|
||||
stamp = VERSION if not MOCK else f"{VERSION}-{int(max(f.stat().st_mtime for f in WEB_DIR.iterdir()))}"
|
||||
html = re.sub(r'(static/[\w.-]+\.(?:js|css|png))"', rf'\1?v={stamp}"', html)
|
||||
html = html.replace("__VERSION__", VERSION)
|
||||
return web.Response(text=html, content_type="text/html", headers={"Cache-Control": "no-store"})
|
||||
|
||||
@@ -198,6 +200,40 @@ async def version(request):
|
||||
return web.json_response({"version": VERSION}, headers={"Cache-Control": "no-store"})
|
||||
|
||||
|
||||
def all_widgets(widgets):
|
||||
for w in widgets or []:
|
||||
if isinstance(w, dict):
|
||||
yield w
|
||||
yield from all_widgets((w.get("cfg") or {}).get("children"))
|
||||
|
||||
|
||||
async def manifest(request):
|
||||
"""The web app manifest, so the dashboard can be installed on a phone's home screen.
|
||||
Widgets marked as a shortcut appear when you long-press the app icon (Android)."""
|
||||
shortcuts = []
|
||||
for d in load_store().get("dashboards", []):
|
||||
for t in d.get("tabs", []):
|
||||
for w in all_widgets(t.get("widgets")):
|
||||
if w.get("shortcut") and len(shortcuts) < 4:
|
||||
shortcuts.append({"name": str(w["shortcut"])[:40], "url": f"./?ds_action={w.get('id')}",
|
||||
"icons": [{"src": "static/icon-192.png", "sizes": "192x192", "type": "image/png"}]})
|
||||
body = {
|
||||
"name": "Dashboard Studio", "short_name": "Dashboards", "id": "./", "start_url": "./", "scope": "./",
|
||||
"display": "standalone", "background_color": "#0f1526", "theme_color": "#0f1526",
|
||||
"icons": [{"src": "static/icon-192.png", "sizes": "192x192", "type": "image/png"},
|
||||
{"src": "static/icon-512.png", "sizes": "512x512", "type": "image/png"},
|
||||
{"src": "static/app-icon.svg", "sizes": "any", "type": "image/svg+xml"}],
|
||||
"shortcuts": shortcuts,
|
||||
}
|
||||
return web.json_response(body, content_type="application/manifest+json", headers={"Cache-Control": "no-store"})
|
||||
|
||||
|
||||
async def service_worker(request):
|
||||
"""Network first; the last good copy of the page, its files and the dashboards when the network fails."""
|
||||
js = (WEB_DIR / "sw.js").read_text().replace("__VERSION__", VERSION)
|
||||
return web.Response(text=js, content_type="application/javascript", headers={"Cache-Control": "no-store"})
|
||||
|
||||
|
||||
async def get_dashboards(request):
|
||||
return web.json_response(load_store())
|
||||
|
||||
@@ -334,6 +370,8 @@ def make_app():
|
||||
app = web.Application(middlewares=[ingress_only], client_max_size=8 * 1024 * 1024)
|
||||
app.router.add_get("/", index)
|
||||
app.router.add_get("/api/version", version)
|
||||
app.router.add_get("/manifest.webmanifest", manifest)
|
||||
app.router.add_get("/sw.js", service_worker)
|
||||
app.router.add_get("/api/changelog", changelog)
|
||||
app.router.add_get("/api/whoami", whoami)
|
||||
app.router.add_get("/api/users", users)
|
||||
|
||||
Reference in New Issue
Block a user