Allow cards and tile features to provide a schema to create the editor (#16142)

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
Paul Bottein 2023-06-21 17:22:33 +02:00 committed by GitHub
parent 7faa165558
commit 1fe5d66a68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 185 additions and 87 deletions

View File

@ -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<LovelaceCardEditor> {
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;

View File

@ -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<LovelaceCardConfig> {
return undefined;
}
protected async getConfigForm(): Promise<LovelaceConfigForm | undefined> {
const elClass = await getCardElementClass(this.configElementType!);
// Check if a schema exists
if (elClass && elClass.getConfigForm) {
return elClass.getConfigForm();
}
return undefined;
}
}
declare global {

View File

@ -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`
<ha-form
.hass=${this.hass}
.data=${this._config}
.schema=${SCHEMA}
.computeLabel=${this._computeLabelCallback}
@value-changed=${this._valueChanged}
></ha-form>
`;
}
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<typeof SCHEMA>) => {
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;

View File

@ -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`
<ha-form
.hass=${this.hass}
.data=${this._config}
.schema=${this.schema}
.computeLabel=${this._computeLabelCallback}
.computeHelper=${this._computeHelperCallback}
@value-changed=${this._valueChanged}
></ha-form>
`;
}
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;
}
}

View File

@ -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<T, C = any> extends LitElement {
return undefined;
}
protected async getConfigForm(): Promise<LovelaceConfigForm | undefined> {
return undefined;
}
protected get configElementType(): string | undefined {
return this.value ? (this.value as any).type : undefined;
}
@ -328,6 +336,25 @@ export abstract class HuiElementEditor<T, C = any> 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) {

View File

@ -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<LovelaceConfigForm | undefined> {
const elClass = await getTileFeatureElementClass(this.configElementType!);
// Check if a schema exists
if (elClass && elClass.getConfigForm) {
return elClass.getConfigForm();
}
return undefined;
}
}
declare global {

View File

@ -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<LovelaceCard> {
getStubConfig?: (
hass: HomeAssistant,
@ -52,6 +67,7 @@ export interface LovelaceCardConstructor extends Constructor<LovelaceCard> {
entitiesFallback: string[]
) => LovelaceCardConfig;
getConfigElement?: () => LovelaceCardEditor;
getConfigForm?: () => LovelaceConfigForm;
}
export interface LovelaceHeaderFooterConstructor
@ -104,11 +120,15 @@ export interface LovelaceTileFeature extends HTMLElement {
export interface LovelaceTileFeatureConstructor
extends Constructor<LovelaceTileFeature> {
getConfigElement?: () => LovelaceTileFeatureEditor;
getStubConfig?: (
hass: HomeAssistant,
stateObj?: HassEntity
) => LovelaceTileFeatureConfig;
getConfigElement?: () => LovelaceTileFeatureEditor;
getConfigForm?: () => {
schema: HaFormSchema[];
assertConfig?: (config: LovelaceCardConfig) => void;
};
isSupported?: (stateObj?: HassEntity) => boolean;
}