mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-02 14:07:55 +00:00
Add group editing to the ZHA config panel (#4382)
* add group editing * Update src/panels/config/zha/zha-devices-data-table.ts Co-Authored-By: Bram Kragten <mail@bramkragten.nl> * Update src/panels/config/zha/zha-group-page.ts Co-Authored-By: Bram Kragten <mail@bramkragten.nl> * Update src/panels/config/zha/zha-devices-data-table.ts Co-Authored-By: Bram Kragten <mail@bramkragten.nl> * Update src/panels/config/zha/zha-group-page.ts Co-Authored-By: Bram Kragten <mail@bramkragten.nl> * Update src/panels/config/zha/zha-group-page.ts Co-Authored-By: Bram Kragten <mail@bramkragten.nl> * Update src/panels/config/zha/zha-group-page.ts Co-Authored-By: Bram Kragten <mail@bramkragten.nl> * review comments Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
parent
711d51c022
commit
bdd18775c3
@ -182,3 +182,32 @@ export const fetchGroup = (
|
|||||||
type: "zha/group",
|
type: "zha/group",
|
||||||
group_id: groupId,
|
group_id: groupId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const fetchGroupableDevices = (
|
||||||
|
hass: HomeAssistant
|
||||||
|
): Promise<ZHADevice[]> =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "zha/devices/groupable",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const addMembersToGroup = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
groupId: number,
|
||||||
|
membersToAdd: string[]
|
||||||
|
): Promise<ZHAGroup> =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "zha/group/members/add",
|
||||||
|
group_id: groupId,
|
||||||
|
members: membersToAdd,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const removeMembersFromGroup = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
groupId: number,
|
||||||
|
membersToRemove: string[]
|
||||||
|
): Promise<ZHAGroup> =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "zha/group/members/remove",
|
||||||
|
group_id: groupId,
|
||||||
|
members: membersToRemove,
|
||||||
|
});
|
||||||
|
110
src/panels/config/zha/zha-devices-data-table.ts
Normal file
110
src/panels/config/zha/zha-devices-data-table.ts
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
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 { ZHADevice } from "../../../data/zha";
|
||||||
|
import { showZHADeviceInfoDialog } from "../../../dialogs/zha-device-info-dialog/show-dialog-zha-device-info";
|
||||||
|
|
||||||
|
export interface DeviceRowData extends ZHADevice {
|
||||||
|
device?: DeviceRowData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement("zha-devices-data-table")
|
||||||
|
export class ZHADevicesDataTable extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public narrow = false;
|
||||||
|
@property({ type: Boolean }) public selectable = false;
|
||||||
|
@property() public devices: ZHADevice[] = [];
|
||||||
|
|
||||||
|
private _devices = memoizeOne((devices: ZHADevice[]) => {
|
||||||
|
let outputDevices: DeviceRowData[] = devices;
|
||||||
|
|
||||||
|
outputDevices = outputDevices.map((device) => {
|
||||||
|
return {
|
||||||
|
...device,
|
||||||
|
name: device.user_given_name || device.name,
|
||||||
|
model: device.model,
|
||||||
|
manufacturer: device.manufacturer,
|
||||||
|
id: device.ieee,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return outputDevices;
|
||||||
|
});
|
||||||
|
|
||||||
|
private _columns = memoizeOne(
|
||||||
|
(narrow: boolean): DataTableColumnContainer =>
|
||||||
|
narrow
|
||||||
|
? {
|
||||||
|
name: {
|
||||||
|
title: "Devices",
|
||||||
|
sortable: true,
|
||||||
|
filterable: true,
|
||||||
|
direction: "asc",
|
||||||
|
template: (name) => html`
|
||||||
|
<div @click=${this._handleClicked} style="cursor: pointer;">
|
||||||
|
${name}
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
name: {
|
||||||
|
title: "Name",
|
||||||
|
sortable: true,
|
||||||
|
filterable: true,
|
||||||
|
direction: "asc",
|
||||||
|
template: (name) => html`
|
||||||
|
<div @click=${this._handleClicked} style="cursor: pointer;">
|
||||||
|
${name}
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
manufacturer: {
|
||||||
|
title: "Manufacturer",
|
||||||
|
sortable: true,
|
||||||
|
filterable: true,
|
||||||
|
},
|
||||||
|
model: {
|
||||||
|
title: "Model",
|
||||||
|
sortable: true,
|
||||||
|
filterable: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<ha-data-table
|
||||||
|
.columns=${this._columns(this.narrow)}
|
||||||
|
.data=${this._devices(this.devices)}
|
||||||
|
.selectable=${this.selectable}
|
||||||
|
></ha-data-table>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _handleClicked(ev: CustomEvent) {
|
||||||
|
const ieee = (ev.target as HTMLElement)
|
||||||
|
.closest("tr")!
|
||||||
|
.getAttribute("data-row-id")!;
|
||||||
|
showZHADeviceInfoDialog(this, { ieee });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"zha-devices-data-table": ZHADevicesDataTable;
|
||||||
|
}
|
||||||
|
}
|
@ -19,11 +19,18 @@ import {
|
|||||||
ZHAGroup,
|
ZHAGroup,
|
||||||
fetchGroup,
|
fetchGroup,
|
||||||
removeGroups,
|
removeGroups,
|
||||||
|
fetchGroupableDevices,
|
||||||
|
addMembersToGroup,
|
||||||
|
removeMembersFromGroup,
|
||||||
} from "../../../data/zha";
|
} from "../../../data/zha";
|
||||||
import { formatAsPaddedHex } from "./functions";
|
import { formatAsPaddedHex } from "./functions";
|
||||||
import "./zha-device-card";
|
import "./zha-device-card";
|
||||||
|
import "./zha-devices-data-table";
|
||||||
import { navigate } from "../../../common/navigate";
|
import { navigate } from "../../../common/navigate";
|
||||||
import "@polymer/paper-icon-button/paper-icon-button";
|
import "@polymer/paper-icon-button/paper-icon-button";
|
||||||
|
import "@polymer/paper-spinner/paper-spinner";
|
||||||
|
import "@material/mwc-button";
|
||||||
|
import { SelectionChangedEvent } from "../../../components/data-table/ha-data-table";
|
||||||
|
|
||||||
@customElement("zha-group-page")
|
@customElement("zha-group-page")
|
||||||
export class ZHAGroupPage extends LitElement {
|
export class ZHAGroupPage extends LitElement {
|
||||||
@ -32,6 +39,13 @@ export class ZHAGroupPage extends LitElement {
|
|||||||
@property() public groupId!: number;
|
@property() public groupId!: number;
|
||||||
@property() public narrow!: boolean;
|
@property() public narrow!: boolean;
|
||||||
@property() public isWide!: boolean;
|
@property() public isWide!: boolean;
|
||||||
|
@property() public devices: ZHADevice[] = [];
|
||||||
|
@property() private _processingAdd: boolean = false;
|
||||||
|
@property() private _processingRemove: boolean = false;
|
||||||
|
@property() private _filteredDevices: ZHADevice[] = [];
|
||||||
|
@property() private _selectedDevicesToAdd: string[] = [];
|
||||||
|
@property() private _selectedDevicesToRemove: string[] = [];
|
||||||
|
|
||||||
private _firstUpdatedCalled: boolean = false;
|
private _firstUpdatedCalled: boolean = false;
|
||||||
|
|
||||||
private _members = memoizeOne(
|
private _members = memoizeOne(
|
||||||
@ -45,6 +59,16 @@ export class ZHAGroupPage extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public disconnectedCallback(): void {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
this._processingAdd = false;
|
||||||
|
this._processingRemove = false;
|
||||||
|
this._selectedDevicesToRemove = [];
|
||||||
|
this._selectedDevicesToAdd = [];
|
||||||
|
this.devices = [];
|
||||||
|
this._filteredDevices = [];
|
||||||
|
}
|
||||||
|
|
||||||
protected firstUpdated(changedProperties: PropertyValues): void {
|
protected firstUpdated(changedProperties: PropertyValues): void {
|
||||||
super.firstUpdated(changedProperties);
|
super.firstUpdated(changedProperties);
|
||||||
if (this.hass) {
|
if (this.hass) {
|
||||||
@ -105,6 +129,77 @@ export class ZHAGroupPage extends LitElement {
|
|||||||
This group has no members
|
This group has no members
|
||||||
</p>
|
</p>
|
||||||
`}
|
`}
|
||||||
|
${members.length
|
||||||
|
? html`
|
||||||
|
<div class="header">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.zha.groups.remove_members"
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<zha-devices-data-table
|
||||||
|
.hass=${this.hass}
|
||||||
|
.devices=${members}
|
||||||
|
.narrow=${this.narrow}
|
||||||
|
selectable
|
||||||
|
@selection-changed=${this._handleRemoveSelectionChanged}
|
||||||
|
class="table"
|
||||||
|
>
|
||||||
|
</zha-devices-data-table>
|
||||||
|
|
||||||
|
<div class="paper-dialog-buttons">
|
||||||
|
<mwc-button
|
||||||
|
.disabled="${!this._selectedDevicesToRemove.length ||
|
||||||
|
this._processingRemove}"
|
||||||
|
@click="${this._removeMembersFromGroup}"
|
||||||
|
class="button"
|
||||||
|
>
|
||||||
|
<paper-spinner
|
||||||
|
?active="${this._processingRemove}"
|
||||||
|
alt=${this.hass.localize(
|
||||||
|
"ui.panel.config.zha.groups.removing_members"
|
||||||
|
)}
|
||||||
|
></paper-spinner>
|
||||||
|
${this.hass!.localize(
|
||||||
|
"ui.panel.config.zha.groups.remove_members"
|
||||||
|
)}</mwc-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: html``}
|
||||||
|
|
||||||
|
<div class="header">
|
||||||
|
${this.hass.localize("ui.panel.config.zha.groups.add_members")}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<zha-devices-data-table
|
||||||
|
.hass=${this.hass}
|
||||||
|
.devices=${this._filteredDevices}
|
||||||
|
.narrow=${this.narrow}
|
||||||
|
selectable
|
||||||
|
@selection-changed=${this._handleAddSelectionChanged}
|
||||||
|
class="table"
|
||||||
|
>
|
||||||
|
</zha-devices-data-table>
|
||||||
|
|
||||||
|
<div class="paper-dialog-buttons">
|
||||||
|
<mwc-button
|
||||||
|
.disabled="${!this._selectedDevicesToAdd.length ||
|
||||||
|
this._processingAdd}"
|
||||||
|
@click="${this._addMembersToGroup}"
|
||||||
|
class="button"
|
||||||
|
>
|
||||||
|
<paper-spinner
|
||||||
|
?active="${this._processingAdd}"
|
||||||
|
alt=${this.hass.localize(
|
||||||
|
"ui.panel.config.zha.groups.adding_members"
|
||||||
|
)}
|
||||||
|
></paper-spinner>
|
||||||
|
${this.hass!.localize(
|
||||||
|
"ui.panel.config.zha.groups.add_members"
|
||||||
|
)}</mwc-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</ha-config-section>
|
</ha-config-section>
|
||||||
</hass-subpage>
|
</hass-subpage>
|
||||||
`;
|
`;
|
||||||
@ -114,6 +209,68 @@ export class ZHAGroupPage extends LitElement {
|
|||||||
if (this.groupId !== null && this.groupId !== undefined) {
|
if (this.groupId !== null && this.groupId !== undefined) {
|
||||||
this.group = await fetchGroup(this.hass!, this.groupId);
|
this.group = await fetchGroup(this.hass!, this.groupId);
|
||||||
}
|
}
|
||||||
|
this.devices = await fetchGroupableDevices(this.hass!);
|
||||||
|
// filter the groupable devices so we only show devices that aren't already in the group
|
||||||
|
this._filterDevices();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _filterDevices() {
|
||||||
|
// filter the groupable devices so we only show devices that aren't already in the group
|
||||||
|
this._filteredDevices = this.devices.filter((device) => {
|
||||||
|
return !this.group!.members.some((member) => member.ieee === device.ieee);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleAddSelectionChanged(ev: CustomEvent): void {
|
||||||
|
const changedSelection = ev.detail as SelectionChangedEvent;
|
||||||
|
const entity = changedSelection.id;
|
||||||
|
if (changedSelection.selected) {
|
||||||
|
this._selectedDevicesToAdd.push(entity);
|
||||||
|
} else {
|
||||||
|
const index = this._selectedDevicesToAdd.indexOf(entity);
|
||||||
|
if (index !== -1) {
|
||||||
|
this._selectedDevicesToAdd.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._selectedDevicesToAdd = [...this._selectedDevicesToAdd];
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleRemoveSelectionChanged(ev: CustomEvent): void {
|
||||||
|
const changedSelection = ev.detail as SelectionChangedEvent;
|
||||||
|
const entity = changedSelection.id;
|
||||||
|
if (changedSelection.selected) {
|
||||||
|
this._selectedDevicesToRemove.push(entity);
|
||||||
|
} else {
|
||||||
|
const index = this._selectedDevicesToRemove.indexOf(entity);
|
||||||
|
if (index !== -1) {
|
||||||
|
this._selectedDevicesToRemove.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._selectedDevicesToRemove = [...this._selectedDevicesToRemove];
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _addMembersToGroup(): Promise<void> {
|
||||||
|
this._processingAdd = true;
|
||||||
|
this.group = await addMembersToGroup(
|
||||||
|
this.hass,
|
||||||
|
this.groupId,
|
||||||
|
this._selectedDevicesToAdd
|
||||||
|
);
|
||||||
|
this._filterDevices();
|
||||||
|
this._selectedDevicesToAdd = [];
|
||||||
|
this._processingAdd = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _removeMembersFromGroup(): Promise<void> {
|
||||||
|
this._processingRemove = true;
|
||||||
|
this.group = await removeMembersFromGroup(
|
||||||
|
this.hass,
|
||||||
|
this.groupId,
|
||||||
|
this._selectedDevicesToRemove
|
||||||
|
);
|
||||||
|
this._filterDevices();
|
||||||
|
this._selectedDevicesToRemove = [];
|
||||||
|
this._processingRemove = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _deleteGroup(): Promise<void> {
|
private async _deleteGroup(): Promise<void> {
|
||||||
@ -139,6 +296,34 @@ export class ZHAGroupPage extends LitElement {
|
|||||||
ha-config-section *:last-child {
|
ha-config-section *:last-child {
|
||||||
padding-bottom: 24px;
|
padding-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
height: 200px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
mwc-button paper-spinner {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
paper-spinner {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
paper-spinner[active] {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.paper-dialog-buttons {
|
||||||
|
align-items: flex-end;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
.paper-dialog-buttons .warning {
|
||||||
|
--mdc-theme-primary: var(--google-red-500);
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -1450,7 +1450,11 @@
|
|||||||
"removing_groups": "Removing Groups",
|
"removing_groups": "Removing Groups",
|
||||||
"group_info": "Group Information",
|
"group_info": "Group Information",
|
||||||
"group_details": "Here are all the details for the selected Zigbee group.",
|
"group_details": "Here are all the details for the selected Zigbee group.",
|
||||||
"group_not_found": "Group not found!"
|
"group_not_found": "Group not found!",
|
||||||
|
"add_members": "Add Members",
|
||||||
|
"remove_members": "Remove Members",
|
||||||
|
"adding_members": "Adding Members",
|
||||||
|
"removing_members": "Removing Members"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"zwave": {
|
"zwave": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user