Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions src/cards.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,14 @@ export const hasActs = (p, id) => (cardCfg(p, id).actions || []).some(a => a &&
export const label = (p, e) => cardCfg(p, e.id).name || name(p.hass, e);
export const isHidden = (p, id) => !!cardCfg(p, id).hidden;
export const icoFor = (p, id, mdi, svg) => uico(cardCfg(p, id).icon, mdi, svg);
/** how a plug may be switched, set per card ("plug"): "" = normally, "confirm" = after asking, "lock" = never (measure
only, e.g. a fridge). A device card and its switch entity each have settings; the stricter one counts */
export const plugMode = (p, ...keys) => { const m = keys.filter(Boolean).map(k => cardCfg(p, k).plug); return m.includes("lock") ? "lock" : m.includes("confirm") ? "confirm" : ""; };
/** `run` (switching a plug) guarded by plugMode: null when locked, a confirmation first when asked for */
export function guardPlug(p, keys, title, on, run) {
const m = plugMode(p, ...keys); if (m === "lock") return null; if (m !== "confirm") return run;
return () => p.openPopup({ type: "confirm", title: t("Stekker"), big: title, sub: on ? t("Echt uitzetten?") : t("Echt aanzetten?"), okLabel: on ? t("Uitzetten") : t("Aanzetten"), ok: run });
}
/** tap/hold wiring honouring the per-card "tap" override: toggle (default) or popup */
const toggleOrPopup = (p, id, toggle, popup) => pressFor(p, id, toggle, popup);

Expand Down Expand Up @@ -216,7 +224,7 @@ export function switchCard(p, e) {
const s = e.state, on = isOn(s), un = unavailable(s), d = e.id.split(".")[0];
const svc = d === "input_boolean" ? "input_boolean" : "switch";
return html`<div class=${classMap({ card: true, on, unavailable: un, acts: hasActs(p, e.id), ...infoCls(cardCfg(p, e.id)) })}
${pressDir(p, toggleOrPopup(p, e.id, () => p.call(svc, "toggle", { entity_id: e.id }), () => p.openPopup({ type: "switch", e })))}>
${pressDir(p, toggleOrPopup(p, e.id, guardPlug(p, [e.id], label(p, e), on, () => p.call(svc, "toggle", { entity_id: e.id })), () => p.openPopup({ type: "switch", e })))}>
${un ? ua() : nothing}
<div class="i">${icoFor(p, e.id, "mdi:power-socket-eu", "plug")}</div>
<div class="c">${ovr(p, e.id, "state") ?? (un ? t("Onbereikbaar") : on ? t("Aan") : t("Uit"))}</div>
Expand Down Expand Up @@ -436,12 +444,13 @@ function plugCard(p, a, st, title, cfg, acts) {
const since = sw?.state?.last_changed ? sinceText(sw.state.last_changed) : "";
const tmr = p.plugTimers?.[a.id], left = tmr ? Math.max(0, Math.round((tmr.at - Date.now()) / 60000)) : null, plain = !!a.plain;
let bottom = nothing;
if (mode === "timer") bottom = html`<div class="lbl chips" @click=${ev => ev.stopPropagation()}>${[15, 30, 60].map(m => html`<button class=${classMap({ on: tmr?.min === m })} @click=${() => p.setPlugTimer(a, m)}>${m}</button>`)}</div>`;
const toggle = sw ? guardPlug(p, [a.id, sw.id], title, on, () => p.call("switch", "toggle", { entity_id: sw.id })) : null;
if (mode === "timer" && (!sw || toggle)) bottom = html`<div class="lbl chips" @click=${ev => ev.stopPropagation()}>${[15, 30, 60].map(m => html`<button class=${classMap({ on: tmr?.min === m })} @click=${() => p.setPlugTimer(a, m)}>${m}</button>`)}</div>`;
else if (mode === "cost") bottom = price ? html`<div class="lbl cost"><span>${h?.today != null ? html`${t("Vandaag")} <b>${eur(h.today * price)}</b>` : ""}${!on && W > 0.5 ? html`${h?.today != null ? dot() : nothing}${t("Sluipverbruik")} <b>${eur(W * 24 * 30 / 1000 * price)}</b> / ${t("maand")}` : nothing}</span></div>` : html`<div class="lbl"><span>${t("Stel een kWh-prijs in bij Energie")}</span></div>`;
else if (mode === "bars") bottom = h?.rows ? html`<div class="lbl bars">${h.rows.map(r => html`<i class=${classMap({ on: r.v > 0.01 })} style="height:${Math.max(9, r.v / (h.maxH || 1) * 100)}%"></i>`)}</div>` : html`<div class="lbl"><span>…</span></div>`;
else if (mode === "spark" && h?.peak) bottom = html`<div class="lbl"><span>${t("piek {w} W om {t}", { w: h.peak.v, t: new Date(h.peak.t).toLocaleTimeString(locale(), { hour: "2-digit", minute: "2-digit" }) })}</span></div>`;
return html`<div class=${classMap({ card: true, plug: true, appl: acts.length > 0, hasspk: mode === "spark" && !!h?.spark?.length, on, unavailable: st.st === "unknown", ...infoCls(cfg) })}
${pressDir(p, pressFor(p, a.id, sw ? () => p.call("switch", "toggle", { entity_id: sw.id }) : null, () => p.openPopup({ type: "appliance", a })))}>
${pressDir(p, pressFor(p, a.id, toggle, () => p.openPopup({ type: "appliance", a })))}>
${st.st === "unknown" ? ua() : nothing}
<div class="i">${uico(cfg.icon, "mdi:power-plug-outline", "plug")}</div>
<div class="c">${ovr(p, a.id, "state") ?? (left != null ? html`<i></i>${t("uit over {n} min", { n: left })}` : st.st === "unknown" ? t("Geen meting") : plain ? (on ? t("Aan") : t("Uit")) : st.st === "running" ? t("Bezig") : st.st === "done" ? t("Klaar") : on ? (W > 0.5 ? t("Standby") : t("Aan")) : t("Uit"))}</div>
Expand All @@ -463,7 +472,8 @@ export function applianceCard(p, a) {
const foot = [progName, st.progress != null ? `${Math.round(st.progress)}%` : "", st.power != null ? `${Math.round(st.power)} W` : "", a.door && p.hass.states[a.door.id]?.state === "open" ? t("Deur open") : ""].filter(Boolean);
const acts = [];
if (a.stop && !a.plain && ["running", "waiting"].includes(st.st)) acts.push({ icon: "mdi:stop", label: t("Stop"), run: () => p.call("button", "press", { entity_id: a.stop.id }) });
if (a.switch && a.op && !unavailable(a.switch.state)) acts.push({ icon: "mdi:power", label: isOn(a.switch.state) ? t("Uit") : t("Aan"), on: isOn(a.switch.state), run: () => p.call("switch", "toggle", { entity_id: a.switch.id }) });
const pw = a.switch && a.op && !unavailable(a.switch.state) ? guardPlug(p, [a.id, a.switch.id], title, isOn(a.switch.state), () => p.call("switch", "toggle", { entity_id: a.switch.id })) : null;
if (pw) acts.push({ icon: "mdi:power", label: isOn(a.switch.state) ? t("Uit") : t("Aan"), on: isOn(a.switch.state), run: pw });
if (!a.op && a.power) return plugCard(p, a, st, title, cfg, acts);
return html`<div class=${classMap({ card: true, appl: acts.length > 0, on: APPLIANCE_ACTIVE.includes(st.st), running: st.st === "running", unavailable: st.st === "unknown" })}
${pressDir(p, { tap: () => p.openPopup({ type: "appliance", a }), hold: () => p.openPopup({ type: "appliance", a }) })}>
Expand Down Expand Up @@ -524,7 +534,7 @@ export function stripCard(p, st) {
<div class="n">${title}</div>
<div class="s">${un ? t("Onbereikbaar") : on ? t("{n} van {m} aan", { n: on, m: st.switches.length }) : t("Alles uit")}</div>
<div class="socks">${st.switches.map((e, i) => html`<button class=${classMap({ on: isOn(e.state), unavailable: unavailable(e.state) })} title=${label(p, e)}
${pressDir(p, { tap: () => p.call("switch", "toggle", { entity_id: e.id }), hold: () => p.openPopup({ type: "switch", e }) })}>
${pressDir(p, { tap: guardPlug(p, [e.id], label(p, e), isOn(e.state), () => p.call("switch", "toggle", { entity_id: e.id })) || (() => p.openPopup({ type: "switch", e })), hold: () => p.openPopup({ type: "switch", e }) })}>
${anyIco(cardCfg(p, e.id).icon || "mdi:power-socket-eu")}<small>${cardCfg(p, e.id).name || (i + 1)}</small></button>`)}</div>
</div>`;
}
Expand Down
6 changes: 6 additions & 0 deletions src/lang/de.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/lang/en.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 6 additions & 4 deletions src/popups.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading