diff --git a/src/panels/lovelace/cards/hui-entity-card.ts b/src/panels/lovelace/cards/hui-entity-card.ts index 548dbd80ec..7424877f52 100644 --- a/src/panels/lovelace/cards/hui-entity-card.ts +++ b/src/panels/lovelace/cards/hui-entity-card.ts @@ -4,14 +4,15 @@ import { CSSResultGroup, html, LitElement, - PropertyValues, nothing, + PropertyValues, } from "lit"; import { customElement, property, state } from "lit/decorators"; import { ifDefined } from "lit/directives/if-defined"; import { styleMap } from "lit/directives/style-map"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { fireEvent } from "../../../common/dom/fire_event"; +import { computeAttributeValueDisplay } from "../../../common/entity/compute_attribute_display"; import { computeStateDisplay } from "../../../common/entity/compute_state_display"; import { computeStateDomain } from "../../../common/entity/compute_state_domain"; import { computeStateName } from "../../../common/entity/compute_state_name"; @@ -27,7 +28,6 @@ import "../../../components/ha-card"; import "../../../components/ha-icon"; import { HVAC_ACTION_TO_MODE } from "../../../data/climate"; import { isUnavailableState } from "../../../data/entity"; -import { computeAttributeValueDisplay } from "../../../common/entity/compute_attribute_display"; import { LightEntity } from "../../../data/light"; import { HomeAssistant } from "../../../types"; import { computeCardSize } from "../common/compute-card-size"; @@ -35,21 +35,12 @@ import { findEntities } from "../common/find-entities"; import { hasConfigOrEntityChanged } from "../common/has-changed"; import { createEntityNotFoundWarning } from "../components/hui-warning"; import { createHeaderFooterElement } from "../create-element/create-header-footer-element"; -import { - LovelaceCard, - LovelaceCardEditor, - LovelaceHeaderFooter, -} from "../types"; +import { LovelaceCard, LovelaceHeaderFooter } from "../types"; import { HuiErrorCard } from "./hui-error-card"; import { EntityCardConfig } from "./types"; @customElement("hui-entity-card") export class HuiEntityCard extends LitElement implements LovelaceCard { - public static async getConfigElement(): Promise { - await import("../editor/config-elements/hui-entity-card-editor"); - return document.createElement("hui-entity-card-editor"); - } - public static getStubConfig( hass: HomeAssistant, entities: string[], @@ -70,6 +61,11 @@ export class HuiEntityCard extends LitElement implements LovelaceCard { }; } + public static async getConfigForm() { + return (await import("../editor/config-elements/hui-entity-card-editor")) + .default; + } + @property({ attribute: false }) public hass?: HomeAssistant; @state() private _config?: EntityCardConfig; diff --git a/src/panels/lovelace/editor/card-editor/hui-card-element-editor.ts b/src/panels/lovelace/editor/card-editor/hui-card-element-editor.ts index 08a0b9bd42..be036fb404 100644 --- a/src/panels/lovelace/editor/card-editor/hui-card-element-editor.ts +++ b/src/panels/lovelace/editor/card-editor/hui-card-element-editor.ts @@ -1,7 +1,7 @@ import { customElement } from "lit/decorators"; import type { LovelaceCardConfig } from "../../../../data/lovelace"; import { getCardElementClass } from "../../create-element/create-card-element"; -import type { LovelaceCardEditor } from "../../types"; +import type { LovelaceCardEditor, LovelaceConfigForm } from "../../types"; import { HuiElementEditor } from "../hui-element-editor"; @customElement("hui-card-element-editor") @@ -16,6 +16,17 @@ export class HuiCardElementEditor extends HuiElementEditor { return undefined; } + + protected async getConfigForm(): Promise { + const elClass = await getCardElementClass(this.configElementType!); + + // Check if a schema exists + if (elClass && elClass.getConfigForm) { + return elClass.getConfigForm(); + } + + return undefined; + } } declare global { diff --git a/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts index 3379792b6f..4c93882620 100644 --- a/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts @@ -1,16 +1,12 @@ -import { html, LitElement, nothing } from "lit"; -import { customElement, property, state } from "lit/decorators"; import { assert, assign, boolean, object, optional, string } from "superstruct"; -import { fireEvent } from "../../../../common/dom/fire_event"; -import "../../../../components/ha-form/ha-form"; -import type { SchemaUnion } from "../../../../components/ha-form/types"; -import type { HomeAssistant } from "../../../../types"; -import type { EntityCardConfig } from "../../cards/types"; +import { LocalizeFunc } from "../../../../common/translations/localize"; +import { HaFormSchema } from "../../../../components/ha-form/types"; +import { EntityCardConfig } from "../../cards/types"; import { headerFooterConfigStructs } from "../../header-footer/structs"; -import type { LovelaceCardEditor } from "../../types"; +import { LovelaceConfigForm } from "../../types"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; -const cardConfigStruct = assign( +const struct = assign( baseLovelaceCardConfig, object({ entity: optional(string()), @@ -54,67 +50,19 @@ const SCHEMA = [ { name: "state_color", selector: { boolean: {} } }, ], }, -] as const; - -@customElement("hui-entity-card-editor") -export class HuiEntityCardEditor - extends LitElement - implements LovelaceCardEditor -{ - @property({ attribute: false }) public hass?: HomeAssistant; - - @state() private _config?: EntityCardConfig; - - public setConfig(config: EntityCardConfig): void { - assert(config, cardConfigStruct); - this._config = config; - } - - protected render() { - if (!this.hass || !this._config) { - return nothing; - } - - return html` - - `; - } - - private _valueChanged(ev: CustomEvent): void { - const config = ev.detail.value; - Object.keys(config).forEach((k) => config[k] === "" && delete config[k]); - fireEvent(this, "config-changed", { config }); - } - - private _computeLabelCallback = (schema: SchemaUnion) => { - if (schema.name === "entity") { - return this.hass!.localize( - "ui.panel.lovelace.editor.card.generic.entity" - ); - } +] as HaFormSchema[]; +const entityCardConfigForm: LovelaceConfigForm = { + schema: SCHEMA, + assertConfig: (config: EntityCardConfig) => assert(config, struct), + computeLabel: (schema: HaFormSchema, localize: LocalizeFunc) => { if (schema.name === "theme") { - return `${this.hass!.localize( + return `${localize( "ui.panel.lovelace.editor.card.generic.theme" - )} (${this.hass!.localize( - "ui.panel.lovelace.editor.card.config.optional" - )})`; + )} (${localize("ui.panel.lovelace.editor.card.config.optional")})`; } + return localize(`ui.panel.lovelace.editor.card.generic.${schema.name}`); + }, +}; - return this.hass!.localize( - `ui.panel.lovelace.editor.card.generic.${schema.name}` - ); - }; -} - -declare global { - interface HTMLElementTagNameMap { - "hui-entity-card-editor": HuiEntityCardEditor; - } -} +export default entityCardConfigForm; diff --git a/src/panels/lovelace/editor/config-elements/hui-form-editor.ts b/src/panels/lovelace/editor/config-elements/hui-form-editor.ts new file mode 100644 index 0000000000..9db046adfe --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/hui-form-editor.ts @@ -0,0 +1,82 @@ +import { CSSResultGroup, html, LitElement, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter"; +import { LocalizeFunc } from "../../../../common/translations/localize"; +import "../../../../components/ha-form/ha-form"; +import type { HaFormSchema } from "../../../../components/ha-form/types"; +import { LovelaceCardConfig } from "../../../../data/lovelace"; +import type { HomeAssistant } from "../../../../types"; +import type { LovelaceGenericElementEditor } from "../../types"; +import { configElementStyle } from "./config-elements-style"; + +@customElement("hui-form-editor") +export class HuiFormEditor + extends LitElement + implements LovelaceGenericElementEditor +{ + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public schema!: HaFormSchema[]; + + @state() private _config?: LovelaceCardConfig; + + public assertConfig(_config: LovelaceCardConfig): void { + return undefined; + } + + public setConfig(config: LovelaceCardConfig): void { + this.assertConfig(config); + this._config = config; + } + + protected render() { + if (!this._config) { + return nothing; + } + + return html` + + `; + } + + public computeLabel = ( + _schema: HaFormSchema, + _localize: LocalizeFunc + ): string | undefined => undefined; + + public computeHelper = ( + _schema: HaFormSchema, + _localize: LocalizeFunc + ): string | undefined => undefined; + + private _computeLabelCallback = (schema: HaFormSchema) => + this.computeLabel(schema, this.hass.localize) || + this.hass.localize( + `ui.panel.lovelace.editor.card.generic.${schema.name}` + ) || + capitalizeFirstLetter(schema.name.split("_").join(" ")); + + private _computeHelperCallback = (schema: HaFormSchema) => + this.computeHelper(schema, this.hass.localize); + + private _valueChanged(ev: CustomEvent): void { + const config = ev.detail.value; + fireEvent(this, "config-changed", { config }); + } + + static styles: CSSResultGroup = configElementStyle; +} + +declare global { + interface HTMLElementTagNameMap { + "hui-form-editor": HuiFormEditor; + } +} diff --git a/src/panels/lovelace/editor/hui-element-editor.ts b/src/panels/lovelace/editor/hui-element-editor.ts index b7b2dbf324..f711c63210 100644 --- a/src/panels/lovelace/editor/hui-element-editor.ts +++ b/src/panels/lovelace/editor/hui-element-editor.ts @@ -8,13 +8,13 @@ import { PropertyValues, TemplateResult, } from "lit"; -import { property, state, query } from "lit/decorators"; +import { property, query, state } from "lit/decorators"; import { fireEvent } from "../../../common/dom/fire_event"; import { handleStructError } from "../../../common/structs/handle-errors"; import { deepEqual } from "../../../common/util/deep-equal"; +import "../../../components/ha-alert"; import "../../../components/ha-circular-progress"; import "../../../components/ha-code-editor"; -import "../../../components/ha-alert"; import type { HaCodeEditor } from "../../../components/ha-code-editor"; import type { LovelaceCardConfig, @@ -23,11 +23,15 @@ import type { import type { HomeAssistant } from "../../../types"; import type { LovelaceRowConfig } from "../entity-rows/types"; import { LovelaceHeaderFooterConfig } from "../header-footer/types"; -import type { LovelaceGenericElementEditor } from "../types"; +import { LovelaceTileFeatureConfig } from "../tile-features/types"; +import type { + LovelaceConfigForm, + LovelaceGenericElementEditor, +} from "../types"; +import type { HuiFormEditor } from "./config-elements/hui-form-editor"; import "./config-elements/hui-generic-entity-row-editor"; import { GUISupportError } from "./gui-support-error"; import { EditSubElementEvent, GUIModeChangedEvent } from "./types"; -import { LovelaceTileFeatureConfig } from "../tile-features/types"; export interface ConfigChangedEvent { config: @@ -182,6 +186,10 @@ export abstract class HuiElementEditor extends LitElement { return undefined; } + protected async getConfigForm(): Promise { + return undefined; + } + protected get configElementType(): string | undefined { return this.value ? (this.value as any).type : undefined; } @@ -328,6 +336,25 @@ export abstract class HuiElementEditor extends LitElement { this._loading = true; configElement = await this.getConfigElement(); + if (!configElement) { + const form = await this.getConfigForm(); + if (form) { + await import("./config-elements/hui-form-editor"); + configElement = document.createElement("hui-form-editor"); + const { schema, assertConfig, computeLabel, computeHelper } = form; + (configElement as HuiFormEditor).schema = schema; + if (computeLabel) { + (configElement as HuiFormEditor).computeLabel = computeLabel; + } + if (computeHelper) { + (configElement as HuiFormEditor).computeHelper = computeHelper; + } + if (assertConfig) { + (configElement as HuiFormEditor).assertConfig = assertConfig; + } + } + } + if (configElement) { configElement.hass = this.hass; if ("lovelace" in configElement) { diff --git a/src/panels/lovelace/editor/tile-feature-editor/hui-tile-feature-element-editor.ts b/src/panels/lovelace/editor/tile-feature-editor/hui-tile-feature-element-editor.ts index 573d1e075c..58fb381399 100644 --- a/src/panels/lovelace/editor/tile-feature-editor/hui-tile-feature-element-editor.ts +++ b/src/panels/lovelace/editor/tile-feature-editor/hui-tile-feature-element-editor.ts @@ -4,7 +4,10 @@ import { LovelaceTileFeatureConfig, LovelaceTileFeatureContext, } from "../../tile-features/types"; -import type { LovelaceTileFeatureEditor } from "../../types"; +import type { + LovelaceConfigForm, + LovelaceTileFeatureEditor, +} from "../../types"; import { HuiElementEditor } from "../hui-element-editor"; @customElement("hui-tile-feature-element-editor") @@ -24,6 +27,17 @@ export class HuiTileFeatureElementEditor extends HuiElementEditor< return undefined; } + + protected async getConfigForm(): Promise { + const elClass = await getTileFeatureElementClass(this.configElementType!); + + // Check if a schema exists + if (elClass && elClass.getConfigForm) { + return elClass.getConfigForm(); + } + + return undefined; + } } declare global { diff --git a/src/panels/lovelace/types.ts b/src/panels/lovelace/types.ts index c99e6a51c9..180c923d2e 100644 --- a/src/panels/lovelace/types.ts +++ b/src/panels/lovelace/types.ts @@ -1,4 +1,6 @@ import { HassEntity } from "home-assistant-js-websocket"; +import { LocalizeFunc } from "../../common/translations/localize"; +import { HaFormSchema } from "../../components/ha-form/types"; import { LovelaceBadgeConfig, LovelaceCardConfig, @@ -45,6 +47,19 @@ export interface LovelaceCard extends HTMLElement { setConfig(config: LovelaceCardConfig): void; } +export interface LovelaceConfigForm { + schema: HaFormSchema[]; + assertConfig?: (config: LovelaceCardConfig) => void; + computeLabel?: ( + schema: HaFormSchema, + localize: LocalizeFunc + ) => string | undefined; + computeHelper?: ( + schema: HaFormSchema, + localize: LocalizeFunc + ) => string | undefined; +} + export interface LovelaceCardConstructor extends Constructor { getStubConfig?: ( hass: HomeAssistant, @@ -52,6 +67,7 @@ export interface LovelaceCardConstructor extends Constructor { entitiesFallback: string[] ) => LovelaceCardConfig; getConfigElement?: () => LovelaceCardEditor; + getConfigForm?: () => LovelaceConfigForm; } export interface LovelaceHeaderFooterConstructor @@ -104,11 +120,15 @@ export interface LovelaceTileFeature extends HTMLElement { export interface LovelaceTileFeatureConstructor extends Constructor { - getConfigElement?: () => LovelaceTileFeatureEditor; getStubConfig?: ( hass: HomeAssistant, stateObj?: HassEntity ) => LovelaceTileFeatureConfig; + getConfigElement?: () => LovelaceTileFeatureEditor; + getConfigForm?: () => { + schema: HaFormSchema[]; + assertConfig?: (config: LovelaceCardConfig) => void; + }; isSupported?: (stateObj?: HassEntity) => boolean; }