From 034fd9b4dfde9160bf0079f773b5b999c332175f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 3 Apr 2024 14:17:32 +0200 Subject: [PATCH] Manage areas from floor dialog (#20347) * manage areas from floor dialog * Finish * fix exclude --- src/components/ha-floor-picker.ts | 12 +- .../areas/dialog-floor-registry-detail.ts | 141 +++++++++++++++++- .../config/areas/ha-config-areas-dashboard.ts | 29 +++- .../show-dialog-floor-registry-detail.ts | 9 +- src/translations/en.json | 5 +- 5 files changed, 179 insertions(+), 17 deletions(-) diff --git a/src/components/ha-floor-picker.ts b/src/components/ha-floor-picker.ts index f8e29dc7ef..c810292677 100644 --- a/src/components/ha-floor-picker.ts +++ b/src/components/ha-floor-picker.ts @@ -10,7 +10,10 @@ import { ScorableTextItem, fuzzyFilterSort, } from "../common/string/filter/sequence-matching"; -import { AreaRegistryEntry } from "../data/area_registry"; +import { + AreaRegistryEntry, + updateAreaRegistryEntry, +} from "../data/area_registry"; import { DeviceEntityDisplayLookup, DeviceRegistryEntry, @@ -441,9 +444,14 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) { showFloorRegistryDetailDialog(this, { suggestedName: newValue === ADD_NEW_SUGGESTION_ID ? this._suggestion : "", - createEntry: async (values) => { + createEntry: async (values, addedAreas) => { try { const floor = await createFloorRegistryEntry(this.hass, values); + addedAreas.forEach((areaId) => { + updateAreaRegistryEntry(this.hass, areaId, { + floor_id: floor.floor_id, + }); + }); const floors = [...this._floors!, floor]; this.comboBox.filteredItems = this._getFloors( floors, diff --git a/src/panels/config/areas/dialog-floor-registry-detail.ts b/src/panels/config/areas/dialog-floor-registry-detail.ts index 12e666873f..d59a324f16 100644 --- a/src/panels/config/areas/dialog-floor-registry-detail.ts +++ b/src/panels/config/areas/dialog-floor-registry-detail.ts @@ -1,8 +1,13 @@ import "@material/mwc-button"; import "@material/mwc-list/mwc-list"; +import { mdiTextureBox } from "@mdi/js"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { property, state } from "lit/decorators"; +import { repeat } from "lit/directives/repeat"; +import memoizeOne from "memoize-one"; import { fireEvent } from "../../../common/dom/fire_event"; +import "../../../components/chips/ha-chip-set"; +import "../../../components/chips/ha-input-chip"; import "../../../components/ha-alert"; import "../../../components/ha-aliases-editor"; import { createCloseHeading } from "../../../components/ha-dialog"; @@ -11,10 +16,15 @@ import "../../../components/ha-picture-upload"; import "../../../components/ha-settings-row"; import "../../../components/ha-svg-icon"; import "../../../components/ha-textfield"; -import { FloorRegistryEntryMutableParams } from "../../../data/floor_registry"; -import { haStyleDialog } from "../../../resources/styles"; +import { + FloorRegistryEntry, + FloorRegistryEntryMutableParams, +} from "../../../data/floor_registry"; +import { haStyle, haStyleDialog } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; import { FloorRegistryDetailDialogParams } from "./show-dialog-floor-registry-detail"; +import { showAreaRegistryDetailDialog } from "./show-dialog-area-registry-detail"; +import { updateAreaRegistryEntry } from "../../../data/area_registry"; class DialogFloorDetail extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -33,9 +43,11 @@ class DialogFloorDetail extends LitElement { @state() private _submitting?: boolean; - public async showDialog( - params: FloorRegistryDetailDialogParams - ): Promise { + @state() private _addedAreas = new Set(); + + @state() private _removedAreas = new Set(); + + public showDialog(params: FloorRegistryDetailDialogParams): void { this._params = params; this._error = undefined; this._name = this._params.entry @@ -44,16 +56,40 @@ class DialogFloorDetail extends LitElement { this._aliases = this._params.entry?.aliases || []; this._icon = this._params.entry?.icon || null; this._level = this._params.entry?.level ?? null; - await this.updateComplete; + this._addedAreas.clear(); + this._removedAreas.clear(); } public closeDialog(): void { this._error = ""; this._params = undefined; + this._addedAreas.clear(); + this._removedAreas.clear(); fireEvent(this, "dialog-closed", { dialog: this.localName }); } + private _floorAreas = memoizeOne( + ( + entry: FloorRegistryEntry | undefined, + areas: HomeAssistant["areas"], + added: Set, + removed: Set + ) => + Object.values(areas).filter( + (area) => + (area.floor_id === entry?.floor_id || added.has(area.area_id)) && + !removed.has(area.area_id) + ) + ); + protected render() { + const areas = this._floorAreas( + this._params?.entry, + this.hass.areas, + this._addedAreas, + this._removedAreas + ); + if (!this._params) { return nothing; } @@ -125,6 +161,52 @@ class DialogFloorDetail extends LitElement { : nothing} +

+ ${this.hass.localize( + "ui.panel.config.floors.editor.areas_section" + )} +

+ +

+ ${this.hass.localize( + "ui.panel.config.floors.editor.areas_description" + )} +

+ ${areas.length + ? html` + ${repeat( + areas, + (area) => area.area_id, + (area) => + html` + ${area.icon + ? html`` + : html``} + ` + )} + ` + : nothing} + a.area_id)} + .label=${this.hass.localize( + "ui.panel.config.floors.editor.add_area" + )} + > +

