From 1f3a5b13969baee5b33d084636929f296d71e8a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Lov=C3=A9n?= Date: Sat, 10 Aug 2019 21:55:32 +0200 Subject: [PATCH] Template markdown card (#3451) * Render templates in markdown card * Add manual entity_id option * Linting * Address review comments * Address review comments * Address review comments * Address review comments * Tweak disconnect function * Remove cardSize instance variable * Fix demo --- src/data/ws-templates.ts | 20 ++++++ src/fake_data/provide_hass.ts | 18 ++++- .../lovelace/cards/hui-markdown-card.ts | 65 +++++++++++++++++-- src/panels/lovelace/cards/types.ts | 2 + 4 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 src/data/ws-templates.ts diff --git a/src/data/ws-templates.ts b/src/data/ws-templates.ts new file mode 100644 index 0000000000..aec9903803 --- /dev/null +++ b/src/data/ws-templates.ts @@ -0,0 +1,20 @@ +import { Connection, UnsubscribeFunc } from "home-assistant-js-websocket"; + +interface RenderTemplateResult { + result: string; +} + +export const subscribeRenderTemplate = ( + conn: Connection, + onChange: (result: string) => void, + params: { + template: string; + entity_ids?: string | string[]; + variables?: object; + } +): Promise => { + return conn.subscribeMessage( + (msg: RenderTemplateResult) => onChange(msg.result), + { type: "render_template", ...params } + ); +}; diff --git a/src/fake_data/provide_hass.ts b/src/fake_data/provide_hass.ts index 7d27a5d1ca..eb711cc348 100644 --- a/src/fake_data/provide_hass.ts +++ b/src/fake_data/provide_hass.ts @@ -24,7 +24,10 @@ export interface MockHomeAssistant extends HomeAssistant { updateHass(obj: Partial); updateStates(newStates: HassEntities); addEntities(entites: Entity | Entity[], replace?: boolean); - mockWS(type: string, callback: (msg: any) => any); + mockWS( + type: string, + callback: (msg: any, onChange?: (response: any) => void) => any + ); mockAPI(path: string | RegExp, callback: MockRestCallback); mockEvent(event); mockTheme(theme: { [key: string]: string } | null); @@ -108,7 +111,7 @@ export const provideHass = ( console.error(`Unknown WS command: ${msg.type}`); } }, - sendMessagePromise: (msg) => { + sendMessagePromise: async (msg) => { const callback = wsCommands[msg.type]; return callback ? callback(msg) @@ -119,6 +122,17 @@ export const provideHass = ( } is not implemented in provide_hass.`, }); }, + subscribeMessage: async (onChange, msg) => { + const callback = wsCommands[msg.type]; + return callback + ? callback(msg, onChange) + : Promise.reject({ + code: "command_not_mocked", + message: `WS Command ${ + msg.type + } is not implemented in provide_hass.`, + }); + }, subscribeEvents: async ( // @ts-ignore callback, diff --git a/src/panels/lovelace/cards/hui-markdown-card.ts b/src/panels/lovelace/cards/hui-markdown-card.ts index b41bd5455e..0df7737f5a 100644 --- a/src/panels/lovelace/cards/hui-markdown-card.ts +++ b/src/panels/lovelace/cards/hui-markdown-card.ts @@ -8,12 +8,15 @@ import { CSSResult, } from "lit-element"; import { classMap } from "lit-html/directives/class-map"; +import { UnsubscribeFunc } from "home-assistant-js-websocket"; import "../../../components/ha-card"; import "../../../components/ha-markdown"; +import { HomeAssistant } from "../../../types"; import { LovelaceCard, LovelaceCardEditor } from "../types"; import { MarkdownCardConfig } from "./types"; +import { subscribeRenderTemplate } from "../../../data/ws-templates"; @customElement("hui-markdown-card") export class HuiMarkdownCard extends LitElement implements LovelaceCard { @@ -27,11 +30,16 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard { } @property() private _config?: MarkdownCardConfig; + @property() private _content?: string = ""; + @property() private _unsubRenderTemplate?: Promise; + @property() private _hass?: HomeAssistant; public getCardSize(): number { - return ( - this._config!.content.split("\n").length + (this._config!.title ? 1 : 0) - ); + return this._config === undefined + ? 3 + : this._config.card_size === undefined + ? this._config.content.split("\n").length + (this._config.title ? 1 : 0) + : this._config.card_size; } public setConfig(config: MarkdownCardConfig): void { @@ -40,6 +48,20 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard { } this._config = config; + + this._disconnect(); + if (this._hass) { + this._connect(); + } + } + + public disconnectedCallback() { + this._disconnect(); + } + + public set hass(hass) { + this._hass = hass; + this._connect(); } protected render(): TemplateResult | void { @@ -53,12 +75,47 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard { class="markdown ${classMap({ "no-header": !this._config.title, })}" - .content="${this._config.content}" + .content="${this._content}" > `; } + private async _connect() { + if (!this._unsubRenderTemplate && this._hass && this._config) { + this._unsubRenderTemplate = subscribeRenderTemplate( + this._hass.connection, + (result) => { + this._content = result; + }, + { + template: this._config.content, + entity_ids: this._config.entity_id, + } + ); + this._unsubRenderTemplate.catch(() => { + this._content = this._config!.content; + this._unsubRenderTemplate = undefined; + }); + } + } + + private async _disconnect() { + if (this._unsubRenderTemplate) { + try { + const unsub = await this._unsubRenderTemplate; + this._unsubRenderTemplate = undefined; + await unsub(); + } catch (e) { + if (e.code === "not_found") { + // If we get here, the connection was probably already closed. Ignore. + } else { + throw e; + } + } + } + } + static get styles(): CSSResult { return css` ha-markdown { diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index 1ad9f31c53..12f0a441c3 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -118,6 +118,8 @@ export interface MarkdownCardConfig extends LovelaceCardConfig { type: "markdown"; content: string; title?: string; + card_size?: number; + entity_ids?: string | string[]; } export interface MediaControlCardConfig extends LovelaceCardConfig {