From 19c44f7c7bf0c4370059366674c08bfd75a85e7d Mon Sep 17 00:00:00 2001 From: yosilevy <37745463+yosilevy@users.noreply.github.com> Date: Sun, 3 Mar 2019 21:27:34 +0200 Subject: [PATCH] Conditional element support for use in picture-elements (#2865) * Conditional element support for use in picture-elements * Refactored * Refactor to HTMLElement, separated files * New file * Added disconnected callback, handled multiple config calls. * Added update method * Refactored and simplified - elements contained internally * Removed excess if * Refactored also picture elements --- .../lovelace/cards/hui-conditional-card.ts | 45 +++++----- .../cards/hui-picture-elements-card.ts | 25 ++---- .../create-styled-hui-element.ts | 20 +++++ .../lovelace/common/create-hui-element.ts | 2 + .../lovelace/common/validate-condition.ts | 28 +++++++ .../elements/hui-conditional-element.ts | 83 +++++++++++++++++++ 6 files changed, 160 insertions(+), 43 deletions(-) create mode 100644 src/panels/lovelace/cards/picture-elements/create-styled-hui-element.ts create mode 100644 src/panels/lovelace/common/validate-condition.ts create mode 100644 src/panels/lovelace/elements/hui-conditional-element.ts diff --git a/src/panels/lovelace/cards/hui-conditional-card.ts b/src/panels/lovelace/cards/hui-conditional-card.ts index 51cdb07a3e..c0c7e39d68 100644 --- a/src/panels/lovelace/cards/hui-conditional-card.ts +++ b/src/panels/lovelace/cards/hui-conditional-card.ts @@ -1,15 +1,14 @@ import { createCardElement } from "../common/create-card-element"; import { computeCardSize } from "../common/compute-card-size"; +import { + Condition, + checkConditionsMet, + validateConditionalConfig, +} from "../../lovelace/common/validate-condition"; import { HomeAssistant } from "../../../types"; import { LovelaceCard } from "../types"; import { LovelaceCardConfig } from "../../../data/lovelace"; -interface Condition { - entity: string; - state?: string; - state_not?: string; -} - interface Config extends LovelaceCardConfig { card: LovelaceCardConfig; conditions: Condition[]; @@ -25,7 +24,7 @@ class HuiConditionalCard extends HTMLElement implements LovelaceCard { !config.card || !config.conditions || !Array.isArray(config.conditions) || - !config.conditions.every((c) => c.entity && (c.state || c.state_not)) + !validateConditionalConfig(config.conditions) ) { throw new Error("Error in card configuration."); } @@ -36,32 +35,30 @@ class HuiConditionalCard extends HTMLElement implements LovelaceCard { this._config = config; this._card = createCardElement(config.card); - if (this._hass) { - this.hass = this._hass; - } + + this.update(); } set hass(hass: HomeAssistant) { this._hass = hass; - if (!this._card) { + this.update(); + } + + public getCardSize() { + return computeCardSize(this._card!); + } + + private update() { + if (!this._card || !this._hass) { return; } const visible = - this._config && - this._config.conditions.every((c) => { - if (!(c.entity in hass.states)) { - return false; - } - if (c.state) { - return hass.states[c.entity].state === c.state; - } - return hass.states[c.entity].state !== c.state_not; - }); + this._config && checkConditionsMet(this._config.conditions, this._hass); if (visible) { - this._card.hass = hass; + this._card.hass = this._hass; if (!this._card.parentElement) { this.appendChild(this._card); } @@ -71,10 +68,6 @@ class HuiConditionalCard extends HTMLElement implements LovelaceCard { // This will hide the complete card so it won't get styled by parent this.style.setProperty("display", visible ? "" : "none"); } - - public getCardSize() { - return computeCardSize(this._card!); - } } declare global { diff --git a/src/panels/lovelace/cards/hui-picture-elements-card.ts b/src/panels/lovelace/cards/hui-picture-elements-card.ts index 8b854d5c16..b24196a047 100644 --- a/src/panels/lovelace/cards/hui-picture-elements-card.ts +++ b/src/panels/lovelace/cards/hui-picture-elements-card.ts @@ -1,6 +1,6 @@ import { html, LitElement, TemplateResult } from "lit-element"; -import { createHuiElement } from "../common/create-hui-element"; +import { createStyledHuiElement } from "./picture-elements/create-styled-hui-element"; import { LovelaceCard } from "../types"; import { LovelaceCardConfig } from "../../../data/lovelace"; @@ -71,8 +71,13 @@ class HuiPictureElementsCard extends LitElement implements LovelaceCard { .entity="${this._config.entity}" .aspectRatio="${this._config.aspect_ratio}" > - ${this._config.elements.map((elementConfig: LovelaceElementConfig) => - this._createHuiElement(elementConfig) + ${this._config.elements.map( + (elementConfig: LovelaceElementConfig) => { + const element = createStyledHuiElement(elementConfig); + element.hass = this._hass; + + return element; + } )} @@ -95,20 +100,6 @@ class HuiPictureElementsCard extends LitElement implements LovelaceCard { `; } - - private _createHuiElement( - elementConfig: LovelaceElementConfig - ): LovelaceElement { - const element = createHuiElement(elementConfig) as LovelaceElement; - element.hass = this._hass; - element.classList.add("element"); - - Object.keys(elementConfig.style).forEach((prop) => { - element.style.setProperty(prop, elementConfig.style[prop]); - }); - - return element; - } } declare global { diff --git a/src/panels/lovelace/cards/picture-elements/create-styled-hui-element.ts b/src/panels/lovelace/cards/picture-elements/create-styled-hui-element.ts new file mode 100644 index 0000000000..3b57bf711d --- /dev/null +++ b/src/panels/lovelace/cards/picture-elements/create-styled-hui-element.ts @@ -0,0 +1,20 @@ +import { LovelaceElement, LovelaceElementConfig } from "../../elements/types"; +import { createHuiElement } from "../../common/create-hui-element"; + +export function createStyledHuiElement( + elementConfig: LovelaceElementConfig +): LovelaceElement { + const element = createHuiElement(elementConfig) as LovelaceElement; + // keep conditional card as a transparent container so let its position remain static + if (element.tagName !== "HUI-CONDITIONAL-ELEMENT") { + element.classList.add("element"); + } + + if (elementConfig.style) { + Object.keys(elementConfig.style).forEach((prop) => { + element.style.setProperty(prop, elementConfig.style[prop]); + }); + } + + return element; +} diff --git a/src/panels/lovelace/common/create-hui-element.ts b/src/panels/lovelace/common/create-hui-element.ts index 8ddff95a83..baea37275a 100644 --- a/src/panels/lovelace/common/create-hui-element.ts +++ b/src/panels/lovelace/common/create-hui-element.ts @@ -1,5 +1,6 @@ import deepClone from "deep-clone-simple"; +import "../elements/hui-conditional-element"; import "../elements/hui-icon-element"; import "../elements/hui-image-element"; import "../elements/hui-service-button-element"; @@ -17,6 +18,7 @@ import { LovelaceElementConfig, LovelaceElement } from "../elements/types"; const CUSTOM_TYPE_PREFIX = "custom:"; const ELEMENT_TYPES = new Set([ + "conditional", "icon", "image", "service-button", diff --git a/src/panels/lovelace/common/validate-condition.ts b/src/panels/lovelace/common/validate-condition.ts new file mode 100644 index 0000000000..7775cafa40 --- /dev/null +++ b/src/panels/lovelace/common/validate-condition.ts @@ -0,0 +1,28 @@ +import { HomeAssistant } from "../../../types"; + +export interface Condition { + entity: string; + state?: string; + state_not?: string; +} + +export function checkConditionsMet( + conditions: Condition[], + hass: HomeAssistant +): boolean { + return conditions.every((c) => { + if (!(c.entity in hass.states)) { + return false; + } + if (c.state) { + return hass.states[c.entity].state === c.state; + } + return hass!.states[c.entity].state !== c.state_not; + }); +} + +export function validateConditionalConfig(conditions: Condition[]): boolean { + return conditions.every( + (c) => ((c.entity && (c.state || c.state_not)) as unknown) as boolean + ); +} diff --git a/src/panels/lovelace/elements/hui-conditional-element.ts b/src/panels/lovelace/elements/hui-conditional-element.ts new file mode 100644 index 0000000000..15901e294d --- /dev/null +++ b/src/panels/lovelace/elements/hui-conditional-element.ts @@ -0,0 +1,83 @@ +import { + Condition, + checkConditionsMet, + validateConditionalConfig, +} from "../../lovelace/common/validate-condition"; +import { createStyledHuiElement } from "../cards/picture-elements/create-styled-hui-element"; + +import { LovelaceElement, LovelaceElementConfig } from "./types"; +import { HomeAssistant } from "../../../types"; + +interface Config extends LovelaceElementConfig { + conditions: Condition[]; + elements: LovelaceElementConfig[]; +} + +class HuiConditionalElement extends HTMLElement implements LovelaceElement { + public _hass?: HomeAssistant; + private _config?: Config; + private _elements: LovelaceElement[] = []; + + public setConfig(config: Config): void { + if ( + !config.conditions || + !Array.isArray(config.conditions) || + !config.elements || + !Array.isArray(config.elements) || + !validateConditionalConfig(config.conditions) + ) { + throw new Error("Error in card configuration."); + } + + if (this._elements.length > 0) { + this._elements.map((el: LovelaceElement) => { + if (el.parentElement) { + el.parentElement.removeChild(el); + } + }); + + this._elements = []; + } + + this._config = config; + + this._config.elements.map((elementConfig: LovelaceElementConfig) => { + this._elements.push(createStyledHuiElement(elementConfig)); + }); + + this.updateElements(); + } + + set hass(hass: HomeAssistant) { + this._hass = hass; + + this.updateElements(); + } + + private updateElements() { + if (!this._hass || !this._config) { + return; + } + + const visible = checkConditionsMet(this._config.conditions, this._hass); + + this._elements.map((el: LovelaceElement) => { + if (visible) { + el.hass = this._hass; + if (!el.parentElement) { + this.appendChild(el); + } + } else if (el.parentElement) { + el.parentElement.removeChild(el); + } + }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-conditional-element": HuiConditionalElement; + } +} + +customElements.define("hui-conditional-element", HuiConditionalElement);