mirror of
https://github.com/home-assistant/frontend.git
synced 2025-11-23 01:37:10 +00:00
Compare commits
8 Commits
add-device
...
water_devi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c38e03546 | ||
|
|
f41b2d0585 | ||
|
|
7be4ffcb83 | ||
|
|
869ab6ffc4 | ||
|
|
effba9b918 | ||
|
|
c848673b1f | ||
|
|
074095d3dc | ||
|
|
c472010ac5 |
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@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
|
uses: github/codeql-action/init@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
|
||||||
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@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
|
uses: github/codeql-action/autobuild@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
|
||||||
|
|
||||||
# ℹ️ 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@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
|
uses: github/codeql-action/analyze@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ export const mockEnergy = (hass: MockHomeAssistant) => {
|
|||||||
stat_consumption: "sensor.energy_boiler",
|
stat_consumption: "sensor.energy_boiler",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
device_consumption_water: [],
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
hass.mockWS(
|
hass.mockWS(
|
||||||
|
|||||||
@@ -115,7 +115,7 @@
|
|||||||
"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.18",
|
"intl-messageformat": "10.7.18",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.1",
|
||||||
"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",
|
||||||
"leaflet.markercluster": "1.5.3",
|
"leaflet.markercluster": "1.5.3",
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export class HaFilterChip extends FilterChip {
|
|||||||
var(--rgb-primary-text-color),
|
var(--rgb-primary-text-color),
|
||||||
0.15
|
0.15
|
||||||
);
|
);
|
||||||
|
--_label-text-font: var(--ha-font-family-body);
|
||||||
border-radius: var(--ha-border-radius-md);
|
border-radius: var(--ha-border-radius-md);
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ class SearchInput extends LitElement {
|
|||||||
return html`
|
return html`
|
||||||
<ha-textfield
|
<ha-textfield
|
||||||
.autofocus=${this.autofocus}
|
.autofocus=${this.autofocus}
|
||||||
|
autocomplete="off"
|
||||||
.label=${this.label || this.hass.localize("ui.common.search")}
|
.label=${this.label || this.hass.localize("ui.common.search")}
|
||||||
.value=${this.filter || ""}
|
.value=${this.filter || ""}
|
||||||
icon
|
icon
|
||||||
|
|||||||
@@ -200,6 +200,7 @@ export type EnergySource =
|
|||||||
export interface EnergyPreferences {
|
export interface EnergyPreferences {
|
||||||
energy_sources: EnergySource[];
|
energy_sources: EnergySource[];
|
||||||
device_consumption: DeviceConsumptionEnergyPreference[];
|
device_consumption: DeviceConsumptionEnergyPreference[];
|
||||||
|
device_consumption_water: DeviceConsumptionEnergyPreference[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EnergyInfo {
|
export interface EnergyInfo {
|
||||||
@@ -216,6 +217,7 @@ export interface EnergyValidationIssue {
|
|||||||
export interface EnergyPreferencesValidation {
|
export interface EnergyPreferencesValidation {
|
||||||
energy_sources: EnergyValidationIssue[][];
|
energy_sources: EnergyValidationIssue[][];
|
||||||
device_consumption: EnergyValidationIssue[][];
|
device_consumption: EnergyValidationIssue[][];
|
||||||
|
device_consumption_water: EnergyValidationIssue[][];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getEnergyInfo = (hass: HomeAssistant) =>
|
export const getEnergyInfo = (hass: HomeAssistant) =>
|
||||||
@@ -356,6 +358,11 @@ export const getReferencedStatisticIds = (
|
|||||||
if (!(includeTypes && !includeTypes.includes("device"))) {
|
if (!(includeTypes && !includeTypes.includes("device"))) {
|
||||||
statIDs.push(...prefs.device_consumption.map((d) => d.stat_consumption));
|
statIDs.push(...prefs.device_consumption.map((d) => d.stat_consumption));
|
||||||
}
|
}
|
||||||
|
if (!(includeTypes && !includeTypes.includes("water"))) {
|
||||||
|
statIDs.push(
|
||||||
|
...prefs.device_consumption_water.map((d) => d.stat_consumption)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return statIDs;
|
return statIDs;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1336,7 +1336,7 @@ class DialogAddAutomationElement
|
|||||||
--md-list-item-trailing-space: var(--md-list-item-leading-space);
|
--md-list-item-trailing-space: var(--md-list-item-leading-space);
|
||||||
--md-list-item-bottom-space: var(--ha-space-1);
|
--md-list-item-bottom-space: var(--ha-space-1);
|
||||||
--md-list-item-top-space: var(--md-list-item-bottom-space);
|
--md-list-item-top-space: var(--md-list-item-bottom-space);
|
||||||
--md-list-item-supporting-text-font: var(--ha-font-size-s);
|
--md-list-item-supporting-text-font: var(--ha-font-family-body);
|
||||||
--md-list-item-one-line-container-height: var(--ha-space-10);
|
--md-list-item-one-line-container-height: var(--ha-space-10);
|
||||||
}
|
}
|
||||||
ha-bottom-sheet .groups {
|
ha-bottom-sheet .groups {
|
||||||
@@ -1400,7 +1400,7 @@ class DialogAddAutomationElement
|
|||||||
--md-list-item-trailing-space: var(--md-list-item-leading-space);
|
--md-list-item-trailing-space: var(--md-list-item-leading-space);
|
||||||
--md-list-item-bottom-space: var(--ha-space-2);
|
--md-list-item-bottom-space: var(--ha-space-2);
|
||||||
--md-list-item-top-space: var(--md-list-item-bottom-space);
|
--md-list-item-top-space: var(--md-list-item-bottom-space);
|
||||||
--md-list-item-supporting-text-font: var(--ha-font-size-s);
|
--md-list-item-supporting-text-font: var(--ha-font-family-body);
|
||||||
gap: var(--ha-space-2);
|
gap: var(--ha-space-2);
|
||||||
padding: var(--ha-space-0) var(--ha-space-4);
|
padding: var(--ha-space-0) var(--ha-space-4);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,257 @@
|
|||||||
|
import {
|
||||||
|
mdiDelete,
|
||||||
|
mdiWater,
|
||||||
|
mdiDragHorizontalVariant,
|
||||||
|
mdiPencil,
|
||||||
|
mdiPlus,
|
||||||
|
} from "@mdi/js";
|
||||||
|
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||||
|
import { css, html, LitElement } from "lit";
|
||||||
|
import { repeat } from "lit/directives/repeat";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import "../../../../components/ha-card";
|
||||||
|
import "../../../../components/ha-button";
|
||||||
|
import "../../../../components/ha-icon-button";
|
||||||
|
import "../../../../components/ha-sortable";
|
||||||
|
import "../../../../components/ha-svg-icon";
|
||||||
|
import type {
|
||||||
|
DeviceConsumptionEnergyPreference,
|
||||||
|
EnergyPreferences,
|
||||||
|
EnergyPreferencesValidation,
|
||||||
|
} from "../../../../data/energy";
|
||||||
|
import { saveEnergyPreferences } from "../../../../data/energy";
|
||||||
|
import type { StatisticsMetaData } from "../../../../data/recorder";
|
||||||
|
import { getStatisticLabel } from "../../../../data/recorder";
|
||||||
|
import {
|
||||||
|
showAlertDialog,
|
||||||
|
showConfirmationDialog,
|
||||||
|
} from "../../../../dialogs/generic/show-dialog-box";
|
||||||
|
import { haStyle } from "../../../../resources/styles";
|
||||||
|
import type { HomeAssistant } from "../../../../types";
|
||||||
|
import { documentationUrl } from "../../../../util/documentation-url";
|
||||||
|
import { showEnergySettingsDeviceWaterDialog } from "../dialogs/show-dialogs-energy";
|
||||||
|
import "./ha-energy-validation-result";
|
||||||
|
import { energyCardStyles } from "./styles";
|
||||||
|
|
||||||
|
@customElement("ha-energy-device-settings-water")
|
||||||
|
export class EnergyDeviceSettingsWater extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
public preferences!: EnergyPreferences;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
public statsMetadata?: Record<string, StatisticsMetaData>;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
public validationResult?: EnergyPreferencesValidation;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<ha-card outlined>
|
||||||
|
<h1 class="card-header">
|
||||||
|
<ha-svg-icon .path=${mdiWater}></ha-svg-icon>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.energy.device_consumption_water.title"
|
||||||
|
)}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div class="card-content">
|
||||||
|
<p>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.energy.device_consumption_water.sub"
|
||||||
|
)}
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href=${documentationUrl(
|
||||||
|
this.hass,
|
||||||
|
"/docs/energy/water/#individual-devices"
|
||||||
|
)}
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.energy.device_consumption_water.learn_more"
|
||||||
|
)}</a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
${this.validationResult?.device_consumption_water.map(
|
||||||
|
(result) => html`
|
||||||
|
<ha-energy-validation-result
|
||||||
|
.hass=${this.hass}
|
||||||
|
.issues=${result}
|
||||||
|
></ha-energy-validation-result>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
<h3>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.energy.device_consumption_water.devices"
|
||||||
|
)}
|
||||||
|
</h3>
|
||||||
|
<ha-sortable handle-selector=".handle" @item-moved=${this._itemMoved}>
|
||||||
|
<div class="devices">
|
||||||
|
${repeat(
|
||||||
|
this.preferences.device_consumption_water,
|
||||||
|
(device) => device.stat_consumption,
|
||||||
|
(device) => html`
|
||||||
|
<div class="row" .device=${device}>
|
||||||
|
<div class="handle">
|
||||||
|
<ha-svg-icon
|
||||||
|
.path=${mdiDragHorizontalVariant}
|
||||||
|
></ha-svg-icon>
|
||||||
|
</div>
|
||||||
|
<span class="content"
|
||||||
|
>${device.name ||
|
||||||
|
getStatisticLabel(
|
||||||
|
this.hass,
|
||||||
|
device.stat_consumption,
|
||||||
|
this.statsMetadata?.[device.stat_consumption]
|
||||||
|
)}</span
|
||||||
|
>
|
||||||
|
<ha-icon-button
|
||||||
|
.label=${this.hass.localize("ui.common.edit")}
|
||||||
|
@click=${this._editDevice}
|
||||||
|
.path=${mdiPencil}
|
||||||
|
></ha-icon-button>
|
||||||
|
<ha-icon-button
|
||||||
|
.label=${this.hass.localize("ui.common.delete")}
|
||||||
|
@click=${this._deleteDevice}
|
||||||
|
.device=${device}
|
||||||
|
.path=${mdiDelete}
|
||||||
|
></ha-icon-button>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ha-sortable>
|
||||||
|
<div class="row">
|
||||||
|
<ha-svg-icon .path=${mdiWater}></ha-svg-icon>
|
||||||
|
<ha-button
|
||||||
|
@click=${this._addDevice}
|
||||||
|
appearance="filled"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<ha-svg-icon slot="start" .path=${mdiPlus}></ha-svg-icon
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.energy.device_consumption_water.add_device"
|
||||||
|
)}</ha-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _itemMoved(ev: CustomEvent): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const { oldIndex, newIndex } = ev.detail;
|
||||||
|
const devices = this.preferences.device_consumption_water.concat();
|
||||||
|
const device = devices.splice(oldIndex, 1)[0];
|
||||||
|
devices.splice(newIndex, 0, device);
|
||||||
|
|
||||||
|
const newPrefs = {
|
||||||
|
...this.preferences,
|
||||||
|
device_consumption_water: devices,
|
||||||
|
};
|
||||||
|
fireEvent(this, "value-changed", { value: newPrefs });
|
||||||
|
this._savePreferences(newPrefs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _editDevice(ev) {
|
||||||
|
const origDevice: DeviceConsumptionEnergyPreference =
|
||||||
|
ev.currentTarget.closest(".row").device;
|
||||||
|
showEnergySettingsDeviceWaterDialog(this, {
|
||||||
|
statsMetadata: this.statsMetadata,
|
||||||
|
device: { ...origDevice },
|
||||||
|
device_consumptions: this.preferences
|
||||||
|
.device_consumption_water as DeviceConsumptionEnergyPreference[],
|
||||||
|
saveCallback: async (newDevice) => {
|
||||||
|
const newPrefs = {
|
||||||
|
...this.preferences,
|
||||||
|
device_consumption_water:
|
||||||
|
this.preferences.device_consumption_water.map((d) =>
|
||||||
|
d === origDevice ? newDevice : d
|
||||||
|
),
|
||||||
|
};
|
||||||
|
this._sanitizeParents(newPrefs);
|
||||||
|
await this._savePreferences(newPrefs);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addDevice() {
|
||||||
|
showEnergySettingsDeviceWaterDialog(this, {
|
||||||
|
statsMetadata: this.statsMetadata,
|
||||||
|
device_consumptions: this.preferences
|
||||||
|
.device_consumption_water as DeviceConsumptionEnergyPreference[],
|
||||||
|
saveCallback: async (device) => {
|
||||||
|
await this._savePreferences({
|
||||||
|
...this.preferences,
|
||||||
|
device_consumption_water:
|
||||||
|
this.preferences.device_consumption_water.concat(device),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _sanitizeParents(prefs: EnergyPreferences) {
|
||||||
|
const statIds = prefs.device_consumption_water.map(
|
||||||
|
(d) => d.stat_consumption
|
||||||
|
);
|
||||||
|
prefs.device_consumption_water.forEach((d) => {
|
||||||
|
if (d.included_in_stat && !statIds.includes(d.included_in_stat)) {
|
||||||
|
delete d.included_in_stat;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _deleteDevice(ev) {
|
||||||
|
const deviceToDelete: DeviceConsumptionEnergyPreference =
|
||||||
|
ev.currentTarget.device;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!(await showConfirmationDialog(this, {
|
||||||
|
title: this.hass.localize("ui.panel.config.energy.delete_source"),
|
||||||
|
}))
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const newPrefs = {
|
||||||
|
...this.preferences,
|
||||||
|
device_consumption_water:
|
||||||
|
this.preferences.device_consumption_water.filter(
|
||||||
|
(device) => device !== deviceToDelete
|
||||||
|
),
|
||||||
|
};
|
||||||
|
this._sanitizeParents(newPrefs);
|
||||||
|
await this._savePreferences(newPrefs);
|
||||||
|
} catch (err: any) {
|
||||||
|
showAlertDialog(this, { title: `Failed to save config: ${err.message}` });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _savePreferences(preferences: EnergyPreferences) {
|
||||||
|
const result = await saveEnergyPreferences(this.hass, preferences);
|
||||||
|
fireEvent(this, "value-changed", { value: result });
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return [
|
||||||
|
haStyle,
|
||||||
|
energyCardStyles,
|
||||||
|
css`
|
||||||
|
.handle {
|
||||||
|
cursor: move; /* fallback if grab cursor is unsupported */
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-energy-device-settings-water": EnergyDeviceSettingsWater;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,268 @@
|
|||||||
|
import { mdiWater } 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 { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||||
|
import "../../../../components/entity/ha-entity-picker";
|
||||||
|
import "../../../../components/entity/ha-statistic-picker";
|
||||||
|
import "../../../../components/ha-dialog";
|
||||||
|
import "../../../../components/ha-radio";
|
||||||
|
import "../../../../components/ha-button";
|
||||||
|
import "../../../../components/ha-select";
|
||||||
|
import "../../../../components/ha-list-item";
|
||||||
|
import type { DeviceConsumptionEnergyPreference } from "../../../../data/energy";
|
||||||
|
import { energyStatisticHelpUrl } from "../../../../data/energy";
|
||||||
|
import { getStatisticLabel } from "../../../../data/recorder";
|
||||||
|
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 { EnergySettingsDeviceWaterDialogParams } from "./show-dialogs-energy";
|
||||||
|
|
||||||
|
const volumeUnitClasses = ["volume"];
|
||||||
|
|
||||||
|
@customElement("dialog-energy-device-settings-water")
|
||||||
|
export class DialogEnergyDeviceSettingsWater
|
||||||
|
extends LitElement
|
||||||
|
implements HassDialog<EnergySettingsDeviceWaterDialogParams>
|
||||||
|
{
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@state() private _params?: EnergySettingsDeviceWaterDialogParams;
|
||||||
|
|
||||||
|
@state() private _device?: DeviceConsumptionEnergyPreference;
|
||||||
|
|
||||||
|
@state() private _volume_units?: string[];
|
||||||
|
|
||||||
|
@state() private _error?: string;
|
||||||
|
|
||||||
|
private _excludeList?: string[];
|
||||||
|
|
||||||
|
private _possibleParents: DeviceConsumptionEnergyPreference[] = [];
|
||||||
|
|
||||||
|
public async showDialog(
|
||||||
|
params: EnergySettingsDeviceWaterDialogParams
|
||||||
|
): Promise<void> {
|
||||||
|
this._params = params;
|
||||||
|
this._device = this._params.device;
|
||||||
|
this._computePossibleParents();
|
||||||
|
this._volume_units = (
|
||||||
|
await getSensorDeviceClassConvertibleUnits(this.hass, "water")
|
||||||
|
).units;
|
||||||
|
this._excludeList = this._params.device_consumptions
|
||||||
|
.map((entry) => entry.stat_consumption)
|
||||||
|
.filter((id) => id !== this._device?.stat_consumption);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computePossibleParents() {
|
||||||
|
if (!this._device || !this._params) {
|
||||||
|
this._possibleParents = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const children: string[] = [];
|
||||||
|
const devices = this._params.device_consumptions;
|
||||||
|
function getChildren(stat) {
|
||||||
|
devices.forEach((d) => {
|
||||||
|
if (d.included_in_stat === stat) {
|
||||||
|
children.push(d.stat_consumption);
|
||||||
|
getChildren(d.stat_consumption);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
getChildren(this._device.stat_consumption);
|
||||||
|
this._possibleParents = this._params.device_consumptions.filter(
|
||||||
|
(d) =>
|
||||||
|
d.stat_consumption !== this._device!.stat_consumption &&
|
||||||
|
d.stat_consumption !== this._params?.device?.stat_consumption &&
|
||||||
|
!children.includes(d.stat_consumption)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public closeDialog() {
|
||||||
|
this._params = undefined;
|
||||||
|
this._device = undefined;
|
||||||
|
this._error = undefined;
|
||||||
|
this._excludeList = undefined;
|
||||||
|
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (!this._params) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pickableUnit = this._volume_units?.join(", ") || "";
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-dialog
|
||||||
|
open
|
||||||
|
.heading=${html`<ha-svg-icon
|
||||||
|
.path=${mdiWater}
|
||||||
|
style="--mdc-icon-size: 32px;"
|
||||||
|
></ha-svg-icon>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.energy.device_consumption_water.dialog.header"
|
||||||
|
)}`}
|
||||||
|
@closed=${this.closeDialog}
|
||||||
|
>
|
||||||
|
${this._error ? html`<p class="error">${this._error}</p>` : ""}
|
||||||
|
<div>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.energy.device_consumption_water.dialog.selected_stat_intro",
|
||||||
|
{ unit: pickableUnit }
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ha-statistic-picker
|
||||||
|
.hass=${this.hass}
|
||||||
|
.helpMissingEntityUrl=${energyStatisticHelpUrl}
|
||||||
|
.includeUnitClass=${volumeUnitClasses}
|
||||||
|
.value=${this._device?.stat_consumption}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.energy.device_consumption_water.dialog.device_consumption_water"
|
||||||
|
)}
|
||||||
|
.excludeStatistics=${this._excludeList}
|
||||||
|
@value-changed=${this._statisticChanged}
|
||||||
|
dialogInitialFocus
|
||||||
|
></ha-statistic-picker>
|
||||||
|
|
||||||
|
<ha-textfield
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.energy.device_consumption_water.dialog.display_name"
|
||||||
|
)}
|
||||||
|
type="text"
|
||||||
|
.disabled=${!this._device}
|
||||||
|
.value=${this._device?.name || ""}
|
||||||
|
.placeholder=${this._device
|
||||||
|
? getStatisticLabel(
|
||||||
|
this.hass,
|
||||||
|
this._device.stat_consumption,
|
||||||
|
this._params?.statsMetadata?.[this._device.stat_consumption]
|
||||||
|
)
|
||||||
|
: ""}
|
||||||
|
@input=${this._nameChanged}
|
||||||
|
>
|
||||||
|
</ha-textfield>
|
||||||
|
|
||||||
|
<ha-select
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.energy.device_consumption_water.dialog.included_in_device"
|
||||||
|
)}
|
||||||
|
.value=${this._device?.included_in_stat || ""}
|
||||||
|
.helper=${this.hass.localize(
|
||||||
|
"ui.panel.config.energy.device_consumption_water.dialog.included_in_device_helper"
|
||||||
|
)}
|
||||||
|
.disabled=${!this._device}
|
||||||
|
@selected=${this._parentSelected}
|
||||||
|
@closed=${stopPropagation}
|
||||||
|
fixedMenuPosition
|
||||||
|
naturalMenuWidth
|
||||||
|
clearable
|
||||||
|
>
|
||||||
|
${!this._possibleParents.length
|
||||||
|
? html`
|
||||||
|
<ha-list-item disabled value="-"
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.energy.device_consumption_water.dialog.no_upstream_devices"
|
||||||
|
)}</ha-list-item
|
||||||
|
>
|
||||||
|
`
|
||||||
|
: this._possibleParents.map(
|
||||||
|
(stat) => html`
|
||||||
|
<ha-list-item .value=${stat.stat_consumption}
|
||||||
|
>${stat.name ||
|
||||||
|
getStatisticLabel(
|
||||||
|
this.hass,
|
||||||
|
stat.stat_consumption,
|
||||||
|
this._params?.statsMetadata?.[stat.stat_consumption]
|
||||||
|
)}</ha-list-item
|
||||||
|
>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</ha-select>
|
||||||
|
|
||||||
|
<ha-button
|
||||||
|
appearance="plain"
|
||||||
|
@click=${this.closeDialog}
|
||||||
|
slot="primaryAction"
|
||||||
|
>
|
||||||
|
${this.hass.localize("ui.common.cancel")}
|
||||||
|
</ha-button>
|
||||||
|
<ha-button
|
||||||
|
@click=${this._save}
|
||||||
|
.disabled=${!this._device}
|
||||||
|
slot="primaryAction"
|
||||||
|
>
|
||||||
|
${this.hass.localize("ui.common.save")}
|
||||||
|
</ha-button>
|
||||||
|
</ha-dialog>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _statisticChanged(ev: CustomEvent<{ value: string }>) {
|
||||||
|
if (!ev.detail.value) {
|
||||||
|
this._device = undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._device = { stat_consumption: ev.detail.value };
|
||||||
|
this._computePossibleParents();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _nameChanged(ev) {
|
||||||
|
const newDevice = {
|
||||||
|
...this._device!,
|
||||||
|
name: ev.target!.value,
|
||||||
|
} as DeviceConsumptionEnergyPreference;
|
||||||
|
if (!newDevice.name) {
|
||||||
|
delete newDevice.name;
|
||||||
|
}
|
||||||
|
this._device = newDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _parentSelected(ev) {
|
||||||
|
const newDevice = {
|
||||||
|
...this._device!,
|
||||||
|
included_in_stat: ev.target!.value,
|
||||||
|
} as DeviceConsumptionEnergyPreference;
|
||||||
|
if (!newDevice.included_in_stat) {
|
||||||
|
delete newDevice.included_in_stat;
|
||||||
|
}
|
||||||
|
this._device = newDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _save() {
|
||||||
|
try {
|
||||||
|
await this._params!.saveCallback(this._device!);
|
||||||
|
this.closeDialog();
|
||||||
|
} catch (err: any) {
|
||||||
|
this._error = err.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return [
|
||||||
|
haStyleDialog,
|
||||||
|
css`
|
||||||
|
ha-statistic-picker {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
ha-select {
|
||||||
|
margin-top: 16px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
ha-textfield {
|
||||||
|
margin-top: 16px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"dialog-energy-device-settings-water": DialogEnergyDeviceSettingsWater;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -83,6 +83,13 @@ export interface EnergySettingsDeviceDialogParams {
|
|||||||
saveCallback: (device: DeviceConsumptionEnergyPreference) => Promise<void>;
|
saveCallback: (device: DeviceConsumptionEnergyPreference) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface EnergySettingsDeviceWaterDialogParams {
|
||||||
|
device?: DeviceConsumptionEnergyPreference;
|
||||||
|
device_consumptions: DeviceConsumptionEnergyPreference[];
|
||||||
|
statsMetadata?: Record<string, StatisticsMetaData>;
|
||||||
|
saveCallback: (device: DeviceConsumptionEnergyPreference) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
export const showEnergySettingsDeviceDialog = (
|
export const showEnergySettingsDeviceDialog = (
|
||||||
element: HTMLElement,
|
element: HTMLElement,
|
||||||
dialogParams: EnergySettingsDeviceDialogParams
|
dialogParams: EnergySettingsDeviceDialogParams
|
||||||
@@ -160,6 +167,17 @@ export const showEnergySettingsGridFlowToDialog = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const showEnergySettingsDeviceWaterDialog = (
|
||||||
|
element: HTMLElement,
|
||||||
|
dialogParams: EnergySettingsDeviceWaterDialogParams
|
||||||
|
): void => {
|
||||||
|
fireEvent(element, "show-dialog", {
|
||||||
|
dialogTag: "dialog-energy-device-settings-water",
|
||||||
|
dialogImport: () => import("./dialog-energy-device-settings-water"),
|
||||||
|
dialogParams: dialogParams,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const showEnergySettingsGridPowerDialog = (
|
export const showEnergySettingsGridPowerDialog = (
|
||||||
element: HTMLElement,
|
element: HTMLElement,
|
||||||
dialogParams: EnergySettingsGridPowerDialogParams
|
dialogParams: EnergySettingsGridPowerDialogParams
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { haStyle } from "../../../resources/styles";
|
|||||||
import type { HomeAssistant, Route } from "../../../types";
|
import type { HomeAssistant, Route } from "../../../types";
|
||||||
import "../../../components/ha-alert";
|
import "../../../components/ha-alert";
|
||||||
import "./components/ha-energy-device-settings";
|
import "./components/ha-energy-device-settings";
|
||||||
|
import "./components/ha-energy-device-settings-water";
|
||||||
import "./components/ha-energy-grid-settings";
|
import "./components/ha-energy-grid-settings";
|
||||||
import "./components/ha-energy-solar-settings";
|
import "./components/ha-energy-solar-settings";
|
||||||
import "./components/ha-energy-battery-settings";
|
import "./components/ha-energy-battery-settings";
|
||||||
@@ -32,6 +33,7 @@ import { fileDownload } from "../../../util/file_download";
|
|||||||
const INITIAL_CONFIG: EnergyPreferences = {
|
const INITIAL_CONFIG: EnergyPreferences = {
|
||||||
energy_sources: [],
|
energy_sources: [],
|
||||||
device_consumption: [],
|
device_consumption: [],
|
||||||
|
device_consumption_water: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
@customElement("ha-config-energy")
|
@customElement("ha-config-energy")
|
||||||
@@ -142,6 +144,13 @@ class HaConfigEnergy extends LitElement {
|
|||||||
.validationResult=${this._validationResult}
|
.validationResult=${this._validationResult}
|
||||||
@value-changed=${this._prefsChanged}
|
@value-changed=${this._prefsChanged}
|
||||||
></ha-energy-device-settings>
|
></ha-energy-device-settings>
|
||||||
|
<ha-energy-device-settings-water
|
||||||
|
.hass=${this.hass}
|
||||||
|
.preferences=${this._preferences!}
|
||||||
|
.statsMetadata=${this._statsMetadata}
|
||||||
|
.validationResult=${this._validationResult}
|
||||||
|
@value-changed=${this._prefsChanged}
|
||||||
|
></ha-energy-device-settings-water>
|
||||||
</div>
|
</div>
|
||||||
</hass-subpage>
|
</hass-subpage>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export class EnergySetupWizard extends LitElement implements LovelaceCard {
|
|||||||
@state() private _preferences: EnergyPreferences = {
|
@state() private _preferences: EnergyPreferences = {
|
||||||
energy_sources: [],
|
energy_sources: [],
|
||||||
device_consumption: [],
|
device_consumption: [],
|
||||||
|
device_consumption_water: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
public getCardSize() {
|
public getCardSize() {
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ function formatTooltip(
|
|||||||
countNegative++;
|
countNegative++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return `${param.marker} ${filterXSS(param.seriesName!)}: ${value} ${unit}`;
|
return `${param.marker} ${filterXSS(param.seriesName!)}: <div style="direction:ltr; display: inline;">${value} ${unit}</div>`;
|
||||||
})
|
})
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
let footer = "";
|
let footer = "";
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ export class HuiEnergyDevicesGraphCard
|
|||||||
this.hass.locale,
|
this.hass.locale,
|
||||||
params.value < 0.1 ? { maximumFractionDigits: 3 } : undefined
|
params.value < 0.1 ? { maximumFractionDigits: 3 } : undefined
|
||||||
)} kWh`;
|
)} kWh`;
|
||||||
return `${title}${params.marker} ${params.seriesName}: ${value}`;
|
return `${title}${params.marker} ${params.seriesName}: <div style="direction:ltr; display: inline;">${value}</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _createOptions = memoizeOne(
|
private _createOptions = memoizeOne(
|
||||||
|
|||||||
@@ -402,7 +402,9 @@ class HuiEnergySankeyCard
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _valueFormatter = (value: number) =>
|
private _valueFormatter = (value: number) =>
|
||||||
`${formatNumber(value, this.hass.locale, value < 0.1 ? { maximumFractionDigits: 3 } : undefined)} kWh`;
|
`<div style="direction:ltr; display: inline;">
|
||||||
|
${formatNumber(value, this.hass.locale, value < 0.1 ? { maximumFractionDigits: 3 } : undefined)}
|
||||||
|
kWh</div>`;
|
||||||
|
|
||||||
protected _groupByFloorAndArea(deviceNodes: Node[]) {
|
protected _groupByFloorAndArea(deviceNodes: Node[]) {
|
||||||
const areas: Record<string, { value: number; devices: Node[] }> = {
|
const areas: Record<string, { value: number; devices: Node[] }> = {
|
||||||
|
|||||||
@@ -3225,6 +3225,22 @@
|
|||||||
"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"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"device_consumption_water": {
|
||||||
|
"title": "Individual water devices",
|
||||||
|
"sub": "Tracking the water usage of individual devices allows Home Assistant to break down your water usage by device.",
|
||||||
|
"learn_more": "More information on how to get started.",
|
||||||
|
"devices": "Devices",
|
||||||
|
"add_device": "Add device",
|
||||||
|
"dialog": {
|
||||||
|
"header": "Add a water device",
|
||||||
|
"display_name": "Display name",
|
||||||
|
"device_consumption_water": "Device water consumption",
|
||||||
|
"selected_stat_intro": "Select the water sensor that measures the device's water usage in either of {unit}.",
|
||||||
|
"included_in_device": "Upstream device",
|
||||||
|
"included_in_device_helper": "If this device is already counted by another device (such as a water meter measured by the main water supply), selecting the upstream device prevents duplicate water tracking.",
|
||||||
|
"no_upstream_devices": "No eligible upstream devices"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"helpers": {
|
"helpers": {
|
||||||
|
|||||||
10
yarn.lock
10
yarn.lock
@@ -9337,7 +9337,7 @@ __metadata:
|
|||||||
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.18"
|
intl-messageformat: "npm:10.7.18"
|
||||||
js-yaml: "npm:4.1.0"
|
js-yaml: "npm:4.1.1"
|
||||||
jsdom: "npm:27.1.0"
|
jsdom: "npm:27.1.0"
|
||||||
jszip: "npm:3.10.1"
|
jszip: "npm:3.10.1"
|
||||||
leaflet: "npm:1.9.4"
|
leaflet: "npm:1.9.4"
|
||||||
@@ -10407,14 +10407,14 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"js-yaml@npm:4.1.0, js-yaml@npm:^4.1.0":
|
"js-yaml@npm:4.1.1, js-yaml@npm:^4.1.0":
|
||||||
version: 4.1.0
|
version: 4.1.1
|
||||||
resolution: "js-yaml@npm:4.1.0"
|
resolution: "js-yaml@npm:4.1.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
argparse: "npm:^2.0.1"
|
argparse: "npm:^2.0.1"
|
||||||
bin:
|
bin:
|
||||||
js-yaml: bin/js-yaml.js
|
js-yaml: bin/js-yaml.js
|
||||||
checksum: 10/c138a34a3fd0d08ebaf71273ad4465569a483b8a639e0b118ff65698d257c2791d3199e3f303631f2cb98213fa7b5f5d6a4621fd0fff819421b990d30d967140
|
checksum: 10/a52d0519f0f4ef5b4adc1cde466cb54c50d56e2b4a983b9d5c9c0f2f99462047007a6274d7e95617a21d3c91fde3ee6115536ed70991cd645ba8521058b78f77
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user