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`
+ ${this.hass.localize("ui.panel.history.start_search")}
+
`
: 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": {