From 982c940381875b4cd0857b612ccf7eed1187a6a1 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 26 Jul 2021 17:26:22 -0400 Subject: [PATCH 01/43] Try to fix download of zwave_js logs (#9612) --- .../integrations/integration-panels/zwave_js/zwave_js-logs.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-logs.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-logs.ts index 0e380a18b3..9edf199887 100644 --- a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-logs.ts +++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-logs.ts @@ -131,7 +131,9 @@ class ZWaveJSLogs extends SubscribeMixin(LitElement) { private _downloadLogs() { fileDownload( this, - `data:text/plain;charset=utf-8,${encodeURI(this._textarea!.value)}`, + `data:text/plain;charset=utf-8,${encodeURIComponent( + this._textarea!.value + )}`, `zwave_js.log` ); } From 32c6fb14dd9b65debc4d7f475ba9d548e9409ac3 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 26 Jul 2021 17:59:49 -0400 Subject: [PATCH 02/43] Re-add success/failure indicator on call service button in dev tools (#9600) * Re-add success/failure indicator on call service button in dev tools * move success outside of try block * Export HaProgressButton --- src/components/buttons/ha-progress-button.ts | 2 +- .../service/developer-tools-service.ts | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/components/buttons/ha-progress-button.ts b/src/components/buttons/ha-progress-button.ts index 259ee75310..ed6ec6ec86 100644 --- a/src/components/buttons/ha-progress-button.ts +++ b/src/components/buttons/ha-progress-button.ts @@ -5,7 +5,7 @@ import { customElement, property, query } from "lit/decorators"; import "../ha-circular-progress"; @customElement("ha-progress-button") -class HaProgressButton extends LitElement { +export class HaProgressButton extends LitElement { @property({ type: Boolean }) public disabled = false; @property({ type: Boolean }) public progress = false; diff --git a/src/panels/developer-tools/service/developer-tools-service.ts b/src/panels/developer-tools/service/developer-tools-service.ts index 282337d0c0..8ca0492192 100644 --- a/src/panels/developer-tools/service/developer-tools-service.ts +++ b/src/panels/developer-tools/service/developer-tools-service.ts @@ -9,7 +9,8 @@ import { computeDomain } from "../../../common/entity/compute_domain"; import { computeObjectId } from "../../../common/entity/compute_object_id"; import { hasTemplate } from "../../../common/string/has-template"; import { extractSearchParam } from "../../../common/url/search-params"; -import "../../../components/buttons/ha-progress-button"; +import { HaProgressButton } from "../../../components/buttons/ha-progress-button"; + import "../../../components/entity/ha-entity-picker"; import "../../../components/ha-card"; import "../../../components/ha-expansion-panel"; @@ -135,11 +136,15 @@ class HaPanelDevService extends LitElement { >` : ""} - + ${this.hass.localize( "ui.panel.developer-tools.tabs.services.call_service" )} - + @@ -295,7 +300,8 @@ class HaPanelDevService extends LitElement { } ); - private async _callService() { + private async _callService(ev) { + const button = ev.currentTarget as HaProgressButton; if (!this._serviceData?.service) { return; } @@ -310,6 +316,7 @@ class HaPanelDevService extends LitElement { return; } forwardHaptic("failure"); + button.actionError(); showToast(this, { message: this.hass.localize( @@ -318,7 +325,9 @@ class HaPanelDevService extends LitElement { this._serviceData.service ) + ` ${err.message}`, }); + return; } + button.actionSuccess(); } private _toggleYaml() { From b05dc5141c4ba29582d93b4b7ff4dd21c7dc4804 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 27 Jul 2021 00:01:32 +0200 Subject: [PATCH 03/43] Fix chart tooltip footer always rendering (#9614) --- src/components/chart/ha-chart-base.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/components/chart/ha-chart-base.ts b/src/components/chart/ha-chart-base.ts index 63b41aef70..9dd4889aa2 100644 --- a/src/components/chart/ha-chart-base.ts +++ b/src/components/chart/ha-chart-base.ts @@ -136,12 +136,9 @@ export default class HaChartBase extends LitElement { )} - ${this._tooltip.footer - ? // footer has white-space: pre; - // prettier-ignore - html`` @@ -324,7 +321,6 @@ export default class HaChartBase extends LitElement { } .chartTooltip .footer { font-weight: 500; - white-space: pre; } .chartTooltip .beforeBody { text-align: center; From ac31eedf65e757b3d3fb4555e2266350541cb7b3 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 27 Jul 2021 00:02:01 +0200 Subject: [PATCH 04/43] Bump material elements (#9610) --- package.json | 36 +- yarn.lock | 1073 +++++++++++++++++++++++++------------------------- 2 files changed, 558 insertions(+), 551 deletions(-) diff --git a/package.json b/package.json index b9628ec2a2..ac40d71b7f 100644 --- a/package.json +++ b/package.json @@ -43,24 +43,24 @@ "@fullcalendar/interaction": "5.1.0", "@fullcalendar/list": "5.1.0", "@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.6.0#./.yarn/patches/@lit-labs/virtualizer/0.7.0.patch", - "@material/chips": "=12.0.0-canary.1a8d06483.0", - "@material/data-table": "=12.0.0-canary.1a8d06483.0", - "@material/mwc-button": "0.22.0-canary.cc04657a.0", - "@material/mwc-checkbox": "0.22.0-canary.cc04657a.0", - "@material/mwc-circular-progress": "0.22.0-canary.cc04657a.0", - "@material/mwc-dialog": "0.22.0-canary.cc04657a.0", - "@material/mwc-fab": "0.22.0-canary.cc04657a.0", - "@material/mwc-formfield": "0.22.0-canary.cc04657a.0", - "@material/mwc-icon-button": "0.22.0-canary.cc04657a.0", - "@material/mwc-linear-progress": "0.22.0-canary.cc04657a.0", - "@material/mwc-list": "0.22.0-canary.cc04657a.0", - "@material/mwc-menu": "0.22.0-canary.cc04657a.0", - "@material/mwc-radio": "0.22.0-canary.cc04657a.0", - "@material/mwc-ripple": "0.22.0-canary.cc04657a.0", - "@material/mwc-switch": "0.22.0-canary.cc04657a.0", - "@material/mwc-tab": "0.22.0-canary.cc04657a.0", - "@material/mwc-tab-bar": "0.22.0-canary.cc04657a.0", - "@material/top-app-bar": "=12.0.0-canary.1a8d06483.0", + "@material/chips": "12.0.0-canary.22d29cbb4.0", + "@material/data-table": "12.0.0-canary.22d29cbb4.0", + "@material/mwc-button": "0.22.1", + "@material/mwc-checkbox": "0.22.1", + "@material/mwc-circular-progress": "0.22.1", + "@material/mwc-dialog": "0.22.1", + "@material/mwc-fab": "0.22.1", + "@material/mwc-formfield": "0.22.1", + "@material/mwc-icon-button": "0.22.1", + "@material/mwc-linear-progress": "0.22.1", + "@material/mwc-list": "0.22.1", + "@material/mwc-menu": "0.22.1", + "@material/mwc-radio": "0.22.1", + "@material/mwc-ripple": "0.22.1", + "@material/mwc-switch": "0.22.1", + "@material/mwc-tab": "0.22.1", + "@material/mwc-tab-bar": "0.22.1", + "@material/top-app-bar": "12.0.0-canary.22d29cbb4.0", "@mdi/js": "5.9.55", "@mdi/svg": "5.9.55", "@polymer/app-layout": "^3.1.0", diff --git a/yarn.lock b/yarn.lock index 63bd07e770..401cca699e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1876,795 +1876,802 @@ __metadata: languageName: node linkType: hard -"@material/animation@npm:12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/animation@npm:12.0.0-canary.1a8d06483.0" +"@material/animation@npm:12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/animation@npm:12.0.0-canary.22d29cbb4.0" dependencies: tslib: ^2.1.0 - checksum: dda21b9a4c8660e675876951ce8484d501e1b19ffc85eebb4dc0ad530f382c733fc579ac9927dd53fa4c3e050057da35813445d63b348a06f55342678243ae32 + checksum: b618c92d61b82cb989c7a714d041104f12ea50b00de58dd9508ce2113f8eb776c1c6f4e69cca2a76b02c69ba955d9663e5bfcdca7f9523968ea733f66b8bcdeb languageName: node linkType: hard -"@material/base@npm:12.0.0-canary.1a8d06483.0, @material/base@npm:=12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/base@npm:12.0.0-canary.1a8d06483.0" +"@material/base@npm:12.0.0-canary.22d29cbb4.0, @material/base@npm:=12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/base@npm:12.0.0-canary.22d29cbb4.0" dependencies: tslib: ^2.1.0 - checksum: 36fcfbb6401a4a044ad4377ac4bee54cc3d657c554ba060a8d70d407d3e928b939b13b5085040561e2adec1598c5e191c3e6cb17a63f427447cbe40012432c10 + checksum: a2fc63d0080f488abe5b79f4ff6acbc58168c5fc66d35bc57c3fb085d12d5ad79ef98101159726655c896a353f6333bede4524470983496cd5a4bd845795b034 languageName: node linkType: hard -"@material/button@npm:12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/button@npm:12.0.0-canary.1a8d06483.0" +"@material/button@npm:12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/button@npm:12.0.0-canary.22d29cbb4.0" dependencies: - "@material/density": 12.0.0-canary.1a8d06483.0 - "@material/dom": 12.0.0-canary.1a8d06483.0 - "@material/elevation": 12.0.0-canary.1a8d06483.0 - "@material/feature-targeting": 12.0.0-canary.1a8d06483.0 - "@material/ripple": 12.0.0-canary.1a8d06483.0 - "@material/rtl": 12.0.0-canary.1a8d06483.0 - "@material/shape": 12.0.0-canary.1a8d06483.0 - "@material/theme": 12.0.0-canary.1a8d06483.0 - "@material/touch-target": 12.0.0-canary.1a8d06483.0 - "@material/typography": 12.0.0-canary.1a8d06483.0 + "@material/density": 12.0.0-canary.22d29cbb4.0 + "@material/dom": 12.0.0-canary.22d29cbb4.0 + "@material/elevation": 12.0.0-canary.22d29cbb4.0 + "@material/feature-targeting": 12.0.0-canary.22d29cbb4.0 + "@material/ripple": 12.0.0-canary.22d29cbb4.0 + "@material/rtl": 12.0.0-canary.22d29cbb4.0 + "@material/shape": 12.0.0-canary.22d29cbb4.0 + "@material/theme": 12.0.0-canary.22d29cbb4.0 + "@material/touch-target": 12.0.0-canary.22d29cbb4.0 + "@material/typography": 12.0.0-canary.22d29cbb4.0 tslib: ^2.1.0 - checksum: a05725f747eebafcff09e5600a2b1586f59f28d10d75bfeed7228eea799cc4f5f1f4a188058a93bdf90ead87d6a2a6630cb4a01d00f477955a1c3129e677895c + checksum: 8c5eff310a7ce64fd5b8e4efd35c3d11162f8c00925d26c988267ad8de2e2445e5d844a2dc958f2b880f3d14fbd68223d68222a1734942e57e756f4530d27e26 languageName: node linkType: hard -"@material/checkbox@npm:12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/checkbox@npm:12.0.0-canary.1a8d06483.0" +"@material/checkbox@npm:12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/checkbox@npm:12.0.0-canary.22d29cbb4.0" dependencies: - "@material/animation": 12.0.0-canary.1a8d06483.0 - "@material/base": 12.0.0-canary.1a8d06483.0 - "@material/density": 12.0.0-canary.1a8d06483.0 - "@material/dom": 12.0.0-canary.1a8d06483.0 - "@material/feature-targeting": 12.0.0-canary.1a8d06483.0 - "@material/ripple": 12.0.0-canary.1a8d06483.0 - "@material/theme": 12.0.0-canary.1a8d06483.0 - "@material/touch-target": 12.0.0-canary.1a8d06483.0 + "@material/animation": 12.0.0-canary.22d29cbb4.0 + "@material/base": 12.0.0-canary.22d29cbb4.0 + "@material/density": 12.0.0-canary.22d29cbb4.0 + "@material/dom": 12.0.0-canary.22d29cbb4.0 + "@material/feature-targeting": 12.0.0-canary.22d29cbb4.0 + "@material/ripple": 12.0.0-canary.22d29cbb4.0 + "@material/theme": 12.0.0-canary.22d29cbb4.0 + "@material/touch-target": 12.0.0-canary.22d29cbb4.0 tslib: ^2.1.0 - checksum: efff30de456725b314cdb0358be5ef56d34dfdf3022cae5f308e7a9dbcc6850855bf684ef20c9b78de5c7ef69b436d879357c7660a861ddb88b2d4c04cbda037 + checksum: 6fe21a7554b379887ddaf4ea6e78b6e9f46f259f08a688b28f2869f3fb229c334881b6f716947f2f93b0f0cc2229fd7b482cb7a56a12e4bb3680cc71ed30f9a5 languageName: node linkType: hard -"@material/chips@npm:=12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/chips@npm:12.0.0-canary.1a8d06483.0" +"@material/chips@npm:12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/chips@npm:12.0.0-canary.22d29cbb4.0" dependencies: - "@material/animation": 12.0.0-canary.1a8d06483.0 - "@material/base": 12.0.0-canary.1a8d06483.0 - "@material/checkbox": 12.0.0-canary.1a8d06483.0 - "@material/density": 12.0.0-canary.1a8d06483.0 - "@material/dom": 12.0.0-canary.1a8d06483.0 - "@material/elevation": 12.0.0-canary.1a8d06483.0 - "@material/feature-targeting": 12.0.0-canary.1a8d06483.0 - "@material/ripple": 12.0.0-canary.1a8d06483.0 - "@material/rtl": 12.0.0-canary.1a8d06483.0 - "@material/shape": 12.0.0-canary.1a8d06483.0 - "@material/theme": 12.0.0-canary.1a8d06483.0 - "@material/touch-target": 12.0.0-canary.1a8d06483.0 - "@material/typography": 12.0.0-canary.1a8d06483.0 + "@material/animation": 12.0.0-canary.22d29cbb4.0 + "@material/base": 12.0.0-canary.22d29cbb4.0 + "@material/checkbox": 12.0.0-canary.22d29cbb4.0 + "@material/density": 12.0.0-canary.22d29cbb4.0 + "@material/dom": 12.0.0-canary.22d29cbb4.0 + "@material/elevation": 12.0.0-canary.22d29cbb4.0 + "@material/feature-targeting": 12.0.0-canary.22d29cbb4.0 + "@material/ripple": 12.0.0-canary.22d29cbb4.0 + "@material/rtl": 12.0.0-canary.22d29cbb4.0 + "@material/shape": 12.0.0-canary.22d29cbb4.0 + "@material/theme": 12.0.0-canary.22d29cbb4.0 + "@material/touch-target": 12.0.0-canary.22d29cbb4.0 + "@material/typography": 12.0.0-canary.22d29cbb4.0 tslib: ^2.1.0 - checksum: 45cffe1991e63edc9aa05bc3e655688b9c5cef76e8d5ebc0813f76123f0e4d5d5d152f3197d96736485f577bf5176f29cc2ae38030342738b8ffa3254093f7c1 + checksum: ea7b9a3e2c6f2a9e56dd521e0838c7a0f4866ccf5a15cb4fb02d453b772ef298c95b256a7088d1b64aee72b8b25b4af6d0b1be50562611223d1847a902dda972 languageName: node linkType: hard -"@material/circular-progress@npm:=12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/circular-progress@npm:12.0.0-canary.1a8d06483.0" +"@material/circular-progress@npm:=12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/circular-progress@npm:12.0.0-canary.22d29cbb4.0" dependencies: - "@material/animation": 12.0.0-canary.1a8d06483.0 - "@material/base": 12.0.0-canary.1a8d06483.0 - "@material/feature-targeting": 12.0.0-canary.1a8d06483.0 - "@material/progress-indicator": 12.0.0-canary.1a8d06483.0 - "@material/theme": 12.0.0-canary.1a8d06483.0 + "@material/animation": 12.0.0-canary.22d29cbb4.0 + "@material/base": 12.0.0-canary.22d29cbb4.0 + "@material/feature-targeting": 12.0.0-canary.22d29cbb4.0 + "@material/progress-indicator": 12.0.0-canary.22d29cbb4.0 + "@material/theme": 12.0.0-canary.22d29cbb4.0 tslib: ^2.1.0 - checksum: 4bfd4bd6438ac19e88a968318b8714efa3f65bf6f939750e7b904e4d56619bb089896580fb829f9c83cd991e947c70efa6ff4acdd06b0e741cd2ca639062ccff + checksum: d9e1187cbc522f81c2dd38854f5c39ad1cac572881df1b742bd9adc80f0bbdb00644c3e8b4ec5b608ef4ee164e67b0bfc0fe0f5528d8a987117dacaeaeaadf2c languageName: node linkType: hard -"@material/data-table@npm:=12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/data-table@npm:12.0.0-canary.1a8d06483.0" +"@material/data-table@npm:12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/data-table@npm:12.0.0-canary.22d29cbb4.0" dependencies: - "@material/animation": 12.0.0-canary.1a8d06483.0 - "@material/base": 12.0.0-canary.1a8d06483.0 - "@material/checkbox": 12.0.0-canary.1a8d06483.0 - "@material/density": 12.0.0-canary.1a8d06483.0 - "@material/dom": 12.0.0-canary.1a8d06483.0 - "@material/elevation": 12.0.0-canary.1a8d06483.0 - "@material/feature-targeting": 12.0.0-canary.1a8d06483.0 - "@material/icon-button": 12.0.0-canary.1a8d06483.0 - "@material/linear-progress": 12.0.0-canary.1a8d06483.0 - "@material/list": 12.0.0-canary.1a8d06483.0 - "@material/menu": 12.0.0-canary.1a8d06483.0 - "@material/rtl": 12.0.0-canary.1a8d06483.0 - "@material/select": 12.0.0-canary.1a8d06483.0 - "@material/shape": 12.0.0-canary.1a8d06483.0 - "@material/theme": 12.0.0-canary.1a8d06483.0 - "@material/touch-target": 12.0.0-canary.1a8d06483.0 - "@material/typography": 12.0.0-canary.1a8d06483.0 + "@material/animation": 12.0.0-canary.22d29cbb4.0 + "@material/base": 12.0.0-canary.22d29cbb4.0 + "@material/checkbox": 12.0.0-canary.22d29cbb4.0 + "@material/density": 12.0.0-canary.22d29cbb4.0 + "@material/dom": 12.0.0-canary.22d29cbb4.0 + "@material/elevation": 12.0.0-canary.22d29cbb4.0 + "@material/feature-targeting": 12.0.0-canary.22d29cbb4.0 + "@material/icon-button": 12.0.0-canary.22d29cbb4.0 + "@material/linear-progress": 12.0.0-canary.22d29cbb4.0 + "@material/list": 12.0.0-canary.22d29cbb4.0 + "@material/menu": 12.0.0-canary.22d29cbb4.0 + "@material/rtl": 12.0.0-canary.22d29cbb4.0 + "@material/select": 12.0.0-canary.22d29cbb4.0 + "@material/shape": 12.0.0-canary.22d29cbb4.0 + "@material/theme": 12.0.0-canary.22d29cbb4.0 + "@material/touch-target": 12.0.0-canary.22d29cbb4.0 + "@material/typography": 12.0.0-canary.22d29cbb4.0 tslib: ^2.1.0 - checksum: 61a5abbc681742a1cd259fa52bda067324313658d438cbef86ee35c6a6858ffb41fa082d592fa8c8390104507e1c2629d0e21f7552598ac4ad2d8d3a00c768cb + checksum: a4ff3483f9baf5bc33342503ca30b26d18cdf20335d6138ab1d8f49f2a690cc2f5efecadd44776b537d95d3bee5bfb0942ba81a8e50f0734ee5892eca2db8a43 languageName: node linkType: hard -"@material/density@npm:12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/density@npm:12.0.0-canary.1a8d06483.0" +"@material/density@npm:12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/density@npm:12.0.0-canary.22d29cbb4.0" dependencies: tslib: ^2.1.0 - checksum: 1279f5a000f43bbb03d43c797a198381110da9baba44757889a69deea580aa25e4f21ffcad2e87a122b6f7c209592fb0a3475e2f9de1b4c2f13e5576f142cb4e + checksum: 29cb900ae2c6ba598a6cd37ea7a1125654b8fcf32d386046f70e2bfdf6fe80ee980e631b77268ad237e926cbe7c8b4723faad309f6b507576efbc21014deee0e languageName: node linkType: hard -"@material/dialog@npm:=12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/dialog@npm:12.0.0-canary.1a8d06483.0" +"@material/dialog@npm:=12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/dialog@npm:12.0.0-canary.22d29cbb4.0" dependencies: - "@material/animation": 12.0.0-canary.1a8d06483.0 - "@material/base": 12.0.0-canary.1a8d06483.0 - "@material/button": 12.0.0-canary.1a8d06483.0 - "@material/dom": 12.0.0-canary.1a8d06483.0 - "@material/elevation": 12.0.0-canary.1a8d06483.0 - "@material/feature-targeting": 12.0.0-canary.1a8d06483.0 - "@material/icon-button": 12.0.0-canary.1a8d06483.0 - "@material/ripple": 12.0.0-canary.1a8d06483.0 - "@material/rtl": 12.0.0-canary.1a8d06483.0 - "@material/shape": 12.0.0-canary.1a8d06483.0 - "@material/theme": 12.0.0-canary.1a8d06483.0 - "@material/touch-target": 12.0.0-canary.1a8d06483.0 - "@material/typography": 12.0.0-canary.1a8d06483.0 + "@material/animation": 12.0.0-canary.22d29cbb4.0 + "@material/base": 12.0.0-canary.22d29cbb4.0 + "@material/button": 12.0.0-canary.22d29cbb4.0 + "@material/dom": 12.0.0-canary.22d29cbb4.0 + "@material/elevation": 12.0.0-canary.22d29cbb4.0 + "@material/feature-targeting": 12.0.0-canary.22d29cbb4.0 + "@material/icon-button": 12.0.0-canary.22d29cbb4.0 + "@material/ripple": 12.0.0-canary.22d29cbb4.0 + "@material/rtl": 12.0.0-canary.22d29cbb4.0 + "@material/shape": 12.0.0-canary.22d29cbb4.0 + "@material/theme": 12.0.0-canary.22d29cbb4.0 + "@material/touch-target": 12.0.0-canary.22d29cbb4.0 + "@material/typography": 12.0.0-canary.22d29cbb4.0 tslib: ^2.1.0 - checksum: c77f329392e382d6af8c4196f6795c85a77b03e1a8117d9760eec1c1f0c282ea5a821378cfa6ad4f06bec8250c3867df59f3051ccea356912b4a5235a1aa2592 + checksum: 8f30743c42129c3211d04ec4436e745a5fed1e27b09f595f5cba1e9db18b3a16ad1bedfef04c6f3701963a9e178ae9920bf31e3c12f16140d10aa2a3cdf3f392 languageName: node linkType: hard -"@material/dom@npm:12.0.0-canary.1a8d06483.0, @material/dom@npm:=12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/dom@npm:12.0.0-canary.1a8d06483.0" +"@material/dom@npm:12.0.0-canary.22d29cbb4.0, @material/dom@npm:=12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/dom@npm:12.0.0-canary.22d29cbb4.0" dependencies: - "@material/feature-targeting": 12.0.0-canary.1a8d06483.0 + "@material/feature-targeting": 12.0.0-canary.22d29cbb4.0 tslib: ^2.1.0 - checksum: 5865b2cec09fe111cedb1126af1db346e108327e6de62cf1fe8709db389df3e73bcf7aee6a25d1ed5b1bf24642f8ef16129208c56448990c794b0227d199add3 + checksum: 9199392b23c62bb3edce88b52cd14b785568b689cfa397fb167d67d4c6c4fc401079b78840ebd64bbb8c3db3ca2f98ec0d4bbccbfd9ac7340a3862813fa3aaf3 languageName: node linkType: hard -"@material/elevation@npm:12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/elevation@npm:12.0.0-canary.1a8d06483.0" +"@material/elevation@npm:12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/elevation@npm:12.0.0-canary.22d29cbb4.0" dependencies: - "@material/animation": 12.0.0-canary.1a8d06483.0 - "@material/base": 12.0.0-canary.1a8d06483.0 - "@material/feature-targeting": 12.0.0-canary.1a8d06483.0 - "@material/theme": 12.0.0-canary.1a8d06483.0 + "@material/animation": 12.0.0-canary.22d29cbb4.0 + "@material/base": 12.0.0-canary.22d29cbb4.0 + "@material/feature-targeting": 12.0.0-canary.22d29cbb4.0 + "@material/theme": 12.0.0-canary.22d29cbb4.0 tslib: ^2.1.0 - checksum: 9163fc8c7967e327ff0d61548378925f8bc7a06ad7579207cdd6f5ba3d833af0114d533d91c0fb7eafdef244669102c37b8a0cbb626322f68a1ef6d55f203270 + checksum: 83542f35f234baf10b6aac1765be644e3b3b653fa4510a30c4456d487ab41bb13b8b61877ea2a3c672ab7bda711af89d33c37b19a18861f2cd410026b854d946 languageName: node linkType: hard -"@material/feature-targeting@npm:12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/feature-targeting@npm:12.0.0-canary.1a8d06483.0" +"@material/feature-targeting@npm:12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/feature-targeting@npm:12.0.0-canary.22d29cbb4.0" dependencies: tslib: ^2.1.0 - checksum: 049b6cbb4ed68063ac8cd0f0ea3843fe305e52b9cae4fbdece7f7b5211d71ba50e01702b07804e8a7cd60a32bd93b98dbefdd9796995abb2f901b1fab29a52f5 + checksum: 6b79778485e32b4032e78fca75b6557b5c1357209e42178a0a15177567f93cb204e83d9d326990057a6eb5d2c92bfe05948a72caa4626c9324a82c07893ffa64 languageName: node linkType: hard -"@material/floating-label@npm:12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/floating-label@npm:12.0.0-canary.1a8d06483.0" +"@material/floating-label@npm:12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/floating-label@npm:12.0.0-canary.22d29cbb4.0" dependencies: - "@material/animation": 12.0.0-canary.1a8d06483.0 - "@material/base": 12.0.0-canary.1a8d06483.0 - "@material/dom": 12.0.0-canary.1a8d06483.0 - "@material/feature-targeting": 12.0.0-canary.1a8d06483.0 - "@material/rtl": 12.0.0-canary.1a8d06483.0 - "@material/theme": 12.0.0-canary.1a8d06483.0 - "@material/typography": 12.0.0-canary.1a8d06483.0 + "@material/animation": 12.0.0-canary.22d29cbb4.0 + "@material/base": 12.0.0-canary.22d29cbb4.0 + "@material/dom": 12.0.0-canary.22d29cbb4.0 + "@material/feature-targeting": 12.0.0-canary.22d29cbb4.0 + "@material/rtl": 12.0.0-canary.22d29cbb4.0 + "@material/theme": 12.0.0-canary.22d29cbb4.0 + "@material/typography": 12.0.0-canary.22d29cbb4.0 tslib: ^2.1.0 - checksum: 49a926db76396dbe5b4b00e4526982afdc059617eaad7abbeee7a48fb05b19924feb512a5b1584e0e3a588fe9b2e572f7da60010810f2a72c8ab51ef29904344 + checksum: 609c8fd91c969023d6e7be37aa74a2ab47301c6b5e5e84185f90292fb28e8092c85c779a447ebb7aaf615f2b16210f6fd39e16cf72c5a7de025436efd76636f2 languageName: node linkType: hard -"@material/form-field@npm:=12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/form-field@npm:12.0.0-canary.1a8d06483.0" +"@material/form-field@npm:=12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/form-field@npm:12.0.0-canary.22d29cbb4.0" dependencies: - "@material/base": 12.0.0-canary.1a8d06483.0 - "@material/feature-targeting": 12.0.0-canary.1a8d06483.0 - "@material/ripple": 12.0.0-canary.1a8d06483.0 - "@material/rtl": 12.0.0-canary.1a8d06483.0 - "@material/theme": 12.0.0-canary.1a8d06483.0 - "@material/typography": 12.0.0-canary.1a8d06483.0 + "@material/base": 12.0.0-canary.22d29cbb4.0 + "@material/feature-targeting": 12.0.0-canary.22d29cbb4.0 + "@material/ripple": 12.0.0-canary.22d29cbb4.0 + "@material/rtl": 12.0.0-canary.22d29cbb4.0 + "@material/theme": 12.0.0-canary.22d29cbb4.0 + "@material/typography": 12.0.0-canary.22d29cbb4.0 tslib: ^2.1.0 - checksum: 66591c0e02d1a388c51b3e84464a832e6027d1d53ea90c7f2c381e3518576056320887c724d474093a7e4ab0e47df19d6b4190ef15bd5db6d7301534c1bc8c11 + checksum: e628ca67c7061cd11fddbf259d39bc2071a604f9ac9b2187239b8c954702d17ee3b31b483005e70c03acfa661a45789659221d53afbed7073858972f24db8296 languageName: node linkType: hard -"@material/icon-button@npm:12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/icon-button@npm:12.0.0-canary.1a8d06483.0" +"@material/icon-button@npm:12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/icon-button@npm:12.0.0-canary.22d29cbb4.0" dependencies: - "@material/base": 12.0.0-canary.1a8d06483.0 - "@material/density": 12.0.0-canary.1a8d06483.0 - "@material/feature-targeting": 12.0.0-canary.1a8d06483.0 - "@material/ripple": 12.0.0-canary.1a8d06483.0 - "@material/rtl": 12.0.0-canary.1a8d06483.0 - "@material/theme": 12.0.0-canary.1a8d06483.0 + "@material/base": 12.0.0-canary.22d29cbb4.0 + "@material/density": 12.0.0-canary.22d29cbb4.0 + "@material/feature-targeting": 12.0.0-canary.22d29cbb4.0 + "@material/ripple": 12.0.0-canary.22d29cbb4.0 + "@material/rtl": 12.0.0-canary.22d29cbb4.0 + "@material/theme": 12.0.0-canary.22d29cbb4.0 + "@material/touch-target": 12.0.0-canary.22d29cbb4.0 tslib: ^2.1.0 - checksum: f6c8fd25355a39995fed3afe87d4383cd4ad7dfd93b11ea2000d12294b84c3d6c046b64033fb211c13ab9e515d551e178ec841c3ae7ffdf9dc4824530836d52a + checksum: e743d1cd17e74fdeb9b64c17e7a22edc4c5d23e444e1217845d23cf12d26448eb2a5b84c72f289f164bff3811f24b691d5ba87b7734b202c36b71790209cdb09 languageName: node linkType: hard -"@material/line-ripple@npm:12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/line-ripple@npm:12.0.0-canary.1a8d06483.0" +"@material/line-ripple@npm:12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/line-ripple@npm:12.0.0-canary.22d29cbb4.0" dependencies: - "@material/animation": 12.0.0-canary.1a8d06483.0 - "@material/base": 12.0.0-canary.1a8d06483.0 - "@material/feature-targeting": 12.0.0-canary.1a8d06483.0 - "@material/theme": 12.0.0-canary.1a8d06483.0 + "@material/animation": 12.0.0-canary.22d29cbb4.0 + "@material/base": 12.0.0-canary.22d29cbb4.0 + "@material/feature-targeting": 12.0.0-canary.22d29cbb4.0 + "@material/theme": 12.0.0-canary.22d29cbb4.0 tslib: ^2.1.0 - checksum: e5a7d102819c5d23089afc3d9e85c771ed9f9b7eddce2a0339bb4ed37f3d0d10c36f90370855f9bef2f6e66d421ec082f732f7d930b503779615072299468002 + checksum: dbb5d43fbff73d419e8cddcd66bb4ee70fff36588ee40fae8ffc3633e39e711bf37e1a6042e2913e473adc4fe9479a7cf56a131d0b3a748d02d1fb28dc4ac2f0 languageName: node linkType: hard -"@material/linear-progress@npm:12.0.0-canary.1a8d06483.0, @material/linear-progress@npm:=12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/linear-progress@npm:12.0.0-canary.1a8d06483.0" +"@material/linear-progress@npm:12.0.0-canary.22d29cbb4.0, @material/linear-progress@npm:=12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/linear-progress@npm:12.0.0-canary.22d29cbb4.0" dependencies: - "@material/animation": 12.0.0-canary.1a8d06483.0 - "@material/base": 12.0.0-canary.1a8d06483.0 - "@material/feature-targeting": 12.0.0-canary.1a8d06483.0 - "@material/progress-indicator": 12.0.0-canary.1a8d06483.0 - "@material/rtl": 12.0.0-canary.1a8d06483.0 - "@material/theme": 12.0.0-canary.1a8d06483.0 + "@material/animation": 12.0.0-canary.22d29cbb4.0 + "@material/base": 12.0.0-canary.22d29cbb4.0 + "@material/feature-targeting": 12.0.0-canary.22d29cbb4.0 + "@material/progress-indicator": 12.0.0-canary.22d29cbb4.0 + "@material/rtl": 12.0.0-canary.22d29cbb4.0 + "@material/theme": 12.0.0-canary.22d29cbb4.0 tslib: ^2.1.0 - checksum: 41433c048ffeef51bd8962876f443858e00f3451abdc0b28fcfb2793a2f55653967c61d6f1190f3b735606dfc70cc451932b6d6ab4a2ec0beac35acdeb0f041f + checksum: 78d48593186a45f5ff6b7e5de6fc93b54508d593272348a5b8316695c4fc3bb86cd41df019533d896f0255f202a9d254ac6d409ef7824faade99b7a9b359c667 languageName: node linkType: hard -"@material/list@npm:12.0.0-canary.1a8d06483.0, @material/list@npm:=12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/list@npm:12.0.0-canary.1a8d06483.0" +"@material/list@npm:12.0.0-canary.22d29cbb4.0, @material/list@npm:=12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/list@npm:12.0.0-canary.22d29cbb4.0" dependencies: - "@material/base": 12.0.0-canary.1a8d06483.0 - "@material/density": 12.0.0-canary.1a8d06483.0 - "@material/dom": 12.0.0-canary.1a8d06483.0 - "@material/feature-targeting": 12.0.0-canary.1a8d06483.0 - "@material/ripple": 12.0.0-canary.1a8d06483.0 - "@material/rtl": 12.0.0-canary.1a8d06483.0 - "@material/shape": 12.0.0-canary.1a8d06483.0 - "@material/theme": 12.0.0-canary.1a8d06483.0 - "@material/typography": 12.0.0-canary.1a8d06483.0 + "@material/base": 12.0.0-canary.22d29cbb4.0 + "@material/density": 12.0.0-canary.22d29cbb4.0 + "@material/dom": 12.0.0-canary.22d29cbb4.0 + "@material/feature-targeting": 12.0.0-canary.22d29cbb4.0 + "@material/ripple": 12.0.0-canary.22d29cbb4.0 + "@material/rtl": 12.0.0-canary.22d29cbb4.0 + "@material/shape": 12.0.0-canary.22d29cbb4.0 + "@material/theme": 12.0.0-canary.22d29cbb4.0 + "@material/typography": 12.0.0-canary.22d29cbb4.0 tslib: ^2.1.0 - checksum: df0f753c3cf97bb5e0709dc51aeb79e837cec3adc95db9b25c4d9e0e2e27f0ce1f2e9baf40f0f81631c83ea935d89221c32a1ef6ac9de600476e088a9417b5da + checksum: 46a5845e398080fb1fd5bfcc3770a62e20b063b09effbeb58e8ed54047f9c9c98f77179e5ae9314a978f8a4a8b4b887f97b256fec663b2a2dc62ca456677e3cf languageName: node linkType: hard -"@material/menu-surface@npm:12.0.0-canary.1a8d06483.0, @material/menu-surface@npm:=12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/menu-surface@npm:12.0.0-canary.1a8d06483.0" +"@material/menu-surface@npm:12.0.0-canary.22d29cbb4.0, @material/menu-surface@npm:=12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/menu-surface@npm:12.0.0-canary.22d29cbb4.0" dependencies: - "@material/animation": 12.0.0-canary.1a8d06483.0 - "@material/base": 12.0.0-canary.1a8d06483.0 - "@material/elevation": 12.0.0-canary.1a8d06483.0 - "@material/feature-targeting": 12.0.0-canary.1a8d06483.0 - "@material/rtl": 12.0.0-canary.1a8d06483.0 - "@material/shape": 12.0.0-canary.1a8d06483.0 - "@material/theme": 12.0.0-canary.1a8d06483.0 + "@material/animation": 12.0.0-canary.22d29cbb4.0 + "@material/base": 12.0.0-canary.22d29cbb4.0 + "@material/elevation": 12.0.0-canary.22d29cbb4.0 + "@material/feature-targeting": 12.0.0-canary.22d29cbb4.0 + "@material/rtl": 12.0.0-canary.22d29cbb4.0 + "@material/shape": 12.0.0-canary.22d29cbb4.0 + "@material/theme": 12.0.0-canary.22d29cbb4.0 tslib: ^2.1.0 - checksum: 76fce5de3d2103b5108989329431037443567dc74eeff96738004e48ba10694527e74c3b65fbd1b8de06b848cfbc10d598808bc21a3b1d4fa6cb0713c8adb858 + checksum: 5d074bb79a76b312645035f289dff09e4b12e46accc265244d64117fac61ac005342479971c6e4f6c34ac491dad2ddaaa38f5769837390a0a9a0a351c9bd7001 languageName: node linkType: hard -"@material/menu@npm:12.0.0-canary.1a8d06483.0, @material/menu@npm:=12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/menu@npm:12.0.0-canary.1a8d06483.0" +"@material/menu@npm:12.0.0-canary.22d29cbb4.0, @material/menu@npm:=12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/menu@npm:12.0.0-canary.22d29cbb4.0" dependencies: - "@material/base": 12.0.0-canary.1a8d06483.0 - "@material/dom": 12.0.0-canary.1a8d06483.0 - "@material/elevation": 12.0.0-canary.1a8d06483.0 - "@material/feature-targeting": 12.0.0-canary.1a8d06483.0 - "@material/list": 12.0.0-canary.1a8d06483.0 - "@material/menu-surface": 12.0.0-canary.1a8d06483.0 - "@material/ripple": 12.0.0-canary.1a8d06483.0 - "@material/rtl": 12.0.0-canary.1a8d06483.0 - "@material/theme": 12.0.0-canary.1a8d06483.0 + "@material/base": 12.0.0-canary.22d29cbb4.0 + "@material/dom": 12.0.0-canary.22d29cbb4.0 + "@material/elevation": 12.0.0-canary.22d29cbb4.0 + "@material/feature-targeting": 12.0.0-canary.22d29cbb4.0 + "@material/list": 12.0.0-canary.22d29cbb4.0 + "@material/menu-surface": 12.0.0-canary.22d29cbb4.0 + "@material/ripple": 12.0.0-canary.22d29cbb4.0 + "@material/rtl": 12.0.0-canary.22d29cbb4.0 + "@material/theme": 12.0.0-canary.22d29cbb4.0 tslib: ^2.1.0 - checksum: 675a250865033c928d4bdead088f2bb09257f6c30b41008e01f900b0ac0c5c10ce533a2f32f71ad15520dc87b0ed27320e82ef6e938eb816b7462877c42b5ce9 + checksum: 26964feb90014c0d7528ff4f21352e806217a42f9a74b112be81a59e5b8d78bfba5f21501dc6852621b7ffd2819a7f6c3a931954b1caa77764515ee04f65f119 languageName: node linkType: hard -"@material/mwc-base@npm:0.22.0-canary.cc04657a.0": - version: 0.22.0-canary.cc04657a.0 - resolution: "@material/mwc-base@npm:0.22.0-canary.cc04657a.0" +"@material/mwc-base@npm:^0.22.1": + version: 0.22.1 + resolution: "@material/mwc-base@npm:0.22.1" dependencies: - "@material/base": =12.0.0-canary.1a8d06483.0 - "@material/dom": =12.0.0-canary.1a8d06483.0 - lit-element: ^2.5.0 + "@material/base": =12.0.0-canary.22d29cbb4.0 + "@material/dom": =12.0.0-canary.22d29cbb4.0 + lit-element: ^2.5.1 tslib: ^2.0.1 - checksum: 4e21573d05ea6a511920e37a6ca775f1e7f645f0160dc3f024c18ad6aa746e96e5baaebb93315eb91ceb9e9b48ceeb0ba3c06cc8e7a2049bdefc11e4c8cce75a + checksum: bcff1df68feef92a7657e0a628152eab9851a3e645fd276e196b68123b1704a710805a5674b88e9acaaa570ccff08b4b3c74bf89c5067683d0fd0e46c6210a89 languageName: node linkType: hard -"@material/mwc-button@npm:0.22.0-canary.cc04657a.0": - version: 0.22.0-canary.cc04657a.0 - resolution: "@material/mwc-button@npm:0.22.0-canary.cc04657a.0" +"@material/mwc-button@npm:0.22.1, @material/mwc-button@npm:^0.22.1": + version: 0.22.1 + resolution: "@material/mwc-button@npm:0.22.1" dependencies: - "@material/mwc-icon": 0.22.0-canary.cc04657a.0 - "@material/mwc-ripple": 0.22.0-canary.cc04657a.0 - lit-element: ^2.5.0 - lit-html: ^1.4.0 + "@material/mwc-icon": ^0.22.1 + "@material/mwc-ripple": ^0.22.1 + lit-element: ^2.5.1 + lit-html: ^1.4.1 tslib: ^2.0.1 - checksum: 9cab8bf29e4a0b70b7c20c363bae2e2dd48f22442ad78fe027bd11a8494adebc0f7f6563c3d28bc16d0554f9b4eb199246db55c69e639f6f88e11fb707199135 + checksum: 74854fd65e79f49d7bfd7f5335df14ea2bc3b673a9df1ab57daf88d2d1564ce31a0d61979931f05834754af478812e6d4384ef49258a8d6e04dea8d3fc50520c languageName: node linkType: hard -"@material/mwc-checkbox@npm:0.22.0-canary.cc04657a.0": - version: 0.22.0-canary.cc04657a.0 - resolution: "@material/mwc-checkbox@npm:0.22.0-canary.cc04657a.0" +"@material/mwc-checkbox@npm:0.22.1, @material/mwc-checkbox@npm:^0.22.1": + version: 0.22.1 + resolution: "@material/mwc-checkbox@npm:0.22.1" dependencies: - "@material/mwc-base": 0.22.0-canary.cc04657a.0 - "@material/mwc-ripple": 0.22.0-canary.cc04657a.0 - lit-element: ^2.5.0 - lit-html: ^1.4.0 + "@material/mwc-base": ^0.22.1 + "@material/mwc-ripple": ^0.22.1 + lit-element: ^2.5.1 + lit-html: ^1.4.1 tslib: ^2.0.1 - checksum: 10a319ca207f9e4ac680e65d351bc2fbd5b8fa39022cfe9c2cd2a67f66f431404d6109c4217f5e971e791eb6758912e0ccc77593b2d7da8612083c93a20c9f4c + checksum: a9e5d4de1709526eef2cc411ba9fc104aa83626f11f256f3ffa6ffaba80a31e583b48ab1ee7daea5bac9c61772635e5dc30185fa6e7ed1b1ee05cb0baa3124ee languageName: node linkType: hard -"@material/mwc-circular-progress@npm:0.22.0-canary.cc04657a.0": - version: 0.22.0-canary.cc04657a.0 - resolution: "@material/mwc-circular-progress@npm:0.22.0-canary.cc04657a.0" +"@material/mwc-circular-progress@npm:0.22.1": + version: 0.22.1 + resolution: "@material/mwc-circular-progress@npm:0.22.1" dependencies: - "@material/circular-progress": =12.0.0-canary.1a8d06483.0 - "@material/mwc-base": 0.22.0-canary.cc04657a.0 - "@material/theme": =12.0.0-canary.1a8d06483.0 - lit-element: ^2.5.0 - lit-html: ^1.4.0 + "@material/circular-progress": =12.0.0-canary.22d29cbb4.0 + "@material/mwc-base": ^0.22.1 + "@material/theme": =12.0.0-canary.22d29cbb4.0 + lit-element: ^2.5.1 + lit-html: ^1.4.1 tslib: ^2.0.1 - checksum: 0a6e7de2540bcdb8595079a7dff6fd535b6b8a47344980babfa9be2b7861f8e203c0effdafa3a2967dc1480bae98fbc4666c1c8a8a0da50b5a9702ec2534e645 + checksum: 18bf0f9ef006d7b57c3a24ce951eb2e4e6d1df0b26a22025278791932d40b26558cc7f3173da01dddc79616273918cc67fa2690a59ebb9a1af9dbe668435ce4a languageName: node linkType: hard -"@material/mwc-dialog@npm:0.22.0-canary.cc04657a.0": - version: 0.22.0-canary.cc04657a.0 - resolution: "@material/mwc-dialog@npm:0.22.0-canary.cc04657a.0" +"@material/mwc-dialog@npm:0.22.1": + version: 0.22.1 + resolution: "@material/mwc-dialog@npm:0.22.1" dependencies: - "@material/dialog": =12.0.0-canary.1a8d06483.0 - "@material/dom": =12.0.0-canary.1a8d06483.0 - "@material/mwc-base": 0.22.0-canary.cc04657a.0 - "@material/mwc-button": 0.22.0-canary.cc04657a.0 + "@material/dialog": =12.0.0-canary.22d29cbb4.0 + "@material/dom": =12.0.0-canary.22d29cbb4.0 + "@material/mwc-base": ^0.22.1 + "@material/mwc-button": ^0.22.1 blocking-elements: ^0.1.0 - lit-element: ^2.5.0 - lit-html: ^1.4.0 + lit-element: ^2.5.1 + lit-html: ^1.4.1 tslib: ^2.0.1 wicg-inert: ^3.0.0 - checksum: 5aafec2257fa0dce23f94911be443bf4dd7315c63f052f5452d10dac949f20f4c161cd0f72694661400400c99272886f28058985f71963e8e8bf492553fa39a1 + checksum: 0a025e3716dc0e20e134d4c2b4cd2bcd1324b68f5ae3bc50353f585d7cdba6e5e531befca08a73e56297089ba867a2b65f0fff413d9ef6e7e5aeda6f71dccc81 languageName: node linkType: hard -"@material/mwc-fab@npm:0.22.0-canary.cc04657a.0": - version: 0.22.0-canary.cc04657a.0 - resolution: "@material/mwc-fab@npm:0.22.0-canary.cc04657a.0" +"@material/mwc-fab@npm:0.22.1": + version: 0.22.1 + resolution: "@material/mwc-fab@npm:0.22.1" dependencies: - "@material/mwc-ripple": 0.22.0-canary.cc04657a.0 - lit-element: ^2.5.0 - lit-html: ^1.4.0 + "@material/mwc-ripple": ^0.22.1 + lit-element: ^2.5.1 + lit-html: ^1.4.1 tslib: ^2.0.1 - checksum: 7fabaa77153420dcd4196e9c2ccfba300900248135a56c79c394e3c6ab7cc8b02feb897f777d3926248287be55d7541579e6e92402f539cf83e0f63efccdaccd + checksum: e866f88eddf7f4271e2b4f226a793875471d4862138fded3745db86390c21661e7c8191d31289ce7c67d20530dc4487606a1be6a5b81905b867b27d272e03fae languageName: node linkType: hard -"@material/mwc-formfield@npm:0.22.0-canary.cc04657a.0": - version: 0.22.0-canary.cc04657a.0 - resolution: "@material/mwc-formfield@npm:0.22.0-canary.cc04657a.0" +"@material/mwc-formfield@npm:0.22.1": + version: 0.22.1 + resolution: "@material/mwc-formfield@npm:0.22.1" dependencies: - "@material/form-field": =12.0.0-canary.1a8d06483.0 - "@material/mwc-base": 0.22.0-canary.cc04657a.0 - lit-element: ^2.5.0 - lit-html: ^1.4.0 + "@material/form-field": =12.0.0-canary.22d29cbb4.0 + "@material/mwc-base": ^0.22.1 + lit-element: ^2.5.1 + lit-html: ^1.4.1 tslib: ^2.0.1 - checksum: a5756519b4173b444f89342f0ea6f091dd8ec74542ba0415f89c108dfa6a97dca7a37e7acff4e84099dfad06636a09c60c54046010882e3908ea48d343a0943f + checksum: ba915330e6e0a6a5b3a1f37cf97620426822b761282c6d127bcd53c8eec841141013e3da796221263c47654a9eea80604d830589acf8a29502b4a76a19272769 languageName: node linkType: hard -"@material/mwc-icon-button@npm:0.22.0-canary.cc04657a.0": - version: 0.22.0-canary.cc04657a.0 - resolution: "@material/mwc-icon-button@npm:0.22.0-canary.cc04657a.0" +"@material/mwc-icon-button@npm:0.22.1": + version: 0.22.1 + resolution: "@material/mwc-icon-button@npm:0.22.1" dependencies: - "@material/mwc-ripple": 0.22.0-canary.cc04657a.0 - lit-element: ^2.5.0 + "@material/mwc-ripple": ^0.22.1 + lit-element: ^2.5.1 tslib: ^2.0.1 - checksum: 7c6c385d88cf2a3eb4db4f5cf1d88e080dc9cb0350da8d50ac9433f6df90c3bb41c764f53fb420c5f8c6b20ec8359c5a250142bbdfc41228179e919b97206828 + checksum: 821d45563310c969b6a11d4679c68234c0fe085384f035ca7dbdbbcc89f94a3afaa2730316fa5bb96414384e46c8016a13cc7a28da1f928a3bad4a35bf06ea11 languageName: node linkType: hard -"@material/mwc-icon@npm:0.22.0-canary.cc04657a.0": - version: 0.22.0-canary.cc04657a.0 - resolution: "@material/mwc-icon@npm:0.22.0-canary.cc04657a.0" +"@material/mwc-icon@npm:^0.22.1": + version: 0.22.1 + resolution: "@material/mwc-icon@npm:0.22.1" dependencies: - lit-element: ^2.5.0 + lit-element: ^2.5.1 tslib: ^2.0.1 - checksum: e6b1c9bb9ee0d2d3356511434743261f97dd024c5a733cb6ee842e5ae4ffc89cefd8a57eb594423f857146d83449e62ce43df9795d332077c7b9f4caa5a554f5 + checksum: 4f4aa981766005854d52bdcd102885b9f137fa392646e03f7b76f1af879e230131b331eccec45e7c698315fc89da668fc56e22f8d68f79ee0b493e74d01478e4 languageName: node linkType: hard -"@material/mwc-linear-progress@npm:0.22.0-canary.cc04657a.0": - version: 0.22.0-canary.cc04657a.0 - resolution: "@material/mwc-linear-progress@npm:0.22.0-canary.cc04657a.0" +"@material/mwc-linear-progress@npm:0.22.1": + version: 0.22.1 + resolution: "@material/mwc-linear-progress@npm:0.22.1" dependencies: - "@material/linear-progress": =12.0.0-canary.1a8d06483.0 - "@material/theme": =12.0.0-canary.1a8d06483.0 - lit-element: ^2.5.0 - lit-html: ^1.4.0 + "@material/linear-progress": =12.0.0-canary.22d29cbb4.0 + "@material/mwc-base": ^0.22.1 + "@material/theme": =12.0.0-canary.22d29cbb4.0 + lit-element: ^2.5.1 + lit-html: ^1.4.1 tslib: ^2.0.1 - checksum: 78026c84ec71bdf22b4c8603014ec79d5ddef13370e4d9a34a27ee53077d8129d43603dcf05738278bf284bed2417e34298aa0736a037c54c337d80c54ea724b + checksum: 248074aa2d38782a9cf55584eead50122845090a89cb03d3c0807e423608ebd67212f46868c806b75a42a359a002380fde0ae3cc0321be953b5355c8ca33fa1a languageName: node linkType: hard -"@material/mwc-list@npm:0.22.0-canary.cc04657a.0": - version: 0.22.0-canary.cc04657a.0 - resolution: "@material/mwc-list@npm:0.22.0-canary.cc04657a.0" +"@material/mwc-list@npm:0.22.1, @material/mwc-list@npm:^0.22.1": + version: 0.22.1 + resolution: "@material/mwc-list@npm:0.22.1" dependencies: - "@material/base": =12.0.0-canary.1a8d06483.0 - "@material/dom": =12.0.0-canary.1a8d06483.0 - "@material/list": =12.0.0-canary.1a8d06483.0 - "@material/mwc-base": 0.22.0-canary.cc04657a.0 - "@material/mwc-checkbox": 0.22.0-canary.cc04657a.0 - "@material/mwc-radio": 0.22.0-canary.cc04657a.0 - "@material/mwc-ripple": 0.22.0-canary.cc04657a.0 - lit-element: ^2.5.0 - lit-html: ^1.4.0 + "@material/base": =12.0.0-canary.22d29cbb4.0 + "@material/dom": =12.0.0-canary.22d29cbb4.0 + "@material/list": =12.0.0-canary.22d29cbb4.0 + "@material/mwc-base": ^0.22.1 + "@material/mwc-checkbox": ^0.22.1 + "@material/mwc-radio": ^0.22.1 + "@material/mwc-ripple": ^0.22.1 + lit-element: ^2.5.1 + lit-html: ^1.4.1 tslib: ^2.0.1 - checksum: bfa8c62ce4a06377542162ce78662a06ab0ab61b51803db4bae4a5156e7b417a259426187d7338a5691cc77005c1abf5b8f5874d46c9f9f88350f261ff8670f9 + checksum: 4f61a99ad47fb87ee40a16027bd0d793c334f9a1c570fce0f304c2844bb3ddf8b3ecd22e32a2f44c7047e47a61a56a57a2ccf37f4a8cb57dfd6025d0f48f320c languageName: node linkType: hard -"@material/mwc-menu@npm:0.22.0-canary.cc04657a.0": - version: 0.22.0-canary.cc04657a.0 - resolution: "@material/mwc-menu@npm:0.22.0-canary.cc04657a.0" +"@material/mwc-menu@npm:0.22.1": + version: 0.22.1 + resolution: "@material/mwc-menu@npm:0.22.1" dependencies: - "@material/menu": =12.0.0-canary.1a8d06483.0 - "@material/menu-surface": =12.0.0-canary.1a8d06483.0 - "@material/mwc-base": 0.22.0-canary.cc04657a.0 - "@material/mwc-list": 0.22.0-canary.cc04657a.0 - "@material/shape": =12.0.0-canary.1a8d06483.0 - "@material/theme": =12.0.0-canary.1a8d06483.0 - lit-element: ^2.5.0 - lit-html: ^1.4.0 + "@material/menu": =12.0.0-canary.22d29cbb4.0 + "@material/menu-surface": =12.0.0-canary.22d29cbb4.0 + "@material/mwc-base": ^0.22.1 + "@material/mwc-list": ^0.22.1 + "@material/shape": =12.0.0-canary.22d29cbb4.0 + "@material/theme": =12.0.0-canary.22d29cbb4.0 + lit-element: ^2.5.1 + lit-html: ^1.4.1 tslib: ^2.0.1 - checksum: 2eaa25fd0846a4855124ab05ec24bed177dea0f8aed333eb472ceb9962665217a57dab02a321584322f7f70da85afbebc29c7fd44af12b79c8502611b4988812 + checksum: caa0120bed96e16b981666e6d4a593e8ff1184e10f56ad42143851a0c794b7ed620f4442576c782ec15bef33b0bdf1b7d4a29e6b0af78c732ecc5563a8ef28e5 languageName: node linkType: hard -"@material/mwc-radio@npm:0.22.0-canary.cc04657a.0": - version: 0.22.0-canary.cc04657a.0 - resolution: "@material/mwc-radio@npm:0.22.0-canary.cc04657a.0" +"@material/mwc-radio@npm:0.22.1, @material/mwc-radio@npm:^0.22.1": + version: 0.22.1 + resolution: "@material/mwc-radio@npm:0.22.1" dependencies: - "@material/mwc-base": 0.22.0-canary.cc04657a.0 - "@material/mwc-ripple": 0.22.0-canary.cc04657a.0 - "@material/radio": =12.0.0-canary.1a8d06483.0 - lit-element: ^2.5.0 + "@material/mwc-base": ^0.22.1 + "@material/mwc-ripple": ^0.22.1 + "@material/radio": =12.0.0-canary.22d29cbb4.0 + lit-element: ^2.5.1 tslib: ^2.0.1 - checksum: 19c041f2e9e6159ee5bdfb3a20218aa7e205f40a8da63e23ab148c626729d8853e1fe080a06a161f60c3691c89e2285c8fc781b7cc14644959a770e39ea87974 + checksum: 32f728d4bccfdee78a6a6eb6a550f946bd7a82b019968f4e7473dc97c87176af4530488b3e40ac00afa308df4e8a555dfa09307cc1e17b6937837126a71e5aff languageName: node linkType: hard -"@material/mwc-ripple@npm:0.22.0-canary.cc04657a.0": - version: 0.22.0-canary.cc04657a.0 - resolution: "@material/mwc-ripple@npm:0.22.0-canary.cc04657a.0" +"@material/mwc-ripple@npm:0.22.1, @material/mwc-ripple@npm:^0.22.1": + version: 0.22.1 + resolution: "@material/mwc-ripple@npm:0.22.1" dependencies: - "@material/dom": =12.0.0-canary.1a8d06483.0 - "@material/mwc-base": 0.22.0-canary.cc04657a.0 - "@material/ripple": =12.0.0-canary.1a8d06483.0 - lit-element: ^2.5.0 - lit-html: ^1.4.0 + "@material/dom": =12.0.0-canary.22d29cbb4.0 + "@material/mwc-base": ^0.22.1 + "@material/ripple": =12.0.0-canary.22d29cbb4.0 + lit-element: ^2.5.1 + lit-html: ^1.4.1 tslib: ^2.0.1 - checksum: 7c09ca245d356f4e53f3f1e1ddcca84d2e280755e9ff287affb31bd1cffd2f31b5bad21f5c2eb8fc02a1aeb2ea2e06b11dcfd6d2cb1f42899a6e935f786f5a8a + checksum: 4377c5b25e69d2fdd172f98cb02eabf15e73af2293338110766024aa5a86b1ffc9876985ad8f731edb8466126c200f8cbcd88b5559a1788366f194942ea96873 languageName: node linkType: hard -"@material/mwc-switch@npm:0.22.0-canary.cc04657a.0": - version: 0.22.0-canary.cc04657a.0 - resolution: "@material/mwc-switch@npm:0.22.0-canary.cc04657a.0" +"@material/mwc-switch@npm:0.22.1": + version: 0.22.1 + resolution: "@material/mwc-switch@npm:0.22.1" dependencies: - "@material/mwc-base": 0.22.0-canary.cc04657a.0 - "@material/mwc-ripple": 0.22.0-canary.cc04657a.0 - "@material/switch": =12.0.0-canary.1a8d06483.0 - lit-element: ^2.5.0 + "@material/mwc-base": ^0.22.1 + "@material/mwc-ripple": ^0.22.1 + "@material/switch": =12.0.0-canary.22d29cbb4.0 + lit-element: ^2.5.1 tslib: ^2.0.1 - checksum: aea397e88419b1c673230d213a76a4d42b39a5837572131158196dc098cff3a71d219a89e139d3ddf65cf4405f9e473ea19fd7699013547a1db64bb6ad6a312d + checksum: 737ec4fb06eaed5bd9f2cf91afbd7b7dd474a5a7eb3e75c488805e87a8425e43ed68dbecbe9f941a96e069ac6d5f3cb14732f74cb8babe18a69158312e9391d1 languageName: node linkType: hard -"@material/mwc-tab-bar@npm:0.22.0-canary.cc04657a.0": - version: 0.22.0-canary.cc04657a.0 - resolution: "@material/mwc-tab-bar@npm:0.22.0-canary.cc04657a.0" +"@material/mwc-tab-bar@npm:0.22.1": + version: 0.22.1 + resolution: "@material/mwc-tab-bar@npm:0.22.1" dependencies: - "@material/mwc-base": 0.22.0-canary.cc04657a.0 - "@material/mwc-tab": 0.22.0-canary.cc04657a.0 - "@material/mwc-tab-scroller": 0.22.0-canary.cc04657a.0 - "@material/tab": =12.0.0-canary.1a8d06483.0 - "@material/tab-bar": =12.0.0-canary.1a8d06483.0 - lit-element: ^2.5.0 + "@material/mwc-base": ^0.22.1 + "@material/mwc-tab": ^0.22.1 + "@material/mwc-tab-scroller": ^0.22.1 + "@material/tab": =12.0.0-canary.22d29cbb4.0 + "@material/tab-bar": =12.0.0-canary.22d29cbb4.0 + lit-element: ^2.5.1 tslib: ^2.0.1 - checksum: 836017ada48a4861f7cdbf2cb362a955db80a6e2b8d14fa67349d7411da587e143f9bd81a425b5441baef22697ebe2fa399048b58842069fc19d6c8a1be48c12 + checksum: f23f06a413a66c73c5157f5f455ed48b8be5472919b8631c39087d65d530b93ee9c8ed923dcba63a7dfdab12b0ef85f3dab711a46a4107ac8c069a83c880100f languageName: node linkType: hard -"@material/mwc-tab-indicator@npm:0.22.0-canary.cc04657a.0": - version: 0.22.0-canary.cc04657a.0 - resolution: "@material/mwc-tab-indicator@npm:0.22.0-canary.cc04657a.0" +"@material/mwc-tab-indicator@npm:^0.22.1": + version: 0.22.1 + resolution: "@material/mwc-tab-indicator@npm:0.22.1" dependencies: - "@material/mwc-base": 0.22.0-canary.cc04657a.0 - "@material/tab-indicator": =12.0.0-canary.1a8d06483.0 - lit-element: ^2.5.0 - lit-html: ^1.4.0 + "@material/mwc-base": ^0.22.1 + "@material/tab-indicator": =12.0.0-canary.22d29cbb4.0 + lit-element: ^2.5.1 + lit-html: ^1.4.1 tslib: ^2.0.1 - checksum: d379732c947f913bbbb075f3e8df65ed3a4dcba6e5a3d81cb73e33234543f6e4e894b21453b99648e2a44ec10d4658b44b6102d52ca8197f8ed8befe5cb29d26 + checksum: f4c79082c381910a3de4edcc8e6d33fc0ddccf51c74fa59abe55b18788414badc68b3377fb99a6f1a319c0ca292fb22521b1af781e938b2a9e3508c9e9daffd2 languageName: node linkType: hard -"@material/mwc-tab-scroller@npm:0.22.0-canary.cc04657a.0": - version: 0.22.0-canary.cc04657a.0 - resolution: "@material/mwc-tab-scroller@npm:0.22.0-canary.cc04657a.0" +"@material/mwc-tab-scroller@npm:^0.22.1": + version: 0.22.1 + resolution: "@material/mwc-tab-scroller@npm:0.22.1" dependencies: - "@material/dom": =12.0.0-canary.1a8d06483.0 - "@material/mwc-base": 0.22.0-canary.cc04657a.0 - "@material/tab-scroller": =12.0.0-canary.1a8d06483.0 - lit-element: ^2.5.0 + "@material/dom": =12.0.0-canary.22d29cbb4.0 + "@material/mwc-base": ^0.22.1 + "@material/tab-scroller": =12.0.0-canary.22d29cbb4.0 + lit-element: ^2.5.1 tslib: ^2.0.1 - checksum: f1408aabe3e5dcee7b1c5eab5d9dcab65f2696d92ee3a7dd1878d5d6e84665ec4c62ea141deeee1a327adc699de45d7d863bce9dcd4d709f5cef259f18882a0d + checksum: a3a111047c67abe578db9f6b483afa2bc02d3a7a3e9e4b04b4d19973dbda170acb334293508d4737207e9832c1cdbbf52433be174b3f660988b67e1bedf191c4 languageName: node linkType: hard -"@material/mwc-tab@npm:0.22.0-canary.cc04657a.0": - version: 0.22.0-canary.cc04657a.0 - resolution: "@material/mwc-tab@npm:0.22.0-canary.cc04657a.0" +"@material/mwc-tab@npm:0.22.1, @material/mwc-tab@npm:^0.22.1": + version: 0.22.1 + resolution: "@material/mwc-tab@npm:0.22.1" dependencies: - "@material/mwc-base": 0.22.0-canary.cc04657a.0 - "@material/mwc-ripple": 0.22.0-canary.cc04657a.0 - "@material/mwc-tab-indicator": 0.22.0-canary.cc04657a.0 - "@material/tab": =12.0.0-canary.1a8d06483.0 - lit-element: ^2.5.0 - lit-html: ^1.4.0 + "@material/mwc-base": ^0.22.1 + "@material/mwc-ripple": ^0.22.1 + "@material/mwc-tab-indicator": ^0.22.1 + "@material/tab": =12.0.0-canary.22d29cbb4.0 + lit-element: ^2.5.1 + lit-html: ^1.4.1 tslib: ^2.0.1 - checksum: 610c35da217f46b3f4dcaea25d62170113eb0ffc47ad3d066ae9e8e1600ff857f20764a6e9fa35e393f2facbc92f3f6755ed0116e9682b27e66fb83a36c4744b + checksum: 6f19ff9f8317d5875dc417b579cd0eeac052738a2b097ca45f7cc20acb47d7f193dc39a44805d9bb9d6df1f313c1ef4c5859e946cab5e572450f84b4989f60a2 languageName: node linkType: hard -"@material/notched-outline@npm:12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/notched-outline@npm:12.0.0-canary.1a8d06483.0" +"@material/notched-outline@npm:12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/notched-outline@npm:12.0.0-canary.22d29cbb4.0" dependencies: - "@material/base": 12.0.0-canary.1a8d06483.0 - "@material/feature-targeting": 12.0.0-canary.1a8d06483.0 - "@material/floating-label": 12.0.0-canary.1a8d06483.0 - "@material/rtl": 12.0.0-canary.1a8d06483.0 - "@material/shape": 12.0.0-canary.1a8d06483.0 - "@material/theme": 12.0.0-canary.1a8d06483.0 + "@material/base": 12.0.0-canary.22d29cbb4.0 + "@material/feature-targeting": 12.0.0-canary.22d29cbb4.0 + "@material/floating-label": 12.0.0-canary.22d29cbb4.0 + "@material/rtl": 12.0.0-canary.22d29cbb4.0 + "@material/shape": 12.0.0-canary.22d29cbb4.0 + "@material/theme": 12.0.0-canary.22d29cbb4.0 tslib: ^2.1.0 - checksum: d0856c5aeba272df09c5c76e99cc630c1614458a8184959fb4a82d54908db5632f09519080831290d9786e989a92601de432c5a4704b6481664ce3fc22d44150 + checksum: a8500bbdb59d12e1625f16ba6f85d266c6d591ea42f726d4eacd65dddc6455eefcfb0f39011f692b9bee7e98b20a4b6540228526445c9872a4a2cefc6ad7312c languageName: node linkType: hard -"@material/progress-indicator@npm:12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/progress-indicator@npm:12.0.0-canary.1a8d06483.0" +"@material/progress-indicator@npm:12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/progress-indicator@npm:12.0.0-canary.22d29cbb4.0" dependencies: tslib: ^2.1.0 - checksum: b59d400bd7e11e3c58a27721a8771473d44cc371de41cce417c6e88f6431da08e6d3401e5e2b51cb1ab593a8ad3d927f19518802e9b3a24581d73f6fcfcd290b + checksum: 80f67a094b69022f4ac3dd7aac5cd9c00d453b723722622b6688778a0e8b5ffd95bb8abd5c5ace4627959d376870451f2dd69e39197f5c18c52c3c24fa5a683c languageName: node linkType: hard -"@material/radio@npm:=12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/radio@npm:12.0.0-canary.1a8d06483.0" +"@material/radio@npm:=12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/radio@npm:12.0.0-canary.22d29cbb4.0" dependencies: - "@material/animation": 12.0.0-canary.1a8d06483.0 - "@material/base": 12.0.0-canary.1a8d06483.0 - "@material/density": 12.0.0-canary.1a8d06483.0 - "@material/dom": 12.0.0-canary.1a8d06483.0 - "@material/feature-targeting": 12.0.0-canary.1a8d06483.0 - "@material/ripple": 12.0.0-canary.1a8d06483.0 - "@material/theme": 12.0.0-canary.1a8d06483.0 - "@material/touch-target": 12.0.0-canary.1a8d06483.0 + "@material/animation": 12.0.0-canary.22d29cbb4.0 + "@material/base": 12.0.0-canary.22d29cbb4.0 + "@material/density": 12.0.0-canary.22d29cbb4.0 + "@material/dom": 12.0.0-canary.22d29cbb4.0 + "@material/feature-targeting": 12.0.0-canary.22d29cbb4.0 + "@material/ripple": 12.0.0-canary.22d29cbb4.0 + "@material/theme": 12.0.0-canary.22d29cbb4.0 + "@material/touch-target": 12.0.0-canary.22d29cbb4.0 tslib: ^2.1.0 - checksum: 2f0a6f3dd862bafb7638c97fa1681a3f2266e2c705eb7c91f648d440521e86d9bd50a36f0216cbb2335cf715e6df9a20b3c4e126359e340518d015959717ae2b + checksum: 531ea652b1c1df6a367a2565ea0e79df106b3d408fbab3bd277dbe1dcdd8a05ccea9fcb3febec940bd200ff9ca915cac6dcdcb134fdb9064f099f98aa725ef78 languageName: node linkType: hard -"@material/ripple@npm:12.0.0-canary.1a8d06483.0, @material/ripple@npm:=12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/ripple@npm:12.0.0-canary.1a8d06483.0" +"@material/ripple@npm:12.0.0-canary.22d29cbb4.0, @material/ripple@npm:=12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/ripple@npm:12.0.0-canary.22d29cbb4.0" dependencies: - "@material/animation": 12.0.0-canary.1a8d06483.0 - "@material/base": 12.0.0-canary.1a8d06483.0 - "@material/dom": 12.0.0-canary.1a8d06483.0 - "@material/feature-targeting": 12.0.0-canary.1a8d06483.0 - "@material/theme": 12.0.0-canary.1a8d06483.0 + "@material/animation": 12.0.0-canary.22d29cbb4.0 + "@material/base": 12.0.0-canary.22d29cbb4.0 + "@material/dom": 12.0.0-canary.22d29cbb4.0 + "@material/feature-targeting": 12.0.0-canary.22d29cbb4.0 + "@material/theme": 12.0.0-canary.22d29cbb4.0 tslib: ^2.1.0 - checksum: d3610bdaa2ec1b647842242dbbf8dec8b27e1e8a35ceb088903540bb4b550b7971183a7cf434e64f7755f5e91ea9a50cfffcf05eac692942a7feeca6759b7e23 + checksum: 21db3d2dc89a648abd6c2111e36c1b95d07e17a473ea152f0a884c3712bbeedeb80daf6849f0098fd20b82fc3e859307395fece0203b4d611bc03d312b4bff3f languageName: node linkType: hard -"@material/rtl@npm:12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/rtl@npm:12.0.0-canary.1a8d06483.0" +"@material/rtl@npm:12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/rtl@npm:12.0.0-canary.22d29cbb4.0" dependencies: - "@material/theme": 12.0.0-canary.1a8d06483.0 + "@material/theme": 12.0.0-canary.22d29cbb4.0 tslib: ^2.1.0 - checksum: f5c7a4bd398707e53a717d391f684a571b89e32f541b795d1bc5fc3d74d0272f09ac7eb07366879cc05b11fd85fa1f022360fca6fd6339541f326bcac1e2d430 + checksum: 3f51ae15556beec7dca39acccd13da1332329d2fc7831e436b149c51bbf7340f9be5f02a206defb4de5ce54fa44376086f986956c20ea515cb75f8ff4fac9b59 languageName: node linkType: hard -"@material/select@npm:12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/select@npm:12.0.0-canary.1a8d06483.0" +"@material/select@npm:12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/select@npm:12.0.0-canary.22d29cbb4.0" dependencies: - "@material/animation": 12.0.0-canary.1a8d06483.0 - "@material/base": 12.0.0-canary.1a8d06483.0 - "@material/density": 12.0.0-canary.1a8d06483.0 - "@material/dom": 12.0.0-canary.1a8d06483.0 - "@material/feature-targeting": 12.0.0-canary.1a8d06483.0 - "@material/floating-label": 12.0.0-canary.1a8d06483.0 - "@material/line-ripple": 12.0.0-canary.1a8d06483.0 - "@material/list": 12.0.0-canary.1a8d06483.0 - "@material/menu": 12.0.0-canary.1a8d06483.0 - "@material/menu-surface": 12.0.0-canary.1a8d06483.0 - "@material/notched-outline": 12.0.0-canary.1a8d06483.0 - "@material/ripple": 12.0.0-canary.1a8d06483.0 - "@material/rtl": 12.0.0-canary.1a8d06483.0 - "@material/shape": 12.0.0-canary.1a8d06483.0 - "@material/theme": 12.0.0-canary.1a8d06483.0 - "@material/typography": 12.0.0-canary.1a8d06483.0 + "@material/animation": 12.0.0-canary.22d29cbb4.0 + "@material/base": 12.0.0-canary.22d29cbb4.0 + "@material/density": 12.0.0-canary.22d29cbb4.0 + "@material/dom": 12.0.0-canary.22d29cbb4.0 + "@material/feature-targeting": 12.0.0-canary.22d29cbb4.0 + "@material/floating-label": 12.0.0-canary.22d29cbb4.0 + "@material/line-ripple": 12.0.0-canary.22d29cbb4.0 + "@material/list": 12.0.0-canary.22d29cbb4.0 + "@material/menu": 12.0.0-canary.22d29cbb4.0 + "@material/menu-surface": 12.0.0-canary.22d29cbb4.0 + "@material/notched-outline": 12.0.0-canary.22d29cbb4.0 + "@material/ripple": 12.0.0-canary.22d29cbb4.0 + "@material/rtl": 12.0.0-canary.22d29cbb4.0 + "@material/shape": 12.0.0-canary.22d29cbb4.0 + "@material/theme": 12.0.0-canary.22d29cbb4.0 + "@material/typography": 12.0.0-canary.22d29cbb4.0 tslib: ^2.1.0 - checksum: 6a5483d3497a9ba835df56ca28e059b6f6693d363e6baea67106405ca19485fc517d2824bcb2ef64b94e836a9fb8977552243db9be7be858e7dfb9c1af51d2ab + checksum: e2f71996ce36dcb7bd17ac60cd40c4c3b5c03101f457f060af3355f893cf7b9e23fb77b495fdf97c62ac44ffde37c5a70d5e7e086341e88fdb22965a44521ef4 languageName: node linkType: hard -"@material/shape@npm:12.0.0-canary.1a8d06483.0, @material/shape@npm:=12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/shape@npm:12.0.0-canary.1a8d06483.0" +"@material/shape@npm:12.0.0-canary.22d29cbb4.0, @material/shape@npm:=12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/shape@npm:12.0.0-canary.22d29cbb4.0" dependencies: - "@material/feature-targeting": 12.0.0-canary.1a8d06483.0 - "@material/rtl": 12.0.0-canary.1a8d06483.0 - "@material/theme": 12.0.0-canary.1a8d06483.0 + "@material/feature-targeting": 12.0.0-canary.22d29cbb4.0 + "@material/rtl": 12.0.0-canary.22d29cbb4.0 + "@material/theme": 12.0.0-canary.22d29cbb4.0 tslib: ^2.1.0 - checksum: c6be54e5153082ab4e1eccd2896ebe8782ab13c47f4bf9b3ebb4092676061f1b65fbea2b1659d2338d36e79b3776d8512d1ec19b13764ab6b711d810902a05b6 + checksum: 7af7d2f433790363c70949a1364c893497e076d64b6d873f504ece8507c81ccb38c12454e54087ddfd5e505288df62f80a288d080cd341dfe1919628ed65d37f languageName: node linkType: hard -"@material/switch@npm:=12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/switch@npm:12.0.0-canary.1a8d06483.0" +"@material/switch@npm:=12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/switch@npm:12.0.0-canary.22d29cbb4.0" dependencies: - "@material/animation": 12.0.0-canary.1a8d06483.0 - "@material/base": 12.0.0-canary.1a8d06483.0 - "@material/density": 12.0.0-canary.1a8d06483.0 - "@material/dom": 12.0.0-canary.1a8d06483.0 - "@material/elevation": 12.0.0-canary.1a8d06483.0 - "@material/feature-targeting": 12.0.0-canary.1a8d06483.0 - "@material/ripple": 12.0.0-canary.1a8d06483.0 - "@material/rtl": 12.0.0-canary.1a8d06483.0 - "@material/shape": 12.0.0-canary.1a8d06483.0 - "@material/theme": 12.0.0-canary.1a8d06483.0 + "@material/animation": 12.0.0-canary.22d29cbb4.0 + "@material/base": 12.0.0-canary.22d29cbb4.0 + "@material/density": 12.0.0-canary.22d29cbb4.0 + "@material/dom": 12.0.0-canary.22d29cbb4.0 + "@material/elevation": 12.0.0-canary.22d29cbb4.0 + "@material/feature-targeting": 12.0.0-canary.22d29cbb4.0 + "@material/ripple": 12.0.0-canary.22d29cbb4.0 + "@material/rtl": 12.0.0-canary.22d29cbb4.0 + "@material/shape": 12.0.0-canary.22d29cbb4.0 + "@material/theme": 12.0.0-canary.22d29cbb4.0 tslib: ^2.1.0 - checksum: 5420ff748c6092d4f67040d507ed44cc9528bb014919475967ade4fa9a5ccc83ed5c8b9895653874bd8e1dc77d2e2a08d752e44a83a0680b37134df90a1b225e + checksum: 75291fe99122d765babbf96187de40c4c51cbd839b214c3944ff864ce9c9ed73c8455293a76c4c3e1698694c5e93a9921352610a444e8dfa9dc920229c39e7b4 languageName: node linkType: hard -"@material/tab-bar@npm:=12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/tab-bar@npm:12.0.0-canary.1a8d06483.0" +"@material/tab-bar@npm:=12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/tab-bar@npm:12.0.0-canary.22d29cbb4.0" dependencies: - "@material/animation": 12.0.0-canary.1a8d06483.0 - "@material/base": 12.0.0-canary.1a8d06483.0 - "@material/density": 12.0.0-canary.1a8d06483.0 - "@material/feature-targeting": 12.0.0-canary.1a8d06483.0 - "@material/tab": 12.0.0-canary.1a8d06483.0 - "@material/tab-scroller": 12.0.0-canary.1a8d06483.0 + "@material/animation": 12.0.0-canary.22d29cbb4.0 + "@material/base": 12.0.0-canary.22d29cbb4.0 + "@material/density": 12.0.0-canary.22d29cbb4.0 + "@material/elevation": 12.0.0-canary.22d29cbb4.0 + "@material/feature-targeting": 12.0.0-canary.22d29cbb4.0 + "@material/tab": 12.0.0-canary.22d29cbb4.0 + "@material/tab-indicator": 12.0.0-canary.22d29cbb4.0 + "@material/tab-scroller": 12.0.0-canary.22d29cbb4.0 + "@material/theme": 12.0.0-canary.22d29cbb4.0 + "@material/typography": 12.0.0-canary.22d29cbb4.0 tslib: ^2.1.0 - checksum: 7a6be3b3779309417823d70772dba984e391080e3ff6e506f86024e62c5077e093184411a72ee87dfb8dc63cedf840916887432504d868cf198afbef6cb35de0 + checksum: c9f7cea1158fce88a4e2a38757e1a362e69edea748bc580afb8d256978b0d09f11b5d819224c5274f7d165ae08e7d1bc77a7d7e5b622916b3eb79ecae3058c7c languageName: node linkType: hard -"@material/tab-indicator@npm:12.0.0-canary.1a8d06483.0, @material/tab-indicator@npm:=12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/tab-indicator@npm:12.0.0-canary.1a8d06483.0" +"@material/tab-indicator@npm:12.0.0-canary.22d29cbb4.0, @material/tab-indicator@npm:=12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/tab-indicator@npm:12.0.0-canary.22d29cbb4.0" dependencies: - "@material/animation": 12.0.0-canary.1a8d06483.0 - "@material/base": 12.0.0-canary.1a8d06483.0 - "@material/feature-targeting": 12.0.0-canary.1a8d06483.0 - "@material/theme": 12.0.0-canary.1a8d06483.0 + "@material/animation": 12.0.0-canary.22d29cbb4.0 + "@material/base": 12.0.0-canary.22d29cbb4.0 + "@material/feature-targeting": 12.0.0-canary.22d29cbb4.0 + "@material/theme": 12.0.0-canary.22d29cbb4.0 tslib: ^2.1.0 - checksum: a157c80d9ec4c396f888aa6ef299973474c24c997ddb128e50f777333be3cd91f53caa9404af4a728482c285fa79886ccd54d4fbfa7e91b0f32f6a4a1de89e90 + checksum: d07c36863c8aed45c63e8f1f02c07e9e312144017828f29a95733ad2273f39b52ffaeaf706ee3f472333a756476475e3c927078b11c9eff25ee1113576996cd5 languageName: node linkType: hard -"@material/tab-scroller@npm:12.0.0-canary.1a8d06483.0, @material/tab-scroller@npm:=12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/tab-scroller@npm:12.0.0-canary.1a8d06483.0" +"@material/tab-scroller@npm:12.0.0-canary.22d29cbb4.0, @material/tab-scroller@npm:=12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/tab-scroller@npm:12.0.0-canary.22d29cbb4.0" dependencies: - "@material/animation": 12.0.0-canary.1a8d06483.0 - "@material/base": 12.0.0-canary.1a8d06483.0 - "@material/dom": 12.0.0-canary.1a8d06483.0 - "@material/feature-targeting": 12.0.0-canary.1a8d06483.0 - "@material/tab": 12.0.0-canary.1a8d06483.0 + "@material/animation": 12.0.0-canary.22d29cbb4.0 + "@material/base": 12.0.0-canary.22d29cbb4.0 + "@material/dom": 12.0.0-canary.22d29cbb4.0 + "@material/feature-targeting": 12.0.0-canary.22d29cbb4.0 + "@material/tab": 12.0.0-canary.22d29cbb4.0 tslib: ^2.1.0 - checksum: 870e28bb4508997ba22175bfdaeac7a0eb8d23007c608fdd4e40d1ac10f30e73daebd9099a41210ca723f62514b594f6ef6635f8ce7f9b1155827a80c0e6e92e + checksum: d7abb5e8ee9f1da768592f918819ebd459deec710bb400651af65de1534deee225f2b4cc470dc89dd11102c971276fc87a85f7249219fc50ce5c1911c3b6fcc9 languageName: node linkType: hard -"@material/tab@npm:12.0.0-canary.1a8d06483.0, @material/tab@npm:=12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/tab@npm:12.0.0-canary.1a8d06483.0" +"@material/tab@npm:12.0.0-canary.22d29cbb4.0, @material/tab@npm:=12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/tab@npm:12.0.0-canary.22d29cbb4.0" dependencies: - "@material/base": 12.0.0-canary.1a8d06483.0 - "@material/feature-targeting": 12.0.0-canary.1a8d06483.0 - "@material/ripple": 12.0.0-canary.1a8d06483.0 - "@material/rtl": 12.0.0-canary.1a8d06483.0 - "@material/tab-indicator": 12.0.0-canary.1a8d06483.0 - "@material/theme": 12.0.0-canary.1a8d06483.0 - "@material/typography": 12.0.0-canary.1a8d06483.0 + "@material/base": 12.0.0-canary.22d29cbb4.0 + "@material/elevation": 12.0.0-canary.22d29cbb4.0 + "@material/feature-targeting": 12.0.0-canary.22d29cbb4.0 + "@material/ripple": 12.0.0-canary.22d29cbb4.0 + "@material/rtl": 12.0.0-canary.22d29cbb4.0 + "@material/tab-indicator": 12.0.0-canary.22d29cbb4.0 + "@material/theme": 12.0.0-canary.22d29cbb4.0 + "@material/typography": 12.0.0-canary.22d29cbb4.0 tslib: ^2.1.0 - checksum: 90e008c397cc9994eb694190ff8df62bb91a3aa0266a9e7ecd0200f18113b78b824ecc390fe530b417d514cf2b99d681c53409b7161da1b091e12ec7a5552334 + checksum: 10d0bb2ebd0da8b4eb03761397beecc88645718723386c4f0f0c264f5737243a4335e3b8d50047ea68bede4adbe56cc90d10ab9f7d75a3d1d995dca509ebb74c languageName: node linkType: hard -"@material/theme@npm:12.0.0-canary.1a8d06483.0, @material/theme@npm:=12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/theme@npm:12.0.0-canary.1a8d06483.0" +"@material/theme@npm:12.0.0-canary.22d29cbb4.0, @material/theme@npm:=12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/theme@npm:12.0.0-canary.22d29cbb4.0" dependencies: - "@material/feature-targeting": 12.0.0-canary.1a8d06483.0 + "@material/feature-targeting": 12.0.0-canary.22d29cbb4.0 tslib: ^2.1.0 - checksum: cb6bd4562ed038ef9c56227ec840f05f0cbef06687f2a4e9ad89aefac16325681d6a5f53dc12828fd787ddd8d58950c69e0933a413aa55d6d8ec97ce5a63dbfb + checksum: 69727d0a7b5c6b4be438c2ff874f9d3f0db75e26fb0b98acc57f5c1b65c2571fbf86ea559ee3cbdf5cebf45700d18efffa87fbf8fa72512eeb567a74b22ad8d9 languageName: node linkType: hard -"@material/top-app-bar@npm:=12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/top-app-bar@npm:12.0.0-canary.1a8d06483.0" +"@material/top-app-bar@npm:12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/top-app-bar@npm:12.0.0-canary.22d29cbb4.0" dependencies: - "@material/animation": 12.0.0-canary.1a8d06483.0 - "@material/base": 12.0.0-canary.1a8d06483.0 - "@material/elevation": 12.0.0-canary.1a8d06483.0 - "@material/ripple": 12.0.0-canary.1a8d06483.0 - "@material/rtl": 12.0.0-canary.1a8d06483.0 - "@material/shape": 12.0.0-canary.1a8d06483.0 - "@material/theme": 12.0.0-canary.1a8d06483.0 - "@material/typography": 12.0.0-canary.1a8d06483.0 + "@material/animation": 12.0.0-canary.22d29cbb4.0 + "@material/base": 12.0.0-canary.22d29cbb4.0 + "@material/elevation": 12.0.0-canary.22d29cbb4.0 + "@material/ripple": 12.0.0-canary.22d29cbb4.0 + "@material/rtl": 12.0.0-canary.22d29cbb4.0 + "@material/shape": 12.0.0-canary.22d29cbb4.0 + "@material/theme": 12.0.0-canary.22d29cbb4.0 + "@material/typography": 12.0.0-canary.22d29cbb4.0 tslib: ^2.1.0 - checksum: d608552fd00260b1a79756d02b421f2f8b516028742934d52a5920a439687c6c0bf5d231c2eafa1e1e63b0721899d1bdc1e192b73730c923bca7a26ec2abb91d + checksum: 7769215628c86a353565f647c3dcdf99aba2baf0eade345bd8fd695a93a8348224bc569c5e2ff22123e91e31cbed847c54a246418cc93c6ce6e162d7c66655cd languageName: node linkType: hard -"@material/touch-target@npm:12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/touch-target@npm:12.0.0-canary.1a8d06483.0" +"@material/touch-target@npm:12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/touch-target@npm:12.0.0-canary.22d29cbb4.0" dependencies: - "@material/base": 12.0.0-canary.1a8d06483.0 - "@material/feature-targeting": 12.0.0-canary.1a8d06483.0 + "@material/base": 12.0.0-canary.22d29cbb4.0 + "@material/feature-targeting": 12.0.0-canary.22d29cbb4.0 tslib: ^2.1.0 - checksum: 16e0ba9051b8823f6c1126faf95e20a06078b9408321be374f0dbb8f0808cd6f3e39cdc19fc4824bf1aa09149028ffea068cd680e85edd338990580e9290cd36 + checksum: 4108809ad673383cf162318ab9f69c3faeacddc9c4130d0d540575b40bc56f17119d20b199b947059e5db576fc65902d0c43bd156564536692641853e4fb002f languageName: node linkType: hard -"@material/typography@npm:12.0.0-canary.1a8d06483.0": - version: 12.0.0-canary.1a8d06483.0 - resolution: "@material/typography@npm:12.0.0-canary.1a8d06483.0" +"@material/typography@npm:12.0.0-canary.22d29cbb4.0": + version: 12.0.0-canary.22d29cbb4.0 + resolution: "@material/typography@npm:12.0.0-canary.22d29cbb4.0" dependencies: - "@material/feature-targeting": 12.0.0-canary.1a8d06483.0 - "@material/theme": 12.0.0-canary.1a8d06483.0 + "@material/feature-targeting": 12.0.0-canary.22d29cbb4.0 + "@material/theme": 12.0.0-canary.22d29cbb4.0 tslib: ^2.1.0 - checksum: 99d54dc2b985526dddc09336cc36c02e5b2ff97bf1210e7ef5484e96e81ee6e7fb0dd8995ff22a9420f51c4b67b059f3413c4f058250fa20976989aca7faa60e + checksum: f87fbdb008dbac707ec0544c5f6bf0da21590cb5641423c2d2031013f8d23d53cc7f23067f3987768dc7b0cddff48d717df24046ecef79336e30f1e449cab4eb languageName: node linkType: hard @@ -8829,24 +8836,24 @@ fsevents@~2.3.1: "@fullcalendar/list": 5.1.0 "@koa/cors": ^3.1.0 "@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.6.0#./.yarn/patches/@lit-labs/virtualizer/0.7.0.patch" - "@material/chips": =12.0.0-canary.1a8d06483.0 - "@material/data-table": =12.0.0-canary.1a8d06483.0 - "@material/mwc-button": 0.22.0-canary.cc04657a.0 - "@material/mwc-checkbox": 0.22.0-canary.cc04657a.0 - "@material/mwc-circular-progress": 0.22.0-canary.cc04657a.0 - "@material/mwc-dialog": 0.22.0-canary.cc04657a.0 - "@material/mwc-fab": 0.22.0-canary.cc04657a.0 - "@material/mwc-formfield": 0.22.0-canary.cc04657a.0 - "@material/mwc-icon-button": 0.22.0-canary.cc04657a.0 - "@material/mwc-linear-progress": 0.22.0-canary.cc04657a.0 - "@material/mwc-list": 0.22.0-canary.cc04657a.0 - "@material/mwc-menu": 0.22.0-canary.cc04657a.0 - "@material/mwc-radio": 0.22.0-canary.cc04657a.0 - "@material/mwc-ripple": 0.22.0-canary.cc04657a.0 - "@material/mwc-switch": 0.22.0-canary.cc04657a.0 - "@material/mwc-tab": 0.22.0-canary.cc04657a.0 - "@material/mwc-tab-bar": 0.22.0-canary.cc04657a.0 - "@material/top-app-bar": =12.0.0-canary.1a8d06483.0 + "@material/chips": 12.0.0-canary.22d29cbb4.0 + "@material/data-table": 12.0.0-canary.22d29cbb4.0 + "@material/mwc-button": 0.22.1 + "@material/mwc-checkbox": 0.22.1 + "@material/mwc-circular-progress": 0.22.1 + "@material/mwc-dialog": 0.22.1 + "@material/mwc-fab": 0.22.1 + "@material/mwc-formfield": 0.22.1 + "@material/mwc-icon-button": 0.22.1 + "@material/mwc-linear-progress": 0.22.1 + "@material/mwc-list": 0.22.1 + "@material/mwc-menu": 0.22.1 + "@material/mwc-radio": 0.22.1 + "@material/mwc-ripple": 0.22.1 + "@material/mwc-switch": 0.22.1 + "@material/mwc-tab": 0.22.1 + "@material/mwc-tab-bar": 0.22.1 + "@material/top-app-bar": 12.0.0-canary.22d29cbb4.0 "@mdi/js": 5.9.55 "@mdi/svg": 5.9.55 "@open-wc/dev-server-hmr": ^0.0.2 From bf4192a1f06426357d32e99127edfdce4ecb335d Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 27 Jul 2021 00:02:23 +0200 Subject: [PATCH 05/43] Bump Vaadin elements (#9609) --- package.json | 4 +- yarn.lock | 165 ++++++++++++++++++++++++++------------------------- 2 files changed, 85 insertions(+), 84 deletions(-) diff --git a/package.json b/package.json index ac40d71b7f..c7b05c06df 100644 --- a/package.json +++ b/package.json @@ -89,8 +89,8 @@ "@polymer/paper-tooltip": "^3.0.1", "@polymer/polymer": "3.4.1", "@thomasloven/round-slider": "0.5.2", - "@vaadin/vaadin-combo-box": "^5.0.10", - "@vaadin/vaadin-date-picker": "^4.0.7", + "@vaadin/vaadin-combo-box": "^20.0.1", + "@vaadin/vaadin-date-picker": "^20.0.1", "@vibrant/color": "^3.2.1-alpha.1", "@vibrant/core": "^3.2.1-alpha.1", "@vibrant/quantizer-mmcq": "^3.2.1-alpha.1", diff --git a/yarn.lock b/yarn.lock index 401cca699e..355356e0ed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3974,68 +3974,67 @@ __metadata: languageName: node linkType: hard -"@vaadin/vaadin-button@npm:^2.4.0": - version: 2.4.0 - resolution: "@vaadin/vaadin-button@npm:2.4.0" +"@vaadin/vaadin-button@npm:20.0.1": + version: 20.0.1 + resolution: "@vaadin/vaadin-button@npm:20.0.1" dependencies: "@polymer/polymer": ^3.0.0 - "@vaadin/vaadin-control-state-mixin": ^2.2.1 - "@vaadin/vaadin-element-mixin": ^2.4.1 - "@vaadin/vaadin-lumo-styles": ^1.3.3 - "@vaadin/vaadin-material-styles": ^1.2.0 - "@vaadin/vaadin-themable-mixin": ^1.6.1 - checksum: f0b79e3b8e2e83c90fde9a6b7730ccfed26b4b75bb5c54f5b1d55e1efa3ae4905f5e5d0218743824569f54324056422b69979e1b1e50d34c9d69322845083735 + "@vaadin/vaadin-control-state-mixin": 20.0.1 + "@vaadin/vaadin-element-mixin": 20.0.1 + "@vaadin/vaadin-lumo-styles": 20.0.1 + "@vaadin/vaadin-material-styles": 20.0.1 + "@vaadin/vaadin-themable-mixin": 20.0.1 + checksum: 096a669d76f6b3587c7d3849a68aa05123248924a388568b69ae45970b7155af1abca83085a84a66d22e397f0d88acc9c2b289eeff9bc4a758b75fe945fc4a1f languageName: node linkType: hard -"@vaadin/vaadin-combo-box@npm:^5.0.10": - version: 5.4.7 - resolution: "@vaadin/vaadin-combo-box@npm:5.4.7" +"@vaadin/vaadin-combo-box@npm:^20.0.1": + version: 20.0.1 + resolution: "@vaadin/vaadin-combo-box@npm:20.0.1" dependencies: "@polymer/iron-a11y-announcer": ^3.0.0 - "@polymer/iron-a11y-keys-behavior": ^3.0.0 "@polymer/iron-list": ^3.0.0 "@polymer/iron-resizable-behavior": ^3.0.0 "@polymer/polymer": ^3.0.0 - "@vaadin/vaadin-control-state-mixin": ^2.2.2 - "@vaadin/vaadin-element-mixin": ^2.4.1 - "@vaadin/vaadin-item": ^2.3.0 - "@vaadin/vaadin-lumo-styles": ^1.1.1 - "@vaadin/vaadin-material-styles": ^1.1.2 - "@vaadin/vaadin-overlay": ^3.5.0 - "@vaadin/vaadin-text-field": ^2.8.0 - "@vaadin/vaadin-themable-mixin": ^1.6.1 - checksum: f606af912f2db8e2725086540f13a0e199ca150dc6cd2abccf68f7ddaa903d59dfbc3ad29f31b89b520b739ccc6af6740b1e87f54f6b7c9461e26377f599d8d4 + "@vaadin/vaadin-control-state-mixin": 20.0.1 + "@vaadin/vaadin-element-mixin": 20.0.1 + "@vaadin/vaadin-item": 20.0.1 + "@vaadin/vaadin-lumo-styles": 20.0.1 + "@vaadin/vaadin-material-styles": 20.0.1 + "@vaadin/vaadin-overlay": 20.0.1 + "@vaadin/vaadin-text-field": 20.0.1 + "@vaadin/vaadin-themable-mixin": 20.0.1 + checksum: ec4cf4d687fe3ee1cf7b2fbbb6c65d5701f30179d41d591ce75664864afc15f6ecd95f9baf595c2179a0a4211a9b46fd42b214d20b60c45bfdd80c05ca5e24b2 languageName: node linkType: hard -"@vaadin/vaadin-control-state-mixin@npm:^2.2.1, @vaadin/vaadin-control-state-mixin@npm:^2.2.2": - version: 2.2.4 - resolution: "@vaadin/vaadin-control-state-mixin@npm:2.2.4" +"@vaadin/vaadin-control-state-mixin@npm:20.0.1": + version: 20.0.1 + resolution: "@vaadin/vaadin-control-state-mixin@npm:20.0.1" dependencies: "@polymer/polymer": ^3.0.0 - checksum: 7efdc2e8f546164983f1dfba2c153f4801e18de2f5abefe7260d3bde2d71ad1c9214afed316b6d2d79752cd72d9987d430c3400bd5995f5bc9e0146844e302ab + checksum: dfb7c89cdc5f88497194b0f3b61a08c70abf3e5633f302feb4b5a42557fd270f02e59b586a47dd482dffa76dee820422e388c2bee3ca9bfedea48da01592ff74 languageName: node linkType: hard -"@vaadin/vaadin-date-picker@npm:^4.0.7": - version: 4.4.1 - resolution: "@vaadin/vaadin-date-picker@npm:4.4.1" +"@vaadin/vaadin-date-picker@npm:^20.0.1": + version: 20.0.1 + resolution: "@vaadin/vaadin-date-picker@npm:20.0.1" dependencies: "@polymer/iron-a11y-announcer": ^3.0.0 "@polymer/iron-a11y-keys-behavior": ^3.0.0 "@polymer/iron-media-query": ^3.0.0 "@polymer/iron-resizable-behavior": ^3.0.0 - "@polymer/polymer": ^3.0.0 - "@vaadin/vaadin-button": ^2.4.0 - "@vaadin/vaadin-control-state-mixin": ^2.2.2 - "@vaadin/vaadin-element-mixin": ^2.4.1 - "@vaadin/vaadin-lumo-styles": ^1.6.0 - "@vaadin/vaadin-material-styles": ^1.3.2 - "@vaadin/vaadin-overlay": ^3.5.0 - "@vaadin/vaadin-text-field": ^2.8.0 - "@vaadin/vaadin-themable-mixin": ^1.6.1 - checksum: da3bc0b7e3ce22ae1887e9fc79c4fdefaf179fb721b03d9cdb9fc336287c6c1c18889504b6d185b59c1644ca4c1e875e6b845cf29ab0c811b51456b87962c7a6 + "@polymer/polymer": ^3.2.0 + "@vaadin/vaadin-button": 20.0.1 + "@vaadin/vaadin-control-state-mixin": 20.0.1 + "@vaadin/vaadin-element-mixin": 20.0.1 + "@vaadin/vaadin-lumo-styles": 20.0.1 + "@vaadin/vaadin-material-styles": 20.0.1 + "@vaadin/vaadin-overlay": 20.0.1 + "@vaadin/vaadin-text-field": 20.0.1 + "@vaadin/vaadin-themable-mixin": 20.0.1 + checksum: 058e1cae51414c664fac5679ebea435ae4f8da716915c8c0aa9f4c7683e7bd48ce94e6f059dea9d7cf67e87554a098ab7cfcd955afdc4d85291ba0c0c6447236 languageName: node linkType: hard @@ -4046,84 +4045,86 @@ __metadata: languageName: node linkType: hard -"@vaadin/vaadin-element-mixin@npm:^2.4.0, @vaadin/vaadin-element-mixin@npm:^2.4.1": - version: 2.4.2 - resolution: "@vaadin/vaadin-element-mixin@npm:2.4.2" +"@vaadin/vaadin-element-mixin@npm:20.0.1": + version: 20.0.1 + resolution: "@vaadin/vaadin-element-mixin@npm:20.0.1" dependencies: "@polymer/polymer": ^3.0.0 "@vaadin/vaadin-development-mode-detector": ^2.0.0 "@vaadin/vaadin-usage-statistics": ^2.1.0 - checksum: 6bc9a4226e5cfd5b3dabd7bda5a95f3582ab045892dd941430245ce78d4361c20ea3c4c24e1f490d1f5635b76ac962aeb7503b38dd1e09c937d2ae97d1d95d93 + checksum: 34f66bc3332a57672645f00ac55327624a55c686eaaa0a19a6a830d93229c7a5e16a2421c8b9919d729540d829e289f68105bd6754c953fe9c94b38ab547416f languageName: node linkType: hard -"@vaadin/vaadin-item@npm:^2.3.0": - version: 2.3.0 - resolution: "@vaadin/vaadin-item@npm:2.3.0" +"@vaadin/vaadin-item@npm:20.0.1": + version: 20.0.1 + resolution: "@vaadin/vaadin-item@npm:20.0.1" dependencies: "@polymer/polymer": ^3.0.0 - "@vaadin/vaadin-element-mixin": ^2.4.1 - "@vaadin/vaadin-lumo-styles": ^1.1.0 - "@vaadin/vaadin-material-styles": ^1.1.0 - "@vaadin/vaadin-themable-mixin": ^1.6.1 - checksum: 5effafdf4a5135a8af92b86dc29d510331bff2814347568c69a61c7d2012714311e9c3be2761f0a2f599849828f1e5918d979840905c149ce244a75255f0f76e + "@vaadin/vaadin-element-mixin": 20.0.1 + "@vaadin/vaadin-lumo-styles": 20.0.1 + "@vaadin/vaadin-material-styles": 20.0.1 + "@vaadin/vaadin-themable-mixin": 20.0.1 + checksum: 904a549574c7c7c23a98d5ec4067d262ff612eb561f60a9fea9bdf70eb65fe5d297877139ab2398b746a84eb1bfbb8d3270494c067b44a11ef70625fa9dd0177 languageName: node linkType: hard -"@vaadin/vaadin-lumo-styles@npm:^1.1.0, @vaadin/vaadin-lumo-styles@npm:^1.1.1, @vaadin/vaadin-lumo-styles@npm:^1.3.0, @vaadin/vaadin-lumo-styles@npm:^1.3.3, @vaadin/vaadin-lumo-styles@npm:^1.6.0": - version: 1.6.1 - resolution: "@vaadin/vaadin-lumo-styles@npm:1.6.1" +"@vaadin/vaadin-lumo-styles@npm:20.0.1": + version: 20.0.1 + resolution: "@vaadin/vaadin-lumo-styles@npm:20.0.1" dependencies: "@polymer/iron-icon": ^3.0.0 "@polymer/iron-iconset-svg": ^3.0.0 "@polymer/polymer": ^3.0.0 - checksum: e6ae75a68f626ff877a8c4f6a14b12529f168308c8e198b24e91db46657dcec06a3e3df1a77678e9c1a7ac81568592483e3508c8af89d5b578cf6db9814e097e + "@vaadin/vaadin-themable-mixin": 20.0.1 + checksum: bd6be1aa9a2339db332beda63433f301c2f7495070b968da1ff27033c111a8c1cd23c55090832f15b69f04b5b8a65c8ee3feba480eb876fb4fc4d165b4568bdb languageName: node linkType: hard -"@vaadin/vaadin-material-styles@npm:^1.1.0, @vaadin/vaadin-material-styles@npm:^1.1.2, @vaadin/vaadin-material-styles@npm:^1.2.0, @vaadin/vaadin-material-styles@npm:^1.3.2": - version: 1.3.2 - resolution: "@vaadin/vaadin-material-styles@npm:1.3.2" +"@vaadin/vaadin-material-styles@npm:20.0.1": + version: 20.0.1 + resolution: "@vaadin/vaadin-material-styles@npm:20.0.1" dependencies: "@polymer/polymer": ^3.0.0 - checksum: 4fe2cc038ec4dcc6de37263aaf90435f3987cf8a6ed09bf797a845235a6c7a24d9af2e77c0597fb17ec8887b723e59305f88bf19d265c6899c6dd60b5e3b0aee + "@vaadin/vaadin-themable-mixin": 20.0.1 + checksum: 885ba2637157e07fd78829113d3e50eb0346e363435691acd431893b77edf5ae2863fe2a2163eaad4b36e84383b8b73db0e0ebb6f815c7608d3775c0260ae250 languageName: node linkType: hard -"@vaadin/vaadin-overlay@npm:^3.5.0": - version: 3.5.1 - resolution: "@vaadin/vaadin-overlay@npm:3.5.1" +"@vaadin/vaadin-overlay@npm:20.0.1": + version: 20.0.1 + resolution: "@vaadin/vaadin-overlay@npm:20.0.1" dependencies: "@polymer/polymer": ^3.0.0 - "@vaadin/vaadin-element-mixin": ^2.4.0 - "@vaadin/vaadin-lumo-styles": ^1.3.0 - "@vaadin/vaadin-material-styles": ^1.2.0 - "@vaadin/vaadin-themable-mixin": ^1.6.1 - checksum: dcfba6b65bc0001e8d16278c6e63707629f7480f6b0553f4f3338ec79e4edfa3ecf7f27fcfc022cab9e08fdcdf2a5c263d817fd55402a554d28362a333fcd0b6 + "@vaadin/vaadin-element-mixin": 20.0.1 + "@vaadin/vaadin-lumo-styles": 20.0.1 + "@vaadin/vaadin-material-styles": 20.0.1 + "@vaadin/vaadin-themable-mixin": 20.0.1 + checksum: c3ef33e5149381e69d4f64b3500ba342dbc7cf244025a071533f971f0c5c49d92d9ca797c635933842642ff2fa248403a20d8f5a758c0434709878b35cf2324f languageName: node linkType: hard -"@vaadin/vaadin-text-field@npm:^2.8.0": - version: 2.8.4 - resolution: "@vaadin/vaadin-text-field@npm:2.8.4" +"@vaadin/vaadin-text-field@npm:20.0.1": + version: 20.0.1 + resolution: "@vaadin/vaadin-text-field@npm:20.0.1" dependencies: "@polymer/polymer": ^3.0.0 - "@vaadin/vaadin-control-state-mixin": ^2.2.1 - "@vaadin/vaadin-element-mixin": ^2.4.1 - "@vaadin/vaadin-lumo-styles": ^1.6.0 - "@vaadin/vaadin-material-styles": ^1.3.2 - "@vaadin/vaadin-themable-mixin": ^1.6.1 - checksum: fb7994b99b641f8570b5960f6ec40de4422caeca29a43bd760edbb95a39b87d2dfeeeeb21eb4e0d4eeaa33b11cc41a087c5a49c78c602f5ac951effdcc902693 + "@vaadin/vaadin-control-state-mixin": 20.0.1 + "@vaadin/vaadin-element-mixin": 20.0.1 + "@vaadin/vaadin-lumo-styles": 20.0.1 + "@vaadin/vaadin-material-styles": 20.0.1 + "@vaadin/vaadin-themable-mixin": 20.0.1 + checksum: 5bcba688a66a873de5586f6a42d005cd48a0f4d9143c6d23bbd4baaba196427679a74cadcc93a61a878dc33cecb8bef732896f1e8cb6de829dcc68aac9ce6ded languageName: node linkType: hard -"@vaadin/vaadin-themable-mixin@npm:^1.6.1": - version: 1.6.2 - resolution: "@vaadin/vaadin-themable-mixin@npm:1.6.2" +"@vaadin/vaadin-themable-mixin@npm:20.0.1": + version: 20.0.1 + resolution: "@vaadin/vaadin-themable-mixin@npm:20.0.1" dependencies: "@polymer/polymer": ^3.0.0 lit-element: ^2.0.0 - checksum: f8a0aff260ab2c512c4bbec3fcaa1c07e64244a45b971a108c827d94009223e0006150b0c006cd95477a9337a406b44046fc21e15a1e33d07fdc1f396ab7b387 + checksum: 1a0bcab0dbaec2f6ea03480ccd3957f5eb1145bdbcabb2bc8fe9168c279626ab665b55f4f4f8f1de0a79d272e42034a04a7e88b6e4d5b2e2196b6252be6c952b languageName: node linkType: hard @@ -8899,8 +8900,8 @@ fsevents@~2.3.1: "@types/webspeechapi": ^0.0.29 "@typescript-eslint/eslint-plugin": ^4.28.3 "@typescript-eslint/parser": ^4.28.3 - "@vaadin/vaadin-combo-box": ^5.0.10 - "@vaadin/vaadin-date-picker": ^4.0.7 + "@vaadin/vaadin-combo-box": ^20.0.1 + "@vaadin/vaadin-date-picker": ^20.0.1 "@vibrant/color": ^3.2.1-alpha.1 "@vibrant/core": ^3.2.1-alpha.1 "@vibrant/quantizer-mmcq": ^3.2.1-alpha.1 From 586fa1d0f0a5baa84e23237948528433dca56761 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 27 Jul 2021 00:02:50 +0200 Subject: [PATCH 06/43] Fix zoom setting in maps card (#9613) Fixes #9611 --- src/panels/lovelace/cards/hui-map-card.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/panels/lovelace/cards/hui-map-card.ts b/src/panels/lovelace/cards/hui-map-card.ts index 50f6ab3627..d660dae680 100644 --- a/src/panels/lovelace/cards/hui-map-card.ts +++ b/src/panels/lovelace/cards/hui-map-card.ts @@ -133,6 +133,7 @@ class HuiMapCard extends LitElement implements LovelaceCard { this._config, this._configEntities )} + .zoom=${this._config.default_zoom ?? 14} .paths=${this._getHistoryPaths(this._config, this._history)} .darkMode=${this._config.dark_mode} > From f459abdf852edbb08cc5944865334b58b45e63a6 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 27 Jul 2021 00:03:31 +0200 Subject: [PATCH 07/43] Fix statistics graph (#9607) --- src/components/chart/statistics-chart.ts | 7 ++++++- src/panels/lovelace/cards/hui-statistics-graph-card.ts | 5 ++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/components/chart/statistics-chart.ts b/src/components/chart/statistics-chart.ts index 13e5e92a58..6d4db48c70 100644 --- a/src/components/chart/statistics-chart.ts +++ b/src/components/chart/statistics-chart.ts @@ -267,7 +267,12 @@ class StatisticsChart extends LitElement { stats.forEach((stat) => { const dataValues: Array = []; statTypes.forEach((type) => { - const val = stat[type]; + let val: number | null; + if (type === "sum") { + val = stat.state; + } else { + val = stat[type]; + } dataValues.push(val !== null ? Math.round(val * 100) / 100 : null); }); const date = new Date(stat.start); diff --git a/src/panels/lovelace/cards/hui-statistics-graph-card.ts b/src/panels/lovelace/cards/hui-statistics-graph-card.ts index b475097634..5ff105a67c 100644 --- a/src/panels/lovelace/cards/hui-statistics-graph-card.ts +++ b/src/panels/lovelace/cards/hui-statistics-graph-card.ts @@ -146,7 +146,10 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard { return; } const startDate = new Date(); - startDate.setHours(-24 * (this._config!.days_to_show || 30)); + startDate.setTime( + startDate.getTime() - + 1000 * 60 * 60 * (24 * (this._config!.days_to_show || 30) + 1) + ); this._fetching = true; try { this._statistics = await fetchStatistics( From a6312f4279bd2c97cb0606d869acaa792fb4ee00 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 27 Jul 2021 00:11:34 +0200 Subject: [PATCH 08/43] Fix sidebar view edit mode (#9615) --- src/panels/lovelace/views/hui-sidebar-view.ts | 71 ++++++++++--------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/src/panels/lovelace/views/hui-sidebar-view.ts b/src/panels/lovelace/views/hui-sidebar-view.ts index 0b8bd652c5..79f57c2ea2 100644 --- a/src/panels/lovelace/views/hui-sidebar-view.ts +++ b/src/panels/lovelace/views/hui-sidebar-view.ts @@ -17,10 +17,9 @@ import type { } from "../../../data/lovelace"; import type { HomeAssistant } from "../../../types"; import { HuiErrorCard } from "../cards/hui-error-card"; +import { HuiCardOptions } from "../components/hui-card-options"; import type { Lovelace, LovelaceCard } from "../types"; -let editCodeLoaded = false; - export class SideBarView extends LitElement implements LovelaceViewElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -43,8 +42,7 @@ export class SideBarView extends LitElement implements LovelaceViewElement { public willUpdate(changedProperties: PropertyValues): void { super.willUpdate(changedProperties); - if (this.lovelace?.editMode && !editCodeLoaded) { - editCodeLoaded = true; + if (this.lovelace?.editMode) { import("./default-view-editable"); } @@ -71,7 +69,8 @@ export class SideBarView extends LitElement implements LovelaceViewElement { protected render(): TemplateResult { return html` - ${this.lovelace?.editMode && this.cards.length === 0 +
+ ${this.lovelace?.editMode ? html` { - this.renderRoot.appendChild(mainDiv); - this.renderRoot.appendChild(sidebarDiv); + const container = this.renderRoot.querySelector(".container")!; + container.appendChild(mainDiv); + container.appendChild(sidebarDiv); }); } this.cards.forEach((card: LovelaceCard, idx) => { const cardConfig = this._config?.cards?.[idx]; + let element: LovelaceCard | HuiCardOptions; if (this.isStrategy || !this.lovelace?.editMode) { card.editMode = false; - if (cardConfig?.view_layout?.position !== "sidebar") { - mainDiv.appendChild(card); - } else { - sidebarDiv.appendChild(card); - } - return; - } - - const wrapper = document.createElement("hui-card-options"); - wrapper.hass = this.hass; - wrapper.lovelace = this.lovelace; - wrapper.path = [this.index!, 0]; - card.editMode = true; - wrapper.appendChild(card); - if (cardConfig?.view_layout?.position !== "sidebar") { - mainDiv.appendChild(card); + element = card; } else { - sidebarDiv.appendChild(card); + element = document.createElement("hui-card-options"); + element.hass = this.hass; + element.lovelace = this.lovelace; + element.path = [this.index!, idx]; + card.editMode = true; + element.appendChild(card); + } + if (cardConfig?.view_layout?.position !== "sidebar") { + mainDiv.appendChild(element); + } else { + sidebarDiv.appendChild(element); } }); } @@ -147,13 +144,17 @@ export class SideBarView extends LitElement implements LovelaceViewElement { static get styles(): CSSResultGroup { return css` :host { - display: flex; + display: block; padding-top: 4px; - margin-left: 4px; - margin-right: 4px; height: 100%; box-sizing: border-box; + } + + .container { + display: flex; justify-content: center; + margin-left: 4px; + margin-right: 4px; } #main { @@ -166,18 +167,18 @@ export class SideBarView extends LitElement implements LovelaceViewElement { max-width: 380px; } - :host > div { + .container > div { min-width: 0; box-sizing: border-box; } - :host > div > * { + .container > div > * { display: block; margin: var(--masonry-view-card-margin, 4px 4px 8px); } @media (max-width: 760px) { - :host { + .container { flex-direction: column; } #sidebar { @@ -186,7 +187,7 @@ export class SideBarView extends LitElement implements LovelaceViewElement { } @media (max-width: 500px) { - :host > div > * { + .container > div > * { margin-left: 0; margin-right: 0; } From 21a29ed3a56dc010e6314a13842f2dc1bffda69d Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 27 Jul 2021 00:47:50 +0200 Subject: [PATCH 09/43] Statistic sums requires at least 2 values (#9616) --- src/data/history.ts | 16 ++-- .../hui-energy-carbon-consumed-gauge-card.ts | 73 ++++++++++--------- .../hui-energy-solar-consumed-gauge-card.ts | 4 +- .../lovelace/cards/hui-energy-usage-card.ts | 21 ++---- 4 files changed, 54 insertions(+), 60 deletions(-) diff --git a/src/data/history.ts b/src/data/history.ts index 71cc46e231..220c59aaf9 100644 --- a/src/data/history.ts +++ b/src/data/history.ts @@ -302,12 +302,9 @@ export const fetchStatistics = ( export const calculateStatisticSumGrowth = ( values: StatisticValue[] ): number | null => { - if (values.length === 0) { + if (values.length < 2) { return null; } - if (values.length === 1) { - return values[0].sum; - } const endSum = values[values.length - 1].sum; if (endSum === null) { return null; @@ -323,7 +320,7 @@ export const calculateStatisticsSumGrowth = ( data: Statistics, stats: string[] ): number | null => { - let totalGrowth = 0; + let totalGrowth: number | null = null; for (const stat of stats) { if (!(stat in data)) { @@ -332,10 +329,13 @@ export const calculateStatisticsSumGrowth = ( const statGrowth = calculateStatisticSumGrowth(data[stat]); if (statGrowth === null) { - return null; + continue; + } + if (totalGrowth === null) { + totalGrowth = statGrowth; + } else { + totalGrowth += statGrowth; } - - totalGrowth += statGrowth; } return totalGrowth; diff --git a/src/panels/lovelace/cards/hui-energy-carbon-consumed-gauge-card.ts b/src/panels/lovelace/cards/hui-energy-carbon-consumed-gauge-card.ts index 7a3c5f68ba..a5fb506d85 100644 --- a/src/panels/lovelace/cards/hui-energy-carbon-consumed-gauge-card.ts +++ b/src/panels/lovelace/cards/hui-energy-carbon-consumed-gauge-card.ts @@ -14,6 +14,7 @@ import { Statistics, } from "../../../data/history"; import type { HomeAssistant } from "../../../types"; +import { createEntityNotFoundWarning } from "../components/hui-warning"; import type { LovelaceCard } from "../types"; import { severityMap } from "./hui-gauge-card"; import type { EnergyCarbonGaugeCardConfig } from "./types"; @@ -61,7 +62,9 @@ class HuiEnergyCarbonGaugeCard extends LitElement implements LovelaceCard { const co2State = this.hass.states[this._co2SignalEntity]; if (!co2State) { - return html`No CO2 Signal entity found.`; + return html` + ${createEntityNotFoundWarning(this.hass, this._co2SignalEntity)} + `; } const co2percentage = Number(co2State.state); @@ -78,44 +81,46 @@ class HuiEnergyCarbonGaugeCard extends LitElement implements LovelaceCard { types.grid![0].flow_from.map((flow) => flow.stat_energy_from) ); - const totalSolarProduction = types.solar - ? calculateStatisticsSumGrowth( - this._stats, - types.solar.map((source) => source.stat_energy_from) - ) - : undefined; + let value: number | undefined; - const totalGridReturned = calculateStatisticsSumGrowth( - this._stats, - types.grid![0].flow_to.map((flow) => flow.stat_energy_to) - ); + if (totalGridConsumption) { + const totalSolarProduction = types.solar + ? calculateStatisticsSumGrowth( + this._stats, + types.solar.map((source) => source.stat_energy_from) + ) + : undefined; - if (totalGridConsumption === null) { - return html`Couldn't calculate the total grid consumption.`; + const totalGridReturned = calculateStatisticsSumGrowth( + this._stats, + types.grid![0].flow_to.map((flow) => flow.stat_energy_to) + ); + + const highCarbonEnergy = (totalGridConsumption * co2percentage) / 100; + + const totalEnergyConsumed = + totalGridConsumption + + (totalSolarProduction || 0) - + (totalGridReturned || 0); + + value = round((highCarbonEnergy / totalEnergyConsumed) * 100); } - const highCarbonEnergy = (totalGridConsumption * co2percentage) / 100; - - const totalEnergyConsumed = - totalGridConsumption + - (totalSolarProduction || 0) - - (totalGridReturned || 0); - - const value = round((highCarbonEnergy / totalEnergyConsumed) * 100); - return html` - - -
High-carbon energy consumed
+ ${value !== undefined + ? html` +
High-carbon energy consumed
` + : html`Consumed high-carbon energy couldn't be calculated`}
`; } diff --git a/src/panels/lovelace/cards/hui-energy-solar-consumed-gauge-card.ts b/src/panels/lovelace/cards/hui-energy-solar-consumed-gauge-card.ts index c5e9dab835..be3ad03e03 100644 --- a/src/panels/lovelace/cards/hui-energy-solar-consumed-gauge-card.ts +++ b/src/panels/lovelace/cards/hui-energy-solar-consumed-gauge-card.ts @@ -69,8 +69,8 @@ class HuiEnergySolarGaugeCard extends LitElement implements LovelaceCard { } return html` - ${value - ? html` 0; - const totalGridConsumption = calculateStatisticsSumGrowth( - this._stats, - types.grid![0].flow_from.map((flow) => flow.stat_energy_from) - ); - - if (totalGridConsumption === null) { - return html`Total consumption couldn't be calculated`; - } + const totalGridConsumption = + calculateStatisticsSumGrowth( + this._stats, + types.grid![0].flow_from.map((flow) => flow.stat_energy_from) + ) ?? 0; let totalSolarProduction: number | null = null; @@ -84,10 +81,6 @@ class HuiEnergyUsageCard extends LitElement implements LovelaceCard { this._stats, types.solar!.map((source) => source.stat_energy_from) ); - - if (totalSolarProduction === null) { - return html`Total production couldn't be calculated`; - } } let productionReturnedToGrid: number | null = null; @@ -97,10 +90,6 @@ class HuiEnergyUsageCard extends LitElement implements LovelaceCard { this._stats, types.grid![0].flow_to.map((flow) => flow.stat_energy_to) ); - - if (productionReturnedToGrid === undefined) { - return html`Production returned to grid couldn't be calculated`; - } } // total consumption = consumption_from_grid + solar_production - return_to_grid From 0c0091375ce4a0c8836e7ddfaec2ef3d321c4f53 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 27 Jul 2021 21:34:43 +0200 Subject: [PATCH 10/43] Fix grid onboarding (#9621) --- src/data/history.ts | 4 +- .../components/ha-energy-grid-settings.ts | 72 ++++++++++++++----- .../energy/cards/energy-setup-wizard-card.ts | 2 +- 3 files changed, 56 insertions(+), 22 deletions(-) diff --git a/src/data/history.ts b/src/data/history.ts index 220c59aaf9..a111a196b7 100644 --- a/src/data/history.ts +++ b/src/data/history.ts @@ -302,7 +302,7 @@ export const fetchStatistics = ( export const calculateStatisticSumGrowth = ( values: StatisticValue[] ): number | null => { - if (values.length < 2) { + if (!values || values.length < 2) { return null; } const endSum = values[values.length - 1].sum; @@ -324,7 +324,7 @@ export const calculateStatisticsSumGrowth = ( for (const stat of stats) { if (!(stat in data)) { - return null; + continue; } const statGrowth = calculateStatisticSumGrowth(data[stat]); diff --git a/src/panels/config/energy/components/ha-energy-grid-settings.ts b/src/panels/config/energy/components/ha-energy-grid-settings.ts index 999445595e..b919a6d60d 100644 --- a/src/panels/config/energy/components/ha-energy-grid-settings.ts +++ b/src/panels/config/energy/components/ha-energy-grid-settings.ts @@ -24,6 +24,7 @@ import { energySourcesByType, FlowFromGridSourceEnergyPreference, FlowToGridSourceEnergyPreference, + GridSourceTypeEnergyPreference, saveEnergyPreferences, } from "../../../../data/energy"; import { showConfigFlowDialog } from "../../../../dialogs/config-flow/show-dialog-config-flow"; @@ -201,18 +202,33 @@ export class EnergyGridSettings extends LitElement { private _addFromSource() { showEnergySettingsGridFlowFromDialog(this, { currency: this.preferences.currency, - saveCallback: async (source) => { - const flowFrom = energySourcesByType(this.preferences).grid![0] - .flow_from; + saveCallback: async (flow) => { + let preferences: EnergyPreferences; + const gridSource = this.preferences.energy_sources.find( + (src) => src.type === "grid" + ) as GridSourceTypeEnergyPreference | undefined; - const preferences: EnergyPreferences = { - ...this.preferences, - energy_sources: this.preferences.energy_sources.map((src) => - src.type === "grid" - ? { ...src, flow_from: [...flowFrom, source] } - : src - ), - }; + if (!gridSource) { + preferences = { + ...this.preferences, + energy_sources: [ + ...this.preferences.energy_sources, + { + ...emptyGridSourceEnergyPreference(), + flow_from: [flow], + }, + ], + }; + } else { + preferences = { + ...this.preferences, + energy_sources: this.preferences.energy_sources.map((src) => + src.type === "grid" + ? { ...src, flow_from: [...gridSource.flow_from, flow] } + : src + ), + }; + } await this._savePreferences(preferences); }, }); @@ -221,15 +237,33 @@ export class EnergyGridSettings extends LitElement { private _addToSource() { showEnergySettingsGridFlowToDialog(this, { currency: this.preferences.currency, - saveCallback: async (source) => { - const flowTo = energySourcesByType(this.preferences).grid![0].flow_to; + saveCallback: async (flow) => { + let preferences: EnergyPreferences; + const gridSource = this.preferences.energy_sources.find( + (src) => src.type === "grid" + ) as GridSourceTypeEnergyPreference | undefined; - const preferences: EnergyPreferences = { - ...this.preferences, - energy_sources: this.preferences.energy_sources.map((src) => - src.type === "grid" ? { ...src, flow_to: [...flowTo, source] } : src - ), - }; + if (!gridSource) { + preferences = { + ...this.preferences, + energy_sources: [ + ...this.preferences.energy_sources, + { + ...emptyGridSourceEnergyPreference(), + flow_to: [flow], + }, + ], + }; + } else { + preferences = { + ...this.preferences, + energy_sources: this.preferences.energy_sources.map((src) => + src.type === "grid" + ? { ...src, flow_to: [...gridSource.flow_to, flow] } + : src + ), + }; + } await this._savePreferences(preferences); }, }); diff --git a/src/panels/energy/cards/energy-setup-wizard-card.ts b/src/panels/energy/cards/energy-setup-wizard-card.ts index 315fd79ffc..65ddff4a71 100644 --- a/src/panels/energy/cards/energy-setup-wizard-card.ts +++ b/src/panels/energy/cards/energy-setup-wizard-card.ts @@ -20,7 +20,7 @@ export class EnergySetupWizard extends LitElement implements LovelaceCard { @state() private _step = 0; - private _preferences: EnergyPreferences = { + @state() private _preferences: EnergyPreferences = { currency: "€", energy_sources: [], device_consumption: [], From 73b9b87ef3d079acf47f4cf8225dfed9a15aa649 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 27 Jul 2021 22:57:09 +0200 Subject: [PATCH 11/43] Update energy dashboard (#9624) --- src/components/chart/ha-chart-base.ts | 6 +- .../energy/strategies/energy-strategy.ts | 3 +- .../hui-energy-carbon-consumed-gauge-card.ts | 40 +- .../hui-energy-costs-table-card.ts | 18 +- .../hui-energy-devices-graph-card.ts | 16 +- .../energy/hui-energy-distribution-card.ts | 505 ++++++++++++++++++ .../hui-energy-solar-consumed-gauge-card.ts | 25 +- .../hui-energy-solar-graph-card.ts | 70 ++- .../{ => energy}/hui-energy-summary-card.ts | 14 +- .../hui-energy-summary-graph-card.ts | 69 +-- .../lovelace/cards/hui-energy-usage-card.ts | 322 ----------- src/panels/lovelace/cards/types.ts | 11 + .../create-element/create-card-element.ts | 19 +- 13 files changed, 662 insertions(+), 456 deletions(-) rename src/panels/lovelace/cards/{ => energy}/hui-energy-carbon-consumed-gauge-card.ts (83%) rename src/panels/lovelace/cards/{ => energy}/hui-energy-costs-table-card.ts (94%) rename src/panels/lovelace/cards/{ => energy}/hui-energy-devices-graph-card.ts (92%) create mode 100644 src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts rename src/panels/lovelace/cards/{ => energy}/hui-energy-solar-consumed-gauge-card.ts (86%) rename src/panels/lovelace/cards/{ => energy}/hui-energy-solar-graph-card.ts (85%) rename src/panels/lovelace/cards/{ => energy}/hui-energy-summary-card.ts (96%) rename src/panels/lovelace/cards/{ => energy}/hui-energy-summary-graph-card.ts (86%) delete mode 100644 src/panels/lovelace/cards/hui-energy-usage-card.ts diff --git a/src/components/chart/ha-chart-base.ts b/src/components/chart/ha-chart-base.ts index 9dd4889aa2..c662823f58 100644 --- a/src/components/chart/ha-chart-base.ts +++ b/src/components/chart/ha-chart-base.ts @@ -272,7 +272,7 @@ export default class HaChartBase extends LitElement { border-radius: 50%; display: inline-block; height: 16px; - margin-right: 4px; + margin-right: 6px; width: 16px; flex-shrink: 0; box-sizing: border-box; @@ -280,9 +280,10 @@ export default class HaChartBase extends LitElement { .chartTooltip .bullet { align-self: baseline; } + :host([rtl]) .chartLegend .bullet, :host([rtl]) .chartTooltip .bullet { margin-right: inherit; - margin-left: 4px; + margin-left: 6px; } .chartTooltip { padding: 8px; @@ -314,6 +315,7 @@ export default class HaChartBase extends LitElement { white-space: pre-line; align-items: center; line-height: 16px; + padding: 4px 0; } .chartTooltip .title { text-align: center; diff --git a/src/panels/energy/strategies/energy-strategy.ts b/src/panels/energy/strategies/energy-strategy.ts index 7e19dbfd3a..0f63492569 100644 --- a/src/panels/energy/strategies/energy-strategy.ts +++ b/src/panels/energy/strategies/energy-strategy.ts @@ -85,7 +85,8 @@ export class EnergyStrategy { // Only include if we have a grid. if (hasGrid) { view.cards!.push({ - type: "energy-usage", + title: "Energy distribution", + type: "energy-distribution", prefs: energyPrefs, view_layout: { position: "sidebar" }, }); diff --git a/src/panels/lovelace/cards/hui-energy-carbon-consumed-gauge-card.ts b/src/panels/lovelace/cards/energy/hui-energy-carbon-consumed-gauge-card.ts similarity index 83% rename from src/panels/lovelace/cards/hui-energy-carbon-consumed-gauge-card.ts rename to src/panels/lovelace/cards/energy/hui-energy-carbon-consumed-gauge-card.ts index a5fb506d85..4885489c47 100644 --- a/src/panels/lovelace/cards/hui-energy-carbon-consumed-gauge-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-carbon-consumed-gauge-card.ts @@ -1,23 +1,23 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { styleMap } from "lit/directives/style-map"; -import { round } from "../../../common/number/round"; -import { subscribeOne } from "../../../common/util/subscribe-one"; -import "../../../components/ha-card"; -import "../../../components/ha-gauge"; -import { getConfigEntries } from "../../../data/config_entries"; -import { energySourcesByType } from "../../../data/energy"; -import { subscribeEntityRegistry } from "../../../data/entity_registry"; +import { round } from "../../../../common/number/round"; +import { subscribeOne } from "../../../../common/util/subscribe-one"; +import "../../../../components/ha-card"; +import "../../../../components/ha-gauge"; +import { getConfigEntries } from "../../../../data/config_entries"; +import { energySourcesByType } from "../../../../data/energy"; +import { subscribeEntityRegistry } from "../../../../data/entity_registry"; import { calculateStatisticsSumGrowth, fetchStatistics, Statistics, -} from "../../../data/history"; -import type { HomeAssistant } from "../../../types"; -import { createEntityNotFoundWarning } from "../components/hui-warning"; -import type { LovelaceCard } from "../types"; -import { severityMap } from "./hui-gauge-card"; -import type { EnergyCarbonGaugeCardConfig } from "./types"; +} from "../../../../data/history"; +import type { HomeAssistant } from "../../../../types"; +import { createEntityNotFoundWarning } from "../../components/hui-warning"; +import type { LovelaceCard } from "../../types"; +import { severityMap } from "../hui-gauge-card"; +import type { EnergyCarbonGaugeCardConfig } from "../types"; @customElement("hui-energy-carbon-consumed-gauge-card") class HuiEnergyCarbonGaugeCard extends LitElement implements LovelaceCard { @@ -103,7 +103,7 @@ class HuiEnergyCarbonGaugeCard extends LitElement implements LovelaceCard { (totalSolarProduction || 0) - (totalGridReturned || 0); - value = round((highCarbonEnergy / totalEnergyConsumed) * 100); + value = round((1 - highCarbonEnergy / totalEnergyConsumed) * 100); } return html` @@ -116,23 +116,23 @@ class HuiEnergyCarbonGaugeCard extends LitElement implements LovelaceCard { .locale=${this.hass!.locale} label="%" style=${styleMap({ - "--gauge-color": this._computeSeverity(64), + "--gauge-color": this._computeSeverity(value), })} > -
High-carbon energy consumed
` - : html`Consumed high-carbon energy couldn't be calculated`} +
Non-fossil energy consumed
` + : html`Consumed non-fossil energy couldn't be calculated`}
`; } private _computeSeverity(numberValue: number): string { - if (numberValue > 50) { + if (numberValue < 10) { return severityMap.red; } - if (numberValue > 30) { + if (numberValue < 30) { return severityMap.yellow; } - if (numberValue < 10) { + if (numberValue > 75) { return severityMap.green; } return severityMap.normal; diff --git a/src/panels/lovelace/cards/hui-energy-costs-table-card.ts b/src/panels/lovelace/cards/energy/hui-energy-costs-table-card.ts similarity index 94% rename from src/panels/lovelace/cards/hui-energy-costs-table-card.ts rename to src/panels/lovelace/cards/energy/hui-energy-costs-table-card.ts index 9c3a843c0f..1a886d620b 100644 --- a/src/panels/lovelace/cards/hui-energy-costs-table-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-costs-table-card.ts @@ -9,23 +9,23 @@ import { unsafeCSS, } from "lit"; import { customElement, property, state } from "lit/decorators"; -import { computeStateName } from "../../../common/entity/compute_state_name"; -import { round } from "../../../common/number/round"; -import "../../../components/chart/statistics-chart"; -import "../../../components/ha-card"; +import { computeStateName } from "../../../../common/entity/compute_state_name"; +import { round } from "../../../../common/number/round"; +import "../../../../components/chart/statistics-chart"; +import "../../../../components/ha-card"; import { EnergyInfo, getEnergyInfo, GridSourceTypeEnergyPreference, -} from "../../../data/energy"; +} from "../../../../data/energy"; import { calculateStatisticSumGrowth, fetchStatistics, Statistics, -} from "../../../data/history"; -import { HomeAssistant } from "../../../types"; -import { LovelaceCard } from "../types"; -import { EnergyDevicesGraphCardConfig } from "./types"; +} from "../../../../data/history"; +import { HomeAssistant } from "../../../../types"; +import { LovelaceCard } from "../../types"; +import { EnergyDevicesGraphCardConfig } from "../types"; @customElement("hui-energy-costs-table-card") export class HuiEnergyCostsTableCard diff --git a/src/panels/lovelace/cards/hui-energy-devices-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-devices-graph-card.ts similarity index 92% rename from src/panels/lovelace/cards/hui-energy-devices-graph-card.ts rename to src/panels/lovelace/cards/energy/hui-energy-devices-graph-card.ts index 2897dc386b..66e4a75518 100644 --- a/src/panels/lovelace/cards/hui-energy-devices-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-devices-graph-card.ts @@ -14,18 +14,18 @@ import { } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; -import { getColorByIndex } from "../../../common/color/colors"; -import { computeStateName } from "../../../common/entity/compute_state_name"; -import "../../../components/chart/ha-chart-base"; -import "../../../components/ha-card"; +import { getColorByIndex } from "../../../../common/color/colors"; +import { computeStateName } from "../../../../common/entity/compute_state_name"; +import "../../../../components/chart/ha-chart-base"; +import "../../../../components/ha-card"; import { calculateStatisticSumGrowth, fetchStatistics, Statistics, -} from "../../../data/history"; -import { HomeAssistant } from "../../../types"; -import { LovelaceCard } from "../types"; -import { EnergyDevicesGraphCardConfig } from "./types"; +} from "../../../../data/history"; +import { HomeAssistant } from "../../../../types"; +import { LovelaceCard } from "../../types"; +import { EnergyDevicesGraphCardConfig } from "../types"; @customElement("hui-energy-devices-graph-card") export class HuiEnergyDevicesGraphCard diff --git a/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts b/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts new file mode 100644 index 0000000000..a75ad559cf --- /dev/null +++ b/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts @@ -0,0 +1,505 @@ +import { + mdiArrowLeft, + mdiArrowRight, + mdiHome, + mdiLeaf, + mdiSolarPower, + mdiTransmissionTower, +} from "@mdi/js"; +import { css, html, LitElement, svg } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { classMap } from "lit/directives/class-map"; +import { round } from "../../../../common/number/round"; +import { subscribeOne } from "../../../../common/util/subscribe-one"; +import "../../../../components/ha-card"; +import "../../../../components/ha-svg-icon"; +import { getConfigEntries } from "../../../../data/config_entries"; +import { energySourcesByType } from "../../../../data/energy"; +import { subscribeEntityRegistry } from "../../../../data/entity_registry"; +import { + calculateStatisticsSumGrowth, + fetchStatistics, + Statistics, +} from "../../../../data/history"; +import { HomeAssistant } from "../../../../types"; +import { LovelaceCard } from "../../types"; +import { EnergyDistributionCardConfig } from "../types"; + +const CIRCLE_CIRCUMFERENCE = 238.76104; + +@customElement("hui-energy-distribution-card") +class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard { + @property({ attribute: false }) public hass!: HomeAssistant; + + @state() private _config?: EnergyDistributionCardConfig; + + @state() private _stats?: Statistics; + + @state() private _co2SignalEntity?: string; + + private _fetching = false; + + public setConfig(config: EnergyDistributionCardConfig): void { + this._config = config; + } + + public getCardSize(): Promise | number { + return 3; + } + + public willUpdate(changedProps) { + super.willUpdate(changedProps); + + if (!this._fetching && !this._stats) { + this._fetching = true; + Promise.all([this._getStatistics(), this._fetchCO2SignalEntity()]).then( + () => { + this._fetching = false; + } + ); + } + } + + protected render() { + if (!this._config) { + return html``; + } + + if (!this._stats) { + return html`Loading…`; + } + + const prefs = this._config!.prefs; + const types = energySourcesByType(prefs); + + // The strategy only includes this card if we have a grid. + const hasConsumption = true; + + const hasSolarProduction = types.solar !== undefined; + const hasReturnToGrid = hasConsumption && types.grid![0].flow_to.length > 0; + + const totalGridConsumption = + calculateStatisticsSumGrowth( + this._stats, + types.grid![0].flow_from.map((flow) => flow.stat_energy_from) + ) ?? 0; + + let totalSolarProduction: number | null = null; + + if (hasSolarProduction) { + totalSolarProduction = calculateStatisticsSumGrowth( + this._stats, + types.solar!.map((source) => source.stat_energy_from) + ); + } + + let productionReturnedToGrid: number | null = null; + + if (hasReturnToGrid) { + productionReturnedToGrid = calculateStatisticsSumGrowth( + this._stats, + types.grid![0].flow_to.map((flow) => flow.stat_energy_to) + ); + } + + // total consumption = consumption_from_grid + solar_production - return_to_grid + + let co2percentage: number | undefined; + + if (this._co2SignalEntity) { + const co2State = this.hass.states[this._co2SignalEntity]; + if (co2State) { + co2percentage = Number(co2State.state); + if (isNaN(co2percentage)) { + co2percentage = undefined; + } + } + } + + const totalConsumption = + totalGridConsumption + + (totalSolarProduction || 0) - + (productionReturnedToGrid || 0); + + let homeSolarCircumference: number | undefined; + if (hasSolarProduction) { + const homePctSolar = + ((totalSolarProduction || 0) - (productionReturnedToGrid || 0)) / + totalConsumption; + homeSolarCircumference = CIRCLE_CIRCUMFERENCE * homePctSolar; + } + + let lowCarbonConsumption: number | undefined; + + let homeLowCarbonCircumference: number | undefined; + let homeHighCarbonCircumference: number | undefined; + if (co2percentage !== undefined) { + const gridPctHighCarbon = co2percentage / 100; + + lowCarbonConsumption = + totalGridConsumption - totalGridConsumption * gridPctHighCarbon; + + const homePctGridHighCarbon = + (gridPctHighCarbon * totalGridConsumption) / totalConsumption; + + homeHighCarbonCircumference = + CIRCLE_CIRCUMFERENCE * homePctGridHighCarbon; + + homeLowCarbonCircumference = + CIRCLE_CIRCUMFERENCE - + (homeSolarCircumference || 0) - + homeHighCarbonCircumference; + } + + return html` + +
+ ${lowCarbonConsumption !== undefined || hasSolarProduction + ? html`
+ ${lowCarbonConsumption === undefined + ? html`
` + : html` +
+ Non-fossil +
+ + ${round(lowCarbonConsumption, 1)} kWh +
+ + + +
+ `} + ${hasSolarProduction + ? html`
+ Solar +
+ + ${round(totalSolarProduction || 0, 1)} kWh +
+
` + : ""} +
+
` + : ""} +
+
+
+ + + ${hasReturnToGrid + ? html`` + : ""}${round(totalGridConsumption, 1)} + kWh + + ${productionReturnedToGrid + ? html` + ${round(productionReturnedToGrid, 1)} kWh + ` + : ""} +
+ Grid +
+
+
+ + ${round(totalConsumption, 1)} kWh + ${homeSolarCircumference !== undefined || + homeLowCarbonCircumference !== undefined + ? html` + ${homeSolarCircumference !== undefined + ? svg` + ` + : ""} + ${homeHighCarbonCircumference + ? svg` + ` + : ""} + + ` + : ""} +
+ Home +
+
+
+ + ${productionReturnedToGrid && hasSolarProduction + ? svg`` + : ""} + ${totalSolarProduction + ? svg`` + : ""} + ${totalGridConsumption + ? svg`` + : ""} + +
+
+
+ `; + } + + private async _fetchCO2SignalEntity() { + const [configEntries, entityRegistryEntries] = await Promise.all([ + getConfigEntries(this.hass), + subscribeOne(this.hass.connection, subscribeEntityRegistry), + ]); + + const co2ConfigEntry = configEntries.find( + (entry) => entry.domain === "co2signal" + ); + + if (!co2ConfigEntry) { + return; + } + + for (const entry of entityRegistryEntries) { + if (entry.config_entry_id !== co2ConfigEntry.entry_id) { + continue; + } + + // The integration offers 2 entities. We want the % one. + const co2State = this.hass.states[entry.entity_id]; + if (!co2State || co2State.attributes.unit_of_measurement !== "%") { + continue; + } + + this._co2SignalEntity = co2State.entity_id; + break; + } + } + + private async _getStatistics(): Promise { + const startDate = new Date(); + startDate.setHours(0, 0, 0, 0); + startDate.setTime(startDate.getTime() - 1000 * 60 * 60); // subtract 1 hour to get a startpoint + + const statistics: string[] = []; + const prefs = this._config!.prefs; + for (const source of prefs.energy_sources) { + if (source.type === "solar") { + statistics.push(source.stat_energy_from); + continue; + } + + // grid source + for (const flowFrom of source.flow_from) { + statistics.push(flowFrom.stat_energy_from); + } + for (const flowTo of source.flow_to) { + statistics.push(flowTo.stat_energy_to); + } + } + + this._stats = await fetchStatistics( + this.hass!, + startDate, + undefined, + statistics + ); + } + + static styles = css` + :host { + --mdc-icon-size: 24px; + } + .card-content { + position: relative; + } + .lines { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 146px; + display: flex; + justify-content: center; + padding: 0 16px 16px; + box-sizing: border-box; + } + .lines svg { + width: calc(100% - 160px); + height: 100%; + max-width: 340px; + } + .row { + display: flex; + justify-content: space-between; + max-width: 500px; + margin: 0 auto; + } + .circle-container { + display: flex; + flex-direction: column; + align-items: center; + } + .circle-container.solar { + height: 130px; + } + .spacer { + width: 80px; + height: 30px; + } + .circle { + width: 80px; + height: 80px; + border-radius: 50%; + box-sizing: border-box; + border: 2px solid; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + font-size: 12px; + line-height: 12px; + position: relative; + } + ha-svg-icon { + padding-bottom: 2px; + } + ha-svg-icon.small { + --mdc-icon-size: 12px; + } + .label { + color: var(--secondary-text-color); + font-size: 12px; + } + line, + path { + stroke: var(--primary-text-color); + stroke-width: 1; + fill: none; + } + .circle svg { + position: absolute; + fill: none; + stroke-width: 4px; + width: 100%; + height: 100%; + } + .circle svg circle { + animation: rotate-in 0.2s ease-in; + } + .low-carbon line { + stroke: #0f9d58; + } + .low-carbon .circle { + border-color: #0f9d58; + } + .low-carbon ha-svg-icon { + color: #0f9d58; + } + .solar .circle { + border-color: #ff9800; + } + path.solar, + circle.solar { + stroke: #ff9800; + } + circle.low-carbon { + stroke: #0f9d58; + } + circle.return, + path.return { + stroke: #673ab7; + } + .return { + color: #673ab7; + } + .grid .circle { + border-color: #126a9a; + } + .consumption { + color: #126a9a; + } + circle.grid, + path.grid { + stroke: #126a9a; + } + .home .circle { + border: none; + } + .home .circle.border { + border-color: var(--primary-color); + } + @keyframes rotate-in { + from { + stroke-dashoffset: 0; + } + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "hui-energy-distribution-card": HuiEnergyDistrubutionCard; + } +} diff --git a/src/panels/lovelace/cards/hui-energy-solar-consumed-gauge-card.ts b/src/panels/lovelace/cards/energy/hui-energy-solar-consumed-gauge-card.ts similarity index 86% rename from src/panels/lovelace/cards/hui-energy-solar-consumed-gauge-card.ts rename to src/panels/lovelace/cards/energy/hui-energy-solar-consumed-gauge-card.ts index be3ad03e03..f6582c9af6 100644 --- a/src/panels/lovelace/cards/hui-energy-solar-consumed-gauge-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-solar-consumed-gauge-card.ts @@ -1,19 +1,19 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { styleMap } from "lit/directives/style-map"; -import { round } from "../../../common/number/round"; -import "../../../components/ha-card"; -import "../../../components/ha-gauge"; -import { energySourcesByType } from "../../../data/energy"; +import { round } from "../../../../common/number/round"; +import "../../../../components/ha-card"; +import "../../../../components/ha-gauge"; +import { energySourcesByType } from "../../../../data/energy"; import { calculateStatisticsSumGrowth, fetchStatistics, Statistics, -} from "../../../data/history"; -import type { HomeAssistant } from "../../../types"; -import type { LovelaceCard } from "../types"; -import { severityMap } from "./hui-gauge-card"; -import type { EnergySolarGaugeCardConfig } from "./types"; +} from "../../../../data/history"; +import type { HomeAssistant } from "../../../../types"; +import type { LovelaceCard } from "../../types"; +import { severityMap } from "../hui-gauge-card"; +import type { EnergySolarGaugeCardConfig } from "../types"; @customElement("hui-energy-solar-consumed-gauge-card") class HuiEnergySolarGaugeCard extends LitElement implements LovelaceCard { @@ -77,7 +77,7 @@ class HuiEnergySolarGaugeCard extends LitElement implements LovelaceCard { .locale=${this.hass!.locale} label="%" style=${styleMap({ - "--gauge-color": this._computeSeverity(64), + "--gauge-color": this._computeSeverity(value), })} >
Self consumed solar energy
` @@ -87,9 +87,12 @@ class HuiEnergySolarGaugeCard extends LitElement implements LovelaceCard { } private _computeSeverity(numberValue: number): string { - if (numberValue > 50) { + if (numberValue > 75) { return severityMap.green; } + if (numberValue < 50) { + return severityMap.yellow; + } return severityMap.normal; } diff --git a/src/panels/lovelace/cards/hui-energy-solar-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-solar-graph-card.ts similarity index 85% rename from src/panels/lovelace/cards/hui-energy-solar-graph-card.ts rename to src/panels/lovelace/cards/energy/hui-energy-solar-graph-card.ts index c258ac3f79..6e3c756e47 100644 --- a/src/panels/lovelace/cards/hui-energy-solar-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-solar-graph-card.ts @@ -8,31 +8,31 @@ import { } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; -import "../../../components/ha-card"; +import "../../../../components/ha-card"; import { ChartData, ChartDataset, ChartOptions } from "chart.js"; -import { HomeAssistant } from "../../../types"; -import { LovelaceCard } from "../types"; -import { EnergySolarGraphCardConfig } from "./types"; -import { fetchStatistics, Statistics } from "../../../data/history"; +import { HomeAssistant } from "../../../../types"; +import { LovelaceCard } from "../../types"; +import { EnergySolarGraphCardConfig } from "../types"; +import { fetchStatistics, Statistics } from "../../../../data/history"; import { hex2rgb, lab2rgb, rgb2hex, rgb2lab, -} from "../../../common/color/convert-color"; -import { labDarken } from "../../../common/color/lab"; -import { SolarSourceTypeEnergyPreference } from "../../../data/energy"; -import { isComponentLoaded } from "../../../common/config/is_component_loaded"; +} from "../../../../common/color/convert-color"; +import { labDarken } from "../../../../common/color/lab"; +import { SolarSourceTypeEnergyPreference } from "../../../../data/energy"; +import { isComponentLoaded } from "../../../../common/config/is_component_loaded"; import { ForecastSolarForecast, getForecastSolarForecasts, -} from "../../../data/forecast_solar"; -import { computeStateName } from "../../../common/entity/compute_state_name"; -import "../../../components/chart/ha-chart-base"; -import "../../../components/ha-switch"; -import "../../../components/ha-formfield"; +} from "../../../../data/forecast_solar"; +import { computeStateName } from "../../../../common/entity/compute_state_name"; +import "../../../../components/chart/ha-chart-base"; +import "../../../../components/ha-switch"; +import "../../../../components/ha-formfield"; -const SOLAR_COLOR = { border: "#FF9800", background: "#ffcb80" }; +const SOLAR_COLOR = "#FF9800"; @customElement("hui-energy-solar-graph-card") export class HuiEnergySolarGraphCard @@ -123,17 +123,11 @@ export class HuiEnergySolarGraphCard "has-header": !!this._config.title, })}" > - ${this._chartData ? html`` : ""} @@ -142,12 +136,18 @@ export class HuiEnergySolarGraphCard } private _createOptions() { + const startDate = new Date(); + startDate.setHours(0, 0, 0, 0); + const startTime = startDate.getTime(); + this._chartOptions = { parsing: false, animation: false, scales: { x: { type: "time", + suggestedMin: startTime, + suggestedMax: startTime + 24 * 60 * 60 * 1000, adapters: { date: { locale: this.hass.locale, @@ -168,9 +168,14 @@ export class HuiEnergySolarGraphCard time: { tooltipFormat: "datetimeseconds", }, + offset: true, }, y: { type: "linear", + title: { + display: true, + text: "kWh", + }, ticks: { beginAtZero: true, }, @@ -199,9 +204,10 @@ export class HuiEnergySolarGraphCard }, elements: { line: { - tension: 0.4, + tension: 0.3, borderWidth: 1.5, }, + bar: { borderWidth: 1.5 }, point: { hitRadius: 5, }, @@ -252,7 +258,7 @@ export class HuiEnergySolarGraphCard ) as SolarSourceTypeEnergyPreference[]; const statisticsData = Object.values(this._data!); - const datasets: ChartDataset<"line">[] = []; + const datasets: ChartDataset<"bar">[] = []; let endTime: Date; if (statisticsData.length === 0) { @@ -272,22 +278,18 @@ export class HuiEnergySolarGraphCard } solarSources.forEach((source, idx) => { - const data: ChartDataset<"line">[] = []; + const data: ChartDataset<"bar" | "line">[] = []; const entity = this.hass.states[source.stat_energy_from]; const borderColor = idx > 0 - ? rgb2hex( - lab2rgb(labDarken(rgb2lab(hex2rgb(SOLAR_COLOR.border)), idx)) - ) - : SOLAR_COLOR.border; + ? rgb2hex(lab2rgb(labDarken(rgb2lab(hex2rgb(SOLAR_COLOR)), idx))) + : SOLAR_COLOR; data.push({ label: `Production ${ entity ? computeStateName(entity) : source.stat_energy_from }`, - fill: true, - stepped: false, borderColor: borderColor, backgroundColor: borderColor + "7F", data: [], @@ -343,6 +345,7 @@ export class HuiEnergySolarGraphCard if (forecastsData) { const forecast: ChartDataset<"line"> = { + type: "line", label: `Forecast ${ entity ? computeStateName(entity) : source.stat_energy_from }`, @@ -382,11 +385,6 @@ export class HuiEnergySolarGraphCard }; } - private _showAllForecastChanged(ev) { - this._showAllForecastData = ev.target.checked; - this._renderChart(); - } - static get styles(): CSSResultGroup { return css` ha-card { diff --git a/src/panels/lovelace/cards/hui-energy-summary-card.ts b/src/panels/lovelace/cards/energy/hui-energy-summary-card.ts similarity index 96% rename from src/panels/lovelace/cards/hui-energy-summary-card.ts rename to src/panels/lovelace/cards/energy/hui-energy-summary-card.ts index 0b5c47ccb3..9fd7dd20e2 100644 --- a/src/panels/lovelace/cards/hui-energy-summary-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-summary-card.ts @@ -1,21 +1,21 @@ import { mdiCashMultiple, mdiSolarPower } from "@mdi/js"; import { css, html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; -import "../../../components/ha-svg-icon"; +import "../../../../components/ha-svg-icon"; import { energySourcesByType, GridSourceTypeEnergyPreference, SolarSourceTypeEnergyPreference, -} from "../../../data/energy"; +} from "../../../../data/energy"; import { calculateStatisticSumGrowth, fetchStatistics, Statistics, -} from "../../../data/history"; -import { HomeAssistant } from "../../../types"; -import { LovelaceCard } from "../types"; -import { EnergySummaryCardConfig } from "./types"; -import "../../../components/ha-card"; +} from "../../../../data/history"; +import { HomeAssistant } from "../../../../types"; +import { LovelaceCard } from "../../types"; +import { EnergySummaryCardConfig } from "../types"; +import "../../../../components/ha-card"; const renderSumStatHelper = ( data: Statistics, diff --git a/src/panels/lovelace/cards/hui-energy-summary-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-summary-graph-card.ts similarity index 86% rename from src/panels/lovelace/cards/hui-energy-summary-graph-card.ts rename to src/panels/lovelace/cards/energy/hui-energy-summary-graph-card.ts index f0ceab258e..7e98facdac 100644 --- a/src/panels/lovelace/cards/hui-energy-summary-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-summary-graph-card.ts @@ -8,33 +8,28 @@ import { } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; -import "../../../components/ha-card"; +import "../../../../components/ha-card"; import { ChartData, ChartDataset, ChartOptions } from "chart.js"; -import { HomeAssistant } from "../../../types"; -import { LovelaceCard } from "../types"; -import { EnergySummaryGraphCardConfig } from "./types"; -import { fetchStatistics, Statistics } from "../../../data/history"; +import { HomeAssistant } from "../../../../types"; +import { LovelaceCard } from "../../types"; +import { EnergySummaryGraphCardConfig } from "../types"; +import { fetchStatistics, Statistics } from "../../../../data/history"; import { hex2rgb, lab2rgb, rgb2hex, rgb2lab, -} from "../../../common/color/convert-color"; -import { labDarken } from "../../../common/color/lab"; -import { computeStateName } from "../../../common/entity/compute_state_name"; -import "../../../components/chart/ha-chart-base"; -import { round } from "../../../common/number/round"; +} from "../../../../common/color/convert-color"; +import { labDarken } from "../../../../common/color/lab"; +import { computeStateName } from "../../../../common/entity/compute_state_name"; +import "../../../../components/chart/ha-chart-base"; +import { round } from "../../../../common/number/round"; const NEGATIVE = ["to_grid"]; -const ORDER = { - used_solar: 0, - from_grid: 100, - to_grid: 200, -}; const COLORS = { - to_grid: { border: "#56d256", background: "#87ceab" }, - from_grid: { border: "#126A9A", background: "#88b5cd" }, - used_solar: { border: "#FF9800", background: "#ffcb80" }, + to_grid: { border: "#673ab7", background: "#b39bdb" }, + from_grid: { border: "#126A9A", background: "#8ab5cd" }, + used_solar: { border: "#FF9800", background: "#fecc8e" }, }; @customElement("hui-energy-summary-graph-card") @@ -126,7 +121,7 @@ export class HuiEnergySummaryGraphCard ? html`` : ""} @@ -135,12 +130,18 @@ export class HuiEnergySummaryGraphCard } private _createOptions() { + const startDate = new Date(); + startDate.setHours(0, 0, 0, 0); + const startTime = startDate.getTime(); + this._chartOptions = { parsing: false, animation: false, scales: { x: { type: "time", + suggestedMin: startTime, + suggestedMax: startTime + 24 * 60 * 60 * 1000, adapters: { date: { locale: this.hass.locale, @@ -161,10 +162,15 @@ export class HuiEnergySummaryGraphCard time: { tooltipFormat: "datetimeseconds", }, + offset: true, }, y: { stacked: true, type: "linear", + title: { + display: true, + text: "kWh", + }, ticks: { beginAtZero: true, callback: (value) => Math.abs(round(value)), @@ -193,9 +199,13 @@ export class HuiEnergySummaryGraphCard } } return [ - `Total consumed: ${totalConsumed.toFixed(2)} kWh`, - `Total returned: ${totalReturned.toFixed(2)} kWh`, - ]; + totalConsumed + ? `Total consumed: ${totalConsumed.toFixed(2)} kWh` + : "", + totalReturned + ? `Total returned: ${totalReturned.toFixed(2)} kWh` + : "", + ].filter(Boolean); }, }, }, @@ -213,10 +223,7 @@ export class HuiEnergySummaryGraphCard mode: "nearest", }, elements: { - line: { - tension: 0.4, - borderWidth: 1.5, - }, + bar: { borderWidth: 1.5 }, point: { hitRadius: 5, }, @@ -280,7 +287,7 @@ export class HuiEnergySummaryGraphCard } const statisticsData = Object.values(this._data!); - const datasets: ChartDataset<"line">[] = []; + const datasets: ChartDataset<"bar">[] = []; let endTime: Date; if (statisticsData.length === 0) { @@ -370,7 +377,7 @@ export class HuiEnergySummaryGraphCard const negative = NEGATIVE.includes(type); Object.entries(sources).forEach(([statId, source], idx) => { - const data: ChartDataset<"line">[] = []; + const data: ChartDataset<"bar">[] = []; const entity = this.hass.states[statId]; const color = COLORS[type]; @@ -381,9 +388,6 @@ export class HuiEnergySummaryGraphCard : entity ? computeStateName(entity) : statId, - fill: true, - stepped: false, - order: ORDER[type] + idx, borderColor: idx > 0 ? rgb2hex(lab2rgb(labDarken(rgb2lab(hex2rgb(color.border)), idx))) @@ -394,7 +398,7 @@ export class HuiEnergySummaryGraphCard lab2rgb(labDarken(rgb2lab(hex2rgb(color.background)), idx)) ) : color.background, - stack: negative ? "negative" : "positive", + stack: "stack", data: [], }); @@ -402,6 +406,7 @@ export class HuiEnergySummaryGraphCard for (const key of uniqueKeys) { const value = key in source ? Math.round(source[key] * 100) / 100 : 0; const date = new Date(key); + // @ts-expect-error data[0].data.push({ x: date.getTime(), y: value && negative ? -1 * value : value, diff --git a/src/panels/lovelace/cards/hui-energy-usage-card.ts b/src/panels/lovelace/cards/hui-energy-usage-card.ts deleted file mode 100644 index f297fbdaf3..0000000000 --- a/src/panels/lovelace/cards/hui-energy-usage-card.ts +++ /dev/null @@ -1,322 +0,0 @@ -import { mdiHome, mdiLeaf, mdiSolarPower, mdiTransmissionTower } from "@mdi/js"; -import { css, html, LitElement } from "lit"; -import { customElement, property, state } from "lit/decorators"; -import { subscribeOne } from "../../../common/util/subscribe-one"; -import "../../../components/ha-svg-icon"; -import { getConfigEntries } from "../../../data/config_entries"; -import { energySourcesByType } from "../../../data/energy"; -import { subscribeEntityRegistry } from "../../../data/entity_registry"; -import { - calculateStatisticsSumGrowth, - fetchStatistics, - Statistics, -} from "../../../data/history"; -import { HomeAssistant } from "../../../types"; -import { LovelaceCard } from "../types"; -import { EnergySummaryCardConfig } from "./types"; -import "../../../components/ha-card"; -import { round } from "../../../common/number/round"; - -@customElement("hui-energy-usage-card") -class HuiEnergyUsageCard extends LitElement implements LovelaceCard { - @property({ attribute: false }) public hass!: HomeAssistant; - - @state() private _config?: EnergySummaryCardConfig; - - @state() private _stats?: Statistics; - - @state() private _co2SignalEntity?: string; - - private _fetching = false; - - public setConfig(config: EnergySummaryCardConfig): void { - this._config = config; - } - - public getCardSize(): Promise | number { - return 3; - } - - public willUpdate(changedProps) { - super.willUpdate(changedProps); - - if (!this._fetching && !this._stats) { - this._fetching = true; - Promise.all([this._getStatistics(), this._fetchCO2SignalEntity()]).then( - () => { - this._fetching = false; - } - ); - } - } - - protected render() { - if (!this._config) { - return html``; - } - - if (!this._stats) { - return html`Loading…`; - } - - const prefs = this._config!.prefs; - const types = energySourcesByType(prefs); - - // The strategy only includes this card if we have a grid. - const hasConsumption = true; - - const hasSolarProduction = types.solar !== undefined; - const hasReturnToGrid = hasConsumption && types.grid![0].flow_to.length > 0; - - const totalGridConsumption = - calculateStatisticsSumGrowth( - this._stats, - types.grid![0].flow_from.map((flow) => flow.stat_energy_from) - ) ?? 0; - - let totalSolarProduction: number | null = null; - - if (hasSolarProduction) { - totalSolarProduction = calculateStatisticsSumGrowth( - this._stats, - types.solar!.map((source) => source.stat_energy_from) - ); - } - - let productionReturnedToGrid: number | null = null; - - if (hasReturnToGrid) { - productionReturnedToGrid = calculateStatisticsSumGrowth( - this._stats, - types.grid![0].flow_to.map((flow) => flow.stat_energy_to) - ); - } - - // total consumption = consumption_from_grid + solar_production - return_to_grid - - let co2percentage: number | undefined; - - if (this._co2SignalEntity) { - const co2State = this.hass.states[this._co2SignalEntity]; - if (co2State) { - co2percentage = Number(co2State.state); - if (isNaN(co2percentage)) { - co2percentage = undefined; - } - } - } - - // We are calculating low carbon consumption based on what we got from the grid - // minus what we gave back because what we gave back is low carbon - const relativeGridFlow = - totalGridConsumption - (productionReturnedToGrid || 0); - - let lowCarbonConsumption: number | undefined; - - if (co2percentage !== undefined) { - if (relativeGridFlow > 0) { - lowCarbonConsumption = round(relativeGridFlow * (co2percentage / 100)); - } else { - lowCarbonConsumption = 0; - } - } - - const totalConsumption = - totalGridConsumption + - (totalSolarProduction || 0) - - (productionReturnedToGrid || 0); - - const gridPctLowCarbon = - co2percentage === undefined ? 0 : co2percentage / 100; - const gridPctHighCarbon = 1 - gridPctLowCarbon; - - const homePctSolar = - ((totalSolarProduction || 0) - (productionReturnedToGrid || 0)) / - totalConsumption; - // When we know the ratio solar-grid, we can adjust the low/high carbon - // percentages to reflect that. - const homePctGridLowCarbon = gridPctLowCarbon * (1 - homePctSolar); - const homePctGridHighCarbon = gridPctHighCarbon * (1 - homePctSolar); - - return html` - -
-
- ${co2percentage === undefined - ? "" - : html` -
- Low-carbon -
- - ${co2percentage}% / ${round(lowCarbonConsumption!)} kWh -
-
- `} -
- Solar -
- - ${round(totalSolarProduction || 0)} kWh -
-
-
-
-
-
- - ${round(totalGridConsumption - (productionReturnedToGrid || 0))} - kWh -
    -
  • - Grid high carbon: ${round(gridPctHighCarbon * 100, 1)}% -
  • -
  • Grid low carbon: ${round(gridPctLowCarbon * 100, 1)}%
  • -
-
- Grid -
-
-
- - ${round(totalConsumption)} kWh -
    -
  • - Grid high carbon: ${round(homePctGridHighCarbon * 100)}% -
  • -
  • - Grid low carbon: ${round(homePctGridLowCarbon * 100)}% -
  • -
  • Solar: ${round(homePctSolar * 100)}%
  • -
-
- Home -
-
-
-
- `; - } - - private async _fetchCO2SignalEntity() { - const [configEntries, entityRegistryEntries] = await Promise.all([ - getConfigEntries(this.hass), - subscribeOne(this.hass.connection, subscribeEntityRegistry), - ]); - - const co2ConfigEntry = configEntries.find( - (entry) => entry.domain === "co2signal" - ); - - if (!co2ConfigEntry) { - return; - } - - for (const entry of entityRegistryEntries) { - if (entry.config_entry_id !== co2ConfigEntry.entry_id) { - continue; - } - - // The integration offers 2 entities. We want the % one. - const co2State = this.hass.states[entry.entity_id]; - if (!co2State || co2State.attributes.unit_of_measurement !== "%") { - continue; - } - - this._co2SignalEntity = co2State.entity_id; - break; - } - } - - private async _getStatistics(): Promise { - const startDate = new Date(); - startDate.setHours(0, 0, 0, 0); - startDate.setTime(startDate.getTime() - 1000 * 60 * 60); // subtract 1 hour to get a startpoint - - const statistics: string[] = []; - const prefs = this._config!.prefs; - for (const source of prefs.energy_sources) { - if (source.type === "solar") { - statistics.push(source.stat_energy_from); - continue; - } - - // grid source - for (const flowFrom of source.flow_from) { - statistics.push(flowFrom.stat_energy_from); - } - for (const flowTo of source.flow_to) { - statistics.push(flowTo.stat_energy_to); - } - } - - this._stats = await fetchStatistics( - this.hass!, - startDate, - undefined, - statistics - ); - } - - static styles = css` - :host { - --mdc-icon-size: 26px; - } - .row { - display: flex; - margin-bottom: 30px; - } - .row:last-child { - margin-bottom: 0; - } - .circle-container { - display: flex; - flex-direction: column; - align-items: center; - margin-right: 40px; - } - .circle { - width: 80px; - height: 80px; - border-radius: 50%; - border: 2px solid; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - text-align: center; - font-size: 12px; - } - .label { - color: var(--secondary-text-color); - font-size: 12px; - } - .circle-container:last-child { - margin-right: 0; - } - .circle ul { - display: none; - } - .low-carbon { - border-color: #0da035; - } - .low-carbon ha-svg-icon { - color: #0da035; - } - .solar { - border-color: #ff9800; - } - .grid { - border-color: #134763; - } - .circle-container.home { - margin-left: 120px; - } - `; -} - -declare global { - interface HTMLElementTagNameMap { - "hui-energy-usage-card": HuiEnergyUsageCard; - } -} diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index ebaecf3e9e..dcbf69054a 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -92,31 +92,42 @@ export interface ButtonCardConfig extends LovelaceCardConfig { export interface EnergySummaryCardConfig extends LovelaceCardConfig { type: "energy-summary"; + title?: string; prefs: EnergyPreferences; } +export interface EnergyDistributionCardConfig extends LovelaceCardConfig { + type: "energy-distribution"; + title?: string; + prefs: EnergyPreferences; +} export interface EnergySummaryGraphCardConfig extends LovelaceCardConfig { type: "energy-summary-graph"; + title?: string; prefs: EnergyPreferences; } export interface EnergySolarGraphCardConfig extends LovelaceCardConfig { type: "energy-solar-graph"; + title?: string; prefs: EnergyPreferences; } export interface EnergyDevicesGraphCardConfig extends LovelaceCardConfig { type: "energy-devices-graph"; + title?: string; prefs: EnergyPreferences; } export interface EnergySolarGaugeCardConfig extends LovelaceCardConfig { type: "energy-solar-consumed-gauge"; + title?: string; prefs: EnergyPreferences; } export interface EnergyCarbonGaugeCardConfig extends LovelaceCardConfig { type: "energy-carbon-consumed-gauge"; + title?: string; prefs: EnergyPreferences; } diff --git a/src/panels/lovelace/create-element/create-card-element.ts b/src/panels/lovelace/create-element/create-card-element.ts index 33871cfe40..0524406b79 100644 --- a/src/panels/lovelace/create-element/create-card-element.ts +++ b/src/panels/lovelace/create-element/create-card-element.ts @@ -35,18 +35,21 @@ const LAZY_LOAD_TYPES = { "alarm-panel": () => import("../cards/hui-alarm-panel-card"), error: () => import("../cards/hui-error-card"), "empty-state": () => import("../cards/hui-empty-state-card"), - "energy-summary": () => import("../cards/hui-energy-summary-card"), + "energy-summary": () => import("../cards/energy/hui-energy-summary-card"), "energy-summary-graph": () => - import("../cards/hui-energy-summary-graph-card"), - "energy-solar-graph": () => import("../cards/hui-energy-solar-graph-card"), + import("../cards/energy/hui-energy-summary-graph-card"), + "energy-solar-graph": () => + import("../cards/energy/hui-energy-solar-graph-card"), "energy-devices-graph": () => - import("../cards/hui-energy-devices-graph-card"), - "energy-costs-table": () => import("../cards/hui-energy-costs-table-card"), - "energy-usage": () => import("../cards/hui-energy-usage-card"), + import("../cards/energy/hui-energy-devices-graph-card"), + "energy-costs-table": () => + import("../cards/energy/hui-energy-costs-table-card"), + "energy-distribution": () => + import("../cards/energy/hui-energy-distribution-card"), "energy-solar-consumed-gauge": () => - import("../cards/hui-energy-solar-consumed-gauge-card"), + import("../cards/energy/hui-energy-solar-consumed-gauge-card"), "energy-carbon-consumed-gauge": () => - import("../cards/hui-energy-carbon-consumed-gauge-card"), + import("../cards/energy/hui-energy-carbon-consumed-gauge-card"), grid: () => import("../cards/hui-grid-card"), starting: () => import("../cards/hui-starting-card"), "entity-filter": () => import("../cards/hui-entity-filter-card"), From 07d7fa26feb4b376db962ad674478eb7b2ebb862 Mon Sep 17 00:00:00 2001 From: Michelle Fuchs <50397440+MichelleFuchs@users.noreply.github.com> Date: Wed, 28 Jul 2021 00:13:38 +0200 Subject: [PATCH 12/43] Show value of 'Before'-time in automation editor #9142 (#9598) --- .../automation/condition/types/ha-automation-condition-time.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-time.ts b/src/panels/config/automation/condition/types/ha-automation-condition-time.ts index 5b0e56b350..e606e31eac 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-time.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-time.ts @@ -132,6 +132,7 @@ export class HaTimeCondition extends LitElement implements ConditionElement { .value=${before?.startsWith("input_datetime.") ? before : ""} @value-changed=${this._valueChanged} .hass=${this.hass} + allow-custom-entity >` : html` Date: Tue, 27 Jul 2021 18:16:19 -0400 Subject: [PATCH 13/43] Add Z-Wave JS Heal Node wizard (#9562) --- src/data/zwave_js.ts | 11 + .../zwave_js/ha-device-actions-zwave_js.ts | 15 + .../zwave_js/dialog-zwave_js-heal-node.ts | 273 ++++++++++++++++++ .../show-dialog-zwave_js-heal-node.ts | 21 ++ src/translations/en.json | 14 +- 5 files changed, 333 insertions(+), 1 deletion(-) create mode 100644 src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-heal-node.ts create mode 100644 src/panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-heal-node.ts diff --git a/src/data/zwave_js.ts b/src/data/zwave_js.ts index 16751e26b2..3219480313 100644 --- a/src/data/zwave_js.ts +++ b/src/data/zwave_js.ts @@ -178,6 +178,17 @@ export const reinterviewNode = ( } ); +export const healNode = ( + hass: HomeAssistant, + entry_id: string, + node_id: number +): Promise => + hass.callWS({ + type: "zwave_js/heal_node", + entry_id: entry_id, + node_id: node_id, + }); + export const healNetwork = ( hass: HomeAssistant, entry_id: string diff --git a/src/panels/config/devices/device-detail/integration-elements/zwave_js/ha-device-actions-zwave_js.ts b/src/panels/config/devices/device-detail/integration-elements/zwave_js/ha-device-actions-zwave_js.ts index 8695c556b1..461a6ab387 100644 --- a/src/panels/config/devices/device-detail/integration-elements/zwave_js/ha-device-actions-zwave_js.ts +++ b/src/panels/config/devices/device-detail/integration-elements/zwave_js/ha-device-actions-zwave_js.ts @@ -16,6 +16,7 @@ import { import { haStyle } from "../../../../../../resources/styles"; import { HomeAssistant } from "../../../../../../types"; import { showZWaveJSReinterviewNodeDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-reinterview-node"; +import { showZWaveJSHealNodeDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-heal-node"; @customElement("ha-device-actions-zwave_js") export class HaDeviceActionsZWaveJS extends LitElement { @@ -56,6 +57,9 @@ export class HaDeviceActionsZWaveJS extends LitElement { "ui.panel.config.zwave_js.device_info.reinterview_device" )} + + ${this.hass.localize("ui.panel.config.zwave_js.device_info.heal_node")} + `; } @@ -69,6 +73,17 @@ export class HaDeviceActionsZWaveJS extends LitElement { }); } + private async _healNodeClicked() { + if (!this._nodeId || !this._entryId) { + return; + } + showZWaveJSHealNodeDialog(this, { + entry_id: this._entryId, + node_id: this._nodeId, + device: this.device, + }); + } + static get styles(): CSSResultGroup { return [ haStyle, diff --git a/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-heal-node.ts b/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-heal-node.ts new file mode 100644 index 0000000000..9da32c7820 --- /dev/null +++ b/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-heal-node.ts @@ -0,0 +1,273 @@ +import "../../../../../components/ha-circular-progress"; +import "@material/mwc-button/mwc-button"; +import "@material/mwc-linear-progress/mwc-linear-progress"; +import { mdiStethoscope, mdiCheckCircle, mdiCloseCircle } from "@mdi/js"; +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { fireEvent } from "../../../../../common/dom/fire_event"; +import { createCloseHeading } from "../../../../../components/ha-dialog"; +import { + DeviceRegistryEntry, + computeDeviceName, +} from "../../../../../data/device_registry"; +import { + fetchNetworkStatus, + healNode, + ZWaveJSNetwork, +} from "../../../../../data/zwave_js"; +import { haStyleDialog } from "../../../../../resources/styles"; +import { HomeAssistant } from "../../../../../types"; +import { ZWaveJSHealNodeDialogParams } from "./show-dialog-zwave_js-heal-node"; + +@customElement("dialog-zwave_js-heal-node") +class DialogZWaveJSHealNode extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @state() private entry_id?: string; + + @state() private node_id?: number; + + @state() private device?: DeviceRegistryEntry; + + @state() private _status?: string; + + @state() private _error?: string; + + public showDialog(params: ZWaveJSHealNodeDialogParams): void { + this.entry_id = params.entry_id; + this.device = params.device; + this.node_id = params.node_id; + this._fetchData(); + } + + public closeDialog(): void { + this.entry_id = undefined; + this._status = undefined; + this.node_id = undefined; + this.device = undefined; + this._error = undefined; + + fireEvent(this, "dialog-closed", { dialog: this.localName }); + } + + protected render(): TemplateResult { + if (!this.entry_id || !this.device) { + return html``; + } + + return html` + + ${!this._status + ? html` +
+ +
+

+ ${this.hass.localize( + "ui.panel.config.zwave_js.heal_node.introduction", + { + device: html`${computeDeviceName(this.device, this.hass!)}`, + } + )} +

+
+
+

+ + ${this.hass.localize( + "ui.panel.config.zwave_js.heal_node.traffic_warning" + )} + +

+ + ${this.hass.localize( + "ui.panel.config.zwave_js.heal_node.start_heal" + )} + + ` + : ``} + ${this._status === "started" + ? html` +
+ +
+

+ ${this.hass.localize( + "ui.panel.config.zwave_js.heal_node.in_progress", + { + device: html`${computeDeviceName(this.device, this.hass!)}`, + } + )} +

+
+
+ + ${this.hass.localize("ui.common.close")} + + ` + : ``} + ${this._status === "failed" + ? html` +
+ +
+

+ ${this.hass.localize( + "ui.panel.config.zwave_js.heal_node.healing_failed", + { + device: html`${computeDeviceName(this.device, this.hass!)}`, + } + )} +

+

+ ${this._error + ? html` ${this._error} ` + : ` + ${this.hass.localize( + "ui.panel.config.zwave_js.heal_node.healing_failed_check_logs" + )} + `} +

+
+
+ + ${this.hass.localize("ui.common.close")} + + ` + : ``} + ${this._status === "finished" + ? html` +
+ +
+

+ ${this.hass.localize( + "ui.panel.config.zwave_js.heal_node.healing_complete", + { + device: html`${computeDeviceName(this.device, this.hass!)}`, + } + )} +

+
+
+ + ${this.hass.localize("ui.panel.config.zwave_js.common.close")} + + ` + : ``} + ${this._status === "network-healing" + ? html` +
+ +
+

+ ${this.hass.localize( + "ui.panel.config.zwave_js.heal_node.network_heal_in_progress" + )} +

+
+
+ + ${this.hass.localize("ui.panel.config.zwave_js.common.close")} + + ` + : ``} +
+ `; + } + + private async _fetchData(): Promise { + if (!this.hass) { + return; + } + const network: ZWaveJSNetwork = await fetchNetworkStatus( + this.hass!, + this.entry_id! + ); + if (network.controller.is_heal_network_active) { + this._status = "network-healing"; + } + } + + private async _startHeal(): Promise { + if (!this.hass) { + return; + } + this._status = "started"; + try { + this._status = (await healNode(this.hass, this.entry_id!, this.node_id!)) + ? "finished" + : "failed"; + } catch (error) { + this._error = error.message; + this._status = "failed"; + } + } + + static get styles(): CSSResultGroup { + return [ + haStyleDialog, + css` + .success { + color: var(--success-color); + } + + .failed { + color: var(--error-color); + } + + .flex-container { + display: flex; + align-items: center; + } + + ha-svg-icon { + width: 68px; + height: 48px; + } + + ha-svg-icon.introduction { + color: var(--primary-color); + } + + .flex-container ha-svg-icon, + .flex-container ha-circular-progress { + margin-right: 20px; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "dialog-zwave_js-heal-node": DialogZWaveJSHealNode; + } +} diff --git a/src/panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-heal-node.ts b/src/panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-heal-node.ts new file mode 100644 index 0000000000..646b9f1b3e --- /dev/null +++ b/src/panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-heal-node.ts @@ -0,0 +1,21 @@ +import { fireEvent } from "../../../../../common/dom/fire_event"; +import { DeviceRegistryEntry } from "../../../../../data/device_registry"; + +export interface ZWaveJSHealNodeDialogParams { + entry_id: string; + node_id: number; + device: DeviceRegistryEntry; +} + +export const loadHealNodeDialog = () => import("./dialog-zwave_js-heal-node"); + +export const showZWaveJSHealNodeDialog = ( + element: HTMLElement, + healNodeDialogParams: ZWaveJSHealNodeDialogParams +): void => { + fireEvent(element, "show-dialog", { + dialogTag: "dialog-zwave_js-heal-node", + dialogImport: loadHealNodeDialog, + dialogParams: healNodeDialogParams, + }); +}; diff --git a/src/translations/en.json b/src/translations/en.json index c434885e8f..21fd0916bc 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2688,7 +2688,8 @@ "node_status": "Node Status", "node_ready": "Node Ready", "device_config": "Configure Device", - "reinterview_device": "Re-interview Device" + "reinterview_device": "Re-interview Device", + "heal_node": "Heal Node" }, "node_config": { "header": "Z-Wave Device Configuration", @@ -2763,6 +2764,17 @@ "healing_failed": "Healing failed. Additional information may be available in the logs.", "healing_cancelled": "Network healing has been cancelled." }, + "heal_node": { + "title": "Heal a Z-Wave Device", + "introduction": "Tell {device} to update its routes back to the controller. This can help with communication issues if you have recently moved the device or your controller.", + "traffic_warning": "The healing process generates a large amount of traffic on the Z-Wave network. This may cause devices to respond slowly (or not at all) while the heal is in progress.", + "start_heal": "Heal Device", + "healing_failed": "{device} could not be healed.", + "healing_failed_check_logs": "Additional information may be available in the logs.", + "healing_complete": "{device} has been healed.", + "in_progress": "{device} healing is in progress.", + "network_heal_in_progress": "A Z-Wave network heal is already in progress. Please wait for it to finish before healing an individual device." + }, "logs": { "title": "Z-Wave JS Logs", "log_level": "Log Level", From 37d754d0693ec1bfa1b533635dbed932f2b0c644 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 27 Jul 2021 15:25:12 -0700 Subject: [PATCH 14/43] Bumped version to 20210727.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 87b564b33c..7315e0b189 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup( name="home-assistant-frontend", - version="20210726.0", + version="20210727.0", description="The Home Assistant frontend", url="https://github.com/home-assistant/frontend", author="The Home Assistant Authors", From 6ab0f1db5737c4f96c308dd8af92684a133e38f7 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 28 Jul 2021 12:48:39 +0200 Subject: [PATCH 15/43] Add currency core configuration (#9620) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Joakim Sørensen Co-authored-by: Paulus Schoutsen --- package.json | 2 +- src/components/currency-datalist.ts | 168 ++++++++++++++++++ src/data/core.ts | 1 + src/data/currency.ts | 8 + src/fake_data/demo_config.ts | 1 + src/panels/config/core/ha-config-core-form.ts | 64 ++++++- src/translations/en.json | 4 +- yarn.lock | 10 +- 8 files changed, 247 insertions(+), 11 deletions(-) create mode 100644 src/components/currency-datalist.ts create mode 100644 src/data/currency.ts diff --git a/package.json b/package.json index c7b05c06df..f6c7c0fcb7 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,7 @@ "fuse.js": "^6.0.0", "google-timezones-json": "^1.0.2", "hls.js": "^1.0.7", - "home-assistant-js-websocket": "^5.10.0", + "home-assistant-js-websocket": "^5.11.0", "idb-keyval": "^5.0.5", "intl-messageformat": "^9.6.16", "js-yaml": "^4.1.0", diff --git a/src/components/currency-datalist.ts b/src/components/currency-datalist.ts new file mode 100644 index 0000000000..7133aa560b --- /dev/null +++ b/src/components/currency-datalist.ts @@ -0,0 +1,168 @@ +export const createCurrencyListEl = () => { + const list = document.createElement("datalist"); + list.id = "currencies"; + for (const currency of [ + "AED", + "AFN", + "ALL", + "AMD", + "ANG", + "AOA", + "ARS", + "AUD", + "AWG", + "AZN", + "BAM", + "BBD", + "BDT", + "BGN", + "BHD", + "BIF", + "BMD", + "BND", + "BOB", + "BRL", + "BSD", + "BTN", + "BWP", + "BYR", + "BZD", + "CAD", + "CDF", + "CHF", + "CLP", + "CNY", + "COP", + "CRC", + "CUP", + "CVE", + "CZK", + "DJF", + "DKK", + "DOP", + "DZD", + "EGP", + "ERN", + "ETB", + "EUR", + "FJD", + "FKP", + "GBP", + "GEL", + "GHS", + "GIP", + "GMD", + "GNF", + "GTQ", + "GYD", + "HKD", + "HNL", + "HRK", + "HTG", + "HUF", + "IDR", + "ILS", + "INR", + "IQD", + "IRR", + "ISK", + "JMD", + "JOD", + "JPY", + "KES", + "KGS", + "KHR", + "KMF", + "KPW", + "KRW", + "KWD", + "KYD", + "KZT", + "LAK", + "LBP", + "LKR", + "LRD", + "LSL", + "LTL", + "LYD", + "MAD", + "MDL", + "MGA", + "MKD", + "MMK", + "MNT", + "MOP", + "MRO", + "MUR", + "MVR", + "MWK", + "MXN", + "MYR", + "MZN", + "NAD", + "NGN", + "NIO", + "NOK", + "NPR", + "NZD", + "OMR", + "PAB", + "PEN", + "PGK", + "PHP", + "PKR", + "PLN", + "PYG", + "QAR", + "RON", + "RSD", + "RUB", + "RWF", + "SAR", + "SBD", + "SCR", + "SDG", + "SEK", + "SGD", + "SHP", + "SLL", + "SOS", + "SRD", + "SSP", + "STD", + "SYP", + "SZL", + "THB", + "TJS", + "TMT", + "TND", + "TOP", + "TRY", + "TTD", + "TWD", + "TZS", + "UAH", + "UGX", + "USD", + "UYU", + "UZS", + "VEF", + "VND", + "VUV", + "WST", + "XAF", + "XCD", + "XOF", + "XPF", + "YER", + "ZAR", + "ZMK", + "ZWL", + ]) { + const option = document.createElement("option"); + option.value = currency; + option.innerHTML = currency; + list.appendChild(option); + } + return list; +}; diff --git a/src/data/core.ts b/src/data/core.ts index 667a90cd61..32006fbb7e 100644 --- a/src/data/core.ts +++ b/src/data/core.ts @@ -10,6 +10,7 @@ export interface ConfigUpdateValues { time_zone: string; external_url?: string | null; internal_url?: string | null; + currency?: string | null; } export interface CheckConfigResult { diff --git a/src/data/currency.ts b/src/data/currency.ts new file mode 100644 index 0000000000..d8ddf7933e --- /dev/null +++ b/src/data/currency.ts @@ -0,0 +1,8 @@ +export const SYMBOL_TO_ISO = { + $: "USD", + "€": "EUR", + "¥": "JPY", + "£": "GBP", + "₽": "RUB", + "₹": "INR", +}; diff --git a/src/fake_data/demo_config.ts b/src/fake_data/demo_config.ts index 68a99ee8e2..91052868ea 100644 --- a/src/fake_data/demo_config.ts +++ b/src/fake_data/demo_config.ts @@ -22,4 +22,5 @@ export const demoConfig: HassConfig = { state: STATE_RUNNING, internal_url: "http://homeassistant.local:8123", external_url: null, + currency: "USD", }; diff --git a/src/panels/config/core/ha-config-core-form.ts b/src/panels/config/core/ha-config-core-form.ts index 2d26def4ce..980f1cd5f9 100644 --- a/src/panels/config/core/ha-config-core-form.ts +++ b/src/panels/config/core/ha-config-core-form.ts @@ -7,11 +7,13 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { UNIT_C } from "../../../common/const"; +import { createCurrencyListEl } from "../../../components/currency-datalist"; import "../../../components/ha-card"; import "../../../components/map/ha-locations-editor"; import type { MarkerLocation } from "../../../components/map/ha-locations-editor"; import { createTimezoneListEl } from "../../../components/timezone-datalist"; import { ConfigUpdateValues, saveCoreConfig } from "../../../data/core"; +import { SYMBOL_TO_ISO } from "../../../data/currency"; import type { PolymerChangedEvent } from "../../../polymer-types"; import type { HomeAssistant } from "../../../types"; @@ -23,6 +25,8 @@ class ConfigCoreForm extends LitElement { @state() private _location?: [number, number]; + @state() private _currency?: string; + @state() private _elevation?: string; @state() private _unitSystem?: ConfigUpdateValues["unit_system"]; @@ -143,6 +147,33 @@ class ConfigCoreForm extends LitElement { +
+
+ ${this.hass.localize( + "ui.panel.config.core.section.core.core_config.currency" + )}
+ ${this.hass.localize( + "ui.panel.config.core.section.core.core_config.find_currency_value" + )} +
+ + +
@@ -157,10 +188,16 @@ class ConfigCoreForm extends LitElement { protected firstUpdated(changedProps) { super.firstUpdated(changedProps); - const input = this.shadowRoot!.querySelector( + + const tzInput = this.shadowRoot!.querySelector( "[name=timeZone]" ) as PaperInputElement; - input.inputElement.appendChild(createTimezoneListEl()); + tzInput.inputElement.appendChild(createTimezoneListEl()); + + const cInput = this.shadowRoot!.querySelector( + "[name=currency]" + ) as PaperInputElement; + cInput.inputElement.appendChild(createCurrencyListEl()); } private _markerLocation = memoizeOne( @@ -178,6 +215,12 @@ class ConfigCoreForm extends LitElement { ] ); + private get _currencyValue() { + return this._currency !== undefined + ? this._currency + : this.hass.config.currency; + } + private get _elevationValue() { return this._elevation !== undefined ? this._elevation @@ -200,7 +243,15 @@ class ConfigCoreForm extends LitElement { private _handleChange(ev: PolymerChangedEvent) { const target = ev.currentTarget as PaperInputElement; - this[`_${target.name}`] = target.value; + let value = target.value; + + if (target.name === "currency" && value) { + if (value in SYMBOL_TO_ISO) { + value = SYMBOL_TO_ISO[value]; + } + } + + this[`_${target.name}`] = value; } private _locationChanged(ev) { @@ -223,12 +274,13 @@ class ConfigCoreForm extends LitElement { await saveCoreConfig(this.hass, { latitude: location[0], longitude: location[1], + currency: this._currencyValue, elevation: Number(this._elevationValue), unit_system: this._unitSystemValue, time_zone: this._timeZoneValue, }); } catch (err) { - alert("FAIL"); + alert(`Error saving config: ${err.message}`); } finally { this._working = false; } @@ -258,6 +310,10 @@ class ConfigCoreForm extends LitElement { .card-actions { text-align: right; } + + a { + color: var(--primary-color); + } `; } } diff --git a/src/translations/en.json b/src/translations/en.json index 21fd0916bc..39ebb12067 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1084,9 +1084,11 @@ "unit_system_metric": "Metric", "imperial_example": "Fahrenheit, pounds", "metric_example": "Celsius, kilograms", + "find_currency_value": "Find your value", "save_button": "Save", "external_url": "External URL", - "internal_url": "Internal URL" + "internal_url": "Internal URL", + "currency": "Currency" } } } diff --git a/yarn.lock b/yarn.lock index 355356e0ed..8b8220541c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8941,7 +8941,7 @@ fsevents@~2.3.1: gulp-rename: ^2.0.0 gulp-zopfli-green: ^3.0.1 hls.js: ^1.0.7 - home-assistant-js-websocket: ^5.10.0 + home-assistant-js-websocket: ^5.11.0 html-minifier: ^4.0.0 husky: ^1.3.1 idb-keyval: ^5.0.5 @@ -9011,10 +9011,10 @@ fsevents@~2.3.1: languageName: unknown linkType: soft -"home-assistant-js-websocket@npm:^5.10.0": - version: 5.10.0 - resolution: "home-assistant-js-websocket@npm:5.10.0" - checksum: 86c11025bbc1146dbc1e22492324170993c540d7744b89f19471a6e5400051fee053dd13cc1b3e46d0c61858ee2cba739681f820dc2b73cddd258e4ff04829f8 +"home-assistant-js-websocket@npm:^5.11.0": + version: 5.11.0 + resolution: "home-assistant-js-websocket@npm:5.11.0" + checksum: 7e493f2528a49a2ea8b35468a7c655f97627820542bd4dd841060ca7b34d0fc9c5b4b7cc22d0ba16f1f7932342fd5978c923fc6a213d2af05509ad8ff6912422 languageName: node linkType: hard From 8cd9f891fb054aaaca622a82a63c2982ce16dfd2 Mon Sep 17 00:00:00 2001 From: Charles Garwood Date: Wed, 28 Jul 2021 09:38:50 -0400 Subject: [PATCH 16/43] Add remove failed node support to Z-Wave JS devices (#9560) Co-authored-by: Bram Kragten --- src/data/zwave_js.ts | 21 ++ .../zwave_js/ha-device-actions-zwave_js.ts | 16 ++ .../dialog-zwave_js-remove-failed-node.ts | 237 ++++++++++++++++++ ...show-dialog-zwave_js-remove-failed-node.ts | 20 ++ src/translations/en.json | 11 +- 5 files changed, 304 insertions(+), 1 deletion(-) create mode 100644 src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-remove-failed-node.ts create mode 100644 src/panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-remove-failed-node.ts diff --git a/src/data/zwave_js.ts b/src/data/zwave_js.ts index 3219480313..b443d62fbc 100644 --- a/src/data/zwave_js.ts +++ b/src/data/zwave_js.ts @@ -83,6 +83,12 @@ export interface ZWaveJSHealNetworkStatusMessage { heal_node_status: { [key: number]: string }; } +export interface ZWaveJSRemovedNode { + node_id: number; + manufacturer: string; + label: string; +} + export enum NodeStatus { Unknown, Asleep, @@ -189,6 +195,21 @@ export const healNode = ( node_id: node_id, }); +export const removeFailedNode = ( + hass: HomeAssistant, + entry_id: string, + node_id: number, + callbackFunction: (message: any) => void +): Promise => + hass.connection.subscribeMessage( + (message: any) => callbackFunction(message), + { + type: "zwave_js/remove_failed_node", + entry_id: entry_id, + node_id: node_id, + } + ); + export const healNetwork = ( hass: HomeAssistant, entry_id: string diff --git a/src/panels/config/devices/device-detail/integration-elements/zwave_js/ha-device-actions-zwave_js.ts b/src/panels/config/devices/device-detail/integration-elements/zwave_js/ha-device-actions-zwave_js.ts index 461a6ab387..cd6b6e5f7d 100644 --- a/src/panels/config/devices/device-detail/integration-elements/zwave_js/ha-device-actions-zwave_js.ts +++ b/src/panels/config/devices/device-detail/integration-elements/zwave_js/ha-device-actions-zwave_js.ts @@ -17,6 +17,7 @@ import { haStyle } from "../../../../../../resources/styles"; import { HomeAssistant } from "../../../../../../types"; import { showZWaveJSReinterviewNodeDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-reinterview-node"; import { showZWaveJSHealNodeDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-heal-node"; +import { showZWaveJSRemoveFailedNodeDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-remove-failed-node"; @customElement("ha-device-actions-zwave_js") export class HaDeviceActionsZWaveJS extends LitElement { @@ -60,6 +61,11 @@ export class HaDeviceActionsZWaveJS extends LitElement { ${this.hass.localize("ui.panel.config.zwave_js.device_info.heal_node")} + + ${this.hass.localize( + "ui.panel.config.zwave_js.device_info.remove_failed" + )} + `; } @@ -84,6 +90,16 @@ export class HaDeviceActionsZWaveJS extends LitElement { }); } + private async _removeFailedNode() { + if (!this._nodeId || !this._entryId) { + return; + } + showZWaveJSRemoveFailedNodeDialog(this, { + entry_id: this._entryId, + node_id: this._nodeId, + }); + } + static get styles(): CSSResultGroup { return [ haStyle, diff --git a/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-remove-failed-node.ts b/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-remove-failed-node.ts new file mode 100644 index 0000000000..ca9cce32f6 --- /dev/null +++ b/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-remove-failed-node.ts @@ -0,0 +1,237 @@ +import "@material/mwc-button/mwc-button"; +import { mdiCheckCircle, mdiCloseCircle, mdiRobotDead } from "@mdi/js"; +import { UnsubscribeFunc } from "home-assistant-js-websocket"; +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { fireEvent } from "../../../../../common/dom/fire_event"; +import "../../../../../components/ha-circular-progress"; +import { createCloseHeading } from "../../../../../components/ha-dialog"; +import { + removeFailedNode, + ZWaveJSRemovedNode, +} from "../../../../../data/zwave_js"; +import { haStyleDialog } from "../../../../../resources/styles"; +import { HomeAssistant } from "../../../../../types"; +import { ZWaveJSRemoveFailedNodeDialogParams } from "./show-dialog-zwave_js-remove-failed-node"; + +@customElement("dialog-zwave_js-remove-failed-node") +class DialogZWaveJSRemoveFailedNode extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @state() private entry_id?: string; + + @state() private node_id?: number; + + @state() private _status = ""; + + @state() private _error?: any; + + @state() private _node?: ZWaveJSRemovedNode; + + private _subscribed?: Promise; + + public disconnectedCallback(): void { + super.disconnectedCallback(); + this._unsubscribe(); + } + + public async showDialog( + params: ZWaveJSRemoveFailedNodeDialogParams + ): Promise { + this.entry_id = params.entry_id; + this.node_id = params.node_id; + } + + public closeDialog(): void { + this._unsubscribe(); + this.entry_id = undefined; + this._status = ""; + + fireEvent(this, "dialog-closed", { dialog: this.localName }); + } + + public closeDialogFinished(): void { + history.back(); + this.closeDialog(); + } + + protected render(): TemplateResult { + if (!this.entry_id || !this.node_id) { + return html``; + } + + return html` + + ${this._status === "" + ? html` +
+ +
+ ${this.hass.localize( + "ui.panel.config.zwave_js.remove_failed_node.introduction" + )} +
+
+ + ${this.hass.localize( + "ui.panel.config.zwave_js.remove_failed_node.remove_device" + )} + + ` + : ``} + ${this._status === "started" + ? html` +
+ +
+

+ + ${this.hass.localize( + "ui.panel.config.zwave_js.remove_failed_node.in_progress" + )} + +

+
+
+ ` + : ``} + ${this._status === "failed" + ? html` +
+ +
+

+ ${this.hass.localize( + "ui.panel.config.zwave_js.remove_failed_node.removal_failed" + )} +

+ ${this._error + ? html`

${this._error.message}

` + : ``} +
+
+ + ${this.hass.localize("ui.common.close")} + + ` + : ``} + ${this._status === "finished" + ? html` +
+ +
+

+ ${this.hass.localize( + "ui.panel.config.zwave_js.remove_failed_node.removal_finished", + "id", + this._node!.node_id + )} +

+
+
+ + ${this.hass.localize("ui.common.close")} + + ` + : ``} +
+ `; + } + + private _startExclusion(): void { + if (!this.hass) { + return; + } + this._status = "started"; + this._subscribed = removeFailedNode( + this.hass, + this.entry_id!, + this.node_id!, + (message: any) => this._handleMessage(message) + ).catch((error) => { + this._status = "failed"; + this._error = error; + }); + } + + private _handleMessage(message: any): void { + if (message.event === "exclusion started") { + this._status = "started"; + } + if (message.event === "node removed") { + this._status = "finished"; + this._node = message.node; + this._unsubscribe(); + } + } + + private async _unsubscribe(): Promise { + if (this._subscribed) { + const unsubFunc = await this._subscribed; + if (unsubFunc instanceof Function) { + unsubFunc(); + } + this._subscribed = undefined; + } + if (this._status !== "finished") { + this._status = ""; + } + } + + static get styles(): CSSResultGroup { + return [ + haStyleDialog, + css` + .success { + color: var(--success-color); + } + + .failed { + color: var(--warning-color); + } + + .flex-container { + display: flex; + align-items: center; + } + + ha-svg-icon { + width: 68px; + height: 48px; + } + + .flex-container ha-circular-progress, + .flex-container ha-svg-icon { + margin-right: 20px; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "dialog-zwave_js-remove-failed-node": DialogZWaveJSRemoveFailedNode; + } +} diff --git a/src/panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-remove-failed-node.ts b/src/panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-remove-failed-node.ts new file mode 100644 index 0000000000..e64f0ee483 --- /dev/null +++ b/src/panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-remove-failed-node.ts @@ -0,0 +1,20 @@ +import { fireEvent } from "../../../../../common/dom/fire_event"; + +export interface ZWaveJSRemoveFailedNodeDialogParams { + entry_id: string; + node_id: number; +} + +export const loadRemoveFailedNodeDialog = () => + import("./dialog-zwave_js-remove-failed-node"); + +export const showZWaveJSRemoveFailedNodeDialog = ( + element: HTMLElement, + removeFailedNodeDialogParams: ZWaveJSRemoveFailedNodeDialogParams +): void => { + fireEvent(element, "show-dialog", { + dialogTag: "dialog-zwave_js-remove-failed-node", + dialogImport: loadRemoveFailedNodeDialog, + dialogParams: removeFailedNodeDialogParams, + }); +}; diff --git a/src/translations/en.json b/src/translations/en.json index 39ebb12067..13d6484223 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2691,7 +2691,8 @@ "node_ready": "Node Ready", "device_config": "Configure Device", "reinterview_device": "Re-interview Device", - "heal_node": "Heal Node" + "heal_node": "Heal Node", + "remove_failed": "Remove Failed Device" }, "node_config": { "header": "Z-Wave Device Configuration", @@ -2744,6 +2745,14 @@ "exclusion_failed": "The node could not be removed. Please check the logs for more information.", "exclusion_finished": "Node {id} has been removed from your Z-Wave network." }, + "remove_failed_node": { + "title": "Remove a Failed Z-Wave Device", + "introduction": "Remove a failed device from your Z-Wave network. Use this if you are unable to exclude a device normally because it is broken.", + "remove_device": "Remove Device", + "in_progress": "The device removal is in progress.", + "removal_finished": "Node {id} has been removed from your Z-Wave network.", + "removal_failed": "The device could not be removed from your Z-Wave network." + }, "reinterview_node": { "title": "Re-interview a Z-Wave Device", "introduction": "Re-interview a device on your Z-Wave network. Use this feature if your device has missing or incorrect functionality.", From 1d7007584c05c7cb9438fd2dff41b4ba3ae6f5ea Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 28 Jul 2021 17:38:04 +0200 Subject: [PATCH 17/43] Use currency from core config (#9628) --- src/data/energy.ts | 1 - .../components/ha-energy-grid-settings.ts | 4 -- .../dialog-energy-grid-flow-settings.ts | 2 +- .../energy/dialogs/show-dialogs-energy.ts | 3 -- src/panels/config/energy/ha-config-energy.ts | 41 +------------------ .../energy/cards/energy-setup-wizard-card.ts | 1 - .../energy/hui-energy-costs-table-card.ts | 16 ++++++-- .../cards/energy/hui-energy-summary-card.ts | 2 +- 8 files changed, 17 insertions(+), 53 deletions(-) diff --git a/src/data/energy.ts b/src/data/energy.ts index 4b8536424c..216bf566ff 100644 --- a/src/data/energy.ts +++ b/src/data/energy.ts @@ -85,7 +85,6 @@ type EnergySource = | GridSourceTypeEnergyPreference; export interface EnergyPreferences { - currency: string; energy_sources: EnergySource[]; device_consumption: DeviceConsumptionEnergyPreference[]; } diff --git a/src/panels/config/energy/components/ha-energy-grid-settings.ts b/src/panels/config/energy/components/ha-energy-grid-settings.ts index b919a6d60d..15574cb38a 100644 --- a/src/panels/config/energy/components/ha-energy-grid-settings.ts +++ b/src/panels/config/energy/components/ha-energy-grid-settings.ts @@ -201,7 +201,6 @@ export class EnergyGridSettings extends LitElement { private _addFromSource() { showEnergySettingsGridFlowFromDialog(this, { - currency: this.preferences.currency, saveCallback: async (flow) => { let preferences: EnergyPreferences; const gridSource = this.preferences.energy_sources.find( @@ -236,7 +235,6 @@ export class EnergyGridSettings extends LitElement { private _addToSource() { showEnergySettingsGridFlowToDialog(this, { - currency: this.preferences.currency, saveCallback: async (flow) => { let preferences: EnergyPreferences; const gridSource = this.preferences.energy_sources.find( @@ -273,7 +271,6 @@ export class EnergyGridSettings extends LitElement { const origSource: FlowFromGridSourceEnergyPreference = ev.currentTarget.closest(".row").source; showEnergySettingsGridFlowFromDialog(this, { - currency: this.preferences.currency, source: { ...origSource }, saveCallback: async (source) => { const flowFrom = energySourcesByType(this.preferences).grid![0] @@ -301,7 +298,6 @@ export class EnergyGridSettings extends LitElement { const origSource: FlowToGridSourceEnergyPreference = ev.currentTarget.closest(".row").source; showEnergySettingsGridFlowToDialog(this, { - currency: this.preferences.currency, source: { ...origSource }, saveCallback: async (source) => { const flowTo = energySourcesByType(this.preferences).grid![0].flow_to; diff --git a/src/panels/config/energy/dialogs/dialog-energy-grid-flow-settings.ts b/src/panels/config/energy/dialogs/dialog-energy-grid-flow-settings.ts index 607e3384fb..c7fb18e36b 100644 --- a/src/panels/config/energy/dialogs/dialog-energy-grid-flow-settings.ts +++ b/src/panels/config/energy/dialogs/dialog-energy-grid-flow-settings.ts @@ -203,7 +203,7 @@ export class DialogEnergyGridFlowSettings ${this.hass.localize( `ui.panel.config.energy.grid.flow_dialog.${this._params.direction}.cost_number_suffix`, - { currency: this._params.currency } + { currency: this.hass.config.currency } )} ` diff --git a/src/panels/config/energy/dialogs/show-dialogs-energy.ts b/src/panels/config/energy/dialogs/show-dialogs-energy.ts index 62f9d4a48d..f471cce7a7 100644 --- a/src/panels/config/energy/dialogs/show-dialogs-energy.ts +++ b/src/panels/config/energy/dialogs/show-dialogs-energy.ts @@ -10,7 +10,6 @@ export interface EnergySettingsGridFlowDialogParams { source?: | FlowFromGridSourceEnergyPreference | FlowToGridSourceEnergyPreference; - currency: string; direction: "from" | "to"; saveCallback: ( source: @@ -21,13 +20,11 @@ export interface EnergySettingsGridFlowDialogParams { export interface EnergySettingsGridFlowFromDialogParams { source?: FlowFromGridSourceEnergyPreference; - currency: string; saveCallback: (source: FlowFromGridSourceEnergyPreference) => Promise; } export interface EnergySettingsGridFlowToDialogParams { source?: FlowToGridSourceEnergyPreference; - currency: string; saveCallback: (source: FlowToGridSourceEnergyPreference) => Promise; } diff --git a/src/panels/config/energy/ha-config-energy.ts b/src/panels/config/energy/ha-config-energy.ts index 3b81c275ca..d55210dcd3 100644 --- a/src/panels/config/energy/ha-config-energy.ts +++ b/src/panels/config/energy/ha-config-energy.ts @@ -1,22 +1,15 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; - import "../../../components/ha-svg-icon"; -import { - EnergyPreferences, - getEnergyPreferences, - saveEnergyPreferences, -} from "../../../data/energy"; - +import { EnergyPreferences, getEnergyPreferences } from "../../../data/energy"; import "../../../layouts/hass-loading-screen"; import "../../../layouts/hass-tabs-subpage"; import { haStyle } from "../../../resources/styles"; import type { HomeAssistant, Route } from "../../../types"; import { configSections } from "../ha-panel-config"; +import "./components/ha-energy-device-settings"; import "./components/ha-energy-grid-settings"; import "./components/ha-energy-solar-settings"; -import "./components/ha-energy-device-settings"; -import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; const INITIAL_CONFIG = { currency: "€", @@ -72,18 +65,6 @@ class HaConfigEnergy extends LitElement { .route=${this.route} .tabs=${configSections.experiences} > - -
- - - - Save -
-
- ${this._config!.prefs.currency} ${cost.toFixed(2)} + ${formatNumber(cost, this.hass.locale, { + style: "currency", + currency: this.hass.config.currency!, + })} `; })} @@ -162,7 +166,10 @@ export class HuiEnergyCostsTableCard - ${this._config!.prefs.currency} ${cost.toFixed(2)} + ${formatNumber(cost, this.hass.locale, { + style: "currency", + currency: this.hass.config.currency!, + })} `; })} @@ -172,7 +179,10 @@ export class HuiEnergyCostsTableCard ${round(totalEnergy)} kWh - ${this._config!.prefs.currency} ${totalCost.toFixed(2)} + ${formatNumber(totalCost, this.hass.locale, { + style: "currency", + currency: this.hass.config.currency!, + })} diff --git a/src/panels/lovelace/cards/energy/hui-energy-summary-card.ts b/src/panels/lovelace/cards/energy/hui-energy-summary-card.ts index 9fd7dd20e2..60179e94f0 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-summary-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-summary-card.ts @@ -171,7 +171,7 @@ class HuiEnergySummaryCard extends LitElement implements LovelaceCard { types .grid![0].flow_from.map((flow) => flow.stat_cost) .filter(Boolean) as string[], - prefs.currency + this.hass.config.currency! )}
From f8dd1795bc306d684e7144e764179ef66c757e39 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 28 Jul 2021 17:39:12 +0200 Subject: [PATCH 18/43] Add animation to distribution (#9625) --- .../energy/hui-energy-devices-graph-card.ts | 2 +- .../energy/hui-energy-distribution-card.ts | 108 +++++++++++++++--- .../energy/hui-energy-solar-graph-card.ts | 2 +- .../energy/hui-energy-summary-graph-card.ts | 2 +- 4 files changed, 92 insertions(+), 22 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 66e4a75518..cfbdd4f845 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 @@ -138,7 +138,7 @@ export class HuiEnergyDevicesGraphCard }, }, }, - elements: { bar: { borderWidth: 1.5 } }, + elements: { bar: { borderWidth: 1.5, borderRadius: 4 } }, plugins: { tooltip: { mode: "nearest", 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 a75ad559cf..9debef5f18 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts @@ -276,28 +276,84 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard { - ${productionReturnedToGrid && hasSolarProduction + ${hasReturnToGrid && hasSolarProduction ? svg`` + id="return" + class="return" + d="M47,0 v15 c0,40 -10,35 -30,35 h-20" + vector-effect="non-scaling-stroke" + > ` : ""} - ${totalSolarProduction + ${hasSolarProduction ? svg`` : ""} - ${totalGridConsumption - ? svg`` + > + ${productionReturnedToGrid && hasSolarProduction + ? svg` + + + + ` + : ""} + ${totalSolarProduction + ? svg` + + + + + ` + : ""} + ${totalGridConsumption + ? svg` + + + + ` : ""} @@ -445,9 +501,6 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard { width: 100%; height: 100%; } - .circle svg circle { - animation: rotate-in 0.2s ease-in; - } .low-carbon line { stroke: #0f9d58; } @@ -460,17 +513,26 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard { .solar .circle { border-color: #ff9800; } - path.solar, - circle.solar { + circle.solar, + path.solar { stroke: #ff9800; } + circle.solar { + stroke-width: 4; + fill: #ff9800; + } circle.low-carbon { stroke: #0f9d58; + fill: #0f9d58; } - circle.return, - path.return { + path.return, + circle.return { stroke: #673ab7; } + circle.return { + stroke-width: 4; + fill: #673ab7; + } .return { color: #673ab7; } @@ -484,12 +546,20 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard { path.grid { stroke: #126a9a; } + circle.grid { + stroke-width: 4; + fill: #126a9a; + } .home .circle { border: none; } .home .circle.border { border-color: var(--primary-color); } + .circle svg circle { + animation: rotate-in 0.2s ease-in; + fill: none; + } @keyframes rotate-in { from { stroke-dashoffset: 0; 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 6e3c756e47..3cb8500e10 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 @@ -207,7 +207,7 @@ export class HuiEnergySolarGraphCard tension: 0.3, borderWidth: 1.5, }, - bar: { borderWidth: 1.5 }, + bar: { borderWidth: 1.5, borderRadius: 4 }, point: { hitRadius: 5, }, diff --git a/src/panels/lovelace/cards/energy/hui-energy-summary-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-summary-graph-card.ts index 7e98facdac..75b95360c1 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-summary-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-summary-graph-card.ts @@ -223,7 +223,7 @@ export class HuiEnergySummaryGraphCard mode: "nearest", }, elements: { - bar: { borderWidth: 1.5 }, + bar: { borderWidth: 1.5, borderRadius: 4 }, point: { hitRadius: 5, }, From 0d9d0aa18b0b671ba3a51c47b581e3f3f747c5df Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 28 Jul 2021 17:40:43 +0200 Subject: [PATCH 19/43] Add legend to electricity graph, fix text color in table (#9629) --- .../energy/strategies/energy-strategy.ts | 2 +- .../energy/hui-energy-costs-table-card.ts | 12 ++- .../energy/hui-energy-solar-graph-card.ts | 16 +-- .../energy/hui-energy-summary-graph-card.ts | 101 +++++++++++++++--- 4 files changed, 104 insertions(+), 27 deletions(-) diff --git a/src/panels/energy/strategies/energy-strategy.ts b/src/panels/energy/strategies/energy-strategy.ts index 0f63492569..ffd6f166d4 100644 --- a/src/panels/energy/strategies/energy-strategy.ts +++ b/src/panels/energy/strategies/energy-strategy.ts @@ -49,7 +49,7 @@ export class EnergyStrategy { // Only include if we have a grid source. if (hasGrid) { view.cards!.push({ - title: "Electricity", + title: "Energy usage", type: "energy-summary-graph", prefs: energyPrefs, }); diff --git a/src/panels/lovelace/cards/energy/hui-energy-costs-table-card.ts b/src/panels/lovelace/cards/energy/hui-energy-costs-table-card.ts index e0ecb05ff2..413288bc59 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-costs-table-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-costs-table-card.ts @@ -238,10 +238,20 @@ export class HuiEnergyCostsTableCard width: 100%; border: 0; } + .mdc-data-table__header-cell, + .mdc-data-table__cell { + color: var(--primary-text-color); + border-bottom-color: var(--divider-color); + } + .mdc-data-table__row:not(.mdc-data-table__row--selected):hover { + background-color: rgba(var(--rgb-primary-text-color), 0.04); + } .total { - background-color: var(--primary-background-color); --mdc-typography-body2-font-weight: 500; } + .total .mdc-data-table__cell { + border-top: 1px solid var(--divider-color); + } ha-card { height: 100%; } 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 3cb8500e10..35488c331d 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 @@ -45,7 +45,9 @@ export class HuiEnergySolarGraphCard @state() private _data?: Statistics; - @state() private _chartData?: ChartData; + @state() private _chartData: ChartData = { + datasets: [], + }; @state() private _forecasts?: Record; @@ -123,13 +125,11 @@ export class HuiEnergySolarGraphCard "has-header": !!this._config.title, })}" > - ${this._chartData - ? html`` - : ""} +
`; diff --git a/src/panels/lovelace/cards/energy/hui-energy-summary-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-summary-graph-card.ts index 75b95360c1..2e3eb96354 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-summary-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-summary-graph-card.ts @@ -1,3 +1,4 @@ +import { ChartData, ChartDataset, ChartOptions } from "chart.js"; import { css, CSSResultGroup, @@ -8,12 +9,7 @@ import { } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; -import "../../../../components/ha-card"; -import { ChartData, ChartDataset, ChartOptions } from "chart.js"; -import { HomeAssistant } from "../../../../types"; -import { LovelaceCard } from "../../types"; -import { EnergySummaryGraphCardConfig } from "../types"; -import { fetchStatistics, Statistics } from "../../../../data/history"; +import { styleMap } from "lit/directives/style-map"; import { hex2rgb, lab2rgb, @@ -22,8 +18,14 @@ import { } from "../../../../common/color/convert-color"; import { labDarken } from "../../../../common/color/lab"; import { computeStateName } from "../../../../common/entity/compute_state_name"; -import "../../../../components/chart/ha-chart-base"; import { round } from "../../../../common/number/round"; +import { formatNumber } from "../../../../common/string/format_number"; +import "../../../../components/chart/ha-chart-base"; +import "../../../../components/ha-card"; +import { fetchStatistics, Statistics } from "../../../../data/history"; +import { HomeAssistant } from "../../../../types"; +import { LovelaceCard } from "../../types"; +import { EnergySummaryGraphCardConfig } from "../types"; const NEGATIVE = ["to_grid"]; const COLORS = { @@ -43,7 +45,9 @@ export class HuiEnergySummaryGraphCard @state() private _data?: Statistics; - @state() private _chartData?: ChartData; + @state() private _chartData: ChartData = { + datasets: [], + }; @state() private _chartOptions?: ChartOptions; @@ -111,19 +115,48 @@ export class HuiEnergySummaryGraphCard } return html` - + +

${this._config.title}

- ${this._chartData - ? html`` - : ""} +
+
    + ${this._chartData.datasets.map( + (dataset) => html`
  • +
    +
    + ${dataset.label} +
    + ${formatNumber( + Math.abs( + dataset.data.reduce( + (total, point) => total + (point as any).y, + 0 + ) as number + ), + this.hass.locale + )} + kWh +
  • ` + )} +
+
+
`; @@ -337,7 +370,7 @@ export class HuiEnergySummaryGraphCard totalStats[stat.start] = stat.start in totalStats ? totalStats[stat.start] + val : val; } - if (add) { + if (add && !(stat.start in set)) { set[stat.start] = val; } prevValue = stat.sum; @@ -428,12 +461,46 @@ export class HuiEnergySummaryGraphCard ha-card { height: 100%; } + .card-header { + padding-bottom: 0; + } .content { padding: 16px; } .has-header { padding-top: 0; } + .chartLegend ul { + padding-left: 20px; + } + .chartLegend li { + padding: 2px 8px; + display: flex; + justify-content: space-between; + align-items: center; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + box-sizing: border-box; + color: var(--secondary-text-color); + } + .chartLegend li > div { + display: flex; + align-items: center; + } + .chartLegend .bullet { + border-width: 1px; + border-style: solid; + border-radius: 4px; + display: inline-block; + height: 16px; + margin-right: 6px; + width: 32px; + box-sizing: border-box; + } + .value { + font-weight: 300; + } `; } } From 2e04a55d5c7dc8a624a3c0096f2b55c76953e12a Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 28 Jul 2021 17:41:18 +0200 Subject: [PATCH 20/43] Add statistics card to card picker (#9631) Co-authored-by: Zack Barett --- .../cards/hui-statistics-graph-card.ts | 9 ++ .../hui-history-graph-card-editor.ts | 4 - .../hui-statistics-graph-card-editor.ts | 130 ++++++++++++++++++ src/panels/lovelace/editor/lovelace-cards.ts | 4 + src/translations/en.json | 5 + 5 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 src/panels/lovelace/editor/config-elements/hui-statistics-graph-card-editor.ts diff --git a/src/panels/lovelace/cards/hui-statistics-graph-card.ts b/src/panels/lovelace/cards/hui-statistics-graph-card.ts index 5ff105a67c..fd44114d3e 100644 --- a/src/panels/lovelace/cards/hui-statistics-graph-card.ts +++ b/src/panels/lovelace/cards/hui-statistics-graph-card.ts @@ -19,6 +19,15 @@ import { fetchStatistics, Statistics } from "../../../data/history"; @customElement("hui-statistics-graph-card") export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard { + public static async getConfigElement() { + await import("../editor/config-elements/hui-statistics-graph-card-editor"); + return document.createElement("hui-statistics-graph-card-editor"); + } + + public static getStubConfig(): StatisticsGraphCardConfig { + return { type: "statistics-graph", entities: [] }; + } + @property({ attribute: false }) public hass?: HomeAssistant; @state() private _statistics?: Statistics; diff --git a/src/panels/lovelace/editor/config-elements/hui-history-graph-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-history-graph-card-editor.ts index 7df5df1faa..405277dd74 100644 --- a/src/panels/lovelace/editor/config-elements/hui-history-graph-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-history-graph-card-editor.ts @@ -38,10 +38,6 @@ export class HuiHistoryGraphCardEditor this._configEntities = processEditorEntities(config.entities); } - get _entity(): string { - return this._config!.entity || ""; - } - get _title(): string { return this._config!.title || ""; } diff --git a/src/panels/lovelace/editor/config-elements/hui-statistics-graph-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-statistics-graph-card-editor.ts new file mode 100644 index 0000000000..831177442b --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/hui-statistics-graph-card-editor.ts @@ -0,0 +1,130 @@ +import "@polymer/paper-input/paper-input"; +import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { array, assert, number, object, optional, string } from "superstruct"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { HomeAssistant } from "../../../../types"; +import { StatisticsGraphCardConfig } from "../../cards/types"; +import { LovelaceCardEditor } from "../../types"; +import { entitiesConfigStruct } from "../structs/entities-struct"; +import { EditorTarget } from "../types"; +import { configElementStyle } from "./config-elements-style"; +import "../../../../components/entity/ha-statistics-picker"; +import { processConfigEntities } from "../../common/process-config-entities"; + +const cardConfigStruct = object({ + type: string(), + entities: array(entitiesConfigStruct), + title: optional(string()), + days_to_show: optional(number()), +}); + +@customElement("hui-statistics-graph-card-editor") +export class HuiStatisticsGraphCardEditor + extends LitElement + implements LovelaceCardEditor +{ + @property({ attribute: false }) public hass?: HomeAssistant; + + @state() private _config?: StatisticsGraphCardConfig; + + @state() private _configEntities?: string[]; + + public setConfig(config: StatisticsGraphCardConfig): void { + assert(config, cardConfigStruct); + this._config = config; + this._configEntities = config.entities + ? processConfigEntities(config.entities).map((cfg) => cfg.entity) + : []; + } + + get _title(): string { + return this._config!.title || ""; + } + + get _days_to_show(): number { + return this._config!.days_to_show || 30; + } + + protected render(): TemplateResult { + if (!this.hass || !this._config) { + return html``; + } + + return html` +
+ +
+ +
+ +
+ `; + } + + private _valueChanged(ev: CustomEvent): void { + if (!this._config || !this.hass) { + return; + } + const target = ev.target! as EditorTarget; + + const newValue = ev.detail?.value || target.value; + + if (this[`_${target.configValue}`] === newValue) { + return; + } + + if (newValue === "") { + this._config = { ...this._config }; + delete this._config[target.configValue!]; + } else { + let value: any = newValue; + if (target.type === "number") { + value = Number(value); + } + this._config = { + ...this._config, + [target.configValue!]: value, + }; + } + + fireEvent(this, "config-changed", { config: this._config }); + } + + static get styles(): CSSResultGroup { + return configElementStyle; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-statistics-graph-card-editor": HuiStatisticsGraphCardEditor; + } +} diff --git a/src/panels/lovelace/editor/lovelace-cards.ts b/src/panels/lovelace/editor/lovelace-cards.ts index 6e29ec6dfd..2a9beb0371 100644 --- a/src/panels/lovelace/editor/lovelace-cards.ts +++ b/src/panels/lovelace/editor/lovelace-cards.ts @@ -33,6 +33,10 @@ export const coreCards: Card[] = [ type: "history-graph", showElement: true, }, + { + type: "statistics-graph", + showElement: false, + }, { type: "humidifier", showElement: true, diff --git a/src/translations/en.json b/src/translations/en.json index 13d6484223..21b8d52461 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3109,6 +3109,10 @@ "name": "History Graph", "description": "The History Graph card allows you to display a graph for each of the entities listed." }, + "statistics-graph": { + "name": "Statistics Graph", + "description": "The Statistics Graph card allows you to display a graph of the statistics for each of the entities listed." + }, "horizontal-stack": { "name": "Horizontal Stack", "description": "The Horizontal Stack card allows you to stack together multiple cards, so they always sit next to each other in the space of one column." @@ -3135,6 +3139,7 @@ "entity": "Entity", "hold_action": "Hold Action", "hours_to_show": "Hours to Show", + "days_to_show": "Days to Show", "icon": "Icon", "icon_height": "Icon Height", "image": "Image Path", From 4d330fba8a1465393a0aa1db8808ec57dd69a289 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 28 Jul 2021 09:20:06 -0700 Subject: [PATCH 21/43] Use historic CO2 data into account (#9626) * Use historic CO2 data into account * Also add to gauge * Apply suggestions from code review * Format Co-authored-by: Bram Kragten --- src/data/history.ts | 125 ++++++++++++++++++ .../hui-energy-carbon-consumed-gauge-card.ts | 40 ++++-- .../energy/hui-energy-distribution-card.ts | 101 +++++++------- test-mocha/data/history.spec.ts | 77 +++++++++++ 4 files changed, 278 insertions(+), 65 deletions(-) create mode 100644 test-mocha/data/history.spec.ts diff --git a/src/data/history.ts b/src/data/history.ts index a111a196b7..481ddf7792 100644 --- a/src/data/history.ts +++ b/src/data/history.ts @@ -345,3 +345,128 @@ export const statisticsHaveType = ( stats: StatisticValue[], type: StatisticType ) => stats.some((stat) => stat[type] !== null); + +/** + * Get the earliest start from a list of statistics. + */ +const getMinStatisticStart = (stats: StatisticValue[][]): string | null => { + let earliestString: string | null = null; + let earliestTime: Date | null = null; + + for (const stat of stats) { + if (stat.length === 0) { + continue; + } + const curTime = new Date(stat[0].start); + + if (earliestString === null) { + earliestString = stat[0].start; + earliestTime = curTime; + continue; + } + + if (curTime < earliestTime!) { + earliestString = stat[0].start; + earliestTime = curTime; + } + } + + return earliestString; +}; + +// Merge multiple sum statistics into one +const mergeSumStatistics = (stats: StatisticValue[][]) => { + const result: { start: string; sum: number }[] = []; + + const statsCopy: StatisticValue[][] = stats.map((stat) => [...stat]); + + while (statsCopy.some((stat) => stat.length > 0)) { + const earliestStart = getMinStatisticStart(statsCopy)!; + + let sum = 0; + + for (const stat of statsCopy) { + if (stat.length === 0) { + continue; + } + if (stat[0].start !== earliestStart) { + continue; + } + const statVal = stat.shift()!; + if (!statVal.sum) { + continue; + } + sum += statVal.sum; + } + + result.push({ + start: earliestStart, + sum, + }); + } + + return result; +}; + +/** + * Get the growth of a statistic over the given period while applying a + * per-period percentage. + */ +export const calculateStatisticsSumGrowthWithPercentage = ( + percentageStat: StatisticValue[], + sumStats: StatisticValue[][] +): number | null => { + let sum: number | null = null; + + if (sumStats.length === 0) { + return null; + } + + const sumStatsToProcess = mergeSumStatistics(sumStats); + const percentageStatToProcess = [...percentageStat]; + + let lastSum: number | null = null; + + // pre-populate lastSum with last sum statistic _before_ the first percentage statistic + for (const stat of sumStatsToProcess) { + if (new Date(stat.start) >= new Date(percentageStat[0].start)) { + break; + } + lastSum = stat.sum; + } + + while (percentageStatToProcess.length > 0) { + if (!sumStatsToProcess.length) { + return sum; + } + + // If they are not equal, pop the value that is earlier in time + if (sumStatsToProcess[0].start !== percentageStatToProcess[0].start) { + if ( + new Date(sumStatsToProcess[0].start) < + new Date(percentageStatToProcess[0].start) + ) { + sumStatsToProcess.shift(); + } else { + percentageStatToProcess.shift(); + } + continue; + } + + const sumStatValue = sumStatsToProcess.shift()!; + const percentageStatValue = percentageStatToProcess.shift()!; + + if (lastSum !== null) { + const sumGrowth = sumStatValue.sum! - lastSum; + if (sum === null) { + sum = sumGrowth * (percentageStatValue.mean! / 100); + } else { + sum += sumGrowth * (percentageStatValue.mean! / 100); + } + } + + lastSum = sumStatValue.sum; + } + + return sum; +}; diff --git a/src/panels/lovelace/cards/energy/hui-energy-carbon-consumed-gauge-card.ts b/src/panels/lovelace/cards/energy/hui-energy-carbon-consumed-gauge-card.ts index 4885489c47..a54f12c88c 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-carbon-consumed-gauge-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-carbon-consumed-gauge-card.ts @@ -10,6 +10,7 @@ import { energySourcesByType } from "../../../../data/energy"; import { subscribeEntityRegistry } from "../../../../data/entity_registry"; import { calculateStatisticsSumGrowth, + calculateStatisticsSumGrowthWithPercentage, fetchStatistics, Statistics, } from "../../../../data/history"; @@ -42,7 +43,6 @@ class HuiEnergyCarbonGaugeCard extends LitElement implements LovelaceCard { if (!this.hasUpdated) { this._getStatistics(); - this._fetchCO2SignalEntity(); } } @@ -51,12 +51,12 @@ class HuiEnergyCarbonGaugeCard extends LitElement implements LovelaceCard { return html``; } - if (!this._stats || this._co2SignalEntity === undefined) { - return html`Loading...`; + if (this._co2SignalEntity === null) { + return html``; } - if (!this._co2SignalEntity) { - return html``; + if (!this._stats || !this._co2SignalEntity) { + return html`Loading...`; } const co2State = this.hass.states[this._co2SignalEntity]; @@ -67,12 +67,6 @@ class HuiEnergyCarbonGaugeCard extends LitElement implements LovelaceCard { `; } - const co2percentage = Number(co2State.state); - - if (isNaN(co2percentage)) { - return html``; - } - const prefs = this._config!.prefs; const types = energySourcesByType(prefs); @@ -83,7 +77,17 @@ class HuiEnergyCarbonGaugeCard extends LitElement implements LovelaceCard { let value: number | undefined; - if (totalGridConsumption) { + if (this._co2SignalEntity in this._stats && totalGridConsumption) { + const highCarbonEnergy = + calculateStatisticsSumGrowthWithPercentage( + this._stats[this._co2SignalEntity], + types + .grid![0].flow_from.map( + (flow) => this._stats![flow.stat_energy_from] + ) + .filter(Boolean) + ) || 0; + const totalSolarProduction = types.solar ? calculateStatisticsSumGrowth( this._stats, @@ -96,8 +100,6 @@ class HuiEnergyCarbonGaugeCard extends LitElement implements LovelaceCard { types.grid![0].flow_to.map((flow) => flow.stat_energy_to) ); - const highCarbonEnergy = (totalGridConsumption * co2percentage) / 100; - const totalEnergyConsumed = totalGridConsumption + (totalSolarProduction || 0) - @@ -171,6 +173,12 @@ class HuiEnergyCarbonGaugeCard extends LitElement implements LovelaceCard { } private async _getStatistics(): Promise { + await this._fetchCO2SignalEntity(); + + if (this._co2SignalEntity === null) { + return; + } + const startDate = new Date(); startDate.setHours(0, 0, 0, 0); startDate.setTime(startDate.getTime() - 1000 * 60 * 60); // subtract 1 hour to get a startpoint @@ -192,6 +200,10 @@ class HuiEnergyCarbonGaugeCard extends LitElement implements LovelaceCard { } } + if (this._co2SignalEntity) { + statistics.push(this._co2SignalEntity); + } + this._stats = await fetchStatistics( this.hass!, startDate, 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 9debef5f18..78f1af3b55 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts @@ -18,6 +18,7 @@ import { energySourcesByType } from "../../../../data/energy"; import { subscribeEntityRegistry } from "../../../../data/entity_registry"; import { calculateStatisticsSumGrowth, + calculateStatisticsSumGrowthWithPercentage, fetchStatistics, Statistics, } from "../../../../data/history"; @@ -52,11 +53,9 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard { if (!this._fetching && !this._stats) { this._fetching = true; - Promise.all([this._getStatistics(), this._fetchCO2SignalEntity()]).then( - () => { - this._fetching = false; - } - ); + this._getStatistics().then(() => { + this._fetching = false; + }); } } @@ -102,20 +101,6 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard { ); } - // total consumption = consumption_from_grid + solar_production - return_to_grid - - let co2percentage: number | undefined; - - if (this._co2SignalEntity) { - const co2State = this.hass.states[this._co2SignalEntity]; - if (co2State) { - co2percentage = Number(co2State.state); - if (isNaN(co2percentage)) { - co2percentage = undefined; - } - } - } - const totalConsumption = totalGridConsumption + (totalSolarProduction || 0) - @@ -133,22 +118,33 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard { let homeLowCarbonCircumference: number | undefined; let homeHighCarbonCircumference: number | undefined; - if (co2percentage !== undefined) { - const gridPctHighCarbon = co2percentage / 100; - lowCarbonConsumption = - totalGridConsumption - totalGridConsumption * gridPctHighCarbon; + if (this._co2SignalEntity && this._co2SignalEntity in this._stats) { + // Calculate high carbon consumption + const highCarbonConsumption = calculateStatisticsSumGrowthWithPercentage( + this._stats[this._co2SignalEntity], + types + .grid![0].flow_from.map((flow) => this._stats![flow.stat_energy_from]) + .filter(Boolean) + ); - const homePctGridHighCarbon = - (gridPctHighCarbon * totalGridConsumption) / totalConsumption; + if (highCarbonConsumption !== null) { + const gridPctHighCarbon = highCarbonConsumption / totalConsumption; - homeHighCarbonCircumference = - CIRCLE_CIRCUMFERENCE * homePctGridHighCarbon; + lowCarbonConsumption = + totalGridConsumption - totalGridConsumption * gridPctHighCarbon; - homeLowCarbonCircumference = - CIRCLE_CIRCUMFERENCE - - (homeSolarCircumference || 0) - - homeHighCarbonCircumference; + const homePctGridHighCarbon = + (gridPctHighCarbon * totalGridConsumption) / totalConsumption; + + homeHighCarbonCircumference = + CIRCLE_CIRCUMFERENCE * homePctGridHighCarbon; + + homeLowCarbonCircumference = + CIRCLE_CIRCUMFERENCE - + (homeSolarCircumference || 0) - + homeHighCarbonCircumference; + } } return html` @@ -362,7 +358,7 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard { `; } - private async _fetchCO2SignalEntity() { + private async _getStatistics(): Promise { const [configEntries, entityRegistryEntries] = await Promise.all([ getConfigEntries(this.hass), subscribeOne(this.hass.connection, subscribeEntityRegistry), @@ -372,32 +368,35 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard { (entry) => entry.domain === "co2signal" ); - if (!co2ConfigEntry) { - return; + this._co2SignalEntity = undefined; + + if (co2ConfigEntry) { + for (const entry of entityRegistryEntries) { + if (entry.config_entry_id !== co2ConfigEntry.entry_id) { + continue; + } + + // The integration offers 2 entities. We want the % one. + const co2State = this.hass.states[entry.entity_id]; + if (!co2State || co2State.attributes.unit_of_measurement !== "%") { + continue; + } + + this._co2SignalEntity = co2State.entity_id; + break; + } } - for (const entry of entityRegistryEntries) { - if (entry.config_entry_id !== co2ConfigEntry.entry_id) { - continue; - } - - // The integration offers 2 entities. We want the % one. - const co2State = this.hass.states[entry.entity_id]; - if (!co2State || co2State.attributes.unit_of_measurement !== "%") { - continue; - } - - this._co2SignalEntity = co2State.entity_id; - break; - } - } - - private async _getStatistics(): Promise { const startDate = new Date(); startDate.setHours(0, 0, 0, 0); startDate.setTime(startDate.getTime() - 1000 * 60 * 60); // subtract 1 hour to get a startpoint const statistics: string[] = []; + + if (this._co2SignalEntity !== undefined) { + statistics.push(this._co2SignalEntity); + } + const prefs = this._config!.prefs; for (const source of prefs.energy_sources) { if (source.type === "solar") { diff --git a/test-mocha/data/history.spec.ts b/test-mocha/data/history.spec.ts new file mode 100644 index 0000000000..8ba9efd651 --- /dev/null +++ b/test-mocha/data/history.spec.ts @@ -0,0 +1,77 @@ +import { assert } from "chai"; + +import { calculateStatisticsSumGrowthWithPercentage } from "../../src/data/history"; + +describe("calculateStatisticsSumGrowthWithPercentage", () => { + it("Returns null if not enough values", async () => { + assert.strictEqual( + calculateStatisticsSumGrowthWithPercentage([], []), + null + ); + }); + + it("Returns null if not enough values", async () => { + assert.strictEqual( + calculateStatisticsSumGrowthWithPercentage( + [ + { + statistic_id: "sensor.carbon_intensity", + start: "2021-07-28T05:00:00Z", + last_reset: null, + max: 75, + mean: 50, + min: 25, + sum: null, + state: null, + }, + { + statistic_id: "sensor.carbon_intensity", + start: "2021-07-28T07:00:00Z", + last_reset: null, + max: 100, + mean: 75, + min: 50, + sum: null, + state: null, + }, + ], + [ + [ + { + statistic_id: "sensor.peak_consumption", + start: "2021-07-28T04:00:00Z", + last_reset: null, + max: null, + mean: null, + min: null, + sum: 50, + state: null, + }, + { + statistic_id: "sensor.peak_consumption", + start: "2021-07-28T05:00:00Z", + last_reset: null, + max: null, + mean: null, + min: null, + sum: 100, + state: null, + }, + { + statistic_id: "sensor.peak_consumption", + start: "2021-07-28T07:00:00Z", + last_reset: null, + max: null, + mean: null, + min: null, + sum: 200, + state: null, + }, + ], + [], + ] + ), + 100 + ); + }); +}); From 521d5df0648e5218fbee3f409d9dfe9ebb9d5cc5 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 28 Jul 2021 09:20:31 -0700 Subject: [PATCH 22/43] Show solar forecast even if no prod stats available (#9632) --- .../lovelace/cards/energy/hui-energy-solar-graph-card.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) 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 35488c331d..47a8ffa69d 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 @@ -261,10 +261,6 @@ export class HuiEnergySolarGraphCard const datasets: ChartDataset<"bar">[] = []; let endTime: Date; - if (statisticsData.length === 0) { - return; - } - endTime = new Date( Math.max( ...statisticsData.map((stats) => @@ -273,7 +269,7 @@ export class HuiEnergySolarGraphCard ) ); - if (endTime > new Date()) { + if (!endTime || endTime > new Date()) { endTime = new Date(); } From 9c31b749d71ea5e426ed4ff9816dbc6b1cc3ddf4 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 28 Jul 2021 18:34:03 +0200 Subject: [PATCH 23/43] Format monetary sensor as currency (#9633) --- src/common/entity/compute_state_display.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/common/entity/compute_state_display.ts b/src/common/entity/compute_state_display.ts index c5eb00c2d7..cb31f5ec10 100644 --- a/src/common/entity/compute_state_display.ts +++ b/src/common/entity/compute_state_display.ts @@ -21,6 +21,16 @@ export const computeStateDisplay = ( } if (stateObj.attributes.unit_of_measurement) { + if (stateObj.attributes.device_class === "monetary") { + try { + return formatNumber(compareState, locale, { + style: "currency", + currency: stateObj.attributes.unit_of_measurement, + }); + } catch (_err) { + // fallback to default + } + } return `${formatNumber(compareState, locale)} ${ stateObj.attributes.unit_of_measurement }`; From f87d4a5ab6614c3b9f08930751a6a56d4138038e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 28 Jul 2021 10:46:51 -0700 Subject: [PATCH 24/43] If we have solar defined, always make sure sum is number so it shows placeholders (#9634) --- .../energy/hui-energy-distribution-card.ts | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) 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 78f1af3b55..960dbe752f 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts @@ -86,19 +86,21 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard { let totalSolarProduction: number | null = null; if (hasSolarProduction) { - totalSolarProduction = calculateStatisticsSumGrowth( - this._stats, - types.solar!.map((source) => source.stat_energy_from) - ); + totalSolarProduction = + calculateStatisticsSumGrowth( + this._stats, + types.solar!.map((source) => source.stat_energy_from) + ) || 0; } let productionReturnedToGrid: number | null = null; if (hasReturnToGrid) { - productionReturnedToGrid = calculateStatisticsSumGrowth( - this._stats, - types.grid![0].flow_to.map((flow) => flow.stat_energy_to) - ); + productionReturnedToGrid = + calculateStatisticsSumGrowth( + this._stats, + types.grid![0].flow_to.map((flow) => flow.stat_energy_to) + ) || 0; } const totalConsumption = @@ -191,7 +193,7 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard { : ""}${round(totalGridConsumption, 1)} kWh - ${productionReturnedToGrid + ${productionReturnedToGrid !== null ? html` Date: Wed, 28 Jul 2021 10:46:59 -0700 Subject: [PATCH 25/43] Remove the energy summary card since it's already in other cards (#9635) --- .../energy/strategies/energy-strategy.ts | 6 - .../cards/energy/hui-energy-summary-card.ts | 302 ------------------ .../create-element/create-card-element.ts | 1 - 3 files changed, 309 deletions(-) delete mode 100644 src/panels/lovelace/cards/energy/hui-energy-summary-card.ts diff --git a/src/panels/energy/strategies/energy-strategy.ts b/src/panels/energy/strategies/energy-strategy.ts index ffd6f166d4..787d1de38b 100644 --- a/src/panels/energy/strategies/energy-strategy.ts +++ b/src/panels/energy/strategies/energy-strategy.ts @@ -110,12 +110,6 @@ export class EnergyStrategy { }); } - view.cards!.push({ - type: "energy-summary", - prefs: energyPrefs, - view_layout: { position: "sidebar" }, - }); - return view; } } diff --git a/src/panels/lovelace/cards/energy/hui-energy-summary-card.ts b/src/panels/lovelace/cards/energy/hui-energy-summary-card.ts deleted file mode 100644 index 60179e94f0..0000000000 --- a/src/panels/lovelace/cards/energy/hui-energy-summary-card.ts +++ /dev/null @@ -1,302 +0,0 @@ -import { mdiCashMultiple, mdiSolarPower } from "@mdi/js"; -import { css, html, LitElement } from "lit"; -import { customElement, property, state } from "lit/decorators"; -import "../../../../components/ha-svg-icon"; -import { - energySourcesByType, - GridSourceTypeEnergyPreference, - SolarSourceTypeEnergyPreference, -} from "../../../../data/energy"; -import { - calculateStatisticSumGrowth, - fetchStatistics, - Statistics, -} from "../../../../data/history"; -import { HomeAssistant } from "../../../../types"; -import { LovelaceCard } from "../../types"; -import { EnergySummaryCardConfig } from "../types"; -import "../../../../components/ha-card"; - -const renderSumStatHelper = ( - data: Statistics, - stats: string[], - unit: string -) => { - let totalGrowth = 0; - - for (const stat of stats) { - if (!(stat in data)) { - return "stat missing"; - } - const statGrowth = calculateStatisticSumGrowth(data[stat]); - - if (statGrowth === null) { - return "incomplete data"; - } - - totalGrowth += statGrowth; - } - - return `${totalGrowth.toFixed(2)} ${unit}`; -}; - -@customElement("hui-energy-summary-card") -class HuiEnergySummaryCard extends LitElement implements LovelaceCard { - @property({ attribute: false }) public hass?: HomeAssistant; - - @state() private _config?: EnergySummaryCardConfig; - - @state() private _data?: Statistics; - - private _fetching = false; - - public setConfig(config: EnergySummaryCardConfig): void { - this._config = config; - } - - public getCardSize(): Promise | number { - return 3; - } - - public willUpdate(changedProps) { - super.willUpdate(changedProps); - - if (!this._fetching && !this._data) { - this._getStatistics(); - } - } - - protected render() { - if (!this._config || !this.hass) { - return html``; - } - - const prefs = this._config!.prefs; - const types = energySourcesByType(prefs); - - const hasConsumption = types.grid !== undefined; - const hasProduction = types.solar !== undefined; - const hasReturnToGrid = hasConsumption && types.grid![0].flow_to.length > 0; - const hasCost = - hasConsumption && - types.grid![0].flow_from.some((flow) => flow.stat_cost !== null); - - // total consumption = consumption_from_grid + solar_production - return_to_grid - - return html` - -
- ${!hasConsumption - ? "" - : html` -
- -
Total Consumption
-
- ${!this._data - ? "" - : renderSumStatHelper( - this._data, - types.grid![0].flow_from.map( - (flow) => flow.stat_energy_from - ), - "kWh" - )} -
-
- `} - ${!hasProduction - ? "" - : html` -
- -
Total Production
-
- ${!this._data - ? "" - : renderSumStatHelper( - this._data, - types.solar!.map((source) => source.stat_energy_from), - "kWh" - )} -
-
- `} - ${!hasReturnToGrid - ? "" - : html` -
- -
Production returned to grid
-
- ${!this._data - ? "" - : renderSumStatHelper( - this._data, - types.grid![0].flow_to.map( - (flow) => flow.stat_energy_to - ), - "kWh" - )} -
-
- `} - ${!hasReturnToGrid || !hasProduction - ? "" - : html` -
- -
Amount of produced power self used
-
- ${!this._data - ? "" - : this._renderSolarPowerConsumptionRatio( - types.solar![0], - types.grid![0] - )} -
-
- `} - ${!hasCost - ? "" - : html` -
- -
Total costs of today
-
- ${!this._data - ? "" - : renderSumStatHelper( - this._data, - types - .grid![0].flow_from.map((flow) => flow.stat_cost) - .filter(Boolean) as string[], - this.hass.config.currency! - )} -
-
- `} -
-
- `; - } - - // This is superduper temp. - private async _getStatistics(): Promise { - if (this._fetching) { - return; - } - const startDate = new Date(); - startDate.setHours(0, 0, 0, 0); - startDate.setTime(startDate.getTime() - 1000 * 60 * 60); // subtract 1 hour to get a startpoint - - this._fetching = true; - const statistics: string[] = []; - const prefs = this._config!.prefs; - for (const source of prefs.energy_sources) { - if (source.type === "solar") { - statistics.push(source.stat_energy_from); - // Use ws command to get solar forecast - - // if (source.stat_predicted_energy_from) { - // statistics.push(source.stat_predicted_energy_from); - // } - continue; - } - - // grid source - for (const flowFrom of source.flow_from) { - statistics.push(flowFrom.stat_energy_from); - if (flowFrom.stat_cost) { - statistics.push(flowFrom.stat_cost); - } - } - for (const flowTo of source.flow_to) { - statistics.push(flowTo.stat_energy_to); - } - } - - try { - this._data = await fetchStatistics( - this.hass!, - startDate, - undefined, - statistics - ); - } finally { - this._fetching = false; - } - } - - private _renderSolarPowerConsumptionRatio( - solarSource: SolarSourceTypeEnergyPreference, - gridSource: GridSourceTypeEnergyPreference - ) { - let returnToGrid = 0; - - for (const flowTo of gridSource.flow_to) { - if (!flowTo.stat_energy_to || !(flowTo.stat_energy_to in this._data!)) { - continue; - } - const flowReturned = calculateStatisticSumGrowth( - this._data![flowTo.stat_energy_to] - ); - if (flowReturned === null) { - return "incomplete return data"; - } - returnToGrid += flowReturned; - } - - if (!(solarSource.stat_energy_from in this._data!)) { - return "sun stat missing"; - } - - const production = calculateStatisticSumGrowth( - this._data![solarSource.stat_energy_from] - ); - - if (production === null) { - return "incomplete solar data"; - } - - if (production === 0) { - return "-"; - } - - const consumed = Math.max( - Math.min(((production - returnToGrid) / production) * 100, 100), - 0 - ); - - return `${consumed.toFixed(1)}%`; - } - - static styles = css` - .row { - display: flex; - align-items: center; - color: var(--primary-text-color); - } - ha-svg-icon { - padding: 8px; - color: var(--paper-item-icon-color); - } - div { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - .label { - flex: 1; - margin-left: 16px; - } - .data { - } - `; -} - -declare global { - interface HTMLElementTagNameMap { - "hui-energy-summary-card": HuiEnergySummaryCard; - } -} diff --git a/src/panels/lovelace/create-element/create-card-element.ts b/src/panels/lovelace/create-element/create-card-element.ts index 0524406b79..71cee8f6fd 100644 --- a/src/panels/lovelace/create-element/create-card-element.ts +++ b/src/panels/lovelace/create-element/create-card-element.ts @@ -35,7 +35,6 @@ const LAZY_LOAD_TYPES = { "alarm-panel": () => import("../cards/hui-alarm-panel-card"), error: () => import("../cards/hui-error-card"), "empty-state": () => import("../cards/hui-empty-state-card"), - "energy-summary": () => import("../cards/energy/hui-energy-summary-card"), "energy-summary-graph": () => import("../cards/energy/hui-energy-summary-graph-card"), "energy-solar-graph": () => From 9a928259549b255ae79fbdb412538109e31d62d2 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 28 Jul 2021 11:05:00 -0700 Subject: [PATCH 26/43] Move energy panel up in sidebar (#9636) * Move energy panel up in sidebar * Remove headers from wizard * Update text * Always show all configured devices * Make leaf clickable * Bump HAWS --- package.json | 2 +- src/components/ha-sidebar.ts | 7 +++--- .../components/ha-energy-device-settings.ts | 24 ++++++++++++++++--- .../components/ha-energy-grid-settings.ts | 20 +++++++++++++--- .../components/ha-energy-solar-settings.ts | 19 +++++++++++---- .../dialogs/dialog-energy-device-settings.ts | 1 - .../dialog-energy-grid-flow-settings.ts | 7 ++++-- .../dialogs/dialog-energy-solar-settings.ts | 4 +++- .../energy/cards/energy-setup-wizard-card.ts | 3 --- .../energy/hui-energy-devices-graph-card.ts | 21 ++++++++-------- .../energy/hui-energy-distribution-card.ts | 20 ++++++++++++++-- src/translations/en.json | 18 ++++++++------ yarn.lock | 10 ++++---- 13 files changed, 110 insertions(+), 46 deletions(-) diff --git a/package.json b/package.json index f6c7c0fcb7..1b7cb54886 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,7 @@ "fuse.js": "^6.0.0", "google-timezones-json": "^1.0.2", "hls.js": "^1.0.7", - "home-assistant-js-websocket": "^5.11.0", + "home-assistant-js-websocket": "^5.11.1", "idb-keyval": "^5.0.5", "intl-messageformat": "^9.6.16", "js-yaml": "^4.1.0", diff --git a/src/components/ha-sidebar.ts b/src/components/ha-sidebar.ts index 0a56b4df48..e234408f3e 100644 --- a/src/components/ha-sidebar.ts +++ b/src/components/ha-sidebar.ts @@ -53,9 +53,10 @@ const SHOW_AFTER_SPACER = ["config", "developer-tools", "hassio"]; const SUPPORT_SCROLL_IF_NEEDED = "scrollIntoViewIfNeeded" in document.body; const SORT_VALUE_URL_PATHS = { - map: 1, - logbook: 2, - history: 3, + energy: 1, + map: 2, + logbook: 3, + history: 4, "developer-tools": 9, hassio: 10, config: 11, diff --git a/src/panels/config/energy/components/ha-energy-device-settings.ts b/src/panels/config/energy/components/ha-energy-device-settings.ts index 2ec08d014a..92a6dda0b5 100644 --- a/src/panels/config/energy/components/ha-energy-device-settings.ts +++ b/src/panels/config/energy/components/ha-energy-device-settings.ts @@ -19,6 +19,7 @@ import { } from "../../../../dialogs/generic/show-dialog-box"; import { haStyle } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; +import { documentationUrl } from "../../../../util/documentation-url"; import { showEnergySettingsDeviceDialog } from "../dialogs/show-dialogs-energy"; import { energyCardStyles } from "./styles"; @@ -33,12 +34,29 @@ export class EnergyDeviceSettings extends LitElement { return html`

- Monitor individual - devices + + ${this.hass.localize( + "ui.panel.config.energy.device_consumption.title" + )}

-

Monitor individual devices.

+

+ ${this.hass.localize( + "ui.panel.config.energy.device_consumption.sub" + )} + ${this.hass.localize( + "ui.panel.config.energy.device_consumption.learn_more" + )} +

Devices

${this.preferences.device_consumption.map((device) => { const entityState = this.hass.states[device.stat_consumption]; diff --git a/src/panels/config/energy/components/ha-energy-grid-settings.ts b/src/panels/config/energy/components/ha-energy-grid-settings.ts index 15574cb38a..6888c2df4f 100644 --- a/src/panels/config/energy/components/ha-energy-grid-settings.ts +++ b/src/panels/config/energy/components/ha-energy-grid-settings.ts @@ -34,6 +34,7 @@ import { } from "../../../../dialogs/generic/show-dialog-box"; import { haStyle } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; +import { documentationUrl } from "../../../../util/documentation-url"; import { showEnergySettingsGridFlowFromDialog, showEnergySettingsGridFlowToDialog, @@ -63,12 +64,25 @@ export class EnergyGridSettings extends LitElement { return html`

- ${this.hass.localize("ui.panel.config.energy.grid.title")} + + ${this.hass.localize("ui.panel.config.energy.grid.title")}

-

${this.hass.localize("ui.panel.config.energy.grid.sub")}

+

+ ${this.hass.localize("ui.panel.config.energy.grid.sub")} + ${this.hass.localize( + "ui.panel.config.energy.grid.learn_more" + )} +

Grid consumption

${gridSource.flow_from.map((flow) => { const entityState = this.hass.states[flow.stat_energy_from]; diff --git a/src/panels/config/energy/components/ha-energy-solar-settings.ts b/src/panels/config/energy/components/ha-energy-solar-settings.ts index a9023dc5ba..b2479251a3 100644 --- a/src/panels/config/energy/components/ha-energy-solar-settings.ts +++ b/src/panels/config/energy/components/ha-energy-solar-settings.ts @@ -19,6 +19,7 @@ import { } from "../../../../dialogs/generic/show-dialog-box"; import { haStyle } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; +import { documentationUrl } from "../../../../util/documentation-url"; import { showEnergySettingsSolarDialog } from "../dialogs/show-dialogs-energy"; import { energyCardStyles } from "./styles"; @@ -37,14 +38,24 @@ export class EnergySolarSettings extends LitElement { return html`

- Configure solar - panels + + ${this.hass.localize("ui.panel.config.energy.solar.title")}

- Let Home Assistant monitor your solar panels and give you insight on - their performace. + ${this.hass.localize("ui.panel.config.energy.solar.sub")} + ${this.hass.localize( + "ui.panel.config.energy.solar.learn_more" + )}

Solar production

${solarSources.map((source) => { diff --git a/src/panels/config/energy/dialogs/dialog-energy-device-settings.ts b/src/panels/config/energy/dialogs/dialog-energy-device-settings.ts index 47826b1836..9d3a383be5 100644 --- a/src/panels/config/energy/dialogs/dialog-energy-device-settings.ts +++ b/src/panels/config/energy/dialogs/dialog-energy-device-settings.ts @@ -58,7 +58,6 @@ export class DialogEnergyDeviceSettings @closed=${this.closeDialog} > ${this._error ? html`

${this._error}

` : ""} -

Track your devices Learn more

${this._error ? html`

${this._error}

` : ""} -

+

${this.hass.localize( `ui.panel.config.energy.grid.flow_dialog.${this._params.direction}.paragraph` )} -

+
${this._error ? html`

${this._error}

` : ""} -

Solar production for the win! Learn more

${this.hass.localize("ui.panel.energy.setup.header")} -

${this.hass.localize("ui.panel.energy.setup.slogan")}

-

Step ${this._step + 1} of 3

${this._step === 0 ? html` @@ -190,7 +186,7 @@ export class HuiEnergyDevicesGraphCard ) ); - if (endTime > new Date()) { + if (!endTime || endTime > new Date()) { endTime = new Date(); } @@ -207,27 +203,30 @@ export class HuiEnergyDevicesGraphCard }, ]; - Object.entries(this._data).forEach(([id, statistics], idx) => { - const entity = this.hass.states[id]; - const label = entity ? computeStateName(entity) : id; + for (let idx = 0; idx < prefs.device_consumption.length; idx++) { + const device = prefs.device_consumption[idx]; + const entity = this.hass.states[device.stat_consumption]; + const label = entity ? computeStateName(entity) : device.stat_consumption; const color = getColorByIndex(idx); borderColor.push(color); backgroundColor.push(color + "7F"); - const value = calculateStatisticSumGrowth(statistics); + const value = + device.stat_consumption in this._data + ? calculateStatisticSumGrowth(this._data[device.stat_consumption]) + : 0; data.push({ // @ts-expect-error y: label, x: value || 0, }); - }); + } data.sort((a, b) => b.x - a.x); this._chartData = { - // labels, datasets, }; } 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 960dbe752f..4546c15c33 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts @@ -9,6 +9,7 @@ import { import { css, html, LitElement, svg } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; +import { ifDefined } from "lit/directives/if-defined"; import { round } from "../../../../common/number/round"; import { subscribeOne } from "../../../../common/util/subscribe-one"; import "../../../../components/ha-card"; @@ -121,6 +122,8 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard { let homeLowCarbonCircumference: number | undefined; let homeHighCarbonCircumference: number | undefined; + let electricityMapUrl: string | undefined; + if (this._co2SignalEntity && this._co2SignalEntity in this._stats) { // Calculate high carbon consumption const highCarbonConsumption = calculateStatisticsSumGrowthWithPercentage( @@ -130,6 +133,12 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard { .filter(Boolean) ); + const co2State = this.hass.states[this._co2SignalEntity]; + + if (co2State) { + electricityMapUrl = `https://www.electricitymap.org/zone/${co2State.attributes.country_code}`; + } + if (highCarbonConsumption !== null) { const gridPctHighCarbon = highCarbonConsumption / totalConsumption; @@ -159,10 +168,15 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard { : html`
Non-fossil - + @@ -478,6 +492,8 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard { font-size: 12px; line-height: 12px; position: relative; + text-decoration: none; + color: var(--primary-text-color); } ha-svg-icon { padding-bottom: 2px; diff --git a/src/translations/en.json b/src/translations/en.json index 21b8d52461..47621bb9c3 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -992,8 +992,9 @@ "description": "Monitor your energy production and consumption", "currency": "", "grid": { - "title": "Configure grid", - "sub": "Configure the different tarrifs for the energy you consume from the grid, and, if you return energy to the grid, the energy you return to the grid.", + "title": "Electricity grid", + "sub": "Configure the amount of energy that you consume from the grid and, if you produce energy, give back to the grid. This allows Home Assistant to track your whole home energy usage.", + "learn_more": "More information on how to get started.", "flow_dialog": { "from": { "header": "Configure grid consumption", @@ -1002,7 +1003,7 @@ "cost_para": "Select how Home Assistant should keep track of the costs of the consumed energy.", "no_cost": "Do not track costs", "cost_stat": "Use an entity tracking the total costs", - "cost_stat_input": "Entity keeping track of the total costs", + "cost_stat_input": "Total Costs Entity", "cost_entity": "Use an entity with current price", "cost_entity_input": "Entity with the current price", "cost_number": "Use a static price", @@ -1016,7 +1017,7 @@ "cost_para": "Do you get money back when you return energy to the grid?", "no_cost": "I do not get money back", "cost_stat": "Use an entity tracking the total recieved money", - "cost_stat_input": "Entity keeping track of the total of received money", + "cost_stat_input": "Total Compensation Entity", "cost_entity": "Use an entity with current rate", "cost_entity_input": "Entity with the current rate", "cost_number": "Use a static rate", @@ -1026,12 +1027,17 @@ } }, "solar": { + "title": "Solar Panels", + "sub": "Let Home Assistant monitor your solar panels and give you insight on their performance.", + "learn_more": "More information on how to get started.", "stat_production": "Your solar energy production", "stat_return_to_grid": "Solar energy returned to the grid", "stat_predicted_production": "Prediction of your solar energy production" }, "device_consumption": { - "description": "If you measure the power consumption of individual devices, you can select the entities with the power consumption below", + "title": "Individual devices", + "sub": "Tracking the energy usage of individual devices allows Home Assistant to break down your energy usage by device.", + "learn_more": "More information on how to get started.", "add_stat": "Pick entity to track energy of", "selected_stat": "Tracking energy for" } @@ -3713,8 +3719,6 @@ }, "energy": { "setup": { - "header": "Setup your energy dashboard", - "slogan": "The world is heating up. Together we can fix that.", "next": "Next", "back": "Back", "done": "Show me my energy dashboard!" diff --git a/yarn.lock b/yarn.lock index 8b8220541c..50b861edd9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8941,7 +8941,7 @@ fsevents@~2.3.1: gulp-rename: ^2.0.0 gulp-zopfli-green: ^3.0.1 hls.js: ^1.0.7 - home-assistant-js-websocket: ^5.11.0 + home-assistant-js-websocket: ^5.11.1 html-minifier: ^4.0.0 husky: ^1.3.1 idb-keyval: ^5.0.5 @@ -9011,10 +9011,10 @@ fsevents@~2.3.1: languageName: unknown linkType: soft -"home-assistant-js-websocket@npm:^5.11.0": - version: 5.11.0 - resolution: "home-assistant-js-websocket@npm:5.11.0" - checksum: 7e493f2528a49a2ea8b35468a7c655f97627820542bd4dd841060ca7b34d0fc9c5b4b7cc22d0ba16f1f7932342fd5978c923fc6a213d2af05509ad8ff6912422 +"home-assistant-js-websocket@npm:^5.11.1": + version: 5.11.1 + resolution: "home-assistant-js-websocket@npm:5.11.1" + checksum: 6766cff890831741cfb9ebc5372ed465be578897a8c763daac09a7673d804fa591658542ca1e93619104486956972c94b7f6f9098caaddbf36f89c7808db563d languageName: node linkType: hard From 65bfdf94c9e33bdaf7af35b5bd9af141c039fa48 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 28 Jul 2021 11:05:28 -0700 Subject: [PATCH 27/43] Bumped version to 20210728.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7315e0b189..46ad3ec8f2 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup( name="home-assistant-frontend", - version="20210727.0", + version="20210728.0", description="The Home Assistant frontend", url="https://github.com/home-assistant/frontend", author="The Home Assistant Authors", From e1bae65aeb594d7d83e9c5b26272a5059c12f9bf Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 29 Jul 2021 08:12:49 -0700 Subject: [PATCH 28/43] Remove currency from initial config (#9638) --- src/panels/config/energy/ha-config-energy.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/panels/config/energy/ha-config-energy.ts b/src/panels/config/energy/ha-config-energy.ts index d55210dcd3..a15a6c4179 100644 --- a/src/panels/config/energy/ha-config-energy.ts +++ b/src/panels/config/energy/ha-config-energy.ts @@ -11,8 +11,7 @@ import "./components/ha-energy-device-settings"; import "./components/ha-energy-grid-settings"; import "./components/ha-energy-solar-settings"; -const INITIAL_CONFIG = { - currency: "€", +const INITIAL_CONFIG: EnergyPreferences = { energy_sources: [], device_consumption: [], }; From 6e7af184948e6f7d155d4a5878531bfa119ee7c4 Mon Sep 17 00:00:00 2001 From: Julien Ehrhart Date: Thu, 29 Jul 2021 17:20:12 +0200 Subject: [PATCH 29/43] Add support for DEVICE_CLASS_MONETARY (#9640) --- src/common/const.ts | 1 + src/util/hass-attributes-util.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/common/const.ts b/src/common/const.ts index 2bd338c895..dd4eace281 100644 --- a/src/common/const.ts +++ b/src/common/const.ts @@ -63,6 +63,7 @@ export const FIXED_DEVICE_CLASS_ICONS = { humidity: "hass:water-percent", illuminance: "hass:brightness-5", temperature: "hass:thermometer", + monetary: "mdi:cash", pressure: "hass:gauge", power: "hass:flash", power_factor: "hass:angle-acute", diff --git a/src/util/hass-attributes-util.ts b/src/util/hass-attributes-util.ts index 7a9bc9c232..73fa60cd4e 100644 --- a/src/util/hass-attributes-util.ts +++ b/src/util/hass-attributes-util.ts @@ -60,6 +60,7 @@ const hassAttributeUtil = { "power", "power_factor", "pressure", + "monetary", "signal_strength", "temperature", "timestamp", From 1531e99528a68e0011a9a81ef2cffe2960a2b991 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 29 Jul 2021 19:44:33 +0200 Subject: [PATCH 30/43] Energy dashboard tweaks and fixes (#9643) * Energy dashboard tweaks and fixes * Make headers smaller * Change button styling in onboarding * Disable add when no stat choosen * Oops * Update hui-energy-carbon-consumed-gauge-card.ts * Update hui-energy-distribution-card.ts --- src/common/string/format_number.ts | 46 +- src/components/chart/ha-chart-base.ts | 12 +- .../chart/state-history-chart-line.ts | 2 + .../chart/state-history-chart-timeline.ts | 3 + src/components/chart/statistics-chart.ts | 5 +- .../dialog-energy-grid-flow-settings.ts | 46 +- .../dialogs/dialog-energy-solar-settings.ts | 8 +- .../energy/cards/energy-setup-wizard-card.ts | 6 +- .../energy/strategies/energy-strategy.ts | 9 +- .../hui-energy-carbon-consumed-gauge-card.ts | 2 +- .../energy/hui-energy-costs-table-card.ts | 272 ----------- .../energy/hui-energy-devices-graph-card.ts | 21 +- .../energy/hui-energy-distribution-card.ts | 214 +++++---- .../hui-energy-solar-consumed-gauge-card.ts | 15 +- .../energy/hui-energy-solar-graph-card.ts | 41 +- .../energy/hui-energy-sources-table-card.ts | 426 ++++++++++++++++++ ...card.ts => hui-energy-usage-graph-card.ts} | 156 +++---- src/panels/lovelace/cards/types.ts | 8 +- .../create-element/create-card-element.ts | 8 +- src/resources/ha-style.ts | 8 + src/resources/styles.ts | 1 + 21 files changed, 780 insertions(+), 529 deletions(-) delete mode 100644 src/panels/lovelace/cards/energy/hui-energy-costs-table-card.ts create mode 100644 src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts rename src/panels/lovelace/cards/energy/{hui-energy-summary-graph-card.ts => hui-energy-usage-graph-card.ts} (74%) diff --git a/src/common/string/format_number.ts b/src/common/string/format_number.ts index 2cfa22458b..a921ee50c4 100644 --- a/src/common/string/format_number.ts +++ b/src/common/string/format_number.ts @@ -1,5 +1,22 @@ import { FrontendLocaleData, NumberFormat } from "../../data/translation"; +export const numberFormatToLocale = ( + localeOptions: FrontendLocaleData +): string | string[] | undefined => { + switch (localeOptions.number_format) { + case NumberFormat.comma_decimal: + return ["en-US", "en"]; // Use United States with fallback to English formatting 1,234,567.89 + case NumberFormat.decimal_comma: + return ["de", "es", "it"]; // Use German with fallback to Spanish then Italian formatting 1.234.567,89 + case NumberFormat.space_comma: + return ["fr", "sv", "cs"]; // Use French with fallback to Swedish and Czech formatting 1 234 567,89 + case NumberFormat.system: + return undefined; + default: + return localeOptions.language; + } +}; + /** * Formats a number based on the user's preference with thousands separator(s) and decimal character for better legibility. * @@ -9,27 +26,12 @@ import { FrontendLocaleData, NumberFormat } from "../../data/translation"; */ export const formatNumber = ( num: string | number, - locale?: FrontendLocaleData, + localeOptions?: FrontendLocaleData, options?: Intl.NumberFormatOptions ): string => { - let format: string | string[] | undefined; - - switch (locale?.number_format) { - case NumberFormat.comma_decimal: - format = ["en-US", "en"]; // Use United States with fallback to English formatting 1,234,567.89 - break; - case NumberFormat.decimal_comma: - format = ["de", "es", "it"]; // Use German with fallback to Spanish then Italian formatting 1.234.567,89 - break; - case NumberFormat.space_comma: - format = ["fr", "sv", "cs"]; // Use French with fallback to Swedish and Czech formatting 1 234 567,89 - break; - case NumberFormat.system: - format = undefined; - break; - default: - format = locale?.language; - } + const locale = localeOptions + ? numberFormatToLocale(localeOptions) + : undefined; // Polyfill for Number.isNaN, which is more reliable than the global isNaN() Number.isNaN = @@ -39,13 +41,13 @@ export const formatNumber = ( }; if ( + localeOptions?.number_format !== NumberFormat.none && !Number.isNaN(Number(num)) && - Intl && - locale?.number_format !== NumberFormat.none + Intl ) { try { return new Intl.NumberFormat( - format, + locale, getDefaultFormatOptions(num, options) ).format(Number(num)); } catch (error) { diff --git a/src/components/chart/ha-chart-base.ts b/src/components/chart/ha-chart-base.ts index c662823f58..8c4c3cc16e 100644 --- a/src/components/chart/ha-chart-base.ts +++ b/src/components/chart/ha-chart-base.ts @@ -152,7 +152,17 @@ export default class HaChartBase extends LitElement { .querySelector("canvas")! .getContext("2d")!; - this.chart = new (await import("../../resources/chartjs")).Chart(ctx, { + const ChartConstructor = (await import("../../resources/chartjs")).Chart; + + const computedStyles = getComputedStyle(this); + + ChartConstructor.defaults.borderColor = + computedStyles.getPropertyValue("--divider-color"); + ChartConstructor.defaults.color = computedStyles.getPropertyValue( + "--secondary-text-color" + ); + + this.chart = new ChartConstructor(ctx, { type: this.chartType, data: this.data, options: this._createOptions(), diff --git a/src/components/chart/state-history-chart-line.ts b/src/components/chart/state-history-chart-line.ts index 30e6dff80f..797fd55316 100644 --- a/src/components/chart/state-history-chart-line.ts +++ b/src/components/chart/state-history-chart-line.ts @@ -109,6 +109,8 @@ class StateHistoryChartLine extends LitElement { hitRadius: 5, }, }, + // @ts-expect-error + locale: numberFormatToLocale(this.hass.locale), }; } if (changedProps.has("data")) { diff --git a/src/components/chart/state-history-chart-timeline.ts b/src/components/chart/state-history-chart-timeline.ts index 2ce6277871..db81411b6a 100644 --- a/src/components/chart/state-history-chart-timeline.ts +++ b/src/components/chart/state-history-chart-timeline.ts @@ -5,6 +5,7 @@ import { customElement, property, state } from "lit/decorators"; import { getColorByIndex } from "../../common/color/colors"; import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time"; import { computeDomain } from "../../common/entity/compute_domain"; +import { numberFormatToLocale } from "../../common/string/format_number"; import { computeRTL } from "../../common/util/compute_rtl"; import { TimelineEntity } from "../../data/history"; import { HomeAssistant } from "../../types"; @@ -186,6 +187,8 @@ export class StateHistoryChartTimeline extends LitElement { propagate: true, }, }, + // @ts-expect-error + locale: numberFormatToLocale(this.hass.locale), }; } if (changedProps.has("data")) { diff --git a/src/components/chart/statistics-chart.ts b/src/components/chart/statistics-chart.ts index 6d4db48c70..0ac2abb66a 100644 --- a/src/components/chart/statistics-chart.ts +++ b/src/components/chart/statistics-chart.ts @@ -16,6 +16,7 @@ import { customElement, property, state } from "lit/decorators"; import { getColorByIndex } from "../../common/color/colors"; import { isComponentLoaded } from "../../common/config/is_component_loaded"; import { computeStateName } from "../../common/entity/compute_state_name"; +import { numberFormatToLocale } from "../../common/string/format_number"; import { Statistics, statisticsHaveType, @@ -119,7 +120,7 @@ class StatisticsChart extends LitElement { : {}, }, time: { - tooltipFormat: "datetimeseconds", + tooltipFormat: "datetime", }, }, y: { @@ -157,6 +158,8 @@ class StatisticsChart extends LitElement { hitRadius: 5, }, }, + // @ts-expect-error + locale: numberFormatToLocale(this.hass.locale), }; } diff --git a/src/panels/config/energy/dialogs/dialog-energy-grid-flow-settings.ts b/src/panels/config/energy/dialogs/dialog-energy-grid-flow-settings.ts index 3869ae6dfe..0963c6beed 100644 --- a/src/panels/config/energy/dialogs/dialog-energy-grid-flow-settings.ts +++ b/src/panels/config/energy/dialogs/dialog-energy-grid-flow-settings.ts @@ -212,7 +212,15 @@ export class DialogEnergyGridFlowSettings ${this.hass.localize("ui.common.cancel")} - + ${this.hass.localize("ui.common.save")} @@ -231,32 +239,42 @@ export class DialogEnergyGridFlowSettings } private _numberPriceChanged(ev: CustomEvent) { - this._source!.number_energy_price = Number(ev.detail.value); - this._source!.entity_energy_price = null; this._costStat = null; + this._source = { + ...this._source!, + number_energy_price: Number(ev.detail.value), + entity_energy_price: null, + }; } private _priceStatChanged(ev: CustomEvent) { this._costStat = ev.detail.value; - this._source!.entity_energy_price = null; - this._source!.number_energy_price = null; + this._source = { + ...this._source!, + entity_energy_price: null, + number_energy_price: null, + }; } private _priceEntityChanged(ev: CustomEvent) { - this._source!.entity_energy_price = ev.detail.value; - this._source!.number_energy_price = null; this._costStat = null; + this._source = { + ...this._source!, + entity_energy_price: ev.detail.value, + number_energy_price: null, + }; } private _statisticChanged(ev: CustomEvent<{ value: string }>) { - this._source![ - this._params!.direction === "from" ? "stat_energy_from" : "stat_energy_to" - ] = ev.detail.value; - this._source![ - this._params!.direction === "from" + this._source = { + ...this._source!, + [this._params!.direction === "from" + ? "stat_energy_from" + : "stat_energy_to"]: ev.detail.value, + [this._params!.direction === "from" ? "entity_energy_from" - : "entity_energy_to" - ] = ev.detail.value; + : "entity_energy_to"]: ev.detail.value, + }; } private async _save() { diff --git a/src/panels/config/energy/dialogs/dialog-energy-solar-settings.ts b/src/panels/config/energy/dialogs/dialog-energy-solar-settings.ts index ba65d67349..2e55ba598d 100644 --- a/src/panels/config/energy/dialogs/dialog-energy-solar-settings.ts +++ b/src/panels/config/energy/dialogs/dialog-energy-solar-settings.ts @@ -140,7 +140,11 @@ export class DialogEnergySolarSettings ${this.hass.localize("ui.common.cancel")} - + ${this.hass.localize("ui.common.save")} @@ -191,7 +195,7 @@ export class DialogEnergySolarSettings } private _statisticChanged(ev: CustomEvent<{ value: string }>) { - this._source!.stat_energy_from = ev.detail.value; + this._source = { ...this._source!, stat_energy_from: ev.detail.value }; } private async _save() { diff --git a/src/panels/energy/cards/energy-setup-wizard-card.ts b/src/panels/energy/cards/energy-setup-wizard-card.ts index 69c6b42b1e..aa5178ac5f 100644 --- a/src/panels/energy/cards/energy-setup-wizard-card.ts +++ b/src/panels/energy/cards/energy-setup-wizard-card.ts @@ -61,15 +61,15 @@ export class EnergySetupWizard extends LitElement implements LovelaceCard { >`}
${this._step > 0 - ? html`${this.hass.localize("ui.panel.energy.setup.back")}` : html`
`} ${this._step < 2 - ? html`${this.hass.localize("ui.panel.energy.setup.next")}` - : html` + : html` ${this.hass.localize("ui.panel.energy.setup.done")} `}
diff --git a/src/panels/energy/strategies/energy-strategy.ts b/src/panels/energy/strategies/energy-strategy.ts index 787d1de38b..7177e6d9a7 100644 --- a/src/panels/energy/strategies/energy-strategy.ts +++ b/src/panels/energy/strategies/energy-strategy.ts @@ -50,7 +50,7 @@ export class EnergyStrategy { if (hasGrid) { view.cards!.push({ title: "Energy usage", - type: "energy-summary-graph", + type: "energy-usage-graph", prefs: energyPrefs, }); } @@ -64,11 +64,10 @@ export class EnergyStrategy { }); } - // Only include if we have a grid. - if (hasGrid) { + if (hasGrid || hasSolar) { view.cards!.push({ - title: "Costs", - type: "energy-costs-table", + title: "Sources", + type: "energy-sources-table", prefs: energyPrefs, }); } diff --git a/src/panels/lovelace/cards/energy/hui-energy-carbon-consumed-gauge-card.ts b/src/panels/lovelace/cards/energy/hui-energy-carbon-consumed-gauge-card.ts index a54f12c88c..f682895585 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-carbon-consumed-gauge-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-carbon-consumed-gauge-card.ts @@ -105,7 +105,7 @@ class HuiEnergyCarbonGaugeCard extends LitElement implements LovelaceCard { (totalSolarProduction || 0) - (totalGridReturned || 0); - value = round((1 - highCarbonEnergy / totalEnergyConsumed) * 100); + value = round((highCarbonEnergy / totalEnergyConsumed) * 100); } return html` diff --git a/src/panels/lovelace/cards/energy/hui-energy-costs-table-card.ts b/src/panels/lovelace/cards/energy/hui-energy-costs-table-card.ts deleted file mode 100644 index 413288bc59..0000000000 --- a/src/panels/lovelace/cards/energy/hui-energy-costs-table-card.ts +++ /dev/null @@ -1,272 +0,0 @@ -// @ts-ignore -import dataTableStyles from "@material/data-table/dist/mdc.data-table.min.css"; -import { - css, - CSSResultGroup, - html, - LitElement, - TemplateResult, - unsafeCSS, -} from "lit"; -import { customElement, property, state } from "lit/decorators"; -import { computeStateName } from "../../../../common/entity/compute_state_name"; -import { round } from "../../../../common/number/round"; -import { formatNumber } from "../../../../common/string/format_number"; -import "../../../../components/chart/statistics-chart"; -import "../../../../components/ha-card"; -import { - EnergyInfo, - getEnergyInfo, - GridSourceTypeEnergyPreference, -} from "../../../../data/energy"; -import { - calculateStatisticSumGrowth, - fetchStatistics, - Statistics, -} from "../../../../data/history"; -import { HomeAssistant } from "../../../../types"; -import { LovelaceCard } from "../../types"; -import { EnergyDevicesGraphCardConfig } from "../types"; - -@customElement("hui-energy-costs-table-card") -export class HuiEnergyCostsTableCard - extends LitElement - implements LovelaceCard -{ - @property({ attribute: false }) public hass!: HomeAssistant; - - @state() private _config?: EnergyDevicesGraphCardConfig; - - @state() private _stats?: Statistics; - - @state() private _energyInfo?: EnergyInfo; - - public getCardSize(): Promise | number { - return 3; - } - - public setConfig(config: EnergyDevicesGraphCardConfig): void { - this._config = config; - } - - public willUpdate() { - if (!this.hasUpdated) { - this._getEnergyInfo().then(() => this._getStatistics()); - } - } - - protected render(): TemplateResult { - if (!this.hass || !this._config) { - return html``; - } - - if (!this._stats) { - return html`Loading...`; - } - - const source = this._config.prefs.energy_sources?.find( - (src) => src.type === "grid" - ) as GridSourceTypeEnergyPreference | undefined; - - if (!source) { - return html`No grid source found.`; - } - - let totalEnergy = 0; - let totalCost = 0; - - return html` -
-
- - - - - - - - - - ${source.flow_from.map((flow) => { - const entity = this.hass.states[flow.stat_energy_from]; - const energy = - calculateStatisticSumGrowth( - this._stats![flow.stat_energy_from] - ) || 0; - totalEnergy += energy; - const cost_stat = - flow.stat_cost || - this._energyInfo!.cost_sensors[flow.stat_energy_from]; - const cost = - (cost_stat && - calculateStatisticSumGrowth(this._stats![cost_stat])) || - 0; - totalCost += cost; - return html` - - - - `; - })} - ${source.flow_to.map((flow) => { - const entity = this.hass.states[flow.stat_energy_to]; - const energy = - (calculateStatisticSumGrowth( - this._stats![flow.stat_energy_to] - ) || 0) * -1; - totalEnergy += energy; - const cost_stat = - flow.stat_compensation || - this._energyInfo!.cost_sensors[flow.stat_energy_to]; - const cost = - ((cost_stat && - calculateStatisticSumGrowth(this._stats![cost_stat])) || - 0) * -1; - totalCost += cost; - return html` - - - - `; - })} - - - - - - -
- Grid source - - Energy - - Cost -
- ${entity ? computeStateName(entity) : flow.stat_energy_from} - - ${round(energy)} kWh - - ${formatNumber(cost, this.hass.locale, { - style: "currency", - currency: this.hass.config.currency!, - })} -
- ${entity ? computeStateName(entity) : flow.stat_energy_to} - - ${round(energy)} kWh - - ${formatNumber(cost, this.hass.locale, { - style: "currency", - currency: this.hass.config.currency!, - })} -
Total - ${round(totalEnergy)} kWh - - ${formatNumber(totalCost, this.hass.locale, { - style: "currency", - currency: this.hass.config.currency!, - })} -
-
-
-
`; - } - - private async _getEnergyInfo() { - this._energyInfo = await getEnergyInfo(this.hass); - } - - private async _getStatistics(): Promise { - const startDate = new Date(); - startDate.setHours(0, 0, 0, 0); - startDate.setTime(startDate.getTime() - 1000 * 60 * 60); // subtract 1 hour to get a startpoint - - const statistics: string[] = Object.values(this._energyInfo!.cost_sensors); - const prefs = this._config!.prefs; - for (const source of prefs.energy_sources) { - if (source.type === "solar") { - continue; - } - - // grid source - for (const flowFrom of source.flow_from) { - statistics.push(flowFrom.stat_energy_from); - if (flowFrom.stat_cost) { - statistics.push(flowFrom.stat_cost); - } - } - for (const flowTo of source.flow_to) { - statistics.push(flowTo.stat_energy_to); - if (flowTo.stat_compensation) { - statistics.push(flowTo.stat_compensation); - } - } - } - - this._stats = await fetchStatistics( - this.hass!, - startDate, - undefined, - statistics - ); - } - - static get styles(): CSSResultGroup { - return css` - ${unsafeCSS(dataTableStyles)} - .mdc-data-table { - width: 100%; - border: 0; - } - .mdc-data-table__header-cell, - .mdc-data-table__cell { - color: var(--primary-text-color); - border-bottom-color: var(--divider-color); - } - .mdc-data-table__row:not(.mdc-data-table__row--selected):hover { - background-color: rgba(var(--rgb-primary-text-color), 0.04); - } - .total { - --mdc-typography-body2-font-weight: 500; - } - .total .mdc-data-table__cell { - border-top: 1px solid var(--divider-color); - } - ha-card { - height: 100%; - } - .content { - padding: 16px; - } - .has-header { - padding-top: 0; - } - `; - } -} - -declare global { - interface HTMLElementTagNameMap { - "hui-energy-costs-table-card": HuiEnergyCostsTableCard; - } -} 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 758a6c23b9..f9d8c1cb5d 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 @@ -16,6 +16,10 @@ import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { getColorByIndex } from "../../../../common/color/colors"; import { computeStateName } from "../../../../common/entity/compute_state_name"; +import { + formatNumber, + numberFormatToLocale, +} from "../../../../common/string/format_number"; import "../../../../components/chart/ha-chart-base"; import "../../../../components/ha-card"; import { @@ -106,7 +110,10 @@ export class HuiEnergyDevicesGraphCard } return html` - + + ${this._config.title + ? html`

${this._config.title}

` + : ""}
- `${context.dataset.label}: ${ - Math.round(context.parsed.x * 100) / 100 - } kWh`, + `${context.dataset.label}: ${formatNumber( + context.parsed.x, + this.hass.locale + )} kWh`, }, }, }, + // @ts-expect-error + locale: numberFormatToLocale(this.hass.locale), }; } @@ -236,6 +246,9 @@ export class HuiEnergyDevicesGraphCard ha-card { height: 100%; } + .card-header { + padding-bottom: 0; + } .content { padding: 16px; } 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 4546c15c33..286940fd58 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts @@ -10,7 +10,7 @@ import { css, html, LitElement, svg } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { ifDefined } from "lit/directives/if-defined"; -import { round } from "../../../../common/number/round"; +import { formatNumber } from "../../../../common/string/format_number"; import { subscribeOne } from "../../../../common/util/subscribe-one"; import "../../../../components/ha-card"; import "../../../../components/ha-svg-icon"; @@ -140,13 +140,9 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard { } if (highCarbonConsumption !== null) { - const gridPctHighCarbon = highCarbonConsumption / totalConsumption; + lowCarbonConsumption = totalGridConsumption - highCarbonConsumption; - lowCarbonConsumption = - totalGridConsumption - totalGridConsumption * gridPctHighCarbon; - - const homePctGridHighCarbon = - (gridPctHighCarbon * totalGridConsumption) / totalConsumption; + const homePctGridHighCarbon = highCarbonConsumption / totalConsumption; homeHighCarbonCircumference = CIRCLE_CIRCUMFERENCE * homePctGridHighCarbon; @@ -158,6 +154,15 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard { } } + homeSolarCircumference = CIRCLE_CIRCUMFERENCE * 0.1; + + homeHighCarbonCircumference = CIRCLE_CIRCUMFERENCE * 0.8; + + homeLowCarbonCircumference = + CIRCLE_CIRCUMFERENCE - + (homeSolarCircumference || 0) - + homeHighCarbonCircumference; + return html`
@@ -165,29 +170,39 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard { ? html`
${lowCarbonConsumption === undefined ? html`
` - : html` - - `} + : html``} ${hasSolarProduction ? html`
Solar
- ${round(totalSolarProduction || 0, 1)} kWh + ${formatNumber( + totalSolarProduction || 0, + this.hass.locale, + { maximumFractionDigits: 1 } + )} + kWh
` : ""} @@ -204,7 +219,11 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard { class="small" .path=${mdiArrowRight} >` - : ""}${round(totalGridConsumption, 1)} + : ""}${formatNumber( + totalGridConsumption, + this.hass.locale, + { maximumFractionDigits: 1 } + )} kWh ${productionReturnedToGrid !== null @@ -213,7 +232,12 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard { class="small" .path=${mdiArrowLeft} >${round(productionReturnedToGrid, 1)} kWh + >${formatNumber( + productionReturnedToGrid, + this.hass.locale, + { maximumFractionDigits: 1 } + )} + kWh ` : ""}
@@ -228,41 +252,44 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard { })}" > - ${round(totalConsumption, 1)} kWh + ${formatNumber(totalConsumption, this.hass.locale, { + maximumFractionDigits: 1, + })} + kWh ${homeSolarCircumference !== undefined || homeLowCarbonCircumference !== undefined ? html` ${homeSolarCircumference !== undefined - ? svg` - ` + shape-rendering="geometricPrecision" + stroke-dashoffset="-${ + CIRCLE_CIRCUMFERENCE - homeSolarCircumference + }" + />` : ""} - ${homeHighCarbonCircumference - ? svg` - ` + stroke-dashoffset="-${ + CIRCLE_CIRCUMFERENCE - + homeLowCarbonCircumference - + (homeSolarCircumference || 0) + }" + shape-rendering="geometricPrecision" + />` : ""} ` @@ -315,7 +342,11 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard { vector-effect="non-scaling-stroke" > ${productionReturnedToGrid && hasSolarProduction - ? svg` + ? svg` + ? svg` + ? svg` ${value !== undefined @@ -81,7 +84,9 @@ class HuiEnergySolarGaugeCard extends LitElement implements LovelaceCard { })} >
Self consumed solar energy
` - : html`Self consumed solar energy couldn't be calculated`} + : totalSolarProduction === 0 + ? "You have not produced any solar energy" + : "Self consumed solar energy couldn't be calculated"} `; } 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 47a8ffa69d..391585329b 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 @@ -31,8 +31,10 @@ import { computeStateName } from "../../../../common/entity/compute_state_name"; import "../../../../components/chart/ha-chart-base"; import "../../../../components/ha-switch"; import "../../../../components/ha-formfield"; - -const SOLAR_COLOR = "#FF9800"; +import { + formatNumber, + numberFormatToLocale, +} from "../../../../common/string/format_number"; @customElement("hui-energy-solar-graph-card") export class HuiEnergySolarGraphCard @@ -119,7 +121,10 @@ export class HuiEnergySolarGraphCard } return html` - + + ${this._config.title + ? html`

${this._config.title}

` + : ""}
- `${context.dataset.label}: ${context.parsed.y} kWh`, + `${context.dataset.label}: ${formatNumber( + context.parsed.y, + this.hass.locale + )} kWh`, }, }, filler: { @@ -212,6 +220,8 @@ export class HuiEnergySolarGraphCard hitRadius: 5, }, }, + // @ts-expect-error + locale: numberFormatToLocale(this.hass.locale), }; } @@ -219,6 +229,7 @@ export class HuiEnergySolarGraphCard if (this._fetching) { return; } + const startDate = new Date(); startDate.setHours(0, 0, 0, 0); startDate.setTime(startDate.getTime() - 1000 * 60 * 60); // subtract 1 hour to get a startpoint @@ -273,20 +284,25 @@ export class HuiEnergySolarGraphCard endTime = new Date(); } + const computedStyles = getComputedStyle(this); + const solarColor = computedStyles + .getPropertyValue("--energy-solar-color") + .trim(); + solarSources.forEach((source, idx) => { const data: ChartDataset<"bar" | "line">[] = []; const entity = this.hass.states[source.stat_energy_from]; const borderColor = idx > 0 - ? rgb2hex(lab2rgb(labDarken(rgb2lab(hex2rgb(SOLAR_COLOR)), idx))) - : SOLAR_COLOR; + ? rgb2hex(lab2rgb(labDarken(rgb2lab(hex2rgb(solarColor)), idx))) + : solarColor; data.push({ label: `Production ${ entity ? computeStateName(entity) : source.stat_energy_from }`, - borderColor: borderColor, + borderColor, backgroundColor: borderColor + "7F", data: [], }); @@ -307,7 +323,7 @@ export class HuiEnergySolarGraphCard if (prevStart === point.start) { continue; } - const value = Math.round((point.sum - prevValue) * 100) / 100; + const value = point.sum - prevValue; const date = new Date(point.start); data[0].data.push({ x: date.getTime(), @@ -347,7 +363,9 @@ export class HuiEnergySolarGraphCard }`, fill: false, stepped: false, - borderColor: "#000", + borderColor: computedStyles.getPropertyValue( + "--primary-text-color" + ), borderDash: [7, 5], pointRadius: 0, data: [], @@ -386,6 +404,9 @@ export class HuiEnergySolarGraphCard ha-card { height: 100%; } + .card-header { + padding-bottom: 0; + } .content { padding: 16px; } 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 new file mode 100644 index 0000000000..44f4336609 --- /dev/null +++ b/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts @@ -0,0 +1,426 @@ +// @ts-ignore +import dataTableStyles from "@material/data-table/dist/mdc.data-table.min.css"; +import { + css, + CSSResultGroup, + html, + LitElement, + TemplateResult, + unsafeCSS, +} from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { styleMap } from "lit/directives/style-map"; +import { + rgb2hex, + lab2rgb, + rgb2lab, + hex2rgb, +} from "../../../../common/color/convert-color"; +import { labDarken } from "../../../../common/color/lab"; +import { computeStateName } from "../../../../common/entity/compute_state_name"; +import { formatNumber } from "../../../../common/string/format_number"; +import "../../../../components/chart/statistics-chart"; +import "../../../../components/ha-card"; +import { + EnergyInfo, + energySourcesByType, + getEnergyInfo, +} from "../../../../data/energy"; +import { + calculateStatisticSumGrowth, + fetchStatistics, + Statistics, +} from "../../../../data/history"; +import { HomeAssistant } from "../../../../types"; +import { LovelaceCard } from "../../types"; +import { EnergySourcesTableCardConfig } from "../types"; + +@customElement("hui-energy-sources-table-card") +export class HuiEnergySourcesTableCard + extends LitElement + implements LovelaceCard +{ + @property({ attribute: false }) public hass!: HomeAssistant; + + @state() private _config?: EnergySourcesTableCardConfig; + + @state() private _stats?: Statistics; + + @state() private _energyInfo?: EnergyInfo; + + public getCardSize(): Promise | number { + return 3; + } + + public setConfig(config: EnergySourcesTableCardConfig): void { + this._config = config; + } + + public willUpdate() { + if (!this.hasUpdated) { + this._getEnergyInfo().then(() => this._getStatistics()); + } + } + + protected render(): TemplateResult { + if (!this.hass || !this._config) { + return html``; + } + + if (!this._stats) { + return html`Loading...`; + } + + let totalGrid = 0; + let totalSolar = 0; + let totalCost = 0; + + const types = energySourcesByType(this._config.prefs); + + const computedStyles = getComputedStyle(this); + const solarColor = computedStyles + .getPropertyValue("--energy-solar-color") + .trim(); + const returnColor = computedStyles + .getPropertyValue("--energy-grid-return-color") + .trim(); + const consumptionColor = computedStyles + .getPropertyValue("--energy-grid-consumption-color") + .trim(); + + const showCosts = + types.grid?.[0].flow_from.some( + (flow) => + flow.stat_cost || flow.entity_energy_price || flow.number_energy_price + ) || + types.grid?.[0].flow_to.some( + (flow) => + flow.stat_compensation || + flow.entity_energy_price || + flow.number_energy_price + ); + + return html` + ${this._config.title + ? html`

${this._config.title}

` + : ""} +
+
+ + + + + + + ${showCosts + ? html` ` + : ""} + + + + ${types.solar?.map((source, idx) => { + const entity = this.hass.states[source.stat_energy_from]; + const energy = + calculateStatisticSumGrowth( + this._stats![source.stat_energy_from] + ) || 0; + totalSolar += energy; + const color = + idx > 0 + ? rgb2hex( + lab2rgb(labDarken(rgb2lab(hex2rgb(solarColor)), idx)) + ) + : solarColor; + return html` + + + + ${showCosts + ? html`` + : ""} + `; + })} + ${types.solar + ? html` + + + + ${showCosts + ? html`` + : ""} + ` + : ""} + ${types.grid?.map( + (source) => html`${source.flow_from.map((flow, idx) => { + const entity = this.hass.states[flow.stat_energy_from]; + const energy = + calculateStatisticSumGrowth( + this._stats![flow.stat_energy_from] + ) || 0; + totalGrid += energy; + const cost_stat = + flow.stat_cost || + this._energyInfo!.cost_sensors[flow.stat_energy_from]; + const cost = cost_stat + ? calculateStatisticSumGrowth(this._stats![cost_stat]) + : null; + if (cost !== null) { + totalCost += cost; + } + const color = + idx > 0 + ? rgb2hex( + lab2rgb( + labDarken(rgb2lab(hex2rgb(consumptionColor)), idx) + ) + ) + : consumptionColor; + return html` + + + + ${showCosts + ? html` ` + : ""} + `; + })} + ${source.flow_to.map((flow, idx) => { + const entity = this.hass.states[flow.stat_energy_to]; + const energy = + (calculateStatisticSumGrowth( + this._stats![flow.stat_energy_to] + ) || 0) * -1; + totalGrid += energy; + const cost_stat = + flow.stat_compensation || + this._energyInfo!.cost_sensors[flow.stat_energy_to]; + const cost = cost_stat + ? calculateStatisticSumGrowth(this._stats![cost_stat]) + : null; + if (cost !== null) { + totalCost += cost; + } + const color = + idx > 0 + ? rgb2hex( + lab2rgb(labDarken(rgb2lab(hex2rgb(returnColor)), idx)) + ) + : returnColor; + return html` + + + + ${showCosts + ? html` ` + : ""} + `; + })}` + )} + ${types.grid + ? html` + + + + ${showCosts + ? html`` + : ""} + ` + : ""} + +
+ Source + + Energy + + Cost +
+
+
+ ${entity + ? computeStateName(entity) + : source.stat_energy_from} + + ${formatNumber(energy, this.hass.locale)} kWh +
+ Solar total + + ${formatNumber(totalSolar, this.hass.locale)} kWh +
+
+
+ ${entity + ? computeStateName(entity) + : flow.stat_energy_from} + + ${formatNumber(energy, this.hass.locale)} kWh + + ${cost !== null + ? formatNumber(cost, this.hass.locale, { + style: "currency", + currency: this.hass.config.currency!, + }) + : ""} +
+
+
+ ${entity ? computeStateName(entity) : flow.stat_energy_to} + + ${formatNumber(energy, this.hass.locale)} kWh + + ${cost !== null + ? formatNumber(cost, this.hass.locale, { + style: "currency", + currency: this.hass.config.currency!, + }) + : ""} +
Grid total + ${formatNumber(totalGrid, this.hass.locale)} kWh + + ${formatNumber(totalCost, this.hass.locale, { + style: "currency", + currency: this.hass.config.currency!, + })} +
+
+
+
`; + } + + private async _getEnergyInfo() { + this._energyInfo = await getEnergyInfo(this.hass); + } + + private async _getStatistics(): Promise { + const startDate = new Date(); + startDate.setHours(0, 0, 0, 0); + startDate.setTime(startDate.getTime() - 1000 * 60 * 60); // subtract 1 hour to get a startpoint + + const statistics: string[] = Object.values(this._energyInfo!.cost_sensors); + const prefs = this._config!.prefs; + for (const source of prefs.energy_sources) { + if (source.type === "solar") { + statistics.push(source.stat_energy_from); + } else { + // grid source + for (const flowFrom of source.flow_from) { + statistics.push(flowFrom.stat_energy_from); + if (flowFrom.stat_cost) { + statistics.push(flowFrom.stat_cost); + } + } + for (const flowTo of source.flow_to) { + statistics.push(flowTo.stat_energy_to); + if (flowTo.stat_compensation) { + statistics.push(flowTo.stat_compensation); + } + } + } + } + + this._stats = await fetchStatistics( + this.hass!, + startDate, + undefined, + statistics + ); + } + + static get styles(): CSSResultGroup { + return css` + ${unsafeCSS(dataTableStyles)} + .mdc-data-table { + width: 100%; + border: 0; + } + .mdc-data-table__header-cell, + .mdc-data-table__cell { + color: var(--primary-text-color); + border-bottom-color: var(--divider-color); + } + .mdc-data-table__row:not(.mdc-data-table__row--selected):hover { + background-color: rgba(var(--rgb-primary-text-color), 0.04); + } + .total { + --mdc-typography-body2-font-weight: 500; + } + .total .mdc-data-table__cell { + border-top: 1px solid var(--divider-color); + } + ha-card { + height: 100%; + } + .card-header { + padding-bottom: 0; + } + .content { + padding: 16px; + } + .has-header { + padding-top: 0; + } + .cell-bullet { + width: 32px; + padding-right: 0; + } + .bullet { + border-width: 1px; + border-style: solid; + border-radius: 4px; + height: 16px; + width: 32px; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-energy-sources-table-card": HuiEnergySourcesTableCard; + } +} diff --git a/src/panels/lovelace/cards/energy/hui-energy-summary-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts similarity index 74% rename from src/panels/lovelace/cards/energy/hui-energy-summary-graph-card.ts rename to src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts index 2e3eb96354..5c455a4865 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-summary-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts @@ -9,39 +9,34 @@ import { } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; -import { styleMap } from "lit/directives/style-map"; import { hex2rgb, lab2rgb, rgb2hex, rgb2lab, } from "../../../../common/color/convert-color"; +import { hexBlend } from "../../../../common/color/hex"; import { labDarken } from "../../../../common/color/lab"; import { computeStateName } from "../../../../common/entity/compute_state_name"; -import { round } from "../../../../common/number/round"; -import { formatNumber } from "../../../../common/string/format_number"; +import { + formatNumber, + numberFormatToLocale, +} from "../../../../common/string/format_number"; import "../../../../components/chart/ha-chart-base"; import "../../../../components/ha-card"; import { fetchStatistics, Statistics } from "../../../../data/history"; import { HomeAssistant } from "../../../../types"; import { LovelaceCard } from "../../types"; -import { EnergySummaryGraphCardConfig } from "../types"; +import { EnergyUsageGraphCardConfig } from "../types"; -const NEGATIVE = ["to_grid"]; -const COLORS = { - to_grid: { border: "#673ab7", background: "#b39bdb" }, - from_grid: { border: "#126A9A", background: "#8ab5cd" }, - used_solar: { border: "#FF9800", background: "#fecc8e" }, -}; - -@customElement("hui-energy-summary-graph-card") -export class HuiEnergySummaryGraphCard +@customElement("hui-energy-usage-graph-card") +export class HuiEnergyUsageGraphCard extends LitElement implements LovelaceCard { @property({ attribute: false }) public hass!: HomeAssistant; - @state() private _config?: EnergySummaryGraphCardConfig; + @state() private _config?: EnergyUsageGraphCardConfig; @state() private _data?: Statistics; @@ -81,7 +76,7 @@ export class HuiEnergySummaryGraphCard return 3; } - public setConfig(config: EnergySummaryGraphCardConfig): void { + public setConfig(config: EnergyUsageGraphCardConfig): void { this._config = config; } @@ -95,7 +90,7 @@ export class HuiEnergySummaryGraphCard } const oldConfig = changedProps.get("_config") as - | EnergySummaryGraphCardConfig + | EnergyUsageGraphCardConfig | undefined; if (oldConfig !== this._config) { @@ -116,42 +111,14 @@ export class HuiEnergySummaryGraphCard return html` -

${this._config.title}

+ ${this._config.title + ? html`

${this._config.title}

` + : ""}
-
-
    - ${this._chartData.datasets.map( - (dataset) => html`
  • -
    -
    - ${dataset.label} -
    - ${formatNumber( - Math.abs( - dataset.data.reduce( - (total, point) => total + (point as any).y, - 0 - ) as number - ), - this.hass.locale - )} - kWh -
  • ` - )} -
-
Math.abs(round(value)), + callback: (value) => + formatNumber(Math.abs(value), this.hass.locale), }, }, }, @@ -218,7 +186,10 @@ export class HuiEnergySummaryGraphCard filter: (val) => val.formattedValue !== "0", callbacks: { label: (context) => - `${context.dataset.label}: ${Math.abs(context.parsed.y)} kWh`, + `${context.dataset.label}: ${formatNumber( + Math.abs(context.parsed.y), + this.hass.locale + )} kWh`, footer: (contexts) => { let totalConsumed = 0; let totalReturned = 0; @@ -233,10 +204,16 @@ export class HuiEnergySummaryGraphCard } return [ totalConsumed - ? `Total consumed: ${totalConsumed.toFixed(2)} kWh` + ? `Total consumed: ${formatNumber( + totalConsumed, + this.hass.locale + )} kWh` : "", totalReturned - ? `Total returned: ${totalReturned.toFixed(2)} kWh` + ? `Total returned: ${formatNumber( + totalReturned, + this.hass.locale + )} kWh` : "", ].filter(Boolean); }, @@ -261,6 +238,8 @@ export class HuiEnergySummaryGraphCard hitRadius: 5, }, }, + // @ts-expect-error + locale: numberFormatToLocale(this.hass.locale), }; } @@ -344,6 +323,23 @@ export class HuiEnergySummaryGraphCard } = {}; const summedData: { [key: string]: { [start: string]: number } } = {}; + const computedStyles = getComputedStyle(this); + const colors = { + to_grid: computedStyles + .getPropertyValue("--energy-grid-return-color") + .trim(), + from_grid: computedStyles + .getPropertyValue("--energy-grid-consumption-color") + .trim(), + used_solar: computedStyles + .getPropertyValue("--energy-solar-color") + .trim(), + }; + + const backgroundColor = computedStyles + .getPropertyValue("--card-background-color") + .trim(); + Object.entries(statistics).forEach(([key, statIds]) => { const sum = ["solar", "to_grid"].includes(key); const add = key !== "solar"; @@ -407,12 +403,13 @@ export class HuiEnergySummaryGraphCard const uniqueKeys = Array.from(new Set(allKeys)); Object.entries(combinedData).forEach(([type, sources]) => { - const negative = NEGATIVE.includes(type); - Object.entries(sources).forEach(([statId, source], idx) => { const data: ChartDataset<"bar">[] = []; const entity = this.hass.states[statId]; - const color = COLORS[type]; + const borderColor = + idx > 0 + ? rgb2hex(lab2rgb(labDarken(rgb2lab(hex2rgb(colors[type])), idx))) + : colors[type]; data.push({ label: @@ -421,28 +418,20 @@ export class HuiEnergySummaryGraphCard : entity ? computeStateName(entity) : statId, - borderColor: - idx > 0 - ? rgb2hex(lab2rgb(labDarken(rgb2lab(hex2rgb(color.border)), idx))) - : color.border, - backgroundColor: - idx > 0 - ? rgb2hex( - lab2rgb(labDarken(rgb2lab(hex2rgb(color.background)), idx)) - ) - : color.background, + borderColor, + backgroundColor: hexBlend(borderColor, backgroundColor, 50), stack: "stack", data: [], }); // Process chart data. for (const key of uniqueKeys) { - const value = key in source ? Math.round(source[key] * 100) / 100 : 0; + const value = source[key] || 0; const date = new Date(key); // @ts-expect-error data[0].data.push({ x: date.getTime(), - y: value && negative ? -1 * value : value, + y: value && type === "to_grid" ? -1 * value : value, }); } @@ -470,43 +459,12 @@ export class HuiEnergySummaryGraphCard .has-header { padding-top: 0; } - .chartLegend ul { - padding-left: 20px; - } - .chartLegend li { - padding: 2px 8px; - display: flex; - justify-content: space-between; - align-items: center; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - box-sizing: border-box; - color: var(--secondary-text-color); - } - .chartLegend li > div { - display: flex; - align-items: center; - } - .chartLegend .bullet { - border-width: 1px; - border-style: solid; - border-radius: 4px; - display: inline-block; - height: 16px; - margin-right: 6px; - width: 32px; - box-sizing: border-box; - } - .value { - font-weight: 300; - } `; } } declare global { interface HTMLElementTagNameMap { - "hui-energy-summary-graph-card": HuiEnergySummaryGraphCard; + "hui-energy-usage-graph-card": HuiEnergyUsageGraphCard; } } diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index dcbf69054a..88684adfa8 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -101,7 +101,7 @@ export interface EnergyDistributionCardConfig extends LovelaceCardConfig { title?: string; prefs: EnergyPreferences; } -export interface EnergySummaryGraphCardConfig extends LovelaceCardConfig { +export interface EnergyUsageGraphCardConfig extends LovelaceCardConfig { type: "energy-summary-graph"; title?: string; prefs: EnergyPreferences; @@ -119,6 +119,12 @@ export interface EnergyDevicesGraphCardConfig extends LovelaceCardConfig { prefs: EnergyPreferences; } +export interface EnergySourcesTableCardConfig extends LovelaceCardConfig { + type: "energy-sources-table"; + title?: string; + prefs: EnergyPreferences; +} + export interface EnergySolarGaugeCardConfig extends LovelaceCardConfig { type: "energy-solar-consumed-gauge"; title?: string; diff --git a/src/panels/lovelace/create-element/create-card-element.ts b/src/panels/lovelace/create-element/create-card-element.ts index 71cee8f6fd..f6a489c098 100644 --- a/src/panels/lovelace/create-element/create-card-element.ts +++ b/src/panels/lovelace/create-element/create-card-element.ts @@ -35,14 +35,14 @@ const LAZY_LOAD_TYPES = { "alarm-panel": () => import("../cards/hui-alarm-panel-card"), error: () => import("../cards/hui-error-card"), "empty-state": () => import("../cards/hui-empty-state-card"), - "energy-summary-graph": () => - import("../cards/energy/hui-energy-summary-graph-card"), + "energy-usage-graph": () => + import("../cards/energy/hui-energy-usage-graph-card"), "energy-solar-graph": () => import("../cards/energy/hui-energy-solar-graph-card"), "energy-devices-graph": () => import("../cards/energy/hui-energy-devices-graph-card"), - "energy-costs-table": () => - import("../cards/energy/hui-energy-costs-table-card"), + "energy-sources-table": () => + import("../cards/energy/hui-energy-sources-table-card"), "energy-distribution": () => import("../cards/energy/hui-energy-distribution-card"), "energy-solar-consumed-gauge": () => diff --git a/src/resources/ha-style.ts b/src/resources/ha-style.ts index a4ff54ff8f..9416b15f99 100644 --- a/src/resources/ha-style.ts +++ b/src/resources/ha-style.ts @@ -82,6 +82,14 @@ documentContainer.innerHTML = ` --state-climate-dry-color: #efbd07; --state-climate-idle-color: #8a8a8a; + /* energy */ + --energy-grid-consumption-color: #126a9a; + --energy-grid-return-color: #673ab7; + --energy-solar-color: #ff9800; + --energy-non-fossil-color: #0f9d58; + + --rgb-energy-solar-color: 255, 152, 0; + /* Paper-styles color.html dependency is stripped on build. When a default paper-style color is used, it needs to be copied diff --git a/src/resources/styles.ts b/src/resources/styles.ts index f2b2494416..716de34179 100644 --- a/src/resources/styles.ts +++ b/src/resources/styles.ts @@ -31,6 +31,7 @@ export const darkStyles = { "codemirror-property": "#C792EA", "codemirror-qualifier": "#DECB6B", "codemirror-type": "#DECB6B", + "energy-grid-return-color": "#b39bdb", }; export const derivedStyles = { From 1bd6392a4ce2cbc6e79172f70ea991cf27d8efe8 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 29 Jul 2021 20:26:22 +0200 Subject: [PATCH 31/43] Add text when no statistics found (#9642) * Add text when no statistics found * Update src/components/entity/ha-statistic-picker.ts Co-authored-by: Paulus Schoutsen * fix typos * Update src/components/entity/ha-statistic-picker.ts * Prettier Co-authored-by: Paulus Schoutsen --- src/components/entity/ha-entities-picker.ts | 3 + src/components/entity/ha-statistic-picker.ts | 115 +++++++++++------- src/components/entity/ha-statistics-picker.ts | 3 + src/panels/config/core/ha-config-core-form.ts | 2 +- .../components/ha-energy-device-settings.ts | 2 +- .../components/ha-energy-grid-settings.ts | 2 +- .../components/ha-energy-solar-settings.ts | 2 +- src/translations/en.json | 7 ++ 8 files changed, 91 insertions(+), 45 deletions(-) diff --git a/src/components/entity/ha-entities-picker.ts b/src/components/entity/ha-entities-picker.ts index fff8041e72..19c20886fa 100644 --- a/src/components/entity/ha-entities-picker.ts +++ b/src/components/entity/ha-entities-picker.ts @@ -131,6 +131,9 @@ class HaEntitiesPickerLight extends LitElement { private async _addEntity(event: PolymerChangedEvent) { event.stopPropagation(); const toAdd = event.detail.value; + if (!toAdd) { + return; + } (event.currentTarget as any).value = ""; if (!toAdd) { return; diff --git a/src/components/entity/ha-statistic-picker.ts b/src/components/entity/ha-statistic-picker.ts index 912188ea8d..aa09f92d25 100644 --- a/src/components/entity/ha-statistic-picker.ts +++ b/src/components/entity/ha-statistic-picker.ts @@ -22,46 +22,12 @@ import { compare } from "../../common/string/compare"; import { getStatisticIds, StatisticsMetaData } from "../../data/history"; import { PolymerChangedEvent } from "../../polymer-types"; import { HomeAssistant } from "../../types"; +import { documentationUrl } from "../../util/documentation-url"; import "../ha-combo-box"; import type { HaComboBox } from "../ha-combo-box"; import "../ha-svg-icon"; import "./state-badge"; -// vaadin-combo-box-item - -const rowRenderer: ComboBoxLitRenderer<{ - id: string; - name: string; - state?: HassEntity; -}> = (item) => html` - - - - - ${item.name} - ${item.id} - - `; - @customElement("ha-statistic-picker") export class HaStatisticPicker extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -99,6 +65,53 @@ export class HaStatisticPicker extends LitElement { private _init = false; + private _rowRenderer: ComboBoxLitRenderer<{ + id: string; + name: string; + state?: HassEntity; + }> = (item) => html` + + + + + ${item.name} + ${item.id === "" || item.id === "__missing" + ? html`${this.hass.localize( + "ui.components.statistic-picker.learn_more" + )}` + : item.id} + + `; + private _getStatistics = memoizeOne( ( statisticIds: StatisticsMetaData[], @@ -110,7 +123,7 @@ export class HaStatisticPicker extends LitElement { { id: "", name: this.hass.localize( - "ui.components.statistics-picker.no_statistics" + "ui.components.statistic-picker.no_statistics" ), }, ]; @@ -142,10 +155,27 @@ export class HaStatisticPicker extends LitElement { }); }); - if (output.length === 1) { - return output; + if (!output.length) { + return [ + { + id: "", + name: this.hass.localize("ui.components.statistic-picker.no_match"), + }, + ]; } - return output.sort((a, b) => compare(a.name || "", b.name || "")); + + if (output.length > 1) { + output.sort((a, b) => compare(a.name || "", b.name || "")); + } + + output.push({ + id: "__missing", + name: this.hass.localize( + "ui.components.statistic-picker.missing_entity" + ), + }); + + return output; } ); @@ -195,7 +225,7 @@ export class HaStatisticPicker extends LitElement { ? this.hass.localize("ui.components.statistic-picker.statistic") : this.label} .value=${this._value} - .renderer=${rowRenderer} + .renderer=${this._rowRenderer} .disabled=${this.disabled} item-value-path="id" item-id-path="id" @@ -216,7 +246,10 @@ export class HaStatisticPicker extends LitElement { private _statisticChanged(ev: PolymerChangedEvent) { ev.stopPropagation(); - const newValue = ev.detail.value; + let newValue = ev.detail.value; + if (newValue === "__missing") { + newValue = ""; + } if (newValue !== this._value) { this._setValue(newValue); diff --git a/src/components/entity/ha-statistics-picker.ts b/src/components/entity/ha-statistics-picker.ts index 3ad6af9203..22a6593ffd 100644 --- a/src/components/entity/ha-statistics-picker.ts +++ b/src/components/entity/ha-statistics-picker.ts @@ -90,6 +90,9 @@ class HaStatisticsPicker extends LitElement { private async _addStatistic(event: PolymerChangedEvent) { event.stopPropagation(); const toAdd = event.detail.value; + if (!toAdd) { + return; + } (event.currentTarget as any).value = ""; if (!toAdd) { return; diff --git a/src/panels/config/core/ha-config-core-form.ts b/src/panels/config/core/ha-config-core-form.ts index 980f1cd5f9..edc69e73e4 100644 --- a/src/panels/config/core/ha-config-core-form.ts +++ b/src/panels/config/core/ha-config-core-form.ts @@ -155,7 +155,7 @@ class ConfigCoreForm extends LitElement { ${this.hass.localize( "ui.panel.config.core.section.core.core_config.find_currency_value" )} Date: Thu, 29 Jul 2021 20:29:49 +0200 Subject: [PATCH 32/43] Fix energy calculations (#9647) * Fix calculations * max.. not min... --- .../hui-energy-carbon-consumed-gauge-card.ts | 5 ++- .../energy/hui-energy-distribution-card.ts | 31 ++++++------------- 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/src/panels/lovelace/cards/energy/hui-energy-carbon-consumed-gauge-card.ts b/src/panels/lovelace/cards/energy/hui-energy-carbon-consumed-gauge-card.ts index f682895585..84a2e54206 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-carbon-consumed-gauge-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-carbon-consumed-gauge-card.ts @@ -102,10 +102,9 @@ class HuiEnergyCarbonGaugeCard extends LitElement implements LovelaceCard { const totalEnergyConsumed = totalGridConsumption + - (totalSolarProduction || 0) - - (totalGridReturned || 0); + Math.max(0, (totalSolarProduction || 0) - (totalGridReturned || 0)); - value = round((highCarbonEnergy / totalEnergyConsumed) * 100); + value = round((1 - highCarbonEnergy / totalEnergyConsumed) * 100); } return html` 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 286940fd58..a3b27745f6 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts @@ -104,17 +104,17 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard { ) || 0; } - const totalConsumption = - totalGridConsumption + - (totalSolarProduction || 0) - - (productionReturnedToGrid || 0); + const solarConsumption = Math.max( + 0, + (totalSolarProduction || 0) - (productionReturnedToGrid || 0) + ); + + const totalHomeConsumption = totalGridConsumption + solarConsumption; let homeSolarCircumference: number | undefined; if (hasSolarProduction) { - const homePctSolar = - ((totalSolarProduction || 0) - (productionReturnedToGrid || 0)) / - totalConsumption; - homeSolarCircumference = CIRCLE_CIRCUMFERENCE * homePctSolar; + homeSolarCircumference = + CIRCLE_CIRCUMFERENCE * (solarConsumption / totalHomeConsumption); } let lowCarbonConsumption: number | undefined; @@ -142,10 +142,8 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard { if (highCarbonConsumption !== null) { lowCarbonConsumption = totalGridConsumption - highCarbonConsumption; - const homePctGridHighCarbon = highCarbonConsumption / totalConsumption; - homeHighCarbonCircumference = - CIRCLE_CIRCUMFERENCE * homePctGridHighCarbon; + CIRCLE_CIRCUMFERENCE * (highCarbonConsumption / totalHomeConsumption); homeLowCarbonCircumference = CIRCLE_CIRCUMFERENCE - @@ -154,15 +152,6 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard { } } - homeSolarCircumference = CIRCLE_CIRCUMFERENCE * 0.1; - - homeHighCarbonCircumference = CIRCLE_CIRCUMFERENCE * 0.8; - - homeLowCarbonCircumference = - CIRCLE_CIRCUMFERENCE - - (homeSolarCircumference || 0) - - homeHighCarbonCircumference; - return html`
@@ -252,7 +241,7 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard { })}" > - ${formatNumber(totalConsumption, this.hass.locale, { + ${formatNumber(totalHomeConsumption, this.hass.locale, { maximumFractionDigits: 1, })} kWh From ae10ff42e113825c434f6c0437176b21474ecbb5 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 29 Jul 2021 20:45:25 +0200 Subject: [PATCH 33/43] Update state-history-chart-line.ts --- src/components/chart/state-history-chart-line.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/chart/state-history-chart-line.ts b/src/components/chart/state-history-chart-line.ts index 797fd55316..b2bddfba4f 100644 --- a/src/components/chart/state-history-chart-line.ts +++ b/src/components/chart/state-history-chart-line.ts @@ -2,6 +2,7 @@ import type { ChartData, ChartDataset, ChartOptions } from "chart.js"; import { html, LitElement, PropertyValues } from "lit"; import { property, state } from "lit/decorators"; import { getColorByIndex } from "../../common/color/colors"; +import { numberFormatToLocale } from "../../common/string/format_number"; import { LineChartEntity, LineChartState } from "../../data/history"; import { HomeAssistant } from "../../types"; import "./ha-chart-base"; From 749079c1c3d3658ca98b8f667b7c0637d35433e1 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 29 Jul 2021 12:02:34 -0700 Subject: [PATCH 34/43] Bumped version to 20210729.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 46ad3ec8f2..e1ca544083 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup( name="home-assistant-frontend", - version="20210728.0", + version="20210729.0", description="The Home Assistant frontend", url="https://github.com/home-assistant/frontend", author="The Home Assistant Authors", From a4f51b0cb337e3c79e6a9dea8a0d8356b416642e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 30 Jul 2021 00:42:54 -0700 Subject: [PATCH 35/43] Fix label for device consumption (#9648) --- .../config/energy/dialogs/dialog-energy-device-settings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/config/energy/dialogs/dialog-energy-device-settings.ts b/src/panels/config/energy/dialogs/dialog-energy-device-settings.ts index 9d3a383be5..3705b4b6a2 100644 --- a/src/panels/config/energy/dialogs/dialog-energy-device-settings.ts +++ b/src/panels/config/energy/dialogs/dialog-energy-device-settings.ts @@ -62,7 +62,7 @@ export class DialogEnergyDeviceSettings From 03080973be7b1aa4e0717c1783bb9574dcc9046d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Lov=C3=A9n?= Date: Fri, 30 Jul 2021 10:51:21 +0200 Subject: [PATCH 36/43] Add needle option to ha-gauge and gauge card (#9637) --- src/components/ha-gauge.ts | 77 ++++++++++++++++--- src/panels/lovelace/cards/hui-gauge-card.ts | 16 ++++ src/panels/lovelace/cards/types.ts | 1 + .../config-elements/hui-gauge-card-editor.ts | 30 +++++++- src/translations/en.json | 1 + 5 files changed, 112 insertions(+), 13 deletions(-) diff --git a/src/components/ha-gauge.ts b/src/components/ha-gauge.ts index e5d785b435..2322f2e28a 100644 --- a/src/components/ha-gauge.ts +++ b/src/components/ha-gauge.ts @@ -13,6 +13,11 @@ const getAngle = (value: number, min: number, max: number) => { return (percentage * 180) / 100; }; +interface LevelDefinition { + level: number; + stroke: string; +} + @customElement("ha-gauge") export class Gauge extends LitElement { @property({ type: Number }) public min = 0; @@ -23,6 +28,10 @@ export class Gauge extends LitElement { @property() public locale!: FrontendLocaleData; + @property({ type: Boolean }) public needle?: boolean; + + @property() public levels?: LevelDefinition[]; + @property() public label = ""; @state() private _angle = 0; @@ -55,18 +64,53 @@ export class Gauge extends LitElement { class="dial" d="M 10 50 A 40 40 0 0 1 90 50" > - + + ${ + this.levels + ? this.levels + .sort((a, b) => a.level - b.level) + .map((level) => { + const angle = getAngle(level.level, this.min, this.max); + return svg``; + }) + : "" + } + ${ + this.needle + ? svg` + ` + : svg`` + } ${ // Workaround for https://github.com/home-assistant/frontend/issues/6467 isSafari @@ -117,6 +161,15 @@ export class Gauge extends LitElement { transform-origin: 50% 100%; transition: all 1s ease 0s; } + .needle { + fill: var(--primary-text-color); + transform-origin: 50% 100%; + transition: all 1s ease 0s; + } + .level { + fill: none; + stroke-width: 15; + } .gauge { display: block; } diff --git a/src/panels/lovelace/cards/hui-gauge-card.ts b/src/panels/lovelace/cards/hui-gauge-card.ts index f87a9081e8..1db4cb6334 100644 --- a/src/panels/lovelace/cards/hui-gauge-card.ts +++ b/src/panels/lovelace/cards/hui-gauge-card.ts @@ -135,6 +135,8 @@ class HuiGaugeCard extends LitElement implements LovelaceCard { style=${styleMap({ "--gauge-color": this._computeSeverity(entityState), })} + .needle=${this._config!.needle} + .levels=${this._config!.needle ? this._severityLevels() : undefined} >
${this._config.name || computeStateName(stateObj)} @@ -200,6 +202,20 @@ class HuiGaugeCard extends LitElement implements LovelaceCard { return severityMap.normal; } + private _severityLevels() { + const sections = this._config!.severity; + + if (!sections) { + return []; + } + + const sectionsArray = Object.keys(sections); + return sectionsArray.map((severity) => ({ + level: sections[severity], + stroke: severityMap[severity], + })); + } + private _handleClick(): void { fireEvent(this, "hass-more-info", { entityId: this._config!.entity }); } diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index 88684adfa8..5d191334a3 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -164,6 +164,7 @@ export interface GaugeCardConfig extends LovelaceCardConfig { max?: number; severity?: SeverityConfig; theme?: string; + needle?: boolean; } export interface ConfigEntity extends EntityConfig { diff --git a/src/panels/lovelace/editor/config-elements/hui-gauge-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-gauge-card-editor.ts index 16e1e049c9..3776dee7b0 100644 --- a/src/panels/lovelace/editor/config-elements/hui-gauge-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-gauge-card-editor.ts @@ -1,7 +1,7 @@ import "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; -import { assert, number, object, optional, string } from "superstruct"; +import { assert, boolean, number, object, optional, string } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; import { computeRTLDirection } from "../../../../common/util/compute_rtl"; import "../../../../components/ha-formfield"; @@ -23,6 +23,7 @@ const cardConfigStruct = object({ max: optional(number()), severity: optional(object()), theme: optional(string()), + needle: optional(boolean()), }); const includeDomains = ["counter", "input_number", "number", "sensor"]; @@ -137,6 +138,17 @@ export class HuiGaugeCardEditor .configValue=${"max"} @value-changed="${this._valueChanged}" > + + Date: Fri, 30 Jul 2021 12:28:47 +0200 Subject: [PATCH 37/43] fix self consumed solar (#9651) --- .../cards/energy/hui-energy-solar-consumed-gauge-card.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/lovelace/cards/energy/hui-energy-solar-consumed-gauge-card.ts b/src/panels/lovelace/cards/energy/hui-energy-solar-consumed-gauge-card.ts index b7030d997d..3d79fe48ab 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-solar-consumed-gauge-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-solar-consumed-gauge-card.ts @@ -63,7 +63,7 @@ class HuiEnergySolarGaugeCard extends LitElement implements LovelaceCard { let value: number | undefined; if (productionReturnedToGrid !== null && totalSolarProduction) { - const cosumedSolar = Math.min( + const cosumedSolar = Math.max( 0, totalSolarProduction - productionReturnedToGrid ); From 5234e9bce563317e9227c1f3a036f7007e9c50bb Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 30 Jul 2021 17:36:31 +0200 Subject: [PATCH 38/43] still round numbers when not formatting them (#9653) --- src/common/string/format_number.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/common/string/format_number.ts b/src/common/string/format_number.ts index a921ee50c4..01279cb715 100644 --- a/src/common/string/format_number.ts +++ b/src/common/string/format_number.ts @@ -1,4 +1,5 @@ import { FrontendLocaleData, NumberFormat } from "../../data/translation"; +import { round } from "../number/round"; export const numberFormatToLocale = ( localeOptions: FrontendLocaleData @@ -60,7 +61,12 @@ export const formatNumber = ( ).format(Number(num)); } } - return num.toString(); + if (typeof num === "string") { + return num; + } + return `${round(num, options?.maximumFractionDigits).toString()}${ + options?.style === "currency" ? ` ${options.currency}` : "" + }`; }; /** From cfad45b7c22b3bed627a9d7fa1c38e68559df8de Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 30 Jul 2021 21:52:38 +0200 Subject: [PATCH 39/43] fix and finish statistics card (#9658) --- src/components/chart/ha-chart-base.ts | 2 +- src/components/chart/statistics-chart.ts | 60 ++++++-- src/components/ha-gauge.ts | 8 +- src/panels/lovelace/cards/hui-gauge-card.ts | 2 +- .../cards/hui-statistics-graph-card.ts | 5 +- .../hui-statistics-graph-card-editor.ts | 134 ++++++++++++++++-- 6 files changed, 179 insertions(+), 32 deletions(-) diff --git a/src/components/chart/ha-chart-base.ts b/src/components/chart/ha-chart-base.ts index 8c4c3cc16e..2ff0550efa 100644 --- a/src/components/chart/ha-chart-base.ts +++ b/src/components/chart/ha-chart-base.ts @@ -55,7 +55,7 @@ export default class HaChartBase extends LitElement { this._setupChart(); return; } - if (changedProps.has("type")) { + if (changedProps.has("chartType")) { this.chart.config.type = this.chartType; } if (changedProps.has("data")) { diff --git a/src/components/chart/statistics-chart.ts b/src/components/chart/statistics-chart.ts index 0ac2abb66a..68431696ba 100644 --- a/src/components/chart/statistics-chart.ts +++ b/src/components/chart/statistics-chart.ts @@ -38,8 +38,8 @@ class StatisticsChart extends LitElement { @property({ type: Array }) public statTypes: Array = [ "sum", "min", - "max", "mean", + "max", ]; @property() public chartType: ChartType = "line"; @@ -58,7 +58,7 @@ class StatisticsChart extends LitElement { if (!this.hasUpdated) { this._createOptions(); } - if (changedProps.has("statisticsData")) { + if (changedProps.has("statisticsData") || changedProps.has("statTypes")) { this._generateData(); } } @@ -164,6 +164,9 @@ class StatisticsChart extends LitElement { } private _generateData() { + if (!this.statisticsData) { + return; + } let colorIndex = 0; const statisticsData = Object.values(this.statisticsData); const totalDataSets: ChartDataset<"line">[] = []; @@ -231,21 +234,21 @@ class StatisticsChart extends LitElement { prevValues = dataValues; }; + const color = getColorByIndex(colorIndex); + colorIndex++; + const addDataSet = ( nameY: string, + borderColor: string, + backgroundColor: string, step = false, - fill = false, - color?: string + fill?: boolean | number | string ) => { - if (!color) { - color = getColorByIndex(colorIndex); - colorIndex++; - } statDataSets.push({ label: nameY, - fill: fill ? "origin" : false, - borderColor: color, - backgroundColor: color + "7F", + fill: fill || false, + borderColor, + backgroundColor: backgroundColor, stepped: step ? "before" : false, pointRadius: 0, data: [], @@ -254,20 +257,50 @@ class StatisticsChart extends LitElement { const statTypes: this["statTypes"] = []; - this.statTypes.forEach((type) => { + const sortedTypes = [...this.statTypes].sort((a, _b) => { + if (a === "min") { + return -1; + } + if (a === "max") { + return +1; + } + return 0; + }); + + const drawBands = + this.statTypes.includes("mean") && statisticsHaveType(stats, "mean"); + + sortedTypes.forEach((type) => { if (statisticsHaveType(stats, type)) { statTypes.push(type); addDataSet( `${name} (${this.hass.localize( `ui.components.statistics_charts.statistic_types.${type}` )})`, - false + drawBands && (type === "min" || type === "max") + ? color + "7F" + : color, + color + "7F", + false, + drawBands + ? type === "min" + ? "+1" + : type === "max" + ? "-1" + : false + : false ); } }); + let prevDate: Date | null = null; // Process chart data. stats.forEach((stat) => { + const date = new Date(stat.start); + if (prevDate === date) { + return; + } + prevDate = date; const dataValues: Array = []; statTypes.forEach((type) => { let val: number | null; @@ -278,7 +311,6 @@ class StatisticsChart extends LitElement { } dataValues.push(val !== null ? Math.round(val * 100) / 100 : null); }); - const date = new Date(stat.start); pushData(date, dataValues); }); diff --git a/src/components/ha-gauge.ts b/src/components/ha-gauge.ts index 2322f2e28a..81161ed64d 100644 --- a/src/components/ha-gauge.ts +++ b/src/components/ha-gauge.ts @@ -60,10 +60,14 @@ export class Gauge extends LitElement { protected render() { return svg` - + >` + : "" + } ${ this.levels diff --git a/src/panels/lovelace/cards/hui-gauge-card.ts b/src/panels/lovelace/cards/hui-gauge-card.ts index 1db4cb6334..f8ff918de2 100644 --- a/src/panels/lovelace/cards/hui-gauge-card.ts +++ b/src/panels/lovelace/cards/hui-gauge-card.ts @@ -206,7 +206,7 @@ class HuiGaugeCard extends LitElement implements LovelaceCard { const sections = this._config!.severity; if (!sections) { - return []; + return [{ level: 0, stroke: severityMap.normal }]; } const sectionsArray = Object.keys(sections); diff --git a/src/panels/lovelace/cards/hui-statistics-graph-card.ts b/src/panels/lovelace/cards/hui-statistics-graph-card.ts index fd44114d3e..2b4e90522d 100644 --- a/src/panels/lovelace/cards/hui-statistics-graph-card.ts +++ b/src/panels/lovelace/cards/hui-statistics-graph-card.ts @@ -114,7 +114,10 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard { | StatisticsGraphCardConfig | undefined; - if (oldConfig?.entities !== this._config.entities) { + if ( + oldConfig?.entities !== this._config.entities || + oldConfig?.days_to_show !== this._config.days_to_show + ) { this._getStatistics(); // statistics are created every hour clearInterval(this._interval); diff --git a/src/panels/lovelace/editor/config-elements/hui-statistics-graph-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-statistics-graph-card-editor.ts index 831177442b..4acd31bca9 100644 --- a/src/panels/lovelace/editor/config-elements/hui-statistics-graph-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-statistics-graph-card-editor.ts @@ -1,7 +1,16 @@ import "@polymer/paper-input/paper-input"; import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; -import { array, assert, number, object, optional, string } from "superstruct"; +import { + array, + assert, + literal, + number, + object, + optional, + string, + union, +} from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; import { HomeAssistant } from "../../../../types"; import { StatisticsGraphCardConfig } from "../../cards/types"; @@ -11,12 +20,26 @@ import { EditorTarget } from "../types"; import { configElementStyle } from "./config-elements-style"; import "../../../../components/entity/ha-statistics-picker"; import { processConfigEntities } from "../../common/process-config-entities"; +import "../../../../components/ha-formfield"; +import "../../../../components/ha-checkbox"; +import { StatisticType } from "../../../../data/history"; +import "../../../../components/ha-radio"; +import type { HaRadio } from "../../../../components/ha-radio"; + +const statTypeStruct = union([ + literal("sum"), + literal("min"), + literal("max"), + literal("mean"), +]); const cardConfigStruct = object({ type: string(), entities: array(entitiesConfigStruct), title: optional(string()), days_to_show: optional(number()), + chart_type: optional(union([literal("bar"), literal("line")])), + stat_types: optional(union([array(statTypeStruct), statTypeStruct])), }); @customElement("hui-statistics-graph-card-editor") @@ -46,6 +69,18 @@ export class HuiStatisticsGraphCardEditor return this._config!.days_to_show || 30; } + get _chart_type(): StatisticsGraphCardConfig["chart_type"] { + return this._config!.chart_type || "line"; + } + + get _stat_types(): StatisticType[] { + return this._config!.stat_types + ? Array.isArray(this._config!.stat_types) + ? this._config!.stat_types + : [this._config!.stat_types] + : ["mean", "min", "max", "sum"]; + } + protected render(): TemplateResult { if (!this.hass || !this._config) { return html``; @@ -63,19 +98,67 @@ export class HuiStatisticsGraphCardEditor .configValue=${"title"} @value-changed=${this._valueChanged} > + +

Show stat types:

- + + + + + + + + + + + + +
+
+

Chart type:

+ + + + + +
stat !== name), + }, + }); + } + private _valueChanged(ev: CustomEvent): void { if (!this._config || !this.hass) { return; From 2cdf78c50460723dd6497d33c87f73464cd254cc Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 30 Jul 2021 21:55:58 +0200 Subject: [PATCH 40/43] Add grid neutrality gauge (#9655) --- src/components/ha-gauge.ts | 8 +- .../energy/strategies/energy-strategy.ts | 20 +- .../hui-energy-grid-neutrality-gauge-card.ts | 177 ++++++++++++++++++ src/panels/lovelace/cards/types.ts | 6 + .../create-element/create-card-element.ts | 2 + 5 files changed, 208 insertions(+), 5 deletions(-) create mode 100644 src/panels/lovelace/cards/energy/hui-energy-grid-neutrality-gauge-card.ts diff --git a/src/components/ha-gauge.ts b/src/components/ha-gauge.ts index 81161ed64d..25dbae2b5f 100644 --- a/src/components/ha-gauge.ts +++ b/src/components/ha-gauge.ts @@ -13,7 +13,7 @@ const getAngle = (value: number, min: number, max: number) => { return (percentage * 180) / 100; }; -interface LevelDefinition { +export interface LevelDefinition { level: number; stroke: string; } @@ -26,6 +26,8 @@ export class Gauge extends LitElement { @property({ type: Number }) public value = 0; + @property({ type: String }) public valueText?: string; + @property() public locale!: FrontendLocaleData; @property({ type: Boolean }) public needle?: boolean; @@ -131,7 +133,9 @@ export class Gauge extends LitElement { - ${formatNumber(this.value, this.locale)} ${this.label} + ${this.valueText || formatNumber(this.value, this.locale)} ${ + this.label + } `; } diff --git a/src/panels/energy/strategies/energy-strategy.ts b/src/panels/energy/strategies/energy-strategy.ts index 7177e6d9a7..325bb65ada 100644 --- a/src/panels/energy/strategies/energy-strategy.ts +++ b/src/panels/energy/strategies/energy-strategy.ts @@ -1,4 +1,8 @@ -import { EnergyPreferences, getEnergyPreferences } from "../../../data/energy"; +import { + EnergyPreferences, + getEnergyPreferences, + GridSourceTypeEnergyPreference, +} from "../../../data/energy"; import { LovelaceViewConfig } from "../../../data/lovelace"; import { LovelaceViewStrategy } from "../../lovelace/strategies/get-strategy"; @@ -39,9 +43,10 @@ export class EnergyStrategy { view.type = "sidebar"; - const hasGrid = energyPrefs.energy_sources.some( + const hasGrid = energyPrefs.energy_sources.find( (source) => source.type === "grid" - ); + ) as GridSourceTypeEnergyPreference; + const hasReturn = hasGrid && hasGrid.flow_to.length; const hasSolar = energyPrefs.energy_sources.some( (source) => source.type === "solar" ); @@ -100,6 +105,15 @@ export class EnergyStrategy { }); } + // Only include if we have a grid source & return. + if (hasReturn) { + view.cards!.push({ + type: "energy-grid-neutrality-gauge", + prefs: energyPrefs, + view_layout: { position: "sidebar" }, + }); + } + // Only include if we have a grid if (hasGrid) { view.cards!.push({ diff --git a/src/panels/lovelace/cards/energy/hui-energy-grid-neutrality-gauge-card.ts b/src/panels/lovelace/cards/energy/hui-energy-grid-neutrality-gauge-card.ts new file mode 100644 index 0000000000..97f8dab168 --- /dev/null +++ b/src/panels/lovelace/cards/energy/hui-energy-grid-neutrality-gauge-card.ts @@ -0,0 +1,177 @@ +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { formatNumber } from "../../../../common/string/format_number"; +import "../../../../components/ha-card"; +import "../../../../components/ha-gauge"; +import type { LevelDefinition } from "../../../../components/ha-gauge"; +import { GridSourceTypeEnergyPreference } from "../../../../data/energy"; +import { + calculateStatisticsSumGrowth, + fetchStatistics, + Statistics, +} from "../../../../data/history"; +import type { HomeAssistant } from "../../../../types"; +import type { LovelaceCard } from "../../types"; +import type { EnergyGridGaugeCardConfig } from "../types"; + +const LEVELS: LevelDefinition[] = [ + { level: -1, stroke: "var(--label-badge-red)" }, + { level: -0.2, stroke: "var(--label-badge-yellow)" }, + { level: 0, stroke: "var(--label-badge-green)" }, +]; + +@customElement("hui-energy-grid-neutrality-gauge-card") +class HuiEnergyGridGaugeCard extends LitElement implements LovelaceCard { + @property({ attribute: false }) public hass?: HomeAssistant; + + @state() private _config?: EnergyGridGaugeCardConfig; + + @state() private _stats?: Statistics; + + public getCardSize(): number { + return 4; + } + + public setConfig(config: EnergyGridGaugeCardConfig): void { + this._config = config; + } + + public willUpdate(changedProps) { + super.willUpdate(changedProps); + + if (!this.hasUpdated) { + this._getStatistics(); + } + } + + protected render(): TemplateResult { + if (!this._config || !this.hass) { + return html``; + } + + if (!this._stats) { + return html`Loading...`; + } + + const prefs = this._config!.prefs; + const gridSource = prefs.energy_sources.find( + (src) => src.type === "grid" + ) as GridSourceTypeEnergyPreference | undefined; + + let value: number | undefined; + + if (!gridSource) { + return html``; + } + + const consumedFromGrid = calculateStatisticsSumGrowth( + this._stats, + gridSource.flow_from.map((flow) => flow.stat_energy_from) + ); + + const returnedToGrid = calculateStatisticsSumGrowth( + this._stats, + gridSource.flow_to.map((flow) => flow.stat_energy_to) + ); + + if (consumedFromGrid !== null && returnedToGrid !== null) { + if (returnedToGrid > consumedFromGrid) { + value = 1 - consumedFromGrid / returnedToGrid; + } else if (returnedToGrid < consumedFromGrid) { + value = (1 - returnedToGrid / consumedFromGrid) * -1; + } else { + value = 0; + } + } + + return html` + + ${value !== undefined + ? html` +
+ ${returnedToGrid! >= consumedFromGrid! + ? "Returned to the grid" + : "Consumed from the grid"} +
` + : "Grid neutrality could not be calculated"} +
+ `; + } + + private async _getStatistics(): Promise { + const startDate = new Date(); + startDate.setHours(0, 0, 0, 0); + startDate.setTime(startDate.getTime() - 1000 * 60 * 60); // subtract 1 hour to get a startpoint + + const statistics: string[] = []; + const prefs = this._config!.prefs; + for (const source of prefs.energy_sources) { + if (source.type === "solar") { + continue; + } + + // grid source + for (const flowFrom of source.flow_from) { + statistics.push(flowFrom.stat_energy_from); + } + for (const flowTo of source.flow_to) { + statistics.push(flowTo.stat_energy_to); + } + } + + this._stats = await fetchStatistics( + this.hass!, + startDate, + undefined, + statistics + ); + } + + static get styles(): CSSResultGroup { + return css` + ha-card { + height: 100%; + overflow: hidden; + padding: 16px; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + box-sizing: border-box; + } + + ha-gauge { + width: 100%; + max-width: 250px; + } + + .name { + text-align: center; + line-height: initial; + color: var(--primary-text-color); + width: 100%; + font-size: 15px; + margin-top: 8px; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-energy-grid-neutrality-gauge-card": HuiEnergyGridGaugeCard; + } +} diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index 5d191334a3..4894837543 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -131,6 +131,12 @@ export interface EnergySolarGaugeCardConfig extends LovelaceCardConfig { prefs: EnergyPreferences; } +export interface EnergyGridGaugeCardConfig extends LovelaceCardConfig { + type: "energy-grid-result-gauge"; + title?: string; + prefs: EnergyPreferences; +} + export interface EnergyCarbonGaugeCardConfig extends LovelaceCardConfig { type: "energy-carbon-consumed-gauge"; title?: string; diff --git a/src/panels/lovelace/create-element/create-card-element.ts b/src/panels/lovelace/create-element/create-card-element.ts index f6a489c098..e73a60619d 100644 --- a/src/panels/lovelace/create-element/create-card-element.ts +++ b/src/panels/lovelace/create-element/create-card-element.ts @@ -47,6 +47,8 @@ const LAZY_LOAD_TYPES = { import("../cards/energy/hui-energy-distribution-card"), "energy-solar-consumed-gauge": () => import("../cards/energy/hui-energy-solar-consumed-gauge-card"), + "energy-grid-neutrality-gauge": () => + import("../cards/energy/hui-energy-grid-neutrality-gauge-card"), "energy-carbon-consumed-gauge": () => import("../cards/energy/hui-energy-carbon-consumed-gauge-card"), grid: () => import("../cards/hui-grid-card"), From 5147dff67036962c3da9bea607f2427f1dc4f674 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 30 Jul 2021 21:57:01 +0200 Subject: [PATCH 41/43] Sidebar view: Move all cards to 1 column on mobile (#9656) --- .../energy/strategies/energy-strategy.ts | 34 ++++++++--------- src/panels/lovelace/views/hui-masonry-view.ts | 10 ++++- src/panels/lovelace/views/hui-sidebar-view.ts | 37 +++++++++++++------ 3 files changed, 51 insertions(+), 30 deletions(-) diff --git a/src/panels/energy/strategies/energy-strategy.ts b/src/panels/energy/strategies/energy-strategy.ts index 325bb65ada..86ff55d8d1 100644 --- a/src/panels/energy/strategies/energy-strategy.ts +++ b/src/panels/energy/strategies/energy-strategy.ts @@ -69,23 +69,6 @@ export class EnergyStrategy { }); } - if (hasGrid || hasSolar) { - view.cards!.push({ - title: "Sources", - type: "energy-sources-table", - prefs: energyPrefs, - }); - } - - // Only include if we have at least 1 device in the config. - if (energyPrefs.device_consumption.length) { - view.cards!.push({ - title: "Monitor individual devices", - type: "energy-devices-graph", - prefs: energyPrefs, - }); - } - // Only include if we have a grid. if (hasGrid) { view.cards!.push({ @@ -96,6 +79,14 @@ export class EnergyStrategy { }); } + if (hasGrid || hasSolar) { + view.cards!.push({ + title: "Sources", + type: "energy-sources-table", + prefs: energyPrefs, + }); + } + // Only include if we have a solar source. if (hasSolar) { view.cards!.push({ @@ -123,6 +114,15 @@ export class EnergyStrategy { }); } + // Only include if we have at least 1 device in the config. + if (energyPrefs.device_consumption.length) { + view.cards!.push({ + title: "Monitor individual devices", + type: "energy-devices-graph", + prefs: energyPrefs, + }); + } + return view; } } diff --git a/src/panels/lovelace/views/hui-masonry-view.ts b/src/panels/lovelace/views/hui-masonry-view.ts index 5ce2edf13e..f8e7e1bbe8 100644 --- a/src/panels/lovelace/views/hui-masonry-view.ts +++ b/src/panels/lovelace/views/hui-masonry-view.ts @@ -64,6 +64,8 @@ export class MasonryView extends LitElement implements LovelaceViewElement { private _mqls?: MediaQueryList[]; + private _mqlListenerRef?: () => void; + public constructor() { super(); this.addEventListener("iron-resize", (ev: Event) => ev.stopPropagation()); @@ -77,8 +79,9 @@ export class MasonryView extends LitElement implements LovelaceViewElement { public disconnectedCallback() { super.disconnectedCallback(); this._mqls?.forEach((mql) => { - mql.removeListener(this._updateColumns); + mql.removeListener(this._mqlListenerRef!); }); + this._mqlListenerRef = undefined; this._mqls = undefined; } @@ -112,7 +115,10 @@ export class MasonryView extends LitElement implements LovelaceViewElement { private _initMqls() { this._mqls = [300, 600, 900, 1200].map((width) => { const mql = window.matchMedia(`(min-width: ${width}px)`); - mql.addListener(this._updateColumns.bind(this)); + if (!this._mqlListenerRef) { + this._mqlListenerRef = this._updateColumns.bind(this); + } + mql.addListener(this._mqlListenerRef); return mql; }); } diff --git a/src/panels/lovelace/views/hui-sidebar-view.ts b/src/panels/lovelace/views/hui-sidebar-view.ts index 79f57c2ea2..5792b52b2b 100644 --- a/src/panels/lovelace/views/hui-sidebar-view.ts +++ b/src/panels/lovelace/views/hui-sidebar-view.ts @@ -35,6 +35,24 @@ export class SideBarView extends LitElement implements LovelaceViewElement { @state() private _config?: LovelaceViewConfig; + private _mqlListenerRef?: () => void; + + private _mql?: MediaQueryList; + + public connectedCallback() { + super.connectedCallback(); + this._mql = window.matchMedia("(min-width: 760px)"); + this._mqlListenerRef = this._createCards.bind(this); + this._mql.addListener(this._mqlListenerRef); + } + + public disconnectedCallback() { + super.disconnectedCallback(); + this._mql?.removeListener(this._mqlListenerRef!); + this._mqlListenerRef = undefined; + this._mql = undefined; + } + public setConfig(config: LovelaceViewConfig): void { this._config = config; } @@ -96,8 +114,14 @@ export class SideBarView extends LitElement implements LovelaceViewElement { private _createCards(): void { const mainDiv = document.createElement("div"); mainDiv.id = "main"; - const sidebarDiv = document.createElement("div"); - sidebarDiv.id = "sidebar"; + + let sidebarDiv: HTMLDivElement; + if (this._mql?.matches) { + sidebarDiv = document.createElement("div"); + sidebarDiv.id = "sidebar"; + } else { + sidebarDiv = mainDiv; + } if (this.hasUpdated) { const oldMain = this.renderRoot.querySelector("#main"); @@ -177,15 +201,6 @@ export class SideBarView extends LitElement implements LovelaceViewElement { margin: var(--masonry-view-card-margin, 4px 4px 8px); } - @media (max-width: 760px) { - .container { - flex-direction: column; - } - #sidebar { - max-width: unset; - } - } - @media (max-width: 500px) { .container > div > * { margin-left: 0; From 2982adbfa73876fb8119339d8f84f4ff1850d190 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 30 Jul 2021 21:57:27 +0200 Subject: [PATCH 42/43] Fix return compensation not being negative (#9654) --- .../lovelace/cards/energy/hui-energy-sources-table-card.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 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 44f4336609..83252fe9fe 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 @@ -202,7 +202,7 @@ export class HuiEnergySourcesTableCard flow.stat_cost || this._energyInfo!.cost_sensors[flow.stat_energy_from]; const cost = cost_stat - ? calculateStatisticSumGrowth(this._stats![cost_stat]) + ? calculateStatisticSumGrowth(this._stats![cost_stat]) || 0 : null; if (cost !== null) { totalCost += cost; @@ -260,7 +260,8 @@ export class HuiEnergySourcesTableCard flow.stat_compensation || this._energyInfo!.cost_sensors[flow.stat_energy_to]; const cost = cost_stat - ? calculateStatisticSumGrowth(this._stats![cost_stat]) + ? (calculateStatisticSumGrowth(this._stats![cost_stat]) || + 0) * -1 : null; if (cost !== null) { totalCost += cost; From 539d2b880c70b661327b88ffe0b500c1ac91ca10 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 30 Jul 2021 22:14:21 +0200 Subject: [PATCH 43/43] Bumped version to 20210730.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e1ca544083..758a5198f2 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup( name="home-assistant-frontend", - version="20210729.0", + version="20210730.0", description="The Home Assistant frontend", url="https://github.com/home-assistant/frontend", author="The Home Assistant Authors",