diff --git a/src/panels/config/automation/ha-automation-picker.ts b/src/panels/config/automation/ha-automation-picker.ts index 0d1d506d6a..4d58866e34 100644 --- a/src/panels/config/automation/ha-automation-picker.ts +++ b/src/panels/config/automation/ha-automation-picker.ts @@ -424,9 +424,11 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) { ${this.hass.localize("ui.panel.config.labels.add_label")} `; + const labelsInOverflow = (this._sizeController.value && this._sizeController.value < 700) || (!this._sizeController.value && this.hass.dockedSidebar === "docked"); + const automations = this._automations( this.automations, this._entityReg, diff --git a/src/panels/config/devices/ha-config-devices-dashboard.ts b/src/panels/config/devices/ha-config-devices-dashboard.ts index 0e5c0eff43..5cfc421492 100644 --- a/src/panels/config/devices/ha-config-devices-dashboard.ts +++ b/src/panels/config/devices/ha-config-devices-dashboard.ts @@ -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` + ${area.icon + ? html`` + : html``} +
${area.name}
+
` + )} + +
+ ${this.hass.localize( + "ui.panel.config.devices.picker.bulk_actions.no_area" + )} +
+
+ + +
+ ${this.hass.localize( + "ui.panel.config.devices.picker.bulk_actions.add_area" + )} +
+
`; + 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` - - - - ${labelItems} - ` - : html` - - + + + + ${labelItems} + + + ${areasInOverflow + ? nothing + : html` + + + + ${areaItems} + `}` + : nothing} + ${this.narrow || areasInOverflow + ? html` + ${this.narrow + ? html` + + ` + : html``} + ${this.narrow + ? html` + +
+ ${this.hass.localize( + "ui.panel.config.automation.picker.bulk_actions.add_label" + )} +
+ +
+ ${labelItems} +
` + : nothing}
${this.hass.localize( - "ui.panel.config.automation.picker.bulk_actions.add_label" + "ui.panel.config.devices.picker.bulk_actions.move_area" )}
- ${labelItems} + ${areaItems}
-
`} + ` + : nothing} `; } @@ -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[] = []; + 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`
+${rejected
+            .map((r) => r.reason.message || r.reason.code || r.reason)
+            .join("\r\n")}
`, + }); + } + } + + 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; } diff --git a/src/translations/en.json b/src/translations/en.json index 100b5536e5..c360ded244 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -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": {