Update humidifier Ui with current humidity and action (#17072)

This commit is contained in:
Paul Bottein 2023-06-28 16:14:18 +02:00 committed by GitHub
parent c0613545e7
commit eaeb37da4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 331 additions and 42 deletions

View File

@ -182,6 +182,7 @@ export const DOMAINS_WITH_CARD = [
"input_select", "input_select",
"input_number", "input_number",
"input_text", "input_text",
"humidifier",
"lock", "lock",
"media_player", "media_player",
"number", "number",

View File

@ -135,6 +135,7 @@ const FIXED_DOMAIN_ATTRIBUTE_STATES = {
}, },
humidifier: { humidifier: {
device_class: ["humidifier", "dehumidifier"], device_class: ["humidifier", "dehumidifier"],
action: ["off", "idle", "humidifying", "drying"],
}, },
media_player: { media_player: {
device_class: ["tv", "speaker", "receiver"], device_class: ["tv", "speaker", "receiver"],

View File

@ -0,0 +1,137 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { computeAttributeValueDisplay } from "../common/entity/compute_attribute_display";
import { computeStateDisplay } from "../common/entity/compute_state_display";
import { formatNumber } from "../common/number/format_number";
import { blankBeforePercent } from "../common/translations/blank_before_percent";
import { isUnavailableState, OFF } from "../data/entity";
import { HumidifierEntity } from "../data/humidifier";
import type { HomeAssistant } from "../types";
@customElement("ha-humidifier-state")
class HaHumidifierState extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public stateObj!: HumidifierEntity;
protected render(): TemplateResult {
const currentStatus = this._computeCurrentStatus();
return html`<div class="target">
${!isUnavailableState(this.stateObj.state)
? html`<span class="state-label">
${this._localizeState()}
${this.stateObj.attributes.mode
? html`-
${computeAttributeValueDisplay(
this.hass.localize,
this.stateObj,
this.hass.locale,
this.hass.config,
this.hass.entities,
"mode"
)}`
: ""}
</span>
<div class="unit">${this._computeTarget()}</div>`
: this._localizeState()}
</div>
${currentStatus && !isUnavailableState(this.stateObj.state)
? html`<div class="current">
${this.hass.localize("ui.card.climate.currently")}:
<div class="unit">${currentStatus}</div>
</div>`
: ""}`;
}
private _computeCurrentStatus(): string | undefined {
if (!this.hass || !this.stateObj) {
return undefined;
}
if (this.stateObj.attributes.current_humidity != null) {
return `${formatNumber(
this.stateObj.attributes.current_humidity,
this.hass.locale
)}${blankBeforePercent(this.hass.locale)}%`;
}
return undefined;
}
private _computeTarget(): string {
if (!this.hass || !this.stateObj) {
return "";
}
if (this.stateObj.attributes.humidity != null) {
return `${formatNumber(
this.stateObj.attributes.humidity,
this.hass.locale
)}${blankBeforePercent(this.hass.locale)}%`;
}
return "";
}
private _localizeState(): string {
if (isUnavailableState(this.stateObj.state)) {
return this.hass.localize(`state.default.${this.stateObj.state}`);
}
const stateString = computeStateDisplay(
this.hass.localize,
this.stateObj,
this.hass.locale,
this.hass.config,
this.hass.entities
);
return this.stateObj.attributes.action && this.stateObj.state !== OFF
? `${computeAttributeValueDisplay(
this.hass.localize,
this.stateObj,
this.hass.locale,
this.hass.config,
this.hass.entities,
"action"
)} (${stateString})`
: stateString;
}
static get styles(): CSSResultGroup {
return css`
:host {
display: flex;
flex-direction: column;
justify-content: center;
white-space: nowrap;
}
.target {
color: var(--primary-text-color);
}
.current {
color: var(--secondary-text-color);
}
.state-label {
font-weight: bold;
text-transform: capitalize;
}
.unit {
display: inline-block;
direction: ltr;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-humidifier-state": HaHumidifierState;
}
}

View File

@ -2,21 +2,19 @@ import {
HassEntityAttributeBase, HassEntityAttributeBase,
HassEntityBase, HassEntityBase,
} from "home-assistant-js-websocket"; } from "home-assistant-js-websocket";
import { FIXED_DOMAIN_STATES } from "../common/entity/get_states";
import { UNAVAILABLE_STATES } from "./entity";
type HumidifierState = export type HumidifierState = "on" | "off";
| (typeof FIXED_DOMAIN_STATES.humidifier)[number]
| (typeof UNAVAILABLE_STATES)[number]; export type HumidifierAction = "off" | "idle" | "humidifying" | "drying";
export type HumidifierEntity = HassEntityBase & { export type HumidifierEntity = HassEntityBase & {
state: HumidifierState;
attributes: HassEntityAttributeBase & { attributes: HassEntityAttributeBase & {
humidity?: number; humidity?: number;
current_humidity?: number; current_humidity?: number;
min_humidity?: number; min_humidity?: number;
max_humidity?: number; max_humidity?: number;
mode?: string; mode?: string;
action: HumidifierAction;
available_modes?: string[]; available_modes?: string[];
}; };
}; };

View File

@ -10,7 +10,10 @@ import {
import { property } from "lit/decorators"; import { property } from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import { computeAttributeValueDisplay } from "../../../common/entity/compute_attribute_display"; import {
computeAttributeNameDisplay,
computeAttributeValueDisplay,
} from "../../../common/entity/compute_attribute_display";
import { stopPropagation } from "../../../common/dom/stop_propagation"; import { stopPropagation } from "../../../common/dom/stop_propagation";
import { supportsFeature } from "../../../common/entity/supports-feature"; import { supportsFeature } from "../../../common/entity/supports-feature";
import { computeRTLDirection } from "../../../common/util/compute_rtl"; import { computeRTLDirection } from "../../../common/util/compute_rtl";
@ -22,6 +25,7 @@ import {
HUMIDIFIER_SUPPORT_MODES, HUMIDIFIER_SUPPORT_MODES,
} from "../../../data/humidifier"; } from "../../../data/humidifier";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
class MoreInfoHumidifier extends LitElement { class MoreInfoHumidifier extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@ -49,7 +53,14 @@ class MoreInfoHumidifier extends LitElement {
})} })}
> >
<div class="container-humidity"> <div class="container-humidity">
<div>${hass.localize("ui.card.humidifier.humidity")}</div> <div>
${computeAttributeNameDisplay(
hass.localize,
stateObj,
hass.entities,
"humidity"
)}
</div>
<div class="single-row"> <div class="single-row">
<div class="target-humidity">${stateObj.attributes.humidity} %</div> <div class="target-humidity">${stateObj.attributes.humidity} %</div>
<ha-slider <ha-slider
@ -65,6 +76,35 @@ class MoreInfoHumidifier extends LitElement {
</ha-slider> </ha-slider>
</div> </div>
</div> </div>
<ha-select
.label=${hass.localize("ui.card.humidifier.state")}
.value=${stateObj.state}
fixedMenuPosition
naturalMenuWidth
@selected=${this._handleStateChanged}
@closed=${stopPropagation}
>
<mwc-list-item value="off">
${computeStateDisplay(
hass.localize,
stateObj,
hass.locale,
this.hass.config,
hass.entities,
"off"
)}
</mwc-list-item>
<mwc-list-item value="on">
${computeStateDisplay(
hass.localize,
stateObj,
hass.locale,
this.hass.config,
hass.entities,
"on"
)}
</mwc-list-item>
</ha-select>
${supportModes ${supportModes
? html` ? html`
@ -123,6 +163,16 @@ class MoreInfoHumidifier extends LitElement {
); );
} }
private _handleStateChanged(ev) {
const newVal = ev.target.value || null;
this._callServiceHelper(
this.stateObj!.state,
newVal,
newVal === "on" ? "turn_on" : "turn_off",
{}
);
}
private _handleModeChanged(ev) { private _handleModeChanged(ev) {
const newVal = ev.target.value || null; const newVal = ev.target.value || null;
this._callServiceHelper( this._callServiceHelper(
@ -179,6 +229,11 @@ class MoreInfoHumidifier extends LitElement {
ha-select { ha-select {
width: 100%; width: 100%;
margin-top: 8px;
}
ha-slider {
width: 100%;
} }
.container-humidity .single-row { .container-humidity .single-row {

View File

@ -225,15 +225,6 @@ 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( const stateDisplay = computeStateDisplay(
this.hass!.localize, this.hass!.localize,
stateObj, stateObj,
@ -251,6 +242,16 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
return `${stateDisplay}${positionStateDisplay}`; return `${stateDisplay}${positionStateDisplay}`;
} }
} }
if (domain === "humidifier" && stateActive(stateObj)) {
const humidity = (stateObj as HumidifierEntity).attributes.humidity;
if (humidity) {
return `${stateDisplay}${Math.round(humidity)}${blankBeforePercent(
this.hass!.locale
)}%`;
}
}
return stateDisplay; return stateDisplay;
} }

View File

@ -0,0 +1,41 @@
import {
mdiArrowDownBold,
mdiArrowUpBold,
mdiClockOutline,
mdiPower,
} from "@mdi/js";
import { stateColorCss } from "../../../../../common/entity/state_color";
import {
HumidifierAction,
HumidifierEntity,
HumidifierState,
} from "../../../../../data/humidifier";
import { ComputeBadgeFunction } from "./tile-badge";
export const HUMIDIFIER_ACTION_ICONS: Record<HumidifierAction, string> = {
drying: mdiArrowDownBold,
humidifying: mdiArrowUpBold,
idle: mdiClockOutline,
off: mdiPower,
};
export const HUMIDIFIER_ACTION_MODE: Record<HumidifierAction, HumidifierState> =
{
drying: "on",
humidifying: "on",
idle: "off",
off: "off",
};
export const computeHumidifierBadge: ComputeBadgeFunction = (stateObj) => {
const hvacAction = (stateObj as HumidifierEntity).attributes.action;
if (!hvacAction || hvacAction === "off") {
return undefined;
}
return {
iconPath: HUMIDIFIER_ACTION_ICONS[hvacAction],
color: stateColorCss(stateObj, HUMIDIFIER_ACTION_MODE[hvacAction]),
};
};

View File

@ -5,6 +5,7 @@ import { UNAVAILABLE, UNKNOWN } from "../../../../../data/entity";
import { HomeAssistant } from "../../../../../types"; import { HomeAssistant } from "../../../../../types";
import { computeClimateBadge } from "./tile-badge-climate"; import { computeClimateBadge } from "./tile-badge-climate";
import { computePersonBadge } from "./tile-badge-person"; import { computePersonBadge } from "./tile-badge-person";
import { computeHumidifierBadge } from "./tile-badge-humidifier";
export type TileBadge = { export type TileBadge = {
color?: string; color?: string;
@ -34,6 +35,8 @@ export const computeTileBadge: ComputeBadgeFunction = (stateObj, hass) => {
return computePersonBadge(stateObj, hass); return computePersonBadge(stateObj, hass);
case "climate": case "climate":
return computeClimateBadge(stateObj, hass); return computeClimateBadge(stateObj, hass);
case "humidifier":
return computeHumidifierBadge(stateObj, hass);
default: default:
return undefined; return undefined;
} }

View File

@ -1,11 +1,18 @@
import { html, LitElement, PropertyValues, nothing } from "lit"; import {
CSSResultGroup,
LitElement,
PropertyValues,
css,
html,
nothing,
} from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import "../../../components/entity/ha-entity-toggle"; import "../../../components/entity/ha-entity-toggle";
import "../../../components/ha-humidifier-state";
import { HumidifierEntity } from "../../../data/humidifier"; import { HumidifierEntity } from "../../../data/humidifier";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { hasConfigOrEntityChanged } from "../common/has-changed"; import { hasConfigOrEntityChanged } from "../common/has-changed";
import "../components/hui-generic-entity-row"; import "../components/hui-generic-entity-row";
import { computeAttributeValueDisplay } from "../../../common/entity/compute_attribute_display";
import { createEntityNotFoundWarning } from "../components/hui-warning"; import { createEntityNotFoundWarning } from "../components/hui-warning";
import { EntityConfig, LovelaceRow } from "./types"; import { EntityConfig, LovelaceRow } from "./types";
@ -43,32 +50,20 @@ class HuiHumidifierEntityRow extends LitElement implements LovelaceRow {
} }
return html` return html`
<hui-generic-entity-row <hui-generic-entity-row .hass=${this.hass} .config=${this._config}>
.hass=${this.hass} <ha-humidifier-state .hass=${this.hass} .stateObj=${stateObj}>
.config=${this._config} </ha-humidifier-state>
.secondaryText=${stateObj.attributes.humidity
? `${this.hass!.localize("ui.card.humidifier.humidity")}:
${stateObj.attributes.humidity} %${
stateObj.attributes.mode
? ` (${computeAttributeValueDisplay(
this.hass.localize,
stateObj,
this.hass.locale,
this.hass.config,
this.hass.entities,
"mode"
)})`
: ""
}`
: ""}
>
<ha-entity-toggle
.hass=${this.hass}
.stateObj=${stateObj}
></ha-entity-toggle>
</hui-generic-entity-row> </hui-generic-entity-row>
`; `;
} }
static get styles(): CSSResultGroup {
return css`
ha-humidifier-state {
text-align: right;
}
`;
}
} }
declare global { declare global {

View File

@ -4,6 +4,7 @@ import dynamicContentUpdater from "../common/dom/dynamic_content_updater";
import { stateCardType } from "../common/entity/state_card_type"; import { stateCardType } from "../common/entity/state_card_type";
import "./state-card-button"; import "./state-card-button";
import "./state-card-climate"; import "./state-card-climate";
import "./state-card-humidifier";
import "./state-card-configurator"; import "./state-card-configurator";
import "./state-card-cover"; import "./state-card-cover";
import "./state-card-display"; import "./state-card-display";

View File

@ -0,0 +1,55 @@
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../components/entity/state-info";
import "../components/ha-humidifier-state";
class StateCardHumidifier extends PolymerElement {
static get template() {
return html`
<style include="iron-flex iron-flex-alignment"></style>
<style>
:host {
@apply --paper-font-body1;
line-height: 1.5;
}
ha-humidifier-state {
margin-left: 16px;
text-align: right;
}
</style>
<div class="horizontal justified layout">
${this.stateInfoTemplate}
<ha-humidifier-state
hass="[[hass]]"
state-obj="[[stateObj]]"
></ha-humidifier-state>
</div>
`;
}
static get stateInfoTemplate() {
return html`
<state-info
hass="[[hass]]"
state-obj="[[stateObj]]"
in-dialog="[[inDialog]]"
></state-info>
`;
}
static get properties() {
return {
hass: Object,
stateObj: Object,
inDialog: {
type: Boolean,
value: false,
},
};
}
}
customElements.define("state-card-humidifier", StateCardHumidifier);

View File

@ -131,6 +131,7 @@
}, },
"humidifier": { "humidifier": {
"humidity": "Target humidity", "humidity": "Target humidity",
"state": "State",
"mode": "Mode", "mode": "Mode",
"target_humidity_entity": "{name} target humidity", "target_humidity_entity": "{name} target humidity",
"on_entity": "{name} on" "on_entity": "{name} on"