From 5b4504688aa8b38cd394d2cc25397448bbaabe68 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 21 Sep 2023 17:41:30 +0200 Subject: [PATCH] Migrate state condition ui to ha-form --- .../common/ha-card-condition-editor.ts | 102 +++++++++++++ .../common/types/ha-card-condition-state.ts | 134 +++++++++++++++++ .../hui-conditional-card-editor.ts | 140 +++++------------- 3 files changed, 269 insertions(+), 107 deletions(-) create mode 100644 src/panels/lovelace/common/ha-card-condition-editor.ts create mode 100644 src/panels/lovelace/common/types/ha-card-condition-state.ts diff --git a/src/panels/lovelace/common/ha-card-condition-editor.ts b/src/panels/lovelace/common/ha-card-condition-editor.ts new file mode 100644 index 0000000000..7209868ed4 --- /dev/null +++ b/src/panels/lovelace/common/ha-card-condition-editor.ts @@ -0,0 +1,102 @@ +import { mdiCodeBraces, mdiDelete, mdiListBoxOutline } from "@mdi/js"; +import { LitElement, css, html } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { dynamicElement } from "../../../common/dom/dynamic-element-directive"; +import { fireEvent } from "../../../common/dom/fire_event"; +import "../../../components/ha-icon-button"; +import "../../../components/ha-yaml-editor"; +import { haStyle } from "../../../resources/styles"; +import type { HomeAssistant } from "../../../types"; +import "./types/ha-card-condition-state"; +import { Condition } from "./validate-condition"; + +@customElement("ha-card-condition-editor") +export default class HaCardConditionEditor extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) condition!: Condition; + + @state() public _yamlMode = false; + + protected render() { + const condition = this.condition; + const supported = + customElements.get(`ha-card-condition-${condition.condition}`) !== + undefined; + const yamlMode = this._yamlMode || !supported; + + return html` +
+ + + +
+
+ ${yamlMode + ? html` + + ` + : html` + ${dynamicElement(`ha-card-condition-${condition.condition}`, { + hass: this.hass, + condition: condition, + })} + `} +
+ `; + } + + private _toggleMode() { + this._yamlMode = !this._yamlMode; + } + + private _delete() { + fireEvent(this, "value-changed", { value: null }); + } + + private _onYamlChange(ev: CustomEvent) { + ev.stopPropagation(); + if (!ev.detail.isValid) { + return; + } + // @ts-ignore + fireEvent(this, "value-changed", { value: ev.detail.value }); + } + + static styles = [ + haStyle, + css` + .header { + display: flex; + flex-direction: row; + justify-content: space-between; + } + .content { + padding: 12px; + } + `, + ]; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-card-condition-editor": HaCardConditionEditor; + } +} diff --git a/src/panels/lovelace/common/types/ha-card-condition-state.ts b/src/panels/lovelace/common/types/ha-card-condition-state.ts new file mode 100644 index 0000000000..436a3b3619 --- /dev/null +++ b/src/panels/lovelace/common/types/ha-card-condition-state.ts @@ -0,0 +1,134 @@ +import type { HassEntity } from "home-assistant-js-websocket"; +import { html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import "../../../../components/ha-form/ha-form"; +import type { SchemaUnion } from "../../../../components/ha-form/types"; +import { HaFormSchema } from "../../../../components/ha-form/types"; +import type { HomeAssistant } from "../../../../types"; +import { StateCondition } from "../validate-condition"; + +type StateConditionData = { + condition: "state"; + entity: string; + invert: "true" | "false"; + state?: string; +}; + +const SCHEMA = [ + { name: "entity", selector: { entity: {} } }, + { + name: "", + type: "grid", + schema: [ + { + name: "invert", + selector: { + select: { + mode: "dropdown", + options: [ + { + label: "State equal", + value: "false", + }, + { + label: "State not equal", + value: "true", + }, + ], + }, + }, + }, + { + name: "state", + selector: { + state: {}, + }, + context: { + filter_entity: "entity", + }, + }, + ], + }, +] as const satisfies readonly HaFormSchema[]; + +@customElement("ha-card-condition-state") +export class HaCardConditionState extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public condition!: StateCondition; + + @property({ type: Boolean }) public disabled = false; + + public static get defaultConfig(): StateCondition { + return { condition: "state", entity: "", state: "" }; + } + + protected render() { + const { state, state_not, ...content } = this.condition; + + const data: StateConditionData = { + ...content, + invert: this.condition.state_not ? "true" : "false", + state: this.condition.state_not ?? this.condition.state ?? "", + }; + + return html` + + `; + } + + private _valueChanged(ev: CustomEvent): void { + ev.stopPropagation(); + const data = ev.detail.value as StateConditionData; + + const { invert, state, entity, condition: _, ...content } = data; + + const condition: StateCondition = { + condition: "state", + ...content, + entity: entity ?? "", + state: invert === "false" ? state ?? "" : undefined, + state_not: invert === "true" ? state ?? "" : undefined, + }; + + fireEvent(this, "value-changed", { value: condition }); + } + + private _computeLabelCallback = ( + schema: SchemaUnion + ): string => { + const entity = this.hass.states[this.condition.entity] as + | HassEntity + | undefined; + switch (schema.name) { + case "entity": + return this.hass.localize("ui.components.entity.entity-picker.entity"); + case "state": + if (entity) { + return `${this.hass.localize( + "ui.components.entity.entity-state-picker.state" + )} (${this.hass.formatEntityState(entity)})`; + } + return `${this.hass.localize( + "ui.components.entity.entity-state-picker.state" + )}`; + + default: + return ""; + } + }; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-card-condition-state": HaCardConditionState; + } +} diff --git a/src/panels/lovelace/editor/config-elements/hui-conditional-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-conditional-card-editor.ts index 92e98453d6..e4e098d323 100644 --- a/src/panels/lovelace/editor/config-elements/hui-conditional-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-conditional-card-editor.ts @@ -1,10 +1,10 @@ import "@material/mwc-list/mwc-list-item"; import "@material/mwc-tab-bar/mwc-tab-bar"; import "@material/mwc-tab/mwc-tab"; +import type { MDCTabBarActivatedEvent } from "@material/tab-bar"; import { mdiCodeBraces, mdiContentCopy, mdiListBoxOutline } from "@mdi/js"; import deepClone from "deep-clone-simple"; -import type { MDCTabBarActivatedEvent } from "@material/tab-bar"; -import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; +import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { any, @@ -19,8 +19,7 @@ import { union, } from "superstruct"; import { storage } from "../../../../common/decorators/storage"; -import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event"; -import { stopPropagation } from "../../../../common/dom/stop_propagation"; +import { HASSDomEvent, fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/entity/ha-entity-picker"; import "../../../../components/ha-select"; import "../../../../components/ha-textfield"; @@ -30,6 +29,7 @@ import type { } from "../../../../data/lovelace"; import type { HomeAssistant } from "../../../../types"; import type { ConditionalCardConfig } from "../../cards/types"; +import "../../common/ha-card-condition-editor"; import type { LovelaceCardEditor } from "../../types"; import "../card-editor/hui-card-element-editor"; import type { HuiCardElementEditor } from "../card-editor/hui-card-element-editor"; @@ -39,7 +39,6 @@ import type { ConfigChangedEvent } from "../hui-element-editor"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; import type { GUIModeChangedEvent } from "../types"; import { configElementStyle } from "./config-elements-style"; -import { StateCondition } from "../../common/validate-condition"; const stateConditionStruct = object({ condition: optional(literal("state")), @@ -177,66 +176,25 @@ export class HuiConditionalCardEditor ${this.hass!.localize( "ui.panel.lovelace.editor.card.conditional.condition_explanation" )} - ${this._config.conditions.map((cond, idx) => { - if (cond.condition && cond.condition !== "state") - return nothing; - return html` + ${this._config.conditions.map( + (cond, idx) => html`
-
- -
-
- - - ${this.hass!.localize( - "ui.panel.lovelace.editor.card.conditional.state_equal" - )} - - - ${this.hass!.localize( - "ui.panel.lovelace.editor.card.conditional.state_not_equal" - )} - - - -
+
- `; - })} + ` + )}
- +
+ +
`} @@ -321,39 +279,19 @@ export class HuiConditionalCardEditor fireEvent(this, "config-changed", { config: this._config }); } - private _changeCondition(ev: Event): void { - const target = ev.target as any; - if (!this._config || !target) { - return; - } - const conditions = [...this._config.conditions]; - if (target.configValue === "entity" && target.value === "") { - conditions.splice(target.idx, 1); + private _conditionChanged(ev: CustomEvent) { + ev.stopPropagation(); + const conditions = [...this._config!.conditions]; + const newValue = ev.detail.value; + const index = (ev.target as any).index; + + if (newValue === null) { + conditions.splice(index, 1); } else { - // Only state condition are supported by the editor - const condition = { ...conditions[target.idx] } as StateCondition; - if (target.configValue === "entity") { - condition.entity = target.value; - } else if (target.configValue === "state") { - if (condition.state_not !== undefined) { - condition.state_not = target.value; - } else { - condition.state = target.value; - } - } else if (target.configValue === "invert") { - if (target.value === "true") { - if (condition.state) { - condition.state_not = condition.state; - delete condition.state; - } - } else if (condition.state_not) { - condition.state = condition.state_not; - delete condition.state_not; - } - } - conditions[target.idx] = condition; + conditions[index] = newValue; } - this._config = { ...this._config, conditions }; + + this._config = { ...this._config!, conditions }; fireEvent(this, "config-changed", { config: this._config }); } @@ -370,22 +308,10 @@ export class HuiConditionalCardEditor .condition { margin-top: 8px; border: 1px solid var(--divider-color); + } + .condition .content { padding: 12px; } - .condition .state { - display: flex; - align-items: flex-end; - } - .condition .state ha-select { - margin-right: 16px; - margin-inline-end: 16px; - margin-inline-start: initial; - direction: var(--direction); - } - .condition .state ha-textfield { - flex-grow: 1; - } - .card { margin-top: 8px; border: 1px solid var(--divider-color);