From 577a21fc5ca19ac7bca0bbdd547671924db54c3d Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Tue, 12 May 2020 15:42:29 -0400 Subject: [PATCH] Rework ZHA group adds and removes (#5602) --- src/data/zha.ts | 22 ++- src/panels/config/zha/zha-add-group-page.ts | 34 ++-- src/panels/config/zha/zha-config-dashboard.ts | 13 +- .../zha/zha-device-endpoint-data-table.ts | 182 ++++++++++++++++++ .../config/zha/zha-devices-data-table.ts | 124 ------------ src/panels/config/zha/zha-group-page.ts | 95 +++++---- 6 files changed, 276 insertions(+), 194 deletions(-) create mode 100644 src/panels/config/zha/zha-device-endpoint-data-table.ts delete mode 100644 src/panels/config/zha/zha-devices-data-table.ts diff --git a/src/data/zha.ts b/src/data/zha.ts index af8f79be8d..41c4f0805c 100644 --- a/src/data/zha.ts +++ b/src/data/zha.ts @@ -3,6 +3,7 @@ import { HomeAssistant } from "../types"; export interface ZHAEntityReference extends HassEntity { name: string; + original_name?: string; } export interface ZHADevice { @@ -26,6 +27,12 @@ export interface ZHADevice { signature: any; } +export interface ZHADeviceEndpoint { + device: ZHADevice; + endpoint_id: number; + entities: ZHAEntityReference[]; +} + export interface Attribute { name: string; id: number; @@ -56,7 +63,12 @@ export interface ReadAttributeServiceData { export interface ZHAGroup { name: string; group_id: number; - members: ZHADevice[]; + members: ZHADeviceEndpoint[]; +} + +export interface ZHAGroupMember { + ieee: string; + endpoint_id: string; } export const reconfigureNode = ( @@ -213,7 +225,7 @@ export const fetchGroup = ( export const fetchGroupableDevices = ( hass: HomeAssistant -): Promise => +): Promise => hass.callWS({ type: "zha/devices/groupable", }); @@ -221,7 +233,7 @@ export const fetchGroupableDevices = ( export const addMembersToGroup = ( hass: HomeAssistant, groupId: number, - membersToAdd: string[] + membersToAdd: ZHAGroupMember[] ): Promise => hass.callWS({ type: "zha/group/members/add", @@ -232,7 +244,7 @@ export const addMembersToGroup = ( export const removeMembersFromGroup = ( hass: HomeAssistant, groupId: number, - membersToRemove: string[] + membersToRemove: ZHAGroupMember[] ): Promise => hass.callWS({ type: "zha/group/members/remove", @@ -243,7 +255,7 @@ export const removeMembersFromGroup = ( export const addGroup = ( hass: HomeAssistant, groupName: string, - membersToAdd?: string[] + membersToAdd?: ZHAGroupMember[] ): Promise => hass.callWS({ type: "zha/group/add", diff --git a/src/panels/config/zha/zha-add-group-page.ts b/src/panels/config/zha/zha-add-group-page.ts index c52e6da84a..eb85e899ab 100644 --- a/src/panels/config/zha/zha-add-group-page.ts +++ b/src/panels/config/zha/zha-add-group-page.ts @@ -18,31 +18,31 @@ import type { SelectionChangedEvent } from "../../../components/data-table/ha-da import { addGroup, fetchGroupableDevices, - ZHADevice, ZHAGroup, + ZHADeviceEndpoint, } from "../../../data/zha"; import "../../../layouts/hass-error-screen"; import "../../../layouts/hass-subpage"; import type { PolymerChangedEvent } from "../../../polymer-types"; import type { HomeAssistant } from "../../../types"; import "../ha-config-section"; -import "./zha-devices-data-table"; -import type { ZHADevicesDataTable } from "./zha-devices-data-table"; +import "./zha-device-endpoint-data-table"; +import type { ZHADeviceEndpointDataTable } from "./zha-device-endpoint-data-table"; @customElement("zha-add-group-page") export class ZHAAddGroupPage extends LitElement { - @property() public hass!: HomeAssistant; + @property({ type: Object }) public hass!: HomeAssistant; - @property() public narrow!: boolean; + @property({ type: Boolean }) public narrow!: boolean; - @property() public devices: ZHADevice[] = []; + @property({ type: Array }) public deviceEndpoints: ZHADeviceEndpoint[] = []; @property() private _processingAdd = false; @property() private _groupName = ""; - @query("zha-devices-data-table") - private _zhaDevicesDataTable!: ZHADevicesDataTable; + @query("zha-device-endpoint-data-table") + private _zhaDevicesDataTable!: ZHADeviceEndpointDataTable; private _firstUpdatedCalled = false; @@ -87,14 +87,14 @@ export class ZHAAddGroupPage extends LitElement { ${this.hass.localize("ui.panel.config.zha.groups.add_members")} - - +
{ this._processingAdd = true; - const group: ZHAGroup = await addGroup( - this.hass, - this._groupName, - this._selectedDevicesToAdd - ); + const members = this._selectedDevicesToAdd.map((member) => { + const memberParts = member.split("_"); + return { ieee: memberParts[0], endpoint_id: memberParts[1] }; + }); + const group: ZHAGroup = await addGroup(this.hass, this._groupName, members); this._selectedDevicesToAdd = []; this._processingAdd = false; this._groupName = ""; diff --git a/src/panels/config/zha/zha-config-dashboard.ts b/src/panels/config/zha/zha-config-dashboard.ts index 4acf5faa91..88422f56c8 100644 --- a/src/panels/config/zha/zha-config-dashboard.ts +++ b/src/panels/config/zha/zha-config-dashboard.ts @@ -16,6 +16,7 @@ import "../../../components/data-table/ha-data-table"; import type { DataTableColumnContainer, RowClickedEvent, + DataTableRowData, } from "../../../components/data-table/ha-data-table"; import "../../../components/ha-card"; import "../../../components/ha-icon-next"; @@ -27,19 +28,19 @@ import type { HomeAssistant, Route } from "../../../types"; import "../ha-config-section"; import { formatAsPaddedHex, sortZHADevices } from "./functions"; -export interface DeviceRowData extends ZHADevice { +export interface DeviceRowData extends DataTableRowData { device?: DeviceRowData; } @customElement("zha-config-dashboard") class ZHAConfigDashboard extends LitElement { - @property() public hass!: HomeAssistant; + @property({ type: Object }) public hass!: HomeAssistant; - @property() public route!: Route; + @property({ type: Object }) public route!: Route; - @property() public narrow!: boolean; + @property({ type: Boolean }) public narrow!: boolean; - @property() public isWide!: boolean; + @property({ type: Boolean }) public isWide!: boolean; @property() private _devices: ZHADevice[] = []; @@ -91,7 +92,7 @@ class ZHAConfigDashboard extends LitElement { title: "IEEE", sortable: true, filterable: true, - width: "25%", + width: "30%", }, } ); diff --git a/src/panels/config/zha/zha-device-endpoint-data-table.ts b/src/panels/config/zha/zha-device-endpoint-data-table.ts new file mode 100644 index 0000000000..e758bb79e0 --- /dev/null +++ b/src/panels/config/zha/zha-device-endpoint-data-table.ts @@ -0,0 +1,182 @@ +import { + customElement, + html, + LitElement, + property, + query, + TemplateResult, + css, + CSSResult, +} from "lit-element"; +import memoizeOne from "memoize-one"; +import "../../../components/data-table/ha-data-table"; +import type { + DataTableColumnContainer, + HaDataTable, + DataTableRowData, +} from "../../../components/data-table/ha-data-table"; +import "../../../components/entity/ha-state-icon"; +import type { ZHADeviceEndpoint, ZHAEntityReference } from "../../../data/zha"; +import { showZHADeviceInfoDialog } from "../../../dialogs/zha-device-info-dialog/show-dialog-zha-device-info"; +import type { HomeAssistant } from "../../../types"; + +export interface DeviceEndpointRowData extends DataTableRowData { + id: string; + name: string; + model: string; + manufacturer: string; + endpoint_id: number; + entities: ZHAEntityReference[]; +} + +@customElement("zha-device-endpoint-data-table") +export class ZHADeviceEndpointDataTable extends LitElement { + @property({ type: Object }) public hass!: HomeAssistant; + + @property({ type: Boolean }) public narrow = false; + + @property({ type: Boolean }) public selectable = false; + + @property({ type: Array }) public deviceEndpoints: ZHADeviceEndpoint[] = []; + + @query("ha-data-table") private _dataTable!: HaDataTable; + + private _deviceEndpoints = memoizeOne( + (deviceEndpoints: ZHADeviceEndpoint[]) => { + const outputDevices: DeviceEndpointRowData[] = []; + + deviceEndpoints.forEach((deviceEndpoint) => { + outputDevices.push({ + name: + deviceEndpoint.device.user_given_name || deviceEndpoint.device.name, + model: deviceEndpoint.device.model, + manufacturer: deviceEndpoint.device.manufacturer, + id: deviceEndpoint.device.ieee + "_" + deviceEndpoint.endpoint_id, + ieee: deviceEndpoint.device.ieee, + endpoint_id: deviceEndpoint.endpoint_id, + entities: deviceEndpoint.entities, + }); + }); + + return outputDevices; + } + ); + + private _columns = memoizeOne( + (narrow: boolean): DataTableColumnContainer => + narrow + ? { + name: { + title: "Devices", + sortable: true, + filterable: true, + direction: "asc", + grows: true, + template: (name) => html` +
+ ${name} +
+ `, + }, + endpoint_id: { + title: "Endpoint", + sortable: true, + filterable: true, + }, + } + : { + name: { + title: "Name", + sortable: true, + filterable: true, + direction: "asc", + grows: true, + template: (name) => html` +
+ ${name} +
+ `, + }, + endpoint_id: { + title: "Endpoint", + sortable: true, + filterable: true, + }, + entities: { + title: "Associated Entities", + sortable: false, + filterable: false, + width: "50%", + template: (entities) => html` + ${entities.length + ? entities.length > 3 + ? html`${entities.slice(0, 2).map( + (entity) => + html`
+ ${entity.name || entity.original_name} +
` + )} +
And ${entities.length - 2} more...
` + : entities.map( + (entity) => + html`
+ ${entity.name || entity.original_name} +
` + ) + : "This endpoint has no associated entities"} + `, + }, + } + ); + + public clearSelection() { + this._dataTable.clearSelection(); + } + + protected render(): TemplateResult { + return html` + + `; + } + + private async _handleClicked(ev: CustomEvent) { + const rowId = ((ev.target as HTMLElement).closest( + ".mdc-data-table__row" + ) as any).rowId; + const ieee = rowId.substring(0, rowId.indexOf("_")); + showZHADeviceInfoDialog(this, { ieee }); + } + + static get styles(): CSSResult[] { + return [ + css` + .table-cell-text { + word-break: break-word; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "zha-device-endpoint-data-table": ZHADeviceEndpointDataTable; + } +} diff --git a/src/panels/config/zha/zha-devices-data-table.ts b/src/panels/config/zha/zha-devices-data-table.ts deleted file mode 100644 index 3808868633..0000000000 --- a/src/panels/config/zha/zha-devices-data-table.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { - customElement, - html, - LitElement, - property, - query, - TemplateResult, -} from "lit-element"; -import memoizeOne from "memoize-one"; -import "../../../components/data-table/ha-data-table"; -import type { - DataTableColumnContainer, - HaDataTable, -} from "../../../components/data-table/ha-data-table"; -import "../../../components/entity/ha-state-icon"; -import type { ZHADevice } from "../../../data/zha"; -import { showZHADeviceInfoDialog } from "../../../dialogs/zha-device-info-dialog/show-dialog-zha-device-info"; -import type { HomeAssistant } from "../../../types"; - -export interface DeviceRowData extends ZHADevice { - device?: DeviceRowData; -} - -@customElement("zha-devices-data-table") -export class ZHADevicesDataTable extends LitElement { - @property() public hass!: HomeAssistant; - - @property() public narrow = false; - - @property({ type: Boolean }) public selectable = false; - - @property() public devices: ZHADevice[] = []; - - @query("ha-data-table") private _dataTable!: HaDataTable; - - private _devices = memoizeOne((devices: ZHADevice[]) => { - let outputDevices: DeviceRowData[] = devices; - - outputDevices = outputDevices.map((device) => { - return { - ...device, - name: device.user_given_name || device.name, - model: device.model, - manufacturer: device.manufacturer, - id: device.ieee, - }; - }); - - return outputDevices; - }); - - private _columns = memoizeOne( - (narrow: boolean): DataTableColumnContainer => - narrow - ? { - name: { - title: "Devices", - sortable: true, - filterable: true, - direction: "asc", - grows: true, - template: (name) => html` -
- ${name} -
- `, - }, - } - : { - name: { - title: "Name", - sortable: true, - filterable: true, - direction: "asc", - grows: true, - template: (name) => html` -
- ${name} -
- `, - }, - manufacturer: { - title: "Manufacturer", - sortable: true, - filterable: true, - width: "20%", - }, - model: { - title: "Model", - sortable: true, - filterable: true, - width: "20%", - }, - } - ); - - public clearSelection() { - this._dataTable.clearSelection(); - } - - protected render(): TemplateResult { - return html` - - `; - } - - private async _handleClicked(ev: CustomEvent) { - const ieee = ((ev.target as HTMLElement).closest( - ".mdc-data-table__row" - ) as any).rowId; - showZHADeviceInfoDialog(this, { ieee }); - } -} - -declare global { - interface HTMLElementTagNameMap { - "zha-devices-data-table": ZHADevicesDataTable; - } -} diff --git a/src/panels/config/zha/zha-group-page.ts b/src/panels/config/zha/zha-group-page.ts index cdb0ee2dc0..461df7d9fd 100644 --- a/src/panels/config/zha/zha-group-page.ts +++ b/src/panels/config/zha/zha-group-page.ts @@ -9,8 +9,8 @@ import { LitElement, property, PropertyValues, + query, } from "lit-element"; -import memoizeOne from "memoize-one"; import { HASSDomEvent } from "../../../common/dom/fire_event"; import { navigate } from "../../../common/navigate"; import { SelectionChangedEvent } from "../../../components/data-table/ha-data-table"; @@ -20,8 +20,8 @@ import { fetchGroupableDevices, removeGroups, removeMembersFromGroup, - ZHADevice, ZHAGroup, + ZHADeviceEndpoint, } from "../../../data/zha"; import "../../../layouts/hass-error-screen"; import "../../../layouts/hass-subpage"; @@ -29,37 +29,40 @@ import { HomeAssistant } from "../../../types"; import "../ha-config-section"; import { formatAsPaddedHex } from "./functions"; import "./zha-device-card"; -import "./zha-devices-data-table"; +import "./zha-device-endpoint-data-table"; +import type { ZHADeviceEndpointDataTable } from "./zha-device-endpoint-data-table"; @customElement("zha-group-page") export class ZHAGroupPage extends LitElement { - @property() public hass!: HomeAssistant; + @property({ type: Object }) public hass!: HomeAssistant; - @property() public group?: ZHAGroup; + @property({ type: Object }) public group?: ZHAGroup; - @property() public groupId!: number; + @property({ type: Number }) public groupId!: number; - @property() public narrow!: boolean; + @property({ type: Boolean }) public narrow!: boolean; - @property() public isWide!: boolean; + @property({ type: Boolean }) public isWide!: boolean; - @property() public devices: ZHADevice[] = []; + @property({ type: Array }) public deviceEndpoints: ZHADeviceEndpoint[] = []; @property() private _processingAdd = false; @property() private _processingRemove = false; - @property() private _filteredDevices: ZHADevice[] = []; + @property() private _filteredDeviceEndpoints: ZHADeviceEndpoint[] = []; @property() private _selectedDevicesToAdd: string[] = []; @property() private _selectedDevicesToRemove: string[] = []; - private _firstUpdatedCalled = false; + @query("#addMembers") + private _zhaAddMembersDataTable!: ZHADeviceEndpointDataTable; - private _members = memoizeOne( - (group: ZHAGroup): ZHADevice[] => group.members - ); + @query("#removeMembers") + private _zhaRemoveMembersDataTable!: ZHADeviceEndpointDataTable; + + private _firstUpdatedCalled = false; public connectedCallback(): void { super.connectedCallback(); @@ -74,8 +77,8 @@ export class ZHAGroupPage extends LitElement { this._processingRemove = false; this._selectedDevicesToRemove = []; this._selectedDevicesToAdd = []; - this.devices = []; - this._filteredDevices = []; + this.deviceEndpoints = []; + this._filteredDeviceEndpoints = []; } protected firstUpdated(changedProperties: PropertyValues): void { @@ -97,8 +100,6 @@ export class ZHAGroupPage extends LitElement { `; } - const members = this._members(this.group); - return html` - ${members.length - ? members.map( + ${this.group.members.length + ? this.group.members.map( (member) => html` `} - ${members.length + ${this.group.members.length ? html`
${this.hass.localize( @@ -148,14 +149,15 @@ export class ZHAGroupPage extends LitElement { )}
- - +
- - +
{ - return !this.group!.members.some((member) => member.ieee === device.ieee); - }); + this._filteredDeviceEndpoints = this.deviceEndpoints.filter( + (deviceEndpoint) => { + return !this.group!.members.some( + (member) => + member.device.ieee === deviceEndpoint.device.ieee && + member.endpoint_id === deviceEndpoint.endpoint_id + ); + } + ); } private _handleAddSelectionChanged( @@ -244,25 +253,27 @@ export class ZHAGroupPage extends LitElement { private async _addMembersToGroup(): Promise { this._processingAdd = true; - this.group = await addMembersToGroup( - this.hass, - this.groupId, - this._selectedDevicesToAdd - ); + const members = this._selectedDevicesToAdd.map((member) => { + const memberParts = member.split("_"); + return { ieee: memberParts[0], endpoint_id: memberParts[1] }; + }); + this.group = await addMembersToGroup(this.hass, this.groupId, members); this._filterDevices(); this._selectedDevicesToAdd = []; + this._zhaAddMembersDataTable.clearSelection(); this._processingAdd = false; } private async _removeMembersFromGroup(): Promise { this._processingRemove = true; - this.group = await removeMembersFromGroup( - this.hass, - this.groupId, - this._selectedDevicesToRemove - ); + const members = this._selectedDevicesToRemove.map((member) => { + const memberParts = member.split("_"); + return { ieee: memberParts[0], endpoint_id: memberParts[1] }; + }); + this.group = await removeMembersFromGroup(this.hass, this.groupId, members); this._filterDevices(); this._selectedDevicesToRemove = []; + this._zhaRemoveMembersDataTable.clearSelection(); this._processingRemove = false; }