From 3083d5b04ca71cf5e2c80090d9ae2018bec19dad Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Tue, 27 Sep 2022 10:02:45 -0400 Subject: [PATCH] Clean up ZHA configuration UI (#13610) --- src/data/zha.ts | 2 +- .../zha/device-actions.ts | 25 +- .../zha/ha-device-info-zha.ts | 1 + .../zha/dialog-zha-cluster.ts | 142 --------- .../zha/dialog-zha-device-zigbee-info.ts | 69 ----- .../zha/dialog-zha-manage-zigbee-device.ts | 291 ++++++++++++++++++ .../zha/dialog-zha-reconfigure-device.ts | 22 +- .../zha/show-dialog-zha-cluster.ts | 19 -- .../zha/show-dialog-zha-device-children.ts | 20 -- .../zha/show-dialog-zha-device-zigbee-info.ts | 20 -- .../show-dialog-zha-manage-zigbee-device.ts | 23 ++ .../integration-panels/zha/types.ts | 6 +- .../zha/zha-cluster-attributes.ts | 206 ++++--------- .../zha/zha-cluster-commands.ts | 198 ++++-------- .../zha/zha-clusters-data-table.ts | 2 +- .../integration-panels/zha/zha-clusters.ts | 195 ------------ .../zha/zha-device-binding.ts | 204 +++++------- ...ice-children.ts => zha-device-children.ts} | 88 ++---- .../zha/zha-device-signature.ts | 47 +++ .../zha/zha-group-binding.ts | 213 ++++--------- .../zha/zha-manage-clusters.ts | 198 ++++++++++++ src/translations/en.json | 30 +- 22 files changed, 903 insertions(+), 1118 deletions(-) delete mode 100644 src/panels/config/integrations/integration-panels/zha/dialog-zha-cluster.ts delete mode 100644 src/panels/config/integrations/integration-panels/zha/dialog-zha-device-zigbee-info.ts create mode 100644 src/panels/config/integrations/integration-panels/zha/dialog-zha-manage-zigbee-device.ts delete mode 100644 src/panels/config/integrations/integration-panels/zha/show-dialog-zha-cluster.ts delete mode 100644 src/panels/config/integrations/integration-panels/zha/show-dialog-zha-device-children.ts delete mode 100644 src/panels/config/integrations/integration-panels/zha/show-dialog-zha-device-zigbee-info.ts create mode 100644 src/panels/config/integrations/integration-panels/zha/show-dialog-zha-manage-zigbee-device.ts delete mode 100644 src/panels/config/integrations/integration-panels/zha/zha-clusters.ts rename src/panels/config/integrations/integration-panels/zha/{dialog-zha-device-children.ts => zha-device-children.ts} (52%) create mode 100644 src/panels/config/integrations/integration-panels/zha/zha-device-signature.ts create mode 100644 src/panels/config/integrations/integration-panels/zha/zha-manage-clusters.ts diff --git a/src/data/zha.ts b/src/data/zha.ts index fb0e2b0f31..55bf16d7c4 100644 --- a/src/data/zha.ts +++ b/src/data/zha.ts @@ -309,7 +309,7 @@ export const fetchCommandsForCluster = ( cluster_type: clusterType, }); -export const fetchClustersForZhaNode = ( +export const fetchClustersForZhaDevice = ( hass: HomeAssistant, ieeeAddress: string ): Promise => diff --git a/src/panels/config/devices/device-detail/integration-elements/zha/device-actions.ts b/src/panels/config/devices/device-detail/integration-elements/zha/device-actions.ts index e9e17a3f20..907393e7c1 100644 --- a/src/panels/config/devices/device-detail/integration-elements/zha/device-actions.ts +++ b/src/panels/config/devices/device-detail/integration-elements/zha/device-actions.ts @@ -1,9 +1,7 @@ import { mdiCogRefresh, mdiDelete, - mdiDrawPen, mdiFamilyTree, - mdiFileTree, mdiGroup, mdiPlus, } from "@mdi/js"; @@ -12,9 +10,7 @@ import type { DeviceRegistryEntry } from "../../../../../../data/device_registry import { fetchZHADevice } from "../../../../../../data/zha"; import { showConfirmationDialog } from "../../../../../../dialogs/generic/show-dialog-box"; import type { HomeAssistant } from "../../../../../../types"; -import { showZHAClusterDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-cluster"; -import { showZHADeviceChildrenDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-device-children"; -import { showZHADeviceZigbeeInfoDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-device-zigbee-info"; +import { showZHAManageZigbeeDeviceDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-manage-zigbee-device"; import { showZHAReconfigureDeviceDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-reconfigure-device"; import type { DeviceAction } from "../../../ha-config-device-page"; @@ -59,13 +55,6 @@ export const getZHADeviceActions = async ( icon: mdiPlus, action: () => navigate(`/config/zha/add/${zhaDevice!.ieee}`), }, - { - label: hass.localize( - "ui.dialogs.zha_device_info.buttons.device_children" - ), - icon: mdiFileTree, - action: () => showZHADeviceChildrenDialog(el, { device: zhaDevice! }), - }, ] ); } @@ -73,16 +62,10 @@ export const getZHADeviceActions = async ( actions.push( ...[ { - label: hass.localize( - "ui.dialogs.zha_device_info.buttons.zigbee_information" - ), - icon: mdiDrawPen, - action: () => showZHADeviceZigbeeInfoDialog(el, { device: zhaDevice }), - }, - { - label: hass.localize("ui.dialogs.zha_device_info.buttons.clusters"), + label: hass.localize("ui.dialogs.zha_device_info.buttons.manage"), icon: mdiGroup, - action: () => showZHAClusterDialog(el, { device: zhaDevice }), + action: () => + showZHAManageZigbeeDeviceDialog(el, { device: zhaDevice }), }, { label: hass.localize("ui.dialogs.zha_device_info.buttons.view_network"), diff --git a/src/panels/config/devices/device-detail/integration-elements/zha/ha-device-info-zha.ts b/src/panels/config/devices/device-detail/integration-elements/zha/ha-device-info-zha.ts index 5ccaf7c5e5..3f1f0fad16 100644 --- a/src/panels/config/devices/device-detail/integration-elements/zha/ha-device-info-zha.ts +++ b/src/panels/config/devices/device-detail/integration-elements/zha/ha-device-info-zha.ts @@ -23,6 +23,7 @@ export class HaDeviceActionsZha extends LitElement { @state() private _zhaDevice?: ZHADevice; protected updated(changedProperties: PropertyValues) { + super.updated(changedProperties); if (changedProperties.has("device")) { const zigbeeConnection = this.device.connections.find( (conn) => conn[0] === "zigbee" diff --git a/src/panels/config/integrations/integration-panels/zha/dialog-zha-cluster.ts b/src/panels/config/integrations/integration-panels/zha/dialog-zha-cluster.ts deleted file mode 100644 index 84ea96c629..0000000000 --- a/src/panels/config/integrations/integration-panels/zha/dialog-zha-cluster.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { - CSSResultGroup, - html, - LitElement, - PropertyValues, - TemplateResult, -} from "lit"; -import { customElement, property, state } from "lit/decorators"; -import { HASSDomEvent } from "../../../../../common/dom/fire_event"; -import "../../../../../components/ha-code-editor"; -import { createCloseHeading } from "../../../../../components/ha-dialog"; -import { - Cluster, - fetchBindableDevices, - fetchGroups, - ZHADevice, - ZHAGroup, -} from "../../../../../data/zha"; -import { haStyleDialog } from "../../../../../resources/styles"; -import { HomeAssistant } from "../../../../../types"; -import { sortZHADevices, sortZHAGroups } from "./functions"; -import { ZHADeviceZigbeeInfoDialogParams } from "./show-dialog-zha-device-zigbee-info"; -import { ZHAClusterSelectedParams } from "./types"; -import "./zha-cluster-attributes"; -import "./zha-cluster-commands"; -import "./zha-clusters"; -import "./zha-device-binding"; -import "./zha-group-binding"; - -@customElement("dialog-zha-cluster") -class DialogZHACluster extends LitElement { - @property({ attribute: false }) public hass!: HomeAssistant; - - @state() private _device?: ZHADevice; - - @state() private _selectedCluster?: Cluster; - - @state() private _bindableDevices: ZHADevice[] = []; - - @state() private _groups: ZHAGroup[] = []; - - public async showDialog( - params: ZHADeviceZigbeeInfoDialogParams - ): Promise { - this._device = params.device; - } - - protected updated(changedProperties: PropertyValues): void { - super.update(changedProperties); - if (changedProperties.has("_device")) { - this._fetchData(); - } - } - - protected render(): TemplateResult { - if (!this._device) { - return html``; - } - - return html` - - - ${this._selectedCluster - ? html` - - - ` - : ""} - ${this._bindableDevices.length > 0 - ? html` - - ` - : ""} - ${this._device && this._groups.length > 0 - ? html` - - ` - : ""} - - `; - } - - private _onClusterSelected( - selectedClusterEvent: HASSDomEvent - ): void { - this._selectedCluster = selectedClusterEvent.detail.cluster; - } - - private _close(): void { - this._device = undefined; - } - - private async _fetchData(): Promise { - if (this._device && this.hass) { - this._bindableDevices = - this._device && this._device.device_type !== "Coordinator" - ? (await fetchBindableDevices(this.hass, this._device.ieee)).sort( - sortZHADevices - ) - : []; - this._groups = (await fetchGroups(this.hass!)).sort(sortZHAGroups); - } - } - - static get styles(): CSSResultGroup { - return haStyleDialog; - } -} - -declare global { - interface HTMLElementTagNameMap { - "dialog-zha-cluster": DialogZHACluster; - } -} diff --git a/src/panels/config/integrations/integration-panels/zha/dialog-zha-device-zigbee-info.ts b/src/panels/config/integrations/integration-panels/zha/dialog-zha-device-zigbee-info.ts deleted file mode 100644 index b9334e46d4..0000000000 --- a/src/panels/config/integrations/integration-panels/zha/dialog-zha-device-zigbee-info.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; -import { customElement, property, state } from "lit/decorators"; -import "../../../../../components/ha-code-editor"; -import { createCloseHeading } from "../../../../../components/ha-dialog"; -import { haStyleDialog } from "../../../../../resources/styles"; -import { HomeAssistant } from "../../../../../types"; -import { ZHADeviceZigbeeInfoDialogParams } from "./show-dialog-zha-device-zigbee-info"; - -@customElement("dialog-zha-device-zigbee-info") -class DialogZHADeviceZigbeeInfo extends LitElement { - @property({ attribute: false }) public hass!: HomeAssistant; - - @state() private _signature: any; - - public async showDialog( - params: ZHADeviceZigbeeInfoDialogParams - ): Promise { - this._signature = JSON.stringify( - { - ...params.device.signature, - manufacturer: params.device.manufacturer, - model: params.device.model, - class: params.device.quirk_class, - }, - null, - 2 - ); - } - - protected render(): TemplateResult { - if (!this._signature) { - return html``; - } - - return html` - - - - - `; - } - - private _close(): void { - this._signature = undefined; - } - - static get styles(): CSSResultGroup { - return haStyleDialog; - } -} - -declare global { - interface HTMLElementTagNameMap { - "dialog-zha-device-zigbee-info": DialogZHADeviceZigbeeInfo; - } -} diff --git a/src/panels/config/integrations/integration-panels/zha/dialog-zha-manage-zigbee-device.ts b/src/panels/config/integrations/integration-panels/zha/dialog-zha-manage-zigbee-device.ts new file mode 100644 index 0000000000..5ccacb9d90 --- /dev/null +++ b/src/panels/config/integrations/integration-panels/zha/dialog-zha-manage-zigbee-device.ts @@ -0,0 +1,291 @@ +import { + css, + CSSResultGroup, + html, + LitElement, + PropertyValues, + TemplateResult, +} from "lit"; +import { mdiClose } from "@mdi/js"; +import { customElement, property, state } from "lit/decorators"; +import { cache } from "lit/directives/cache"; +import memoizeOne from "memoize-one"; +import { fireEvent } from "../../../../../common/dom/fire_event"; +import "../../../../../components/ha-code-editor"; +import { createCloseHeading } from "../../../../../components/ha-dialog"; +import { + fetchBindableDevices, + fetchGroups, + ZHADevice, + ZHAGroup, +} from "../../../../../data/zha"; +import { haStyleDialog } from "../../../../../resources/styles"; +import { HomeAssistant } from "../../../../../types"; +import { sortZHADevices, sortZHAGroups } from "./functions"; +import "./zha-cluster-attributes"; +import "./zha-cluster-commands"; +import "./zha-manage-clusters"; +import "./zha-device-binding"; +import "./zha-group-binding"; +import "./zha-device-children"; +import "./zha-device-signature"; +import { + Tab, + ZHAManageZigbeeDeviceDialogParams, +} from "./show-dialog-zha-manage-zigbee-device"; +import "../../../../../components/ha-header-bar"; +import "@material/mwc-tab-bar/mwc-tab-bar"; +import "@material/mwc-tab/mwc-tab"; + +@customElement("dialog-zha-manage-zigbee-device") +class DialogZHAManageZigbeeDevice extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ type: Boolean, reflect: true }) public large = false; + + @state() private _currTab: Tab = "clusters"; + + @state() private _device?: ZHADevice; + + @state() private _bindableDevices: ZHADevice[] = []; + + @state() private _groups: ZHAGroup[] = []; + + public async showDialog( + params: ZHAManageZigbeeDeviceDialogParams + ): Promise { + this._device = params.device; + if (!this._device) { + this.closeDialog(); + return; + } + this._currTab = params.tab || "clusters"; + this.large = false; + } + + public closeDialog() { + this._device = undefined; + fireEvent(this, "dialog-closed", { dialog: this.localName }); + } + + protected firstUpdated(changedProps: PropertyValues) { + super.firstUpdated(changedProps); + this.addEventListener("close-dialog", () => this.closeDialog()); + } + + protected willUpdate(changedProps: PropertyValues) { + super.willUpdate(changedProps); + if (!this._device) { + return; + } + if (changedProps.has("_device")) { + const tabs = this._getTabs(this._device); + if (!tabs.includes(this._currTab)) { + this._currTab = tabs[0]; + } + this._fetchData(); + } + } + + protected render(): TemplateResult { + if (!this._device) { + return html``; + } + + const tabs = this._getTabs(this._device); + + return html` + +
+ + +
+ ${this.hass.localize("ui.dialogs.zha_manage_device.heading")} +
+
+ + ${tabs.map( + (tab) => html` + + ` + )} + +
+ +
+ ${cache( + this._currTab === "clusters" + ? html` + + ` + : this._currTab === "bindings" + ? html` + ${this._bindableDevices.length > 0 + ? html` + + ` + : ""} + ${this._device && this._groups.length > 0 + ? html` + + ` + : ""} + ` + : this._currTab === "signature" + ? html` + + ` + : html` + + ` + )} +
+
+ `; + } + + private async _fetchData(): Promise { + if (this._device && this.hass) { + this._bindableDevices = + this._device && this._device.device_type !== "Coordinator" + ? (await fetchBindableDevices(this.hass, this._device.ieee)).sort( + sortZHADevices + ) + : []; + this._groups = (await fetchGroups(this.hass!)).sort(sortZHAGroups); + } + } + + private _enlarge() { + this.large = !this.large; + } + + private _handleTabChanged(ev: CustomEvent): void { + const newTab = this._getTabs(this._device)[ev.detail.index]; + if (newTab === this._currTab) { + return; + } + this._currTab = newTab; + } + + private _getTabs = memoizeOne((device: ZHADevice | undefined) => { + const tabs: Tab[] = ["clusters", "bindings", "signature"]; + + if ( + device && + (device.device_type === "Router" || device.device_type === "Coordinator") + ) { + tabs.push("children"); + } + + return tabs; + }); + + static get styles(): CSSResultGroup { + return [ + haStyleDialog, + css` + ha-dialog { + --dialog-surface-position: static; + --dialog-content-position: static; + --vertial-align-dialog: flex-start; + } + + ha-header-bar { + --mdc-theme-on-primary: var(--primary-text-color); + --mdc-theme-primary: var(--mdc-theme-surface); + flex-shrink: 0; + display: block; + } + .content { + outline: none; + } + @media all and (max-width: 450px), all and (max-height: 500px) { + ha-header-bar { + --mdc-theme-primary: var(--app-header-background-color); + --mdc-theme-on-primary: var(--app-header-text-color, white); + border-bottom: none; + } + } + + .heading { + border-bottom: 1px solid + var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12)); + } + + @media all and (min-width: 600px) and (min-height: 501px) { + ha-dialog { + --mdc-dialog-min-width: 560px; + --mdc-dialog-max-width: 560px; + --dialog-surface-margin-top: 40px; + --mdc-dialog-max-height: calc(100% - 72px); + } + + .main-title { + overflow: hidden; + text-overflow: ellipsis; + cursor: default; + } + + :host([large]) ha-dialog, + ha-dialog[data-domain="camera"] { + --mdc-dialog-min-width: 90vw; + --mdc-dialog-max-width: 90vw; + } + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "dialog-zha-manage-zigbee-device": DialogZHAManageZigbeeDevice; + } +} diff --git a/src/panels/config/integrations/integration-panels/zha/dialog-zha-reconfigure-device.ts b/src/panels/config/integrations/integration-panels/zha/dialog-zha-reconfigure-device.ts index 1ae26ad04d..4c43266385 100644 --- a/src/panels/config/integrations/integration-panels/zha/dialog-zha-reconfigure-device.ts +++ b/src/panels/config/integrations/integration-panels/zha/dialog-zha-reconfigure-device.ts @@ -12,7 +12,7 @@ import { Cluster, ClusterConfigurationEvent, ClusterConfigurationStatus, - fetchClustersForZhaNode, + fetchClustersForZhaDevice, reconfigureNode, ZHA_CHANNEL_CFG_DONE, ZHA_CHANNEL_MSG_BIND, @@ -321,16 +321,16 @@ class DialogZHAReconfigureDevice extends LitElement { return; } this._clusterConfigurationStatuses = new Map( - (await fetchClustersForZhaNode(this.hass, this._params.device.ieee)).map( - (cluster: Cluster) => [ - cluster.id, - { - cluster: cluster, - bindSuccess: undefined, - attributes: new Map(), - }, - ] - ) + ( + await fetchClustersForZhaDevice(this.hass, this._params.device.ieee) + ).map((cluster: Cluster) => [ + cluster.id, + { + cluster: cluster, + bindSuccess: undefined, + attributes: new Map(), + }, + ]) ); this._subscribe(this._params); this._status = "started"; diff --git a/src/panels/config/integrations/integration-panels/zha/show-dialog-zha-cluster.ts b/src/panels/config/integrations/integration-panels/zha/show-dialog-zha-cluster.ts deleted file mode 100644 index 9b0c44ad5d..0000000000 --- a/src/panels/config/integrations/integration-panels/zha/show-dialog-zha-cluster.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { fireEvent } from "../../../../../common/dom/fire_event"; -import { ZHADevice } from "../../../../../data/zha"; - -export interface ZHAClusterDialogParams { - device: ZHADevice; -} - -export const loadZHAClusterDialog = () => import("./dialog-zha-cluster"); - -export const showZHAClusterDialog = ( - element: HTMLElement, - params: ZHAClusterDialogParams -): void => { - fireEvent(element, "show-dialog", { - dialogTag: "dialog-zha-cluster", - dialogImport: loadZHAClusterDialog, - dialogParams: params, - }); -}; diff --git a/src/panels/config/integrations/integration-panels/zha/show-dialog-zha-device-children.ts b/src/panels/config/integrations/integration-panels/zha/show-dialog-zha-device-children.ts deleted file mode 100644 index f8a3e64ab0..0000000000 --- a/src/panels/config/integrations/integration-panels/zha/show-dialog-zha-device-children.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { fireEvent } from "../../../../../common/dom/fire_event"; -import { ZHADevice } from "../../../../../data/zha"; - -export interface ZHADeviceChildrenDialogParams { - device: ZHADevice; -} - -export const loadZHADeviceChildrenDialog = () => - import("./dialog-zha-device-children"); - -export const showZHADeviceChildrenDialog = ( - element: HTMLElement, - zhaDeviceChildrenParams: ZHADeviceChildrenDialogParams -): void => { - fireEvent(element, "show-dialog", { - dialogTag: "dialog-zha-device-children", - dialogImport: loadZHADeviceChildrenDialog, - dialogParams: zhaDeviceChildrenParams, - }); -}; diff --git a/src/panels/config/integrations/integration-panels/zha/show-dialog-zha-device-zigbee-info.ts b/src/panels/config/integrations/integration-panels/zha/show-dialog-zha-device-zigbee-info.ts deleted file mode 100644 index e4a2191563..0000000000 --- a/src/panels/config/integrations/integration-panels/zha/show-dialog-zha-device-zigbee-info.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { fireEvent } from "../../../../../common/dom/fire_event"; -import { ZHADevice } from "../../../../../data/zha"; - -export interface ZHADeviceZigbeeInfoDialogParams { - device: ZHADevice; -} - -export const loadZHADeviceZigbeeInfoDialog = () => - import("./dialog-zha-device-zigbee-info"); - -export const showZHADeviceZigbeeInfoDialog = ( - element: HTMLElement, - zhaDeviceZigbeeInfoParams: ZHADeviceZigbeeInfoDialogParams -): void => { - fireEvent(element, "show-dialog", { - dialogTag: "dialog-zha-device-zigbee-info", - dialogImport: loadZHADeviceZigbeeInfoDialog, - dialogParams: zhaDeviceZigbeeInfoParams, - }); -}; diff --git a/src/panels/config/integrations/integration-panels/zha/show-dialog-zha-manage-zigbee-device.ts b/src/panels/config/integrations/integration-panels/zha/show-dialog-zha-manage-zigbee-device.ts new file mode 100644 index 0000000000..a2854748ff --- /dev/null +++ b/src/panels/config/integrations/integration-panels/zha/show-dialog-zha-manage-zigbee-device.ts @@ -0,0 +1,23 @@ +import { fireEvent } from "../../../../../common/dom/fire_event"; +import { ZHADevice } from "../../../../../data/zha"; + +export type Tab = "clusters" | "bindings" | "signature" | "children"; + +export interface ZHAManageZigbeeDeviceDialogParams { + device: ZHADevice; + tab?: Tab; +} + +export const loadZHAManageZigbeeDeviceDialog = () => + import("./dialog-zha-manage-zigbee-device"); + +export const showZHAManageZigbeeDeviceDialog = ( + element: HTMLElement, + params: ZHAManageZigbeeDeviceDialogParams +): void => { + fireEvent(element, "show-dialog", { + dialogTag: "dialog-zha-manage-zigbee-device", + dialogImport: loadZHAManageZigbeeDeviceDialog, + dialogParams: params, + }); +}; diff --git a/src/panels/config/integrations/integration-panels/zha/types.ts b/src/panels/config/integrations/integration-panels/zha/types.ts index dbc2954f2d..3e9af96a18 100644 --- a/src/panels/config/integrations/integration-panels/zha/types.ts +++ b/src/panels/config/integrations/integration-panels/zha/types.ts @@ -1,5 +1,5 @@ import { HaSelect } from "../../../../../components/ha-select"; -import { Cluster, ZHADevice } from "../../../../../data/zha"; +import { ZHADevice } from "../../../../../data/zha"; export interface ItemSelectedEvent { target?: HaSelect; @@ -41,10 +41,6 @@ export interface ZHADeviceSelectedParams { node: ZHADevice; } -export interface ZHAClusterSelectedParams { - cluster: Cluster; -} - export interface NodeServiceData { ieee_address: string; } diff --git a/src/panels/config/integrations/integration-panels/zha/zha-cluster-attributes.ts b/src/panels/config/integrations/integration-panels/zha/zha-cluster-attributes.ts index c764a135b3..a0a3695dea 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-cluster-attributes.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-cluster-attributes.ts @@ -1,6 +1,4 @@ -import "@material/mwc-button"; import "@material/mwc-list/mwc-list-item"; -import { mdiHelpCircle } from "@mdi/js"; import "@polymer/paper-input/paper-input"; import { css, @@ -10,13 +8,12 @@ import { PropertyValues, TemplateResult, } from "lit"; -import { property, state } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import { stopPropagation } from "../../../../../common/dom/stop_propagation"; import "../../../../../components/buttons/ha-call-service-button"; import "../../../../../components/ha-card"; -import "../../../../../components/ha-icon-button"; import "../../../../../components/ha-select"; -import "../../../../../components/ha-service-description"; +import "../../../../../components/buttons/ha-progress-button"; import { Attribute, Cluster, @@ -27,7 +24,7 @@ import { } from "../../../../../data/zha"; import { haStyle } from "../../../../../resources/styles"; import { HomeAssistant } from "../../../../../types"; -import "../../../ha-config-section"; +import { forwardHaptic } from "../../../../../data/haptics"; import { formatAsPaddedHex } from "./functions"; import { ChangeEvent, @@ -35,18 +32,15 @@ import { SetAttributeServiceData, } from "./types"; +@customElement("zha-cluster-attributes") export class ZHAClusterAttributes extends LitElement { @property({ attribute: false }) public hass?: HomeAssistant; - @property() public isWide?: boolean; - - @property() public showHelp = false; - - @property() public selectedNode?: ZHADevice; + @property() public device?: ZHADevice; @property() public selectedCluster?: Cluster; - @state() private _attributes: Attribute[] = []; + @state() private _attributes: Attribute[] | undefined; @state() private _selectedAttributeId?: number; @@ -54,78 +48,52 @@ export class ZHAClusterAttributes extends LitElement { @state() private _manufacturerCodeOverride?: string | number; + @state() private _readingAttribute = false; + @state() private _setAttributeServiceData?: SetAttributeServiceData; protected updated(changedProperties: PropertyValues): void { if (changedProperties.has("selectedCluster")) { - this._attributes = []; + this._attributes = undefined; this._selectedAttributeId = undefined; this._attributeValue = ""; this._fetchAttributesForCluster(); } - super.update(changedProperties); + super.updated(changedProperties); } protected render(): TemplateResult { + if (!this.device || !this.selectedCluster || !this._attributes) { + return html``; + } return html` - -
- - ${this.hass!.localize( - "ui.panel.config.zha.cluster_attributes.header" + +
+ - - -
- - ${this.hass!.localize( - "ui.panel.config.zha.cluster_attributes.introduction" - )} - - - -
- - ${this._attributes.map( - (entry) => html` - - ${entry.name + " (id: " + formatAsPaddedHex(entry.id) + ")"} - - ` - )} - -
- ${this.showHelp - ? html` -
- ${this.hass!.localize( - "ui.panel.config.zha.cluster_attributes.help_attribute_dropdown" - )} -
+ ${this._attributes.map( + (entry) => html` + + ${`${entry.name} (id: ${formatAsPaddedHex(entry.id)})`} + ` - : ""} - ${this._selectedAttributeId !== undefined - ? this._renderAttributeInteractions() - : ""} -
- + )} + +
+ ${this._selectedAttributeId !== undefined + ? this._renderAttributeInteractions() + : ""} + `; } @@ -152,20 +120,15 @@ export class ZHAClusterAttributes extends LitElement { >
- + ${this.hass!.localize( - "ui.panel.config.zha.cluster_attributes.get_zigbee_attribute" + "ui.panel.config.zha.cluster_attributes.read_zigbee_attribute" )} - - ${this.showHelp - ? html` -
- ${this.hass!.localize( - "ui.panel.config.zha.cluster_attributes.help_get_zigbee_attribute" - )} -
- ` - : ""} + ${this.hass!.localize( - "ui.panel.config.zha.cluster_attributes.set_zigbee_attribute" + "ui.panel.config.zha.cluster_attributes.write_zigbee_attribute" )} - ${this.showHelp - ? html` - - ` - : ""}
`; } private async _fetchAttributesForCluster(): Promise { - if (this.selectedNode && this.selectedCluster && this.hass) { + if (this.device && this.selectedCluster && this.hass) { this._attributes = await fetchAttributesForCluster( this.hass, - this.selectedNode!.ieee, + this.device!.ieee, this.selectedCluster!.endpoint_id, this.selectedCluster!.id, this.selectedCluster!.type ); this._attributes.sort((a, b) => a.name.localeCompare(b.name)); + if (this._attributes.length > 0) { + this._selectedAttributeId = this._attributes[0].id; + } } } private _computeReadAttributeServiceData(): | ReadAttributeServiceData | undefined { - if (!this.selectedCluster || !this.selectedNode) { + if (!this.selectedCluster || !this.device) { return undefined; } return { - ieee: this.selectedNode!.ieee, + ieee: this.device!.ieee, endpoint_id: this.selectedCluster!.endpoint_id, cluster_id: this.selectedCluster!.id, cluster_type: this.selectedCluster!.type, @@ -224,11 +180,11 @@ export class ZHAClusterAttributes extends LitElement { private _computeSetAttributeServiceData(): | SetAttributeServiceData | undefined { - if (!this.selectedCluster || !this.selectedNode) { + if (!this.selectedCluster || !this.device) { return undefined; } return { - ieee: this.selectedNode!.ieee, + ieee: this.device!.ieee, endpoint_id: this.selectedCluster!.endpoint_id, cluster_id: this.selectedCluster!.id, cluster_type: this.selectedCluster!.type, @@ -250,17 +206,24 @@ export class ZHAClusterAttributes extends LitElement { this._setAttributeServiceData = this._computeSetAttributeServiceData(); } - private async _onGetZigbeeAttributeClick(): Promise { + private async _onGetZigbeeAttributeClick(ev: CustomEvent): Promise { + const button = ev.currentTarget as any; const data = this._computeReadAttributeServiceData(); if (data && this.hass) { - this._attributeValue = await readAttributeValue(this.hass, data); + this._readingAttribute = true; + try { + this._attributeValue = await readAttributeValue(this.hass, data); + forwardHaptic("success"); + button.actionSuccess(); + } catch (err: any) { + forwardHaptic("failure"); + button.actionError(); + } finally { + this._readingAttribute = false; + } } } - private _onHelpTap(): void { - this.showHelp = !this.showHelp; - } - private _selectedAttributeChanged(event: ItemSelectedEvent): void { this._selectedAttributeId = Number(event.target!.value); this._attributeValue = ""; @@ -278,14 +241,6 @@ export class ZHAClusterAttributes extends LitElement { width: 100%; } - .content { - margin-top: 24px; - } - - ha-card { - max-width: 680px; - } - .card-actions.warning ha-call-service-button { color: var(--error-color); } @@ -306,33 +261,6 @@ export class ZHAClusterAttributes extends LitElement { .header { flex-grow: 1; } - - .toggle-help-icon { - float: right; - top: -6px; - right: 0; - padding-right: 0px; - color: var(--primary-color); - } - - ha-service-description { - display: block; - color: grey; - } - - [hidden] { - display: none; - } - .help-text { - color: grey; - padding-left: 28px; - padding-right: 28px; - padding-bottom: 16px; - } - .help-text2 { - color: grey; - padding: 16px; - } `, ]; } @@ -343,5 +271,3 @@ declare global { "zha-cluster-attributes": ZHAClusterAttributes; } } - -customElements.define("zha-cluster-attributes", ZHAClusterAttributes); diff --git a/src/panels/config/integrations/integration-panels/zha/zha-cluster-commands.ts b/src/panels/config/integrations/integration-panels/zha/zha-cluster-commands.ts index ca3a29be5f..129e42e74f 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-cluster-commands.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-cluster-commands.ts @@ -1,5 +1,4 @@ import "@material/mwc-list/mwc-list-item"; -import { mdiHelpCircle } from "@mdi/js"; import "@polymer/paper-input/paper-input"; import { css, @@ -13,9 +12,7 @@ import { property, state } from "lit/decorators"; import { stopPropagation } from "../../../../../common/dom/stop_propagation"; import "../../../../../components/buttons/ha-call-service-button"; import "../../../../../components/ha-card"; -import "../../../../../components/ha-icon-button"; import "../../../../../components/ha-select"; -import "../../../../../components/ha-service-description"; import { Cluster, Command, @@ -24,7 +21,6 @@ import { } from "../../../../../data/zha"; import { haStyle } from "../../../../../resources/styles"; import { HomeAssistant } from "../../../../../types"; -import "../../../ha-config-section"; import { formatAsPaddedHex } from "./functions"; import { ChangeEvent, IssueCommandServiceData } from "./types"; @@ -33,13 +29,11 @@ export class ZHAClusterCommands extends LitElement { @property() public isWide?: boolean; - @property() public selectedNode?: ZHADevice; + @property() public device?: ZHADevice; @property() public selectedCluster?: Cluster; - @state() private _showHelp = false; - - @state() private _commands: Command[] = []; + @state() private _commands: Command[] | undefined; @state() private _selectedCommandId?: number; @@ -50,132 +44,97 @@ export class ZHAClusterCommands extends LitElement { protected updated(changedProperties: PropertyValues): void { if (changedProperties.has("selectedCluster")) { - this._commands = []; + this._commands = undefined; this._selectedCommandId = undefined; this._fetchCommandsForCluster(); } - super.update(changedProperties); + super.updated(changedProperties); } protected render(): TemplateResult { + if (!this.device || !this.selectedCluster || !this._commands) { + return html``; + } return html` - -
- - ${this.hass!.localize( - "ui.panel.config.zha.cluster_commands.header" + +
+ - - + ${this._commands.map( + (entry) => html` + + ${entry.name + " (id: " + formatAsPaddedHex(entry.id) + ")"} + + ` + )} +
- - ${this.hass!.localize( - "ui.panel.config.zha.cluster_commands.introduction" - )} - - - -
- - ${this._commands.map( - (entry) => html` - - ${entry.name + " (id: " + formatAsPaddedHex(entry.id) + ")"} - - ` - )} - -
- ${this._showHelp - ? html` -
- ${this.hass!.localize( - "ui.panel.config.zha.cluster_commands.help_command_dropdown" + ${this._selectedCommandId !== undefined + ? html` +
+ - ` - : ""} - ${this._selectedCommandId !== undefined - ? html` -
- -
-
- - ${this.hass!.localize( - "ui.panel.config.zha.cluster_commands.issue_zigbee_command" - )} - - ${this._showHelp - ? html` - - ` - : ""} -
- ` - : ""} - - + type="number" + .value=${this._manufacturerCodeOverride} + @value-changed=${this._onManufacturerCodeOverrideChanged} + placeholder=${this.hass!.localize( + "ui.panel.config.zha.common.value" + )} + >
+
+
+ + ${this.hass!.localize( + "ui.panel.config.zha.cluster_commands.issue_zigbee_command" + )} + +
+ ` + : ""} + `; } private async _fetchCommandsForCluster(): Promise { - if (this.selectedNode && this.selectedCluster && this.hass) { + if (this.device && this.selectedCluster && this.hass) { this._commands = await fetchCommandsForCluster( this.hass, - this.selectedNode!.ieee, + this.device!.ieee, this.selectedCluster!.endpoint_id, this.selectedCluster!.id, this.selectedCluster!.type ); this._commands.sort((a, b) => a.name.localeCompare(b.name)); + if (this._commands.length > 0) { + this._selectedCommandId = this._commands[0].id; + } } } private _computeIssueClusterCommandServiceData(): | IssueCommandServiceData | undefined { - if (!this.selectedNode || !this.selectedCluster) { + if (!this.device || !this.selectedCluster || !this._commands) { return undefined; } return { - ieee: this.selectedNode!.ieee, + ieee: this.device!.ieee, endpoint_id: this.selectedCluster!.endpoint_id, cluster_id: this.selectedCluster!.id, cluster_type: this.selectedCluster!.type, @@ -192,10 +151,6 @@ export class ZHAClusterCommands extends LitElement { this._computeIssueClusterCommandServiceData(); } - private _onHelpTap(): void { - this._showHelp = !this._showHelp; - } - private _selectedCommandChanged(event): void { this._selectedCommandId = Number(event.target.value); this._issueClusterCommandServiceData = @@ -213,14 +168,6 @@ export class ZHAClusterCommands extends LitElement { width: 100%; } - .content { - margin-top: 24px; - } - - ha-card { - max-width: 680px; - } - .card-actions.warning ha-call-service-button { color: var(--error-color); } @@ -238,18 +185,6 @@ export class ZHAClusterCommands extends LitElement { padding-bottom: 10px; } - .help-text { - color: grey; - padding-left: 28px; - padding-right: 28px; - padding-bottom: 16px; - } - - .help-text2 { - color: grey; - padding: 16px; - } - .header { flex-grow: 1; } @@ -261,15 +196,6 @@ export class ZHAClusterCommands extends LitElement { padding-right: 0px; color: var(--primary-color); } - - ha-service-description { - display: block; - color: grey; - } - - [hidden] { - display: none; - } `, ]; } diff --git a/src/panels/config/integrations/integration-panels/zha/zha-clusters-data-table.ts b/src/panels/config/integrations/integration-panels/zha/zha-clusters-data-table.ts index f4f0e5fdbf..9cebc071b2 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-clusters-data-table.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-clusters-data-table.ts @@ -59,7 +59,7 @@ export class ZHAClustersDataTable extends LitElement { title: "ID", template: (id: number) => html` ${formatAsPaddedHex(id)} `, sortable: true, - width: "15%", + width: "25%", }, endpoint_id: { title: "Endpoint ID", diff --git a/src/panels/config/integrations/integration-panels/zha/zha-clusters.ts b/src/panels/config/integrations/integration-panels/zha/zha-clusters.ts deleted file mode 100644 index 19f2d270d2..0000000000 --- a/src/panels/config/integrations/integration-panels/zha/zha-clusters.ts +++ /dev/null @@ -1,195 +0,0 @@ -import "@material/mwc-list/mwc-list-item"; -import { mdiHelpCircle } from "@mdi/js"; -import { - css, - CSSResultGroup, - html, - LitElement, - PropertyValues, - TemplateResult, -} from "lit"; -import { property, state } from "lit/decorators"; -import { fireEvent } from "../../../../../common/dom/fire_event"; -import { stopPropagation } from "../../../../../common/dom/stop_propagation"; -import "../../../../../components/buttons/ha-call-service-button"; -import "../../../../../components/ha-card"; -import "../../../../../components/ha-icon-button"; -import "../../../../../components/ha-select"; -import "../../../../../components/ha-service-description"; -import { - Cluster, - fetchClustersForZhaNode, - ZHADevice, -} from "../../../../../data/zha"; -import { haStyle } from "../../../../../resources/styles"; -import { HomeAssistant } from "../../../../../types"; -import "../../../ha-config-section"; -import { computeClusterKey } from "./functions"; - -declare global { - // for fire event - interface HASSDomEvents { - "zha-cluster-selected": { - cluster?: Cluster; - }; - } -} - -export class ZHAClusters extends LitElement { - @property({ attribute: false }) public hass?: HomeAssistant; - - @property() public isWide?: boolean; - - @property() public selectedDevice?: ZHADevice; - - @property() public showHelp = false; - - @state() private _selectedClusterIndex = -1; - - @state() private _clusters: Cluster[] = []; - - protected updated(changedProperties: PropertyValues): void { - if (changedProperties.has("selectedDevice")) { - this._clusters = []; - this._selectedClusterIndex = -1; - fireEvent(this, "zha-cluster-selected", { - cluster: undefined, - }); - this._fetchClustersForZhaNode(); - } - super.update(changedProperties); - } - - protected render(): TemplateResult { - return html` - -
- - -
- - ${this.hass!.localize("ui.panel.config.zha.clusters.introduction")} - - - -
- - ${this._clusters.map( - (entry, idx) => html` - ${computeClusterKey(entry)} - ` - )} - -
- ${this.showHelp - ? html` -
- ${this.hass!.localize( - "ui.panel.config.zha.clusters.help_cluster_dropdown" - )} -
- ` - : ""} -
-
- `; - } - - private async _fetchClustersForZhaNode(): Promise { - if (this.hass) { - this._clusters = await fetchClustersForZhaNode( - this.hass, - this.selectedDevice!.ieee - ); - this._clusters.sort((a, b) => a.name.localeCompare(b.name)); - } - } - - private _selectedClusterChanged(event): void { - this._selectedClusterIndex = Number(event.target!.value); - fireEvent(this, "zha-cluster-selected", { - cluster: this._clusters[this._selectedClusterIndex], - }); - } - - private _onHelpTap(): void { - this.showHelp = !this.showHelp; - } - - static get styles(): CSSResultGroup { - return [ - haStyle, - css` - ha-select { - margin-top: 16px; - } - .menu { - width: 100%; - } - - .content { - margin-top: 24px; - } - - .header { - flex-grow: 1; - } - - ha-card { - max-width: 680px; - } - - .node-picker { - align-items: center; - padding-left: 28px; - padding-right: 28px; - padding-bottom: 10px; - } - - .toggle-help-icon { - float: right; - top: -6px; - right: 0; - padding-right: 0px; - color: var(--primary-color); - } - - [hidden] { - display: none; - } - - .help-text { - color: grey; - padding-left: 28px; - padding-right: 28px; - padding-bottom: 16px; - } - `, - ]; - } -} - -declare global { - interface HTMLElementTagNameMap { - "zha-cluster": ZHAClusters; - } -} - -customElements.define("zha-clusters", ZHAClusters); diff --git a/src/panels/config/integrations/integration-panels/zha/zha-device-binding.ts b/src/panels/config/integrations/integration-panels/zha/zha-device-binding.ts index 1445a572d5..993aaad79e 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-device-binding.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-device-binding.ts @@ -1,6 +1,4 @@ -import "@material/mwc-button/mwc-button"; import "@material/mwc-list/mwc-list-item"; -import { mdiHelpCircle } from "@mdi/js"; import { css, CSSResultGroup, @@ -11,26 +9,19 @@ import { } from "lit"; import { customElement, property, state } from "lit/decorators"; import { stopPropagation } from "../../../../../common/dom/stop_propagation"; -import "../../../../../components/buttons/ha-call-service-button"; +import "../../../../../components/buttons/ha-progress-button"; import "../../../../../components/ha-card"; -import "../../../../../components/ha-icon-button"; import "../../../../../components/ha-select"; -import "../../../../../components/ha-service-description"; import { bindDevices, unbindDevices, ZHADevice } from "../../../../../data/zha"; import { haStyle } from "../../../../../resources/styles"; import { HomeAssistant } from "../../../../../types"; -import "../../../ha-config-section"; import { ItemSelectedEvent } from "./types"; @customElement("zha-device-binding-control") export class ZHADeviceBindingControl extends LitElement { @property({ attribute: false }) public hass?: HomeAssistant; - @property() public isWide?: boolean; - - @property() public selectedDevice?: ZHADevice; - - @state() private _showHelp = false; + @property() public device?: ZHADevice; @state() private _bindTargetIndex = -1; @@ -38,77 +29,58 @@ export class ZHADeviceBindingControl extends LitElement { @state() private _deviceToBind?: ZHADevice; + @state() private _bindingOperationInProgress = false; + protected updated(changedProperties: PropertyValues): void { - if (changedProperties.has("selectedDevice")) { + if (changedProperties.has("device")) { this._bindTargetIndex = -1; } - super.update(changedProperties); + super.updated(changedProperties); } protected render(): TemplateResult { return html` - -
- Device Binding - +
+ - -
- Bind and unbind devices. - - -
- - ${this.bindableDevices.map( - (device, idx) => html` - - ${device.user_given_name - ? device.user_given_name - : device.name} - - ` - )} - -
- ${this._showHelp - ? html` -
- Select a device to issue a bind command. -
+ ${this.bindableDevices.map( + (device, idx) => html` + + ${device.user_given_name + ? device.user_given_name + : device.name} + ` - : ""} -
- Bind - ${this._showHelp - ? html`
Bind devices.
` - : ""} - Unbind - ${this._showHelp - ? html`
Unbind devices.
` - : ""} -
-
- + )} + +
+
+ + ${this.hass!.localize("ui.panel.config.zha.device_binding.bind")} + + + ${this.hass!.localize("ui.panel.config.zha.device_binding.unbind")} + +
+ `; } @@ -120,27 +92,41 @@ export class ZHADeviceBindingControl extends LitElement { : this.bindableDevices[this._bindTargetIndex]; } - private _onHelpTap(): void { - this._showHelp = !this._showHelp; - } - - private async _onBindDevicesClick(): Promise { - if (this.hass && this._deviceToBind && this.selectedDevice) { - await bindDevices( - this.hass, - this.selectedDevice.ieee, - this._deviceToBind.ieee - ); + private async _onBindDevicesClick(ev: CustomEvent): Promise { + const button = ev.currentTarget as any; + if (this.hass && this._deviceToBind && this.device) { + this._bindingOperationInProgress = true; + button.progress = true; + try { + await bindDevices(this.hass, this.device.ieee, this._deviceToBind.ieee); + button.actionSuccess(); + } catch (err: any) { + button.actionError(); + } finally { + this._bindingOperationInProgress = false; + button.progress = false; + } } } - private async _onUnbindDevicesClick(): Promise { - if (this.hass && this._deviceToBind && this.selectedDevice) { - await unbindDevices( - this.hass, - this.selectedDevice.ieee, - this._deviceToBind.ieee - ); + private async _onUnbindDevicesClick(ev: CustomEvent): Promise { + const button = ev.currentTarget as any; + if (this.hass && this._deviceToBind && this.device) { + this._bindingOperationInProgress = true; + button.progress = true; + try { + await unbindDevices( + this.hass, + this.device.ieee, + this._deviceToBind.ieee + ); + button.actionSuccess(); + } catch (err: any) { + button.actionError(); + } finally { + this._bindingOperationInProgress = false; + button.progress = false; + } } } @@ -152,18 +138,6 @@ export class ZHADeviceBindingControl extends LitElement { width: 100%; } - .content { - margin-top: 24px; - } - - ha-card { - max-width: 680px; - } - - .card-actions.warning ha-call-service-button { - color: var(--error-color); - } - .command-picker { align-items: center; padding-left: 28px; @@ -171,33 +145,9 @@ export class ZHADeviceBindingControl extends LitElement { padding-bottom: 10px; } - .helpText { - color: grey; - padding-left: 28px; - padding-right: 28px; - padding-bottom: 10px; - } - .header { flex-grow: 1; } - - .toggle-help-icon { - float: right; - top: -6px; - right: 0; - padding-right: 0px; - color: var(--primary-color); - } - - ha-service-description { - display: block; - color: grey; - } - - [hidden] { - display: none; - } `, ]; } diff --git a/src/panels/config/integrations/integration-panels/zha/dialog-zha-device-children.ts b/src/panels/config/integrations/integration-panels/zha/zha-device-children.ts similarity index 52% rename from src/panels/config/integrations/integration-panels/zha/dialog-zha-device-children.ts rename to src/panels/config/integrations/integration-panels/zha/zha-device-children.ts index 91bd86246c..52a1bdf1b3 100644 --- a/src/panels/config/integrations/integration-panels/zha/dialog-zha-device-children.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-device-children.ts @@ -1,12 +1,9 @@ -import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import memoizeOne from "memoize-one"; import { customElement, property, state } from "lit/decorators"; import { computeRTLDirection } from "../../../../../common/util/compute_rtl"; import "../../../../../components/ha-code-editor"; -import { createCloseHeading } from "../../../../../components/ha-dialog"; -import { haStyleDialog } from "../../../../../resources/styles"; import { HomeAssistant } from "../../../../../types"; -import { ZHADeviceChildrenDialogParams } from "./show-dialog-zha-device-children"; import "../../../../../components/data-table/ha-data-table"; import type { DataTableColumnContainer, @@ -14,7 +11,6 @@ import type { } from "../../../../../components/data-table/ha-data-table"; import "../../../../../components/ha-circular-progress"; import { fetchDevices, ZHADevice } from "../../../../../data/zha"; -import { fireEvent } from "../../../../../common/dom/fire_event"; export interface DeviceRowData extends DataTableRowData { id: string; @@ -22,14 +18,21 @@ export interface DeviceRowData extends DataTableRowData { lqi: number; } -@customElement("dialog-zha-device-children") -class DialogZHADeviceChildren extends LitElement { +@customElement("zha-device-children") +class ZHADeviceChildren extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @state() private _device: ZHADevice | undefined; + @property() public device: ZHADevice | undefined; @state() private _devices: Map | undefined; + protected updated(changedProperties: PropertyValues) { + super.updated(changedProperties); + if (this.hass && changedProperties.has("device")) { + this._fetchData(); + } + } + private _deviceChildren = memoizeOne( ( device: ZHADevice | undefined, @@ -69,70 +72,45 @@ class DialogZHADeviceChildren extends LitElement { }, }; - public showDialog(params: ZHADeviceChildrenDialogParams): void { - this._device = params.device; - this._fetchData(); - } - - public closeDialog(): void { - this._device = undefined; - this._devices = undefined; - fireEvent(this, "dialog-closed", { dialog: this.localName }); - } - protected render(): TemplateResult { - if (!this._device) { + if (!this.device) { return html``; } return html` - - ${!this._devices - ? html`` - : html``} - + ${!this._devices + ? html`` + : html``} `; } private async _fetchData(): Promise { - if (this._device && this.hass) { + if (this.device && this.hass) { const devices = await fetchDevices(this.hass!); this._devices = new Map( devices.map((device: ZHADevice) => [device.ieee, device]) ); } } - - static get styles(): CSSResultGroup { - return haStyleDialog; - } } declare global { interface HTMLElementTagNameMap { - "dialog-zha-device-children": DialogZHADeviceChildren; + "zha-device-children": ZHADeviceChildren; } } diff --git a/src/panels/config/integrations/integration-panels/zha/zha-device-signature.ts b/src/panels/config/integrations/integration-panels/zha/zha-device-signature.ts new file mode 100644 index 0000000000..0ce2b9767a --- /dev/null +++ b/src/panels/config/integrations/integration-panels/zha/zha-device-signature.ts @@ -0,0 +1,47 @@ +import { html, LitElement, PropertyValues, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import "../../../../../components/ha-code-editor"; +import { ZHADevice } from "../../../../../data/zha"; +import { HomeAssistant } from "../../../../../types"; + +@customElement("zha-device-zigbee-info") +class ZHADeviceZigbeeInfo extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property() public device: ZHADevice | undefined; + + @state() private _signature: any; + + protected updated(changedProperties: PropertyValues): void { + if (changedProperties.has("device") && this.hass && this.device) { + this._signature = JSON.stringify( + { + ...this.device.signature, + manufacturer: this.device.manufacturer, + model: this.device.model, + class: this.device.quirk_class, + }, + null, + 2 + ); + } + super.updated(changedProperties); + } + + protected render(): TemplateResult { + if (!this._signature) { + return html``; + } + + return html` + + + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "zha-device-zigbee-info": ZHADeviceZigbeeInfo; + } +} diff --git a/src/panels/config/integrations/integration-panels/zha/zha-group-binding.ts b/src/panels/config/integrations/integration-panels/zha/zha-group-binding.ts index 67671e9047..d96b27c9ad 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-group-binding.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-group-binding.ts @@ -1,5 +1,3 @@ -import "@material/mwc-button/mwc-button"; -import { mdiHelpCircle } from "@mdi/js"; import { css, CSSResultGroup, @@ -11,37 +9,29 @@ import { import { customElement, property, state, query } from "lit/decorators"; import type { HASSDomEvent } from "../../../../../common/dom/fire_event"; import { stopPropagation } from "../../../../../common/dom/stop_propagation"; -import "../../../../../components/buttons/ha-call-service-button"; +import "../../../../../components/buttons/ha-progress-button"; import { SelectionChangedEvent } from "../../../../../components/data-table/ha-data-table"; import "../../../../../components/ha-card"; -import "../../../../../components/ha-icon-button"; -import "../../../../../components/ha-service-description"; import { bindDeviceToGroup, Cluster, - fetchClustersForZhaNode, + fetchClustersForZhaDevice, unbindDeviceFromGroup, ZHADevice, ZHAGroup, } from "../../../../../data/zha"; import { haStyle } from "../../../../../resources/styles"; import type { HomeAssistant } from "../../../../../types"; -import "../../../ha-config-section"; import { ItemSelectedEvent } from "./types"; import "./zha-clusters-data-table"; import type { ZHAClustersDataTable } from "./zha-clusters-data-table"; +import "@material/mwc-list/mwc-list-item"; @customElement("zha-group-binding-control") export class ZHAGroupBindingControl extends LitElement { @property({ attribute: false }) public hass?: HomeAssistant; - @property() public isWide?: boolean; - - @property() public narrow?: boolean; - - @property() public selectedDevice?: ZHADevice; - - @state() private _showHelp = false; + @property() public device?: ZHADevice; @state() private _bindTargetIndex = -1; @@ -51,6 +41,8 @@ export class ZHAGroupBindingControl extends LitElement { @state() private _clusters: Cluster[] = []; + @state() private _bindingOperationInProgress = false; + private _groupToBind?: ZHAGroup; private _clustersToBind?: Cluster[]; @@ -59,38 +51,17 @@ export class ZHAGroupBindingControl extends LitElement { private _zhaClustersDataTable!: ZHAClustersDataTable; protected updated(changedProperties: PropertyValues): void { - if (changedProperties.has("selectedDevice")) { + if (changedProperties.has("device")) { this._bindTargetIndex = -1; this._selectedClusters = []; this._clustersToBind = []; this._fetchClustersForZhaNode(); } - super.update(changedProperties); + super.updated(changedProperties); } protected render(): TemplateResult { return html` - -
- ${this.hass!.localize( - "ui.panel.config.zha.group_binding.header" - )} - - -
- ${this.hass!.localize( - "ui.panel.config.zha.group_binding.introduction" - )} -
- ${this._showHelp - ? html` -
- ${this.hass!.localize( - "ui.panel.config.zha.group_binding.group_picker_help" - )} -
- ` - : ""}
- ${this._showHelp - ? html` -
- ${this.hass!.localize( - "ui.panel.config.zha.group_binding.cluster_selection_help" - )} -
- ` - : ""}
- ${this.hass!.localize( - "ui.panel.config.zha.group_binding.bind_button_label" - )} - ${this._showHelp - ? html` -
- ${this.hass!.localize( - "ui.panel.config.zha.group_binding.bind_button_help" - )} -
- ` - : ""} - ${this.hass!.localize( - "ui.panel.config.zha.group_binding.unbind_button_label" - )} - ${this._showHelp - ? html` -
- ${this.hass!.localize( - "ui.panel.config.zha.group_binding.unbind_button_help" - )} -
- ` - : ""} + + ${this.hass!.localize( + "ui.panel.config.zha.group_binding.bind_button_label" + )} + + + + ${this.hass!.localize( + "ui.panel.config.zha.group_binding.unbind_button_label" + )} +
@@ -186,31 +123,49 @@ export class ZHAGroupBindingControl extends LitElement { : this.groups[this._bindTargetIndex]; } - private _onHelpTap(): void { - this._showHelp = !this._showHelp; - } - - private async _onBindGroupClick(): Promise { + private async _onBindGroupClick(ev: CustomEvent): Promise { + const button = ev.currentTarget as any; if (this.hass && this._canBind) { - await bindDeviceToGroup( - this.hass, - this.selectedDevice!.ieee, - this._groupToBind!.group_id, - this._clustersToBind! - ); - this._zhaClustersDataTable.clearSelection(); + this._bindingOperationInProgress = true; + button.progress = true; + try { + await bindDeviceToGroup( + this.hass, + this.device!.ieee, + this._groupToBind!.group_id, + this._clustersToBind! + ); + this._zhaClustersDataTable.clearSelection(); + button.actionSuccess(); + } catch (err: any) { + button.actionError(); + } finally { + this._bindingOperationInProgress = false; + button.progress = false; + } } } - private async _onUnbindGroupClick(): Promise { + private async _onUnbindGroupClick(ev: CustomEvent): Promise { + const button = ev.currentTarget as any; if (this.hass && this._canBind) { - await unbindDeviceFromGroup( - this.hass, - this.selectedDevice!.ieee, - this._groupToBind!.group_id, - this._clustersToBind! - ); - this._zhaClustersDataTable.clearSelection(); + this._bindingOperationInProgress = true; + button.progress = true; + try { + await unbindDeviceFromGroup( + this.hass, + this.device!.ieee, + this._groupToBind!.group_id, + this._clustersToBind! + ); + this._zhaClustersDataTable.clearSelection(); + button.actionSuccess(); + } catch (err: any) { + button.actionError(); + } finally { + this._bindingOperationInProgress = false; + button.progress = false; + } } } @@ -230,9 +185,9 @@ export class ZHAGroupBindingControl extends LitElement { private async _fetchClustersForZhaNode(): Promise { if (this.hass) { - this._clusters = await fetchClustersForZhaNode( + this._clusters = await fetchClustersForZhaDevice( this.hass, - this.selectedDevice!.ieee + this.device!.ieee ); this._clusters = this._clusters .filter((cluster) => cluster.type.toLowerCase() === "out") @@ -245,7 +200,7 @@ export class ZHAGroupBindingControl extends LitElement { this._groupToBind && this._clustersToBind && this._clustersToBind?.length > 0 && - this.selectedDevice + this.device ); } @@ -257,18 +212,6 @@ export class ZHAGroupBindingControl extends LitElement { width: 100%; } - .content { - margin-top: 24px; - } - - ha-card { - max-width: 680px; - } - - .card-actions.warning ha-call-service-button { - color: var(--error-color); - } - .command-picker { align-items: center; padding-left: 28px; @@ -285,30 +228,6 @@ export class ZHAGroupBindingControl extends LitElement { .sectionHeader { flex-grow: 1; } - - .helpText { - color: grey; - padding-left: 28px; - padding-right: 28px; - padding-bottom: 10px; - } - - .toggle-help-icon { - float: right; - top: -6px; - right: 0; - padding-right: 0px; - color: var(--primary-color); - } - - ha-service-description { - display: block; - color: grey; - } - - [hidden] { - display: none; - } `, ]; } diff --git a/src/panels/config/integrations/integration-panels/zha/zha-manage-clusters.ts b/src/panels/config/integrations/integration-panels/zha/zha-manage-clusters.ts new file mode 100644 index 0000000000..d956723242 --- /dev/null +++ b/src/panels/config/integrations/integration-panels/zha/zha-manage-clusters.ts @@ -0,0 +1,198 @@ +import "@material/mwc-list/mwc-list-item"; +import { + css, + CSSResultGroup, + html, + LitElement, + PropertyValues, + TemplateResult, +} from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { cache } from "lit/directives/cache"; +import { stopPropagation } from "../../../../../common/dom/stop_propagation"; +import "../../../../../components/ha-card"; +import "../../../../../components/ha-select"; +import { + Cluster, + fetchClustersForZhaDevice, + ZHADevice, +} from "../../../../../data/zha"; +import { haStyle } from "../../../../../resources/styles"; +import { HomeAssistant } from "../../../../../types"; +import { computeClusterKey } from "./functions"; +import "@material/mwc-tab-bar/mwc-tab-bar"; +import "@material/mwc-tab/mwc-tab"; +import "./zha-cluster-attributes"; +import "./zha-cluster-commands"; + +declare global { + // for fire event + interface HASSDomEvents { + "zha-cluster-selected": { + cluster?: Cluster; + }; + } +} + +const tabs = ["attributes", "commands"] as const; + +@customElement("zha-manage-clusters") +export class ZHAManageClusters extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property() public isWide?: boolean; + + @property() public device?: ZHADevice; + + @state() private _selectedClusterIndex = -1; + + @state() private _clusters: Cluster[] = []; + + @state() private _selectedCluster?: Cluster; + + @state() private _currTab: typeof tabs[number] = "attributes"; + + @state() private _clustersLoaded = false; + + protected willUpdate(changedProps: PropertyValues) { + super.willUpdate(changedProps); + if (!this.device) { + return; + } + if (!tabs.includes(this._currTab)) { + this._currTab = tabs[0]; + } + } + + protected updated(changedProperties: PropertyValues): void { + if (changedProperties.has("device")) { + this._clusters = []; + this._selectedClusterIndex = -1; + this._clustersLoaded = false; + this._fetchClustersForZhaDevice(); + } + super.updated(changedProperties); + } + + protected render(): TemplateResult { + if (!this.device || !this._clustersLoaded) { + return html``; + } + return html` + +
+ + ${this._clusters.map( + (entry, idx) => html` + ${computeClusterKey(entry)} + ` + )} + +
+ ${this._selectedCluster + ? html` + + ${tabs.map( + (tab) => html` + + ` + )} + + +
+ ${cache( + this._currTab === "attributes" + ? html` + + ` + : html` + + ` + )} +
+ ` + : ""} +
+ `; + } + + private async _fetchClustersForZhaDevice(): Promise { + if (this.hass) { + this._clusters = await fetchClustersForZhaDevice( + this.hass, + this.device!.ieee + ); + this._clusters.sort((a, b) => a.name.localeCompare(b.name)); + if (this._clusters.length > 0) { + this._selectedClusterIndex = 0; + this._selectedCluster = this._clusters[0]; + } + this._clustersLoaded = true; + } + } + + private _handleTabChanged(ev: CustomEvent): void { + const newTab = tabs[ev.detail.index]; + if (newTab === this._currTab) { + return; + } + this._currTab = newTab; + } + + private _selectedClusterChanged(event): void { + this._selectedClusterIndex = Number(event.target!.value); + this._selectedCluster = this._clusters[this._selectedClusterIndex]; + } + + static get styles(): CSSResultGroup { + return [ + haStyle, + css` + ha-select { + margin-top: 16px; + } + .menu { + width: 100%; + } + .header { + flex-grow: 1; + } + .node-picker { + align-items: center; + padding-bottom: 10px; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "zha-manage-clusters": ZHAManageClusters; + } +} diff --git a/src/translations/en.json b/src/translations/en.json index a7f7dbe100..23933d2f6d 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1002,6 +1002,15 @@ "attribute": "Attribute", "min_max_change": "min/max/change" }, + "zha_manage_device": { + "heading": "Manage Zigbee Device", + "tabs": { + "clusters": "Clusters", + "bindings": "Bindings", + "signature": "Signature", + "children": "Children" + } + }, "zha_device_info": { "manuf": "by {manufacturer}", "no_area": "No Area", @@ -1010,10 +1019,8 @@ "buttons": { "add": "Add devices via this device", "remove": "Remove", - "clusters": "Manage clusters", + "manage": "Manage zigbee device", "reconfigure": "Reconfigure", - "zigbee_information": "Zigbee signature", - "device_children": "View children", "view_network": "View network" }, "services": { @@ -3078,17 +3085,17 @@ "clusters": { "header": "Clusters", "help_cluster_dropdown": "Select a cluster to view attributes and commands.", - "introduction": "Clusters are the building blocks for Zigbee functionality. They separate functionality into logical units. There are client and server types and that are comprised of attributes and commands." + "tabs": { + "attributes": "Attributes", + "commands": "Commands" + } }, "cluster_attributes": { "header": "Cluster Attributes", "introduction": "View and edit cluster attributes.", "attributes_of_cluster": "Attributes of the selected cluster", - "get_zigbee_attribute": "Get Zigbee Attribute", - "set_zigbee_attribute": "Set Zigbee Attribute", - "help_attribute_dropdown": "Select an attribute to view or set its value.", - "help_get_zigbee_attribute": "Get the value for the selected attribute.", - "help_set_zigbee_attribute": "Set attribute value for the specified cluster on the specified entity." + "read_zigbee_attribute": "Read Attribute", + "write_zigbee_attribute": "Write Attribute" }, "cluster_commands": { "header": "Cluster Commands", @@ -3138,6 +3145,11 @@ "enable_physics": "Enable Physics", "refresh_topology": "Refresh Topology" }, + "device_binding": { + "bind": "Bind", + "unbind": "Unbind", + "picker_label": "Bindable Devices" + }, "group_binding": { "header": "Group Binding", "introduction": "Bind and unbind groups.",