Add stateValue parameter to ha-state-icon (#19508)

* Add state value option to entityIcon

* Migrate lock, valve and cover more info to icon translations

* Migrate tile card

* Remove domain icon from area card

* Use attribute
This commit is contained in:
Paul Bottein 2024-01-22 20:10:08 +01:00 committed by GitHub
parent f6af73b222
commit 1c9ea0a9d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 241 additions and 178 deletions

View File

@ -14,6 +14,8 @@ export class HaStateIcon extends LitElement {
@property({ attribute: false }) public stateObj?: HassEntity; @property({ attribute: false }) public stateObj?: HassEntity;
@property({ attribute: false }) public stateValue?: string;
@property() public icon?: string; @property() public icon?: string;
protected render() { protected render() {
@ -30,12 +32,14 @@ export class HaStateIcon extends LitElement {
if (!this.hass) { if (!this.hass) {
return this._renderFallback(); return this._renderFallback();
} }
const icon = entityIcon(this.hass, this.stateObj).then((icn) => { const icon = entityIcon(this.hass, this.stateObj, this.stateValue).then(
if (icn) { (icn) => {
return html`<ha-icon .icon=${icn}></ha-icon>`; if (icn) {
return html`<ha-icon .icon=${icn}></ha-icon>`;
}
return this._renderFallback();
} }
return this._renderFallback(); );
});
return html`${until(icon)}`; return html`${until(icon)}`;
} }

View File

@ -1,19 +1,13 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement } from "lit/decorators";
import "../ha-icon"; import "../ha-icon";
@customElement("ha-tile-badge") @customElement("ha-tile-badge")
export class HaTileBadge extends LitElement { export class HaTileBadge extends LitElement {
@property() public iconPath?: string;
@property() public icon?: string;
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<div class="badge"> <div class="badge">
${this.icon <slot></slot>
? html`<ha-icon .icon=${this.icon}></ha-icon>`
: html`<ha-svg-icon .path=${this.iconPath}></ha-svg-icon>`}
</div> </div>
`; `;
} }
@ -36,8 +30,7 @@ export class HaTileBadge extends LitElement {
background-color: var(--tile-badge-background-color); background-color: var(--tile-badge-background-color);
transition: background-color 280ms ease-in-out; transition: background-color 280ms ease-in-out;
} }
.badge ha-icon, .badge ::slotted(*) {
.badge ha-svg-icon {
color: var(--tile-badge-icon-color); color: var(--tile-badge-icon-color);
} }
`; `;

View File

@ -1,20 +1,14 @@
import { CSSResultGroup, html, css, LitElement, TemplateResult } from "lit"; import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement } from "lit/decorators";
import "../ha-icon"; import "../ha-icon";
import "../ha-svg-icon"; import "../ha-svg-icon";
@customElement("ha-tile-icon") @customElement("ha-tile-icon")
export class HaTileIcon extends LitElement { export class HaTileIcon extends LitElement {
@property() public iconPath?: string;
@property() public icon?: string;
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<div class="shape"> <div class="shape">
${this.icon <slot></slot>
? html`<ha-icon .icon=${this.icon}></ha-icon>`
: html`<ha-svg-icon .path=${this.iconPath}></ha-svg-icon>`}
</div> </div>
`; `;
} }
@ -47,8 +41,7 @@ export class HaTileIcon extends LitElement {
transition: color 180ms ease-in-out; transition: color 180ms ease-in-out;
overflow: hidden; overflow: hidden;
} }
.shape ha-icon, .shape ::slotted(*) {
.shape ha-svg-icon {
display: flex; display: flex;
color: var(--tile-icon-color); color: var(--tile-icon-color);
transition: color 180ms ease-in-out; transition: color 180ms ease-in-out;

View File

@ -71,10 +71,15 @@ export const getComponentIcons = async (
return resources.entity_component.then((res) => res[domain]); return resources.entity_component.then((res) => res[domain]);
}; };
export const entityIcon = async (hass: HomeAssistant, state: HassEntity) => { export const entityIcon = async (
hass: HomeAssistant,
state: HassEntity,
stateValue?: string
) => {
let icon: string | undefined; let icon: string | undefined;
const domain = computeStateDomain(state); const domain = computeStateDomain(state);
const entity = hass.entities?.[state.entity_id]; const entity = hass.entities?.[state.entity_id];
const value = stateValue ?? state.state;
if (entity?.icon) { if (entity?.icon) {
return entity.icon; return entity.icon;
} }
@ -82,18 +87,19 @@ export const entityIcon = async (hass: HomeAssistant, state: HassEntity) => {
const platformIcons = await getPlatformIcons(hass, entity.platform); const platformIcons = await getPlatformIcons(hass, entity.platform);
if (platformIcons) { if (platformIcons) {
icon = icon =
platformIcons[domain]?.[entity.translation_key]?.state?.[state.state] || platformIcons[domain]?.[entity.translation_key]?.state?.[value] ||
platformIcons[domain]?.[entity.translation_key]?.default; platformIcons[domain]?.[entity.translation_key]?.default;
} }
} }
if (!icon) { if (!icon) {
const entityComponentIcons = await getComponentIcons(hass, domain); const entityComponentIcons = await getComponentIcons(hass, domain);
if (entityComponentIcons) { if (entityComponentIcons) {
icon = icon =
entityComponentIcons[state.attributes.device_class || "_"]?.state?.[ entityComponentIcons[state.attributes.device_class || "_"]?.state?.[
state.state value
] || ] ||
entityComponentIcons._?.state?.[state.state] || entityComponentIcons._?.state?.[value] ||
entityComponentIcons[state.attributes.device_class || "_"]?.default || entityComponentIcons[state.attributes.device_class || "_"]?.default ||
entityComponentIcons._?.default; entityComponentIcons._?.default;
} }
@ -110,13 +116,14 @@ export const attributeIcon = async (
let icon: string | undefined; let icon: string | undefined;
const domain = computeStateDomain(state); const domain = computeStateDomain(state);
const entity = hass.entities?.[state.entity_id]; const entity = hass.entities?.[state.entity_id];
const value = attributeValue ?? state.attributes[attribute];
if (entity?.translation_key && entity.platform) { if (entity?.translation_key && entity.platform) {
const platformIcons = await getPlatformIcons(hass, entity.platform); const platformIcons = await getPlatformIcons(hass, entity.platform);
if (platformIcons) { if (platformIcons) {
icon = icon =
platformIcons[domain]?.[entity.translation_key]?.state_attributes?.[ platformIcons[domain]?.[entity.translation_key]?.state_attributes?.[
attribute attribute
]?.state?.[attributeValue || state.attributes[attribute]]; ]?.state?.[value];
} }
} }
if (!icon) { if (!icon) {
@ -124,12 +131,8 @@ export const attributeIcon = async (
if (entityComponentIcons) { if (entityComponentIcons) {
icon = icon =
entityComponentIcons[state.attributes.device_class || "_"] entityComponentIcons[state.attributes.device_class || "_"]
.state_attributes?.[attribute]?.state?.[ .state_attributes?.[attribute]?.state?.[value] ||
attributeValue || state.attributes[attribute] entityComponentIcons._.state_attributes?.[attribute]?.state?.[value];
] ||
entityComponentIcons._.state_attributes?.[attribute]?.state?.[
attributeValue || state.attributes[attribute]
];
} }
} }
return icon; return icon;

View File

@ -2,9 +2,9 @@ import { mdiShieldOff } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { styleMap } from "lit/directives/style-map"; import { styleMap } from "lit/directives/style-map";
import { domainIcon } from "../../../common/entity/domain_icon";
import { stateColorCss } from "../../../common/entity/state_color"; import { stateColorCss } from "../../../common/entity/state_color";
import "../../../components/ha-outlined-button"; import "../../../components/ha-outlined-button";
import "../../../components/ha-state-icon";
import { AlarmControlPanelEntity } from "../../../data/alarm_control_panel"; import { AlarmControlPanelEntity } from "../../../data/alarm_control_panel";
import "../../../state-control/alarm_control_panel/ha-state-control-alarm_control_panel-modes"; import "../../../state-control/alarm_control_panel/ha-state-control-alarm_control_panel-modes";
import type { HomeAssistant } from "../../../types"; import type { HomeAssistant } from "../../../types";
@ -59,9 +59,8 @@ class MoreInfoAlarmControlPanel extends LitElement {
<div class="status"> <div class="status">
<span></span> <span></span>
<div class="icon"> <div class="icon">
<ha-svg-icon <ha-state-icon .hass=${this.hass} .stateObj=${this.stateObj}>
.path=${domainIcon("alarm_control_panel", this.stateObj)} </ha-state-icon>
></ha-svg-icon>
</div> </div>
<ha-outlined-button @click=${this._disarm}> <ha-outlined-button @click=${this._disarm}>
${this.hass.localize("ui.card.alarm_control_panel.disarm")} ${this.hass.localize("ui.card.alarm_control_panel.disarm")}

View File

@ -2,11 +2,11 @@ import { mdiDoorOpen, mdiLock, mdiLockOff } from "@mdi/js";
import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { styleMap } from "lit/directives/style-map"; import { styleMap } from "lit/directives/style-map";
import { domainIcon } from "../../../common/entity/domain_icon";
import { stateColorCss } from "../../../common/entity/state_color"; import { stateColorCss } from "../../../common/entity/state_color";
import { supportsFeature } from "../../../common/entity/supports-feature"; import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-attributes"; import "../../../components/ha-attributes";
import "../../../components/ha-outlined-icon-button"; import "../../../components/ha-outlined-icon-button";
import "../../../components/ha-state-icon";
import { UNAVAILABLE } from "../../../data/entity"; import { UNAVAILABLE } from "../../../data/entity";
import { import {
LockEntity, LockEntity,
@ -62,9 +62,10 @@ class MoreInfoLock extends LitElement {
<div class="status"> <div class="status">
<span></span> <span></span>
<div class="icon"> <div class="icon">
<ha-svg-icon <ha-state-icon
.path=${domainIcon("lock", this.stateObj)} .hass=${this.hass}
></ha-svg-icon> .stateObj=${this.stateObj}
></ha-state-icon>
</div> </div>
</div> </div>
` `

View File

@ -1,5 +1,7 @@
import "@material/mwc-ripple"; import "@material/mwc-ripple";
import { import {
mdiFan,
mdiFanOff,
mdiLightbulbMultiple, mdiLightbulbMultiple,
mdiLightbulbMultipleOff, mdiLightbulbMultipleOff,
mdiRun, mdiRun,
@ -9,30 +11,30 @@ import {
} from "@mdi/js"; } from "@mdi/js";
import type { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket"; import type { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
import { import {
css,
CSSResultGroup, CSSResultGroup,
html,
LitElement, LitElement,
PropertyValues, PropertyValues,
TemplateResult, TemplateResult,
css,
html,
nothing, nothing,
} from "lit"; } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map"; import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { STATES_OFF, FIXED_DEVICE_CLASS_ICONS } from "../../../common/const"; import { FIXED_DEVICE_CLASS_ICONS, STATES_OFF } from "../../../common/const";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { computeDomain } from "../../../common/entity/compute_domain";
import { binarySensorIcon } from "../../../common/entity/binary_sensor_icon"; import { binarySensorIcon } from "../../../common/entity/binary_sensor_icon";
import { domainIcon } from "../../../common/entity/domain_icon"; import { computeDomain } from "../../../common/entity/compute_domain";
import { navigate } from "../../../common/navigate"; import { navigate } from "../../../common/navigate";
import { import {
formatNumber, formatNumber,
isNumericState, isNumericState,
} from "../../../common/number/format_number"; } from "../../../common/number/format_number";
import { subscribeOne } from "../../../common/util/subscribe-one"; import { blankBeforeUnit } from "../../../common/translations/blank_before_unit";
import parseAspectRatio from "../../../common/util/parse-aspect-ratio"; import parseAspectRatio from "../../../common/util/parse-aspect-ratio";
import { subscribeOne } from "../../../common/util/subscribe-one";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-icon-button"; import "../../../components/ha-icon-button";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
@ -56,7 +58,6 @@ import "../components/hui-image";
import "../components/hui-warning"; import "../components/hui-warning";
import { LovelaceCard, LovelaceCardEditor } from "../types"; import { LovelaceCard, LovelaceCardEditor } from "../types";
import { AreaCardConfig } from "./types"; import { AreaCardConfig } from "./types";
import { blankBeforeUnit } from "../../../common/translations/blank_before_unit";
export const DEFAULT_ASPECT_RATIO = "16:9"; export const DEFAULT_ASPECT_RATIO = "16:9";
@ -76,7 +77,7 @@ export const DEVICE_CLASSES = {
const DOMAIN_ICONS = { const DOMAIN_ICONS = {
light: { on: mdiLightbulbMultiple, off: mdiLightbulbMultipleOff }, light: { on: mdiLightbulbMultiple, off: mdiLightbulbMultipleOff },
switch: { on: mdiToggleSwitch, off: mdiToggleSwitchOff }, switch: { on: mdiToggleSwitch, off: mdiToggleSwitchOff },
fan: { on: domainIcon("fan"), off: domainIcon("fan") }, fan: { on: mdiFan, off: mdiFanOff },
binary_sensor: { binary_sensor: {
motion: mdiRun, motion: mdiRun,
moisture: mdiWaterAlert, moisture: mdiWaterAlert,

View File

@ -28,8 +28,9 @@ import { DOMAINS_TOGGLE } from "../../../common/const";
import { computeDomain } from "../../../common/entity/compute_domain"; import { computeDomain } from "../../../common/entity/compute_domain";
import { stateActive } from "../../../common/entity/state_active"; import { stateActive } from "../../../common/entity/state_active";
import { stateColorCss } from "../../../common/entity/state_color"; import { stateColorCss } from "../../../common/entity/state_color";
import { stateIconPath } from "../../../common/entity/state_icon_path";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-state-icon";
import "../../../components/ha-svg-icon";
import "../../../components/tile/ha-tile-badge"; import "../../../components/tile/ha-tile-badge";
import "../../../components/tile/ha-tile-icon"; import "../../../components/tile/ha-tile-icon";
import "../../../components/tile/ha-tile-image"; import "../../../components/tile/ha-tile-image";
@ -48,7 +49,7 @@ import { handleAction } from "../common/handle-action";
import { hasAction } from "../common/has-action"; import { hasAction } from "../common/has-action";
import "../components/hui-timestamp-display"; import "../components/hui-timestamp-display";
import type { LovelaceCard, LovelaceCardEditor } from "../types"; import type { LovelaceCard, LovelaceCardEditor } from "../types";
import { computeTileBadge } from "./tile/badges/tile-badge"; import { renderTileBadge } from "./tile/badges/tile-badge";
import type { ThermostatCardConfig, TileCardConfig } from "./types"; import type { ThermostatCardConfig, TileCardConfig } from "./types";
const TIMESTAMP_STATE_DOMAINS = ["button", "input_button", "scene"]; const TIMESTAMP_STATE_DOMAINS = ["button", "input_button", "scene"];
@ -341,17 +342,14 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
<ha-card> <ha-card>
<div class="content ${classMap(contentClasses)}"> <div class="content ${classMap(contentClasses)}">
<div class="icon-container"> <div class="icon-container">
<ha-tile-icon class="icon" .iconPath=${mdiHelp}></ha-tile-icon> <ha-tile-icon>
<ha-tile-badge <ha-svg-icon .path=${mdiHelp}></ha-svg-icon>
class="badge" </ha-tile-icon>
.iconPath=${mdiExclamationThick} <ha-tile-badge class="not-found">
style=${styleMap({ <ha-svg-icon .path=${mdiExclamationThick}></ha-svg-icon>
"--tile-badge-background-color": `var(--red-color)`, </ha-tile-badge>
})}
></ha-tile-badge>
</div> </div>
<ha-tile-info <ha-tile-info
class="info"
.primary=${entityId} .primary=${entityId}
secondary=${this.hass.localize("ui.card.tile.not_found")} secondary=${this.hass.localize("ui.card.tile.not_found")}
></ha-tile-info> ></ha-tile-info>
@ -360,9 +358,6 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
`; `;
} }
const icon = this._config.icon || stateObj.attributes.icon;
const iconPath = stateIconPath(stateObj);
const name = this._config.name || stateObj.attributes.friendly_name; const name = this._config.name || stateObj.attributes.friendly_name;
const localizedState = this._config.hide_state const localizedState = this._config.hide_state
@ -382,7 +377,6 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
const imageUrl = this._config.show_entity_picture const imageUrl = this._config.show_entity_picture
? this._getImageUrl(stateObj) ? this._getImageUrl(stateObj)
: undefined; : undefined;
const badge = computeTileBadge(stateObj, this.hass);
return html` return html`
<ha-card style=${styleMap(style)} class=${classMap({ active })}> <ha-card style=${styleMap(style)} class=${classMap({ active })}>
@ -420,7 +414,6 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
? html` ? html`
<ha-tile-image <ha-tile-image
.imageStyle=${DOMAIN_IMAGE_STYLE[domain] || "circle"} .imageStyle=${DOMAIN_IMAGE_STYLE[domain] || "circle"}
class="icon"
.imageUrl=${imageUrl} .imageUrl=${imageUrl}
></ha-tile-image> ></ha-tile-image>
` `
@ -428,27 +421,18 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
<ha-tile-icon <ha-tile-icon
data-domain=${ifDefined(domain)} data-domain=${ifDefined(domain)}
data-state=${ifDefined(stateObj?.state)} data-state=${ifDefined(stateObj?.state)}
class="icon" >
.icon=${icon} <ha-state-icon
.iconPath=${iconPath} .icon=${this._config.icon}
></ha-tile-icon> .stateObj=${stateObj}
.hass=${this.hass}
></ha-state-icon>
</ha-tile-icon>
`} `}
${badge ${renderTileBadge(stateObj, this.hass)}
? html`
<ha-tile-badge
class="badge"
.icon=${badge.icon}
.iconPath=${badge.iconPath}
style=${styleMap({
"--tile-badge-background-color": badge.color,
})}
></ha-tile-badge>
`
: nothing}
</div> </div>
<ha-tile-info <ha-tile-info
id="info" id="info"
class="info"
.primary=${name} .primary=${name}
.secondary=${localizedState} .secondary=${localizedState}
></ha-tile-info> ></ha-tile-info>
@ -516,7 +500,7 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
margin-inline-start: initial; margin-inline-start: initial;
margin-inline-end: initial; margin-inline-end: initial;
} }
.vertical .info { .vertical ha-tile-info {
width: 100%; width: 100%;
} }
.icon-container { .icon-container {
@ -528,14 +512,15 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
direction: var(--direction); direction: var(--direction);
transition: transform 180ms ease-in-out; transition: transform 180ms ease-in-out;
} }
.icon-container .icon { .icon-container ha-tile-icon,
.icon-container ha-tile-image {
--tile-icon-color: var(--tile-color); --tile-icon-color: var(--tile-color);
user-select: none; user-select: none;
-ms-user-select: none; -ms-user-select: none;
-webkit-user-select: none; -webkit-user-select: none;
-moz-user-select: none; -moz-user-select: none;
} }
.icon-container .badge { .icon-container ha-tile-badge {
position: absolute; position: absolute;
top: -3px; top: -3px;
right: -3px; right: -3px;
@ -544,7 +529,7 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
.icon-container[role="button"]:active { .icon-container[role="button"]:active {
transform: scale(1.2); transform: scale(1.2);
} }
.info { ha-tile-info {
position: relative; position: relative;
padding: 12px; padding: 12px;
flex: 1; flex: 1;
@ -564,6 +549,10 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
animation: pulse 1s infinite; animation: pulse 1s infinite;
} }
ha-tile-badge.not-found {
--tile-badge-background-color: var(--red-color);
}
@keyframes pulse { @keyframes pulse {
0% { 0% {
opacity: 1; opacity: 1;

View File

@ -1,20 +1,36 @@
import { html, nothing } from "lit";
import { styleMap } from "lit/directives/style-map";
import { stateColorCss } from "../../../../../common/entity/state_color"; import { stateColorCss } from "../../../../../common/entity/state_color";
import "../../../../../components/ha-attribute-icon";
import "../../../../../components/tile/ha-tile-badge";
import { import {
CLIMATE_HVAC_ACTION_ICONS,
CLIMATE_HVAC_ACTION_TO_MODE, CLIMATE_HVAC_ACTION_TO_MODE,
ClimateEntity, ClimateEntity,
} from "../../../../../data/climate"; } from "../../../../../data/climate";
import { ComputeBadgeFunction } from "./tile-badge"; import { RenderBadgeFunction } from "./tile-badge";
export const computeClimateBadge: ComputeBadgeFunction = (stateObj) => { export const renderClimateBadge: RenderBadgeFunction = (stateObj, hass) => {
const hvacAction = (stateObj as ClimateEntity).attributes.hvac_action; const hvacAction = (stateObj as ClimateEntity).attributes.hvac_action;
if (!hvacAction || hvacAction === "off") { if (!hvacAction || hvacAction === "off") {
return undefined; return nothing;
} }
return { return html`
iconPath: CLIMATE_HVAC_ACTION_ICONS[hvacAction], <ha-tile-badge
color: stateColorCss(stateObj, CLIMATE_HVAC_ACTION_TO_MODE[hvacAction]), style=${styleMap({
}; "--tile-badge-background-color": stateColorCss(
stateObj,
CLIMATE_HVAC_ACTION_TO_MODE[hvacAction]
),
})}
>
<ha-attribute-icon
.hass=${hass}
.stateObj=${stateObj}
attribute="hvac_action"
>
</ha-attribute-icon>
</ha-tile-badge>
`;
}; };

View File

@ -1,20 +1,32 @@
import { html, nothing } from "lit";
import { styleMap } from "lit/directives/style-map";
import { stateColorCss } from "../../../../../common/entity/state_color"; import { stateColorCss } from "../../../../../common/entity/state_color";
import "../../../../../components/ha-attribute-icon";
import "../../../../../components/tile/ha-tile-badge";
import { import {
HUMIDIFIER_ACTION_ICONS,
HUMIDIFIER_ACTION_MODE, HUMIDIFIER_ACTION_MODE,
HumidifierEntity, HumidifierEntity,
} from "../../../../../data/humidifier"; } from "../../../../../data/humidifier";
import { ComputeBadgeFunction } from "./tile-badge"; import { RenderBadgeFunction } from "./tile-badge";
export const computeHumidifierBadge: ComputeBadgeFunction = (stateObj) => { export const renderHumidifierBadge: RenderBadgeFunction = (stateObj, hass) => {
const hvacAction = (stateObj as HumidifierEntity).attributes.action; const hvacAction = (stateObj as HumidifierEntity).attributes.action;
if (!hvacAction || hvacAction === "off") { if (!hvacAction || hvacAction === "off") {
return undefined; return nothing;
} }
return { return html`
iconPath: HUMIDIFIER_ACTION_ICONS[hvacAction], <ha-tile-badge
color: stateColorCss(stateObj, HUMIDIFIER_ACTION_MODE[hvacAction]), style=${styleMap({
}; "--tile-badge-background-color": stateColorCss(
stateObj,
HUMIDIFIER_ACTION_MODE[hvacAction]
),
})}
>
<ha-attribute-icon .hass=${hass} .stateObj=${stateObj} attribute="action">
</ha-attribute-icon>
</ha-tile-badge>
`;
}; };

View File

@ -1,8 +1,13 @@
import { mdiHome, mdiHomeExportOutline } from "@mdi/js"; import { mdiHome, mdiHomeExportOutline } from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity } from "home-assistant-js-websocket";
import { html } from "lit";
import { styleMap } from "lit/directives/style-map";
import { stateColorCss } from "../../../../../common/entity/state_color"; import { stateColorCss } from "../../../../../common/entity/state_color";
import "../../../../../components/ha-icon";
import "../../../../../components/ha-svg-icon";
import "../../../../../components/tile/ha-tile-badge";
import { HomeAssistant } from "../../../../../types"; import { HomeAssistant } from "../../../../../types";
import { ComputeBadgeFunction } from "./tile-badge"; import { RenderBadgeFunction } from "./tile-badge";
function getZone(entity: HassEntity, hass: HomeAssistant) { function getZone(entity: HassEntity, hass: HomeAssistant) {
const state = entity.state; const state = entity.state;
@ -15,17 +20,33 @@ function getZone(entity: HassEntity, hass: HomeAssistant) {
return zones.find((z) => state === z.attributes.friendly_name); return zones.find((z) => state === z.attributes.friendly_name);
} }
function personBadgeIcon(entity: HassEntity) { export const renderPersonBadge: RenderBadgeFunction = (stateObj, hass) => {
const state = entity.state;
return state === "not_home" ? mdiHomeExportOutline : mdiHome;
}
export const computePersonBadge: ComputeBadgeFunction = (stateObj, hass) => {
const zone = getZone(stateObj, hass); const zone = getZone(stateObj, hass);
return { const zoneIcon = zone?.attributes.icon;
iconPath: personBadgeIcon(stateObj),
icon: zone?.attributes.icon, if (zoneIcon) {
color: stateColorCss(stateObj), return html`
}; <ha-tile-badge
style=${styleMap({
"--tile-badge-background-color": stateColorCss(stateObj),
})}
>
<ha-icon .icon=${zoneIcon}></ha-icon>
</ha-tile-badge>
`;
}
const defaultIcon =
stateObj.state === "not_home" ? mdiHomeExportOutline : mdiHome;
return html`
<ha-tile-badge
style=${styleMap({
"--tile-badge-background-color": stateColorCss(stateObj),
})}
>
<ha-svg-icon .path=${defaultIcon}></ha-svg-icon>
</ha-tile-badge>
`;
}; };

View File

@ -1,43 +1,46 @@
import { mdiExclamationThick } from "@mdi/js"; import { mdiExclamationThick } from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity } from "home-assistant-js-websocket";
import { TemplateResult, html, nothing } from "lit";
import { styleMap } from "lit/directives/style-map";
import { computeDomain } from "../../../../../common/entity/compute_domain"; import { computeDomain } from "../../../../../common/entity/compute_domain";
import { UNAVAILABLE, UNKNOWN } from "../../../../../data/entity"; import { UNAVAILABLE, UNKNOWN } from "../../../../../data/entity";
import { HomeAssistant } from "../../../../../types"; import { HomeAssistant } from "../../../../../types";
import { computeClimateBadge } from "./tile-badge-climate"; import { renderClimateBadge } from "./tile-badge-climate";
import { computePersonBadge } from "./tile-badge-person"; import { renderHumidifierBadge } from "./tile-badge-humidifier";
import { computeHumidifierBadge } from "./tile-badge-humidifier"; import { renderPersonBadge } from "./tile-badge-person";
import "../../../../../components/tile/ha-tile-badge";
import "../../../../../components/ha-svg-icon";
export type TileBadge = { export type RenderBadgeFunction = (
color?: string;
icon?: string;
iconPath?: string;
};
export type ComputeBadgeFunction = (
stateObj: HassEntity, stateObj: HassEntity,
hass: HomeAssistant hass: HomeAssistant
) => TileBadge | undefined; ) => TemplateResult | typeof nothing;
export const computeTileBadge: ComputeBadgeFunction = (stateObj, hass) => { export const renderTileBadge: RenderBadgeFunction = (stateObj, hass) => {
if (stateObj.state === UNKNOWN) { if (stateObj.state === UNKNOWN) {
return undefined; return nothing;
} }
if (stateObj.state === UNAVAILABLE) { if (stateObj.state === UNAVAILABLE) {
return { return html`
color: "var(--orange-color)", <ha-tile-badge
iconPath: mdiExclamationThick, style=${styleMap({
}; "--tile-badge-background-color": "var(--orange-color)",
})}
>
<ha-svg-icon .path=${mdiExclamationThick}></ha-svg-icon>
</ha-tile-badge>
`;
} }
const domain = computeDomain(stateObj.entity_id); const domain = computeDomain(stateObj.entity_id);
switch (domain) { switch (domain) {
case "person": case "person":
case "device_tracker": case "device_tracker":
return computePersonBadge(stateObj, hass); return renderPersonBadge(stateObj, hass);
case "climate": case "climate":
return computeClimateBadge(stateObj, hass); return renderClimateBadge(stateObj, hass);
case "humidifier": case "humidifier":
return computeHumidifierBadge(stateObj, hass); return renderHumidifierBadge(stateObj, hass);
default: default:
return undefined; return nothing;
} }
}; };

View File

@ -3,10 +3,10 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map"; import { styleMap } from "lit/directives/style-map";
import { domainIcon } from "../../common/entity/domain_icon";
import { stateColorCss } from "../../common/entity/state_color"; import { stateColorCss } from "../../common/entity/state_color";
import "../../components/ha-control-button"; import "../../components/ha-control-button";
import "../../components/ha-control-switch"; import "../../components/ha-control-switch";
import "../../components/ha-state-icon";
import { UNAVAILABLE, UNKNOWN } from "../../data/entity"; import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
import { forwardHaptic } from "../../data/haptics"; import { forwardHaptic } from "../../data/haptics";
import { HomeAssistant } from "../../types"; import { HomeAssistant } from "../../types";
@ -77,9 +77,11 @@ export class HaStateControlCoverToggle extends LitElement {
"--color": onColor, "--color": onColor,
})} })}
> >
<ha-svg-icon <ha-state-icon
.path=${domainIcon("cover", this.stateObj, "open")} .hass=${this.hass}
></ha-svg-icon> .stateObj=${this.stateObj}
stateValue="open"
></ha-state-icon>
</ha-control-button> </ha-control-button>
<ha-control-button <ha-control-button
.label=${this.hass.localize("ui.card.cover.close_cover")} .label=${this.hass.localize("ui.card.cover.close_cover")}
@ -92,9 +94,11 @@ export class HaStateControlCoverToggle extends LitElement {
"--color": offColor, "--color": offColor,
})} })}
> >
<ha-svg-icon <ha-state-icon
.path=${domainIcon("cover", this.stateObj, "closed")} .hass=${this.hass}
></ha-svg-icon> .stateObj=${this.stateObj}
stateValue="closed"
></ha-state-icon>
</ha-control-button> </ha-control-button>
</div> </div>
`; `;
@ -102,8 +106,6 @@ export class HaStateControlCoverToggle extends LitElement {
return html` return html`
<ha-control-switch <ha-control-switch
.pathOn=${domainIcon("cover", this.stateObj, "open")}
.pathOff=${domainIcon("cover", this.stateObj, "closed")}
vertical vertical
reversed reversed
.checked=${isOn} .checked=${isOn}
@ -117,6 +119,18 @@ export class HaStateControlCoverToggle extends LitElement {
})} })}
.disabled=${this.stateObj.state === UNAVAILABLE} .disabled=${this.stateObj.state === UNAVAILABLE}
> >
<ha-state-icon
slot="icon-on"
.hass=${this.hass}
.stateObj=${this.stateObj}
stateValue="open"
></ha-state-icon>
<ha-state-icon
slot="icon-off"
.hass=${this.hass}
.stateObj=${this.stateObj}
stateValue="closed"
></ha-state-icon>
</ha-control-switch> </ha-control-switch>
`; `;
} }

View File

@ -9,10 +9,10 @@ import {
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map"; import { styleMap } from "lit/directives/style-map";
import { domainIcon } from "../../common/entity/domain_icon";
import { stateColorCss } from "../../common/entity/state_color"; import { stateColorCss } from "../../common/entity/state_color";
import "../../components/ha-control-button"; import "../../components/ha-control-button";
import "../../components/ha-control-switch"; import "../../components/ha-control-switch";
import "../../components/ha-state-icon";
import { UNAVAILABLE, UNKNOWN } from "../../data/entity"; import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
import { forwardHaptic } from "../../data/haptics"; import { forwardHaptic } from "../../data/haptics";
import { callProtectedLockService, LockEntity } from "../../data/lock"; import { callProtectedLockService, LockEntity } from "../../data/lock";
@ -81,18 +81,6 @@ export class HaStateControlLockToggle extends LitElement {
const color = stateColorCss(this.stateObj); const color = stateColorCss(this.stateObj);
const onIcon = domainIcon(
"lock",
this.stateObj,
locking ? "locking" : "locked"
);
const offIcon = domainIcon(
"lock",
this.stateObj,
unlocking ? "unlocking" : "unlocked"
);
if (this.stateObj.state === UNKNOWN) { if (this.stateObj.state === UNKNOWN) {
return html` return html`
<div class="buttons"> <div class="buttons">
@ -100,13 +88,21 @@ export class HaStateControlLockToggle extends LitElement {
.label=${this.hass.localize("ui.card.lock.lock")} .label=${this.hass.localize("ui.card.lock.lock")}
@click=${this._turnOn} @click=${this._turnOn}
> >
<ha-svg-icon .path=${onIcon}></ha-svg-icon> <ha-state-icon
.hass=${this.hass}
.stateObj=${this.stateObj}
.stateValue=${locking ? "locking" : "locked"}
></ha-state-icon>
</ha-control-button> </ha-control-button>
<ha-control-button <ha-control-button
.label=${this.hass.localize("ui.card.lock.unlock")} .label=${this.hass.localize("ui.card.lock.unlock")}
@click=${this._turnOff} @click=${this._turnOff}
> >
<ha-svg-icon .path=${offIcon}></ha-svg-icon> <ha-state-icon
.hass=${this.hass}
.stateObj=${this.stateObj}
.stateValue=${unlocking ? "unlocking" : "unlocked"}
></ha-state-icon>
</ha-control-button> </ha-control-button>
</div> </div>
`; `;
@ -127,16 +123,20 @@ export class HaStateControlLockToggle extends LitElement {
})} })}
.disabled=${this.stateObj.state === UNAVAILABLE} .disabled=${this.stateObj.state === UNAVAILABLE}
> >
<ha-svg-icon <ha-state-icon
slot="icon-on" slot="icon-on"
.path=${onIcon} .hass=${this.hass}
.stateObj=${this.stateObj}
.stateValue=${locking ? "locking" : "locked"}
class=${classMap({ pulse: locking })} class=${classMap({ pulse: locking })}
></ha-svg-icon> ></ha-state-icon>
<ha-svg-icon <ha-state-icon
slot="icon-off" slot="icon-off"
.path=${offIcon} .hass=${this.hass}
.stateObj=${this.stateObj}
.stateValue=${unlocking ? "unlocking" : "unlocked"}
class=${classMap({ pulse: unlocking })} class=${classMap({ pulse: unlocking })}
></ha-svg-icon> ></ha-state-icon>
</ha-control-switch> </ha-control-switch>
`; `;
} }

View File

@ -3,10 +3,10 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map"; import { styleMap } from "lit/directives/style-map";
import { domainIcon } from "../../common/entity/domain_icon";
import { stateColorCss } from "../../common/entity/state_color"; import { stateColorCss } from "../../common/entity/state_color";
import "../../components/ha-control-button"; import "../../components/ha-control-button";
import "../../components/ha-control-switch"; import "../../components/ha-control-switch";
import "../../components/ha-state-icon";
import { UNAVAILABLE, UNKNOWN } from "../../data/entity"; import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
import { forwardHaptic } from "../../data/haptics"; import { forwardHaptic } from "../../data/haptics";
import { HomeAssistant } from "../../types"; import { HomeAssistant } from "../../types";
@ -77,9 +77,11 @@ export class HaStateControlValveToggle extends LitElement {
"--color": onColor, "--color": onColor,
})} })}
> >
<ha-svg-icon <ha-state-icon
.path=${domainIcon("valve", this.stateObj, "open")} .hass=${this.hass}
></ha-svg-icon> .stateObj=${this.stateObj}
stateValue="open"
></ha-state-icon>
</ha-control-button> </ha-control-button>
<ha-control-button <ha-control-button
.label=${this.hass.localize("ui.card.valve.close_valve")} .label=${this.hass.localize("ui.card.valve.close_valve")}
@ -92,9 +94,11 @@ export class HaStateControlValveToggle extends LitElement {
"--color": offColor, "--color": offColor,
})} })}
> >
<ha-svg-icon <ha-state-icon
.path=${domainIcon("valve", this.stateObj, "closed")} .hass=${this.hass}
></ha-svg-icon> .stateObj=${this.stateObj}
stateValue="closed"
></ha-state-icon>
</ha-control-button> </ha-control-button>
</div> </div>
`; `;
@ -102,8 +106,6 @@ export class HaStateControlValveToggle extends LitElement {
return html` return html`
<ha-control-switch <ha-control-switch
.pathOn=${domainIcon("valve", this.stateObj, "open")}
.pathOff=${domainIcon("valve", this.stateObj, "closed")}
vertical vertical
reversed reversed
.checked=${isOn} .checked=${isOn}
@ -117,6 +119,18 @@ export class HaStateControlValveToggle extends LitElement {
})} })}
.disabled=${this.stateObj.state === UNAVAILABLE} .disabled=${this.stateObj.state === UNAVAILABLE}
> >
<ha-state-icon
slot="icon-on"
.hass=${this.hass}
.stateObj=${this.stateObj}
stateValue="open"
></ha-state-icon>
<ha-state-icon
slot="icon-off"
.hass=${this.hass}
.stateObj=${this.stateObj}
stateValue="closed"
></ha-state-icon>
</ha-control-switch> </ha-control-switch>
`; `;
} }