diff --git a/src/card-controller/actions/actions-manager.ts b/src/card-controller/actions/actions-manager.ts index 0de56568..9b38e0df 100644 --- a/src/card-controller/actions/actions-manager.ts +++ b/src/card-controller/actions/actions-manager.ts @@ -91,7 +91,7 @@ export class ActionsManager implements ActionsExecutor { /** * This method is called when an ll-custom event is fired. This is used by * cards to fire custom actions. This card itself should not call this, but - * embedded elements may. + * embedded picture elements may. */ public handleCustomActionEvent = async ( ev: Event | CustomEvent, @@ -111,7 +111,11 @@ export class ActionsManager implements ActionsExecutor { return; } - await this.executeActions({ actions: action }); + await this.executeActions( + { actions: action }, + // Elements rendered by this card will already have rendered templates. + true, + ); }; /** @@ -129,10 +133,13 @@ export class ActionsManager implements ActionsExecutor { await allPromises(this._actionsInFlight, (actionSet) => actionSet.stop()); } - public async executeActions(request: ActionsExecutionRequest): Promise { + public async executeActions( + request: ActionsExecutionRequest, + renderTemplates = true, + ): Promise { const hass = this._api.getHASSManager().getHASS(); const renderedAction: ActionConfig | ActionConfig[] = - hass && this._templateRenderer + renderTemplates && hass && this._templateRenderer ? (this._templateRenderer.renderRecursively(hass, request.actions, { conditionState: this._api.getConditionStateManager().getState(), triggerData: request?.triggerData, diff --git a/src/card-controller/templates/index.ts b/src/card-controller/templates/index.ts index fd8573ff..82ccd941 100644 --- a/src/card-controller/templates/index.ts +++ b/src/card-controller/templates/index.ts @@ -20,15 +20,17 @@ interface TemplateContext { acc: TemplateContextInternal; } +interface TemplateRenderOptions { + conditionState?: ConditionState; + triggerData?: ConditionsTriggerData; + mediaData?: TemplateMediaData; +} + export class TemplateRenderer { public renderRecursively = ( hass: HomeAssistant, data: unknown, - options?: { - conditionState?: ConditionState; - triggerData?: ConditionsTriggerData; - mediaData?: TemplateMediaData; - }, + options?: TemplateRenderOptions, ): unknown => { return this._renderTemplateRecursively( hass, @@ -37,11 +39,9 @@ export class TemplateRenderer { ); }; - protected _generateTemplateContext(options?: { - conditionState?: ConditionState; - triggerData?: ConditionsTriggerData; - mediaData?: TemplateMediaData; - }): TemplateContext | undefined { + protected _generateTemplateContext( + options?: TemplateRenderOptions, + ): TemplateContext | undefined { if ( !options?.conditionState?.camera && !options?.conditionState?.view && diff --git a/src/card.ts b/src/card.ts index 7997e839..d9c44625 100644 --- a/src/card.ts +++ b/src/card.ts @@ -409,6 +409,7 @@ class AdvancedCameraCard extends LitElement { ${ref(this._refElements)} .hass=${this._hass} .elements=${this._config?.elements} + .conditionStateManager=${this._controller.getConditionStateManager()} @advanced-camera-card:menu:add=${(ev: CustomEvent) => { this._menuButtonController.addDynamicMenuButton(ev.detail); this.requestUpdate(); diff --git a/src/components/elements.ts b/src/components/elements.ts index ecf2b222..9c8e4262 100644 --- a/src/components/elements.ts +++ b/src/components/elements.ts @@ -7,9 +7,12 @@ import { unsafeCSS, } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; +import { isEqual } from 'lodash-es'; +import { TemplateRenderer } from '../card-controller/templates/index.js'; import { dispatchAdvancedCameraCardErrorEvent } from '../components-lib/message/dispatch.js'; import { ConditionsManager } from '../conditions/conditions-manager.js'; import { getConditionStateManagerViaEvent } from '../conditions/state-manager-via-event.js'; +import { ConditionStateManager } from '../conditions/state-manager.js'; import { StatusBarIcon, StatusBarImage, @@ -75,12 +78,19 @@ interface HuiConditionalElement extends HTMLElement { @customElement('advanced-camera-card-elements-core') export class AdvancedCameraCardElementsCore extends LitElement { @property({ attribute: false }) - public elements?: PictureElements; - - protected _root: HuiConditionalElement | null = null; + public hass?: HomeAssistant; @property({ attribute: false }) - public hass?: HomeAssistant; + public elements?: PictureElements; + + @property({ attribute: false }) + public conditionStateManager?: ConditionStateManager; + + @state() + private _root: HuiConditionalElement | null = null; + + private _renderedElements?: PictureElements; + private _templateRenderer = new TemplateRenderer(); /** * Create a transparent render root. @@ -104,7 +114,7 @@ export class AdvancedCameraCardElementsCore extends LitElement { const config = { type: 'conditional', conditions: [], - elements: this.elements, + elements: this._renderedElements, }; try { element.setConfig(config); @@ -115,26 +125,58 @@ export class AdvancedCameraCardElementsCore extends LitElement { return element; } - /** - * Create the root as necessary prior to rendering. - */ - protected willUpdate(changedProps: PropertyValues): void { + private _setNewRoot = (): void => { + if (!this.hass) { + return; + } + + const elements = this._templateRenderer.renderRecursively(this.hass, this.elements, { + conditionState: this.conditionStateManager?.getState(), + }) as PictureElements | undefined; + + // Condition state changes won't change the actual rendered config unless + // `elements` has a template, which is more likely does not. Avoid updating + // the root if nothing changes. + if (this._root && isEqual(this._renderedElements, elements)) { + return; + } + try { - // The root is only created once per elements configuration change, to - // avoid the elements being continually re-created & destroyed (for some - // elements, e.g. image, recreation causes a flicker). - if (this.elements && (!this._root || changedProps.has('elements'))) { - this._root = this._createRoot(); - } + this._renderedElements = elements; + this._root = this._createRoot(); } catch (e) { return dispatchAdvancedCameraCardErrorEvent(this, e as AdvancedCameraCardError); } + }; + + connectedCallback(): void { + super.connectedCallback(); + this.conditionStateManager?.addListener(this._setNewRoot); + } + + disconnectedCallback(): void { + this.conditionStateManager?.removeListener(this._setNewRoot); + super.disconnectedCallback(); + } + + protected willUpdate(changedProps: PropertyValues): void { + if (changedProps.has('conditionStateManager') && this.conditionStateManager) { + changedProps.get('conditionStateManager')?.removeEventListener(this._setNewRoot); + this.conditionStateManager.addListener(this._setNewRoot); + } + + // The root is only created once per elements configuration change, to + // avoid the elements being continually re-created & destroyed (for some + // elements, e.g. image, recreation causes a flicker). + if ( + !this._root || + changedProps.has('elements') || + changedProps.has('conditionStateManager') + ) { + this._setNewRoot(); + } } - /** - * Render the elements. - * @returns A rendered template or void. - */ protected render(): TemplateResult | void { return html`${this._root || ''}`; } @@ -148,16 +190,16 @@ export class AdvancedCameraCardElementsCore extends LitElement { } } -/** - * The master class, handles event listeners and styles. - */ @customElement('advanced-camera-card-elements') export class AdvancedCameraCardElements extends LitElement { @property({ attribute: false }) public hass?: HomeAssistant; @property({ attribute: false }) - public elements: PictureElements; + public elements?: PictureElements; + + @property({ attribute: false }) + public conditionStateManager?: ConditionStateManager; protected _addHandler( target: EventTarget, @@ -238,6 +280,7 @@ export class AdvancedCameraCardElements extends LitElement { protected render(): TemplateResult { return html` @@ -285,9 +328,6 @@ export class AdvancedCameraCardElementsConditional extends LitElement { return this; } - /** - * Connected callback. - */ connectedCallback(): void { super.connectedCallback(); @@ -317,9 +357,6 @@ export class AdvancedCameraCardElementsConditional extends LitElement { this._conditionManager.addListener(() => this.requestUpdate()); } - /** - * Render the card. - */ protected render(): TemplateResult | void { if (this._conditionManager?.getEvaluation()?.result) { return html` l !== listener); }