From fa13b95498ace48ab2d530aac1ecb06fd2dc749f Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 7 May 2019 20:57:23 -0700 Subject: [PATCH] Use collections for registries (#3168) * Use collections * Fix bugs * Lint --- src/data/area_registry.ts | 36 ++++++++++- src/data/config_entries.ts | 32 ++++++++++ src/data/device_registry.ts | 35 ++++++++++- src/data/entity_registry.ts | 37 +++++++++-- src/dialogs/config-flow/dialog-config-flow.ts | 62 +++++++++--------- .../area_registry/ha-config-area-registry.ts | 63 ++++++++----------- .../ha-config-entity-registry.ts | 47 +++++++------- .../integrations/ha-config-integrations.js | 10 +-- src/panels/config/zha/zha-device-card.ts | 24 ++++--- 9 files changed, 237 insertions(+), 109 deletions(-) diff --git a/src/data/area_registry.ts b/src/data/area_registry.ts index cf312bda3f..3347232028 100644 --- a/src/data/area_registry.ts +++ b/src/data/area_registry.ts @@ -1,4 +1,7 @@ +import { createCollection } from "home-assistant-js-websocket"; import { HomeAssistant } from "../types"; +import { compare } from "../common/string/compare"; +import { debounce } from "../common/util/debounce"; export interface AreaRegistryEntry { area_id: string; @@ -9,9 +12,6 @@ export interface AreaRegistryEntryMutableParams { name: string; } -export const fetchAreaRegistry = (hass: HomeAssistant) => - hass.callWS({ type: "config/area_registry/list" }); - export const createAreaRegistryEntry = ( hass: HomeAssistant, values: AreaRegistryEntryMutableParams @@ -37,3 +37,33 @@ export const deleteAreaRegistryEntry = (hass: HomeAssistant, areaId: string) => type: "config/area_registry/delete", area_id: areaId, }); + +const fetchAreaRegistry = (conn) => + conn + .sendMessagePromise({ + type: "config/area_registry/list", + }) + .then((areas) => areas.sort((ent1, ent2) => compare(ent1.name, ent2.name))); + +const subscribeAreaRegistryUpdates = (conn, store) => + conn.subscribeEvents( + debounce( + () => + fetchAreaRegistry(conn).then((areas) => store.setState(areas, true)), + 500, + true + ), + "area_registry_updated" + ); + +export const subscribeAreaRegistry = ( + hass: HomeAssistant, + onChange: (areas: AreaRegistryEntry[]) => void +) => + createCollection( + "_areaRegistry", + fetchAreaRegistry, + subscribeAreaRegistryUpdates, + hass.connection, + onChange + ); diff --git a/src/data/config_entries.ts b/src/data/config_entries.ts index 3741fea239..1acf44f39b 100644 --- a/src/data/config_entries.ts +++ b/src/data/config_entries.ts @@ -1,4 +1,6 @@ import { HomeAssistant } from "../types"; +import { createCollection } from "home-assistant-js-websocket"; +import { debounce } from "../common/util/debounce"; export interface FieldSchema { name: string; @@ -74,3 +76,33 @@ export const getConfigFlowsInProgress = (hass: HomeAssistant) => export const getConfigFlowHandlers = (hass: HomeAssistant) => hass.callApi("GET", "config/config_entries/flow_handlers"); + +const fetchConfigFlowInProgress = (conn) => + conn.sendMessagePromise({ + type: "config/entity_registry/list", + }); + +const subscribeConfigFlowInProgressUpdates = (conn, store) => + debounce( + conn.subscribeEvents( + () => + fetchConfigFlowInProgress(conn).then((flows) => + store.setState(flows, true) + ), + 500, + true + ), + "config_entry_discovered" + ); + +export const subscribeConfigFlowInProgress = ( + hass: HomeAssistant, + onChange: (flows: ConfigFlowProgress[]) => void +) => + createCollection( + "_configFlowProgress", + fetchConfigFlowInProgress, + subscribeConfigFlowInProgressUpdates, + hass.connection, + onChange + ); diff --git a/src/data/device_registry.ts b/src/data/device_registry.ts index 348f85f8cb..51084a802b 100644 --- a/src/data/device_registry.ts +++ b/src/data/device_registry.ts @@ -1,4 +1,6 @@ import { HomeAssistant } from "../types"; +import { createCollection } from "home-assistant-js-websocket"; +import { debounce } from "../common/util/debounce"; export interface DeviceRegistryEntry { id: string; @@ -18,9 +20,6 @@ export interface DeviceRegistryEntryMutableParams { name_by_user?: string; } -export const fetchDeviceRegistry = (hass: HomeAssistant) => - hass.callWS({ type: "config/device_registry/list" }); - export const updateDeviceRegistryEntry = ( hass: HomeAssistant, deviceId: string, @@ -31,3 +30,33 @@ export const updateDeviceRegistryEntry = ( device_id: deviceId, ...updates, }); + +const fetchDeviceRegistry = (conn) => + conn.sendMessagePromise({ + type: "config/device_registry/list", + }); + +const subscribeDeviceRegistryUpdates = (conn, store) => + conn.subscribeEvents( + debounce( + () => + fetchDeviceRegistry(conn).then((devices) => + store.setState(devices, true) + ), + 500, + true + ), + "device_registry_updated" + ); + +export const subscribeDeviceRegistry = ( + hass: HomeAssistant, + onChange: (devices: DeviceRegistryEntry[]) => void +) => + createCollection( + "_dr", + fetchDeviceRegistry, + subscribeDeviceRegistryUpdates, + hass.connection, + onChange + ); diff --git a/src/data/entity_registry.ts b/src/data/entity_registry.ts index c11d8ec371..4ca2c76d23 100644 --- a/src/data/entity_registry.ts +++ b/src/data/entity_registry.ts @@ -1,5 +1,7 @@ +import { createCollection } from "home-assistant-js-websocket"; import { HomeAssistant } from "../types"; import computeStateName from "../common/entity/compute_state_name"; +import { debounce } from "../common/util/debounce"; export interface EntityRegistryEntry { entity_id: string; @@ -26,11 +28,6 @@ export const computeEntityRegistryName = ( return state ? computeStateName(state) : null; }; -export const fetchEntityRegistry = ( - hass: HomeAssistant -): Promise => - hass.callWS({ type: "config/entity_registry/list" }); - export const updateEntityRegistryEntry = ( hass: HomeAssistant, entityId: string, @@ -50,3 +47,33 @@ export const removeEntityRegistryEntry = ( type: "config/entity_registry/remove", entity_id: entityId, }); + +const fetchEntityRegistry = (conn) => + conn.sendMessagePromise({ + type: "config/entity_registry/list", + }); + +const subscribeEntityRegistryUpdates = (conn, store) => + conn.subscribeEvents( + debounce( + () => + fetchEntityRegistry(conn).then((entities) => + store.setState(entities, true) + ), + 500, + true + ), + "entity_registry_updated" + ); + +export const subscribeEntityRegistry = ( + hass: HomeAssistant, + onChange: (entities: EntityRegistryEntry[]) => void +) => + createCollection( + "_entityRegistry", + fetchEntityRegistry, + subscribeEntityRegistryUpdates, + hass.connection, + onChange + ); diff --git a/src/dialogs/config-flow/dialog-config-flow.ts b/src/dialogs/config-flow/dialog-config-flow.ts index 3b495419d8..705fd9985f 100644 --- a/src/dialogs/config-flow/dialog-config-flow.ts +++ b/src/dialogs/config-flow/dialog-config-flow.ts @@ -37,10 +37,14 @@ import "./step-flow-abort"; import "./step-flow-create-entry"; import { DeviceRegistryEntry, - fetchDeviceRegistry, + subscribeDeviceRegistry, } from "../../data/device_registry"; -import { AreaRegistryEntry, fetchAreaRegistry } from "../../data/area_registry"; +import { + AreaRegistryEntry, + subscribeAreaRegistry, +} from "../../data/area_registry"; import { HomeAssistant } from "../../types"; +import { UnsubscribeFunc } from "home-assistant-js-websocket"; let instance = 0; @@ -57,30 +61,19 @@ declare global { @customElement("dialog-config-flow") class ConfigFlowDialog extends LitElement { public hass!: HomeAssistant; - - @property() - private _params?: HaConfigFlowParams; - - @property() - private _loading = true; - + @property() private _params?: HaConfigFlowParams; + @property() private _loading = true; private _instance = instance; - - @property() - private _step: + @property() private _step: | ConfigFlowStep | undefined // Null means we need to pick a config flow | null; - - @property() - private _devices?: DeviceRegistryEntry[]; - - @property() - private _areas?: AreaRegistryEntry[]; - - @property() - private _handlers?: string[]; + @property() private _devices?: DeviceRegistryEntry[]; + @property() private _areas?: AreaRegistryEntry[]; + @property() private _handlers?: string[]; + private _unsubAreas?: UnsubscribeFunc; + private _unsubDevices?: UnsubscribeFunc; public async showDialog(params: HaConfigFlowParams): Promise { this._params = params; @@ -196,6 +189,10 @@ class ConfigFlowDialog extends LitElement { this._fetchDevices(this._step.result); this._fetchAreas(); } + + if (changedProps.has("_devices") && this._dialog) { + this._scheduleCenterDialog(); + } } private _scheduleCenterDialog() { @@ -207,16 +204,17 @@ class ConfigFlowDialog extends LitElement { } private async _fetchDevices(configEntryId) { - // Wait 5 seconds to give integrations time to find devices - await new Promise((resolve) => setTimeout(resolve, 5000)); - const devices = await fetchDeviceRegistry(this.hass); - this._devices = devices.filter((device) => - device.config_entries.includes(configEntryId) - ); + this._unsubDevices = subscribeDeviceRegistry(this.hass, (devices) => { + this._devices = devices.filter((device) => + device.config_entries.includes(configEntryId) + ); + }); } private async _fetchAreas() { - this._areas = await fetchAreaRegistry(this.hass); + this._unsubAreas = subscribeAreaRegistry(this.hass, (areas) => { + this._areas = areas; + }); } private async _processStep( @@ -261,6 +259,14 @@ class ConfigFlowDialog extends LitElement { this._step = undefined; this._params = undefined; this._devices = undefined; + if (this._unsubAreas) { + this._unsubAreas(); + this._unsubAreas = undefined; + } + if (this._unsubDevices) { + this._unsubDevices(); + this._unsubDevices = undefined; + } } private _openedChanged(ev: PolymerChangedEvent): void { diff --git a/src/panels/config/area_registry/ha-config-area-registry.ts b/src/panels/config/area_registry/ha-config-area-registry.ts index 1f62ce1d6c..598b884cde 100644 --- a/src/panels/config/area_registry/ha-config-area-registry.ts +++ b/src/panels/config/area_registry/ha-config-area-registry.ts @@ -4,7 +4,7 @@ import { html, css, CSSResult, - PropertyDeclarations, + property, } from "lit-element"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item-body"; @@ -13,15 +13,14 @@ import "@polymer/paper-fab/paper-fab"; import { HomeAssistant } from "../../../types"; import { AreaRegistryEntry, - fetchAreaRegistry, updateAreaRegistryEntry, deleteAreaRegistryEntry, createAreaRegistryEntry, + subscribeAreaRegistry, } from "../../../data/area_registry"; import "../../../components/ha-card"; import "../../../layouts/hass-subpage"; import "../../../layouts/hass-loading-screen"; -import { compare } from "../../../common/string/compare"; import "../ha-config-section"; import { showAreaRegistryDetailDialog, @@ -29,22 +28,23 @@ import { } from "./show-dialog-area-registry-detail"; import { classMap } from "lit-html/directives/class-map"; import { computeRTL } from "../../../common/util/compute_rtl"; +import { UnsubscribeFunc } from "home-assistant-js-websocket"; class HaConfigAreaRegistry extends LitElement { - public hass?: HomeAssistant; - public isWide?: boolean; - private _items?: AreaRegistryEntry[]; + @property() public hass!: HomeAssistant; + @property() public isWide?: boolean; + @property() private _areas?: AreaRegistryEntry[]; + private _unsubAreas?: UnsubscribeFunc; - static get properties(): PropertyDeclarations { - return { - hass: {}, - isWide: {}, - _items: {}, - }; + public disconnectedCallback() { + super.disconnectedCallback(); + if (this._unsubAreas) { + this._unsubAreas(); + } } protected render(): TemplateResult | void { - if (!this.hass || this._items === undefined) { + if (!this.hass || this._areas === undefined) { return html` `; @@ -73,7 +73,7 @@ class HaConfigAreaRegistry extends LitElement { - ${this._items.map((entry) => { + ${this._areas.map((entry) => { return html` @@ -82,7 +82,7 @@ class HaConfigAreaRegistry extends LitElement { `; })} - ${this._items.length === 0 + ${this._areas.length === 0 ? html`
${this.hass.localize( @@ -116,14 +116,16 @@ class HaConfigAreaRegistry extends LitElement { protected firstUpdated(changedProps) { super.firstUpdated(changedProps); - this._fetchData(); loadAreaRegistryDetailDialog(); } - private async _fetchData() { - this._items = (await fetchAreaRegistry(this.hass!)).sort((ent1, ent2) => - compare(ent1.name, ent2.name) - ); + protected updated(changedProps) { + super.updated(changedProps); + if (!this._unsubAreas) { + this._unsubAreas = subscribeAreaRegistry(this.hass, (areas) => { + this._areas = areas; + }); + } } private _createArea() { @@ -137,22 +139,10 @@ class HaConfigAreaRegistry extends LitElement { private _openDialog(entry?: AreaRegistryEntry) { showAreaRegistryDetailDialog(this, { entry, - createEntry: async (values) => { - const created = await createAreaRegistryEntry(this.hass!, values); - this._items = this._items!.concat(created).sort((ent1, ent2) => - compare(ent1.name, ent2.name) - ); - }, - updateEntry: async (values) => { - const updated = await updateAreaRegistryEntry( - this.hass!, - entry!.area_id, - values - ); - this._items = this._items!.map((ent) => - ent === entry ? updated : ent - ); - }, + createEntry: async (values) => + createAreaRegistryEntry(this.hass!, values), + updateEntry: async (values) => + updateAreaRegistryEntry(this.hass!, entry!.area_id, values), removeEntry: async () => { if ( !confirm(`Are you sure you want to delete this area? @@ -164,7 +154,6 @@ All devices in this area will become unassigned.`) try { await deleteAreaRegistryEntry(this.hass!, entry!.area_id); - this._items = this._items!.filter((ent) => ent !== entry); return true; } catch (err) { return false; diff --git a/src/panels/config/entity_registry/ha-config-entity-registry.ts b/src/panels/config/entity_registry/ha-config-entity-registry.ts index 37ba01f76e..86f4dd1e78 100644 --- a/src/panels/config/entity_registry/ha-config-entity-registry.ts +++ b/src/panels/config/entity_registry/ha-config-entity-registry.ts @@ -4,7 +4,7 @@ import { html, css, CSSResult, - PropertyDeclarations, + property, } from "lit-element"; import "@polymer/paper-item/paper-icon-item"; import "@polymer/paper-item/paper-item-body"; @@ -12,16 +12,15 @@ import "@polymer/paper-item/paper-item-body"; import { HomeAssistant } from "../../../types"; import { EntityRegistryEntry, - fetchEntityRegistry, computeEntityRegistryName, updateEntityRegistryEntry, removeEntityRegistryEntry, + subscribeEntityRegistry, } from "../../../data/entity_registry"; import "../../../layouts/hass-subpage"; import "../../../layouts/hass-loading-screen"; import "../../../components/ha-card"; import "../../../components/ha-icon"; -import { compare } from "../../../common/string/compare"; import domainIcon from "../../../common/entity/domain_icon"; import stateIcon from "../../../common/entity/state_icon"; import computeDomain from "../../../common/entity/compute_domain"; @@ -30,22 +29,24 @@ import { showEntityRegistryDetailDialog, loadEntityRegistryDetailDialog, } from "./show-dialog-entity-registry-detail"; +import { UnsubscribeFunc } from "home-assistant-js-websocket"; +import { compare } from "../../../common/string/compare"; class HaConfigEntityRegistry extends LitElement { - public hass?: HomeAssistant; - public isWide?: boolean; - private _items?: EntityRegistryEntry[]; + @property() public hass!: HomeAssistant; + @property() public isWide?: boolean; + @property() private _entities?: EntityRegistryEntry[]; + private _unsubEntities?: UnsubscribeFunc; - static get properties(): PropertyDeclarations { - return { - hass: {}, - isWide: {}, - _items: {}, - }; + public disconnectedCallback() { + super.disconnectedCallback(); + if (this._unsubEntities) { + this._unsubEntities(); + } } protected render(): TemplateResult | void { - if (!this.hass || this._items === undefined) { + if (!this.hass || this._entities === undefined) { return html` `; @@ -78,7 +79,7 @@ class HaConfigEntityRegistry extends LitElement { - ${this._items.map((entry) => { + ${this._entities.map((entry) => { const state = this.hass!.states[entry.entity_id]; return html` @@ -111,14 +112,18 @@ class HaConfigEntityRegistry extends LitElement { protected firstUpdated(changedProps): void { super.firstUpdated(changedProps); - this._fetchData(); loadEntityRegistryDetailDialog(); } - private async _fetchData(): Promise { - this._items = (await fetchEntityRegistry(this.hass!)).sort((ent1, ent2) => - compare(ent1.entity_id, ent2.entity_id) - ); + protected updated(changedProps) { + super.updated(changedProps); + if (!this._unsubEntities) { + this._unsubEntities = subscribeEntityRegistry(this.hass, (entities) => { + this._entities = entities.sort((ent1, ent2) => + compare(ent1.entity_id, ent2.entity_id) + ); + }); + } } private _openEditEntry(ev: MouseEvent): void { @@ -131,7 +136,7 @@ class HaConfigEntityRegistry extends LitElement { entry.entity_id, updates ); - this._items = this._items!.map((ent) => + this._entities = this._entities!.map((ent) => ent === entry ? updated : ent ); }, @@ -148,7 +153,7 @@ Deleting an entry will not remove the entity from Home Assistant. To do this, yo try { await removeEntityRegistryEntry(this.hass!, entry.entity_id); - this._items = this._items!.filter((ent) => ent !== entry); + this._entities = this._entities!.filter((ent) => ent !== entry); return true; } catch (err) { return false; diff --git a/src/panels/config/integrations/ha-config-integrations.js b/src/panels/config/integrations/ha-config-integrations.js index 30250593fa..bd759f4ded 100644 --- a/src/panels/config/integrations/ha-config-integrations.js +++ b/src/panels/config/integrations/ha-config-integrations.js @@ -8,7 +8,7 @@ import "./ha-config-entries-dashboard"; import "./ha-config-entry-page"; import NavigateMixin from "../../../mixins/navigate-mixin"; import { compare } from "../../../common/string/compare"; -import { fetchAreaRegistry } from "../../../data/area_registry"; +import { subscribeAreaRegistry } from "../../../data/area_registry"; class HaConfigIntegrations extends NavigateMixin(PolymerElement) { static get template() { @@ -96,6 +96,9 @@ class HaConfigIntegrations extends NavigateMixin(PolymerElement) { connectedCallback() { super.connectedCallback(); this._loadData(); + this._unsubAreas = subscribeAreaRegistry(this.hass, (areas) => { + this._areas = areas; + }); this.hass.connection .subscribeEvents(() => { @@ -113,6 +116,7 @@ class HaConfigIntegrations extends NavigateMixin(PolymerElement) { disconnectedCallback() { super.disconnectedCallback(); if (this._unsubEvents) this._unsubEvents(); + if (this._unsubAreas) this._unsubAreas(); } _loadData() { @@ -143,10 +147,6 @@ class HaConfigIntegrations extends NavigateMixin(PolymerElement) { .then((devices) => { this._devices = devices; }); - - fetchAreaRegistry(this.hass).then((areas) => { - this._areas = areas.sort((a, b) => compare(a.name, b.name)); - }); } _computeConfigEntry(routeData, entries) { diff --git a/src/panels/config/zha/zha-device-card.ts b/src/panels/config/zha/zha-device-card.ts index 4b4ee0f785..791234564e 100644 --- a/src/panels/config/zha/zha-device-card.ts +++ b/src/panels/config/zha/zha-device-card.ts @@ -22,10 +22,9 @@ import { } from "lit-element"; import { fireEvent } from "../../../common/dom/fire_event"; -import { compare } from "../../../common/string/compare"; import { AreaRegistryEntry, - fetchAreaRegistry, + subscribeAreaRegistry, } from "../../../data/area_registry"; import { DeviceRegistryEntryMutableParams, @@ -36,6 +35,7 @@ import { haStyle } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; import { ItemSelectedEvent, NodeServiceData } from "./types"; import { navigate } from "../../../common/navigate"; +import { UnsubscribeFunc } from "home-assistant-js-websocket"; declare global { // for fire event @@ -48,7 +48,7 @@ declare global { @customElement("zha-device-card") class ZHADeviceCard extends LitElement { - @property() public hass?: HomeAssistant; + @property() public hass!: HomeAssistant; @property() public narrow?: boolean; @property() public device?: ZHADevice; @property() public showHelp: boolean = false; @@ -58,8 +58,16 @@ class ZHADeviceCard extends LitElement { @property() private _areas: AreaRegistryEntry[] = []; @property() private _selectedAreaIndex: number = -1; @property() private _userGivenName?: string; + private _unsubAreas?: UnsubscribeFunc; - public firstUpdated(changedProperties: PropertyValues): void { + public disconnectedCallback() { + super.disconnectedCallback(); + if (this._unsubAreas) { + this._unsubAreas(); + } + } + + protected firstUpdated(changedProperties: PropertyValues): void { super.firstUpdated(changedProperties); this.addEventListener("hass-service-called", (ev) => this.serviceCalled(ev) @@ -67,9 +75,6 @@ class ZHADeviceCard extends LitElement { this._serviceData = { ieee_address: this.device!.ieee, }; - fetchAreaRegistry(this.hass!).then((areas) => { - this._areas = areas.sort((a, b) => compare(a.name, b.name)); - }); } protected updated(changedProperties: PropertyValues): void { @@ -84,6 +89,11 @@ class ZHADeviceCard extends LitElement { } this._userGivenName = this.device!.user_given_name; } + if (!this._unsubAreas) { + this._unsubAreas = subscribeAreaRegistry(this.hass, (areas) => { + this._areas = areas; + }); + } super.update(changedProperties); }