mirror of
https://github.com/home-assistant/frontend.git
synced 2025-10-26 12:09:47 +00:00
Compare commits
25 Commits
cursor/ena
...
power-char
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b3b7e701a | ||
|
|
299c08c0ea | ||
|
|
2fd33069c1 | ||
|
|
a322182f45 | ||
|
|
9926730497 | ||
|
|
daec91bbe2 | ||
|
|
8dde4c9a21 | ||
|
|
24388e924f | ||
|
|
7a2826a580 | ||
|
|
c32e5a049a | ||
|
|
f294c1bae6 | ||
|
|
4aadd7ec71 | ||
|
|
0b71ae51b3 | ||
|
|
3301c00471 | ||
|
|
6b0a5d783b | ||
|
|
23e2f94d11 | ||
|
|
c250777858 | ||
|
|
c35d0da9bd | ||
|
|
794aa45a2b | ||
|
|
d0b85d0c0b | ||
|
|
f68d885dd8 | ||
|
|
2d602a13e0 | ||
|
|
9d78043915 | ||
|
|
18bcc1c4f9 | ||
|
|
14133e28ad |
6
.github/workflows/codeql-analysis.yml
vendored
6
.github/workflows/codeql-analysis.yml
vendored
@@ -36,14 +36,14 @@ jobs:
|
|||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6
|
uses: github/codeql-action/init@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
|
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6
|
uses: github/codeql-action/autobuild@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 https://git.io/JvXDl
|
# 📚 https://git.io/JvXDl
|
||||||
@@ -57,4 +57,4 @@ jobs:
|
|||||||
# make release
|
# make release
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6
|
uses: github/codeql-action/analyze@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8
|
||||||
|
|||||||
6
.github/workflows/release.yaml
vendored
6
.github/workflows/release.yaml
vendored
@@ -55,7 +55,7 @@ jobs:
|
|||||||
script/release
|
script/release
|
||||||
|
|
||||||
- name: Upload release assets
|
- name: Upload release assets
|
||||||
uses: softprops/action-gh-release@62c96d0c4e8a889135c1f3a25910db8dbe0e85f7 # v2.3.4
|
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
dist/*.whl
|
dist/*.whl
|
||||||
@@ -108,7 +108,7 @@ jobs:
|
|||||||
- name: Tar folder
|
- name: Tar folder
|
||||||
run: tar -czf landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz -C landing-page/dist .
|
run: tar -czf landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz -C landing-page/dist .
|
||||||
- name: Upload release asset
|
- name: Upload release asset
|
||||||
uses: softprops/action-gh-release@62c96d0c4e8a889135c1f3a25910db8dbe0e85f7 # v2.3.4
|
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
|
||||||
with:
|
with:
|
||||||
files: landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz
|
files: landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz
|
||||||
|
|
||||||
@@ -137,6 +137,6 @@ jobs:
|
|||||||
- name: Tar folder
|
- name: Tar folder
|
||||||
run: tar -czf hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz -C hassio/build .
|
run: tar -czf hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz -C hassio/build .
|
||||||
- name: Upload release asset
|
- name: Upload release asset
|
||||||
uses: softprops/action-gh-release@62c96d0c4e8a889135c1f3a25910db8dbe0e85f7 # v2.3.4
|
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
|
||||||
with:
|
with:
|
||||||
files: hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz
|
files: hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz
|
||||||
|
|||||||
22
package.json
22
package.json
@@ -37,15 +37,15 @@
|
|||||||
"@codemirror/view": "6.38.5",
|
"@codemirror/view": "6.38.5",
|
||||||
"@date-fns/tz": "1.4.1",
|
"@date-fns/tz": "1.4.1",
|
||||||
"@egjs/hammerjs": "2.0.17",
|
"@egjs/hammerjs": "2.0.17",
|
||||||
"@formatjs/intl-datetimeformat": "6.18.1",
|
"@formatjs/intl-datetimeformat": "6.18.2",
|
||||||
"@formatjs/intl-displaynames": "6.8.12",
|
"@formatjs/intl-displaynames": "6.8.13",
|
||||||
"@formatjs/intl-durationformat": "0.7.5",
|
"@formatjs/intl-durationformat": "0.7.6",
|
||||||
"@formatjs/intl-getcanonicallocales": "2.5.6",
|
"@formatjs/intl-getcanonicallocales": "2.5.6",
|
||||||
"@formatjs/intl-listformat": "7.7.12",
|
"@formatjs/intl-listformat": "7.7.13",
|
||||||
"@formatjs/intl-locale": "4.2.12",
|
"@formatjs/intl-locale": "4.2.13",
|
||||||
"@formatjs/intl-numberformat": "8.15.5",
|
"@formatjs/intl-numberformat": "8.15.6",
|
||||||
"@formatjs/intl-pluralrules": "5.4.5",
|
"@formatjs/intl-pluralrules": "5.4.6",
|
||||||
"@formatjs/intl-relativetimeformat": "11.4.12",
|
"@formatjs/intl-relativetimeformat": "11.4.13",
|
||||||
"@fullcalendar/core": "6.1.19",
|
"@fullcalendar/core": "6.1.19",
|
||||||
"@fullcalendar/daygrid": "6.1.19",
|
"@fullcalendar/daygrid": "6.1.19",
|
||||||
"@fullcalendar/interaction": "6.1.19",
|
"@fullcalendar/interaction": "6.1.19",
|
||||||
@@ -99,7 +99,7 @@
|
|||||||
"barcode-detector": "3.0.6",
|
"barcode-detector": "3.0.6",
|
||||||
"color-name": "2.0.2",
|
"color-name": "2.0.2",
|
||||||
"comlink": "4.4.2",
|
"comlink": "4.4.2",
|
||||||
"core-js": "3.45.1",
|
"core-js": "3.46.0",
|
||||||
"cropperjs": "1.6.2",
|
"cropperjs": "1.6.2",
|
||||||
"culori": "4.0.2",
|
"culori": "4.0.2",
|
||||||
"date-fns": "4.1.0",
|
"date-fns": "4.1.0",
|
||||||
@@ -114,7 +114,7 @@
|
|||||||
"hls.js": "1.6.13",
|
"hls.js": "1.6.13",
|
||||||
"home-assistant-js-websocket": "9.5.0",
|
"home-assistant-js-websocket": "9.5.0",
|
||||||
"idb-keyval": "6.2.2",
|
"idb-keyval": "6.2.2",
|
||||||
"intl-messageformat": "10.7.17",
|
"intl-messageformat": "10.7.18",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"leaflet": "1.9.4",
|
"leaflet": "1.9.4",
|
||||||
"leaflet-draw": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch",
|
"leaflet-draw": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch",
|
||||||
@@ -135,7 +135,7 @@
|
|||||||
"stacktrace-js": "2.0.2",
|
"stacktrace-js": "2.0.2",
|
||||||
"superstruct": "2.0.2",
|
"superstruct": "2.0.2",
|
||||||
"tinykeys": "3.0.0",
|
"tinykeys": "3.0.0",
|
||||||
"ua-parser-js": "2.0.5",
|
"ua-parser-js": "2.0.6",
|
||||||
"vue": "2.7.16",
|
"vue": "2.7.16",
|
||||||
"vue2-daterange-picker": "0.6.8",
|
"vue2-daterange-picker": "0.6.8",
|
||||||
"weekstart": "2.0.0",
|
"weekstart": "2.0.0",
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import "../ha-combo-box-item";
|
|||||||
import "../ha-generic-picker";
|
import "../ha-generic-picker";
|
||||||
import type { HaGenericPicker } from "../ha-generic-picker";
|
import type { HaGenericPicker } from "../ha-generic-picker";
|
||||||
import "../ha-icon-button";
|
import "../ha-icon-button";
|
||||||
import "../ha-input-helper-text";
|
|
||||||
import type {
|
import type {
|
||||||
PickerComboBoxItem,
|
PickerComboBoxItem,
|
||||||
PickerComboBoxSearchFn,
|
PickerComboBoxSearchFn,
|
||||||
@@ -477,6 +476,7 @@ export class HaStatisticPicker extends LitElement {
|
|||||||
.hideClearIcon=${this.hideClearIcon}
|
.hideClearIcon=${this.hideClearIcon}
|
||||||
.searchFn=${this._searchFn}
|
.searchFn=${this._searchFn}
|
||||||
.valueRenderer=${this._valueRenderer}
|
.valueRenderer=${this._valueRenderer}
|
||||||
|
.helper=${this.helper}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
>
|
>
|
||||||
</ha-generic-picker>
|
</ha-generic-picker>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { mdiPlus } from "@mdi/js";
|
import { mdiPlayBox, mdiPlus } from "@mdi/js";
|
||||||
import type { PropertyValues } from "lit";
|
import type { PropertyValues } from "lit";
|
||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
@@ -7,7 +7,10 @@ import { fireEvent } from "../../common/dom/fire_event";
|
|||||||
import { supportsFeature } from "../../common/entity/supports-feature";
|
import { supportsFeature } from "../../common/entity/supports-feature";
|
||||||
import { getSignedPath } from "../../data/auth";
|
import { getSignedPath } from "../../data/auth";
|
||||||
import type { MediaPickedEvent } from "../../data/media-player";
|
import type { MediaPickedEvent } from "../../data/media-player";
|
||||||
import { MediaPlayerEntityFeature } from "../../data/media-player";
|
import {
|
||||||
|
MediaClassBrowserSettings,
|
||||||
|
MediaPlayerEntityFeature,
|
||||||
|
} from "../../data/media-player";
|
||||||
import type { MediaSelector, MediaSelectorValue } from "../../data/selector";
|
import type { MediaSelector, MediaSelectorValue } from "../../data/selector";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import { brandsUrl, extractDomainFromBrandUrl } from "../../util/brands-url";
|
import { brandsUrl, extractDomainFromBrandUrl } from "../../util/brands-url";
|
||||||
@@ -17,8 +20,6 @@ import type { SchemaUnion } from "../ha-form/types";
|
|||||||
import { showMediaBrowserDialog } from "../media-player/show-media-browser-dialog";
|
import { showMediaBrowserDialog } from "../media-player/show-media-browser-dialog";
|
||||||
import { ensureArray } from "../../common/array/ensure-array";
|
import { ensureArray } from "../../common/array/ensure-array";
|
||||||
import "../ha-picture-upload";
|
import "../ha-picture-upload";
|
||||||
import "../chips/ha-chip-set";
|
|
||||||
import "../chips/ha-input-chip";
|
|
||||||
|
|
||||||
const MANUAL_SCHEMA = [
|
const MANUAL_SCHEMA = [
|
||||||
{ name: "media_content_id", required: false, selector: { text: {} } },
|
{ name: "media_content_id", required: false, selector: { text: {} } },
|
||||||
@@ -35,8 +36,7 @@ export class HaMediaSelector extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public selector!: MediaSelector;
|
@property({ attribute: false }) public selector!: MediaSelector;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false }) public value?: MediaSelectorValue;
|
||||||
public value?: MediaSelectorValue | MediaSelectorValue[];
|
|
||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@@ -52,9 +52,6 @@ export class HaMediaSelector extends LitElement {
|
|||||||
|
|
||||||
@state() private _thumbnailUrl?: string | null;
|
@state() private _thumbnailUrl?: string | null;
|
||||||
|
|
||||||
// For multiple selection mode, cache signed/rewritten URLs per thumbnail string
|
|
||||||
@state() private _thumbnailUrlMap: Record<string, string | null> = {};
|
|
||||||
|
|
||||||
private _contextEntities: string[] | undefined;
|
private _contextEntities: string[] | undefined;
|
||||||
|
|
||||||
private get _hasAccept(): boolean {
|
private get _hasAccept(): boolean {
|
||||||
@@ -62,15 +59,6 @@ export class HaMediaSelector extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
willUpdate(changedProps: PropertyValues<this>) {
|
willUpdate(changedProps: PropertyValues<this>) {
|
||||||
if (changedProps.has("selector") && this.value !== undefined) {
|
|
||||||
if (this.selector.media?.multiple && !Array.isArray(this.value)) {
|
|
||||||
this.value = [this.value];
|
|
||||||
fireEvent(this, "value-changed", { value: this.value });
|
|
||||||
} else if (!this.selector.media?.multiple && Array.isArray(this.value)) {
|
|
||||||
this.value = this.value[0];
|
|
||||||
fireEvent(this, "value-changed", { value: this.value });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (changedProps.has("context")) {
|
if (changedProps.has("context")) {
|
||||||
if (!this._hasAccept) {
|
if (!this._hasAccept) {
|
||||||
this._contextEntities = ensureArray(this.context?.filter_entity);
|
this._contextEntities = ensureArray(this.context?.filter_entity);
|
||||||
@@ -78,91 +66,32 @@ export class HaMediaSelector extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (changedProps.has("value")) {
|
if (changedProps.has("value")) {
|
||||||
if (this.selector.media?.multiple) {
|
const thumbnail = this.value?.metadata?.thumbnail;
|
||||||
const values = Array.isArray(this.value)
|
const oldThumbnail = (changedProps.get("value") as this["value"])
|
||||||
? this.value
|
?.metadata?.thumbnail;
|
||||||
: this.value
|
if (thumbnail === oldThumbnail) {
|
||||||
? [this.value]
|
return;
|
||||||
: [];
|
}
|
||||||
const seenThumbs = new Set<string>();
|
if (thumbnail && thumbnail.startsWith("/")) {
|
||||||
values.forEach((val) => {
|
this._thumbnailUrl = undefined;
|
||||||
const thumbnail = val.metadata?.thumbnail;
|
// Thumbnails served by local API require authentication
|
||||||
if (!thumbnail) {
|
getSignedPath(this.hass, thumbnail).then((signedPath) => {
|
||||||
return;
|
this._thumbnailUrl = signedPath.path;
|
||||||
}
|
|
||||||
seenThumbs.add(thumbnail);
|
|
||||||
// Only (re)compute if not cached yet
|
|
||||||
if (this._thumbnailUrlMap[thumbnail] !== undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (thumbnail.startsWith("/")) {
|
|
||||||
this._thumbnailUrlMap = {
|
|
||||||
...this._thumbnailUrlMap,
|
|
||||||
[thumbnail]: null,
|
|
||||||
};
|
|
||||||
getSignedPath(this.hass, thumbnail).then((signedPath) => {
|
|
||||||
// Avoid losing other keys
|
|
||||||
this._thumbnailUrlMap = {
|
|
||||||
...this._thumbnailUrlMap,
|
|
||||||
[thumbnail]: signedPath.path,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
} else if (thumbnail.startsWith("https://brands.home-assistant.io")) {
|
|
||||||
this._thumbnailUrlMap = {
|
|
||||||
...this._thumbnailUrlMap,
|
|
||||||
[thumbnail]: brandsUrl({
|
|
||||||
domain: extractDomainFromBrandUrl(thumbnail),
|
|
||||||
type: "icon",
|
|
||||||
useFallback: true,
|
|
||||||
darkOptimized: this.hass.themes?.darkMode,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
this._thumbnailUrlMap = {
|
|
||||||
...this._thumbnailUrlMap,
|
|
||||||
[thumbnail]: thumbnail,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
// Clean up thumbnails no longer present
|
} else if (
|
||||||
const newMap: Record<string, string | null> = {};
|
thumbnail &&
|
||||||
Object.keys(this._thumbnailUrlMap).forEach((key) => {
|
thumbnail.startsWith("https://brands.home-assistant.io")
|
||||||
if (seenThumbs.has(key)) {
|
) {
|
||||||
newMap[key] = this._thumbnailUrlMap[key];
|
// The backend is not aware of the theme used by the users,
|
||||||
}
|
// so we rewrite the URL to show a proper icon
|
||||||
|
this._thumbnailUrl = brandsUrl({
|
||||||
|
domain: extractDomainFromBrandUrl(thumbnail),
|
||||||
|
type: "icon",
|
||||||
|
useFallback: true,
|
||||||
|
darkOptimized: this.hass.themes?.darkMode,
|
||||||
});
|
});
|
||||||
this._thumbnailUrlMap = newMap;
|
|
||||||
} else {
|
} else {
|
||||||
const currVal = Array.isArray(this.value) ? this.value[0] : this.value;
|
this._thumbnailUrl = thumbnail;
|
||||||
const prevVal = Array.isArray(changedProps.get("value") as any)
|
|
||||||
? (changedProps.get("value") as MediaSelectorValue[])[0]
|
|
||||||
: (changedProps.get("value") as MediaSelectorValue);
|
|
||||||
const thumbnail = currVal?.metadata?.thumbnail;
|
|
||||||
const oldThumbnail = prevVal?.metadata?.thumbnail;
|
|
||||||
if (thumbnail === oldThumbnail) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (thumbnail && thumbnail.startsWith("/")) {
|
|
||||||
this._thumbnailUrl = undefined;
|
|
||||||
// Thumbnails served by local API require authentication
|
|
||||||
getSignedPath(this.hass, thumbnail).then((signedPath) => {
|
|
||||||
this._thumbnailUrl = signedPath.path;
|
|
||||||
});
|
|
||||||
} else if (
|
|
||||||
thumbnail &&
|
|
||||||
thumbnail.startsWith("https://brands.home-assistant.io")
|
|
||||||
) {
|
|
||||||
// The backend is not aware of the theme used by the users,
|
|
||||||
// so we rewrite the URL to show a proper icon
|
|
||||||
this._thumbnailUrl = brandsUrl({
|
|
||||||
domain: extractDomainFromBrandUrl(thumbnail),
|
|
||||||
type: "icon",
|
|
||||||
useFallback: true,
|
|
||||||
darkOptimized: this.hass.themes?.darkMode,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this._thumbnailUrl = thumbnail ?? undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,12 +106,7 @@ export class HaMediaSelector extends LitElement {
|
|||||||
(stateObj &&
|
(stateObj &&
|
||||||
supportsFeature(stateObj, MediaPlayerEntityFeature.BROWSE_MEDIA));
|
supportsFeature(stateObj, MediaPlayerEntityFeature.BROWSE_MEDIA));
|
||||||
|
|
||||||
const isMultiple = this.selector.media?.multiple === true;
|
if (this.selector.media?.image_upload && !this.value) {
|
||||||
|
|
||||||
if (
|
|
||||||
this.selector.media?.image_upload &&
|
|
||||||
(!this.value || (Array.isArray(this.value) && this.value.length === 0))
|
|
||||||
) {
|
|
||||||
return html`<ha-picture-upload
|
return html`<ha-picture-upload
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value=${null}
|
.value=${null}
|
||||||
@@ -224,47 +148,19 @@ export class HaMediaSelector extends LitElement {
|
|||||||
</ha-alert>
|
</ha-alert>
|
||||||
<ha-form
|
<ha-form
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.data=${Array.isArray(this.value)
|
.data=${this.value || EMPTY_FORM}
|
||||||
? this.value[0]
|
|
||||||
: this.value || EMPTY_FORM}
|
|
||||||
.schema=${MANUAL_SCHEMA}
|
.schema=${MANUAL_SCHEMA}
|
||||||
.computeLabel=${this._computeLabelCallback}
|
.computeLabel=${this._computeLabelCallback}
|
||||||
.computeHelper=${this._computeHelperCallback}
|
.computeHelper=${this._computeHelperCallback}
|
||||||
></ha-form>
|
></ha-form>
|
||||||
`
|
`
|
||||||
: html`
|
: html`<ha-card
|
||||||
${isMultiple && Array.isArray(this.value) && this.value.length
|
|
||||||
? html`
|
|
||||||
<ha-chip-set>
|
|
||||||
${this.value.map(
|
|
||||||
(item, idx) => html`
|
|
||||||
<ha-input-chip
|
|
||||||
selected
|
|
||||||
.idx=${idx}
|
|
||||||
@remove=${this._removeItem}
|
|
||||||
>${item.metadata?.title ||
|
|
||||||
item.media_content_id}</ha-input-chip
|
|
||||||
>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</ha-chip-set>
|
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
|
|
||||||
<ha-card
|
|
||||||
outlined
|
outlined
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
role="button"
|
role="button"
|
||||||
aria-label=${(() => {
|
aria-label=${!this.value?.media_content_id
|
||||||
const currVal = Array.isArray(this.value)
|
? this.hass.localize("ui.components.selectors.media.pick_media")
|
||||||
? this.value[this.value.length - 1]
|
: this.value.metadata?.title || this.value.media_content_id}
|
||||||
: this.value;
|
|
||||||
return !currVal?.media_content_id
|
|
||||||
? this.hass.localize(
|
|
||||||
"ui.components.selectors.media.pick_media"
|
|
||||||
)
|
|
||||||
: currVal.metadata?.title || currVal.media_content_id;
|
|
||||||
})()}
|
|
||||||
@click=${this._pickMedia}
|
@click=${this._pickMedia}
|
||||||
@keydown=${this._handleKeyDown}
|
@keydown=${this._handleKeyDown}
|
||||||
class=${this.disabled || (!entityId && !this._hasAccept)
|
class=${this.disabled || (!entityId && !this._hasAccept)
|
||||||
@@ -273,22 +169,14 @@ export class HaMediaSelector extends LitElement {
|
|||||||
>
|
>
|
||||||
<div class="content-container">
|
<div class="content-container">
|
||||||
<div class="thumbnail">
|
<div class="thumbnail">
|
||||||
${!isMultiple &&
|
${this.value?.metadata?.thumbnail
|
||||||
(Array.isArray(this.value) ? this.value[0] : this.value)
|
|
||||||
?.metadata?.thumbnail
|
|
||||||
? html`
|
? html`
|
||||||
<div
|
<div
|
||||||
class="${classMap({
|
class="${classMap({
|
||||||
"centered-image":
|
"centered-image":
|
||||||
!!(
|
!!this.value.metadata.media_class &&
|
||||||
Array.isArray(this.value)
|
|
||||||
? this.value[0]
|
|
||||||
: this.value
|
|
||||||
)!.metadata!.media_class &&
|
|
||||||
["app", "directory"].includes(
|
["app", "directory"].includes(
|
||||||
(Array.isArray(this.value)
|
this.value.metadata.media_class
|
||||||
? this.value[0]
|
|
||||||
: this.value)!.metadata!.media_class!
|
|
||||||
),
|
),
|
||||||
})}
|
})}
|
||||||
image"
|
image"
|
||||||
@@ -301,27 +189,32 @@ export class HaMediaSelector extends LitElement {
|
|||||||
<div class="icon-holder image">
|
<div class="icon-holder image">
|
||||||
<ha-svg-icon
|
<ha-svg-icon
|
||||||
class="folder"
|
class="folder"
|
||||||
.path=${mdiPlus}
|
.path=${!this.value?.media_content_id
|
||||||
|
? mdiPlus
|
||||||
|
: this.value?.metadata?.media_class
|
||||||
|
? MediaClassBrowserSettings[
|
||||||
|
this.value.metadata.media_class ===
|
||||||
|
"directory"
|
||||||
|
? this.value.metadata
|
||||||
|
.children_media_class ||
|
||||||
|
this.value.metadata.media_class
|
||||||
|
: this.value.metadata.media_class
|
||||||
|
].icon
|
||||||
|
: mdiPlayBox}
|
||||||
></ha-svg-icon>
|
></ha-svg-icon>
|
||||||
</div>
|
</div>
|
||||||
`}
|
`}
|
||||||
</div>
|
</div>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
${(() => {
|
${!this.value?.media_content_id
|
||||||
const currVal = Array.isArray(this.value)
|
? this.hass.localize(
|
||||||
? this.value[this.value.length - 1]
|
"ui.components.selectors.media.pick_media"
|
||||||
: this.value;
|
)
|
||||||
return !currVal?.media_content_id
|
: this.value.metadata?.title || this.value.media_content_id}
|
||||||
? this.hass.localize(
|
|
||||||
"ui.components.selectors.media.pick_media"
|
|
||||||
)
|
|
||||||
: currVal.metadata?.title || currVal.media_content_id;
|
|
||||||
})()}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ha-card>
|
</ha-card>
|
||||||
${this.selector.media?.clearable &&
|
${this.selector.media?.clearable
|
||||||
(Array.isArray(this.value) ? this.value.length : this.value)
|
|
||||||
? html`<div>
|
? html`<div>
|
||||||
<ha-button
|
<ha-button
|
||||||
appearance="plain"
|
appearance="plain"
|
||||||
@@ -334,8 +227,7 @@ export class HaMediaSelector extends LitElement {
|
|||||||
)}
|
)}
|
||||||
</ha-button>
|
</ha-button>
|
||||||
</div>`
|
</div>`
|
||||||
: nothing}
|
: nothing}`}
|
||||||
`}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,61 +268,41 @@ export class HaMediaSelector extends LitElement {
|
|||||||
showMediaBrowserDialog(this, {
|
showMediaBrowserDialog(this, {
|
||||||
action: "pick",
|
action: "pick",
|
||||||
entityId: this._getActiveEntityId(),
|
entityId: this._getActiveEntityId(),
|
||||||
navigateIds: (Array.isArray(this.value)
|
navigateIds: this.value?.metadata?.navigateIds,
|
||||||
? this.value[this.value.length - 1]
|
|
||||||
: this.value
|
|
||||||
)?.metadata?.navigateIds,
|
|
||||||
accept: this.selector.media?.accept,
|
accept: this.selector.media?.accept,
|
||||||
defaultId: Array.isArray(this.value)
|
defaultId: this.value?.media_content_id,
|
||||||
? this.value[this.value.length - 1]?.media_content_id
|
defaultType: this.value?.media_content_type,
|
||||||
: this.value?.media_content_id,
|
|
||||||
defaultType: Array.isArray(this.value)
|
|
||||||
? this.value[this.value.length - 1]?.media_content_type
|
|
||||||
: this.value?.media_content_type,
|
|
||||||
hideContentType: this.selector.media?.hide_content_type,
|
hideContentType: this.selector.media?.hide_content_type,
|
||||||
contentIdHelper: this.selector.media?.content_id_helper,
|
contentIdHelper: this.selector.media?.content_id_helper,
|
||||||
mediaPickedCallback: (pickedMedia: MediaPickedEvent) => {
|
mediaPickedCallback: (pickedMedia: MediaPickedEvent) => {
|
||||||
const newItem: MediaSelectorValue = {
|
fireEvent(this, "value-changed", {
|
||||||
...(Array.isArray(this.value) ? {} : (this.value as any)),
|
value: {
|
||||||
media_content_id: pickedMedia.item.media_content_id,
|
...this.value,
|
||||||
media_content_type: pickedMedia.item.media_content_type,
|
media_content_id: pickedMedia.item.media_content_id,
|
||||||
metadata: {
|
media_content_type: pickedMedia.item.media_content_type,
|
||||||
title: pickedMedia.item.title,
|
metadata: {
|
||||||
thumbnail: pickedMedia.item.thumbnail,
|
title: pickedMedia.item.title,
|
||||||
media_class: pickedMedia.item.media_class,
|
thumbnail: pickedMedia.item.thumbnail,
|
||||||
children_media_class: pickedMedia.item.children_media_class,
|
media_class: pickedMedia.item.media_class,
|
||||||
navigateIds: pickedMedia.navigateIds?.map((id) => ({
|
children_media_class: pickedMedia.item.children_media_class,
|
||||||
media_content_type: id.media_content_type,
|
navigateIds: pickedMedia.navigateIds?.map((id) => ({
|
||||||
media_content_id: id.media_content_id,
|
media_content_type: id.media_content_type,
|
||||||
})),
|
media_content_id: id.media_content_id,
|
||||||
...(!this._hasAccept && this.context?.filter_entity
|
})),
|
||||||
? { browse_entity_id: this._getActiveEntityId() }
|
...(!this._hasAccept && this.context?.filter_entity
|
||||||
: {}),
|
? { browse_entity_id: this._getActiveEntityId() }
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
if (this.selector.media?.multiple) {
|
|
||||||
const current = Array.isArray(this.value)
|
|
||||||
? this.value
|
|
||||||
: this.value
|
|
||||||
? [this.value]
|
|
||||||
: [];
|
|
||||||
fireEvent(this, "value-changed", {
|
|
||||||
value: [...current, newItem],
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fireEvent(this, "value-changed", { value: newItem });
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getActiveEntityId(): string | undefined {
|
private _getActiveEntityId(): string | undefined {
|
||||||
const val = Array.isArray(this.value)
|
const metaId = this.value?.metadata?.browse_entity_id;
|
||||||
? this.value[this.value.length - 1]
|
|
||||||
: this.value;
|
|
||||||
const metaId = val?.metadata?.browse_entity_id;
|
|
||||||
return (
|
return (
|
||||||
val?.entity_id ||
|
this.value?.entity_id ||
|
||||||
(metaId && this._contextEntities?.includes(metaId) && metaId) ||
|
(metaId && this._contextEntities?.includes(metaId) && metaId) ||
|
||||||
this._contextEntities?.[0]
|
this._contextEntities?.[0]
|
||||||
);
|
);
|
||||||
@@ -445,47 +317,27 @@ export class HaMediaSelector extends LitElement {
|
|||||||
|
|
||||||
private _pictureUploadMediaPicked(ev) {
|
private _pictureUploadMediaPicked(ev) {
|
||||||
const pickedMedia = ev.detail as MediaPickedEvent;
|
const pickedMedia = ev.detail as MediaPickedEvent;
|
||||||
const newItem: MediaSelectorValue = {
|
|
||||||
...(Array.isArray(this.value) ? {} : (this.value as any)),
|
|
||||||
media_content_id: pickedMedia.item.media_content_id,
|
|
||||||
media_content_type: pickedMedia.item.media_content_type,
|
|
||||||
metadata: {
|
|
||||||
title: pickedMedia.item.title,
|
|
||||||
thumbnail: pickedMedia.item.thumbnail,
|
|
||||||
media_class: pickedMedia.item.media_class,
|
|
||||||
children_media_class: pickedMedia.item.children_media_class,
|
|
||||||
navigateIds: pickedMedia.navigateIds?.map((id) => ({
|
|
||||||
media_content_type: id.media_content_type,
|
|
||||||
media_content_id: id.media_content_id,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
if (this.selector.media?.multiple) {
|
|
||||||
const current = Array.isArray(this.value)
|
|
||||||
? this.value
|
|
||||||
: this.value
|
|
||||||
? [this.value]
|
|
||||||
: [];
|
|
||||||
fireEvent(this, "value-changed", { value: [...current, newItem] });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fireEvent(this, "value-changed", { value: newItem });
|
|
||||||
}
|
|
||||||
|
|
||||||
private _clearValue() {
|
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
value: this.selector.media?.multiple ? [] : undefined,
|
value: {
|
||||||
|
...this.value,
|
||||||
|
media_content_id: pickedMedia.item.media_content_id,
|
||||||
|
media_content_type: pickedMedia.item.media_content_type,
|
||||||
|
metadata: {
|
||||||
|
title: pickedMedia.item.title,
|
||||||
|
thumbnail: pickedMedia.item.thumbnail,
|
||||||
|
media_class: pickedMedia.item.media_class,
|
||||||
|
children_media_class: pickedMedia.item.children_media_class,
|
||||||
|
navigateIds: pickedMedia.navigateIds?.map((id) => ({
|
||||||
|
media_content_type: id.media_content_type,
|
||||||
|
media_content_id: id.media_content_id,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _removeItem(ev: CustomEvent) {
|
private _clearValue() {
|
||||||
ev.stopPropagation();
|
fireEvent(this, "value-changed", { value: undefined });
|
||||||
if (!Array.isArray(this.value)) return;
|
|
||||||
const idx = (ev.currentTarget as any).idx as number;
|
|
||||||
if (idx === undefined) return;
|
|
||||||
const newValue = this.value.slice();
|
|
||||||
newValue.splice(idx, 1);
|
|
||||||
fireEvent(this, "value-changed", { value: newValue });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
@@ -497,9 +349,6 @@ export class HaMediaSelector extends LitElement {
|
|||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
ha-chip-set {
|
|
||||||
padding-bottom: 8px;
|
|
||||||
}
|
|
||||||
ha-card {
|
ha-card {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ export type EnergySolarForecasts = Record<string, EnergySolarForecast>;
|
|||||||
export interface DeviceConsumptionEnergyPreference {
|
export interface DeviceConsumptionEnergyPreference {
|
||||||
// This is an ever increasing value
|
// This is an ever increasing value
|
||||||
stat_consumption: string;
|
stat_consumption: string;
|
||||||
|
stat_power?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
included_in_stat?: string;
|
included_in_stat?: string;
|
||||||
}
|
}
|
||||||
@@ -130,11 +131,17 @@ export interface FlowToGridSourceEnergyPreference {
|
|||||||
number_energy_price: number | null;
|
number_energy_price: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GridPowerSourceEnergyPreference {
|
||||||
|
// W meter
|
||||||
|
stat_power: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface GridSourceTypeEnergyPreference {
|
export interface GridSourceTypeEnergyPreference {
|
||||||
type: "grid";
|
type: "grid";
|
||||||
|
|
||||||
flow_from: FlowFromGridSourceEnergyPreference[];
|
flow_from: FlowFromGridSourceEnergyPreference[];
|
||||||
flow_to: FlowToGridSourceEnergyPreference[];
|
flow_to: FlowToGridSourceEnergyPreference[];
|
||||||
|
power?: GridPowerSourceEnergyPreference[];
|
||||||
|
|
||||||
cost_adjustment_day: number;
|
cost_adjustment_day: number;
|
||||||
}
|
}
|
||||||
@@ -143,6 +150,7 @@ export interface SolarSourceTypeEnergyPreference {
|
|||||||
type: "solar";
|
type: "solar";
|
||||||
|
|
||||||
stat_energy_from: string;
|
stat_energy_from: string;
|
||||||
|
stat_power?: string;
|
||||||
config_entry_solar_forecast: string[] | null;
|
config_entry_solar_forecast: string[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,6 +158,7 @@ export interface BatterySourceTypeEnergyPreference {
|
|||||||
type: "battery";
|
type: "battery";
|
||||||
stat_energy_from: string;
|
stat_energy_from: string;
|
||||||
stat_energy_to: string;
|
stat_energy_to: string;
|
||||||
|
stat_power?: string;
|
||||||
}
|
}
|
||||||
export interface GasSourceTypeEnergyPreference {
|
export interface GasSourceTypeEnergyPreference {
|
||||||
type: "gas";
|
type: "gas";
|
||||||
@@ -351,6 +360,35 @@ export const getReferencedStatisticIds = (
|
|||||||
return statIDs;
|
return statIDs;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getReferencedStatisticIdsPower = (
|
||||||
|
prefs: EnergyPreferences
|
||||||
|
): string[] => {
|
||||||
|
const statIDs: (string | undefined)[] = [];
|
||||||
|
|
||||||
|
for (const source of prefs.energy_sources) {
|
||||||
|
if (source.type === "gas" || source.type === "water") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source.type === "solar") {
|
||||||
|
statIDs.push(source.stat_power);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source.type === "battery") {
|
||||||
|
statIDs.push(source.stat_power);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source.power) {
|
||||||
|
statIDs.push(...source.power.map((p) => p.stat_power));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
statIDs.push(...prefs.device_consumption.map((d) => d.stat_power));
|
||||||
|
|
||||||
|
return statIDs.filter(Boolean) as string[];
|
||||||
|
};
|
||||||
|
|
||||||
export const enum CompareMode {
|
export const enum CompareMode {
|
||||||
NONE = "",
|
NONE = "",
|
||||||
PREVIOUS = "previous",
|
PREVIOUS = "previous",
|
||||||
@@ -398,9 +436,10 @@ const getEnergyData = async (
|
|||||||
"gas",
|
"gas",
|
||||||
"device",
|
"device",
|
||||||
]);
|
]);
|
||||||
|
const powerStatIds = getReferencedStatisticIdsPower(prefs);
|
||||||
const waterStatIds = getReferencedStatisticIds(prefs, info, ["water"]);
|
const waterStatIds = getReferencedStatisticIds(prefs, info, ["water"]);
|
||||||
|
|
||||||
const allStatIDs = [...energyStatIds, ...waterStatIds];
|
const allStatIDs = [...energyStatIds, ...waterStatIds, ...powerStatIds];
|
||||||
|
|
||||||
const dayDifference = differenceInDays(end || new Date(), start);
|
const dayDifference = differenceInDays(end || new Date(), start);
|
||||||
const period =
|
const period =
|
||||||
@@ -411,6 +450,8 @@ const getEnergyData = async (
|
|||||||
: dayDifference > 2
|
: dayDifference > 2
|
||||||
? "day"
|
? "day"
|
||||||
: "hour";
|
: "hour";
|
||||||
|
const finePeriod =
|
||||||
|
dayDifference > 64 ? "day" : dayDifference > 8 ? "hour" : "5minute";
|
||||||
|
|
||||||
const statsMetadata: Record<string, StatisticsMetaData> = {};
|
const statsMetadata: Record<string, StatisticsMetaData> = {};
|
||||||
const statsMetadataArray = allStatIDs.length
|
const statsMetadataArray = allStatIDs.length
|
||||||
@@ -432,6 +473,9 @@ const getEnergyData = async (
|
|||||||
? (gasUnit as (typeof VOLUME_UNITS)[number])
|
? (gasUnit as (typeof VOLUME_UNITS)[number])
|
||||||
: undefined,
|
: undefined,
|
||||||
};
|
};
|
||||||
|
const powerUnits: StatisticsUnitConfiguration = {
|
||||||
|
power: "kW",
|
||||||
|
};
|
||||||
const waterUnit = getEnergyWaterUnit(hass, prefs, statsMetadata);
|
const waterUnit = getEnergyWaterUnit(hass, prefs, statsMetadata);
|
||||||
const waterUnits: StatisticsUnitConfiguration = {
|
const waterUnits: StatisticsUnitConfiguration = {
|
||||||
volume: waterUnit,
|
volume: waterUnit,
|
||||||
@@ -442,6 +486,12 @@ const getEnergyData = async (
|
|||||||
"change",
|
"change",
|
||||||
])
|
])
|
||||||
: {};
|
: {};
|
||||||
|
const _powerStats: Statistics | Promise<Statistics> = powerStatIds.length
|
||||||
|
? fetchStatistics(hass!, start, end, powerStatIds, finePeriod, powerUnits, [
|
||||||
|
"mean",
|
||||||
|
])
|
||||||
|
: {};
|
||||||
|
|
||||||
const _waterStats: Statistics | Promise<Statistics> = waterStatIds.length
|
const _waterStats: Statistics | Promise<Statistics> = waterStatIds.length
|
||||||
? fetchStatistics(hass!, start, end, waterStatIds, period, waterUnits, [
|
? fetchStatistics(hass!, start, end, waterStatIds, period, waterUnits, [
|
||||||
"change",
|
"change",
|
||||||
@@ -548,6 +598,7 @@ const getEnergyData = async (
|
|||||||
|
|
||||||
const [
|
const [
|
||||||
energyStats,
|
energyStats,
|
||||||
|
powerStats,
|
||||||
waterStats,
|
waterStats,
|
||||||
energyStatsCompare,
|
energyStatsCompare,
|
||||||
waterStatsCompare,
|
waterStatsCompare,
|
||||||
@@ -555,13 +606,14 @@ const getEnergyData = async (
|
|||||||
fossilEnergyConsumptionCompare,
|
fossilEnergyConsumptionCompare,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
_energyStats,
|
_energyStats,
|
||||||
|
_powerStats,
|
||||||
_waterStats,
|
_waterStats,
|
||||||
_energyStatsCompare,
|
_energyStatsCompare,
|
||||||
_waterStatsCompare,
|
_waterStatsCompare,
|
||||||
_fossilEnergyConsumption,
|
_fossilEnergyConsumption,
|
||||||
_fossilEnergyConsumptionCompare,
|
_fossilEnergyConsumptionCompare,
|
||||||
]);
|
]);
|
||||||
const stats = { ...energyStats, ...waterStats };
|
const stats = { ...energyStats, ...waterStats, ...powerStats };
|
||||||
if (compare) {
|
if (compare) {
|
||||||
statsCompare = { ...energyStatsCompare, ...waterStatsCompare };
|
statsCompare = { ...energyStatsCompare, ...waterStatsCompare };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -316,7 +316,6 @@ export interface MediaSelector {
|
|||||||
clearable?: boolean;
|
clearable?: boolean;
|
||||||
hide_content_type?: boolean;
|
hide_content_type?: boolean;
|
||||||
content_id_helper?: string;
|
content_id_helper?: string;
|
||||||
multiple?: boolean;
|
|
||||||
} | null;
|
} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import type {
|
|||||||
EnergySource,
|
EnergySource,
|
||||||
FlowFromGridSourceEnergyPreference,
|
FlowFromGridSourceEnergyPreference,
|
||||||
FlowToGridSourceEnergyPreference,
|
FlowToGridSourceEnergyPreference,
|
||||||
|
GridPowerSourceEnergyPreference,
|
||||||
GridSourceTypeEnergyPreference,
|
GridSourceTypeEnergyPreference,
|
||||||
} from "../../../../data/energy";
|
} from "../../../../data/energy";
|
||||||
import {
|
import {
|
||||||
@@ -47,6 +48,7 @@ import { documentationUrl } from "../../../../util/documentation-url";
|
|||||||
import {
|
import {
|
||||||
showEnergySettingsGridFlowFromDialog,
|
showEnergySettingsGridFlowFromDialog,
|
||||||
showEnergySettingsGridFlowToDialog,
|
showEnergySettingsGridFlowToDialog,
|
||||||
|
showEnergySettingsGridPowerDialog,
|
||||||
} from "../dialogs/show-dialogs-energy";
|
} from "../dialogs/show-dialogs-energy";
|
||||||
import "./ha-energy-validation-result";
|
import "./ha-energy-validation-result";
|
||||||
import { energyCardStyles } from "./styles";
|
import { energyCardStyles } from "./styles";
|
||||||
@@ -226,6 +228,58 @@ export class EnergyGridSettings extends LitElement {
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h3>
|
||||||
|
${this.hass.localize("ui.panel.config.energy.grid.grid_power")}
|
||||||
|
</h3>
|
||||||
|
${gridSource.power?.map((power) => {
|
||||||
|
const entityState = this.hass.states[power.stat_power];
|
||||||
|
return html`
|
||||||
|
<div class="row" .source=${power}>
|
||||||
|
${entityState?.attributes.icon
|
||||||
|
? html`<ha-icon
|
||||||
|
.icon=${entityState.attributes.icon}
|
||||||
|
></ha-icon>`
|
||||||
|
: html`<ha-svg-icon
|
||||||
|
.path=${mdiTransmissionTower}
|
||||||
|
></ha-svg-icon>`}
|
||||||
|
<span class="content"
|
||||||
|
>${getStatisticLabel(
|
||||||
|
this.hass,
|
||||||
|
power.stat_power,
|
||||||
|
this.statsMetadata?.[power.stat_power]
|
||||||
|
)}</span
|
||||||
|
>
|
||||||
|
<ha-icon-button
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.energy.grid.edit_power"
|
||||||
|
)}
|
||||||
|
@click=${this._editPowerSource}
|
||||||
|
.path=${mdiPencil}
|
||||||
|
></ha-icon-button>
|
||||||
|
<ha-icon-button
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.energy.grid.delete_power"
|
||||||
|
)}
|
||||||
|
@click=${this._deletePowerSource}
|
||||||
|
.path=${mdiDelete}
|
||||||
|
></ha-icon-button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
})}
|
||||||
|
<div class="row border-bottom">
|
||||||
|
<ha-svg-icon .path=${mdiTransmissionTower}></ha-svg-icon>
|
||||||
|
<ha-button
|
||||||
|
@click=${this._addPowerSource}
|
||||||
|
appearance="filled"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<ha-svg-icon .path=${mdiPlus} slot="start"></ha-svg-icon
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.energy.grid.add_power"
|
||||||
|
)}</ha-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3>
|
<h3>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.energy.grid.grid_carbon_footprint"
|
"ui.panel.config.energy.grid.grid_carbon_footprint"
|
||||||
@@ -499,6 +553,97 @@ export class EnergyGridSettings extends LitElement {
|
|||||||
await this._savePreferences(cleanedPreferences);
|
await this._savePreferences(cleanedPreferences);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _addPowerSource() {
|
||||||
|
const gridSource = this.preferences.energy_sources.find(
|
||||||
|
(src) => src.type === "grid"
|
||||||
|
) as GridSourceTypeEnergyPreference | undefined;
|
||||||
|
showEnergySettingsGridPowerDialog(this, {
|
||||||
|
grid_source: gridSource,
|
||||||
|
saveCallback: async (power) => {
|
||||||
|
let preferences: EnergyPreferences;
|
||||||
|
if (!gridSource) {
|
||||||
|
preferences = {
|
||||||
|
...this.preferences,
|
||||||
|
energy_sources: [
|
||||||
|
...this.preferences.energy_sources,
|
||||||
|
{
|
||||||
|
...emptyGridSourceEnergyPreference(),
|
||||||
|
power: [power],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
preferences = {
|
||||||
|
...this.preferences,
|
||||||
|
energy_sources: this.preferences.energy_sources.map((src) =>
|
||||||
|
src.type === "grid"
|
||||||
|
? { ...src, power: [...(gridSource.power || []), power] }
|
||||||
|
: src
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
await this._savePreferences(preferences);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _editPowerSource(ev) {
|
||||||
|
const origSource: GridPowerSourceEnergyPreference =
|
||||||
|
ev.currentTarget.closest(".row").source;
|
||||||
|
const gridSource = this.preferences.energy_sources.find(
|
||||||
|
(src) => src.type === "grid"
|
||||||
|
) as GridSourceTypeEnergyPreference | undefined;
|
||||||
|
showEnergySettingsGridPowerDialog(this, {
|
||||||
|
source: { ...origSource },
|
||||||
|
grid_source: gridSource,
|
||||||
|
saveCallback: async (source) => {
|
||||||
|
const power =
|
||||||
|
energySourcesByType(this.preferences).grid![0].power || [];
|
||||||
|
|
||||||
|
const preferences: EnergyPreferences = {
|
||||||
|
...this.preferences,
|
||||||
|
energy_sources: this.preferences.energy_sources.map((src) =>
|
||||||
|
src.type === "grid"
|
||||||
|
? {
|
||||||
|
...src,
|
||||||
|
power: power.map((p) => (p === origSource ? source : p)),
|
||||||
|
}
|
||||||
|
: src
|
||||||
|
),
|
||||||
|
};
|
||||||
|
await this._savePreferences(preferences);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _deletePowerSource(ev) {
|
||||||
|
const sourceToDelete: GridPowerSourceEnergyPreference =
|
||||||
|
ev.currentTarget.closest(".row").source;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!(await showConfirmationDialog(this, {
|
||||||
|
title: this.hass.localize("ui.panel.config.energy.delete_source"),
|
||||||
|
}))
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const power =
|
||||||
|
energySourcesByType(this.preferences).grid![0].power?.filter(
|
||||||
|
(p) => p !== sourceToDelete
|
||||||
|
) || [];
|
||||||
|
|
||||||
|
const preferences: EnergyPreferences = {
|
||||||
|
...this.preferences,
|
||||||
|
energy_sources: this.preferences.energy_sources.map((source) =>
|
||||||
|
source.type === "grid" ? { ...source, power } : source
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const cleanedPreferences = this._removeEmptySources(preferences);
|
||||||
|
await this._savePreferences(cleanedPreferences);
|
||||||
|
}
|
||||||
|
|
||||||
private _removeEmptySources(preferences: EnergyPreferences) {
|
private _removeEmptySources(preferences: EnergyPreferences) {
|
||||||
// Check if grid sources became an empty type and remove if so
|
// Check if grid sources became an empty type and remove if so
|
||||||
preferences.energy_sources = preferences.energy_sources.reduce<
|
preferences.energy_sources = preferences.energy_sources.reduce<
|
||||||
@@ -507,7 +652,8 @@ export class EnergyGridSettings extends LitElement {
|
|||||||
if (
|
if (
|
||||||
source.type !== "grid" ||
|
source.type !== "grid" ||
|
||||||
source.flow_from.length > 0 ||
|
source.flow_from.length > 0 ||
|
||||||
source.flow_to.length > 0
|
source.flow_to.length > 0 ||
|
||||||
|
(source.power && source.power.length > 0)
|
||||||
) {
|
) {
|
||||||
acc.push(source);
|
acc.push(source);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import type { HomeAssistant } from "../../../../types";
|
|||||||
import type { EnergySettingsBatteryDialogParams } from "./show-dialogs-energy";
|
import type { EnergySettingsBatteryDialogParams } from "./show-dialogs-energy";
|
||||||
|
|
||||||
const energyUnitClasses = ["energy"];
|
const energyUnitClasses = ["energy"];
|
||||||
|
const powerUnitClasses = ["power"];
|
||||||
|
|
||||||
@customElement("dialog-energy-battery-settings")
|
@customElement("dialog-energy-battery-settings")
|
||||||
export class DialogEnergyBatterySettings
|
export class DialogEnergyBatterySettings
|
||||||
@@ -32,10 +33,14 @@ export class DialogEnergyBatterySettings
|
|||||||
|
|
||||||
@state() private _energy_units?: string[];
|
@state() private _energy_units?: string[];
|
||||||
|
|
||||||
|
@state() private _power_units?: string[];
|
||||||
|
|
||||||
@state() private _error?: string;
|
@state() private _error?: string;
|
||||||
|
|
||||||
private _excludeList?: string[];
|
private _excludeList?: string[];
|
||||||
|
|
||||||
|
private _excludeListPower?: string[];
|
||||||
|
|
||||||
public async showDialog(
|
public async showDialog(
|
||||||
params: EnergySettingsBatteryDialogParams
|
params: EnergySettingsBatteryDialogParams
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
@@ -46,6 +51,9 @@ export class DialogEnergyBatterySettings
|
|||||||
this._energy_units = (
|
this._energy_units = (
|
||||||
await getSensorDeviceClassConvertibleUnits(this.hass, "energy")
|
await getSensorDeviceClassConvertibleUnits(this.hass, "energy")
|
||||||
).units;
|
).units;
|
||||||
|
this._power_units = (
|
||||||
|
await getSensorDeviceClassConvertibleUnits(this.hass, "power")
|
||||||
|
).units;
|
||||||
const allSources: string[] = [];
|
const allSources: string[] = [];
|
||||||
this._params.battery_sources.forEach((entry) => {
|
this._params.battery_sources.forEach((entry) => {
|
||||||
allSources.push(entry.stat_energy_from);
|
allSources.push(entry.stat_energy_from);
|
||||||
@@ -56,6 +64,9 @@ export class DialogEnergyBatterySettings
|
|||||||
id !== this._source?.stat_energy_from &&
|
id !== this._source?.stat_energy_from &&
|
||||||
id !== this._source?.stat_energy_to
|
id !== this._source?.stat_energy_to
|
||||||
);
|
);
|
||||||
|
this._excludeListPower = this._params.battery_sources
|
||||||
|
.map((entry) => entry.stat_power)
|
||||||
|
.filter((id) => id && id !== this._source?.stat_power) as string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
public closeDialog() {
|
public closeDialog() {
|
||||||
@@ -72,8 +83,6 @@ export class DialogEnergyBatterySettings
|
|||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pickableUnit = this._energy_units?.join(", ") || "";
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-dialog
|
<ha-dialog
|
||||||
open
|
open
|
||||||
@@ -85,12 +94,6 @@ export class DialogEnergyBatterySettings
|
|||||||
@closed=${this.closeDialog}
|
@closed=${this.closeDialog}
|
||||||
>
|
>
|
||||||
${this._error ? html`<p class="error">${this._error}</p>` : ""}
|
${this._error ? html`<p class="error">${this._error}</p>` : ""}
|
||||||
<div>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.energy.battery.dialog.entity_para",
|
|
||||||
{ unit: pickableUnit }
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ha-statistic-picker
|
<ha-statistic-picker
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@@ -105,6 +108,10 @@ export class DialogEnergyBatterySettings
|
|||||||
this._source.stat_energy_from,
|
this._source.stat_energy_from,
|
||||||
]}
|
]}
|
||||||
@value-changed=${this._statisticToChanged}
|
@value-changed=${this._statisticToChanged}
|
||||||
|
.helper=${this.hass.localize(
|
||||||
|
"ui.panel.config.energy.battery.dialog.energy_helper_into",
|
||||||
|
{ unit: this._energy_units?.join(", ") || "" }
|
||||||
|
)}
|
||||||
dialogInitialFocus
|
dialogInitialFocus
|
||||||
></ha-statistic-picker>
|
></ha-statistic-picker>
|
||||||
|
|
||||||
@@ -121,6 +128,25 @@ export class DialogEnergyBatterySettings
|
|||||||
this._source.stat_energy_to,
|
this._source.stat_energy_to,
|
||||||
]}
|
]}
|
||||||
@value-changed=${this._statisticFromChanged}
|
@value-changed=${this._statisticFromChanged}
|
||||||
|
.helper=${this.hass.localize(
|
||||||
|
"ui.panel.config.energy.battery.dialog.energy_helper_out",
|
||||||
|
{ unit: this._energy_units?.join(", ") || "" }
|
||||||
|
)}
|
||||||
|
></ha-statistic-picker>
|
||||||
|
|
||||||
|
<ha-statistic-picker
|
||||||
|
.hass=${this.hass}
|
||||||
|
.includeUnitClass=${powerUnitClasses}
|
||||||
|
.value=${this._source.stat_power}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.energy.battery.dialog.power"
|
||||||
|
)}
|
||||||
|
.excludeStatistics=${this._excludeListPower}
|
||||||
|
@value-changed=${this._powerChanged}
|
||||||
|
.helper=${this.hass.localize(
|
||||||
|
"ui.panel.config.energy.battery.dialog.power_helper",
|
||||||
|
{ unit: this._power_units?.join(", ") || "" }
|
||||||
|
)}
|
||||||
></ha-statistic-picker>
|
></ha-statistic-picker>
|
||||||
|
|
||||||
<ha-button
|
<ha-button
|
||||||
@@ -150,6 +176,10 @@ export class DialogEnergyBatterySettings
|
|||||||
this._source = { ...this._source!, stat_energy_from: ev.detail.value };
|
this._source = { ...this._source!, stat_energy_from: ev.detail.value };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _powerChanged(ev: CustomEvent<{ value: string }>) {
|
||||||
|
this._source = { ...this._source!, stat_power: ev.detail.value };
|
||||||
|
}
|
||||||
|
|
||||||
private async _save() {
|
private async _save() {
|
||||||
try {
|
try {
|
||||||
await this._params!.saveCallback(this._source!);
|
await this._params!.saveCallback(this._source!);
|
||||||
@@ -168,7 +198,11 @@ export class DialogEnergyBatterySettings
|
|||||||
--mdc-dialog-max-width: 430px;
|
--mdc-dialog-max-width: 430px;
|
||||||
}
|
}
|
||||||
ha-statistic-picker {
|
ha-statistic-picker {
|
||||||
width: 100%;
|
display: block;
|
||||||
|
margin-bottom: var(--ha-space-4);
|
||||||
|
}
|
||||||
|
ha-statistic-picker:last-of-type {
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import type { HomeAssistant } from "../../../../types";
|
|||||||
import type { EnergySettingsDeviceDialogParams } from "./show-dialogs-energy";
|
import type { EnergySettingsDeviceDialogParams } from "./show-dialogs-energy";
|
||||||
|
|
||||||
const energyUnitClasses = ["energy"];
|
const energyUnitClasses = ["energy"];
|
||||||
|
const powerUnitClasses = ["power"];
|
||||||
|
|
||||||
@customElement("dialog-energy-device-settings")
|
@customElement("dialog-energy-device-settings")
|
||||||
export class DialogEnergyDeviceSettings
|
export class DialogEnergyDeviceSettings
|
||||||
@@ -35,10 +36,14 @@ export class DialogEnergyDeviceSettings
|
|||||||
|
|
||||||
@state() private _energy_units?: string[];
|
@state() private _energy_units?: string[];
|
||||||
|
|
||||||
|
@state() private _power_units?: string[];
|
||||||
|
|
||||||
@state() private _error?: string;
|
@state() private _error?: string;
|
||||||
|
|
||||||
private _excludeList?: string[];
|
private _excludeList?: string[];
|
||||||
|
|
||||||
|
private _excludeListPower?: string[];
|
||||||
|
|
||||||
private _possibleParents: DeviceConsumptionEnergyPreference[] = [];
|
private _possibleParents: DeviceConsumptionEnergyPreference[] = [];
|
||||||
|
|
||||||
public async showDialog(
|
public async showDialog(
|
||||||
@@ -50,9 +55,15 @@ export class DialogEnergyDeviceSettings
|
|||||||
this._energy_units = (
|
this._energy_units = (
|
||||||
await getSensorDeviceClassConvertibleUnits(this.hass, "energy")
|
await getSensorDeviceClassConvertibleUnits(this.hass, "energy")
|
||||||
).units;
|
).units;
|
||||||
|
this._power_units = (
|
||||||
|
await getSensorDeviceClassConvertibleUnits(this.hass, "power")
|
||||||
|
).units;
|
||||||
this._excludeList = this._params.device_consumptions
|
this._excludeList = this._params.device_consumptions
|
||||||
.map((entry) => entry.stat_consumption)
|
.map((entry) => entry.stat_consumption)
|
||||||
.filter((id) => id !== this._device?.stat_consumption);
|
.filter((id) => id !== this._device?.stat_consumption);
|
||||||
|
this._excludeListPower = this._params.device_consumptions
|
||||||
|
.map((entry) => entry.stat_power)
|
||||||
|
.filter((id) => id && id !== this._device?.stat_power) as string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
private _computePossibleParents() {
|
private _computePossibleParents() {
|
||||||
@@ -93,8 +104,6 @@ export class DialogEnergyDeviceSettings
|
|||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pickableUnit = this._energy_units?.join(", ") || "";
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-dialog
|
<ha-dialog
|
||||||
open
|
open
|
||||||
@@ -108,12 +117,6 @@ export class DialogEnergyDeviceSettings
|
|||||||
@closed=${this.closeDialog}
|
@closed=${this.closeDialog}
|
||||||
>
|
>
|
||||||
${this._error ? html`<p class="error">${this._error}</p>` : ""}
|
${this._error ? html`<p class="error">${this._error}</p>` : ""}
|
||||||
<div>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.energy.device_consumption.dialog.selected_stat_intro",
|
|
||||||
{ unit: pickableUnit }
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ha-statistic-picker
|
<ha-statistic-picker
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@@ -125,9 +128,28 @@ export class DialogEnergyDeviceSettings
|
|||||||
)}
|
)}
|
||||||
.excludeStatistics=${this._excludeList}
|
.excludeStatistics=${this._excludeList}
|
||||||
@value-changed=${this._statisticChanged}
|
@value-changed=${this._statisticChanged}
|
||||||
|
.helper=${this.hass.localize(
|
||||||
|
"ui.panel.config.energy.device_consumption.dialog.selected_stat_intro",
|
||||||
|
{ unit: this._energy_units?.join(", ") || "" }
|
||||||
|
)}
|
||||||
dialogInitialFocus
|
dialogInitialFocus
|
||||||
></ha-statistic-picker>
|
></ha-statistic-picker>
|
||||||
|
|
||||||
|
<ha-statistic-picker
|
||||||
|
.hass=${this.hass}
|
||||||
|
.includeUnitClass=${powerUnitClasses}
|
||||||
|
.value=${this._device?.stat_power}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.energy.device_consumption.dialog.device_consumption_power"
|
||||||
|
)}
|
||||||
|
.excludeStatistics=${this._excludeListPower}
|
||||||
|
@value-changed=${this._powerStatisticChanged}
|
||||||
|
.helper=${this.hass.localize(
|
||||||
|
"ui.panel.config.energy.device_consumption.dialog.selected_stat_intro",
|
||||||
|
{ unit: this._power_units?.join(", ") || "" }
|
||||||
|
)}
|
||||||
|
></ha-statistic-picker>
|
||||||
|
|
||||||
<ha-textfield
|
<ha-textfield
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.config.energy.device_consumption.dialog.display_name"
|
"ui.panel.config.energy.device_consumption.dialog.display_name"
|
||||||
@@ -210,6 +232,20 @@ export class DialogEnergyDeviceSettings
|
|||||||
this._computePossibleParents();
|
this._computePossibleParents();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _powerStatisticChanged(ev: CustomEvent<{ value: string }>) {
|
||||||
|
if (!this._device) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newDevice = {
|
||||||
|
...this._device,
|
||||||
|
stat_power: ev.detail.value,
|
||||||
|
} as DeviceConsumptionEnergyPreference;
|
||||||
|
if (!newDevice.stat_power) {
|
||||||
|
delete newDevice.stat_power;
|
||||||
|
}
|
||||||
|
this._device = newDevice;
|
||||||
|
}
|
||||||
|
|
||||||
private _nameChanged(ev) {
|
private _nameChanged(ev) {
|
||||||
const newDevice = {
|
const newDevice = {
|
||||||
...this._device!,
|
...this._device!,
|
||||||
@@ -245,15 +281,19 @@ export class DialogEnergyDeviceSettings
|
|||||||
return [
|
return [
|
||||||
haStyleDialog,
|
haStyleDialog,
|
||||||
css`
|
css`
|
||||||
|
ha-statistic-picker {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: var(--ha-space-2);
|
||||||
|
}
|
||||||
ha-statistic-picker {
|
ha-statistic-picker {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
ha-select {
|
ha-select {
|
||||||
margin-top: 16px;
|
margin-top: var(--ha-space-4);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
ha-textfield {
|
ha-textfield {
|
||||||
margin-top: 16px;
|
margin-top: var(--ha-space-4);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
|||||||
@@ -115,8 +115,6 @@ export class DialogEnergyGridFlowSettings
|
|||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pickableUnit = this._energy_units?.join(", ") || "";
|
|
||||||
|
|
||||||
const unitPriceSensor = this._pickedDisplayUnit
|
const unitPriceSensor = this._pickedDisplayUnit
|
||||||
? `${this.hass.config.currency}/${this._pickedDisplayUnit}`
|
? `${this.hass.config.currency}/${this._pickedDisplayUnit}`
|
||||||
: undefined;
|
: undefined;
|
||||||
@@ -150,19 +148,11 @@ export class DialogEnergyGridFlowSettings
|
|||||||
@closed=${this.closeDialog}
|
@closed=${this.closeDialog}
|
||||||
>
|
>
|
||||||
${this._error ? html`<p class="error">${this._error}</p>` : ""}
|
${this._error ? html`<p class="error">${this._error}</p>` : ""}
|
||||||
<div>
|
<p>
|
||||||
<p>
|
${this.hass.localize(
|
||||||
${this.hass.localize(
|
`ui.panel.config.energy.grid.flow_dialog.${this._params.direction}.paragraph`
|
||||||
`ui.panel.config.energy.grid.flow_dialog.${this._params.direction}.paragraph`
|
)}
|
||||||
)}
|
</p>
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
${this.hass.localize(
|
|
||||||
`ui.panel.config.energy.grid.flow_dialog.${this._params.direction}.entity_para`,
|
|
||||||
{ unit: pickableUnit }
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ha-statistic-picker
|
<ha-statistic-picker
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@@ -178,6 +168,10 @@ export class DialogEnergyGridFlowSettings
|
|||||||
)}
|
)}
|
||||||
.excludeStatistics=${this._excludeList}
|
.excludeStatistics=${this._excludeList}
|
||||||
@value-changed=${this._statisticChanged}
|
@value-changed=${this._statisticChanged}
|
||||||
|
.helper=${this.hass.localize(
|
||||||
|
`ui.panel.config.energy.grid.flow_dialog.${this._params.direction}.entity_para`,
|
||||||
|
{ unit: this._energy_units?.join(", ") || "" }
|
||||||
|
)}
|
||||||
dialogInitialFocus
|
dialogInitialFocus
|
||||||
></ha-statistic-picker>
|
></ha-statistic-picker>
|
||||||
|
|
||||||
@@ -380,6 +374,10 @@ export class DialogEnergyGridFlowSettings
|
|||||||
ha-dialog {
|
ha-dialog {
|
||||||
--mdc-dialog-max-width: 430px;
|
--mdc-dialog-max-width: 430px;
|
||||||
}
|
}
|
||||||
|
ha-statistic-picker {
|
||||||
|
display: block;
|
||||||
|
margin: var(--ha-space-4) 0;
|
||||||
|
}
|
||||||
ha-formfield {
|
ha-formfield {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
import { mdiTransmissionTower } from "@mdi/js";
|
||||||
|
import type { CSSResultGroup } from "lit";
|
||||||
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import "../../../../components/entity/ha-statistic-picker";
|
||||||
|
import "../../../../components/ha-dialog";
|
||||||
|
import "../../../../components/ha-button";
|
||||||
|
import type { GridPowerSourceEnergyPreference } from "../../../../data/energy";
|
||||||
|
import { energyStatisticHelpUrl } from "../../../../data/energy";
|
||||||
|
import { getSensorDeviceClassConvertibleUnits } from "../../../../data/sensor";
|
||||||
|
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
||||||
|
import { haStyleDialog } from "../../../../resources/styles";
|
||||||
|
import type { HomeAssistant } from "../../../../types";
|
||||||
|
import type { EnergySettingsGridPowerDialogParams } from "./show-dialogs-energy";
|
||||||
|
|
||||||
|
const powerUnitClasses = ["power"];
|
||||||
|
|
||||||
|
@customElement("dialog-energy-grid-power-settings")
|
||||||
|
export class DialogEnergyGridPowerSettings
|
||||||
|
extends LitElement
|
||||||
|
implements HassDialog<EnergySettingsGridPowerDialogParams>
|
||||||
|
{
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@state() private _params?: EnergySettingsGridPowerDialogParams;
|
||||||
|
|
||||||
|
@state() private _source?: GridPowerSourceEnergyPreference;
|
||||||
|
|
||||||
|
@state() private _power_units?: string[];
|
||||||
|
|
||||||
|
@state() private _error?: string;
|
||||||
|
|
||||||
|
private _excludeListPower?: string[];
|
||||||
|
|
||||||
|
public async showDialog(
|
||||||
|
params: EnergySettingsGridPowerDialogParams
|
||||||
|
): Promise<void> {
|
||||||
|
this._params = params;
|
||||||
|
this._source = params.source ? { ...params.source } : { stat_power: "" };
|
||||||
|
|
||||||
|
const initialSourceIdPower = this._source.stat_power;
|
||||||
|
|
||||||
|
this._power_units = (
|
||||||
|
await getSensorDeviceClassConvertibleUnits(this.hass, "power")
|
||||||
|
).units;
|
||||||
|
|
||||||
|
this._excludeListPower = [
|
||||||
|
...(this._params.grid_source?.power?.map((entry) => entry.stat_power) ||
|
||||||
|
[]),
|
||||||
|
].filter((id) => id && id !== initialSourceIdPower) as string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
public closeDialog() {
|
||||||
|
this._params = undefined;
|
||||||
|
this._source = undefined;
|
||||||
|
this._error = undefined;
|
||||||
|
this._excludeListPower = undefined;
|
||||||
|
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (!this._params || !this._source) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-dialog
|
||||||
|
open
|
||||||
|
.heading=${html`<ha-svg-icon
|
||||||
|
.path=${mdiTransmissionTower}
|
||||||
|
style="--mdc-icon-size: 32px;"
|
||||||
|
></ha-svg-icon
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.energy.grid.power_dialog.header"
|
||||||
|
)}`}
|
||||||
|
@closed=${this.closeDialog}
|
||||||
|
>
|
||||||
|
${this._error ? html`<p class="error">${this._error}</p>` : ""}
|
||||||
|
|
||||||
|
<ha-statistic-picker
|
||||||
|
.hass=${this.hass}
|
||||||
|
.helpMissingEntityUrl=${energyStatisticHelpUrl}
|
||||||
|
.includeUnitClass=${powerUnitClasses}
|
||||||
|
.value=${this._source.stat_power}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.energy.grid.power_dialog.power_stat"
|
||||||
|
)}
|
||||||
|
.excludeStatistics=${this._excludeListPower}
|
||||||
|
@value-changed=${this._powerStatisticChanged}
|
||||||
|
.helper=${this.hass.localize(
|
||||||
|
"ui.panel.config.energy.grid.power_dialog.power_helper",
|
||||||
|
{ unit: this._power_units?.join(", ") || "" }
|
||||||
|
)}
|
||||||
|
dialogInitialFocus
|
||||||
|
></ha-statistic-picker>
|
||||||
|
|
||||||
|
<ha-button
|
||||||
|
appearance="plain"
|
||||||
|
@click=${this.closeDialog}
|
||||||
|
slot="primaryAction"
|
||||||
|
>
|
||||||
|
${this.hass.localize("ui.common.cancel")}
|
||||||
|
</ha-button>
|
||||||
|
<ha-button
|
||||||
|
@click=${this._save}
|
||||||
|
.disabled=${!this._source.stat_power}
|
||||||
|
slot="primaryAction"
|
||||||
|
>
|
||||||
|
${this.hass.localize("ui.common.save")}
|
||||||
|
</ha-button>
|
||||||
|
</ha-dialog>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _powerStatisticChanged(ev: CustomEvent<{ value: string }>) {
|
||||||
|
this._source = {
|
||||||
|
...this._source!,
|
||||||
|
stat_power: ev.detail.value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _save() {
|
||||||
|
try {
|
||||||
|
await this._params!.saveCallback(this._source!);
|
||||||
|
this.closeDialog();
|
||||||
|
} catch (err: any) {
|
||||||
|
this._error = err.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return [
|
||||||
|
haStyleDialog,
|
||||||
|
css`
|
||||||
|
ha-dialog {
|
||||||
|
--mdc-dialog-max-width: 430px;
|
||||||
|
}
|
||||||
|
ha-statistic-picker {
|
||||||
|
display: block;
|
||||||
|
margin: var(--ha-space-4) 0;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"dialog-energy-grid-power-settings": DialogEnergyGridPowerSettings;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ import { brandsUrl } from "../../../../util/brands-url";
|
|||||||
import type { EnergySettingsSolarDialogParams } from "./show-dialogs-energy";
|
import type { EnergySettingsSolarDialogParams } from "./show-dialogs-energy";
|
||||||
|
|
||||||
const energyUnitClasses = ["energy"];
|
const energyUnitClasses = ["energy"];
|
||||||
|
const powerUnitClasses = ["power"];
|
||||||
|
|
||||||
@customElement("dialog-energy-solar-settings")
|
@customElement("dialog-energy-solar-settings")
|
||||||
export class DialogEnergySolarSettings
|
export class DialogEnergySolarSettings
|
||||||
@@ -46,10 +47,14 @@ export class DialogEnergySolarSettings
|
|||||||
|
|
||||||
@state() private _energy_units?: string[];
|
@state() private _energy_units?: string[];
|
||||||
|
|
||||||
|
@state() private _power_units?: string[];
|
||||||
|
|
||||||
@state() private _error?: string;
|
@state() private _error?: string;
|
||||||
|
|
||||||
private _excludeList?: string[];
|
private _excludeList?: string[];
|
||||||
|
|
||||||
|
private _excludeListPower?: string[];
|
||||||
|
|
||||||
public async showDialog(
|
public async showDialog(
|
||||||
params: EnergySettingsSolarDialogParams
|
params: EnergySettingsSolarDialogParams
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
@@ -62,9 +67,15 @@ export class DialogEnergySolarSettings
|
|||||||
this._energy_units = (
|
this._energy_units = (
|
||||||
await getSensorDeviceClassConvertibleUnits(this.hass, "energy")
|
await getSensorDeviceClassConvertibleUnits(this.hass, "energy")
|
||||||
).units;
|
).units;
|
||||||
|
this._power_units = (
|
||||||
|
await getSensorDeviceClassConvertibleUnits(this.hass, "power")
|
||||||
|
).units;
|
||||||
this._excludeList = this._params.solar_sources
|
this._excludeList = this._params.solar_sources
|
||||||
.map((entry) => entry.stat_energy_from)
|
.map((entry) => entry.stat_energy_from)
|
||||||
.filter((id) => id !== this._source?.stat_energy_from);
|
.filter((id) => id !== this._source?.stat_energy_from);
|
||||||
|
this._excludeListPower = this._params.solar_sources
|
||||||
|
.map((entry) => entry.stat_power)
|
||||||
|
.filter((id) => id && id !== this._source?.stat_power) as string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
public closeDialog() {
|
public closeDialog() {
|
||||||
@@ -81,8 +92,6 @@ export class DialogEnergySolarSettings
|
|||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pickableUnit = this._energy_units?.join(", ") || "";
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-dialog
|
<ha-dialog
|
||||||
open
|
open
|
||||||
@@ -94,12 +103,6 @@ export class DialogEnergySolarSettings
|
|||||||
@closed=${this.closeDialog}
|
@closed=${this.closeDialog}
|
||||||
>
|
>
|
||||||
${this._error ? html`<p class="error">${this._error}</p>` : ""}
|
${this._error ? html`<p class="error">${this._error}</p>` : ""}
|
||||||
<div>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.energy.solar.dialog.entity_para",
|
|
||||||
{ unit: pickableUnit }
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ha-statistic-picker
|
<ha-statistic-picker
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@@ -111,9 +114,28 @@ export class DialogEnergySolarSettings
|
|||||||
)}
|
)}
|
||||||
.excludeStatistics=${this._excludeList}
|
.excludeStatistics=${this._excludeList}
|
||||||
@value-changed=${this._statisticChanged}
|
@value-changed=${this._statisticChanged}
|
||||||
|
.helper=${this.hass.localize(
|
||||||
|
"ui.panel.config.energy.solar.dialog.entity_para",
|
||||||
|
{ unit: this._energy_units?.join(", ") || "" }
|
||||||
|
)}
|
||||||
dialogInitialFocus
|
dialogInitialFocus
|
||||||
></ha-statistic-picker>
|
></ha-statistic-picker>
|
||||||
|
|
||||||
|
<ha-statistic-picker
|
||||||
|
.hass=${this.hass}
|
||||||
|
.includeUnitClass=${powerUnitClasses}
|
||||||
|
.value=${this._source.stat_power}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.energy.solar.dialog.solar_production_power"
|
||||||
|
)}
|
||||||
|
.excludeStatistics=${this._excludeListPower}
|
||||||
|
@value-changed=${this._powerStatisticChanged}
|
||||||
|
.helper=${this.hass.localize(
|
||||||
|
"ui.panel.config.energy.solar.dialog.entity_para",
|
||||||
|
{ unit: this._power_units?.join(", ") || "" }
|
||||||
|
)}
|
||||||
|
></ha-statistic-picker>
|
||||||
|
|
||||||
<h3>
|
<h3>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.energy.solar.dialog.solar_production_forecast"
|
"ui.panel.config.energy.solar.dialog.solar_production_forecast"
|
||||||
@@ -267,6 +289,10 @@ export class DialogEnergySolarSettings
|
|||||||
this._source = { ...this._source!, stat_energy_from: ev.detail.value };
|
this._source = { ...this._source!, stat_energy_from: ev.detail.value };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _powerStatisticChanged(ev: CustomEvent<{ value: string }>) {
|
||||||
|
this._source = { ...this._source!, stat_power: ev.detail.value };
|
||||||
|
}
|
||||||
|
|
||||||
private async _save() {
|
private async _save() {
|
||||||
try {
|
try {
|
||||||
if (!this._forecast) {
|
if (!this._forecast) {
|
||||||
@@ -287,6 +313,10 @@ export class DialogEnergySolarSettings
|
|||||||
ha-dialog {
|
ha-dialog {
|
||||||
--mdc-dialog-max-width: 430px;
|
--mdc-dialog-max-width: 430px;
|
||||||
}
|
}
|
||||||
|
ha-statistic-picker {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: var(--ha-space-4);
|
||||||
|
}
|
||||||
img {
|
img {
|
||||||
height: 24px;
|
height: 24px;
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import type {
|
|||||||
FlowFromGridSourceEnergyPreference,
|
FlowFromGridSourceEnergyPreference,
|
||||||
FlowToGridSourceEnergyPreference,
|
FlowToGridSourceEnergyPreference,
|
||||||
GasSourceTypeEnergyPreference,
|
GasSourceTypeEnergyPreference,
|
||||||
|
GridPowerSourceEnergyPreference,
|
||||||
GridSourceTypeEnergyPreference,
|
GridSourceTypeEnergyPreference,
|
||||||
SolarSourceTypeEnergyPreference,
|
SolarSourceTypeEnergyPreference,
|
||||||
WaterSourceTypeEnergyPreference,
|
WaterSourceTypeEnergyPreference,
|
||||||
@@ -41,6 +42,12 @@ export interface EnergySettingsGridFlowToDialogParams {
|
|||||||
saveCallback: (source: FlowToGridSourceEnergyPreference) => Promise<void>;
|
saveCallback: (source: FlowToGridSourceEnergyPreference) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface EnergySettingsGridPowerDialogParams {
|
||||||
|
source?: GridPowerSourceEnergyPreference;
|
||||||
|
grid_source?: GridSourceTypeEnergyPreference;
|
||||||
|
saveCallback: (source: GridPowerSourceEnergyPreference) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface EnergySettingsSolarDialogParams {
|
export interface EnergySettingsSolarDialogParams {
|
||||||
info: EnergyInfo;
|
info: EnergyInfo;
|
||||||
source?: SolarSourceTypeEnergyPreference;
|
source?: SolarSourceTypeEnergyPreference;
|
||||||
@@ -152,3 +159,14 @@ export const showEnergySettingsGridFlowToDialog = (
|
|||||||
dialogParams: { ...dialogParams, direction: "to" },
|
dialogParams: { ...dialogParams, direction: "to" },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const showEnergySettingsGridPowerDialog = (
|
||||||
|
element: HTMLElement,
|
||||||
|
dialogParams: EnergySettingsGridPowerDialogParams
|
||||||
|
): void => {
|
||||||
|
fireEvent(element, "show-dialog", {
|
||||||
|
dialogTag: "dialog-energy-grid-power-settings",
|
||||||
|
dialogImport: () => import("./dialog-energy-grid-power-settings"),
|
||||||
|
dialogParams: dialogParams,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
import type {
|
import type {
|
||||||
BarSeriesOption,
|
BarSeriesOption,
|
||||||
CallbackDataParams,
|
CallbackDataParams,
|
||||||
|
LineSeriesOption,
|
||||||
TopLevelFormatterParams,
|
TopLevelFormatterParams,
|
||||||
} from "echarts/types/dist/shared";
|
} from "echarts/types/dist/shared";
|
||||||
import type { FrontendLocaleData } from "../../../../../data/translation";
|
import type { FrontendLocaleData } from "../../../../../data/translation";
|
||||||
@@ -170,11 +171,10 @@ function formatTooltip(
|
|||||||
compare
|
compare
|
||||||
? `${(showCompareYear ? formatDateShort : formatDateVeryShort)(date, locale, config)}: `
|
? `${(showCompareYear ? formatDateShort : formatDateVeryShort)(date, locale, config)}: `
|
||||||
: ""
|
: ""
|
||||||
}${formatTime(date, locale, config)} – ${formatTime(
|
}${formatTime(date, locale, config)}`;
|
||||||
addHours(date, 1),
|
if (params[0].componentSubType === "bar") {
|
||||||
locale,
|
period += ` – ${formatTime(addHours(date, 1), locale, config)}`;
|
||||||
config
|
}
|
||||||
)}`;
|
|
||||||
}
|
}
|
||||||
const title = `<h4 style="text-align: center; margin: 0;">${period}</h4>`;
|
const title = `<h4 style="text-align: center; margin: 0;">${period}</h4>`;
|
||||||
|
|
||||||
@@ -281,6 +281,35 @@ export function fillDataGapsAndRoundCaps(datasets: BarSeriesOption[]) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function fillLineGaps(datasets: LineSeriesOption[]) {
|
||||||
|
const buckets = Array.from(
|
||||||
|
new Set(
|
||||||
|
datasets
|
||||||
|
.map((dataset) =>
|
||||||
|
dataset.data!.map((datapoint) => Number(datapoint![0]))
|
||||||
|
)
|
||||||
|
.flat()
|
||||||
|
)
|
||||||
|
).sort((a, b) => a - b);
|
||||||
|
buckets.forEach((bucket, index) => {
|
||||||
|
for (let i = datasets.length - 1; i >= 0; i--) {
|
||||||
|
const dataPoint = datasets[i].data![index];
|
||||||
|
const item: any =
|
||||||
|
dataPoint && typeof dataPoint === "object" && "value" in dataPoint
|
||||||
|
? dataPoint
|
||||||
|
: { value: dataPoint };
|
||||||
|
const x = item.value?.[0];
|
||||||
|
if (x === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (Number(x) !== bucket) {
|
||||||
|
datasets[i].data?.splice(index, 0, [bucket, 0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return datasets;
|
||||||
|
}
|
||||||
|
|
||||||
export function getCompareTransform(start: Date, compareStart?: Date) {
|
export function getCompareTransform(start: Date, compareStart?: Date) {
|
||||||
if (!compareStart) {
|
if (!compareStart) {
|
||||||
return (ts: Date) => ts;
|
return (ts: Date) => ts;
|
||||||
|
|||||||
305
src/panels/lovelace/cards/energy/hui-power-sources-graph-card.ts
Normal file
305
src/panels/lovelace/cards/energy/hui-power-sources-graph-card.ts
Normal file
@@ -0,0 +1,305 @@
|
|||||||
|
import { endOfToday, isToday, startOfToday } from "date-fns";
|
||||||
|
import type { HassConfig, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
|
import type { PropertyValues } from "lit";
|
||||||
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { classMap } from "lit/directives/class-map";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import type { LineSeriesOption } from "echarts/charts";
|
||||||
|
import { graphic } from "echarts";
|
||||||
|
import "../../../../components/chart/ha-chart-base";
|
||||||
|
import "../../../../components/ha-card";
|
||||||
|
import type { EnergyData } from "../../../../data/energy";
|
||||||
|
import { getEnergyDataCollection } from "../../../../data/energy";
|
||||||
|
import type { StatisticValue } from "../../../../data/recorder";
|
||||||
|
import type { FrontendLocaleData } from "../../../../data/translation";
|
||||||
|
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||||
|
import type { HomeAssistant } from "../../../../types";
|
||||||
|
import type { LovelaceCard } from "../../types";
|
||||||
|
import type { PowerSourcesGraphCardConfig } from "../types";
|
||||||
|
import { hasConfigChanged } from "../../common/has-changed";
|
||||||
|
import { getCommonOptions, fillLineGaps } from "./common/energy-chart-options";
|
||||||
|
import type { ECOption } from "../../../../resources/echarts";
|
||||||
|
import { hex2rgb } from "../../../../common/color/convert-color";
|
||||||
|
|
||||||
|
@customElement("hui-power-sources-graph-card")
|
||||||
|
export class HuiPowerSourcesGraphCard
|
||||||
|
extends SubscribeMixin(LitElement)
|
||||||
|
implements LovelaceCard
|
||||||
|
{
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@state() private _config?: PowerSourcesGraphCardConfig;
|
||||||
|
|
||||||
|
@state() private _chartData: LineSeriesOption[] = [];
|
||||||
|
|
||||||
|
@state() private _start = startOfToday();
|
||||||
|
|
||||||
|
@state() private _end = endOfToday();
|
||||||
|
|
||||||
|
@state() private _compareStart?: Date;
|
||||||
|
|
||||||
|
@state() private _compareEnd?: Date;
|
||||||
|
|
||||||
|
protected hassSubscribeRequiredHostProps = ["_config"];
|
||||||
|
|
||||||
|
public hassSubscribe(): UnsubscribeFunc[] {
|
||||||
|
return [
|
||||||
|
getEnergyDataCollection(this.hass, {
|
||||||
|
key: this._config?.collection_key,
|
||||||
|
}).subscribe((data) => this._getStatistics(data)),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCardSize(): Promise<number> | number {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setConfig(config: PowerSourcesGraphCardConfig): void {
|
||||||
|
this._config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||||
|
return (
|
||||||
|
hasConfigChanged(this, changedProps) ||
|
||||||
|
changedProps.size > 1 ||
|
||||||
|
!changedProps.has("hass")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (!this.hass || !this._config) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-card>
|
||||||
|
${this._config.title
|
||||||
|
? html`<h1 class="card-header">${this._config.title}</h1>`
|
||||||
|
: ""}
|
||||||
|
<div
|
||||||
|
class="content ${classMap({
|
||||||
|
"has-header": !!this._config.title,
|
||||||
|
})}"
|
||||||
|
>
|
||||||
|
<ha-chart-base
|
||||||
|
.hass=${this.hass}
|
||||||
|
.data=${this._chartData}
|
||||||
|
.options=${this._createOptions(
|
||||||
|
this._start,
|
||||||
|
this._end,
|
||||||
|
this.hass.locale,
|
||||||
|
this.hass.config,
|
||||||
|
this._compareStart,
|
||||||
|
this._compareEnd
|
||||||
|
)}
|
||||||
|
></ha-chart-base>
|
||||||
|
${!this._chartData.some((dataset) => dataset.data!.length)
|
||||||
|
? html`<div class="no-data">
|
||||||
|
${isToday(this._start)
|
||||||
|
? this.hass.localize("ui.panel.lovelace.cards.energy.no_data")
|
||||||
|
: this.hass.localize(
|
||||||
|
"ui.panel.lovelace.cards.energy.no_data_period"
|
||||||
|
)}
|
||||||
|
</div>`
|
||||||
|
: nothing}
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _createOptions = memoizeOne(
|
||||||
|
(
|
||||||
|
start: Date,
|
||||||
|
end: Date,
|
||||||
|
locale: FrontendLocaleData,
|
||||||
|
config: HassConfig,
|
||||||
|
compareStart?: Date,
|
||||||
|
compareEnd?: Date
|
||||||
|
): ECOption =>
|
||||||
|
getCommonOptions(
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
locale,
|
||||||
|
config,
|
||||||
|
"kW",
|
||||||
|
compareStart,
|
||||||
|
compareEnd
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
private async _getStatistics(energyData: EnergyData): Promise<void> {
|
||||||
|
const datasets: LineSeriesOption[] = [];
|
||||||
|
|
||||||
|
const statIds = {
|
||||||
|
solar: {
|
||||||
|
stats: [] as string[],
|
||||||
|
color: "--energy-solar-color",
|
||||||
|
name: this.hass.localize(
|
||||||
|
"ui.panel.lovelace.cards.energy.power_graph.solar"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
stats: [] as string[],
|
||||||
|
color: "--energy-grid-consumption-color",
|
||||||
|
name: this.hass.localize(
|
||||||
|
"ui.panel.lovelace.cards.energy.power_graph.grid"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
battery: {
|
||||||
|
stats: [] as string[],
|
||||||
|
color: "--energy-battery-out-color",
|
||||||
|
name: this.hass.localize(
|
||||||
|
"ui.panel.lovelace.cards.energy.power_graph.battery"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const computedStyles = getComputedStyle(this);
|
||||||
|
|
||||||
|
for (const source of energyData.prefs.energy_sources) {
|
||||||
|
if (source.type === "solar") {
|
||||||
|
if (source.stat_power) {
|
||||||
|
statIds.solar.stats.push(source.stat_power);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source.type === "battery") {
|
||||||
|
if (source.stat_power) {
|
||||||
|
statIds.battery.stats.push(source.stat_power);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source.type === "grid" && source.power) {
|
||||||
|
statIds.grid.stats.push(...source.power.map((p) => p.stat_power));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const commonSeriesOptions: LineSeriesOption = {
|
||||||
|
type: "line",
|
||||||
|
smooth: 0.4,
|
||||||
|
smoothMonotone: "x",
|
||||||
|
lineStyle: {
|
||||||
|
width: 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.keys(statIds).forEach((key, keyIndex) => {
|
||||||
|
if (statIds[key].stats.length) {
|
||||||
|
const colorHex = computedStyles.getPropertyValue(statIds[key].color);
|
||||||
|
const rgb = hex2rgb(colorHex);
|
||||||
|
const { positive, negative } = this._processData(
|
||||||
|
statIds[key].stats.map((id: string) => energyData.stats[id] ?? [])
|
||||||
|
);
|
||||||
|
datasets.push({
|
||||||
|
...commonSeriesOptions,
|
||||||
|
id: key,
|
||||||
|
name: statIds[key].name,
|
||||||
|
color: colorHex,
|
||||||
|
stack: "positive",
|
||||||
|
areaStyle: {
|
||||||
|
color: new graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, 0.75)`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, 0.25)`,
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
data: positive,
|
||||||
|
z: 3 - keyIndex, // draw in reverse order so 0 value lines are overwritten
|
||||||
|
});
|
||||||
|
if (key !== "solar") {
|
||||||
|
datasets.push({
|
||||||
|
...commonSeriesOptions,
|
||||||
|
id: `${key}-negative`,
|
||||||
|
name: statIds[key].name,
|
||||||
|
color: colorHex,
|
||||||
|
stack: "negative",
|
||||||
|
areaStyle: {
|
||||||
|
color: new graphic.LinearGradient(0, 1, 0, 0, [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, 0.75)`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, 0.25)`,
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
data: negative,
|
||||||
|
z: 4 - keyIndex, // draw in reverse order but above positive series
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this._start = energyData.start;
|
||||||
|
this._end = energyData.end || endOfToday();
|
||||||
|
|
||||||
|
this._chartData = fillLineGaps(datasets);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _processData(stats: StatisticValue[][]) {
|
||||||
|
const data: Record<number, number[]> = {};
|
||||||
|
stats.forEach((statSet) => {
|
||||||
|
statSet.forEach((point) => {
|
||||||
|
if (point.mean == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const x = (point.start + point.end) / 2;
|
||||||
|
data[x] = [...(data[x] ?? []), point.mean];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const positive: [number, number][] = [];
|
||||||
|
const negative: [number, number][] = [];
|
||||||
|
Object.entries(data).forEach(([x, y]) => {
|
||||||
|
const ts = Number(x);
|
||||||
|
const meanY = y.reduce((a, b) => a + b, 0) / y.length;
|
||||||
|
positive.push([ts, Math.max(0, meanY)]);
|
||||||
|
negative.push([ts, Math.min(0, meanY)]);
|
||||||
|
});
|
||||||
|
return { positive, negative };
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
ha-card {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.card-header {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding: var(--ha-space-4);
|
||||||
|
}
|
||||||
|
.has-header {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
.no-data {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20%;
|
||||||
|
margin-left: var(--ha-space-8);
|
||||||
|
margin-inline-start: var(--ha-space-8);
|
||||||
|
margin-inline-end: initial;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"hui-power-sources-graph-card": HuiPowerSourcesGraphCard;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -227,6 +227,11 @@ export interface EnergySankeyCardConfig extends EnergyCardBaseConfig {
|
|||||||
group_by_area?: boolean;
|
group_by_area?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PowerSourcesGraphCardConfig extends EnergyCardBaseConfig {
|
||||||
|
type: "power-sources-graph";
|
||||||
|
title?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface EntityFilterCardConfig extends LovelaceCardConfig {
|
export interface EntityFilterCardConfig extends LovelaceCardConfig {
|
||||||
type: "entity-filter";
|
type: "entity-filter";
|
||||||
entities: (EntityFilterEntityConfig | string)[];
|
entities: (EntityFilterEntityConfig | string)[];
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ const LAZY_LOAD_TYPES = {
|
|||||||
"energy-usage-graph": () =>
|
"energy-usage-graph": () =>
|
||||||
import("../cards/energy/hui-energy-usage-graph-card"),
|
import("../cards/energy/hui-energy-usage-graph-card"),
|
||||||
"energy-sankey": () => import("../cards/energy/hui-energy-sankey-card"),
|
"energy-sankey": () => import("../cards/energy/hui-energy-sankey-card"),
|
||||||
|
"power-sources-graph": () =>
|
||||||
|
import("../cards/energy/hui-power-sources-graph-card"),
|
||||||
"entity-filter": () => import("../cards/hui-entity-filter-card"),
|
"entity-filter": () => import("../cards/hui-entity-filter-card"),
|
||||||
error: () => import("../cards/hui-error-card"),
|
error: () => import("../cards/hui-error-card"),
|
||||||
"home-summary": () => import("../cards/hui-home-summary-card"),
|
"home-summary": () => import("../cards/hui-home-summary-card"),
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ const NON_STANDARD_URLS = {
|
|||||||
"energy-devices-graph": "energy/#devices-energy-graph",
|
"energy-devices-graph": "energy/#devices-energy-graph",
|
||||||
"energy-devices-detail-graph": "energy/#detail-devices-energy-graph",
|
"energy-devices-detail-graph": "energy/#detail-devices-energy-graph",
|
||||||
"energy-sankey": "energy/#sankey-energy-graph",
|
"energy-sankey": "energy/#sankey-energy-graph",
|
||||||
|
"power-sources-graph": "energy/#power-sources-graph",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getCardDocumentationURL = (
|
export const getCardDocumentationURL = (
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { isComponentLoaded } from "../../../../common/config/is_component_loaded
|
|||||||
import { generateEntityFilter } from "../../../../common/entity/entity_filter";
|
import { generateEntityFilter } from "../../../../common/entity/entity_filter";
|
||||||
import type { AreaRegistryEntry } from "../../../../data/area_registry";
|
import type { AreaRegistryEntry } from "../../../../data/area_registry";
|
||||||
import { getEnergyPreferences } from "../../../../data/energy";
|
import { getEnergyPreferences } from "../../../../data/energy";
|
||||||
|
import type { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
|
||||||
import type {
|
import type {
|
||||||
LovelaceSectionConfig,
|
LovelaceSectionConfig,
|
||||||
LovelaceSectionRawConfig,
|
LovelaceSectionRawConfig,
|
||||||
@@ -18,8 +19,9 @@ import type {
|
|||||||
TileCardConfig,
|
TileCardConfig,
|
||||||
WeatherForecastCardConfig,
|
WeatherForecastCardConfig,
|
||||||
} from "../../cards/types";
|
} from "../../cards/types";
|
||||||
import { getAreas } from "../areas/helpers/areas-strategy-helper";
|
import { getAreas, getFloors } from "../areas/helpers/areas-strategy-helper";
|
||||||
import type { CommonControlSectionStrategyConfig } from "../usage_prediction/common-controls-section-strategy";
|
import type { CommonControlSectionStrategyConfig } from "../usage_prediction/common-controls-section-strategy";
|
||||||
|
import { getHomeStructure } from "./helpers/home-structure";
|
||||||
|
|
||||||
export interface HomeMainViewStrategyConfig {
|
export interface HomeMainViewStrategyConfig {
|
||||||
type: "home-main";
|
type: "home-main";
|
||||||
@@ -59,25 +61,67 @@ export class HomeMainViewStrategy extends ReactiveElement {
|
|||||||
hass: HomeAssistant
|
hass: HomeAssistant
|
||||||
): Promise<LovelaceViewConfig> {
|
): Promise<LovelaceViewConfig> {
|
||||||
const areas = getAreas(hass.areas);
|
const areas = getAreas(hass.areas);
|
||||||
|
const floors = getFloors(hass.floors);
|
||||||
|
|
||||||
const areasSection: LovelaceSectionConfig = {
|
const home = getHomeStructure(floors, areas);
|
||||||
type: "grid",
|
|
||||||
column_span: 2,
|
const floorCount = home.floors.length + (home.areas.length ? 1 : 0);
|
||||||
cards: [
|
|
||||||
{
|
|
||||||
type: "heading",
|
|
||||||
heading_style: "title",
|
|
||||||
heading: hass.localize("ui.panel.lovelace.strategy.home.areas"),
|
|
||||||
},
|
|
||||||
...areas.map<AreaCardConfig>((area) =>
|
|
||||||
computeAreaCard(area.area_id, hass)
|
|
||||||
),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
// Allow between 2 and 3 columns (the max should be set to define the width of the header)
|
// Allow between 2 and 3 columns (the max should be set to define the width of the header)
|
||||||
const maxColumns = 2;
|
const maxColumns = 2;
|
||||||
|
|
||||||
|
const floorsSections: LovelaceSectionConfig[] = [];
|
||||||
|
for (const floorStructure of home.floors) {
|
||||||
|
const floorId = floorStructure.id;
|
||||||
|
const areaIds = floorStructure.areas;
|
||||||
|
const floor = hass.floors[floorId];
|
||||||
|
|
||||||
|
const cards: LovelaceCardConfig[] = [];
|
||||||
|
for (const areaId of areaIds) {
|
||||||
|
cards.push(computeAreaCard(areaId, hass));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cards.length) {
|
||||||
|
floorsSections.push({
|
||||||
|
type: "grid",
|
||||||
|
column_span: maxColumns,
|
||||||
|
cards: [
|
||||||
|
{
|
||||||
|
type: "heading",
|
||||||
|
heading:
|
||||||
|
floorCount > 1
|
||||||
|
? floor.name
|
||||||
|
: hass.localize("ui.panel.lovelace.strategy.home.areas"),
|
||||||
|
heading_style: "title",
|
||||||
|
},
|
||||||
|
...cards,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (home.areas.length) {
|
||||||
|
const cards: LovelaceCardConfig[] = [];
|
||||||
|
for (const areaId of home.areas) {
|
||||||
|
cards.push(computeAreaCard(areaId, hass));
|
||||||
|
}
|
||||||
|
floorsSections.push({
|
||||||
|
type: "grid",
|
||||||
|
column_span: maxColumns,
|
||||||
|
cards: [
|
||||||
|
{
|
||||||
|
type: "heading",
|
||||||
|
heading:
|
||||||
|
floorCount > 1
|
||||||
|
? hass.localize("ui.panel.lovelace.strategy.home.other_areas")
|
||||||
|
: hass.localize("ui.panel.lovelace.strategy.home.areas"),
|
||||||
|
heading_style: "title",
|
||||||
|
},
|
||||||
|
...cards,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const favoriteSection: LovelaceSectionConfig = {
|
const favoriteSection: LovelaceSectionConfig = {
|
||||||
type: "grid",
|
type: "grid",
|
||||||
column_span: maxColumns,
|
column_span: maxColumns,
|
||||||
@@ -234,7 +278,7 @@ export class HomeMainViewStrategy extends ReactiveElement {
|
|||||||
favoriteSection.cards && favoriteSection,
|
favoriteSection.cards && favoriteSection,
|
||||||
commonControlsSection,
|
commonControlsSection,
|
||||||
summarySection,
|
summarySection,
|
||||||
areasSection,
|
...floorsSections,
|
||||||
widgetSection.cards && widgetSection,
|
widgetSection.cards && widgetSection,
|
||||||
] satisfies (LovelaceSectionRawConfig | undefined)[]
|
] satisfies (LovelaceSectionRawConfig | undefined)[]
|
||||||
).filter(Boolean) as LovelaceSectionRawConfig[];
|
).filter(Boolean) as LovelaceSectionRawConfig[];
|
||||||
|
|||||||
@@ -3036,6 +3036,15 @@
|
|||||||
"grid_carbon_footprint": "Grid carbon footprint",
|
"grid_carbon_footprint": "Grid carbon footprint",
|
||||||
"remove_co2_signal": "Remove Electricity Maps integration",
|
"remove_co2_signal": "Remove Electricity Maps integration",
|
||||||
"add_co2_signal": "Add Electricity Maps integration",
|
"add_co2_signal": "Add Electricity Maps integration",
|
||||||
|
"grid_power": "Grid power",
|
||||||
|
"add_power": "Add power sensor",
|
||||||
|
"edit_power": "Edit power sensor",
|
||||||
|
"delete_power": "Delete power sensor",
|
||||||
|
"power_dialog": {
|
||||||
|
"header": "Configure grid power",
|
||||||
|
"power_stat": "Power sensor",
|
||||||
|
"power_helper": "Pick a sensor which measures grid power in either of {unit}. Positive values indicate using electricity from the grid, negative values indicate exporting electricity to the grid."
|
||||||
|
},
|
||||||
"flow_dialog": {
|
"flow_dialog": {
|
||||||
"from": {
|
"from": {
|
||||||
"header": "Configure grid consumption",
|
"header": "Configure grid consumption",
|
||||||
@@ -3082,6 +3091,7 @@
|
|||||||
"header": "Configure solar panels",
|
"header": "Configure solar panels",
|
||||||
"entity_para": "Pick a sensor which measures solar energy production in either of {unit}.",
|
"entity_para": "Pick a sensor which measures solar energy production in either of {unit}.",
|
||||||
"solar_production_energy": "Solar production energy",
|
"solar_production_energy": "Solar production energy",
|
||||||
|
"solar_production_power": "Solar production power",
|
||||||
"solar_production_forecast": "Solar production forecast",
|
"solar_production_forecast": "Solar production forecast",
|
||||||
"solar_production_forecast_description": "Adding solar production forecast information will allow you to quickly see your expected production for today.",
|
"solar_production_forecast_description": "Adding solar production forecast information will allow you to quickly see your expected production for today.",
|
||||||
"dont_forecast_production": "Don't forecast production",
|
"dont_forecast_production": "Don't forecast production",
|
||||||
@@ -3099,9 +3109,12 @@
|
|||||||
"add_battery_system": "Add battery system",
|
"add_battery_system": "Add battery system",
|
||||||
"dialog": {
|
"dialog": {
|
||||||
"header": "Configure battery system",
|
"header": "Configure battery system",
|
||||||
"entity_para": "Pick sensors which measure energy going into and coming out of the battery in either of {unit}.",
|
"energy_helper_into": "Pick a sensor that measures the electricity flowing into the battery in either of {unit}.",
|
||||||
"energy_into_battery": "Energy going into the battery",
|
"energy_helper_out": "Pick a sensor that measures the electricity flowing out of the battery in either of {unit}.",
|
||||||
"energy_out_of_battery": "Energy coming out of the battery"
|
"energy_into_battery": "Energy charged into the battery",
|
||||||
|
"energy_out_of_battery": "Energy discharged from the battery",
|
||||||
|
"power": "Battery power",
|
||||||
|
"power_helper": "Pick a sensor which measures the electricity flowing into and out of the battery in either of {unit}. Positive values indicate discharging the battery, negative values indicate charging the battery."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"gas": {
|
"gas": {
|
||||||
@@ -3163,7 +3176,8 @@
|
|||||||
"header": "Add a device",
|
"header": "Add a device",
|
||||||
"display_name": "Display name",
|
"display_name": "Display name",
|
||||||
"device_consumption_energy": "Device energy consumption",
|
"device_consumption_energy": "Device energy consumption",
|
||||||
"selected_stat_intro": "Select the energy sensor that measures the device's energy usage in either of {unit}.",
|
"device_consumption_power": "Device power consumption",
|
||||||
|
"selected_stat_intro": "Select the sensor that measures the device's electricity usage in either of {unit}.",
|
||||||
"included_in_device": "Upstream device",
|
"included_in_device": "Upstream device",
|
||||||
"included_in_device_helper": "If this device is already counted by another device (such as a smart switch measured by a smart breaker), selecting the upstream device prevents duplicate energy tracking.",
|
"included_in_device_helper": "If this device is already counted by another device (such as a smart switch measured by a smart breaker), selecting the upstream device prevents duplicate energy tracking.",
|
||||||
"no_upstream_devices": "No eligible upstream devices"
|
"no_upstream_devices": "No eligible upstream devices"
|
||||||
@@ -7039,6 +7053,11 @@
|
|||||||
"card_indicates_energy_used": "This card indicates how much of the electricity consumed by your home was generated using non-fossil fuels like solar, wind, and nuclear. The higher, the better!",
|
"card_indicates_energy_used": "This card indicates how much of the electricity consumed by your home was generated using non-fossil fuels like solar, wind, and nuclear. The higher, the better!",
|
||||||
"low_carbon_energy_consumed": "Low-carbon electricity consumed",
|
"low_carbon_energy_consumed": "Low-carbon electricity consumed",
|
||||||
"low_carbon_energy_not_calculated": "Consumed low-carbon electricity couldn't be calculated"
|
"low_carbon_energy_not_calculated": "Consumed low-carbon electricity couldn't be calculated"
|
||||||
|
},
|
||||||
|
"power_graph": {
|
||||||
|
"grid": "Grid",
|
||||||
|
"solar": "Solar",
|
||||||
|
"battery": "Battery"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"heading": {
|
"heading": {
|
||||||
|
|||||||
180
yarn.lock
180
yarn.lock
@@ -1698,15 +1698,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@formatjs/ecma402-abstract@npm:2.3.5":
|
"@formatjs/ecma402-abstract@npm:2.3.6":
|
||||||
version: 2.3.5
|
version: 2.3.6
|
||||||
resolution: "@formatjs/ecma402-abstract@npm:2.3.5"
|
resolution: "@formatjs/ecma402-abstract@npm:2.3.6"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@formatjs/fast-memoize": "npm:2.2.7"
|
"@formatjs/fast-memoize": "npm:2.2.7"
|
||||||
"@formatjs/intl-localematcher": "npm:0.6.2"
|
"@formatjs/intl-localematcher": "npm:0.6.2"
|
||||||
decimal.js: "npm:^10.4.3"
|
decimal.js: "npm:^10.4.3"
|
||||||
tslib: "npm:^2.8.0"
|
tslib: "npm:^2.8.0"
|
||||||
checksum: 10/254651057170836237dc4f0fbb372157f97133c4dcee414007e0cdb5b589baf0546c2f6337d117b988ee0a4f0a4d8247780aaa9e96b410c568495f162c40dc50
|
checksum: 10/30b1b5cd6b62ba46245f934429936592df5500bc1b089dc92dd49c826757b873dd92c305dcfe370701e4df6b057bf007782113abb9b65db550d73be4961718bc
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -1719,68 +1719,68 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@formatjs/icu-messageformat-parser@npm:2.11.3":
|
"@formatjs/icu-messageformat-parser@npm:2.11.4":
|
||||||
version: 2.11.3
|
version: 2.11.4
|
||||||
resolution: "@formatjs/icu-messageformat-parser@npm:2.11.3"
|
resolution: "@formatjs/icu-messageformat-parser@npm:2.11.4"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@formatjs/ecma402-abstract": "npm:2.3.5"
|
"@formatjs/ecma402-abstract": "npm:2.3.6"
|
||||||
"@formatjs/icu-skeleton-parser": "npm:1.8.15"
|
"@formatjs/icu-skeleton-parser": "npm:1.8.16"
|
||||||
tslib: "npm:^2.8.0"
|
tslib: "npm:^2.8.0"
|
||||||
checksum: 10/339f5ff5ea7417e2db7f01bd41340f78fd5a8e56a66e723272d21ce7ab4b265dcb45748cdca76eac7137e2b5e6767986812b471e011b4602cf7afbc6da57fb98
|
checksum: 10/2acb100c06c2ade666d72787fb9f9795b1ace41e8e73bfadc2b1a7b8562e81f655e484f0f33d8c39473aa17bf0ad96fb2228871806a9b3dc4f5f876754a0de3a
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@formatjs/icu-skeleton-parser@npm:1.8.15":
|
"@formatjs/icu-skeleton-parser@npm:1.8.16":
|
||||||
version: 1.8.15
|
version: 1.8.16
|
||||||
resolution: "@formatjs/icu-skeleton-parser@npm:1.8.15"
|
resolution: "@formatjs/icu-skeleton-parser@npm:1.8.16"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@formatjs/ecma402-abstract": "npm:2.3.5"
|
"@formatjs/ecma402-abstract": "npm:2.3.6"
|
||||||
tslib: "npm:^2.8.0"
|
tslib: "npm:^2.8.0"
|
||||||
checksum: 10/19825abc1a5eef0288456c08420d06f3da8256fbe81db0b9ead48cacc94954d748c8068988e26d184d38fca2e50c191ecda5a10ff3935529c3134b8d80db0538
|
checksum: 10/428001e5bed81889b276a2356a1393157af91dc59220b765a1a132f6407ac5832b7ac6ae9737674ac38e44035295c0c1c310b2630f383f2b5779ea90bf2849e6
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@formatjs/intl-datetimeformat@npm:6.18.1":
|
"@formatjs/intl-datetimeformat@npm:6.18.2":
|
||||||
version: 6.18.1
|
version: 6.18.2
|
||||||
resolution: "@formatjs/intl-datetimeformat@npm:6.18.1"
|
resolution: "@formatjs/intl-datetimeformat@npm:6.18.2"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@formatjs/ecma402-abstract": "npm:2.3.5"
|
"@formatjs/ecma402-abstract": "npm:2.3.6"
|
||||||
"@formatjs/intl-localematcher": "npm:0.6.2"
|
"@formatjs/intl-localematcher": "npm:0.6.2"
|
||||||
decimal.js: "npm:^10.4.3"
|
decimal.js: "npm:^10.4.3"
|
||||||
tslib: "npm:^2.8.0"
|
tslib: "npm:^2.8.0"
|
||||||
checksum: 10/66938778ecf37472a7e2f1d9349b0ac249fcbd5d684ae5614dea07287876182429980ba2fe3671224f981065baf017ac955f4b3c1f3c924c89bf2ec82dd1acd8
|
checksum: 10/e6f80d0eb2049564502370839697a18858268a0dff8d199b1908137c4a229b1303131c12b8b8a8e8e259a1feba26dbc25b003b150adabea10d1c43f68086efbe
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@formatjs/intl-displaynames@npm:6.8.12":
|
"@formatjs/intl-displaynames@npm:6.8.13":
|
||||||
version: 6.8.12
|
version: 6.8.13
|
||||||
resolution: "@formatjs/intl-displaynames@npm:6.8.12"
|
resolution: "@formatjs/intl-displaynames@npm:6.8.13"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@formatjs/ecma402-abstract": "npm:2.3.5"
|
"@formatjs/ecma402-abstract": "npm:2.3.6"
|
||||||
"@formatjs/intl-localematcher": "npm:0.6.2"
|
"@formatjs/intl-localematcher": "npm:0.6.2"
|
||||||
tslib: "npm:^2.8.0"
|
tslib: "npm:^2.8.0"
|
||||||
checksum: 10/7de27ef7e8cde2febce84d5443f00b70062cbd0c3f1039ce8ed1caacb15c4c7a36da16295f26657d59aa4663141a04d7b1083bfd1eea6a4e8ad9dc6093a2c886
|
checksum: 10/adefd25fa42266c7bc33dd3cd50f3681bdce51d18b32a03c98f8ad7587dfd8b9291345e185a4b16f31f4eee10fc799fd1b6361bdfd3a2c9fe127744e1e0f3b07
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@formatjs/intl-durationformat@npm:0.7.5":
|
"@formatjs/intl-durationformat@npm:0.7.6":
|
||||||
version: 0.7.5
|
version: 0.7.6
|
||||||
resolution: "@formatjs/intl-durationformat@npm:0.7.5"
|
resolution: "@formatjs/intl-durationformat@npm:0.7.6"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@formatjs/ecma402-abstract": "npm:2.3.5"
|
"@formatjs/ecma402-abstract": "npm:2.3.6"
|
||||||
"@formatjs/intl-localematcher": "npm:0.6.2"
|
"@formatjs/intl-localematcher": "npm:0.6.2"
|
||||||
tslib: "npm:^2.8.0"
|
tslib: "npm:^2.8.0"
|
||||||
checksum: 10/4dc81b112fed25dc8da0a16ddeff033b7c763bf9a1cfd7b1b25c1216f7f147eb67a47059a3cf95b4d4ade150c54a813542b84e69298905a4bc22548d74bf8567
|
checksum: 10/442236ba85bcd9cb7296c43a708271fa09f110b1ca9d5899066d00812fc2965eaeaec6b5240be421b80daba62860352131088449ba0fcd2061f671cec6240f0b
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@formatjs/intl-enumerator@npm:1.8.11":
|
"@formatjs/intl-enumerator@npm:1.8.12":
|
||||||
version: 1.8.11
|
version: 1.8.12
|
||||||
resolution: "@formatjs/intl-enumerator@npm:1.8.11"
|
resolution: "@formatjs/intl-enumerator@npm:1.8.12"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@formatjs/ecma402-abstract": "npm:2.3.5"
|
"@formatjs/ecma402-abstract": "npm:2.3.6"
|
||||||
tslib: "npm:^2.8.0"
|
tslib: "npm:^2.8.0"
|
||||||
checksum: 10/8646a517cd4160c1ceff888ec8fdf652caa3d375fa41231e829c13bc7be0cd156c9642e339b75e9cfa8ef60ae8140c766f9055318c62f1c1d9345f25cdb7f426
|
checksum: 10/8dfd7ca5383b4dca530e1df5118a72f71347f4e0daa6131b82dbf7e860a8b96bec0fed43bfa6f6e650e55fa50fcd3e9e3a5253515131b578539d8eaa84630927
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -1793,26 +1793,26 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@formatjs/intl-listformat@npm:7.7.12":
|
"@formatjs/intl-listformat@npm:7.7.13":
|
||||||
version: 7.7.12
|
version: 7.7.13
|
||||||
resolution: "@formatjs/intl-listformat@npm:7.7.12"
|
resolution: "@formatjs/intl-listformat@npm:7.7.13"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@formatjs/ecma402-abstract": "npm:2.3.5"
|
"@formatjs/ecma402-abstract": "npm:2.3.6"
|
||||||
"@formatjs/intl-localematcher": "npm:0.6.2"
|
"@formatjs/intl-localematcher": "npm:0.6.2"
|
||||||
tslib: "npm:^2.8.0"
|
tslib: "npm:^2.8.0"
|
||||||
checksum: 10/eee910e83ad28b3b3c24ab6e155720187ae5b5ac936ffa2c8ec6cc8c392c194fd5c79a166290da1c6de8dc1857e3d9d11241029832ec88f7a85cce1821b7f067
|
checksum: 10/476d7cffb64eb996a888b1865aa237f04088de60fa7c65b6d073bca8a3c0f4304040ef12f16eafaf6587895976b773607296951afa7f119447d8f9b2c40daa55
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@formatjs/intl-locale@npm:4.2.12":
|
"@formatjs/intl-locale@npm:4.2.13":
|
||||||
version: 4.2.12
|
version: 4.2.13
|
||||||
resolution: "@formatjs/intl-locale@npm:4.2.12"
|
resolution: "@formatjs/intl-locale@npm:4.2.13"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@formatjs/ecma402-abstract": "npm:2.3.5"
|
"@formatjs/ecma402-abstract": "npm:2.3.6"
|
||||||
"@formatjs/intl-enumerator": "npm:1.8.11"
|
"@formatjs/intl-enumerator": "npm:1.8.12"
|
||||||
"@formatjs/intl-getcanonicallocales": "npm:2.5.6"
|
"@formatjs/intl-getcanonicallocales": "npm:2.5.6"
|
||||||
tslib: "npm:^2.8.0"
|
tslib: "npm:^2.8.0"
|
||||||
checksum: 10/42111a3002a5a2076b3eb012073230f69c62355dc03647bc17f4d0805f39c7e720e2281b359277d020fef623944a5bcc1ddc3dae9a3af74886d876147680147d
|
checksum: 10/865615561b4bad8b8d7d93539cae7eb3ed2d46b6156486ef3ccb1b8f9f46f075c7cf2f6e5325aba1cf07150e19280858dff7dfd86d530fbf45fd31ea4fabf8d4
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -1825,38 +1825,38 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@formatjs/intl-numberformat@npm:8.15.5":
|
"@formatjs/intl-numberformat@npm:8.15.6":
|
||||||
version: 8.15.5
|
version: 8.15.6
|
||||||
resolution: "@formatjs/intl-numberformat@npm:8.15.5"
|
resolution: "@formatjs/intl-numberformat@npm:8.15.6"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@formatjs/ecma402-abstract": "npm:2.3.5"
|
"@formatjs/ecma402-abstract": "npm:2.3.6"
|
||||||
"@formatjs/intl-localematcher": "npm:0.6.2"
|
"@formatjs/intl-localematcher": "npm:0.6.2"
|
||||||
decimal.js: "npm:^10.4.3"
|
decimal.js: "npm:^10.4.3"
|
||||||
tslib: "npm:^2.8.0"
|
tslib: "npm:^2.8.0"
|
||||||
checksum: 10/3440371a43c54cdd2aa3714cb518ad22e491dd19fbc0c046e712dde078d3f6ed709474376863d64d2bddb506957d1cf265d440f6723b88211044a7b56186e550
|
checksum: 10/674c5fefa0b14fcd7c58d0c0e592b4887dc2563fa5a11d80a0a82328ac12b2bb82b9a5367fa0a4d80060d61d15a1821bca7085e20cad09aa93b87edb3cff68ea
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@formatjs/intl-pluralrules@npm:5.4.5":
|
"@formatjs/intl-pluralrules@npm:5.4.6":
|
||||||
version: 5.4.5
|
version: 5.4.6
|
||||||
resolution: "@formatjs/intl-pluralrules@npm:5.4.5"
|
resolution: "@formatjs/intl-pluralrules@npm:5.4.6"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@formatjs/ecma402-abstract": "npm:2.3.5"
|
"@formatjs/ecma402-abstract": "npm:2.3.6"
|
||||||
"@formatjs/intl-localematcher": "npm:0.6.2"
|
"@formatjs/intl-localematcher": "npm:0.6.2"
|
||||||
decimal.js: "npm:^10.4.3"
|
decimal.js: "npm:^10.4.3"
|
||||||
tslib: "npm:^2.8.0"
|
tslib: "npm:^2.8.0"
|
||||||
checksum: 10/00f650891893b743d126dd2bf0d17c1b16a8c9e0e0dd94cd0895e66cb556246116263e9603204e1991924814d0ed3a3503765914aff08181d5e4435dfc5e547c
|
checksum: 10/88aa244e69ccfdf459899f5fa3c64df345f451ef91ce1188eab35b7e37daa225d22120f64be633f2cd8b826ea705d19831915118f555f2d17611ee842a9a86dc
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@formatjs/intl-relativetimeformat@npm:11.4.12":
|
"@formatjs/intl-relativetimeformat@npm:11.4.13":
|
||||||
version: 11.4.12
|
version: 11.4.13
|
||||||
resolution: "@formatjs/intl-relativetimeformat@npm:11.4.12"
|
resolution: "@formatjs/intl-relativetimeformat@npm:11.4.13"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@formatjs/ecma402-abstract": "npm:2.3.5"
|
"@formatjs/ecma402-abstract": "npm:2.3.6"
|
||||||
"@formatjs/intl-localematcher": "npm:0.6.2"
|
"@formatjs/intl-localematcher": "npm:0.6.2"
|
||||||
tslib: "npm:^2.8.0"
|
tslib: "npm:^2.8.0"
|
||||||
checksum: 10/f6adca59738cb7f58d2ea985558d8fc45e567406de6fb6e67894afe790e2a9fa1a19d34853afc36805fa4a3d638e29c62d6c6ba3ec2a85628c240081dcdfebc1
|
checksum: 10/c2058d5f29a13aa216d317d309a6ffd7d203f0fe11696b7bd524e17ac3cc22ae50ad56a26dbf18125e4c115a3e75f01e6cf2134a83df6c7916ae6d3fb21a1e9b
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -6925,10 +6925,10 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"core-js@npm:3.45.1":
|
"core-js@npm:3.46.0":
|
||||||
version: 3.45.1
|
version: 3.46.0
|
||||||
resolution: "core-js@npm:3.45.1"
|
resolution: "core-js@npm:3.46.0"
|
||||||
checksum: 10/b9dca79b1af8bb4f0d4af0752ea98d694fe157abaf55513fd4084df32dfd4398f0fc57898b32cdb643c1cecb87b9231c2a2ce535797c80ae328eac6d6078ee61
|
checksum: 10/82993ca487c6cbbf8bbf00e45eeb9705eb63dc2f9c90d7f35696733efbc3f4b52426e1f8dbef0f0b68ea16caa21e4f44cc5490e08120e1cad4a72b031ed8adaa
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -9192,15 +9192,15 @@ __metadata:
|
|||||||
"@codemirror/view": "npm:6.38.5"
|
"@codemirror/view": "npm:6.38.5"
|
||||||
"@date-fns/tz": "npm:1.4.1"
|
"@date-fns/tz": "npm:1.4.1"
|
||||||
"@egjs/hammerjs": "npm:2.0.17"
|
"@egjs/hammerjs": "npm:2.0.17"
|
||||||
"@formatjs/intl-datetimeformat": "npm:6.18.1"
|
"@formatjs/intl-datetimeformat": "npm:6.18.2"
|
||||||
"@formatjs/intl-displaynames": "npm:6.8.12"
|
"@formatjs/intl-displaynames": "npm:6.8.13"
|
||||||
"@formatjs/intl-durationformat": "npm:0.7.5"
|
"@formatjs/intl-durationformat": "npm:0.7.6"
|
||||||
"@formatjs/intl-getcanonicallocales": "npm:2.5.6"
|
"@formatjs/intl-getcanonicallocales": "npm:2.5.6"
|
||||||
"@formatjs/intl-listformat": "npm:7.7.12"
|
"@formatjs/intl-listformat": "npm:7.7.13"
|
||||||
"@formatjs/intl-locale": "npm:4.2.12"
|
"@formatjs/intl-locale": "npm:4.2.13"
|
||||||
"@formatjs/intl-numberformat": "npm:8.15.5"
|
"@formatjs/intl-numberformat": "npm:8.15.6"
|
||||||
"@formatjs/intl-pluralrules": "npm:5.4.5"
|
"@formatjs/intl-pluralrules": "npm:5.4.6"
|
||||||
"@formatjs/intl-relativetimeformat": "npm:11.4.12"
|
"@formatjs/intl-relativetimeformat": "npm:11.4.13"
|
||||||
"@fullcalendar/core": "npm:6.1.19"
|
"@fullcalendar/core": "npm:6.1.19"
|
||||||
"@fullcalendar/daygrid": "npm:6.1.19"
|
"@fullcalendar/daygrid": "npm:6.1.19"
|
||||||
"@fullcalendar/interaction": "npm:6.1.19"
|
"@fullcalendar/interaction": "npm:6.1.19"
|
||||||
@@ -9283,7 +9283,7 @@ __metadata:
|
|||||||
browserslist-useragent-regexp: "npm:4.1.3"
|
browserslist-useragent-regexp: "npm:4.1.3"
|
||||||
color-name: "npm:2.0.2"
|
color-name: "npm:2.0.2"
|
||||||
comlink: "npm:4.4.2"
|
comlink: "npm:4.4.2"
|
||||||
core-js: "npm:3.45.1"
|
core-js: "npm:3.46.0"
|
||||||
cropperjs: "npm:1.6.2"
|
cropperjs: "npm:1.6.2"
|
||||||
culori: "npm:4.0.2"
|
culori: "npm:4.0.2"
|
||||||
date-fns: "npm:4.1.0"
|
date-fns: "npm:4.1.0"
|
||||||
@@ -9317,7 +9317,7 @@ __metadata:
|
|||||||
html-minifier-terser: "npm:7.2.0"
|
html-minifier-terser: "npm:7.2.0"
|
||||||
husky: "npm:9.1.7"
|
husky: "npm:9.1.7"
|
||||||
idb-keyval: "npm:6.2.2"
|
idb-keyval: "npm:6.2.2"
|
||||||
intl-messageformat: "npm:10.7.17"
|
intl-messageformat: "npm:10.7.18"
|
||||||
js-yaml: "npm:4.1.0"
|
js-yaml: "npm:4.1.0"
|
||||||
jsdom: "npm:27.0.0"
|
jsdom: "npm:27.0.0"
|
||||||
jszip: "npm:3.10.1"
|
jszip: "npm:3.10.1"
|
||||||
@@ -9355,7 +9355,7 @@ __metadata:
|
|||||||
ts-lit-plugin: "npm:2.0.2"
|
ts-lit-plugin: "npm:2.0.2"
|
||||||
typescript: "npm:5.9.3"
|
typescript: "npm:5.9.3"
|
||||||
typescript-eslint: "npm:8.46.0"
|
typescript-eslint: "npm:8.46.0"
|
||||||
ua-parser-js: "npm:2.0.5"
|
ua-parser-js: "npm:2.0.6"
|
||||||
vite-tsconfig-paths: "npm:5.1.4"
|
vite-tsconfig-paths: "npm:5.1.4"
|
||||||
vitest: "npm:3.2.4"
|
vitest: "npm:3.2.4"
|
||||||
vue: "npm:2.7.16"
|
vue: "npm:2.7.16"
|
||||||
@@ -9712,15 +9712,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"intl-messageformat@npm:10.7.17":
|
"intl-messageformat@npm:10.7.18":
|
||||||
version: 10.7.17
|
version: 10.7.18
|
||||||
resolution: "intl-messageformat@npm:10.7.17"
|
resolution: "intl-messageformat@npm:10.7.18"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@formatjs/ecma402-abstract": "npm:2.3.5"
|
"@formatjs/ecma402-abstract": "npm:2.3.6"
|
||||||
"@formatjs/fast-memoize": "npm:2.2.7"
|
"@formatjs/fast-memoize": "npm:2.2.7"
|
||||||
"@formatjs/icu-messageformat-parser": "npm:2.11.3"
|
"@formatjs/icu-messageformat-parser": "npm:2.11.4"
|
||||||
tslib: "npm:^2.8.0"
|
tslib: "npm:^2.8.0"
|
||||||
checksum: 10/4f8c30c998bfc14eb64894414b94a8923045ab31d7bbf0978dab6621c644d451ff5c533c04ce8128163b74dd6d59061ec1ef3acb1cbab3302d31cbdb21947620
|
checksum: 10/96650d673912763d21bbfa14b50749b992d45f1901092a020e3155961e3c70f4644dd1731c3ecb1207a1eb94d84bedf4c34b1ac8127c29ad6b015b6a2a4045cb
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -14379,17 +14379,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"ua-parser-js@npm:2.0.5":
|
"ua-parser-js@npm:2.0.6":
|
||||||
version: 2.0.5
|
version: 2.0.6
|
||||||
resolution: "ua-parser-js@npm:2.0.5"
|
resolution: "ua-parser-js@npm:2.0.6"
|
||||||
dependencies:
|
dependencies:
|
||||||
detect-europe-js: "npm:^0.1.2"
|
detect-europe-js: "npm:^0.1.2"
|
||||||
is-standalone-pwa: "npm:^0.1.1"
|
is-standalone-pwa: "npm:^0.1.1"
|
||||||
ua-is-frozen: "npm:^0.1.2"
|
ua-is-frozen: "npm:^0.1.2"
|
||||||
undici: "npm:^7.12.0"
|
|
||||||
bin:
|
bin:
|
||||||
ua-parser-js: script/cli.js
|
ua-parser-js: script/cli.js
|
||||||
checksum: 10/e946cb1c85bfcd0f2d30c7d5e1b605e340bb458432e7e87fc4aa1b2f90117e4220521d4e0bc7dd8c2a5cadd0935dedb5ac434b70efdc0007221288c1d98b3cd5
|
checksum: 10/b0049d3b272979049c7df6af2ec2ce032e4351316b10c33699f6e3f0bec701336f67530cc3ccb363c554b1bb5047b75d2f46575699afacd6e541762ca3861f4d
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -14452,13 +14451,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"undici@npm:^7.12.0":
|
|
||||||
version: 7.16.0
|
|
||||||
resolution: "undici@npm:7.16.0"
|
|
||||||
checksum: 10/2bb71672b23d3dc0f56f1b7fb6c936e4487a350db46eaafc03f2f9107f99cdf8e51ecdd32e589e2381ef47a64b6369cfb31f328b2c3ea663023aa47bc5258b9e
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"unicode-canonical-property-names-ecmascript@npm:^2.0.0":
|
"unicode-canonical-property-names-ecmascript@npm:^2.0.0":
|
||||||
version: 2.0.1
|
version: 2.0.1
|
||||||
resolution: "unicode-canonical-property-names-ecmascript@npm:2.0.1"
|
resolution: "unicode-canonical-property-names-ecmascript@npm:2.0.1"
|
||||||
|
|||||||
Reference in New Issue
Block a user