diff --git a/gallery/src/pages/automation/describe-action.ts b/gallery/src/pages/automation/describe-action.ts index 1a32ac0829..6465e9fc1e 100644 --- a/gallery/src/pages/automation/describe-action.ts +++ b/gallery/src/pages/automation/describe-action.ts @@ -136,7 +136,7 @@ export class DemoAutomationDescribeAction extends LitElement {
${this._action - ? describeAction(this.hass, this._action) + ? describeAction(this.hass, [], this._action) : ""} html`
- ${describeAction(this.hass, conf as any)} + ${describeAction(this.hass, [], conf as any)}
${dump(conf)}
` diff --git a/src/common/entity/compute_attribute_display.ts b/src/common/entity/compute_attribute_display.ts index f969646265..7cba3466f7 100644 --- a/src/common/entity/compute_attribute_display.ts +++ b/src/common/entity/compute_attribute_display.ts @@ -1,5 +1,5 @@ import { HassEntity } from "home-assistant-js-websocket"; -import { EntityRegistryEntry } from "../../data/entity_registry"; +import { EntityRegistryDisplayEntry } from "../../data/entity_registry"; import { HomeAssistant } from "../../types"; import { LocalizeFunc } from "../translations/localize"; import { computeDomain } from "./compute_domain"; @@ -15,7 +15,7 @@ export const computeAttributeValueDisplay = ( const attributeValue = value !== undefined ? value : stateObj.attributes[attribute]; const domain = computeDomain(entityId); - const entity = entities[entityId] as EntityRegistryEntry | undefined; + const entity = entities[entityId] as EntityRegistryDisplayEntry | undefined; const translationKey = entity?.translation_key; return ( @@ -38,7 +38,7 @@ export const computeAttributeNameDisplay = ( ): string => { const entityId = stateObj.entity_id; const domain = computeDomain(entityId); - const entity = entities[entityId] as EntityRegistryEntry | undefined; + const entity = entities[entityId] as EntityRegistryDisplayEntry | undefined; const translationKey = entity?.translation_key; return ( diff --git a/src/common/entity/compute_state_display.ts b/src/common/entity/compute_state_display.ts index c469585a9d..cb327710c2 100644 --- a/src/common/entity/compute_state_display.ts +++ b/src/common/entity/compute_state_display.ts @@ -1,6 +1,6 @@ import { HassEntity } from "home-assistant-js-websocket"; import { UNAVAILABLE, UNKNOWN } from "../../data/entity"; -import { EntityRegistryEntry } from "../../data/entity_registry"; +import { EntityRegistryDisplayEntry } from "../../data/entity_registry"; import { FrontendLocaleData } from "../../data/translation"; import { updateIsInstallingFromAttributes, @@ -49,7 +49,7 @@ export const computeStateDisplayFromEntityAttributes = ( return localize(`state.default.${state}`); } - const entity = entities[entityId] as EntityRegistryEntry | undefined; + const entity = entities[entityId] as EntityRegistryDisplayEntry | undefined; // Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber` if (isNumericFromAttributes(attributes)) { diff --git a/src/common/number/format_number.ts b/src/common/number/format_number.ts index 2e461ab822..3834ca1a65 100644 --- a/src/common/number/format_number.ts +++ b/src/common/number/format_number.ts @@ -2,7 +2,7 @@ import { HassEntity, HassEntityAttributeBase, } from "home-assistant-js-websocket"; -import { EntityRegistryEntry } from "../../data/entity_registry"; +import { EntityRegistryDisplayEntry } from "../../data/entity_registry"; import { FrontendLocaleData, NumberFormat } from "../../data/translation"; import { round } from "./round"; @@ -92,11 +92,9 @@ export const formatNumber = ( */ export const getNumberFormatOptions = ( entityState: HassEntity, - entity?: EntityRegistryEntry + entity?: EntityRegistryDisplayEntry ): Intl.NumberFormatOptions | undefined => { - const precision = - entity?.options?.sensor?.display_precision ?? - entity?.options?.sensor?.suggested_display_precision; + const precision = entity?.display_precision; if (precision != null) { return { maximumFractionDigits: precision, diff --git a/src/components/entity/ha-state-label-badge.ts b/src/components/entity/ha-state-label-badge.ts index 03079a331f..3f9262e781 100644 --- a/src/components/entity/ha-state-label-badge.ts +++ b/src/components/entity/ha-state-label-badge.ts @@ -22,7 +22,7 @@ import { isNumericState, } from "../../common/number/format_number"; import { isUnavailableState, UNAVAILABLE, UNKNOWN } from "../../data/entity"; -import { EntityRegistryEntry } from "../../data/entity_registry"; +import { EntityRegistryDisplayEntry } from "../../data/entity_registry"; import { timerTimeRemaining } from "../../data/timer"; import { HomeAssistant } from "../../types"; import "../ha-label-badge"; @@ -160,7 +160,7 @@ export class HaStateLabelBadge extends LitElement { private _computeValue( domain: string, entityState: HassEntity, - entry?: EntityRegistryEntry + entry?: EntityRegistryDisplayEntry ) { switch (domain) { case "alarm_control_panel": @@ -200,7 +200,7 @@ export class HaStateLabelBadge extends LitElement { private _computeShowIcon( domain: string, entityState: HassEntity, - entry?: EntityRegistryEntry + entry?: EntityRegistryDisplayEntry ): boolean { if (entityState.state === UNAVAILABLE) { return false; diff --git a/src/components/ha-area-picker.ts b/src/components/ha-area-picker.ts index 6f016cc1f7..471ce71401 100644 --- a/src/components/ha-area-picker.ts +++ b/src/components/ha-area-picker.ts @@ -12,10 +12,11 @@ import { createAreaRegistryEntry, } from "../data/area_registry"; import { - DeviceEntityLookup, + DeviceEntityDisplayLookup, DeviceRegistryEntry, + getDeviceEntityDisplayLookup, } from "../data/device_registry"; -import { EntityRegistryEntry } from "../data/entity_registry"; +import { EntityRegistryDisplayEntry } from "../data/entity_registry"; import { showAlertDialog, showPromptDialog, @@ -113,7 +114,7 @@ export class HaAreaPicker extends LitElement { ( areas: AreaRegistryEntry[], devices: DeviceRegistryEntry[], - entities: EntityRegistryEntry[], + entities: EntityRegistryDisplayEntry[], includeDomains: this["includeDomains"], excludeDomains: this["excludeDomains"], includeDeviceClasses: this["includeDeviceClasses"], @@ -133,9 +134,9 @@ export class HaAreaPicker extends LitElement { ]; } - const deviceEntityLookup: DeviceEntityLookup = {}; + let deviceEntityLookup: DeviceEntityDisplayLookup = {}; let inputDevices: DeviceRegistryEntry[] | undefined; - let inputEntities: EntityRegistryEntry[] | undefined; + let inputEntities: EntityRegistryDisplayEntry[] | undefined; if ( includeDomains || @@ -143,15 +144,7 @@ export class HaAreaPicker extends LitElement { includeDeviceClasses || entityFilter ) { - for (const entity of entities) { - if (!entity.device_id) { - continue; - } - if (!(entity.device_id in deviceEntityLookup)) { - deviceEntityLookup[entity.device_id] = []; - } - deviceEntityLookup[entity.device_id].push(entity); - } + deviceEntityLookup = getDeviceEntityDisplayLookup(entities); } inputDevices = devices; inputEntities = entities.filter((entity) => entity.area_id); diff --git a/src/components/ha-selector/ha-selector-area.ts b/src/components/ha-selector/ha-selector-area.ts index e8c2539295..54b91a662d 100644 --- a/src/components/ha-selector/ha-selector-area.ts +++ b/src/components/ha-selector/ha-selector-area.ts @@ -1,14 +1,10 @@ -import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket"; +import { HassEntity } from "home-assistant-js-websocket"; import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { ensureArray } from "../../common/array/ensure-array"; import type { DeviceRegistryEntry } from "../../data/device_registry"; import { getDeviceIntegrationLookup } from "../../data/device_registry"; -import { - EntityRegistryEntry, - subscribeEntityRegistry, -} from "../../data/entity_registry"; import { EntitySources, fetchEntitySourcesWithCache, @@ -18,13 +14,12 @@ import { filterSelectorDevices, filterSelectorEntities, } from "../../data/selector"; -import { SubscribeMixin } from "../../mixins/subscribe-mixin"; import { HomeAssistant } from "../../types"; import "../ha-area-picker"; import "../ha-areas-picker"; @customElement("ha-selector-area") -export class HaAreaSelector extends SubscribeMixin(LitElement) { +export class HaAreaSelector extends LitElement { @property() public hass!: HomeAssistant; @property() public selector!: AreaSelector; @@ -41,18 +36,8 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) { @state() private _entitySources?: EntitySources; - @state() private _entities?: EntityRegistryEntry[]; - private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup); - public hassSubscribe(): UnsubscribeFunc[] { - return [ - subscribeEntityRegistry(this.hass.connection!, (entities) => { - this._entities = entities.filter((entity) => entity.device_id !== null); - }), - ]; - } - private _hasIntegration(selector: AreaSelector) { return ( (selector.area?.entity && @@ -127,10 +112,12 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) { return true; } - const deviceIntegrations = - this._entitySources && this._entities - ? this._deviceIntegrationLookup(this._entitySources, this._entities) - : undefined; + const deviceIntegrations = this._entitySources + ? this._deviceIntegrationLookup( + this._entitySources, + Object.values(this.hass.entities) + ) + : undefined; return ensureArray(this.selector.area.device).some((filter) => filterSelectorDevices(filter, device, deviceIntegrations) diff --git a/src/components/ha-selector/ha-selector-attribute.ts b/src/components/ha-selector/ha-selector-attribute.ts index da194bdf22..50e1367dbe 100644 --- a/src/components/ha-selector/ha-selector-attribute.ts +++ b/src/components/ha-selector/ha-selector-attribute.ts @@ -2,12 +2,11 @@ import { html, LitElement, PropertyValues } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; import { AttributeSelector } from "../../data/selector"; -import { SubscribeMixin } from "../../mixins/subscribe-mixin"; import { HomeAssistant } from "../../types"; import "../entity/ha-entity-attribute-picker"; @customElement("ha-selector-attribute") -export class HaSelectorAttribute extends SubscribeMixin(LitElement) { +export class HaSelectorAttribute extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public selector!: AttributeSelector; diff --git a/src/components/ha-selector/ha-selector-device.ts b/src/components/ha-selector/ha-selector-device.ts index db080201c0..80eb481a9a 100644 --- a/src/components/ha-selector/ha-selector-device.ts +++ b/src/components/ha-selector/ha-selector-device.ts @@ -1,14 +1,10 @@ -import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket"; +import { HassEntity } from "home-assistant-js-websocket"; import { html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { ensureArray } from "../../common/array/ensure-array"; import type { DeviceRegistryEntry } from "../../data/device_registry"; import { getDeviceIntegrationLookup } from "../../data/device_registry"; -import { - EntityRegistryEntry, - subscribeEntityRegistry, -} from "../../data/entity_registry"; import { EntitySources, fetchEntitySourcesWithCache, @@ -18,21 +14,18 @@ import { filterSelectorDevices, filterSelectorEntities, } from "../../data/selector"; -import { SubscribeMixin } from "../../mixins/subscribe-mixin"; import type { HomeAssistant } from "../../types"; import "../device/ha-device-picker"; import "../device/ha-devices-picker"; @customElement("ha-selector-device") -export class HaDeviceSelector extends SubscribeMixin(LitElement) { +export class HaDeviceSelector extends LitElement { @property() public hass!: HomeAssistant; @property() public selector!: DeviceSelector; @state() private _entitySources?: EntitySources; - @state() private _entities?: EntityRegistryEntry[]; - @property() public value?: any; @property() public label?: string; @@ -45,14 +38,6 @@ export class HaDeviceSelector extends SubscribeMixin(LitElement) { private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup); - public hassSubscribe(): UnsubscribeFunc[] { - return [ - subscribeEntityRegistry(this.hass.connection!, (entities) => { - this._entities = entities.filter((entity) => entity.device_id !== null); - }), - ]; - } - private _hasIntegration(selector: DeviceSelector) { return ( (selector.device?.filter && @@ -118,10 +103,12 @@ export class HaDeviceSelector extends SubscribeMixin(LitElement) { if (!this.selector.device?.filter) { return true; } - const deviceIntegrations = - this._entitySources && this._entities - ? this._deviceIntegrationLookup(this._entitySources, this._entities) - : undefined; + const deviceIntegrations = this._entitySources + ? this._deviceIntegrationLookup( + this._entitySources, + Object.values(this.hass.entities) + ) + : undefined; return ensureArray(this.selector.device.filter).some((filter) => filterSelectorDevices(filter, device, deviceIntegrations) diff --git a/src/components/ha-target-picker.ts b/src/components/ha-target-picker.ts index 200feca500..db1bd5705f 100644 --- a/src/components/ha-target-picker.ts +++ b/src/components/ha-target-picker.ts @@ -1,6 +1,7 @@ // @ts-ignore import chipStyles from "@material/chips/dist/mdc.chips.min.css"; import "@material/mwc-button/mwc-button"; +import "@material/mwc-menu/mwc-menu-surface"; import { mdiClose, mdiDevices, @@ -9,13 +10,14 @@ import { mdiUnfoldMoreVertical, } from "@mdi/js"; import "@polymer/paper-tooltip/paper-tooltip"; +import { ComboBoxLightOpenedChangedEvent } from "@vaadin/combo-box/vaadin-combo-box-light"; import { HassEntity, HassServiceTarget } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement, unsafeCSS } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; -import { ComboBoxLightOpenedChangedEvent } from "@vaadin/combo-box/vaadin-combo-box-light"; import { ensureArray } from "../common/array/ensure-array"; 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"; @@ -23,7 +25,7 @@ import { computeDeviceName, DeviceRegistryEntry, } from "../data/device_registry"; -import { EntityRegistryEntry } from "../data/entity_registry"; +import { EntityRegistryDisplayEntry } from "../data/entity_registry"; import { HomeAssistant } from "../types"; import "./device/ha-device-picker"; import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker"; @@ -33,8 +35,6 @@ import "./ha-area-picker"; import "./ha-icon-button"; import "./ha-input-helper-text"; import "./ha-svg-icon"; -import { stopPropagation } from "../common/dom/stop_propagation"; -import "@material/mwc-menu/mwc-menu-surface"; @customElement("ha-target-picker") export class HaTargetPicker extends LitElement { @@ -551,7 +551,7 @@ export class HaTargetPicker extends LitElement { return true; } - private _entityRegMeetsFilter(entity: EntityRegistryEntry): boolean { + private _entityRegMeetsFilter(entity: EntityRegistryDisplayEntry): boolean { if (entity.entity_category) { return false; } diff --git a/src/components/trace/hat-trace-timeline.ts b/src/components/trace/hat-trace-timeline.ts index 0174a12bf0..5c6cc532ec 100644 --- a/src/components/trace/hat-trace-timeline.ts +++ b/src/components/trace/hat-trace-timeline.ts @@ -6,6 +6,7 @@ import { mdiProgressWrench, mdiRecordCircleOutline, } from "@mdi/js"; +import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, CSSResultGroup, @@ -14,12 +15,16 @@ import { PropertyValues, TemplateResult, } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import { ifDefined } from "lit/directives/if-defined"; import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time"; import { relativeTime } from "../../common/datetime/relative_time"; import { fireEvent } from "../../common/dom/fire_event"; import { toggleAttribute } from "../../common/dom/toggle_attribute"; +import { + EntityRegistryEntry, + subscribeEntityRegistry, +} from "../../data/entity_registry"; import { LogbookEntry } from "../../data/logbook"; import { ChooseAction, @@ -193,6 +198,7 @@ class ActionRenderer { constructor( private hass: HomeAssistant, + private entityReg: EntityRegistryEntry[], private entries: TemplateResult[], private trace: AutomationTraceExtended, private logbookRenderer: LogbookRenderer, @@ -298,7 +304,7 @@ class ActionRenderer { this._renderEntry( path, - describeAction(this.hass, data, actionType), + describeAction(this.hass, this.entityReg, data, actionType), undefined, data.enabled === false ); @@ -441,7 +447,9 @@ class ActionRenderer { ) as RepeatAction; const disabled = repeatConfig.enabled === false; - const name = repeatConfig.alias || describeAction(this.hass, repeatConfig); + const name = + repeatConfig.alias || + describeAction(this.hass, this.entityReg, repeatConfig); this._renderEntry(repeatPath, name, undefined, disabled); @@ -577,6 +585,16 @@ export class HaAutomationTracer extends LitElement { @property({ type: Boolean }) public allowPick = false; + @state() private _entityReg: EntityRegistryEntry[] = []; + + public hassSubscribe(): UnsubscribeFunc[] { + return [ + subscribeEntityRegistry(this.hass.connection!, (entities) => { + this._entityReg = entities; + }), + ]; + } + protected render(): TemplateResult { if (!this.trace) { return html``; @@ -592,6 +610,7 @@ export class HaAutomationTracer extends LitElement { ); const actionRenderer = new ActionRenderer( this.hass, + this._entityReg, entries, this.trace, logbookRenderer, diff --git a/src/data/device_registry.ts b/src/data/device_registry.ts index bc1437adfa..37685de007 100644 --- a/src/data/device_registry.ts +++ b/src/data/device_registry.ts @@ -4,7 +4,10 @@ import { computeStateName } from "../common/entity/compute_state_name"; import { caseInsensitiveStringCompare } from "../common/string/compare"; import { debounce } from "../common/util/debounce"; import type { HomeAssistant } from "../types"; -import type { EntityRegistryEntry } from "./entity_registry"; +import type { + EntityRegistryDisplayEntry, + EntityRegistryEntry, +} from "./entity_registry"; import type { EntitySources } from "./entity_sources"; export interface DeviceRegistryEntry { @@ -25,6 +28,10 @@ export interface DeviceRegistryEntry { configuration_url: string | null; } +export interface DeviceEntityDisplayLookup { + [deviceId: string]: EntityRegistryDisplayEntry[]; +} + export interface DeviceEntityLookup { [deviceId: string]: EntityRegistryEntry[]; } @@ -147,9 +154,25 @@ export const getDeviceEntityLookup = ( return deviceEntityLookup; }; +export const getDeviceEntityDisplayLookup = ( + entities: EntityRegistryDisplayEntry[] +): DeviceEntityDisplayLookup => { + const deviceEntityLookup: DeviceEntityDisplayLookup = {}; + for (const entity of entities) { + if (!entity.device_id) { + continue; + } + if (!(entity.device_id in deviceEntityLookup)) { + deviceEntityLookup[entity.device_id] = []; + } + deviceEntityLookup[entity.device_id].push(entity); + } + return deviceEntityLookup; +}; + export const getDeviceIntegrationLookup = ( entitySources: EntitySources, - entities: EntityRegistryEntry[] + entities: EntityRegistryDisplayEntry[] ): Record => { const deviceIntegrations: Record = {}; diff --git a/src/data/entity_registry.ts b/src/data/entity_registry.ts index ede328f7e1..d0e2afb2cd 100644 --- a/src/data/entity_registry.ts +++ b/src/data/entity_registry.ts @@ -6,6 +6,35 @@ import { caseInsensitiveStringCompare } from "../common/string/compare"; import { debounce } from "../common/util/debounce"; import { HomeAssistant } from "../types"; +type entityCategory = "config" | "diagnostic"; + +export interface EntityRegistryDisplayEntry { + entity_id: string; + name?: string; + device_id?: string; + area_id?: string; + hidden?: boolean; + entity_category?: entityCategory; + translation_key?: string; + platform?: string; + display_precision?: number; +} + +interface EntityRegistryDisplayEntryResponse { + entities: { + ei: string; + di?: string; + ai?: string; + ec?: number; + en?: string; + pl?: string; + tk?: string; + hb?: boolean; + dp?: number; + }[]; + entity_categories: Record; +} + export interface EntityRegistryEntry { id: string; entity_id: string; @@ -17,7 +46,7 @@ export interface EntityRegistryEntry { area_id: string | null; disabled_by: "user" | "device" | "integration" | "config_entry" | null; hidden_by: Exclude; - entity_category: "config" | "diagnostic" | null; + entity_category: entityCategory | null; has_entity_name: boolean; original_name?: string; unique_id: string; @@ -154,6 +183,11 @@ export const fetchEntityRegistry = (conn: Connection) => type: "config/entity_registry/list", }); +export const fetchEntityRegistryDisplay = (conn: Connection) => + conn.sendMessagePromise({ + type: "config/entity_registry/list_for_display", + }); + const subscribeEntityRegistryUpdates = ( conn: Connection, store: Store @@ -182,6 +216,34 @@ export const subscribeEntityRegistry = ( onChange ); +const subscribeEntityRegistryDisplayUpdates = ( + conn: Connection, + store: Store +) => + conn.subscribeEvents( + debounce( + () => + fetchEntityRegistryDisplay(conn).then((entities) => + store.setState(entities, true) + ), + 500, + true + ), + "entity_registry_updated" + ); + +export const subscribeEntityRegistryDisplay = ( + conn: Connection, + onChange: (entities: EntityRegistryDisplayEntryResponse) => void +) => + createCollection( + "_entityRegistryDisplay", + fetchEntityRegistryDisplay, + subscribeEntityRegistryDisplayUpdates, + conn, + onChange + ); + export const sortEntityRegistryByName = ( entries: EntityRegistryEntry[], language: string @@ -190,10 +252,20 @@ export const sortEntityRegistryByName = ( caseInsensitiveStringCompare(entry1.name || "", entry2.name || "", language) ); +export const entityRegistryByEntityId = memoizeOne( + (entries: EntityRegistryEntry[]) => { + const entities: Record = {}; + for (const entity of entries) { + entities[entity.entity_id] = entity; + } + return entities; + } +); + export const entityRegistryById = memoizeOne( - (entries: HomeAssistant["entities"]) => { - const entities: HomeAssistant["entities"] = {}; - for (const entity of Object.values(entries)) { + (entries: EntityRegistryEntry[]) => { + const entities: Record = {}; + for (const entity of entries) { entities[entity.id] = entity; } return entities; diff --git a/src/data/script_i18n.ts b/src/data/script_i18n.ts index 1d99a9c2d5..8d3c1f88b2 100644 --- a/src/data/script_i18n.ts +++ b/src/data/script_i18n.ts @@ -11,6 +11,7 @@ import { computeDeviceName } from "./device_registry"; import { computeEntityRegistryName, entityRegistryById, + EntityRegistryEntry, } from "./entity_registry"; import { domainToName } from "./integration"; import { @@ -33,6 +34,7 @@ import { export const describeAction = ( hass: HomeAssistant, + entityRegistry: EntityRegistryEntry[], action: ActionTypes[T], actionType?: T, ignoreAlias = false @@ -91,7 +93,7 @@ export const describeAction = ( targets.push(targetThing); } } else { - const entityReg = entityRegistryById(hass.entities)[targetThing]; + const entityReg = entityRegistryById(entityRegistry)[targetThing]; if (entityReg) { targets.push( computeEntityRegistryName(hass, entityReg) || targetThing diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index 2cbf2bffc3..4f79d757c4 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -25,7 +25,11 @@ import "../../components/ha-icon-button"; import "../../components/ha-icon-button-prev"; import "../../components/ha-list-item"; import "../../components/ha-related-items"; -import { EntityRegistryEntry } from "../../data/entity_registry"; +import { + EntityRegistryEntry, + ExtEntityRegistryEntry, + getExtendedEntityRegistryEntry, +} from "../../data/entity_registry"; import { haStyleDialog } from "../../resources/styles"; import "../../state-summary/state-card-content"; import { HomeAssistant } from "../../types"; @@ -77,6 +81,8 @@ export class MoreInfoDialog extends LitElement { @state() private _childView?: ChildView; + @state() private _entry?: ExtEntityRegistryEntry; + public showDialog(params: MoreInfoDialogParams) { this._entityId = params.entityId; if (!this._entityId) { @@ -86,10 +92,22 @@ export class MoreInfoDialog extends LitElement { this._currView = params.view || "info"; this._childView = undefined; this.large = false; + this._loadEntityRegistryEntry(); + } + + private async _loadEntityRegistryEntry() { + if (!this._entityId) { + return; + } + this._entry = await getExtendedEntityRegistryEntry( + this.hass, + this._entityId + ); } public closeDialog() { this._entityId = undefined; + this._entry = undefined; this._childView = undefined; fireEvent(this, "dialog-closed", { dialog: this.localName }); } @@ -172,7 +190,10 @@ export class MoreInfoDialog extends LitElement { idToPassThroughUrl = stateObj.attributes.id; } if (EDITABLE_DOMAINS_WITH_UNIQUE_ID.includes(domain)) { - idToPassThroughUrl = this.hass.entities[this._entityId!].unique_id; + if (!this._entry) { + return; + } + idToPassThroughUrl = this._entry.unique_id; } navigate(`/config/${domain}/edit/${idToPassThroughUrl}`); @@ -358,6 +379,8 @@ export class MoreInfoDialog extends LitElement { ` : this._currView === "related" @@ -387,6 +410,10 @@ export class MoreInfoDialog extends LitElement { } } + private _entryUpdated(ev: CustomEvent) { + this._entry = ev.detail; + } + private _enlarge() { this.large = !this.large; } diff --git a/src/dialogs/more-info/ha-more-info-settings.ts b/src/dialogs/more-info/ha-more-info-settings.ts index 1f558305e1..54dbffcb4c 100644 --- a/src/dialogs/more-info/ha-more-info-settings.ts +++ b/src/dialogs/more-info/ha-more-info-settings.ts @@ -6,12 +6,11 @@ import { dynamicElement } from "../../common/dom/dynamic-element-directive"; import { EntityRegistryEntry, ExtEntityRegistryEntry, - getExtendedEntityRegistryEntry, } from "../../data/entity_registry"; import { PLATFORMS_WITH_SETTINGS_TAB } from "../../panels/config/entities/const"; +import "../../panels/config/entities/entity-registry-settings"; import type { HomeAssistant } from "../../types"; import { documentationUrl } from "../../util/documentation-url"; -import "../../panels/config/entities/entity-registry-settings"; @customElement("ha-more-info-settings") export class HaMoreInfoSettings extends LitElement { @@ -19,18 +18,18 @@ export class HaMoreInfoSettings extends LitElement { @property({ attribute: false }) public entityId!: string; - @state() private _entry?: EntityRegistryEntry | ExtEntityRegistryEntry | null; + @state() private entry?: EntityRegistryEntry | ExtEntityRegistryEntry | null; @state() private _settingsElementTag?: string; protected render() { // loading. - if (this._entry === undefined) { + if (this.entry === undefined) { return html``; } // No unique ID - if (this._entry === null) { + if (this.entry === null) { return html`
${this.hass.localize( @@ -54,53 +53,31 @@ export class HaMoreInfoSettings extends LitElement { } return html` -
- ${dynamicElement(this._settingsElementTag, { - hass: this.hass, - entry: this._entry, - entityId: this.entityId, - })} -
+ ${dynamicElement(this._settingsElementTag, { + hass: this.hass, + entry: this.entry, + entityId: this.entityId, + })} `; } - protected willUpdate(changedProps: PropertyValues) { - super.willUpdate(changedProps); - if (changedProps.has("entityId")) { - this._entry = undefined; - if (this.entityId) { - this._getEntityReg(); - } - } - } - - private async _getEntityReg() { - try { - this._entry = await getExtendedEntityRegistryEntry( - this.hass, - this.entityId - ); + public willUpdate(changedProps: PropertyValues) { + if (changedProps.has("entry")) { this._loadPlatformSettingTabs(); - } catch { - this._entry = null; } } - private _entryUpdated(ev: CustomEvent) { - this._entry = ev.detail; - } - private async _loadPlatformSettingTabs(): Promise { - if (!this._entry) { + if (!this.entry) { return; } if ( - !Object.keys(PLATFORMS_WITH_SETTINGS_TAB).includes(this._entry.platform) + !Object.keys(PLATFORMS_WITH_SETTINGS_TAB).includes(this.entry.platform) ) { this._settingsElementTag = "entity-registry-settings"; return; } - const tag = PLATFORMS_WITH_SETTINGS_TAB[this._entry.platform]; + const tag = PLATFORMS_WITH_SETTINGS_TAB[this.entry.platform]; await import(`../../panels/config/entities/editor-tabs/settings/${tag}`); this._settingsElementTag = tag; } diff --git a/src/panels/config/automation/action/ha-automation-action-row.ts b/src/panels/config/automation/action/ha-automation-action-row.ts index 71593afff5..b6474dc2ac 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -11,6 +11,7 @@ import { mdiStopCircleOutline, mdiSort, } from "@mdi/js"; +import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; @@ -26,6 +27,10 @@ import "../../../../components/ha-icon-button"; import type { HaYamlEditor } from "../../../../components/ha-yaml-editor"; import { ACTION_TYPES } from "../../../../data/action"; import { validateConfig } from "../../../../data/config"; +import { + EntityRegistryEntry, + subscribeEntityRegistry, +} from "../../../../data/entity_registry"; import { Action, getActionType } from "../../../../data/script"; import { describeAction } from "../../../../data/script_i18n"; import { callExecuteScript } from "../../../../data/service"; @@ -107,6 +112,8 @@ export default class HaAutomationActionRow extends LitElement { @property({ type: Boolean }) public reOrderMode = false; + @state() private _entityReg: EntityRegistryEntry[] = []; + @state() private _warnings?: string[]; @state() private _uiModeAvailable = true; @@ -115,6 +122,14 @@ export default class HaAutomationActionRow extends LitElement { @query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor; + public hassSubscribe(): UnsubscribeFunc[] { + return [ + subscribeEntityRegistry(this.hass.connection!, (entities) => { + this._entityReg = entities; + }), + ]; + } + protected willUpdate(changedProperties: PropertyValues) { if (!changedProperties.has("action")) { return; @@ -156,7 +171,9 @@ export default class HaAutomationActionRow extends LitElement { class="action-icon" .path=${ACTION_TYPES[type!]} > - ${capitalizeFirstLetter(describeAction(this.hass, this.action))} + ${capitalizeFirstLetter( + describeAction(this.hass, this._entityReg, this.action) + )} @@ -465,7 +482,7 @@ export default class HaAutomationActionRow extends LitElement { ), inputType: "string", placeholder: capitalizeFirstLetter( - describeAction(this.hass, this.action, undefined, true) + describeAction(this.hass, this._entityReg, this.action, undefined, true) ), defaultValue: this.action.alias, confirmText: this.hass.localize("ui.common.submit"), diff --git a/src/panels/config/automation/ha-automation-editor.ts b/src/panels/config/automation/ha-automation-editor.ts index c32dbdfb94..3000c3ba91 100644 --- a/src/panels/config/automation/ha-automation-editor.ts +++ b/src/panels/config/automation/ha-automation-editor.ts @@ -49,6 +49,7 @@ import { showAutomationEditor, triggerAutomationActions, } from "../../../data/automation"; +import { fetchEntityRegistry } from "../../../data/entity_registry"; import { showAlertDialog, showConfirmationDialog, @@ -479,7 +480,8 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { this._readOnly = false; this._config = this._normalizeConfig(config); } catch (err: any) { - const entity = Object.values(this.hass.entities).find( + const entityRegistry = await fetchEntityRegistry(this.hass.connection); + const entity = entityRegistry.find( (ent) => ent.platform === "automation" && ent.unique_id === this.automationId ); diff --git a/src/panels/config/script/ha-config-script.ts b/src/panels/config/script/ha-config-script.ts index 386b798506..5584075dea 100644 --- a/src/panels/config/script/ha-config-script.ts +++ b/src/panels/config/script/ha-config-script.ts @@ -1,14 +1,19 @@ -import { HassEntities } from "home-assistant-js-websocket"; +import { HassEntities, UnsubscribeFunc } from "home-assistant-js-websocket"; import { PropertyValues } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { computeStateDomain } from "../../../common/entity/compute_state_domain"; import { debounce } from "../../../common/util/debounce"; +import { + EntityRegistryEntry, + subscribeEntityRegistry, +} from "../../../data/entity_registry"; import { ScriptEntity } from "../../../data/script"; import { HassRouterPage, RouterOptions, } from "../../../layouts/hass-router-page"; +import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { HomeAssistant } from "../../../types"; import "./ha-script-editor"; import "./ha-script-picker"; @@ -21,7 +26,7 @@ const equal = (a: ScriptEntity[], b: ScriptEntity[]): boolean => { }; @customElement("ha-config-script") -class HaConfigScript extends HassRouterPage { +class HaConfigScript extends SubscribeMixin(HassRouterPage) { @property({ attribute: false }) public hass!: HomeAssistant; @property() public narrow!: boolean; @@ -32,6 +37,16 @@ class HaConfigScript extends HassRouterPage { @property() public scripts: ScriptEntity[] = []; + @state() private _entityReg: EntityRegistryEntry[] = []; + + public hassSubscribe(): UnsubscribeFunc[] { + return [ + subscribeEntityRegistry(this.hass.connection!, (entities) => { + this._entityReg = entities; + }), + ]; + } + protected routerOptions: RouterOptions = { defaultPage: "dashboard", routes: { @@ -78,6 +93,7 @@ class HaConfigScript extends HassRouterPage { pageEl.isWide = this.isWide; pageEl.route = this.routeTail; pageEl.showAdvanced = this.showAdvanced; + pageEl.entityRegistry = this._entityReg; if (this.hass) { if (!pageEl.scripts || !changedProps) { diff --git a/src/panels/config/script/ha-script-editor.ts b/src/panels/config/script/ha-script-editor.ts index 40def1a659..9329a88aa6 100644 --- a/src/panels/config/script/ha-script-editor.ts +++ b/src/panels/config/script/ha-script-editor.ts @@ -39,6 +39,7 @@ import "../../../components/ha-icon-button"; import "../../../components/ha-svg-icon"; import "../../../components/ha-yaml-editor"; import type { HaYamlEditor } from "../../../components/ha-yaml-editor"; +import { EntityRegistryEntry } from "../../../data/entity_registry"; import { deleteScript, getScriptStateConfig, @@ -75,6 +76,8 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { @property({ type: Boolean }) public narrow!: boolean; + @property({ attribute: false }) public entityRegistry!: EntityRegistryEntry[]; + @state() private _config?: ScriptConfig; @state() private _entityId?: string; @@ -431,7 +434,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { this._config = this._normalizeConfig(config); }, (resp) => { - const entity = Object.values(this.hass.entities).find( + const entity = this.entityRegistry.find( (ent) => ent.platform === "script" && ent.unique_id === this.scriptId ); @@ -477,7 +480,9 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { getScriptStateConfig(this.hass, this.entityId).then((c) => { this._config = this._normalizeConfig(c.config); }); - const regEntry = this.hass.entities[this.entityId]; + const regEntry = this.entityRegistry.find( + (ent) => ent.entity_id === this.entityId + ); if (regEntry?.unique_id) { this.scriptId = regEntry.unique_id; } @@ -544,7 +549,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { if (!this.scriptId) { return; } - const entity = Object.values(this.hass.entities).find( + const entity = this.entityRegistry.find( (entry) => entry.unique_id === this.scriptId ); if (!entity) { diff --git a/src/panels/config/script/ha-script-picker.ts b/src/panels/config/script/ha-script-picker.ts index 98ee9f9d70..7b8ee39922 100644 --- a/src/panels/config/script/ha-script-picker.ts +++ b/src/panels/config/script/ha-script-picker.ts @@ -44,6 +44,7 @@ import { HomeAssistant, Route } from "../../../types"; import { documentationUrl } from "../../../util/documentation-url"; import { showToast } from "../../../util/toast"; import { configSections } from "../ha-panel-config"; +import { EntityRegistryEntry } from "../../../data/entity_registry"; @customElement("ha-script-picker") class HaScriptPicker extends LitElement { @@ -57,7 +58,9 @@ class HaScriptPicker extends LitElement { @property() public route!: Route; - @property() private _activeFilters?: string[]; + @property({ attribute: false }) public entityRegistry!: EntityRegistryEntry[]; + + @state() private _activeFilters?: string[]; @state() private _filteredScripts?: string[] | null; @@ -266,7 +269,7 @@ class HaScriptPicker extends LitElement { } private _handleRowClicked(ev: HASSDomEvent) { - const entry = this.hass.entities[ev.detail.id]; + const entry = this.entityRegistry.find((e) => e.entity_id === ev.detail.id); if (entry) { navigate(`/config/script/edit/${entry.unique_id}`); } else { @@ -275,7 +278,12 @@ class HaScriptPicker extends LitElement { } private _runScript = async (script: any) => { - const entry = this.hass.entities[script.entity_id]; + const entry = this.entityRegistry.find( + (e) => e.entity_id === script.entity_id + ); + if (!entry) { + return; + } await triggerScript(this.hass, entry.unique_id); showToast(this, { message: this.hass.localize( @@ -291,7 +299,9 @@ class HaScriptPicker extends LitElement { } private _showTrace(script: any) { - const entry = this.hass.entities[script.entity_id]; + const entry = this.entityRegistry.find( + (e) => e.entity_id === script.entity_id + ); if (entry) { navigate(`/config/script/trace/${entry.unique_id}`); } @@ -317,7 +327,12 @@ class HaScriptPicker extends LitElement { private async _duplicate(script: any) { try { - const entry = this.hass.entities[script.entity_id]; + const entry = this.entityRegistry.find( + (e) => e.entity_id === script.entity_id + ); + if (!entry) { + return; + } const config = await fetchScriptFileConfig(this.hass, entry.unique_id); showScriptEditor({ ...config, @@ -362,8 +377,12 @@ class HaScriptPicker extends LitElement { private async _delete(script: any) { try { - const entry = this.hass.entities[script.entity_id]; - await deleteScript(this.hass, entry.unique_id); + const entry = this.entityRegistry.find( + (e) => e.entity_id === script.entity_id + ); + if (entry) { + await deleteScript(this.hass, entry.unique_id); + } } catch (err: any) { await showAlertDialog(this, { text: diff --git a/src/panels/config/script/ha-script-trace.ts b/src/panels/config/script/ha-script-trace.ts index a8addf7bd8..c8082dace5 100644 --- a/src/panels/config/script/ha-script-trace.ts +++ b/src/panels/config/script/ha-script-trace.ts @@ -39,6 +39,7 @@ import { HomeAssistant, Route } from "../../../types"; import "../../../layouts/hass-subpage"; import "../../../components/ha-button-menu"; import { fireEvent } from "../../../common/dom/fire_event"; +import { EntityRegistryEntry } from "../../../data/entity_registry"; @customElement("ha-script-trace") export class HaScriptTrace extends LitElement { @@ -54,6 +55,8 @@ export class HaScriptTrace extends LitElement { @property({ attribute: false }) public route!: Route; + @property({ attribute: false }) public entityRegistry!: EntityRegistryEntry[]; + @state() private _entityId?: string; @state() private _traces?: ScriptTrace[]; @@ -318,7 +321,7 @@ export class HaScriptTrace extends LitElement { const params = new URLSearchParams(location.search); this._loadTraces(params.get("run_id") || undefined); - this._entityId = Object.values(this.hass.entities).find( + this._entityId = this.entityRegistry.find( (entry) => entry.unique_id === this.scriptId )?.entity_id; } @@ -335,7 +338,7 @@ export class HaScriptTrace extends LitElement { if (this.scriptId) { this._loadTraces(); - this._entityId = Object.values(this.hass.entities).find( + this._entityId = this.entityRegistry.find( (entry) => entry.unique_id === this.scriptId )?.entity_id; } diff --git a/src/panels/lovelace/cards/hui-gauge-card.ts b/src/panels/lovelace/cards/hui-gauge-card.ts index ec243f8846..975cea74d3 100644 --- a/src/panels/lovelace/cards/hui-gauge-card.ts +++ b/src/panels/lovelace/cards/hui-gauge-card.ts @@ -130,7 +130,10 @@ class HuiGaugeCard extends LitElement implements LovelaceCard { .min=${this._config.min!} .max=${this._config.max!} .value=${stateObj.state} - .formatOptions=${getNumberFormatOptions(stateObj)} + .formatOptions=${getNumberFormatOptions( + stateObj, + this.hass.entities[stateObj.entity_id] + )} .locale=${this.hass!.locale} .label=${this._config!.unit || this.hass?.states[this._config!.entity].attributes diff --git a/src/panels/lovelace/common/generate-lovelace-config.ts b/src/panels/lovelace/common/generate-lovelace-config.ts index f1343842c0..759e63ae92 100644 --- a/src/panels/lovelace/common/generate-lovelace-config.ts +++ b/src/panels/lovelace/common/generate-lovelace-config.ts @@ -278,8 +278,8 @@ const computeDefaultViewStates = ( .filter( (entry) => entry.entity_category || - HIDE_PLATFORM.has(entry.platform) || - entry.hidden_by + (entry.platform && HIDE_PLATFORM.has(entry.platform)) || + entry.hidden ) .map((entry) => entry.entity_id) ); diff --git a/src/state/connection-mixin.ts b/src/state/connection-mixin.ts index ca44abf7d5..f490f4b3e2 100644 --- a/src/state/connection-mixin.ts +++ b/src/state/connection-mixin.ts @@ -14,7 +14,7 @@ import { polyfillsLoaded } from "../common/translations/localize"; import { subscribeAreaRegistry } from "../data/area_registry"; import { broadcastConnectionStatus } from "../data/connection-status"; import { subscribeDeviceRegistry } from "../data/device_registry"; -import { subscribeEntityRegistry } from "../data/entity_registry"; +import { subscribeEntityRegistryDisplay } from "../data/entity_registry"; import { subscribeFrontendUserData } from "../data/frontend"; import { forwardHaptic } from "../data/haptics"; import { DEFAULT_PANEL } from "../data/panel"; @@ -188,10 +188,22 @@ export const connectionMixin = >( }); subscribeEntities(conn, (states) => this._updateHass({ states })); - subscribeEntityRegistry(conn, (entityReg) => { + subscribeEntityRegistryDisplay(conn, (entityReg) => { const entities: HomeAssistant["entities"] = {}; - for (const entity of entityReg) { - entities[entity.entity_id] = entity; + for (const entity of entityReg.entities) { + entities[entity.ei] = { + entity_id: entity.ei, + device_id: entity.di, + area_id: entity.ai, + translation_key: entity.tk, + platform: entity.pl, + entity_category: entity.ec + ? entityReg.entity_categories[entity.ec] + : undefined, + name: entity.en, + hidden: entity.hb, + display_precision: entity.dp, + }; } this._updateHass({ entities }); }); diff --git a/src/types.ts b/src/types.ts index 30f9139310..c0f0823baf 100644 --- a/src/types.ts +++ b/src/types.ts @@ -10,7 +10,7 @@ import { import { LocalizeFunc } from "./common/translations/localize"; import { AreaRegistryEntry } from "./data/area_registry"; import { DeviceRegistryEntry } from "./data/device_registry"; -import { EntityRegistryEntry } from "./data/entity_registry"; +import { EntityRegistryDisplayEntry } from "./data/entity_registry"; import { CoreFrontendUserData } from "./data/frontend"; import { FrontendLocaleData, getHassTranslations } from "./data/translation"; import { Themes } from "./data/ws-themes"; @@ -189,7 +189,7 @@ export interface HomeAssistant { connection: Connection; connected: boolean; states: HassEntities; - entities: { [id: string]: EntityRegistryEntry }; + entities: { [id: string]: EntityRegistryDisplayEntry }; devices: { [id: string]: DeviceRegistryEntry }; areas: { [id: string]: AreaRegistryEntry }; services: HassServices;