Area/multiple devices and name support for thingtalk automations (#4272)

* WIP: Area/multiple devices and name support

* Fix removing devices

* Don't recalc entities for all devices every time

* Use guards

* Update ha-thingtalk-placeholders.ts
This commit is contained in:
Bram Kragten 2019-12-02 10:30:30 +01:00 committed by GitHub
parent caa604d5ca
commit 93165c9111
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 816 additions and 111 deletions

View File

@ -0,0 +1,412 @@
import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
import "@polymer/paper-listbox/paper-listbox";
import memoizeOne from "memoize-one";
import {
LitElement,
TemplateResult,
html,
css,
CSSResult,
customElement,
property,
PropertyValues,
} from "lit-element";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import "./ha-devices-picker";
import { HomeAssistant } from "../../types";
import { fireEvent } from "../../common/dom/fire_event";
import {
DeviceRegistryEntry,
subscribeDeviceRegistry,
} from "../../data/device_registry";
import { compare } from "../../common/string/compare";
import { PolymerChangedEvent } from "../../polymer-types";
import {
AreaRegistryEntry,
subscribeAreaRegistry,
} from "../../data/area_registry";
import { DeviceEntityLookup } from "../../panels/config/devices/ha-devices-data-table";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../../data/entity_registry";
import { computeDomain } from "../../common/entity/compute_domain";
interface DevicesByArea {
[areaId: string]: AreaDevices;
}
interface AreaDevices {
id?: string;
name: string;
devices: string[];
}
const rowRenderer = (
root: HTMLElement,
_owner,
model: { item: AreaDevices }
) => {
if (!root.firstElementChild) {
root.innerHTML = `
<style>
paper-item {
width: 100%;
margin: -10px 0;
padding: 0;
}
paper-icon-button {
float: right;
}
.devices {
display: none;
}
.devices.visible {
display: block;
}
</style>
<paper-item>
<paper-item-body two-line="">
<div class='name'>[[item.name]]</div>
<div secondary>[[item.devices.length]] devices</div>
</paper-item-body>
</paper-item>
`;
}
root.querySelector(".name")!.textContent = model.item.name!;
root.querySelector(
"[secondary]"
)!.textContent = `${model.item.devices.length.toString()} devices`;
};
@customElement("ha-area-devices-picker")
export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
@property() public hass!: HomeAssistant;
@property() public label?: string;
@property() public value?: string;
@property() public area?: string;
@property() public devices?: string[];
/**
* Show only devices with entities from specific domains.
* @type {Array}
* @attr include-domains
*/
@property({ type: Array, attribute: "include-domains" })
public includeDomains?: string[];
/**
* Show no devices with entities of these domains.
* @type {Array}
* @attr exclude-domains
*/
@property({ type: Array, attribute: "exclude-domains" })
public excludeDomains?: string[];
/**
* Show only deviced with entities of these device classes.
* @type {Array}
* @attr include-device-classes
*/
@property({ type: Array, attribute: "include-device-classes" })
public includeDeviceClasses?: string[];
@property({ type: Boolean })
private _opened?: boolean;
@property() private _areaPicker = true;
@property() private _devices?: DeviceRegistryEntry[];
@property() private _areas?: AreaRegistryEntry[];
@property() private _entities?: EntityRegistryEntry[];
private _selectedDevices: string[] = [];
private _filteredDevices: DeviceRegistryEntry[] = [];
private _getDevices = memoizeOne(
(
devices: DeviceRegistryEntry[],
areas: AreaRegistryEntry[],
entities: EntityRegistryEntry[],
includeDomains: this["includeDomains"],
excludeDomains: this["excludeDomains"],
includeDeviceClasses: this["includeDeviceClasses"]
): AreaDevices[] => {
if (!devices.length) {
return [];
}
const deviceEntityLookup: DeviceEntityLookup = {};
for (const entity of entities) {
if (!entity.device_id) {
continue;
}
if (!(entity.device_id in deviceEntityLookup)) {
deviceEntityLookup[entity.device_id] = [];
}
deviceEntityLookup[entity.device_id].push(entity);
}
let inputDevices = [...devices];
if (includeDomains) {
inputDevices = inputDevices.filter((device) => {
const devEntities = deviceEntityLookup[device.id];
if (!devEntities || !devEntities.length) {
return false;
}
return deviceEntityLookup[device.id].some((entity) =>
includeDomains.includes(computeDomain(entity.entity_id))
);
});
}
if (excludeDomains) {
inputDevices = inputDevices.filter((device) => {
const devEntities = deviceEntityLookup[device.id];
if (!devEntities || !devEntities.length) {
return true;
}
return entities.every(
(entity) =>
!excludeDomains.includes(computeDomain(entity.entity_id))
);
});
}
if (includeDeviceClasses) {
inputDevices = inputDevices.filter((device) => {
const devEntities = deviceEntityLookup[device.id];
if (!devEntities || !devEntities.length) {
return false;
}
return deviceEntityLookup[device.id].some((entity) => {
const stateObj = this.hass.states[entity.entity_id];
if (!stateObj) {
return false;
}
return (
stateObj.attributes.device_class &&
includeDeviceClasses.includes(stateObj.attributes.device_class)
);
});
});
}
this._filteredDevices = inputDevices;
const areaLookup: { [areaId: string]: AreaRegistryEntry } = {};
for (const area of areas) {
areaLookup[area.area_id] = area;
}
const devicesByArea: DevicesByArea = {};
for (const device of inputDevices) {
const areaId = device.area_id;
if (areaId) {
if (!(areaId in devicesByArea)) {
devicesByArea[areaId] = {
id: areaId,
name: areaLookup[areaId].name,
devices: [],
};
}
devicesByArea[areaId].devices.push(device.id);
}
}
const sorted = Object.keys(devicesByArea)
.sort((a, b) =>
compare(devicesByArea[a].name || "", devicesByArea[b].name || "")
)
.map((key) => devicesByArea[key]);
return sorted;
}
);
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeDeviceRegistry(this.hass.connection!, (devices) => {
this._devices = devices;
}),
subscribeAreaRegistry(this.hass.connection!, (areas) => {
this._areas = areas;
}),
subscribeEntityRegistry(this.hass.connection!, (entities) => {
this._entities = entities;
}),
];
}
protected updated(changedProps: PropertyValues) {
if (changedProps.has("area") && this.area) {
this._areaPicker = true;
this.value = this.area;
} else if (changedProps.has("devices") && this.devices) {
this._areaPicker = false;
const filteredDeviceIds = this._filteredDevices.map(
(device) => device.id
);
const selectedDevices = this.devices.filter((device) =>
filteredDeviceIds.includes(device)
);
this._setValue(selectedDevices);
}
}
protected render(): TemplateResult | void {
if (!this._devices || !this._areas || !this._entities) {
return;
}
const areas = this._getDevices(
this._devices,
this._areas,
this._entities,
this.includeDomains,
this.excludeDomains,
this.includeDeviceClasses
);
if (!this._areaPicker || areas.length === 0) {
return html`
<ha-devices-picker
@value-changed=${this._devicesPicked}
.hass=${this.hass}
.includeDomains=${this.includeDomains}
.includeDeviceClasses=${this.includeDeviceClasses}
.value=${this._selectedDevices}
.pickDeviceLabel=${`Add ${this.label} device`}
.pickedDeviceLabel=${`${this.label} device`}
></ha-devices-picker>
${areas.length > 0
? html`
<mwc-button @click=${this._switchPicker}
>Choose an area</mwc-button
>
`
: ""}
`;
}
return html`
<vaadin-combo-box-light
item-value-path="id"
item-id-path="id"
item-label-path="name"
.items=${areas}
.value=${this._value}
.renderer=${rowRenderer}
@opened-changed=${this._openedChanged}
@value-changed=${this._areaPicked}
>
<paper-input
.label=${this.label === undefined && this.hass
? this.hass.localize("ui.components.device-picker.device")
: `${this.label} in area`}
class="input"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
spellcheck="false"
>
${this.value
? html`
<paper-icon-button
aria-label=${this.hass.localize(
"ui.components.device-picker.clear"
)}
slot="suffix"
class="clear-button"
icon="hass:close"
@click=${this._clearValue}
no-ripple
>
Clear
</paper-icon-button>
`
: ""}
${areas.length > 0
? html`
<paper-icon-button
aria-label=${this.hass.localize(
"ui.components.device-picker.show_devices"
)}
slot="suffix"
class="toggle-button"
.icon=${this._opened ? "hass:menu-up" : "hass:menu-down"}
>
Toggle
</paper-icon-button>
`
: ""}
</paper-input>
</vaadin-combo-box-light>
<mwc-button @click=${this._switchPicker}
>Choose individual devices</mwc-button
>
`;
}
private _clearValue(ev: Event) {
ev.stopPropagation();
this._setValue([]);
}
private get _value() {
return this.value || [];
}
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
this._opened = ev.detail.value;
}
private async _switchPicker() {
this._areaPicker = !this._areaPicker;
}
private async _areaPicked(ev: PolymerChangedEvent<string>) {
const value = ev.detail.value;
let selectedDevices = [];
const target = ev.target as any;
if (target.selectedItem) {
selectedDevices = target.selectedItem.devices;
}
if (value !== this._value || this._selectedDevices !== selectedDevices) {
this._setValue(selectedDevices, value);
}
}
private _devicesPicked(ev: CustomEvent) {
ev.stopPropagation();
const selectedDevices = ev.detail.value;
this._setValue(selectedDevices);
}
private _setValue(selectedDevices: string[], value = "") {
this.value = value;
this._selectedDevices = selectedDevices;
setTimeout(() => {
fireEvent(this, "value-changed", { value: selectedDevices });
fireEvent(this, "change");
}, 0);
}
static get styles(): CSSResult {
return css`
paper-input > paper-icon-button {
width: 24px;
height: 24px;
padding: 2px;
color: var(--secondary-text-color);
}
[hidden] {
display: none;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-area-devices-picker": HaAreaDevicesPicker;
}
}

View File

@ -0,0 +1,127 @@
import {
LitElement,
TemplateResult,
property,
html,
customElement,
} from "lit-element";
import "@polymer/paper-icon-button/paper-icon-button-light";
import { HomeAssistant } from "../../types";
import { PolymerChangedEvent } from "../../polymer-types";
import { fireEvent } from "../../common/dom/fire_event";
import "./ha-device-picker";
@customElement("ha-devices-picker")
class HaDevicesPicker extends LitElement {
@property() public hass?: HomeAssistant;
@property() public value?: string[];
/**
* Show entities from specific domains.
* @type {string}
* @attr include-domains
*/
@property({ type: Array, attribute: "include-domains" })
public includeDomains?: string[];
/**
* Show no entities of these domains.
* @type {Array}
* @attr exclude-domains
*/
@property({ type: Array, attribute: "exclude-domains" })
public excludeDomains?: string[];
@property({ attribute: "picked-device-label" })
@property({ type: Array, attribute: "include-device-classes" })
public includeDeviceClasses?: string[];
public pickedDeviceLabel?: string;
@property({ attribute: "pick-device-label" }) public pickDeviceLabel?: string;
protected render(): TemplateResult | void {
if (!this.hass) {
return;
}
const currentDevices = this._currentDevices;
return html`
${currentDevices.map(
(entityId) => html`
<div>
<ha-device-picker
allow-custom-entity
.curValue=${entityId}
.hass=${this.hass}
.includeDomains=${this.includeDomains}
.excludeDomains=${this.excludeDomains}
.includeDeviceClasses=${this.includeDeviceClasses}
.value=${entityId}
.label=${this.pickedDeviceLabel}
@value-changed=${this._deviceChanged}
></ha-device-picker>
</div>
`
)}
<div>
<ha-device-picker
.hass=${this.hass}
.includeDomains=${this.includeDomains}
.excludeDomains=${this.excludeDomains}
.includeDeviceClasses=${this.includeDeviceClasses}
.label=${this.pickDeviceLabel}
@value-changed=${this._addDevice}
></ha-device-picker>
</div>
`;
}
private get _currentDevices() {
return this.value || [];
}
private async _updateDevices(devices) {
fireEvent(this, "value-changed", {
value: devices,
});
this.value = devices;
}
private _deviceChanged(event: PolymerChangedEvent<string>) {
event.stopPropagation();
const curValue = (event.currentTarget as any).curValue;
const newValue = event.detail.value;
if (newValue === curValue || newValue !== "") {
return;
}
if (newValue === "") {
this._updateDevices(
this._currentDevices.filter((dev) => dev !== curValue)
);
} else {
this._updateDevices(
this._currentDevices.map((dev) => (dev === curValue ? newValue : dev))
);
}
}
private async _addDevice(event: PolymerChangedEvent<string>) {
event.stopPropagation();
const toAdd = event.detail.value;
(event.currentTarget as any).value = "";
if (!toAdd) {
return;
}
const currentDevices = this._currentDevices;
if (currentDevices.includes(toAdd)) {
return;
}
this._updateDevices([...currentDevices, toAdd]);
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-devices-picker": HaDevicesPicker;
}
}

View File

@ -27,6 +27,7 @@ import { PlaceholderValues } from "./ha-thingtalk-placeholders";
import { convertThingTalk } from "../../../../data/cloud";
export interface Placeholder {
name: string;
index: number;
fields: string[];
domains: string[];
@ -177,8 +178,21 @@ class DialogThingtalk extends LitElement {
const placeholderValues = ev.detail.value as PlaceholderValues;
Object.entries(placeholderValues).forEach(([type, values]) => {
Object.entries(values).forEach(([index, placeholder]) => {
Object.entries(placeholder).forEach(([field, value]) => {
this._config[type][index][field] = value;
const devices = Object.values(placeholder);
if (devices.length === 1) {
Object.entries(devices[0]).forEach(([field, value]) => {
this._config[type][index][field] = value;
return;
});
}
const automation = { ...this._config[type][index] };
delete this._config[type][index];
devices.forEach((fields) => {
const newAutomation = { ...automation };
Object.entries(fields).forEach(([field, value]) => {
newAutomation[field] = value;
});
this._config[type].push(newAutomation);
});
});
});

View File

@ -6,8 +6,11 @@ import {
customElement,
css,
CSSResult,
query,
PropertyValues,
} from "lit-element";
import "../../../../components/device/ha-area-devices-picker";
import { HomeAssistant } from "../../../../types";
import { PolymerChangedEvent } from "../../../../polymer-types";
import { fireEvent } from "../../../../common/dom/fire_event";
@ -17,8 +20,15 @@ import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
import { subscribeEntityRegistry } from "../../../../data/entity_registry";
import { computeDomain } from "../../../../common/entity/compute_domain";
import { HassEntity } from "home-assistant-js-websocket";
import { HaDevicePicker } from "../../../../components/device/ha-device-picker";
import { getPath, applyPatch } from "../../../../common/util/patch";
import {
subscribeAreaRegistry,
AreaRegistryEntry,
} from "../../../../data/area_registry";
import {
subscribeDeviceRegistry,
DeviceRegistryEntry,
} from "../../../../data/device_registry";
declare global {
// for fire event
@ -28,7 +38,23 @@ declare global {
}
export interface PlaceholderValues {
[key: string]: { [index: number]: { [key: string]: string } };
[key: string]: {
[index: number]: {
[index: number]: { device_id?: string; entity_id?: string };
};
};
}
export interface ExtraInfo {
[key: string]: {
[index: number]: {
[index: number]: {
area_id?: string;
device_ids?: string[];
manualEntity: boolean;
};
};
};
}
interface DeviceEntitiesLookup {
@ -43,9 +69,11 @@ export class ThingTalkPlaceholders extends SubscribeMixin(LitElement) {
@property() public placeholders!: PlaceholderContainer;
@property() private _error?: string;
private _deviceEntityLookup: DeviceEntitiesLookup = {};
private _manualEntities: PlaceholderValues = {};
@property() private _extraInfo: ExtraInfo = {};
@property() private _placeholderValues: PlaceholderValues = {};
@query("#device-entity-picker") private _deviceEntityPicker?: HaDevicePicker;
private _devices?: DeviceRegistryEntry[];
private _areas?: AreaRegistryEntry[];
private _search = false;
public hassSubscribe() {
return [
@ -66,12 +94,28 @@ export class ThingTalkPlaceholders extends SubscribeMixin(LitElement) {
}
}
}),
subscribeDeviceRegistry(this.hass.connection!, (devices) => {
this._devices = devices;
this._searchNames();
}),
subscribeAreaRegistry(this.hass.connection!, (areas) => {
this._areas = areas;
this._searchNames();
}),
];
}
protected updated(changedProps: PropertyValues) {
if (changedProps.has("placeholders")) {
this._search = true;
this._searchNames();
}
}
protected render(): TemplateResult | void {
return html`
<ha-paper-dialog
modal
with-backdrop
.opened=${this.opened}
@opened-changed="${this._openedChanged}"
@ -93,55 +137,66 @@ export class ThingTalkPlaceholders extends SubscribeMixin(LitElement) {
</h3>
${placeholders.map((placeholder) => {
if (placeholder.fields.includes("device_id")) {
const extraInfo = getPath(this._extraInfo, [
type,
placeholder.index,
]);
return html`
<ha-device-picker
<ha-area-devices-picker
.type=${type}
.placeholder=${placeholder}
@change=${this._devicePicked}
@value-changed=${this._devicePicked}
.hass=${this.hass}
.area=${extraInfo ? extraInfo.area_id : undefined}
.devices=${extraInfo && extraInfo.device_ids
? extraInfo.device_ids
: undefined}
.includeDomains=${placeholder.domains}
.includeDeviceClasses=${placeholder.device_classes}
.label=${this._getLabel(
placeholder.domains,
placeholder.device_classes
)}
></ha-device-picker>
${(getPath(this._placeholderValues, [
type,
placeholder.index,
"device_id",
]) &&
placeholder.fields.includes("entity_id") &&
getPath(this._placeholderValues, [
type,
placeholder.index,
"entity_id",
]) === undefined) ||
getPath(this._manualEntities, [
type,
placeholder.index,
"manual",
]) === true
></ha-area-devices-picker>
${extraInfo && extraInfo.manualEntity
? html`
<ha-entity-picker
id="device-entity-picker"
.type=${type}
.placeholder=${placeholder}
@change=${this._entityPicked}
.includeDomains=${placeholder.domains}
.includeDeviceClasses=${placeholder.device_classes}
.hass=${this.hass}
.label=${this._getLabel(
placeholder.domains,
placeholder.device_classes
)}
.entityFilter=${(state: HassEntity) =>
this._deviceEntityLookup[
this._placeholderValues[type][
placeholder.index
].device_id
].includes(state.entity_id)}
></ha-entity-picker>
<h3>
One or more devices have more than one matching
entity, please pick the one you want to use.
</h3>
${Object.keys(extraInfo.manualEntity).map(
(idx) => html`
<ha-entity-picker
id="device-entity-picker"
.type=${type}
.placeholder=${placeholder}
.index=${idx}
@change=${this._entityPicked}
.includeDomains=${placeholder.domains}
.includeDeviceClasses=${placeholder.device_classes}
.hass=${this.hass}
.label=${`${this._getLabel(
placeholder.domains,
placeholder.device_classes
)} of device ${this._getDeviceName(
getPath(this._placeholderValues, [
type,
placeholder.index,
idx,
"device_id",
])
)}`}
.entityFilter=${(state: HassEntity) => {
const devId = this._placeholderValues[type][
placeholder.index
][idx].device_id;
return this._deviceEntityLookup[
devId
].includes(state.entity_id);
}}
></ha-entity-picker>
`
)}
`
: ""}
`;
@ -189,17 +244,74 @@ export class ThingTalkPlaceholders extends SubscribeMixin(LitElement) {
`;
}
private _getDeviceName(deviceId: string): string {
if (!this._devices) {
return "";
}
const foundDevice = this._devices.find((device) => device.id === deviceId);
if (!foundDevice) {
return "";
}
return foundDevice.name_by_user || foundDevice.name || "";
}
private _searchNames() {
if (!this._search || !this._areas || !this._devices) {
return;
}
this._search = false;
Object.entries(this.placeholders).forEach(([type, placeholders]) =>
placeholders.forEach((placeholder) => {
if (!placeholder.name) {
return;
}
const name = placeholder.name;
const foundArea = this._areas!.find((area) =>
area.name.toLowerCase().includes(name)
);
if (foundArea) {
applyPatch(
this._extraInfo,
[type, placeholder.index, "area_id"],
foundArea.area_id
);
this.requestUpdate("_extraInfo");
return;
}
const foundDevices = this._devices!.filter((device) => {
const deviceName = device.name_by_user || device.name;
if (!deviceName) {
return false;
}
return deviceName.toLowerCase().includes(name);
});
if (foundDevices.length) {
applyPatch(
this._extraInfo,
[type, placeholder.index, "device_ids"],
foundDevices.map((device) => device.id)
);
this.requestUpdate("_extraInfo");
}
})
);
}
private get _isDone(): boolean {
return Object.entries(this.placeholders).every(([type, placeholders]) =>
placeholders.every((placeholder) =>
placeholder.fields.every(
(field) =>
getPath(this._placeholderValues, [
type,
placeholder.index,
field,
]) !== undefined
)
placeholder.fields.every((field) => {
const entries: {
[key: number]: { device_id?: string; entity_id?: string };
} = getPath(this._placeholderValues, [type, placeholder.index]);
if (!entries) {
return false;
}
const values = Object.values(entries);
return values.every(
(entry) => entry[field] !== undefined && entry[field] !== ""
);
})
)
);
}
@ -212,76 +324,115 @@ export class ThingTalkPlaceholders extends SubscribeMixin(LitElement) {
}`;
}
private _devicePicked(ev: Event): void {
private _devicePicked(ev: CustomEvent): void {
const value: string[] = ev.detail.value;
if (!value) {
return;
}
const target = ev.target as any;
const placeholder = target.placeholder as Placeholder;
const value = target.value;
const type = target.type;
applyPatch(
this._placeholderValues,
[type, placeholder.index, "device_id"],
value
);
if (!placeholder.fields.includes("entity_id")) {
return;
let oldValues = getPath(this._placeholderValues, [type, placeholder.index]);
if (oldValues) {
oldValues = Object.values(oldValues);
}
if (value === "") {
delete this._placeholderValues[type][placeholder.index].entity_id;
if (this._deviceEntityPicker) {
this._deviceEntityPicker.value = undefined;
}
applyPatch(
this._manualEntities,
[type, placeholder.index, "manual"],
false
);
const oldExtraInfo = getPath(this._extraInfo, [type, placeholder.index]);
if (this._placeholderValues[type]) {
delete this._placeholderValues[type][placeholder.index];
}
if (this._extraInfo[type]) {
delete this._extraInfo[type][placeholder.index];
}
if (!value.length) {
this.requestUpdate("_placeholderValues");
return;
}
const devEntities = this._deviceEntityLookup[value];
const entities = devEntities.filter((eid) => {
if (placeholder.device_classes) {
const stateObj = this.hass.states[eid];
if (!stateObj) {
return false;
value.forEach((deviceId, index) => {
let oldIndex;
if (oldValues) {
const oldDevice = oldValues.find((oldVal, idx) => {
oldIndex = idx;
return oldVal.device_id === deviceId;
});
if (oldDevice) {
applyPatch(
this._placeholderValues,
[type, placeholder.index, index],
oldDevice
);
if (oldExtraInfo) {
applyPatch(
this._extraInfo,
[type, placeholder.index, index],
oldExtraInfo[oldIndex]
);
}
return;
}
return (
placeholder.domains.includes(computeDomain(eid)) &&
stateObj.attributes.device_class &&
placeholder.device_classes.includes(stateObj.attributes.device_class)
);
}
return placeholder.domains.includes(computeDomain(eid));
});
if (entities.length === 0) {
// Should not happen because we filter the device picker on domain
this._error = `No ${placeholder.domains
.map((domain) => this.hass.localize(`domain.${domain}`))
.join(", ")} entities found in this device.`;
} else if (entities.length === 1) {
applyPatch(
this._placeholderValues,
[type, placeholder.index, "entity_id"],
entities[0]
[type, placeholder.index, index, "device_id"],
deviceId
);
applyPatch(
this._manualEntities,
[type, placeholder.index, "manual"],
false
);
this.requestUpdate("_placeholderValues");
} else {
delete this._placeholderValues[type][placeholder.index].entity_id;
if (this._deviceEntityPicker) {
this._deviceEntityPicker.value = undefined;
if (!placeholder.fields.includes("entity_id")) {
return;
}
applyPatch(
this._manualEntities,
[type, placeholder.index, "manual"],
true
);
this.requestUpdate("_placeholderValues");
}
const devEntities = this._deviceEntityLookup[deviceId];
const entities = devEntities.filter((eid) => {
if (placeholder.device_classes) {
const stateObj = this.hass.states[eid];
if (!stateObj) {
return false;
}
return (
placeholder.domains.includes(computeDomain(eid)) &&
stateObj.attributes.device_class &&
placeholder.device_classes.includes(
stateObj.attributes.device_class
)
);
}
return placeholder.domains.includes(computeDomain(eid));
});
if (entities.length === 0) {
// Should not happen because we filter the device picker on domain
this._error = `No ${placeholder.domains
.map((domain) => this.hass.localize(`domain.${domain}`))
.join(", ")} entities found in this device.`;
} else if (entities.length === 1) {
applyPatch(
this._placeholderValues,
[type, placeholder.index, index, "entity_id"],
entities[0]
);
this.requestUpdate("_placeholderValues");
} else {
delete this._placeholderValues[type][placeholder.index][index]
.entity_id;
applyPatch(
this._extraInfo,
[type, placeholder.index, "manualEntity", index],
true
);
this.requestUpdate("_placeholderValues");
}
});
fireEvent(
this.shadowRoot!.querySelector("ha-paper-dialog")! as HTMLElement,
"iron-resize"
);
}
private _entityPicked(ev: Event): void {
@ -289,9 +440,10 @@ export class ThingTalkPlaceholders extends SubscribeMixin(LitElement) {
const placeholder = target.placeholder as Placeholder;
const value = target.value;
const type = target.type;
const index = target.index || 0;
applyPatch(
this._placeholderValues,
[type, placeholder.index, "entity_id"],
[type, placeholder.index, index, "entity_id"],
value
);
this.requestUpdate("_placeholderValues");