From 7833a680a9530e83e3baf5591ee48271c7435a2f Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 27 Feb 2025 15:35:12 +0100 Subject: [PATCH] Use switch and add support for light, fan and valve (#24426) * Use switch and add support for light and fan * Add icon per domains --- .../more-info/controls/more-info-light.ts | 6 +- .../common/card-feature-styles.ts | 8 + .../card-features/hui-toggle-card-feature.ts | 186 +++++++++++++----- 3 files changed, 144 insertions(+), 56 deletions(-) diff --git a/src/dialogs/more-info/controls/more-info-light.ts b/src/dialogs/more-info/controls/more-info-light.ts index 30b3fa1d4f..9d81cfdc01 100644 --- a/src/dialogs/more-info/controls/more-info-light.ts +++ b/src/dialogs/more-info/controls/more-info-light.ts @@ -3,8 +3,8 @@ import { mdiBrightness6, mdiCreation, mdiFileWordBox, - mdiLightbulb, mdiLightbulbOff, + mdiLightbulbOn, mdiPower, } from "@mdi/js"; import type { CSSResultGroup, PropertyValues } from "lit"; @@ -12,8 +12,8 @@ import { LitElement, css, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { stopPropagation } from "../../../common/dom/stop_propagation"; import { supportsFeature } from "../../../common/entity/supports-feature"; -import "../../../components/ha-attributes"; import "../../../components/ha-attribute-icon"; +import "../../../components/ha-attributes"; import "../../../components/ha-control-select-menu"; import "../../../components/ha-icon-button-group"; import "../../../components/ha-icon-button-toggle"; @@ -121,7 +121,7 @@ class MoreInfoLight extends LitElement { ` diff --git a/src/panels/lovelace/card-features/common/card-feature-styles.ts b/src/panels/lovelace/card-features/common/card-feature-styles.ts index 6a0fdcb1c0..5787f1d1d2 100644 --- a/src/panels/lovelace/card-features/common/card-feature-styles.ts +++ b/src/panels/lovelace/card-features/common/card-feature-styles.ts @@ -27,4 +27,12 @@ export const cardFeatureStyles = css` --control-slider-thickness: var(--feature-height); --control-slider-border-radius: var(--feature-border-radius); } + ha-control-switch { + --control-switch-on-color: var(--feature-color); + --control-switch-off-color: var(--feature-color); + --control-switch-background-opacity: 0.2; + --control-switch-thickness: var(--feature-height); + --control-switch-border-radius: var(--feature-border-radius); + --control-switch-padding: 0px; + } `; diff --git a/src/panels/lovelace/card-features/hui-toggle-card-feature.ts b/src/panels/lovelace/card-features/hui-toggle-card-feature.ts index bf233f5a9a..96cfce4161 100644 --- a/src/panels/lovelace/card-features/hui-toggle-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-toggle-card-feature.ts @@ -1,23 +1,50 @@ -import { mdiPowerOff, mdiPower } from "@mdi/js"; +import { + mdiFan, + mdiFanOff, + mdiLightbulbOff, + mdiLightbulbOn, + mdiPower, + mdiPowerOff, + mdiVolumeHigh, + mdiVolumeOff, +} from "@mdi/js"; import type { HassEntity } from "home-assistant-js-websocket"; -import type { TemplateResult } from "lit"; -import { LitElement, html } from "lit"; +import { LitElement, css, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; +import { classMap } from "lit/directives/class-map"; import { styleMap } from "lit/directives/style-map"; import { computeDomain } from "../../../common/entity/compute_domain"; import { stateColorCss } from "../../../common/entity/state_color"; -import "../../../components/ha-control-select"; -import type { ControlSelectOption } from "../../../components/ha-control-select"; -import { UNAVAILABLE } from "../../../data/entity"; +import "../../../components/ha-control-button"; +import "../../../components/ha-control-button-group"; +import "../../../components/ha-control-switch"; +import { UNAVAILABLE, UNKNOWN } from "../../../data/entity"; +import { forwardHaptic } from "../../../data/haptics"; import type { HomeAssistant } from "../../../types"; import type { LovelaceCardFeature } from "../types"; import { cardFeatureStyles } from "./common/card-feature-styles"; import type { ToggleCardFeatureConfig } from "./types"; -import { showToast } from "../../../util/toast"; export const supportsToggleCardFeature = (stateObj: HassEntity) => { const domain = computeDomain(stateObj.entity_id); - return ["switch", "input_boolean"].includes(domain); + return ["switch", "input_boolean", "light", "fan", "siren", "valve"].includes( + domain + ); +}; + +const DOMAIN_ICONS: Record = { + siren: { + on: mdiVolumeHigh, + off: mdiVolumeOff, + }, + light: { + on: mdiLightbulbOn, + off: mdiLightbulbOff, + }, + fan: { + on: mdiFan, + off: mdiFanOff, + }, }; @customElement("hui-toggle-card-feature") @@ -41,67 +68,120 @@ class HuiToggleCardFeature extends LitElement implements LovelaceCardFeature { this._config = config; } - protected render(): TemplateResult | null { + private _valueChanged(ev) { + const checked = ev.target.checked as boolean; + + if (checked) { + this._turnOn(); + } else { + this._turnOff(); + } + } + + private _turnOn() { + this._callService(true); + } + + private _turnOff() { + this._callService(false); + } + + private async _callService(turnOn): Promise { + if (!this.hass || !this.stateObj) { + return; + } + forwardHaptic("light"); + const stateDomain = computeDomain(this.stateObj.entity_id); + const serviceDomain = stateDomain; + const service = turnOn ? "turn_on" : "turn_off"; + + await this.hass.callService(serviceDomain, service, { + entity_id: this.stateObj.entity_id, + }); + } + + protected render() { if ( !this._config || !this.hass || !this.stateObj || !supportsToggleCardFeature(this.stateObj) ) { - return null; + return nothing; } - const color = stateColorCss(this.stateObj); + const onColor = "var(--feature-color)"; + const offColor = stateColorCss(this.stateObj, "off"); - const options = ["off", "on"].map((entityState) => ({ - value: entityState, - label: this.hass!.formatEntityState(this.stateObj!, entityState), - path: entityState === "on" ? mdiPower : mdiPowerOff, - })); + const isOn = this.stateObj.state === "on"; + const isOff = this.stateObj.state === "off"; + + const domain = computeDomain(this.stateObj.entity_id); + const onIcon = DOMAIN_ICONS[domain]?.on || mdiPower; + const offIcon = DOMAIN_ICONS[domain]?.off || mdiPowerOff; + + if ( + this.stateObj.attributes.assumed_state || + this.stateObj.state === UNKNOWN + ) { + return html` + + + + + + + + + `; + } return html` - - + `; } - private async _valueChanged(ev: CustomEvent) { - const newState = (ev.detail as any).value; - - if ( - newState === this.stateObj!.state && - !this.stateObj!.attributes.assumed_state - ) - return; - const service = newState === "on" ? "turn_on" : "turn_off"; - const domain = computeDomain(this.stateObj!.entity_id); - - try { - await this.hass!.callService(domain, service, { - entity_id: this.stateObj!.entity_id, - }); - } catch (_err) { - showToast(this, { - message: this.hass!.localize("ui.notification_toast.action_failed", { - service: domain + "." + service, - }), - duration: 5000, - dismissable: true, - }); - } + static get styles() { + return [ + cardFeatureStyles, + css` + ha-control-button.active { + --control-button-icon-color: white; + --control-button-background-color: var(--color); + --control-button-background-opacity: 1; + } + `, + ]; } - - static styles = cardFeatureStyles; } declare global {