From 1208af510c1c9bee5dffc44b037283e03ea25c41 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 26 Mar 2025 16:13:11 +0100 Subject: [PATCH 01/53] Fix typo in Arithmetic (#24786) Fix type in Arithmetic --- src/data/recorder.ts | 2 +- src/translations/en.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/data/recorder.ts b/src/data/recorder.ts index 260f02f32b..67fd71b215 100644 --- a/src/data/recorder.ts +++ b/src/data/recorder.ts @@ -38,7 +38,7 @@ export interface Statistic { export enum StatisticMeanType { NONE = 0, - ARIMETHIC = 1, + ARITHMETIC = 1, CIRCULAR = 2, } diff --git a/src/translations/en.json b/src/translations/en.json index ebe92fa7a4..9052d42df1 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -8129,7 +8129,7 @@ }, "mean_type": { "0": "None", - "1": "Arimethic", + "1": "Arithmetic", "2": "Circular" }, "fix_issue": { From a7acee0438c1c1778a7606d27788120885383473 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 27 Mar 2025 11:10:45 +0100 Subject: [PATCH 02/53] Remove fixed height in ha tile info (#24787) Remove unused height in ha tile info --- src/components/tile/ha-tile-info.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/tile/ha-tile-info.ts b/src/components/tile/ha-tile-info.ts index c7067b5595..b18adc3e20 100644 --- a/src/components/tile/ha-tile-info.ts +++ b/src/components/tile/ha-tile-info.ts @@ -26,7 +26,6 @@ export class HaTileInfo extends LitElement { flex-direction: column; align-items: flex-start; justify-content: center; - height: 36px; } span { text-overflow: ellipsis; From e2b9a06242bd05167f8a1907ba55b9bf2d16b4c8 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 27 Mar 2025 07:49:25 +0100 Subject: [PATCH 03/53] Fix more info for disabled entities (#24789) --- src/common/entity/get_entity_context.ts | 23 +++++++++++++++++++- src/dialogs/more-info/ha-more-info-dialog.ts | 21 ++++++++++++++---- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/common/entity/get_entity_context.ts b/src/common/entity/get_entity_context.ts index b08e2d20a9..a3d7b8a05b 100644 --- a/src/common/entity/get_entity_context.ts +++ b/src/common/entity/get_entity_context.ts @@ -1,7 +1,11 @@ import type { HassEntity } from "home-assistant-js-websocket"; import type { AreaRegistryEntry } from "../../data/area_registry"; import type { DeviceRegistryEntry } from "../../data/device_registry"; -import type { EntityRegistryDisplayEntry } from "../../data/entity_registry"; +import type { + EntityRegistryDisplayEntry, + EntityRegistryEntry, + ExtEntityRegistryEntry, +} from "../../data/entity_registry"; import type { FloorRegistryEntry } from "../../data/floor_registry"; import type { HomeAssistant } from "../../types"; @@ -19,6 +23,23 @@ export const getEntityContext = ( | EntityRegistryDisplayEntry | undefined; + if (!entry) { + return { + device: null, + area: null, + floor: null, + }; + } + return getEntityEntryContext(entry, hass); +}; + +export const getEntityEntryContext = ( + entry: + | EntityRegistryDisplayEntry + | EntityRegistryEntry + | ExtEntityRegistryEntry, + hass: HomeAssistant +): EntityContext => { const deviceId = entry?.device_id; const device = deviceId ? hass.devices[deviceId] : null; const areaId = entry?.area_id || device?.area_id; diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index 19f79acb0e..c00c9438b4 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -21,8 +21,10 @@ import { stopPropagation } from "../../common/dom/stop_propagation"; import { computeAreaName } from "../../common/entity/compute_area_name"; import { computeDeviceName } from "../../common/entity/compute_device_name"; import { computeDomain } from "../../common/entity/compute_domain"; -import { computeEntityName } from "../../common/entity/compute_entity_name"; -import { getEntityContext } from "../../common/entity/get_entity_context"; +import { + computeEntityEntryName, + computeEntityName, +} from "../../common/entity/compute_entity_name"; import { shouldHandleRequestSelectedEvent } from "../../common/mwc/handle-request-selected-event"; import { navigate } from "../../common/navigate"; import "../../components/ha-button-menu"; @@ -56,6 +58,10 @@ import "./ha-more-info-history-and-logbook"; import "./ha-more-info-info"; import "./ha-more-info-settings"; import "./more-info-content"; +import { + getEntityContext, + getEntityEntryContext, +} from "../../common/entity/get_entity_context"; export interface MoreInfoDialogParams { entityId: string | null; @@ -293,11 +299,18 @@ export class MoreInfoDialog extends LitElement { this._initialView !== DEFAULT_VIEW && !this._childView; const showCloseIcon = isDefaultView || isSpecificInitialView; - const context = stateObj ? getEntityContext(stateObj, this.hass) : null; + const context = stateObj + ? getEntityContext(stateObj, this.hass) + : this._entry + ? getEntityEntryContext(this._entry, this.hass) + : undefined; const entityName = stateObj ? computeEntityName(stateObj, this.hass) - : undefined; + : this._entry + ? computeEntityEntryName(this._entry, this.hass) + : entityId; + const deviceName = context?.device ? computeDeviceName(context.device) : undefined; From ed2940edc37af9537bea29b93d5ce1363a605e3d Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 27 Mar 2025 11:10:23 +0100 Subject: [PATCH 04/53] Revert "Restore scroll position when using back navigation in dashboard" (#24795) Revert "Restore scroll position when using back navigation in dashboard (#24777)" This reverts commit 9cfcd21a93dd50d61fb64039ce7bec973c721806. --- src/panels/lovelace/hui-root.ts | 39 ++++++--------------------------- 1 file changed, 7 insertions(+), 32 deletions(-) diff --git a/src/panels/lovelace/hui-root.ts b/src/panels/lovelace/hui-root.ts index d20b865b86..5b39a0f8e8 100644 --- a/src/panels/lovelace/hui-root.ts +++ b/src/panels/lovelace/hui-root.ts @@ -76,9 +76,9 @@ import { getLovelaceStrategy } from "./strategies/get-strategy"; import { isLegacyStrategyConfig } from "./strategies/legacy-strategy"; import type { Lovelace } from "./types"; import "./views/hui-view"; +import "./views/hui-view-container"; import type { HUIView } from "./views/hui-view"; import "./views/hui-view-background"; -import "./views/hui-view-container"; @customElement("hui-root") class HUIRoot extends LitElement { @@ -99,8 +99,6 @@ class HUIRoot extends LitElement { private _viewCache?: Record; - private _viewScrollPositions: Record = {}; - private _debouncedConfigChanged: () => void; private _conversation = memoizeOne((_components) => @@ -112,7 +110,7 @@ class HUIRoot extends LitElement { // The view can trigger a re-render when it knows that certain // web components have been loaded. this._debouncedConfigChanged = debounce( - () => this._selectView(this._curView, true, false), + () => this._selectView(this._curView, true), 100, false ); @@ -527,22 +525,13 @@ class HUIRoot extends LitElement { window.addEventListener("scroll", this._handleWindowScroll, { passive: true, }); - window.addEventListener("popstate", this._handlePopState); } public disconnectedCallback(): void { super.disconnectedCallback(); window.removeEventListener("scroll", this._handleWindowScroll); - window.removeEventListener("popstate", this._handlePopState); } - private _restoreScroll = false; - - private _handlePopState = () => { - // If we navigated back, we want to restore the scroll position. - this._restoreScroll = true; - }; - protected updated(changedProperties: PropertyValues): void { super.updated(changedProperties); @@ -583,6 +572,9 @@ class HUIRoot extends LitElement { } newSelectView = index; } + + // Will allow to override history scroll restoration when using back button + setTimeout(() => scrollTo({ behavior: "auto", top: 0 }), 1); } if (changedProperties.has("lovelace")) { @@ -621,10 +613,7 @@ class HUIRoot extends LitElement { newSelectView = this._curView; } // Will allow for ripples to start rendering - afterNextRender(() => { - this._selectView(newSelectView, force, this._restoreScroll); - this._restoreScroll = false; - }); + afterNextRender(() => this._selectView(newSelectView, force)); } } @@ -932,26 +921,17 @@ class HUIRoot extends LitElement { } } - private _selectView( - viewIndex: HUIRoot["_curView"], - force: boolean, - restoreScroll: boolean - ): void { + private _selectView(viewIndex: HUIRoot["_curView"], force: boolean): void { if (!force && this._curView === viewIndex) { return; } - if (this._curView != null) { - this._viewScrollPositions[this._curView] = window.scrollY; - } - viewIndex = viewIndex === undefined ? 0 : viewIndex; this._curView = viewIndex; if (force) { this._viewCache = {}; - this._viewScrollPositions = {}; } // Recreate a new element to clear the applied themes. @@ -983,15 +963,10 @@ class HUIRoot extends LitElement { if (!force && this._viewCache![viewIndex]) { view = this._viewCache![viewIndex]; - const position = restoreScroll - ? this._viewScrollPositions[viewIndex] || 0 - : 0; - setTimeout(() => scrollTo({ behavior: "auto", top: position }), 0); } else { view = document.createElement("hui-view"); view.index = viewIndex; this._viewCache![viewIndex] = view; - setTimeout(() => scrollTo({ behavior: "auto", top: 0 }), 0); } view.lovelace = this.lovelace; From 2ab8209622a935c79b049104200476e047dece47 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 27 Mar 2025 13:53:35 +0100 Subject: [PATCH 05/53] Align behavior of template selector with text selector (#24796) --- src/components/ha-selector/ha-selector-template.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/ha-selector/ha-selector-template.ts b/src/components/ha-selector/ha-selector-template.ts index 80047498c7..50f6e039ed 100644 --- a/src/components/ha-selector/ha-selector-template.ts +++ b/src/components/ha-selector/ha-selector-template.ts @@ -69,11 +69,14 @@ export class HaTemplateSelector extends LitElement { } private _handleChange(ev) { - const value = ev.target.value; + let value = ev.target.value; if (this.value === value) { return; } this.warn = WARNING_STRINGS.find((str) => value.includes(str)); + if (value === "" && !this.required) { + value = undefined; + } fireEvent(this, "value-changed", { value }); } From b009d71e8ff7ea8086ae2105c670439e485126e8 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 27 Mar 2025 13:33:52 +0100 Subject: [PATCH 06/53] Fix take control of the dashboard (#24800) --- cast/src/receiver/layout/hc-main.ts | 7 +-- src/panels/lovelace/ha-panel-lovelace.ts | 16 ++--- src/panels/lovelace/sections/hui-section.ts | 2 +- .../lovelace/strategies/get-strategy.ts | 60 +++++++++++++------ src/panels/lovelace/views/hui-view.ts | 5 +- 5 files changed, 52 insertions(+), 38 deletions(-) diff --git a/cast/src/receiver/layout/hc-main.ts b/cast/src/receiver/layout/hc-main.ts index 958d94cccc..46267f715a 100644 --- a/cast/src/receiver/layout/hc-main.ts +++ b/cast/src/receiver/layout/hc-main.ts @@ -309,7 +309,7 @@ export class HcMain extends HassElement { "../../../../src/panels/lovelace/strategies/get-strategy" ); const config = await generateLovelaceDashboardStrategy( - rawConfig.strategy, + rawConfig, this.hass! ); this._handleNewLovelaceConfig(config); @@ -351,10 +351,7 @@ export class HcMain extends HassElement { "../../../../src/panels/lovelace/strategies/get-strategy" ); this._handleNewLovelaceConfig( - await generateLovelaceDashboardStrategy( - DEFAULT_CONFIG.strategy, - this.hass! - ) + await generateLovelaceDashboardStrategy(DEFAULT_CONFIG, this.hass!) ); } diff --git a/src/panels/lovelace/ha-panel-lovelace.ts b/src/panels/lovelace/ha-panel-lovelace.ts index eaf0d05ba5..bc7471d9c3 100644 --- a/src/panels/lovelace/ha-panel-lovelace.ts +++ b/src/panels/lovelace/ha-panel-lovelace.ts @@ -187,7 +187,7 @@ export class LovelacePanel extends LitElement { private async _regenerateConfig() { const conf = await generateLovelaceDashboardStrategy( - DEFAULT_CONFIG.strategy, + DEFAULT_CONFIG, this.hass! ); this._setLovelaceConfig(conf, DEFAULT_CONFIG, "generated"); @@ -281,10 +281,7 @@ export class LovelacePanel extends LitElement { // We need these to generate a dashboard, wait for them return; } - conf = await generateLovelaceDashboardStrategy( - rawConf.strategy, - this.hass! - ); + conf = await generateLovelaceDashboardStrategy(rawConf, this.hass!); } else { conf = rawConf; } @@ -301,7 +298,7 @@ export class LovelacePanel extends LitElement { return; } conf = await generateLovelaceDashboardStrategy( - DEFAULT_CONFIG.strategy, + DEFAULT_CONFIG, this.hass! ); rawConf = DEFAULT_CONFIG; @@ -378,10 +375,7 @@ export class LovelacePanel extends LitElement { let conf: LovelaceConfig; // If strategy defined, apply it here. if (isStrategyDashboard(newConfig)) { - conf = await generateLovelaceDashboardStrategy( - newConfig.strategy, - this.hass! - ); + conf = await generateLovelaceDashboardStrategy(newConfig, this.hass!); } else { conf = newConfig; } @@ -415,7 +409,7 @@ export class LovelacePanel extends LitElement { try { // Optimistic update const generatedConf = await generateLovelaceDashboardStrategy( - DEFAULT_CONFIG.strategy, + DEFAULT_CONFIG, this.hass! ); this._updateLovelace({ diff --git a/src/panels/lovelace/sections/hui-section.ts b/src/panels/lovelace/sections/hui-section.ts index 5bcd11b56a..1db1de751e 100644 --- a/src/panels/lovelace/sections/hui-section.ts +++ b/src/panels/lovelace/sections/hui-section.ts @@ -185,7 +185,7 @@ export class HuiSection extends ReactiveElement { if (isStrategySection(sectionConfig)) { isStrategy = true; sectionConfig = await generateLovelaceSectionStrategy( - sectionConfig.strategy, + sectionConfig, this.hass! ); } diff --git a/src/panels/lovelace/strategies/get-strategy.ts b/src/panels/lovelace/strategies/get-strategy.ts index 107bc6e918..ad55a027ce 100644 --- a/src/panels/lovelace/strategies/get-strategy.ts +++ b/src/panels/lovelace/strategies/get-strategy.ts @@ -1,10 +1,18 @@ +import type { + LovelaceSectionConfig, + LovelaceStrategySectionConfig, +} from "../../../data/lovelace/config/section"; +import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy"; import type { LovelaceConfig, + LovelaceDashboardStrategyConfig, LovelaceRawConfig, } from "../../../data/lovelace/config/types"; import { isStrategyDashboard } from "../../../data/lovelace/config/types"; -import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy"; -import type { LovelaceViewConfig } from "../../../data/lovelace/config/view"; +import type { + LovelaceStrategyViewConfig, + LovelaceViewConfig, +} from "../../../data/lovelace/config/view"; import { isStrategyView } from "../../../data/lovelace/config/view"; import type { AsyncReturnType, HomeAssistant } from "../../../types"; import { cleanLegacyStrategyConfig, isLegacyStrategy } from "./legacy-strategy"; @@ -133,10 +141,11 @@ const generateStrategy = async ( }; export const generateLovelaceDashboardStrategy = async ( - strategyConfig: LovelaceStrategyConfig, + config: LovelaceDashboardStrategyConfig, hass: HomeAssistant -): Promise => - generateStrategy( +): Promise => { + const { strategy, ...base } = config; + const generated = generateStrategy( "dashboard", (err) => ({ views: [ @@ -151,15 +160,21 @@ export const generateLovelaceDashboardStrategy = async ( }, ], }), - strategyConfig, + strategy, hass ); + return { + ...base, + ...generated, + }; +}; export const generateLovelaceViewStrategy = async ( - strategyConfig: LovelaceStrategyConfig, + config: LovelaceStrategyViewConfig, hass: HomeAssistant -): Promise => - generateStrategy( +): Promise => { + const { strategy, ...base } = config; + const generated = await generateStrategy( "view", (err) => ({ cards: [ @@ -169,15 +184,21 @@ export const generateLovelaceViewStrategy = async ( }, ], }), - strategyConfig, + strategy, hass ); + return { + ...base, + ...generated, + }; +}; export const generateLovelaceSectionStrategy = async ( - strategyConfig: LovelaceStrategyConfig, + config: LovelaceStrategySectionConfig, hass: HomeAssistant -): Promise => - generateStrategy( +): Promise => { + const { strategy, ...base } = config; + const generated = await generateStrategy( "section", (err) => ({ cards: [ @@ -187,9 +208,14 @@ export const generateLovelaceSectionStrategy = async ( }, ], }), - strategyConfig, + strategy, hass ); + return { + ...base, + ...generated, + }; +}; /** * Find all references to strategies and replaces them with the generated output @@ -199,20 +225,20 @@ export const expandLovelaceConfigStrategies = async ( hass: HomeAssistant ): Promise => { const newConfig = isStrategyDashboard(config) - ? await generateLovelaceDashboardStrategy(config.strategy, hass) + ? await generateLovelaceDashboardStrategy(config, hass) : { ...config }; newConfig.views = await Promise.all( newConfig.views.map(async (view) => { const newView = isStrategyView(view) - ? await generateLovelaceViewStrategy(view.strategy, hass) + ? await generateLovelaceViewStrategy(view, hass) : { ...view }; if (newView.sections) { newView.sections = await Promise.all( newView.sections.map(async (section) => { const newSection = isStrategyView(section) - ? await generateLovelaceSectionStrategy(section.strategy, hass) + ? await generateLovelaceSectionStrategy(section, hass) : { ...section }; return newSection; }) diff --git a/src/panels/lovelace/views/hui-view.ts b/src/panels/lovelace/views/hui-view.ts index 6b2f19739b..12d10e3214 100644 --- a/src/panels/lovelace/views/hui-view.ts +++ b/src/panels/lovelace/views/hui-view.ts @@ -233,10 +233,7 @@ export class HUIView extends ReactiveElement { if (isStrategyView(viewConfig)) { isStrategy = true; - viewConfig = await generateLovelaceViewStrategy( - viewConfig.strategy, - this.hass! - ); + viewConfig = await generateLovelaceViewStrategy(viewConfig, this.hass!); } viewConfig = { From 94ee99160b948e7911025c2feca41919ec653eb4 Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Thu, 27 Mar 2025 08:30:17 -0700 Subject: [PATCH 07/53] Energy device settings fixes (#24801) --- .../components/ha-energy-device-settings.ts | 24 ++++++++------ .../dialogs/dialog-energy-device-settings.ts | 33 ++++++++++++------- src/translations/en.json | 3 +- 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/src/panels/config/energy/components/ha-energy-device-settings.ts b/src/panels/config/energy/components/ha-energy-device-settings.ts index 59d28b8a23..11eb58d42f 100644 --- a/src/panels/config/energy/components/ha-energy-device-settings.ts +++ b/src/panels/config/energy/components/ha-energy-device-settings.ts @@ -152,12 +152,14 @@ export class EnergyDeviceSettings extends LitElement { device_consumptions: this.preferences .device_consumption as DeviceConsumptionEnergyPreference[], saveCallback: async (newDevice) => { - await this._savePreferences({ + const newPrefs = { ...this.preferences, device_consumption: this.preferences.device_consumption.map((d) => d === origDevice ? newDevice : d ), - }); + }; + this._sanitizeParents(newPrefs); + await this._savePreferences(newPrefs); }, }); } @@ -177,6 +179,15 @@ export class EnergyDeviceSettings extends LitElement { }); } + private _sanitizeParents(prefs: EnergyPreferences) { + const statIds = prefs.device_consumption.map((d) => d.stat_consumption); + prefs.device_consumption.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; @@ -196,14 +207,7 @@ export class EnergyDeviceSettings extends LitElement { (device) => device !== deviceToDelete ), }; - newPrefs.device_consumption.forEach((d, idx) => { - if (d.included_in_stat === deviceToDelete.stat_consumption) { - newPrefs.device_consumption[idx] = { - ...newPrefs.device_consumption[idx], - }; - delete newPrefs.device_consumption[idx].included_in_stat; - } - }); + this._sanitizeParents(newPrefs); await this._savePreferences(newPrefs); } catch (err: any) { showAlertDialog(this, { title: `Failed to save config: ${err.message}` }); diff --git a/src/panels/config/energy/dialogs/dialog-energy-device-settings.ts b/src/panels/config/energy/dialogs/dialog-energy-device-settings.ts index cb190faf13..c713b3e35c 100644 --- a/src/panels/config/energy/dialogs/dialog-energy-device-settings.ts +++ b/src/panels/config/energy/dialogs/dialog-energy-device-settings.ts @@ -74,6 +74,7 @@ export class DialogEnergyDeviceSettings 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) ); } @@ -160,18 +161,26 @@ export class DialogEnergyDeviceSettings naturalMenuWidth clearable > - ${this._possibleParents.map( - (stat) => html` - ${stat.name || - getStatisticLabel( - this.hass, - stat.stat_consumption, - this._params?.statsMetadata?.[stat.stat_consumption] - )} - ` - )} + ${!this._possibleParents.length + ? html` + ${this.hass.localize( + "ui.panel.config.energy.device_consumption.dialog.no_upstream_devices" + )} + ` + : this._possibleParents.map( + (stat) => html` + ${stat.name || + getStatisticLabel( + this.hass, + stat.stat_consumption, + this._params?.statsMetadata?.[stat.stat_consumption] + )} + ` + )} diff --git a/src/translations/en.json b/src/translations/en.json index 9052d42df1..a030e84897 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2902,7 +2902,8 @@ "device_consumption_energy": "Device energy consumption", "selected_stat_intro": "Select the energy sensor that measures the device's energy 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 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" } } }, From 23229b3e3b8ef8687945237c06112989d13a6208 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 27 Mar 2025 16:39:47 +0100 Subject: [PATCH 08/53] Set the max number of columns to 3 for area dashboard (#24802) * Set the max number of columns to 4 for area dashboard * Set it to 3 --- .../lovelace/strategies/areas/area-view-strategy.ts | 8 ++++++-- .../strategies/areas/areas-overview-view-strategy.ts | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/panels/lovelace/strategies/areas/area-view-strategy.ts b/src/panels/lovelace/strategies/areas/area-view-strategy.ts index 89668343c5..b9c9025091 100644 --- a/src/panels/lovelace/strategies/areas/area-view-strategy.ts +++ b/src/panels/lovelace/strategies/areas/area-view-strategy.ts @@ -1,5 +1,6 @@ import { ReactiveElement } from "lit"; import { customElement } from "lit/decorators"; +import { clamp } from "../../../../common/number/clamp"; import type { LovelaceBadgeConfig } from "../../../../data/lovelace/config/badge"; import type { LovelaceCardConfig } from "../../../../data/lovelace/config/card"; import type { LovelaceSectionRawConfig } from "../../../../data/lovelace/config/section"; @@ -144,7 +145,10 @@ export class AreaViewStrategy extends ReactiveElement { }); } - // Take the full width if there is only one section to avoid misalignment between cards and header + // Allow between 2 and 3 columns (the max should be set to define the width of the header) + const maxColumns = clamp(sections.length, 2, 3); + + // Take the full width if there is only one section to avoid narrow header on desktop if (sections.length === 1) { sections[0].column_span = 2; } @@ -160,7 +164,7 @@ export class AreaViewStrategy extends ReactiveElement { content: `## ${area.name}`, }, }, - max_columns: 2, + max_columns: maxColumns, sections: sections, badges: badges, }; diff --git a/src/panels/lovelace/strategies/areas/areas-overview-view-strategy.ts b/src/panels/lovelace/strategies/areas/areas-overview-view-strategy.ts index d3e706bab1..547f93ed34 100644 --- a/src/panels/lovelace/strategies/areas/areas-overview-view-strategy.ts +++ b/src/panels/lovelace/strategies/areas/areas-overview-view-strategy.ts @@ -95,7 +95,7 @@ export class AreasOverviewViewStrategy extends ReactiveElement { return { type: "sections", - max_columns: 2, + max_columns: 3, sections: areaSections, }; } From 534df3d37818b9cd0a2ae8fa265c805326c569ff Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 27 Mar 2025 16:37:32 +0100 Subject: [PATCH 09/53] Add loading state to area strategy (#24803) --- .../areas/areas-dashboard-strategy.ts | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/panels/lovelace/strategies/areas/areas-dashboard-strategy.ts b/src/panels/lovelace/strategies/areas/areas-dashboard-strategy.ts index fb0898769c..ef397b4a8e 100644 --- a/src/panels/lovelace/strategies/areas/areas-dashboard-strategy.ts +++ b/src/panels/lovelace/strategies/areas/areas-dashboard-strategy.ts @@ -1,13 +1,14 @@ +import { STATE_NOT_RUNNING } from "home-assistant-js-websocket"; import { ReactiveElement } from "lit"; import { customElement } from "lit/decorators"; import type { LovelaceConfig } from "../../../../data/lovelace/config/types"; import type { LovelaceViewRawConfig } from "../../../../data/lovelace/config/view"; import type { HomeAssistant } from "../../../../types"; +import type { LovelaceStrategyEditor } from "../types"; import type { AreaViewStrategyConfig, EntitiesDisplay, } from "./area-view-strategy"; -import type { LovelaceStrategyEditor } from "../types"; import type { AreasViewStrategyConfig } from "./areas-overview-view-strategy"; import { computeAreaPath, getAreas } from "./helpers/areas-strategy-helper"; @@ -30,6 +31,28 @@ export class AreasDashboardStrategy extends ReactiveElement { config: AreasDashboardStrategyConfig, hass: HomeAssistant ): Promise { + if (hass.config.state === STATE_NOT_RUNNING) { + return { + views: [ + { + type: "sections", + sections: [{ cards: [{ type: "starting" }] }], + }, + ], + }; + } + + if (hass.config.recovery_mode) { + return { + views: [ + { + type: "sections", + sections: [{ cards: [{ type: "recovery-mode" }] }], + }, + ], + }; + } + const areas = getAreas( hass.areas, config.areas_display?.hidden, From 1770a513039c4eae85d74b0f08eb725b5094eb50 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 27 Mar 2025 16:46:17 +0100 Subject: [PATCH 10/53] Bumped version to 20250327.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9c0446d162..fbf80a0ceb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20250326.0" +version = "20250327.0" license = "Apache-2.0" license-files = ["LICENSE*"] description = "The Home Assistant frontend" From 3b6e267fb51bc93645109e45fb440d57d5001465 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 27 Mar 2025 18:51:08 +0100 Subject: [PATCH 11/53] Fallback to state name when the entry doesn't have name (#24805) --- src/common/entity/compute_entity_name.ts | 9 ++++++++- src/dialogs/more-info/ha-more-info-dialog.ts | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/common/entity/compute_entity_name.ts b/src/common/entity/compute_entity_name.ts index 57843063d4..86dd0a096b 100644 --- a/src/common/entity/compute_entity_name.ts +++ b/src/common/entity/compute_entity_name.ts @@ -33,7 +33,14 @@ export const computeEntityEntryName = ( const device = entry.device_id ? hass.devices[entry.device_id] : undefined; if (!device) { - return name; + if (name) { + return name; + } + const stateObj = hass.states[entry.entity_id] as HassEntity | undefined; + if (stateObj) { + return computeStateName(stateObj); + } + return undefined; } const deviceName = computeDeviceName(device); diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index c00c9438b4..fd605e983b 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -319,7 +319,7 @@ export class MoreInfoDialog extends LitElement { const breadcrumb = [areaName, deviceName, entityName].filter( (v): v is string => Boolean(v) ); - const title = this._childView?.viewTitle || breadcrumb.pop(); + const title = this._childView?.viewTitle || breadcrumb.pop() || entityId; return html` Date: Thu, 27 Mar 2025 19:09:46 +0100 Subject: [PATCH 12/53] Fix dashboard strategy (#24808) --- src/panels/lovelace/strategies/get-strategy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/lovelace/strategies/get-strategy.ts b/src/panels/lovelace/strategies/get-strategy.ts index ad55a027ce..af7db8413f 100644 --- a/src/panels/lovelace/strategies/get-strategy.ts +++ b/src/panels/lovelace/strategies/get-strategy.ts @@ -145,7 +145,7 @@ export const generateLovelaceDashboardStrategy = async ( hass: HomeAssistant ): Promise => { const { strategy, ...base } = config; - const generated = generateStrategy( + const generated = await generateStrategy( "dashboard", (err) => ({ views: [ From 118c25d25fb33e42218495d63cc0a5b6f815460f Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 27 Mar 2025 19:12:22 +0100 Subject: [PATCH 13/53] Bumped version to 20250327.1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index fbf80a0ceb..958a1c56d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20250327.0" +version = "20250327.1" license = "Apache-2.0" license-files = ["LICENSE*"] description = "The Home Assistant frontend" From 8751dc46f4e22af970927176c4e99e47f5e6b7e0 Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Fri, 28 Mar 2025 02:25:07 -0400 Subject: [PATCH 14/53] Show hardware integrations in the integration list (#24820) Show hardware integrations in the frontend --- src/panels/config/integrations/ha-config-integrations.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/config/integrations/ha-config-integrations.ts b/src/panels/config/integrations/ha-config-integrations.ts index d93cf28bb7..e00106271d 100644 --- a/src/panels/config/integrations/ha-config-integrations.ts +++ b/src/panels/config/integrations/ha-config-integrations.ts @@ -120,7 +120,7 @@ class HaConfigIntegrations extends SubscribeMixin(HassRouterPage) { const existingEntries = fullUpdate ? [] : this._configEntries; this._configEntries = [...existingEntries!, ...newEntries]; }, - { type: ["device", "hub", "service"] } + { type: ["device", "hub", "service", "hardware"] } ), subscribeConfigFlowInProgress(this.hass, async (flowsInProgress) => { const integrations = new Set(); From 909fc119b73fea525252b598e1970401b91551f9 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Fri, 28 Mar 2025 12:07:42 +0100 Subject: [PATCH 15/53] Add scroll restoration when using back navigation in dashboard (#24822) Add scroll restoration when using back navigation with subviews --- src/panels/lovelace/hui-root.ts | 37 +++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/src/panels/lovelace/hui-root.ts b/src/panels/lovelace/hui-root.ts index 5b39a0f8e8..38ea992b8e 100644 --- a/src/panels/lovelace/hui-root.ts +++ b/src/panels/lovelace/hui-root.ts @@ -99,6 +99,10 @@ class HUIRoot extends LitElement { private _viewCache?: Record; + private _viewScrollPositions: Record = {}; + + private _restoreScroll = false; + private _debouncedConfigChanged: () => void; private _conversation = memoizeOne((_components) => @@ -485,6 +489,10 @@ class HUIRoot extends LitElement { this.toggleAttribute("scrolled", window.scrollY !== 0); }; + private _handlePopState = () => { + this._restoreScroll = true; + }; + private _isVisible = (view: LovelaceViewConfig) => Boolean( this._editMode || @@ -525,11 +533,18 @@ class HUIRoot extends LitElement { window.addEventListener("scroll", this._handleWindowScroll, { passive: true, }); + window.addEventListener("popstate", this._handlePopState); + // Disable history scroll restoration because it is managed manually here + window.history.scrollRestoration = "manual"; } public disconnectedCallback(): void { super.disconnectedCallback(); window.removeEventListener("scroll", this._handleWindowScroll); + window.removeEventListener("popstate", this._handlePopState); + this.toggleAttribute("scrolled", window.scrollY !== 0); + // Re-enable history scroll restoration when leaving the page + window.history.scrollRestoration = "auto"; } protected updated(changedProperties: PropertyValues): void { @@ -572,9 +587,6 @@ class HUIRoot extends LitElement { } newSelectView = index; } - - // Will allow to override history scroll restoration when using back button - setTimeout(() => scrollTo({ behavior: "auto", top: 0 }), 1); } if (changedProperties.has("lovelace")) { @@ -613,7 +625,18 @@ class HUIRoot extends LitElement { newSelectView = this._curView; } // Will allow for ripples to start rendering - afterNextRender(() => this._selectView(newSelectView, force)); + afterNextRender(() => { + if (changedProperties.has("route")) { + const position = + (this._restoreScroll && this._viewScrollPositions[newSelectView]) || + 0; + this._restoreScroll = false; + requestAnimationFrame(() => + scrollTo({ behavior: "auto", top: position }) + ); + } + this._selectView(newSelectView, force); + }); } } @@ -926,12 +949,18 @@ class HUIRoot extends LitElement { return; } + // Save scroll position of current view + if (this._curView != null) { + this._viewScrollPositions[this._curView] = window.scrollY; + } + viewIndex = viewIndex === undefined ? 0 : viewIndex; this._curView = viewIndex; if (force) { this._viewCache = {}; + this._viewScrollPositions = {}; } // Recreate a new element to clear the applied themes. From 916dec101f83d036af587ab7d5417292e4aace6c Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Fri, 28 Mar 2025 13:12:07 +0100 Subject: [PATCH 16/53] Add hold and double tap action in the UI for every card that supports it. (#24824) * Add double tap action to button card UI editor * Add double tap action to light card UI editor * Add hold action and double tap action to gauge card UI editor * Add hold action and double tap action to picture glance card UI editor * Add hold action and double tap action to picture card UI editor * Add hold action and double tap action to entity card UI editor * Add hold action and double tap action to elements --- src/panels/lovelace/cards/hui-button-card.ts | 4 +- src/panels/lovelace/cards/hui-picture-card.ts | 5 +- .../lovelace/cards/hui-picture-entity-card.ts | 11 +++- .../lovelace/cards/hui-picture-glance-card.ts | 2 +- .../elements/hui-icon-element-editor.ts | 44 +++++++++++---- .../elements/hui-image-element-editor.ts | 44 +++++++++++---- .../hui-state-badge-element-editor.ts | 40 +++++++++---- .../elements/hui-state-icon-element-editor.ts | 44 +++++++++++---- .../hui-state-label-element-editor.ts | 44 +++++++++++---- .../config-elements/hui-button-card-editor.ts | 49 ++++++++++++---- .../config-elements/hui-gauge-card-editor.ts | 56 ++++++++++++++++--- .../config-elements/hui-light-card-editor.ts | 45 +++++++++++++-- .../hui-picture-card-editor.ts | 37 ++++++++++-- .../hui-picture-entity-card-editor.ts | 42 +++++++++++--- .../hui-picture-glance-card-editor.ts | 38 +++++++++++-- .../lovelace/elements/hui-icon-element.ts | 2 +- .../lovelace/elements/hui-image-element.ts | 2 +- .../elements/hui-state-badge-element.ts | 2 +- .../elements/hui-state-icon-element.ts | 2 +- .../elements/hui-state-label-element.ts | 2 +- 20 files changed, 397 insertions(+), 118 deletions(-) diff --git a/src/panels/lovelace/cards/hui-button-card.ts b/src/panels/lovelace/cards/hui-button-card.ts index 67c1adaa88..59d6db9adc 100644 --- a/src/panels/lovelace/cards/hui-button-card.ts +++ b/src/panels/lovelace/cards/hui-button-card.ts @@ -78,9 +78,6 @@ export class HuiButtonCard extends LitElement implements LovelaceCard { return { type: "button", - tap_action: { - action: "toggle", - }, entity: foundEntities[0] || "", }; } @@ -164,6 +161,7 @@ export class HuiButtonCard extends LitElement implements LovelaceCard { action: getEntityDefaultButtonAction(config.entity), }, hold_action: { action: "more-info" }, + double_tap_action: { action: "none" }, show_icon: true, show_name: true, state_color: true, diff --git a/src/panels/lovelace/cards/hui-picture-card.ts b/src/panels/lovelace/cards/hui-picture-card.ts index d7575775bb..8bc3afb40a 100644 --- a/src/panels/lovelace/cards/hui-picture-card.ts +++ b/src/panels/lovelace/cards/hui-picture-card.ts @@ -46,7 +46,10 @@ export class HuiPictureCard extends LitElement implements LovelaceCard { throw new Error("Image required"); } - this._config = config; + this._config = { + tap_action: { action: "more-info" }, + ...config, + }; } protected shouldUpdate(changedProps: PropertyValues): boolean { diff --git a/src/panels/lovelace/cards/hui-picture-entity-card.ts b/src/panels/lovelace/cards/hui-picture-entity-card.ts index 901e46c225..22bca69b24 100644 --- a/src/panels/lovelace/cards/hui-picture-entity-card.ts +++ b/src/panels/lovelace/cards/hui-picture-entity-card.ts @@ -6,9 +6,11 @@ import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_elemen import { computeDomain } from "../../../common/entity/compute_domain"; import { computeStateName } from "../../../common/entity/compute_state_name"; import "../../../components/ha-card"; +import type { CameraEntity } from "../../../data/camera"; import type { ImageEntity } from "../../../data/image"; import { computeImageUrl } from "../../../data/image"; import type { ActionHandlerEvent } from "../../../data/lovelace/action_handler"; +import type { PersonEntity } from "../../../data/person"; import type { HomeAssistant } from "../../../types"; import { actionHandler } from "../common/directives/action-handler-directive"; import { findEntities } from "../common/find-entities"; @@ -19,8 +21,6 @@ import "../components/hui-image"; import { createEntityNotFoundWarning } from "../components/hui-warning"; import type { LovelaceCard, LovelaceCardEditor } from "../types"; import type { PictureEntityCardConfig } from "./types"; -import type { CameraEntity } from "../../../data/camera"; -import type { PersonEntity } from "../../../data/person"; export const STUB_IMAGE = "https://demo.home-assistant.io/stub_config/bedroom.png"; @@ -75,7 +75,12 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard { throw new Error("No image source configured"); } - this._config = { show_name: true, show_state: true, ...config }; + this._config = { + show_name: true, + show_state: true, + tap_action: { action: "more-info" }, + ...config, + }; } protected shouldUpdate(changedProps: PropertyValues): boolean { diff --git a/src/panels/lovelace/cards/hui-picture-glance-card.ts b/src/panels/lovelace/cards/hui-picture-glance-card.ts index 1ba3ad0fb3..fb1f8311cd 100644 --- a/src/panels/lovelace/cards/hui-picture-glance-card.ts +++ b/src/panels/lovelace/cards/hui-picture-glance-card.ts @@ -105,7 +105,7 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard { }); this._config = { - hold_action: { action: "more-info" }, + tap_action: { action: "more-info" }, ...config, }; } diff --git a/src/panels/lovelace/editor/config-elements/elements/hui-icon-element-editor.ts b/src/panels/lovelace/editor/config-elements/elements/hui-icon-element-editor.ts index 9256a0d6c3..f10da54dbc 100644 --- a/src/panels/lovelace/editor/config-elements/elements/hui-icon-element-editor.ts +++ b/src/panels/lovelace/editor/config-elements/elements/hui-icon-element-editor.ts @@ -1,12 +1,13 @@ +import { mdiGestureTap } from "@mdi/js"; import { html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { any, assert, literal, object, optional, string } from "superstruct"; import { fireEvent } from "../../../../../common/dom/fire_event"; +import "../../../../../components/ha-form/ha-form"; import type { SchemaUnion } from "../../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../../types"; -import "../../../../../components/ha-form/ha-form"; -import type { LovelacePictureElementEditor } from "../../../types"; import type { IconElementConfig } from "../../../elements/types"; +import type { LovelacePictureElementEditor } from "../../../types"; import { actionConfigStruct } from "../../structs/action-struct"; const iconElementConfigStruct = object({ @@ -25,16 +26,35 @@ const SCHEMA = [ { name: "title", selector: { text: {} } }, { name: "entity", selector: { entity: {} } }, { - name: "tap_action", - selector: { - ui_action: {}, - }, - }, - { - name: "hold_action", - selector: { - ui_action: {}, - }, + name: "interactions", + type: "expandable", + flatten: true, + iconPath: mdiGestureTap, + schema: [ + { + name: "tap_action", + selector: { + ui_action: { + default_action: "more-info", + }, + }, + }, + { + name: "", + type: "optional_actions", + flatten: true, + schema: (["hold_action", "double_tap_action"] as const).map( + (action) => ({ + name: action, + selector: { + ui_action: { + default_action: "none" as const, + }, + }, + }) + ), + }, + ], }, { name: "style", selector: { object: {} } }, ] as const; diff --git a/src/panels/lovelace/editor/config-elements/elements/hui-image-element-editor.ts b/src/panels/lovelace/editor/config-elements/elements/hui-image-element-editor.ts index 570e80706c..dd7c86a9b4 100644 --- a/src/panels/lovelace/editor/config-elements/elements/hui-image-element-editor.ts +++ b/src/panels/lovelace/editor/config-elements/elements/hui-image-element-editor.ts @@ -1,12 +1,13 @@ +import { mdiGestureTap } from "@mdi/js"; import { html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { any, assert, literal, object, optional, string } from "superstruct"; import { fireEvent } from "../../../../../common/dom/fire_event"; +import "../../../../../components/ha-form/ha-form"; import type { SchemaUnion } from "../../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../../types"; -import "../../../../../components/ha-form/ha-form"; -import type { LovelacePictureElementEditor } from "../../../types"; import type { ImageElementConfig } from "../../../elements/types"; +import type { LovelacePictureElementEditor } from "../../../types"; import { actionConfigStruct } from "../../structs/action-struct"; const imageElementConfigStruct = object({ @@ -30,16 +31,35 @@ const SCHEMA = [ { name: "entity", selector: { entity: {} } }, { name: "title", selector: { text: {} } }, { - name: "tap_action", - selector: { - ui_action: {}, - }, - }, - { - name: "hold_action", - selector: { - ui_action: {}, - }, + name: "interactions", + type: "expandable", + flatten: true, + iconPath: mdiGestureTap, + schema: [ + { + name: "tap_action", + selector: { + ui_action: { + default_action: "more-info", + }, + }, + }, + { + name: "", + type: "optional_actions", + flatten: true, + schema: (["hold_action", "double_tap_action"] as const).map( + (action) => ({ + name: action, + selector: { + ui_action: { + default_action: "none" as const, + }, + }, + }) + ), + }, + ], }, { name: "image", selector: { image: {} } }, { name: "camera_image", selector: { entity: { domain: "camera" } } }, diff --git a/src/panels/lovelace/editor/config-elements/elements/hui-state-badge-element-editor.ts b/src/panels/lovelace/editor/config-elements/elements/hui-state-badge-element-editor.ts index 81571919f6..aab46a1025 100644 --- a/src/panels/lovelace/editor/config-elements/elements/hui-state-badge-element-editor.ts +++ b/src/panels/lovelace/editor/config-elements/elements/hui-state-badge-element-editor.ts @@ -1,6 +1,7 @@ import { html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { any, assert, literal, object, optional, string } from "superstruct"; +import { mdiGestureTap } from "@mdi/js"; import { fireEvent } from "../../../../../common/dom/fire_event"; import type { SchemaUnion } from "../../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../../types"; @@ -23,16 +24,35 @@ const SCHEMA = [ { name: "entity", required: true, selector: { entity: {} } }, { name: "title", selector: { text: {} } }, { - name: "tap_action", - selector: { - ui_action: {}, - }, - }, - { - name: "hold_action", - selector: { - ui_action: {}, - }, + name: "interactions", + type: "expandable", + flatten: true, + iconPath: mdiGestureTap, + schema: [ + { + name: "tap_action", + selector: { + ui_action: { + default_action: "more-info", + }, + }, + }, + { + name: "", + type: "optional_actions", + flatten: true, + schema: (["hold_action", "double_tap_action"] as const).map( + (action) => ({ + name: action, + selector: { + ui_action: { + default_action: "none" as const, + }, + }, + }) + ), + }, + ], }, { name: "style", selector: { object: {} } }, ] as const; diff --git a/src/panels/lovelace/editor/config-elements/elements/hui-state-icon-element-editor.ts b/src/panels/lovelace/editor/config-elements/elements/hui-state-icon-element-editor.ts index bee2c275e1..333d220b68 100644 --- a/src/panels/lovelace/editor/config-elements/elements/hui-state-icon-element-editor.ts +++ b/src/panels/lovelace/editor/config-elements/elements/hui-state-icon-element-editor.ts @@ -1,3 +1,4 @@ +import { mdiGestureTap } from "@mdi/js"; import { html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { @@ -10,11 +11,11 @@ import { string, } from "superstruct"; import { fireEvent } from "../../../../../common/dom/fire_event"; +import "../../../../../components/ha-form/ha-form"; import type { SchemaUnion } from "../../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../../types"; -import "../../../../../components/ha-form/ha-form"; -import type { LovelacePictureElementEditor } from "../../../types"; import type { StateIconElementConfig } from "../../../elements/types"; +import type { LovelacePictureElementEditor } from "../../../types"; import { actionConfigStruct } from "../../structs/action-struct"; const stateIconElementConfigStruct = object({ @@ -35,16 +36,35 @@ const SCHEMA = [ { name: "title", selector: { text: {} } }, { name: "state_color", default: true, selector: { boolean: {} } }, { - name: "tap_action", - selector: { - ui_action: {}, - }, - }, - { - name: "hold_action", - selector: { - ui_action: {}, - }, + name: "interactions", + type: "expandable", + flatten: true, + iconPath: mdiGestureTap, + schema: [ + { + name: "tap_action", + selector: { + ui_action: { + default_action: "more-info", + }, + }, + }, + { + name: "", + type: "optional_actions", + flatten: true, + schema: (["hold_action", "double_tap_action"] as const).map( + (action) => ({ + name: action, + selector: { + ui_action: { + default_action: "none" as const, + }, + }, + }) + ), + }, + ], }, { name: "style", selector: { object: {} } }, ] as const; diff --git a/src/panels/lovelace/editor/config-elements/elements/hui-state-label-element-editor.ts b/src/panels/lovelace/editor/config-elements/elements/hui-state-label-element-editor.ts index 7c531a3836..6589e426e0 100644 --- a/src/panels/lovelace/editor/config-elements/elements/hui-state-label-element-editor.ts +++ b/src/panels/lovelace/editor/config-elements/elements/hui-state-label-element-editor.ts @@ -1,12 +1,13 @@ +import { mdiGestureTap } from "@mdi/js"; import { html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { any, assert, literal, object, optional, string } from "superstruct"; import { fireEvent } from "../../../../../common/dom/fire_event"; +import "../../../../../components/ha-form/ha-form"; import type { SchemaUnion } from "../../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../../types"; -import "../../../../../components/ha-form/ha-form"; -import type { LovelacePictureElementEditor } from "../../../types"; import type { StateLabelElementConfig } from "../../../elements/types"; +import type { LovelacePictureElementEditor } from "../../../types"; import { actionConfigStruct } from "../../structs/action-struct"; const stateLabelElementConfigStruct = object({ @@ -35,16 +36,35 @@ const SCHEMA = [ { name: "suffix", selector: { text: {} } }, { name: "title", selector: { text: {} } }, { - name: "tap_action", - selector: { - ui_action: {}, - }, - }, - { - name: "hold_action", - selector: { - ui_action: {}, - }, + name: "interactions", + type: "expandable", + flatten: true, + iconPath: mdiGestureTap, + schema: [ + { + name: "tap_action", + selector: { + ui_action: { + default_action: "more-info", + }, + }, + }, + { + name: "", + type: "optional_actions", + flatten: true, + schema: (["hold_action", "double_tap_action"] as const).map( + (action) => ({ + name: action, + selector: { + ui_action: { + default_action: "none" as const, + }, + }, + }) + ), + }, + ], }, { name: "style", selector: { object: {} } }, ] as const; diff --git a/src/panels/lovelace/editor/config-elements/hui-button-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-button-card-editor.ts index 2e611a435b..6acd0e5867 100644 --- a/src/panels/lovelace/editor/config-elements/hui-button-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-button-card-editor.ts @@ -1,3 +1,4 @@ +import { mdiGestureTap } from "@mdi/js"; import type { CSSResultGroup } from "lit"; import { html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; @@ -28,6 +29,7 @@ const cardConfigStruct = assign( icon_height: optional(string()), tap_action: optional(actionConfigStruct), hold_action: optional(actionConfigStruct), + double_tap_action: optional(actionConfigStruct), theme: optional(string()), show_state: optional(boolean()), }) @@ -86,20 +88,43 @@ export class HuiButtonCardEditor ], }, { - name: "tap_action", - selector: { - ui_action: { - default_action: getEntityDefaultButtonAction(entityId), + name: "interactions", + type: "expandable", + flatten: true, + iconPath: mdiGestureTap, + schema: [ + { + name: "tap_action", + selector: { + ui_action: { + default_action: getEntityDefaultButtonAction(entityId), + }, + }, }, - }, - }, - { - name: "hold_action", - selector: { - ui_action: { - default_action: "more-info", + { + name: "hold_action", + selector: { + ui_action: { + default_action: "more-info", + }, + }, }, - }, + { + name: "", + type: "optional_actions", + flatten: true, + schema: [ + { + name: "double_tap_action", + selector: { + ui_action: { + default_action: "none", + }, + }, + }, + ], + }, + ], }, ] as const satisfies readonly HaFormSchema[] ); diff --git a/src/panels/lovelace/editor/config-elements/hui-gauge-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-gauge-card-editor.ts index ddea1b8b71..0b7359a9e8 100644 --- a/src/panels/lovelace/editor/config-elements/hui-gauge-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-gauge-card-editor.ts @@ -1,3 +1,4 @@ +import { mdiGestureTap } from "@mdi/js"; import { html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; @@ -16,14 +17,21 @@ import { fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/ha-form/ha-form"; import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; +import { DEFAULT_MAX, DEFAULT_MIN } from "../../cards/hui-gauge-card"; import type { GaugeCardConfig } from "../../cards/types"; +import type { UiAction } from "../../components/hui-action-editor"; import type { LovelaceCardEditor } from "../../types"; import { actionConfigStruct } from "../structs/action-struct"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; -import { DEFAULT_MIN, DEFAULT_MAX } from "../../cards/hui-gauge-card"; -import type { UiAction } from "../../components/hui-action-editor"; -const TAP_ACTIONS: UiAction[] = ["navigate", "url", "perform-action", "none"]; +const TAP_ACTIONS: UiAction[] = [ + "more-info", + "navigate", + "url", + "perform-action", + "assist", + "none", +]; const gaugeSegmentStruct = object({ from: number(), @@ -134,13 +142,37 @@ export class HuiGaugeCardEditor ] as const) : []), { - name: "tap_action", - selector: { - ui_action: { - actions: TAP_ACTIONS, - default_action: "more-info", + name: "interactions", + type: "expandable", + flatten: true, + iconPath: mdiGestureTap, + schema: [ + { + name: "tap_action", + selector: { + ui_action: { + actions: TAP_ACTIONS, + default_action: "more-info", + }, + }, }, - }, + { + name: "", + type: "optional_actions", + flatten: true, + schema: (["hold_action", "double_tap_action"] as const).map( + (action) => ({ + name: action, + selector: { + ui_action: { + actions: TAP_ACTIONS, + default_action: "none" as const, + }, + }, + }) + ), + }, + ], }, ] as const ); @@ -231,7 +263,13 @@ export class HuiGaugeCardEditor return this.hass!.localize( "ui.panel.lovelace.editor.card.generic.unit" ); + case "interactions": + return this.hass!.localize( + "ui.panel.lovelace.editor.card.generic.interactions" + ); case "tap_action": + case "hold_action": + case "double_tap_action": return `${this.hass!.localize( `ui.panel.lovelace.editor.card.generic.${schema.name}` )} (${this.hass!.localize( diff --git a/src/panels/lovelace/editor/config-elements/hui-light-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-light-card-editor.ts index 601f59a2c8..2427b5d0d4 100644 --- a/src/panels/lovelace/editor/config-elements/hui-light-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-light-card-editor.ts @@ -2,6 +2,7 @@ import type { CSSResultGroup } from "lit"; import { html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { assert, assign, object, optional, string } from "superstruct"; +import { mdiGestureTap } from "@mdi/js"; import { fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/ha-form/ha-form"; import type { SchemaUnion } from "../../../../components/ha-form/types"; @@ -19,6 +20,7 @@ const cardConfigStruct = assign( entity: optional(string()), theme: optional(string()), icon: optional(string()), + tap_action: optional(actionConfigStruct), hold_action: optional(actionConfigStruct), double_tap_action: optional(actionConfigStruct), }) @@ -48,12 +50,43 @@ const SCHEMA = [ }, { name: "theme", selector: { theme: {} } }, { - name: "hold_action", - selector: { ui_action: {} }, - }, - { - name: "double_tap_action", - selector: { ui_action: {} }, + name: "interactions", + type: "expandable", + flatten: true, + iconPath: mdiGestureTap, + schema: [ + { + name: "tap_action", + selector: { + ui_action: { + default_action: "toggle", + }, + }, + }, + { + name: "hold_action", + selector: { + ui_action: { + default_action: "more-info", + }, + }, + }, + { + name: "", + type: "optional_actions", + flatten: true, + schema: [ + { + name: "double_tap_action", + selector: { + ui_action: { + default_action: "none", + }, + }, + }, + ], + }, + ], }, ] as const; diff --git a/src/panels/lovelace/editor/config-elements/hui-picture-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-picture-card-editor.ts index 12796b572c..d084a2a400 100644 --- a/src/panels/lovelace/editor/config-elements/hui-picture-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-picture-card-editor.ts @@ -1,3 +1,4 @@ +import { mdiGestureTap } from "@mdi/js"; import { html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { assert, assign, object, optional, string } from "superstruct"; @@ -18,6 +19,7 @@ const cardConfigStruct = assign( image_entity: optional(string()), tap_action: optional(actionConfigStruct), hold_action: optional(actionConfigStruct), + double_tap_action: optional(actionConfigStruct), theme: optional(string()), alt_text: optional(string()), }) @@ -32,12 +34,35 @@ const SCHEMA = [ { name: "alt_text", selector: { text: {} } }, { name: "theme", selector: { theme: {} } }, { - name: "tap_action", - selector: { ui_action: {} }, - }, - { - name: "hold_action", - selector: { ui_action: {} }, + name: "interactions", + type: "expandable", + flatten: true, + iconPath: mdiGestureTap, + schema: [ + { + name: "tap_action", + selector: { + ui_action: { + default_action: "more-info", + }, + }, + }, + { + name: "", + type: "optional_actions", + flatten: true, + schema: (["hold_action", "double_tap_action"] as const).map( + (action) => ({ + name: action, + selector: { + ui_action: { + default_action: "none" as const, + }, + }, + }) + ), + }, + ], }, ] as const; diff --git a/src/panels/lovelace/editor/config-elements/hui-picture-entity-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-picture-entity-card-editor.ts index 4c9c7a0615..86a5686909 100644 --- a/src/panels/lovelace/editor/config-elements/hui-picture-entity-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-picture-entity-card-editor.ts @@ -1,18 +1,19 @@ +import { mdiGestureTap } from "@mdi/js"; import type { CSSResultGroup } from "lit"; import { html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { assert, assign, boolean, object, optional, string } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; +import { computeDomain } from "../../../../common/entity/compute_domain"; import "../../../../components/ha-form/ha-form"; import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; +import { STUB_IMAGE } from "../../cards/hui-picture-entity-card"; import type { PictureEntityCardConfig } from "../../cards/types"; import type { LovelaceCardEditor } from "../../types"; import { actionConfigStruct } from "../structs/action-struct"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; import { configElementStyle } from "./config-elements-style"; -import { computeDomain } from "../../../../common/entity/compute_domain"; -import { STUB_IMAGE } from "../../cards/hui-picture-entity-card"; const cardConfigStruct = assign( baseLovelaceCardConfig, @@ -25,6 +26,7 @@ const cardConfigStruct = assign( aspect_ratio: optional(string()), tap_action: optional(actionConfigStruct), hold_action: optional(actionConfigStruct), + double_tap_action: optional(actionConfigStruct), show_name: optional(boolean()), show_state: optional(boolean()), theme: optional(string()), @@ -64,12 +66,35 @@ const SCHEMA = [ }, { name: "theme", selector: { theme: {} } }, { - name: "tap_action", - selector: { ui_action: {} }, - }, - { - name: "hold_action", - selector: { ui_action: {} }, + name: "interactions", + type: "expandable", + flatten: true, + iconPath: mdiGestureTap, + schema: [ + { + name: "tap_action", + selector: { + ui_action: { + default_action: "more-info", + }, + }, + }, + { + name: "", + type: "optional_actions", + flatten: true, + schema: (["hold_action", "double_tap_action"] as const).map( + (action) => ({ + name: action, + selector: { + ui_action: { + default_action: "none" as const, + }, + }, + }) + ), + }, + ], }, ] as const; @@ -132,6 +157,7 @@ export class HuiPictureEntityCardEditor case "theme": case "tap_action": case "hold_action": + case "double_tap_action": return `${this.hass!.localize( `ui.panel.lovelace.editor.card.generic.${schema.name}` )} (${this.hass!.localize( diff --git a/src/panels/lovelace/editor/config-elements/hui-picture-glance-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-picture-glance-card-editor.ts index 86c477c519..ff52bae510 100644 --- a/src/panels/lovelace/editor/config-elements/hui-picture-glance-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-picture-glance-card-editor.ts @@ -2,6 +2,7 @@ import type { CSSResultGroup } from "lit"; import { html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { array, assert, assign, object, optional, string } from "superstruct"; +import { mdiGestureTap } from "@mdi/js"; import { fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/ha-form/ha-form"; import type { SchemaUnion } from "../../../../components/ha-form/types"; @@ -29,6 +30,7 @@ const cardConfigStruct = assign( aspect_ratio: optional(string()), tap_action: optional(actionConfigStruct), hold_action: optional(actionConfigStruct), + double_tap_action: optional(actionConfigStruct), entities: array(entitiesConfigStruct), theme: optional(string()), }) @@ -56,12 +58,35 @@ const SCHEMA = [ { name: "entity", selector: { entity: {} } }, { name: "theme", selector: { theme: {} } }, { - name: "tap_action", - selector: { ui_action: {} }, - }, - { - name: "hold_action", - selector: { ui_action: {} }, + name: "interactions", + type: "expandable", + flatten: true, + iconPath: mdiGestureTap, + schema: [ + { + name: "tap_action", + selector: { + ui_action: { + default_action: "more-info", + }, + }, + }, + { + name: "", + type: "optional_actions", + flatten: true, + schema: (["hold_action", "double_tap_action"] as const).map( + (action) => ({ + name: action, + selector: { + ui_action: { + default_action: "none" as const, + }, + }, + }) + ), + }, + ], }, ] as const; @@ -136,6 +161,7 @@ export class HuiPictureGlanceCardEditor case "theme": case "tap_action": case "hold_action": + case "double_tap_action": return `${this.hass!.localize( `ui.panel.lovelace.editor.card.generic.${schema.name}` )} (${this.hass!.localize( diff --git a/src/panels/lovelace/elements/hui-icon-element.ts b/src/panels/lovelace/elements/hui-icon-element.ts index 4b93e0677d..1d54b0c05a 100644 --- a/src/panels/lovelace/elements/hui-icon-element.ts +++ b/src/panels/lovelace/elements/hui-icon-element.ts @@ -31,7 +31,7 @@ export class HuiIconElement extends LitElement implements LovelaceElement { throw Error("Icon required"); } - this._config = { hold_action: { action: "more-info" }, ...config }; + this._config = { tap_action: { action: "more-info" }, ...config }; } protected render() { diff --git a/src/panels/lovelace/elements/hui-image-element.ts b/src/panels/lovelace/elements/hui-image-element.ts index f36006df20..ff94f9b68e 100644 --- a/src/panels/lovelace/elements/hui-image-element.ts +++ b/src/panels/lovelace/elements/hui-image-element.ts @@ -29,7 +29,7 @@ export class HuiImageElement extends LitElement implements LovelaceElement { throw Error("Invalid configuration"); } - this._config = { hold_action: { action: "more-info" }, ...config }; + this._config = { tap_action: { action: "more-info" }, ...config }; this.classList.toggle( "clickable", diff --git a/src/panels/lovelace/elements/hui-state-badge-element.ts b/src/panels/lovelace/elements/hui-state-badge-element.ts index e9d67f4280..1c08801d90 100644 --- a/src/panels/lovelace/elements/hui-state-badge-element.ts +++ b/src/panels/lovelace/elements/hui-state-badge-element.ts @@ -60,7 +60,7 @@ export class HuiStateBadgeElement throw Error("Entity required"); } - this._config = { hold_action: { action: "more-info" }, ...config }; + this._config = { tap_action: { action: "more-info" }, ...config }; } protected shouldUpdate(changedProps: PropertyValues): boolean { diff --git a/src/panels/lovelace/elements/hui-state-icon-element.ts b/src/panels/lovelace/elements/hui-state-icon-element.ts index e151850569..6e6a08c995 100644 --- a/src/panels/lovelace/elements/hui-state-icon-element.ts +++ b/src/panels/lovelace/elements/hui-state-icon-element.ts @@ -59,7 +59,7 @@ export class HuiStateIconElement extends LitElement implements LovelaceElement { this._config = { state_color: true, - hold_action: { action: "more-info" }, + tap_action: { action: "more-info" }, ...config, }; } diff --git a/src/panels/lovelace/elements/hui-state-label-element.ts b/src/panels/lovelace/elements/hui-state-label-element.ts index 56a3cf0bf4..cc64ae8ea8 100644 --- a/src/panels/lovelace/elements/hui-state-label-element.ts +++ b/src/panels/lovelace/elements/hui-state-label-element.ts @@ -56,7 +56,7 @@ class HuiStateLabelElement extends LitElement implements LovelaceElement { throw Error("Entity required"); } - this._config = { hold_action: { action: "more-info" }, ...config }; + this._config = { tap_action: { action: "more-info" }, ...config }; } protected shouldUpdate(changedProps: PropertyValues): boolean { From e765cc10fb0517ff2cc1b66a08f14f4fec5b755a Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 28 Mar 2025 13:53:08 +0100 Subject: [PATCH 17/53] Fix voice flow (#24825) * Fix voice flow * Apply suggestions from code review --------- Co-authored-by: Paulus Schoutsen --- .../voice-assistant-setup-step-local.ts | 18 +++++++------ .../voice-assistant-setup-step-pipeline.ts | 25 +++++++++++-------- src/translations/en.json | 4 +-- 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-local.ts b/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-local.ts index 69c1381233..d0765f589b 100644 --- a/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-local.ts +++ b/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-local.ts @@ -325,14 +325,16 @@ export class HaVoiceAssistantSetupStepLocal extends LitElement { (flow) => flow.handler === "wyoming" && flow.context.source === "hassio" && - (flow.context.configuration_url.includes( - type === "tts" ? this._ttsHostName : this._sttHostName - ) || - flow.context.title_placeholders.title - .toLowerCase() - .includes( - type === "tts" ? this._ttsProviderName : this._sttProviderName - )) + ((flow.context.configuration_url && + flow.context.configuration_url.includes( + type === "tts" ? this._ttsAddonName : this._sttAddonName + )) || + (flow.context.title_placeholders.title && + flow.context.title_placeholders.title + .toLowerCase() + .includes( + type === "tts" ? this._ttsProviderName : this._sttProviderName + ))) ); } diff --git a/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-pipeline.ts b/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-pipeline.ts index d1fb161d30..f3bda9c260 100644 --- a/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-pipeline.ts +++ b/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-pipeline.ts @@ -15,7 +15,7 @@ import { } from "../../data/assist_pipeline"; import type { AssistSatelliteConfiguration } from "../../data/assist_satellite"; import { fetchCloudStatus } from "../../data/cloud"; -import type { LanguageScores } from "../../data/conversation"; +import type { LanguageScore, LanguageScores } from "../../data/conversation"; import { getLanguageScores, listAgents } from "../../data/conversation"; import { listSTTEngines } from "../../data/stt"; import { listTTSEngines, listTTSVoices } from "../../data/tts"; @@ -26,6 +26,12 @@ import { documentationUrl } from "../../util/documentation-url"; const OPTIONS = ["cloud", "focused_local", "full_local"] as const; +const EMPTY_SCORE: LanguageScore = { + cloud: 0, + focused_local: 0, + full_local: 0, +}; + @customElement("ha-voice-assistant-setup-step-pipeline") export class HaVoiceAssistantSetupStepPipeline extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -61,12 +67,12 @@ export class HaVoiceAssistantSetupStepPipeline extends LitElement { this._languageScores ) { const lang = this.language; - if (this._value && this._languageScores[lang][this._value] === 0) { + if (this._value && this._languageScores[lang]?.[this._value] === 0) { this._value = undefined; } if (!this._value) { this._value = this._getOptions( - this._languageScores[lang], + this._languageScores[lang] || EMPTY_SCORE, this.hass.localize ).supportedOptions[0]?.value as | "cloud" @@ -147,12 +153,9 @@ export class HaVoiceAssistantSetupStepPipeline extends LitElement { `; } - const score = this._languageScores[this.language]; + const score = this._languageScores[this.language] || EMPTY_SCORE; - const options = this._getOptions( - score || { cloud: 3, focused_local: 0, full_local: 0 }, - this.hass.localize - ); + const options = this._getOptions(score, this.hass.localize); const performance = !this._value ? "" @@ -162,11 +165,11 @@ export class HaVoiceAssistantSetupStepPipeline extends LitElement { const commands = !this._value ? "" - : score?.[this._value] > 2 + : score[this._value] > 2 ? "high" - : score?.[this._value] > 1 + : score[this._value] > 1 ? "ready" - : score?.[this._value] > 0 + : score[this._value] > 0 ? "low" : ""; diff --git a/src/translations/en.json b/src/translations/en.json index a030e84897..bfaffcf359 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3431,9 +3431,9 @@ }, "local": { "title": "Installing add-ons", - "secondary": "The Whisper and Piper add-ons are being installed and configured based on your hardware.", + "secondary": "The add-ons for speech-to-text and text-to-speech are being installed and configured based on your hardware.", "failed_title": "Failed to install add-ons", - "failed_secondary": "We were unable to install the Whisper and Piper add-ons automatically for you. Read the documentation to learn how to install them.", + "failed_secondary": "We were unable to install the add-ons for speech-to-text and text-to-speech automatically for you. Read the documentation to learn how to install them.", "not_supported_title": "Installation of add-ons is not supported on your system", "not_supported_secondary": "Your system is not supported to automatically install a local TTS and STT provider. Learn how to set up local TTS and STT providers in the documentation.", "local_pipeline": "Local Assistant", From d5ff8ab1e19334763576e21f66d5aece6dd16d37 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 28 Mar 2025 08:40:57 -0400 Subject: [PATCH 18/53] Do not play pre-announce sound when testing voice on satellite (#24827) --- src/data/assist_satellite.ts | 9 ++++++--- .../voice-assistant-setup-step-success.ts | 5 ++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/data/assist_satellite.ts b/src/data/assist_satellite.ts index 19a0e180b0..cee5862cd0 100644 --- a/src/data/assist_satellite.ts +++ b/src/data/assist_satellite.ts @@ -49,9 +49,12 @@ export const testAssistSatelliteConnection = ( export const assistSatelliteAnnounce = ( hass: HomeAssistant, entity_id: string, - message: string -) => - hass.callService("assist_satellite", "announce", { message }, { entity_id }); + args: { + message?: string; + media_id?: string; + preannounce_media_id?: string | null; + } +) => hass.callService("assist_satellite", "announce", args, { entity_id }); export const fetchAssistSatelliteConfiguration = ( hass: HomeAssistant, diff --git a/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-success.ts b/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-success.ts index e9db911f49..1ec7a400d8 100644 --- a/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-success.ts +++ b/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-success.ts @@ -246,7 +246,10 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement { if (!this.assistEntityId) { return; } - await assistSatelliteAnnounce(this.hass, this.assistEntityId, message); + await assistSatelliteAnnounce(this.hass, this.assistEntityId, { + message, + preannounce_media_id: null, + }); } private _testWakeWord() { From a8e5c8482b186287d857526b2a5f4ab30710c70d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 28 Mar 2025 08:55:39 -0400 Subject: [PATCH 19/53] Hide backup from default dashboard (#24828) --- src/panels/lovelace/common/generate-lovelace-config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/lovelace/common/generate-lovelace-config.ts b/src/panels/lovelace/common/generate-lovelace-config.ts index b1ef399835..8e373cf384 100644 --- a/src/panels/lovelace/common/generate-lovelace-config.ts +++ b/src/panels/lovelace/common/generate-lovelace-config.ts @@ -50,7 +50,7 @@ const HIDE_DOMAIN = new Set([ ...ASSIST_ENTITIES, ]); -const HIDE_PLATFORM = new Set(["mobile_app"]); +const HIDE_PLATFORM = new Set(["backup", "mobile_app"]); interface SplittedByAreaDevice { areasWithEntities: Record; From a88d066d7e6e23a47fc081a2d2cd1f2bb730cf3f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 28 Mar 2025 14:06:54 +0100 Subject: [PATCH 20/53] Update text voice wizard install addons step (#24829) --- src/translations/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/translations/en.json b/src/translations/en.json index bfaffcf359..ac1e18c203 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3431,7 +3431,7 @@ }, "local": { "title": "Installing add-ons", - "secondary": "The add-ons for speech-to-text and text-to-speech are being installed and configured based on your hardware.", + "secondary": "We are preparing your system for local voice processing.", "failed_title": "Failed to install add-ons", "failed_secondary": "We were unable to install the add-ons for speech-to-text and text-to-speech automatically for you. Read the documentation to learn how to install them.", "not_supported_title": "Installation of add-ons is not supported on your system", From 345ad6c9c5f7644e9fb3e65f74ddd165de86bed6 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 28 Mar 2025 15:01:08 +0100 Subject: [PATCH 21/53] Update voice-assistant-setup-step-local.ts --- .../voice-assistant-setup-step-local.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-local.ts b/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-local.ts index d0765f589b..8aaf6c0a08 100644 --- a/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-local.ts +++ b/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-local.ts @@ -243,7 +243,7 @@ export class HaVoiceAssistantSetupStepLocal extends LitElement { private readonly _ttsHostName = "core-piper"; - private readonly _ttsPort = "10200"; + private readonly _ttsPort = 10200; private get _sttProviderName() { return this.localOption === "focused_local" @@ -263,7 +263,7 @@ export class HaVoiceAssistantSetupStepLocal extends LitElement { : "core-whisper"; } - private readonly _sttPort = "10300"; + private readonly _sttPort = 10300; private async _findLocalEntities() { const wyomingEntities = Object.values(this.hass.entities).filter( @@ -329,8 +329,8 @@ export class HaVoiceAssistantSetupStepLocal extends LitElement { flow.context.configuration_url.includes( type === "tts" ? this._ttsAddonName : this._sttAddonName )) || - (flow.context.title_placeholders.title && - flow.context.title_placeholders.title + (flow.context.title_placeholders.name && + flow.context.title_placeholders.name .toLowerCase() .includes( type === "tts" ? this._ttsProviderName : this._sttProviderName From 724adab2d6a3535dea082d0f049aa6616dac201d Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 28 Mar 2025 15:02:51 +0100 Subject: [PATCH 22/53] Bumped version to 20250328.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 958a1c56d8..74d56c8454 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20250327.1" +version = "20250328.0" license = "Apache-2.0" license-files = ["LICENSE*"] description = "The Home Assistant frontend" From e8e65a4293aff81401531bd0aa25ceb18cdad7f1 Mon Sep 17 00:00:00 2001 From: Darren Griffin Date: Fri, 28 Mar 2025 14:13:50 +0000 Subject: [PATCH 23/53] Fix default time_format option. Fixes #24798 (#24819) * Fix default time_format option. Fixes #24798 * Update en.json * Update src/translations/en.json --------- Co-authored-by: Bram Kragten --- .../editor/config-elements/hui-clock-card-editor.ts | 8 ++++++-- src/translations/en.json | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/panels/lovelace/editor/config-elements/hui-clock-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-clock-card-editor.ts index cbfffc1784..83a140b553 100644 --- a/src/panels/lovelace/editor/config-elements/hui-clock-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-clock-card-editor.ts @@ -72,7 +72,7 @@ export class HuiClockCardEditor selector: { select: { mode: "dropdown", - options: Object.values(TimeFormat).map((value) => ({ + options: ["auto", ...Object.values(TimeFormat)].map((value) => ({ value, label: localize( `ui.panel.lovelace.editor.card.clock.time_formats.${value}` @@ -86,7 +86,7 @@ export class HuiClockCardEditor private _data = memoizeOne((config) => ({ clock_size: "small", - time_format: TimeFormat.language, + time_format: "auto", show_seconds: false, ...config, })); @@ -113,6 +113,10 @@ export class HuiClockCardEditor } private _valueChanged(ev: CustomEvent): void { + if (ev.detail.value.time_format === "auto") { + delete ev.detail.value.time_format; + } + fireEvent(this, "config-changed", { config: ev.detail.value }); } diff --git a/src/translations/en.json b/src/translations/en.json index ac1e18c203..ddb3c37d37 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -7163,6 +7163,7 @@ "show_seconds": "Display seconds", "time_format": "Time format", "time_formats": { + "auto": "Use user settings", "language": "[%key:ui::panel::profile::time_format::formats::language%]", "system": "[%key:ui::panel::profile::time_format::formats::system%]", "24": "[%key:ui::panel::profile::time_format::formats::24%]", From 216135722617e77e4ed87889c223901fa5365e8a Mon Sep 17 00:00:00 2001 From: Eloy Rodriguez Date: Fri, 28 Mar 2025 15:32:50 +0100 Subject: [PATCH 24/53] Add title and time zone to clock card (#24818) * Add title and time zone to clock card * Small changes to the spacing and text sizing of the clock card * Update translations to use dropdown labels from profile configuration * Use similar approach as #24819 for setting automatic time zone * Update hui-clock-card.ts --------- Co-authored-by: Bram Kragten --- src/panels/lovelace/cards/hui-clock-card.ts | 53 ++++++++++++++++--- src/panels/lovelace/cards/types.ts | 2 + .../config-elements/hui-clock-card-editor.ts | 44 ++++++++++++--- src/translations/en.json | 6 ++- 4 files changed, 91 insertions(+), 14 deletions(-) diff --git a/src/panels/lovelace/cards/hui-clock-card.ts b/src/panels/lovelace/cards/hui-clock-card.ts index c99498614d..346ddb9adb 100644 --- a/src/panels/lovelace/cards/hui-clock-card.ts +++ b/src/panels/lovelace/cards/hui-clock-card.ts @@ -65,7 +65,9 @@ export class HuiClockCard extends LitElement implements LovelaceCard { minute: "2-digit", second: "2-digit", hourCycle: useAmPm(locale) ? "h12" : "h23", - timeZone: resolveTimeZone(locale.time_zone, this.hass.config?.time_zone), + timeZone: + this._config?.time_zone || + resolveTimeZone(locale.time_zone, this.hass.config?.time_zone), }); this._tick(); @@ -79,7 +81,7 @@ export class HuiClockCard extends LitElement implements LovelaceCard { public getGridOptions(): LovelaceGridOptions { if (this._config?.clock_size === "medium") { return { - min_rows: 1, + min_rows: this._config?.title ? 2 : 1, rows: 2, max_rows: 4, min_columns: 4, @@ -101,7 +103,7 @@ export class HuiClockCard extends LitElement implements LovelaceCard { min_rows: 1, rows: 1, max_rows: 4, - min_columns: 4, + min_columns: 3, columns: 6, }; } @@ -160,6 +162,9 @@ export class HuiClockCard extends LitElement implements LovelaceCard { ? `size-${this._config.clock_size}` : ""}" > + ${this._config.title !== undefined + ? html`
${this._config.title}
` + : nothing}
${this._timeHour}
${this._timeMinute}
@@ -182,9 +187,41 @@ export class HuiClockCard extends LitElement implements LovelaceCard { .time-wrapper { display: flex; - height: 100%; + height: calc(100% - 12px); align-items: center; + flex-direction: column; justify-content: center; + padding: 6px 8px; + row-gap: 6px; + } + + .time-wrapper.size-medium, + .time-wrapper.size-large { + height: calc(100% - 32px); + padding: 16px; + row-gap: 12px; + } + + .time-title { + color: var(--primary-text-color); + font-size: 14px; + font-weight: 400; + line-height: 18px; + overflow: hidden; + text-align: center; + text-overflow: ellipsis; + white-space: nowrap; + width: 100%; + } + + .time-wrapper.size-medium .time-title { + font-size: 18px; + line-height: 21px; + } + + .time-wrapper.size-large .time-title { + font-size: 24px; + line-height: 28px; } .time-parts { @@ -197,7 +234,10 @@ export class HuiClockCard extends LitElement implements LovelaceCard { font-size: 2rem; font-weight: 500; line-height: 0.8; - padding: 16px 0; + } + + .time-title + .time-parts { + font-size: 1.5rem; } .time-wrapper.size-medium .time-parts { @@ -242,8 +282,7 @@ export class HuiClockCard extends LitElement implements LovelaceCard { .time-parts .time-part.second, .time-parts .time-part.am-pm { - font-size: 12px; - font-weight: 500; + font-size: 10px; margin-left: 4px; } diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index 5e7e25ed8d..30227b5fe3 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -349,9 +349,11 @@ export interface MarkdownCardConfig extends LovelaceCardConfig { export interface ClockCardConfig extends LovelaceCardConfig { type: "clock"; + title?: string; clock_size?: "small" | "medium" | "large"; show_seconds?: boolean | undefined; time_format?: TimeFormat; + time_zone?: string; } export interface MediaControlCardConfig extends LovelaceCardConfig { diff --git a/src/panels/lovelace/editor/config-elements/hui-clock-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-clock-card-editor.ts index 83a140b553..cb73a2460d 100644 --- a/src/panels/lovelace/editor/config-elements/hui-clock-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-clock-card-editor.ts @@ -1,3 +1,4 @@ +import timezones from "google-timezones-json"; import { html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; @@ -9,6 +10,7 @@ import { literal, object, optional, + string, union, } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; @@ -27,10 +29,12 @@ import { TimeFormat } from "../../../../data/translation"; const cardConfigStruct = assign( baseLovelaceCardConfig, object({ + title: optional(string()), clock_size: optional( union([literal("small"), literal("medium"), literal("large")]) ), time_format: optional(enums(Object.values(TimeFormat))), + time_zone: optional(enums(Object.keys(timezones))), show_seconds: optional(boolean()), }) ); @@ -47,6 +51,7 @@ export class HuiClockCardEditor private _schema = memoizeOne( (localize: LocalizeFunc) => [ + { name: "title", selector: { text: {} } }, { name: "clock_size", selector: { @@ -61,12 +66,7 @@ export class HuiClockCardEditor }, }, }, - { - name: "show_seconds", - selector: { - boolean: {}, - }, - }, + { name: "show_seconds", selector: { boolean: {} } }, { name: "time_format", selector: { @@ -81,11 +81,32 @@ export class HuiClockCardEditor }, }, }, + { + name: "time_zone", + selector: { + select: { + mode: "dropdown", + options: [ + [ + "auto", + localize( + `ui.panel.lovelace.editor.card.clock.time_zones.auto` + ), + ], + ...Object.entries(timezones as Record), + ].map(([key, value]) => ({ + value: key, + label: value, + })), + }, + }, + }, ] as const satisfies readonly HaFormSchema[] ); private _data = memoizeOne((config) => ({ clock_size: "small", + time_zone: "auto", time_format: "auto", show_seconds: false, ...config, @@ -113,6 +134,9 @@ export class HuiClockCardEditor } private _valueChanged(ev: CustomEvent): void { + if (ev.detail.value.time_zone === "auto") { + delete ev.detail.value.time_zone; + } if (ev.detail.value.time_format === "auto") { delete ev.detail.value.time_format; } @@ -124,6 +148,10 @@ export class HuiClockCardEditor schema: SchemaUnion> ) => { switch (schema.name) { + case "title": + return this.hass!.localize( + "ui.panel.lovelace.editor.card.generic.title" + ); case "clock_size": return this.hass!.localize( `ui.panel.lovelace.editor.card.clock.clock_size` @@ -132,6 +160,10 @@ export class HuiClockCardEditor return this.hass!.localize( `ui.panel.lovelace.editor.card.clock.time_format` ); + case "time_zone": + return this.hass!.localize( + `ui.panel.lovelace.editor.card.clock.time_zone` + ); case "show_seconds": return this.hass!.localize( `ui.panel.lovelace.editor.card.clock.show_seconds` diff --git a/src/translations/en.json b/src/translations/en.json index ddb3c37d37..937aae1df3 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -7161,13 +7161,17 @@ "large": "Large" }, "show_seconds": "Display seconds", - "time_format": "Time format", + "time_format": "[%key:ui::panel::profile::time_format::dropdown_label%]", "time_formats": { "auto": "Use user settings", "language": "[%key:ui::panel::profile::time_format::formats::language%]", "system": "[%key:ui::panel::profile::time_format::formats::system%]", "24": "[%key:ui::panel::profile::time_format::formats::24%]", "12": "[%key:ui::panel::profile::time_format::formats::12%]" + }, + "time_zone": "[%key:ui::panel::profile::time_zone::dropdown_label%]", + "time_zones": { + "auto": "Use user settings" } }, "media-control": { From fe7e8e17aee095fdcdaea090d3c18e01a88d95dc Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Fri, 28 Mar 2025 15:26:09 +0100 Subject: [PATCH 25/53] More info breadcrumb clickable (#24830) * Make more info breadcrum clickable * css adjustements --- src/components/ha-related-items.ts | 57 ++++++++++---------- src/dialogs/more-info/ha-more-info-dialog.ts | 38 +++++++++---- 2 files changed, 56 insertions(+), 39 deletions(-) diff --git a/src/components/ha-related-items.ts b/src/components/ha-related-items.ts index cce81fba76..63c2cf1882 100644 --- a/src/components/ha-related-items.ts +++ b/src/components/ha-related-items.ts @@ -211,36 +211,12 @@ export class HaRelatedItems extends LitElement { )} ` : nothing} - ${this._related.device - ? html`

- ${this.hass.localize("ui.components.related-items.device")} -

- ${this._related.device.map((relatedDeviceId) => { - const device = this.hass.devices[relatedDeviceId]; - if (!device) { - return nothing; - } - return html` - - - - ${device.name_by_user || device.name} - - - - `; - })} - ` - : nothing} ${this._related.area ? html`

${this.hass.localize("ui.components.related-items.area")}

- ${this._related.area.map((relatedAreaId) => { + + ${this._related.area.map((relatedAreaId) => { const area = this.hass.areas[relatedAreaId]; if (!area) { return nothing; @@ -268,8 +244,33 @@ export class HaRelatedItems extends LitElement { `; - })}` + })} + ` + : nothing} + ${this._related.device + ? html`

+ ${this.hass.localize("ui.components.related-items.device")} +

+ + ${this._related.device.map((relatedDeviceId) => { + const device = this.hass.devices[relatedDeviceId]; + if (!device) { + return nothing; + } + return html` + + + + ${device.name_by_user || device.name} + + + + `; + })} + ` : nothing} ${this._related.entity ? html` diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index fd605e983b..57c415f3b9 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -276,6 +276,11 @@ export class MoreInfoDialog extends LitElement { this._setView("related"); } + private _breadcrumbClick(ev: Event) { + ev.stopPropagation(); + this._setView("related"); + } + private async _loadNumericDeviceClasses() { const deviceClasses = await getSensorNumericDeviceClasses(this.hass); this._sensorNumericDeviceClasses = deviceClasses.numeric_device_classes; @@ -350,17 +355,16 @@ export class MoreInfoDialog extends LitElement { )} > `} - + ${breadcrumb.length > 0 ? html` -

${title}

@@ -656,6 +660,7 @@ export class MoreInfoDialog extends LitElement { .title { display: flex; flex-direction: column; + align-items: flex-start; } .title p { @@ -676,11 +681,22 @@ export class MoreInfoDialog extends LitElement { color: var(--secondary-text-color); font-size: 14px; line-height: 16px; - margin-top: -6px; + --mdc-icon-size: 16px; + padding: 4px; + margin: -4px; + margin-top: -10px; + background: none; + border: none; + cursor: pointer; + outline: none; + display: inline-flex; + border-radius: 6px; + transition: background-color 180ms ease-in-out; } - .title .breadcrumb { - --mdc-icon-size: 16px; + .title .breadcrumb:focus-visible, + .title .breadcrumb:hover { + background-color: rgba(var(--rgb-secondary-text-color), 0.08); } `, ]; From b281d095cd338a2262562e61c71db3bdeb534cac Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Fri, 28 Mar 2025 16:01:09 +0100 Subject: [PATCH 26/53] Remove add-on word in satellite wizard translations for state (#24832) --- src/translations/en.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/translations/en.json b/src/translations/en.json index 937aae1df3..9657a7fcb2 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3438,14 +3438,14 @@ "not_supported_secondary": "Your system is not supported to automatically install a local TTS and STT provider. Learn how to set up local TTS and STT providers in the documentation.", "local_pipeline": "Local Assistant", "state": { - "installing_piper": "Installing Piper add-on", - "starting_piper": "Starting Piper add-on", + "installing_piper": "Installing Piper", + "starting_piper": "Starting Piper", "setup_piper": "Setting up Piper", - "installing_faster-whisper": "Installing Whisper add-on", - "starting_faster-whisper": "Starting Whisper add-on", + "installing_faster-whisper": "Installing Whisper", + "starting_faster-whisper": "Starting Whisper", "setup_faster-whisper": "Setting up Whisper", - "installing_speech-to-phrase": "Installing Speech-to-Phrase add-on", - "starting_speech-to-phrase": "Starting Speech-to-Phrase add-on", + "installing_speech-to-phrase": "Installing Speech-to-Phrase", + "starting_speech-to-phrase": "Starting Speech-to-Phrase", "setup_speech-to-phrase": "Setting up Speech-to-Phrase", "creating_pipeline": "Creating assistant" }, From 9e32c24f3cbd660dc18c3eb0591338d124e8674a Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 28 Mar 2025 17:04:48 +0100 Subject: [PATCH 27/53] Update lang support text in voice wizard (#24834) --- src/translations/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/translations/en.json b/src/translations/en.json index 9657a7fcb2..a9e6b1707a 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3396,8 +3396,8 @@ "high": "High" }, "commands": { - "header": "Supported commands", - "low": "Needs work", + "header": "Language support", + "low": "Needs more work", "ready": "Ready to be used", "high": "Fully supported" }, From a4f07423eceae6e4bb3de522b6a4dcc44f13c698 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 28 Mar 2025 18:29:47 +0100 Subject: [PATCH 28/53] Name local pipeline based on local or full choice (#24835) --- .../voice-assistant-setup/voice-assistant-setup-step-local.ts | 4 ++-- src/translations/en.json | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-local.ts b/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-local.ts index 8aaf6c0a08..034d17b39c 100644 --- a/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-local.ts +++ b/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-local.ts @@ -465,7 +465,7 @@ export class HaVoiceAssistantSetupStepLocal extends LitElement { } let pipelineName = this.hass.localize( - "ui.panel.config.voice_assistants.satellite_wizard.local.local_pipeline" + `ui.panel.config.voice_assistants.satellite_wizard.local.${this.localOption}_pipeline` ); let i = 1; while ( @@ -474,7 +474,7 @@ export class HaVoiceAssistantSetupStepLocal extends LitElement { (pipeline) => pipeline.name === pipelineName ) ) { - pipelineName = `${this.hass.localize("ui.panel.config.voice_assistants.satellite_wizard.local.local_pipeline")} ${i}`; + pipelineName = `${this.hass.localize(`ui.panel.config.voice_assistants.satellite_wizard.local.${this.localOption}_pipeline`)} ${i}`; i++; } diff --git a/src/translations/en.json b/src/translations/en.json index a9e6b1707a..1ad07de064 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3436,7 +3436,8 @@ "failed_secondary": "We were unable to install the add-ons for speech-to-text and text-to-speech automatically for you. Read the documentation to learn how to install them.", "not_supported_title": "Installation of add-ons is not supported on your system", "not_supported_secondary": "Your system is not supported to automatically install a local TTS and STT provider. Learn how to set up local TTS and STT providers in the documentation.", - "local_pipeline": "Local Assistant", + "full_local_pipeline": "Full local Assistant", + "focused_local_pipeline": "Focused local Assistant", "state": { "installing_piper": "Installing Piper", "starting_piper": "Starting Piper", From 34dce5b2792b82014d7ebc298db8b710f92414e7 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Fri, 28 Mar 2025 18:03:56 +0100 Subject: [PATCH 29/53] Only use button for breadcrumb for admin users (#24836) --- src/dialogs/more-info/ha-more-info-dialog.ts | 33 +++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index 57c415f3b9..05fd468616 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -357,15 +357,21 @@ export class MoreInfoDialog extends LitElement { `} ${breadcrumb.length > 0 - ? html` - - ` + ? !__DEMO__ && isAdmin + ? html` + + ` + : html` + + ` : nothing}

${title}

@@ -687,15 +693,18 @@ export class MoreInfoDialog extends LitElement { margin-top: -10px; background: none; border: none; - cursor: pointer; outline: none; display: inline-flex; border-radius: 6px; transition: background-color 180ms ease-in-out; } - .title .breadcrumb:focus-visible, - .title .breadcrumb:hover { + .title button.breadcrumb { + cursor: pointer; + } + + .title button.breadcrumb:focus-visible, + .title button.breadcrumb:hover { background-color: rgba(var(--rgb-secondary-text-color), 0.08); } `, From f5496c21e8e59f11da607da0e4af0b7e827f9e9d Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Mon, 31 Mar 2025 03:13:03 -0700 Subject: [PATCH 30/53] Bar charts start from 0 (#24854) --- src/components/chart/statistics-chart.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/chart/statistics-chart.ts b/src/components/chart/statistics-chart.ts index 15353a1034..0bec8532ec 100644 --- a/src/components/chart/statistics-chart.ts +++ b/src/components/chart/statistics-chart.ts @@ -296,7 +296,11 @@ export class StatisticsChart extends LitElement { align: "left", }, position: computeRTL(this.hass) ? "right" : "left", - scale: true, + scale: + this.chartType !== "bar" || + this.logarithmicScale || + minYAxis !== undefined || + maxYAxis !== undefined, min: this._clampYAxis(minYAxis), max: this._clampYAxis(maxYAxis), splitLine: { From 706f43e99e8336d05e39394ea4547d94a3311d78 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 31 Mar 2025 14:19:00 +0200 Subject: [PATCH 31/53] Add interactions for weather card editor (#24864) --- .../hui-weather-forecast-card-editor.ts | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts index 6904259835..ccc286cca5 100644 --- a/src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts @@ -1,3 +1,4 @@ +import { mdiGestureTap } from "@mdi/js"; import { html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; @@ -5,12 +6,13 @@ import { assert, assign, boolean, + number, object, optional, string, - number, } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; +import { supportsFeature } from "../../../../common/entity/supports-feature"; import type { LocalizeFunc } from "../../../../common/translations/localize"; import "../../../../components/ha-form/ha-form"; import type { SchemaUnion } from "../../../../components/ha-form/types"; @@ -22,7 +24,6 @@ import type { WeatherForecastCardConfig } from "../../cards/types"; import type { LovelaceCardEditor } from "../../types"; import { actionConfigStruct } from "../structs/action-struct"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; -import { supportsFeature } from "../../../../common/entity/supports-feature"; const cardConfigStruct = assign( baseLovelaceCardConfig, @@ -239,6 +240,37 @@ export class HuiWeatherForecastCardEditor selector: { number: { min: 1, max: 12 } }, default: 5, }, + { + name: "interactions", + type: "expandable", + flatten: true, + iconPath: mdiGestureTap, + schema: [ + { + name: "tap_action", + selector: { + ui_action: { + default_action: "more-info", + }, + }, + }, + { + name: "", + type: "optional_actions", + flatten: true, + schema: (["hold_action", "double_tap_action"] as const).map( + (action) => ({ + name: action, + selector: { + ui_action: { + default_action: "none" as const, + }, + }, + }) + ), + }, + ], + }, ] as const) : []), ] as const From c42a899b52aaaa3a2685f8d5aec877fe05b9770c Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 31 Mar 2025 14:18:39 +0200 Subject: [PATCH 32/53] Force clock card to display time LTR (#24865) --- src/panels/lovelace/cards/hui-clock-card.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/panels/lovelace/cards/hui-clock-card.ts b/src/panels/lovelace/cards/hui-clock-card.ts index 346ddb9adb..729cfb2c14 100644 --- a/src/panels/lovelace/cards/hui-clock-card.ts +++ b/src/panels/lovelace/cards/hui-clock-card.ts @@ -234,6 +234,7 @@ export class HuiClockCard extends LitElement implements LovelaceCard { font-size: 2rem; font-weight: 500; line-height: 0.8; + direction: ltr; } .time-title + .time-parts { From a2f70f682f9b7be605cec17cbbabedb91c62f296 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 31 Mar 2025 14:18:20 +0200 Subject: [PATCH 33/53] Take lang into account when search existing pipeline (#24866) * Take lang into account when search existing pipeline * Simplify logic --- .../voice-assistant-setup-step-local.ts | 32 +++++------------ .../voice-assistant-setup-step-pipeline.ts | 36 ++++++------------- 2 files changed, 18 insertions(+), 50 deletions(-) diff --git a/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-local.ts b/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-local.ts index 034d17b39c..35c0c4a8bb 100644 --- a/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-local.ts +++ b/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-local.ts @@ -359,40 +359,24 @@ export class HaVoiceAssistantSetupStepLocal extends LitElement { } const pipelines = await listAssistPipelines(this.hass); - const preferredPipeline = pipelines.pipelines.find( - (pipeline) => pipeline.id === pipelines.preferred_pipeline - ); + + if (pipelines.preferred_pipeline) { + pipelines.pipelines.sort((a) => + a.id === pipelines.preferred_pipeline ? -1 : 0 + ); + } const ttsEntityIds = this._localTts.map((ent) => ent.entity_id); const sttEntityIds = this._localStt.map((ent) => ent.entity_id); - if (preferredPipeline) { - if ( - preferredPipeline.conversation_engine === - "conversation.home_assistant" && - preferredPipeline.tts_engine && - ttsEntityIds.includes(preferredPipeline.tts_engine) && - preferredPipeline.stt_engine && - sttEntityIds.includes(preferredPipeline.stt_engine) - ) { - await this.hass.callService( - "select", - "select_option", - { option: "preferred" }, - { entity_id: this.assistConfiguration?.pipeline_entity_id } - ); - this._nextStep(); - return; - } - } - let localPipeline = pipelines.pipelines.find( (pipeline) => pipeline.conversation_engine === "conversation.home_assistant" && pipeline.tts_engine && ttsEntityIds.includes(pipeline.tts_engine) && pipeline.stt_engine && - sttEntityIds.includes(pipeline.stt_engine) + sttEntityIds.includes(pipeline.stt_engine) && + pipeline.language.split("-")[0] === this.language.split("-")[0] ); if (!localPipeline) { diff --git a/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-pipeline.ts b/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-pipeline.ts index f3bda9c260..415ab4d073 100644 --- a/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-pipeline.ts +++ b/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-pipeline.ts @@ -246,7 +246,7 @@ export class HaVoiceAssistantSetupStepPipeline extends LitElement { private async _fetchData() { const cloud = - (await this._hasCloud()) && (await this._createCloudPipeline()); + (await this._hasCloud()) && (await this._createCloudPipeline(false)); if (!cloud) { this._cloudChecked = true; this._languageScores = (await getLanguageScores(this.hass)).languages; @@ -264,7 +264,7 @@ export class HaVoiceAssistantSetupStepPipeline extends LitElement { return true; } - private async _createCloudPipeline(): Promise { + private async _createCloudPipeline(useLanguage: boolean): Promise { let cloudTtsEntityId; let cloudSttEntityId; for (const entity of Object.values(this.hass.entities)) { @@ -284,36 +284,20 @@ export class HaVoiceAssistantSetupStepPipeline extends LitElement { } try { const pipelines = await listAssistPipelines(this.hass); - const preferredPipeline = pipelines.pipelines.find( - (pipeline) => pipeline.id === pipelines.preferred_pipeline - ); - if (preferredPipeline) { - if ( - preferredPipeline.conversation_engine === - "conversation.home_assistant" && - preferredPipeline.tts_engine === cloudTtsEntityId && - preferredPipeline.stt_engine === cloudSttEntityId - ) { - await this.hass.callService( - "select", - "select_option", - { option: "preferred" }, - { entity_id: this.assistConfiguration?.pipeline_entity_id } - ); - fireEvent(this, "next-step", { - step: STEP.SUCCESS, - noPrevious: true, - }); - return true; - } + if (pipelines.preferred_pipeline) { + pipelines.pipelines.sort((a) => + a.id === pipelines.preferred_pipeline ? -1 : 0 + ); } let cloudPipeline = pipelines.pipelines.find( (pipeline) => pipeline.conversation_engine === "conversation.home_assistant" && pipeline.tts_engine === cloudTtsEntityId && - pipeline.stt_engine === cloudSttEntityId + pipeline.stt_engine === cloudSttEntityId && + (!useLanguage || + pipeline.language.split("-")[0] === this.language!.split("-")[0]) ); if (!cloudPipeline) { @@ -405,7 +389,7 @@ export class HaVoiceAssistantSetupStepPipeline extends LitElement { private async _setupCloud() { if (await this._hasCloud()) { - this._createCloudPipeline(); + this._createCloudPipeline(true); return; } fireEvent(this, "next-step", { step: STEP.CLOUD }); From 40c200a172af104f6ee370f396eda367e122244b Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 31 Mar 2025 15:06:15 +0200 Subject: [PATCH 34/53] fix spinner in tts try dialog (#24867) --- src/components/buttons/ha-progress-button.ts | 30 +++++++++------- src/dialogs/tts-try/dialog-tts-try.ts | 36 +++++++------------- 2 files changed, 29 insertions(+), 37 deletions(-) diff --git a/src/components/buttons/ha-progress-button.ts b/src/components/buttons/ha-progress-button.ts index 3208f37e88..a0c535316b 100644 --- a/src/components/buttons/ha-progress-button.ts +++ b/src/components/buttons/ha-progress-button.ts @@ -1,13 +1,15 @@ -import "@material/mwc-button"; import { mdiAlertOctagram, mdiCheckBold } from "@mdi/js"; import type { TemplateResult } from "lit"; import { css, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; +import "../ha-button"; import "../ha-spinner"; import "../ha-svg-icon"; @customElement("ha-progress-button") export class HaProgressButton extends LitElement { + @property() public label?: string; + @property({ type: Boolean }) public disabled = false; @property({ type: Boolean }) public progress = false; @@ -21,14 +23,16 @@ export class HaProgressButton extends LitElement { public render(): TemplateResult { const overlay = this._result || this.progress; return html` - + - + ${!overlay ? nothing : html` @@ -68,12 +72,12 @@ export class HaProgressButton extends LitElement { pointer-events: none; } - mwc-button { + ha-button { transition: all 1s; pointer-events: initial; } - mwc-button.success { + ha-button.success { --mdc-theme-primary: white; background-color: var(--success-color); transition: none; @@ -81,13 +85,13 @@ export class HaProgressButton extends LitElement { pointer-events: none; } - mwc-button[unelevated].success, - mwc-button[raised].success { + ha-button[unelevated].success, + ha-button[raised].success { --mdc-theme-primary: var(--success-color); --mdc-theme-on-primary: white; } - mwc-button.error { + ha-button.error { --mdc-theme-primary: white; background-color: var(--error-color); transition: none; @@ -95,8 +99,8 @@ export class HaProgressButton extends LitElement { pointer-events: none; } - mwc-button[unelevated].error, - mwc-button[raised].error { + ha-button[unelevated].error, + ha-button[raised].error { --mdc-theme-primary: var(--error-color); --mdc-theme-on-primary: white; } @@ -113,8 +117,8 @@ export class HaProgressButton extends LitElement { color: white; } - mwc-button.success slot, - mwc-button.error slot { + ha-button.success slot, + ha-button.error slot { visibility: hidden; } :host([destructive]) { diff --git a/src/dialogs/tts-try/dialog-tts-try.ts b/src/dialogs/tts-try/dialog-tts-try.ts index b89077a636..6fc474b616 100644 --- a/src/dialogs/tts-try/dialog-tts-try.ts +++ b/src/dialogs/tts-try/dialog-tts-try.ts @@ -3,7 +3,6 @@ import { css, html, LitElement, nothing } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { storage } from "../../common/decorators/storage"; import { fireEvent } from "../../common/dom/fire_event"; -import "../../components/ha-button"; import { createCloseHeading } from "../../components/ha-dialog"; import "../../components/ha-textarea"; import type { HaTextArea } from "../../components/ha-textarea"; @@ -11,7 +10,7 @@ import { convertTextToSpeech } from "../../data/tts"; import type { HomeAssistant } from "../../types"; import { showAlertDialog } from "../generic/show-dialog-box"; import type { TTSTryDialogParams } from "./show-dialog-tts-try"; -import "../../components/ha-spinner"; +import "../../components/buttons/ha-progress-button"; @customElement("dialog-tts-try") export class TTSTryDialog extends LitElement { @@ -81,28 +80,17 @@ export class TTSTryDialog extends LitElement { ?dialogInitialFocus=${!this._defaultMessage} > - ${this._loadingExample - ? html` - - ` - : html` - - - - `} + + + + `; } From 6017d82c21d0b7613eb403c7fef94407f402c759 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 31 Mar 2025 14:58:26 +0200 Subject: [PATCH 35/53] Handle date range shift during daylight saving time days (#24868) --- src/common/datetime/calc_date.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/common/datetime/calc_date.ts b/src/common/datetime/calc_date.ts index f298819d11..bb94dc2bbc 100644 --- a/src/common/datetime/calc_date.ts +++ b/src/common/datetime/calc_date.ts @@ -6,6 +6,10 @@ import { differenceInMilliseconds, differenceInMonths, endOfMonth, + startOfDay, + endOfDay, + differenceInDays, + addDays, } from "date-fns"; import { toZonedTime, fromZonedTime } from "date-fns-tz"; import type { HassConfig } from "home-assistant-js-websocket"; @@ -100,6 +104,32 @@ export const shiftDateRange = ( locale, config ); + } else if ( + calcDateProperty( + startDate, + (date) => startOfDay(date).getMilliseconds() === date.getMilliseconds(), + locale, + config + ) && + calcDateProperty( + endDate, + (date) => endOfDay(date).getMilliseconds() === date.getMilliseconds(), + locale, + config + ) + ) { + const difference = + ((calcDateDifferenceProperty( + endDate, + startDate, + differenceInDays, + locale, + config + ) as number) + + 1) * + (forward ? 1 : -1); + start = calcDate(startDate, addDays, locale, config, difference); + end = calcDate(endDate, addDays, locale, config, difference); } else { const difference = ((calcDateDifferenceProperty( From 7e06bbc467832d6b4c67211d083bd4a90b58327a Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 31 Mar 2025 16:01:15 +0200 Subject: [PATCH 36/53] Fix add zwave device my link (#24871) --- .../zwave_js/zwave_js-add-node.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-add-node.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-add-node.ts index 20e7c5a2f5..6c7a36486b 100644 --- a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-add-node.ts +++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-add-node.ts @@ -11,12 +11,19 @@ export class DialogZWaveJSAddNode extends HTMLElement { public configEntryId!: string; connectedCallback() { + this._openDialog(); + } + + private async _openDialog() { + await navigate( + `/config/devices/dashboard?config_entry=${this.configEntryId}`, + { + replace: true, + } + ); showZWaveJSAddNodeDialog(this, { entry_id: this.configEntryId, }); - navigate(`/config/devices/dashboard?config_entry=${this.configEntryId}`, { - replace: true, - }); } } From 2ba8f9f99db0d8ee47a0e4604a4fc2796c865d8f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 31 Mar 2025 20:43:31 +0200 Subject: [PATCH 37/53] Bumped version to 20250331.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 74d56c8454..1198ae957a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20250328.0" +version = "20250331.0" license = "Apache-2.0" license-files = ["LICENSE*"] description = "The Home Assistant frontend" From 69f0a4a728681f895853c3d8cd720bace9ead64e Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Tue, 1 Apr 2025 01:07:25 -0700 Subject: [PATCH 38/53] Fix condition rendering in trace choose node (#24878) --- src/components/trace/hat-script-graph.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/trace/hat-script-graph.ts b/src/components/trace/hat-script-graph.ts index dd24c88cfd..f3ac1ab98e 100644 --- a/src/components/trace/hat-script-graph.ts +++ b/src/components/trace/hat-script-graph.ts @@ -186,7 +186,10 @@ export class HatScriptGraph extends LitElement { ? ensureArray(config.choose)?.map((branch, i) => { const branchPath = `${path}/choose/${i}`; const trackThis = tracePath.includes(i); - this.renderedNodes[branchPath] = { config, path: branchPath }; + this.renderedNodes[branchPath] = { + config: branch, + path: branchPath, + }; if (trackThis) { this.trackedNodes[branchPath] = this.renderedNodes[branchPath]; } @@ -196,7 +199,7 @@ export class HatScriptGraph extends LitElement { .iconPath=${!trace || trackThis ? mdiCheckboxMarkedOutline : mdiCheckboxBlankOutline} - @focus=${this._selectNode(config, branchPath)} + @focus=${this._selectNode(branch, branchPath)} ?track=${trackThis} ?active=${this.selected === branchPath} .notEnabled=${disabled || config.enabled === false} From c464d344db803fde3d5428b7a7ba66d4feb8befe Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Tue, 1 Apr 2025 12:11:13 +0200 Subject: [PATCH 39/53] Add ellipsis for more info breadcrumb (#24882) --- src/dialogs/more-info/ha-more-info-dialog.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index 05fd468616..c8a1c34437 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -694,9 +694,14 @@ export class MoreInfoDialog extends LitElement { background: none; border: none; outline: none; - display: inline-flex; + display: inline; border-radius: 6px; transition: background-color 180ms ease-in-out; + min-width: 0; + max-width: 100%; + text-overflow: ellipsis; + overflow: hidden; + text-align: left; } .title button.breadcrumb { From 6b3f807129beafd19e26b8d63382d834b05deb90 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 1 Apr 2025 12:18:04 +0200 Subject: [PATCH 40/53] Developer tools action fixes (#24876) --- .../action/developer-tools-action.ts | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/panels/developer-tools/action/developer-tools-action.ts b/src/panels/developer-tools/action/developer-tools-action.ts index de909e652f..83cf76526f 100644 --- a/src/panels/developer-tools/action/developer-tools-action.ts +++ b/src/panels/developer-tools/action/developer-tools-action.ts @@ -511,7 +511,20 @@ class HaPanelDevAction extends LitElement { return; } this._yamlValid = true; - this._serviceDataChanged(ev); + + if (typeof ev.detail.value !== "object") { + return; + } + + if (this._serviceData?.action !== ev.detail.value.action) { + this._error = undefined; + } + + this._serviceData = migrateAutomationAction( + ev.detail.value + ) as ServiceAction; + + this._checkUiSupported(); } private _checkUiSupported() { @@ -547,18 +560,18 @@ class HaPanelDevAction extends LitElement { if (this._serviceData?.action !== ev.detail.value.action) { this._error = undefined; } - this._serviceData = migrateAutomationAction( - ev.detail.value - ) as ServiceAction; + this._serviceData = ev.detail.value; this._checkUiSupported(); } private _serviceChanged(ev) { ev.stopPropagation(); - this._serviceData = { action: ev.detail.value || "", data: {} }; + if (ev.detail.value) { + this._serviceData = { action: ev.detail.value, data: {} }; + this._yamlEditor?.setValue(this._serviceData); + } this._response = undefined; this._error = undefined; - this._yamlEditor?.setValue(this._serviceData); this._checkUiSupported(); } From 0bcaa104e7628ee7cbfe8a78d3ef505d8cea7997 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 1 Apr 2025 17:30:31 +0200 Subject: [PATCH 41/53] Bumped version to 20250401.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1198ae957a..90a98459e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20250331.0" +version = "20250401.0" license = "Apache-2.0" license-files = ["LICENSE*"] description = "The Home Assistant frontend" From 825e707a8009cb58d64c65583afe0fdcb8c559db Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 3 Apr 2025 12:10:11 +0200 Subject: [PATCH 42/53] Refresh dashboard strategy when registries changed (#24902) * Refresh dashboard strategy when registries changed * Display toast before refreshing dashboard * Apply suggestions --- src/panels/lovelace/ha-panel-lovelace.ts | 53 ++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/panels/lovelace/ha-panel-lovelace.ts b/src/panels/lovelace/ha-panel-lovelace.ts index bc7471d9c3..8c17d07796 100644 --- a/src/panels/lovelace/ha-panel-lovelace.ts +++ b/src/panels/lovelace/ha-panel-lovelace.ts @@ -178,6 +178,59 @@ export class LovelacePanel extends LitElement { } } + protected updated(changedProperties: PropertyValues): void { + super.updated(changedProperties); + if (!changedProperties.has("hass")) { + return; + } + const oldHass = changedProperties.get("hass") as HomeAssistant | undefined; + if ( + oldHass && + this.hass && + (oldHass.entities !== this.hass.entities || + oldHass.devices !== this.hass.devices || + oldHass.areas !== this.hass.areas || + oldHass.floors !== this.hass.floors) + ) { + this._registriesChanged(); + } + } + + private _registriesChanged = () => { + if (this.lovelace && isStrategyDashboard(this.lovelace.rawConfig)) { + showToast(this, { + message: this.hass!.localize("ui.panel.lovelace.changed_toast.message"), + action: { + action: () => this._refreshConfig(), + text: this.hass!.localize("ui.common.refresh"), + }, + duration: -1, + id: "entity-registry-changed", + dismissable: false, + }); + } + }; + + private async _refreshConfig() { + if (!this.hass || !this.lovelace) { + return; + } + + const rawConf = this.lovelace.rawConfig; + + if (!isStrategyDashboard(rawConf)) { + return; + } + + try { + const conf = await generateLovelaceDashboardStrategy(rawConf, this.hass!); + this._setLovelaceConfig(conf, rawConf, "generated"); + } catch (err: any) { + // eslint-disable-next-line no-console + console.error(err); + } + } + private _handleConnectionStatus = (ev) => { // reload lovelace on reconnect so we are sure we have the latest config if (ev.detail === "connected") { From 88f1dc9c16d23ccdbb29c0cb40358c7ce0f443fb Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 3 Apr 2025 14:02:27 +0200 Subject: [PATCH 43/53] Add missing translations for areas strategy (#24905) --- .../lovelace/strategies/areas/area-view-strategy.ts | 13 +++++++------ .../editor/hui-areas-dashboard-strategy-editor.ts | 9 ++++++--- .../areas/helpers/areas-strategy-helper.ts | 9 --------- src/translations/en.json | 12 +++++++++++- 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/panels/lovelace/strategies/areas/area-view-strategy.ts b/src/panels/lovelace/strategies/areas/area-view-strategy.ts index b9c9025091..853568dfbb 100644 --- a/src/panels/lovelace/strategies/areas/area-view-strategy.ts +++ b/src/panels/lovelace/strategies/areas/area-view-strategy.ts @@ -8,7 +8,6 @@ import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view"; import type { HomeAssistant } from "../../../../types"; import { AREA_STRATEGY_GROUP_ICONS, - AREA_STRATEGY_GROUP_LABELS, computeAreaTileCardConfig, getAreaGroupedEntities, } from "./helpers/areas-strategy-helper"; @@ -85,7 +84,7 @@ export class AreaViewStrategy extends ReactiveElement { type: "grid", cards: [ computeHeadingCard( - AREA_STRATEGY_GROUP_LABELS.lights, + hass.localize("ui.panel.lovelace.strategy.areas.groups.lights"), AREA_STRATEGY_GROUP_ICONS.lights ), ...lights.map(computeTileCard), @@ -98,7 +97,7 @@ export class AreaViewStrategy extends ReactiveElement { type: "grid", cards: [ computeHeadingCard( - AREA_STRATEGY_GROUP_LABELS.climate, + hass.localize("ui.panel.lovelace.strategy.areas.groups.climate"), AREA_STRATEGY_GROUP_ICONS.climate ), ...climate.map(computeTileCard), @@ -111,7 +110,9 @@ export class AreaViewStrategy extends ReactiveElement { type: "grid", cards: [ computeHeadingCard( - AREA_STRATEGY_GROUP_LABELS.media_players, + hass.localize( + "ui.panel.lovelace.strategy.areas.groups.media_players" + ), AREA_STRATEGY_GROUP_ICONS.media_players ), ...media_players.map(computeTileCard), @@ -124,7 +125,7 @@ export class AreaViewStrategy extends ReactiveElement { type: "grid", cards: [ computeHeadingCard( - AREA_STRATEGY_GROUP_LABELS.security, + hass.localize("ui.panel.lovelace.strategy.areas.groups.security"), AREA_STRATEGY_GROUP_ICONS.security ), ...security.map(computeTileCard), @@ -137,7 +138,7 @@ export class AreaViewStrategy extends ReactiveElement { type: "grid", cards: [ computeHeadingCard( - AREA_STRATEGY_GROUP_LABELS.others, + hass.localize("ui.panel.lovelace.strategy.areas.groups.others"), AREA_STRATEGY_GROUP_ICONS.others ), ...others.map(computeTileCard), diff --git a/src/panels/lovelace/strategies/areas/editor/hui-areas-dashboard-strategy-editor.ts b/src/panels/lovelace/strategies/areas/editor/hui-areas-dashboard-strategy-editor.ts index a3add6bfe8..10975e4d35 100644 --- a/src/panels/lovelace/strategies/areas/editor/hui-areas-dashboard-strategy-editor.ts +++ b/src/panels/lovelace/strategies/areas/editor/hui-areas-dashboard-strategy-editor.ts @@ -12,7 +12,6 @@ import type { AreaStrategyGroup } from "../helpers/areas-strategy-helper"; import { AREA_STRATEGY_GROUP_ICONS, AREA_STRATEGY_GROUPS, - AREA_STRATEGY_GROUP_LABELS, getAreaGroupedEntities, } from "../helpers/areas-strategy-helper"; import type { LovelaceStrategyEditor } from "../../types"; @@ -57,7 +56,9 @@ export class HuiAreasDashboardStrategyEditor return html` @@ -79,7 +80,9 @@ export class HuiAreasDashboardStrategyEditor ` : html`

- No entities in this section, it will not be displayed. + ${this.hass!.localize( + "ui.panel.lovelace.editor.strategy.areas.no_entities" + )}

`}
diff --git a/src/panels/lovelace/strategies/areas/helpers/areas-strategy-helper.ts b/src/panels/lovelace/strategies/areas/helpers/areas-strategy-helper.ts index 64934c6216..2c284fcca8 100644 --- a/src/panels/lovelace/strategies/areas/helpers/areas-strategy-helper.ts +++ b/src/panels/lovelace/strategies/areas/helpers/areas-strategy-helper.ts @@ -32,15 +32,6 @@ export const AREA_STRATEGY_GROUP_ICONS = { others: "mdi:shape", }; -// Todo be replace by translation when validated -export const AREA_STRATEGY_GROUP_LABELS = { - lights: "Lights", - climate: "Climate", - media_players: "Entertainment", - security: "Security", - others: "Others", -}; - export type AreaStrategyGroup = (typeof AREA_STRATEGY_GROUPS)[number]; type AreaEntitiesByGroup = Record; diff --git a/src/translations/en.json b/src/translations/en.json index 1ad07de064..13a02188d5 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -6328,6 +6328,15 @@ "strategy": { "original-states": { "helpers": "[%key:ui::panel::config::helpers::caption%]" + }, + "areas": { + "groups": { + "lights": "Lights", + "climate": "Climate", + "media_players": "Entertainment", + "security": "Security", + "others": "Others" + } } }, "cards": { @@ -7531,7 +7540,8 @@ "url": "URL" }, "areas": { - "areas_display": "Areas to display" + "areas_display": "Areas to display", + "no_entities": "No entities in this group, the section will not be displayed" } }, "view": { From 27e13017c3096596f70d4443b42f15b4e3089c6b Mon Sep 17 00:00:00 2001 From: Simon Lamon <32477463+silamon@users.noreply.github.com> Date: Fri, 4 Apr 2025 15:09:36 +0200 Subject: [PATCH 44/53] Show the correct area icon in entity breadcrumb (#24913) --- src/components/ha-related-items.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/components/ha-related-items.ts b/src/components/ha-related-items.ts index 63c2cf1882..7cd3bfd2d0 100644 --- a/src/components/ha-related-items.ts +++ b/src/components/ha-related-items.ts @@ -3,7 +3,7 @@ import { mdiAlertCircleOutline, mdiDevices, mdiPaletteSwatch, - mdiSofa, + mdiTextureBox, } from "@mdi/js"; import type { CSSResultGroup, PropertyValues } from "lit"; import { LitElement, css, html, nothing } from "lit"; @@ -235,10 +235,15 @@ export class HaRelatedItems extends LitElement { })} slot="graphic" >
` - : html``} + : area.icon + ? html`` + : html``} ${area.name} From 1e26f155a7df9f57d8ba6fbba63533e1ab72bfbd Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 4 Apr 2025 15:10:56 +0200 Subject: [PATCH 45/53] Bumped version to 20250404.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 90a98459e5..d4d80f5d0a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20250401.0" +version = "20250404.0" license = "Apache-2.0" license-files = ["LICENSE*"] description = "The Home Assistant frontend" From 63d2718f6775f2072959bec69514d9a75093604b Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Sat, 5 Apr 2025 16:39:34 +0200 Subject: [PATCH 46/53] Import missing ha-tip in quick bar dialog (#24929) --- src/dialogs/quick-bar/ha-quick-bar.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dialogs/quick-bar/ha-quick-bar.ts b/src/dialogs/quick-bar/ha-quick-bar.ts index d64fb13ce8..8520dc0151 100644 --- a/src/dialogs/quick-bar/ha-quick-bar.ts +++ b/src/dialogs/quick-bar/ha-quick-bar.ts @@ -31,6 +31,7 @@ import "../../components/ha-label"; import "../../components/ha-list-item"; import "../../components/ha-spinner"; import "../../components/ha-textfield"; +import "../../components/ha-tip"; import { fetchHassioAddonsInfo } from "../../data/hassio/addon"; import { domainToName } from "../../data/integration"; import { getPanelNameTranslationKey } from "../../data/panel"; From 5237cc72b7de6c98ec6958ce5d501432799ec9f0 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 7 Apr 2025 13:18:05 +0200 Subject: [PATCH 47/53] Restore default hold action for some cards (#24947) --- .../lovelace/cards/hui-picture-glance-card.ts | 1 + .../elements/hui-icon-element-editor.ts | 20 +++++++++++++------ .../elements/hui-image-element-editor.ts | 20 +++++++++++++------ .../hui-state-badge-element-editor.ts | 20 +++++++++++++------ .../elements/hui-state-icon-element-editor.ts | 20 +++++++++++++------ .../hui-state-label-element-editor.ts | 20 +++++++++++++------ .../hui-picture-glance-card-editor.ts | 20 +++++++++++++------ .../lovelace/elements/hui-icon-element.ts | 6 +++++- .../lovelace/elements/hui-image-element.ts | 6 +++++- .../elements/hui-state-badge-element.ts | 6 +++++- .../elements/hui-state-icon-element.ts | 1 + .../elements/hui-state-label-element.ts | 6 +++++- 12 files changed, 106 insertions(+), 40 deletions(-) diff --git a/src/panels/lovelace/cards/hui-picture-glance-card.ts b/src/panels/lovelace/cards/hui-picture-glance-card.ts index fb1f8311cd..ecd421cf41 100644 --- a/src/panels/lovelace/cards/hui-picture-glance-card.ts +++ b/src/panels/lovelace/cards/hui-picture-glance-card.ts @@ -106,6 +106,7 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard { this._config = { tap_action: { action: "more-info" }, + hold_action: { action: "more-info" }, ...config, }; } diff --git a/src/panels/lovelace/editor/config-elements/elements/hui-icon-element-editor.ts b/src/panels/lovelace/editor/config-elements/elements/hui-icon-element-editor.ts index f10da54dbc..350327a747 100644 --- a/src/panels/lovelace/editor/config-elements/elements/hui-icon-element-editor.ts +++ b/src/panels/lovelace/editor/config-elements/elements/hui-icon-element-editor.ts @@ -39,20 +39,28 @@ const SCHEMA = [ }, }, }, + { + name: "hold_action", + selector: { + ui_action: { + default_action: "more-info", + }, + }, + }, { name: "", type: "optional_actions", flatten: true, - schema: (["hold_action", "double_tap_action"] as const).map( - (action) => ({ - name: action, + schema: [ + { + name: "double_tap_action", selector: { ui_action: { - default_action: "none" as const, + default_action: "none", }, }, - }) - ), + }, + ], }, ], }, diff --git a/src/panels/lovelace/editor/config-elements/elements/hui-image-element-editor.ts b/src/panels/lovelace/editor/config-elements/elements/hui-image-element-editor.ts index dd7c86a9b4..092f26456e 100644 --- a/src/panels/lovelace/editor/config-elements/elements/hui-image-element-editor.ts +++ b/src/panels/lovelace/editor/config-elements/elements/hui-image-element-editor.ts @@ -44,20 +44,28 @@ const SCHEMA = [ }, }, }, + { + name: "hold_action", + selector: { + ui_action: { + default_action: "more-info", + }, + }, + }, { name: "", type: "optional_actions", flatten: true, - schema: (["hold_action", "double_tap_action"] as const).map( - (action) => ({ - name: action, + schema: [ + { + name: "double_tap_action", selector: { ui_action: { - default_action: "none" as const, + default_action: "none", }, }, - }) - ), + }, + ], }, ], }, diff --git a/src/panels/lovelace/editor/config-elements/elements/hui-state-badge-element-editor.ts b/src/panels/lovelace/editor/config-elements/elements/hui-state-badge-element-editor.ts index aab46a1025..5e012c7a9e 100644 --- a/src/panels/lovelace/editor/config-elements/elements/hui-state-badge-element-editor.ts +++ b/src/panels/lovelace/editor/config-elements/elements/hui-state-badge-element-editor.ts @@ -37,20 +37,28 @@ const SCHEMA = [ }, }, }, + { + name: "hold_action", + selector: { + ui_action: { + default_action: "more-info", + }, + }, + }, { name: "", type: "optional_actions", flatten: true, - schema: (["hold_action", "double_tap_action"] as const).map( - (action) => ({ - name: action, + schema: [ + { + name: "double_tap_action", selector: { ui_action: { - default_action: "none" as const, + default_action: "none", }, }, - }) - ), + }, + ], }, ], }, diff --git a/src/panels/lovelace/editor/config-elements/elements/hui-state-icon-element-editor.ts b/src/panels/lovelace/editor/config-elements/elements/hui-state-icon-element-editor.ts index 333d220b68..217d8edac6 100644 --- a/src/panels/lovelace/editor/config-elements/elements/hui-state-icon-element-editor.ts +++ b/src/panels/lovelace/editor/config-elements/elements/hui-state-icon-element-editor.ts @@ -49,20 +49,28 @@ const SCHEMA = [ }, }, }, + { + name: "hold_action", + selector: { + ui_action: { + default_action: "more-info", + }, + }, + }, { name: "", type: "optional_actions", flatten: true, - schema: (["hold_action", "double_tap_action"] as const).map( - (action) => ({ - name: action, + schema: [ + { + name: "double_tap_action", selector: { ui_action: { - default_action: "none" as const, + default_action: "none", }, }, - }) - ), + }, + ], }, ], }, diff --git a/src/panels/lovelace/editor/config-elements/elements/hui-state-label-element-editor.ts b/src/panels/lovelace/editor/config-elements/elements/hui-state-label-element-editor.ts index 6589e426e0..561419dd9e 100644 --- a/src/panels/lovelace/editor/config-elements/elements/hui-state-label-element-editor.ts +++ b/src/panels/lovelace/editor/config-elements/elements/hui-state-label-element-editor.ts @@ -49,20 +49,28 @@ const SCHEMA = [ }, }, }, + { + name: "hold_action", + selector: { + ui_action: { + default_action: "more-info", + }, + }, + }, { name: "", type: "optional_actions", flatten: true, - schema: (["hold_action", "double_tap_action"] as const).map( - (action) => ({ - name: action, + schema: [ + { + name: "double_tap_action", selector: { ui_action: { - default_action: "none" as const, + default_action: "none", }, }, - }) - ), + }, + ], }, ], }, diff --git a/src/panels/lovelace/editor/config-elements/hui-picture-glance-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-picture-glance-card-editor.ts index ff52bae510..763380265d 100644 --- a/src/panels/lovelace/editor/config-elements/hui-picture-glance-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-picture-glance-card-editor.ts @@ -71,20 +71,28 @@ const SCHEMA = [ }, }, }, + { + name: "hold_action", + selector: { + ui_action: { + default_action: "more-info", + }, + }, + }, { name: "", type: "optional_actions", flatten: true, - schema: (["hold_action", "double_tap_action"] as const).map( - (action) => ({ - name: action, + schema: [ + { + name: "double_tap_action", selector: { ui_action: { - default_action: "none" as const, + default_action: "none", }, }, - }) - ), + }, + ], }, ], }, diff --git a/src/panels/lovelace/elements/hui-icon-element.ts b/src/panels/lovelace/elements/hui-icon-element.ts index 1d54b0c05a..7d31a9f441 100644 --- a/src/panels/lovelace/elements/hui-icon-element.ts +++ b/src/panels/lovelace/elements/hui-icon-element.ts @@ -31,7 +31,11 @@ export class HuiIconElement extends LitElement implements LovelaceElement { throw Error("Icon required"); } - this._config = { tap_action: { action: "more-info" }, ...config }; + this._config = { + tap_action: { action: "more-info" }, + hold_action: { action: "more-info" }, + ...config, + }; } protected render() { diff --git a/src/panels/lovelace/elements/hui-image-element.ts b/src/panels/lovelace/elements/hui-image-element.ts index ff94f9b68e..e59d5e20bd 100644 --- a/src/panels/lovelace/elements/hui-image-element.ts +++ b/src/panels/lovelace/elements/hui-image-element.ts @@ -29,7 +29,11 @@ export class HuiImageElement extends LitElement implements LovelaceElement { throw Error("Invalid configuration"); } - this._config = { tap_action: { action: "more-info" }, ...config }; + this._config = { + tap_action: { action: "more-info" }, + hold_action: { action: "more-info" }, + ...config, + }; this.classList.toggle( "clickable", diff --git a/src/panels/lovelace/elements/hui-state-badge-element.ts b/src/panels/lovelace/elements/hui-state-badge-element.ts index 1c08801d90..dcb76ae25a 100644 --- a/src/panels/lovelace/elements/hui-state-badge-element.ts +++ b/src/panels/lovelace/elements/hui-state-badge-element.ts @@ -60,7 +60,11 @@ export class HuiStateBadgeElement throw Error("Entity required"); } - this._config = { tap_action: { action: "more-info" }, ...config }; + this._config = { + tap_action: { action: "more-info" }, + hold_action: { action: "more-info" }, + ...config, + }; } protected shouldUpdate(changedProps: PropertyValues): boolean { diff --git a/src/panels/lovelace/elements/hui-state-icon-element.ts b/src/panels/lovelace/elements/hui-state-icon-element.ts index 6e6a08c995..1d6948642f 100644 --- a/src/panels/lovelace/elements/hui-state-icon-element.ts +++ b/src/panels/lovelace/elements/hui-state-icon-element.ts @@ -60,6 +60,7 @@ export class HuiStateIconElement extends LitElement implements LovelaceElement { this._config = { state_color: true, tap_action: { action: "more-info" }, + hold_action: { action: "more-info" }, ...config, }; } diff --git a/src/panels/lovelace/elements/hui-state-label-element.ts b/src/panels/lovelace/elements/hui-state-label-element.ts index cc64ae8ea8..08a1f16b4d 100644 --- a/src/panels/lovelace/elements/hui-state-label-element.ts +++ b/src/panels/lovelace/elements/hui-state-label-element.ts @@ -56,7 +56,11 @@ class HuiStateLabelElement extends LitElement implements LovelaceElement { throw Error("Entity required"); } - this._config = { tap_action: { action: "more-info" }, ...config }; + this._config = { + tap_action: { action: "more-info" }, + hold_action: { action: "more-info" }, + ...config, + }; } protected shouldUpdate(changedProps: PropertyValues): boolean { From b76a723fd98c3475c14a1874d8ec736378bdf114 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 7 Apr 2025 12:41:29 +0200 Subject: [PATCH 48/53] fix voice wizard bugs (#24950) --- src/data/assist_satellite.ts | 3 ++- .../voice-assistant-setup-dialog.ts | 3 +++ .../voice-assistant-setup-step-success.ts | 20 ++++++++++++++----- src/translations/en.json | 6 +++++- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/data/assist_satellite.ts b/src/data/assist_satellite.ts index cee5862cd0..7b13c4a47c 100644 --- a/src/data/assist_satellite.ts +++ b/src/data/assist_satellite.ts @@ -52,7 +52,8 @@ export const assistSatelliteAnnounce = ( args: { message?: string; media_id?: string; - preannounce_media_id?: string | null; + preannounce?: boolean; + preannounce_media_id?: string; } ) => hass.callService("assist_satellite", "announce", args, { entity_id }); diff --git a/src/dialogs/voice-assistant-setup/voice-assistant-setup-dialog.ts b/src/dialogs/voice-assistant-setup/voice-assistant-setup-dialog.ts index 5110be9605..7b292fe9ed 100644 --- a/src/dialogs/voice-assistant-setup/voice-assistant-setup-dialog.ts +++ b/src/dialogs/voice-assistant-setup/voice-assistant-setup-dialog.ts @@ -90,6 +90,9 @@ export class HaVoiceAssistantSetupDialog extends LitElement { this._previousSteps = []; this._nextStep = undefined; this._step = STEP.INIT; + this._language = undefined; + this._languages = []; + this._localOption = undefined; fireEvent(this, "dialog-closed", { dialog: this.localName }); } diff --git a/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-success.ts b/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-success.ts index 1ec7a400d8..9e8a40f8c5 100644 --- a/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-success.ts +++ b/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-success.ts @@ -103,7 +103,9 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement { - Test + ${this.hass.localize( + "ui.panel.config.voice_assistants.satellite_wizard.success.test_wakeword" + )} ` : nothing} @@ -126,7 +128,9 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement { - Edit + ${this.hass.localize( + "ui.panel.config.voice_assistants.satellite_wizard.success.edit_pipeline" + )} ` : nothing} @@ -142,14 +146,20 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement { > - Try + ${this.hass.localize( + "ui.panel.config.voice_assistants.satellite_wizard.success.try_tts" + )} ` : nothing} `; } @@ -248,7 +258,7 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement { } await assistSatelliteAnnounce(this.hass, this.assistEntityId, { message, - preannounce_media_id: null, + preannounce: false, }); } diff --git a/src/translations/en.json b/src/translations/en.json index 13a02188d5..ec1da1c269 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3457,7 +3457,11 @@ }, "success": { "title": "Ready to Assist!", - "secondary": "Make any final customizations here. You can always change these in the Voice Assistants section of the settings page." + "secondary": "Make any final customizations here. You can always change these in the Voice assistants section of the settings page.", + "test_wakeword": "Test", + "edit_pipeline": "Edit", + "try_tts": "Try", + "done": "Done" } } }, From 6fa226d30a8eb961284e0ca0b0a31f8966e1047a Mon Sep 17 00:00:00 2001 From: Wendelin <12148533+wendevlin@users.noreply.github.com> Date: Tue, 8 Apr 2025 12:39:42 +0200 Subject: [PATCH 49/53] Fix submit spinner in config-flow-form (#24969) --- src/dialogs/config-flow/step-flow-form.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/dialogs/config-flow/step-flow-form.ts b/src/dialogs/config-flow/step-flow-form.ts index 645ba4de9c..a36f4a7152 100644 --- a/src/dialogs/config-flow/step-flow-form.ts +++ b/src/dialogs/config-flow/step-flow-form.ts @@ -84,7 +84,7 @@ class StepFlowForm extends LitElement { ${this._loading ? html`
- +
` : html` @@ -263,6 +263,9 @@ class StepFlowForm extends LitElement { } .submit-spinner { + height: 36px; + display: flex; + align-items: center; margin-right: 16px; margin-inline-end: 16px; margin-inline-start: initial; From ec9fbe7d772d4ce5ea0a1f000b41337ba1f7e3fb Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 9 Apr 2025 16:16:47 +0200 Subject: [PATCH 50/53] fix dropdown behind datatable (#24981) --- src/layouts/hass-tabs-subpage-data-table.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/layouts/hass-tabs-subpage-data-table.ts b/src/layouts/hass-tabs-subpage-data-table.ts index cd66b170a6..f68276b96c 100644 --- a/src/layouts/hass-tabs-subpage-data-table.ts +++ b/src/layouts/hass-tabs-subpage-data-table.ts @@ -4,11 +4,11 @@ import { mdiArrowDown, mdiArrowUp, mdiClose, - mdiTableCog, mdiFilterVariant, mdiFilterVariantRemove, mdiFormatListChecks, mdiMenuDown, + mdiTableCog, mdiUnfoldLessHorizontal, mdiUnfoldMoreHorizontal, } from "@mdi/js"; @@ -27,17 +27,17 @@ import type { HaDataTable, SortingDirection, } from "../components/data-table/ha-data-table"; -import "../components/ha-md-button-menu"; +import { showDataTableSettingsDialog } from "../components/data-table/show-dialog-data-table-settings"; import "../components/ha-dialog"; import "../components/ha-dialog-header"; +import "../components/ha-md-button-menu"; import "../components/ha-md-divider"; import "../components/ha-md-menu-item"; import "../components/search-input-outlined"; +import { KeyboardShortcutMixin } from "../mixins/keyboard-shortcut-mixin"; import type { HomeAssistant, Route } from "../types"; import "./hass-tabs-subpage"; import type { PageNavigation } from "./hass-tabs-subpage"; -import { showDataTableSettingsDialog } from "../components/data-table/show-dialog-data-table-settings"; -import { KeyboardShortcutMixin } from "../mixins/keyboard-shortcut-mixin"; @customElement("hass-tabs-subpage-data-table") export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) { @@ -256,7 +256,7 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) { const sortByMenu = Object.values(this.columns).find((col) => col.sortable) ? html` - + col.groupable) ? html` - + - + Date: Thu, 10 Apr 2025 10:04:43 +0200 Subject: [PATCH 51/53] Fix refresh strategy config on HA start-up (#24984) --- src/panels/lovelace/ha-panel-lovelace.ts | 48 +++++++++++++++--------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/src/panels/lovelace/ha-panel-lovelace.ts b/src/panels/lovelace/ha-panel-lovelace.ts index 8c17d07796..508ff4b23c 100644 --- a/src/panels/lovelace/ha-panel-lovelace.ts +++ b/src/panels/lovelace/ha-panel-lovelace.ts @@ -183,32 +183,46 @@ export class LovelacePanel extends LitElement { if (!changedProperties.has("hass")) { return; } + const oldHass = changedProperties.get("hass") as HomeAssistant | undefined; if ( oldHass && this.hass && - (oldHass.entities !== this.hass.entities || + this.lovelace && + isStrategyDashboard(this.lovelace.rawConfig) + ) { + // If the entity registry changed, ask the user if they want to refresh the config + if ( + oldHass.entities !== this.hass.entities || oldHass.devices !== this.hass.devices || oldHass.areas !== this.hass.areas || - oldHass.floors !== this.hass.floors) - ) { - this._registriesChanged(); + oldHass.floors !== this.hass.floors + ) { + if (this.hass.config.state === "RUNNING") { + this._askRefreshConfig(); + } + } + // If ha started, refresh the config + if ( + this.hass.config.state === "RUNNING" && + oldHass.config.state !== "RUNNING" + ) { + this._refreshConfig(); + } } } - private _registriesChanged = () => { - if (this.lovelace && isStrategyDashboard(this.lovelace.rawConfig)) { - showToast(this, { - message: this.hass!.localize("ui.panel.lovelace.changed_toast.message"), - action: { - action: () => this._refreshConfig(), - text: this.hass!.localize("ui.common.refresh"), - }, - duration: -1, - id: "entity-registry-changed", - dismissable: false, - }); - } + private _askRefreshConfig = () => { + showToast(this, { + message: this.hass!.localize("ui.panel.lovelace.changed_toast.message"), + action: { + action: () => this._refreshConfig(), + text: this.hass!.localize("ui.common.refresh"), + }, + duration: -1, + id: "entity-registry-changed", + dismissable: false, + }); }; private async _refreshConfig() { From 7742ccf631ddbe5c85ad0917256052e36f5d0678 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Fri, 11 Apr 2025 14:41:12 +0200 Subject: [PATCH 52/53] Only ask to refresh dashboard if necessary (#24993) --- src/panels/lovelace/ha-panel-lovelace.ts | 55 +++++++++-- src/panels/lovelace/views/hui-view.ts | 119 ++++++++++++++++++++--- 2 files changed, 155 insertions(+), 19 deletions(-) diff --git a/src/panels/lovelace/ha-panel-lovelace.ts b/src/panels/lovelace/ha-panel-lovelace.ts index 508ff4b23c..deb0432d63 100644 --- a/src/panels/lovelace/ha-panel-lovelace.ts +++ b/src/panels/lovelace/ha-panel-lovelace.ts @@ -9,6 +9,8 @@ import { addSearchParam, removeSearchParam, } from "../../common/url/search-params"; +import { debounce } from "../../common/util/debounce"; +import { deepEqual } from "../../common/util/deep-equal"; import { domainToName } from "../../data/integration"; import { subscribeLovelaceUpdates } from "../../data/lovelace"; import type { @@ -51,6 +53,12 @@ interface LovelacePanelConfig { let editorLoaded = false; let resourcesLoaded = false; +declare global { + interface HASSDomEvents { + "strategy-config-changed": undefined; + } +} + @customElement("ha-panel-lovelace") export class LovelacePanel extends LitElement { @property({ attribute: false }) public panel?: PanelInfo; @@ -127,6 +135,7 @@ export class LovelacePanel extends LitElement { .route=${this.route} .narrow=${this.narrow} @config-refresh=${this._forceFetchConfig} + @strategy-config-changed=${this._strategyConfigChanged} > `; } @@ -199,7 +208,7 @@ export class LovelacePanel extends LitElement { oldHass.floors !== this.hass.floors ) { if (this.hass.config.state === "RUNNING") { - this._askRefreshConfig(); + this._debounceRegistriesChanged(); } } // If ha started, refresh the config @@ -207,25 +216,59 @@ export class LovelacePanel extends LitElement { this.hass.config.state === "RUNNING" && oldHass.config.state !== "RUNNING" ) { - this._refreshConfig(); + this._regenerateStrategyConfig(); } } } - private _askRefreshConfig = () => { + private _debounceRegistriesChanged = debounce( + () => this._registriesChanged(), + 200 + ); + + private _registriesChanged = async () => { + if (!this.hass || !this.lovelace) { + return; + } + const rawConfig = this.lovelace.rawConfig; + + if (!isStrategyDashboard(rawConfig)) { + return; + } + + const oldConfig = this.lovelace.config; + const generatedConfig = await generateLovelaceDashboardStrategy( + rawConfig, + this.hass! + ); + + const newConfig = checkLovelaceConfig(generatedConfig) as LovelaceConfig; + + // Ask to regenerate if the config changed + if (!deepEqual(newConfig, oldConfig)) { + this._askRegenerateStrategyConfig(); + } + }; + + private _strategyConfigChanged = (ev: CustomEvent) => { + ev.stopPropagation(); + this._askRegenerateStrategyConfig(); + }; + + private _askRegenerateStrategyConfig = () => { showToast(this, { message: this.hass!.localize("ui.panel.lovelace.changed_toast.message"), action: { - action: () => this._refreshConfig(), + action: () => this._regenerateStrategyConfig(), text: this.hass!.localize("ui.common.refresh"), }, duration: -1, - id: "entity-registry-changed", + id: "regenerate-strategy-config", dismissable: false, }); }; - private async _refreshConfig() { + private async _regenerateStrategyConfig() { if (!this.hass || !this.lovelace) { return; } diff --git a/src/panels/lovelace/views/hui-view.ts b/src/panels/lovelace/views/hui-view.ts index 12d10e3214..6c93cb2124 100644 --- a/src/panels/lovelace/views/hui-view.ts +++ b/src/panels/lovelace/views/hui-view.ts @@ -3,7 +3,9 @@ import type { PropertyValues } from "lit"; import { ReactiveElement } from "lit"; import { customElement, property, state } from "lit/decorators"; import { storage } from "../../../common/decorators/storage"; -import type { HASSDomEvent } from "../../../common/dom/fire_event"; +import { fireEvent, type HASSDomEvent } from "../../../common/dom/fire_event"; +import { debounce } from "../../../common/util/debounce"; +import { deepEqual } from "../../../common/util/deep-equal"; import "../../../components/entity/ha-state-label-badge"; import "../../../components/ha-svg-icon"; import type { LovelaceViewElement } from "../../../data/lovelace"; @@ -11,7 +13,10 @@ import type { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge"; import { ensureBadgeConfig } from "../../../data/lovelace/config/badge"; import type { LovelaceCardConfig } from "../../../data/lovelace/config/card"; import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section"; -import type { LovelaceViewConfig } from "../../../data/lovelace/config/view"; +import type { + LovelaceViewConfig, + LovelaceViewRawConfig, +} from "../../../data/lovelace/config/view"; import { isStrategyView } from "../../../data/lovelace/config/view"; import type { HomeAssistant } from "../../../types"; import "../badges/hui-badge"; @@ -85,6 +90,10 @@ export class HUIView extends ReactiveElement { private _layoutElement?: LovelaceViewElement; + private _layoutElementConfig?: LovelaceViewConfig; + + private _rendered = false; + @storage({ key: "dashboardCardClipboard", state: false, @@ -145,6 +154,18 @@ export class HUIView extends ReactiveElement { return this; } + connectedCallback(): void { + super.connectedCallback(); + this.updateComplete.then(() => { + this._rendered = true; + }); + } + + disconnectedCallback(): void { + super.disconnectedCallback(); + this._rendered = false; + } + public willUpdate(changedProperties: PropertyValues): void { super.willUpdate(changedProperties); @@ -169,9 +190,62 @@ export class HUIView extends ReactiveElement { oldLovelace.config.views[this.index])) ) { this._initializeConfig(); + return; + } + + if (!changedProperties.has("hass")) { + return; + } + + const oldHass = changedProperties.get("hass") as HomeAssistant | undefined; + const viewConfig = this.lovelace.config.views[this.index]; + if (oldHass && this.hass && this.lovelace && isStrategyView(viewConfig)) { + if ( + oldHass.entities !== this.hass.entities || + oldHass.devices !== this.hass.devices || + oldHass.areas !== this.hass.areas || + oldHass.floors !== this.hass.floors + ) { + if (this.hass.config.state === "RUNNING") { + // If the page is not rendered yet, we can force the refresh + if (this._rendered) { + this._debounceRefreshConfig(false); + } else { + this._refreshConfig(true); + } + } + } } } + private _debounceRefreshConfig = debounce( + (force: boolean) => this._refreshConfig(force), + 200 + ); + + private _refreshConfig = async (force: boolean) => { + if (!this.hass || !this.lovelace) { + return; + } + const viewConfig = this.lovelace.config.views[this.index]; + + if (!isStrategyView(viewConfig)) { + return; + } + + const oldConfig = this._layoutElementConfig; + const newConfig = await this._generateConfig(viewConfig); + + // Don't ask if the config is the same + if (!deepEqual(newConfig, oldConfig)) { + if (force) { + this._setConfig(newConfig, true); + } else { + fireEvent(this, "strategy-config-changed"); + } + } + }; + protected update(changedProperties: PropertyValues) { super.update(changedProperties); @@ -227,20 +301,30 @@ export class HUIView extends ReactiveElement { } } - private async _initializeConfig() { - let viewConfig = this.lovelace.config.views[this.index]; - let isStrategy = false; - - if (isStrategyView(viewConfig)) { - isStrategy = true; - viewConfig = await generateLovelaceViewStrategy(viewConfig, this.hass!); + private async _generateConfig( + config: LovelaceViewRawConfig + ): Promise { + if (isStrategyView(config)) { + const generatedConfig = await generateLovelaceViewStrategy( + config, + this.hass! + ); + return { + ...generatedConfig, + type: getViewType(generatedConfig), + }; } - viewConfig = { - ...viewConfig, - type: getViewType(viewConfig), + return { + ...config, + type: getViewType(config), }; + } + private async _setConfig( + viewConfig: LovelaceViewConfig, + isStrategy: boolean + ) { // Create a new layout element if necessary. let addLayoutElement = false; @@ -248,7 +332,7 @@ export class HUIView extends ReactiveElement { addLayoutElement = true; this._createLayoutElement(viewConfig); } - + this._layoutElementConfig = viewConfig; this._createBadges(viewConfig); this._createCards(viewConfig); this._createSections(viewConfig); @@ -269,6 +353,15 @@ export class HUIView extends ReactiveElement { } } + private async _initializeConfig() { + const rawConfig = this.lovelace.config.views[this.index]; + + const viewConfig = await this._generateConfig(rawConfig); + const isStrategy = isStrategyView(viewConfig); + + this._setConfig(viewConfig, isStrategy); + } + private _createLayoutElement(config: LovelaceViewConfig): void { this._layoutElement = createViewElement(config) as LovelaceViewElement; this._layoutElementType = config.type; From 6a333a4774b02564e4832bc17dee1e859fab36be Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 11 Apr 2025 14:45:42 +0200 Subject: [PATCH 53/53] Bumped version to 20250411.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d4d80f5d0a..c3e3848e96 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20250404.0" +version = "20250411.0" license = "Apache-2.0" license-files = ["LICENSE*"] description = "The Home Assistant frontend"