mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-27 03:06:41 +00:00
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:
parent
1b54d51e4a
commit
a428ad0655
@ -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,
|
||||
|
@ -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) =>
|
||||
@ -696,36 +744,77 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
||||
|
||||
${!this.narrow
|
||||
? html`<ha-button-menu-new slot="selection-bar">
|
||||
<ha-assist-chip
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.picker.bulk_actions.add_label"
|
||||
)}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="trailing-icon"
|
||||
.path=${mdiMenuDown}
|
||||
></ha-svg-icon>
|
||||
</ha-assist-chip>
|
||||
${labelItems}
|
||||
</ha-button-menu-new>`
|
||||
: html` <ha-button-menu-new has-overflow slot="selection-bar"
|
||||
><ha-assist-chip
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.picker.bulk_action"
|
||||
)}
|
||||
slot="trigger"
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="trailing-icon"
|
||||
.path=${mdiMenuDown}
|
||||
></ha-svg-icon>
|
||||
</ha-assist-chip>
|
||||
<ha-assist-chip
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.picker.bulk_actions.add_label"
|
||||
)}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="trailing-icon"
|
||||
.path=${mdiMenuDown}
|
||||
></ha-svg-icon>
|
||||
</ha-assist-chip>
|
||||
${labelItems}
|
||||
</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"
|
||||
)}
|
||||
slot="trigger"
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="trailing-icon"
|
||||
.path=${mdiMenuDown}
|
||||
></ha-svg-icon>
|
||||
</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(
|
||||
"ui.panel.config.automation.picker.bulk_actions.add_label"
|
||||
)}
|
||||
</div>
|
||||
<ha-svg-icon
|
||||
slot="end"
|
||||
.path=${mdiChevronRight}
|
||||
></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.automation.picker.bulk_actions.add_label"
|
||||
"ui.panel.config.devices.picker.bulk_actions.move_area"
|
||||
)}
|
||||
</div>
|
||||
<ha-svg-icon
|
||||
@ -733,9 +822,10 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
||||
.path=${mdiChevronRight}
|
||||
></ha-svg-icon>
|
||||
</ha-menu-item>
|
||||
<ha-menu slot="menu">${labelItems}</ha-menu>
|
||||
<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;
|
||||
}
|
||||
|
@ -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": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user