Rework the ZHA config panel (#4415)

* convert zha config panel to tabs

* add spacer to prevent combobox from hitting bottom

* break clusters out into their own section

* cleanup buttons

* remove header

* make devices default tab

* convert from tabs to a list view

* convert to table on dashboard

* fix anchor on mobile safari

* cleanup CSS to fix display on mobile

* cleanup card css

* more css cleanup

* fix group page

* remove translations changes

* Update src/panels/config/zha/zha-clusters.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update src/panels/config/zha/zha-config-dashboard.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update src/panels/config/zha/zha-device-page.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update src/panels/config/zha/zha-groups-dashboard.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* review comments

* fix dangling quote after commit suggestion

* css cleanup

* remove flex rules

* remove flex rules

* css  cleanup

* remove dialog per review comments

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
David F. Mulcahey 2020-01-08 12:35:21 -05:00 committed by Bram Kragten
parent 357a67c00d
commit 5415068917
14 changed files with 411 additions and 557 deletions

View File

@ -127,10 +127,10 @@ class HaPanelConfig extends HassRouterPage {
),
},
zha: {
tag: "zha-config-panel",
tag: "zha-config-dashboard-router",
load: () =>
import(
/* webpackChunkName: "panel-config-zha" */ "./zha/zha-config-panel"
/* webpackChunkName: "panel-config-zha" */ "./zha/zha-config-dashboard-router"
),
},
zwave: {

View File

@ -44,7 +44,7 @@ export class ZHABindingControl extends LitElement {
protected render(): TemplateResult | void {
return html`
<ha-config-section .isWide="${this.isWide}">
<div class="sectionHeader" slot="header">
<div class="header" slot="header">
<span>Device Binding</span>
<paper-icon-button
class="toggle-help-icon"
@ -57,7 +57,7 @@ export class ZHABindingControl extends LitElement {
<ha-card class="content">
<div class="command-picker">
<paper-dropdown-menu label="Bindable Devices" class="flex">
<paper-dropdown-menu label="Bindable Devices" class="menu">
<paper-listbox
slot="dropdown-content"
.selected="${this._bindTargetIndex}"
@ -149,12 +149,8 @@ export class ZHABindingControl extends LitElement {
return [
haStyle,
css`
.flex {
-ms-flex: 1 1 0.000000001px;
-webkit-flex: 1;
flex: 1;
-webkit-flex-basis: 0.000000001px;
flex-basis: 0.000000001px;
.menu {
width: 100%;
}
.content {
@ -171,37 +167,23 @@ export class ZHABindingControl extends LitElement {
}
.command-picker {
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-ms-flex-direction: row;
-webkit-flex-direction: row;
flex-direction: row;
-ms-flex-align: center;
-webkit-align-items: center;
align-items: center;
padding-left: 28px;
padding-right: 28px;
padding-bottom: 10px;
}
.input-text {
padding-left: 28px;
padding-right: 28px;
padding-bottom: 10px;
}
.sectionHeader {
position: relative;
}
.helpText {
color: grey;
padding: 16px;
}
.header {
flex-grow: 1;
}
.toggle-help-icon {
position: absolute;
float: right;
top: -6px;
right: 0;
color: var(--primary-color);

View File

@ -61,7 +61,7 @@ export class ZHAClusterAttributes extends LitElement {
protected render(): TemplateResult | void {
return html`
<ha-config-section .isWide="${this.isWide}">
<div style="position: relative" slot="header">
<div class="header" slot="header">
<span>
${this.hass!.localize(
"ui.panel.config.zha.cluster_attributes.header"
@ -86,7 +86,7 @@ export class ZHAClusterAttributes extends LitElement {
label="${this.hass!.localize(
"ui.panel.config.zha.cluster_attributes.attributes_of_cluster"
)}"
class="flex"
class="menu"
>
<paper-listbox
slot="dropdown-content"
@ -270,12 +270,8 @@ export class ZHAClusterAttributes extends LitElement {
return [
haStyle,
css`
.flex {
-ms-flex: 1 1 0.000000001px;
-webkit-flex: 1;
flex: 1;
-webkit-flex-basis: 0.000000001px;
flex-basis: 0.000000001px;
.menu {
width: 100%;
}
.content {
@ -292,14 +288,6 @@ export class ZHAClusterAttributes extends LitElement {
}
.attribute-picker {
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-ms-flex-direction: row;
-webkit-flex-direction: row;
flex-direction: row;
-ms-flex-align: center;
-webkit-align-items: center;
align-items: center;
padding-left: 28px;
padding-right: 28px;
@ -312,8 +300,12 @@ export class ZHAClusterAttributes extends LitElement {
padding-bottom: 10px;
}
.header {
flex-grow: 1;
}
.toggle-help-icon {
position: absolute;
float: right;
top: -6px;
right: 0;
color: var(--primary-color);

View File

@ -56,7 +56,7 @@ export class ZHAClusterCommands extends LitElement {
protected render(): TemplateResult | void {
return html`
<ha-config-section .isWide="${this.isWide}">
<div class="sectionHeader" slot="header">
<div class="header" slot="header">
<span>
${this.hass!.localize(
"ui.panel.config.zha.cluster_commands.header"
@ -81,7 +81,7 @@ export class ZHAClusterCommands extends LitElement {
label="${this.hass!.localize(
"ui.panel.config.zha.cluster_commands.commands_of_cluster"
)}"
class="flex"
class="menu"
>
<paper-listbox
slot="dropdown-content"
@ -203,12 +203,8 @@ export class ZHAClusterCommands extends LitElement {
return [
haStyle,
css`
.flex {
-ms-flex: 1 1 0.000000001px;
-webkit-flex: 1;
flex: 1;
-webkit-flex-basis: 0.000000001px;
flex-basis: 0.000000001px;
.menu {
width: 100%;
}
.content {
@ -225,14 +221,6 @@ export class ZHAClusterCommands extends LitElement {
}
.command-picker {
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-ms-flex-direction: row;
-webkit-flex-direction: row;
flex-direction: row;
-ms-flex-align: center;
-webkit-align-items: center;
align-items: center;
padding-left: 28px;
padding-right: 28px;
@ -245,10 +233,6 @@ export class ZHAClusterCommands extends LitElement {
padding-bottom: 10px;
}
.sectionHeader {
position: relative;
}
.help-text {
color: grey;
padding-left: 28px;
@ -261,8 +245,12 @@ export class ZHAClusterCommands extends LitElement {
padding: 16px;
}
.header {
flex-grow: 1;
}
.toggle-help-icon {
position: absolute;
float: right;
top: -6px;
right: 0;
color: var(--primary-color);

View File

@ -3,6 +3,7 @@ import "../../../components/ha-service-description";
import "../../../components/ha-card";
import "../ha-config-section";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
@ -41,8 +42,8 @@ const computeClusterKey = (cluster: Cluster): string => {
export class ZHAClusters extends LitElement {
@property() public hass?: HomeAssistant;
@property() public isWide?: boolean;
@property() public showHelp = false;
@property() public selectedDevice?: ZHADevice;
@property() public showHelp = false;
@property() private _selectedClusterIndex = -1;
@property() private _clusters: Cluster[] = [];
@ -60,33 +61,54 @@ export class ZHAClusters extends LitElement {
protected render(): TemplateResult | void {
return html`
<div class="node-picker">
<paper-dropdown-menu
label="${this.hass!.localize("ui.panel.config.zha.common.clusters")}"
class="flex"
>
<paper-listbox
slot="dropdown-content"
.selected="${this._selectedClusterIndex}"
@iron-select="${this._selectedClusterChanged}"
<ha-config-section .isWide="${this.isWide}">
<div class="header" slot="header">
<span>
${this.hass!.localize("ui.panel.config.zha.clusters.header")}
</span>
<paper-icon-button
class="toggle-help-icon"
@click="${this._onHelpTap}"
icon="hass:help-circle"
>
${this._clusters.map(
(entry) => html`
<paper-item>${computeClusterKey(entry)}</paper-item>
`
)}
</paper-listbox>
</paper-dropdown-menu>
</div>
${this.showHelp
? html`
<div class="help-text">
${this.hass!.localize(
"ui.panel.config.zha.clusters.help_cluster_dropdown"
</paper-icon-button>
</div>
<span slot="introduction">
${this.hass!.localize("ui.panel.config.zha.clusters.introduction")}
</span>
<ha-card class="content">
<div class="node-picker">
<paper-dropdown-menu
.label=${this.hass!.localize(
"ui.panel.config.zha.common.clusters"
)}
</div>
`
: ""}
class="menu"
>
<paper-listbox
slot="dropdown-content"
.selected="${this._selectedClusterIndex}"
@iron-select="${this._selectedClusterChanged}"
>
${this._clusters.map(
(entry) => html`
<paper-item>${computeClusterKey(entry)}</paper-item>
`
)}
</paper-listbox>
</paper-dropdown-menu>
</div>
${this.showHelp
? html`
<div class="help-text">
${this.hass!.localize(
"ui.panel.config.zha.clusters.help_cluster_dropdown"
)}
</div>
`
: ""}
</ha-card>
</ha-config-section>
`;
}
@ -109,32 +131,49 @@ export class ZHAClusters extends LitElement {
});
}
private _onHelpTap(): void {
this.showHelp = !this.showHelp;
}
static get styles(): CSSResult[] {
return [
haStyle,
css`
.flex {
-ms-flex: 1 1 0.000000001px;
-webkit-flex: 1;
flex: 1;
-webkit-flex-basis: 0.000000001px;
flex-basis: 0.000000001px;
.menu {
width: 100%;
}
.content {
margin-top: 24px;
}
.header {
flex-grow: 1;
}
ha-card {
margin: 0 auto;
max-width: 600px;
}
.node-picker {
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-ms-flex-direction: row;
-webkit-flex-direction: row;
flex-direction: row;
-ms-flex-align: center;
-webkit-align-items: center;
align-items: center;
padding-left: 28px;
padding-right: 28px;
padding-bottom: 10px;
}
.toggle-help-icon {
float: right;
top: -6px;
right: 0;
color: var(--primary-color);
}
[hidden] {
display: none;
}
.help-text {
color: grey;
padding-left: 28px;

View File

@ -1,29 +1,34 @@
import "../../../layouts/hass-loading-screen";
import { customElement, property } from "lit-element";
import {
HassRouterPage,
RouterOptions,
} from "../../../layouts/hass-router-page";
import { customElement, property } from "lit-element";
import { HomeAssistant } from "../../../types";
@customElement("zha-config-panel")
class ZHAConfigPanel extends HassRouterPage {
@customElement("zha-config-dashboard-router")
class ZHAConfigDashboardRouter extends HassRouterPage {
@property() public hass!: HomeAssistant;
@property() public isWide!: boolean;
@property() public narrow!: boolean;
protected routerOptions: RouterOptions = {
defaultPage: "configuration",
defaultPage: "dashboard",
cacheAll: true,
preloadAll: true,
showLoading: true,
routes: {
configuration: {
tag: "ha-config-zha",
dashboard: {
tag: "zha-config-dashboard",
load: () =>
import(
/* webpackChunkName: "zha-configuration-page" */ "./ha-config-zha"
/* webpackChunkName: "zha-config-dashboard" */ "./zha-config-dashboard"
),
},
device: {
tag: "zha-device-page",
load: () =>
import(
/* webpackChunkName: "zha-devices-page" */ "./zha-device-page"
),
},
add: {
@ -62,12 +67,14 @@ class ZHAConfigPanel extends HassRouterPage {
el.narrow = this.narrow;
if (this._currentPage === "group") {
el.groupId = this.routeTail.path.substr(1);
} else if (this._currentPage === "device") {
el.ieee = this.routeTail.path.substr(1);
}
}
}
declare global {
interface HTMLElementTagNameMap {
"zha-config-panel": ZHAConfigPanel;
"zha-config-dashboard-router": ZHAConfigDashboardRouter;
}
}

View File

@ -0,0 +1,175 @@
import {
LitElement,
TemplateResult,
html,
CSSResultArray,
css,
customElement,
property,
PropertyValues,
} from "lit-element";
import "@polymer/paper-item/paper-item-body";
import "@polymer/paper-item/paper-item";
import "../../../components/ha-card";
import "../../../components/ha-icon-next";
import "../../../layouts/hass-subpage";
import "../ha-config-section";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant, Route } from "../../../types";
import { fetchDevices, ZHADevice } from "../../../data/zha";
import { sortZHADevices, formatAsPaddedHex } from "./functions";
import memoizeOne from "memoize-one";
import {
DataTableColumnContainer,
RowClickedEvent,
} from "../../../components/data-table/ha-data-table";
import { navigate } from "../../../common/navigate";
export interface DeviceRowData extends ZHADevice {
device?: DeviceRowData;
}
@customElement("zha-config-dashboard")
class ZHAConfigDashboard extends LitElement {
@property() public hass!: HomeAssistant;
@property() public route!: Route;
@property() public narrow!: boolean;
@property() public isWide!: boolean;
@property() private _devices: ZHADevice[] = [];
private pages: string[] = ["add", "groups"];
private _firstUpdatedCalled: boolean = false;
private _memoizeDevices = memoizeOne((devices: ZHADevice[]) => {
let outputDevices: DeviceRowData[] = devices;
outputDevices = outputDevices.map((device) => {
return {
...device,
name: device.user_given_name ? device.user_given_name : device.name,
nwk: formatAsPaddedHex(device.nwk),
id: device.ieee,
};
});
return outputDevices;
});
private _columns = memoizeOne(
(narrow: boolean): DataTableColumnContainer =>
narrow
? {
name: {
title: "Devices",
sortable: true,
filterable: true,
direction: "asc",
},
}
: {
name: {
title: "Name",
sortable: true,
filterable: true,
direction: "asc",
},
nwk: {
title: "Nwk",
sortable: true,
filterable: true,
},
ieee: {
title: "IEEE",
sortable: true,
filterable: true,
},
}
);
public connectedCallback(): void {
super.connectedCallback();
if (this.hass && this._firstUpdatedCalled) {
this._fetchDevices();
}
}
protected firstUpdated(changedProperties: PropertyValues): void {
super.firstUpdated(changedProperties);
if (this.hass) {
this._fetchDevices();
}
this._firstUpdatedCalled = true;
}
protected render(): TemplateResult | void {
return html`
<hass-subpage .header=${this.hass.localize("ui.panel.config.zha.title")}>
<ha-config-section .narrow=${this.narrow} .isWide=${this.isWide}>
<div slot="header">
${this.hass.localize("ui.panel.config.zha.header")}
</div>
<div slot="introduction">
${this.hass.localize("ui.panel.config.zha.introduction")}
</div>
<ha-card>
${this.pages.map((page) => {
return html`
<a href=${`/config/zha/${page}`}>
<paper-item>
<paper-item-body two-line="">
${this.hass.localize(
`ui.panel.config.zha.${page}.caption`
)}
<div secondary>
${this.hass.localize(
`ui.panel.config.zha.${page}.description`
)}
</div>
</paper-item-body>
<ha-icon-next></ha-icon-next>
</paper-item>
</a>
`;
})}
</ha-card>
<ha-card>
<ha-data-table
.columns=${this._columns(this.narrow)}
.data=${this._memoizeDevices(this._devices)}
@row-click=${this._handleDeviceClicked}
></ha-data-table>
</ha-card>
</ha-config-section>
</hass-subpage>
`;
}
private async _fetchDevices() {
this._devices = (await fetchDevices(this.hass!)).sort(sortZHADevices);
}
private async _handleDeviceClicked(ev: CustomEvent) {
const deviceId = (ev.detail as RowClickedEvent).id;
navigate(this, `/config/zha/device/${deviceId}`);
}
static get styles(): CSSResultArray {
return [
haStyle,
css`
a {
text-decoration: none;
color: var(--primary-text-color);
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"zha-config-dashboard": ZHAConfigDashboard;
}
}

View File

@ -233,7 +233,7 @@ class ZHADeviceCard extends LitElement {
label="${this.hass!.localize(
"ui.dialogs.zha_device_info.zha_device_card.area_picker_label"
)}"
class="flex"
class="menu"
>
<paper-listbox
slot="dropdown-content"
@ -385,7 +385,7 @@ class ZHADeviceCard extends LitElement {
css`
:host(:not([narrow])) .device-entities {
max-height: 225px;
overflow: auto;
overflow-y: auto;
display: flex;
flex-wrap: wrap;
padding: 4px;
@ -394,7 +394,7 @@ class ZHADeviceCard extends LitElement {
ha-card {
flex: 1 0 100%;
padding-bottom: 10px;
min-width: 425px;
min-width: 300px;
}
.device {
width: 30%;
@ -411,25 +411,35 @@ class ZHADeviceCard extends LitElement {
}
.manuf,
.zha-info,
.name {
text-overflow: ellipsis;
}
.entity-id {
text-overflow: ellipsis;
color: var(--secondary-text-color);
}
.info {
margin-left: 16px;
}
dl {
display: grid;
grid-template-columns: 125px 1fr;
display: flex;
flex-wrap: wrap;
width: 100%;
}
dl dt {
display: inline-block;
width: 30%;
padding-left: 12px;
float: left;
text-align: left;
}
dl dd {
max-width: 200px;
width: 60%;
overflow-wrap: break-word;
margin-inline-start: 20px;
}
paper-icon-item {
overflow-x: hidden;
cursor: pointer;
padding-top: 4px;
padding-bottom: 4px;
@ -443,22 +453,10 @@ class ZHADeviceCard extends LitElement {
color: grey;
padding: 16px;
}
.flex {
-ms-flex: 1 1 0.000000001px;
-webkit-flex: 1;
flex: 1;
-webkit-flex-basis: 0.000000001px;
flex-basis: 0.000000001px;
.menu {
width: 100%;
}
.node-picker {
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-ms-flex-direction: row;
-webkit-flex-direction: row;
flex-direction: row;
-ms-flex-align: center;
-webkit-align-items: center;
align-items: center;
padding-left: 28px;
padding-right: 28px;

View File

@ -1,11 +1,10 @@
import "../../../components/ha-paper-icon-button-arrow-prev";
import "../../../layouts/hass-subpage";
import "../../../components/ha-paper-icon-button-arrow-prev";
import "./zha-binding";
import "./zha-cluster-attributes";
import "./zha-cluster-commands";
import "./zha-network";
import "./zha-clusters";
import "./zha-node";
import "./zha-groups-tile";
import "@polymer/paper-icon-button/paper-icon-button";
import {
@ -15,75 +14,82 @@ import {
property,
PropertyValues,
TemplateResult,
customElement,
css,
} from "lit-element";
import { HASSDomEvent } from "../../../common/dom/fire_event";
import { Cluster, fetchBindableDevices, ZHADevice } from "../../../data/zha";
import {
Cluster,
fetchBindableDevices,
ZHADevice,
fetchZHADevice,
} from "../../../data/zha";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import { sortZHADevices } from "./functions";
import { ZHAClusterSelectedParams, ZHADeviceSelectedParams } from "./types";
import { ZHAClusterSelectedParams } from "./types";
export class HaConfigZha extends LitElement {
@customElement("zha-device-page")
export class ZHADevicePage extends LitElement {
@property() public hass?: HomeAssistant;
@property() public isWide?: boolean;
@property() private _selectedDevice?: ZHADevice;
@property() public ieee?: string;
@property() public device?: ZHADevice;
@property() private _selectedCluster?: Cluster;
@property() private _bindableDevices: ZHADevice[] = [];
protected updated(changedProperties: PropertyValues): void {
if (changedProperties.has("_selectedDevice")) {
this._fetchBindableDevices();
if (changedProperties.has("ieee")) {
this._fetchData();
}
super.update(changedProperties);
}
protected render(): TemplateResult | void {
return html`
<hass-subpage header="Zigbee Home Automation">
<zha-network
.isWide="${this.isWide}"
.hass="${this.hass}"
></zha-network>
<zha-groups-tile
.isWide="${this.isWide}"
.hass="${this.hass}"
></zha-groups-tile>
<hass-subpage
.header=${this.hass!.localize("ui.panel.config.zha.devices.header")}
>
<zha-node
.isWide="${this.isWide}"
.hass="${this.hass}"
@zha-cluster-selected="${this._onClusterSelected}"
@zha-node-selected="${this._onDeviceSelected}"
.device=${this.device}
></zha-node>
<zha-clusters
.hass="${this.hass}"
.isWide="${this.isWide}"
.selectedDevice="${this.device}"
@zha-cluster-selected="${this._onClusterSelected}"
></zha-clusters>
${this._selectedCluster
? html`
<zha-cluster-attributes
.isWide="${this.isWide}"
.hass="${this.hass}"
.selectedNode="${this._selectedDevice}"
.selectedNode="${this.device}"
.selectedCluster="${this._selectedCluster}"
></zha-cluster-attributes>
<zha-cluster-commands
.isWide="${this.isWide}"
.hass="${this.hass}"
.selectedNode="${this._selectedDevice}"
.selectedNode="${this.device}"
.selectedCluster="${this._selectedCluster}"
></zha-cluster-commands>
`
: ""}
${this._selectedDevice && this._bindableDevices.length > 0
${this._bindableDevices.length > 0
? html`
<zha-binding-control
.isWide="${this.isWide}"
.hass="${this.hass}"
.selectedDevice="${this._selectedDevice}"
.selectedDevice="${this.device}"
.bindableDevices="${this._bindableDevices}"
></zha-binding-control>
`
: ""}
<div class="spacer" />
</hass-subpage>
`;
}
@ -94,30 +100,29 @@ export class HaConfigZha extends LitElement {
this._selectedCluster = selectedClusterEvent.detail.cluster;
}
private _onDeviceSelected(
selectedNodeEvent: HASSDomEvent<ZHADeviceSelectedParams>
): void {
this._selectedDevice = selectedNodeEvent.detail.node;
this._selectedCluster = undefined;
}
private async _fetchBindableDevices(): Promise<void> {
if (this._selectedDevice && this.hass) {
private async _fetchData(): Promise<void> {
if (this.ieee && this.hass) {
this.device = await fetchZHADevice(this.hass, this.ieee);
this._bindableDevices = (
await fetchBindableDevices(this.hass, this._selectedDevice!.ieee)
await fetchBindableDevices(this.hass, this.ieee)
).sort(sortZHADevices);
}
}
static get styles(): CSSResult[] {
return [haStyle];
return [
haStyle,
css`
.spacer {
height: 50px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-config-zha": HaConfigZha;
"zha-device-page": ZHADevicePage;
}
}
customElements.define("ha-config-zha", HaConfigZha);

View File

@ -1,4 +1,3 @@
import "../../../layouts/hass-subpage";
import "./zha-groups-data-table";
import {
@ -19,6 +18,7 @@ import "@material/mwc-button";
import "@polymer/paper-spinner/paper-spinner";
import "@polymer/paper-icon-button/paper-icon-button";
import { navigate } from "../../../common/navigate";
import "../../../layouts/hass-subpage";
@customElement("zha-groups-dashboard")
export class ZHAGroupsDashboard extends LitElement {
@ -47,9 +47,9 @@ export class ZHAGroupsDashboard extends LitElement {
protected render(): TemplateResult {
return html`
<hass-subpage
header=${this.hass.localize(
"ui.panel.config.zha.groups.zha_zigbee_groups"
)}
.header="${this.hass!.localize(
"ui.panel.config.zha.groups.groups-header"
)}"
>
<paper-icon-button
slot="toolbar-icon"

View File

@ -1,108 +0,0 @@
import "../../../components/ha-card";
import "../ha-config-section";
import "@material/mwc-button";
import "@polymer/paper-icon-button/paper-icon-button";
import {
css,
CSSResult,
html,
LitElement,
TemplateResult,
property,
customElement,
} from "lit-element";
import { navigate } from "../../../common/navigate";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
@customElement("zha-groups-tile")
export class ZHAGroupsTile extends LitElement {
@property() public hass?: HomeAssistant;
@property() public isWide?: boolean;
@property() private _showHelp = false;
protected render(): TemplateResult | void {
return html`
<ha-config-section .isWide="${this.isWide}">
<div style="position: relative" slot="header">
<span>
${this.hass!.localize("ui.panel.config.zha.groups.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.groups.introduction")}
</span>
<ha-card class="content">
<div class="card-actions">
<mwc-button @click=${this._onManageGroupsClick}>
${this.hass!.localize("ui.panel.config.zha.groups.manage_groups")}
</mwc-button>
</div>
</ha-card>
</ha-config-section>
`;
}
private _onHelpTap(): void {
this._showHelp = !this._showHelp;
}
private _onManageGroupsClick() {
navigate(this, "groups");
}
static get styles(): CSSResult[] {
return [
haStyle,
css`
.content {
margin-top: 24px;
}
ha-card {
margin: 0 auto;
max-width: 600px;
}
.card-actions.warning ha-call-service-button {
color: var(--google-red-500);
}
.toggle-help-icon {
position: absolute;
top: -6px;
right: 0;
color: var(--primary-color);
}
ha-service-description {
display: block;
color: grey;
}
[hidden] {
display: none;
}
.help-text2 {
color: grey;
padding: 16px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"zha-groups-tile": ZHAGroupsTile;
}
}

View File

@ -1,124 +0,0 @@
import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-service-description";
import "../../../components/ha-card";
import "../ha-config-section";
import "@material/mwc-button";
import "@polymer/paper-icon-button/paper-icon-button";
import {
css,
CSSResult,
html,
LitElement,
TemplateResult,
property,
} from "lit-element";
import { navigate } from "../../../common/navigate";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
export class ZHANetwork extends LitElement {
@property() public hass?: HomeAssistant;
@property() public isWide?: boolean;
@property() private _showHelp = false;
protected render(): TemplateResult | void {
return html`
<ha-config-section .isWide="${this.isWide}">
<div style="position: relative" slot="header">
<span>
${this.hass!.localize(
"ui.panel.config.zha.network_management.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.network_management.introduction"
)}
</span>
<ha-card class="content">
<div class="card-actions">
<mwc-button @click=${this._onAddDevicesClick}>
${this.hass!.localize("ui.panel.config.zha.common.add_devices")}
</mwc-button>
${this._showHelp
? html`
<ha-service-description
.hass="${this.hass}"
domain="zha"
service="permit"
class="help-text2"
/>
`
: ""}
</div>
</ha-card>
</ha-config-section>
`;
}
private _onHelpTap(): void {
this._showHelp = !this._showHelp;
}
private _onAddDevicesClick() {
navigate(this, "add");
}
static get styles(): CSSResult[] {
return [
haStyle,
css`
.content {
margin-top: 24px;
}
ha-card {
margin: 0 auto;
max-width: 600px;
}
.card-actions.warning ha-call-service-button {
color: var(--google-red-500);
}
.toggle-help-icon {
position: absolute;
top: -6px;
right: 0;
color: var(--primary-color);
}
ha-service-description {
display: block;
color: grey;
}
[hidden] {
display: none;
}
.help-text2 {
color: grey;
padding: 16px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"zha-network": ZHANetwork;
}
}
customElements.define("zha-network", ZHANetwork);

View File

@ -2,13 +2,8 @@ import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-service-description";
import "../../../components/ha-card";
import "../ha-config-section";
import "./zha-clusters";
import "./zha-device-card";
import "@material/mwc-button";
import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import {
css,
@ -20,40 +15,22 @@ import {
TemplateResult,
} from "lit-element";
import { fireEvent } from "../../../common/dom/fire_event";
import { fetchDevices, ZHADevice } from "../../../data/zha";
import { ZHADevice } from "../../../data/zha";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import { sortZHADevices } from "./functions";
import { ItemSelectedEvent, ZHADeviceRemovedEvent } from "./types";
declare global {
// for fire event
interface HASSDomEvents {
"zha-node-selected": {
node?: ZHADevice;
};
}
}
import { navigate } from "../../../common/navigate";
@customElement("zha-node")
export class ZHANode extends LitElement {
@property() public hass?: HomeAssistant;
@property() public isWide?: boolean;
@property() public device?: ZHADevice;
@property() private _showHelp: boolean = false;
@property() private _selectedDeviceIndex: number = -1;
@property() private _selectedDevice?: ZHADevice;
@property() private _nodes: ZHADevice[] = [];
public connectedCallback(): void {
super.connectedCallback();
this._fetchDevices();
}
protected render(): TemplateResult | void {
return html`
<ha-config-section .isWide="${this.isWide}">
<div class="sectionHeader" slot="header">
<div class="header" slot="header">
<span
>${this.hass!.localize(
"ui.panel.config.zha.node_management.header"
@ -78,115 +55,37 @@ export class ZHANode extends LitElement {
"ui.panel.config.zha.node_management.hint_wakeup"
)}
</span>
<ha-card class="content">
<div class="node-picker">
<paper-dropdown-menu
label="${this.hass!.localize(
"ui.panel.config.zha.common.devices"
)}"
class="flex"
id="zha-device-selector"
>
<paper-listbox
slot="dropdown-content"
@iron-select="${this._selectedDeviceChanged}"
.selected="${this._selectedDeviceIndex}"
>
${this._nodes.map(
(entry) => html`
<paper-item
>${entry.user_given_name
? entry.user_given_name
: entry.name}</paper-item
>
`
)}
</paper-listbox>
</paper-dropdown-menu>
</div>
${this._showHelp
? html`
<div class="help-text">
${this.hass!.localize(
"ui.panel.config.zha.node_management.help_node_dropdown"
)}
</div>
`
: ""}
${this._selectedDeviceIndex !== -1
? html`
<zha-device-card
class="card"
.hass=${this.hass}
.device=${this._selectedDevice}
.narrow=${!this.isWide}
.showHelp=${this._showHelp}
showActions
@zha-device-removed=${this._onDeviceRemoved}
></zha-device-card>
`
: ""}
${this._selectedDevice ? this._renderClusters() : ""}
</ha-card>
<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>
</ha-config-section>
`;
}
private _renderClusters(): TemplateResult {
return html`
<zha-clusters
.hass="${this.hass}"
.selectedDevice="${this._selectedDevice}"
.showHelp="${this._showHelp}"
></zha-clusters>
`;
}
private _onHelpTap(): void {
this._showHelp = !this._showHelp;
}
private _selectedDeviceChanged(event: ItemSelectedEvent): void {
this._selectedDeviceIndex = event!.target!.selected;
this._selectedDevice = this._nodes[this._selectedDeviceIndex];
fireEvent(this, "zha-node-selected", { node: this._selectedDevice });
}
private async _fetchDevices() {
this._nodes = (await fetchDevices(this.hass!)).sort(sortZHADevices);
}
private _onDeviceRemoved(event: ZHADeviceRemovedEvent): void {
this._selectedDeviceIndex = -1;
this._nodes.splice(this._nodes.indexOf(event.detail!.device!), 1);
this._selectedDevice = undefined;
fireEvent(this, "zha-node-selected", { node: this._selectedDevice });
private _onDeviceRemoved(): void {
this.device = undefined;
navigate(this, `/config/zha`, true);
}
static get styles(): CSSResult[] {
return [
haStyle,
css`
.flex {
-ms-flex: 1 1 0.000000001px;
-webkit-flex: 1;
flex: 1;
-webkit-flex-basis: 0.000000001px;
flex-basis: 0.000000001px;
}
.content {
margin-top: 24px;
}
.node-info {
margin-left: 16px;
}
.sectionHeader {
position: relative;
}
.help-text {
color: grey;
padding-left: 28px;
@ -199,30 +98,11 @@ export class ZHANode extends LitElement {
max-width: 600px;
}
.node-picker {
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-ms-flex-direction: row;
-webkit-flex-direction: row;
flex-direction: row;
-ms-flex-align: center;
-webkit-align-items: center;
align-items: center;
padding-left: 28px;
padding-right: 28px;
padding-bottom: 10px;
}
.card {
margin-top: 24px;
box-sizing: border-box;
display: flex;
flex: 1 0 300px;
min-width: 0;
max-width: 600px;
padding-left: 28px;
padding-right: 28px;
padding-bottom: 10px;
flex: 1;
word-wrap: break-word;
}
@ -235,8 +115,12 @@ export class ZHANode extends LitElement {
display: none;
}
.header {
flex-grow: 1;
}
.toggle-help-icon {
position: absolute;
float: right;
top: 6px;
right: 0;
color: var(--primary-color);

View File

@ -1424,6 +1424,9 @@
},
"zha": {
"caption": "ZHA",
"title": "Zigbee Home Automation",
"header": "Configure Zigbee Home Automation",
"introduction": "Here it is possible to configure the ZHA component. Not everything is possible to configure from the UI yet, but we're working on it.",
"description": "Zigbee Home Automation network management",
"common": {
"add_devices": "Add Devices",
@ -1442,6 +1445,13 @@
"header": "Network Management",
"introduction": "Commands that affect the entire network"
},
"add": {
"caption": "Add Devices",
"description": "Add devices to the Zigbee network"
},
"devices": {
"header": "Zigbee Home Automation - Device"
},
"node_management": {
"header": "Device Management",
"introduction": "Run ZHA commands that affect a single device. Pick a device to see a list of available commands.",
@ -1450,7 +1460,9 @@
"help_node_dropdown": "Select a device to view per-device options."
},
"clusters": {
"help_cluster_dropdown": "Select a cluster to view attributes and commands."
"header": "Clusters",
"help_cluster_dropdown": "Select a cluster to view attributes and commands.",
"introduction": "Clusters are the building blocks for Zigbee functionality. They seperate functionality into logical units. There are client and server types and that are comprised of attributes and commands."
},
"cluster_attributes": {
"header": "Cluster Attributes",
@ -1470,12 +1482,16 @@
"help_command_dropdown": "Select a command to interact with."
},
"groups": {
"caption": "Groups",
"description": "Create and modify Zigbee groups",
"zha_zigbee_groups": "ZHA Zigbee Groups",
"manage_groups": "Manage Zigbee Groups",
"groups": "Groups",
"group_id": "Group ID",
"members": "Members",
"header": "Zigbee Group Management",
"header": "Zigbee Home Automation - Group Management",
"groups-header": "Zigbee Home Automation - Group Management",
"group-header": "Zigbee Home Automation - Group Details",
"introduction": "Create and modify zigbee groups",
"remove_groups": "Remove Groups",
"removing_groups": "Removing Groups",
@ -1488,7 +1504,7 @@
"removing_members": "Removing Members",
"create_group_details": "Enter the required details to create a new zigbee group",
"group_name_placeholder": "Group Name",
"create_group": "Create New ZHA Zigbee Group",
"create_group": "Zigbee Home Automation - Create Group",
"create": "Create Group",
"creating_group": "Creating Group"
}