Add bulk area assignment to device dashboard (#20581)

* Add bulk area assignment to device dashboard

* Update ha-config-devices-dashboard.ts
This commit is contained in:
Bram Kragten 2024-04-22 18:35:58 +02:00 committed by GitHub
parent 1b54d51e4a
commit a428ad0655
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 176 additions and 36 deletions

View File

@ -424,9 +424,11 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
${this.hass.localize("ui.panel.config.labels.add_label")}
</div></ha-menu-item
>`;
const labelsInOverflow =
(this._sizeController.value && this._sizeController.value < 700) ||
(!this._sizeController.value && this.hass.dockedSidebar === "docked");
const automations = this._automations(
this.automations,
this._entityReg,

View File

@ -1,6 +1,12 @@
import { consume } from "@lit-labs/context";
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
import { mdiChevronRight, mdiMenuDown, mdiPlus } from "@mdi/js";
import {
mdiChevronRight,
mdiDotsVertical,
mdiMenuDown,
mdiPlus,
mdiTextureBox,
} from "@mdi/js";
import {
CSSResultGroup,
LitElement,
@ -10,10 +16,12 @@ import {
nothing,
} from "lit";
import { ResizeController } from "@lit-labs/observers/resize-controller";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { computeCssColor } from "../../../common/color/compute-color";
import { storage } from "../../../common/decorators/storage";
import { HASSDomEvent } from "../../../common/dom/fire_event";
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import {
@ -22,6 +30,10 @@ import {
} from "../../../common/integrations/protocolIntegrationPicked";
import { navigate } from "../../../common/navigate";
import { LocalizeFunc } from "../../../common/translations/localize";
import {
hasRejectedItems,
rejectedItems,
} from "../../../common/util/promise-all-settled-results";
import {
DataTableColumnContainer,
RowClickedEvent,
@ -42,6 +54,7 @@ import "../../../components/ha-filter-states";
import "../../../components/ha-icon-button";
import "../../../components/ha-menu-item";
import "../../../components/ha-sub-menu";
import { createAreaRegistryEntry } from "../../../data/area_registry";
import { ConfigEntry, sortConfigEntries } from "../../../data/config_entries";
import { fullEntitiesContext } from "../../../data/context";
import {
@ -66,16 +79,12 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant, Route } from "../../../types";
import { brandsUrl } from "../../../util/brands-url";
import { showAreaRegistryDetailDialog } from "../areas/show-dialog-area-registry-detail";
import { configSections } from "../ha-panel-config";
import "../integrations/ha-integration-overflow-menu";
import { showAddIntegrationDialog } from "../integrations/show-add-integration-dialog";
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
import {
hasRejectedItems,
rejectedItems,
} from "../../../common/util/promise-all-settled-results";
import { showAlertDialog } from "../../lovelace/custom-card-helpers";
import { storage } from "../../../common/decorators/storage";
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
interface DeviceRowData extends DeviceRegistryEntry {
device?: DeviceRowData;
@ -125,6 +134,10 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
@storage({ key: "devices-table-grouping", state: false, subscribe: false })
private _activeGrouping?: string;
private _sizeController = new ResizeController(this, {
callback: (entries) => entries[0]?.contentRect.width,
});
private _ignoreLocationChange = false;
public connectedCallback() {
@ -557,6 +570,41 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
this._labels
);
const areasInOverflow =
(this._sizeController.value && this._sizeController.value < 700) ||
(!this._sizeController.value && this.hass.dockedSidebar === "docked");
const areaItems = html`${Object.values(this.hass.areas).map(
(area) =>
html`<ha-menu-item
.value=${area.area_id}
@click=${this._handleBulkArea}
>
${area.icon
? html`<ha-icon slot="start" .icon=${area.icon}></ha-icon>`
: html`<ha-svg-icon
slot="start"
.path=${mdiTextureBox}
></ha-svg-icon>`}
<div slot="headline">${area.name}</div>
</ha-menu-item>`
)}
<ha-menu-item .value=${null} @click=${this._handleBulkArea}>
<div slot="headline">
${this.hass.localize(
"ui.panel.config.devices.picker.bulk_actions.no_area"
)}
</div>
</ha-menu-item>
<md-divider role="separator" tabindex="-1"></md-divider>
<ha-menu-item @click=${this._bulkCreateArea}>
<div slot="headline">
${this.hass.localize(
"ui.panel.config.devices.picker.bulk_actions.add_area"
)}
</div>
</ha-menu-item>`;
const labelItems = html`${this._labels?.map((label) => {
const color = label.color ? computeCssColor(label.color) : undefined;
const selected = this._selected.every((deviceId) =>
@ -708,9 +756,29 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
></ha-svg-icon>
</ha-assist-chip>
${labelItems}
</ha-button-menu-new>`
: html` <ha-button-menu-new has-overflow slot="selection-bar"
><ha-assist-chip
</ha-button-menu-new>
${areasInOverflow
? nothing
: html`<ha-button-menu-new slot="selection-bar">
<ha-assist-chip
slot="trigger"
.label=${this.hass.localize(
"ui.panel.config.devices.picker.bulk_actions.move_area"
)}
>
<ha-svg-icon
slot="trailing-icon"
.path=${mdiMenuDown}
></ha-svg-icon>
</ha-assist-chip>
${areaItems}
</ha-button-menu-new>`}`
: nothing}
${this.narrow || areasInOverflow
? html`<ha-button-menu-new has-overflow slot="selection-bar">
${this.narrow
? html`<ha-assist-chip
.label=${this.hass.localize(
"ui.panel.config.automation.picker.bulk_action"
)}
@ -720,8 +788,14 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
slot="trailing-icon"
.path=${mdiMenuDown}
></ha-svg-icon>
</ha-assist-chip>
<ha-sub-menu>
</ha-assist-chip>`
: html`<ha-icon-button
.path=${mdiDotsVertical}
.label=${"ui.panel.config.automation.picker.bulk_action"}
slot="trigger"
></ha-icon-button>`}
${this.narrow
? html` <ha-sub-menu>
<ha-menu-item slot="item">
<div slot="headline">
${this.hass.localize(
@ -734,8 +808,24 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
></ha-svg-icon>
</ha-menu-item>
<ha-menu slot="menu">${labelItems}</ha-menu>
</ha-sub-menu>`
: nothing}
<ha-sub-menu>
<ha-menu-item slot="item">
<div slot="headline">
${this.hass.localize(
"ui.panel.config.devices.picker.bulk_actions.move_area"
)}
</div>
<ha-svg-icon
slot="end"
.path=${mdiChevronRight}
></ha-svg-icon>
</ha-menu-item>
<ha-menu slot="menu">${areaItems}</ha-menu>
</ha-sub-menu>
</ha-button-menu-new>`}
</ha-button-menu-new>`
: nothing}
</hass-tabs-subpage-data-table>
`;
}
@ -821,6 +911,46 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
this._selected = ev.detail.value;
}
private async _handleBulkArea(ev) {
const area = ev.currentTarget.value;
this._bulkAddArea(area);
}
private async _bulkAddArea(area: string) {
const promises: Promise<DeviceRegistryEntry>[] = [];
this._selected.forEach((deviceId) => {
promises.push(
updateDeviceRegistryEntry(this.hass, deviceId, {
area_id: area,
})
);
});
const result = await Promise.allSettled(promises);
if (hasRejectedItems(result)) {
const rejected = rejectedItems(result);
showAlertDialog(this, {
title: this.hass.localize("ui.panel.config.common.multiselect.failed", {
number: rejected.length,
}),
text: html`<pre>
${rejected
.map((r) => r.reason.message || r.reason.code || r.reason)
.join("\r\n")}</pre
>`,
});
}
}
private async _bulkCreateArea() {
showAreaRegistryDetailDialog(this, {
createEntry: async (values) => {
const area = await createAreaRegistryEntry(this.hass, values);
this._bulkAddArea(area.area_id);
return area;
},
});
}
private async _handleBulkLabel(ev) {
const label = ev.currentTarget.value;
const action = ev.currentTarget.action;
@ -878,6 +1008,9 @@ ${rejected
static get styles(): CSSResultGroup {
return [
css`
:host {
display: block;
}
hass-tabs-subpage-data-table {
--data-table-row-height: 60px;
}

View File

@ -4026,7 +4026,12 @@
"confirm_delete_integration": "Are you sure you want to remove this device from {integration}?",
"picker": {
"search": "Search {number} devices",
"state": "State"
"state": "State",
"bulk_actions": {
"move_area": "Move to area",
"no_area": "No area",
"add_area": "Add area"
}
}
},
"entities": {