diff --git a/src/panels/lovelace/ha-panel-lovelace.ts b/src/panels/lovelace/ha-panel-lovelace.ts index 508ff4b23c..deb0432d63 100644 --- a/src/panels/lovelace/ha-panel-lovelace.ts +++ b/src/panels/lovelace/ha-panel-lovelace.ts @@ -9,6 +9,8 @@ import { addSearchParam, removeSearchParam, } from "../../common/url/search-params"; +import { debounce } from "../../common/util/debounce"; +import { deepEqual } from "../../common/util/deep-equal"; import { domainToName } from "../../data/integration"; import { subscribeLovelaceUpdates } from "../../data/lovelace"; import type { @@ -51,6 +53,12 @@ interface LovelacePanelConfig { let editorLoaded = false; let resourcesLoaded = false; +declare global { + interface HASSDomEvents { + "strategy-config-changed": undefined; + } +} + @customElement("ha-panel-lovelace") export class LovelacePanel extends LitElement { @property({ attribute: false }) public panel?: PanelInfo; @@ -127,6 +135,7 @@ export class LovelacePanel extends LitElement { .route=${this.route} .narrow=${this.narrow} @config-refresh=${this._forceFetchConfig} + @strategy-config-changed=${this._strategyConfigChanged} > `; } @@ -199,7 +208,7 @@ export class LovelacePanel extends LitElement { oldHass.floors !== this.hass.floors ) { if (this.hass.config.state === "RUNNING") { - this._askRefreshConfig(); + this._debounceRegistriesChanged(); } } // If ha started, refresh the config @@ -207,25 +216,59 @@ export class LovelacePanel extends LitElement { this.hass.config.state === "RUNNING" && oldHass.config.state !== "RUNNING" ) { - this._refreshConfig(); + this._regenerateStrategyConfig(); } } } - private _askRefreshConfig = () => { + private _debounceRegistriesChanged = debounce( + () => this._registriesChanged(), + 200 + ); + + private _registriesChanged = async () => { + if (!this.hass || !this.lovelace) { + return; + } + const rawConfig = this.lovelace.rawConfig; + + if (!isStrategyDashboard(rawConfig)) { + return; + } + + const oldConfig = this.lovelace.config; + const generatedConfig = await generateLovelaceDashboardStrategy( + rawConfig, + this.hass! + ); + + const newConfig = checkLovelaceConfig(generatedConfig) as LovelaceConfig; + + // Ask to regenerate if the config changed + if (!deepEqual(newConfig, oldConfig)) { + this._askRegenerateStrategyConfig(); + } + }; + + private _strategyConfigChanged = (ev: CustomEvent) => { + ev.stopPropagation(); + this._askRegenerateStrategyConfig(); + }; + + private _askRegenerateStrategyConfig = () => { showToast(this, { message: this.hass!.localize("ui.panel.lovelace.changed_toast.message"), action: { - action: () => this._refreshConfig(), + action: () => this._regenerateStrategyConfig(), text: this.hass!.localize("ui.common.refresh"), }, duration: -1, - id: "entity-registry-changed", + id: "regenerate-strategy-config", dismissable: false, }); }; - private async _refreshConfig() { + private async _regenerateStrategyConfig() { if (!this.hass || !this.lovelace) { return; } diff --git a/src/panels/lovelace/views/hui-view.ts b/src/panels/lovelace/views/hui-view.ts index 12d10e3214..6c93cb2124 100644 --- a/src/panels/lovelace/views/hui-view.ts +++ b/src/panels/lovelace/views/hui-view.ts @@ -3,7 +3,9 @@ import type { PropertyValues } from "lit"; import { ReactiveElement } from "lit"; import { customElement, property, state } from "lit/decorators"; import { storage } from "../../../common/decorators/storage"; -import type { HASSDomEvent } from "../../../common/dom/fire_event"; +import { fireEvent, type HASSDomEvent } from "../../../common/dom/fire_event"; +import { debounce } from "../../../common/util/debounce"; +import { deepEqual } from "../../../common/util/deep-equal"; import "../../../components/entity/ha-state-label-badge"; import "../../../components/ha-svg-icon"; import type { LovelaceViewElement } from "../../../data/lovelace"; @@ -11,7 +13,10 @@ import type { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge"; import { ensureBadgeConfig } from "../../../data/lovelace/config/badge"; import type { LovelaceCardConfig } from "../../../data/lovelace/config/card"; import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section"; -import type { LovelaceViewConfig } from "../../../data/lovelace/config/view"; +import type { + LovelaceViewConfig, + LovelaceViewRawConfig, +} from "../../../data/lovelace/config/view"; import { isStrategyView } from "../../../data/lovelace/config/view"; import type { HomeAssistant } from "../../../types"; import "../badges/hui-badge"; @@ -85,6 +90,10 @@ export class HUIView extends ReactiveElement { private _layoutElement?: LovelaceViewElement; + private _layoutElementConfig?: LovelaceViewConfig; + + private _rendered = false; + @storage({ key: "dashboardCardClipboard", state: false, @@ -145,6 +154,18 @@ export class HUIView extends ReactiveElement { return this; } + connectedCallback(): void { + super.connectedCallback(); + this.updateComplete.then(() => { + this._rendered = true; + }); + } + + disconnectedCallback(): void { + super.disconnectedCallback(); + this._rendered = false; + } + public willUpdate(changedProperties: PropertyValues): void { super.willUpdate(changedProperties); @@ -169,9 +190,62 @@ export class HUIView extends ReactiveElement { oldLovelace.config.views[this.index])) ) { this._initializeConfig(); + return; + } + + if (!changedProperties.has("hass")) { + return; + } + + const oldHass = changedProperties.get("hass") as HomeAssistant | undefined; + const viewConfig = this.lovelace.config.views[this.index]; + if (oldHass && this.hass && this.lovelace && isStrategyView(viewConfig)) { + if ( + oldHass.entities !== this.hass.entities || + oldHass.devices !== this.hass.devices || + oldHass.areas !== this.hass.areas || + oldHass.floors !== this.hass.floors + ) { + if (this.hass.config.state === "RUNNING") { + // If the page is not rendered yet, we can force the refresh + if (this._rendered) { + this._debounceRefreshConfig(false); + } else { + this._refreshConfig(true); + } + } + } } } + private _debounceRefreshConfig = debounce( + (force: boolean) => this._refreshConfig(force), + 200 + ); + + private _refreshConfig = async (force: boolean) => { + if (!this.hass || !this.lovelace) { + return; + } + const viewConfig = this.lovelace.config.views[this.index]; + + if (!isStrategyView(viewConfig)) { + return; + } + + const oldConfig = this._layoutElementConfig; + const newConfig = await this._generateConfig(viewConfig); + + // Don't ask if the config is the same + if (!deepEqual(newConfig, oldConfig)) { + if (force) { + this._setConfig(newConfig, true); + } else { + fireEvent(this, "strategy-config-changed"); + } + } + }; + protected update(changedProperties: PropertyValues) { super.update(changedProperties); @@ -227,20 +301,30 @@ export class HUIView extends ReactiveElement { } } - private async _initializeConfig() { - let viewConfig = this.lovelace.config.views[this.index]; - let isStrategy = false; - - if (isStrategyView(viewConfig)) { - isStrategy = true; - viewConfig = await generateLovelaceViewStrategy(viewConfig, this.hass!); + private async _generateConfig( + config: LovelaceViewRawConfig + ): Promise { + if (isStrategyView(config)) { + const generatedConfig = await generateLovelaceViewStrategy( + config, + this.hass! + ); + return { + ...generatedConfig, + type: getViewType(generatedConfig), + }; } - viewConfig = { - ...viewConfig, - type: getViewType(viewConfig), + return { + ...config, + type: getViewType(config), }; + } + private async _setConfig( + viewConfig: LovelaceViewConfig, + isStrategy: boolean + ) { // Create a new layout element if necessary. let addLayoutElement = false; @@ -248,7 +332,7 @@ export class HUIView extends ReactiveElement { addLayoutElement = true; this._createLayoutElement(viewConfig); } - + this._layoutElementConfig = viewConfig; this._createBadges(viewConfig); this._createCards(viewConfig); this._createSections(viewConfig); @@ -269,6 +353,15 @@ export class HUIView extends ReactiveElement { } } + private async _initializeConfig() { + const rawConfig = this.lovelace.config.views[this.index]; + + const viewConfig = await this._generateConfig(rawConfig); + const isStrategy = isStrategyView(viewConfig); + + this._setConfig(viewConfig, isStrategy); + } + private _createLayoutElement(config: LovelaceViewConfig): void { this._layoutElement = createViewElement(config) as LovelaceViewElement; this._layoutElementType = config.type;