mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-28 11:46:42 +00:00
Add group binding to the ZHA config panel and misc. cleanup (#4466)
* clean up zha device card and usage * group binding tile * add cluster selection to group binding tile * fix css class name * fix filtering * multiselect for clusters in group binding * pass narrow to cluster table * fix tables * fix device page * address remaing comments from previous PR * fix bad cherry-pick * css cleanup * consistency * use properties * translations * add confirmation dialog to remove button * fix css * review comments * remove noise
This commit is contained in:
parent
1064aed1b0
commit
a2a039ebc5
@ -126,6 +126,32 @@ export const unbindDevices = (
|
||||
target_ieee: targetIEEE,
|
||||
});
|
||||
|
||||
export const bindDeviceToGroup = (
|
||||
hass: HomeAssistant,
|
||||
deviceIEEE: string,
|
||||
groupId: number,
|
||||
clusters: Cluster[]
|
||||
): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "zha/groups/bind",
|
||||
source_ieee: deviceIEEE,
|
||||
group_id: groupId,
|
||||
bindings: clusters,
|
||||
});
|
||||
|
||||
export const unbindDeviceFromGroup = (
|
||||
hass: HomeAssistant,
|
||||
deviceIEEE: string,
|
||||
groupId: number,
|
||||
clusters: Cluster[]
|
||||
): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "zha/groups/unbind",
|
||||
source_ieee: deviceIEEE,
|
||||
group_id: groupId,
|
||||
bindings: clusters,
|
||||
});
|
||||
|
||||
export const readAttributeValue = (
|
||||
hass: HomeAssistant,
|
||||
data: ReadAttributeServiceData
|
||||
|
@ -54,9 +54,8 @@ class DialogZHADeviceInfo extends LitElement {
|
||||
class="card"
|
||||
.hass=${this.hass}
|
||||
.device=${this._device}
|
||||
showActions
|
||||
isJoinPage
|
||||
@zha-device-removed=${this._onDeviceRemoved}
|
||||
.showEntityDetail=${false}
|
||||
></zha-device-card>
|
||||
`}
|
||||
</ha-paper-dialog>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ZHADevice, ZHAGroup } from "../../../data/zha";
|
||||
import { ZHADevice, ZHAGroup, Cluster } from "../../../data/zha";
|
||||
|
||||
export const formatAsPaddedHex = (value: string | number): string => {
|
||||
let hex = value;
|
||||
@ -19,3 +19,9 @@ export const sortZHAGroups = (a: ZHAGroup, b: ZHAGroup): number => {
|
||||
const nameb = b.name;
|
||||
return nameA.localeCompare(nameb);
|
||||
};
|
||||
|
||||
export const computeClusterKey = (cluster: Cluster): string => {
|
||||
return `${cluster.name} (Endpoint id: ${
|
||||
cluster.endpoint_id
|
||||
}, Id: ${formatAsPaddedHex(cluster.id)}, Type: ${cluster.type})`;
|
||||
};
|
||||
|
@ -120,7 +120,7 @@ class ZHAAddDevicesPage extends LitElement {
|
||||
.narrow=${!this.isWide}
|
||||
.showHelp=${this._showHelp}
|
||||
.showActions=${!this._active}
|
||||
isJoinPage
|
||||
.showEntityDetail=${false}
|
||||
></zha-device-card>
|
||||
`
|
||||
)}
|
||||
|
@ -117,7 +117,10 @@ export class ZHAAddGroupPage extends LitElement {
|
||||
private _handleAddSelectionChanged(ev: CustomEvent): void {
|
||||
const changedSelection = ev.detail as SelectionChangedEvent;
|
||||
const entity = changedSelection.id;
|
||||
if (changedSelection.selected) {
|
||||
if (
|
||||
changedSelection.selected &&
|
||||
!this._selectedDevicesToAdd.includes(entity)
|
||||
) {
|
||||
this._selectedDevicesToAdd.push(entity);
|
||||
} else {
|
||||
const index = this._selectedDevicesToAdd.indexOf(entity);
|
||||
|
92
src/panels/config/zha/zha-clusters-data-table.ts
Normal file
92
src/panels/config/zha/zha-clusters-data-table.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import "../../../components/data-table/ha-data-table";
|
||||
import "../../../components/entity/ha-state-icon";
|
||||
|
||||
import memoizeOne from "memoize-one";
|
||||
|
||||
import {
|
||||
LitElement,
|
||||
html,
|
||||
TemplateResult,
|
||||
property,
|
||||
customElement,
|
||||
} from "lit-element";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
// tslint:disable-next-line
|
||||
import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table";
|
||||
// tslint:disable-next-line
|
||||
import { Cluster } from "../../../data/zha";
|
||||
import { formatAsPaddedHex } from "./functions";
|
||||
|
||||
export interface ClusterRowData extends Cluster {
|
||||
cluster?: Cluster;
|
||||
cluster_id?: string;
|
||||
}
|
||||
|
||||
@customElement("zha-clusters-data-table")
|
||||
export class ZHAClustersDataTable extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public narrow = false;
|
||||
@property() public clusters: Cluster[] = [];
|
||||
|
||||
private _clusters = memoizeOne((clusters: Cluster[]) => {
|
||||
let outputClusters: ClusterRowData[] = clusters;
|
||||
|
||||
outputClusters = outputClusters.map((cluster) => {
|
||||
return {
|
||||
...cluster,
|
||||
cluster_id: cluster.endpoint_id + "-" + cluster.id,
|
||||
};
|
||||
});
|
||||
|
||||
return outputClusters;
|
||||
});
|
||||
|
||||
private _columns = memoizeOne(
|
||||
(narrow: boolean): DataTableColumnContainer =>
|
||||
narrow
|
||||
? {
|
||||
name: {
|
||||
title: "Name",
|
||||
sortable: true,
|
||||
direction: "asc",
|
||||
},
|
||||
}
|
||||
: {
|
||||
name: {
|
||||
title: "Name",
|
||||
sortable: true,
|
||||
direction: "asc",
|
||||
},
|
||||
id: {
|
||||
title: "ID",
|
||||
template: (id: number) => {
|
||||
return html`
|
||||
${formatAsPaddedHex(id)}
|
||||
`;
|
||||
},
|
||||
sortable: true,
|
||||
},
|
||||
endpoint_id: {
|
||||
title: "Endpoint ID",
|
||||
sortable: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-data-table
|
||||
.columns=${this._columns(this.narrow)}
|
||||
.data=${this._clusters(this.clusters)}
|
||||
.id=${"cluster_id"}
|
||||
selectable
|
||||
></ha-data-table>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"zha-clusters-data-table": ZHAClustersDataTable;
|
||||
}
|
||||
}
|
@ -21,7 +21,7 @@ import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { Cluster, fetchClustersForZhaNode, ZHADevice } from "../../../data/zha";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { formatAsPaddedHex } from "./functions";
|
||||
import { computeClusterKey } from "./functions";
|
||||
import { ItemSelectedEvent } from "./types";
|
||||
|
||||
declare global {
|
||||
@ -33,12 +33,6 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
const computeClusterKey = (cluster: Cluster): string => {
|
||||
return `${cluster.name} (Endpoint id: ${
|
||||
cluster.endpoint_id
|
||||
}, Id: ${formatAsPaddedHex(cluster.id)}, Type: ${cluster.type})`;
|
||||
};
|
||||
|
||||
export class ZHAClusters extends LitElement {
|
||||
@property() public hass?: HomeAssistant;
|
||||
@property() public isWide?: boolean;
|
||||
|
@ -48,7 +48,6 @@ class ZHAConfigDashboard extends LitElement {
|
||||
...device,
|
||||
name: device.user_given_name ? device.user_given_name : device.name,
|
||||
nwk: formatAsPaddedHex(device.nwk),
|
||||
id: device.ieee,
|
||||
};
|
||||
});
|
||||
|
||||
@ -142,6 +141,7 @@ class ZHAConfigDashboard extends LitElement {
|
||||
.columns=${this._columns(this.narrow)}
|
||||
.data=${this._memoizeDevices(this._devices)}
|
||||
@row-click=${this._handleDeviceClicked}
|
||||
.id=${"ieee"}
|
||||
></ha-data-table>
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
|
@ -24,8 +24,8 @@ import { HomeAssistant } from "../../../types";
|
||||
import { ItemSelectedEvent } from "./types";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
|
||||
@customElement("zha-binding-control")
|
||||
export class ZHABindingControl extends LitElement {
|
||||
@customElement("zha-device-binding-control")
|
||||
export class ZHADeviceBindingControl extends LitElement {
|
||||
@property() public hass?: HomeAssistant;
|
||||
@property() public isWide?: boolean;
|
||||
@property() public selectedDevice?: ZHADevice;
|
||||
@ -175,7 +175,9 @@ export class ZHABindingControl extends LitElement {
|
||||
|
||||
.helpText {
|
||||
color: grey;
|
||||
padding: 16px;
|
||||
padding-left: 28px;
|
||||
padding-right: 28px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.header {
|
||||
@ -204,6 +206,6 @@ export class ZHABindingControl extends LitElement {
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"zha-binding-control": ZHABindingControl;
|
||||
"zha-device-binding-control": ZHADeviceBindingControl;
|
||||
}
|
||||
}
|
@ -58,8 +58,11 @@ class ZHADeviceCard extends LitElement {
|
||||
@property() public device?: ZHADevice;
|
||||
@property({ type: Boolean }) public narrow?: boolean;
|
||||
@property({ type: Boolean }) public showHelp?: boolean = false;
|
||||
@property({ type: Boolean }) public showActions?: boolean;
|
||||
@property({ type: Boolean }) public isJoinPage?: boolean;
|
||||
@property({ type: Boolean }) public showActions?: boolean = true;
|
||||
@property({ type: Boolean }) public showName?: boolean = true;
|
||||
@property({ type: Boolean }) public showEntityDetail?: boolean = true;
|
||||
@property({ type: Boolean }) public showModelInfo?: boolean = true;
|
||||
@property({ type: Boolean }) public showEditableInfo?: boolean = true;
|
||||
@property() private _serviceData?: NodeServiceData;
|
||||
@property() private _areas: AreaRegistryEntry[] = [];
|
||||
@property() private _selectedAreaIndex: number = -1;
|
||||
@ -137,9 +140,9 @@ class ZHADeviceCard extends LitElement {
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
return html`
|
||||
<ha-card header="${this.isJoinPage ? this.device!.name : ""}">
|
||||
<ha-card header="${this.showName ? this.device!.name : ""}">
|
||||
${
|
||||
this.isJoinPage
|
||||
this.showModelInfo
|
||||
? html`
|
||||
<div class="info">
|
||||
<div class="model">${this.device!.model}</div>
|
||||
@ -202,7 +205,7 @@ class ZHADeviceCard extends LitElement {
|
||||
.stateObj="${this.hass!.states[entity.entity_id]}"
|
||||
slot="item-icon"
|
||||
></state-badge>
|
||||
${!this.isJoinPage
|
||||
${this.showEntityDetail
|
||||
? html`
|
||||
<paper-item-body>
|
||||
<div class="name">
|
||||
@ -218,40 +221,48 @@ class ZHADeviceCard extends LitElement {
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
<div class="editable">
|
||||
<paper-input
|
||||
type="string"
|
||||
@change="${this._saveCustomName}"
|
||||
.value="${this._userGivenName}"
|
||||
placeholder="${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.zha_device_card.device_name_placeholder"
|
||||
)}"
|
||||
></paper-input>
|
||||
</div>
|
||||
<div class="node-picker">
|
||||
<paper-dropdown-menu
|
||||
label="${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.zha_device_card.area_picker_label"
|
||||
)}"
|
||||
class="menu"
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected="${this._selectedAreaIndex}"
|
||||
@iron-select="${this._selectedAreaChanged}"
|
||||
>
|
||||
<paper-item>
|
||||
${this.hass!.localize("ui.dialogs.zha_device_info.no_area")}
|
||||
</paper-item>
|
||||
${
|
||||
this.showEditableInfo
|
||||
? html`
|
||||
<div class="editable">
|
||||
<paper-input
|
||||
type="string"
|
||||
@change="${this._saveCustomName}"
|
||||
.value="${this._userGivenName}"
|
||||
.placeholder="${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.zha_device_card.device_name_placeholder"
|
||||
)}"
|
||||
></paper-input>
|
||||
</div>
|
||||
<div class="node-picker">
|
||||
<paper-dropdown-menu
|
||||
.label="${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.zha_device_card.area_picker_label"
|
||||
)}"
|
||||
class="menu"
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected="${this._selectedAreaIndex}"
|
||||
@iron-select="${this._selectedAreaChanged}"
|
||||
>
|
||||
<paper-item>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.no_area"
|
||||
)}
|
||||
</paper-item>
|
||||
|
||||
${this._areas.map(
|
||||
(entry) => html`
|
||||
<paper-item area="${entry}">${entry.name}</paper-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
</div>
|
||||
${this._areas.map(
|
||||
(entry) => html`
|
||||
<paper-item>${entry.name}</paper-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
</div>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
this.showActions
|
||||
? html`
|
||||
@ -275,6 +286,9 @@ class ZHADeviceCard extends LitElement {
|
||||
.hass="${this.hass}"
|
||||
domain="zha"
|
||||
service="remove"
|
||||
.confirmation=${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.confirmations.remove"
|
||||
)}
|
||||
.serviceData="${this._serviceData}"
|
||||
>
|
||||
${this.hass!.localize(
|
||||
|
@ -1,6 +1,7 @@
|
||||
import "../../../layouts/hass-subpage";
|
||||
import "../../../components/ha-paper-icon-button-arrow-prev";
|
||||
import "./zha-binding";
|
||||
import "./zha-device-binding";
|
||||
import "./zha-group-binding";
|
||||
import "./zha-cluster-attributes";
|
||||
import "./zha-cluster-commands";
|
||||
import "./zha-clusters";
|
||||
@ -24,10 +25,12 @@ import {
|
||||
fetchBindableDevices,
|
||||
ZHADevice,
|
||||
fetchZHADevice,
|
||||
ZHAGroup,
|
||||
fetchGroups,
|
||||
} from "../../../data/zha";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { sortZHADevices } from "./functions";
|
||||
import { sortZHADevices, sortZHAGroups } from "./functions";
|
||||
import { ZHAClusterSelectedParams } from "./types";
|
||||
|
||||
@customElement("zha-device-page")
|
||||
@ -36,8 +39,27 @@ export class ZHADevicePage extends LitElement {
|
||||
@property() public isWide?: boolean;
|
||||
@property() public ieee?: string;
|
||||
@property() public device?: ZHADevice;
|
||||
@property() public narrow?: boolean;
|
||||
@property() private _selectedCluster?: Cluster;
|
||||
@property() private _bindableDevices: ZHADevice[] = [];
|
||||
@property() private _groups: ZHAGroup[] = [];
|
||||
|
||||
private _firstUpdatedCalled: boolean = false;
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
if (this.hass && this._firstUpdatedCalled) {
|
||||
this._fetchGroups();
|
||||
}
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues): void {
|
||||
super.firstUpdated(changedProperties);
|
||||
if (this.hass) {
|
||||
this._fetchGroups();
|
||||
}
|
||||
this._firstUpdatedCalled = true;
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
if (changedProperties.has("ieee")) {
|
||||
@ -82,12 +104,23 @@ export class ZHADevicePage extends LitElement {
|
||||
: ""}
|
||||
${this._bindableDevices.length > 0
|
||||
? html`
|
||||
<zha-binding-control
|
||||
<zha-device-binding-control
|
||||
.isWide="${this.isWide}"
|
||||
.hass="${this.hass}"
|
||||
.selectedDevice="${this.device}"
|
||||
.bindableDevices="${this._bindableDevices}"
|
||||
></zha-binding-control>
|
||||
></zha-device-binding-control>
|
||||
`
|
||||
: ""}
|
||||
${this.device && this._groups.length > 0
|
||||
? html`
|
||||
<zha-group-binding-control
|
||||
.isWide="${this.isWide}"
|
||||
.narrow="${this.narrow}"
|
||||
.hass="${this.hass}"
|
||||
.selectedDevice="${this.device}"
|
||||
.groups="${this._groups}"
|
||||
></zha-group-binding-control>
|
||||
`
|
||||
: ""}
|
||||
<div class="spacer" />
|
||||
@ -110,6 +143,10 @@ export class ZHADevicePage extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async _fetchGroups() {
|
||||
this._groups = (await fetchGroups(this.hass!)).sort(sortZHAGroups);
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
|
321
src/panels/config/zha/zha-group-binding.ts
Normal file
321
src/panels/config/zha/zha-group-binding.ts
Normal file
@ -0,0 +1,321 @@
|
||||
import "../../../components/buttons/ha-call-service-button";
|
||||
import "../../../components/ha-service-description";
|
||||
import "../../../components/ha-card";
|
||||
import "../ha-config-section";
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
|
||||
import {
|
||||
bindDeviceToGroup,
|
||||
unbindDeviceFromGroup,
|
||||
ZHADevice,
|
||||
ZHAGroup,
|
||||
Cluster,
|
||||
fetchClustersForZhaNode,
|
||||
} from "../../../data/zha";
|
||||
import "./zha-clusters-data-table";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { ItemSelectedEvent } from "./types";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import { SelectionChangedEvent } from "../../../components/data-table/ha-data-table";
|
||||
|
||||
@customElement("zha-group-binding-control")
|
||||
export class ZHAGroupBindingControl extends LitElement {
|
||||
@property() public hass?: HomeAssistant;
|
||||
@property() public isWide?: boolean;
|
||||
@property() public narrow?: boolean;
|
||||
@property() public selectedDevice?: ZHADevice;
|
||||
@property() private _showHelp: boolean = false;
|
||||
@property() private _bindTargetIndex: number = -1;
|
||||
@property() private groups: ZHAGroup[] = [];
|
||||
@property() private _selectedClusters: string[] = [];
|
||||
@property() private _clusters: Cluster[] = [];
|
||||
private _groupToBind?: ZHAGroup;
|
||||
private _clustersToBind?: Cluster[];
|
||||
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
if (changedProperties.has("selectedDevice")) {
|
||||
this._bindTargetIndex = -1;
|
||||
this._selectedClusters = [];
|
||||
this._clustersToBind = [];
|
||||
this._fetchClustersForZhaNode();
|
||||
}
|
||||
super.update(changedProperties);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
return html`
|
||||
<ha-config-section .isWide="${this.isWide}">
|
||||
<div class="sectionHeader" slot="header">
|
||||
<span
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.zha.group_binding.header"
|
||||
)}</span
|
||||
>
|
||||
<paper-icon-button
|
||||
class="toggle-help-icon"
|
||||
@click="${this._onHelpTap}"
|
||||
icon="hass:help-circle"
|
||||
>
|
||||
</paper-icon-button>
|
||||
</div>
|
||||
<span slot="introduction"
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.zha.group_binding.introduction"
|
||||
)}</span
|
||||
>
|
||||
|
||||
<ha-card class="content">
|
||||
<div class="command-picker">
|
||||
<paper-dropdown-menu
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.zha.group_binding.group_picker_label"
|
||||
)}
|
||||
class="menu"
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected="${this._bindTargetIndex}"
|
||||
@iron-select="${this._bindTargetIndexChanged}"
|
||||
>
|
||||
${this.groups.map(
|
||||
(group) => html`
|
||||
<paper-item>${group.name}</paper-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
</div>
|
||||
${this._showHelp
|
||||
? html`
|
||||
<div class="helpText">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.group_binding.group_picker_help"
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<div class="command-picker">
|
||||
<zha-clusters-data-table
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.clusters=${this._clusters}
|
||||
@selection-changed=${this._handleClusterSelectionChanged}
|
||||
class="menu"
|
||||
></zha-clusters-data-table>
|
||||
</div>
|
||||
${this._showHelp
|
||||
? html`
|
||||
<div class="helpText">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.group_binding.cluster_selection_help"
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<div class="card-actions">
|
||||
<mwc-button
|
||||
@click="${this._onBindGroupClick}"
|
||||
.disabled="${!this._canBind}"
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.zha.group_binding.bind_button_label"
|
||||
)}</mwc-button
|
||||
>
|
||||
${this._showHelp
|
||||
? html`
|
||||
<div class="helpText">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.group_binding.bind_button_help"
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<mwc-button
|
||||
@click="${this._onUnbindGroupClick}"
|
||||
.disabled="${!this._canBind}"
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.zha.group_binding.unbind_button_label"
|
||||
)}</mwc-button
|
||||
>
|
||||
${this._showHelp
|
||||
? html`
|
||||
<div class="helpText">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.group_binding.unbind_button_help"
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
`;
|
||||
}
|
||||
|
||||
private _bindTargetIndexChanged(event: ItemSelectedEvent): void {
|
||||
this._bindTargetIndex = event.target!.selected;
|
||||
this._groupToBind =
|
||||
this._bindTargetIndex === -1
|
||||
? undefined
|
||||
: this.groups[this._bindTargetIndex];
|
||||
}
|
||||
|
||||
private _onHelpTap(): void {
|
||||
this._showHelp = !this._showHelp;
|
||||
}
|
||||
|
||||
private async _onBindGroupClick(): Promise<void> {
|
||||
if (this.hass && this._canBind) {
|
||||
await bindDeviceToGroup(
|
||||
this.hass,
|
||||
this.selectedDevice!.ieee,
|
||||
this._groupToBind!.group_id,
|
||||
this._clustersToBind!
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async _onUnbindGroupClick(): Promise<void> {
|
||||
if (this.hass && this._canBind) {
|
||||
await unbindDeviceFromGroup(
|
||||
this.hass,
|
||||
this.selectedDevice!.ieee,
|
||||
this._groupToBind!.group_id,
|
||||
this._clustersToBind!
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private _handleClusterSelectionChanged(event: CustomEvent): void {
|
||||
const changedSelection = event.detail as SelectionChangedEvent;
|
||||
const clusterId = changedSelection.id;
|
||||
if (
|
||||
changedSelection.selected &&
|
||||
!this._selectedClusters.includes(clusterId)
|
||||
) {
|
||||
this._selectedClusters.push(clusterId);
|
||||
} else {
|
||||
const index = this._selectedClusters.indexOf(clusterId);
|
||||
if (index !== -1) {
|
||||
this._selectedClusters.splice(index, 1);
|
||||
}
|
||||
}
|
||||
this._selectedClusters = [...this._selectedClusters];
|
||||
this._clustersToBind = [];
|
||||
for (const clusterIndex of this._selectedClusters) {
|
||||
const selectedCluster = this._clusters.find((cluster) => {
|
||||
return clusterIndex === cluster.endpoint_id + "-" + cluster.id;
|
||||
});
|
||||
this._clustersToBind.push(selectedCluster!);
|
||||
}
|
||||
}
|
||||
|
||||
private async _fetchClustersForZhaNode(): Promise<void> {
|
||||
if (this.hass) {
|
||||
this._clusters = await fetchClustersForZhaNode(
|
||||
this.hass,
|
||||
this.selectedDevice!.ieee
|
||||
);
|
||||
this._clusters = this._clusters
|
||||
.filter((cluster) => {
|
||||
return cluster.type.toLowerCase() === "out";
|
||||
})
|
||||
.sort((a, b) => {
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private get _canBind(): boolean {
|
||||
return Boolean(
|
||||
this._groupToBind &&
|
||||
this._clustersToBind &&
|
||||
this._clustersToBind?.length > 0 &&
|
||||
this.selectedDevice
|
||||
);
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.menu {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
ha-card {
|
||||
margin: 0 auto;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.card-actions.warning ha-call-service-button {
|
||||
color: var(--google-red-500);
|
||||
}
|
||||
|
||||
.command-picker {
|
||||
align-items: center;
|
||||
padding-left: 28px;
|
||||
padding-right: 28px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.input-text {
|
||||
padding-left: 28px;
|
||||
padding-right: 28px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.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;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
ha-service-description {
|
||||
display: block;
|
||||
color: grey;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"zha-group-binding-control": ZHAGroupBindingControl;
|
||||
}
|
||||
}
|
@ -121,6 +121,8 @@ export class ZHAGroupPage extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.device=${member}
|
||||
.narrow=${this.narrow}
|
||||
.showActions=${false}
|
||||
.showEditableInfo=${false}
|
||||
></zha-device-card>
|
||||
`
|
||||
)
|
||||
@ -224,7 +226,10 @@ export class ZHAGroupPage extends LitElement {
|
||||
private _handleAddSelectionChanged(ev: CustomEvent): void {
|
||||
const changedSelection = ev.detail as SelectionChangedEvent;
|
||||
const entity = changedSelection.id;
|
||||
if (changedSelection.selected) {
|
||||
if (
|
||||
changedSelection.selected &&
|
||||
!this._selectedDevicesToAdd.includes(entity)
|
||||
) {
|
||||
this._selectedDevicesToAdd.push(entity);
|
||||
} else {
|
||||
const index = this._selectedDevicesToAdd.indexOf(entity);
|
||||
@ -238,7 +243,10 @@ export class ZHAGroupPage extends LitElement {
|
||||
private _handleRemoveSelectionChanged(ev: CustomEvent): void {
|
||||
const changedSelection = ev.detail as SelectionChangedEvent;
|
||||
const entity = changedSelection.id;
|
||||
if (changedSelection.selected) {
|
||||
if (
|
||||
changedSelection.selected &&
|
||||
!this._selectedDevicesToRemove.includes(entity)
|
||||
) {
|
||||
this._selectedDevicesToRemove.push(entity);
|
||||
} else {
|
||||
const index = this._selectedDevicesToRemove.indexOf(entity);
|
||||
|
@ -47,9 +47,9 @@ export class ZHAGroupsDashboard extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<hass-subpage
|
||||
.header="${this.hass!.localize(
|
||||
.header=${this.hass!.localize(
|
||||
"ui.panel.config.zha.groups.groups-header"
|
||||
)}"
|
||||
)}
|
||||
>
|
||||
<paper-icon-button
|
||||
slot="toolbar-icon"
|
||||
@ -105,7 +105,10 @@ export class ZHAGroupsDashboard extends LitElement {
|
||||
private _handleRemoveSelectionChanged(ev: CustomEvent): void {
|
||||
const changedSelection = ev.detail as SelectionChangedEvent;
|
||||
const groupId = Number(changedSelection.id);
|
||||
if (changedSelection.selected) {
|
||||
if (
|
||||
changedSelection.selected &&
|
||||
!this._selectedGroupsToRemove.includes(groupId)
|
||||
) {
|
||||
this._selectedGroupsToRemove.push(groupId);
|
||||
} else {
|
||||
const index = this._selectedGroupsToRemove.indexOf(groupId);
|
||||
|
@ -20,7 +20,7 @@ import { navigate } from "../../../common/navigate";
|
||||
|
||||
export interface GroupRowData extends ZHAGroup {
|
||||
group?: GroupRowData;
|
||||
id?: number;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
@customElement("zha-groups-data-table")
|
||||
@ -30,6 +30,19 @@ export class ZHAGroupsDataTable extends LitElement {
|
||||
@property() public groups: ZHAGroup[] = [];
|
||||
@property() public selectable = false;
|
||||
|
||||
private _groups = memoizeOne((groups: ZHAGroup[]) => {
|
||||
let outputGroups: GroupRowData[] = groups;
|
||||
|
||||
outputGroups = outputGroups.map((group) => {
|
||||
return {
|
||||
...group,
|
||||
id: String(group.group_id),
|
||||
};
|
||||
});
|
||||
|
||||
return outputGroups;
|
||||
});
|
||||
|
||||
private _columns = memoizeOne(
|
||||
(narrow: boolean): DataTableColumnContainer =>
|
||||
narrow
|
||||
@ -83,8 +96,7 @@ export class ZHAGroupsDataTable extends LitElement {
|
||||
return html`
|
||||
<ha-data-table
|
||||
.columns=${this._columns(this.narrow)}
|
||||
.data=${this.groups}
|
||||
.id=${"group_id"}
|
||||
.data=${this._groups(this.groups)}
|
||||
.selectable=${this.selectable}
|
||||
></ha-data-table>
|
||||
`;
|
||||
|
@ -55,16 +55,19 @@ export class ZHANode extends LitElement {
|
||||
"ui.panel.config.zha.node_management.hint_wakeup"
|
||||
)}
|
||||
</span>
|
||||
<zha-device-card
|
||||
class="card"
|
||||
.hass=${this.hass}
|
||||
.device=${this.device}
|
||||
.narrow=${!this.isWide}
|
||||
.showHelp=${this._showHelp}
|
||||
isJoinPage
|
||||
showActions
|
||||
@zha-device-removed=${this._onDeviceRemoved}
|
||||
></zha-device-card>
|
||||
<div class="content">
|
||||
<zha-device-card
|
||||
class="card"
|
||||
.hass=${this.hass}
|
||||
.device=${this.device}
|
||||
.narrow=${!this.isWide}
|
||||
.showHelp=${this._showHelp}
|
||||
showName
|
||||
showModelInfo
|
||||
.showEntityDetail=${false}
|
||||
@zha-device-removed=${this._onDeviceRemoved}
|
||||
></zha-device-card>
|
||||
</div>
|
||||
</ha-config-section>
|
||||
`;
|
||||
}
|
||||
@ -93,17 +96,14 @@ export class ZHANode extends LitElement {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
ha-card {
|
||||
margin: 0 auto;
|
||||
.content {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 28px 20px 0;
|
||||
margin-top: 24px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
ha-service-description {
|
||||
|
@ -624,6 +624,9 @@
|
||||
"updateDeviceName": "Set a custom name for this device in the device registry.",
|
||||
"remove": "Remove a device from the Zigbee network."
|
||||
},
|
||||
"confirmations": {
|
||||
"remove": "Are you sure that you want to remove the device?"
|
||||
},
|
||||
"quirk": "Quirk",
|
||||
"last_seen": "Last Seen",
|
||||
"power_source": "Power Source",
|
||||
@ -1510,6 +1513,17 @@
|
||||
"create_group": "Zigbee Home Automation - Create Group",
|
||||
"create": "Create Group",
|
||||
"creating_group": "Creating Group"
|
||||
},
|
||||
"group_binding": {
|
||||
"header": "Group Binding",
|
||||
"introduction": "Bind and unbind groups.",
|
||||
"group_picker_label": "Bindable Groups",
|
||||
"group_picker_help": "Select a group to issue a bind command.",
|
||||
"cluster_selection_help": "Select clusters to bind to the selected group.",
|
||||
"bind_button_label": "Bind Group",
|
||||
"unbind_button_label": "Unbind Group",
|
||||
"bind_button_help": "Bind the selected group to the selected device clusters.",
|
||||
"unbind_button_help": "Unbind the selected group from the selected device clusters."
|
||||
}
|
||||
},
|
||||
"zwave": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user