Align humidifier thermostat card (#17054)

This commit is contained in:
Paul Bottein 2023-06-27 18:36:56 +02:00 committed by GitHub
parent 3b8ea5edbe
commit 343708cdaa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 233 additions and 177 deletions

View File

@ -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" ||

View File

@ -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` <round-slider disabled="true"></round-slider> `
: html`
<round-slider
class=${classMap({ "round-slider_off": stateObj.state === "off" })}
.value=${targetHumidity}
.min=${stateObj.attributes.min_humidity}
.max=${stateObj.attributes.max_humidity}
@ -121,60 +120,89 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
></round-slider>
`;
const setValues = html`
<svg viewBox="0 0 30 20">
<text x="50%" dx="1" y="73%" text-anchor="middle" id="set-values">
${isUnavailableState(stateObj.state) ||
setHumidity === undefined ||
setHumidity === null
? ""
: svg`
${formatNumber(setHumidity, this.hass.locale, {
maximumFractionDigits: 0,
})}
<tspan dx="-3" dy="-6.5" style="font-size: 4px;">
%
</tspan>
`}
const currentHumidity = svg`
<svg viewBox="0 0 40 20">
<text
x="50%"
dx="1"
y="60%"
text-anchor="middle"
style="font-size: 13px;"
>
${
stateObj.state !== UNAVAILABLE &&
stateObj.attributes.current_humidity != null &&
!isNaN(stateObj.attributes.current_humidity)
? svg`
${formatNumber(
stateObj.attributes.current_humidity,
this.hass.locale
)}
<tspan dx="-3" dy="-6.5" style="font-size: 4px;">
%
</tspan>
`
: nothing
}
</text>
</svg>
`;
`;
const currentHumidity = html`
<svg viewBox="0 0 40 10" id="current_humidity">
<text x="50%" y="50%" text-anchor="middle" id="current-humidity">
${curHumidity
? svg`${formatNumber(curHumidity, this.hass.locale)}`
: ""}
</text>
</svg>
`;
const currentMode = html`
<svg viewBox="0 0 40 10" id="mode">
<text x="50%" y="50%" text-anchor="middle" id="set-mode">
${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"
)}
`
: ""}
</text>
const setValues = svg`
<svg id="set-values">
<g>
<text text-anchor="middle" class="set-value">
${
stateObj.state !== UNAVAILABLE && setHumidity != null
? formatNumber(setHumidity, this.hass.locale, {
maximumFractionDigits: 0,
})
: nothing
}
</text>
<text
dy="22"
text-anchor="middle"
id="set-mode"
>
${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
}
</text>
</g>
</svg>
`;
return html`
<ha-card>
<ha-card
style=${styleMap({
"--mode-color": stateColorCss(stateObj),
})}
>
<ha-icon-button
.path=${mdiDotsVertical}
.label=${this.hass!.localize(
"ui.panel.lovelace.cards.show_more_info"
)}
class="more-info"
@click=${this._handleMoreInfo}
tabindex="0"
@ -185,19 +213,35 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
<div id="slider">
${slider}
<div id="slider-center">
<ha-icon-button
class="toggle-button"
.disabled=${isUnavailableState(stateObj.state)}
@click=${this._toggle}
tabindex="0"
>
${setValues}
</ha-icon-button>
${currentHumidity} ${currentMode}
<div id="humidity">${currentHumidity} ${setValues}</div>
</div>
</div>
</div>
<div id="info" .title=${name}>${name}</div>
<div id="info" .title=${name}>
<div id="modes">
<ha-icon-button
class=${classMap({ "selected-icon": stateObj.state === "on" })}
@click=${this._turnOn}
tabindex="0"
.path=${mdiWaterPercent}
.label=${this.hass!.localize(
`component.humidifier.entity_component._.state.on`
)}
>
</ha-icon-button>
<ha-icon-button
class=${classMap({ "selected-icon": stateObj.state === "off" })}
@click=${this._turnOff}
tabindex="0"
.path=${mdiPower}
.label=${this.hass!.localize(
`component.humidifier.entity_component._.state.off`
)}
>
</ha-icon-button>
</div>
${name}
</div>
</div>
</ha-card>
`;
@ -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);
}

View File

@ -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
)}
<tspan dx="-3" dy="-6.5" style="font-size: 4px;">
${this.hass.config.unit_system.temperature}
</tspan>`
: ""
? svg`
${formatNumber(
stateObj.attributes.current_temperature,
this.hass.locale
)}
<tspan dx="-3" dy="-6.5" style="font-size: 4px;">
${this.hass.config.unit_system.temperature}
</tspan>
`
: nothing
}
</text>
</svg>
@ -186,42 +189,14 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
<g>
<text text-anchor="middle" class="set-value">
${
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
}
</text>
<text
@ -230,7 +205,7 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
id="set-mode"
>
${
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
}
</text>
</g>
@ -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

View File

@ -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,