From e0d6aa8778ced365420b08e1785d5758770a3dc8 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 14 Sep 2023 12:08:36 +0200 Subject: [PATCH 1/5] Split view and dashboard strategies --- cast/src/receiver/layout/hc-main.ts | 15 ++- ...gy-strategy.ts => energy-view-strategy.ts} | 15 ++- src/panels/lovelace/ha-panel-lovelace.ts | 45 ++++--- .../lovelace/strategies/get-strategy.ts | 114 ++++++++---------- .../original-states-dashboard-strategy.ts | 20 +++ ...gy.ts => original-states-view-strategy.ts} | 30 ++--- src/panels/lovelace/strategies/types.ts | 20 +++ src/panels/lovelace/views/hui-view.ts | 3 +- 8 files changed, 148 insertions(+), 114 deletions(-) rename src/panels/energy/strategies/{energy-strategy.ts => energy-view-strategy.ts} (91%) create mode 100644 src/panels/lovelace/strategies/original-states-dashboard-strategy.ts rename src/panels/lovelace/strategies/{original-states-strategy.ts => original-states-view-strategy.ts} (71%) create mode 100644 src/panels/lovelace/strategies/types.ts diff --git a/cast/src/receiver/layout/hc-main.ts b/cast/src/receiver/layout/hc-main.ts index de31b9bce12f..3153ff042a54 100644 --- a/cast/src/receiver/layout/hc-main.ts +++ b/cast/src/receiver/layout/hc-main.ts @@ -318,13 +318,16 @@ export class HcMain extends HassElement { "../../../../src/panels/lovelace/strategies/get-strategy" ); this._handleNewLovelaceConfig( - await generateLovelaceDashboardStrategy( - { - hass: this.hass!, - narrow: false, + await generateLovelaceDashboardStrategy({ + hass: this.hass!, + narrow: false, + config: { + strategy: { + type: "original-states", + }, + views: [], }, - "original-states" - ) + }) ); } diff --git a/src/panels/energy/strategies/energy-strategy.ts b/src/panels/energy/strategies/energy-view-strategy.ts similarity index 91% rename from src/panels/energy/strategies/energy-strategy.ts rename to src/panels/energy/strategies/energy-view-strategy.ts index 42b0c6678a2e..4bc385e0216e 100644 --- a/src/panels/energy/strategies/energy-strategy.ts +++ b/src/panels/energy/strategies/energy-view-strategy.ts @@ -1,10 +1,12 @@ +import { ReactiveElement } from "lit"; +import { customElement } from "lit/decorators"; import { EnergyPreferences, getEnergyPreferences, GridSourceTypeEnergyPreference, } from "../../../data/energy"; import { LovelaceViewConfig } from "../../../data/lovelace"; -import { LovelaceViewStrategy } from "../../lovelace/strategies/get-strategy"; +import { LovelaceStrategyInfo } from "../../lovelace/strategies/types"; const setupWizard = async (): Promise => { await import("../cards/energy-setup-wizard-card"); @@ -18,10 +20,11 @@ const setupWizard = async (): Promise => { }; }; -export class EnergyStrategy { - static async generateView( - info: Parameters[0] - ): ReturnType { +@customElement("energy-view-strategy") +export class EnergyViewStrategy extends ReactiveElement { + static async generate( + info: LovelaceStrategyInfo + ): Promise { const hass = info.hass; const view: LovelaceViewConfig = { cards: [] }; @@ -56,7 +59,7 @@ export class EnergyStrategy { (source) => source.type === "water" ); - if (info.narrow || info.view.strategy?.options?.show_date_selection) { + if (info.narrow || info.config.strategy?.options?.show_date_selection) { view.cards!.push({ type: "energy-date-selection", collection_key: "energy_dashboard", diff --git a/src/panels/lovelace/ha-panel-lovelace.ts b/src/panels/lovelace/ha-panel-lovelace.ts index 40c2a678f054..d954a13c8a5e 100644 --- a/src/panels/lovelace/ha-panel-lovelace.ts +++ b/src/panels/lovelace/ha-panel-lovelace.ts @@ -163,13 +163,16 @@ export class LovelacePanel extends LitElement { } private async _regenerateConfig() { - const conf = await generateLovelaceDashboardStrategy( - { - hass: this.hass!, - narrow: this.narrow, + const conf = await generateLovelaceDashboardStrategy({ + hass: this.hass!, + narrow: this.narrow, + config: { + strategy: { + type: DEFAULT_STRATEGY, + }, + views: [], }, - DEFAULT_STRATEGY - ); + }); this._setLovelaceConfig(conf, undefined, "generated"); this._panelState = "loaded"; } @@ -272,13 +275,16 @@ export class LovelacePanel extends LitElement { this._errorMsg = err.message; return; } - conf = await generateLovelaceDashboardStrategy( - { - hass: this.hass!, - narrow: this.narrow, + conf = await generateLovelaceDashboardStrategy({ + hass: this.hass!, + narrow: this.narrow, + config: { + strategy: { + type: DEFAULT_STRATEGY, + }, + views: [], }, - DEFAULT_STRATEGY - ); + }); confMode = "generated"; } finally { // Ignore updates for another 2 seconds. @@ -400,13 +406,16 @@ export class LovelacePanel extends LitElement { } = this.lovelace!; try { // Optimistic update - const generatedConf = await generateLovelaceDashboardStrategy( - { - hass: this.hass!, - narrow: this.narrow, + const generatedConf = await generateLovelaceDashboardStrategy({ + hass: this.hass!, + narrow: this.narrow, + config: { + strategy: { + type: DEFAULT_STRATEGY, + }, + views: [], }, - DEFAULT_STRATEGY - ); + }); this._updateLovelace({ config: generatedConf, rawConfig: undefined, diff --git a/src/panels/lovelace/strategies/get-strategy.ts b/src/panels/lovelace/strategies/get-strategy.ts index 59ae47b141a7..6359856034d7 100644 --- a/src/panels/lovelace/strategies/get-strategy.ts +++ b/src/panels/lovelace/strategies/get-strategy.ts @@ -1,50 +1,52 @@ import { LovelaceConfig, LovelaceViewConfig } from "../../../data/lovelace"; -import { AsyncReturnType, HomeAssistant } from "../../../types"; +import { AsyncReturnType } from "../../../types"; +import { + LovelaceDashboardStrategy, + LovelaceStrategy, + LovelaceStrategyConfigType, + LovelaceStrategyInfo, + LovelaceViewStrategy, +} from "./types"; const MAX_WAIT_STRATEGY_LOAD = 5000; const CUSTOM_PREFIX = "custom:"; -export interface LovelaceDashboardStrategy { - generateDashboard(info: { - config?: LovelaceConfig; - hass: HomeAssistant; - narrow: boolean | undefined; - }): Promise; -} - -export interface LovelaceViewStrategy { - generateView(info: { - view: LovelaceViewConfig; - config: LovelaceConfig; - hass: HomeAssistant; - narrow: boolean | undefined; - }): Promise; -} - -const strategies: Record< - string, - () => Promise -> = { - "original-states": async () => - (await import("./original-states-strategy")).OriginalStatesStrategy, - energy: async () => - (await import("../../energy/strategies/energy-strategy")).EnergyStrategy, +const STRATEGIES: Record> = { + dashboard: { + "original-states": () => import("./original-states-dashboard-strategy"), + }, + view: { + "original-states": () => import("./original-states-view-strategy"), + energy: () => import("../../energy/strategies/energy-view-strategy"), + }, }; -const getLovelaceStrategy = async < - T extends LovelaceDashboardStrategy | LovelaceViewStrategy, ->( +type Strategies = { + dashboard: LovelaceDashboardStrategy; + view: LovelaceViewStrategy; +}; + +type StrategyConfig = AsyncReturnType< + Strategies[T]["generate"] +>; + +const getLovelaceStrategy = async ( + configType: T, strategyType: string -): Promise => { - if (strategyType in strategies) { - return (await strategies[strategyType]()) as T; +): Promise => { + if (strategyType in STRATEGIES[configType]) { + await STRATEGIES[configType][strategyType](); + const tag = `${strategyType}-${configType}-strategy`; + return customElements.get(tag) as unknown as Strategies[T]; } if (!strategyType.startsWith(CUSTOM_PREFIX)) { throw new Error("Unknown strategy"); } - const tag = `ll-strategy-${strategyType.substr(CUSTOM_PREFIX.length)}`; + const tag = `ll-strategy-${configType}-${strategyType.slice( + CUSTOM_PREFIX.length + )}`; if ( (await Promise.race([ @@ -59,29 +61,24 @@ const getLovelaceStrategy = async < ); } - return customElements.get(tag) as unknown as T; + return customElements.get(tag) as unknown as Strategies[T]; }; -interface GenerateMethods { - generateDashboard: LovelaceDashboardStrategy["generateDashboard"]; - generateView: LovelaceViewStrategy["generateView"]; -} - -const generateStrategy = async ( - generateMethod: T, - renderError: (err: string | Error) => AsyncReturnType, - info: Parameters[0], +const generateStrategy = async ( + configType: T, + renderError: (err: string | Error) => StrategyConfig, + info: LovelaceStrategyInfo>, strategyType: string | undefined -): Promise> => { +): Promise> => { if (!strategyType) { // @ts-ignore return renderError("No strategy type found"); } try { - const strategy = (await getLovelaceStrategy(strategyType)) as any; + const strategy = await getLovelaceStrategy(configType, strategyType); // eslint-disable-next-line @typescript-eslint/return-await - return await strategy[generateMethod](info); + return await strategy.generate(info); } catch (err: any) { if (err.message !== "timeout") { // eslint-disable-next-line @@ -93,11 +90,10 @@ const generateStrategy = async ( }; export const generateLovelaceDashboardStrategy = async ( - info: Parameters[0], - strategyType?: string -): ReturnType => + info: LovelaceStrategyInfo +): Promise => generateStrategy( - "generateDashboard", + "dashboard", (err) => ({ views: [ { @@ -112,15 +108,14 @@ export const generateLovelaceDashboardStrategy = async ( ], }), info, - strategyType || info.config?.strategy?.type + info.config?.strategy?.type ); export const generateLovelaceViewStrategy = async ( - info: Parameters[0], - strategyType?: string -): ReturnType => + info: LovelaceStrategyInfo +): Promise => generateStrategy( - "generateView", + "view", (err) => ({ cards: [ { @@ -130,16 +125,14 @@ export const generateLovelaceViewStrategy = async ( ], }), info, - strategyType || info.view?.strategy?.type + info.config?.strategy?.type ); /** * Find all references to strategies and replaces them with the generated output */ export const expandLovelaceConfigStrategies = async ( - info: Parameters[0] & { - config: LovelaceConfig; - } + info: LovelaceStrategyInfo ): Promise => { const config = info.config.strategy ? await generateLovelaceDashboardStrategy(info) @@ -151,8 +144,7 @@ export const expandLovelaceConfigStrategies = async ( ? generateLovelaceViewStrategy({ hass: info.hass, narrow: info.narrow, - config, - view, + config: view, }) : view ) diff --git a/src/panels/lovelace/strategies/original-states-dashboard-strategy.ts b/src/panels/lovelace/strategies/original-states-dashboard-strategy.ts new file mode 100644 index 000000000000..ecd694445f12 --- /dev/null +++ b/src/panels/lovelace/strategies/original-states-dashboard-strategy.ts @@ -0,0 +1,20 @@ +import { ReactiveElement } from "lit"; +import { customElement } from "lit/decorators"; +import { LovelaceConfig } from "../../../data/lovelace"; +import { LovelaceStrategyInfo } from "./types"; + +@customElement("original-states-dashboard-strategy") +export class OriginalStatesDashboardStrategy extends ReactiveElement { + static async generate( + info: LovelaceStrategyInfo + ): Promise { + return { + title: info.hass.config.location_name, + views: [ + { + strategy: { type: "original-states" }, + }, + ], + }; + } +} diff --git a/src/panels/lovelace/strategies/original-states-strategy.ts b/src/panels/lovelace/strategies/original-states-view-strategy.ts similarity index 71% rename from src/panels/lovelace/strategies/original-states-strategy.ts rename to src/panels/lovelace/strategies/original-states-view-strategy.ts index 7c538d5fc14a..87c349990848 100644 --- a/src/panels/lovelace/strategies/original-states-strategy.ts +++ b/src/panels/lovelace/strategies/original-states-view-strategy.ts @@ -1,16 +1,17 @@ import { STATE_NOT_RUNNING } from "home-assistant-js-websocket"; +import { ReactiveElement } from "lit"; +import { customElement } from "lit/decorators"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { getEnergyPreferences } from "../../../data/energy"; import { generateDefaultViewConfig } from "../common/generate-lovelace-config"; -import { - LovelaceDashboardStrategy, - LovelaceViewStrategy, -} from "./get-strategy"; +import { LovelaceStrategyInfo } from "./types"; +import { LovelaceViewConfig } from "../../../data/lovelace"; -export class OriginalStatesStrategy { - static async generateView( - info: Parameters[0] - ): ReturnType { +@customElement("original-states-view-strategy") +export class OriginalStatesViewStrategy extends ReactiveElement { + static async generate( + info: LovelaceStrategyInfo + ): Promise { const hass = info.hass; if (hass.config.state === STATE_NOT_RUNNING) { @@ -63,17 +64,4 @@ export class OriginalStatesStrategy { return view; } - - static async generateDashboard( - info: Parameters[0] - ): ReturnType { - return { - title: info.hass.config.location_name, - views: [ - { - strategy: { type: "original-states" }, - }, - ], - }; - } } diff --git a/src/panels/lovelace/strategies/types.ts b/src/panels/lovelace/strategies/types.ts new file mode 100644 index 000000000000..d5d11a528b29 --- /dev/null +++ b/src/panels/lovelace/strategies/types.ts @@ -0,0 +1,20 @@ +import { LovelaceConfig, LovelaceViewConfig } from "../../../data/lovelace"; +import { HomeAssistant } from "../../../types"; + +export type LovelaceStrategyConfigType = "dashboard" | "view"; + +export type LovelaceStrategyInfo = { + config: T; + hass: HomeAssistant; + narrow: boolean | undefined; +}; + +export type LovelaceStrategy = { + generate(info: LovelaceStrategyInfo): Promise; +}; + +export interface LovelaceDashboardStrategy + extends LovelaceStrategy {} + +export interface LovelaceViewStrategy + extends LovelaceStrategy {} diff --git a/src/panels/lovelace/views/hui-view.ts b/src/panels/lovelace/views/hui-view.ts index 579a75128fcc..6b24117534cb 100644 --- a/src/panels/lovelace/views/hui-view.ts +++ b/src/panels/lovelace/views/hui-view.ts @@ -192,9 +192,8 @@ export class HUIView extends ReactiveElement { isStrategy = true; viewConfig = await generateLovelaceViewStrategy({ hass: this.hass, - config: this.lovelace.config, narrow: this.narrow, - view: viewConfig, + config: viewConfig, }); } From f7c929c4395830908275a00606db899a8892788e Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 14 Sep 2023 14:22:07 +0200 Subject: [PATCH 2/5] Simplify strategies params --- cast/src/receiver/layout/hc-main.ts | 17 +++-- .../energy/strategies/energy-view-strategy.ts | 18 +++-- .../lovelace/editor/hui-dialog-save-config.ts | 6 +- src/panels/lovelace/ha-panel-lovelace.ts | 65 ++++++++----------- .../lovelace/strategies/get-strategy.ts | 53 ++++++++------- .../original-states-dashboard-strategy.ts | 9 ++- .../original-states-view-strategy.ts | 11 ++-- src/panels/lovelace/strategies/types.ts | 17 +++-- src/panels/lovelace/views/hui-view.ts | 10 +-- 9 files changed, 110 insertions(+), 96 deletions(-) diff --git a/cast/src/receiver/layout/hc-main.ts b/cast/src/receiver/layout/hc-main.ts index 3153ff042a54..341c4e0fe18d 100644 --- a/cast/src/receiver/layout/hc-main.ts +++ b/cast/src/receiver/layout/hc-main.ts @@ -32,6 +32,8 @@ import { HassElement } from "../../../../src/state/hass-element"; import { castContext } from "../cast_context"; import "./hc-launch-screen"; +const DEFAULT_STRATEGY = "original-states"; + let resourcesLoaded = false; @customElement("hc-main") export class HcMain extends HassElement { @@ -318,16 +320,13 @@ export class HcMain extends HassElement { "../../../../src/panels/lovelace/strategies/get-strategy" ); this._handleNewLovelaceConfig( - await generateLovelaceDashboardStrategy({ - hass: this.hass!, - narrow: false, - config: { - strategy: { - type: "original-states", - }, - views: [], + await generateLovelaceDashboardStrategy( + { + type: DEFAULT_STRATEGY, }, - }) + this.hass!, + { narrow: false } + ) ); } diff --git a/src/panels/energy/strategies/energy-view-strategy.ts b/src/panels/energy/strategies/energy-view-strategy.ts index 4bc385e0216e..538955b6892c 100644 --- a/src/panels/energy/strategies/energy-view-strategy.ts +++ b/src/panels/energy/strategies/energy-view-strategy.ts @@ -6,7 +6,11 @@ import { GridSourceTypeEnergyPreference, } from "../../../data/energy"; import { LovelaceViewConfig } from "../../../data/lovelace"; -import { LovelaceStrategyInfo } from "../../lovelace/strategies/types"; +import { HomeAssistant } from "../../../types"; +import { + LovelaceStrategyConfig, + LovelaceStrategyParams, +} from "../../lovelace/strategies/types"; const setupWizard = async (): Promise => { await import("../cards/energy-setup-wizard-card"); @@ -20,13 +24,17 @@ const setupWizard = async (): Promise => { }; }; +export type EnergeryViewStrategyOptions = { + show_date_selection?: boolean; +}; + @customElement("energy-view-strategy") export class EnergyViewStrategy extends ReactiveElement { static async generate( - info: LovelaceStrategyInfo + config: LovelaceStrategyConfig, + hass: HomeAssistant, + params: LovelaceStrategyParams ): Promise { - const hass = info.hass; - const view: LovelaceViewConfig = { cards: [] }; let prefs: EnergyPreferences; @@ -59,7 +67,7 @@ export class EnergyViewStrategy extends ReactiveElement { (source) => source.type === "water" ); - if (info.narrow || info.config.strategy?.options?.show_date_selection) { + if (params.narrow || config.options?.show_date_selection) { view.cards!.push({ type: "energy-date-selection", collection_key: "energy_dashboard", diff --git a/src/panels/lovelace/editor/hui-dialog-save-config.ts b/src/panels/lovelace/editor/hui-dialog-save-config.ts index 06c0dcfd0e07..008014e50237 100644 --- a/src/panels/lovelace/editor/hui-dialog-save-config.ts +++ b/src/panels/lovelace/editor/hui-dialog-save-config.ts @@ -174,10 +174,8 @@ export class HuiSaveConfig extends LitElement implements HassDialog { await lovelace.saveConfig( this._emptyConfig ? EMPTY_CONFIG - : await expandLovelaceConfigStrategies({ - config: lovelace.config, - hass: this.hass!, - narrow: this._params!.narrow, + : await expandLovelaceConfigStrategies(lovelace.config, this.hass, { + narrow: this._params.narrow, }) ); lovelace.setEditMode(true); diff --git a/src/panels/lovelace/ha-panel-lovelace.ts b/src/panels/lovelace/ha-panel-lovelace.ts index d954a13c8a5e..587a9746c488 100644 --- a/src/panels/lovelace/ha-panel-lovelace.ts +++ b/src/panels/lovelace/ha-panel-lovelace.ts @@ -163,16 +163,13 @@ export class LovelacePanel extends LitElement { } private async _regenerateConfig() { - const conf = await generateLovelaceDashboardStrategy({ - hass: this.hass!, - narrow: this.narrow, - config: { - strategy: { - type: DEFAULT_STRATEGY, - }, - views: [], + const conf = await generateLovelaceDashboardStrategy( + { + type: DEFAULT_STRATEGY, }, - }); + this.hass!, + { narrow: this.narrow } + ); this._setLovelaceConfig(conf, undefined, "generated"); this._panelState = "loaded"; } @@ -259,11 +256,11 @@ export class LovelacePanel extends LitElement { // If strategy defined, apply it here. if (rawConf.strategy) { - conf = await generateLovelaceDashboardStrategy({ - config: rawConf, - hass: this.hass!, - narrow: this.narrow, - }); + conf = await generateLovelaceDashboardStrategy( + rawConf.strategy, + this.hass!, + { narrow: this.narrow } + ); } else { conf = rawConf; } @@ -275,16 +272,13 @@ export class LovelacePanel extends LitElement { this._errorMsg = err.message; return; } - conf = await generateLovelaceDashboardStrategy({ - hass: this.hass!, - narrow: this.narrow, - config: { - strategy: { - type: DEFAULT_STRATEGY, - }, - views: [], + conf = await generateLovelaceDashboardStrategy( + { + type: DEFAULT_STRATEGY, }, - }); + this.hass!, + { narrow: this.narrow } + ); confMode = "generated"; } finally { // Ignore updates for another 2 seconds. @@ -369,11 +363,11 @@ export class LovelacePanel extends LitElement { let conf: LovelaceConfig; // If strategy defined, apply it here. if (newConfig.strategy) { - conf = await generateLovelaceDashboardStrategy({ - config: newConfig, - hass: this.hass!, - narrow: this.narrow, - }); + conf = await generateLovelaceDashboardStrategy( + newConfig.strategy, + this.hass!, + { narrow: this.narrow } + ); } else { conf = newConfig; } @@ -406,16 +400,13 @@ export class LovelacePanel extends LitElement { } = this.lovelace!; try { // Optimistic update - const generatedConf = await generateLovelaceDashboardStrategy({ - hass: this.hass!, - narrow: this.narrow, - config: { - strategy: { - type: DEFAULT_STRATEGY, - }, - views: [], + const generatedConf = await generateLovelaceDashboardStrategy( + { + type: DEFAULT_STRATEGY, }, - }); + this.hass!, + { narrow: this.narrow } + ); this._updateLovelace({ config: generatedConf, rawConfig: undefined, diff --git a/src/panels/lovelace/strategies/get-strategy.ts b/src/panels/lovelace/strategies/get-strategy.ts index 6359856034d7..0fcf2c4dc7e9 100644 --- a/src/panels/lovelace/strategies/get-strategy.ts +++ b/src/panels/lovelace/strategies/get-strategy.ts @@ -1,10 +1,11 @@ import { LovelaceConfig, LovelaceViewConfig } from "../../../data/lovelace"; -import { AsyncReturnType } from "../../../types"; +import { AsyncReturnType, HomeAssistant } from "../../../types"; import { LovelaceDashboardStrategy, LovelaceStrategy, + LovelaceStrategyConfig, LovelaceStrategyConfigType, - LovelaceStrategyInfo, + LovelaceStrategyParams, LovelaceViewStrategy, } from "./types"; @@ -67,9 +68,11 @@ const getLovelaceStrategy = async ( const generateStrategy = async ( configType: T, renderError: (err: string | Error) => StrategyConfig, - info: LovelaceStrategyInfo>, - strategyType: string | undefined + strategyConfig: LovelaceStrategyConfig, + hass: HomeAssistant, + params: LovelaceStrategyParams ): Promise> => { + const strategyType = strategyConfig.type; if (!strategyType) { // @ts-ignore return renderError("No strategy type found"); @@ -78,7 +81,7 @@ const generateStrategy = async ( try { const strategy = await getLovelaceStrategy(configType, strategyType); // eslint-disable-next-line @typescript-eslint/return-await - return await strategy.generate(info); + return await strategy.generate(strategyConfig, hass, params); } catch (err: any) { if (err.message !== "timeout") { // eslint-disable-next-line @@ -90,7 +93,9 @@ const generateStrategy = async ( }; export const generateLovelaceDashboardStrategy = async ( - info: LovelaceStrategyInfo + strategyConfig: LovelaceStrategyConfig, + hass: HomeAssistant, + params: LovelaceStrategyParams ): Promise => generateStrategy( "dashboard", @@ -107,12 +112,15 @@ export const generateLovelaceDashboardStrategy = async ( }, ], }), - info, - info.config?.strategy?.type + strategyConfig, + hass, + params ); export const generateLovelaceViewStrategy = async ( - info: LovelaceStrategyInfo + strategyConfig: LovelaceStrategyConfig, + hass: HomeAssistant, + params: LovelaceStrategyParams ): Promise => generateStrategy( "view", @@ -124,31 +132,30 @@ export const generateLovelaceViewStrategy = async ( }, ], }), - info, - info.config?.strategy?.type + strategyConfig, + hass, + params ); /** * Find all references to strategies and replaces them with the generated output */ export const expandLovelaceConfigStrategies = async ( - info: LovelaceStrategyInfo + config: LovelaceConfig, + hass: HomeAssistant, + params: LovelaceStrategyParams ): Promise => { - const config = info.config.strategy - ? await generateLovelaceDashboardStrategy(info) - : { ...info.config }; + const newConfig = config.strategy + ? await generateLovelaceDashboardStrategy(config.strategy, hass, params) + : { ...config }; - config.views = await Promise.all( - config.views.map((view) => + newConfig.views = await Promise.all( + newConfig.views.map((view) => view.strategy - ? generateLovelaceViewStrategy({ - hass: info.hass, - narrow: info.narrow, - config: view, - }) + ? generateLovelaceViewStrategy(view.strategy, hass, params) : view ) ); - return config; + return newConfig; }; diff --git a/src/panels/lovelace/strategies/original-states-dashboard-strategy.ts b/src/panels/lovelace/strategies/original-states-dashboard-strategy.ts index ecd694445f12..c14cb62e7e47 100644 --- a/src/panels/lovelace/strategies/original-states-dashboard-strategy.ts +++ b/src/panels/lovelace/strategies/original-states-dashboard-strategy.ts @@ -1,15 +1,18 @@ import { ReactiveElement } from "lit"; import { customElement } from "lit/decorators"; import { LovelaceConfig } from "../../../data/lovelace"; -import { LovelaceStrategyInfo } from "./types"; +import { HomeAssistant } from "../../../types"; +import { LovelaceStrategyConfig, LovelaceStrategyParams } from "./types"; @customElement("original-states-dashboard-strategy") export class OriginalStatesDashboardStrategy extends ReactiveElement { static async generate( - info: LovelaceStrategyInfo + _config: LovelaceStrategyConfig, + hass: HomeAssistant, + _params?: LovelaceStrategyParams ): Promise { return { - title: info.hass.config.location_name, + title: hass.config.location_name, views: [ { strategy: { type: "original-states" }, diff --git a/src/panels/lovelace/strategies/original-states-view-strategy.ts b/src/panels/lovelace/strategies/original-states-view-strategy.ts index 87c349990848..e8a85953de18 100644 --- a/src/panels/lovelace/strategies/original-states-view-strategy.ts +++ b/src/panels/lovelace/strategies/original-states-view-strategy.ts @@ -3,17 +3,18 @@ import { ReactiveElement } from "lit"; import { customElement } from "lit/decorators"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { getEnergyPreferences } from "../../../data/energy"; -import { generateDefaultViewConfig } from "../common/generate-lovelace-config"; -import { LovelaceStrategyInfo } from "./types"; import { LovelaceViewConfig } from "../../../data/lovelace"; +import { HomeAssistant } from "../../../types"; +import { generateDefaultViewConfig } from "../common/generate-lovelace-config"; +import { LovelaceStrategyConfig, LovelaceStrategyParams } from "./types"; @customElement("original-states-view-strategy") export class OriginalStatesViewStrategy extends ReactiveElement { static async generate( - info: LovelaceStrategyInfo + _config: LovelaceStrategyConfig, + hass: HomeAssistant, + _params?: LovelaceStrategyParams ): Promise { - const hass = info.hass; - if (hass.config.state === STATE_NOT_RUNNING) { return { cards: [{ type: "starting" }], diff --git a/src/panels/lovelace/strategies/types.ts b/src/panels/lovelace/strategies/types.ts index d5d11a528b29..d7e4f32ea54d 100644 --- a/src/panels/lovelace/strategies/types.ts +++ b/src/panels/lovelace/strategies/types.ts @@ -3,14 +3,21 @@ import { HomeAssistant } from "../../../types"; export type LovelaceStrategyConfigType = "dashboard" | "view"; -export type LovelaceStrategyInfo = { - config: T; - hass: HomeAssistant; - narrow: boolean | undefined; +export type LovelaceStrategyConfig> = { + type: string; + options?: T; +}; + +export type LovelaceStrategyParams = { + narrow?: boolean; }; export type LovelaceStrategy = { - generate(info: LovelaceStrategyInfo): Promise; + generate( + config: LovelaceStrategyConfig, + hass: HomeAssistant, + params?: LovelaceStrategyParams + ): Promise; }; export interface LovelaceDashboardStrategy diff --git a/src/panels/lovelace/views/hui-view.ts b/src/panels/lovelace/views/hui-view.ts index 6b24117534cb..e00e5af00e1b 100644 --- a/src/panels/lovelace/views/hui-view.ts +++ b/src/panels/lovelace/views/hui-view.ts @@ -190,11 +190,11 @@ export class HUIView extends ReactiveElement { if (viewConfig.strategy) { isStrategy = true; - viewConfig = await generateLovelaceViewStrategy({ - hass: this.hass, - narrow: this.narrow, - config: viewConfig, - }); + viewConfig = await generateLovelaceViewStrategy( + viewConfig.strategy, + this.hass!, + { narrow: this.narrow } + ); } viewConfig = { From 7dca4d21f45acf31b05e41a0e09dc3b66c631498 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 14 Sep 2023 15:50:29 +0200 Subject: [PATCH 3/5] Add backward compatibility for custom strategies --- .../lovelace/strategies/get-strategy.ts | 27 +++++++++++++++++-- .../lovelace/strategies/legacy-strategy.ts | 24 +++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 src/panels/lovelace/strategies/legacy-strategy.ts diff --git a/src/panels/lovelace/strategies/get-strategy.ts b/src/panels/lovelace/strategies/get-strategy.ts index 0fcf2c4dc7e9..724305cb84a9 100644 --- a/src/panels/lovelace/strategies/get-strategy.ts +++ b/src/panels/lovelace/strategies/get-strategy.ts @@ -1,5 +1,6 @@ import { LovelaceConfig, LovelaceViewConfig } from "../../../data/lovelace"; import { AsyncReturnType, HomeAssistant } from "../../../types"; +import { isLegacyStrategy } from "./legacy-strategy"; import { LovelaceDashboardStrategy, LovelaceStrategy, @@ -45,12 +46,14 @@ const getLovelaceStrategy = async ( throw new Error("Unknown strategy"); } + const legacyTag = `ll-strategy-${strategyType.slice(CUSTOM_PREFIX.length)}`; const tag = `ll-strategy-${configType}-${strategyType.slice( CUSTOM_PREFIX.length )}`; if ( (await Promise.race([ + customElements.whenDefined(legacyTag), customElements.whenDefined(tag), new Promise((resolve) => { setTimeout(() => resolve(true), MAX_WAIT_STRATEGY_LOAD); @@ -62,7 +65,8 @@ const getLovelaceStrategy = async ( ); } - return customElements.get(tag) as unknown as Strategies[T]; + return (customElements.get(tag) ?? + customElements.get(legacyTag)) as unknown as Strategies[T]; }; const generateStrategy = async ( @@ -80,7 +84,26 @@ const generateStrategy = async ( try { const strategy = await getLovelaceStrategy(configType, strategyType); - // eslint-disable-next-line @typescript-eslint/return-await + + // Backward compatibility for custom strategies for loading old strategies format + if (isLegacyStrategy(strategy)) { + if (configType === "dashboard" && "generateDashboard" in strategy) { + return (await strategy.generateDashboard({ + config: { strategy: strategyConfig, views: [] }, + hass, + narrow: params.narrow, + })) as StrategyConfig; + } + if (configType === "view" && "generateView" in strategy) { + return (await strategy.generateView({ + config: { views: [] }, + view: { strategy: strategyConfig }, + hass, + narrow: params.narrow, + })) as StrategyConfig; + } + } + return await strategy.generate(strategyConfig, hass, params); } catch (err: any) { if (err.message !== "timeout") { diff --git a/src/panels/lovelace/strategies/legacy-strategy.ts b/src/panels/lovelace/strategies/legacy-strategy.ts new file mode 100644 index 000000000000..d3618cf71b36 --- /dev/null +++ b/src/panels/lovelace/strategies/legacy-strategy.ts @@ -0,0 +1,24 @@ +import { LovelaceConfig, LovelaceViewConfig } from "../../../data/lovelace"; +import { HomeAssistant } from "../../../types"; + +export const isLegacyStrategy = ( + strategy: any +): strategy is LovelaceDashboardStrategy | LovelaceViewStrategy => + !("generate" in strategy); + +export interface LovelaceDashboardStrategy { + generateDashboard(info: { + config?: LovelaceConfig; + hass: HomeAssistant; + narrow: boolean | undefined; + }): Promise; +} + +export interface LovelaceViewStrategy { + generateView(info: { + view: LovelaceViewConfig; + config: LovelaceConfig; + hass: HomeAssistant; + narrow: boolean | undefined; + }): Promise; +} From ddb8971d9a3811cc00671b9d9dfe7f23ce4c38e6 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Tue, 19 Sep 2023 11:26:08 +0200 Subject: [PATCH 4/5] Improve typings --- src/data/lovelace.ts | 15 +++++++-------- .../energy/strategies/energy-view-strategy.ts | 8 ++++---- src/panels/lovelace/strategies/get-strategy.ts | 10 +++++++--- .../original-states-dashboard-strategy.ts | 4 ++-- .../strategies/original-states-view-strategy.ts | 7 +++++-- src/panels/lovelace/strategies/types.ts | 13 +++++-------- 6 files changed, 30 insertions(+), 27 deletions(-) diff --git a/src/data/lovelace.ts b/src/data/lovelace.ts index 091ced7c7b89..3942d2763c1d 100644 --- a/src/data/lovelace.ts +++ b/src/data/lovelace.ts @@ -17,12 +17,14 @@ export interface LovelacePanelConfig { mode: "yaml" | "storage"; } +export type LovelaceStrategyConfig> = { + type: string; + options?: T; +}; + export interface LovelaceConfig { title?: string; - strategy?: { - type: string; - options?: Record; - }; + strategy?: LovelaceStrategyConfig; views: LovelaceViewConfig[]; background?: string; } @@ -81,10 +83,7 @@ export interface LovelaceViewConfig { index?: number; title?: string; type?: string; - strategy?: { - type: string; - options?: Record; - }; + strategy?: LovelaceStrategyConfig; badges?: Array; cards?: LovelaceCardConfig[]; path?: string; diff --git a/src/panels/energy/strategies/energy-view-strategy.ts b/src/panels/energy/strategies/energy-view-strategy.ts index 538955b6892c..1f2d539fac40 100644 --- a/src/panels/energy/strategies/energy-view-strategy.ts +++ b/src/panels/energy/strategies/energy-view-strategy.ts @@ -5,12 +5,12 @@ import { getEnergyPreferences, GridSourceTypeEnergyPreference, } from "../../../data/energy"; -import { LovelaceViewConfig } from "../../../data/lovelace"; -import { HomeAssistant } from "../../../types"; import { LovelaceStrategyConfig, - LovelaceStrategyParams, -} from "../../lovelace/strategies/types"; + LovelaceViewConfig, +} from "../../../data/lovelace"; +import { HomeAssistant } from "../../../types"; +import { LovelaceStrategyParams } from "../../lovelace/strategies/types"; const setupWizard = async (): Promise => { await import("../cards/energy-setup-wizard-card"); diff --git a/src/panels/lovelace/strategies/get-strategy.ts b/src/panels/lovelace/strategies/get-strategy.ts index 724305cb84a9..df697f5bc250 100644 --- a/src/panels/lovelace/strategies/get-strategy.ts +++ b/src/panels/lovelace/strategies/get-strategy.ts @@ -1,11 +1,13 @@ -import { LovelaceConfig, LovelaceViewConfig } from "../../../data/lovelace"; +import { + LovelaceConfig, + LovelaceStrategyConfig, + LovelaceViewConfig, +} from "../../../data/lovelace"; import { AsyncReturnType, HomeAssistant } from "../../../types"; import { isLegacyStrategy } from "./legacy-strategy"; import { LovelaceDashboardStrategy, LovelaceStrategy, - LovelaceStrategyConfig, - LovelaceStrategyConfigType, LovelaceStrategyParams, LovelaceViewStrategy, } from "./types"; @@ -23,6 +25,8 @@ const STRATEGIES: Record> = { }, }; +export type LovelaceStrategyConfigType = "dashboard" | "view"; + type Strategies = { dashboard: LovelaceDashboardStrategy; view: LovelaceViewStrategy; diff --git a/src/panels/lovelace/strategies/original-states-dashboard-strategy.ts b/src/panels/lovelace/strategies/original-states-dashboard-strategy.ts index c14cb62e7e47..99b5366ac1c2 100644 --- a/src/panels/lovelace/strategies/original-states-dashboard-strategy.ts +++ b/src/panels/lovelace/strategies/original-states-dashboard-strategy.ts @@ -1,8 +1,8 @@ import { ReactiveElement } from "lit"; import { customElement } from "lit/decorators"; -import { LovelaceConfig } from "../../../data/lovelace"; +import { LovelaceConfig, LovelaceStrategyConfig } from "../../../data/lovelace"; import { HomeAssistant } from "../../../types"; -import { LovelaceStrategyConfig, LovelaceStrategyParams } from "./types"; +import { LovelaceStrategyParams } from "./types"; @customElement("original-states-dashboard-strategy") export class OriginalStatesDashboardStrategy extends ReactiveElement { diff --git a/src/panels/lovelace/strategies/original-states-view-strategy.ts b/src/panels/lovelace/strategies/original-states-view-strategy.ts index e8a85953de18..a6e163f758f2 100644 --- a/src/panels/lovelace/strategies/original-states-view-strategy.ts +++ b/src/panels/lovelace/strategies/original-states-view-strategy.ts @@ -3,10 +3,13 @@ import { ReactiveElement } from "lit"; import { customElement } from "lit/decorators"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { getEnergyPreferences } from "../../../data/energy"; -import { LovelaceViewConfig } from "../../../data/lovelace"; +import { + LovelaceStrategyConfig, + LovelaceViewConfig, +} from "../../../data/lovelace"; import { HomeAssistant } from "../../../types"; import { generateDefaultViewConfig } from "../common/generate-lovelace-config"; -import { LovelaceStrategyConfig, LovelaceStrategyParams } from "./types"; +import { LovelaceStrategyParams } from "./types"; @customElement("original-states-view-strategy") export class OriginalStatesViewStrategy extends ReactiveElement { diff --git a/src/panels/lovelace/strategies/types.ts b/src/panels/lovelace/strategies/types.ts index d7e4f32ea54d..8b807a61e2dc 100644 --- a/src/panels/lovelace/strategies/types.ts +++ b/src/panels/lovelace/strategies/types.ts @@ -1,13 +1,10 @@ -import { LovelaceConfig, LovelaceViewConfig } from "../../../data/lovelace"; +import { + LovelaceConfig, + LovelaceStrategyConfig, + LovelaceViewConfig, +} from "../../../data/lovelace"; import { HomeAssistant } from "../../../types"; -export type LovelaceStrategyConfigType = "dashboard" | "view"; - -export type LovelaceStrategyConfig> = { - type: string; - options?: T; -}; - export type LovelaceStrategyParams = { narrow?: boolean; }; From 584f083552f426cc4ecc76fb9703943357e73c87 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Tue, 19 Sep 2023 16:58:38 +0200 Subject: [PATCH 5/5] Put options at the root level --- cast/src/receiver/layout/hc-main.ts | 2 +- src/data/lovelace.ts | 4 ++-- src/panels/energy/strategies/energy-view-strategy.ts | 8 ++++---- src/panels/lovelace/strategies/get-strategy.ts | 9 ++++++++- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/cast/src/receiver/layout/hc-main.ts b/cast/src/receiver/layout/hc-main.ts index 341c4e0fe18d..7287b024667f 100644 --- a/cast/src/receiver/layout/hc-main.ts +++ b/cast/src/receiver/layout/hc-main.ts @@ -260,7 +260,7 @@ export class HcMain extends HassElement { { strategy: { type: "energy", - options: { show_date_selection: true }, + show_date_selection: true, }, }, ], diff --git a/src/data/lovelace.ts b/src/data/lovelace.ts index 3942d2763c1d..76a2e401e3e7 100644 --- a/src/data/lovelace.ts +++ b/src/data/lovelace.ts @@ -17,9 +17,9 @@ export interface LovelacePanelConfig { mode: "yaml" | "storage"; } -export type LovelaceStrategyConfig> = { +export type LovelaceStrategyConfig = { type: string; - options?: T; + [key: string]: any; }; export interface LovelaceConfig { diff --git a/src/panels/energy/strategies/energy-view-strategy.ts b/src/panels/energy/strategies/energy-view-strategy.ts index 1f2d539fac40..ff10a985f9fc 100644 --- a/src/panels/energy/strategies/energy-view-strategy.ts +++ b/src/panels/energy/strategies/energy-view-strategy.ts @@ -24,14 +24,14 @@ const setupWizard = async (): Promise => { }; }; -export type EnergeryViewStrategyOptions = { +export interface EnergeryViewStrategyConfig extends LovelaceStrategyConfig { show_date_selection?: boolean; -}; +} @customElement("energy-view-strategy") export class EnergyViewStrategy extends ReactiveElement { static async generate( - config: LovelaceStrategyConfig, + config: EnergeryViewStrategyConfig, hass: HomeAssistant, params: LovelaceStrategyParams ): Promise { @@ -67,7 +67,7 @@ export class EnergyViewStrategy extends ReactiveElement { (source) => source.type === "water" ); - if (params.narrow || config.options?.show_date_selection) { + if (params.narrow || config.show_date_selection) { view.cards!.push({ type: "energy-date-selection", collection_key: "energy_dashboard", diff --git a/src/panels/lovelace/strategies/get-strategy.ts b/src/panels/lovelace/strategies/get-strategy.ts index df697f5bc250..4f30b7c17592 100644 --- a/src/panels/lovelace/strategies/get-strategy.ts +++ b/src/panels/lovelace/strategies/get-strategy.ts @@ -108,7 +108,14 @@ const generateStrategy = async ( } } - return await strategy.generate(strategyConfig, hass, params); + const config = { + ...strategyConfig, + ...strategyConfig.options, + }; + + delete config.options; + + return await strategy.generate(config, hass, params); } catch (err: any) { if (err.message !== "timeout") { // eslint-disable-next-line