From 2424376fba12c5070a56aaae063cb6cb951fd8c6 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 21 Oct 2019 21:40:16 +0200 Subject: [PATCH] Change ha-device-picker to combo box + improve name handling + show area (#4089) * Change ha-device-picker to combo box + improve name handling + show area * unused import --- src/components/device/ha-device-picker.ts | 205 ++++++++++++++++++---- 1 file changed, 168 insertions(+), 37 deletions(-) diff --git a/src/components/device/ha-device-picker.ts b/src/components/device/ha-device-picker.ts index edea4378c5..80da813f6b 100644 --- a/src/components/device/ha-device-picker.ts +++ b/src/components/device/ha-device-picker.ts @@ -1,7 +1,7 @@ import "@polymer/paper-input/paper-input"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item-body"; -import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light"; +import "@vaadin/vaadin-combo-box/vaadin-combo-box-light"; import "@polymer/paper-listbox/paper-listbox"; import memoizeOne from "memoize-one"; import { @@ -23,52 +23,165 @@ import { 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 { computeStateName } from "../../common/entity/compute_state_name"; + +interface Device { + name: string; + area: string; + id: string; +} + +const rowRenderer = (root: HTMLElement, _owner, model: { item: Device }) => { + if (!root.firstElementChild) { + root.innerHTML = ` + + + +
[[item.name]]
+
[[item.area]]
+
+
+ `; + } + + root.querySelector(".name")!.textContent = model.item.name!; + root.querySelector("[secondary]")!.textContent = model.item.area!; +}; @customElement("ha-device-picker") class HaDevicePicker extends SubscribeMixin(LitElement) { - @property() public hass?: HomeAssistant; + @property() public hass!: HomeAssistant; @property() public label?: string; @property() public value?: string; @property() public devices?: DeviceRegistryEntry[]; + @property() public areas?: AreaRegistryEntry[]; + @property() public entities?: EntityRegistryEntry[]; + @property({ type: Boolean }) private _opened?: boolean; - private _sortedDevices = memoizeOne((devices?: DeviceRegistryEntry[]) => { - if (!devices || devices.length === 1) { - return devices || []; + private _getDevices = memoizeOne( + ( + devices: DeviceRegistryEntry[], + areas: AreaRegistryEntry[], + entities: EntityRegistryEntry[] + ): Device[] => { + 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); + } + + const areaLookup: { [areaId: string]: AreaRegistryEntry } = {}; + for (const area of areas) { + areaLookup[area.area_id] = area; + } + + const outputDevices = devices.map((device) => { + return { + id: device.id, + name: + device.name_by_user || + device.name || + this._fallbackDeviceName(device.id, deviceEntityLookup) || + "No name", + area: device.area_id ? areaLookup[device.area_id].name : "No area", + }; + }); + if (outputDevices.length === 1) { + return outputDevices; + } + return outputDevices.sort((a, b) => compare(a.name || "", b.name || "")); } - const sorted = [...devices]; - sorted.sort((a, b) => compare(a.name || "", b.name || "")); - return sorted; - }); + ); public hassSubscribe(): UnsubscribeFunc[] { return [ - subscribeDeviceRegistry(this.hass!.connection!, (devices) => { + 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 render(): TemplateResult | void { + if (!this.devices || !this.areas || !this.entities) { + return; + } + const devices = this._getDevices(this.devices, this.areas, this.entities); return html` - - + - - No device - - ${this._sortedDevices(this.devices).map( - (device) => html` - - ${device.name_by_user || device.name} - - ` - )} - - + ${this.value + ? html` + + Clear + + ` + : ""} + ${devices.length > 0 + ? html` + + Toggle + + ` + : ""} + + `; } @@ -76,8 +189,12 @@ class HaDevicePicker extends SubscribeMixin(LitElement) { return this.value || ""; } - private _deviceChanged(ev) { - const newValue = ev.detail.item.dataset.deviceId; + private _openedChanged(ev: PolymerChangedEvent) { + this._opened = ev.detail.value; + } + + private _deviceChanged(ev: PolymerChangedEvent) { + const newValue = ev.detail.value; if (newValue !== this._value) { this.value = newValue; @@ -88,16 +205,30 @@ class HaDevicePicker extends SubscribeMixin(LitElement) { } } + private _fallbackDeviceName( + deviceId: string, + deviceEntityLookup: DeviceEntityLookup + ): string | undefined { + for (const entity of deviceEntityLookup[deviceId] || []) { + const stateObj = this.hass.states[entity.entity_id]; + if (stateObj) { + return computeStateName(stateObj); + } + } + + return undefined; + } + static get styles(): CSSResult { return css` - paper-dropdown-menu-light { - width: 100%; + paper-input > paper-icon-button { + width: 24px; + height: 24px; + padding: 2px; + color: var(--secondary-text-color); } - paper-listbox { - min-width: 200px; - } - paper-item { - cursor: pointer; + [hidden] { + display: none; } `; }