From 6cd38472cd2e1a6ab6a15aed8605f417e595dad9 Mon Sep 17 00:00:00 2001 From: D3v01dZA Date: Tue, 5 Jul 2022 10:23:38 -0400 Subject: [PATCH] Multiple entities on history panel bugfix and additional improvements (#13045) Co-authored-by: Zack Barett --- src/components/chart/state-history-charts.ts | 1 + src/components/ha-target-picker.ts | 199 ++++++++-------- src/panels/history/ha-panel-history.ts | 233 +++++++++++++------ src/translations/en.json | 5 + 4 files changed, 276 insertions(+), 162 deletions(-) diff --git a/src/components/chart/state-history-charts.ts b/src/components/chart/state-history-charts.ts index b00fb19cdf..415ecbb4e7 100644 --- a/src/components/chart/state-history-charts.ts +++ b/src/components/chart/state-history-charts.ts @@ -186,6 +186,7 @@ class StateHistoryCharts extends LitElement { line-height: 60px; color: var(--secondary-text-color); } + .container { max-height: var(--history-max-height); } diff --git a/src/components/ha-target-picker.ts b/src/components/ha-target-picker.ts index 7545a19a36..b98d969d05 100644 --- a/src/components/ha-target-picker.ts +++ b/src/components/ha-target-picker.ts @@ -42,8 +42,8 @@ import "./entity/ha-entity-picker"; import type { HaEntityPickerEntityFilterFunc } from "./entity/ha-entity-picker"; import "./ha-area-picker"; import "./ha-icon-button"; -import "./ha-svg-icon"; import "./ha-input-helper-text"; +import "./ha-svg-icon"; @customElement("ha-target-picker") export class HaTargetPicker extends SubscribeMixin(LitElement) { @@ -119,55 +119,68 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) { if (!this._areas || !this._devices || !this._entities) { return html``; } - return html`
- ${this.horizontal ? this._renderChips() : this._renderItems()} - ${this._renderPicker()} - ${this.horizontal ? this._renderItems() : this._renderChips()} -
`; + return html` + ${this.horizontal + ? html` +
+ ${this._renderChips()} ${this._renderPicker()} +
+ ${this._renderItems()} + ` + : html` +
+ ${this._renderItems()} ${this._renderPicker()} + ${this._renderChips()} +
+ `} + `; } private _renderItems() { - return html`
- ${this.value?.area_id - ? ensureArray(this.value.area_id).map((area_id) => { - const area = this._areas![area_id]; - return this._renderChip( - "area_id", - area_id, - area?.name || area_id, - undefined, - mdiSofa - ); - }) - : ""} - ${this.value?.device_id - ? ensureArray(this.value.device_id).map((device_id) => { - const device = this._devices![device_id]; - return this._renderChip( - "device_id", - device_id, - device ? computeDeviceName(device, this.hass) : device_id, - undefined, - mdiDevices - ); - }) - : ""} - ${this.value?.entity_id - ? ensureArray(this.value.entity_id).map((entity_id) => { - const entity = this.hass.states[entity_id]; - return this._renderChip( - "entity_id", - entity_id, - entity ? computeStateName(entity) : entity_id, - entity - ); - }) - : ""} -
`; + return html` +
+ ${this.value?.area_id + ? ensureArray(this.value.area_id).map((area_id) => { + const area = this._areas![area_id]; + return this._renderChip( + "area_id", + area_id, + area?.name || area_id, + undefined, + mdiSofa + ); + }) + : ""} + ${this.value?.device_id + ? ensureArray(this.value.device_id).map((device_id) => { + const device = this._devices![device_id]; + return this._renderChip( + "device_id", + device_id, + device ? computeDeviceName(device, this.hass) : device_id, + undefined, + mdiDevices + ); + }) + : ""} + ${this.value?.entity_id + ? ensureArray(this.value.entity_id).map((entity_id) => { + const entity = this.hass.states[entity_id]; + return this._renderChip( + "entity_id", + entity_id, + entity ? computeStateName(entity) : entity_id, + entity + ); + }) + : ""} +
+ `; } private _renderChips() { - return html`
+ return html` +
${this.helper ? html`${this.helper}` - : ""} `; + : ""} + `; } private async _showPicker(ev) { @@ -320,51 +334,54 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) { private _renderPicker() { switch (this._addMode) { case "area_id": - return html``; + return html` + + `; case "device_id": - return html``; + return html` + + `; case "entity_id": - return html``; + return html` + + `; } return html``; } @@ -553,12 +570,6 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) { static get styles(): CSSResultGroup { return css` ${unsafeCSS(chipStyles)} - .hidden-picker { - height: 0px; - display: inline-block; - overflow: hidden; - position: absolute; - } .horizontal-container { display: flex; flex-wrap: wrap; diff --git a/src/panels/history/ha-panel-history.ts b/src/panels/history/ha-panel-history.ts index 9deb2cf1b1..83a5dba36b 100644 --- a/src/panels/history/ha-panel-history.ts +++ b/src/panels/history/ha-panel-history.ts @@ -1,4 +1,4 @@ -import { mdiRefresh } from "@mdi/js"; +import { mdiCollapseAll, mdiRefresh } from "@mdi/js"; import "@polymer/app-layout/app-header/app-header"; import "@polymer/app-layout/app-toolbar/app-toolbar"; import { @@ -34,6 +34,10 @@ import { EntityRegistryEntry, subscribeEntityRegistry, } from "../../data/entity_registry"; +import { + DeviceRegistryEntry, + subscribeDeviceRegistry, +} from "../../data/device_registry"; import { SubscribeMixin } from "../../mixins/subscribe-mixin"; import { computeStateName } from "../../common/entity/compute_state_name"; import { computeDomain } from "../../common/entity/compute_domain"; @@ -57,9 +61,23 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { @state() private _ranges?: DateRangePickerRanges; - @state() private _entities?: EntityRegistryEntry[]; + @state() private _devices?: { [deviceId: string]: DeviceRegistryEntry }; - @state() private _stateEntities?: EntityRegistryEntry[]; + @state() private _entities?: { [entityId: string]: EntityRegistryEntry }; + + @state() private _stateEntities?: { [entityId: string]: EntityRegistryEntry }; + + @state() private _deviceIdToEntities?: { + [deviceId: string]: EntityRegistryEntry[]; + }; + + @state() private _areaIdToEntities?: { + [areaId: string]: EntityRegistryEntry[]; + }; + + @state() private _areaIdToDevices?: { + [areaId: string]: DeviceRegistryEntry[]; + }; public constructor() { super(); @@ -76,7 +94,52 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { public hassSubscribe(): UnsubscribeFunc[] { return [ subscribeEntityRegistry(this.hass.connection!, (entities) => { - this._entities = entities; + this._entities = entities.reduce((accumulator, current) => { + accumulator[current.entity_id] = current; + return accumulator; + }, {}); + this._deviceIdToEntities = entities.reduce((accumulator, current) => { + if (!current.device_id) { + return accumulator; + } + let found = accumulator[current.device_id]; + if (found === undefined) { + found = []; + accumulator[current.device_id] = found; + } + found.push(current); + return accumulator; + }, {}); + this._areaIdToEntities = entities.reduce((accumulator, current) => { + if (!current.area_id) { + return accumulator; + } + let found = accumulator[current.area_id]; + if (found === undefined) { + found = []; + accumulator[current.area_id] = found; + } + found.push(current); + return accumulator; + }, {}); + }), + subscribeDeviceRegistry(this.hass.connection!, (devices) => { + this._devices = devices.reduce((accumulator, current) => { + accumulator[current.id] = current; + return accumulator; + }, {}); + this._areaIdToDevices = devices.reduce((accumulator, current) => { + if (!current.area_id) { + return accumulator; + } + let found = accumulator[current.area_id]; + if (found === undefined) { + found = []; + accumulator[current.area_id] = found; + } + found.push(current); + return accumulator; + }, {}); }), ]; } @@ -91,6 +154,12 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { .narrow=${this.narrow} >
${this.hass.localize("panel.history")}
+
` + : !this._targetPickerValue + ? html`` : html` `}
- ${this._isLoading - ? html`
- -
` - : html` - - - `} `; } @@ -199,8 +254,7 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { if ( changedProps.has("_startDate") || changedProps.has("_endDate") || - changedProps.has("_targetPickerValue") || - changedProps.has("_entities") + changedProps.has("_targetPickerValue") ) { this._getHistory(); } @@ -211,15 +265,13 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { this.rtl = computeRTL(this.hass); } if (this._entities) { - const stateEntities: EntityRegistryEntry[] = []; - const regEntityIds = new Set( - this._entities.map((entity) => entity.entity_id) - ); + const stateEntities: { [entityId: string]: EntityRegistryEntry } = {}; + const regEntityIds = new Set(Object.keys(this._entities)); for (const entityId of Object.keys(this.hass.states)) { if (regEntityIds.has(entityId)) { continue; } - stateEntities.push({ + stateEntities[entityId] = { name: computeStateName(this.hass.states[entityId]), entity_id: entityId, platform: computeDomain(entityId), @@ -230,17 +282,27 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { device_id: null, icon: null, entity_category: null, - }); + }; } this._stateEntities = stateEntities; } } } + private _removeAll() { + this._targetPickerValue = undefined; + } + private _refreshHistory() { this._getHistory(); } + private _shouldShowEntityByLargerSelection( + entity: EntityRegistryEntry + ): boolean { + return entity.entity_category === null; + } + private async _getHistory() { this._isLoading = true; const entityIds = this._getEntityIds(); @@ -261,50 +323,79 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { this._isLoading = false; } - private _filterEntity(entity: EntityRegistryEntry): boolean { - const { area_id, device_id, entity_id } = this._targetPickerValue; - if (area_id !== undefined) { - if (typeof area_id === "string" && area_id === entity.area_id) { - return true; - } - if (Array.isArray(area_id) && area_id.includes(entity.area_id)) { - return true; - } - } - if (device_id !== undefined) { - if (typeof device_id === "string" && device_id === entity.device_id) { - return true; - } - if (Array.isArray(device_id) && device_id.includes(entity.device_id)) { - return true; - } - } - if (entity_id !== undefined) { - if (typeof entity_id === "string" && entity_id === entity.entity_id) { - return true; - } - if (Array.isArray(entity_id) && entity_id.includes(entity.entity_id)) { - return true; - } - } - return false; - } - private _getEntityIds(): string[] { if ( this._targetPickerValue === undefined || this._entities === undefined || - this._stateEntities === undefined + this._stateEntities === undefined || + this._devices === undefined || + this._deviceIdToEntities === undefined || + this._areaIdToEntities === undefined || + this._areaIdToDevices === undefined ) { return []; } - const entityIds = this._entities - .filter((entity) => this._filterEntity(entity)) - .map((entity) => entity.entity_id); - const stateEntityIds = this._stateEntities - .filter((entity) => this._filterEntity(entity)) - .map((entity) => entity.entity_id); - return [...entityIds, ...stateEntityIds]; + const entityIds = new Set(); + let { + area_id: searchingAreaId, + device_id: searchingDeviceId, + entity_id: searchingEntityId, + } = this._targetPickerValue; + if (searchingAreaId !== undefined) { + if (typeof searchingAreaId === "string") { + searchingAreaId = [searchingAreaId]; + } + for (const singleSearchingAreaId of searchingAreaId) { + const foundEntities = this._areaIdToEntities[singleSearchingAreaId]; + if (foundEntities !== undefined) { + for (const foundEntity of foundEntities) { + if (this._shouldShowEntityByLargerSelection(foundEntity)) { + entityIds.add(foundEntity.entity_id); + } + } + } + const foundDevices = this._areaIdToDevices[singleSearchingAreaId]; + if (foundDevices !== undefined) { + for (const foundDevice of foundDevices) { + const foundDeviceEntities = + this._deviceIdToEntities[foundDevice.id]; + for (const foundDeviceEntity of foundDeviceEntities) { + if ( + (!foundDeviceEntity.area_id || + foundDeviceEntity.area_id === singleSearchingAreaId) && + this._shouldShowEntityByLargerSelection(foundDeviceEntity) + ) { + entityIds.add(foundDeviceEntity.entity_id); + } + } + } + } + } + } + if (searchingDeviceId !== undefined) { + if (typeof searchingDeviceId === "string") { + searchingDeviceId = [searchingDeviceId]; + } + for (const singleSearchingDeviceId of searchingDeviceId) { + const foundEntities = this._deviceIdToEntities[singleSearchingDeviceId]; + if (foundEntities !== undefined) { + for (const foundEntity of foundEntities) { + if (this._shouldShowEntityByLargerSelection(foundEntity)) { + entityIds.add(foundEntity.entity_id); + } + } + } + } + } + if (searchingEntityId !== undefined) { + if (typeof searchingEntityId === "string") { + searchingEntityId = [searchingEntityId]; + } + for (const singleSearchingEntityId of searchingEntityId) { + entityIds.add(singleSearchingEntityId); + } + } + return [...entityIds]; } private _dateRangeChanged(ev) { @@ -389,7 +480,7 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { .filters { display: flex; - align-items: flex-end; + align-items: flex-start; padding: 8px 16px 0; } @@ -429,6 +520,12 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { max-width: none; width: 100%; } + + .start-search { + padding-top: 16px; + text-align: center; + color: var(--secondary-text-color); + } `, ]; } diff --git a/src/translations/en.json b/src/translations/en.json index bfe9582b37..cc53fac488 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -4559,6 +4559,11 @@ "energy_sources_table_title": "Sources", "energy_devices_graph_title": "Monitor individual devices" } + }, + "history": { + "start_search": "Start by selecting an area, device or entity above", + "add_all": "Add all entities", + "remove_all": "Remove all selections" } }, "tips": {