From 03f8f76426d794509a93064557617fbe3e6b2cd7 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Wed, 13 Sep 2023 12:05:53 +0200 Subject: [PATCH] First draft of strategy editor --- .../hui-dashboard-strategy-element-editor.ts | 30 ++++ ...iginal-states-dashboard-strategy-editor.ts | 75 ++++++++++ .../lovelace/editor/hui-element-editor.ts | 4 +- src/panels/lovelace/hui-root.ts | 13 +- .../dialog-dashboard-strategy-editor.ts | 128 ++++++++++++++++++ .../show-dialog-dashboard-strategy-editor.ts | 25 ++++ .../lovelace/strategies/get-strategy.ts | 11 +- .../lovelace/strategies/legacy-strategy.ts | 19 ++- .../original-states-dashboard-strategy.ts | 22 ++- .../original-states-view-strategy.ts | 13 +- src/panels/lovelace/strategies/types.ts | 6 + 11 files changed, 325 insertions(+), 21 deletions(-) create mode 100644 src/panels/lovelace/editor/dashboard-strategy-editor/hui-dashboard-strategy-element-editor.ts create mode 100644 src/panels/lovelace/editor/dashboard-strategy-editor/hui-original-states-dashboard-strategy-editor.ts create mode 100644 src/panels/lovelace/strategies/device-registry-detail/dialog-dashboard-strategy-editor.ts create mode 100644 src/panels/lovelace/strategies/device-registry-detail/show-dialog-dashboard-strategy-editor.ts diff --git a/src/panels/lovelace/editor/dashboard-strategy-editor/hui-dashboard-strategy-element-editor.ts b/src/panels/lovelace/editor/dashboard-strategy-editor/hui-dashboard-strategy-element-editor.ts new file mode 100644 index 000000000000..5377df0a2c0a --- /dev/null +++ b/src/panels/lovelace/editor/dashboard-strategy-editor/hui-dashboard-strategy-element-editor.ts @@ -0,0 +1,30 @@ +import { customElement } from "lit/decorators"; +import { LovelaceConfig } from "../../../../data/lovelace"; +import { getLovelaceStrategy } from "../../strategies/get-strategy"; +import { LovelaceStrategyEditor } from "../../strategies/types"; +import { HuiElementEditor } from "../hui-element-editor"; + +@customElement("hui-dashboard-strategy-element-editor") +export class HuiDashboardStrategyElementEditor extends HuiElementEditor { + protected async getConfigElement(): Promise< + LovelaceStrategyEditor | undefined + > { + const elClass = await getLovelaceStrategy( + "dashboard", + this.configElementType! + ); + + // Check if a GUI editor exists + if (elClass && elClass.getConfigElement) { + return elClass.getConfigElement(); + } + + return undefined; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-dashboard-strategy-element-editor": HuiDashboardStrategyElementEditor; + } +} diff --git a/src/panels/lovelace/editor/dashboard-strategy-editor/hui-original-states-dashboard-strategy-editor.ts b/src/panels/lovelace/editor/dashboard-strategy-editor/hui-original-states-dashboard-strategy-editor.ts new file mode 100644 index 000000000000..d411ce5af9c2 --- /dev/null +++ b/src/panels/lovelace/editor/dashboard-strategy-editor/hui-original-states-dashboard-strategy-editor.ts @@ -0,0 +1,75 @@ +import { html, LitElement, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import "../../../../components/ha-form/ha-form"; +import type { + HaFormSchema, + SchemaUnion, +} from "../../../../components/ha-form/types"; +import type { HomeAssistant } from "../../../../types"; +import { OriginalStatesDashboardStrategyConfig } from "../../strategies/original-states-dashboard-strategy"; +import { LovelaceStrategyEditor } from "../../strategies/types"; + +const SCHEMA = [ + { + name: "no_area_group", + selector: { + boolean: {}, + }, + }, +] as const satisfies readonly HaFormSchema[]; + +@customElement("hui-original-states-dashboard-strategy-editor") +export class HuiOriginalStatesDashboarStrategyEditor + extends LitElement + implements LovelaceStrategyEditor +{ + @property({ attribute: false }) public hass?: HomeAssistant; + + @state() + private _config?: OriginalStatesDashboardStrategyConfig; + + public setConfig(config: OriginalStatesDashboardStrategyConfig): void { + this._config = config; + } + + protected render() { + if (!this.hass || !this._config) { + return nothing; + } + + const data = this._config; + + return html` + + `; + } + + private _valueChanged(ev: CustomEvent): void { + const config = ev.detail.value; + fireEvent(this, "config-changed", { config }); + } + + private _computeLabelCallback = (schema: SchemaUnion) => { + switch (schema.name) { + case "no_area_group": + return "Do not group by area"; + default: + return this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.${schema.name}` + ); + } + }; +} + +declare global { + interface HTMLElementTagNameMap { + "hui-original-states-dashboard-strategy-editor": HuiOriginalStatesDashboarStrategyEditor; + } +} diff --git a/src/panels/lovelace/editor/hui-element-editor.ts b/src/panels/lovelace/editor/hui-element-editor.ts index 2d2b58f4a10f..0ac7eb6e8783 100644 --- a/src/panels/lovelace/editor/hui-element-editor.ts +++ b/src/panels/lovelace/editor/hui-element-editor.ts @@ -19,6 +19,7 @@ import type { HaCodeEditor } from "../../../components/ha-code-editor"; import type { LovelaceCardConfig, LovelaceConfig, + LovelaceStrategyConfig, } from "../../../data/lovelace"; import type { HomeAssistant } from "../../../types"; import type { LovelaceRowConfig } from "../entity-rows/types"; @@ -38,7 +39,8 @@ export interface ConfigChangedEvent { | LovelaceCardConfig | LovelaceRowConfig | LovelaceHeaderFooterConfig - | LovelaceTileFeatureConfig; + | LovelaceTileFeatureConfig + | LovelaceStrategyConfig; error?: string; guiModeAvailable?: boolean; } diff --git a/src/panels/lovelace/hui-root.ts b/src/panels/lovelace/hui-root.ts index e9423fb81a21..349cad4f6dd3 100644 --- a/src/panels/lovelace/hui-root.ts +++ b/src/panels/lovelace/hui-root.ts @@ -19,12 +19,12 @@ import { import "@polymer/paper-tabs/paper-tab"; import "@polymer/paper-tabs/paper-tabs"; import { - css, CSSResultGroup, - html, LitElement, PropertyValues, TemplateResult, + css, + html, } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; @@ -68,6 +68,7 @@ import { documentationUrl } from "../../util/documentation-url"; import { swapView } from "./editor/config-util"; import { showEditLovelaceDialog } from "./editor/lovelace-editor/show-edit-lovelace-dialog"; import { showEditViewDialog } from "./editor/view-editor/show-edit-view-dialog"; +import { showDashboardStrategyEditorDialog } from "./strategies/device-registry-detail/show-dialog-dashboard-strategy-editor"; import type { Lovelace } from "./types"; import "./views/hui-view"; import type { HUIView } from "./views/hui-view"; @@ -806,6 +807,14 @@ class HUIRoot extends LitElement { }); return; } + if (this.lovelace!.rawConfig?.strategy != null) { + showDashboardStrategyEditorDialog(this, { + lovelaceConfig: this.lovelace!.rawConfig, + saveConfig: this.lovelace!.saveConfig, + strategyConfig: this.lovelace!.rawConfig.strategy, + }); + return; + } this.lovelace!.setEditMode(true); } diff --git a/src/panels/lovelace/strategies/device-registry-detail/dialog-dashboard-strategy-editor.ts b/src/panels/lovelace/strategies/device-registry-detail/dialog-dashboard-strategy-editor.ts new file mode 100644 index 000000000000..ce321a09e815 --- /dev/null +++ b/src/panels/lovelace/strategies/device-registry-detail/dialog-dashboard-strategy-editor.ts @@ -0,0 +1,128 @@ +import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; +import { customElement, property, query, state } from "lit/decorators"; +import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event"; +import { createCloseHeading } from "../../../../components/ha-dialog"; +import { LovelaceStrategyConfig } from "../../../../data/lovelace"; +import { haStyle, haStyleDialog } from "../../../../resources/styles"; +import type { HomeAssistant } from "../../../../types"; +import { showSaveSuccessToast } from "../../../../util/toast-saved-success"; +import "../../editor/dashboard-strategy-editor/hui-dashboard-strategy-element-editor"; +import type { HuiDashboardStrategyElementEditor } from "../../editor/dashboard-strategy-editor/hui-dashboard-strategy-element-editor"; +import { ConfigChangedEvent } from "../../editor/hui-element-editor"; +import { GUIModeChangedEvent } from "../../editor/types"; +import type { DashboardStrategyEditorDialogParams } from "./show-dialog-dashboard-strategy-editor"; +import { cleanStrategyConfig } from "../legacy-strategy"; + +@customElement("dialog-dashboard-strategy-editor") +class DialogDashboardStrategyEditor extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @state() private _params?: DashboardStrategyEditorDialogParams; + + @state() private _strategyConfig?: LovelaceStrategyConfig; + + @state() private _GUImode = true; + + @state() private _guiModeAvailable? = true; + + @query("hui-dashboard-strategy-element-editor") + private _strategyEditorEl?: HuiDashboardStrategyElementEditor; + + public async showDialog( + params: DashboardStrategyEditorDialogParams + ): Promise { + this._params = params; + this._strategyConfig = params.strategyConfig; + await this.updateComplete; + } + + public closeDialog(): void { + this._params = undefined; + this._strategyConfig = undefined; + fireEvent(this, "dialog-closed", { dialog: this.localName }); + } + + private _handleConfigChanged(ev: HASSDomEvent) { + ev.stopPropagation(); + this._guiModeAvailable = ev.detail.guiModeAvailable; + this._strategyConfig = ev.detail.config as LovelaceStrategyConfig; + } + + private _handleGUIModeChanged(ev: HASSDomEvent): void { + ev.stopPropagation(); + this._GUImode = ev.detail.guiMode; + this._guiModeAvailable = ev.detail.guiModeAvailable; + } + + private _toggleMode(): void { + this._strategyEditorEl?.toggleMode(); + } + + private _opened() { + this._strategyEditorEl?.focusYamlEditor(); + } + + private async _save(): Promise { + await this._params!.saveConfig({ + ...this._params!.lovelaceConfig, + strategy: this._strategyConfig, + }); + showSaveSuccessToast(this, this.hass); + this.closeDialog(); + } + + protected render() { + if (!this._params || !this._strategyConfig) { + return nothing; + } + + const config = cleanStrategyConfig(this._strategyConfig); + + return html` + + + ${this._strategyConfig !== undefined + ? html` + + ${this.hass!.localize( + !this._strategyEditorEl || this._GUImode + ? "ui.panel.lovelace.editor.edit_card.show_code_editor" + : "ui.panel.lovelace.editor.edit_card.show_visual_editor" + )} + + + ${this.hass!.localize("ui.common.save")} + + ` + : nothing} + + `; + } + + static get styles(): CSSResultGroup { + return [haStyle, haStyleDialog, css``]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "dialog-dashboard-strategy-editor": DialogDashboardStrategyEditor; + } +} diff --git a/src/panels/lovelace/strategies/device-registry-detail/show-dialog-dashboard-strategy-editor.ts b/src/panels/lovelace/strategies/device-registry-detail/show-dialog-dashboard-strategy-editor.ts new file mode 100644 index 000000000000..8125a115f9a4 --- /dev/null +++ b/src/panels/lovelace/strategies/device-registry-detail/show-dialog-dashboard-strategy-editor.ts @@ -0,0 +1,25 @@ +import { fireEvent } from "../../../../common/dom/fire_event"; +import { + LovelaceConfig, + LovelaceStrategyConfig, +} from "../../../../data/lovelace"; + +export interface DashboardStrategyEditorDialogParams { + lovelaceConfig: LovelaceConfig; + saveConfig: (config: LovelaceConfig) => void; + strategyConfig: LovelaceStrategyConfig; +} + +export const loadDashboardStrategyEditorDialog = () => + import("./dialog-dashboard-strategy-editor"); + +export const showDashboardStrategyEditorDialog = ( + element: HTMLElement, + params: DashboardStrategyEditorDialogParams +): void => { + fireEvent(element, "show-dialog", { + dialogTag: "dialog-dashboard-strategy-editor", + dialogImport: loadDashboardStrategyEditorDialog, + dialogParams: params, + }); +}; diff --git a/src/panels/lovelace/strategies/get-strategy.ts b/src/panels/lovelace/strategies/get-strategy.ts index 4f30b7c17592..c1d0fe2f4b99 100644 --- a/src/panels/lovelace/strategies/get-strategy.ts +++ b/src/panels/lovelace/strategies/get-strategy.ts @@ -4,7 +4,7 @@ import { LovelaceViewConfig, } from "../../../data/lovelace"; import { AsyncReturnType, HomeAssistant } from "../../../types"; -import { isLegacyStrategy } from "./legacy-strategy"; +import { cleanStrategyConfig, isLegacyStrategy } from "./legacy-strategy"; import { LovelaceDashboardStrategy, LovelaceStrategy, @@ -36,7 +36,7 @@ type StrategyConfig = AsyncReturnType< Strategies[T]["generate"] >; -const getLovelaceStrategy = async ( +export const getLovelaceStrategy = async ( configType: T, strategyType: string ): Promise => { @@ -108,12 +108,7 @@ const generateStrategy = async ( } } - const config = { - ...strategyConfig, - ...strategyConfig.options, - }; - - delete config.options; + const config = cleanStrategyConfig(strategyConfig); return await strategy.generate(config, hass, params); } catch (err: any) { diff --git a/src/panels/lovelace/strategies/legacy-strategy.ts b/src/panels/lovelace/strategies/legacy-strategy.ts index d3618cf71b36..237128595d58 100644 --- a/src/panels/lovelace/strategies/legacy-strategy.ts +++ b/src/panels/lovelace/strategies/legacy-strategy.ts @@ -1,4 +1,8 @@ -import { LovelaceConfig, LovelaceViewConfig } from "../../../data/lovelace"; +import { + LovelaceConfig, + LovelaceStrategyConfig, + LovelaceViewConfig, +} from "../../../data/lovelace"; import { HomeAssistant } from "../../../types"; export const isLegacyStrategy = ( @@ -22,3 +26,16 @@ export interface LovelaceViewStrategy { narrow: boolean | undefined; }): Promise; } + +export const cleanStrategyConfig = (config: LovelaceStrategyConfig) => { + if (!(Object.keys(config).length === 2 && "options" in config)) { + return config; + } + const cleanedConfig = { + ...config, + ...config.options, + }; + + delete cleanedConfig.options; + return cleanedConfig; +}; diff --git a/src/panels/lovelace/strategies/original-states-dashboard-strategy.ts b/src/panels/lovelace/strategies/original-states-dashboard-strategy.ts index 99b5366ac1c2..93bf2a56e2d5 100644 --- a/src/panels/lovelace/strategies/original-states-dashboard-strategy.ts +++ b/src/panels/lovelace/strategies/original-states-dashboard-strategy.ts @@ -2,12 +2,16 @@ import { ReactiveElement } from "lit"; import { customElement } from "lit/decorators"; import { LovelaceConfig, LovelaceStrategyConfig } from "../../../data/lovelace"; import { HomeAssistant } from "../../../types"; -import { LovelaceStrategyParams } from "./types"; +import { LovelaceStrategyEditor, LovelaceStrategyParams } from "./types"; + +export type OriginalStatesDashboardStrategyConfig = LovelaceStrategyConfig & { + no_area_group?: boolean; +}; @customElement("original-states-dashboard-strategy") export class OriginalStatesDashboardStrategy extends ReactiveElement { static async generate( - _config: LovelaceStrategyConfig, + config: OriginalStatesDashboardStrategyConfig, hass: HomeAssistant, _params?: LovelaceStrategyParams ): Promise { @@ -15,9 +19,21 @@ export class OriginalStatesDashboardStrategy extends ReactiveElement { title: hass.config.location_name, views: [ { - strategy: { type: "original-states" }, + strategy: { + type: "original-states", + no_area_group: config.no_area_group, + }, }, ], }; } + + public static async getConfigElement(): Promise { + await import( + "../editor/dashboard-strategy-editor/hui-original-states-dashboard-strategy-editor" + ); + return document.createElement( + "hui-original-states-dashboard-strategy-editor" + ); + } } diff --git a/src/panels/lovelace/strategies/original-states-view-strategy.ts b/src/panels/lovelace/strategies/original-states-view-strategy.ts index a6e163f758f2..298e86bef46d 100644 --- a/src/panels/lovelace/strategies/original-states-view-strategy.ts +++ b/src/panels/lovelace/strategies/original-states-view-strategy.ts @@ -3,18 +3,19 @@ import { ReactiveElement } from "lit"; import { customElement } from "lit/decorators"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { getEnergyPreferences } from "../../../data/energy"; -import { - LovelaceStrategyConfig, - LovelaceViewConfig, -} from "../../../data/lovelace"; +import { LovelaceViewConfig } from "../../../data/lovelace"; import { HomeAssistant } from "../../../types"; import { generateDefaultViewConfig } from "../common/generate-lovelace-config"; import { LovelaceStrategyParams } from "./types"; +export type OriginalStatesViewStrategyConfig = { + no_area_group?: boolean; +}; + @customElement("original-states-view-strategy") export class OriginalStatesViewStrategy extends ReactiveElement { static async generate( - _config: LovelaceStrategyConfig, + config: OriginalStatesViewStrategyConfig, hass: HomeAssistant, _params?: LovelaceStrategyParams ): Promise { @@ -41,7 +42,7 @@ export class OriginalStatesViewStrategy extends ReactiveElement { // User can override default view. If they didn't, we will add one // that contains all entities. const view = generateDefaultViewConfig( - hass.areas, + config.no_area_group ? {} : hass.areas, hass.devices, hass.entities, hass.states, diff --git a/src/panels/lovelace/strategies/types.ts b/src/panels/lovelace/strategies/types.ts index 8b807a61e2dc..2760c0a9380b 100644 --- a/src/panels/lovelace/strategies/types.ts +++ b/src/panels/lovelace/strategies/types.ts @@ -4,6 +4,7 @@ import { LovelaceViewConfig, } from "../../../data/lovelace"; import { HomeAssistant } from "../../../types"; +import { LovelaceGenericElementEditor } from "../types"; export type LovelaceStrategyParams = { narrow?: boolean; @@ -15,6 +16,7 @@ export type LovelaceStrategy = { hass: HomeAssistant, params?: LovelaceStrategyParams ): Promise; + getConfigElement?: () => LovelaceStrategyEditor; }; export interface LovelaceDashboardStrategy @@ -22,3 +24,7 @@ export interface LovelaceDashboardStrategy export interface LovelaceViewStrategy extends LovelaceStrategy {} + +export interface LovelaceStrategyEditor extends LovelaceGenericElementEditor { + setConfig(config: LovelaceStrategyConfig): void; +}