From a4adc33eb22ca873d6027f8ca5f5384f711060ee Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Tue, 23 Jul 2024 11:07:36 +0200 Subject: [PATCH] Add template badge (#1475) * Add template badge * Fix css * Fix text only and icon only badge * Add support for weather icon and add documentation * Improve doc with image * Improve doc * Improve doc --- docs/badges/template.md | 47 +++ docs/cards/template.md | 61 +-- docs/images/template-badge-dark.png | Bin 0 -> 8010 bytes docs/images/template-badge-light.png | Bin 0 -> 8020 bytes src/badges/template/const.ts | 4 + src/badges/template/template-badge-config.ts | 32 ++ src/badges/template/template-badge-editor.ts | 99 +++++ src/badges/template/template-badge.ts | 386 +++++++++++++++++++ src/cards/template-card/template-card.ts | 4 +- src/ha/common/color/compute-color.ts | 35 ++ src/ha/panels/lovelace/types.ts | 15 +- src/mushroom.ts | 2 + src/shared/config/lovelace-badge-config.ts | 6 + src/translations/en.json | 2 + src/utils/custom-badges.ts | 20 + src/utils/form/generic-fields.ts | 1 + 16 files changed, 681 insertions(+), 33 deletions(-) create mode 100644 docs/badges/template.md create mode 100644 docs/images/template-badge-dark.png create mode 100644 docs/images/template-badge-light.png create mode 100644 src/badges/template/const.ts create mode 100644 src/badges/template/template-badge-config.ts create mode 100644 src/badges/template/template-badge-editor.ts create mode 100644 src/badges/template/template-badge.ts create mode 100644 src/ha/common/color/compute-color.ts create mode 100644 src/shared/config/lovelace-badge-config.ts create mode 100644 src/utils/custom-badges.ts diff --git a/docs/badges/template.md b/docs/badges/template.md new file mode 100644 index 000000000..6a39ce8c0 --- /dev/null +++ b/docs/badges/template.md @@ -0,0 +1,47 @@ +# Template badge + +![Template light](../images/template-badge-light.png) +![Template dark](../images/template-badge-dark.png) + +## Description + +A template badge allows you to build a custom badge. You can use `entity` as a variable for the entity set on the badge e.g. `{{ states(entity) }}`. + +> [!WARNING] +> Home Assistant **2024.8** is required to use custom badges. + +## Configuration variables + +All the options are available in the lovelace editor but you can use `yaml` if you want. + +| Name | Type | Default | Description | +| :------------------ | :-------------- | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------- | +| `entity` | string | Optional | Entity for template and actions | +| `icon` | string | Optional | Icon to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/) \*. | +| `color` | string | Optional | Color to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | +| `label` | string | Optional | Label to render. Only displayed if content is not empty. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | +| `content` | string | Optional | Content to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | +| `picture` | string | Optional | Picture to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | +| `tap_action` | action | `none` | Home assistant action to perform on tap | +| `hold_action` | action | `none` | Home assistant action to perform on hold | +| `double_tap_action` | action | `none` | Home assistant action to perform on double_tap | +| `entity_id` | `string` `list` | Optional | Only reacts to the state changes of these entities. This can be used if the automatic analysis fails to find all relevant entities. | + +#### Notes + +\* You can render weather svg icons using [weather state](https://developers.home-assistant.io/docs/core/entity/weather/#recommended-values-for-state-and-condition) as icon : + +- weather-clear-night +- weather-cloudy +- weather-fog +- weather-lightning +- weather-lightning-rainy +- weather-partlycloudy +- weather-pouring +- weather-rainy +- weather-hail +- weather-snowy +- weather-snowy-rainy +- weather-sunny +- weather-windy +- weather-windy-variant diff --git a/docs/cards/template.md b/docs/cards/template.md index aa4958f25..0b8d81017 100644 --- a/docs/cards/template.md +++ b/docs/cards/template.md @@ -11,38 +11,39 @@ A template card allows you to build a custom card. You can use `entity` as a var All the options are available in the lovelace editor but you can use `yaml` if you want. -| Name | Type | Default | Description | -| :-------------------- | :-------------- | :---------- | :---------------------------------------------------------------------------------------------------------------------------------- | -| `icon` | string | Optional | Icon to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/) \*. | -| `icon_color` | string | Optional | Icon color to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | -| `primary` | string | Optional | Primary info to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | -| `secondary` | string | Optional | Secondary info to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | -| `badge_icon` | string | Optional | Badge icon to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | -| `badge_color` | string | Optional | Badge icon color to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | -| `picture` | string | Optional | Picture to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | -| `multiline_secondary` | boolean | `false` | Enables support for multiline text for the secondary info. | -| `layout` | string | Optional | Layout of the card. Vertical, horizontal and default layout are supported | -| `fill_container` | boolean | `false` | Fill container or not. Useful when card is in a grid, vertical or horizontal layout | -| `tap_action` | action | `none` | Home assistant action to perform on tap | -| `hold_action` | action | `none` | Home assistant action to perform on hold | -| `entity_id` | `string` `list` | Optional | Only reacts to the state changes of these entities. This can be used if the automatic analysis fails to find all relevant entities. | -| `double_tap_action` | action | `more-info` | Home assistant action to perform on double_tap | +| Name | Type | Default | Description | +| :-------------------- | :-------------- | :------- | :---------------------------------------------------------------------------------------------------------------------------------- | +| `entity` | string | Optional | Entity for template and actions | +| `icon` | string | Optional | Icon to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/) \*. | +| `icon_color` | string | Optional | Icon color to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | +| `primary` | string | Optional | Primary info to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | +| `secondary` | string | Optional | Secondary info to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | +| `badge_icon` | string | Optional | Badge icon to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | +| `badge_color` | string | Optional | Badge icon color to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | +| `picture` | string | Optional | Picture to render. May contain [templates](https://www.home-assistant.io/docs/configuration/templating/). | +| `multiline_secondary` | boolean | `false` | Enables support for multiline text for the secondary info. | +| `layout` | string | Optional | Layout of the card. Vertical, horizontal and default layout are supported | +| `fill_container` | boolean | `false` | Fill container or not. Useful when card is in a grid, vertical or horizontal layout | +| `tap_action` | action | `none` | Home assistant action to perform on tap | +| `hold_action` | action | `none` | Home assistant action to perform on hold | +| `double_tap_action` | action | `none` | Home assistant action to perform on double_tap | +| `entity_id` | `string` `list` | Optional | Only reacts to the state changes of these entities. This can be used if the automatic analysis fails to find all relevant entities. | #### Notes \* You can render weather svg icons using [weather state](https://developers.home-assistant.io/docs/core/entity/weather/#recommended-values-for-state-and-condition) as icon : -- weather-clear-night -- weather-cloudy -- weather-fog -- weather-lightning -- weather-lightning-rainy -- weather-partlycloudy -- weather-pouring -- weather-rainy -- weather-hail -- weather-snowy -- weather-snowy-rainy -- weather-sunny -- weather-windy -- weather-windy-variant +- weather-clear-night +- weather-cloudy +- weather-fog +- weather-lightning +- weather-lightning-rainy +- weather-partlycloudy +- weather-pouring +- weather-rainy +- weather-hail +- weather-snowy +- weather-snowy-rainy +- weather-sunny +- weather-windy +- weather-windy-variant diff --git a/docs/images/template-badge-dark.png b/docs/images/template-badge-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..8fbff658a280c314c3dab505a3284f21e657516a GIT binary patch literal 8010 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 literal 0 HcmV?d00001 diff --git a/docs/images/template-badge-light.png b/docs/images/template-badge-light.png new file mode 100644 index 0000000000000000000000000000000000000000..e09492813344cbc3f8747dfe340dbce01460fa96 GIT binary patch literal 8020 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 { + const customLocalize = setupCustomlocalize(this.hass!); + + if (schema.name === "entity") { + return `${this.hass!.localize( + "ui.panel.lovelace.editor.card.generic.entity" + )} (${customLocalize("editor.card.template.entity_extra")})`; + } + if (GENERIC_LABELS.includes(schema.name)) { + return customLocalize(`editor.card.generic.${schema.name}`); + } + if (TEMPLATE_LABELS.includes(schema.name)) { + return customLocalize(`editor.card.template.${schema.name}`); + } + return this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.${schema.name}` + ); + }; + + protected render() { + if (!this.hass || !this._config) { + return nothing; + } + + return html` + + `; + } + + private _valueChanged(ev: CustomEvent): void { + fireEvent(this, "config-changed", { config: ev.detail.value }); + } +} diff --git a/src/badges/template/template-badge.ts b/src/badges/template/template-badge.ts new file mode 100644 index 000000000..c3eaa4721 --- /dev/null +++ b/src/badges/template/template-badge.ts @@ -0,0 +1,386 @@ +import { UnsubscribeFunc } from "home-assistant-js-websocket"; +import { + css, + CSSResultGroup, + html, + LitElement, + nothing, + PropertyValues, +} from "lit"; +import { customElement, property, state } from "lit/decorators.js"; +import { classMap } from "lit/directives/class-map.js"; +import { ifDefined } from "lit/directives/if-defined.js"; +import { styleMap } from "lit/directives/style-map.js"; +import { + actionHandler, + ActionHandlerEvent, + handleAction, + hasAction, + HomeAssistant, + LovelaceBadge, + LovelaceBadgeEditor, + RenderTemplateResult, + subscribeRenderTemplate, +} from "../../ha"; +import { computeCssColor } from "../../ha/common/color/compute-color"; +import { registerCustomBadge } from "../../utils/custom-badges"; +import { TEMPLATE_BADGE_EDITOR_NAME, TEMPLATE_BADGE_NAME } from "./const"; +import { TemplateBadgeConfig } from "./template-badge-config"; +import { getWeatherSvgIcon } from "../../utils/icons/weather-icon"; +import { weatherSVGStyles } from "../../utils/weather"; + +registerCustomBadge({ + type: TEMPLATE_BADGE_NAME, + name: "Mushroom Template", + description: "Build your own badge using templates", +}); + +const TEMPLATE_KEYS = ["icon", "color", "label", "content", "picture"] as const; +type TemplateKey = (typeof TEMPLATE_KEYS)[number]; + +@customElement(TEMPLATE_BADGE_NAME) +export class HuiEntityBadge extends LitElement implements LovelaceBadge { + public static async getConfigElement(): Promise { + await import("./template-badge-editor"); + return document.createElement( + TEMPLATE_BADGE_EDITOR_NAME + ) as LovelaceBadgeEditor; + } + + public static async getStubConfig( + _hass: HomeAssistant + ): Promise { + return { + type: `custom:${TEMPLATE_BADGE_NAME}`, + content: "Hello", + icon: "mdi:mushroom", + color: "red", + }; + } + + @property({ attribute: false }) public hass?: HomeAssistant; + + @state() protected _config?: TemplateBadgeConfig; + + @state() private _templateResults: Partial< + Record + > = {}; + + @state() private _unsubRenderTemplates: Map< + TemplateKey, + Promise + > = new Map(); + + public connectedCallback() { + super.connectedCallback(); + this._tryConnect(); + } + + public disconnectedCallback() { + this._tryDisconnect(); + } + + protected updated(changedProps: PropertyValues): void { + super.updated(changedProps); + if (!this._config || !this.hass) { + return; + } + + this._tryConnect(); + } + + private async _tryConnect(): Promise { + TEMPLATE_KEYS.forEach((key) => { + this._tryConnectKey(key); + }); + } + + private async _tryConnectKey(key: TemplateKey): Promise { + if ( + this._unsubRenderTemplates.get(key) !== undefined || + !this.hass || + !this._config || + !this.isTemplate(key) + ) { + return; + } + + try { + const sub = subscribeRenderTemplate( + this.hass.connection, + (result) => { + this._templateResults = { + ...this._templateResults, + [key]: result, + }; + }, + { + template: this._config[key] ?? "", + entity_ids: this._config.entity_id, + variables: { + config: this._config, + user: this.hass.user!.name, + entity: this._config.entity, + }, + strict: true, + } + ); + this._unsubRenderTemplates.set(key, sub); + await sub; + } catch (_err) { + const result = { + result: this._config[key] ?? "", + listeners: { + all: false, + domains: [], + entities: [], + time: false, + }, + }; + this._templateResults = { + ...this._templateResults, + [key]: result, + }; + this._unsubRenderTemplates.delete(key); + } + } + private async _tryDisconnect(): Promise { + TEMPLATE_KEYS.forEach((key) => { + this._tryDisconnectKey(key); + }); + } + + private async _tryDisconnectKey(key: TemplateKey): Promise { + const unsubRenderTemplate = this._unsubRenderTemplates.get(key); + if (!unsubRenderTemplate) { + return; + } + + try { + const unsub = await unsubRenderTemplate; + unsub(); + this._unsubRenderTemplates.delete(key); + } catch (err: any) { + if (err.code === "not_found" || err.code === "template_error") { + // If we get here, the connection was probably already closed. Ignore. + } else { + throw err; + } + } + } + + setConfig(config: TemplateBadgeConfig): void { + TEMPLATE_KEYS.forEach((key) => { + if ( + this._config?.[key] !== config[key] || + this._config?.entity != config.entity + ) { + this._tryDisconnectKey(key); + } + }); + this._config = { + tap_action: { + action: "none", + }, + ...config, + }; + } + + get hasAction() { + return ( + !this._config?.tap_action || + hasAction(this._config?.tap_action) || + hasAction(this._config?.hold_action) || + hasAction(this._config?.double_tap_action) + ); + } + + protected render() { + if (!this._config || !this.hass) { + return nothing; + } + + const icon = this.getValue("icon"); + const color = this.getValue("color"); + const content = this.getValue("content"); + const label = this.getValue("label"); + const picture = this.getValue("picture"); + + const hasContent = !!content; + const hasIcon = !!icon || !!picture; + + const style = {}; + if (color) { + style["--badge-color"] = computeCssColor(color); + } + + const weatherSvg = getWeatherSvgIcon(icon); + + return html` +
+ + ${picture + ? html`` + : weatherSvg + ? weatherSvg + : icon + ? html` + + ` + : nothing} + ${content + ? html` + + ${label ? html`${label}` : nothing} + ${content} + + ` + : nothing} +
+ `; + } + + private _handleAction(ev: ActionHandlerEvent) { + handleAction(this, this.hass!, this._config!, ev.detail.action!); + } + + public isTemplate(key: TemplateKey) { + const value = this._config?.[key]; + return value?.includes("{"); + } + + private getValue(key: TemplateKey) { + return this.isTemplate(key) + ? this._templateResults[key]?.result?.toString() + : this._config?.[key]; + } + + static get styles(): CSSResultGroup { + return css` + :host { + -webkit-tap-highlight-color: transparent; + } + .badge { + position: relative; + --ha-ripple-color: var(--badge-color); + --ha-ripple-hover-opacity: 0.04; + --ha-ripple-pressed-opacity: 0.12; + transition: + box-shadow 180ms ease-in-out, + border-color 180ms ease-in-out; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + gap: 8px; + height: 36px; + min-width: 36px; + padding: 0px 8px; + box-sizing: border-box; + width: auto; + border-radius: 18px; + background-color: var(--card-background-color, white); + border-width: var(--ha-card-border-width, 1px); + border-style: solid; + border-color: var( + --ha-card-border-color, + var(--divider-color, #e0e0e0) + ); + --mdc-icon-size: 18px; + text-align: center; + font-family: Roboto; + } + .badge:focus-visible { + --shadow-default: var(--ha-card-box-shadow, 0 0 0 0 transparent); + --shadow-focus: 0 0 0 1px var(--badge-color); + border-color: var(--badge-color); + box-shadow: var(--shadow-default), var(--shadow-focus); + } + button, + [role="button"] { + cursor: pointer; + } + button:focus, + [role="button"]:focus { + outline: none; + } + .content { + display: flex; + flex-direction: column; + align-items: flex-start; + padding-right: 4px; + padding-inline-end: 4px; + padding-inline-start: initial; + } + .name { + font-size: 10px; + font-style: normal; + font-weight: 500; + line-height: 10px; + letter-spacing: 0.1px; + color: var(--secondary-text-color); + } + .state { + font-size: 12px; + font-style: normal; + font-weight: 500; + line-height: 16px; + letter-spacing: 0.1px; + color: var(--primary-text-color); + } + svg { + width: var(--mdc-icon-size); + height: var(--mdc-icon-size); + display: flex; + } + ha-state-icon { + color: var(--badge-color); + line-height: 0; + } + img { + width: 30px; + height: 30px; + border-radius: 50%; + object-fit: cover; + overflow: hidden; + } + .badge.icon-only { + padding: 0; + } + .badge:not(.icon-only) img { + margin-left: -6px; + margin-inline-start: -6px; + margin-inline-end: initial; + } + .badge.content-only .content { + padding-right: 4px; + padding-left: 4px; + padding-inline-end: 4px; + padding-inline-start: 4px; + } + ${weatherSVGStyles} + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-entity-badge": HuiEntityBadge; + } +} diff --git a/src/cards/template-card/template-card.ts b/src/cards/template-card/template-card.ts index 721058297..d67841ac7 100644 --- a/src/cards/template-card/template-card.ts +++ b/src/cards/template-card/template-card.ts @@ -38,8 +38,8 @@ import { TemplateCardConfig } from "./template-card-config"; registerCustomCard({ type: TEMPLATE_CARD_NAME, - name: "Mushroom Template Card", - description: "Card for custom rendering with templates", + name: "Mushroom Template", + description: "Build your own mushroom card using templates", }); const TEMPLATE_KEYS = [ diff --git a/src/ha/common/color/compute-color.ts b/src/ha/common/color/compute-color.ts new file mode 100644 index 000000000..bd28631c1 --- /dev/null +++ b/src/ha/common/color/compute-color.ts @@ -0,0 +1,35 @@ +export const THEME_COLORS = new Set([ + "primary", + "accent", + "disabled", + "red", + "pink", + "purple", + "deep-purple", + "indigo", + "blue", + "light-blue", + "cyan", + "teal", + "green", + "light-green", + "lime", + "yellow", + "amber", + "orange", + "deep-orange", + "brown", + "light-grey", + "grey", + "dark-grey", + "blue-grey", + "black", + "white", +]); + +export function computeCssColor(color: string): string { + if (THEME_COLORS.has(color)) { + return `var(--${color}-color)`; + } + return color; +} diff --git a/src/ha/panels/lovelace/types.ts b/src/ha/panels/lovelace/types.ts index 9a1e2eda3..2f7d32c77 100644 --- a/src/ha/panels/lovelace/types.ts +++ b/src/ha/panels/lovelace/types.ts @@ -1,4 +1,8 @@ -import { LovelaceCardConfig, LovelaceConfig } from "../../data/lovelace"; +import { + LovelaceBadgeConfig, + LovelaceCardConfig, + LovelaceConfig, +} from "../../data/lovelace"; import { FrontendLocaleData } from "../../data/translation"; import { Constructor, HomeAssistant } from "../../types"; @@ -24,6 +28,11 @@ export interface Lovelace { deleteConfig: () => Promise; } +export interface LovelaceBadge extends HTMLElement { + hass?: HomeAssistant; + setConfig(config: LovelaceBadgeConfig): void; +} + export interface LovelaceCard extends HTMLElement { hass?: HomeAssistant; isPanel?: boolean; @@ -45,6 +54,10 @@ export interface LovelaceCardEditor extends LovelaceGenericElementEditor { setConfig(config: LovelaceCardConfig): void; } +export interface LovelaceBadgeEditor extends LovelaceGenericElementEditor { + setConfig(config: LovelaceBadgeConfig): void; +} + export interface LovelaceGenericElementEditor extends HTMLElement { hass?: HomeAssistant; lovelace?: LovelaceConfig; diff --git a/src/mushroom.ts b/src/mushroom.ts index 1c4be62b4..a1b7e446b 100644 --- a/src/mushroom.ts +++ b/src/mushroom.ts @@ -23,6 +23,8 @@ import "./cards/title-card/title-card"; import "./cards/update-card/update-card"; import "./cards/vacuum-card/vacuum-card"; +import "./badges/template/template-badge"; + console.info( `%c🍄 Mushroom 🍄 - ${version}`, "color: #ef5350; font-weight: 700;" diff --git a/src/shared/config/lovelace-badge-config.ts b/src/shared/config/lovelace-badge-config.ts new file mode 100644 index 000000000..956a0e4db --- /dev/null +++ b/src/shared/config/lovelace-badge-config.ts @@ -0,0 +1,6 @@ +import { any, object, string } from "superstruct"; + +export const lovelaceBadgeConfigStruct = object({ + type: string(), + visibility: any(), +}); diff --git a/src/translations/en.json b/src/translations/en.json index 44e6ccd69..b6ef26b48 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -43,6 +43,7 @@ }, "card": { "generic": { + "color": "Color", "icon_color": "Icon color", "layout": "Layout", "fill_container": "Fill container", @@ -75,6 +76,7 @@ "secondary": "Secondary information", "multiline_secondary": "Multiline secondary?", "entity_extra": "Used in templates and actions", + "label": "Label", "content": "Content", "badge_icon": "Badge icon", "badge_color": "Badge color", diff --git a/src/utils/custom-badges.ts b/src/utils/custom-badges.ts new file mode 100644 index 000000000..0595d166b --- /dev/null +++ b/src/utils/custom-badges.ts @@ -0,0 +1,20 @@ +import { repository } from "../../package.json"; + +interface RegisterBadgeParams { + type: string; + name: string; + description: string; +} +export function registerCustomBadge(params: RegisterBadgeParams) { + const windowWithBadges = window as unknown as Window & { + customBadges: unknown[]; + }; + windowWithBadges.customBadges = windowWithBadges.customBadges || []; + + const badgePage = params.type.replace("-badge", "").replace("mushroom-", ""); + windowWithBadges.customBadges.push({ + ...params, + preview: true, + documentationURL: `${repository.url}/blob/main/docs/badges/${badgePage}.md`, + }); +} diff --git a/src/utils/form/generic-fields.ts b/src/utils/form/generic-fields.ts index 9367bcdd0..3ea2273d4 100644 --- a/src/utils/form/generic-fields.ts +++ b/src/utils/form/generic-fields.ts @@ -1,4 +1,5 @@ export const GENERIC_LABELS = [ + "color", "icon_color", "layout", "fill_container",