From 44f5f7bdb52f1823fb18c920e834976003feb619 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Mon, 5 May 2025 11:12:43 +0200 Subject: [PATCH] Use new entity picker style in quick bar (#25265) * Use new entity picker style in quick bar * Cleanup * Add missing no area * Process code review --- src/dialogs/quick-bar/ha-quick-bar.ts | 162 ++++++++++++++++++++------ 1 file changed, 124 insertions(+), 38 deletions(-) diff --git a/src/dialogs/quick-bar/ha-quick-bar.ts b/src/dialogs/quick-bar/ha-quick-bar.ts index 828397f353..cf57a14e31 100644 --- a/src/dialogs/quick-bar/ha-quick-bar.ts +++ b/src/dialogs/quick-bar/ha-quick-bar.ts @@ -15,24 +15,26 @@ import { customElement, property, query, state } from "lit/decorators"; import { ifDefined } from "lit/directives/if-defined"; import { styleMap } from "lit/directives/style-map"; import memoizeOne from "memoize-one"; +import Fuse from "fuse.js"; import { canShowPage } from "../../common/config/can_show_page"; import { componentsWithService } from "../../common/config/components_with_service"; import { isComponentLoaded } from "../../common/config/is_component_loaded"; import { fireEvent } from "../../common/dom/fire_event"; -import { computeDeviceNameDisplay } from "../../common/entity/compute_device_name"; -import { computeStateName } from "../../common/entity/compute_state_name"; +import { + computeDeviceName, + computeDeviceNameDisplay, +} from "../../common/entity/compute_device_name"; import { navigate } from "../../common/navigate"; import { caseInsensitiveStringCompare } from "../../common/string/compare"; import type { ScorableTextItem } from "../../common/string/filter/sequence-matching"; -import { fuzzyFilterSort } from "../../common/string/filter/sequence-matching"; import { debounce } from "../../common/util/debounce"; import "../../components/ha-icon-button"; import "../../components/ha-label"; import "../../components/ha-list"; -import "../../components/ha-list-item"; import "../../components/ha-spinner"; import "../../components/ha-textfield"; import "../../components/ha-tip"; +import "../../components/ha-md-list-item"; import { fetchHassioAddonsInfo } from "../../data/hassio/addon"; import { domainToName } from "../../data/integration"; import { getPanelNameTranslationKey } from "../../data/panel"; @@ -44,6 +46,13 @@ import type { HomeAssistant } from "../../types"; import { showConfirmationDialog } from "../generic/show-dialog-box"; import { showShortcutsDialog } from "../shortcuts/show-shortcuts-dialog"; import { QuickBarMode, type QuickBarParams } from "./show-dialog-quick-bar"; +import { getEntityContext } from "../../common/entity/context/get_entity_context"; +import { computeEntityName } from "../../common/entity/compute_entity_name"; +import { computeAreaName } from "../../common/entity/compute_area_name"; +import { computeRTL } from "../../common/util/compute_rtl"; +import { computeDomain } from "../../common/entity/compute_domain"; +import { computeStateName } from "../../common/entity/compute_state_name"; +import { HaFuse } from "../../resources/fuse"; interface QuickBarItem extends ScorableTextItem { primaryText: string; @@ -59,6 +68,9 @@ interface CommandItem extends QuickBarItem { interface EntityItem extends QuickBarItem { altText: string; icon?: TemplateResult; + translatedDomain: string; + entityId: string; + friendlyName: string; } interface DeviceItem extends QuickBarItem { @@ -82,6 +94,23 @@ type BaseNavigationCommand = Pick< QuickBarNavigationItem, "primaryText" | "path" >; + +const DOMAIN_STYLE = styleMap({ + fontSize: "var(--ha-font-size-s)", + fontWeight: "var(--ha-font-weight-normal)", + lineHeight: "var(--ha-line-height-normal)", + alignSelf: "flex-end", + maxWidth: "30%", + textOverflow: "ellipsis", + overflow: "hidden", + whiteSpace: "nowrap", +}); + +const ENTITY_ID_STYLE = styleMap({ + fontFamily: "var(--ha-font-family-code)", + fontSize: "var(--ha-font-size-xs)", +}); + @customElement("ha-quick-bar") export class QuickBar extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -139,6 +168,11 @@ export class QuickBar extends LitElement { } } + protected firstUpdated(changedProps) { + super.firstUpdated(changedProps); + this.hass.loadBackendTranslation("title"); + } + private _getItems = memoizeOne( ( mode: QuickBarMode, @@ -323,61 +357,65 @@ export class QuickBar extends LitElement { private _renderDeviceItem(item: DeviceItem, index?: number) { return html` - - ${item.primaryText} + ${item.primaryText} ${item.area - ? html` - ${item.area} - ` + ? html` ${item.area} ` : nothing} - + `; } private _renderEntityItem(item: EntityItem, index?: number) { + const showEntityId = this.hass.userData?.showEntityIdPicker; + return html` - ${item.iconPath ? html` ` - : html`${item.icon}`} - ${item.primaryText} + : html`${item.icon}`} + ${item.primaryText} ${item.altText - ? html` - ${item.altText} - ` + ? html` ${item.altText} ` : nothing} - + ${item.entityId && showEntityId + ? html`${item.entityId}` + : nothing} + ${item.translatedDomain && !showEntityId + ? html`
+ ${item.translatedDomain} +
` + : nothing} + `; } private _renderCommandItem(item: CommandItem, index?: number) { return html` - ${item.iconPath ? html` - + ` : nothing} ${item.categoryText} @@ -394,7 +435,7 @@ export class QuickBar extends LitElement { ${item.primaryText} - + `; } @@ -421,7 +462,7 @@ export class QuickBar extends LitElement { } private _getItemAtIndex(index: number): ListItem | null { - return this.renderRoot.querySelector(`ha-list-item[index="${index}"]`); + return this.renderRoot.querySelector(`ha-md-list-item[index="${index}"]`); } private _addSpinnerToCommandItem(index: number): void { @@ -519,7 +560,7 @@ export class QuickBar extends LitElement { } private _handleItemClick(ev) { - const listItem = ev.target.closest("ha-list-item"); + const listItem = ev.target.closest("ha-md-list-item"); this._processItemAndCloseDialog( listItem.item, Number(listItem.getAttribute("index")) @@ -555,18 +596,43 @@ export class QuickBar extends LitElement { } private _generateEntityItems(): EntityItem[] { + const isRTL = computeRTL(this.hass); + return Object.keys(this.hass.states) .map((entityId) => { - const entityState = this.hass.states[entityId]; + const stateObj = this.hass.states[entityId]; + + const { area, device } = getEntityContext(stateObj, this.hass); + + const friendlyName = computeStateName(stateObj); // Keep this for search + const entityName = computeEntityName(stateObj, this.hass); + const deviceName = device ? computeDeviceName(device) : undefined; + const areaName = area ? computeAreaName(area) : undefined; + + const primary = entityName || deviceName || entityId; + const secondary = [areaName, entityName ? deviceName : undefined] + .filter(Boolean) + .join(isRTL ? " ◂ " : " ▸ "); + + const translatedDomain = domainToName( + this.hass.localize, + computeDomain(entityId) + ); + const entityItem = { - primaryText: computeStateName(entityState), - altText: entityId, + primaryText: primary, + altText: + secondary || + this.hass.localize("ui.components.device-picker.no_area"), icon: html` `, + translatedDomain: translatedDomain, + entityId: entityId, + friendlyName: friendlyName, action: () => fireEvent(this, "hass-more-info", { entityId }), }; @@ -846,9 +912,30 @@ export class QuickBar extends LitElement { }); } + private _fuseIndex = memoizeOne((items: QuickBarItem[]) => + Fuse.createIndex( + [ + "primaryText", + "altText", + "friendlyName", + "translatedDomain", + "entityId", // for technical search + ], + items + ) + ); + private _filterItems = memoizeOne( - (items: QuickBarItem[], filter: string): QuickBarItem[] => - fuzzyFilterSort(filter.trimLeft(), items) + (items: QuickBarItem[], filter: string): QuickBarItem[] => { + const index = this._fuseIndex(items); + const fuse = new HaFuse(items, {}, index); + + const results = fuse.multiTermsSearch(filter.trim()); + if (!results || !results.length) { + return items; + } + return results.map((result) => result.item); + } ); static get styles() { @@ -930,9 +1017,8 @@ export class QuickBar extends LitElement { direction: var(--direction); } - ha-list-item { + ha-md-list-item { width: 100%; - --mdc-list-item-graphic-margin: 20px; } ha-tip {