diff --git a/.hass_dev/lovelace-mushroom-showcase.yaml b/.hass_dev/lovelace-mushroom-showcase.yaml index a67e95208..9a7d201f6 100644 --- a/.hass_dev/lovelace-mushroom-showcase.yaml +++ b/.hass_dev/lovelace-mushroom-showcase.yaml @@ -6,5 +6,6 @@ views: - !include views/person-view.yaml - !include views/alarm-control-panel-view.yaml - !include views/fan-view.yaml - - !include views/chips-view.yaml - - !include views/sensor-view.yaml \ No newline at end of file + - !include views/template-view.yaml + - !include views/sensor-view.yaml + - !include views/chips-view.yaml \ No newline at end of file diff --git a/.hass_dev/views/chips-view.yaml b/.hass_dev/views/chips-view.yaml index c83bfdb9f..ff2150f8e 100644 --- a/.hass_dev/views/chips-view.yaml +++ b/.hass_dev/views/chips-view.yaml @@ -90,4 +90,31 @@ cards: icon: mdi:github tap_action: action: url - url_path: https://github.com/piitaya/mushroom-cards \ No newline at end of file + url_path: https://github.com/piitaya/mushroom-cards + - type: vertical-stack + title: Template chip + cards: + - type: custom:mushroom-chips-card + chips: + - type: template + content: | + {% if is_state('light.bed_light', 'on') %} + The bedroom light is on ! + {% else %} + The bedroom light is not on ! + {% endif %} + icon: | + {% if is_state('light.bed_light', 'on') %} + mdi:ceiling-light + {% else %} + mdi:ceiling-light-outline + {% endif %} + tap_action: + action: call-service + service: light.toggle + target: + entity_id: light.bed_light + - type: template + content: | + {{ states | count }} entities + icon: mdi:format-list-bulleted \ No newline at end of file diff --git a/.hass_dev/views/template-view.yaml b/.hass_dev/views/template-view.yaml new file mode 100644 index 000000000..c6990df24 --- /dev/null +++ b/.hass_dev/views/template-view.yaml @@ -0,0 +1,35 @@ + + +title: Switch +icon: mdi:card-text +cards: + - type: grid + title: Simple + cards: + - type: custom:mushroom-template-card + name: Hello, {{user}} + state: How are you? + icon: mdi:home + - type: custom:mushroom-template-card + name: Number of entities + state: | + {{ states | count }} entities + icon: mdi:format-list-bulleted + columns: 2 + square: false + - type: grid + title: Vertical + cards: + - type: custom:mushroom-template-card + name: Hello, {{user}} + state: How are you? + icon: mdi:home + vertical: true + - type: custom:mushroom-template-card + name: Number of entities + state: | + {{ states | count }} entities + icon: mdi:format-list-bulleted + vertical: true + columns: 2 + square: false \ No newline at end of file diff --git a/src/cards/chips-card/chips-card-chips-editor.ts b/src/cards/chips-card/chips-card-chips-editor.ts index 69f885223..b17564d43 100644 --- a/src/cards/chips-card/chips-card-chips-editor.ts +++ b/src/cards/chips-card/chips-card-chips-editor.ts @@ -130,6 +130,7 @@ export class ChipsCardEditorChips extends LitElement { Entity Weather Action + Template `; diff --git a/src/cards/chips-card/chips-card.ts b/src/cards/chips-card/chips-card.ts index 805ebdf27..398b62754 100644 --- a/src/cards/chips-card/chips-card.ts +++ b/src/cards/chips-card/chips-card.ts @@ -9,7 +9,13 @@ import { customElement, property, state } from "lit/decorators.js"; import { registerCustomCard } from "../../utils/custom-cards"; import { CHIPS_CARD_EDITOR_NAME, CHIPS_CARD_NAME } from "./const"; import "../../shared/chip"; -import { BackChip, createChipElement, EntityChip, WeatherChip } from "./chips"; +import { + BackChip, + createChipElement, + EntityChip, + LovelaceChip, + WeatherChip, +} from "./chips"; import "./chips"; import "./chips-card-editor"; import { LovelaceChipConfig } from "../../utils/lovelace/chip/types"; @@ -45,10 +51,19 @@ export class ChipsCard extends LitElement implements LovelaceCard { }; } - @property({ attribute: false }) public hass!: HomeAssistant; - @state() private _config?: ChipsCardConfig; + private _hass?: HomeAssistant; + + set hass(hass: HomeAssistant) { + this._hass = hass; + this.shadowRoot + ?.querySelectorAll("div > *") + .forEach((element: unknown) => { + (element as LovelaceChip).hass = hass; + }); + } + getCardSize(): number | Promise { return 1; } @@ -58,7 +73,7 @@ export class ChipsCard extends LitElement implements LovelaceCard { } protected render(): TemplateResult { - if (!this._config || !this.hass) { + if (!this._config || !this._hass) { return html``; } @@ -74,10 +89,10 @@ export class ChipsCard extends LitElement implements LovelaceCard { if (!element) { return html``; } - if (this.hass) { - element.hass = this.hass; + if (this._hass) { + element.hass = this._hass; } - return html`${element}`; + return html`
${element}
`; } static get styles(): CSSResultGroup { diff --git a/src/cards/chips-card/chips/index.ts b/src/cards/chips-card/chips/index.ts index 2ee8e7f2d..7f2dfe47a 100644 --- a/src/cards/chips-card/chips/index.ts +++ b/src/cards/chips-card/chips/index.ts @@ -29,3 +29,4 @@ export { WeatherChip } from "./weather-chip"; export { BackChip } from "./back-chip"; export { ActionChip } from "./action-chip"; export { MenuChip } from "./menu-chip"; +export { TemplateChip } from "./template-chip"; diff --git a/src/cards/chips-card/chips/template-chip-editor.ts b/src/cards/chips-card/chips/template-chip-editor.ts new file mode 100644 index 000000000..4a3ff39f1 --- /dev/null +++ b/src/cards/chips-card/chips/template-chip-editor.ts @@ -0,0 +1,131 @@ +import { fireEvent, HomeAssistant } from "custom-card-helpers"; +import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; +import setupCustomlocalize from "../../../localize"; +import { configElementStyle } from "../../../utils/editor-styles"; +import { TemplateChipConfig } from "../../../utils/lovelace/chip/types"; +import { EditorTarget } from "../../../utils/lovelace/editor/types"; +import { LovelaceChipEditor } from "../../../utils/lovelace/types"; +import { computeChipEditorComponentName } from "../utils"; + +const actions = ["navigate", "url", "call-service", "none"]; + +@customElement(computeChipEditorComponentName("template")) +export class EntityChipEditor extends LitElement implements LovelaceChipEditor { + @property({ attribute: false }) public hass?: HomeAssistant; + + @state() private _config?: TemplateChipConfig; + + public setConfig(config: TemplateChipConfig): void { + this._config = config; + } + + protected render(): TemplateResult { + if (!this.hass || !this._config) { + return html``; + } + + const customlocalize = setupCustomlocalize(this.hass); + + return html` +
+ + +
+ + +
+
+ `; + } + + private _ignoreKeydown(ev: KeyboardEvent) { + // Stop keyboard events from the paper-textarea from propagating to avoid accidentally closing the dialog when the user presses Enter. + ev.stopPropagation(); + } + + private _valueChanged(ev: CustomEvent): void { + if (!this._config || !this.hass) { + return; + } + const target = ev.target! as EditorTarget; + const value = + target.checked !== undefined ? target.checked : ev.detail.value; + + if (this[`_${target.configValue}`] === value) { + return; + } + + let newConfig; + if (target.configValue) { + if (!value) { + newConfig = { ...this._config }; + delete newConfig[target.configValue!]; + } else { + newConfig = { + ...this._config, + [target.configValue!]: value, + }; + } + } + fireEvent(this, "config-changed", { config: newConfig }); + } + + static get styles(): CSSResultGroup { + return configElementStyle; + } +} diff --git a/src/cards/chips-card/chips/template-chip.ts b/src/cards/chips-card/chips/template-chip.ts new file mode 100644 index 000000000..68cd087ca --- /dev/null +++ b/src/cards/chips-card/chips/template-chip.ts @@ -0,0 +1,198 @@ +import { + ActionHandlerEvent, + handleAction, + HomeAssistant, +} from "custom-card-helpers"; +import { UnsubscribeFunc } from "home-assistant-js-websocket"; +import { + css, + CSSResultGroup, + html, + LitElement, + PropertyValues, + TemplateResult, +} from "lit"; +import { customElement, property, state } from "lit/decorators.js"; +import { LovelaceChip } from "."; +import { actionHandler } from "../../../utils/directives/action-handler-directive"; +import { TemplateChipConfig } from "../../../utils/lovelace/chip/types"; +import { LovelaceChipEditor } from "../../../utils/lovelace/types"; +import { + RenderTemplateResult, + subscribeRenderTemplate, +} from "../../../utils/ws-templates"; +import { + computeChipComponentName, + computeChipEditorComponentName, +} from "../utils"; +import "./menu-chip-editor"; +import "./template-chip-editor"; + +const TEMPLATE_KEYS = ["content", "icon"] as const; +type TemplateKey = typeof TEMPLATE_KEYS[number]; + +@customElement(computeChipComponentName("template")) +export class TemplateChip extends LitElement implements LovelaceChip { + public static async getConfigElement(): Promise { + return document.createElement( + computeChipEditorComponentName("template") + ) as LovelaceChipEditor; + } + + public static async getStubConfig( + _hass: HomeAssistant + ): Promise { + return { + type: `template`, + }; + } + + @property({ attribute: false }) public hass?: HomeAssistant; + + @state() private _config?: TemplateChipConfig; + + @state() private _templateResults: Partial< + Record + > = {}; + + @state() private _unsubRenderTemplates: Map< + TemplateKey, + Promise + > = new Map(); + + public setConfig(config: TemplateChipConfig): void { + TEMPLATE_KEYS.forEach((key) => { + if (this._config?.[key] !== config[key]) { + this._tryDisconnectKey(key); + } + }); + this._config = config; + } + + public connectedCallback() { + super.connectedCallback(); + this._tryConnect(); + } + + public disconnectedCallback() { + this._tryDisconnect(); + } + + private _handleAction(ev: ActionHandlerEvent) { + handleAction(this, this.hass!, this._config!, ev.detail.action!); + } + + protected render(): TemplateResult { + if (!this.hass || !this._config) { + return html``; + } + + const icon = this._templateResults.icon?.result; + const content = this._templateResults.content?.result; + + return html` + + ${icon ? html`` : null} + ${content ? html`${content}` : null} + + `; + } + + 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 + ) { + return; + } + + try { + this._unsubRenderTemplates.set( + key, + 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, + }, + } + ) + ); + } 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") { + // If we get here, the connection was probably already closed. Ignore. + } else { + throw err; + } + } + } + + static get styles(): CSSResultGroup { + return css` + mushroom-chip { + cursor: pointer; + } + `; + } +} diff --git a/src/cards/template-card/const.ts b/src/cards/template-card/const.ts new file mode 100644 index 000000000..62e68b32a --- /dev/null +++ b/src/cards/template-card/const.ts @@ -0,0 +1,4 @@ +import { PREFIX_NAME } from "../../const"; + +export const TEMPLATE_CARD_NAME = `${PREFIX_NAME}-template-card`; +export const TEMPLATE_CARD_EDITOR_NAME = `${TEMPLATE_CARD_NAME}-editor`; diff --git a/src/cards/template-card/template-card-config.ts b/src/cards/template-card/template-card-config.ts new file mode 100644 index 000000000..bddb5277d --- /dev/null +++ b/src/cards/template-card/template-card-config.ts @@ -0,0 +1,35 @@ +import { ActionConfig, LovelaceCardConfig } from "custom-card-helpers"; +import { + array, + assign, + boolean, + object, + optional, + string, + union, +} from "superstruct"; +import { actionConfigStruct } from "../../utils/action-struct"; +import { baseLovelaceCardConfig } from "../../utils/editor-styles"; + +export interface TemplateCardConfig extends LovelaceCardConfig { + name?: string; + icon?: string; + state?: string; + vertical?: boolean; + tap_action?: ActionConfig; + hold_action?: ActionConfig; + entity_id?: string | string[]; +} + +export const templateCardConfigStruct = assign( + baseLovelaceCardConfig, + object({ + name: optional(string()), + icon: optional(string()), + state: optional(string()), + vertical: optional(boolean()), + tap_action: optional(actionConfigStruct), + hold_action: optional(actionConfigStruct), + entity_id: optional(union([string(), array(string())])), + }) +); diff --git a/src/cards/template-card/template-card-editor.ts b/src/cards/template-card/template-card-editor.ts new file mode 100644 index 000000000..6ef84e3f6 --- /dev/null +++ b/src/cards/template-card/template-card-editor.ts @@ -0,0 +1,158 @@ +import { + computeRTLDirection, + fireEvent, + HomeAssistant, + LovelaceCardEditor, + stateIcon, +} from "custom-card-helpers"; +import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; +import { assert } from "superstruct"; +import setupCustomlocalize from "../../localize"; +import { configElementStyle } from "../../utils/editor-styles"; +import { EditorTarget } from "../../utils/lovelace/editor/types"; +import { TEMPLATE_CARD_EDITOR_NAME } from "./const"; +import { + TemplateCardConfig, + templateCardConfigStruct, +} from "./template-card-config"; + +const actions = ["more-info", "navigate", "url", "call-service", "none"]; + +@customElement(TEMPLATE_CARD_EDITOR_NAME) +export class TemplateCardEditor + extends LitElement + implements LovelaceCardEditor +{ + @property({ attribute: false }) public hass?: HomeAssistant; + + @state() private _config?: TemplateCardConfig; + + public setConfig(config: TemplateCardConfig): void { + assert(config, templateCardConfigStruct); + this._config = config; + } + + protected render(): TemplateResult { + if (!this.hass || !this._config) { + return html``; + } + + const customlocalize = setupCustomlocalize(this.hass); + + return html` +
+ + + +
+ + +
+
+ `; + } + + private _ignoreKeydown(ev: KeyboardEvent) { + // Stop keyboard events from the paper-textarea from propagating to avoid accidentally closing the dialog when the user presses Enter. + ev.stopPropagation(); + } + + private _valueChanged(ev: CustomEvent): void { + if (!this._config || !this.hass) { + return; + } + const target = ev.target! as EditorTarget; + const value = + target.checked !== undefined ? target.checked : ev.detail.value; + + if (this[`_${target.configValue}`] === value) { + return; + } + + let newConfig; + if (target.configValue) { + if (!value) { + newConfig = { ...this._config }; + delete newConfig[target.configValue!]; + } else { + newConfig = { + ...this._config, + [target.configValue!]: value, + }; + } + } + fireEvent(this, "config-changed", { config: newConfig }); + } + + static get styles(): CSSResultGroup { + return configElementStyle; + } +} diff --git a/src/cards/template-card/template-card.ts b/src/cards/template-card/template-card.ts new file mode 100644 index 000000000..48224ba3f --- /dev/null +++ b/src/cards/template-card/template-card.ts @@ -0,0 +1,249 @@ +import { + ActionHandlerEvent, + computeStateDisplay, + handleAction, + hasAction, + HomeAssistant, + LovelaceCard, + LovelaceCardEditor, + stateIcon, +} from "custom-card-helpers"; +import { + css, + CSSResultGroup, + html, + LitElement, + PropertyValues, + TemplateResult, +} from "lit"; +import { customElement, property, state } from "lit/decorators.js"; +import "../../shared/shape-icon"; +import "../../shared/state-info"; +import "../../shared/state-item"; +import { cardStyle } from "../../utils/card-styles"; +import { registerCustomCard } from "../../utils/custom-cards"; +import { actionHandler } from "../../utils/directives/action-handler-directive"; +import { TEMPLATE_CARD_EDITOR_NAME, TEMPLATE_CARD_NAME } from "./const"; +import { TemplateCardConfig } from "./template-card-config"; +import "./template-card-editor"; +import { + RenderTemplateResult, + subscribeRenderTemplate, +} from "../../utils/ws-templates"; +import { UnsubscribeFunc } from "home-assistant-js-websocket"; + +registerCustomCard({ + type: TEMPLATE_CARD_NAME, + name: "Mushroom Template Card", + description: "Card for custom rendering with templates", +}); + +const TEMPLATE_KEYS = ["state", "icon", "name"] as const; +type TemplateKey = typeof TEMPLATE_KEYS[number]; + +@customElement(TEMPLATE_CARD_NAME) +export class TemplateCard extends LitElement implements LovelaceCard { + public static async getConfigElement(): Promise { + return document.createElement( + TEMPLATE_CARD_EDITOR_NAME + ) as LovelaceCardEditor; + } + + public static async getStubConfig( + _hass: HomeAssistant + ): Promise { + return { + type: `custom:${TEMPLATE_CARD_NAME}`, + name: "Hello, {{user}}", + state: "How are you?", + icon: "mdi:home", + }; + } + + @property({ attribute: false }) public hass!: HomeAssistant; + + @state() private _config?: TemplateCardConfig; + + @state() private _templateResults: Partial< + Record + > = {}; + + @state() private _unsubRenderTemplates: Map< + TemplateKey, + Promise + > = new Map(); + + getCardSize(): number | Promise { + return 1; + } + + setConfig(config: TemplateCardConfig): void { + TEMPLATE_KEYS.forEach((key) => { + if (this._config?.[key] !== config[key]) { + this._tryDisconnectKey(key); + } + }); + this._config = { + tap_action: { + action: "toggle", + }, + hold_action: { + action: "more-info", + }, + ...config, + }; + } + + public connectedCallback() { + super.connectedCallback(); + this._tryConnect(); + } + + public disconnectedCallback() { + this._tryDisconnect(); + } + + private _handleAction(ev: ActionHandlerEvent) { + handleAction(this, this.hass!, this._config!, ev.detail.action!); + } + + protected render(): TemplateResult { + if (!this._config || !this.hass) { + return html``; + } + + const name = this._templateResults.name?.result; + const icon = this._templateResults.icon?.result; + const state = this._templateResults.state?.result; + + const vertical = this._config.vertical; + + return html` +
+ + + + +
+
`; + } + + 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 + ) { + return; + } + + try { + this._unsubRenderTemplates.set( + key, + 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, + }, + } + ) + ); + } 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") { + // If we get here, the connection was probably already closed. Ignore. + } else { + throw err; + } + } + } + + static get styles(): CSSResultGroup { + return [ + cardStyle, + css` + :host { + --rgb-color: 61, 90, 254; + } + mushroom-state-item { + cursor: pointer; + } + mushroom-shape-icon { + --icon-color: rgba(var(--rgb-color), 1); + --shape-color: rgba(var(--rgb-color), 0.2); + } + `, + ]; + } +} diff --git a/src/index.ts b/src/index.ts index 37c675233..5ba048b22 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,7 @@ export { LightCard } from "./cards/light-card/light-card"; export { PersonCard } from "./cards/person-card/person-card"; export { SensorCard } from "./cards/sensor-card/sensor-card"; export { SwitchCard } from "./cards/switch-card/switch-card"; +export { TemplateCard } from "./cards/template-card/template-card"; console.info( `%c🍄 Mushroom 🍄 - ${version}`, diff --git a/src/translations/en.json b/src/translations/en.json index b0e278454..c7fec69de 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3,7 +3,8 @@ "card": { "generic": { "vertical": "Vertical?", - "hide_state": "Hide state?" + "hide_state": "Hide state?", + "state": "State" }, "light": { "show_brightness_control": "Brightness control?", @@ -33,12 +34,15 @@ "edit": "Edit", "clear": "Clear" }, + "generic": { + "icon_color": "Icon color" + }, "weather": { "show_conditions": "Conditions?", "show_temperature": "Temperature?" }, - "generic": { - "icon_color": "Icon color" + "template": { + "content": "Content" } } } diff --git a/src/translations/fr.json b/src/translations/fr.json index ccd9e4f1b..20d97283d 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -3,7 +3,8 @@ "card": { "generic": { "vertical": "Vertical ?", - "hide_state": "Cacher l'état ?" + "hide_state": "Cacher l'état ?", + "state": "État" }, "light": { "show_brightness_control": "Contrôle de luminosité ?", @@ -33,12 +34,15 @@ "edit": "Modifier", "clear": "Effacer" }, + "generic": { + "icon_color": "Couleur de l'icône" + }, "weather": { "show_conditons": "Conditions ?", "show_temperature": "Température ?" }, - "generic": { - "icon_color": "Couleur de l'icône" + "template": { + "content": "Contenu" } } } diff --git a/src/utils/lovelace/chip/types.ts b/src/utils/lovelace/chip/types.ts index d6346eaa2..ca8795f29 100644 --- a/src/utils/lovelace/chip/types.ts +++ b/src/utils/lovelace/chip/types.ts @@ -38,9 +38,19 @@ export type WeatherChipConfig = { show_conditions?: boolean; }; +export type TemplateChipConfig = { + type: "template"; + hold_action?: ActionConfig; + tap_action?: ActionConfig; + content?: string; + icon?: string; + entity_id?: string | string[]; +}; + export type LovelaceChipConfig = | ActionChipConfig | BackChipConfig | EntityChipConfig | MenuChipConfig - | WeatherChipConfig; + | WeatherChipConfig + | TemplateChipConfig; diff --git a/src/utils/ws-templates.ts b/src/utils/ws-templates.ts new file mode 100644 index 000000000..3893fb7a0 --- /dev/null +++ b/src/utils/ws-templates.ts @@ -0,0 +1,28 @@ +import { Connection, UnsubscribeFunc } from "home-assistant-js-websocket"; + +export interface RenderTemplateResult { + result: string; + listeners: TemplateListeners; +} + +interface TemplateListeners { + all: boolean; + domains: string[]; + entities: string[]; + time: boolean; +} + +export const subscribeRenderTemplate = ( + conn: Connection, + onChange: (result: RenderTemplateResult) => void, + params: { + template: string; + entity_ids?: string | string[]; + variables?: Record; + timeout?: number; + } +): Promise => + conn.subscribeMessage((msg: RenderTemplateResult) => onChange(msg), { + type: "render_template", + ...params, + });