mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-27 11:16:35 +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")}
|
${this.hass.localize("ui.panel.config.labels.add_label")}
|
||||||
</div></ha-menu-item
|
</div></ha-menu-item
|
||||||
>`;
|
>`;
|
||||||
|
|
||||||
const labelsInOverflow =
|
const labelsInOverflow =
|
||||||
(this._sizeController.value && this._sizeController.value < 700) ||
|
(this._sizeController.value && this._sizeController.value < 700) ||
|
||||||
(!this._sizeController.value && this.hass.dockedSidebar === "docked");
|
(!this._sizeController.value && this.hass.dockedSidebar === "docked");
|
||||||
|
|
||||||
const automations = this._automations(
|
const automations = this._automations(
|
||||||
this.automations,
|
this.automations,
|
||||||
this._entityReg,
|
this._entityReg,
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
import { consume } from "@lit-labs/context";
|
import { consume } from "@lit-labs/context";
|
||||||
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
|
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
|
||||||
import { mdiChevronRight, mdiMenuDown, mdiPlus } from "@mdi/js";
|
import {
|
||||||
|
mdiChevronRight,
|
||||||
|
mdiDotsVertical,
|
||||||
|
mdiMenuDown,
|
||||||
|
mdiPlus,
|
||||||
|
mdiTextureBox,
|
||||||
|
} from "@mdi/js";
|
||||||
import {
|
import {
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
LitElement,
|
LitElement,
|
||||||
@ -10,10 +16,12 @@ import {
|
|||||||
nothing,
|
nothing,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
|
|
||||||
|
import { ResizeController } from "@lit-labs/observers/resize-controller";
|
||||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { computeCssColor } from "../../../common/color/compute-color";
|
import { computeCssColor } from "../../../common/color/compute-color";
|
||||||
|
import { storage } from "../../../common/decorators/storage";
|
||||||
import { HASSDomEvent } from "../../../common/dom/fire_event";
|
import { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||||
import {
|
import {
|
||||||
@ -22,6 +30,10 @@ import {
|
|||||||
} from "../../../common/integrations/protocolIntegrationPicked";
|
} from "../../../common/integrations/protocolIntegrationPicked";
|
||||||
import { navigate } from "../../../common/navigate";
|
import { navigate } from "../../../common/navigate";
|
||||||
import { LocalizeFunc } from "../../../common/translations/localize";
|
import { LocalizeFunc } from "../../../common/translations/localize";
|
||||||
|
import {
|
||||||
|
hasRejectedItems,
|
||||||
|
rejectedItems,
|
||||||
|
} from "../../../common/util/promise-all-settled-results";
|
||||||
import {
|
import {
|
||||||
DataTableColumnContainer,
|
DataTableColumnContainer,
|
||||||
RowClickedEvent,
|
RowClickedEvent,
|
||||||
@ -42,6 +54,7 @@ import "../../../components/ha-filter-states";
|
|||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-icon-button";
|
||||||
import "../../../components/ha-menu-item";
|
import "../../../components/ha-menu-item";
|
||||||
import "../../../components/ha-sub-menu";
|
import "../../../components/ha-sub-menu";
|
||||||
|
import { createAreaRegistryEntry } from "../../../data/area_registry";
|
||||||
import { ConfigEntry, sortConfigEntries } from "../../../data/config_entries";
|
import { ConfigEntry, sortConfigEntries } from "../../../data/config_entries";
|
||||||
import { fullEntitiesContext } from "../../../data/context";
|
import { fullEntitiesContext } from "../../../data/context";
|
||||||
import {
|
import {
|
||||||
@ -66,16 +79,12 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
|||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import { HomeAssistant, Route } from "../../../types";
|
import { HomeAssistant, Route } from "../../../types";
|
||||||
import { brandsUrl } from "../../../util/brands-url";
|
import { brandsUrl } from "../../../util/brands-url";
|
||||||
|
import { showAreaRegistryDetailDialog } from "../areas/show-dialog-area-registry-detail";
|
||||||
import { configSections } from "../ha-panel-config";
|
import { configSections } from "../ha-panel-config";
|
||||||
import "../integrations/ha-integration-overflow-menu";
|
import "../integrations/ha-integration-overflow-menu";
|
||||||
import { showAddIntegrationDialog } from "../integrations/show-add-integration-dialog";
|
import { showAddIntegrationDialog } from "../integrations/show-add-integration-dialog";
|
||||||
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
|
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
|
||||||
import {
|
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||||
hasRejectedItems,
|
|
||||||
rejectedItems,
|
|
||||||
} from "../../../common/util/promise-all-settled-results";
|
|
||||||
import { showAlertDialog } from "../../lovelace/custom-card-helpers";
|
|
||||||
import { storage } from "../../../common/decorators/storage";
|
|
||||||
|
|
||||||
interface DeviceRowData extends DeviceRegistryEntry {
|
interface DeviceRowData extends DeviceRegistryEntry {
|
||||||
device?: DeviceRowData;
|
device?: DeviceRowData;
|
||||||
@ -125,6 +134,10 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
|||||||
@storage({ key: "devices-table-grouping", state: false, subscribe: false })
|
@storage({ key: "devices-table-grouping", state: false, subscribe: false })
|
||||||
private _activeGrouping?: string;
|
private _activeGrouping?: string;
|
||||||
|
|
||||||
|
private _sizeController = new ResizeController(this, {
|
||||||
|
callback: (entries) => entries[0]?.contentRect.width,
|
||||||
|
});
|
||||||
|
|
||||||
private _ignoreLocationChange = false;
|
private _ignoreLocationChange = false;
|
||||||
|
|
||||||
public connectedCallback() {
|
public connectedCallback() {
|
||||||
@ -557,6 +570,41 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
|||||||
this._labels
|
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 labelItems = html`${this._labels?.map((label) => {
|
||||||
const color = label.color ? computeCssColor(label.color) : undefined;
|
const color = label.color ? computeCssColor(label.color) : undefined;
|
||||||
const selected = this._selected.every((deviceId) =>
|
const selected = this._selected.every((deviceId) =>
|
||||||
@ -708,9 +756,29 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
|||||||
></ha-svg-icon>
|
></ha-svg-icon>
|
||||||
</ha-assist-chip>
|
</ha-assist-chip>
|
||||||
${labelItems}
|
${labelItems}
|
||||||
</ha-button-menu-new>`
|
</ha-button-menu-new>
|
||||||
: html` <ha-button-menu-new has-overflow slot="selection-bar"
|
|
||||||
><ha-assist-chip
|
${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(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.config.automation.picker.bulk_action"
|
"ui.panel.config.automation.picker.bulk_action"
|
||||||
)}
|
)}
|
||||||
@ -720,8 +788,14 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
|||||||
slot="trailing-icon"
|
slot="trailing-icon"
|
||||||
.path=${mdiMenuDown}
|
.path=${mdiMenuDown}
|
||||||
></ha-svg-icon>
|
></ha-svg-icon>
|
||||||
</ha-assist-chip>
|
</ha-assist-chip>`
|
||||||
<ha-sub-menu>
|
: 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">
|
<ha-menu-item slot="item">
|
||||||
<div slot="headline">
|
<div slot="headline">
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
@ -734,8 +808,24 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
|||||||
></ha-svg-icon>
|
></ha-svg-icon>
|
||||||
</ha-menu-item>
|
</ha-menu-item>
|
||||||
<ha-menu slot="menu">${labelItems}</ha-menu>
|
<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-sub-menu>
|
||||||
</ha-button-menu-new>`}
|
</ha-button-menu-new>`
|
||||||
|
: nothing}
|
||||||
</hass-tabs-subpage-data-table>
|
</hass-tabs-subpage-data-table>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -821,6 +911,46 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
|||||||
this._selected = ev.detail.value;
|
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) {
|
private async _handleBulkLabel(ev) {
|
||||||
const label = ev.currentTarget.value;
|
const label = ev.currentTarget.value;
|
||||||
const action = ev.currentTarget.action;
|
const action = ev.currentTarget.action;
|
||||||
@ -878,6 +1008,9 @@ ${rejected
|
|||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
css`
|
css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
hass-tabs-subpage-data-table {
|
hass-tabs-subpage-data-table {
|
||||||
--data-table-row-height: 60px;
|
--data-table-row-height: 60px;
|
||||||
}
|
}
|
||||||
|
@ -4026,7 +4026,12 @@
|
|||||||
"confirm_delete_integration": "Are you sure you want to remove this device from {integration}?",
|
"confirm_delete_integration": "Are you sure you want to remove this device from {integration}?",
|
||||||
"picker": {
|
"picker": {
|
||||||
"search": "Search {number} devices",
|
"search": "Search {number} devices",
|
||||||
"state": "State"
|
"state": "State",
|
||||||
|
"bulk_actions": {
|
||||||
|
"move_area": "Move to area",
|
||||||
|
"no_area": "No area",
|
||||||
|
"add_area": "Add area"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entities": {
|
"entities": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user