diff --git a/dashboard_studio/CHANGELOG.md b/dashboard_studio/CHANGELOG.md index def8cce..5a5722f 100644 --- a/dashboard_studio/CHANGELOG.md +++ b/dashboard_studio/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## 2.6.0 + +Easier to use on a phone, in the browser and in Haven (now version 2.3.0, offered as an update in the app). + +- **Back closes what's open:** Android's back gesture in Haven, and the browser's back button, now close an open sheet or the top bar instead of leaving the app. +- **Haven finds your Home Assistant:** when you set it up or add a home, Haven lists the Home Assistant it finds on your home network. Tap it instead of typing the address. +- **Haven keeps only homes that work:** an address that doesn't connect is no longer saved as a home; it is filled in again so you can correct it. Removing a home asks first, in the app itself. +- **Haven says what it's doing:** the loading screen says which home it is connecting to, and after a while offers to choose another home or address. +- **A phone opens a phone dashboard:** on a phone, Dashboard Studio opens the dashboard you last used on that phone, or else the first one designed for phones; and always on the dashboard, not the builder. +- **Connecting, not offline:** while it first connects, the top bar says *Connecting…* instead of a red *Offline*. +- **The ⋯ button steps aside:** on a phone dashboard, the round button in the corner moves out of the way while you scroll down, so it doesn't cover a card, and comes back when you scroll up. +- **Friendlier details:** tapping a sensor or device shows what it is now in words, a line of the last 24 hours for sensors, and buttons for blinds, media players and vacuums. The entity ID and attributes are folded away under *Technical details*. +- Weather reads *Partly cloudy* instead of *Partlycloudy* (and the same for a few other states). +- Fixed: on a lit lamp tile in the phone sizes, the text was white on a pale lamp colour. + ## 2.5.0 - **Phone sizes:** on a phone dashboard every card now comes in four sizes, like widgets on a phone: **Small** (half width, square), **Medium** (full width, two rows), **Large** (full width, four rows) and **Row** (full width, one line). Pick one under **Size on the phone** in the inspector, or drag the corner: it snaps to the nearest size. diff --git a/dashboard_studio/DOCS.md b/dashboard_studio/DOCS.md index 1173466..1546bcf 100644 --- a/dashboard_studio/DOCS.md +++ b/dashboard_studio/DOCS.md @@ -43,10 +43,10 @@ Under **Address for the link**, use the address of Home Assistant on your home n Haven shows the dashboards and nothing else: no builder and no Admin screen, for admins too. 1. On the phone, scan the QR code under **Admin → Haven app** (or tap **Download Haven** on the phone itself), open the file and allow installing apps from that source when Android asks (once). -2. Open Haven and enter the address you use for Home Assistant. On the phone itself, **Open in Haven** fills it in. +2. Open Haven and tap your Home Assistant under **On this network** (Haven finds it on your home Wi-Fi), or enter the address you use for it. On the phone itself, **Open in Haven** fills it in. 3. Sign in with your own Home Assistant login, once. -From then on Haven opens straight on your dashboard, full screen with its own icon, and keeps you signed in. It works with plain http addresses too. Kiosk mode keeps the screen on and hides Android's bars. To connect to another Home Assistant: tap the phone button in the top bar (admins) or the kiosk corner five times (everyone else), then **Change address**. Haven needs Android 7 or newer. The earlier *Dashboard Studio* Android app is replaced by Haven; you can uninstall it. +From then on Haven opens straight on your dashboard, full screen with its own icon, and keeps you signed in. It works with plain http addresses too. Kiosk mode keeps the screen on and hides Android's bars. To add another home, such as a holiday home, or switch between them: tap the phone button in the top bar (admins) or the kiosk corner five times (everyone else), then **Homes and addresses**. An address that doesn't connect isn't kept. If connecting takes long, the loading screen offers to choose another home or address. Android's back gesture closes an open sheet first; with nothing open, it puts Haven in the background. Haven needs Android 7 or newer. The earlier *Dashboard Studio* Android app is replaced by Haven; you can uninstall it. - **Several homes:** under **Change address**, **Add another home** connects Haven to another Home Assistant, such as a holiday home or your parents'. Each home keeps its own sign-in; tap a home to open it, × removes it. - **Home screen widgets:** long-press your home screen, choose **Widgets → Haven**, and pick a light, switch, scene or thermostat. A tap does what it does on the dashboard; a thermostat gets − and + for its temperature. @@ -297,6 +297,9 @@ On a phone, such a dashboard fills the whole screen, without the top bar with th ### On phones +- **Which dashboard opens:** on a phone, Dashboard Studio opens the dashboard last used on that phone, or else the first one designed for phones (a link, the device's own dashboard or the person's dashboard still come first). It always opens on the dashboard, not the builder. +- **Details:** tap a sensor or device for its details: what it is now, a line of the last 24 hours for sensors, and its buttons. The entity ID and attributes are under *Technical details*. Back (the gesture, or the browser's button) closes the sheet. + - **Install as an app:** on Android, use **Haven** (see above). The phone button in the top bar explains the other options: browsers can install Dashboard Studio as a web app when Home Assistant is opened over **https** (open Dashboard Studio in its own tab first); on iPhone and iPad, apps on the home screen can't use Home Assistant's sign-in, so use the sidebar in the Home Assistant app. - **Shortcuts:** tick **Shortcut on the app icon** in the settings of a button, tile, light, thermostat or garage card. When Dashboard Studio is installed from the browser on Android, long-press its icon to run one straight away (up to four shortcuts; Haven and iPhones don't support this). - **Offline:** each device keeps the last known states of its dashboard. When the connection drops, the dashboard keeps showing them with a small note saying from when, and catches up when it reconnects. diff --git a/dashboard_studio/config.yaml b/dashboard_studio/config.yaml index b0cdac6..1537529 100644 --- a/dashboard_studio/config.yaml +++ b/dashboard_studio/config.yaml @@ -1,5 +1,5 @@ name: Dashboard Studio -version: "2.5.0" +version: "2.6.0" slug: dashboard_studio description: Drag-and-drop dashboard builder with a free-form grid, live entity widgets and themes. url: https://git.melvanveen.nl/veenm/ha-dashboard-studio diff --git a/dashboard_studio/web/app.js b/dashboard_studio/web/app.js index 08814a6..ea06347 100644 --- a/dashboard_studio/web/app.js +++ b/dashboard_studio/web/app.js @@ -301,8 +301,8 @@ const p = (t, cls = "") => `
${esc(t)}
`; let body, actions = ``; if (IN_APP) { - body = p(tr("This is Haven, connected to {0}.", location.host)); - actions += ``; + body = p(tr("This is Haven, connected to {0}. You can add another home, such as a holiday home, or switch between them.", location.host)); + actions += ``; } else if (android) { // A home screen app from the browser needs https, and Home Assistant's own app can't make one at all: Haven always works. body = p(tr("Get Haven, the Android app for your dashboards. It opens full screen with its own icon and keeps you signed in.")) @@ -325,7 +325,7 @@ if (browserInstall) actions += ``; } if (!IN_APP && !android && isAdmin()) body += p(tr("For Android phones there is Haven: see Admin, Haven app."), "muted small"); - const choice = await openModal(`| ${esc(cap(k))} | ${esc(typeof v === "object" ? JSON.stringify(v) : v)} |
| ${tr("Entity ID")} | ${esc(eid)} |
| ${esc(cap(k))} | ${esc(typeof v === "object" ? JSON.stringify(v) : v)} |
${esc(tr("This invite ends {0}.", new Date(me.until * 1000).toLocaleString(LOCALE, { weekday: "long", hour: "2-digit", minute: "2-digit" })))}
` : ""}${others.length ? `${tr("This is the only dashboard you can see. An admin can add more for you.")}
`} -${tr("This closes by itself in a few seconds.")}
`, (e, close) => { const x = e.target.closest("[data-x]")?.dataset.x; if (x) close(x); }); @@ -3003,11 +3055,23 @@ if (on) peekTimer = setTimeout(() => document.body.classList.remove("peek"), 15000); } $("#phone-menu").onclick = () => peek(!document.body.classList.contains("peek")); + // Scrolling down, the ⋯ button steps aside so it doesn't sit on a card; scrolling up brings it back. + let lastScrollY = 0; + document.addEventListener("scroll", (e) => { + if (!document.body.classList.contains("phone-ui")) return; + const el = e.target === document ? document.scrollingElement : e.target; + if (!el || el.closest?.(".w, #modal")) return; // a list inside a card, or a sheet: not the page + const y = el.scrollTop || 0; + if (Math.abs(y - lastScrollY) < 6) return; + document.body.classList.toggle("menu-away", y > lastScrollY && y > 40); + lastScrollY = y; + }, { capture: true, passive: true }); // ------------------------------------------------------------ chrome wiring function setCurrent(id) { current = store.dashboards.find((d) => d.id === id) || store.dashboards[0]; lsSet("ds.current", current.id); + if (phoneScreen.matches) lsSet("ds.currentPhone", current.id); activeTabId = lsGet(`ds.tab.${current.id}`); guestOn = false; guestCache = null; awayState = null; awayFrom = null; undoStack.length = redoStack.length = 0; @@ -3721,10 +3785,14 @@ document.querySelectorAll("#menu [data-icon], #mode-seg [data-icon]").forEach((b) => b.insertAdjacentHTML("afterbegin", Icons.svg(b.dataset.icon))); } + let everConnected = false; ha.onStatus = (ok) => { const c = $("#conn"); + if (ok) everConnected = true; c.classList.toggle("ok", ok); - c.textContent = ok ? tr("Connected") : offlineText(); + // the first moments after opening are connecting, not offline + c.classList.toggle("connecting", !ok && !everConnected && !lastLive); + c.textContent = ok ? tr("Connected") : !everConnected && !lastLive ? tr("Connecting…") : offlineText(); showOffline(!ok); // An update restarts the app, which drops the connection: check right after reconnecting. if (ok) checkVersion(); @@ -3824,9 +3892,12 @@ const exists = (id) => id && store.dashboards.some((d) => d.id === id); // The screen it was on last time; people who aren't admins, and Haven, only get the dashboard. const lastMode = lsGet("ds.mode") || (lsGet("ds.edit") === "1" ? "builder" : "dashboard"); - mode = isAdmin() && !IN_APP ? lastMode : "dashboard"; + // the builder is for bigger screens: a phone always opens on the dashboard + mode = isAdmin() && !IN_APP && !phoneScreen.matches ? lastMode : "dashboard"; editMode = mode === "builder"; - setCurrent([hashId, dev?.dashboard, home, lsGet("ds.current")].find(exists)); + // On a phone: the one last used on this phone, else the first dashboard designed for phones. + const phone = phoneScreen.matches ? [lsGet("ds.currentPhone"), store.dashboards.find((d) => d.mobile)?.id] : []; + setCurrent([hashId, dev?.dashboard, home, ...phone, lsGet("ds.current")].find(exists)); if (!isAdmin()) { lockedKiosk = true; enterKiosk({ fullscreen: false }); } else if (location.hash.includes("kiosk") || shared.get("ds_kiosk") === "1" || (dev?.kiosk && !hashId)) enterKiosk({ fullscreen: false }); else setMode(mode); diff --git a/dashboard_studio/web/haven.apk b/dashboard_studio/web/haven.apk index 260bad6..53fb850 100644 Binary files a/dashboard_studio/web/haven.apk and b/dashboard_studio/web/haven.apk differ diff --git a/dashboard_studio/web/haven.json b/dashboard_studio/web/haven.json index 7fdabfb..c711432 100644 --- a/dashboard_studio/web/haven.json +++ b/dashboard_studio/web/haven.json @@ -1 +1 @@ -{"version": "2.2.0", "code": 2} +{"version": "2.3.0", "code": 3} diff --git a/dashboard_studio/web/i18n.js b/dashboard_studio/web/i18n.js index a6d0043..05d63cc 100644 --- a/dashboard_studio/web/i18n.js +++ b/dashboard_studio/web/i18n.js @@ -303,6 +303,10 @@ "Emptying": "Aan het legen", "Levelling": "Aan het egaliseren", "Sanitizing": "Aan het ontsmetten", "Sleeping": "Slaapt", "Ready": "Klaar voor gebruik", "Bin full": "Afvalbak vol", "Check the litter box": "Kijk even bij de kattenbak", "Cat inside": "Kat in de bak", "Needs attention": "Heeft aandacht nodig", "visits today": "bezoekjes vandaag", "visits": "bezoekjes", "visit": "bezoekje", + "Connecting…": "Verbinden…", "Technical details": "Technische details", "Entity ID": "Entiteit-ID", "The last 24 hours": "De afgelopen 24 uur", + "No history for the last 24 hours.": "Geen geschiedenis van de afgelopen 24 uur.", "24 hours ago": "24 uur geleden", "Low {0} · high {1}": "Laagst {0} · hoogst {1}", + "Homes and addresses": "Huizen en adressen", + "This is Haven, connected to {0}. You can add another home, such as a holiday home, or switch between them.": "Dit is Haven, verbonden met {0}. Je kunt nog een huis toevoegen, zoals een vakantiehuis, of tussen huizen wisselen.", "Size on the phone": "Formaat op de telefoon", "Small": "Klein", "Medium": "Middel", "Now {0}°": "Nu {0}°", "Nobody home": "Niemand thuis", "Tap to watch": "Tik om te kijken", "Returning": "Terug naar het dock", "Sun {0}": "Zon {0}", "Grid {0}": "Net {0}", "Sending back {0}": "Teruglevering {0}", "{0} open": "{0} open", "Nothing on the way": "Niets onderweg", "Partly open": "Half open", "{0} of {1} open": "{0} van {1} open", "Nothing planned": "Niets gepland", "Only show when the bin is full or something is wrong": "Alleen tonen als de afvalbak vol is of er iets mis is", "Clean": "Schoonmaken", "Auto": "Auto", "in the box today": "in de bak vandaag", "cat’s weight": "gewicht kat", "Clean now": "Nu schoonmaken", "Auto clean": "Automatisch schoonmaken", @@ -811,6 +815,12 @@ armed_home: "Ingeschakeld (thuis)", armed_night: "Ingeschakeld (nacht)", disarmed: "Uitgeschakeld", triggered: "Geactiveerd", }; + // States whose names don't read well as they are ("partlycloudy", "not_home"). + const STATES_EN = { + not_home: "Away", partlycloudy: "Partly cloudy", "clear-night": "Clear night", "lightning-rainy": "Thunder and rain", "snowy-rainy": "Sleet", + "windy-variant": "Windy", heat_cool: "Heat and cool", fan_only: "Fan only", above_horizon: "Above the horizon", below_horizon: "Below the horizon", + armed_away: "Armed (away)", armed_home: "Armed (home)", armed_night: "Armed (night)", armed_vacation: "Armed (vacation)", disarmed: "Disarmed", + }; const read = (k) => { try { return localStorage.getItem(k); } catch { return null; } }; function detect() { const pref = read("ds.lang"); @@ -837,7 +847,7 @@ return args.length ? s.replace(/\{(\d)\}/g, (_, i) => args[i] ?? "") : s; }, /** A Home Assistant state as readable text. */ - state: (st) => (lang === "nl" && STATES_NL[st]) || cap(st), + state: (st) => (lang === "nl" ? STATES_NL[st] : STATES_EN[st]) || cap(st), }; document.documentElement.lang = lang; })(); diff --git a/dashboard_studio/web/index.html b/dashboard_studio/web/index.html index 8830f23..fb93a5f 100644 --- a/dashboard_studio/web/index.html +++ b/dashboard_studio/web/index.html @@ -30,7 +30,7 @@Looking for Home Assistant…
Or enter the address yourself: