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
This commit is contained in:
Bram Kragten 2019-10-21 21:40:16 +02:00 committed by Paulus Schoutsen
parent 3973374f3f
commit 2424376fba

View File

@ -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 = `
<style>
paper-item {
margin: -10px 0;
padding: 0;
}
</style>
<paper-item>
<paper-item-body two-line="">
<div class='name'>[[item.name]]</div>
<div secondary>[[item.area]]</div>
</paper-item-body>
</paper-item>
`;
}
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`
<paper-dropdown-menu-light .label=${this.label}>
<paper-listbox
slot="dropdown-content"
.selected=${this._value}
attr-for-selected="data-device-id"
@iron-select=${this._deviceChanged}
<vaadin-combo-box-light
item-value-path="id"
item-id-path="id"
item-label-path="name"
.items=${devices}
.value=${this._value}
.renderer=${rowRenderer}
@opened-changed=${this._openedChanged}
@value-changed=${this._deviceChanged}
>
<paper-input
.label=${this.label}
class="input"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
spellcheck="false"
>
<paper-item data-device-id="">
No device
</paper-item>
${this._sortedDevices(this.devices).map(
(device) => html`
<paper-item data-device-id=${device.id}>
${device.name_by_user || device.name}
</paper-item>
`
)}
</paper-listbox>
</paper-dropdown-menu-light>
${this.value
? html`
<paper-icon-button
aria-label="Clear"
slot="suffix"
class="clear-button"
icon="hass:close"
no-ripple
>
Clear
</paper-icon-button>
`
: ""}
${devices.length > 0
? html`
<paper-icon-button
aria-label="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>
`;
}
@ -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<boolean>) {
this._opened = ev.detail.value;
}
private _deviceChanged(ev: PolymerChangedEvent<string>) {
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;
}
`;
}