Compare commits

..

77 Commits

Author SHA1 Message Date
Petar Petrov
9001cd3e65 Replace gauges with energy usage graph in energy overview (#28150) 2025-11-26 17:37:18 +01:00
renovate[bot]
ca8923d8f4 Update dependency glob to v13 (#28135)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-26 18:13:05 +02:00
Bram Kragten
e7e4407a09 Merge branch 'rc' into dev 2025-11-26 16:10:48 +01:00
Wendelin
3f0c9538bd Remove SubscribeMixin from automation and Z-Wave JS dialog components (#28146) 2025-11-26 15:05:47 +00:00
Bram Kragten
5c3ccbfdad Remove hard coded mqtt trigger, and migrate to new format (#28143) 2025-11-26 14:56:26 +00:00
Bram Kragten
9710142c47 Resubscribe to descriptions when labs feat changes (#28145) 2025-11-26 15:54:31 +01:00
Petar Petrov
57640d17cd Add show_only_totals option to energy sources table (#28147) 2025-11-26 15:49:14 +01:00
Wendelin
b5d93e7515 Remove chains of new conditions (#28140) 2025-11-26 15:09:40 +01:00
Steven Travers
ca9b29d82a Show encryption key in actions for esphome device (#28080)
Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
2025-11-26 13:43:58 +00:00
Bram Kragten
51efa4f61f Set defaults for platform triggers and conditions (#28138) 2025-11-26 14:05:39 +01:00
Bram Kragten
a6c71719d1 Only show section title when it has content (#28141) 2025-11-26 12:56:55 +00:00
Bram Kragten
ccc48d158a Bumped version to 20251105.1 2025-11-21 13:50:20 +01:00
Bram Kragten
b11e787f09 Dont add store token for external auth flows (#28026)
* Dont add store token for external auth flows

* Apply suggestion from @MindFreeze

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2025-11-21 13:50:04 +01:00
renovate[bot]
cdb6562de8 Update dependency js-yaml to v4.1.1 [SECURITY] (#27955)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-21 13:48:48 +01:00
karwosts
d8e8c9aa02 Fix media image on dashboard-level background (#27934) 2025-11-21 13:48:10 +01:00
Petar Petrov
be392be1e6 Increase ZHA reconfiguration dialog width for details view (#27909) 2025-11-21 13:44:41 +01:00
Petar Petrov
10dc432445 Fix entity name in statistics chart (#27896) 2025-11-21 13:44:40 +01:00
Wendelin
19187f887d Fix target picker for entity_id: none (#27893)
Fix notFound condition to exclude 'none' in ha-target-picker-item-row
2025-11-21 13:44:40 +01:00
Petar Petrov
dc76a42aaa Smooth sensor card more when "Show more detail" is disabled (#27891)
* Smooth sensor card more when "Show more detail" is disabled

* Set minimum sample points to 10
2025-11-21 13:44:38 +01:00
Wendelin
1f2b8047a6 Use ha-ripple in ha-md-list-item (#27889) 2025-11-21 13:44:38 +01:00
Petar Petrov
e8c9ed0528 Dynamic total energy for pie chart (#27883) 2025-11-21 13:44:37 +01:00
Petar Petrov
c7ae78c02f Fix chart label outline color (#27882) 2025-11-21 13:44:36 +01:00
karwosts
dc8f1211e6 Fix entity editor with non-existant entity (#27875) 2025-11-21 13:44:35 +01:00
Yuksel Beyti
5c25a63ea5 Fix malformed HTML tags in backup backups component (#27872) 2025-11-21 13:44:34 +01:00
Paul Bottein
c1787ab994 Fix backup download and delete actions (#27851) 2025-11-21 13:44:33 +01:00
Paul Bottein
6fea535fdc Fix OHF logo theme (#27830) 2025-11-21 13:44:32 +01:00
Wendelin
e8cee84380 Fix floor details area picker (#27827) 2025-11-21 13:44:31 +01:00
Wendelin
b4613edeb7 Target picker row check if not found entity isn't "all" (#27826)
Target picker row check if not found entity isn't all
2025-11-21 13:44:30 +01:00
Wendelin
a8b6e5aa3d Add trigger/condition/action dialog: select single search result with enter key (#27825)
* Add trigger/condition/action dialog: select single search result with enter key

* Update src/panels/config/automation/add-automation-element-dialog.ts

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2025-11-21 13:44:29 +01:00
Jan Bouwhuis
e842193cd6 Fix index for service action translation in service action dialog (#27824) 2025-11-21 13:44:28 +01:00
Bram Kragten
bb0813333d Fix landing page build (#27817) 2025-11-21 13:44:27 +01:00
Petar Petrov
ab4c6f80f4 Disable graph resize animation for general resizing (#27816) 2025-11-21 13:44:26 +01:00
Bram Kragten
89796e425a Bumped version to 20251105.0 2025-11-05 15:26:35 +01:00
Wendelin
9c42c8bbc4 Add fallback icon for domain template (#27814) 2025-11-05 15:25:54 +01:00
Wendelin
616237caee Fix target picker with empty sections (#27813) 2025-11-05 15:25:53 +01:00
Wendelin
2d36a0d37f Add trigger/condition/action dialog - Show device group always on top (#27812)
add automation element dialog Device always on top
2025-11-05 15:25:52 +01:00
Wendelin
1ec432a20f Change add trigger/condition/action dialog title (#27811)
Change add dialog title
2025-11-05 15:25:51 +01:00
Wendelin
afd91b2261 Fix auth language picker styles (#27805) 2025-11-05 15:25:50 +01:00
Paul Bottein
cdfb7f914f Fix target picker in logbook card editor (#27804)
Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
2025-11-05 15:24:49 +01:00
Wendelin
33b0897522 Add trigger/condition/action dialog: fix empty elements in search results (#27802) 2025-11-05 15:16:14 +01:00
Wendelin
5f0cf1b522 Add condition/action dialog: blocks title (#27801) 2025-11-05 15:16:13 +01:00
Wendelin
afb2ad95a4 Fix target picker in card editor (#27800) 2025-11-05 15:16:12 +01:00
Jan-Philipp Benecke
27beab3133 Fix z-index for target picker item row icon (#27798) 2025-11-05 15:16:10 +01:00
Wendelin
435c82489b Fix assist conversation language picker (#27764) 2025-11-05 15:16:09 +01:00
Bram Kragten
3ba6bf272e Bumped version to 20251104.0 2025-11-04 18:21:11 +01:00
Paul Bottein
eec99b2fa3 Rename safety panel to security panel (#27796) 2025-11-04 18:20:10 +01:00
Bram Kragten
d23e45e410 Handle unknown items in target picker (#27795)
* Handle unknown items in target picker

* Update ha-target-picker-item-row.ts

* update colors

* fallback to domain icons
2025-11-04 18:19:02 +01:00
Paul Bottein
3c82d12609 Auto refresh summary dashboard when registries changed (#27794) 2025-11-04 18:17:46 +01:00
Paul Bottein
15d67997e7 Don't show summary card if summary dashboards are empty (#27788)
Don't show summary card if summary dashboard are empty
2025-11-04 18:16:37 +01:00
Paul Bottein
a6dfcb3100 Fix tooltip hide delay (#27786) 2025-11-04 18:16:37 +01:00
karwosts
26c2369228 Fix sankey with external statistics devices (#27784) 2025-11-04 18:16:35 +01:00
Paul Bottein
2eed446492 Display entities without area in summary dashboard (#27777)
* Add support for no area, no floor and no device in entity filter

* Display entities without area in summary dashboard
2025-11-04 18:16:34 +01:00
Wendelin
7ebdeab6b2 Fix-labels-yaml-helper (#27776)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2025-11-04 18:16:33 +01:00
Tobias Bieniek
0c35278f51 Hide media players summary when no entities exist (#27642) 2025-11-04 18:16:32 +01:00
Bram Kragten
561122f03d Bumped version to 20251103.0 2025-11-03 16:34:11 +01:00
Petar Petrov
95311be034 Apply theme variables to pi charts (#27773) 2025-11-03 16:34:07 +01:00
Wendelin
1eda44a102 Fix selected element text color (#27771) 2025-11-03 16:32:51 +01:00
Petar Petrov
d76781eb91 Fix for Y axis label formatting in history graph (#27770) 2025-11-03 16:32:50 +01:00
Petar Petrov
82d44e051f Fix sensor card graph in Safari (#27768) 2025-11-03 16:32:49 +01:00
Aidan Timson
fdc9f5a3b7 Use supervisor endpoint for downloading logs (when avaliable) (#27765) 2025-11-03 16:32:47 +01:00
Paul Bottein
ee6c82aba9 Don't show tooltip on overflow menu in dashboard toolbar (#27763) 2025-11-03 16:32:46 +01:00
Paul Bottein
11d3f5c2ba Fix suggest cards dialog for sections view (#27762) 2025-11-03 16:32:45 +01:00
Aarni Koskela
feb68ce373 Add support for PM4 sensor state (#27754) 2025-11-03 16:32:44 +01:00
Simon Lamon
7f9a9de157 Move label translations to ui.dialog (#27752) 2025-11-03 16:32:43 +01:00
Simon Lamon
8e1b6a3d3b Fixes in backup overflow (#27745)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2025-11-03 16:32:41 +01:00
Jan-Philipp Benecke
6e6e5a53e2 Fix button text overflow (#27744) 2025-11-03 16:32:41 +01:00
Bram Kragten
0408734ec5 Bumped version to 20251029.1 2025-10-30 18:19:31 +01:00
Paul Bottein
317519fc08 Revert "Fix entities card size and add grid contstraints" (#27725) 2025-10-30 18:18:27 +01:00
Paul Bottein
843d79eab4 Don't show tooltip for ha button menu in top bar (#27723) 2025-10-30 18:18:26 +01:00
Paul Bottein
165a757f06 Revert entity naming in target picker chips (#27722) 2025-10-30 18:18:25 +01:00
Aidan Timson
ea8b730142 Revert "Migrate dialog-device-registry-detail to ha-wa-dialog (#27668)" (#27716)
This reverts commit 2a8d935601.
2025-10-30 18:18:24 +01:00
Paul Bottein
e88c97d625 Use entity naming in more cards (#27714)
* Use entity naming in more cards

* Migrate statistic card

* Fix localize
2025-10-30 18:18:23 +01:00
Aidan Timson
7560988b76 Trend feature: make sure content is centered when loading (#27708)
* Make sure content is centered when loading

* Restore from test
2025-10-30 18:18:22 +01:00
Aidan Timson
eecd8077b6 Calendar card height: account for title and stop overflow (#27707) 2025-10-30 18:18:21 +01:00
Paul Bottein
cbab5c3f7b Restore trigger id in overflow menu for trigger (#27702) 2025-10-30 18:18:20 +01:00
Paul Bottein
a5d27c8bb8 Only clear from and to trigger in state trigger (#27700) 2025-10-30 18:18:19 +01:00
Paul Bottein
a6a340b5db Only display add button if at least one entity is selected in entities picker (#27699) 2025-10-30 18:18:18 +01:00
26 changed files with 663 additions and 268 deletions

View File

@@ -18,7 +18,6 @@ import { HaEventTrigger } from "../../../../src/panels/config/automation/trigger
import { HaGeolocationTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-geo_location";
import { HaHassTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-homeassistant";
import { HaTriggerList } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-list";
import { HaMQTTTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-mqtt";
import { HaNumericStateTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state";
import { HaPersistentNotificationTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-persistent_notification";
import { HaStateTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-state";
@@ -38,11 +37,6 @@ const SCHEMAS: { name: string; triggers: Trigger[] }[] = [
triggers: [{ ...HaStateTrigger.defaultConfig }],
},
{
name: "MQTT",
triggers: [{ ...HaMQTTTrigger.defaultConfig }],
},
{
name: "GeoLocation",
triggers: [{ ...HaGeolocationTrigger.defaultConfig }],

View File

@@ -194,7 +194,7 @@
"eslint-plugin-wc": "3.0.2",
"fancy-log": "2.0.0",
"fs-extra": "11.3.2",
"glob": "12.0.0",
"glob": "13.0.0",
"gulp": "5.0.1",
"gulp-brotli": "3.0.0",
"gulp-json-transform": "0.5.0",

View File

@@ -114,12 +114,6 @@ export interface StateTrigger extends BaseTrigger {
for?: string | number | ForDict;
}
export interface MqttTrigger extends BaseTrigger {
trigger: "mqtt";
topic: string;
payload?: string;
}
export interface GeoLocationTrigger extends BaseTrigger {
trigger: "geo_location";
source: string;
@@ -127,6 +121,12 @@ export interface GeoLocationTrigger extends BaseTrigger {
event: "enter" | "leave";
}
export interface MqttTrigger extends BaseTrigger {
trigger: "mqtt";
topic: string;
payload?: string;
}
export interface HassTrigger extends BaseTrigger {
trigger: "homeassistant";
event: "start" | "shutdown";

14
src/data/esphome.ts Normal file
View File

@@ -0,0 +1,14 @@
import type { HomeAssistant } from "../types";
export interface ESPHomeEncryptionKey {
encryption_key: string;
}
export const fetchESPHomeEncryptionKey = (
hass: HomeAssistant,
entry_id: string
): Promise<ESPHomeEncryptionKey> =>
hass.callWS({
type: "esphome/get_encryption_key",
entry_id,
});

View File

@@ -40,7 +40,6 @@ export const TRIGGER_COLLECTIONS: AutomationElementGroupCollection[] = [
event: {},
geo_location: {},
homeassistant: {},
mqtt: {},
conversation: {},
tag: {},
template: {},

View File

@@ -63,11 +63,6 @@ const _SHORTCUTS: Section[] = [
descriptionTranslationKey:
"ui.dialogs.shortcuts.searching.search_in_table",
},
{
shortcut: ["N"],
descriptionTranslationKey:
"ui.dialogs.shortcuts.searching.new_in_table",
},
],
},
{

View File

@@ -190,21 +190,6 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
};
}
protected supportedSingleKeyShortcuts(): SupportedShortcuts {
if (this.hasFab) {
return {
n: () => {
const fab = this.querySelector<HTMLElement>('[slot="fab"]');
if (fab) {
fab.click();
}
},
};
}
return {};
}
private _showPaneController = new ResizeController(this, {
callback: (entries) => entries[0]?.contentRect.width > 750,
});

View File

@@ -114,7 +114,6 @@ import {
} from "../../../data/trigger";
import type { HassDialog } from "../../../dialogs/make-dialog-manager";
import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import type { HomeAssistant } from "../../../types";
import { isMac } from "../../../util/is_mac";
import { showToast } from "../../../util/toast";
@@ -168,7 +167,7 @@ const DYNAMIC_KEYWORDS = ["dynamicGroups", "helpers", "other"];
@customElement("add-automation-element-dialog")
class DialogAddAutomationElement
extends KeyboardShortcutMixin(SubscribeMixin(LitElement))
extends KeyboardShortcutMixin(LitElement)
implements HassDialog
{
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -233,6 +232,8 @@ class DialogAddAutomationElement
private _unsub?: Promise<UnsubscribeFunc>;
private _unsubscribeLabFeatures?: UnsubscribeFunc;
private _configEntryLookup: Record<string, ConfigEntry> = {};
// #endregion variables
@@ -246,11 +247,47 @@ class DialogAddAutomationElement
) {
this._calculateUsedDomains();
}
if (changedProps.has("_newTriggersAndConditions")) {
this._subscribeDescriptions();
}
}
public hassSubscribe() {
return [
subscribeLabFeatures(this.hass!.connection, (features) => {
private _subscribeDescriptions() {
this._unsubscribe();
if (this._params?.type === "trigger") {
this._triggerDescriptions = {};
this._unsub = subscribeTriggers(this.hass, (triggers) => {
this._triggerDescriptions = {
...this._triggerDescriptions,
...triggers,
};
});
} else if (this._params?.type === "condition") {
this._conditionDescriptions = {};
this._unsub = subscribeConditions(this.hass, (conditions) => {
this._conditionDescriptions = {
...this._conditionDescriptions,
...conditions,
};
});
}
}
public showDialog(params): void {
this._params = params;
this.addKeyboardShortcuts();
this._loadConfigEntries();
this._unsubscribe();
this._fetchManifests();
this._calculateUsedDomains();
this._unsubscribeLabFeatures = subscribeLabFeatures(
this.hass.connection,
(features) => {
this._newTriggersAndConditions =
features.find(
(feature) =>
@@ -261,25 +298,8 @@ class DialogAddAutomationElement
this._newTriggersAndConditions && this._params?.type !== "condition"
? "targets"
: "groups";
}),
];
}
public showDialog(params): void {
this._params = params;
this._tab =
this._newTriggersAndConditions && this._params?.type !== "condition"
? "targets"
: "groups";
this.addKeyboardShortcuts();
this._loadConfigEntries();
this._unsubscribe();
this._fetchManifests();
this._calculateUsedDomains();
}
);
if (this._params?.type === "action") {
this.hass.loadBackendTranslation("services");
@@ -287,21 +307,11 @@ class DialogAddAutomationElement
} else if (this._params?.type === "trigger") {
this.hass.loadBackendTranslation("triggers");
getTriggerIcons(this.hass);
this._unsub = subscribeTriggers(this.hass, (triggers) => {
this._triggerDescriptions = {
...this._triggerDescriptions,
...triggers,
};
});
this._subscribeDescriptions();
} else if (this._params?.type === "condition") {
this.hass.loadBackendTranslation("conditions");
getConditionIcons(this.hass);
this._unsub = subscribeConditions(this.hass, (conditions) => {
this._conditionDescriptions = {
...this._conditionDescriptions,
...conditions,
};
});
this._subscribeDescriptions();
}
window.addEventListener("resize", this._updateNarrow);
@@ -379,6 +389,10 @@ class DialogAddAutomationElement
this._unsub.then((unsub) => unsub());
this._unsub = undefined;
}
if (this._unsubscribeLabFeatures) {
this._unsubscribeLabFeatures();
this._unsubscribeLabFeatures = undefined;
}
}
// #endregion lifecycle
@@ -426,10 +440,7 @@ class DialogAddAutomationElement
},
];
if (
this._newTriggersAndConditions &&
automationElementType !== "condition"
) {
if (this._newTriggersAndConditions) {
tabButtons.unshift({
label: this.hass.localize(`ui.panel.config.automation.editor.targets`),
value: "targets",
@@ -519,8 +530,7 @@ class DialogAddAutomationElement
this._manifests
)}
.convertToItem=${this._convertToItem}
.newTriggersAndConditions=${this._newTriggersAndConditions &&
automationElementType !== "condition"}
.newTriggersAndConditions=${this._newTriggersAndConditions}
@search-element-picked=${this._searchItemSelected}
>
</ha-automation-add-search>`
@@ -599,7 +609,7 @@ class DialogAddAutomationElement
: nothing}
${collections.map(
(collection, index) => html`
${collection.titleKey
${collection.titleKey && collection.groups.length
? html`<ha-section-title>
${this.hass.localize(collection.titleKey)}
</ha-section-title>`

View File

@@ -1,6 +1,9 @@
import { mdiDragHorizontalVariant, mdiPlus } from "@mdi/js";
import deepClone from "deep-clone-simple";
import type { HassServiceTarget } from "home-assistant-js-websocket";
import type {
HassServiceTarget,
UnsubscribeFunc,
} from "home-assistant-js-websocket";
import type { PropertyValues } from "lit";
import { html, LitElement, nothing } from "lit";
import { customElement, property, queryAll, state } from "lit/decorators";
@@ -25,6 +28,7 @@ import {
CONDITION_BUILDING_BLOCKS,
subscribeConditions,
} from "../../../../data/condition";
import { subscribeLabFeatures } from "../../../../data/labs";
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
import type { HomeAssistant } from "../../../../types";
import {
@@ -74,19 +78,52 @@ export default class HaAutomationCondition extends SubscribeMixin(LitElement) {
private _conditionKeys = new WeakMap<Condition, string>();
private _unsub?: Promise<UnsubscribeFunc>;
// @ts-ignore
@state() private _newTriggersAndConditions = false;
public disconnectedCallback() {
super.disconnectedCallback();
this._unsubscribe();
}
protected hassSubscribe() {
return [
subscribeConditions(this.hass, (conditions) =>
this._addConditions(conditions)
),
subscribeLabFeatures(this.hass!.connection, (features) => {
this._newTriggersAndConditions =
features.find(
(feature) =>
feature.domain === "automation" &&
feature.preview_feature === "new_triggers_conditions"
)?.enabled ?? false;
}),
];
}
private _addConditions(conditions: ConditionDescriptions) {
this._conditionDescriptions = {
...this._conditionDescriptions,
...conditions,
};
private _subscribeDescriptions() {
this._unsubscribe();
this._conditionDescriptions = {};
this._unsub = subscribeConditions(this.hass, (descriptions) => {
this._conditionDescriptions = {
...this._conditionDescriptions,
...descriptions,
};
});
}
private _unsubscribe() {
if (this._unsub) {
this._unsub.then((unsub) => unsub());
this._unsub = undefined;
}
}
protected willUpdate(changedProperties: PropertyValues): void {
super.willUpdate(changedProperties);
if (changedProperties.has("_newTriggersAndConditions")) {
this._subscribeDescriptions();
}
}
protected firstUpdated(changedProps: PropertyValues) {

View File

@@ -69,6 +69,45 @@ export class HaPlatformCondition extends LitElement {
} else {
this._manifest = undefined;
}
if (
oldValue?.condition !== this.condition?.condition &&
this.condition &&
this.description?.fields
) {
let updatedDefaultValue = false;
const updatedOptions = {};
const loadDefaults = !("options" in this.condition);
// Set mandatory bools without a default value to false
Object.entries(this.description.fields).forEach(([key, field]) => {
if (
field.selector &&
field.required &&
field.default === undefined &&
"boolean" in field.selector &&
updatedOptions[key] === undefined
) {
updatedDefaultValue = true;
updatedOptions[key] = false;
} else if (
loadDefaults &&
field.selector &&
field.default !== undefined &&
updatedOptions[key] === undefined
) {
updatedDefaultValue = true;
updatedOptions[key] = field.default;
}
});
if (updatedDefaultValue) {
fireEvent(this, "value-changed", {
value: {
...this.condition,
options: updatedOptions,
},
});
}
}
}
protected render() {

View File

@@ -72,7 +72,6 @@ import "./types/ha-automation-trigger-event";
import "./types/ha-automation-trigger-geo_location";
import "./types/ha-automation-trigger-homeassistant";
import "./types/ha-automation-trigger-list";
import "./types/ha-automation-trigger-mqtt";
import "./types/ha-automation-trigger-numeric_state";
import "./types/ha-automation-trigger-persistent_notification";
import "./types/ha-automation-trigger-platform";

View File

@@ -1,6 +1,9 @@
import { mdiDragHorizontalVariant, mdiPlus } from "@mdi/js";
import deepClone from "deep-clone-simple";
import type { HassServiceTarget } from "home-assistant-js-websocket";
import type {
HassServiceTarget,
UnsubscribeFunc,
} from "home-assistant-js-websocket";
import type { PropertyValues } from "lit";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
@@ -21,6 +24,7 @@ import {
type Trigger,
type TriggerList,
} from "../../../../data/automation";
import { subscribeLabFeatures } from "../../../../data/labs";
import type { TriggerDescriptions } from "../../../../data/trigger";
import { isTriggerList, subscribeTriggers } from "../../../../data/trigger";
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
@@ -67,16 +71,54 @@ export default class HaAutomationTrigger extends SubscribeMixin(LitElement) {
private _triggerKeys = new WeakMap<Trigger, string>();
private _unsub?: Promise<UnsubscribeFunc>;
@state() private _triggerDescriptions: TriggerDescriptions = {};
// @ts-ignore
@state() private _newTriggersAndConditions = false;
public disconnectedCallback() {
super.disconnectedCallback();
this._unsubscribe();
}
protected hassSubscribe() {
return [
subscribeTriggers(this.hass, (triggers) => this._addTriggers(triggers)),
subscribeLabFeatures(this.hass!.connection, (features) => {
this._newTriggersAndConditions =
features.find(
(feature) =>
feature.domain === "automation" &&
feature.preview_feature === "new_triggers_conditions"
)?.enabled ?? false;
}),
];
}
private _addTriggers(triggers: TriggerDescriptions) {
this._triggerDescriptions = { ...this._triggerDescriptions, ...triggers };
private _subscribeDescriptions() {
this._unsubscribe();
this._triggerDescriptions = {};
this._unsub = subscribeTriggers(this.hass, (descriptions) => {
this._triggerDescriptions = {
...this._triggerDescriptions,
...descriptions,
};
});
}
private _unsubscribe() {
if (this._unsub) {
this._unsub.then((unsub) => unsub());
this._unsub = undefined;
}
}
protected willUpdate(changedProperties: PropertyValues): void {
super.willUpdate(changedProperties);
if (changedProperties.has("_newTriggersAndConditions")) {
this._subscribeDescriptions();
}
}
protected firstUpdated(changedProps: PropertyValues) {

View File

@@ -1,58 +0,0 @@
import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../../components/ha-form/types";
import type { MqttTrigger } from "../../../../../data/automation";
import type { HomeAssistant } from "../../../../../types";
import type { TriggerElement } from "../ha-automation-trigger-row";
const SCHEMA = [
{ name: "topic", required: true, selector: { text: {} } },
{ name: "payload", selector: { text: {} } },
] as const;
@customElement("ha-automation-trigger-mqtt")
export class HaMQTTTrigger extends LitElement implements TriggerElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public trigger!: MqttTrigger;
@property({ type: Boolean }) public disabled = false;
public static get defaultConfig(): MqttTrigger {
return { trigger: "mqtt", topic: "" };
}
protected render() {
return html`
<ha-form
.schema=${SCHEMA}
.data=${this.trigger}
.hass=${this.hass}
.disabled=${this.disabled}
.computeLabel=${this._computeLabelCallback}
@value-changed=${this._valueChanged}
></ha-form>
`;
}
private _valueChanged(ev: CustomEvent): void {
ev.stopPropagation();
const newTrigger = ev.detail.value;
fireEvent(this, "value-changed", { value: newTrigger });
}
private _computeLabelCallback = (
schema: SchemaUnion<typeof SCHEMA>
): string =>
this.hass.localize(
`ui.panel.config.automation.editor.triggers.type.mqtt.${schema.name}`
);
}
declare global {
interface HTMLElementTagNameMap {
"ha-automation-trigger-mqtt": HaMQTTTrigger;
}
}

View File

@@ -25,6 +25,16 @@ const showOptionalToggle = (field: TriggerDescription["fields"][string]) =>
!field.required &&
!("boolean" in field.selector && field.default);
const DEFAULT_KEYS: (keyof PlatformTrigger)[] = [
"trigger",
"target",
"alias",
"id",
"variables",
"enabled",
"options",
] as const;
@customElement("ha-automation-trigger-platform")
export class HaPlatformTrigger extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -52,6 +62,31 @@ export class HaPlatformTrigger extends LitElement {
if (!changedProperties.has("trigger")) {
return;
}
let newValue: PlatformTrigger | undefined;
for (const key in this.trigger) {
// Migrate old options to `options`
if (DEFAULT_KEYS.includes(key as keyof PlatformTrigger)) {
continue;
}
if (newValue === undefined) {
newValue = {
...this.trigger,
options: { [key]: this.trigger[key] },
};
} else {
newValue.options![key] = this.trigger[key];
}
delete newValue[key];
}
if (newValue !== undefined) {
fireEvent(this, "value-changed", {
value: newValue,
});
this.trigger = newValue;
}
const oldValue = changedProperties.get("trigger") as
| undefined
| this["trigger"];
@@ -69,6 +104,46 @@ export class HaPlatformTrigger extends LitElement {
} else {
this._manifest = undefined;
}
if (
oldValue?.trigger !== this.trigger?.trigger &&
this.trigger &&
this.description?.fields
) {
let updatedDefaultValue = false;
const updatedOptions = {};
const loadDefaults = !("options" in this.trigger);
// Set mandatory bools without a default value to false
Object.entries(this.description.fields).forEach(([key, field]) => {
if (
field.selector &&
field.required &&
field.default === undefined &&
"boolean" in field.selector &&
updatedOptions[key] === undefined
) {
updatedDefaultValue = true;
updatedOptions[key] = false;
} else if (
loadDefaults &&
field.selector &&
field.default !== undefined &&
updatedOptions[key] === undefined
) {
updatedDefaultValue = true;
updatedOptions[key] = field.default;
}
});
if (updatedDefaultValue) {
fireEvent(this, "value-changed", {
value: {
...this.trigger,
options: updatedOptions,
},
});
}
}
}
protected render() {

View File

@@ -0,0 +1,52 @@
import { mdiKey } from "@mdi/js";
import { getConfigEntries } from "../../../../../../data/config_entries";
import type { DeviceRegistryEntry } from "../../../../../../data/device_registry";
import { fetchESPHomeEncryptionKey } from "../../../../../../data/esphome";
import type { HomeAssistant } from "../../../../../../types";
import { showESPHomeEncryptionKeyDialog } from "../../../../integrations/integration-panels/esphome/show-dialog-esphome-encryption-key";
import type { DeviceAction } from "../../../ha-config-device-page";
export const getESPHomeDeviceActions = async (
el: HTMLElement,
hass: HomeAssistant,
device: DeviceRegistryEntry
): Promise<DeviceAction[]> => {
const actions: DeviceAction[] = [];
const configEntries = await getConfigEntries(hass, {
domain: "esphome",
});
const configEntry = configEntries.find((entry) =>
device.config_entries.includes(entry.entry_id)
);
if (!configEntry) {
return [];
}
const entryId = configEntry.entry_id;
try {
const encryptionKey = await fetchESPHomeEncryptionKey(hass, entryId);
if (encryptionKey.encryption_key) {
actions.push({
label: hass.localize(
"ui.panel.config.devices.esphome.show_encryption_key"
),
icon: mdiKey,
action: () =>
showESPHomeEncryptionKeyDialog(el, {
entry_id: entryId,
encryption_key: encryptionKey.encryption_key,
}),
});
}
} catch (err) {
// eslint-disable-next-line no-console
console.error("Failed to fetch ESPHome encryption key:", err);
}
return actions;
};

View File

@@ -1162,6 +1162,17 @@ export class HaConfigDevicePage extends LitElement {
);
deviceActions.push(...actions);
}
if (domains.includes("esphome")) {
const esphome = await import(
"./device-detail/integration-elements/esphome/device-actions"
);
const actions = await esphome.getESPHomeDeviceActions(
this,
this.hass,
device
);
deviceActions.push(...actions);
}
if (domains.includes("matter")) {
const matter = await import(
"./device-detail/integration-elements/matter/device-actions"

View File

@@ -968,12 +968,6 @@ class HaConfigIntegrationsDashboard extends KeyboardShortcutMixin(
};
}
protected supportedSingleKeyShortcuts(): SupportedShortcuts {
return {
n: () => this._createFlow(),
};
}
static get styles(): CSSResultGroup {
return [
haStyle,

View File

@@ -0,0 +1,140 @@
import { mdiClose, mdiContentCopy } 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 { copyToClipboard } from "../../../../../common/util/copy-clipboard";
import "../../../../../components/ha-button";
import "../../../../../components/ha-dialog-footer";
import "../../../../../components/ha-dialog-header";
import "../../../../../components/ha-icon-button";
import "../../../../../components/ha-wa-dialog";
import { haStyleDialog } from "../../../../../resources/styles";
import type { HomeAssistant } from "../../../../../types";
import { showToast } from "../../../../../util/toast";
import type { ESPHomeEncryptionKeyDialogParams } from "./show-dialog-esphome-encryption-key";
@customElement("dialog-esphome-encryption-key")
class DialogESPHomeEncryptionKey extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _params?: ESPHomeEncryptionKeyDialogParams;
public async showDialog(
params: ESPHomeEncryptionKeyDialogParams
): Promise<void> {
this._params = params;
}
public closeDialog(): void {
this._params = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render() {
if (!this._params) {
return nothing;
}
return html`
<ha-wa-dialog
open
@closed=${this.closeDialog}
header-title=${this.hass.localize(
"ui.panel.config.devices.esphome.encryption_key_title"
)}
>
<ha-dialog-header slot="heading">
<ha-icon-button
slot="navigationIcon"
dialogAction="cancel"
.label=${this.hass.localize("ui.common.close")}
.path=${mdiClose}
></ha-icon-button>
<span slot="title">
${this.hass.localize(
"ui.panel.config.devices.esphome.encryption_key_title"
)}
</span>
</ha-dialog-header>
<div class="content">
<p>
${this.hass.localize(
"ui.panel.config.devices.esphome.encryption_key_description"
)}
</p>
<div class="key-row">
<div class="key-container">
<code>${this._params.encryption_key}</code>
</div>
<ha-icon-button
@click=${this._copyToClipboard}
.label=${this.hass.localize("ui.common.copy")}
.path=${mdiContentCopy}
></ha-icon-button>
</div>
</div>
<ha-dialog-footer slot="footer">
<ha-button slot="primaryAction" data-dialog="close">
${this.hass.localize("ui.common.close")}
</ha-button>
</ha-dialog-footer>
</ha-wa-dialog>
`;
}
private async _copyToClipboard(): Promise<void> {
if (!this._params?.encryption_key) {
return;
}
await copyToClipboard(this._params.encryption_key);
showToast(this, {
message: this.hass.localize("ui.common.copied_clipboard"),
});
}
static get styles(): CSSResultGroup {
return [
haStyleDialog,
css`
.content {
display: flex;
flex-direction: column;
gap: var(--ha-space-6);
}
.key-row {
display: flex;
gap: var(--ha-space-2);
align-items: center;
}
.key-container {
flex: 1;
border-radius: var(--ha-space-2);
border: 1px solid var(--divider-color);
background-color: var(
--code-editor-background-color,
var(--secondary-background-color)
);
padding: var(--ha-space-3);
overflow: auto;
}
p {
margin: 0;
color: var(--secondary-text-color);
line-height: var(--ha-line-height-condensed);
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-esphome-encryption-key": DialogESPHomeEncryptionKey;
}
}

View File

@@ -0,0 +1,20 @@
import { fireEvent } from "../../../../../common/dom/fire_event";
export interface ESPHomeEncryptionKeyDialogParams {
entry_id: string;
encryption_key: string;
}
export const loadESPHomeEncryptionKeyDialog = () =>
import("./dialog-esphome-encryption-key");
export const showESPHomeEncryptionKeyDialog = (
element: HTMLElement,
dialogParams: ESPHomeEncryptionKeyDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-esphome-encryption-key",
dialogImport: loadESPHomeEncryptionKeyDialog,
dialogParams,
});
};

View File

@@ -55,7 +55,6 @@ import {
subscribeEntityRegistry,
updateEntityRegistryEntry,
} from "../../../../../../data/entity_registry";
import { SubscribeMixin } from "../../../../../../mixins/subscribe-mixin";
import "./zwave-js-add-node-added-insecure";
import "./zwave-js-add-node-code-input";
import "./zwave-js-add-node-configure-device";
@@ -69,7 +68,7 @@ import "./zwave-js-add-node-select-security-strategy";
const INCLUSION_TIMEOUT_MINUTES = 5;
@customElement("dialog-zwave_js-add-node")
class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
class DialogZWaveJSAddNode extends LitElement {
// #region variables
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -103,6 +102,8 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
@state() private _securityClasses: SecurityClass[] = [];
@state() private _entities: EntityRegistryEntry[] = [];
@state() private _codeInput = "";
@query("ha-dialog") private _dialog?: HaDialog;
@@ -113,22 +114,14 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
private _onStop?: () => void;
private _subscribed?: Promise<UnsubscribeFunc | undefined>;
private _subscribedAddZwaveNode?: Promise<UnsubscribeFunc | undefined>;
private _newDeviceSubscription?: Promise<UnsubscribeFunc | undefined>;
@state() private _entities: EntityRegistryEntry[] = [];
private _subscribedEntityRegistry?: UnsubscribeFunc;
// #endregion
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeEntityRegistry(this.hass.connection, (entities) => {
this._entities = entities;
}),
];
}
protected render() {
if (!this._entryId) {
return nothing;
@@ -439,11 +432,6 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
></zwave-js-add-node-loading>`;
}
public connectedCallback(): void {
super.connectedCallback();
window.addEventListener("beforeunload", this._onBeforeUnload);
}
private _onBeforeUnload = (event: BeforeUnloadEvent) => {
if (this._step && this._shouldPreventClose(this._step)) {
event.preventDefault();
@@ -468,6 +456,14 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
}
public async showDialog(params: ZWaveJSAddNodeDialogParams): Promise<void> {
window.addEventListener("beforeunload", this._onBeforeUnload);
this._subscribedEntityRegistry = subscribeEntityRegistry(
this.hass.connection,
(entities) => {
this._entities = entities;
}
);
if (this._step) {
// already started
return;
@@ -562,7 +558,7 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
this._step = "select_method";
break;
case "search_devices":
this._unsubscribe();
this._unsubscribeAddZwaveNode();
if (
this._supportsSmartStart &&
this.hass.auth.external?.config.hasBarCodeScanner
@@ -604,7 +600,7 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
}
private _searchDevicesShowSecurityOptions() {
this._unsubscribe();
this._unsubscribeAddZwaveNode();
this._step = "choose_security_strategy";
}
@@ -626,7 +622,7 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
this._lowSecurity = false;
const s2Device = qrProvisioningInformation || dsk;
this._subscribed = subscribeAddZwaveNode(
this._subscribedAddZwaveNode = subscribeAddZwaveNode(
this.hass,
this._entryId!,
(message) => {
@@ -635,7 +631,7 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
this._step = s2Device ? "search_s2_device" : "search_devices";
break;
case "inclusion failed":
this._unsubscribe();
this._unsubscribeAddZwaveNode();
this._step = "failed";
break;
case "inclusion stopped":
@@ -677,7 +673,7 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
this._lowSecurityReason = message.node.low_security_reason;
break;
case "interview completed":
this._unsubscribe();
this._unsubscribeAddZwaveNode();
this._step = "configure_device";
break;
}
@@ -694,7 +690,7 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
});
this._addNodeTimeoutHandle = window.setTimeout(
() => {
this._unsubscribe();
this._unsubscribeAddZwaveNode();
this._error = this.hass.localize(
"ui.panel.config.zwave_js.add_node.timeout_error",
{ minutes: INCLUSION_TIMEOUT_MINUTES }
@@ -1023,10 +1019,10 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
}
}
private _unsubscribe(): void {
if (this._subscribed) {
this._subscribed.then((unsub) => unsub && unsub());
this._subscribed = undefined;
private _unsubscribeAddZwaveNode(): void {
if (this._subscribedAddZwaveNode) {
this._subscribedAddZwaveNode.then((unsub) => unsub && unsub());
this._subscribedAddZwaveNode = undefined;
if (this._entryId) {
stopZwaveInclusion(this.hass, this._entryId);
@@ -1060,8 +1056,17 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
window.removeEventListener("beforeunload", this._onBeforeUnload);
}
private _unsubscribeDialog() {
if (this._subscribedEntityRegistry) {
this._subscribedEntityRegistry();
this._subscribedEntityRegistry = undefined;
}
}
private _dialogClosed() {
this._unsubscribe();
window.removeEventListener("beforeunload", this._onBeforeUnload);
this._unsubscribeAddZwaveNode();
this._unsubscribeDialog();
this._open = false;
this._entryId = undefined;
this._step = undefined;
@@ -1100,7 +1105,8 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
super.disconnectedCallback();
window.removeEventListener("beforeunload", this._onBeforeUnload);
this._unsubscribe();
this._unsubscribeAddZwaveNode();
this._unsubscribeDialog();
}
static get styles(): CSSResultGroup {

View File

@@ -47,12 +47,9 @@ import { configSections } from "../ha-panel-config";
import { showHomeZoneDetailDialog } from "./show-dialog-home-zone-detail";
import { showZoneDetailDialog } from "./show-dialog-zone-detail";
import { slugify } from "../../../common/string/slugify";
import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin";
@customElement("ha-config-zone")
export class HaConfigZone extends KeyboardShortcutMixin(
SubscribeMixin(LitElement)
) {
export class HaConfigZone extends SubscribeMixin(LitElement) {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: "is-wide", type: Boolean }) public isWide = false;
@@ -540,12 +537,6 @@ export class HaConfigZone extends KeyboardShortcutMixin(
});
}
protected supportedSingleKeyShortcuts(): SupportedShortcuts {
return {
n: () => this._createZone(),
};
}
static styles = css`
hass-loading-screen {
--app-header-background-color: var(--sidebar-background-color);

View File

@@ -6,7 +6,6 @@ import type { HomeAssistant } from "../../../types";
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../ha-panel-energy";
const sourceHasCost = (source: Record<string, any>): boolean =>
@@ -52,10 +51,6 @@ export class EnergyViewStrategy extends ReactiveElement {
source.type === "grid" &&
(source.flow_from?.length || source.flow_to?.length)
) as GridSourceTypeEnergyPreference;
const hasReturn = hasGrid && hasGrid.flow_to.length > 0;
const hasSolar = prefs.energy_sources.some(
(source) => source.type === "solar"
);
const hasGas = prefs.energy_sources.some((source) => source.type === "gas");
const hasBattery = prefs.energy_sources.some(
(source) => source.type === "battery"
@@ -107,6 +102,7 @@ export class EnergyViewStrategy extends ReactiveElement {
overviewSection.cards!.push({
type: "energy-sources-table",
collection_key: collectionKey,
show_only_totals: true,
});
}
view.sections!.push(overviewSection);
@@ -142,43 +138,10 @@ export class EnergyViewStrategy extends ReactiveElement {
modes: ["bar"],
});
} else if (hasGrid) {
const gauges: LovelaceCardConfig[] = [];
// Only include if we have a grid source & return.
if (hasReturn) {
gauges.push({
type: "energy-grid-neutrality-gauge",
view_layout: { position: "sidebar" },
collection_key: collectionKey,
});
}
gauges.push({
type: "energy-carbon-consumed-gauge",
view_layout: { position: "sidebar" },
collection_key: collectionKey,
});
// Only include if we have a solar source.
if (hasSolar) {
if (hasReturn) {
gauges.push({
type: "energy-solar-consumed-gauge",
view_layout: { position: "sidebar" },
collection_key: collectionKey,
});
}
gauges.push({
type: "energy-self-sufficiency-gauge",
view_layout: { position: "sidebar" },
collection_key: collectionKey,
});
}
electricitySection.cards!.push({
type: "grid",
columns: 2,
square: false,
cards: gauges,
title: hass.localize("ui.panel.energy.cards.energy_usage_graph_title"),
type: "energy-usage-graph",
collection_key: collectionKey,
});
}

View File

@@ -166,10 +166,24 @@ export class HuiEnergySourcesTableCard
cost: number | null,
compareCost: number | null,
showCosts: boolean,
compare: boolean
compare: boolean,
bulletColor?: { border: string; background: string },
isFinalTotal?: boolean
) {
return html` <tr class="mdc-data-table__row total">
<td class="mdc-data-table__cell"></td>
return html` <tr
class="mdc-data-table__row ${bulletColor && !isFinalTotal ? "" : "total"}"
>
<td class="mdc-data-table__cell cell-bullet">
${bulletColor
? html`<div
class="bullet"
style=${styleMap({
borderColor: bulletColor.border,
backgroundColor: bulletColor.background,
})}
></div>`
: nothing}
</td>
<th class="mdc-data-table__cell" scope="row">${label}</th>
${compare
? html`<td class="mdc-data-table__cell mdc-data-table__cell--numeric">
@@ -339,6 +353,8 @@ export class HuiEnergySourcesTableCard
};
};
const showOnlyTotals = this._config.show_only_totals;
const _renderSimpleCategory = (type: "solar" | "gas" | "water") =>
html` ${types[type]?.map((source, idx) => {
const cost_stat =
@@ -361,6 +377,10 @@ export class HuiEnergySourcesTableCard
totalCostsCompare[type] += costCompare;
}
if (showOnlyTotals) {
return nothing;
}
return this._renderRow(
computedStyles,
type,
@@ -386,7 +406,27 @@ export class HuiEnergySourcesTableCard
hasCosts[type] ? totalCosts[type] : null,
hasCosts[type] ? totalCostsCompare[type] : null,
showCosts,
compare
compare,
showOnlyTotals
? {
border: getEnergyColor(
computedStyles,
this.hass.themes.darkMode,
false,
false,
colorPropertyMap[type],
0
),
background: getEnergyColor(
computedStyles,
this.hass.themes.darkMode,
true,
false,
colorPropertyMap[type],
0
),
}
: undefined
)
: ""}`;
@@ -474,6 +514,10 @@ export class HuiEnergySourcesTableCard
totalBattery += energyFrom - energyTo;
totalBatteryCompare += energyFromCompare - energyToCompare;
if (showOnlyTotals) {
return nothing;
}
return html` ${this._renderRow(
computedStyles,
"battery_out",
@@ -511,7 +555,27 @@ export class HuiEnergySourcesTableCard
null,
null,
showCosts,
compare
compare,
showOnlyTotals
? {
border: getEnergyColor(
computedStyles,
this.hass.themes.darkMode,
false,
false,
colorPropertyMap.battery_out,
0
),
background: getEnergyColor(
computedStyles,
this.hass.themes.darkMode,
true,
false,
colorPropertyMap.battery_out,
0
),
}
: undefined
)
: ""}
${types.grid?.map(
@@ -543,6 +607,11 @@ export class HuiEnergySourcesTableCard
totalGridCost += cost;
totalGridCostCompare += costCompare;
}
if (showOnlyTotals) {
return nothing;
}
return this._renderRow(
computedStyles,
"grid_consumption",
@@ -583,6 +652,11 @@ export class HuiEnergySourcesTableCard
totalGridCost -= cost;
totalGridCostCompare -= costCompare;
}
if (showOnlyTotals) {
return nothing;
}
return this._renderRow(
computedStyles,
"grid_return",
@@ -611,7 +685,27 @@ export class HuiEnergySourcesTableCard
hasGridCost ? totalGridCost : null,
hasGridCost ? totalGridCostCompare : null,
showCosts,
compare
compare,
showOnlyTotals
? {
border: getEnergyColor(
computedStyles,
this.hass.themes.darkMode,
false,
false,
colorPropertyMap.grid_consumption,
0
),
background: getEnergyColor(
computedStyles,
this.hass.themes.darkMode,
true,
false,
colorPropertyMap.grid_consumption,
0
),
}
: undefined
)
: ""}
${_renderSimpleCategory("gas")} ${_renderSimpleCategory("water")}
@@ -629,7 +723,9 @@ export class HuiEnergySourcesTableCard
totalGridCostCompare +
totalCostsCompare.water,
showCosts,
compare
compare,
undefined,
true
)
: ""}
</tbody>
@@ -672,8 +768,8 @@ export class HuiEnergySourcesTableCard
border-top: 1px solid var(--divider-color);
}
ha-card {
height: 100%;
overflow: hidden;
max-height: 100%;
overflow: auto;
}
.card-header {
padding-bottom: 0;

View File

@@ -194,6 +194,7 @@ export interface EnergySourcesTableCardConfig extends EnergyCardBaseConfig {
type: "energy-sources-table";
title?: string;
types?: (keyof EnergySourceByType)[];
show_only_totals?: boolean;
}
export interface EnergySolarGaugeCardConfig extends EnergyCardBaseConfig {

View File

@@ -2125,8 +2125,7 @@
"search_command": "search command",
"search_entities": "search entities",
"search_devices": "search devices",
"search_in_table": "to search in tables",
"new_in_table": "to create a new item in tables"
"search_in_table": "to search in tables"
},
"assist": {
"title": "Assist",
@@ -5466,6 +5465,11 @@
"partial_failure": "Some devices failed to delete successfully. Check system logs for more information."
}
}
},
"esphome": {
"show_encryption_key": "Show encryption key",
"encryption_key_title": "ESPHome Encryption Key",
"encryption_key_description": "This is the encryption key for your ESPHome device. Keep it in a safe place, as you may need it when transferring devices between Home Assistant instances."
}
},
"entities": {
@@ -7225,9 +7229,9 @@
"solar_total": "Solar total",
"water_total": "Water total",
"source": "Source",
"energy": "Energy",
"energy": "Usage",
"cost": "Cost",
"previous_energy": "Previous energy",
"previous_energy": "Previous usage",
"previous_cost": "Previous cost",
"battery_total": "Battery total",
"total_costs": "Total costs"

View File

@@ -8598,7 +8598,7 @@ __metadata:
languageName: node
linkType: hard
"foreground-child@npm:^3.1.0, foreground-child@npm:^3.3.1":
"foreground-child@npm:^3.1.0":
version: 3.3.1
resolution: "foreground-child@npm:3.3.1"
dependencies:
@@ -8869,19 +8869,14 @@ __metadata:
languageName: node
linkType: hard
"glob@npm:12.0.0":
version: 12.0.0
resolution: "glob@npm:12.0.0"
"glob@npm:13.0.0":
version: 13.0.0
resolution: "glob@npm:13.0.0"
dependencies:
foreground-child: "npm:^3.3.1"
jackspeak: "npm:^4.1.1"
minimatch: "npm:^10.1.1"
minipass: "npm:^7.1.2"
package-json-from-dist: "npm:^1.0.0"
path-scurry: "npm:^2.0.0"
bin:
glob: dist/esm/bin.mjs
checksum: 10/6e21b3f1f1fa635836d45e54bbe50704884cc3e310e0cc011cfb5429db65a030e12936d99b07e66236370efe45dc8c8b26fa5334dbf555d6f8709e0315c77c30
checksum: 10/de390721d29ee1c9ea41e40ec2aa0de2cabafa68022e237dc4297665a5e4d650776f2573191984ea1640aba1bf0ea34eddef2d8cbfbfc2ad24b5fb0af41d8846
languageName: node
linkType: hard
@@ -9324,7 +9319,7 @@ __metadata:
fancy-log: "npm:2.0.0"
fs-extra: "npm:11.3.2"
fuse.js: "npm:7.1.0"
glob: "npm:12.0.0"
glob: "npm:13.0.0"
google-timezones-json: "npm:1.2.0"
gulp: "npm:5.0.1"
gulp-brotli: "npm:3.0.0"
@@ -10346,15 +10341,6 @@ __metadata:
languageName: node
linkType: hard
"jackspeak@npm:^4.1.1":
version: 4.1.1
resolution: "jackspeak@npm:4.1.1"
dependencies:
"@isaacs/cliui": "npm:^8.0.2"
checksum: 10/ffceb270ec286841f48413bfb4a50b188662dfd599378ce142b6540f3f0a66821dc9dcb1e9ebc55c6c3b24dc2226c96e5819ba9bd7a241bd29031b61911718c7
languageName: node
linkType: hard
"jake@npm:^10.8.5":
version: 10.9.4
resolution: "jake@npm:10.9.4"