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;
}
`;
}