ha-frontend/src/panels/config/integrations/integration-panels/zha/zha-group-page.ts

340 lines
11 KiB
TypeScript

import "@material/mwc-button";
import { mdiDelete } from "@mdi/js";
import {
CSSResultGroup,
LitElement,
PropertyValues,
css,
html,
nothing,
} from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { HASSDomEvent } from "../../../../../common/dom/fire_event";
import { navigate } from "../../../../../common/navigate";
import { SelectionChangedEvent } from "../../../../../components/data-table/ha-data-table";
import "../../../../../components/ha-card";
import "../../../../../components/ha-circular-progress";
import "../../../../../components/ha-icon-button";
import {
ZHADeviceEndpoint,
ZHAGroup,
addMembersToGroup,
fetchGroup,
fetchGroupableDevices,
removeGroups,
removeMembersFromGroup,
} from "../../../../../data/zha";
import "../../../../../layouts/hass-error-screen";
import "../../../../../layouts/hass-subpage";
import { HomeAssistant } from "../../../../../types";
import "../../../ha-config-section";
import { formatAsPaddedHex } from "./functions";
import "./zha-device-endpoint-data-table";
import type { ZHADeviceEndpointDataTable } from "./zha-device-endpoint-data-table";
import "@material/mwc-list/mwc-list";
import "../../../../../components/ha-list-item";
@customElement("zha-group-page")
export class ZHAGroupPage extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Object }) public group?: ZHAGroup;
@property({ type: Number }) public groupId!: number;
@property({ type: Boolean }) public narrow = false;
@property({ type: Boolean }) public isWide = false;
@property({ type: Array }) public deviceEndpoints: ZHADeviceEndpoint[] = [];
@state() private _processingAdd = false;
@state() private _processingRemove = false;
@state()
private _filteredDeviceEndpoints: ZHADeviceEndpoint[] = [];
@state() private _selectedDevicesToAdd: string[] = [];
@state() private _selectedDevicesToRemove: string[] = [];
@query("#addMembers", true)
private _zhaAddMembersDataTable!: ZHADeviceEndpointDataTable;
@query("#removeMembers")
private _zhaRemoveMembersDataTable!: ZHADeviceEndpointDataTable;
private _firstUpdatedCalled = false;
public connectedCallback(): void {
super.connectedCallback();
if (this.hass && this._firstUpdatedCalled) {
this._fetchData();
}
}
public disconnectedCallback(): void {
super.disconnectedCallback();
this._processingAdd = false;
this._processingRemove = false;
this._selectedDevicesToRemove = [];
this._selectedDevicesToAdd = [];
this.deviceEndpoints = [];
this._filteredDeviceEndpoints = [];
}
protected firstUpdated(changedProperties: PropertyValues): void {
super.firstUpdated(changedProperties);
if (this.hass) {
this._fetchData();
}
this._firstUpdatedCalled = true;
}
protected render() {
if (!this.group) {
return html`
<hass-error-screen
.hass=${this.hass}
.error=${this.hass.localize(
"ui.panel.config.zha.groups.group_not_found"
)}
></hass-error-screen>
`;
}
return html`
<hass-subpage
.hass=${this.hass}
.narrow=${this.narrow}
.header=${this.group.name}
>
<ha-icon-button
slot="toolbar-icon"
.path=${mdiDelete}
@click=${this._deleteGroup}
.label=${this.hass.localize("ui.panel.config.zha.groups.delete")}
></ha-icon-button>
<ha-config-section .isWide=${this.isWide}>
<div class="header">
${this.hass.localize("ui.panel.config.zha.groups.group_info")}
</div>
<p slot="introduction">
${this.hass.localize("ui.panel.config.zha.groups.group_details")}
</p>
<p><b>Name:</b> ${this.group.name}</p>
<p><b>Group Id:</b> ${formatAsPaddedHex(this.group.group_id)}</p>
<div class="header">
${this.hass.localize("ui.panel.config.zha.groups.members")}
</div>
<ha-card>
<mwc-list>
${this.group.members.length
? this.group.members.map(
(member) =>
html`<a
href="/config/devices/device/${member.device
.device_reg_id}"
>
<ha-list-item
>${member.device.user_given_name ||
member.device.name}</ha-list-item
>
</a>`
)
: html`
<ha-list-item> This group has no members </ha-list-item>
`}
</mwc-list>
</ha-card>
${this.group.members.length
? html`
<div class="header">
${this.hass.localize(
"ui.panel.config.zha.groups.remove_members"
)}
</div>
<zha-device-endpoint-data-table
id="removeMembers"
.hass=${this.hass}
.deviceEndpoints=${this.group.members}
.narrow=${this.narrow}
selectable
@selection-changed=${this._handleRemoveSelectionChanged}
>
</zha-device-endpoint-data-table>
<div class="buttons">
<mwc-button
.disabled=${!this._selectedDevicesToRemove.length ||
this._processingRemove}
@click=${this._removeMembersFromGroup}
class="button"
>
${this._processingRemove
? html`<ha-circular-progress
indeterminate
.ariaLabel=${this.hass.localize(
"ui.panel.config.zha.groups.removing_members"
)}
></ha-circular-progress>`
: nothing}
${this.hass!.localize(
"ui.panel.config.zha.groups.remove_members"
)}</mwc-button
>
</div>
`
: nothing}
<div class="header">
${this.hass.localize("ui.panel.config.zha.groups.add_members")}
</div>
<zha-device-endpoint-data-table
id="addMembers"
.hass=${this.hass}
.deviceEndpoints=${this._filteredDeviceEndpoints}
.narrow=${this.narrow}
selectable
@selection-changed=${this._handleAddSelectionChanged}
>
</zha-device-endpoint-data-table>
<div class="buttons">
<mwc-button
.disabled=${!this._selectedDevicesToAdd.length ||
this._processingAdd}
@click=${this._addMembersToGroup}
class="button"
>
${this._processingAdd
? html`<ha-circular-progress
active
size="small"
aria-label="Saving"
></ha-circular-progress>`
: ""}
${this.hass!.localize(
"ui.panel.config.zha.groups.add_members"
)}</mwc-button
>
</div>
</ha-config-section>
</hass-subpage>
`;
}
private async _fetchData() {
if (this.groupId !== null && this.groupId !== undefined) {
this.group = await fetchGroup(this.hass!, this.groupId);
}
this.deviceEndpoints = 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._filteredDeviceEndpoints = this.deviceEndpoints.filter(
(deviceEndpoint) =>
!this.group!.members.some(
(member) =>
member.device.ieee === deviceEndpoint.device.ieee &&
member.endpoint_id === deviceEndpoint.endpoint_id
)
);
}
private _handleAddSelectionChanged(
ev: HASSDomEvent<SelectionChangedEvent>
): void {
this._selectedDevicesToAdd = ev.detail.value;
}
private _handleRemoveSelectionChanged(
ev: HASSDomEvent<SelectionChangedEvent>
): void {
this._selectedDevicesToRemove = ev.detail.value;
}
private async _addMembersToGroup(): Promise<void> {
this._processingAdd = true;
const members = this._selectedDevicesToAdd.map((member) => {
const memberParts = member.split("_");
return { ieee: memberParts[0], endpoint_id: memberParts[1] };
});
this.group = await addMembersToGroup(this.hass, this.groupId, members);
this._filterDevices();
this._selectedDevicesToAdd = [];
this._zhaAddMembersDataTable.clearSelection();
this._processingAdd = false;
}
private async _removeMembersFromGroup(): Promise<void> {
this._processingRemove = true;
const members = this._selectedDevicesToRemove.map((member) => {
const memberParts = member.split("_");
return { ieee: memberParts[0], endpoint_id: memberParts[1] };
});
this.group = await removeMembersFromGroup(this.hass, this.groupId, members);
this._filterDevices();
this._selectedDevicesToRemove = [];
this._zhaRemoveMembersDataTable.clearSelection();
this._processingRemove = false;
}
private async _deleteGroup(): Promise<void> {
await removeGroups(this.hass, [this.groupId]);
navigate(`/config/zha/groups`, { replace: true });
}
static get styles(): CSSResultGroup {
return [
css`
hass-subpage {
--app-header-text-color: var(--sidebar-icon-color);
}
.header {
font-family: var(--paper-font-display1_-_font-family);
-webkit-font-smoothing: var(
--paper-font-display1_-_-webkit-font-smoothing
);
font-size: var(--paper-font-display1_-_font-size);
font-weight: var(--paper-font-display1_-_font-weight);
letter-spacing: var(--paper-font-display1_-_letter-spacing);
line-height: var(--paper-font-display1_-_line-height);
opacity: var(--dark-primary-opacity);
}
.button {
float: right;
}
a {
color: var(--primary-color);
text-decoration: none;
}
.buttons {
align-items: flex-end;
padding: 8px;
}
.buttons .warning {
--mdc-theme-primary: var(--error-color);
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"zha-group-page": ZHAGroupPage;
}
}