diff --git a/cast/src/receiver/layout/hc-main.ts b/cast/src/receiver/layout/hc-main.ts index de31b9bce12f..7287b024667f 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 { @@ -258,7 +260,7 @@ export class HcMain extends HassElement { { strategy: { type: "energy", - options: { show_date_selection: true }, + show_date_selection: true, }, }, ], @@ -320,10 +322,10 @@ export class HcMain extends HassElement { this._handleNewLovelaceConfig( await generateLovelaceDashboardStrategy( { - hass: this.hass!, - narrow: false, + type: DEFAULT_STRATEGY, }, - "original-states" + this.hass!, + { narrow: false } ) ); } diff --git a/src/data/lovelace.ts b/src/data/lovelace.ts index 091ced7c7b89..76a2e401e3e7 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; + [key: string]: any; +}; + 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-strategy.ts b/src/panels/energy/strategies/energy-view-strategy.ts similarity index 86% rename from src/panels/energy/strategies/energy-strategy.ts rename to src/panels/energy/strategies/energy-view-strategy.ts index 42b0c6678a2e..ff10a985f9fc 100644 --- a/src/panels/energy/strategies/energy-strategy.ts +++ b/src/panels/energy/strategies/energy-view-strategy.ts @@ -1,10 +1,16 @@ +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 { + LovelaceStrategyConfig, + 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"); @@ -18,12 +24,17 @@ const setupWizard = async (): Promise => { }; }; -export class EnergyStrategy { - static async generateView( - info: Parameters[0] - ): ReturnType { - const hass = info.hass; +export interface EnergeryViewStrategyConfig extends LovelaceStrategyConfig { + show_date_selection?: boolean; +} +@customElement("energy-view-strategy") +export class EnergyViewStrategy extends ReactiveElement { + static async generate( + config: EnergeryViewStrategyConfig, + hass: HomeAssistant, + params: LovelaceStrategyParams + ): Promise { const view: LovelaceViewConfig = { cards: [] }; let prefs: EnergyPreferences; @@ -56,7 +67,7 @@ export class EnergyStrategy { (source) => source.type === "water" ); - if (info.narrow || info.view.strategy?.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/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 40c2a678f054..587a9746c488 100644 --- a/src/panels/lovelace/ha-panel-lovelace.ts +++ b/src/panels/lovelace/ha-panel-lovelace.ts @@ -165,10 +165,10 @@ export class LovelacePanel extends LitElement { private async _regenerateConfig() { const conf = await generateLovelaceDashboardStrategy( { - hass: this.hass!, - narrow: this.narrow, + type: DEFAULT_STRATEGY, }, - DEFAULT_STRATEGY + this.hass!, + { narrow: this.narrow } ); this._setLovelaceConfig(conf, undefined, "generated"); this._panelState = "loaded"; @@ -256,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; } @@ -274,10 +274,10 @@ export class LovelacePanel extends LitElement { } conf = await generateLovelaceDashboardStrategy( { - hass: this.hass!, - narrow: this.narrow, + type: DEFAULT_STRATEGY, }, - DEFAULT_STRATEGY + this.hass!, + { narrow: this.narrow } ); confMode = "generated"; } finally { @@ -363,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; } @@ -402,10 +402,10 @@ export class LovelacePanel extends LitElement { // Optimistic update const generatedConf = await generateLovelaceDashboardStrategy( { - hass: this.hass!, - narrow: this.narrow, + type: DEFAULT_STRATEGY, }, - DEFAULT_STRATEGY + this.hass!, + { narrow: this.narrow } ); this._updateLovelace({ config: generatedConf, diff --git a/src/panels/lovelace/strategies/get-strategy.ts b/src/panels/lovelace/strategies/get-strategy.ts index 59ae47b141a7..4f30b7c17592 100644 --- a/src/panels/lovelace/strategies/get-strategy.ts +++ b/src/panels/lovelace/strategies/get-strategy.ts @@ -1,53 +1,63 @@ -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, + LovelaceStrategyParams, + 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, ->( +export type LovelaceStrategyConfigType = "dashboard" | "view"; + +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 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); @@ -59,29 +69,53 @@ const getLovelaceStrategy = async < ); } - return customElements.get(tag) as unknown as T; + return (customElements.get(tag) ?? + customElements.get(legacyTag)) 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], - strategyType: string | undefined -): Promise> => { +const generateStrategy = async ( + configType: T, + renderError: (err: string | Error) => StrategyConfig, + strategyConfig: LovelaceStrategyConfig, + hass: HomeAssistant, + params: LovelaceStrategyParams +): Promise> => { + const strategyType = strategyConfig.type; if (!strategyType) { // @ts-ignore return renderError("No strategy type found"); } try { - const strategy = (await getLovelaceStrategy(strategyType)) as any; - // eslint-disable-next-line @typescript-eslint/return-await - return await strategy[generateMethod](info); + const strategy = await getLovelaceStrategy(configType, strategyType); + + // 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; + } + } + + 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 @@ -93,11 +127,12 @@ const generateStrategy = async ( }; export const generateLovelaceDashboardStrategy = async ( - info: Parameters[0], - strategyType?: string -): ReturnType => + strategyConfig: LovelaceStrategyConfig, + hass: HomeAssistant, + params: LovelaceStrategyParams +): Promise => generateStrategy( - "generateDashboard", + "dashboard", (err) => ({ views: [ { @@ -111,16 +146,18 @@ export const generateLovelaceDashboardStrategy = async ( }, ], }), - info, - strategyType || info.config?.strategy?.type + strategyConfig, + hass, + params ); export const generateLovelaceViewStrategy = async ( - info: Parameters[0], - strategyType?: string -): ReturnType => + strategyConfig: LovelaceStrategyConfig, + hass: HomeAssistant, + params: LovelaceStrategyParams +): Promise => generateStrategy( - "generateView", + "view", (err) => ({ cards: [ { @@ -129,34 +166,30 @@ export const generateLovelaceViewStrategy = async ( }, ], }), - info, - strategyType || info.view?.strategy?.type + strategyConfig, + hass, + params ); /** * Find all references to strategies and replaces them with the generated output */ export const expandLovelaceConfigStrategies = async ( - info: Parameters[0] & { - config: LovelaceConfig; - } + 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/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; +} 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..99b5366ac1c2 --- /dev/null +++ b/src/panels/lovelace/strategies/original-states-dashboard-strategy.ts @@ -0,0 +1,23 @@ +import { ReactiveElement } from "lit"; +import { customElement } from "lit/decorators"; +import { LovelaceConfig, LovelaceStrategyConfig } from "../../../data/lovelace"; +import { HomeAssistant } from "../../../types"; +import { LovelaceStrategyParams } from "./types"; + +@customElement("original-states-dashboard-strategy") +export class OriginalStatesDashboardStrategy extends ReactiveElement { + static async generate( + _config: LovelaceStrategyConfig, + hass: HomeAssistant, + _params?: LovelaceStrategyParams + ): Promise { + return { + title: 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 70% rename from src/panels/lovelace/strategies/original-states-strategy.ts rename to src/panels/lovelace/strategies/original-states-view-strategy.ts index 7c538d5fc14a..a6e163f758f2 100644 --- a/src/panels/lovelace/strategies/original-states-strategy.ts +++ b/src/panels/lovelace/strategies/original-states-view-strategy.ts @@ -1,18 +1,23 @@ 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"; - -export class OriginalStatesStrategy { - static async generateView( - info: Parameters[0] - ): ReturnType { - const hass = info.hass; + LovelaceStrategyConfig, + LovelaceViewConfig, +} from "../../../data/lovelace"; +import { HomeAssistant } from "../../../types"; +import { generateDefaultViewConfig } from "../common/generate-lovelace-config"; +import { LovelaceStrategyParams } from "./types"; +@customElement("original-states-view-strategy") +export class OriginalStatesViewStrategy extends ReactiveElement { + static async generate( + _config: LovelaceStrategyConfig, + hass: HomeAssistant, + _params?: LovelaceStrategyParams + ): Promise { if (hass.config.state === STATE_NOT_RUNNING) { return { cards: [{ type: "starting" }], @@ -63,17 +68,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..8b807a61e2dc --- /dev/null +++ b/src/panels/lovelace/strategies/types.ts @@ -0,0 +1,24 @@ +import { + LovelaceConfig, + LovelaceStrategyConfig, + LovelaceViewConfig, +} from "../../../data/lovelace"; +import { HomeAssistant } from "../../../types"; + +export type LovelaceStrategyParams = { + narrow?: boolean; +}; + +export type LovelaceStrategy = { + generate( + config: LovelaceStrategyConfig, + hass: HomeAssistant, + params?: LovelaceStrategyParams + ): 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..e00e5af00e1b 100644 --- a/src/panels/lovelace/views/hui-view.ts +++ b/src/panels/lovelace/views/hui-view.ts @@ -190,12 +190,11 @@ export class HUIView extends ReactiveElement { if (viewConfig.strategy) { isStrategy = true; - viewConfig = await generateLovelaceViewStrategy({ - hass: this.hass, - config: this.lovelace.config, - narrow: this.narrow, - view: viewConfig, - }); + viewConfig = await generateLovelaceViewStrategy( + viewConfig.strategy, + this.hass!, + { narrow: this.narrow } + ); } viewConfig = {