From 1d85f0717a8d4fed15094bc67d4b81f2d7e763ae Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 28 Mar 2024 09:15:58 +0100 Subject: [PATCH 1/8] Add label and floor to target struct (#20213) --- src/data/script.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/data/script.ts b/src/data/script.ts index 55e154a826..942cf0fa84 100644 --- a/src/data/script.ts +++ b/src/data/script.ts @@ -42,6 +42,8 @@ const targetStruct = object({ entity_id: optional(union([string(), array(string())])), device_id: optional(union([string(), array(string())])), area_id: optional(union([string(), array(string())])), + floor_id: optional(union([string(), array(string())])), + label_id: optional(union([string(), array(string())])), }); export const serviceActionStruct: Describe = assign( From 31c0850b14dafa1685c634a0089fada7090118aa Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 28 Mar 2024 11:45:41 +0100 Subject: [PATCH 2/8] Add default icons to categories (#20215) * Add default icons to categories * Update ha-filter-categories.ts * smaller graphic margin in filters --- src/components/ha-filter-categories.ts | 15 +++++++++++++-- src/components/ha-filter-entities.ts | 1 + src/components/ha-filter-floor-areas.ts | 3 +++ src/panels/config/category/ha-category-picker.ts | 3 ++- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/components/ha-filter-categories.ts b/src/components/ha-filter-categories.ts index 4824305fa9..571c6b27ff 100644 --- a/src/components/ha-filter-categories.ts +++ b/src/components/ha-filter-categories.ts @@ -1,5 +1,11 @@ import { ActionDetail, SelectedDetail } from "@material/mwc-list"; -import { mdiDelete, mdiDotsVertical, mdiPencil, mdiPlus } from "@mdi/js"; +import { + mdiDelete, + mdiDotsVertical, + mdiPencil, + mdiPlus, + mdiTag, +} from "@mdi/js"; import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; @@ -17,6 +23,7 @@ import type { HomeAssistant } from "../types"; import "./ha-expansion-panel"; import "./ha-icon"; import "./ha-list-item"; +import { stopPropagation } from "../common/dom/stop_propagation"; @customElement("ha-filter-categories") export class HaFilterCategories extends SubscribeMixin(LitElement) { @@ -90,9 +97,13 @@ export class HaFilterCategories extends SubscribeMixin(LitElement) { slot="graphic" .icon=${category.icon} >` - : nothing} + : html``} ${category.name} = (item) => > ${item.icon ? html`` - : nothing} + : html``} ${item.name} `; From 2978ca13c55f4ed3c52622a61c2783fb6422e6a1 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 28 Mar 2024 13:09:51 +0100 Subject: [PATCH 3/8] Fix label creation in label picker (#20233) --- src/components/ha-label-picker.ts | 77 +++++++++++++++++-------------- 1 file changed, 42 insertions(+), 35 deletions(-) diff --git a/src/components/ha-label-picker.ts b/src/components/ha-label-picker.ts index 222f660162..734df1081f 100644 --- a/src/components/ha-label-picker.ts +++ b/src/components/ha-label-picker.ts @@ -31,12 +31,16 @@ import "./ha-icon-button"; import "./ha-list-item"; import "./ha-svg-icon"; -type ScorableLabelRegistryEntry = ScorableTextItem & LabelRegistryEntry; +type ScorableLabelItem = ScorableTextItem & LabelRegistryEntry; + +const ADD_NEW_ID = "___ADD_NEW___"; +const NO_LABELS_ID = "___NO_LABELS___"; +const ADD_NEW_SUGGESTION_ID = "___ADD_NEW_SUGGESTION___"; const rowRenderer: ComboBoxLitRenderer = (item) => html` ${item.icon ? html`` @@ -143,17 +147,6 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) { noAdd: this["noAdd"], excludeLabels: this["excludeLabels"] ): LabelRegistryEntry[] => { - if (!labels.length) { - return [ - { - label_id: "no_labels", - name: this.hass.localize("ui.components.label-picker.no_labels"), - icon: null, - color: null, - }, - ]; - } - let deviceEntityLookup: DeviceEntityDisplayLookup = {}; let inputDevices: DeviceRegistryEntry[] | undefined; let inputEntities: EntityRegistryDisplayEntry[] | undefined; @@ -305,7 +298,7 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) { if (!outputLabels.length) { outputLabels = [ { - label_id: "no_labels", + label_id: NO_LABELS_ID, name: this.hass.localize("ui.components.label-picker.no_match"), icon: null, color: null, @@ -318,7 +311,7 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) { : [ ...outputLabels, { - label_id: "add_new", + label_id: ADD_NEW_ID, name: this.hass.localize("ui.components.label-picker.add_new"), icon: "mdi:plus", color: null, @@ -333,7 +326,7 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) { (this._init && changedProps.has("_opened") && this._opened) ) { this._init = true; - const labels = this._getLabels( + const items = this._getLabels( this._labels!, this.hass.areas, Object.values(this.hass.devices), @@ -350,8 +343,8 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) { strings: [label.label_id, label.name], })); - this.comboBox.items = labels; - this.comboBox.filteredItems = labels; + this.comboBox.items = items; + this.comboBox.filteredItems = items; } } @@ -390,22 +383,36 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) { return; } - const filteredItems = fuzzyFilterSort( + const filteredItems = fuzzyFilterSort( filterString, - target.items || [] + target.items?.filter((item) => + [NO_LABELS_ID, ADD_NEW_ID].includes(item.ignoreFilter) + ) || [] ); - if (!this.noAdd && filteredItems?.length === 0) { - this._suggestion = filterString; - this.comboBox.filteredItems = [ - { - label_id: "add_new_suggestion", - name: this.hass.localize( - "ui.components.label-picker.add_new_sugestion", - { name: this._suggestion } - ), - picture: null, - }, - ]; + if (filteredItems.length === 0) { + if (this.noAdd) { + this.comboBox.filteredItems = [ + { + label_id: NO_LABELS_ID, + name: this.hass.localize("ui.components.label-picker.no_match"), + icon: null, + color: null, + }, + ] as ScorableLabelItem[]; + } else { + this._suggestion = filterString; + this.comboBox.filteredItems = [ + { + label_id: ADD_NEW_SUGGESTION_ID, + name: this.hass.localize( + "ui.components.label-picker.add_new_sugestion", + { name: this._suggestion } + ), + icon: "mdi:plus", + color: null, + }, + ] as ScorableLabelItem[]; + } } else { this.comboBox.filteredItems = filteredItems; } @@ -423,13 +430,13 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) { ev.stopPropagation(); let newValue = ev.detail.value; - if (newValue === "no_labels") { + if (newValue === NO_LABELS_ID) { newValue = ""; this.comboBox.setInputValue(""); return; } - if (!["add_new_suggestion", "add_new"].includes(newValue)) { + if (![ADD_NEW_SUGGESTION_ID, ADD_NEW_ID].includes(newValue)) { if (newValue !== this._value) { this._setValue(newValue); } @@ -440,7 +447,7 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) { showLabelDetailDialog(this, { entry: undefined, - suggestedName: newValue === "add_new_suggestion" ? this._suggestion : "", + suggestedName: newValue === ADD_NEW_SUGGESTION_ID ? this._suggestion : "", createEntry: async (values) => { const label = await createLabelRegistryEntry(this.hass, values); const labels = [...this._labels!, label]; From a08484f4506bfd7acb5cefad09cc87af40f5a2ed Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 28 Mar 2024 13:30:53 +0100 Subject: [PATCH 4/8] Fix default label color (#20232) --- src/components/data-table/ha-data-table-labels.ts | 3 ++- src/components/ha-filter-labels.ts | 2 +- src/components/ha-labels-picker.ts | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/data-table/ha-data-table-labels.ts b/src/components/data-table/ha-data-table-labels.ts index 7156d6d4ca..1c47f0bf64 100644 --- a/src/components/data-table/ha-data-table-labels.ts +++ b/src/components/data-table/ha-data-table-labels.ts @@ -104,13 +104,14 @@ class HaDataTableLabels extends LitElement { flex-wrap: nowrap; } ha-label { - --ha-label-background-color: var(--color); + --ha-label-background-color: var(--color, var(--grey-color)); --ha-label-background-opacity: 0.5; } ha-button-menu { border-radius: 10px; } .plus { + --ha-label-background-color: transparent; border: 1px solid var(--divider-color); } `; diff --git a/src/components/ha-filter-labels.ts b/src/components/ha-filter-labels.ts index 5b816272cd..317ed7f57f 100644 --- a/src/components/ha-filter-labels.ts +++ b/src/components/ha-filter-labels.ts @@ -168,7 +168,7 @@ export class HaFilterLabels extends SubscribeMixin(LitElement) { color: var(--error-color); } ha-label { - --ha-label-background-color: var(--color); + --ha-label-background-color: var(--color, var(--grey-color)); --ha-label-background-opacity: 0.5; } `, diff --git a/src/components/ha-labels-picker.ts b/src/components/ha-labels-picker.ts index a011499120..ccf4511a61 100644 --- a/src/components/ha-labels-picker.ts +++ b/src/components/ha-labels-picker.ts @@ -199,7 +199,7 @@ export class HaLabelsPicker extends SubscribeMixin(LitElement) { margin-bottom: 8px; } ha-input-chip { - --md-input-chip-selected-container-color: var(--color); + --md-input-chip-selected-container-color: var(--color, var(--grey-color)); --ha-input-chip-selected-container-opacity: 0.5; } `; From b590b21183a0631dd63b38668f2f5e9f00e20ced Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 28 Mar 2024 13:51:37 +0100 Subject: [PATCH 5/8] Fix unassign category action (#20235) --- src/panels/config/category/dialog-assign-category.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/config/category/dialog-assign-category.ts b/src/panels/config/category/dialog-assign-category.ts index 48c6dfe953..3847d4b6b3 100644 --- a/src/panels/config/category/dialog-assign-category.ts +++ b/src/panels/config/category/dialog-assign-category.ts @@ -85,7 +85,7 @@ class DialogAssignCategory extends LitElement { private _categoryChanged(ev: CustomEvent): void { if (!ev.detail.value) { - return; + this._category = undefined; } this._category = ev.detail.value; } From 6b8f4e92a7c03cc1b5cd31c57a476a8ef47b9836 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 28 Mar 2024 13:51:53 +0100 Subject: [PATCH 6/8] Display category dialog in category picker (#20234) --- src/components/ha-filter-categories.ts | 10 +- .../dialog-category-registry-detail.ts | 32 ++--- .../config/category/ha-category-picker.ts | 130 +++++++++--------- .../show-dialog-category-registry-detail.ts | 12 +- 4 files changed, 96 insertions(+), 88 deletions(-) diff --git a/src/components/ha-filter-categories.ts b/src/components/ha-filter-categories.ts index 571c6b27ff..9b5a7c539e 100644 --- a/src/components/ha-filter-categories.ts +++ b/src/components/ha-filter-categories.ts @@ -12,8 +12,10 @@ import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../common/dom/fire_event"; import { CategoryRegistryEntry, + createCategoryRegistryEntry, deleteCategoryRegistryEntry, subscribeCategoryRegistry, + updateCategoryRegistryEntry, } from "../data/category_registry"; import { showConfirmationDialog } from "../dialogs/generic/show-dialog-box"; import { SubscribeMixin } from "../mixins/subscribe-mixin"; @@ -174,6 +176,8 @@ export class HaFilterCategories extends SubscribeMixin(LitElement) { showCategoryRegistryDetailDialog(this, { scope: this.scope!, entry: this._categories.find((cat) => cat.category_id === id), + updateEntry: (updates) => + updateCategoryRegistryEntry(this.hass, this.scope!, id, updates), }); } @@ -206,7 +210,11 @@ export class HaFilterCategories extends SubscribeMixin(LitElement) { if (!this.scope) { return; } - showCategoryRegistryDetailDialog(this, { scope: this.scope }); + showCategoryRegistryDetailDialog(this, { + scope: this.scope, + createEntry: (values) => + createCategoryRegistryEntry(this.hass, this.scope!, values), + }); } private _expandedWillChange(ev) { diff --git a/src/panels/config/category/dialog-category-registry-detail.ts b/src/panels/config/category/dialog-category-registry-detail.ts index 508e5b9b42..74f89f0bd7 100644 --- a/src/panels/config/category/dialog-category-registry-detail.ts +++ b/src/panels/config/category/dialog-category-registry-detail.ts @@ -1,6 +1,6 @@ import "@material/mwc-button"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; -import { property, state } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../../common/dom/fire_event"; import "../../../components/ha-alert"; import { createCloseHeading } from "../../../components/ha-dialog"; @@ -8,14 +8,14 @@ import "../../../components/ha-icon-picker"; import "../../../components/ha-settings-row"; import "../../../components/ha-textfield"; import { + CategoryRegistryEntry, CategoryRegistryEntryMutableParams, - createCategoryRegistryEntry, - updateCategoryRegistryEntry, } from "../../../data/category_registry"; import { haStyleDialog } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; import { CategoryRegistryDetailDialogParams } from "./show-dialog-category-registry-detail"; +@customElement("dialog-category-registry-detail") class DialogCategoryDetail extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -34,8 +34,13 @@ class DialogCategoryDetail extends LitElement { ): Promise { this._params = params; this._error = undefined; - this._name = this._params.entry ? this._params.entry.name : ""; - this._icon = this._params.entry?.icon || null; + if (this._params.entry) { + this._name = this._params.entry.name || ""; + this._icon = this._params.entry.icon || null; + } else { + this._name = this._params.suggestedName || ""; + this._icon = null; + } await this.updateComplete; } @@ -123,24 +128,16 @@ class DialogCategoryDetail extends LitElement { private async _updateEntry() { const create = !this._params!.entry; this._submitting = true; + let newValue: CategoryRegistryEntry | undefined; try { const values: CategoryRegistryEntryMutableParams = { name: this._name.trim(), icon: this._icon || (create ? undefined : null), }; if (create) { - await createCategoryRegistryEntry( - this.hass, - this._params!.scope, - values - ); + newValue = await this._params!.createEntry!(values); } else { - await updateCategoryRegistryEntry( - this.hass, - this._params!.scope, - this._params!.entry!.category_id, - values - ); + newValue = await this._params!.updateEntry!(values); } this.closeDialog(); } catch (err: any) { @@ -150,6 +147,7 @@ class DialogCategoryDetail extends LitElement { } finally { this._submitting = false; } + return newValue; } static get styles(): CSSResultGroup { @@ -171,5 +169,3 @@ declare global { "dialog-category-registry-detail": DialogCategoryDetail; } } - -customElements.define("dialog-category-registry-detail", DialogCategoryDetail); diff --git a/src/panels/config/category/ha-category-picker.ts b/src/panels/config/category/ha-category-picker.ts index 583eaf4f44..529db65b2c 100644 --- a/src/panels/config/category/ha-category-picker.ts +++ b/src/panels/config/category/ha-category-picker.ts @@ -20,19 +20,20 @@ import { createCategoryRegistryEntry, subscribeCategoryRegistry, } from "../../../data/category_registry"; -import { - showAlertDialog, - showPromptDialog, -} from "../../../dialogs/generic/show-dialog-box"; import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { HomeAssistant, ValueChangedEvent } from "../../../types"; +import { showCategoryRegistryDetailDialog } from "./show-dialog-category-registry-detail"; type ScorableCategoryRegistryEntry = ScorableTextItem & CategoryRegistryEntry; +const ADD_NEW_ID = "___ADD_NEW___"; +const NO_CATEGORIES_ID = "___NO_CATEGORIES___"; +const ADD_NEW_SUGGESTION_ID = "___ADD_NEW_SUGGESTION___"; + const rowRenderer: ComboBoxLitRenderer = (item) => html` ${item.icon ? html`` @@ -103,7 +104,7 @@ export class HaCategoryPicker extends SubscribeMixin(LitElement) { const result = categories ? [...categories] : []; if (!result?.length) { result.push({ - category_id: "no_categories", + category_id: NO_CATEGORIES_ID, name: this.hass.localize( "ui.components.category-picker.no_categories" ), @@ -116,7 +117,7 @@ export class HaCategoryPicker extends SubscribeMixin(LitElement) { : [ ...result, { - category_id: "add_new", + category_id: ADD_NEW_ID, name: this.hass.localize("ui.components.category-picker.add_new"), icon: "mdi:plus", }, @@ -130,7 +131,12 @@ export class HaCategoryPicker extends SubscribeMixin(LitElement) { (this._init && changedProps.has("_opened") && this._opened) ) { this._init = true; - const categories = this._getCategories(this._categories, this.noAdd); + const categories = this._getCategories(this._categories, this.noAdd).map( + (label) => ({ + ...label, + strings: [label.name], + }) + ); this.comboBox.items = categories; this.comboBox.filteredItems = categories; } @@ -175,18 +181,30 @@ export class HaCategoryPicker extends SubscribeMixin(LitElement) { filterString, target.items || [] ); - if (!this.noAdd && filteredItems?.length === 0) { - this._suggestion = filterString; - this.comboBox.filteredItems = [ - { - category_id: "add_new_suggestion", - name: this.hass.localize( - "ui.components.category-picker.add_new_sugestion", - { name: this._suggestion } - ), - picture: null, - }, - ]; + if (filteredItems?.length === 0) { + if (this.noAdd) { + this.comboBox.filteredItems = [ + { + category_id: NO_CATEGORIES_ID, + name: this.hass.localize( + "ui.components.category-picker.no_categories" + ), + icon: null, + }, + ] as ScorableCategoryRegistryEntry[]; + } else { + this._suggestion = filterString; + this.comboBox.filteredItems = [ + { + category_id: ADD_NEW_SUGGESTION_ID, + name: this.hass.localize( + "ui.components.category-picker.add_new_sugestion", + { name: this._suggestion } + ), + icon: "mdi:plus", + }, + ]; + } } else { this.comboBox.filteredItems = filteredItems; } @@ -204,11 +222,11 @@ export class HaCategoryPicker extends SubscribeMixin(LitElement) { ev.stopPropagation(); let newValue = ev.detail.value; - if (newValue === "no_categories") { + if (newValue === NO_CATEGORIES_ID) { newValue = ""; } - if (!["add_new_suggestion", "add_new"].includes(newValue)) { + if (![ADD_NEW_SUGGESTION_ID, ADD_NEW_ID].includes(newValue)) { if (newValue !== this._value) { this._setValue(newValue); } @@ -216,54 +234,30 @@ export class HaCategoryPicker extends SubscribeMixin(LitElement) { } (ev.target as any).value = this._value; - showPromptDialog(this, { - title: this.hass.localize( - "ui.components.category-picker.add_dialog.title" - ), - text: this.hass.localize("ui.components.category-picker.add_dialog.text"), - confirmText: this.hass.localize( - "ui.components.category-picker.add_dialog.add" - ), - inputLabel: this.hass.localize( - "ui.components.category-picker.add_dialog.name" - ), - defaultValue: - newValue === "add_new_suggestion" ? this._suggestion : undefined, - confirm: async (name) => { - if (!name) { - return; - } - try { - const category = await createCategoryRegistryEntry( - this.hass, - this.scope!, - { - name, - } - ); - this._categories = [...this._categories!, category]; - this.comboBox.filteredItems = this._getCategories( - this._categories, - this.noAdd - ); - await this.updateComplete; - await this.comboBox.updateComplete; - this._setValue(category.category_id); - } catch (err: any) { - showAlertDialog(this, { - title: this.hass.localize( - "ui.components.category-picker.add_dialog.failed_create_category" - ), - text: err.message, - }); - } - }, - cancel: () => { - this._setValue(undefined); - this._suggestion = undefined; - this.comboBox.setInputValue(""); + + showCategoryRegistryDetailDialog(this, { + scope: this.scope!, + suggestedName: newValue === ADD_NEW_SUGGESTION_ID ? this._suggestion : "", + createEntry: async (values) => { + const category = await createCategoryRegistryEntry( + this.hass, + this.scope!, + values + ); + this._categories = [...this._categories!, category]; + this.comboBox.filteredItems = this._getCategories( + this._categories, + this.noAdd + ); + await this.updateComplete; + await this.comboBox.updateComplete; + this._setValue(category.category_id); + return category; }, }); + + this._suggestion = undefined; + this.comboBox.setInputValue(""); } private _setValue(value?: string) { diff --git a/src/panels/config/category/show-dialog-category-registry-detail.ts b/src/panels/config/category/show-dialog-category-registry-detail.ts index 00ac4686e8..7adaff611f 100644 --- a/src/panels/config/category/show-dialog-category-registry-detail.ts +++ b/src/panels/config/category/show-dialog-category-registry-detail.ts @@ -1,9 +1,19 @@ import { fireEvent } from "../../../common/dom/fire_event"; -import { CategoryRegistryEntry } from "../../../data/category_registry"; +import { + CategoryRegistryEntry, + CategoryRegistryEntryMutableParams, +} from "../../../data/category_registry"; export interface CategoryRegistryDetailDialogParams { entry?: CategoryRegistryEntry; scope: string; + suggestedName?: string; + createEntry?: ( + values: CategoryRegistryEntryMutableParams + ) => Promise; + updateEntry?: ( + updates: Partial + ) => Promise; } export const loadCategoryRegistryDetailDialog = () => From d3e62454a5c0b9991694875c12911b74fe516111 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 28 Mar 2024 16:35:56 +0100 Subject: [PATCH 7/8] Add default icons for area and floors (#20214) * Add default icons for area and floors * Add default icon for floor based on level * Allow deleting floor level * Use texture box for area --------- Co-authored-by: Paul Bottein --- src/components/ha-area-filter.ts | 6 +- src/components/ha-area-floor-picker.ts | 24 ++++++-- src/components/ha-area-picker.ts | 7 ++- src/components/ha-filter-floor-areas.ts | 19 ++++--- src/components/ha-floor-icon.ts | 56 +++++++++++++++++++ src/components/ha-floor-picker.ts | 24 ++++---- src/components/ha-icon-picker.ts | 2 +- src/components/ha-target-picker.ts | 39 ++++++------- src/data/floor_registry.ts | 4 +- .../areas/dialog-floor-registry-detail.ts | 17 +++++- .../config/areas/ha-config-areas-dashboard.ts | 9 ++- 11 files changed, 146 insertions(+), 61 deletions(-) create mode 100644 src/components/ha-floor-icon.ts diff --git a/src/components/ha-area-filter.ts b/src/components/ha-area-filter.ts index 9640f60e5e..504b4bf9f4 100644 --- a/src/components/ha-area-filter.ts +++ b/src/components/ha-area-filter.ts @@ -1,12 +1,12 @@ -import { mdiSofa } from "@mdi/js"; +import { mdiTextureBox } from "@mdi/js"; import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../common/dom/fire_event"; import { showAreaFilterDialog } from "../dialogs/area-filter/show-area-filter-dialog"; import { HomeAssistant } from "../types"; +import "./ha-icon-next"; import "./ha-svg-icon"; import "./ha-textfield"; -import "./ha-icon-next"; export type AreaFilterValue = { hidden?: string[]; @@ -51,7 +51,7 @@ export class HaAreaPicker extends LitElement { @keydown=${this._edit} .disabled=${this.disabled} > - + ${this.label} ${description} = (item) => @@ -49,9 +52,14 @@ const rowRenderer: ComboBoxLitRenderer = (item) => ? "--mdc-list-side-padding-left: 48px;" : ""} > - ${item.icon - ? html`` - : nothing} + ${item.type === "floor" + ? html`` + : item.icon + ? html`` + : html``} ${item.name} `; @@ -165,6 +173,7 @@ export class HaAreaFloorPicker extends SubscribeMixin(LitElement) { name: this.hass.localize("ui.components.area-picker.no_areas"), icon: null, strings: [], + level: null, }, ]; } @@ -316,6 +325,7 @@ export class HaAreaFloorPicker extends SubscribeMixin(LitElement) { name: this.hass.localize("ui.components.area-picker.no_match"), icon: null, strings: [], + level: null, }, ]; } @@ -350,6 +360,7 @@ export class HaAreaFloorPicker extends SubscribeMixin(LitElement) { name: floor.name, icon: floor.icon, strings: [floor.floor_id, ...floor.aliases, floor.name], + level: floor.level, }); } output.push( @@ -360,6 +371,7 @@ export class HaAreaFloorPicker extends SubscribeMixin(LitElement) { icon: area.icon, strings: [area.area_id, ...area.aliases, area.name], hasFloor: true, + level: null, })) ); }); @@ -373,6 +385,7 @@ export class HaAreaFloorPicker extends SubscribeMixin(LitElement) { ), icon: null, strings: [], + level: null, }); } @@ -383,6 +396,7 @@ export class HaAreaFloorPicker extends SubscribeMixin(LitElement) { name: area.name, icon: area.icon, strings: [area.area_id, ...area.aliases, area.name], + level: null, })) ); diff --git a/src/components/ha-area-picker.ts b/src/components/ha-area-picker.ts index 3f6301060c..a7b404f29a 100644 --- a/src/components/ha-area-picker.ts +++ b/src/components/ha-area-picker.ts @@ -1,14 +1,15 @@ +import { mdiTextureBox } from "@mdi/js"; import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit"; import { HassEntity } from "home-assistant-js-websocket"; -import { html, LitElement, nothing, PropertyValues, TemplateResult } from "lit"; +import { LitElement, PropertyValues, TemplateResult, html } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import memoizeOne from "memoize-one"; import { fireEvent } from "../common/dom/fire_event"; import { computeDomain } from "../common/entity/compute_domain"; import { - fuzzyFilterSort, ScorableTextItem, + fuzzyFilterSort, } from "../common/string/filter/sequence-matching"; import { AreaRegistryEntry, @@ -41,7 +42,7 @@ const rowRenderer: ComboBoxLitRenderer = (item) => > ${item.icon ? html`` - : nothing} + : html``} ${item.name} `; diff --git a/src/components/ha-filter-floor-areas.ts b/src/components/ha-filter-floor-areas.ts index a90fba10f0..2ee817c370 100644 --- a/src/components/ha-filter-floor-areas.ts +++ b/src/components/ha-filter-floor-areas.ts @@ -1,4 +1,5 @@ import "@material/mwc-menu/mwc-menu-surface"; +import { mdiTextureBox } from "@mdi/js"; import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; @@ -15,6 +16,9 @@ import { SubscribeMixin } from "../mixins/subscribe-mixin"; import { haStyleScrollbar } from "../resources/styles"; import type { HomeAssistant } from "../types"; import "./ha-check-list-item"; +import "./ha-floor-icon"; +import "./ha-icon"; +import "./ha-svg-icon"; @customElement("ha-filter-floor-areas") export class HaFilterFloorAreas extends SubscribeMixin(LitElement) { @@ -70,12 +74,10 @@ export class HaFilterFloorAreas extends SubscribeMixin(LitElement) { graphic="icon" @request-selected=${this._handleItemClick} > - ${floor.icon - ? html`` - : nothing} + ${floor.name} ${repeat( @@ -108,7 +110,10 @@ export class HaFilterFloorAreas extends SubscribeMixin(LitElement) { > ${area.icon ? html`` - : nothing} + : html``} ${area.name} `; } diff --git a/src/components/ha-floor-icon.ts b/src/components/ha-floor-icon.ts new file mode 100644 index 0000000000..edb840a57c --- /dev/null +++ b/src/components/ha-floor-icon.ts @@ -0,0 +1,56 @@ +import { + mdiHome, + mdiHomeFloor0, + mdiHomeFloor1, + mdiHomeFloor2, + mdiHomeFloor3, + mdiHomeFloorNegative1, +} from "@mdi/js"; +import { LitElement, html } from "lit"; +import { customElement, property } from "lit/decorators"; +import { FloorRegistryEntry } from "../data/floor_registry"; +import "./ha-icon"; +import "./ha-svg-icon"; + +export const floorDefaultIconPath = ( + floor: Pick +) => { + switch (floor.level) { + case 0: + return mdiHomeFloor0; + case 1: + return mdiHomeFloor1; + case 2: + return mdiHomeFloor2; + case 3: + return mdiHomeFloor3; + case -1: + return mdiHomeFloorNegative1; + } + return mdiHome; +}; + +@customElement("ha-floor-icon") +export class HaFloorIcon extends LitElement { + @property({ attribute: false }) public floor!: Pick< + FloorRegistryEntry, + "icon" | "level" + >; + + @property() public icon?: string; + + protected render() { + if (this.floor.icon) { + return html``; + } + const defaultPath = floorDefaultIconPath(this.floor); + + return html``; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-floor-icon": HaFloorIcon; + } +} diff --git a/src/components/ha-floor-picker.ts b/src/components/ha-floor-picker.ts index b344057543..1c6be3bf28 100644 --- a/src/components/ha-floor-picker.ts +++ b/src/components/ha-floor-picker.ts @@ -1,14 +1,14 @@ import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit"; import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket"; -import { html, LitElement, nothing, PropertyValues, TemplateResult } from "lit"; +import { LitElement, PropertyValues, TemplateResult, html } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import memoizeOne from "memoize-one"; import { fireEvent } from "../common/dom/fire_event"; import { computeDomain } from "../common/entity/compute_domain"; import { - fuzzyFilterSort, ScorableTextItem, + fuzzyFilterSort, } from "../common/string/filter/sequence-matching"; import { AreaRegistryEntry } from "../data/area_registry"; import { @@ -17,24 +17,24 @@ import { getDeviceEntityDisplayLookup, } from "../data/device_registry"; import { EntityRegistryDisplayEntry } from "../data/entity_registry"; +import { + FloorRegistryEntry, + createFloorRegistryEntry, + getFloorAreaLookup, + subscribeFloorRegistry, +} from "../data/floor_registry"; import { showAlertDialog, showPromptDialog, } from "../dialogs/generic/show-dialog-box"; +import { SubscribeMixin } from "../mixins/subscribe-mixin"; import { HomeAssistant, ValueChangedEvent } from "../types"; import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker"; import "./ha-combo-box"; import type { HaComboBox } from "./ha-combo-box"; +import "./ha-floor-icon"; import "./ha-icon-button"; import "./ha-list-item"; -import "./ha-svg-icon"; -import { SubscribeMixin } from "../mixins/subscribe-mixin"; -import { - createFloorRegistryEntry, - FloorRegistryEntry, - getFloorAreaLookup, - subscribeFloorRegistry, -} from "../data/floor_registry"; type ScorableFloorRegistryEntry = ScorableTextItem & FloorRegistryEntry; @@ -43,9 +43,7 @@ const rowRenderer: ComboBoxLitRenderer = (item) => graphic="icon" class=${classMap({ "add-new": item.floor_id === "add_new" })} > - ${item.icon - ? html`` - : nothing} + ${item.name} `; diff --git a/src/components/ha-icon-picker.ts b/src/components/ha-icon-picker.ts index 871adf5380..e148ff86bd 100644 --- a/src/components/ha-icon-picker.ts +++ b/src/components/ha-icon-picker.ts @@ -118,7 +118,7 @@ export class HaIconPicker extends LitElement { ` - : html``} + : html``} `; } diff --git a/src/components/ha-target-picker.ts b/src/components/ha-target-picker.ts index 608a4ba829..21d32d3ea3 100644 --- a/src/components/ha-target-picker.ts +++ b/src/components/ha-target-picker.ts @@ -6,10 +6,10 @@ import "@material/mwc-menu/mwc-menu-surface"; import { mdiClose, mdiDevices, - mdiFloorPlan, + mdiHome, mdiLabel, mdiPlus, - mdiSofa, + mdiTextureBox, mdiUnfoldMoreVertical, } from "@mdi/js"; import { ComboBoxLightOpenedChangedEvent } from "@vaadin/combo-box/vaadin-combo-box-light"; @@ -18,30 +18,23 @@ import { HassServiceTarget, UnsubscribeFunc, } from "home-assistant-js-websocket"; -import { css, CSSResultGroup, html, LitElement, nothing, unsafeCSS } from "lit"; +import { CSSResultGroup, LitElement, css, html, nothing, unsafeCSS } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { ensureArray } from "../common/array/ensure-array"; +import { computeCssColor } from "../common/color/compute-color"; +import { hex2rgb } from "../common/color/convert-color"; import { fireEvent } from "../common/dom/fire_event"; import { stopPropagation } from "../common/dom/stop_propagation"; import { computeDomain } from "../common/entity/compute_domain"; import { computeStateName } from "../common/entity/compute_state_name"; import { isValidEntityId } from "../common/entity/valid_entity_id"; +import { AreaRegistryEntry } from "../data/area_registry"; import { - computeDeviceName, DeviceRegistryEntry, + computeDeviceName, } from "../data/device_registry"; import { EntityRegistryDisplayEntry } from "../data/entity_registry"; -import { HomeAssistant } from "../types"; -import "./device/ha-device-picker"; -import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker"; -import "./entity/ha-entity-picker"; -import type { HaEntityPickerEntityFilterFunc } from "./entity/ha-entity-picker"; -import "./ha-area-floor-picker"; -import "./ha-icon-button"; -import "./ha-input-helper-text"; -import "./ha-svg-icon"; -import { SubscribeMixin } from "../mixins/subscribe-mixin"; import { FloorRegistryEntry, subscribeFloorRegistry, @@ -50,9 +43,17 @@ import { LabelRegistryEntry, subscribeLabelRegistry, } from "../data/label_registry"; -import { computeCssColor } from "../common/color/compute-color"; -import { AreaRegistryEntry } from "../data/area_registry"; -import { hex2rgb } from "../common/color/convert-color"; +import { SubscribeMixin } from "../mixins/subscribe-mixin"; +import { HomeAssistant } from "../types"; +import "./device/ha-device-picker"; +import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker"; +import "./entity/ha-entity-picker"; +import type { HaEntityPickerEntityFilterFunc } from "./entity/ha-entity-picker"; +import "./ha-area-floor-picker"; +import { floorDefaultIconPath } from "./ha-floor-icon"; +import "./ha-icon-button"; +import "./ha-input-helper-text"; +import "./ha-svg-icon"; @customElement("ha-target-picker") export class HaTargetPicker extends SubscribeMixin(LitElement) { @@ -138,7 +139,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) { floor?.name || floor_id, undefined, floor?.icon, - mdiFloorPlan + floor ? floorDefaultIconPath(floor) : mdiHome ); }) : ""} @@ -151,7 +152,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) { area?.name || area_id, undefined, area?.icon, - mdiSofa + mdiTextureBox ); }) : nothing} diff --git a/src/data/floor_registry.ts b/src/data/floor_registry.ts index 32de014100..c69e31cc51 100644 --- a/src/data/floor_registry.ts +++ b/src/data/floor_registry.ts @@ -1,16 +1,16 @@ import { Connection, createCollection } from "home-assistant-js-websocket"; import { Store } from "home-assistant-js-websocket/dist/store"; import { stringCompare } from "../common/string/compare"; +import { debounce } from "../common/util/debounce"; import { HomeAssistant } from "../types"; import { AreaRegistryEntry } from "./area_registry"; -import { debounce } from "../common/util/debounce"; export { subscribeAreaRegistry } from "./ws-area_registry"; export interface FloorRegistryEntry { floor_id: string; name: string; - level: number; + level: number | null; icon: string | null; aliases: string[]; } diff --git a/src/panels/config/areas/dialog-floor-registry-detail.ts b/src/panels/config/areas/dialog-floor-registry-detail.ts index eabd0c03b2..421c996b0c 100644 --- a/src/panels/config/areas/dialog-floor-registry-detail.ts +++ b/src/panels/config/areas/dialog-floor-registry-detail.ts @@ -6,10 +6,11 @@ import { fireEvent } from "../../../common/dom/fire_event"; import "../../../components/ha-alert"; import "../../../components/ha-aliases-editor"; import { createCloseHeading } from "../../../components/ha-dialog"; +import "../../../components/ha-icon-picker"; import "../../../components/ha-picture-upload"; import "../../../components/ha-settings-row"; +import "../../../components/ha-svg-icon"; import "../../../components/ha-textfield"; -import "../../../components/ha-icon-picker"; import { FloorRegistryEntryMutableParams } from "../../../data/floor_registry"; import { haStyleDialog } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; @@ -56,6 +57,7 @@ class DialogFloorDetail extends LitElement { } const entry = this._params.entry; const nameInvalid = !this._isNameValid(); + return html` + > + ${!this._icon + ? html` + + ` + : nothing} +

${this.hass.localize( @@ -157,7 +168,7 @@ class DialogFloorDetail extends LitElement { private _levelChanged(ev) { this._error = undefined; - this._level = Number(ev.target.value); + this._level = ev.target.value === "" ? null : Number(ev.target.value); } private _iconChanged(ev) { diff --git a/src/panels/config/areas/ha-config-areas-dashboard.ts b/src/panels/config/areas/ha-config-areas-dashboard.ts index 02e96bc9c2..e322274d34 100644 --- a/src/panels/config/areas/ha-config-areas-dashboard.ts +++ b/src/panels/config/areas/ha-config-areas-dashboard.ts @@ -1,3 +1,4 @@ +import { ActionDetail } from "@material/mwc-list"; import { mdiDelete, mdiDotsVertical, @@ -17,9 +18,9 @@ import { import { customElement, property, state } from "lit/decorators"; import { styleMap } from "lit/directives/style-map"; import memoizeOne from "memoize-one"; -import { ActionDetail } from "@material/mwc-list"; import { formatListWithAnds } from "../../../common/string/format-list"; import "../../../components/ha-fab"; +import "../../../components/ha-floor-icon"; import "../../../components/ha-icon-button"; import "../../../components/ha-svg-icon"; import { @@ -39,6 +40,7 @@ import { showConfirmationDialog, } from "../../../dialogs/generic/show-dialog-box"; import "../../../layouts/hass-tabs-subpage"; +import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { HomeAssistant, Route } from "../../../types"; import "../ha-config-section"; import { configSections } from "../ha-panel-config"; @@ -47,7 +49,6 @@ import { showAreaRegistryDetailDialog, } from "./show-dialog-area-registry-detail"; import { showFloorRegistryDetailDialog } from "./show-dialog-floor-registry-detail"; -import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; @customElement("ha-config-areas-dashboard") export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) { @@ -154,9 +155,7 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) { html`

- ${floor.icon - ? html`` - : nothing} + ${floor.name}

Date: Thu, 28 Mar 2024 16:45:12 +0100 Subject: [PATCH 8/8] Bumped version to 20240328.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0bbc99fe3f..608b3b0e16 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20240327.0" +version = "20240328.0" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md"