${this.hass.localize( "ui.panel.config.floors.editor.aliases_section" @@ -159,6 +241,41 @@ class DialogFloorDetail extends LitElement { `; } + private _openArea(ev) { + const area = ev.target.area; + showAreaRegistryDetailDialog(this, { + entry: area, + updateEntry: (values) => + updateAreaRegistryEntry(this.hass!, area.area_id, values), + }); + } + + private _removeArea(ev) { + const areaId = ev.target.area.area_id; + if (this._addedAreas.has(areaId)) { + this._addedAreas.delete(areaId); + this._addedAreas = new Set(this._addedAreas); + return; + } + this._removedAreas.add(areaId); + this._removedAreas = new Set(this._removedAreas); + } + + private _addArea(ev) { + const areaId = ev.detail.value; + if (!areaId) { + return; + } + ev.target.value = ""; + if (this._removedAreas.has(areaId)) { + this._removedAreas.delete(areaId); + this._removedAreas = new Set(this._removedAreas); + return; + } + this._addedAreas.add(areaId); + this._addedAreas = new Set(this._addedAreas); + } + private _isNameValid() { return this._name.trim() !== ""; } @@ -189,9 +306,13 @@ class DialogFloorDetail extends LitElement { aliases: this._aliases, }; if (create) { - await this._params!.createEntry!(values); + await this._params!.createEntry!(values, this._addedAreas); } else { - await this._params!.updateEntry!(values); + await this._params!.updateEntry!( + values, + this._addedAreas, + this._removedAreas + ); } this.closeDialog(); } catch (err: any) { @@ -209,6 +330,7 @@ class DialogFloorDetail extends LitElement { static get styles(): CSSResultGroup { return [ + haStyle, haStyleDialog, css` ha-textfield { @@ -218,6 +340,9 @@ class DialogFloorDetail extends LitElement { ha-floor-icon { color: var(--secondary-text-color); } + ha-chip-set { + margin-bottom: 8px; + } `, ]; } diff --git a/src/panels/config/areas/ha-config-areas-dashboard.ts b/src/panels/config/areas/ha-config-areas-dashboard.ts index 12584b4f22..1b00b463d8 100644 --- a/src/panels/config/areas/ha-config-areas-dashboard.ts +++ b/src/panels/config/areas/ha-config-areas-dashboard.ts @@ -414,10 +414,31 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) { private _openFloorDialog(entry?: FloorRegistryEntry) { showFloorRegistryDetailDialog(this, { entry, - createEntry: async (values) => - createFloorRegistryEntry(this.hass!, values), - updateEntry: async (values) => - updateFloorRegistryEntry(this.hass!, entry!.floor_id, values), + createEntry: async (values, addedAreas) => { + const floor = await createFloorRegistryEntry(this.hass!, values); + addedAreas.forEach((areaId) => { + updateAreaRegistryEntry(this.hass, areaId, { + floor_id: floor.floor_id, + }); + }); + }, + updateEntry: async (values, addedAreas, removedAreas) => { + const floor = await updateFloorRegistryEntry( + this.hass!, + entry!.floor_id, + values + ); + addedAreas.forEach((areaId) => { + updateAreaRegistryEntry(this.hass, areaId, { + floor_id: floor.floor_id, + }); + }); + removedAreas.forEach((areaId) => { + updateAreaRegistryEntry(this.hass, areaId, { + floor_id: null, + }); + }); + }, }); } diff --git a/src/panels/config/areas/show-dialog-floor-registry-detail.ts b/src/panels/config/areas/show-dialog-floor-registry-detail.ts index ecaa74786b..d321f60670 100644 --- a/src/panels/config/areas/show-dialog-floor-registry-detail.ts +++ b/src/panels/config/areas/show-dialog-floor-registry-detail.ts @@ -7,9 +7,14 @@ import { export interface FloorRegistryDetailDialogParams { entry?: FloorRegistryEntry; suggestedName?: string; - createEntry?: (values: FloorRegistryEntryMutableParams) => Promise; + createEntry?: ( + values: FloorRegistryEntryMutableParams, + addedAreas: Set + ) => Promise; updateEntry?: ( - updates: Partial + updates: Partial, + addedAreas: Set, + removedAreas: Set ) => Promise; } diff --git a/src/translations/en.json b/src/translations/en.json index ead12d761d..d4e2852c0a 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1927,7 +1927,10 @@ "aliases_section": "Aliases", "no_aliases": "No configured aliases", "configured_aliases": "{count} configured {count, plural,\n one {alias}\n other {aliases}\n}", - "aliases_description": "Aliases are alternative names used in voice assistants to refer to this floor." + "aliases_description": "Aliases are alternative names used in voice assistants to refer to this floor.", + "areas_section": "Areas", + "areas_description": "Specify the areas that are on this floor.", + "add_area": "Add area" } }, "category": {