From 93f4ae1bea4d701dae156b0a6388e158d06cceee Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 26 May 2022 14:58:54 -0700 Subject: [PATCH 01/19] Add redirects to cast to catch some common mistakes in custom cards (#12808) --- cast/public/_redirects | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 cast/public/_redirects diff --git a/cast/public/_redirects b/cast/public/_redirects new file mode 100644 index 0000000000..5849dbfce6 --- /dev/null +++ b/cast/public/_redirects @@ -0,0 +1,9 @@ +# These redirects are handled by Netlify +# + +# Some custom cards are not prefixing the instance URL when fetching data +# and can end up fetching the data from the Cast domain instead of HA. +# This will make sure that some common ones are replaced with a placeholder. +/api/camera_proxy/* /images/google-nest-hub.png +/api/camera_proxy_stream/* /images/google-nest-hub.png +/api/media_player_proxy/* /images/google-nest-hub.png From cbb962f08483532e58e2f346db2bc779185e38b8 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 26 May 2022 23:59:26 +0200 Subject: [PATCH 02/19] Fix width of application creds page (#12806) --- src/components/ha-combo-box.ts | 22 ++++++++++--------- .../ha-config-application-credentials.ts | 1 - 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/components/ha-combo-box.ts b/src/components/ha-combo-box.ts index a6e1fb1d4a..f900c2fa9f 100644 --- a/src/components/ha-combo-box.ts +++ b/src/components/ha-combo-box.ts @@ -1,13 +1,17 @@ import "@material/mwc-list/mwc-list-item"; import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js"; import "@vaadin/combo-box/theme/material/vaadin-combo-box-light"; -import type { ComboBoxLight } from "@vaadin/combo-box/vaadin-combo-box-light"; +import type { + ComboBoxLight, + ComboBoxLightFilterChangedEvent, + ComboBoxLightOpenedChangedEvent, + ComboBoxLightValueChangedEvent, +} from "@vaadin/combo-box/vaadin-combo-box-light"; import { registerStyles } from "@vaadin/vaadin-themable-mixin/register-styles"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers"; import { customElement, property, query } from "lit/decorators"; import { fireEvent } from "../common/dom/fire_event"; -import { PolymerChangedEvent } from "../polymer-types"; import { HomeAssistant } from "../types"; import "./ha-icon-button"; import "./ha-textfield"; @@ -203,7 +207,7 @@ export class HaComboBox extends LitElement { } } - private _openedChanged(ev: PolymerChangedEvent) { + private _openedChanged(ev: ComboBoxLightOpenedChangedEvent) { const opened = ev.detail.value; // delay this so we can handle click event before setting _opened setTimeout(() => { @@ -229,14 +233,12 @@ export class HaComboBox extends LitElement { mutations.forEach((mutation) => { if ( mutation.type === "attributes" && - mutation.attributeName === "inert" && - // @ts-expect-error - overlay.inert === true + mutation.attributeName === "inert" ) { - // @ts-expect-error - overlay.inert = false; this._overlayMutationObserver?.disconnect(); this._overlayMutationObserver = undefined; + // @ts-expect-error + overlay.inert = false; } else if (mutation.type === "childList") { mutation.removedNodes.forEach((node) => { if (node.nodeName === "VAADIN-COMBO-BOX-OVERLAY") { @@ -257,12 +259,12 @@ export class HaComboBox extends LitElement { } } - private _filterChanged(ev: PolymerChangedEvent) { + private _filterChanged(ev: ComboBoxLightFilterChangedEvent) { // @ts-ignore fireEvent(this, ev.type, ev.detail, { composed: false }); } - private _valueChanged(ev: PolymerChangedEvent) { + private _valueChanged(ev: ComboBoxLightValueChangedEvent) { ev.stopPropagation(); const newValue = ev.detail.value; diff --git a/src/panels/config/application_credentials/ha-config-application-credentials.ts b/src/panels/config/application_credentials/ha-config-application-credentials.ts index 79d947ca4e..bacd283923 100644 --- a/src/panels/config/application_credentials/ha-config-application-credentials.ts +++ b/src/panels/config/application_credentials/ha-config-application-credentials.ts @@ -53,7 +53,6 @@ export class HaConfigApplicationCredentials extends LitElement { title: localize( "ui.panel.config.application_credentials.picker.headers.name" ), - width: "40%", direction: "asc", grows: true, template: (_, entry: ApplicationCredential) => html`${entry.name}`, From e2944b098d5aa760e142798feb0f94df68e73471 Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Fri, 27 May 2022 02:37:43 +0200 Subject: [PATCH 03/19] Align "Browse Media" button while being wrapped (#12800) --- src/dialogs/more-info/controls/more-info-media_player.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/dialogs/more-info/controls/more-info-media_player.ts b/src/dialogs/more-info/controls/more-info-media_player.ts index 20f0cd03ad..7989c384d8 100644 --- a/src/dialogs/more-info/controls/more-info-media_player.ts +++ b/src/dialogs/more-info/controls/more-info-media_player.ts @@ -78,6 +78,7 @@ class MoreInfoMediaPlayer extends LitElement { @click=${this._showBrowseMedia} > @@ -243,6 +244,10 @@ class MoreInfoMediaPlayer extends LitElement { mwc-button > ha-svg-icon { vertical-align: text-bottom; } + + .browse-media-icon { + margin-left: 8px; + } `; } From 588fd8765473ea76982d8eee9ee5a1b306bc83dc Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 27 May 2022 16:33:07 -0400 Subject: [PATCH 04/19] Update style of zwave_js controller statistics (#12810) --- .../integration-panels/zwave_js/zwave_js-config-dashboard.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts index 6acef7a572..a588af70d8 100644 --- a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts +++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts @@ -694,6 +694,11 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) { justify-content: space-between; } + span[slot="meta"] { + font-size: 0.95em; + color: var(--primary-text-color); + } + .network-status div.heading { display: flex; align-items: center; From 0183e322673e590ceef2253c1446997426c9554f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Mon, 30 May 2022 12:46:43 +0200 Subject: [PATCH 05/19] Use supervisor envs instead of hassio (#12812) --- .vscode/tasks.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index c6868baeb0..c356435316 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -181,7 +181,7 @@ { "label": "Run HA Core for Supervisor in devcontainer", "type": "shell", - "command": "HASSIO=${input:supervisorHost} HASSIO_TOKEN=${input:supervisorToken} script/core", + "command": "SUPERVISOR=${input:supervisorHost} SUPERVISOR_TOKEN=${input:supervisorToken} script/core", "isBackground": true, "group": { "kind": "build", From 5951f5c5c4cb76b6f564f53d579196352fd36465 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Mon, 30 May 2022 16:56:13 +0200 Subject: [PATCH 06/19] Prefer CSS variables in custom panel entrypoint (#12818) --- src/entrypoints/custom-panel.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/entrypoints/custom-panel.ts b/src/entrypoints/custom-panel.ts index 4a40fcab04..00cdb756a1 100644 --- a/src/entrypoints/custom-panel.ts +++ b/src/entrypoints/custom-panel.ts @@ -51,11 +51,15 @@ function initialize( const style = document.createElement("style"); style.innerHTML = ` - body { margin:0; } + body { + margin:0; + background-color: var(--primary-background-color, #fafafa); + color: var(--primary-text-color, #212121); + } @media (prefers-color-scheme: dark) { body { - background-color: #111111; - color: #e1e1e1; + background-color: var(--primary-background-color, #111111); + color: var(--primary-text-color, #e1e1e1); } }`; document.head.appendChild(style); From 1e011bfe3495bf1a9b06bec73f5e6ee58b5ce6f9 Mon Sep 17 00:00:00 2001 From: Xor <1726716+tgl0be@users.noreply.github.com> Date: Mon, 30 May 2022 16:57:00 +0200 Subject: [PATCH 07/19] Fix color of plant entity when state is problem (#12821) --- src/common/style/icon_color_css.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/common/style/icon_color_css.ts b/src/common/style/icon_color_css.ts index 3f4faeed99..cfddbe42c6 100644 --- a/src/common/style/icon_color_css.ts +++ b/src/common/style/icon_color_css.ts @@ -70,7 +70,9 @@ export const iconColorCSS = css` } } - ha-state-icon[data-domain="plant"][data-state="problem"], + ha-state-icon[data-domain="plant"][data-state="problem"] { + color: var(--state-icon-error-color); + } /* Color the icon if unavailable */ ha-state-icon[data-state="unavailable"] { From ab65ce819fdbeac55ca2fdd38ab69456c99b66c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Mon, 30 May 2022 16:57:28 +0200 Subject: [PATCH 08/19] Fallback to 0 for undefined offsets (#12823) --- .../trigger/types/ha-automation-trigger-calendar.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-calendar.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-calendar.ts index 51717108c2..7d205322cd 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-calendar.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-calendar.ts @@ -106,7 +106,9 @@ export class HaCalendarTrigger extends LitElement implements TriggerElement { const offsetType = ev.detail.value.offset_type === "before" ? "-" : ""; const newTrigger = { ...ev.detail.value, - offset: `${offsetType}${duration.hours}:${duration.minutes}:${duration.seconds}`, + offset: `${offsetType}${duration.hours ?? 0}:${duration.minutes ?? 0}:${ + duration.seconds ?? 0 + }`, }; delete newTrigger.offset_type; fireEvent(this, "value-changed", { value: newTrigger }); From c1d6b510655d9f9bd8755c6e48d2b4896b1d4952 Mon Sep 17 00:00:00 2001 From: wizmo2 Date: Mon, 30 May 2022 11:19:48 -0400 Subject: [PATCH 09/19] Scale oversized brand thumbnails in media browser (#12820) * resize brand icons * add newline JIC * Now Prettier * created brand-image style --- .../media-player/ha-media-player-browse.ts | 14 ++++++++++++-- src/util/brands-url.ts | 3 +++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/components/media-player/ha-media-player-browse.ts b/src/components/media-player/ha-media-player-browse.ts index 23cacf1fc7..4ee029ee13 100644 --- a/src/components/media-player/ha-media-player-browse.ts +++ b/src/components/media-player/ha-media-player-browse.ts @@ -43,7 +43,11 @@ import { showAlertDialog } from "../../dialogs/generic/show-dialog-box"; import { installResizeObserver } from "../../panels/lovelace/common/install-resize-observer"; import { haStyle } from "../../resources/styles"; import type { HomeAssistant } from "../../types"; -import { brandsUrl, extractDomainFromBrandUrl } from "../../util/brands-url"; +import { + brandsUrl, + extractDomainFromBrandUrl, + isBrandUrl, +} from "../../util/brands-url"; import { documentationUrl } from "../../util/documentation-url"; import "../entity/ha-entity-picker"; import "../ha-alert"; @@ -563,6 +567,8 @@ export class HaMediaPlayerBrowse extends LitElement {
@@ -661,7 +667,7 @@ export class HaMediaPlayerBrowse extends LitElement { return (await getSignedPath(this.hass, thumbnailUrl)).path; } - if (thumbnailUrl.startsWith("https://brands.home-assistant.io")) { + if (isBrandUrl(thumbnailUrl)) { // The backend is not aware of the theme used by the users, // so we rewrite the URL to show a proper icon thumbnailUrl = brandsUrl({ @@ -1050,6 +1056,10 @@ export class HaMediaPlayerBrowse extends LitElement { background-size: contain; } + .brand-image { + background-size: 40%; + } + .children ha-card .icon-holder { display: flex; justify-content: center; diff --git a/src/util/brands-url.ts b/src/util/brands-url.ts index e4136bc199..7b0405b696 100644 --- a/src/util/brands-url.ts +++ b/src/util/brands-url.ts @@ -23,3 +23,6 @@ export const hardwareBrandsUrl = (options: HardwareBrandsOptions): string => }${options.manufacturer}${options.model ? `_${options.model}` : ""}.png`; export const extractDomainFromBrandUrl = (url: string) => url.split("/")[4]; + +export const isBrandUrl = (thumbnail: string | ""): boolean => + thumbnail.startsWith("https://brands.home-assistant.io/"); From e54802bd873498093ee92235516426e5188a3256 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Mon, 30 May 2022 10:56:24 -0500 Subject: [PATCH 10/19] Actually add Cloud URL Translation.... (#12813) --- src/translations/en.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/translations/en.json b/src/translations/en.json index 8b6c023faa..471e2b8d83 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2376,6 +2376,7 @@ "instance_is_available": "Your instance is available at your", "instance_will_be_available": "Your instance will be available at your", "link_learn_how_it_works": "Learn how it works", + "nabu_casa_url": "Nabu Casa URL", "certificate_info": "Certificate Info" }, "alexa": { From 10f63180eb7a40d8eb36b8128d3ead9dfae25dd9 Mon Sep 17 00:00:00 2001 From: Yosi Levy <37745463+yosilevy@users.noreply.github.com> Date: Mon, 30 May 2022 18:57:37 +0300 Subject: [PATCH 11/19] RTL Auth fix (#12746) --- src/common/util/compute_rtl.ts | 19 +++++++++++++++++++ .../ha-selector/ha-selector-text.ts | 3 +++ src/components/ha-textfield.ts | 1 + src/mixins/lit-localize-lite-mixin.ts | 10 ++++++++++ src/state/translations-mixin.ts | 16 +++++----------- 5 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/common/util/compute_rtl.ts b/src/common/util/compute_rtl.ts index d73bf8f859..7e71b1b15b 100644 --- a/src/common/util/compute_rtl.ts +++ b/src/common/util/compute_rtl.ts @@ -1,3 +1,4 @@ +import { LitElement } from "lit"; import { HomeAssistant } from "../../types"; export function computeRTL(hass: HomeAssistant) { @@ -15,3 +16,21 @@ export function computeRTLDirection(hass: HomeAssistant) { export function emitRTLDirection(rtl: boolean) { return rtl ? "rtl" : "ltr"; } + +export function computeDirectionStyles(isRTL: boolean, element: LitElement) { + const direction: string = emitRTLDirection(isRTL); + setDirectionStyles(direction, element); +} + +export function setDirectionStyles(direction: string, element: LitElement) { + element.style.direction = direction; + element.style.setProperty("--direction", direction); + element.style.setProperty( + "--float-start", + direction === "ltr" ? "left" : "right" + ); + element.style.setProperty( + "--float-end", + direction === "ltr" ? "right" : "left" + ); +} diff --git a/src/components/ha-selector/ha-selector-text.ts b/src/components/ha-selector/ha-selector-text.ts index b91e0edc33..7af6c36d27 100644 --- a/src/components/ha-selector/ha-selector-text.ts +++ b/src/components/ha-selector/ha-selector-text.ts @@ -103,6 +103,9 @@ export class HaTextSelector extends LitElement { --mdc-icon-button-size: 24px; --mdc-icon-size: 20px; color: var(--secondary-text-color); + inset-inline-start: initial; + inset-inline-end: 16px; + direction: var(--direction); } `; } diff --git a/src/components/ha-textfield.ts b/src/components/ha-textfield.ts index 1f8da98dde..0e0f3bd804 100644 --- a/src/components/ha-textfield.ts +++ b/src/components/ha-textfield.ts @@ -100,6 +100,7 @@ export class HaTextField extends TextFieldBase { inset-inline-end: initial !important; transform-origin: var(--float-start); direction: var(--direction); + transform-origin: var(--float-start); } .mdc-text-field--with-leading-icon.mdc-text-field--filled diff --git a/src/mixins/lit-localize-lite-mixin.ts b/src/mixins/lit-localize-lite-mixin.ts index 864bcd43e7..d95e29d0f6 100644 --- a/src/mixins/lit-localize-lite-mixin.ts +++ b/src/mixins/lit-localize-lite-mixin.ts @@ -3,6 +3,8 @@ import { property } from "lit/decorators"; import { computeLocalize, LocalizeFunc } from "../common/translations/localize"; import { Constructor, Resources } from "../types"; import { getLocalLanguage, getTranslation } from "../util/common-translation"; +import { translationMetadata } from "../resources/translations-metadata"; +import { computeDirectionStyles } from "../common/util/compute_rtl"; const empty = () => ""; @@ -25,6 +27,14 @@ export const litLocalizeLiteMixin = >( this._initializeLocalizeLite(); } + protected firstUpdated(changedProps: PropertyValues) { + super.firstUpdated(changedProps); + computeDirectionStyles( + translationMetadata.translations[this.language!].isRTL, + this + ); + } + protected updated(changedProperties: PropertyValues) { super.updated(changedProperties); if (changedProperties.get("translationFragment")) { diff --git a/src/state/translations-mixin.ts b/src/state/translations-mixin.ts index f399e06695..b84e711548 100644 --- a/src/state/translations-mixin.ts +++ b/src/state/translations-mixin.ts @@ -1,6 +1,9 @@ import { atLeastVersion } from "../common/config/version"; import { computeLocalize, LocalizeFunc } from "../common/translations/localize"; -import { computeRTLDirection } from "../common/util/compute_rtl"; +import { + computeRTLDirection, + setDirectionStyles, +} from "../common/util/compute_rtl"; import { debounce } from "../common/util/debounce"; import { getHassTranslations, @@ -188,17 +191,8 @@ export default >(superClass: T) => private _applyDirection(hass: HomeAssistant) { const direction = computeRTLDirection(hass); - this.style.direction = direction; document.dir = direction; - this.style.setProperty("--direction", direction); - this.style.setProperty( - "--float-start", - direction === "ltr" ? "left" : "right" - ); - this.style.setProperty( - "--float-end", - direction === "ltr" ? "right" : "left" - ); + setDirectionStyles(direction, this); } /** From afd41e79f01757d10ec2afef19b272af61f7ebc2 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 30 May 2022 19:53:39 +0200 Subject: [PATCH 12/19] Make blueprint picker wider (#12830) --- src/panels/config/automation/blueprint-automation-editor.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/panels/config/automation/blueprint-automation-editor.ts b/src/panels/config/automation/blueprint-automation-editor.ts index 1f7e26a452..074bbf9c32 100644 --- a/src/panels/config/automation/blueprint-automation-editor.ts +++ b/src/panels/config/automation/blueprint-automation-editor.ts @@ -315,7 +315,8 @@ export class HaBlueprintAutomationEditor extends LitElement { padding: 0 16px 16px; } ha-textarea, - ha-textfield { + ha-textfield, + ha-blueprint-picker { display: block; } h3 { From ceda911670638b57a470acab5c22d9104dfa0495 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 30 May 2022 10:30:04 -1000 Subject: [PATCH 13/19] Virtualize history panel (#12824) --- src/components/chart/ha-chart-base.ts | 20 ++ .../chart/state-history-chart-line.ts | 24 +-- .../chart/state-history-chart-timeline.ts | 50 ++--- src/components/chart/state-history-charts.ts | 174 ++++++++++++++---- src/panels/history/ha-panel-history.ts | 84 +++++---- 5 files changed, 233 insertions(+), 119 deletions(-) diff --git a/src/components/chart/ha-chart-base.ts b/src/components/chart/ha-chart-base.ts index afc048e5aa..170d933463 100644 --- a/src/components/chart/ha-chart-base.ts +++ b/src/components/chart/ha-chart-base.ts @@ -37,6 +37,26 @@ export default class HaChartBase extends LitElement { @state() private _hiddenDatasets: Set = new Set(); + private _releaseCanvas() { + // release the canvas memory to prevent + // safari from running out of memory. + if (this.chart) { + this.chart.destroy(); + } + } + + public disconnectedCallback() { + this._releaseCanvas(); + super.disconnectedCallback(); + } + + public connectedCallback() { + super.connectedCallback(); + if (this.hasUpdated) { + this._setupChart(); + } + } + protected firstUpdated() { this._setupChart(); this.data.datasets.forEach((dataset, index) => { diff --git a/src/components/chart/state-history-chart-line.ts b/src/components/chart/state-history-chart-line.ts index f355595c1d..f1ca3f4a19 100644 --- a/src/components/chart/state-history-chart-line.ts +++ b/src/components/chart/state-history-chart-line.ts @@ -28,11 +28,11 @@ class StateHistoryChartLine extends LitElement { @property({ type: Boolean }) public isSingleDevice = false; - @property({ attribute: false }) public endTime?: Date; + @property({ attribute: false }) public endTime!: Date; @state() private _chartData?: ChartData<"line">; - @state() private _chartOptions?: ChartOptions<"line">; + @state() private _chartOptions?: ChartOptions; protected render() { return html` @@ -57,6 +57,7 @@ class StateHistoryChartLine extends LitElement { locale: this.hass.locale, }, }, + suggestedMax: this.endTime, ticks: { maxRotation: 0, sampleSize: 5, @@ -130,28 +131,11 @@ class StateHistoryChartLine extends LitElement { const computedStyles = getComputedStyle(this); const entityStates = this.data; const datasets: ChartDataset<"line">[] = []; - let endTime: Date; - if (entityStates.length === 0) { return; } - endTime = - this.endTime || - // Get the highest date from the last date of each device - new Date( - Math.max( - ...entityStates.map((devSts) => - new Date( - devSts.states[devSts.states.length - 1].last_changed - ).getTime() - ) - ) - ); - if (endTime > new Date()) { - endTime = new Date(); - } - + const endTime = this.endTime; const names = this.names || {}; entityStates.forEach((states) => { const domain = states.domain; diff --git a/src/components/chart/state-history-chart-timeline.ts b/src/components/chart/state-history-chart-timeline.ts index 413ee0a831..00f7f5cadc 100644 --- a/src/components/chart/state-history-chart-timeline.ts +++ b/src/components/chart/state-history-chart-timeline.ts @@ -83,6 +83,8 @@ export class StateHistoryChartTimeline extends LitElement { @property({ attribute: false }) public data: TimelineEntity[] = []; + @property() public narrow!: boolean; + @property() public names: boolean | Record = false; @property() public unit?: string; @@ -91,7 +93,11 @@ export class StateHistoryChartTimeline extends LitElement { @property({ type: Boolean }) public isSingleDevice = false; - @property({ attribute: false }) public endTime?: Date; + @property({ type: Boolean }) public dataHasMultipleRows = false; + + @property({ attribute: false }) public startTime!: Date; + + @property({ attribute: false }) public endTime!: Date; @state() private _chartData?: ChartData<"timeline">; @@ -110,6 +116,8 @@ export class StateHistoryChartTimeline extends LitElement { public willUpdate(changedProps: PropertyValues) { if (!this.hasUpdated) { + const narrow = this.narrow; + const multipleRows = this.data.length !== 1 || this.dataHasMultipleRows; this._chartOptions = { maintainAspectRatio: false, parsing: false, @@ -123,6 +131,8 @@ export class StateHistoryChartTimeline extends LitElement { locale: this.hass.locale, }, }, + suggestedMin: this.startTime, + suggestedMax: this.endTime, ticks: { autoSkip: true, maxRotation: 0, @@ -153,11 +163,17 @@ export class StateHistoryChartTimeline extends LitElement { drawTicks: false, }, ticks: { - display: this.data.length !== 1, + display: multipleRows, }, afterSetDimensions: (y) => { y.maxWidth = y.chart.width * 0.18; }, + afterFit: function (scaleInstance) { + if (multipleRows) { + // ensure all the chart labels are the same width + scaleInstance.width = narrow ? 105 : 185; + } + }, position: computeRTL(this.hass) ? "right" : "left", }, }, @@ -208,34 +224,8 @@ export class StateHistoryChartTimeline extends LitElement { stateHistory = []; } - const startTime = new Date( - stateHistory.reduce( - (minTime, stateInfo) => - Math.min(minTime, new Date(stateInfo.data[0].last_changed).getTime()), - new Date().getTime() - ) - ); - - // end time is Math.max(startTime, last_event) - let endTime = - this.endTime || - new Date( - stateHistory.reduce( - (maxTime, stateInfo) => - Math.max( - maxTime, - new Date( - stateInfo.data[stateInfo.data.length - 1].last_changed - ).getTime() - ), - startTime.getTime() - ) - ); - - if (endTime > new Date()) { - endTime = new Date(); - } - + const startTime = this.startTime; + const endTime = this.endTime; const labels: string[] = []; const datasets: ChartDataset<"timeline">[] = []; const names = this.names || {}; diff --git a/src/components/chart/state-history-charts.ts b/src/components/chart/state-history-charts.ts index ae5d7844eb..58cb0b321d 100644 --- a/src/components/chart/state-history-charts.ts +++ b/src/components/chart/state-history-charts.ts @@ -1,3 +1,4 @@ +import "@lit-labs/virtualizer"; import { css, CSSResultGroup, @@ -6,12 +7,29 @@ import { PropertyValues, TemplateResult, } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, state, eventOptions } from "lit/decorators"; import { isComponentLoaded } from "../../common/config/is_component_loaded"; -import { HistoryResult } from "../../data/history"; +import { + HistoryResult, + LineChartUnit, + TimelineEntity, +} from "../../data/history"; import type { HomeAssistant } from "../../types"; import "./state-history-chart-line"; import "./state-history-chart-timeline"; +import { restoreScroll } from "../../common/decorators/restore-scroll"; + +const CANVAS_TIMELINE_ROWS_CHUNK = 10; // Split up the canvases to avoid hitting the render limit + +const chunkData = (inputArray: any[], chunks: number) => + inputArray.reduce((results, item, idx) => { + const chunkIdx = Math.floor(idx / chunks); + if (!results[chunkIdx]) { + results[chunkIdx] = []; + } + results[chunkIdx].push(item); + return results; + }, []); @customElement("state-history-charts") class StateHistoryCharts extends LitElement { @@ -19,8 +37,13 @@ class StateHistoryCharts extends LitElement { @property({ attribute: false }) public historyData!: HistoryResult; + @property() public narrow!: boolean; + @property({ type: Boolean }) public names = false; + @property({ type: Boolean, attribute: "virtualize", reflect: true }) + public virtualize = false; + @property({ attribute: false }) public endTime?: Date; @property({ type: Boolean, attribute: "up-to-now" }) public upToNow = false; @@ -29,6 +52,14 @@ class StateHistoryCharts extends LitElement { @property({ type: Boolean }) public isLoadingData = false; + @state() private _computedStartTime!: Date; + + @state() private _computedEndTime!: Date; + + // @ts-ignore + @restoreScroll(".container") private _savedScrollPos?: number; + + @eventOptions({ passive: true }) protected render(): TemplateResult { if (!isComponentLoaded(this.hass, "history")) { return html`
@@ -48,40 +79,76 @@ class StateHistoryCharts extends LitElement {
`; } - const computedEndTime = this.upToNow - ? new Date() - : this.endTime || new Date(); + const now = new Date(); - return html` - ${this.historyData.timeline.length - ? html` - - ` - : html``} - ${this.historyData.line.map( - (line) => html` - - ` - )} - `; + this._computedEndTime = + this.upToNow || !this.endTime || this.endTime > now ? now : this.endTime; + + this._computedStartTime = new Date( + this.historyData.timeline.reduce( + (minTime, stateInfo) => + Math.min(minTime, new Date(stateInfo.data[0].last_changed).getTime()), + new Date().getTime() + ) + ); + + const combinedItems = chunkData( + this.historyData.timeline, + CANVAS_TIMELINE_ROWS_CHUNK + ).concat(this.historyData.line); + + return this.virtualize + ? html`
+ + +
` + : html`${combinedItems.map((item, index) => + this._renderHistoryItem(item, index) + )}`; } + private _renderHistoryItem = ( + item: TimelineEntity[] | LineChartUnit, + index: number + ): TemplateResult => { + if (!item || index === undefined) { + return html``; + } + if (!Array.isArray(item)) { + return html`
+ +
`; + } + return html`
+ 1} + > +
`; + }; + protected shouldUpdate(changedProps: PropertyValues): boolean { return !(changedProps.size === 1 && changedProps.has("hass")); } @@ -96,6 +163,11 @@ class StateHistoryCharts extends LitElement { return !this.isLoadingData && historyDataEmpty; } + @eventOptions({ passive: true }) + private _saveScrollPos(e: Event) { + this._savedScrollPos = (e.target as HTMLDivElement).scrollTop; + } + static get styles(): CSSResultGroup { return css` :host { @@ -103,11 +175,47 @@ class StateHistoryCharts extends LitElement { /* height of single timeline chart = 60px */ min-height: 60px; } + + :host([virtualize]) { + height: 100%; + } + .info { text-align: center; line-height: 60px; color: var(--secondary-text-color); } + .container { + max-height: var(--history-max-height); + } + + .entry-container { + width: 100%; + } + + .entry-container:hover { + z-index: 1; + } + + :host([virtualize]) .entry-container { + padding-left: 1px; + padding-right: 1px; + } + + .container, + lit-virtualizer { + height: 100%; + width: 100%; + } + + lit-virtualizer { + contain: size layout !important; + } + + state-history-chart-timeline, + state-history-chart-line { + width: 100%; + } `; } } diff --git a/src/panels/history/ha-panel-history.ts b/src/panels/history/ha-panel-history.ts index a1c4554990..7caa849c0a 100644 --- a/src/panels/history/ha-panel-history.ts +++ b/src/panels/history/ha-panel-history.ts @@ -80,44 +80,44 @@ class HaPanelHistory extends LitElement { -
-
- +
+ - -
- ${this._isLoading - ? html`
- -
` - : html` - - - `} +
+ ${this._isLoading + ? html`
+ +
` + : html` + + + `} `; } @@ -235,6 +235,14 @@ class HaPanelHistory extends LitElement { padding: 0 16px 16px; } + state-history-charts { + height: calc(100vh - 136px); + } + + :host([narrow]) state-history-charts { + height: calc(100vh - 198px); + } + .progress-wrapper { height: calc(100vh - 136px); } @@ -243,6 +251,10 @@ class HaPanelHistory extends LitElement { height: calc(100vh - 198px); } + :host([virtualize]) { + height: 100%; + } + .progress-wrapper { position: relative; } From 077fa3f6b2452773d82a07213435f18799f6dacf Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 30 May 2022 19:03:51 -1000 Subject: [PATCH 14/19] Fix live logbook starting empty (#12833) --- src/panels/logbook/ha-logbook.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/panels/logbook/ha-logbook.ts b/src/panels/logbook/ha-logbook.ts index bf5e053027..2241d66038 100644 --- a/src/panels/logbook/ha-logbook.ts +++ b/src/panels/logbook/ha-logbook.ts @@ -310,7 +310,7 @@ export class HaLogbook extends LitElement { // Put newest ones on top. Reverse works in-place so // make a copy first. const newEntries = [...streamMessage.events].reverse(); - if (!this._logbookEntries) { + if (!this._logbookEntries || !this._logbookEntries.length) { this._logbookEntries = newEntries; return; } @@ -320,14 +320,16 @@ export class HaLogbook extends LitElement { return; } const nonExpiredRecords = this._nonExpiredRecords(purgeBeforePythonTime); - this._logbookEntries = - newEntries[0].when >= this._logbookEntries[0].when - ? // The new records are newer than the old records - // append the old records to the end of the new records - newEntries.concat(nonExpiredRecords) - : // The new records are older than the old records - // append the new records to the end of the old records - nonExpiredRecords.concat(newEntries); + this._logbookEntries = !nonExpiredRecords.length + ? // All existing entries expired + newEntries + : newEntries[0].when >= nonExpiredRecords[0].when + ? // The new records are newer than the old records + // append the old records to the end of the new records + newEntries.concat(nonExpiredRecords) + : // The new records are older than the old records + // append the new records to the end of the old records + nonExpiredRecords.concat(newEntries); }; private _updateTraceContexts = throttle(async () => { From a564ceb9e32d4c39bec50aa0d6ca9d5912f47626 Mon Sep 17 00:00:00 2001 From: Pawel Date: Tue, 31 May 2022 10:51:27 +0200 Subject: [PATCH 15/19] Add proper label for gas energy stats (#12828) Co-authored-by: Bram Kragten --- src/data/energy.ts | 42 +++++++------------ src/data/history.ts | 18 +++++++- .../energy/hui-energy-distribution-card.ts | 6 ++- .../cards/energy/hui-energy-gas-graph-card.ts | 29 +++++++++---- .../energy/hui-energy-solar-graph-card.ts | 23 +++++++--- .../energy/hui-energy-sources-table-card.ts | 4 +- .../energy/hui-energy-usage-graph-card.ts | 24 +++++++---- 7 files changed, 96 insertions(+), 50 deletions(-) diff --git a/src/data/energy.ts b/src/data/energy.ts index 54c2577ce5..b4fb94845e 100644 --- a/src/data/energy.ts +++ b/src/data/energy.ts @@ -240,6 +240,7 @@ export interface EnergyData { prefs: EnergyPreferences; info: EnergyInfo; stats: Statistics; + statsMetadata: Record; statsCompare: Statistics; co2SignalConfigEntry?: ConfigEntry; co2SignalEntity?: string; @@ -285,15 +286,6 @@ const getEnergyData = async ( const consumptionStatIDs: string[] = []; const statIDs: string[] = []; - const gasSources: GasSourceTypeEnergyPreference[] = - prefs.energy_sources.filter( - (source) => source.type === "gas" - ) as GasSourceTypeEnergyPreference[]; - const gasStatisticIdsWithMeta: StatisticsMetaData[] = - await getStatisticMetadata( - hass, - gasSources.map((source) => source.stat_energy_from) - ); for (const source of prefs.energy_sources) { if (source.type === "solar") { @@ -303,20 +295,6 @@ const getEnergyData = async ( if (source.type === "gas") { statIDs.push(source.stat_energy_from); - const entity = hass.states[source.stat_energy_from]; - if (!entity) { - for (const statisticIdWithMeta of gasStatisticIdsWithMeta) { - if ( - statisticIdWithMeta?.statistic_id === source.stat_energy_from && - statisticIdWithMeta?.unit_of_measurement - ) { - source.unit_of_measurement = - statisticIdWithMeta?.unit_of_measurement === "Wh" - ? "kWh" - : statisticIdWithMeta?.unit_of_measurement; - } - } - } if (source.stat_cost) { statIDs.push(source.stat_cost); } @@ -432,6 +410,12 @@ const getEnergyData = async ( } }); + const statsMetadataArray = await getStatisticMetadata(hass, statIDs); + const statsMetadata: Record = {}; + statsMetadataArray.forEach((x) => { + statsMetadata[x.statistic_id] = x; + }); + const data: EnergyData = { start, end, @@ -440,6 +424,7 @@ const getEnergyData = async ( info, prefs, stats, + statsMetadata, statsCompare, co2SignalConfigEntry, co2SignalEntity, @@ -628,13 +613,13 @@ export const getEnergyGasUnitCategory = ( export const getEnergyGasUnit = ( hass: HomeAssistant, - prefs: EnergyPreferences + prefs: EnergyPreferences, + statisticsMetaData: Record = {} ): string | undefined => { for (const source of prefs.energy_sources) { if (source.type !== "gas") { continue; } - const entity = hass.states[source.stat_energy_from]; if (entity?.attributes.unit_of_measurement) { // Wh is normalized to kWh by stats generation @@ -642,8 +627,11 @@ export const getEnergyGasUnit = ( ? "kWh" : entity.attributes.unit_of_measurement; } - if (source.unit_of_measurement) { - return source.unit_of_measurement; + const statisticIdWithMeta = statisticsMetaData[source.stat_energy_from]; + if (statisticIdWithMeta?.unit_of_measurement) { + return statisticIdWithMeta.unit_of_measurement === "Wh" + ? "kWh" + : statisticIdWithMeta.unit_of_measurement; } } return undefined; diff --git a/src/data/history.ts b/src/data/history.ts index 451ab126c7..7de7651a75 100644 --- a/src/data/history.ts +++ b/src/data/history.ts @@ -1,7 +1,10 @@ import { HassEntity } from "home-assistant-js-websocket"; import { computeDomain } from "../common/entity/compute_domain"; import { computeStateDisplayFromEntityAttributes } from "../common/entity/compute_state_display"; -import { computeStateNameFromEntityAttributes } from "../common/entity/compute_state_name"; +import { + computeStateName, + computeStateNameFromEntityAttributes, +} from "../common/entity/compute_state_name"; import { LocalizeFunc } from "../common/translations/localize"; import { HomeAssistant } from "../types"; import { FrontendLocaleData } from "./translation"; @@ -547,3 +550,16 @@ export const adjustStatisticsSum = ( start_time, adjustment, }); + +export const getStatisticLabel = ( + hass: HomeAssistant, + statisticsId: string, + statisticsMetaData: Record +): string => { + const entity = hass.states[statisticsId]; + if (entity) { + return computeStateName(entity); + } + const statisticMetaData = statisticsMetaData[statisticsId]; + return statisticMetaData?.name || statisticsId; +}; diff --git a/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts b/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts index c8b573c611..6b10b5b641 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts @@ -315,7 +315,11 @@ class HuiEnergyDistrubutionCard ${formatNumber(gasUsage || 0, this.hass.locale, { maximumFractionDigits: 1, })} - ${getEnergyGasUnit(this.hass, prefs) || "m³"} + ${getEnergyGasUnit( + this.hass, + prefs, + this._data.statsMetadata + ) || "m³"}
diff --git a/src/panels/lovelace/cards/energy/hui-energy-gas-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-gas-graph-card.ts index 17714b42f9..1d4b87943e 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-gas-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-gas-graph-card.ts @@ -26,7 +26,6 @@ import { import { labBrighten, labDarken } from "../../../../common/color/lab"; import { formatDateShort } from "../../../../common/datetime/format_date"; import { formatTime } from "../../../../common/datetime/format_time"; -import { computeStateName } from "../../../../common/entity/compute_state_name"; import { formatNumber, numberFormatToLocale, @@ -39,7 +38,11 @@ import { getEnergyDataCollection, getEnergyGasUnit, } from "../../../../data/energy"; -import { Statistics } from "../../../../data/history"; +import { + Statistics, + StatisticsMetaData, + getStatisticLabel, +} from "../../../../data/history"; import { FrontendLocaleData } from "../../../../data/translation"; import { SubscribeMixin } from "../../../../mixins/subscribe-mixin"; import { HomeAssistant } from "../../../../types"; @@ -270,7 +273,9 @@ export class HuiEnergyGasGraphCard (source) => source.type === "gas" ) as GasSourceTypeEnergyPreference[]; - this._unit = getEnergyGasUnit(this.hass, energyData.prefs) || "m³"; + this._unit = + getEnergyGasUnit(this.hass, energyData.prefs, energyData.statsMetadata) || + "m³"; const datasets: ChartDataset<"bar", ScatterDataPoint[]>[] = []; @@ -280,7 +285,12 @@ export class HuiEnergyGasGraphCard .trim(); datasets.push( - ...this._processDataSet(energyData.stats, gasSources, gasColor) + ...this._processDataSet( + energyData.stats, + energyData.statsMetadata, + gasSources, + gasColor + ) ); if (energyData.statsCompare) { @@ -298,6 +308,7 @@ export class HuiEnergyGasGraphCard datasets.push( ...this._processDataSet( energyData.statsCompare, + energyData.statsMetadata, gasSources, gasColor, true @@ -318,14 +329,14 @@ export class HuiEnergyGasGraphCard private _processDataSet( statistics: Statistics, + statisticsMetaData: Record, gasSources: GasSourceTypeEnergyPreference[], gasColor: string, compare = false ) { const data: ChartDataset<"bar", ScatterDataPoint[]>[] = []; - gasSources.forEach((source, idx) => { - const entity = this.hass.states[source.stat_energy_from]; + gasSources.forEach((source, idx) => { const modifiedColor = idx > 0 ? this.hass.themes.darkMode @@ -368,7 +379,11 @@ export class HuiEnergyGasGraphCard } data.push({ - label: entity ? computeStateName(entity) : source.stat_energy_from, + label: getStatisticLabel( + this.hass, + source.stat_energy_from, + statisticsMetaData + ), borderColor: compare ? borderColor + "7F" : borderColor, backgroundColor: compare ? borderColor + "32" : borderColor + "7F", data: gasConsumptionData, diff --git a/src/panels/lovelace/cards/energy/hui-energy-solar-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-solar-graph-card.ts index f3389b657d..c4fb6c3196 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-solar-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-solar-graph-card.ts @@ -40,7 +40,11 @@ import { getEnergySolarForecasts, SolarSourceTypeEnergyPreference, } from "../../../../data/energy"; -import { Statistics } from "../../../../data/history"; +import { + Statistics, + StatisticsMetaData, + getStatisticLabel, +} from "../../../../data/history"; import { FrontendLocaleData } from "../../../../data/translation"; import { SubscribeMixin } from "../../../../mixins/subscribe-mixin"; import { HomeAssistant } from "../../../../types"; @@ -289,7 +293,12 @@ export class HuiEnergySolarGraphCard .trim(); datasets.push( - ...this._processDataSet(energyData.stats, solarSources, solarColor) + ...this._processDataSet( + energyData.stats, + energyData.statsMetadata, + solarSources, + solarColor + ) ); if (energyData.statsCompare) { @@ -307,6 +316,7 @@ export class HuiEnergySolarGraphCard datasets.push( ...this._processDataSet( energyData.statsCompare, + energyData.statsMetadata, solarSources, solarColor, true @@ -339,6 +349,7 @@ export class HuiEnergySolarGraphCard private _processDataSet( statistics: Statistics, + statisticsMetaData: Record, solarSources: SolarSourceTypeEnergyPreference[], solarColor: string, compare = false @@ -346,8 +357,6 @@ export class HuiEnergySolarGraphCard const data: ChartDataset<"bar", ScatterDataPoint[]>[] = []; solarSources.forEach((source, idx) => { - const entity = this.hass.states[source.stat_energy_from]; - const modifiedColor = idx > 0 ? this.hass.themes.darkMode @@ -393,7 +402,11 @@ export class HuiEnergySolarGraphCard label: this.hass.localize( "ui.panel.lovelace.cards.energy.energy_solar_graph.production", { - name: entity ? computeStateName(entity) : source.stat_energy_from, + name: getStatisticLabel( + this.hass, + source.stat_energy_from, + statisticsMetaData + ), } ), borderColor: compare ? borderColor + "7F" : borderColor, diff --git a/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts b/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts index eee7891937..7dd37e9086 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts @@ -128,7 +128,9 @@ export class HuiEnergySourcesTableCard flow.stat_cost || flow.entity_energy_price || flow.number_energy_price ); - const gasUnit = getEnergyGasUnit(this.hass, this._data.prefs) || ""; + const gasUnit = + getEnergyGasUnit(this.hass, this._data.prefs, this._data.statsMetadata) || + ""; const compare = this._data.statsCompare !== undefined; diff --git a/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts index eea121b0f9..f8c76a0755 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts @@ -26,7 +26,6 @@ import { import { labBrighten, labDarken } from "../../../../common/color/lab"; import { formatDateShort } from "../../../../common/datetime/format_date"; import { formatTime } from "../../../../common/datetime/format_time"; -import { computeStateName } from "../../../../common/entity/compute_state_name"; import { formatNumber, numberFormatToLocale, @@ -34,7 +33,11 @@ import { import "../../../../components/chart/ha-chart-base"; import "../../../../components/ha-card"; import { EnergyData, getEnergyDataCollection } from "../../../../data/energy"; -import { Statistics } from "../../../../data/history"; +import { + Statistics, + StatisticsMetaData, + getStatisticLabel, +} from "../../../../data/history"; import { FrontendLocaleData } from "../../../../data/translation"; import { SubscribeMixin } from "../../../../mixins/subscribe-mixin"; import { HomeAssistant } from "../../../../types"; @@ -378,7 +381,14 @@ export class HuiEnergyUsageGraphCard this._compareEnd = energyData.endCompare; datasets.push( - ...this._processDataSet(energyData.stats, statIds, colors, labels, false) + ...this._processDataSet( + energyData.stats, + energyData.statsMetadata, + statIds, + colors, + labels, + false + ) ); if (energyData.statsCompare) { @@ -396,6 +406,7 @@ export class HuiEnergyUsageGraphCard datasets.push( ...this._processDataSet( energyData.statsCompare, + energyData.statsMetadata, statIds, colors, labels, @@ -411,6 +422,7 @@ export class HuiEnergyUsageGraphCard private _processDataSet( statistics: Statistics, + statisticsMetaData: Record, statIdsByCat: { to_grid?: string[] | undefined; from_grid?: string[] | undefined; @@ -580,8 +592,6 @@ export class HuiEnergyUsageGraphCard Object.entries(combinedData).forEach(([type, sources]) => { Object.entries(sources).forEach(([statId, source], idx) => { - const entity = this.hass.states[statId]; - const modifiedColor = idx > 0 ? this.hass.themes.darkMode @@ -610,9 +620,7 @@ export class HuiEnergyUsageGraphCard label: type in labels ? labels[type] - : entity - ? computeStateName(entity) - : statId, + : getStatisticLabel(this.hass, statId, statisticsMetaData), order: type === "used_solar" ? 1 From 881f6b05310d03496fb51b40a19e6a0da801ad8e Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 31 May 2022 15:29:32 +0200 Subject: [PATCH 16/19] Use icon button for compare on narrow (#12831) --- .../energy/hui-energy-sources-table-card.ts | 8 +-- .../components/hui-energy-period-selector.ts | 60 +++++++++++++------ 2 files changed, 45 insertions(+), 23 deletions(-) diff --git a/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts b/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts index 7dd37e9086..1cf7f1887f 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts @@ -851,7 +851,9 @@ export class HuiEnergySourcesTableCard )} ${compare - ? html` + ? html` ${formatNumber( totalGasCostCompare + totalGridCostCompare, this.hass.locale, @@ -862,9 +864,7 @@ export class HuiEnergySourcesTableCard )} ${showCosts - ? html`` + ? html`` : ""}` : ""} diff --git a/src/panels/lovelace/components/hui-energy-period-selector.ts b/src/panels/lovelace/components/hui-energy-period-selector.ts index fb03502150..6d102da24c 100644 --- a/src/panels/lovelace/components/hui-energy-period-selector.ts +++ b/src/panels/lovelace/components/hui-energy-period-selector.ts @@ -1,5 +1,10 @@ import "@material/mwc-button/mwc-button"; -import { mdiChevronLeft, mdiChevronRight } from "@mdi/js"; +import { + mdiChevronLeft, + mdiChevronRight, + mdiCompare, + mdiCompareRemove, +} from "@mdi/js"; import { addDays, addMonths, @@ -40,13 +45,15 @@ export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) { @property() public collectionKey?: string; + @property({ type: Boolean, reflect: true }) public narrow = false; + @state() _startDate?: Date; @state() _endDate?: Date; @state() private _period?: "day" | "week" | "month" | "year"; - @state() private _compare? = false; + @state() private _compare = false; public connectedCallback() { super.connectedCallback(); @@ -136,14 +143,24 @@ export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) { dense @value-changed=${this._handleView} > - - Compare data - + ${this.narrow + ? html` + Compare data + ` + : html` + Compare data + `} `; @@ -260,9 +277,6 @@ export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) { :host([narrow]) .row { flex-direction: column-reverse; } - :host([narrow]) .period { - margin-bottom: 8px; - } .label { display: flex; justify-content: flex-end; @@ -275,6 +289,17 @@ export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) { justify-content: flex-end; align-items: flex-end; } + :host([narrow]) .period { + margin-bottom: 8px; + } + mwc-button { + margin-left: 8px; + } + ha-icon-button { + margin-left: 4px; + --mdc-icon-size: 20px; + } + ha-icon-button.active::before, mwc-button.active::before { top: 0; left: 0; @@ -288,14 +313,11 @@ export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) { transition: opacity 15ms linear, background-color 15ms linear; opacity: var(--mdc-icon-button-ripple-opacity, 0.12); } + ha-icon-button.active::before { + border-radius: 50%; + } .compare { position: relative; - margin-left: 8px; - width: max-content; - } - :host([narrow]) .compare { - margin-left: auto; - margin-top: 8px; } :host { --mdc-button-outline-color: currentColor; From 6842c479d69d355146231776fbb0894227c3b07c Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 31 May 2022 15:36:04 +0200 Subject: [PATCH 17/19] Add compare data to individual devices graph (#12829) --- .../energy/hui-energy-devices-graph-card.ts | 109 ++++++++++++++---- src/translations/en.json | 3 +- 2 files changed, 91 insertions(+), 21 deletions(-) diff --git a/src/panels/lovelace/cards/energy/hui-energy-devices-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-devices-graph-card.ts index 97fc452517..621d579c93 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-devices-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-devices-graph-card.ts @@ -43,8 +43,6 @@ export class HuiEnergyDevicesGraphCard @state() private _config?: EnergyDevicesGraphCardConfig; - @state() private _data?: Statistics; - @state() private _chartData: ChartData = { datasets: [] }; @query("ha-chart-base") private _chart?: HaChartBase; @@ -162,19 +160,24 @@ export class HuiEnergyDevicesGraphCard energyData.start ); - this._data = await fetchStatistics( - this.hass, - addHours(energyData.start, -1), - energyData.end, - energyData.prefs.device_consumption.map( - (device) => device.stat_consumption - ), - dayDifference > 35 ? "month" : dayDifference > 2 ? "day" : "hour" + const devices = energyData.prefs.device_consumption.map( + (device) => device.stat_consumption ); + const period = + dayDifference > 35 ? "month" : dayDifference > 2 ? "day" : "hour"; + const startMinHour = addHours(energyData.start, -1); - Object.values(this._data).forEach((stat) => { + const data = await fetchStatistics( + this.hass, + startMinHour, + energyData.end, + devices, + period + ); + + Object.values(data).forEach((stat) => { // if the start of the first value is after the requested period, we have the first data point, and should add a zero point if (stat.length && new Date(stat[0].start) > startMinHour) { stat.unshift({ @@ -187,9 +190,41 @@ export class HuiEnergyDevicesGraphCard } }); - const data: Array>["data"]> = []; + let compareData: Statistics | undefined; + + if (energyData.startCompare && energyData.endCompare) { + const startCompareMinHour = addHours(energyData.startCompare, -1); + compareData = await fetchStatistics( + this.hass, + startCompareMinHour, + energyData.endCompare, + devices, + period + ); + + Object.values(compareData).forEach((stat) => { + // if the start of the first value is after the requested period, we have the first data point, and should add a zero point + if (stat.length && new Date(stat[0].start) > startMinHour) { + stat.unshift({ + ...stat[0], + start: startCompareMinHour.toISOString(), + end: startCompareMinHour.toISOString(), + sum: 0, + state: 0, + }); + } + }); + } + + const chartData: Array>["data"]> = + []; + const chartDataCompare: Array< + ChartDataset<"bar", ParsedDataType<"bar">>["data"] + > = []; const borderColor: string[] = []; + const borderColorCompare: string[] = []; const backgroundColor: string[] = []; + const backgroundColorCompare: string[] = []; const datasets: ChartDataset<"bar", ParsedDataType<"bar">[]>[] = [ { @@ -198,35 +233,69 @@ export class HuiEnergyDevicesGraphCard ), borderColor, backgroundColor, - data, - barThickness: 20, + data: chartData, + barThickness: compareData ? 10 : 20, }, ]; + if (compareData) { + datasets.push({ + label: this.hass.localize( + "ui.panel.lovelace.cards.energy.energy_devices_graph.previous_energy_usage" + ), + borderColor: borderColorCompare, + backgroundColor: backgroundColorCompare, + data: chartDataCompare, + barThickness: 10, + }); + } + energyData.prefs.device_consumption.forEach((device, idx) => { const value = - device.stat_consumption in this._data! - ? calculateStatisticSumGrowth(this._data![device.stat_consumption]) || - 0 + device.stat_consumption in data + ? calculateStatisticSumGrowth(data[device.stat_consumption]) || 0 : 0; - data.push({ + chartData.push({ // @ts-expect-error y: device.stat_consumption, x: value, idx, }); + + if (compareData) { + const compareValue = + device.stat_consumption in compareData + ? calculateStatisticSumGrowth( + compareData[device.stat_consumption] + ) || 0 + : 0; + + chartDataCompare.push({ + // @ts-expect-error + y: device.stat_consumption, + x: compareValue, + idx, + }); + } }); - data.sort((a, b) => b.x - a.x); + chartData.sort((a, b) => b.x - a.x); - data.forEach((d: any) => { + chartData.forEach((d: any) => { const color = getColorByIndex(d.idx); borderColor.push(color); backgroundColor.push(color + "7F"); }); + chartDataCompare.forEach((d: any) => { + const color = getColorByIndex(d.idx); + + borderColorCompare.push(color + "7F"); + backgroundColorCompare.push(color + "32"); + }); + this._chartData = { datasets, }; diff --git a/src/translations/en.json b/src/translations/en.json index 471e2b8d83..28a71bc0c7 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3405,7 +3405,8 @@ "go_to_energy_dashboard": "Go to the energy dashboard" }, "energy_devices_graph": { - "energy_usage": "Energy usage" + "energy_usage": "Energy usage", + "previous_energy_usage": "Previous energy usage" }, "carbon_consumed_gauge": { "card_indicates_energy_used": "This card indicates how much of the energy consumed by your home was generated using non-fossil fuels like solar, wind and nuclear. The higher, the better!", From 1938fb89e6aee75da049144fa034864212fe31c4 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 31 May 2022 15:51:20 +0200 Subject: [PATCH 18/19] Bumped version to 20220531.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 583e743a07..0de1e8d8da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20220526.0" +version = "20220531.0" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md" From ced37aab4c7193d16dbd350742bbab95a1a79a7b Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 31 May 2022 15:57:19 +0200 Subject: [PATCH 19/19] Make hardware item non interactive --- src/panels/config/hardware/ha-config-hardware.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/panels/config/hardware/ha-config-hardware.ts b/src/panels/config/hardware/ha-config-hardware.ts index f5c09b5022..532e344340 100644 --- a/src/panels/config/hardware/ha-config-hardware.ts +++ b/src/panels/config/hardware/ha-config-hardware.ts @@ -125,6 +125,7 @@ class HaConfigHardware extends LitElement {