diff --git a/src/common/entity/compute_state_display.ts b/src/common/entity/compute_state_display.ts index 9c768bd55e..c41bde1263 100644 --- a/src/common/entity/compute_state_display.ts +++ b/src/common/entity/compute_state_display.ts @@ -169,12 +169,6 @@ export const computeStateDisplayFromEntityAttributes = ( } } - if (domain === "humidifier") { - if (state === "on" && attributes.humidity) { - return `${attributes.humidity} %`; - } - } - // `counter` `number` and `input_number` domains do not have a unit of measurement but should still use `formatNumber` if ( domain === "counter" || diff --git a/src/panels/lovelace/cards/hui-humidifier-card.ts b/src/panels/lovelace/cards/hui-humidifier-card.ts index 0f65259d24..bca4924cc1 100644 --- a/src/panels/lovelace/cards/hui-humidifier-card.ts +++ b/src/panels/lovelace/cards/hui-humidifier-card.ts @@ -1,26 +1,30 @@ -import { mdiDotsVertical } from "@mdi/js"; +import { mdiDotsVertical, mdiPower, mdiWaterPercent } from "@mdi/js"; import "@thomasloven/round-slider"; import { HassEntity } from "home-assistant-js-websocket"; import { - css, CSSResultGroup, - html, LitElement, PropertyValues, - svg, + css, + html, nothing, + svg, } from "lit"; -import { customElement, property, state } from "lit/decorators"; +import { customElement, property, query, state } from "lit/decorators"; +import { styleMap } from "lit/directives/style-map"; import { classMap } from "lit/directives/class-map"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { fireEvent } from "../../../common/dom/fire_event"; import { computeAttributeValueDisplay } from "../../../common/entity/compute_attribute_display"; +import { computeStateDisplay } from "../../../common/entity/compute_state_display"; import { computeStateName } from "../../../common/entity/compute_state_name"; -import { computeRTLDirection } from "../../../common/util/compute_rtl"; +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 { isUnavailableState } from "../../../data/entity"; +import { UNAVAILABLE, isUnavailableState } from "../../../data/entity"; import { HumidifierEntity } from "../../../data/humidifier"; import { HomeAssistant } from "../../../types"; import { findEntities } from "../common/find-entities"; @@ -60,8 +64,10 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard { @state() private _setHum?: number; + @query("ha-card") private _card?: HaCard; + public getCardSize(): number { - return 6; + return 7; } public setConfig(config: HumidifierCardConfig): void { @@ -98,19 +104,12 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard { const setHumidity = this._setHum ? this._setHum : targetHumidity; - const curHumidity = - stateObj.attributes.current_humidity !== null && - Number.isFinite(Number(stateObj.attributes.current_humidity)) - ? stateObj.attributes.current_humidity - : undefined; - const rtlDirection = computeRTLDirection(this.hass); const slider = isUnavailableState(stateObj.state) ? html` ` : html` `; - const setValues = html` - - - ${isUnavailableState(stateObj.state) || - setHumidity === undefined || - setHumidity === null - ? "" - : svg` - ${formatNumber(setHumidity, this.hass.locale, { - maximumFractionDigits: 0, - })} - - % - - `} + 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 currentHumidity = html` - - - ${curHumidity - ? svg`${formatNumber(curHumidity, this.hass.locale)}` - : ""} - - - `; - - const currentMode = html` - - - ${this.hass!.localize(`state.default.${stateObj.state}`)} - ${stateObj.attributes.mode && !isUnavailableState(stateObj.state) - ? html` - - - ${computeAttributeValueDisplay( - this.hass.localize, - stateObj, - this.hass.locale, - this.hass.config, - this.hass.entities, - "mode" - )} - ` - : ""} - + const setValues = svg` + + + + ${ + stateObj.state !== UNAVAILABLE && setHumidity != null + ? formatNumber(setHumidity, this.hass.locale, { + maximumFractionDigits: 0, + }) + : nothing + } + + + ${computeStateDisplay( + this.hass.localize, + stateObj, + this.hass.locale, + this.hass.config, + this.hass.entities + )} + ${ + stateObj.state !== UNAVAILABLE && stateObj.attributes.mode + ? html` + - + ${computeAttributeValueDisplay( + this.hass.localize, + stateObj, + this.hass.locale, + this.hass.config, + this.hass.entities, + "mode" + )} + ` + : nothing + } + + `; return html` - + ${slider} - - ${setValues} - - ${currentHumidity} ${currentMode} + ${currentHumidity} ${setValues} - ${name} + + + + + + + + ${name} + `; @@ -231,6 +275,15 @@ 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) { @@ -250,6 +303,27 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard { } } + 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; @@ -269,8 +343,14 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard { }); } - private _toggle(): void { - this.hass!.callService("humidifier", "toggle", { + 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, }); } @@ -294,6 +374,7 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard { --name-font-size: 1.2rem; --brightness-font-size: 1.2rem; --rail-border-color: transparent; + --mode-color: var(--state-inactive-color); } .more-info { @@ -301,11 +382,11 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard { cursor: pointer; top: 0; right: 0; + inset-inline-end: 0px; + inset-inline-start: initial; border-radius: 100%; color: var(--secondary-text-color); - z-index: 25; - inset-inline-start: initial; - inset-inline-end: 0; + z-index: 1; direction: var(--direction); } @@ -321,7 +402,6 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard { justify-content: center; padding: 16px; position: relative; - direction: ltr; } #slider { @@ -334,13 +414,7 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard { round-slider { --round-slider-path-color: var(--slider-track-color); - --round-slider-bar-color: var(--primary-color); - padding-bottom: 10%; - } - - .round-slider_off { - --round-slider-path-color: var(--slider-track-color); - --round-slider-bar-color: var(--disabled-text-color); + --round-slider-bar-color: var(--mode-color); padding-bottom: 10%; } @@ -354,47 +428,28 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard { top: 20px; text-align: center; overflow-wrap: break-word; + pointer-events: none; } - #mode { - max-width: 80%; - transform: translate(0, 250%); + #humidity { + position: absolute; + transform: translate(-50%, -50%); + width: 100%; + height: 50%; + top: 45%; + left: 50%; + direction: ltr; } #set-values { - font-size: 13px; - font-family: var(--paper-font-body1_-_font-family); - font-weight: var(--paper-font-body1_-_font-weight); + max-width: 80%; + transform: translate(0, -50%); + font-size: 20px; } #set-mode { fill: var(--secondary-text-color); - font-size: 4px; - } - - #current_humidity { - max-width: 80%; - transform: translate(0, 300%); - } - - #current-humidity { - fill: var(--primary-text-color); - font-size: 5px; - } - - .toggle-button { - color: var(--primary-text-color); - width: 75%; - height: auto; - position: absolute; - max-width: calc(100% - 40px); - box-sizing: border-box; - border-radius: 100%; - top: 39%; - left: 50%; - transform: translate(-50%, -50%); - --mdc-icon-button-size: 100%; - --mdc-icon-size: 100%; + font-size: 16px; } #info { @@ -406,6 +461,16 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard { 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 0438374c7a..b48978274c 100644 --- a/src/panels/lovelace/cards/hui-thermostat-card.ts +++ b/src/panels/lovelace/cards/hui-thermostat-card.ts @@ -166,16 +166,19 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard { style="font-size: 13px;" > ${ - stateObj.attributes.current_temperature !== null && + stateObj.state !== UNAVAILABLE && + stateObj.attributes.current_temperature != null && !isNaN(stateObj.attributes.current_temperature) - ? svg`${formatNumber( - stateObj.attributes.current_temperature, - this.hass.locale - )} - - ${this.hass.config.unit_system.temperature} - ` - : "" + ? svg` + ${formatNumber( + stateObj.attributes.current_temperature, + this.hass.locale + )} + + ${this.hass.config.unit_system.temperature} + + ` + : nothing } @@ -186,42 +189,14 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard { ${ - stateObj.state === UNAVAILABLE - ? this.hass.localize("state.default.unavailable") - : this._setTemp === undefined || this._setTemp === null - ? "" - : Array.isArray(this._setTemp) - ? this._stepSize === 1 + stateObj.state !== UNAVAILABLE && this._setTemp != null + ? Array.isArray(this._setTemp) ? svg` - ${formatNumber(this._setTemp[0], this.hass.locale, { - maximumFractionDigits: 0, - })} - - ${formatNumber(this._setTemp[1], this.hass.locale, { - maximumFractionDigits: 0, - })} - ` - : svg` - ${formatNumber(this._setTemp[0], this.hass.locale, { - minimumFractionDigits: 1, - maximumFractionDigits: 1, - })} - - ${formatNumber(this._setTemp[1], this.hass.locale, { - minimumFractionDigits: 1, - maximumFractionDigits: 1, - })} - ` - : this._stepSize === 1 - ? svg` - ${formatNumber(this._setTemp, this.hass.locale, { - maximumFractionDigits: 0, - })} - ` - : svg` - ${formatNumber(this._setTemp, this.hass.locale, { - minimumFractionDigits: 1, - maximumFractionDigits: 1, - })} - ` + ${this._formatSetTemp(this._setTemp[0])} - + ${this._formatSetTemp(this._setTemp[1])} + ` + : this._formatSetTemp(this._setTemp) + : nothing } ${ - stateObj.attributes.hvac_action + stateObj.state !== UNAVAILABLE && stateObj.attributes.hvac_action ? computeAttributeValueDisplay( this.hass.localize, stateObj, @@ -248,6 +223,7 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard { ) } ${ + stateObj.state !== UNAVAILABLE && stateObj.attributes.preset_mode && stateObj.attributes.preset_mode !== CLIMATE_PRESET_NONE ? html` @@ -261,7 +237,7 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard { "preset_mode" )} ` - : "" + : nothing } @@ -374,6 +350,17 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard { } } + private _formatSetTemp(temp: number) { + return this._stepSize === 1 + ? formatNumber(temp, this.hass!.locale, { + maximumFractionDigits: 0, + }) + : formatNumber(temp, this.hass!.locale, { + minimumFractionDigits: 1, + maximumFractionDigits: 1, + }); + } + private _rescale_svg() { // Set the viewbox of the SVG containing the set temperature to perfectly // fit the text diff --git a/src/panels/lovelace/cards/hui-tile-card.ts b/src/panels/lovelace/cards/hui-tile-card.ts index 0afb105c2f..6afd8be31d 100644 --- a/src/panels/lovelace/cards/hui-tile-card.ts +++ b/src/panels/lovelace/cards/hui-tile-card.ts @@ -3,11 +3,11 @@ import { RippleHandlers } from "@material/mwc-ripple/ripple-handlers"; import { mdiExclamationThick, mdiHelp } from "@mdi/js"; import { HassEntity } from "home-assistant-js-websocket"; import { - css, CSSResultGroup, - html, LitElement, TemplateResult, + css, + html, nothing, } from "lit"; import { @@ -37,13 +37,14 @@ import "../../../components/tile/ha-tile-image"; import "../../../components/tile/ha-tile-info"; import { cameraUrlWithWidthHeight } from "../../../data/camera"; import { - computeCoverPositionStateDisplay, CoverEntity, + computeCoverPositionStateDisplay, } from "../../../data/cover"; import { isUnavailableState } from "../../../data/entity"; -import { computeFanSpeedStateDisplay, FanEntity } from "../../../data/fan"; -import { LightEntity } from "../../../data/light"; -import { ActionHandlerEvent } from "../../../data/lovelace"; +import { FanEntity, computeFanSpeedStateDisplay } from "../../../data/fan"; +import type { HumidifierEntity } from "../../../data/humidifier"; +import type { LightEntity } from "../../../data/light"; +import type { ActionHandlerEvent } from "../../../data/lovelace"; import { SENSOR_DEVICE_CLASS_TIMESTAMP } from "../../../data/sensor"; import { HomeAssistant } from "../../../types"; import { actionHandler } from "../common/directives/action-handler-directive"; @@ -51,15 +52,15 @@ import { findEntities } from "../common/find-entities"; import { handleAction } from "../common/handle-action"; import "../components/hui-timestamp-display"; import { createTileFeatureElement } from "../create-element/create-tile-feature-element"; -import { LovelaceTileFeatureConfig } from "../tile-features/types"; -import { +import type { LovelaceTileFeatureConfig } from "../tile-features/types"; +import type { LovelaceCard, LovelaceCardEditor, LovelaceTileFeature, } from "../types"; -import { HuiErrorCard } from "./hui-error-card"; +import type { HuiErrorCard } from "./hui-error-card"; import { computeTileBadge } from "./tile/badges/tile-badge"; -import { ThermostatCardConfig, TileCardConfig } from "./types"; +import type { ThermostatCardConfig, TileCardConfig } from "./types"; const TIMESTAMP_STATE_DOMAINS = ["button", "input_button", "scene"]; @@ -224,6 +225,15 @@ export class HuiTileCard extends LitElement implements LovelaceCard { } } + if (domain === "humidifier" && stateActive(stateObj)) { + const humidity = (stateObj as HumidifierEntity).attributes.humidity; + if (humidity) { + return `${Math.round(humidity)}${blankBeforePercent( + this.hass!.locale + )}%`; + } + } + const stateDisplay = computeStateDisplay( this.hass!.localize, stateObj,