Add badges for tile card (#14231)

This commit is contained in:
Paul Bottein 2022-10-31 15:42:44 +01:00 committed by GitHub
parent ebc0edac10
commit 9778c0731c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 223 additions and 36 deletions

View File

@ -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`
<div class="badge">
${this.icon
? html`<ha-icon .icon=${this.icon}></ha-icon>`
: html`<ha-svg-icon .path=${this.iconPath}></ha-svg-icon>`}
</div>
`;
}
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;
}
}

View File

@ -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`
<ha-card class="disabled">
<div class="tile">
<ha-tile-icon .iconPath=${mdiHelp}></ha-tile-icon>
<div class="icon-container">
<ha-tile-icon .iconPath=${mdiHelp}></ha-tile-icon>
</div>
<ha-tile-info
.primary=${entityId}
secondary=${this.hass.localize("ui.card.tile.not_found")}
@ -147,32 +151,45 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
const imageUrl = this._config.show_entity_picture
? this._getImageUrl(entity)
: undefined;
const badge = computeTileBadge(entity, this.hass);
return html`
<ha-card style=${styleMap(style)}>
<div class="tile">
${imageUrl
? html`
<ha-tile-image
class="icon"
.imageUrl=${imageUrl}
role="button"
tabindex="0"
@action=${this._handleIconAction}
.actionHandler=${actionHandler()}
></ha-tile-image>
`
: html`
<ha-tile-icon
class="icon"
.icon=${icon}
.iconPath=${iconPath}
role="button"
tabindex="0"
@action=${this._handleIconAction}
.actionHandler=${actionHandler()}
></ha-tile-icon>
`}
<div
class="icon-container"
role="button"
tabindex="0"
@action=${this._handleIconAction}
.actionHandler=${actionHandler()}
>
${imageUrl
? html`
<ha-tile-image
class="icon"
.imageUrl=${imageUrl}
></ha-tile-image>
`
: html`
<ha-tile-icon
class="icon"
.icon=${icon}
.iconPath=${iconPath}
></ha-tile-icon>
`}
${badge
? html`
<ha-tile-badge
class="badge"
.icon=${badge.icon}
.iconPath=${badge.iconPath}
style=${styleMap({
"--tile-badge-background-color": `rgb(${badge.color})`,
})}
></ha-tile-badge>
`
: null}
</div>
<ha-tile-info
.primary=${name}
.secondary=${stateDisplay}
@ -198,32 +215,39 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
ha-card.disabled {
background: rgba(var(--rgb-disabled-color), 0.1);
}
[role="button"] {
cursor: pointer;
}
[role="button"]:focus {
outline: none;
}
.tile {
padding: calc(12px - var(--tile-tap-padding));
display: flex;
flex-direction: row;
align-items: center;
}
.icon {
.icon-container {
position: relative;
padding: var(--tile-tap-padding);
flex: none;
margin-right: calc(12px - 2 * var(--tile-tap-padding));
margin-inline-end: calc(12px - 2 * var(--tile-tap-padding));
margin-inline-start: initial;
direction: var(--direction);
--color: var(--tile-color);
transition: transform 180ms ease-in-out;
}
[role="button"] {
cursor: pointer;
.icon-container .icon {
--icon-color: rgb(var(--tile-color));
--shape-color: rgba(var(--tile-color), 0.2);
}
.icon[role="button"]:focus {
outline: none;
.icon-container .badge {
position: absolute;
top: calc(-3px + var(--tile-tap-padding));
right: calc(-3px + var(--tile-tap-padding));
}
.icon[role="button"]:focus-visible {
transform: scale(1.2);
}
.icon[role="button"]:active {
.icon-container[role="button"]:focus-visible,
.icon-container[role="button"]:active {
transform: scale(1.2);
}
ha-tile-info {
@ -234,9 +258,6 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
border-radius: calc(var(--ha-card-border-radius, 10px) - 2px);
transition: background-color 180ms ease-in-out;
}
ha-tile-info:focus {
outline: none;
}
ha-tile-info:focus-visible {
background-color: rgba(var(--tile-color), 0.1);
}

View File

@ -0,0 +1,38 @@
import {
mdiClockOutline,
mdiFire,
mdiPower,
mdiSnowflake,
mdiWaterPercent,
} from "@mdi/js";
import { ClimateEntity, HvacAction } from "../../../../../data/climate";
import { ComputeBadgeFunction } from "./tile-badge";
export const CLIMATE_HVAC_ACTION_COLORS: Record<HvacAction, string> = {
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<HvacAction, string> = {
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],
};
};

View File

@ -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)),
};
};

View File

@ -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;
}
};

View File

@ -152,6 +152,9 @@ documentContainer.innerHTML = `<custom-style>
--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 = `<custom-style>
--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);