Files
ha-dashboard-studio/haven/assets/setup.html
T
2026-10-31 18:48:32 +01:00

143 lines
9.4 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
:root { color-scheme: dark; }
body { margin: 0; min-height: 100vh; background: linear-gradient(180deg, #17384a 0%, #0d1d29 60%); color: #f4eee4; font: 16px/1.45 system-ui, sans-serif; display: flex; align-items: center; justify-content: center; }
main { width: 100%; max-width: 420px; padding: 28px 22px; box-sizing: border-box; }
img { width: 80px; height: 80px; border-radius: 20px; display: block; margin-bottom: 18px; box-shadow: 0 10px 30px rgba(0, 0, 0, .35); }
.name { font-size: 15px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: #f4a531; margin: 0 0 4px; }
h1 { font-size: 26px; margin: 0 0 8px; }
p { margin: 0 0 18px; color: #a9b8c2; }
.msg { background: rgba(255, 69, 58, .15); color: #ffb4ae; padding: 10px 14px; border-radius: 12px; }
.msg:empty { display: none; }
label { display: block; font-size: 14px; font-weight: 600; margin-bottom: 6px; }
input { width: 100%; box-sizing: border-box; font: inherit; color: inherit; background: rgba(255, 255, 255, .07); border: 1px solid rgba(255, 255, 255, .14); border-radius: 12px; padding: 12px 14px; }
input:focus { outline: 2px solid #f4a531; border-color: transparent; }
.hint { font-size: 13px; margin: 8px 0 22px; }
button { width: 100%; font: inherit; font-weight: 700; color: #2b1a02; background: #f4a531; border: 0; border-radius: 12px; padding: 13px; }
button:active { opacity: .85; }
.homes { display: flex; flex-direction: column; gap: 8px; margin: 0 0 22px; }
.homes:empty { display: none; }
.home { display: flex; align-items: center; gap: 8px; }
.home .go { flex: 1; text-align: left; background: rgba(255, 255, 255, .08); color: #f4eee4; font-weight: 600; padding: 12px 14px; display: flex; flex-direction: column; }
.home .go small { font-weight: 400; color: #a9b8c2; font-size: 13px; }
.home .go.on { outline: 2px solid #f4a531; }
.home .x { width: 44px; flex: none; background: transparent; color: #a9b8c2; font-size: 20px; padding: 12px 0; }
.add { background: transparent; color: #f4a531; border: 1.5px solid rgba(244, 165, 49, .5); margin-bottom: 22px; }
#f.hide { display: none; }
.found { margin: 0 0 22px; }
.found h2 { font-size: 14px; font-weight: 600; margin: 0 0 8px; }
.found .go { display: flex; flex-direction: column; text-align: left; background: rgba(255, 255, 255, .08); color: #f4eee4; padding: 12px 14px; margin-bottom: 8px; }
.found .go small { font-weight: 400; color: #a9b8c2; font-size: 13px; }
.found .look { display: flex; align-items: center; gap: 10px; font-size: 14px; color: #a9b8c2; margin: 0 0 6px; }
.found .look::before { content: ""; width: 14px; height: 14px; border-radius: 50%; border: 2px solid #f4a531; border-right-color: transparent; animation: spin .9s linear infinite; flex: none; }
.found .look.done::before { display: none; }
.found .look[hidden] { display: none; }
@keyframes spin { to { transform: rotate(360deg); } }
.or { font-size: 14px; color: #a9b8c2; margin: 0 0 12px; }
.home.ask { flex-wrap: wrap; background: rgba(255, 69, 58, .12); border-radius: 12px; padding: 12px 14px; }
.home.ask p { flex-basis: 100%; margin: 0 0 10px; color: #f4eee4; }
.home.ask button { flex: 1; padding: 10px; }
.home.ask .keep { background: rgba(255, 255, 255, .1); color: #f4eee4; }
.home.ask .rm { background: #ff453a; color: #fff; }
</style>
</head>
<body>
<main>
<img src="icon.png" alt="">
<p class="name">Haven</p>
<h1 data-t="title">Your home’s dashboards</h1>
<p data-t="intro">Enter the address you use to open Home Assistant, then sign in with your own login. You only do this once.</p>
<p class="msg" id="msg"></p>
<div class="homes" id="homes"></div>
<button type="button" class="add" id="add" hidden data-t="add">Add another home</button>
<div class="found" id="found" hidden><h2 data-t="foundTitle">On this network</h2><p class="look" id="look" data-t="looking">Looking for Home Assistant…</p><div id="foundList"></div><p class="or" data-t="or">Or enter the address yourself:</p></div>
<form id="f">
<label for="addr" data-t="label">Home Assistant address</label>
<input id="addr" type="text" inputmode="url" autocomplete="url" autocapitalize="off" spellcheck="false" placeholder="http://homeassistant.local:8123" required>
<p class="hint" data-t="hint">Ask whoever set up Home Assistant: it’s on the Admin screen of Dashboard Studio, under Haven app.</p>
<button type="submit" data-t="go">Connect</button>
</form>
</main>
<script>
const NL = {
title: "De dashboards van je huis",
intro: "Vul het adres in waarmee je Home Assistant opent en log in met je eigen account. Dat doe je maar één keer.",
label: "Adres van Home Assistant",
hint: "Vraag het aan wie Home Assistant heeft ingesteld: het staat in Dashboard Studio op het beheerscherm, onder Haven-app.",
go: "Verbinden",
add: "Nog een huis toevoegen",
offline: "Home Assistant is niet bereikbaar op dit adres. Controleer het adres en je verbinding.",
notfound: "Dashboard Studio staat niet in Home Assistant. Vraag wie Home Assistant beheert om het te installeren.",
noaddress: "Dashboard Studio is gevonden, maar het adres kon niet worden opgevraagd. Tik in Dashboard Studio op Openen in Haven, of vraag wie Home Assistant beheert.",
error: "Verbinden lukte niet: {0}",
foundTitle: "Op dit netwerk",
looking: "Home Assistant zoeken…",
notFound: "Niet gevonden op dit netwerk.",
or: "Of vul het adres zelf in:",
remove: "Verwijderen", keep: "Bewaren", removeQ: "{0} uit Haven verwijderen?",
};
const EN = {
offline: "Home Assistant can't be reached at this address. Check the address and your connection.",
notfound: "Dashboard Studio isn't installed in Home Assistant. Ask whoever manages Home Assistant to install it.",
noaddress: "Dashboard Studio was found, but its address couldn't be looked up. Tap Open in Haven in Dashboard Studio, or ask whoever manages Home Assistant.",
error: "Couldn't connect: {0}",
notFound: "None found on this network.",
remove: "Remove", keep: "Keep", removeQ: "Remove {0} from Haven?",
};
const T = navigator.language.startsWith("nl") ? NL : EN;
if (T === NL) document.querySelectorAll("[data-t]").forEach((el) => { el.textContent = NL[el.dataset.t] || el.textContent; });
const app = window.HavenApp;
const why = app ? app.message() : "", detail = app ? app.detail() : "";
if (why) document.getElementById("msg").textContent = (T[why] || T.error).replace("{0}", detail || why);
const addr = document.getElementById("addr");
addr.value = app ? app.address() : "";
document.getElementById("f").onsubmit = (e) => { e.preventDefault(); app.setup(addr.value.trim()); };
// Several homes (a holiday home, your parents'): open one, remove one, or add another.
const homes = app ? JSON.parse(app.homes()) : [];
const box = document.getElementById("homes"), form = document.getElementById("f"), add = document.getElementById("add");
const esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[c]));
const removeLabel = T === NL ? "Verwijderen" : "Remove";
box.innerHTML = homes.map((h) => `<div class="home"><button type="button" class="go ${h.current ? "on" : ""}" data-id="${esc(h.id)}">${esc(h.name)}<small>${esc(h.ha)}</small></button>
<button type="button" class="x" data-x="${esc(h.id)}" aria-label="${removeLabel} ${esc(h.name)}">×</button></div>`).join("");
const words = (k) => (T[k] || EN[k] || k);
box.onclick = (e) => {
const go = e.target.closest("[data-id]"), x = e.target.closest("[data-x]"), b = e.target.closest("[data-rm], [data-keep]");
if (go) app.switchHome(go.dataset.id);
// removing asks first, right there in the list
if (x) {
const row = x.closest(".home"), name = row.querySelector(".go").firstChild.textContent;
row.classList.add("ask");
row.innerHTML = `<p>${esc(words("removeQ").replace("{0}", name))}</p><button type="button" class="keep" data-keep>${words("keep")}</button><button type="button" class="rm" data-rm="${esc(x.dataset.x)}">${words("remove")}</button>`;
}
if (b?.dataset.rm) app.removeHome(b.dataset.rm);
if (b && "keep" in b.dataset) location.reload();
};
const adding = !(homes.length && homes.some((h) => h.current) && !why);
if (!adding) { form.classList.add("hide"); add.hidden = false; }
add.onclick = () => app.addHome();
// Home Assistant announces itself on the home network: list what is found, to tap instead of typing.
if (adding && app && app.findHomes) {
const wrap = document.getElementById("found"), list = document.getElementById("foundList"), look = document.getElementById("look");
const known = new Set(homes.map((h) => h.ha.toLowerCase()));
app.findHomes();
wrap.hidden = false;
let n = 0;
const tick = () => {
const found = JSON.parse(app.found()).filter((f) => !known.has(f.url.toLowerCase()));
list.innerHTML = found.map((f) => `<button type="button" class="go" data-url="${esc(f.url)}">${esc(f.name)}<small>${esc(f.url)}</small></button>`).join("");
if (++n < 30) setTimeout(tick, 1000);
else { look.classList.add("done"); if (!found.length) look.textContent = words("notFound"); else look.hidden = true; }
if (found.length) look.hidden = true;
};
tick();
list.onclick = (e) => { const b = e.target.closest("[data-url]"); if (b) app.setup(b.dataset.url); };
}
</script>
</body>
</html>