Add new design to humidifier card (#18711)

This commit is contained in:
Paul Bottein 2023-11-23 00:37:19 +01:00 committed by GitHub
parent 61717e1529
commit 9163b9c124
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 352 additions and 365 deletions

View File

@ -1,4 +1,3 @@
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
import { mdiMinus, mdiPlus, mdiWaterPercent } from "@mdi/js"; import { mdiMinus, mdiPlus, mdiWaterPercent } from "@mdi/js";
import { CSSResultGroup, LitElement, PropertyValues, html } from "lit"; import { CSSResultGroup, LitElement, PropertyValues, html } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";

View File

@ -93,7 +93,6 @@ export const moreInfoControlCircularSliderStyle = css`
font-size: 32px; font-size: 32px;
} }
.info { .info {
margin-top: 12px;
font-size: 14px; font-size: 14px;
gap: 2px; gap: 2px;
--mdc-icon-size: 14px; --mdc-icon-size: 14px;

View File

@ -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 { CSSResultGroup, LitElement, PropertyValues, html } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map"; import { styleMap } from "lit/directives/style-map";
@ -100,13 +100,9 @@ export class HaMoreInfoHumidifierHumidity extends LitElement {
return html` return html`
<p class="label"> <p class="label">
${action && ["drying", "humidifying"].includes(action) ${action && action !== "off" && action !== "idle"
? this.hass.localize("ui.card.humidifier.target_label", { ? actionLabel
action: actionLabel, : this.hass.localize("ui.card.humidifier.target")}
})
: action && action !== "off" && action !== "idle"
? actionLabel
: this.hass.localize("ui.card.humidifier.target")}
</p> </p>
`; `;
} }
@ -118,7 +114,7 @@ export class HaMoreInfoHumidifierHumidity extends LitElement {
return html` return html`
<p class="label"> <p class="label">
${this.hass.localize("ui.card.humidifier.currently")} <ha-svg-icon .path=${mdiWaterPercent}></ha-svg-icon>
<span> <span>
${this.hass.formatEntityAttributeValue( ${this.hass.formatEntityAttributeValue(
this.stateObj, this.stateObj,

View File

@ -1,6 +1,4 @@
import { mdiDotsVertical, mdiPower, mdiWaterPercent } from "@mdi/js"; import { mdiDotsVertical } from "@mdi/js";
import "@thomasloven/round-slider";
import { HassEntity } from "home-assistant-js-websocket";
import { import {
CSSResultGroup, CSSResultGroup,
LitElement, LitElement,
@ -8,26 +6,19 @@ import {
css, css,
html, html,
nothing, nothing,
svg,
} from "lit"; } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import { computeStateName } from "../../../common/entity/compute_state_name"; 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 "../../../components/ha-card";
import type { HaCard } from "../../../components/ha-card";
import "../../../components/ha-icon-button"; import "../../../components/ha-icon-button";
import { UNAVAILABLE, isUnavailableState } from "../../../data/entity";
import { HumidifierEntity } from "../../../data/humidifier"; import { HumidifierEntity } from "../../../data/humidifier";
import "../../../dialogs/more-info/components/humidifier/ha-more-info-humidifier-humidity";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { findEntities } from "../common/find-entities"; import { findEntities } from "../common/find-entities";
import { hasConfigOrEntityChanged } from "../common/has-changed";
import { createEntityNotFoundWarning } from "../components/hui-warning"; import { createEntityNotFoundWarning } from "../components/hui-warning";
import "../tile-features/hui-tile-features";
import { LovelaceCard, LovelaceCardEditor } from "../types"; import { LovelaceCard, LovelaceCardEditor } from "../types";
import { HumidifierCardConfig } from "./types"; import { HumidifierCardConfig } from "./types";
@ -53,17 +44,21 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
includeDomains includeDomains
); );
return { type: "humidifier", entity: foundEntities[0] || "" }; return {
type: "humidifier",
entity: foundEntities[0] || "",
features: [
{
type: "humidifier-modes",
},
],
};
} }
@property({ attribute: false }) public hass?: HomeAssistant; @property({ attribute: false }) public hass?: HomeAssistant;
@state() private _config?: HumidifierCardConfig; @state() private _config?: HumidifierCardConfig;
@state() private _setHum?: number;
@query("ha-card") private _card?: HaCard;
public getCardSize(): number { public getCardSize(): number {
return 7; return 7;
} }
@ -76,168 +71,10 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
this._config = config; this._config = config;
} }
protected render() { private _handleMoreInfo() {
if (!this.hass || !this._config) { fireEvent(this, "hass-more-info", {
return nothing; entityId: this._config!.entity,
} });
const stateObj = this.hass.states[this._config.entity] as HumidifierEntity;
if (!stateObj) {
return html`
<hui-warning>
${createEntityNotFoundWarning(this.hass, this._config.entity)}
</hui-warning>
`;
}
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` <round-slider disabled="true"></round-slider> `
: html`
<round-slider
.value=${targetHumidity}
.disabled=${targetHumidity === null}
.min=${stateObj.attributes.min_humidity}
.max=${stateObj.attributes.max_humidity}
.rtl=${rtlDirection === "rtl"}
step="1"
@value-changing=${this._dragEvent}
@value-changed=${this._setHumidity}
></round-slider>
`;
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 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"
>
${
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
}
</text>
</g>
</svg>
`;
return html`
<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"
></ha-icon-button>
<div class="content">
<div id="controls">
<div id="slider">
${slider}
<div id="slider-center">
<div id="humidity">${currentHumidity} ${setValues}</div>
</div>
</div>
</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>
`;
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
return hasConfigOrEntityChanged(this, changedProps);
} }
protected updated(changedProps: PropertyValues): void { protected updated(changedProps: PropertyValues): void {
@ -264,90 +101,49 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
) { ) {
applyThemesOnElement(this, this.hass.themes, this._config.theme); applyThemesOnElement(this, this.hass.themes, this._config.theme);
} }
}
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) { if (!stateObj) {
return; return html`
<hui-warning>
${createEntityNotFoundWarning(this.hass, this._config.entity)}
</hui-warning>
`;
} }
if (!oldHass || oldHass.states[this._config.entity] !== stateObj) { const name = this._config!.name || computeStateName(stateObj);
this._rescale_svg();
}
}
public willUpdate(changedProps: PropertyValues) { return html`
if (!this.hass || !this._config || !changedProps.has("hass")) { <ha-card>
return; <p class="title">${name}</p>
} <ha-more-info-humidifier-humidity
show-current
const stateObj = this.hass.states[this._config.entity]; .hass=${this.hass}
if (!stateObj) { .stateObj=${stateObj}
return; ></ha-more-info-humidifier-humidity>
} <ha-icon-button
class="more-info"
const oldHass = changedProps.get("hass") as HomeAssistant | undefined; .label=${this.hass!.localize(
"ui.panel.lovelace.cards.show_more_info"
if (!oldHass || oldHass.states[this._config.entity] !== stateObj) { )}
this._setHum = this._getSetHum(stateObj); .path=${mdiDotsVertical}
} @click=${this._handleMoreInfo}
} tabindex="0"
></ha-icon-button>
private _rescale_svg() { <hui-tile-features
// Set the viewbox of the SVG containing the set temperature to perfectly .hass=${this.hass}
// fit the text .stateObj=${stateObj}
// That way it will auto-scale correctly .color=${this._config.color}
// This is not done to the SVG containing the current temperature, because .features=${this._config.features}
// it should not be centered on the text, but only on the value ></hui-tile-features>
const card = this._card; </ha-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 stateObj.attributes.humidity;
}
private _dragEvent(e): void {
this._setHum = e.detail.value;
}
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,
});
} }
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
@ -360,10 +156,27 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
height: 100%; height: 100%;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
--name-font-size: 1.2rem; padding: 0;
--brightness-font-size: 1.2rem; display: flex;
--rail-border-color: transparent; flex-direction: column;
--mode-color: var(--state-inactive-color); 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 { .more-info {
@ -375,93 +188,11 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
inset-inline-start: initial; inset-inline-start: initial;
border-radius: 100%; border-radius: 100%;
color: var(--secondary-text-color); color: var(--secondary-text-color);
z-index: 1;
direction: var(--direction); direction: var(--direction);
} }
.content { hui-tile-features {
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%; 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%);
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);
} }
`; `;
} }

View File

@ -1,5 +1,4 @@
import { mdiDotsVertical } from "@mdi/js"; import { mdiDotsVertical } from "@mdi/js";
import "@thomasloven/round-slider";
import { import {
CSSResultGroup, CSSResultGroup,
LitElement, LitElement,

View File

@ -262,6 +262,7 @@ export interface HumidifierCardConfig extends LovelaceCardConfig {
entity: string; entity: string;
theme?: string; theme?: string;
name?: string; name?: string;
features?: LovelaceTileFeatureConfig[];
} }
export interface IframeCardConfig extends LovelaceCardConfig { export interface IframeCardConfig extends LovelaceCardConfig {

View File

@ -153,6 +153,11 @@ export const computeCards = (
const cardConfig: HumidifierCardConfig = { const cardConfig: HumidifierCardConfig = {
type: "humidifier", type: "humidifier",
entity: entityId, entity: entityId,
features: [
{
type: "humidifier-modes",
},
],
}; };
cards.push(cardConfig); cards.push(cardConfig);
} else if (domain === "media_player") { } else if (domain === "media_player") {

View File

@ -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-position-tile-feature";
import "../tile-features/hui-cover-tilt-tile-feature"; import "../tile-features/hui-cover-tilt-tile-feature";
import "../tile-features/hui-fan-speed-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-lawn-mower-commands-tile-feature";
import "../tile-features/hui-light-brightness-tile-feature"; import "../tile-features/hui-light-brightness-tile-feature";
import "../tile-features/hui-light-color-temp-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-select-options-tile-feature";
import "../tile-features/hui-target-temperature-tile-feature"; import "../tile-features/hui-target-temperature-tile-feature";
import "../tile-features/hui-vacuum-commands-tile-feature"; import "../tile-features/hui-vacuum-commands-tile-feature";
import "../tile-features/hui-water-heater-operation-modes-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 { LovelaceTileFeatureConfig } from "../tile-features/types";
import { import {
createLovelaceElement, createLovelaceElement,
@ -29,6 +30,7 @@ const TYPES: Set<LovelaceTileFeatureConfig["type"]> = new Set([
"cover-tilt-position", "cover-tilt-position",
"cover-tilt", "cover-tilt",
"fan-speed", "fan-speed",
"humidifier-modes",
"lawn-mower-commands", "lawn-mower-commands",
"light-brightness", "light-brightness",
"light-color-temp", "light-color-temp",

View File

@ -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 { customElement, property, state } from "lit/decorators";
import { assert, assign, object, optional, string } from "superstruct"; import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../common/dom/fire_event"; 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 "../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { SchemaUnion } from "../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../types";
import type { HumidifierCardConfig } from "../../cards/types"; import type { HumidifierCardConfig } from "../../cards/types";
import {
LovelaceTileFeatureConfig,
LovelaceTileFeatureContext,
} from "../../tile-features/types";
import type { LovelaceCardEditor } from "../../types"; import type { LovelaceCardEditor } from "../../types";
import { baseLovelaceCardConfig } from "../structs/base-card-struct"; import { baseLovelaceCardConfig } from "../structs/base-card-struct";
import { EditSubElementEvent, SubElementEditorConfig } from "../types";
import "./hui-tile-card-features-editor";
const cardConfigStruct = assign( const cardConfigStruct = assign(
baseLovelaceCardConfig, baseLovelaceCardConfig,
@ -15,6 +30,7 @@ const cardConfigStruct = assign(
entity: optional(string()), entity: optional(string()),
name: optional(string()), name: optional(string()),
theme: optional(string()), theme: optional(string()),
features: optional(array(any())),
}) })
); );
@ -43,16 +59,39 @@ export class HuiHumidifierCardEditor
@state() private _config?: HumidifierCardConfig; @state() private _config?: HumidifierCardConfig;
@state() private _subElementEditorConfig?: SubElementEditorConfig;
public setConfig(config: HumidifierCardConfig): void { public setConfig(config: HumidifierCardConfig): void {
assert(config, cardConfigStruct); assert(config, cardConfigStruct);
this._config = config; this._config = config;
} }
private _context = memoizeOne(
(entity_id?: string): LovelaceTileFeatureContext => ({ entity_id })
);
protected render() { protected render() {
if (!this.hass || !this._config) { if (!this.hass || !this._config) {
return nothing; return nothing;
} }
const stateObj = this._config.entity
? this.hass.states[this._config.entity]
: undefined;
if (this._subElementEditorConfig) {
return html`
<hui-sub-element-editor
.hass=${this.hass}
.config=${this._subElementEditorConfig}
.context=${this._context(this._config.entity)}
@go-back=${this._goBack}
@config-changed=${this.subElementChanged}
>
</hui-sub-element-editor>
`;
}
return html` return html`
<ha-form <ha-form
.hass=${this.hass} .hass=${this.hass}
@ -61,6 +100,13 @@ export class HuiHumidifierCardEditor
.computeLabel=${this._computeLabelCallback} .computeLabel=${this._computeLabelCallback}
@value-changed=${this._valueChanged} @value-changed=${this._valueChanged}
></ha-form> ></ha-form>
<hui-tile-card-features-editor
.hass=${this.hass}
.stateObj=${stateObj}
.features=${this._config!.features ?? []}
@features-changed=${this._featuresChanged}
@edit-detail-element=${this._editDetailElement}
></hui-tile-card-features-editor>
`; `;
} }
@ -68,6 +114,62 @@ export class HuiHumidifierCardEditor
fireEvent(this, "config-changed", { config: ev.detail.value }); 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<EditSubElementEvent>): void {
this._subElementEditorConfig = ev.detail.subElementConfig;
}
private _goBack(): void {
this._subElementEditorConfig = undefined;
}
private _computeLabelCallback = (schema: SchemaUnion<typeof SCHEMA>) => { private _computeLabelCallback = (schema: SchemaUnion<typeof SCHEMA>) => {
if (schema.name === "entity") { if (schema.name === "entity") {
return this.hass!.localize( return this.hass!.localize(
@ -87,6 +189,15 @@ export class HuiHumidifierCardEditor
`ui.panel.lovelace.editor.card.generic.${schema.name}` `ui.panel.lovelace.editor.card.generic.${schema.name}`
); );
}; };
static get styles() {
return css`
ha-form {
display: block;
margin-bottom: 24px;
}
`;
}
} }
declare global { declare global {

View File

@ -24,21 +24,22 @@ import { HomeAssistant } from "../../../../types";
import { getTileFeatureElementClass } from "../../create-element/create-tile-feature-element"; import { getTileFeatureElementClass } from "../../create-element/create-tile-feature-element";
import { supportsAlarmModesTileFeature } from "../../tile-features/hui-alarm-modes-tile-feature"; import { supportsAlarmModesTileFeature } from "../../tile-features/hui-alarm-modes-tile-feature";
import { supportsClimateHvacModesTileFeature } from "../../tile-features/hui-climate-hvac-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 { supportsCoverOpenCloseTileFeature } from "../../tile-features/hui-cover-open-close-tile-feature";
import { supportsCoverPositionTileFeature } from "../../tile-features/hui-cover-position-tile-feature"; import { supportsCoverPositionTileFeature } from "../../tile-features/hui-cover-position-tile-feature";
import { supportsCoverTiltPositionTileFeature } from "../../tile-features/hui-cover-tilt-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 { supportsCoverTiltTileFeature } from "../../tile-features/hui-cover-tilt-tile-feature";
import { supportsFanSpeedTileFeature } from "../../tile-features/hui-fan-speed-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 { supportsLawnMowerCommandTileFeature } from "../../tile-features/hui-lawn-mower-commands-tile-feature";
import { supportsLightBrightnessTileFeature } from "../../tile-features/hui-light-brightness-tile-feature"; import { supportsLightBrightnessTileFeature } from "../../tile-features/hui-light-brightness-tile-feature";
import { supportsLightColorTempTileFeature } from "../../tile-features/hui-light-color-temp-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 { supportsSelectOptionTileFeature } from "../../tile-features/hui-select-options-tile-feature";
import { supportsTargetTemperatureTileFeature } from "../../tile-features/hui-target-temperature-tile-feature"; import { supportsTargetTemperatureTileFeature } from "../../tile-features/hui-target-temperature-tile-feature";
import { supportsVacuumCommandTileFeature } from "../../tile-features/hui-vacuum-commands-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 { supportsWaterHeaterOperationModesTileFeature } from "../../tile-features/hui-water-heater-operation-modes-tile-feature";
import { LovelaceTileFeatureConfig } from "../../tile-features/types"; 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"]; export type FeatureType = LovelaceTileFeatureConfig["type"];
type SupportsFeature = (stateObj: HassEntity) => boolean; type SupportsFeature = (stateObj: HassEntity) => boolean;
@ -52,6 +53,7 @@ const UI_FEATURE_TYPES = [
"cover-tilt-position", "cover-tilt-position",
"cover-tilt", "cover-tilt",
"fan-speed", "fan-speed",
"humidifier-modes",
"lawn-mower-commands", "lawn-mower-commands",
"light-brightness", "light-brightness",
"light-color-temp", "light-color-temp",
@ -86,14 +88,15 @@ const SUPPORTS_FEATURE_TYPES: Record<
"cover-tilt-position": supportsCoverTiltPositionTileFeature, "cover-tilt-position": supportsCoverTiltPositionTileFeature,
"cover-tilt": supportsCoverTiltTileFeature, "cover-tilt": supportsCoverTiltTileFeature,
"fan-speed": supportsFanSpeedTileFeature, "fan-speed": supportsFanSpeedTileFeature,
"humidifier-modes": supportsHumidifierModesTileFeature,
"lawn-mower-commands": supportsLawnMowerCommandTileFeature, "lawn-mower-commands": supportsLawnMowerCommandTileFeature,
"light-brightness": supportsLightBrightnessTileFeature, "light-brightness": supportsLightBrightnessTileFeature,
"light-color-temp": supportsLightColorTempTileFeature, "light-color-temp": supportsLightColorTempTileFeature,
number: supportsNumberTileFeature,
"target-temperature": supportsTargetTemperatureTileFeature, "target-temperature": supportsTargetTemperatureTileFeature,
"vacuum-commands": supportsVacuumCommandTileFeature, "vacuum-commands": supportsVacuumCommandTileFeature,
"water-heater-operation-modes": supportsWaterHeaterOperationModesTileFeature, "water-heater-operation-modes": supportsWaterHeaterOperationModesTileFeature,
"select-options": supportsSelectOptionTileFeature, "select-options": supportsSelectOptionTileFeature,
number: supportsNumberTileFeature,
}; };
const CUSTOM_FEATURE_ENTRIES: Record< const CUSTOM_FEATURE_ENTRIES: Record<

View File

@ -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<ControlSelectOption>((entityState) => ({
value: entityState,
label: this.hass!.formatEntityState(this.stateObj!, entityState),
path: entityState === "on" ? mdiWaterPercent : mdiPower,
}));
return html`
<div class="container">
<ha-control-select
.options=${options}
.value=${this._currentState}
@value-changed=${this._valueChanged}
hide-label
.ariaLabel=${this.hass.localize("ui.card.humidifier.state")}
style=${styleMap({
"--control-select-color": color,
})}
.disabled=${this.stateObj!.state === UNAVAILABLE}
>
</ha-control-select>
</div>
`;
}
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;
}
}

View File

@ -64,6 +64,10 @@ export interface WaterHeaterOperationModesTileFeatureConfig {
operation_modes?: OperationMode[]; operation_modes?: OperationMode[];
} }
export interface HumidifierModesTileFeatureConfig {
type: "humidifier-modes";
}
export const VACUUM_COMMANDS = [ export const VACUUM_COMMANDS = [
"start_pause", "start_pause",
"stop", "stop",
@ -97,6 +101,7 @@ export type LovelaceTileFeatureConfig =
| CoverTiltPositionTileFeatureConfig | CoverTiltPositionTileFeatureConfig
| CoverTiltTileFeatureConfig | CoverTiltTileFeatureConfig
| FanSpeedTileFeatureConfig | FanSpeedTileFeatureConfig
| HumidifierModesTileFeatureConfig
| LawnMowerCommandsTileFeatureConfig | LawnMowerCommandsTileFeatureConfig
| LightBrightnessTileFeatureConfig | LightBrightnessTileFeatureConfig
| LightColorTempTileFeatureConfig | LightColorTempTileFeatureConfig