From 05a22e327126ad74eb9fa815954a1c2555545e00 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 28 Nov 2023 14:42:40 +0100 Subject: [PATCH] Keep targets when switching services (#18772) --- src/components/ha-service-control.ts | 67 +++++++++++++++++++++++++++- src/data/selector.ts | 45 ++++++++++++++++++- 2 files changed, 108 insertions(+), 4 deletions(-) diff --git a/src/components/ha-service-control.ts b/src/components/ha-service-control.ts index a099b02d36..3e009f0df0 100644 --- a/src/components/ha-service-control.ts +++ b/src/components/ha-service-control.ts @@ -17,11 +17,14 @@ import { IntegrationManifest, } from "../data/integration"; import { + areaMeetsTargetSelector, + deviceMeetsTargetSelector, + entityMeetsTargetSelector, expandAreaTarget, expandDeviceTarget, Selector, } from "../data/selector"; -import { ValueChangedEvent, HomeAssistant } from "../types"; +import { HomeAssistant, ValueChangedEvent } from "../types"; import { documentationUrl } from "../util/documentation-url"; import "./ha-checkbox"; import "./ha-icon-button"; @@ -546,8 +549,68 @@ export class HaServiceControl extends LitElement { if (ev.detail.value === this._value?.service) { return; } + + const newService = ev.detail.value || ""; + let target: HassServiceTarget | undefined; + + if (newService) { + const serviceData = this._getServiceInfo(newService, this.hass.services); + const currentTarget = this._value?.target; + if (currentTarget && serviceData?.target) { + const targetSelector = { target: { ...serviceData.target } }; + let targetEntities = + ensureArray( + currentTarget.entity_id || this._value!.data?.entity_id + )?.slice() || []; + let targetDevices = + ensureArray( + currentTarget.device_id || this._value!.data?.device_id + )?.slice() || []; + let targetAreas = + ensureArray( + currentTarget.area_id || this._value!.data?.area_id + )?.slice() || []; + if (targetAreas.length) { + targetAreas = targetAreas.filter((area) => + areaMeetsTargetSelector( + this.hass, + this.hass.entities, + this.hass.devices, + area, + targetSelector + ) + ); + } + if (targetDevices.length) { + targetDevices = targetDevices.filter((device) => + deviceMeetsTargetSelector( + this.hass, + Object.values(this.hass.entities), + this.hass.devices[device], + targetSelector + ) + ); + } + if (targetEntities.length) { + targetEntities = targetEntities.filter((entity) => + entityMeetsTargetSelector(this.hass.states[entity], targetSelector) + ); + } + target = { + entity_id: targetEntities, + device_id: targetDevices, + area_id: targetAreas, + }; + } + } + + const value = { + service: newService, + target, + }; + fireEvent(this, "value-changed", { - value: { service: ev.detail.value || "" }, + value, }); } diff --git a/src/data/selector.ts b/src/data/selector.ts index 863b1e3a9c..3739e1c82e 100644 --- a/src/data/selector.ts +++ b/src/data/selector.ts @@ -464,7 +464,48 @@ export const expandDeviceTarget = ( return { entities: newEntities }; }; -const deviceMeetsTargetSelector = ( +export const areaMeetsTargetSelector = ( + hass: HomeAssistant, + entities: HomeAssistant["entities"], + devices: HomeAssistant["devices"], + areaId: string, + targetSelector: TargetSelector, + entitySources?: EntitySources +): boolean => { + const hasMatchingdevice = Object.values(devices).some((device) => { + if ( + device.area_id === areaId && + deviceMeetsTargetSelector( + hass, + Object.values(entities), + device, + targetSelector, + entitySources + ) + ) { + return true; + } + return false; + }); + if (hasMatchingdevice) { + return true; + } + return Object.values(entities).some((entity) => { + if ( + entity.area_id === areaId && + entityMeetsTargetSelector( + hass.states[entity.entity_id], + targetSelector, + entitySources + ) + ) { + return true; + } + return false; + }); +}; + +export const deviceMeetsTargetSelector = ( hass: HomeAssistant, entityRegistry: EntityRegistryDisplayEntry[], device: DeviceRegistryEntry, @@ -500,7 +541,7 @@ const deviceMeetsTargetSelector = ( return true; }; -const entityMeetsTargetSelector = ( +export const entityMeetsTargetSelector = ( entity: HassEntity, targetSelector: TargetSelector, entitySources?: EntitySources