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:
David F. Mulcahey 2020-01-17 10:39:57 -05:00 committed by Bram Kragten
parent 1064aed1b0
commit a2a039ebc5
17 changed files with 614 additions and 83 deletions

View File

@ -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

View File

@ -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>

View File

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

View File

@ -120,7 +120,7 @@ class ZHAAddDevicesPage extends LitElement {
.narrow=${!this.isWide}
.showHelp=${this._showHelp}
.showActions=${!this._active}
isJoinPage
.showEntityDetail=${false}
></zha-device-card>
`
)}

View File

@ -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);

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

View File

@ -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;

View File

@ -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>

View File

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

View File

@ -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(

View File

@ -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,

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

View File

@ -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);

View File

@ -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);

View File

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

View File

@ -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 {

View File

@ -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": {