diff --git a/src/components/tile/ha-tile-badge.ts b/src/components/tile/ha-tile-badge.ts new file mode 100644 index 0000000000..183f217109 --- /dev/null +++ b/src/components/tile/ha-tile-badge.ts @@ -0,0 +1,51 @@ +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property } from "lit/decorators"; +import "../ha-icon"; + +@customElement("ha-tile-badge") +export class HaTileBadge extends LitElement { + @property() public iconPath?: string; + + @property() public icon?: string; + + protected render(): TemplateResult { + return html` +
+ ${this.icon + ? html`` + : html``} +
+ `; + } + + static get styles(): CSSResultGroup { + return css` + :host { + --tile-badge-background-color: rgb(var(--rgb-primary-color)); + --tile-badge-icon-color: rgb(var(--rgb-white-color)); + --mdc-icon-size: 12px; + } + .badge { + display: flex; + align-items: center; + justify-content: center; + line-height: 0; + width: 16px; + height: 16px; + border-radius: 8px; + background-color: var(--tile-badge-background-color); + transition: background-color 280ms ease-in-out; + } + .badge ha-icon, + .badge ha-svg-icon { + color: var(--tile-badge-icon-color); + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-tile-badge": HaTileBadge; + } +} diff --git a/src/panels/lovelace/cards/hui-tile-card.ts b/src/panels/lovelace/cards/hui-tile-card.ts index 249b6db5fe..c133b86865 100644 --- a/src/panels/lovelace/cards/hui-tile-card.ts +++ b/src/panels/lovelace/cards/hui-tile-card.ts @@ -11,6 +11,7 @@ import { stateActive } from "../../../common/entity/state_active"; import { stateColorCss } from "../../../common/entity/state_color"; import { stateIconPath } from "../../../common/entity/state_icon_path"; import "../../../components/ha-card"; +import "../../../components/tile/ha-tile-badge"; import "../../../components/tile/ha-tile-icon"; import "../../../components/tile/ha-tile-image"; import "../../../components/tile/ha-tile-info"; @@ -21,6 +22,7 @@ import { actionHandler } from "../common/directives/action-handler-directive"; import { findEntities } from "../common/find-entities"; import { handleAction } from "../common/handle-action"; import { LovelaceCard, LovelaceCardEditor } from "../types"; +import { computeTileBadge } from "./tile/badges/tile-badge"; import { ThermostatCardConfig, TileCardConfig } from "./types"; @customElement("hui-tile-card") @@ -116,7 +118,9 @@ export class HuiTileCard extends LitElement implements LovelaceCard { return html`
- +
+ +
- ${imageUrl - ? html` - - ` - : html` - - `} +
+ ${imageUrl + ? html` + + ` + : html` + + `} + ${badge + ? html` + + ` + : null} +
= { + cooling: "var(--rgb-state-climate-cool-color)", + drying: "var(--rgb-state-climate-dry-color)", + heating: "var(--rgb-state-climate-heat-color)", + idle: "var(--rgb-state-climate-idle-color)", + off: "var(--rgb-state-climate-off-color)", +}; + +export const CLIMATE_HVAC_ACTION_ICONS: Record = { + cooling: mdiSnowflake, + drying: mdiWaterPercent, + heating: mdiFire, + idle: mdiClockOutline, + off: mdiPower, +}; + +export const computeClimateBadge: ComputeBadgeFunction = (stateObj) => { + const hvacAction = (stateObj as ClimateEntity).attributes.hvac_action; + + if (!hvacAction || hvacAction === "off") { + return undefined; + } + + return { + iconPath: CLIMATE_HVAC_ACTION_ICONS[hvacAction], + color: CLIMATE_HVAC_ACTION_COLORS[hvacAction], + }; +}; diff --git a/src/panels/lovelace/cards/tile/badges/tile-badge-person.ts b/src/panels/lovelace/cards/tile/badges/tile-badge-person.ts new file mode 100644 index 0000000000..7eaa3ea340 --- /dev/null +++ b/src/panels/lovelace/cards/tile/badges/tile-badge-person.ts @@ -0,0 +1,44 @@ +import { mdiHelp, mdiHome, mdiHomeExportOutline } from "@mdi/js"; +import { HassEntity } from "home-assistant-js-websocket"; +import { UNAVAILABLE_STATES } from "../../../../../data/entity"; +import { HomeAssistant } from "../../../../../types"; +import { ComputeBadgeFunction } from "./tile-badge"; + +function getZone(entity: HassEntity, hass: HomeAssistant) { + const state = entity.state; + if (state === "home" || state === "not_home") return undefined; + + const zones = Object.values(hass.states).filter((stateObj) => + stateObj.entity_id.startsWith("zone.") + ); + + return zones.find((z) => state === z.attributes.friendly_name); +} + +function personBadgeIcon(entity: HassEntity) { + const state = entity.state; + if (UNAVAILABLE_STATES.includes(state)) { + return mdiHelp; + } + return state === "not_home" ? mdiHomeExportOutline : mdiHome; +} + +function personBadgeColor(entity: HassEntity, inZone?: boolean) { + if (inZone) { + return "var(--rgb-state-person-zone-color)"; + } + const state = entity.state; + return state === "not_home" + ? "var(--rgb-state-person-not-home-color)" + : "var(--rgb-state-person-home-color)"; +} + +export const computePersonBadge: ComputeBadgeFunction = (stateObj, hass) => { + const zone = getZone(stateObj, hass); + + return { + iconPath: personBadgeIcon(stateObj), + icon: zone?.attributes.icon, + color: personBadgeColor(stateObj, Boolean(zone)), + }; +}; diff --git a/src/panels/lovelace/cards/tile/badges/tile-badge.ts b/src/panels/lovelace/cards/tile/badges/tile-badge.ts new file mode 100644 index 0000000000..eebda6b5e4 --- /dev/null +++ b/src/panels/lovelace/cards/tile/badges/tile-badge.ts @@ -0,0 +1,29 @@ +import { HassEntity } from "home-assistant-js-websocket"; +import { computeDomain } from "../../../../../common/entity/compute_domain"; +import { HomeAssistant } from "../../../../../types"; +import { computeClimateBadge } from "./tile-badge-climate"; +import { computePersonBadge } from "./tile-badge-person"; + +export type TileBadge = { + color: string; + icon?: string; + iconPath?: string; +}; + +export type ComputeBadgeFunction = ( + stateObj: HassEntity, + hass: HomeAssistant +) => TileBadge | undefined; + +export const computeTileBadge: ComputeBadgeFunction = (stateObj, hass) => { + const domain = computeDomain(stateObj.entity_id); + switch (domain) { + case "person": + case "device_tracker": + return computePersonBadge(stateObj, hass); + case "climate": + return computeClimateBadge(stateObj, hass); + default: + return undefined; + } +}; diff --git a/src/resources/ha-style.ts b/src/resources/ha-style.ts index 96c99e47b3..49e3e8a8f6 100644 --- a/src/resources/ha-style.ts +++ b/src/resources/ha-style.ts @@ -152,6 +152,9 @@ documentContainer.innerHTML = ` --rgb-state-lock-unlocked-color: var(--rgb-red-color); --rgb-state-media-player-color: var(--rgb-indigo-color); --rgb-state-person-color: var(--rgb-blue-grey-color); + --rgb-state-person-home-color: var(--rgb-green-color); + --rgb-state-person-not-home-color: var(--rgb-red-color); + --rgb-state-person-zone-color: var(--rgb-blue-color); --rgb-state-sensor-battery-high-color: var(--rgb-green-color); --rgb-state-sensor-battery-low-color: var(--rgb-red-color); --rgb-state-sensor-battery-medium-color: var(--rgb-orange-color); @@ -173,6 +176,7 @@ documentContainer.innerHTML = ` --rgb-state-climate-fan-only-color: var(--rgb-teal-color); --rgb-state-climate-heat-color: var(--rgb-deep-orange-color); --rgb-state-climate-heat-cool-color: var(--rgb-green-color); + --rgb-state-climate-idle-color: var(--rgb-disabled-color); /* input components */ --input-idle-line-color: rgba(0, 0, 0, 0.42);