From 9163b9c124d71cf242d776b19fd779c8f68e6ba4 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 23 Nov 2023 00:37:19 +0100 Subject: [PATCH] Add new design to humidifier card (#18711) --- .../climate/ha-more-info-climate-humidity.ts | 1 - ...more-info-control-circular-slider-style.ts | 1 - .../ha-more-info-humidifier-humidity.ts | 14 +- .../lovelace/cards/hui-humidifier-card.ts | 417 ++++-------------- .../lovelace/cards/hui-thermostat-card.ts | 1 - src/panels/lovelace/cards/types.ts | 1 + .../common/generate-lovelace-config.ts | 5 + .../create-tile-feature-element.ts | 4 +- .../hui-humidifier-card-editor.ts | 117 ++++- .../hui-tile-card-features-editor.ts | 9 +- .../hui-humidifier-modes-tile-feature.ts | 136 ++++++ src/panels/lovelace/tile-features/types.ts | 5 + 12 files changed, 349 insertions(+), 362 deletions(-) create mode 100644 src/panels/lovelace/tile-features/hui-humidifier-modes-tile-feature.ts diff --git a/src/dialogs/more-info/components/climate/ha-more-info-climate-humidity.ts b/src/dialogs/more-info/components/climate/ha-more-info-climate-humidity.ts index 0040c44d9d78..00a19db3f306 100644 --- a/src/dialogs/more-info/components/climate/ha-more-info-climate-humidity.ts +++ b/src/dialogs/more-info/components/climate/ha-more-info-climate-humidity.ts @@ -1,4 +1,3 @@ -import "@lrnwebcomponents/simple-tooltip/simple-tooltip"; import { mdiMinus, mdiPlus, mdiWaterPercent } from "@mdi/js"; import { CSSResultGroup, LitElement, PropertyValues, html } from "lit"; import { customElement, property, state } from "lit/decorators"; diff --git a/src/dialogs/more-info/components/ha-more-info-control-circular-slider-style.ts b/src/dialogs/more-info/components/ha-more-info-control-circular-slider-style.ts index 89983772dffb..50d43b10f316 100644 --- a/src/dialogs/more-info/components/ha-more-info-control-circular-slider-style.ts +++ b/src/dialogs/more-info/components/ha-more-info-control-circular-slider-style.ts @@ -93,7 +93,6 @@ export const moreInfoControlCircularSliderStyle = css` font-size: 32px; } .info { - margin-top: 12px; font-size: 14px; gap: 2px; --mdc-icon-size: 14px; diff --git a/src/dialogs/more-info/components/humidifier/ha-more-info-humidifier-humidity.ts b/src/dialogs/more-info/components/humidifier/ha-more-info-humidifier-humidity.ts index 721d49c8080d..df6461424d75 100644 --- a/src/dialogs/more-info/components/humidifier/ha-more-info-humidifier-humidity.ts +++ b/src/dialogs/more-info/components/humidifier/ha-more-info-humidifier-humidity.ts @@ -1,4 +1,4 @@ -import { mdiMinus, mdiPlus } from "@mdi/js"; +import { mdiMinus, mdiPlus, mdiWaterPercent } from "@mdi/js"; import { CSSResultGroup, LitElement, PropertyValues, html } from "lit"; import { customElement, property, state } from "lit/decorators"; import { styleMap } from "lit/directives/style-map"; @@ -100,13 +100,9 @@ export class HaMoreInfoHumidifierHumidity extends LitElement { return html`

- ${action && ["drying", "humidifying"].includes(action) - ? this.hass.localize("ui.card.humidifier.target_label", { - action: actionLabel, - }) - : action && action !== "off" && action !== "idle" - ? actionLabel - : this.hass.localize("ui.card.humidifier.target")} + ${action && action !== "off" && action !== "idle" + ? actionLabel + : this.hass.localize("ui.card.humidifier.target")}

`; } @@ -118,7 +114,7 @@ export class HaMoreInfoHumidifierHumidity extends LitElement { return html`

- ${this.hass.localize("ui.card.humidifier.currently")} + ${this.hass.formatEntityAttributeValue( this.stateObj, diff --git a/src/panels/lovelace/cards/hui-humidifier-card.ts b/src/panels/lovelace/cards/hui-humidifier-card.ts index eae7103ec318..2d82274547e0 100644 --- a/src/panels/lovelace/cards/hui-humidifier-card.ts +++ b/src/panels/lovelace/cards/hui-humidifier-card.ts @@ -1,6 +1,4 @@ -import { mdiDotsVertical, mdiPower, mdiWaterPercent } from "@mdi/js"; -import "@thomasloven/round-slider"; -import { HassEntity } from "home-assistant-js-websocket"; +import { mdiDotsVertical } from "@mdi/js"; import { CSSResultGroup, LitElement, @@ -8,26 +6,19 @@ import { css, html, nothing, - svg, } from "lit"; -import { customElement, property, query, state } from "lit/decorators"; -import { classMap } from "lit/directives/class-map"; -import { styleMap } from "lit/directives/style-map"; +import { customElement, property, state } from "lit/decorators"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { fireEvent } from "../../../common/dom/fire_event"; import { computeStateName } from "../../../common/entity/compute_state_name"; -import { stateColorCss } from "../../../common/entity/state_color"; -import { formatNumber } from "../../../common/number/format_number"; -import { computeRTLDirection } from "../../../common/util/compute_rtl"; import "../../../components/ha-card"; -import type { HaCard } from "../../../components/ha-card"; import "../../../components/ha-icon-button"; -import { UNAVAILABLE, isUnavailableState } from "../../../data/entity"; import { HumidifierEntity } from "../../../data/humidifier"; +import "../../../dialogs/more-info/components/humidifier/ha-more-info-humidifier-humidity"; import { HomeAssistant } from "../../../types"; import { findEntities } from "../common/find-entities"; -import { hasConfigOrEntityChanged } from "../common/has-changed"; import { createEntityNotFoundWarning } from "../components/hui-warning"; +import "../tile-features/hui-tile-features"; import { LovelaceCard, LovelaceCardEditor } from "../types"; import { HumidifierCardConfig } from "./types"; @@ -53,17 +44,21 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard { includeDomains ); - return { type: "humidifier", entity: foundEntities[0] || "" }; + return { + type: "humidifier", + entity: foundEntities[0] || "", + features: [ + { + type: "humidifier-modes", + }, + ], + }; } @property({ attribute: false }) public hass?: HomeAssistant; @state() private _config?: HumidifierCardConfig; - @state() private _setHum?: number; - - @query("ha-card") private _card?: HaCard; - public getCardSize(): number { return 7; } @@ -76,168 +71,10 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard { this._config = config; } - protected render() { - if (!this.hass || !this._config) { - return nothing; - } - const stateObj = this.hass.states[this._config.entity] as HumidifierEntity; - - if (!stateObj) { - return html` - - ${createEntityNotFoundWarning(this.hass, this._config.entity)} - - `; - } - - const name = - this._config!.name || - computeStateName(this.hass!.states[this._config!.entity]); - - const targetHumidity = - stateObj.attributes.humidity !== null && - Number.isFinite(Number(stateObj.attributes.humidity)) - ? stateObj.attributes.humidity - : null; - - const setHumidity = this._setHum ? this._setHum : targetHumidity; - - const rtlDirection = computeRTLDirection(this.hass); - - const slider = isUnavailableState(stateObj.state) - ? html` ` - : html` - - `; - - const currentHumidity = svg` - - - ${ - stateObj.state !== UNAVAILABLE && - stateObj.attributes.current_humidity != null && - !isNaN(stateObj.attributes.current_humidity) - ? svg` - ${formatNumber( - stateObj.attributes.current_humidity, - this.hass.locale - )} - - % - - ` - : nothing - } - - - `; - - const setValues = svg` - - - - ${ - stateObj.state !== UNAVAILABLE && setHumidity != null - ? formatNumber(setHumidity, this.hass.locale, { - maximumFractionDigits: 0, - }) - : nothing - } - - - ${ - stateObj.attributes.action - ? this.hass.formatEntityAttributeValue(stateObj, "action") - : this.hass.formatEntityState(stateObj) - } - ${ - stateObj.state !== UNAVAILABLE && stateObj.attributes.mode - ? html` - - ${this.hass.formatEntityAttributeValue(stateObj, "mode")} - ` - : nothing - } - - - - `; - - return html` - - - -

-
-
- ${slider} -
-
${currentHumidity} ${setValues}
-
-
-
-
-
- - - - -
- ${name} -
-
- - `; - } - - protected shouldUpdate(changedProps: PropertyValues): boolean { - return hasConfigOrEntityChanged(this, changedProps); + private _handleMoreInfo() { + fireEvent(this, "hass-more-info", { + entityId: this._config!.entity, + }); } protected updated(changedProps: PropertyValues): void { @@ -264,90 +101,49 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard { ) { applyThemesOnElement(this, this.hass.themes, this._config.theme); } - - const stateObj = this.hass.states[this._config.entity]; - if (!stateObj) { - return; - } - - if (!oldHass || oldHass.states[this._config.entity] !== stateObj) { - this._rescale_svg(); - } } - public willUpdate(changedProps: PropertyValues) { - if (!this.hass || !this._config || !changedProps.has("hass")) { - return; + protected render() { + if (!this.hass || !this._config) { + return nothing; } + const stateObj = this.hass.states[this._config.entity] as HumidifierEntity; - const stateObj = this.hass.states[this._config.entity]; if (!stateObj) { - return; - } - - const oldHass = changedProps.get("hass") as HomeAssistant | undefined; - - if (!oldHass || oldHass.states[this._config.entity] !== stateObj) { - this._setHum = this._getSetHum(stateObj); - } - } - - private _rescale_svg() { - // Set the viewbox of the SVG containing the set temperature to perfectly - // fit the text - // That way it will auto-scale correctly - // This is not done to the SVG containing the current temperature, because - // it should not be centered on the text, but only on the value - const card = this._card; - if (card) { - card.updateComplete.then(() => { - const svgRoot = this.shadowRoot!.querySelector("#set-values")!; - const box = svgRoot.querySelector("g")!.getBBox()!; - svgRoot.setAttribute( - "viewBox", - `${box.x} ${box!.y} ${box.width} ${box.height}` - ); - svgRoot.setAttribute("width", `${box.width}`); - svgRoot.setAttribute("height", `${box.height}`); - }); - } - } - - private _getSetHum(stateObj: HassEntity): undefined | number { - if (isUnavailableState(stateObj.state)) { - return undefined; + return html` + + ${createEntityNotFoundWarning(this.hass, this._config.entity)} + + `; } - return stateObj.attributes.humidity; - } - - private _dragEvent(e): void { - this._setHum = e.detail.value; - } + const name = this._config!.name || computeStateName(stateObj); - private _setHumidity(e): void { - this.hass!.callService("humidifier", "set_humidity", { - entity_id: this._config!.entity, - humidity: e.detail.value, - }); - } - - private _turnOn(): void { - this.hass!.callService("humidifier", "turn_on", { - entity_id: this._config!.entity, - }); - } - - private _turnOff(): void { - this.hass!.callService("humidifier", "turn_off", { - entity_id: this._config!.entity, - }); - } - - private _handleMoreInfo() { - fireEvent(this, "hass-more-info", { - entityId: this._config!.entity, - }); + return html` + +

${name}

+ + + +
+ `; } static get styles(): CSSResultGroup { @@ -360,10 +156,27 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard { height: 100%; position: relative; overflow: hidden; - --name-font-size: 1.2rem; - --brightness-font-size: 1.2rem; - --rail-border-color: transparent; - --mode-color: var(--state-inactive-color); + padding: 0; + display: flex; + flex-direction: column; + align-items: center; + } + + .title { + width: 100%; + font-size: 18px; + line-height: 24px; + padding: 12px 36px 16px 36px; + margin: 0; + text-align: center; + box-sizing: border-box; + } + + ha-more-info-humidifier-humidity { + width: 100%; + max-width: 344px; /* 12px + 12px + 320px */ + padding: 0 12px 12px 12px; + box-sizing: border-box; } .more-info { @@ -375,93 +188,11 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard { inset-inline-start: initial; border-radius: 100%; color: var(--secondary-text-color); - z-index: 1; direction: var(--direction); } - .content { - height: 100%; - display: flex; - flex-direction: column; - justify-content: center; - } - - #controls { - display: flex; - justify-content: center; - padding: 16px; - position: relative; - } - - #slider { - height: 100%; - width: 100%; - position: relative; - max-width: 250px; - min-width: 100px; - } - - round-slider { - --round-slider-path-color: var(--slider-track-color); - --round-slider-bar-color: var(--mode-color); - padding-bottom: 10%; - } - - #slider-center { - position: absolute; - width: calc(100% - 40px); - height: calc(100% - 40px); - box-sizing: border-box; - border-radius: 100%; - left: 20px; - top: 20px; - text-align: center; - overflow-wrap: break-word; - pointer-events: none; - } - - #humidity { - position: absolute; - transform: translate(-50%, -50%); + hui-tile-features { width: 100%; - height: 50%; - top: 45%; - left: 50%; - direction: ltr; - } - - #set-values { - max-width: 80%; - transform: translate(0, -50%); - font-size: 20px; - } - - #set-mode { - fill: var(--secondary-text-color); - font-size: 16px; - } - - #info { - display: flex-vertical; - justify-content: center; - text-align: center; - padding: 16px; - margin-top: -60px; - font-size: var(--name-font-size); - } - - #modes > * { - color: var(--disabled-text-color); - cursor: pointer; - display: inline-block; - } - - #modes .selected-icon { - color: var(--mode-color); - } - - text { - fill: var(--primary-text-color); } `; } diff --git a/src/panels/lovelace/cards/hui-thermostat-card.ts b/src/panels/lovelace/cards/hui-thermostat-card.ts index 0ed4346f82fa..205ca9ea8b98 100644 --- a/src/panels/lovelace/cards/hui-thermostat-card.ts +++ b/src/panels/lovelace/cards/hui-thermostat-card.ts @@ -1,5 +1,4 @@ import { mdiDotsVertical } from "@mdi/js"; -import "@thomasloven/round-slider"; import { CSSResultGroup, LitElement, diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index c0adf7335c30..0be7d44ac554 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -262,6 +262,7 @@ export interface HumidifierCardConfig extends LovelaceCardConfig { entity: string; theme?: string; name?: string; + features?: LovelaceTileFeatureConfig[]; } export interface IframeCardConfig extends LovelaceCardConfig { diff --git a/src/panels/lovelace/common/generate-lovelace-config.ts b/src/panels/lovelace/common/generate-lovelace-config.ts index fdd03510a6a2..82e5a6c43c34 100644 --- a/src/panels/lovelace/common/generate-lovelace-config.ts +++ b/src/panels/lovelace/common/generate-lovelace-config.ts @@ -153,6 +153,11 @@ export const computeCards = ( const cardConfig: HumidifierCardConfig = { type: "humidifier", entity: entityId, + features: [ + { + type: "humidifier-modes", + }, + ], }; cards.push(cardConfig); } else if (domain === "media_player") { diff --git a/src/panels/lovelace/create-element/create-tile-feature-element.ts b/src/panels/lovelace/create-element/create-tile-feature-element.ts index 0f608494c9c5..cb51578c0448 100644 --- a/src/panels/lovelace/create-element/create-tile-feature-element.ts +++ b/src/panels/lovelace/create-element/create-tile-feature-element.ts @@ -6,14 +6,15 @@ import "../tile-features/hui-cover-position-tile-feature"; import "../tile-features/hui-cover-tilt-position-tile-feature"; import "../tile-features/hui-cover-tilt-tile-feature"; import "../tile-features/hui-fan-speed-tile-feature"; +import "../tile-features/hui-humidifier-modes-tile-feature"; import "../tile-features/hui-lawn-mower-commands-tile-feature"; import "../tile-features/hui-light-brightness-tile-feature"; import "../tile-features/hui-light-color-temp-tile-feature"; +import "../tile-features/hui-number-tile-feature"; import "../tile-features/hui-select-options-tile-feature"; import "../tile-features/hui-target-temperature-tile-feature"; import "../tile-features/hui-vacuum-commands-tile-feature"; import "../tile-features/hui-water-heater-operation-modes-tile-feature"; -import "../tile-features/hui-number-tile-feature"; import { LovelaceTileFeatureConfig } from "../tile-features/types"; import { createLovelaceElement, @@ -29,6 +30,7 @@ const TYPES: Set = new Set([ "cover-tilt-position", "cover-tilt", "fan-speed", + "humidifier-modes", "lawn-mower-commands", "light-brightness", "light-color-temp", diff --git a/src/panels/lovelace/editor/config-elements/hui-humidifier-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-humidifier-card-editor.ts index 5cb79b16216f..697af9f2e8a4 100644 --- a/src/panels/lovelace/editor/config-elements/hui-humidifier-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-humidifier-card-editor.ts @@ -1,13 +1,28 @@ -import { html, LitElement, nothing } from "lit"; +import { LitElement, css, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; -import { assert, assign, object, optional, string } from "superstruct"; -import { fireEvent } from "../../../../common/dom/fire_event"; +import memoizeOne from "memoize-one"; +import { + any, + array, + assert, + assign, + object, + optional, + string, +} from "superstruct"; +import { HASSDomEvent, fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/ha-form/ha-form"; import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; import type { HumidifierCardConfig } from "../../cards/types"; +import { + LovelaceTileFeatureConfig, + LovelaceTileFeatureContext, +} from "../../tile-features/types"; import type { LovelaceCardEditor } from "../../types"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; +import { EditSubElementEvent, SubElementEditorConfig } from "../types"; +import "./hui-tile-card-features-editor"; const cardConfigStruct = assign( baseLovelaceCardConfig, @@ -15,6 +30,7 @@ const cardConfigStruct = assign( entity: optional(string()), name: optional(string()), theme: optional(string()), + features: optional(array(any())), }) ); @@ -43,16 +59,39 @@ export class HuiHumidifierCardEditor @state() private _config?: HumidifierCardConfig; + @state() private _subElementEditorConfig?: SubElementEditorConfig; + public setConfig(config: HumidifierCardConfig): void { assert(config, cardConfigStruct); this._config = config; } + private _context = memoizeOne( + (entity_id?: string): LovelaceTileFeatureContext => ({ entity_id }) + ); + protected render() { if (!this.hass || !this._config) { return nothing; } + const stateObj = this._config.entity + ? this.hass.states[this._config.entity] + : undefined; + + if (this._subElementEditorConfig) { + return html` + + + `; + } + return html` + `; } @@ -68,6 +114,62 @@ export class HuiHumidifierCardEditor fireEvent(this, "config-changed", { config: ev.detail.value }); } + private _featuresChanged(ev: CustomEvent) { + ev.stopPropagation(); + if (!this._config || !this.hass) { + return; + } + + const features = ev.detail.features as LovelaceTileFeatureConfig[]; + const config: HumidifierCardConfig = { + ...this._config, + features, + }; + + if (features.length === 0) { + delete config.features; + } + + fireEvent(this, "config-changed", { config }); + } + + private subElementChanged(ev: CustomEvent): void { + ev.stopPropagation(); + if (!this._config || !this.hass) { + return; + } + + const value = ev.detail.config; + + const newConfigFeatures = this._config!.features + ? [...this._config!.features] + : []; + + if (!value) { + newConfigFeatures.splice(this._subElementEditorConfig!.index!, 1); + this._goBack(); + } else { + newConfigFeatures[this._subElementEditorConfig!.index!] = value; + } + + this._config = { ...this._config!, features: newConfigFeatures }; + + this._subElementEditorConfig = { + ...this._subElementEditorConfig!, + elementConfig: value, + }; + + fireEvent(this, "config-changed", { config: this._config }); + } + + private _editDetailElement(ev: HASSDomEvent): void { + this._subElementEditorConfig = ev.detail.subElementConfig; + } + + private _goBack(): void { + this._subElementEditorConfig = undefined; + } + private _computeLabelCallback = (schema: SchemaUnion) => { if (schema.name === "entity") { return this.hass!.localize( @@ -87,6 +189,15 @@ export class HuiHumidifierCardEditor `ui.panel.lovelace.editor.card.generic.${schema.name}` ); }; + + static get styles() { + return css` + ha-form { + display: block; + margin-bottom: 24px; + } + `; + } } declare global { diff --git a/src/panels/lovelace/editor/config-elements/hui-tile-card-features-editor.ts b/src/panels/lovelace/editor/config-elements/hui-tile-card-features-editor.ts index e1e556a0b130..8b80bc8cd989 100644 --- a/src/panels/lovelace/editor/config-elements/hui-tile-card-features-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-tile-card-features-editor.ts @@ -24,21 +24,22 @@ import { HomeAssistant } from "../../../../types"; import { getTileFeatureElementClass } from "../../create-element/create-tile-feature-element"; import { supportsAlarmModesTileFeature } from "../../tile-features/hui-alarm-modes-tile-feature"; import { supportsClimateHvacModesTileFeature } from "../../tile-features/hui-climate-hvac-modes-tile-feature"; +import { supportsClimatePresetModesTileFeature } from "../../tile-features/hui-climate-preset-modes-tile-feature"; import { supportsCoverOpenCloseTileFeature } from "../../tile-features/hui-cover-open-close-tile-feature"; import { supportsCoverPositionTileFeature } from "../../tile-features/hui-cover-position-tile-feature"; import { supportsCoverTiltPositionTileFeature } from "../../tile-features/hui-cover-tilt-position-tile-feature"; import { supportsCoverTiltTileFeature } from "../../tile-features/hui-cover-tilt-tile-feature"; import { supportsFanSpeedTileFeature } from "../../tile-features/hui-fan-speed-tile-feature"; +import { supportsHumidifierModesTileFeature } from "../../tile-features/hui-humidifier-modes-tile-feature"; import { supportsLawnMowerCommandTileFeature } from "../../tile-features/hui-lawn-mower-commands-tile-feature"; import { supportsLightBrightnessTileFeature } from "../../tile-features/hui-light-brightness-tile-feature"; import { supportsLightColorTempTileFeature } from "../../tile-features/hui-light-color-temp-tile-feature"; +import { supportsNumberTileFeature } from "../../tile-features/hui-number-tile-feature"; import { supportsSelectOptionTileFeature } from "../../tile-features/hui-select-options-tile-feature"; import { supportsTargetTemperatureTileFeature } from "../../tile-features/hui-target-temperature-tile-feature"; import { supportsVacuumCommandTileFeature } from "../../tile-features/hui-vacuum-commands-tile-feature"; import { supportsWaterHeaterOperationModesTileFeature } from "../../tile-features/hui-water-heater-operation-modes-tile-feature"; import { LovelaceTileFeatureConfig } from "../../tile-features/types"; -import { supportsClimatePresetModesTileFeature } from "../../tile-features/hui-climate-preset-modes-tile-feature"; -import { supportsNumberTileFeature } from "../../tile-features/hui-number-tile-feature"; export type FeatureType = LovelaceTileFeatureConfig["type"]; type SupportsFeature = (stateObj: HassEntity) => boolean; @@ -52,6 +53,7 @@ const UI_FEATURE_TYPES = [ "cover-tilt-position", "cover-tilt", "fan-speed", + "humidifier-modes", "lawn-mower-commands", "light-brightness", "light-color-temp", @@ -86,14 +88,15 @@ const SUPPORTS_FEATURE_TYPES: Record< "cover-tilt-position": supportsCoverTiltPositionTileFeature, "cover-tilt": supportsCoverTiltTileFeature, "fan-speed": supportsFanSpeedTileFeature, + "humidifier-modes": supportsHumidifierModesTileFeature, "lawn-mower-commands": supportsLawnMowerCommandTileFeature, "light-brightness": supportsLightBrightnessTileFeature, "light-color-temp": supportsLightColorTempTileFeature, + number: supportsNumberTileFeature, "target-temperature": supportsTargetTemperatureTileFeature, "vacuum-commands": supportsVacuumCommandTileFeature, "water-heater-operation-modes": supportsWaterHeaterOperationModesTileFeature, "select-options": supportsSelectOptionTileFeature, - number: supportsNumberTileFeature, }; const CUSTOM_FEATURE_ENTRIES: Record< diff --git a/src/panels/lovelace/tile-features/hui-humidifier-modes-tile-feature.ts b/src/panels/lovelace/tile-features/hui-humidifier-modes-tile-feature.ts new file mode 100644 index 000000000000..2445a1519299 --- /dev/null +++ b/src/panels/lovelace/tile-features/hui-humidifier-modes-tile-feature.ts @@ -0,0 +1,136 @@ +import { mdiPower, mdiWaterPercent } from "@mdi/js"; +import { HassEntity } from "home-assistant-js-websocket"; +import { LitElement, PropertyValues, TemplateResult, css, html } from "lit"; +import { customElement, property, state } from "lit/decorators"; +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 { HumidifierEntity, HumidifierState } from "../../../data/humidifier"; +import { HomeAssistant } from "../../../types"; +import { LovelaceTileFeature } from "../types"; +import { HumidifierModesTileFeatureConfig } from "./types"; + +export const supportsHumidifierModesTileFeature = (stateObj: HassEntity) => { + const domain = computeDomain(stateObj.entity_id); + return domain === "humidifier"; +}; + +@customElement("hui-humidifier-modes-tile-feature") +class HuiHumidifierModeTileFeature + extends LitElement + implements LovelaceTileFeature +{ + @property({ attribute: false }) public hass?: HomeAssistant; + + @property({ attribute: false }) public stateObj?: HumidifierEntity; + + @state() private _config?: HumidifierModesTileFeatureConfig; + + @state() _currentState?: HumidifierState; + + static getStubConfig(): HumidifierModesTileFeatureConfig { + return { + type: "humidifier-modes", + }; + } + + public setConfig(config: HumidifierModesTileFeatureConfig): void { + if (!config) { + throw new Error("Invalid configuration"); + } + this._config = config; + } + + protected willUpdate(changedProp: PropertyValues): void { + super.willUpdate(changedProp); + if (changedProp.has("stateObj") && this.stateObj) { + this._currentState = this.stateObj.state as HumidifierState; + } + } + + private async _valueChanged(ev: CustomEvent) { + const newState = (ev.detail as any).value as HumidifierState; + + if (newState === this.stateObj!.state) return; + + const oldState = this.stateObj!.state as HumidifierState; + this._currentState = newState; + + try { + await this._setState(newState); + } catch (err) { + this._currentState = oldState; + } + } + + private async _setState(newState: HumidifierState) { + await this.hass!.callService( + "humidifier", + newState === "on" ? "turn_on" : "turn_off", + { + entity_id: this.stateObj!.entity_id, + } + ); + } + + protected render(): TemplateResult | null { + if ( + !this._config || + !this.hass || + !this.stateObj || + !supportsHumidifierModesTileFeature(this.stateObj) + ) { + return null; + } + + const color = stateColorCss(this.stateObj); + + const options = ["on", "off"].map((entityState) => ({ + value: entityState, + label: this.hass!.formatEntityState(this.stateObj!, entityState), + path: entityState === "on" ? mdiWaterPercent : mdiPower, + })); + + return html` +
+ + +
+ `; + } + + static get styles() { + return css` + ha-control-select { + --control-select-color: var(--tile-color); + --control-select-padding: 0; + --control-select-thickness: 40px; + --control-select-border-radius: 10px; + --control-select-button-border-radius: 10px; + } + .container { + padding: 0 12px 12px 12px; + width: auto; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-humidifier-modes-tile-feature": HuiHumidifierModeTileFeature; + } +} diff --git a/src/panels/lovelace/tile-features/types.ts b/src/panels/lovelace/tile-features/types.ts index 8eb4fa556ef9..f91c13631bff 100644 --- a/src/panels/lovelace/tile-features/types.ts +++ b/src/panels/lovelace/tile-features/types.ts @@ -64,6 +64,10 @@ export interface WaterHeaterOperationModesTileFeatureConfig { operation_modes?: OperationMode[]; } +export interface HumidifierModesTileFeatureConfig { + type: "humidifier-modes"; +} + export const VACUUM_COMMANDS = [ "start_pause", "stop", @@ -97,6 +101,7 @@ export type LovelaceTileFeatureConfig = | CoverTiltPositionTileFeatureConfig | CoverTiltTileFeatureConfig | FanSpeedTileFeatureConfig + | HumidifierModesTileFeatureConfig | LawnMowerCommandsTileFeatureConfig | LightBrightnessTileFeatureConfig | LightColorTempTileFeatureConfig