From 6d7a40368c36eadff2e78b9febf7130cd39d6b0a Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Sat, 12 Apr 2025 05:03:44 -0700 Subject: [PATCH] Support more templates in action visual editor (#25015) * Support more templates in action visual editor * Make selector sticky * typing --- src/components/ha-service-control.ts | 33 +++++++++++++++- .../types/ha-automation-action-service.ts | 39 +------------------ .../action/developer-tools-action.ts | 19 +-------- 3 files changed, 35 insertions(+), 56 deletions(-) diff --git a/src/components/ha-service-control.ts b/src/components/ha-service-control.ts index a6f9d0126d..85cbfd843e 100644 --- a/src/components/ha-service-control.ts +++ b/src/components/ha-service-control.ts @@ -38,6 +38,7 @@ import "./ha-settings-row"; import "./ha-yaml-editor"; import type { HaYamlEditor } from "./ha-yaml-editor"; import "./ha-service-section-icon"; +import { hasTemplate } from "../common/string/has-template"; const attributeFilter = (values: any[], attribute: any) => { if (typeof attribute === "object") { @@ -101,6 +102,8 @@ export class HaServiceControl extends LitElement { @query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor; + private _stickySelector: Record = {}; + protected willUpdate(changedProperties: PropertyValues) { if (!this.hasUpdated) { this.hass.loadBackendTranslation("services"); @@ -590,7 +593,23 @@ export class HaServiceControl extends LitElement { return nothing; } - const selector = dataField?.selector ?? { text: undefined }; + const fieldDataHasTemplate = + this._value?.data && hasTemplate(this._value.data[dataField.key]); + + const selector = + fieldDataHasTemplate && + typeof this._value!.data![dataField.key] === "string" + ? { template: null } + : fieldDataHasTemplate && + typeof this._value!.data![dataField.key] === "object" + ? { object: null } + : (this._stickySelector[dataField.key] ?? + dataField?.selector ?? { text: null }); + + if (fieldDataHasTemplate) { + // Hold this selector type until the field is cleared + this._stickySelector[dataField.key] = selector; + } const showOptional = showOptionalToggle(dataField); @@ -693,6 +712,7 @@ export class HaServiceControl extends LitElement { this._checkedKeys.delete(key); data = { ...this._value?.data }; delete data[key]; + delete this._stickySelector[key]; } if (data) { fireEvent(this, "value-changed", { @@ -816,6 +836,10 @@ export class HaServiceControl extends LitElement { private _serviceDataChanged(ev: CustomEvent) { ev.stopPropagation(); + if (ev.detail.isValid === false) { + // Don't clear an object selector that returns invalid YAML + return; + } const key = (ev.currentTarget as any).key; const value = ev.detail.value; if ( @@ -828,8 +852,13 @@ export class HaServiceControl extends LitElement { const data = { ...this._value?.data, [key]: value }; - if (value === "" || value === undefined) { + if ( + value === "" || + value === undefined || + (typeof value === "object" && !Object.keys(value).length) + ) { delete data[key]; + delete this._stickySelector[key]; } fireEvent(this, "value-changed", { diff --git a/src/panels/config/automation/action/types/ha-automation-action-service.ts b/src/panels/config/automation/action/types/ha-automation-action-service.ts index 8543cfb93e..4d6f61643b 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-service.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-service.ts @@ -1,11 +1,8 @@ import type { PropertyValues } from "lit"; import { css, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; -import memoizeOne from "memoize-one"; import { assert } from "superstruct"; import { fireEvent } from "../../../../../common/dom/fire_event"; -import { computeDomain } from "../../../../../common/entity/compute_domain"; -import { computeObjectId } from "../../../../../common/entity/compute_object_id"; import { hasTemplate } from "../../../../../common/string/has-template"; import "../../../../../components/ha-service-control"; import type { ServiceAction } from "../../../../../data/script"; @@ -27,26 +24,6 @@ export class HaServiceAction extends LitElement implements ActionElement { @state() private _responseChecked = false; - private _fields = memoizeOne( - ( - serviceDomains: HomeAssistant["services"], - domainService: string | undefined - ): { fields: any } => { - if (!domainService) { - return { fields: {} }; - } - const domain = computeDomain(domainService); - const service = computeObjectId(domainService); - if (!(domain in serviceDomains)) { - return { fields: {} }; - } - if (!(service in serviceDomains[domain])) { - return { fields: {} }; - } - return { fields: serviceDomains[domain][service].fields }; - } - ); - public static get defaultConfig(): ServiceAction { return { action: "", data: {} }; } @@ -62,23 +39,11 @@ export class HaServiceAction extends LitElement implements ActionElement { return; } - const fields = this._fields(this.hass.services, this.action?.action).fields; if ( this.action && - (Object.entries(this.action).some( + Object.entries(this.action).some( ([key, val]) => key !== "data" && hasTemplate(val) - ) || - (this.action.data && - Object.entries(this.action.data).some(([key, val]) => { - const field = fields[key]; - if ( - field?.selector && - ("template" in field.selector || "object" in field.selector) - ) { - return false; - } - return hasTemplate(val); - }))) + ) ) { fireEvent( this, diff --git a/src/panels/developer-tools/action/developer-tools-action.ts b/src/panels/developer-tools/action/developer-tools-action.ts index 83cf76526f..979c653ffc 100644 --- a/src/panels/developer-tools/action/developer-tools-action.ts +++ b/src/panels/developer-tools/action/developer-tools-action.ts @@ -528,26 +528,11 @@ class HaPanelDevAction extends LitElement { } private _checkUiSupported() { - const fields = this._fields( - this.hass.services, - this._serviceData?.action - ).fields; if ( this._serviceData && - (Object.entries(this._serviceData).some( + Object.entries(this._serviceData).some( ([key, val]) => key !== "data" && hasTemplate(val) - ) || - (this._serviceData.data && - Object.entries(this._serviceData.data).some(([key, val]) => { - const field = fields.find((f) => f.key === key); - if ( - field?.selector && - ("template" in field.selector || "object" in field.selector) - ) { - return false; - } - return hasTemplate(val); - }))) + ) ) { this._yamlMode = true; this._uiAvailable = false;