diff --git a/pyproject.toml b/pyproject.toml index 0bbc99fe3f7f..608b3b0e16c2 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" diff --git a/src/components/data-table/ha-data-table-labels.ts b/src/components/data-table/ha-data-table-labels.ts index 7156d6d4ca49..1c47f0bf64d3 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-area-filter.ts b/src/components/ha-area-filter.ts index 9640f60e5ee0..504b4bf9f4f1 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 3f6301060c09..a7b404f29abe 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-categories.ts b/src/components/ha-filter-categories.ts index 4824305fa95d..9b5a7c539e18 100644 --- a/src/components/ha-filter-categories.ts +++ b/src/components/ha-filter-categories.ts @@ -1,13 +1,21 @@ 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"; 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"; @@ -17,6 +25,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 +99,13 @@ export class HaFilterCategories extends SubscribeMixin(LitElement) { slot="graphic" .icon=${category.icon} >` - : nothing} + : html``} ${category.name} cat.category_id === id), + updateEntry: (updates) => + updateCategoryRegistryEntry(this.hass, this.scope!, id, updates), }); } @@ -195,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/components/ha-filter-entities.ts b/src/components/ha-filter-entities.ts index 94c98b87e855..c2948451d91a 100644 --- a/src/components/ha-filter-entities.ts +++ b/src/components/ha-filter-entities.ts @@ -206,6 +206,7 @@ export class HaFilterEntities extends LitElement { color: var(--text-accent-color, var(--text-primary-color)); } ha-check-list-item { + --mdc-list-item-graphic-margin: 16px; width: 100%; } `, diff --git a/src/components/ha-filter-floor-areas.ts b/src/components/ha-filter-floor-areas.ts index a4a67869fc76..2ee817c370c9 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} `; } @@ -268,6 +273,9 @@ export class HaFilterFloorAreas extends SubscribeMixin(LitElement) { padding: 0px 2px; color: var(--text-accent-color, var(--text-primary-color)); } + ha-check-list-item { + --mdc-list-item-graphic-margin: 16px; + } .floor { padding-left: 32px; padding-inline-start: 32px; diff --git a/src/components/ha-filter-labels.ts b/src/components/ha-filter-labels.ts index 5b816272cd5e..317ed7f57f79 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-floor-icon.ts b/src/components/ha-floor-icon.ts new file mode 100644 index 000000000000..edb840a57c58 --- /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 b3440575436f..1c6be3bf28b7 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 871adf5380be..e148ff86bdf5 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-label-picker.ts b/src/components/ha-label-picker.ts index 222f660162f7..734df1081f9b 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]; diff --git a/src/components/ha-labels-picker.ts b/src/components/ha-labels-picker.ts index a011499120a6..ccf4511a6119 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; } `; diff --git a/src/components/ha-target-picker.ts b/src/components/ha-target-picker.ts index 608a4ba82995..21d32d3ea3bc 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,41 +18,42 @@ 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 { + FloorRegistryEntry, + subscribeFloorRegistry, +} from "../data/floor_registry"; +import { + LabelRegistryEntry, + subscribeLabelRegistry, +} from "../data/label_registry"; +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"; -import { SubscribeMixin } from "../mixins/subscribe-mixin"; -import { - FloorRegistryEntry, - subscribeFloorRegistry, -} from "../data/floor_registry"; -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"; @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 32de01410057..c69e31cc51cd 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/data/script.ts b/src/data/script.ts index 55e154a82618..942cf0fa8445 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( diff --git a/src/panels/config/areas/dialog-floor-registry-detail.ts b/src/panels/config/areas/dialog-floor-registry-detail.ts index eabd0c03b207..421c996b0c8f 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 02e96bc9c20a..e322274d341c 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}

{ 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 f07fdfcc5b11..529db65b2ce3 100644 --- a/src/panels/config/category/ha-category-picker.ts +++ b/src/panels/config/category/ha-category-picker.ts @@ -1,3 +1,4 @@ +import { mdiTag } from "@mdi/js"; import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit"; import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { html, LitElement, nothing, PropertyValues } from "lit"; @@ -19,23 +20,24 @@ 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`` - : nothing} + : html``} ${item.name} `; @@ -102,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" ), @@ -115,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", }, @@ -129,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; } @@ -174,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; } @@ -203,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); } @@ -215,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 00ac4686e8cf..7adaff611fe1 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 = () =>