From 1a14511fa6bf8774ef6dc9b48c5a9001cfd32fcc Mon Sep 17 00:00:00 2001 From: Yosi Levy <37745463+yosilevy@users.noreply.github.com> Date: Wed, 30 Apr 2025 22:07:55 +0300 Subject: [PATCH 01/36] Various RTL fixes (#25231) --- src/components/media-player/ha-media-player-browse.ts | 6 ++++++ src/dialogs/config-flow/step-flow-form.ts | 2 ++ .../voice-assistant-setup/voice-assistant-setup-dialog.ts | 1 + src/panels/config/devices/ha-config-device-page.ts | 1 + .../lovelace/dashboards/ha-config-lovelace-dashboards.ts | 2 +- 5 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/components/media-player/ha-media-player-browse.ts b/src/components/media-player/ha-media-player-browse.ts index d183d49b0d..78d5f1be51 100644 --- a/src/components/media-player/ha-media-player-browse.ts +++ b/src/components/media-player/ha-media-player-browse.ts @@ -890,12 +890,18 @@ export class HaMediaPlayerBrowse extends LitElement { display: flex; flex-direction: row-reverse; margin-right: 48px; + margin-inline-end: 48px; + margin-inline-start: initial; + direction: var(--direction); } .highlight-add-button ha-svg-icon { position: relative; top: -0.5em; margin-left: 8px; + margin-inline-start: 8px; + margin-inline-end: initial; + transform: scaleX(var(--scale-direction)); } .content { diff --git a/src/dialogs/config-flow/step-flow-form.ts b/src/dialogs/config-flow/step-flow-form.ts index a36f4a7152..90b301b98a 100644 --- a/src/dialogs/config-flow/step-flow-form.ts +++ b/src/dialogs/config-flow/step-flow-form.ts @@ -278,7 +278,9 @@ class StepFlowForm extends LitElement { } h2 { word-break: break-word; + padding-right: 72px; padding-inline-end: 72px; + padding-inline-start: initial; direction: var(--direction); } `, 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 7b292fe9ed..699d9099dd 100644 --- a/src/dialogs/voice-assistant-setup/voice-assistant-setup-dialog.ts +++ b/src/dialogs/voice-assistant-setup/voice-assistant-setup-dialog.ts @@ -407,6 +407,7 @@ export class HaVoiceAssistantSetupDialog extends LitElement { align-items: center; margin-right: 12px; margin-inline-end: 12px; + margin-inline-start: initial; } `, ]; diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts index 726d314042..af4033b42f 100644 --- a/src/panels/config/devices/ha-config-device-page.ts +++ b/src/panels/config/devices/ha-config-device-page.ts @@ -1559,6 +1559,7 @@ export class HaConfigDevicePage extends LitElement { align-items: center; padding-left: 8px; padding-inline-start: 8px; + padding-inline-end: initial; direction: var(--direction); } diff --git a/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts b/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts index d81f74925c..56f1a398f6 100644 --- a/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts +++ b/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts @@ -161,7 +161,7 @@ export class HaConfigLovelaceDashboards extends LitElement { placement="right" > From 69eaf178cae43c673ef2c1b39152f4c998ae7ab1 Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Wed, 30 Apr 2025 09:47:51 -0700 Subject: [PATCH 02/36] New energy calculation formula (#25242) * New energy calculation * more tests and stricter tests. change priority order --- src/data/energy.ts | 100 +++++++++------- test/data/energy.test.ts | 239 ++++++++++++++++++++++++++++----------- 2 files changed, 234 insertions(+), 105 deletions(-) diff --git a/src/data/energy.ts b/src/data/energy.ts index 70aece7feb..88d318e9ed 100644 --- a/src/data/energy.ts +++ b/src/data/energy.ts @@ -959,21 +959,13 @@ const computeConsumptionDataPartial = ( }; data.timestamps.forEach((t) => { - const used_total = - (data.from_grid?.[t] || 0) + - (data.solar?.[t] || 0) + - (data.from_battery?.[t] || 0) - - (data.to_grid?.[t] || 0) - - (data.to_battery?.[t] || 0); - - outData.used_total[t] = used_total; - outData.total.used_total += used_total; const { grid_to_battery, battery_to_grid, used_solar, used_grid, used_battery, + used_total, solar_to_battery, solar_to_grid, } = computeConsumptionSingle({ @@ -984,6 +976,8 @@ const computeConsumptionDataPartial = ( from_battery: data.from_battery && (data.from_battery[t] ?? 0), }); + outData.used_total[t] = used_total; + outData.total.used_total += used_total; outData.grid_to_battery[t] = grid_to_battery; outData.total.grid_to_battery += grid_to_battery; outData.battery_to_grid![t] = battery_to_grid; @@ -1017,12 +1011,20 @@ export const computeConsumptionSingle = (data: { used_solar: number; used_grid: number; used_battery: number; + used_total: number; } => { - const to_grid = data.to_grid; - const to_battery = data.to_battery; - const solar = data.solar; - const from_grid = data.from_grid; - const from_battery = data.from_battery; + let to_grid = Math.max(data.to_grid || 0, 0); + let to_battery = Math.max(data.to_battery || 0, 0); + let solar = Math.max(data.solar || 0, 0); + let from_grid = Math.max(data.from_grid || 0, 0); + let from_battery = Math.max(data.from_battery || 0, 0); + + const used_total = + (from_grid || 0) + + (solar || 0) + + (from_battery || 0) - + (to_grid || 0) - + (to_battery || 0); let used_solar = 0; let grid_to_battery = 0; @@ -1031,41 +1033,57 @@ export const computeConsumptionSingle = (data: { let solar_to_grid = 0; let used_battery = 0; let used_grid = 0; - if ((to_grid != null || to_battery != null) && solar != null) { - used_solar = (solar || 0) - (to_grid || 0) - (to_battery || 0); - if (used_solar < 0) { - if (to_battery != null) { - grid_to_battery = used_solar * -1; - if (grid_to_battery > (from_grid || 0)) { - battery_to_grid = grid_to_battery - (from_grid || 0); - grid_to_battery = from_grid || 0; - } - } - used_solar = 0; - } - } - if (from_battery != null) { - used_battery = (from_battery || 0) - battery_to_grid; - } + let used_total_remaining = Math.max(used_total, 0); + // Consumption Priority + // Battery_Out -> Grid_Out + // Solar -> Grid_Out + // Solar -> Battery_In + // Grid_In -> Battery_In + // Solar -> Consumption + // Battery_Out -> Consumption + // Grid_In -> Consumption - if (from_grid != null) { - used_grid = from_grid - grid_to_battery; - } + // Battery_Out -> Grid_Out + battery_to_grid = Math.min(from_battery, to_grid); + from_battery -= battery_to_grid; + to_grid -= battery_to_grid; - if (solar != null) { - if (to_battery != null) { - solar_to_battery = Math.max(0, (to_battery || 0) - grid_to_battery); - } - if (to_grid != null) { - solar_to_grid = Math.max(0, (to_grid || 0) - battery_to_grid); - } - } + // Solar -> Grid_Out + solar_to_grid = Math.min(solar, to_grid); + to_grid -= solar_to_grid; + solar -= solar_to_grid; + + // Solar -> Battery_In + solar_to_battery = Math.min(solar, to_battery); + to_battery -= solar_to_battery; + solar -= solar_to_battery; + + // Grid_In -> Battery_In + grid_to_battery = Math.min(from_grid, to_battery); + from_grid -= grid_to_battery; + to_battery -= to_battery; + + // Solar -> Consumption + used_solar = Math.min(used_total_remaining, solar); + used_total_remaining -= used_solar; + solar -= used_solar; + + // Battery_Out -> Consumption + used_battery = Math.min(from_battery, used_total_remaining); + from_battery -= used_battery; + used_total_remaining -= used_battery; + + // Grid_In -> Consumption + used_grid = Math.min(used_total_remaining, from_grid); + from_grid -= used_grid; + used_total_remaining -= from_grid; return { used_solar, used_grid, used_battery, + used_total, grid_to_battery, battery_to_grid, solar_to_battery, diff --git a/test/data/energy.test.ts b/test/data/energy.test.ts index e5fcbe9dc6..5609803ced 100644 --- a/test/data/energy.test.ts +++ b/test/data/energy.test.ts @@ -14,6 +14,47 @@ import { } from "../../src/data/energy"; import type { HomeAssistant } from "../../src/types"; +const checkConsumptionResult = ( + input: { + from_grid: number | undefined; + to_grid: number | undefined; + solar: number | undefined; + to_battery: number | undefined; + from_battery: number | undefined; + }, + exact = true +): { + grid_to_battery: number; + battery_to_grid: number; + solar_to_battery: number; + solar_to_grid: number; + used_solar: number; + used_grid: number; + used_battery: number; + used_total: number; +} => { + const result = computeConsumptionSingle(input); + if (exact) { + assert.equal( + result.used_total, + result.used_solar + result.used_battery + result.used_grid + ); + assert.equal( + input.to_grid || 0, + result.solar_to_grid + result.battery_to_grid + ); + assert.equal( + input.to_battery || 0, + result.grid_to_battery + result.solar_to_battery + ); + assert.equal( + input.solar || 0, + result.solar_to_battery + result.solar_to_grid + result.used_solar + ); + } + return result; +}; + describe("Energy Short Format Test", () => { // Create default to not have to specify a not relevant TimeFormat over and over again. const defaultLocale: FrontendLocaleData = { @@ -88,7 +129,7 @@ describe("Energy Usage Calculation Tests", () => { it("Consuming Energy From the Grid", () => { [0, 5, 1000].forEach((x) => { assert.deepEqual( - computeConsumptionSingle({ + checkConsumptionResult({ from_grid: x, to_grid: undefined, solar: undefined, @@ -101,6 +142,7 @@ describe("Energy Usage Calculation Tests", () => { used_solar: 0, used_grid: x, used_battery: 0, + used_total: x, solar_to_battery: 0, solar_to_grid: 0, } @@ -108,61 +150,78 @@ describe("Energy Usage Calculation Tests", () => { }); }); it("Solar production, consuming some and returning the remainder to grid.", () => { - [2.99, 3, 10, 100].forEach((s) => { + ( + [ + [2.99, false], // unsolveable : solar < to_grid + [3, true], + [10, true], + [100, true], + ] as any + ).forEach(([s, exact]) => { assert.deepEqual( - computeConsumptionSingle({ - from_grid: 0, - to_grid: 3, - solar: s, - to_battery: undefined, - from_battery: undefined, - }), + checkConsumptionResult( + { + from_grid: 0, + to_grid: 3, + solar: s, + to_battery: undefined, + from_battery: undefined, + }, + exact + ), { grid_to_battery: 0, battery_to_grid: 0, - used_solar: Math.max(0, s - 3), + used_solar: Math.min(s, Math.max(0, s - 3)), used_grid: 0, used_battery: 0, + used_total: s - 3, solar_to_battery: 0, - solar_to_grid: 3, + solar_to_grid: Math.min(3, s), } ); }); }); it("Solar production with simultaneous grid consumption. Excess solar returned to the grid.", () => { - [ - [0, 0], - [3, 0], - [0, 3], - [5, 4], - [4, 5], - [10, 3], - [3, 7], - [3, 7.1], - ].forEach(([from_grid, to_grid]) => { + ( + [ + [0, 0, true], + [3, 0, true], + [0, 3, true], + [5, 4, true], + [4, 5, true], + [10, 3, true], + [3, 7, true], + [3, 7.1, false], // unsolveable: to_grid > solar + ] as any + ).forEach(([from_grid, to_grid, exact]) => { assert.deepEqual( - computeConsumptionSingle({ - from_grid, - to_grid, - solar: 7, - to_battery: undefined, - from_battery: undefined, - }), + checkConsumptionResult( + { + from_grid, + to_grid, + solar: 7, + to_battery: undefined, + from_battery: undefined, + }, + exact + ), { grid_to_battery: 0, battery_to_grid: 0, used_solar: Math.max(0, 7 - to_grid), - used_grid: from_grid, + used_grid: from_grid - Math.max(0, to_grid - 7), + used_total: from_grid - to_grid + 7, used_battery: 0, solar_to_battery: 0, - solar_to_grid: to_grid, + solar_to_grid: Math.min(7, to_grid), } ); }); }); it("Charging the battery from the grid", () => { assert.deepEqual( - computeConsumptionSingle({ + checkConsumptionResult({ from_grid: 5, to_grid: 0, solar: 0, @@ -175,6 +234,7 @@ describe("Energy Usage Calculation Tests", () => { used_solar: 0, used_grid: 2, used_battery: 0, + used_total: 2, solar_to_battery: 0, solar_to_grid: 0, } @@ -182,7 +242,7 @@ describe("Energy Usage Calculation Tests", () => { }); it("Consuming from the grid and battery simultaneously", () => { assert.deepEqual( - computeConsumptionSingle({ + checkConsumptionResult({ from_grid: 5, to_grid: 0, solar: 0, @@ -195,6 +255,7 @@ describe("Energy Usage Calculation Tests", () => { used_solar: 0, used_grid: 5, used_battery: 5, + used_total: 10, solar_to_battery: 0, solar_to_grid: 0, } @@ -202,7 +263,7 @@ describe("Energy Usage Calculation Tests", () => { }); it("Consuming some battery and returning some battery to the grid", () => { assert.deepEqual( - computeConsumptionSingle({ + checkConsumptionResult({ from_grid: 0, to_grid: 4, solar: 0, @@ -215,15 +276,15 @@ describe("Energy Usage Calculation Tests", () => { used_solar: 0, used_grid: 0, used_battery: 1, + used_total: 1, solar_to_battery: 0, solar_to_grid: 0, } ); }); - /* Fails it("Charging and discharging the battery to/from the grid in the same interval.", () => { assert.deepEqual( - computeConsumptionSingle({ + checkConsumptionResult({ from_grid: 5, to_grid: 1, solar: 0, @@ -234,15 +295,18 @@ describe("Energy Usage Calculation Tests", () => { grid_to_battery: 3, battery_to_grid: 1, used_solar: 0, - used_grid: 1, + used_grid: 2, used_battery: 0, + used_total: 2, + solar_to_battery: 0, + solar_to_grid: 0, } ); - }); */ - /* Test does not pass, battery is not really correct when solar is not present + }); + it("Charging the battery with no solar sensor.", () => { assert.deepEqual( - computeConsumptionSingle({ + checkConsumptionResult({ from_grid: 5, to_grid: 0, solar: undefined, @@ -255,13 +319,15 @@ describe("Energy Usage Calculation Tests", () => { used_solar: 0, used_grid: 2, used_battery: 0, + used_total: 2, + solar_to_battery: 0, + solar_to_grid: 0, } ); - }); */ - /* Test does not pass + }); it("Discharging battery to grid while also consuming from grid.", () => { assert.deepEqual( - computeConsumptionSingle({ + checkConsumptionResult({ from_grid: 5, to_grid: 4, solar: 0, @@ -274,14 +340,16 @@ describe("Energy Usage Calculation Tests", () => { used_solar: 0, used_grid: 5, used_battery: 0, + used_total: 5, + solar_to_grid: 0, + solar_to_battery: 0, } ); }); - */ it("Grid, solar, and battery", () => { assert.deepEqual( - computeConsumptionSingle({ + checkConsumptionResult({ from_grid: 5, to_grid: 3, solar: 7, @@ -294,12 +362,13 @@ describe("Energy Usage Calculation Tests", () => { used_solar: 1, used_grid: 5, used_battery: 0, + used_total: 6, solar_to_battery: 3, solar_to_grid: 3, } ); assert.deepEqual( - computeConsumptionSingle({ + checkConsumptionResult({ from_grid: 5, to_grid: 3, solar: 7, @@ -308,16 +377,17 @@ describe("Energy Usage Calculation Tests", () => { }), { grid_to_battery: 0, - battery_to_grid: 0, - used_solar: 1, + battery_to_grid: 3, + used_solar: 4, used_grid: 5, - used_battery: 10, + used_battery: 7, + used_total: 16, solar_to_battery: 3, - solar_to_grid: 3, + solar_to_grid: 0, } ); assert.deepEqual( - computeConsumptionSingle({ + checkConsumptionResult({ from_grid: 2, to_grid: 7, solar: 7, @@ -325,17 +395,18 @@ describe("Energy Usage Calculation Tests", () => { from_battery: 1, }), { - grid_to_battery: 1, - battery_to_grid: 0, + grid_to_battery: 0, + battery_to_grid: 1, used_solar: 0, - used_grid: 1, - used_battery: 1, - solar_to_battery: 0, - solar_to_grid: 7, + used_grid: 2, + used_battery: 0, + used_total: 2, + solar_to_battery: 1, + solar_to_grid: 6, } ); assert.deepEqual( - computeConsumptionSingle({ + checkConsumptionResult({ from_grid: 2, to_grid: 7, solar: 9, @@ -344,17 +415,17 @@ describe("Energy Usage Calculation Tests", () => { }), { grid_to_battery: 0, - battery_to_grid: 0, - used_solar: 1, + battery_to_grid: 1, + used_solar: 2, used_grid: 2, - used_battery: 1, + used_battery: 0, + used_total: 4, solar_to_battery: 1, - solar_to_grid: 7, + solar_to_grid: 6, } ); - /* Test does not pass assert.deepEqual( - computeConsumptionSingle({ + checkConsumptionResult({ from_grid: 5, to_grid: 3, solar: 1, @@ -367,8 +438,48 @@ describe("Energy Usage Calculation Tests", () => { used_solar: 0, used_grid: 5, used_battery: 0, + used_total: 5, + solar_to_battery: 0, + solar_to_grid: 1, + } + ); + assert.deepEqual( + checkConsumptionResult({ + from_grid: 6, + to_grid: 0, + solar: 3, + to_battery: 6, + from_battery: 6, + }), + { + grid_to_battery: 3, + battery_to_grid: 0, + used_solar: 0, + used_grid: 3, + used_battery: 6, + solar_to_battery: 3, + solar_to_grid: 0, + used_total: 9, + } + ); + assert.deepEqual( + checkConsumptionResult({ + from_grid: 0, + to_grid: 1, + solar: 1, + to_battery: 1, + from_battery: 1, + }), + { + grid_to_battery: 0, + battery_to_grid: 1, + used_solar: 0, + used_grid: 0, + used_battery: 0, + solar_to_battery: 1, + solar_to_grid: 0, + used_total: 0, } ); - */ }); }); From 9b7e2886b6633efa7c714233af30bc83e0374fe1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 30 Apr 2025 21:10:32 +0200 Subject: [PATCH 03/36] Better explain when DHCP discovery data will be available (#25250) It was pointed out that users likely may not know what DHCP is and wonder why the data is not available yet. --- 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 1fb0d53531..82c4d93ba9 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -6395,7 +6395,7 @@ }, "discovery": { "dhcp": "DHCP browser", - "dhcp_info": "The DHCP browser displays devices discovered by Home Assistant via DHCP, ARP+PTR lookups, and router-based device trackers. All detected devices by these methods will appear here.", + "dhcp_info": "The DHCP browser shows devices detected by Home Assistant using methods like DHCP, ARP+PTR lookups, and router-based device trackers. DHCP (Dynamic Host Configuration Protocol) data is received when devices join the network and request an IP address, allowing Home Assistant to discover them automatically. All devices found through these methods will appear here.", "dhcp_browser": "View DHCP browser", "ssdp": "SSDP browser", "ssdp_info": "The SSDP browser shows devices discovered by Home Assistant using SSDP/UPnP. Devices that Home Assistant has discovered will appear here.", From b81d2013dc27afeec0660d067ed8ffece3497f07 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 30 Apr 2025 21:16:25 +0200 Subject: [PATCH 04/36] Fix formatting of mac address fields in device info (#25251) Previous change lost the `:` seperator, and unexpectedly MAC became title case instead of MAC when dhcp is loaded. --- .../config/devices/device-detail/ha-device-info-card.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/panels/config/devices/device-detail/ha-device-info-card.ts b/src/panels/config/devices/device-detail/ha-device-info-card.ts index dba2bbfa66..29a4b678b8 100644 --- a/src/panels/config/devices/device-detail/ha-device-info-card.ts +++ b/src/panels/config/devices/device-detail/ha-device-info-card.ts @@ -106,7 +106,7 @@ export class HaDeviceCard extends LitElement {
${type === "bluetooth" && isComponentLoaded(this.hass, "bluetooth") - ? html`${titleCase(type)} + ? html`${titleCase(type)}: Date: Wed, 30 Apr 2025 21:53:07 +0200 Subject: [PATCH 05/36] Improve message when no discovery data is found (#25252) * Improve message when no discovery data is found It was pointed out its a bit confusing when a device has not been discovered yet for the discovery/network browser panels as we only said there was no data. Give the user a better hint as to why there is no data. * Improve message when no discovery data is found It was pointed out its a bit confusing when a device has not been discovered yet for the discovery/network browser panels as we only said there was no data. Give the user a better hint as to why there is no data. * Improve message when no discovery data is found It was pointed out its a bit confusing when a device has not been discovered yet for the discovery/network browser panels as we only said there was no data. Give the user a better hint as to why there is no data. * Improve message when no discovery data is found It was pointed out its a bit confusing when a device has not been discovered yet for the discovery/network browser panels as we only said there was no data. Give the user a better hint as to why there is no data. --- .../bluetooth/bluetooth-advertisement-monitor.ts | 3 +++ .../integration-panels/dhcp/dhcp-config-panel.ts | 3 +++ .../integration-panels/ssdp/ssdp-config-panel.ts | 3 +++ .../zeroconf/zeroconf-config-panel.ts | 3 +++ src/translations/en.json | 10 +++++++--- 5 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/panels/config/integrations/integration-panels/bluetooth/bluetooth-advertisement-monitor.ts b/src/panels/config/integrations/integration-panels/bluetooth/bluetooth-advertisement-monitor.ts index 5bcd7389f4..d9e53f3b05 100644 --- a/src/panels/config/integrations/integration-panels/bluetooth/bluetooth-advertisement-monitor.ts +++ b/src/panels/config/integrations/integration-panels/bluetooth/bluetooth-advertisement-monitor.ts @@ -210,6 +210,9 @@ export class BluetoothAdvertisementMonitorPanel extends LitElement { .route=${this.route} .columns=${this._columns(this.hass.localize)} .data=${this._dataWithNamedSourceAndIds(this._data)} + .noDataText=${this.hass.localize( + "ui.panel.config.bluetooth.no_advertisements_found" + )} @row-click=${this._handleRowClicked} .initialGroupColumn=${this._activeGrouping} .initialCollapsedGroups=${this._activeCollapsed} diff --git a/src/panels/config/integrations/integration-panels/dhcp/dhcp-config-panel.ts b/src/panels/config/integrations/integration-panels/dhcp/dhcp-config-panel.ts index ea4c24a2aa..683719ee43 100644 --- a/src/panels/config/integrations/integration-panels/dhcp/dhcp-config-panel.ts +++ b/src/panels/config/integrations/integration-panels/dhcp/dhcp-config-panel.ts @@ -96,6 +96,9 @@ export class DHCPConfigPanel extends SubscribeMixin(LitElement) { .route=${this.route} .columns=${this._columns(this.hass.localize)} .data=${this._dataWithIds(this._data)} + .noDataText=${this.hass.localize( + "ui.panel.config.dhcp.no_devices_found" + )} filter=${this._macAddress || ""} > `; diff --git a/src/panels/config/integrations/integration-panels/ssdp/ssdp-config-panel.ts b/src/panels/config/integrations/integration-panels/ssdp/ssdp-config-panel.ts index 215205cfe8..1433e2d582 100644 --- a/src/panels/config/integrations/integration-panels/ssdp/ssdp-config-panel.ts +++ b/src/panels/config/integrations/integration-panels/ssdp/ssdp-config-panel.ts @@ -105,6 +105,9 @@ export class SSDPConfigPanel extends SubscribeMixin(LitElement) { @grouping-changed=${this._handleGroupingChanged} @collapsed-changed=${this._handleCollapseChanged} .data=${this._dataWithIds(this._data)} + .noDataText=${this.hass.localize( + "ui.panel.config.ssdp.no_devices_found" + )} @row-click=${this._handleRowClicked} clickable > diff --git a/src/panels/config/integrations/integration-panels/zeroconf/zeroconf-config-panel.ts b/src/panels/config/integrations/integration-panels/zeroconf/zeroconf-config-panel.ts index 57ffabcdee..64a237fde1 100644 --- a/src/panels/config/integrations/integration-panels/zeroconf/zeroconf-config-panel.ts +++ b/src/panels/config/integrations/integration-panels/zeroconf/zeroconf-config-panel.ts @@ -112,6 +112,9 @@ export class ZeroconfConfigPanel extends SubscribeMixin(LitElement) { @grouping-changed=${this._handleGroupingChanged} @collapsed-changed=${this._handleCollapseChanged} .data=${this._dataWithIds(this._data)} + .noDataText=${this.hass.localize( + "ui.panel.config.zeroconf.no_devices_found" + )} @row-click=${this._handleRowClicked} clickable > diff --git a/src/translations/en.json b/src/translations/en.json index 82c4d93ba9..c47cd875ad 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -5508,6 +5508,7 @@ "connection_monitor": "Connection monitor", "used_connection_slot_allocations": "Used connection slot allocations", "no_connections": "No active connections", + "no_advertisements_found": "No matching Bluetooth advertisements found", "no_connection_slot_allocations": "No connection slot allocations information available", "no_active_connection_support": "This adapter does not support making active (GATT) connections.", "address": "Address", @@ -5528,7 +5529,8 @@ "title": "DHCP discovery", "mac_address": "MAC Address", "hostname": "Hostname", - "ip_address": "IP Address" + "ip_address": "IP Address", + "no_devices_found": "No recent DHCP requests found; no matching discoveries detected" }, "thread": { "other_networks": "Other networks", @@ -5577,7 +5579,8 @@ "ssdp_headers": "SSDP Headers", "upnp": "Universal Plug and Play (UPnP)", "discovery_information": "Discovery information", - "copy_to_clipboard": "Copy to clipboard" + "copy_to_clipboard": "Copy to clipboard", + "no_devices_found": "No matching SSDP/UPnP discoveries found" }, "zeroconf": { "name": "Name", @@ -5586,7 +5589,8 @@ "ip_addresses": "IP Addresses", "properties": "Properties", "discovery_information": "Discovery information", - "copy_to_clipboard": "Copy to clipboard" + "copy_to_clipboard": "Copy to clipboard", + "no_devices_found": "No matching Zeroconf discoveries found" }, "zha": { "common": { From 9e5b7462aff35e2c7be9316db2641928cc3962a7 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Wed, 30 Apr 2025 22:23:45 +0200 Subject: [PATCH 06/36] Add `?` as shortcut for shortcuts dialog (#25253) Bind shortcuts dialog to `?` key --- src/state/quick-bar-mixin.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/state/quick-bar-mixin.ts b/src/state/quick-bar-mixin.ts index 598d064597..6e931a9bd7 100644 --- a/src/state/quick-bar-mixin.ts +++ b/src/state/quick-bar-mixin.ts @@ -15,6 +15,7 @@ import type { HassElement } from "./hass-element"; import { extractSearchParamsObject } from "../common/url/search-params"; import { showVoiceCommandDialog } from "../dialogs/voice-command-dialog/show-ha-voice-command-dialog"; import { canOverrideAlphanumericInput } from "../common/dom/can-override-input"; +import { showShortcutsDialog } from "../dialogs/shortcuts/show-shortcuts-dialog"; declare global { interface HASSDomEvents { @@ -51,6 +52,8 @@ export default >(superClass: T) => case "a": this._showVoiceCommandDialog(ev.detail); break; + case "?": + this._showShortcutDialog(ev.detail); } }); @@ -65,6 +68,8 @@ export default >(superClass: T) => m: (ev) => this._createMyLink(ev), a: (ev) => this._showVoiceCommandDialog(ev), d: (ev) => this._showQuickBar(ev, QuickBarMode.Device), + // Workaround see https://github.com/jamiebuilds/tinykeys/issues/130 + "Shift+?": (ev) => this._showShortcutDialog(ev), // Those are fallbacks for non-latin keyboards that don't have e, c, m keys (qwerty-based shortcuts) KeyE: (ev) => this._showQuickBar(ev), KeyC: (ev) => this._showQuickBar(ev, QuickBarMode.Command), @@ -111,6 +116,19 @@ export default >(superClass: T) => showQuickBar(this, { mode }); } + private _showShortcutDialog(e: KeyboardEvent) { + if (!this._canShowQuickBar(e)) { + return; + } + + if (e.defaultPrevented) { + return; + } + e.preventDefault(); + + showShortcutsDialog(this); + } + private async _createMyLink(e: KeyboardEvent) { if ( !this.hass?.enableShortcuts || From 2b06742bb9c2a72d7815f78444f25175b23b6318 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Thu, 1 May 2025 09:25:18 +0200 Subject: [PATCH 07/36] Hide the tab when view is a subview (#25256) --- src/panels/lovelace/hui-root.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/panels/lovelace/hui-root.ts b/src/panels/lovelace/hui-root.ts index 883fab5089..8083dd947c 100644 --- a/src/panels/lovelace/hui-root.ts +++ b/src/panels/lovelace/hui-root.ts @@ -300,6 +300,12 @@ class HUIRoot extends LitElement { const background = curViewConfig?.background || this.config.background; + const _isTabHiddenForUser = (view: LovelaceViewConfig) => + view.visible !== undefined && + ((Array.isArray(view.visible) && + !view.visible.some((e) => e.user === this.hass!.user?.id)) || + view.visible === false); + const tabs = html` ${views.map( (view, index) => html` @@ -311,13 +317,7 @@ class HUIRoot extends LitElement { class=${classMap({ icon: Boolean(view.icon), "hide-tab": Boolean( - !this._editMode && - view.visible !== undefined && - ((Array.isArray(view.visible) && - !view.visible.some( - (e) => e.user === this.hass!.user?.id - )) || - view.visible === false) + !this._editMode && (view.subview || _isTabHiddenForUser(view)) ), })} > From f24b6a4cb1556d852857a3935fa07389bc870fe6 Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Wed, 30 Apr 2025 21:27:11 -0700 Subject: [PATCH 08/36] Fix typo in energy calculation (#25259) * New energy calculation * more tests and stricter tests. change priority order * more test and fix error --- src/data/energy.ts | 2 +- test/data/energy.test.ts | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/data/energy.ts b/src/data/energy.ts index 88d318e9ed..06498ace9b 100644 --- a/src/data/energy.ts +++ b/src/data/energy.ts @@ -1062,7 +1062,7 @@ export const computeConsumptionSingle = (data: { // Grid_In -> Battery_In grid_to_battery = Math.min(from_grid, to_battery); from_grid -= grid_to_battery; - to_battery -= to_battery; + to_battery -= grid_to_battery; // Solar -> Consumption used_solar = Math.min(used_total_remaining, solar); diff --git a/test/data/energy.test.ts b/test/data/energy.test.ts index 5609803ced..ba8d159e08 100644 --- a/test/data/energy.test.ts +++ b/test/data/energy.test.ts @@ -462,6 +462,8 @@ describe("Energy Usage Calculation Tests", () => { used_total: 9, } ); + }); + it("Solar -> Battery -> Grid", () => { assert.deepEqual( checkConsumptionResult({ from_grid: 0, @@ -482,4 +484,25 @@ describe("Energy Usage Calculation Tests", () => { } ); }); + it("Solar -> Grid && Grid -> Battery", () => { + assert.deepEqual( + checkConsumptionResult({ + from_grid: 1, + to_grid: 1, + solar: 1, + to_battery: 1, + from_battery: 0, + }), + { + grid_to_battery: 1, + battery_to_grid: 0, + used_solar: 0, + used_grid: 0, + used_battery: 0, + solar_to_battery: 0, + solar_to_grid: 1, + used_total: 0, + } + ); + }); }); From 4624cc609f0dd0d128c7b50ae7809e0c95f7547d Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Thu, 1 May 2025 19:02:55 +0200 Subject: [PATCH 09/36] Always show backup location retention settings (#25261) Always show backup location retention settings --- .../backup/ha-config-backup-location.ts | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/panels/config/backup/ha-config-backup-location.ts b/src/panels/config/backup/ha-config-backup-location.ts index 6e5d5b585f..323ca5097c 100644 --- a/src/panels/config/backup/ha-config-backup-location.ts +++ b/src/panels/config/backup/ha-config-backup-location.ts @@ -118,19 +118,17 @@ class HaConfigBackupDetails extends LitElement {

` - : this.config?.agents[this.agentId] - ? html`` - : nothing} + : html``}
From d7dd11ba7f941235f1fe6b7c2881d6c23d62b646 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 1 May 2025 20:06:42 +0300 Subject: [PATCH 10/36] Improve error handling in automation i18n (#25266) --- .../entity/ha-entity-attribute-picker.ts | 16 +- src/data/automation_i18n.ts | 167 ++++++++++-------- 2 files changed, 103 insertions(+), 80 deletions(-) diff --git a/src/components/entity/ha-entity-attribute-picker.ts b/src/components/entity/ha-entity-attribute-picker.ts index 2468f6fe71..7e08060e33 100644 --- a/src/components/entity/ha-entity-attribute-picker.ts +++ b/src/components/entity/ha-entity-attribute-picker.ts @@ -73,16 +73,20 @@ class HaEntityAttributePicker extends LitElement { return nothing; } + const stateObj = this.hass.states[this.entityId!] as HassEntity | undefined; + return html` @@ -988,12 +1003,14 @@ const tryDescribeCondition = ( ); const attribute = condition.attribute - ? computeAttributeNameDisplay( - hass.localize, - stateObj, - hass.entities, - condition.attribute - ) + ? stateObj + ? computeAttributeNameDisplay( + hass.localize, + stateObj, + hass.entities, + condition.attribute + ) + : condition.attribute : undefined; if (condition.above !== undefined && condition.below !== undefined) { @@ -1187,7 +1204,9 @@ const tryDescribeCondition = ( if (localized) { return localized; } - const stateObj = hass.states[config.entity_id as string]; + const stateObj = hass.states[config.entity_id as string] as + | HassEntity + | undefined; return `${stateObj ? computeStateName(stateObj) : config.entity_id} ${ config.type }`; From 5d89563aa581d243a96d96f3d1b5d17cb922e068 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 1 May 2025 08:57:56 -0400 Subject: [PATCH 11/36] Import missing components on init page (#25269) --- src/layouts/ha-init-page.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/layouts/ha-init-page.ts b/src/layouts/ha-init-page.ts index 84cd396569..af33535a32 100644 --- a/src/layouts/ha-init-page.ts +++ b/src/layouts/ha-init-page.ts @@ -1,6 +1,8 @@ import type { PropertyValues } from "lit"; import { css, html, LitElement } from "lit"; import { property, state } from "lit/decorators"; +import "@material/mwc-button"; +import "../components/ha-spinner"; class HaInitPage extends LitElement { @property({ type: Boolean }) public error = false; From fae619085c6e51ea640496f601662d2fb57084d3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 May 2025 05:46:36 -0500 Subject: [PATCH 12/36] Add my links for the Bluetooth monitors (#25270) The plan is to link these in the Bluetooth docs for help debugging --- src/panels/my/ha-panel-my.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/panels/my/ha-panel-my.ts b/src/panels/my/ha-panel-my.ts index 110e540fbb..cd78c52e55 100644 --- a/src/panels/my/ha-panel-my.ts +++ b/src/panels/my/ha-panel-my.ts @@ -110,6 +110,14 @@ export const getMyRedirects = (): Redirects => ({ component: "bluetooth", redirect: "/config/bluetooth", }, + bluetooth_advertisement_monitor: { + component: "bluetooth", + redirect: "/config/bluetooth/advertisement-monitor", + }, + bluetooth_connection_monitor: { + component: "bluetooth", + redirect: "/config/bluetooth/connection-monitor", + }, config_dhcp: { component: "dhcp", redirect: "/config/dhcp", From 32acef8fad77a65c9684dd7b8571a8eb62b3fa1c Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Fri, 2 May 2025 12:16:04 +0200 Subject: [PATCH 13/36] Add save shortcut to shortcuts dialog (#25271) --- src/dialogs/shortcuts/dialog-shortcuts.ts | 9 +++++++-- src/translations/en.json | 7 ++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/dialogs/shortcuts/dialog-shortcuts.ts b/src/dialogs/shortcuts/dialog-shortcuts.ts index 99a3710ba6..dbd85839ae 100644 --- a/src/dialogs/shortcuts/dialog-shortcuts.ts +++ b/src/dialogs/shortcuts/dialog-shortcuts.ts @@ -69,12 +69,17 @@ const _SHORTCUTS: Section[] = [ ], }, { - key: "ui.dialogs.shortcuts.automations.title", + key: "ui.dialogs.shortcuts.automation_script.title", items: [ { type: "shortcut", shortcut: [{ key: "ui.dialogs.shortcuts.shortcuts.ctrl_cmd" }, "V"], - key: "ui.dialogs.shortcuts.automations.paste", + key: "ui.dialogs.shortcuts.automation_script.paste", + }, + { + type: "shortcut", + shortcut: [{ key: "ui.dialogs.shortcuts.shortcuts.ctrl_cmd" }, "S"], + key: "ui.dialogs.shortcuts.automation_script.save", }, ], }, diff --git a/src/translations/en.json b/src/translations/en.json index c47cd875ad..af02f16e77 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1938,9 +1938,10 @@ "title": "Assist", "open_assist": "open Assist dialog" }, - "automations": { - "title": "Automations", - "paste": "to paste automation YAML from clipboard to automation editor" + "automation_script": { + "title": "Automations / Scripts", + "paste": "to paste automation/script YAML from clipboard to editor", + "save": "to save automation/script" }, "charts": { "title": "Charts", From 358e450e60981efe7110dcb73184f5cc444aff30 Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Fri, 2 May 2025 06:19:12 -0700 Subject: [PATCH 14/36] Fix disabled language picker (#25278) --- src/components/ha-language-picker.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/ha-language-picker.ts b/src/components/ha-language-picker.ts index f771871756..9940b75184 100644 --- a/src/components/ha-language-picker.ts +++ b/src/components/ha-language-picker.ts @@ -102,7 +102,7 @@ export class HaLanguagePicker extends LitElement { localeChanged ) { this._select.layoutOptions(); - if (this._select.value !== this.value) { + if (!this.disabled && this._select.value !== this.value) { fireEvent(this, "value-changed", { value: this._select.value }); } if (!this.value) { @@ -141,7 +141,10 @@ export class HaLanguagePicker extends LitElement { ); const value = - this.value ?? (this.required ? languageOptions[0]?.value : this.value); + this.value ?? + (this.required && !this.disabled + ? languageOptions[0]?.value + : this.value); return html` Date: Fri, 2 May 2025 17:47:20 +0300 Subject: [PATCH 15/36] Fix alignment of ha-labeled-slider (#25279) --- src/components/ha-labeled-slider.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/ha-labeled-slider.ts b/src/components/ha-labeled-slider.ts index 8a54a14496..483d5aefd6 100644 --- a/src/components/ha-labeled-slider.ts +++ b/src/components/ha-labeled-slider.ts @@ -30,8 +30,9 @@ class HaLabeledSlider extends LitElement { @property({ type: Number }) public value?: number; protected render() { + const title = this._getTitle(); return html` -
${this._getTitle()}
+ ${title ? html`
${title}
` : nothing}
${this.icon ? html`` : nothing} @@ -73,17 +74,20 @@ class HaLabeledSlider extends LitElement { .slider-container { display: flex; + align-items: center; } ha-icon { - margin-top: 8px; color: var(--secondary-text-color); } ha-slider { + display: flex; flex-grow: 1; + align-items: center; background-image: var(--ha-slider-background); border-radius: 4px; + height: 32px; } `; } From a820cd45762532c8cd3962c7d6cea8ff3ea124b2 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 2 May 2025 19:21:38 +0300 Subject: [PATCH 16/36] Fix decorators with properties (#25282) * Fix decorators with properties * Green build --------- Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com> --- src/common/decorators/storage.ts | 12 ++---------- src/common/decorators/transform.ts | 14 +------------- src/components/ha-sidebar.ts | 2 ++ src/components/media-player/ha-browse-media-tts.ts | 1 + .../ha-voice-command-dialog.ts | 1 + src/panels/calendar/ha-panel-calendar.ts | 1 + .../ha-config-application-credentials.ts | 1 + .../automation/action/ha-automation-action.ts | 1 + .../condition/ha-automation-condition.ts | 1 + .../config/automation/ha-automation-editor.ts | 1 + .../config/automation/ha-automation-picker.ts | 2 ++ .../automation/option/ha-automation-option.ts | 1 + .../automation/trigger/ha-automation-trigger.ts | 1 + .../config/backup/ha-config-backup-backups.ts | 1 + .../config/blueprint/ha-blueprint-overview.ts | 9 ++++++--- .../config/devices/ha-config-devices-dashboard.ts | 2 ++ src/panels/config/entities/ha-config-entities.ts | 2 ++ src/panels/config/helpers/ha-config-helpers.ts | 1 + .../integration-panels/mqtt/mqtt-config-panel.ts | 7 ++++++- .../integration-panels/mqtt/mqtt-subscribe-card.ts | 3 +++ src/panels/config/labels/ha-config-labels.ts | 1 + .../dashboards/ha-config-lovelace-dashboards.ts | 1 + .../resources/ha-config-lovelace-resources.ts | 1 + src/panels/config/scene/ha-scene-dashboard.ts | 2 ++ src/panels/config/script/ha-script-editor.ts | 1 + src/panels/config/script/ha-script-picker.ts | 2 ++ src/panels/config/tags/ha-config-tags.ts | 1 + .../ha-config-voice-assistants-expose.ts | 1 + .../action/developer-tools-action.ts | 2 ++ .../assist/developer-tools-assist.ts | 1 + .../developer-tools/state/developer-tools-state.ts | 1 + src/panels/history/ha-panel-history.ts | 1 + src/panels/logbook/ha-panel-logbook.ts | 1 + .../energy/hui-energy-devices-detail-graph-card.ts | 1 + src/panels/lovelace/cards/hui-button-card.ts | 2 ++ .../editor/badge-editor/hui-badge-picker.ts | 1 + .../lovelace/editor/card-editor/hui-card-picker.ts | 1 + src/panels/media-browser/ha-panel-media-browser.ts | 2 ++ src/panels/todo/ha-panel-todo.ts | 3 ++- 39 files changed, 62 insertions(+), 28 deletions(-) diff --git a/src/common/decorators/storage.ts b/src/common/decorators/storage.ts index 82e67cd32f..03d4176548 100644 --- a/src/common/decorators/storage.ts +++ b/src/common/decorators/storage.ts @@ -1,6 +1,5 @@ import type { UnsubscribeFunc } from "home-assistant-js-websocket"; -import { ReactiveElement } from "lit"; -import type { InternalPropertyDeclaration } from "lit/decorators"; +import type { ReactiveElement } from "lit"; type Callback = (oldValue: any, newValue: any) => void; @@ -108,7 +107,6 @@ export function storage(options: { storage?: "localStorage" | "sessionStorage"; subscribe?: boolean; state?: boolean; - stateOptions?: InternalPropertyDeclaration; serializer?: (value: any) => any; deserializer?: (value: any) => any; }) { @@ -174,7 +172,7 @@ export function storage(options: { performUpdate.call(this); }; - if (options.state && options.subscribe) { + if (options.subscribe) { const connectedCallback = proto.connectedCallback; const disconnectedCallback = proto.disconnectedCallback; @@ -192,12 +190,6 @@ export function storage(options: { el.__unbsubLocalStorage = undefined; }; } - if (options.state) { - ReactiveElement.createProperty(propertyKey, { - noAccessor: true, - ...options.stateOptions, - }); - } const descriptor = Object.getOwnPropertyDescriptor(proto, propertyKey); let newDescriptor: PropertyDescriptor; diff --git a/src/common/decorators/transform.ts b/src/common/decorators/transform.ts index ee02be719b..b6ac2717d4 100644 --- a/src/common/decorators/transform.ts +++ b/src/common/decorators/transform.ts @@ -1,10 +1,4 @@ -import { - ReactiveElement, - type PropertyDeclaration, - type PropertyValues, -} from "lit"; -import { shallowEqual } from "../util/shallow-equal"; - +import type { ReactiveElement, PropertyValues } from "lit"; /** * Transform function type. */ @@ -23,7 +17,6 @@ type ReactiveTransformElement = ReactiveElement & { export function transform(config: { transformer: Transformer; watch?: PropertyKey[]; - propertyOptions?: PropertyDeclaration; }) { return ( proto: ElemClass, @@ -84,11 +77,6 @@ export function transform(config: { curWatch.add(propertyKey); }); } - ReactiveElement.createProperty(propertyKey, { - noAccessor: true, - hasChanged: (v: any, o: any) => !shallowEqual(v, o), - ...config.propertyOptions, - }); const descriptor = Object.getOwnPropertyDescriptor(proto, propertyKey); let newDescriptor: PropertyDescriptor; diff --git a/src/components/ha-sidebar.ts b/src/components/ha-sidebar.ts index 9b9695a765..b59b15405e 100644 --- a/src/components/ha-sidebar.ts +++ b/src/components/ha-sidebar.ts @@ -210,6 +210,7 @@ class HaSidebar extends SubscribeMixin(LitElement) { private _unsubPersistentNotifications: UnsubscribeFunc | undefined; + @state() @storage({ key: "sidebarPanelOrder", state: true, @@ -217,6 +218,7 @@ class HaSidebar extends SubscribeMixin(LitElement) { }) private _panelOrder: string[] = []; + @state() @storage({ key: "sidebarHiddenPanels", state: true, diff --git a/src/components/media-player/ha-browse-media-tts.ts b/src/components/media-player/ha-browse-media-tts.ts index 2e0be9526e..e6fbdd62d7 100644 --- a/src/components/media-player/ha-browse-media-tts.ts +++ b/src/components/media-player/ha-browse-media-tts.ts @@ -42,6 +42,7 @@ class BrowseMediaTTS extends LitElement { @state() private _provider?: TTSEngine; + @state() @storage({ key: "TtsMessage", state: true, diff --git a/src/dialogs/voice-command-dialog/ha-voice-command-dialog.ts b/src/dialogs/voice-command-dialog/ha-voice-command-dialog.ts index 60aa04fb11..1b2a0e88a0 100644 --- a/src/dialogs/voice-command-dialog/ha-voice-command-dialog.ts +++ b/src/dialogs/voice-command-dialog/ha-voice-command-dialog.ts @@ -36,6 +36,7 @@ export class HaVoiceCommandDialog extends LitElement { @state() private _opened = false; + @state() @storage({ key: "AssistPipelineId", state: true, diff --git a/src/panels/calendar/ha-panel-calendar.ts b/src/panels/calendar/ha-panel-calendar.ts index b27b52874a..73cd194e4e 100644 --- a/src/panels/calendar/ha-panel-calendar.ts +++ b/src/panels/calendar/ha-panel-calendar.ts @@ -42,6 +42,7 @@ class PanelCalendar extends LitElement { @state() private _error?: string = undefined; + @state() @storage({ key: "deSelectedCalendars", state: true, diff --git a/src/panels/config/application_credentials/ha-config-application-credentials.ts b/src/panels/config/application_credentials/ha-config-application-credentials.ts index cb2fa6c5db..0ddb6f89da 100644 --- a/src/panels/config/application_credentials/ha-config-application-credentials.ts +++ b/src/panels/config/application_credentials/ha-config-application-credentials.ts @@ -69,6 +69,7 @@ export class HaConfigApplicationCredentials extends LitElement { }) private _activeHiddenColumns?: string[]; + @state() @storage({ storage: "sessionStorage", key: "application-credentials-table-search", diff --git a/src/panels/config/automation/action/ha-automation-action.ts b/src/panels/config/automation/action/ha-automation-action.ts index a70e45bac4..e41f9e500f 100644 --- a/src/panels/config/automation/action/ha-automation-action.ts +++ b/src/panels/config/automation/action/ha-automation-action.ts @@ -36,6 +36,7 @@ export default class HaAutomationAction extends LitElement { @state() private _showReorder = false; + @state() @storage({ key: "automationClipboard", state: true, diff --git a/src/panels/config/automation/condition/ha-automation-condition.ts b/src/panels/config/automation/condition/ha-automation-condition.ts index 5c3ed0f910..f1557a7956 100644 --- a/src/panels/config/automation/condition/ha-automation-condition.ts +++ b/src/panels/config/automation/condition/ha-automation-condition.ts @@ -36,6 +36,7 @@ export default class HaAutomationCondition extends LitElement { @state() private _showReorder = false; + @state() @storage({ key: "automationClipboard", state: true, diff --git a/src/panels/config/automation/ha-automation-editor.ts b/src/panels/config/automation/ha-automation-editor.ts index ebf232fc29..b6e2da8732 100644 --- a/src/panels/config/automation/ha-automation-editor.ts +++ b/src/panels/config/automation/ha-automation-editor.ts @@ -135,6 +135,7 @@ export class HaAutomationEditor extends PreventUnsavedMixin( @state() private _blueprintConfig?: BlueprintAutomationConfig; + @state() @consume({ context: fullEntitiesContext, subscribe: true }) @transform({ transformer: function (this: HaAutomationEditor, value) { diff --git a/src/panels/config/automation/ha-automation-picker.ts b/src/panels/config/automation/ha-automation-picker.ts index 034fc12695..8f77c41275 100644 --- a/src/panels/config/automation/ha-automation-picker.ts +++ b/src/panels/config/automation/ha-automation-picker.ts @@ -138,6 +138,7 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) { @state() private _filteredAutomations?: string[] | null; + @state() @storage({ storage: "sessionStorage", key: "automation-table-search", @@ -146,6 +147,7 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) { }) private _filter = ""; + @state() @storage({ storage: "sessionStorage", key: "automation-table-filters-full", diff --git a/src/panels/config/automation/option/ha-automation-option.ts b/src/panels/config/automation/option/ha-automation-option.ts index 0bbe6e919d..8a3184a724 100644 --- a/src/panels/config/automation/option/ha-automation-option.ts +++ b/src/panels/config/automation/option/ha-automation-option.ts @@ -29,6 +29,7 @@ export default class HaAutomationOption extends LitElement { @state() private _showReorder = false; + @state() @storage({ key: "automationClipboard", state: true, diff --git a/src/panels/config/automation/trigger/ha-automation-trigger.ts b/src/panels/config/automation/trigger/ha-automation-trigger.ts index fabc23eef6..cc6219d0cc 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger.ts @@ -38,6 +38,7 @@ export default class HaAutomationTrigger extends LitElement { @state() private _showReorder = false; + @state() @storage({ key: "automationClipboard", state: true, diff --git a/src/panels/config/backup/ha-config-backup-backups.ts b/src/panels/config/backup/ha-config-backup-backups.ts index 30af43c705..969c99b275 100644 --- a/src/panels/config/backup/ha-config-backup-backups.ts +++ b/src/panels/config/backup/ha-config-backup-backups.ts @@ -98,6 +98,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { @state() private _selected: string[] = []; + @state() @storage({ storage: "sessionStorage", key: "backups-table-filters", diff --git a/src/panels/config/blueprint/ha-blueprint-overview.ts b/src/panels/config/blueprint/ha-blueprint-overview.ts index 6b329ff8e6..6ac304a956 100644 --- a/src/panels/config/blueprint/ha-blueprint-overview.ts +++ b/src/panels/config/blueprint/ha-blueprint-overview.ts @@ -9,7 +9,7 @@ import { } from "@mdi/js"; import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit"; import { LitElement, html } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import type { HASSDomEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event"; @@ -118,6 +118,7 @@ class HaBlueprintOverview extends LitElement { }) private _activeHiddenColumns?: string[]; + @state() @storage({ storage: "sessionStorage", key: "blueprint-table-search", @@ -499,9 +500,11 @@ class HaBlueprintOverview extends LitElement { list: html`
    ${[...(related.automation || []), ...(related.script || [])].map( (item) => { - const state = this.hass.states[item]; + const automationState = this.hass.states[item]; return html`
  • - ${state ? `${computeStateName(state)} (${item})` : item} + ${automationState + ? `${computeStateName(automationState)} (${item})` + : item}
  • `; } )} diff --git a/src/panels/config/devices/ha-config-devices-dashboard.ts b/src/panels/config/devices/ha-config-devices-dashboard.ts index cd1d4fc6b1..5d6d467cec 100644 --- a/src/panels/config/devices/ha-config-devices-dashboard.ts +++ b/src/panels/config/devices/ha-config-devices-dashboard.ts @@ -120,6 +120,7 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) { @state() private _selected: string[] = []; + @state() @storage({ storage: "sessionStorage", key: "devices-table-search", @@ -128,6 +129,7 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) { }) private _filter: string = history.state?.filter || ""; + @state() @storage({ storage: "sessionStorage", key: "devices-table-filters-full", diff --git a/src/panels/config/entities/ha-config-entities.ts b/src/panels/config/entities/ha-config-entities.ts index 4144c74571..807d03c380 100644 --- a/src/panels/config/entities/ha-config-entities.ts +++ b/src/panels/config/entities/ha-config-entities.ts @@ -159,6 +159,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { @consume({ context: fullEntitiesContext, subscribe: true }) _entities!: EntityRegistryEntry[]; + @state() @storage({ storage: "sessionStorage", key: "entities-table-search", @@ -169,6 +170,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { @state() private _searchParms = new URLSearchParams(window.location.search); + @state() @storage({ storage: "sessionStorage", key: "entities-table-filters", diff --git a/src/panels/config/helpers/ha-config-helpers.ts b/src/panels/config/helpers/ha-config-helpers.ts index 852720b1d8..b6de6abe4a 100644 --- a/src/panels/config/helpers/ha-config-helpers.ts +++ b/src/panels/config/helpers/ha-config-helpers.ts @@ -168,6 +168,7 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) { }) private _activeCollapsed?: string; + @state() @storage({ storage: "sessionStorage", key: "helpers-table-search", diff --git a/src/panels/config/integrations/integration-panels/mqtt/mqtt-config-panel.ts b/src/panels/config/integrations/integration-panels/mqtt/mqtt-config-panel.ts index c87fd42e0d..c3f9302fe4 100644 --- a/src/panels/config/integrations/integration-panels/mqtt/mqtt-config-panel.ts +++ b/src/panels/config/integrations/integration-panels/mqtt/mqtt-config-panel.ts @@ -1,7 +1,7 @@ import "@material/mwc-button"; import type { CSSResultGroup, TemplateResult } from "lit"; import { css, html, LitElement } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import { storage } from "../../../../../common/decorators/storage"; import "../../../../../components/ha-card"; import "../../../../../components/ha-code-editor"; @@ -23,6 +23,7 @@ export class MQTTConfigPanel extends LitElement { @property({ type: Boolean }) public narrow = false; + @state() @storage({ key: "panel-dev-mqtt-topic-ls", state: true, @@ -30,6 +31,7 @@ export class MQTTConfigPanel extends LitElement { }) private _topic = ""; + @state() @storage({ key: "panel-dev-mqtt-payload-ls", state: true, @@ -37,6 +39,7 @@ export class MQTTConfigPanel extends LitElement { }) private _payload = ""; + @state() @storage({ key: "panel-dev-mqtt-qos-ls", state: true, @@ -44,6 +47,7 @@ export class MQTTConfigPanel extends LitElement { }) private _qos = "0"; + @state() @storage({ key: "panel-dev-mqtt-retain-ls", state: true, @@ -51,6 +55,7 @@ export class MQTTConfigPanel extends LitElement { }) private _retain = false; + @state() @storage({ key: "panel-dev-mqtt-allow-template-ls", state: true, diff --git a/src/panels/config/integrations/integration-panels/mqtt/mqtt-subscribe-card.ts b/src/panels/config/integrations/integration-panels/mqtt/mqtt-subscribe-card.ts index b9c917dea7..a0506ca54c 100644 --- a/src/panels/config/integrations/integration-panels/mqtt/mqtt-subscribe-card.ts +++ b/src/panels/config/integrations/integration-panels/mqtt/mqtt-subscribe-card.ts @@ -21,6 +21,7 @@ const qosLevel = ["0", "1", "2"]; class MqttSubscribeCard extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; + @state() @storage({ key: "panel-dev-mqtt-topic-subscribe", state: true, @@ -28,6 +29,7 @@ class MqttSubscribeCard extends LitElement { }) private _topic = ""; + @state() @storage({ key: "panel-dev-mqtt-qos-subscribe", state: true, @@ -35,6 +37,7 @@ class MqttSubscribeCard extends LitElement { }) private _qos = "0"; + @state() @storage({ key: "panel-dev-mqtt-json-format", state: true, diff --git a/src/panels/config/labels/ha-config-labels.ts b/src/panels/config/labels/ha-config-labels.ts index 77ed20ff72..0f6bfdc527 100644 --- a/src/panels/config/labels/ha-config-labels.ts +++ b/src/panels/config/labels/ha-config-labels.ts @@ -55,6 +55,7 @@ export class HaConfigLabels extends LitElement { @state() private _labels: LabelRegistryEntry[] = []; + @state() @storage({ storage: "sessionStorage", key: "labels-table-search", diff --git a/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts b/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts index 56f1a398f6..1dce7fa90f 100644 --- a/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts +++ b/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts @@ -74,6 +74,7 @@ export class HaConfigLovelaceDashboards extends LitElement { @state() private _dashboards: LovelaceDashboard[] = []; + @state() @storage({ storage: "sessionStorage", key: "lovelace-dashboards-table-search", diff --git a/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts b/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts index 73a7847f91..24feda1234 100644 --- a/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts +++ b/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts @@ -46,6 +46,7 @@ export class HaConfigLovelaceRescources extends LitElement { @state() private _resources: LovelaceResource[] = []; + @state() @storage({ storage: "sessionStorage", key: "lovelace-resources-table-search", diff --git a/src/panels/config/scene/ha-scene-dashboard.ts b/src/panels/config/scene/ha-scene-dashboard.ts index 01b2112f64..f01a23ce3b 100644 --- a/src/panels/config/scene/ha-scene-dashboard.ts +++ b/src/panels/config/scene/ha-scene-dashboard.ts @@ -133,6 +133,7 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) { @state() private _filteredScenes?: string[] | null; + @state() @storage({ storage: "sessionStorage", key: "scene-table-search", @@ -141,6 +142,7 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) { }) private _filter = ""; + @state() @storage({ storage: "sessionStorage", key: "scene-table-filters-full", diff --git a/src/panels/config/script/ha-script-editor.ts b/src/panels/config/script/ha-script-editor.ts index b4121a0125..a0e1b0c7ea 100644 --- a/src/panels/config/script/ha-script-editor.ts +++ b/src/panels/config/script/ha-script-editor.ts @@ -105,6 +105,7 @@ export class HaScriptEditor extends SubscribeMixin( @state() private _readOnly = false; + @state() @consume({ context: fullEntitiesContext, subscribe: true }) @transform({ transformer: function (this: HaScriptEditor, value) { diff --git a/src/panels/config/script/ha-script-picker.ts b/src/panels/config/script/ha-script-picker.ts index ef32f99ad7..3265cb158c 100644 --- a/src/panels/config/script/ha-script-picker.ts +++ b/src/panels/config/script/ha-script-picker.ts @@ -138,6 +138,7 @@ class HaScriptPicker extends SubscribeMixin(LitElement) { @state() private _filteredScripts?: string[] | null; + @state() @storage({ storage: "sessionStorage", key: "script-table-search", @@ -146,6 +147,7 @@ class HaScriptPicker extends SubscribeMixin(LitElement) { }) private _filter = ""; + @state() @storage({ storage: "sessionStorage", key: "script-table-filters-full", diff --git a/src/panels/config/tags/ha-config-tags.ts b/src/panels/config/tags/ha-config-tags.ts index 6bbcc4d76a..efa5370cb3 100644 --- a/src/panels/config/tags/ha-config-tags.ts +++ b/src/panels/config/tags/ha-config-tags.ts @@ -62,6 +62,7 @@ export class HaConfigTags extends SubscribeMixin(LitElement) { return this.hass.auth.external?.config.canWriteTag; } + @state() @storage({ storage: "sessionStorage", key: "tags-table-search", diff --git a/src/panels/config/voice-assistants/ha-config-voice-assistants-expose.ts b/src/panels/config/voice-assistants/ha-config-voice-assistants-expose.ts index 4118de6f14..46a5cbba49 100644 --- a/src/panels/config/voice-assistants/ha-config-voice-assistants-expose.ts +++ b/src/panels/config/voice-assistants/ha-config-voice-assistants-expose.ts @@ -76,6 +76,7 @@ export class VoiceAssistantsExpose extends LitElement { @state() private _extEntities?: Record; + @state() @storage({ storage: "sessionStorage", key: "voice-expose-table-search", diff --git a/src/panels/developer-tools/action/developer-tools-action.ts b/src/panels/developer-tools/action/developer-tools-action.ts index 979c653ffc..fd38101a02 100644 --- a/src/panels/developer-tools/action/developer-tools-action.ts +++ b/src/panels/developer-tools/action/developer-tools-action.ts @@ -52,6 +52,7 @@ class HaPanelDevAction extends LitElement { private _yamlValid = true; + @state() @storage({ key: "panel-dev-action-state-service-data", state: true, @@ -59,6 +60,7 @@ class HaPanelDevAction extends LitElement { }) private _serviceData?: ServiceAction = { action: "", target: {}, data: {} }; + @state() @storage({ key: "panel-dev-action-state-yaml-mode", state: true, diff --git a/src/panels/developer-tools/assist/developer-tools-assist.ts b/src/panels/developer-tools/assist/developer-tools-assist.ts index a10e169ea2..220c12300d 100644 --- a/src/panels/developer-tools/assist/developer-tools-assist.ts +++ b/src/panels/developer-tools/assist/developer-tools-assist.ts @@ -33,6 +33,7 @@ class HaPanelDevAssist extends SubscribeMixin(LitElement) { @state() supportedLanguages?: string[]; + @state() @storage({ key: "assist_debug_language", state: true, diff --git a/src/panels/developer-tools/state/developer-tools-state.ts b/src/panels/developer-tools/state/developer-tools-state.ts index 888442a12e..047b4aaaa2 100644 --- a/src/panels/developer-tools/state/developer-tools-state.ts +++ b/src/panels/developer-tools/state/developer-tools-state.ts @@ -62,6 +62,7 @@ class HaPanelDevState extends LitElement { @state() private _validJSON = true; + @state() @storage({ key: "devToolsShowAttributes", state: true, diff --git a/src/panels/history/ha-panel-history.ts b/src/panels/history/ha-panel-history.ts index cc59c9342f..b52f3eaf19 100644 --- a/src/panels/history/ha-panel-history.ts +++ b/src/panels/history/ha-panel-history.ts @@ -63,6 +63,7 @@ class HaPanelHistory extends LitElement { @state() private _endDate: Date; + @state() @storage({ key: "historyPickedValue", state: true, diff --git a/src/panels/logbook/ha-panel-logbook.ts b/src/panels/logbook/ha-panel-logbook.ts index df556370ba..037f5da57d 100644 --- a/src/panels/logbook/ha-panel-logbook.ts +++ b/src/panels/logbook/ha-panel-logbook.ts @@ -39,6 +39,7 @@ export class HaPanelLogbook extends LitElement { @state() private _showBack?: boolean; + @state() @storage({ key: "logbookPickedValue", state: true, diff --git a/src/panels/lovelace/cards/energy/hui-energy-devices-detail-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-devices-detail-graph-card.ts index 47e484d21c..46d39042ab 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-devices-detail-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-devices-detail-graph-card.ts @@ -62,6 +62,7 @@ export class HuiEnergyDevicesDetailGraphCard @state() private _compareEnd?: Date; + @state() @storage({ key: "energy-devices-hidden-stats", state: true, diff --git a/src/panels/lovelace/cards/hui-button-card.ts b/src/panels/lovelace/cards/hui-button-card.ts index 068f8e3866..9b6dfcc812 100644 --- a/src/panels/lovelace/cards/hui-button-card.ts +++ b/src/panels/lovelace/cards/hui-button-card.ts @@ -86,6 +86,7 @@ export class HuiButtonCard extends LitElement implements LovelaceCard { @state() private _config?: ButtonCardConfig; + @state() @consume({ context: statesContext, subscribe: true }) @transform({ transformer: function (this: HuiButtonCard, value: HassEntities) { @@ -111,6 +112,7 @@ export class HuiButtonCard extends LitElement implements LovelaceCard { @consume({ context: configContext, subscribe: true }) _hassConfig!: HassConfig; + @state() @consume({ context: entitiesContext, subscribe: true }) @transform({ transformer: function (this: HuiButtonCard, value) { diff --git a/src/panels/lovelace/editor/badge-editor/hui-badge-picker.ts b/src/panels/lovelace/editor/badge-editor/hui-badge-picker.ts index 1e720a4632..af0030c52c 100644 --- a/src/panels/lovelace/editor/badge-editor/hui-badge-picker.ts +++ b/src/panels/lovelace/editor/badge-editor/hui-badge-picker.ts @@ -43,6 +43,7 @@ export class HuiBadgePicker extends LitElement { @property({ attribute: false }) public suggestedBadges?: string[]; + @state() @storage({ key: "dashboardBadgeClipboard", state: true, diff --git a/src/panels/lovelace/editor/card-editor/hui-card-picker.ts b/src/panels/lovelace/editor/card-editor/hui-card-picker.ts index edb1c704fd..f663f36021 100644 --- a/src/panels/lovelace/editor/card-editor/hui-card-picker.ts +++ b/src/panels/lovelace/editor/card-editor/hui-card-picker.ts @@ -42,6 +42,7 @@ export class HuiCardPicker extends LitElement { @property({ attribute: false }) public suggestedCards?: string[]; + @state() @storage({ key: "dashboardCardClipboard", state: true, diff --git a/src/panels/media-browser/ha-panel-media-browser.ts b/src/panels/media-browser/ha-panel-media-browser.ts index 05b2799ae5..d84f09a703 100644 --- a/src/panels/media-browser/ha-panel-media-browser.ts +++ b/src/panels/media-browser/ha-panel-media-browser.ts @@ -64,6 +64,7 @@ class PanelMediaBrowser extends LitElement { @state() _currentItem?: MediaPlayerItem; + @state() @storage({ key: "mediaBrowserPreferredLayout", state: true, @@ -78,6 +79,7 @@ class PanelMediaBrowser extends LitElement { }, ]; + @state() @storage({ key: "mediaBrowseEntityId", state: true, diff --git a/src/panels/todo/ha-panel-todo.ts b/src/panels/todo/ha-panel-todo.ts index d6c2369822..9dcb961478 100644 --- a/src/panels/todo/ha-panel-todo.ts +++ b/src/panels/todo/ha-panel-todo.ts @@ -9,7 +9,7 @@ import { } from "@mdi/js"; import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit"; import { LitElement, css, html, nothing } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { isComponentLoaded } from "../../common/config/is_component_loaded"; import { storage } from "../../common/decorators/storage"; @@ -55,6 +55,7 @@ class PanelTodo extends LitElement { @property({ type: Boolean, reflect: true }) public mobile = false; + @state() @storage({ key: "selectedTodoEntity", state: true, From 1154d1769d016795c86fd7bdece3bcc080d3b1d8 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 2 May 2025 21:34:23 +0300 Subject: [PATCH 17/36] Bumped version to 20250502.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cb3fd4db2f..7f9b269a72 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20250430.2" +version = "20250502.0" license = "Apache-2.0" license-files = ["LICENSE*"] description = "The Home Assistant frontend" From 74488c0b96cc25cb7d6966c4a1cd6c5101317fe7 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Mon, 5 May 2025 11:12:43 +0200 Subject: [PATCH 18/36] Use new entity picker style in quick bar (#25265) * Use new entity picker style in quick bar * Cleanup * Add missing no area * Process code review --- src/dialogs/quick-bar/ha-quick-bar.ts | 162 ++++++++++++++++++++------ 1 file changed, 124 insertions(+), 38 deletions(-) diff --git a/src/dialogs/quick-bar/ha-quick-bar.ts b/src/dialogs/quick-bar/ha-quick-bar.ts index 828397f353..cf57a14e31 100644 --- a/src/dialogs/quick-bar/ha-quick-bar.ts +++ b/src/dialogs/quick-bar/ha-quick-bar.ts @@ -15,24 +15,26 @@ import { customElement, property, query, state } from "lit/decorators"; import { ifDefined } from "lit/directives/if-defined"; import { styleMap } from "lit/directives/style-map"; import memoizeOne from "memoize-one"; +import Fuse from "fuse.js"; import { canShowPage } from "../../common/config/can_show_page"; import { componentsWithService } from "../../common/config/components_with_service"; import { isComponentLoaded } from "../../common/config/is_component_loaded"; import { fireEvent } from "../../common/dom/fire_event"; -import { computeDeviceNameDisplay } from "../../common/entity/compute_device_name"; -import { computeStateName } from "../../common/entity/compute_state_name"; +import { + computeDeviceName, + computeDeviceNameDisplay, +} from "../../common/entity/compute_device_name"; import { navigate } from "../../common/navigate"; import { caseInsensitiveStringCompare } from "../../common/string/compare"; import type { ScorableTextItem } from "../../common/string/filter/sequence-matching"; -import { fuzzyFilterSort } from "../../common/string/filter/sequence-matching"; import { debounce } from "../../common/util/debounce"; import "../../components/ha-icon-button"; import "../../components/ha-label"; import "../../components/ha-list"; -import "../../components/ha-list-item"; import "../../components/ha-spinner"; import "../../components/ha-textfield"; import "../../components/ha-tip"; +import "../../components/ha-md-list-item"; import { fetchHassioAddonsInfo } from "../../data/hassio/addon"; import { domainToName } from "../../data/integration"; import { getPanelNameTranslationKey } from "../../data/panel"; @@ -44,6 +46,13 @@ import type { HomeAssistant } from "../../types"; import { showConfirmationDialog } from "../generic/show-dialog-box"; import { showShortcutsDialog } from "../shortcuts/show-shortcuts-dialog"; import { QuickBarMode, type QuickBarParams } from "./show-dialog-quick-bar"; +import { getEntityContext } from "../../common/entity/context/get_entity_context"; +import { computeEntityName } from "../../common/entity/compute_entity_name"; +import { computeAreaName } from "../../common/entity/compute_area_name"; +import { computeRTL } from "../../common/util/compute_rtl"; +import { computeDomain } from "../../common/entity/compute_domain"; +import { computeStateName } from "../../common/entity/compute_state_name"; +import { HaFuse } from "../../resources/fuse"; interface QuickBarItem extends ScorableTextItem { primaryText: string; @@ -59,6 +68,9 @@ interface CommandItem extends QuickBarItem { interface EntityItem extends QuickBarItem { altText: string; icon?: TemplateResult; + translatedDomain: string; + entityId: string; + friendlyName: string; } interface DeviceItem extends QuickBarItem { @@ -82,6 +94,23 @@ type BaseNavigationCommand = Pick< QuickBarNavigationItem, "primaryText" | "path" >; + +const DOMAIN_STYLE = styleMap({ + fontSize: "var(--ha-font-size-s)", + fontWeight: "var(--ha-font-weight-normal)", + lineHeight: "var(--ha-line-height-normal)", + alignSelf: "flex-end", + maxWidth: "30%", + textOverflow: "ellipsis", + overflow: "hidden", + whiteSpace: "nowrap", +}); + +const ENTITY_ID_STYLE = styleMap({ + fontFamily: "var(--ha-font-family-code)", + fontSize: "var(--ha-font-size-xs)", +}); + @customElement("ha-quick-bar") export class QuickBar extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -139,6 +168,11 @@ export class QuickBar extends LitElement { } } + protected firstUpdated(changedProps) { + super.firstUpdated(changedProps); + this.hass.loadBackendTranslation("title"); + } + private _getItems = memoizeOne( ( mode: QuickBarMode, @@ -323,61 +357,65 @@ export class QuickBar extends LitElement { private _renderDeviceItem(item: DeviceItem, index?: number) { return html` - - ${item.primaryText} + ${item.primaryText} ${item.area - ? html` - ${item.area} - ` + ? html` ${item.area} ` : nothing} - + `; } private _renderEntityItem(item: EntityItem, index?: number) { + const showEntityId = this.hass.userData?.showEntityIdPicker; + return html` - ${item.iconPath ? html` ` - : html`${item.icon}`} - ${item.primaryText} + : html`${item.icon}`} + ${item.primaryText} ${item.altText - ? html` - ${item.altText} - ` + ? html` ${item.altText} ` : nothing} - + ${item.entityId && showEntityId + ? html`${item.entityId}` + : nothing} + ${item.translatedDomain && !showEntityId + ? html`
    + ${item.translatedDomain} +
    ` + : nothing} + `; } private _renderCommandItem(item: CommandItem, index?: number) { return html` - ${item.iconPath ? html` - + ` : nothing} ${item.categoryText} @@ -394,7 +435,7 @@ export class QuickBar extends LitElement { ${item.primaryText} - + `; } @@ -421,7 +462,7 @@ export class QuickBar extends LitElement { } private _getItemAtIndex(index: number): ListItem | null { - return this.renderRoot.querySelector(`ha-list-item[index="${index}"]`); + return this.renderRoot.querySelector(`ha-md-list-item[index="${index}"]`); } private _addSpinnerToCommandItem(index: number): void { @@ -519,7 +560,7 @@ export class QuickBar extends LitElement { } private _handleItemClick(ev) { - const listItem = ev.target.closest("ha-list-item"); + const listItem = ev.target.closest("ha-md-list-item"); this._processItemAndCloseDialog( listItem.item, Number(listItem.getAttribute("index")) @@ -555,18 +596,43 @@ export class QuickBar extends LitElement { } private _generateEntityItems(): EntityItem[] { + const isRTL = computeRTL(this.hass); + return Object.keys(this.hass.states) .map((entityId) => { - const entityState = this.hass.states[entityId]; + const stateObj = this.hass.states[entityId]; + + const { area, device } = getEntityContext(stateObj, this.hass); + + const friendlyName = computeStateName(stateObj); // Keep this for search + const entityName = computeEntityName(stateObj, this.hass); + const deviceName = device ? computeDeviceName(device) : undefined; + const areaName = area ? computeAreaName(area) : undefined; + + const primary = entityName || deviceName || entityId; + const secondary = [areaName, entityName ? deviceName : undefined] + .filter(Boolean) + .join(isRTL ? " ◂ " : " ▸ "); + + const translatedDomain = domainToName( + this.hass.localize, + computeDomain(entityId) + ); + const entityItem = { - primaryText: computeStateName(entityState), - altText: entityId, + primaryText: primary, + altText: + secondary || + this.hass.localize("ui.components.device-picker.no_area"), icon: html` `, + translatedDomain: translatedDomain, + entityId: entityId, + friendlyName: friendlyName, action: () => fireEvent(this, "hass-more-info", { entityId }), }; @@ -846,9 +912,30 @@ export class QuickBar extends LitElement { }); } + private _fuseIndex = memoizeOne((items: QuickBarItem[]) => + Fuse.createIndex( + [ + "primaryText", + "altText", + "friendlyName", + "translatedDomain", + "entityId", // for technical search + ], + items + ) + ); + private _filterItems = memoizeOne( - (items: QuickBarItem[], filter: string): QuickBarItem[] => - fuzzyFilterSort(filter.trimLeft(), items) + (items: QuickBarItem[], filter: string): QuickBarItem[] => { + const index = this._fuseIndex(items); + const fuse = new HaFuse(items, {}, index); + + const results = fuse.multiTermsSearch(filter.trim()); + if (!results || !results.length) { + return items; + } + return results.map((result) => result.item); + } ); static get styles() { @@ -930,9 +1017,8 @@ export class QuickBar extends LitElement { direction: var(--direction); } - ha-list-item { + ha-md-list-item { width: 100%; - --mdc-list-item-graphic-margin: 20px; } ha-tip { From cfc7f91f0313c58fc661e4cad1861ed88d76e8ef Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Sun, 4 May 2025 14:41:01 +0300 Subject: [PATCH 19/36] Fix select entity row opening more info on select (#25292) --- src/panels/lovelace/entity-rows/hui-select-entity-row.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/panels/lovelace/entity-rows/hui-select-entity-row.ts b/src/panels/lovelace/entity-rows/hui-select-entity-row.ts index fa7fb03ca2..17c6dbf2d9 100644 --- a/src/panels/lovelace/entity-rows/hui-select-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-select-entity-row.ts @@ -93,6 +93,7 @@ class HuiSelectEntityRow extends LitElement implements LovelaceRow { `; private _handleAction(ev): void { + ev.stopPropagation(); const stateObj = this.hass!.states[this._config!.entity] as SelectEntity; const option = ev.target.value; From 55770f3e023059acfbc26895c0292224fb822f42 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Sun, 4 May 2025 10:38:01 +0300 Subject: [PATCH 20/36] Fix display of disabled items in traces (#25293) --- src/components/trace/ha-timeline.ts | 6 +++--- src/components/trace/hat-graph-node.ts | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/trace/ha-timeline.ts b/src/components/trace/ha-timeline.ts index b7ab5b2757..4ee033573c 100644 --- a/src/components/trace/ha-timeline.ts +++ b/src/components/trace/ha-timeline.ts @@ -11,8 +11,8 @@ export class HaTimeline extends LitElement { @property({ type: Boolean, reflect: true }) public raised = false; - @property({ attribute: false, reflect: true, type: Boolean }) notEnabled = - false; + @property({ attribute: "not-enabled", reflect: true, type: Boolean }) + notEnabled = false; @property({ attribute: "last-item", type: Boolean }) public lastItem = false; @@ -82,7 +82,7 @@ export class HaTimeline extends LitElement { margin-inline-start: initial; width: 24px; } - :host([notEnabled]) ha-svg-icon { + :host([not-enabled]) ha-svg-icon { opacity: 0.5; } ha-svg-icon { diff --git a/src/components/trace/hat-graph-node.ts b/src/components/trace/hat-graph-node.ts index 330338b7cc..10ea69d58b 100644 --- a/src/components/trace/hat-graph-node.ts +++ b/src/components/trace/hat-graph-node.ts @@ -17,8 +17,8 @@ export class HatGraphNode extends LitElement { @property({ type: Boolean }) public error = false; - @property({ attribute: false, reflect: true, type: Boolean }) notEnabled = - false; + @property({ attribute: "not-enabled", reflect: true, type: Boolean }) + notEnabled = false; @property({ attribute: "graph-start", reflect: true, type: Boolean }) graphStart = false; @@ -127,13 +127,13 @@ export class HatGraphNode extends LitElement { --stroke-clr: var(--hover-clr); --icon-clr: var(--default-icon-clr); } - :host([notEnabled]) circle { + :host([not-enabled]) circle { --stroke-clr: var(--disabled-clr); } - :host([notEnabled][active]) circle { + :host([not-enabled][active]) circle { --stroke-clr: var(--disabled-active-clr); } - :host([notEnabled]:hover) circle { + :host([not-enabled]:hover) circle { --stroke-clr: var(--disabled-hover-clr); } svg:not(.safari) { From f6e36f203854b2df8c7605c8a03dbc236247b295 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 5 May 2025 01:33:52 -0400 Subject: [PATCH 21/36] Add profile security link to My Home Assistant (#25303) --- src/panels/my/ha-panel-my.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/panels/my/ha-panel-my.ts b/src/panels/my/ha-panel-my.ts index cd78c52e55..c308cc32e4 100644 --- a/src/panels/my/ha-panel-my.ts +++ b/src/panels/my/ha-panel-my.ts @@ -255,6 +255,9 @@ export const getMyRedirects = (): Redirects => ({ profile: { redirect: "/profile", }, + profile_security: { + redirect: "/profile/security", + }, logbook: { component: "logbook", redirect: "/logbook", From 75e9ac9e7351f8e153f04dfbbd443b331a4dde26 Mon Sep 17 00:00:00 2001 From: Wendelin <12148533+wendevlin@users.noreply.github.com> Date: Mon, 5 May 2025 09:40:54 +0200 Subject: [PATCH 22/36] Fix flow-form header (#25305) --- src/dialogs/config-flow/step-flow-form.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/dialogs/config-flow/step-flow-form.ts b/src/dialogs/config-flow/step-flow-form.ts index 90b301b98a..0c6da5eda2 100644 --- a/src/dialogs/config-flow/step-flow-form.ts +++ b/src/dialogs/config-flow/step-flow-form.ts @@ -278,10 +278,6 @@ class StepFlowForm extends LitElement { } h2 { word-break: break-word; - padding-right: 72px; - padding-inline-end: 72px; - padding-inline-start: initial; - direction: var(--direction); } `, ]; From 868daf692dbdbc76f3eb5f719211bf19e224476d Mon Sep 17 00:00:00 2001 From: Wendelin <12148533+wendevlin@users.noreply.github.com> Date: Mon, 5 May 2025 10:01:35 +0200 Subject: [PATCH 23/36] Use md-select for entity-row and state-card (#25307) --- .../hui-input-select-entity-row.ts | 20 +++++++------- src/state-summary/state-card-input_select.ts | 21 +++++++-------- src/state-summary/state-card-select.ts | 27 +++++++++---------- 3 files changed, 31 insertions(+), 37 deletions(-) diff --git a/src/panels/lovelace/entity-rows/hui-input-select-entity-row.ts b/src/panels/lovelace/entity-rows/hui-input-select-entity-row.ts index 99fa0a443d..d5bf01ae11 100644 --- a/src/panels/lovelace/entity-rows/hui-input-select-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-input-select-entity-row.ts @@ -3,8 +3,8 @@ import { css, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { stopPropagation } from "../../../common/dom/stop_propagation"; import { computeStateName } from "../../../common/entity/compute_state_name"; -import "../../../components/ha-list-item"; -import "../../../components/ha-select"; +import "../../../components/ha-md-select"; +import "../../../components/ha-md-select-option"; import { UNAVAILABLE } from "../../../data/entity"; import { forwardHaptic } from "../../../data/haptics"; import type { InputSelectEntity } from "../../../data/input_select"; @@ -57,25 +57,26 @@ class HuiInputSelectEntityRow extends LitElement implements LovelaceRow { .config=${this._config} hide-name > - ${stateObj.attributes.options ? stateObj.attributes.options.map( (option) => - html`${option}` + html` +
    ${option}
    +
    ` ) - : ""} -
    + : nothing} + `; } @@ -85,9 +86,8 @@ class HuiInputSelectEntityRow extends LitElement implements LovelaceRow { display: flex; align-items: center; } - ha-select { + ha-md-select { width: 100%; - --ha-select-min-width: 0; } `; diff --git a/src/state-summary/state-card-input_select.ts b/src/state-summary/state-card-input_select.ts index d08fdccc1f..9f6cbbd112 100644 --- a/src/state-summary/state-card-input_select.ts +++ b/src/state-summary/state-card-input_select.ts @@ -1,11 +1,10 @@ import type { TemplateResult } from "lit"; import { css, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; -import { stopPropagation } from "../common/dom/stop_propagation"; import { computeStateName } from "../common/entity/compute_state_name"; import "../components/entity/state-badge"; -import "../components/ha-list-item"; -import "../components/ha-select"; +import "../components/ha-md-select"; +import "../components/ha-md-select-option"; import { UNAVAILABLE } from "../data/entity"; import type { InputSelectEntity } from "../data/input_select"; import { setInputSelectOption } from "../data/input_select"; @@ -20,23 +19,21 @@ class StateCardInputSelect extends LitElement { protected render(): TemplateResult { return html` - ${this.stateObj.attributes.options.map( (option) => - html`${option}` + html` +
    ${option}
    +
    ` )} -
    + `; } @@ -58,7 +55,7 @@ class StateCardInputSelect extends LitElement { margin-top: 10px; } - ha-select { + ha-md-select { width: 100%; } `; diff --git a/src/state-summary/state-card-select.ts b/src/state-summary/state-card-select.ts index 9027a17100..372cfb8c5d 100644 --- a/src/state-summary/state-card-select.ts +++ b/src/state-summary/state-card-select.ts @@ -1,11 +1,10 @@ import type { TemplateResult } from "lit"; import { css, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; -import { stopPropagation } from "../common/dom/stop_propagation"; import { computeStateName } from "../common/entity/compute_state_name"; import "../components/entity/state-badge"; -import "../components/ha-list-item"; -import "../components/ha-select"; +import "../components/ha-md-select"; +import "../components/ha-md-select-option"; import { UNAVAILABLE } from "../data/entity"; import type { SelectEntity } from "../data/select"; import { setSelectOption } from "../data/select"; @@ -20,24 +19,22 @@ class StateCardSelect extends LitElement { protected render(): TemplateResult { return html` - ${this.stateObj.attributes.options.map( (option) => html` - - ${this.hass.formatEntityState(this.stateObj, option)} - + +
    + ${this.hass.formatEntityState(this.stateObj, option)} +
    +
    ` )} -
    + `; } @@ -59,7 +56,7 @@ class StateCardSelect extends LitElement { margin-top: 10px; } - ha-select { + ha-md-select { width: 100%; } `; From 3121721ac75ac973b71b69e91d204512e6bfd836 Mon Sep 17 00:00:00 2001 From: Wendelin <12148533+wendevlin@users.noreply.github.com> Date: Mon, 5 May 2025 10:57:50 +0200 Subject: [PATCH 24/36] Revert "Use md-select for entity-row and state-card" (#25308) Revert "Use md-select for entity-row and state-card (#25307)" This reverts commit 3c9dce20e2679bb14d9dd6caecc38e47ab919209. --- .../hui-input-select-entity-row.ts | 20 +++++++------- src/state-summary/state-card-input_select.ts | 21 ++++++++------- src/state-summary/state-card-select.ts | 27 ++++++++++--------- 3 files changed, 37 insertions(+), 31 deletions(-) diff --git a/src/panels/lovelace/entity-rows/hui-input-select-entity-row.ts b/src/panels/lovelace/entity-rows/hui-input-select-entity-row.ts index d5bf01ae11..99fa0a443d 100644 --- a/src/panels/lovelace/entity-rows/hui-input-select-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-input-select-entity-row.ts @@ -3,8 +3,8 @@ import { css, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { stopPropagation } from "../../../common/dom/stop_propagation"; import { computeStateName } from "../../../common/entity/compute_state_name"; -import "../../../components/ha-md-select"; -import "../../../components/ha-md-select-option"; +import "../../../components/ha-list-item"; +import "../../../components/ha-select"; import { UNAVAILABLE } from "../../../data/entity"; import { forwardHaptic } from "../../../data/haptics"; import type { InputSelectEntity } from "../../../data/input_select"; @@ -57,26 +57,25 @@ class HuiInputSelectEntityRow extends LitElement implements LovelaceRow { .config=${this._config} hide-name > - ${stateObj.attributes.options ? stateObj.attributes.options.map( (option) => - html` -
    ${option}
    -
    ` + html`${option}` ) - : nothing} -
    + : ""} + `; } @@ -86,8 +85,9 @@ class HuiInputSelectEntityRow extends LitElement implements LovelaceRow { display: flex; align-items: center; } - ha-md-select { + ha-select { width: 100%; + --ha-select-min-width: 0; } `; diff --git a/src/state-summary/state-card-input_select.ts b/src/state-summary/state-card-input_select.ts index 9f6cbbd112..d08fdccc1f 100644 --- a/src/state-summary/state-card-input_select.ts +++ b/src/state-summary/state-card-input_select.ts @@ -1,10 +1,11 @@ import type { TemplateResult } from "lit"; import { css, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; +import { stopPropagation } from "../common/dom/stop_propagation"; import { computeStateName } from "../common/entity/compute_state_name"; import "../components/entity/state-badge"; -import "../components/ha-md-select"; -import "../components/ha-md-select-option"; +import "../components/ha-list-item"; +import "../components/ha-select"; import { UNAVAILABLE } from "../data/entity"; import type { InputSelectEntity } from "../data/input_select"; import { setInputSelectOption } from "../data/input_select"; @@ -19,21 +20,23 @@ class StateCardInputSelect extends LitElement { protected render(): TemplateResult { return html` - ${this.stateObj.attributes.options.map( (option) => - html` -
    ${option}
    -
    ` + html`${option}` )} -
    + `; } @@ -55,7 +58,7 @@ class StateCardInputSelect extends LitElement { margin-top: 10px; } - ha-md-select { + ha-select { width: 100%; } `; diff --git a/src/state-summary/state-card-select.ts b/src/state-summary/state-card-select.ts index 372cfb8c5d..9027a17100 100644 --- a/src/state-summary/state-card-select.ts +++ b/src/state-summary/state-card-select.ts @@ -1,10 +1,11 @@ import type { TemplateResult } from "lit"; import { css, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; +import { stopPropagation } from "../common/dom/stop_propagation"; import { computeStateName } from "../common/entity/compute_state_name"; import "../components/entity/state-badge"; -import "../components/ha-md-select"; -import "../components/ha-md-select-option"; +import "../components/ha-list-item"; +import "../components/ha-select"; import { UNAVAILABLE } from "../data/entity"; import type { SelectEntity } from "../data/select"; import { setSelectOption } from "../data/select"; @@ -19,22 +20,24 @@ class StateCardSelect extends LitElement { protected render(): TemplateResult { return html` - ${this.stateObj.attributes.options.map( (option) => html` - -
    - ${this.hass.formatEntityState(this.stateObj, option)} -
    -
    + + ${this.hass.formatEntityState(this.stateObj, option)} + ` )} -
    + `; } @@ -56,7 +59,7 @@ class StateCardSelect extends LitElement { margin-top: 10px; } - ha-md-select { + ha-select { width: 100%; } `; From 5cd68301ed46243dfdbe8369c5d96d570e6bfd1a Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 5 May 2025 11:42:21 +0200 Subject: [PATCH 25/36] Fix pasting yaml in automation code editor (#25309) Fix pasting yaml in code editor --- src/common/dom/can-override-input.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/common/dom/can-override-input.ts b/src/common/dom/can-override-input.ts index d0896ba0a6..06aa28a8b0 100644 --- a/src/common/dom/can-override-input.ts +++ b/src/common/dom/can-override-input.ts @@ -1,5 +1,11 @@ export const canOverrideAlphanumericInput = (composedPath: EventTarget[]) => { - if (composedPath.some((el) => "tagName" in el && el.tagName === "HA-MENU")) { + if ( + composedPath.some( + (el) => + "tagName" in el && + (el.tagName === "HA-MENU" || el.tagName === "HA-CODE-EDITOR") + ) + ) { return false; } From 376cac60027f8848c59104b4faa2ce4c59061eec Mon Sep 17 00:00:00 2001 From: Wendelin <12148533+wendevlin@users.noreply.github.com> Date: Mon, 5 May 2025 11:13:50 +0200 Subject: [PATCH 26/36] Fix select entity change (#25310) --- src/panels/lovelace/components/hui-generic-entity-row.ts | 1 + src/panels/lovelace/entity-rows/hui-select-entity-row.ts | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/lovelace/components/hui-generic-entity-row.ts b/src/panels/lovelace/components/hui-generic-entity-row.ts index 1ae552452f..26c51b773b 100644 --- a/src/panels/lovelace/components/hui-generic-entity-row.ts +++ b/src/panels/lovelace/components/hui-generic-entity-row.ts @@ -163,6 +163,7 @@ export class HuiGenericEntityRow extends LitElement { @touchend=${stopPropagation} @keydown=${stopPropagation} @click=${stopPropagation} + @action=${stopPropagation} >`}
`; diff --git a/src/panels/lovelace/entity-rows/hui-select-entity-row.ts b/src/panels/lovelace/entity-rows/hui-select-entity-row.ts index 17c6dbf2d9..fa7fb03ca2 100644 --- a/src/panels/lovelace/entity-rows/hui-select-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-select-entity-row.ts @@ -93,7 +93,6 @@ class HuiSelectEntityRow extends LitElement implements LovelaceRow { `; private _handleAction(ev): void { - ev.stopPropagation(); const stateObj = this.hass!.states[this._config!.entity] as SelectEntity; const option = ev.target.value; From 3e053e07c6762ad00d2a87d8373009be5ece2649 Mon Sep 17 00:00:00 2001 From: Wendelin <12148533+wendevlin@users.noreply.github.com> Date: Mon, 5 May 2025 13:11:42 +0200 Subject: [PATCH 27/36] Fix selected entity overflow (#25311) --- src/panels/lovelace/editor/hui-entities-card-row-editor.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/panels/lovelace/editor/hui-entities-card-row-editor.ts b/src/panels/lovelace/editor/hui-entities-card-row-editor.ts index 06d9ab0f35..ab574af88d 100644 --- a/src/panels/lovelace/editor/hui-entities-card-row-editor.ts +++ b/src/panels/lovelace/editor/hui-entities-card-row-editor.ts @@ -210,6 +210,7 @@ export class HuiEntitiesCardRowEditor extends LitElement { .entity ha-entity-picker { flex-grow: 1; + min-width: 0; } .special-row { From 70fef59401ffc3466075ff1e3fb3faae491b099e Mon Sep 17 00:00:00 2001 From: Wendelin <12148533+wendevlin@users.noreply.github.com> Date: Mon, 5 May 2025 19:20:22 +0200 Subject: [PATCH 28/36] Fix options and repair flow success (#25312) --- .../config-flow/step-flow-create-entry.ts | 129 +++++++++--------- 1 file changed, 66 insertions(+), 63 deletions(-) diff --git a/src/dialogs/config-flow/step-flow-create-entry.ts b/src/dialogs/config-flow/step-flow-create-entry.ts index da5ab1d183..6f59bf593d 100644 --- a/src/dialogs/config-flow/step-flow-create-entry.ts +++ b/src/dialogs/config-flow/step-flow-create-entry.ts @@ -129,70 +129,73 @@ class StepFlowCreateEntry extends LitElement { )}` : nothing} - ${devices.length === 0 - ? html`

- ${localize( - "ui.panel.config.integrations.config_flow.created_config", - { name: this.step.title } - )} -

` - : html` -
- ${devices.map( - (device) => html` -
-
- ${this.step.result?.domain - ? html`${domainToName(` - : nothing} -
- ${device.model || device.manufacturer} - ${device.model - ? html` - ${device.manufacturer} - ` - : nothing} -
-
- - -
- ` + ${devices.length === 0 && + ["options_flow", "repair_flow"].includes(this.flowConfig.flowType) + ? nothing + : devices.length === 0 + ? html`

+ ${localize( + "ui.panel.config.integrations.config_flow.created_config", + { name: this.step.title } )} -

- `} +

` + : html` +
+ ${devices.map( + (device) => html` +
+
+ ${this.step.result?.domain + ? html`${domainToName(` + : nothing} +
+ ${device.model || device.manufacturer} + ${device.model + ? html` + ${device.manufacturer} + ` + : nothing} +
+
+ + +
+ ` + )} +
+ `}
Date: Mon, 5 May 2025 15:05:35 +0200 Subject: [PATCH 29/36] Fix zwave add device LR/mesh icons (#25313) Fix zwave LR/mesh icons --- .../images/z-wave-add-node/long-range.svg | 10 +++---- public/static/images/z-wave-add-node/mesh.svg | 30 +++++++++---------- .../images/z-wave-add-node/mesh_dark.svg | 29 +++++++++--------- 3 files changed, 34 insertions(+), 35 deletions(-) diff --git a/public/static/images/z-wave-add-node/long-range.svg b/public/static/images/z-wave-add-node/long-range.svg index 48deddc513..32fa115cc8 100644 --- a/public/static/images/z-wave-add-node/long-range.svg +++ b/public/static/images/z-wave-add-node/long-range.svg @@ -1,13 +1,13 @@ - + - + - - + + - + diff --git a/public/static/images/z-wave-add-node/mesh.svg b/public/static/images/z-wave-add-node/mesh.svg index 92a03c444a..48fba567f4 100644 --- a/public/static/images/z-wave-add-node/mesh.svg +++ b/public/static/images/z-wave-add-node/mesh.svg @@ -1,19 +1,19 @@ - - - - - - - - + + + + + + + + - - + + - - - - - + + + + + diff --git a/public/static/images/z-wave-add-node/mesh_dark.svg b/public/static/images/z-wave-add-node/mesh_dark.svg index 1824489e47..22cda5e4f1 100644 --- a/public/static/images/z-wave-add-node/mesh_dark.svg +++ b/public/static/images/z-wave-add-node/mesh_dark.svg @@ -1,19 +1,18 @@ - - - - - - - - + + + + + + + - - + + - - - - - + + + + + From c178488aacdc51e1e8bb01f8782773e02dfaf64f Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Mon, 5 May 2025 10:09:16 -0700 Subject: [PATCH 30/36] Add energy hourly calculations to CSV report (#25315) --- src/panels/energy/ha-panel-energy.ts | 120 +++++++++++++++++++++++++-- 1 file changed, 113 insertions(+), 7 deletions(-) diff --git a/src/panels/energy/ha-panel-energy.ts b/src/panels/energy/ha-panel-energy.ts index 7bf69dbc23..cb63510b66 100644 --- a/src/panels/energy/ha-panel-energy.ts +++ b/src/panels/energy/ha-panel-energy.ts @@ -22,11 +22,14 @@ import type { DeviceConsumptionEnergyPreference, } from "../../data/energy"; import { + computeConsumptionData, getEnergyDataCollection, getEnergyGasUnit, getEnergyWaterUnit, + getSummedData, } from "../../data/energy"; import { fileDownload } from "../../util/file_download"; +import type { StatisticValue } from "../../data/recorder"; const ENERGY_LOVELACE_CONFIG: LovelaceConfig = { views: [ @@ -177,18 +180,20 @@ class PanelEnergy extends LitElement { const csv: string[] = []; csv[0] = headers; - const processStat = function (stat: string, type: string, unit: string) { + const processCsvRow = function ( + id: string, + type: string, + unit: string, + data: StatisticValue[] + ) { let n = 0; const row: string[] = []; - if (!stats[stat]) { - return; - } - row.push(stat); + row.push(id); row.push(type); row.push(unit.normalize("NFKD")); times.forEach((t) => { - if (n < stats[stat].length && stats[stat][n].start === t) { - row.push((stats[stat][n].change ?? "").toString()); + if (n < data.length && data[n].start === t) { + row.push((data[n].change ?? "").toString()); n++; } else { row.push(""); @@ -197,6 +202,14 @@ class PanelEnergy extends LitElement { csv.push(row.join(",") + "\n"); }; + const processStat = function (stat: string, type: string, unit: string) { + if (!stats[stat]) { + return; + } + + processCsvRow(stat, type, unit, stats[stat]); + }; + const currency = this.hass.config.currency; const printCategory = function ( @@ -335,6 +348,99 @@ class PanelEnergy extends LitElement { printCategory("device_consumption", devices, electricUnit); + const { summedData, compareSummedData: _ } = getSummedData( + energyData.state + ); + const { consumption, compareConsumption: __ } = computeConsumptionData( + summedData, + undefined + ); + + const processConsumptionData = function ( + type: string, + unit: string, + data: Record + ) { + const data2: StatisticValue[] = []; + + Object.entries(data).forEach(([t, value]) => { + data2.push({ + start: Number(t), + end: NaN, + change: value, + }); + }); + + processCsvRow("", type, unit, data2); + }; + + const hasSolar = !!solar_productions.length; + const hasBattery = !!battery_ins.length; + const hasGridReturn = !!grid_productions.length; + const hasGridSource = !!grid_consumptions.length; + + if (hasGridSource) { + processConsumptionData( + "calculated_consumed_grid", + electricUnit, + consumption.used_grid + ); + if (hasBattery) { + processConsumptionData( + "calculated_grid_to_battery", + electricUnit, + consumption.grid_to_battery + ); + } + } + if (hasGridReturn && hasBattery) { + processConsumptionData( + "calculated_battery_to_grid", + electricUnit, + consumption.battery_to_grid + ); + } + if (hasBattery) { + processConsumptionData( + "calculated_consumed_battery", + electricUnit, + consumption.used_battery + ); + } + + if (hasSolar) { + processConsumptionData( + "calculated_consumed_solar", + electricUnit, + consumption.used_solar + ); + if (hasBattery) { + processConsumptionData( + "calculated_solar_to_battery", + electricUnit, + consumption.solar_to_battery + ); + } + if (hasGridReturn) { + processConsumptionData( + "calculated_solar_to_grid", + electricUnit, + consumption.solar_to_grid + ); + } + } + + if ( + (hasGridSource ? 1 : 0) + (hasSolar ? 1 : 0) + (hasBattery ? 1 : 0) > + 1 + ) { + processConsumptionData( + "calculated_total_consumption", + electricUnit, + consumption.used_total + ); + } + const blob = new Blob(csv, { type: "text/csv", }); From 1191a095768b6797d1ea71315cf0bcfa669513b9 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 5 May 2025 19:37:47 +0200 Subject: [PATCH 31/36] Use new entity naming in card entity picker (#25316) --- .../badge-editor/hui-dialog-create-badge.ts | 19 -- .../card-editor/hui-dialog-create-card.ts | 19 -- .../card-editor/hui-entity-picker-table.ts | 255 +++++++++++++----- .../unused-entities/hui-unused-entities.ts | 25 +- 4 files changed, 195 insertions(+), 123 deletions(-) diff --git a/src/panels/lovelace/editor/badge-editor/hui-dialog-create-badge.ts b/src/panels/lovelace/editor/badge-editor/hui-dialog-create-badge.ts index d14959e4fb..9388295802 100644 --- a/src/panels/lovelace/editor/badge-editor/hui-dialog-create-badge.ts +++ b/src/panels/lovelace/editor/badge-editor/hui-dialog-create-badge.ts @@ -4,11 +4,7 @@ import { css, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { cache } from "lit/directives/cache"; import { classMap } from "lit/directives/class-map"; -import memoize from "memoize-one"; import { fireEvent } from "../../../../common/dom/fire_event"; -import { computeDomain } from "../../../../common/entity/compute_domain"; -import { computeStateName } from "../../../../common/entity/compute_state_name"; -import type { DataTableRowData } from "../../../../components/data-table/ha-data-table"; import "../../../../components/ha-dialog"; import "../../../../components/ha-dialog-header"; import "../../../../components/sl-tab-group"; @@ -137,7 +133,6 @@ export class HuiCreateDialogBadge no-label-float .hass=${this.hass} .narrow=${true} - .entities=${this._allEntities(this.hass.states)} @selected-changed=${this._handleSelectedChanged} > ` @@ -276,20 +271,6 @@ export class HuiCreateDialogBadge this.closeDialog(); } - - private _allEntities = memoize((entities) => - Object.keys(entities).map((entity) => { - const stateObj = this.hass.states[entity]; - return { - icon: "", - entity_id: entity, - stateObj, - name: computeStateName(stateObj), - domain: computeDomain(entity), - last_changed: stateObj!.last_changed, - } as DataTableRowData; - }) - ); } declare global { diff --git a/src/panels/lovelace/editor/card-editor/hui-dialog-create-card.ts b/src/panels/lovelace/editor/card-editor/hui-dialog-create-card.ts index 314029fa0d..46927da9c8 100644 --- a/src/panels/lovelace/editor/card-editor/hui-dialog-create-card.ts +++ b/src/panels/lovelace/editor/card-editor/hui-dialog-create-card.ts @@ -5,11 +5,7 @@ import { customElement, property, state } from "lit/decorators"; import { cache } from "lit/directives/cache"; import { classMap } from "lit/directives/class-map"; import { ifDefined } from "lit/directives/if-defined"; -import memoize from "memoize-one"; import { fireEvent } from "../../../../common/dom/fire_event"; -import { computeDomain } from "../../../../common/entity/compute_domain"; -import { computeStateName } from "../../../../common/entity/compute_state_name"; -import type { DataTableRowData } from "../../../../components/data-table/ha-data-table"; import "../../../../components/ha-dialog"; import "../../../../components/ha-dialog-header"; import "../../../../components/sl-tab-group"; @@ -157,7 +153,6 @@ export class HuiCreateDialogCard no-label-float .hass=${this.hass} narrow - .entities=${this._allEntities(this.hass.states)} @selected-changed=${this._handleSelectedChanged} > ` @@ -340,20 +335,6 @@ export class HuiCreateDialogCard this.closeDialog(); } - - private _allEntities = memoize((entities) => - Object.keys(entities).map((entity) => { - const stateObj = this.hass.states[entity]; - return { - icon: "", - entity_id: entity, - stateObj, - name: computeStateName(stateObj), - domain: computeDomain(entity), - last_changed: stateObj!.last_changed, - } as DataTableRowData; - }) - ); } declare global { diff --git a/src/panels/lovelace/editor/card-editor/hui-entity-picker-table.ts b/src/panels/lovelace/editor/card-editor/hui-entity-picker-table.ts index 5918aafef6..dd0c3a549c 100644 --- a/src/panels/lovelace/editor/card-editor/hui-entity-picker-table.ts +++ b/src/panels/lovelace/editor/card-editor/hui-entity-picker-table.ts @@ -1,9 +1,17 @@ -import type { TemplateResult } from "lit"; -import { css, html, LitElement } from "lit"; +import type { PropertyValues, TemplateResult } from "lit"; +import { css, html, LitElement, nothing } from "lit"; import { customElement, property } from "lit/decorators"; +import { styleMap } from "lit/directives/style-map"; import memoizeOne from "memoize-one"; import type { HASSDomEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event"; +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/context/get_entity_context"; +import type { LocalizeFunc } from "../../../../common/translations/localize"; +import { computeRTL } from "../../../../common/util/compute_rtl"; import "../../../../components/data-table/ha-data-table"; import type { DataTableColumnContainer, @@ -12,8 +20,26 @@ import type { } from "../../../../components/data-table/ha-data-table"; import "../../../../components/entity/state-badge"; import "../../../../components/ha-relative-time"; +import { domainToName } from "../../../../data/integration"; import type { HomeAssistant } from "../../../../types"; +const ENTITY_ID_STYLE = styleMap({ + fontFamily: "var(--ha-font-family-code)", + fontSize: "var(--ha-font-size-xs)", +}); + +interface EntityPickerTableRowData extends DataTableRowData { + icon: string; + entity_id: string; + stateObj: any; + name: string; + entity_name?: string; + device_name?: string; + area_name?: string; + domain_name: string; + last_changed: string; +} + @customElement("hui-entity-picker-table") export class HuiEntityPickerTable extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -23,16 +49,69 @@ export class HuiEntityPickerTable extends LitElement { @property({ type: Boolean, attribute: "no-label-float" }) public noLabelFloat? = false; - @property({ type: Array }) public entities!: DataTableRowData[]; + @property({ type: Array }) public entities?: string[]; + + protected firstUpdated(_changedProperties: PropertyValues): void { + super.firstUpdated(_changedProperties); + this.hass.loadBackendTranslation("title"); + } + + private _data = memoizeOne( + ( + states: HomeAssistant["states"], + localize: LocalizeFunc, + entities?: string[] + ): EntityPickerTableRowData[] => + (entities || Object.keys(states)).map( + (entity) => { + const stateObj = this.hass.states[entity]; + + const { area, device } = getEntityContext(stateObj, this.hass); + + const entityName = computeEntityName(stateObj, this.hass); + const deviceName = device ? computeDeviceName(device) : undefined; + const areaName = area ? computeAreaName(area) : undefined; + const name = [deviceName, entityName].filter(Boolean).join(" "); + const domain = computeDomain(entity); + + return { + icon: "", + entity_id: entity, + stateObj, + name: name, + entity_name: entityName, + device_name: deviceName, + area_name: areaName, + domain_name: domainToName(localize, domain), + last_changed: stateObj!.last_changed, + } satisfies EntityPickerTableRowData; + } + ) + ); protected render(): TemplateResult { + const data = this._data( + this.hass.states, + this.hass.localize, + this.entities + ); + + const showEntityId = Boolean(this.hass.userData?.showEntityIdPicker); + + const columns = this._columns( + this.narrow, + computeRTL(this.hass), + showEntityId + ); + return html` { - const columns: DataTableColumnContainer = { - icon: { - title: "", - label: this.hass!.localize( - "ui.panel.lovelace.unused_entities.state_icon" + private _columns = memoizeOne( + (narrow: boolean, isRTL: boolean, showEntityId: boolean) => { + const columns: DataTableColumnContainer = { + icon: { + title: "", + label: this.hass!.localize( + "ui.panel.lovelace.unused_entities.state_icon" + ), + type: "icon", + template: (entity) => html` + + `, + }, + name: { + title: this.hass!.localize( + "ui.panel.lovelace.unused_entities.entity" + ), + sortable: true, + filterable: true, + flex: 2, + main: true, + direction: "asc", + template: (entity: any) => { + const primary = + entity.entity_name || entity.device_name || entity.entity_id; + const secondary = [ + entity.area_name, + entity.entity_name ? entity.device_name : undefined, + ] + .filter(Boolean) + .join(isRTL ? " ◂ " : " ▸ "); + return html` +
+ ${primary} + ${secondary + ? html`
${secondary}
` + : nothing} + ${narrow && showEntityId + ? html` +
+ ${entity.entity_id} +
+ ` + : nothing} +
+ `; + }, + }, + }; + + columns.entity_name = { + title: "entity_name", + filterable: true, + hidden: true, + }; + + columns.device_name = { + title: "device_name", + filterable: true, + hidden: true, + }; + + columns.area_name = { + title: "area_name", + filterable: true, + hidden: true, + }; + + columns.entity_id = { + title: this.hass!.localize( + "ui.panel.lovelace.unused_entities.entity_id" ), - type: "icon", - template: (entity) => html` - - `, - }, - name: { - title: this.hass!.localize("ui.panel.lovelace.unused_entities.entity"), sortable: true, filterable: true, - flex: 2, - main: true, - direction: "asc", - template: (entity: any) => html` -
- ${entity.name} - ${narrow - ? html`
${entity.entity_id}
` - : ""} -
+ hidden: narrow || !showEntityId, + }; + + columns.domain_name = { + title: this.hass!.localize("ui.panel.lovelace.unused_entities.domain"), + sortable: true, + filterable: true, + hidden: narrow || showEntityId, + }; + + columns.last_changed = { + title: this.hass!.localize( + "ui.panel.lovelace.unused_entities.last_changed" + ), + type: "numeric", + sortable: true, + hidden: narrow, + template: (entity) => html` + `, - }, - }; + }; - columns.entity_id = { - title: this.hass!.localize("ui.panel.lovelace.unused_entities.entity_id"), - sortable: true, - filterable: true, - hidden: narrow, - }; - - columns.domain = { - title: this.hass!.localize("ui.panel.lovelace.unused_entities.domain"), - sortable: true, - filterable: true, - hidden: narrow, - }; - - columns.last_changed = { - title: this.hass!.localize( - "ui.panel.lovelace.unused_entities.last_changed" - ), - type: "numeric", - sortable: true, - hidden: narrow, - template: (entity) => html` - - `, - }; - - return columns; - }); + return columns; + } + ); private _handleSelectionChanged( ev: HASSDomEvent @@ -134,6 +254,9 @@ export class HuiEntityPickerTable extends LitElement { --data-table-border-width: 0; height: 100%; } + ha-data-table.show-entity-id { + --data-table-row-height: 64px; + } `; } diff --git a/src/panels/lovelace/editor/unused-entities/hui-unused-entities.ts b/src/panels/lovelace/editor/unused-entities/hui-unused-entities.ts index a63d3e3a50..392f439f73 100644 --- a/src/panels/lovelace/editor/unused-entities/hui-unused-entities.ts +++ b/src/panels/lovelace/editor/unused-entities/hui-unused-entities.ts @@ -3,22 +3,19 @@ import type { PropertyValues } from "lit"; import { css, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; -import { computeDomain } from "../../../../common/entity/compute_domain"; -import { computeStateName } from "../../../../common/entity/compute_state_name"; -import type { DataTableRowData } from "../../../../components/data-table/ha-data-table"; import "../../../../components/ha-fab"; import "../../../../components/ha-svg-icon"; +import type { LovelaceConfig } from "../../../../data/lovelace/config/types"; import type { HomeAssistant } from "../../../../types"; import { computeUnusedEntities } from "../../common/compute-unused-entities"; -import type { Lovelace } from "../../types"; -import "../card-editor/hui-entity-picker-table"; -import { showSuggestCardDialog } from "../card-editor/show-suggest-card-dialog"; -import { showSelectViewDialog } from "../select-view/show-select-view-dialog"; -import type { LovelaceConfig } from "../../../../data/lovelace/config/types"; import { computeCards, computeSection, } from "../../common/generate-lovelace-config"; +import type { Lovelace } from "../../types"; +import "../card-editor/hui-entity-picker-table"; +import { showSuggestCardDialog } from "../card-editor/show-suggest-card-dialog"; +import { showSelectViewDialog } from "../select-view/show-select-view-dialog"; @customElement("hui-unused-entities") export class HuiUnusedEntities extends LitElement { @@ -80,17 +77,7 @@ export class HuiUnusedEntities extends LitElement { { - const stateObj = this.hass!.states[entity]; - return { - icon: "", - entity_id: entity, - stateObj, - name: stateObj ? computeStateName(stateObj) : "Unavailable", - domain: computeDomain(entity), - last_changed: stateObj?.last_changed, - }; - }) as DataTableRowData[]} + .entities=${this._unusedEntities} @selected-changed=${this._handleSelectedChanged} >
From 44485c0de43bb7e0ab3744120aa73213723d1fe4 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 5 May 2025 18:56:16 +0200 Subject: [PATCH 32/36] Do not display no areas in entity pickers (#25317) --- src/components/entity/ha-entity-combo-box.ts | 4 +--- src/components/entity/ha-entity-picker.ts | 5 +---- src/dialogs/quick-bar/ha-quick-bar.ts | 23 +++++++++++++++++--- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/components/entity/ha-entity-combo-box.ts b/src/components/entity/ha-entity-combo-box.ts index 9a89fe23d3..f0f3ede62b 100644 --- a/src/components/entity/ha-entity-combo-box.ts +++ b/src/components/entity/ha-entity-combo-box.ts @@ -314,9 +314,7 @@ export class HaEntityComboBox extends LitElement { ...hass!.states[entityId], label: "", primary: primary, - secondary: - secondary || - this.hass.localize("ui.components.device-picker.no_area"), + secondary: secondary, translated_domain: translatedDomain, sorting_label: [deviceName, entityName].filter(Boolean).join("-"), entity_name: entityName || deviceName, diff --git a/src/components/entity/ha-entity-picker.ts b/src/components/entity/ha-entity-picker.ts index 0cba69c515..515a5a58b4 100644 --- a/src/components/entity/ha-entity-picker.ts +++ b/src/components/entity/ha-entity-picker.ts @@ -162,10 +162,7 @@ export class HaEntityPicker extends LitElement { slot="start" > ${primary} - - ${secondary || - this.hass.localize("ui.components.device-picker.no_area")} - + ${secondary} ${showClearIcon ? html` Date: Mon, 5 May 2025 12:54:33 -0400 Subject: [PATCH 33/36] Reorder my links (#25319) --- src/panels/my/ha-panel-my.ts | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/panels/my/ha-panel-my.ts b/src/panels/my/ha-panel-my.ts index c308cc32e4..d41ef36863 100644 --- a/src/panels/my/ha-panel-my.ts +++ b/src/panels/my/ha-panel-my.ts @@ -16,6 +16,11 @@ import "../../layouts/hass-error-screen"; import type { HomeAssistant, Route } from "../../types"; import { documentationUrl } from "../../util/documentation-url"; +// When a user presses "m", the user is redirected to the first redirect +// for which holds true currentPath.startsWith(redirect.redirect) +// That's why redirects should be sorted with more specific ones first +// Or else pressing "M" will link to the higher level page. + export const getMyRedirects = (): Redirects => ({ application_credentials: { redirect: "/config/application_credentials", @@ -73,15 +78,15 @@ export const getMyRedirects = (): Redirects => ({ brand: "string", }, }, - integrations: { - redirect: "/config/integrations", - }, integration: { redirect: "/config/integrations/integration", params: { domain: "string", }, }, + integrations: { + redirect: "/config/integrations", + }, config_mqtt: { component: "mqtt", redirect: "/config/mqtt", @@ -106,10 +111,6 @@ export const getMyRedirects = (): Redirects => ({ component: "matter", redirect: "/config/matter/add", }, - config_bluetooth: { - component: "bluetooth", - redirect: "/config/bluetooth", - }, bluetooth_advertisement_monitor: { component: "bluetooth", redirect: "/config/bluetooth/advertisement-monitor", @@ -118,6 +119,10 @@ export const getMyRedirects = (): Redirects => ({ component: "bluetooth", redirect: "/config/bluetooth/connection-monitor", }, + config_bluetooth: { + component: "bluetooth", + redirect: "/config/bluetooth", + }, config_dhcp: { component: "dhcp", redirect: "/config/dhcp", @@ -252,12 +257,12 @@ export const getMyRedirects = (): Redirects => ({ // customize was removed in 2021.12, fallback to dashboard redirect: "/config/dashboard", }, - profile: { - redirect: "/profile", - }, profile_security: { redirect: "/profile/security", }, + profile: { + redirect: "/profile", + }, logbook: { component: "logbook", redirect: "/logbook", @@ -270,10 +275,6 @@ export const getMyRedirects = (): Redirects => ({ component: "media_source", redirect: "/media-browser", }, - backup: { - component: "backup", - redirect: "/config/backup", - }, backup_list: { component: "backup", redirect: "/config/backup/backups", @@ -282,6 +283,10 @@ export const getMyRedirects = (): Redirects => ({ component: "backup", redirect: "/config/backup/settings", }, + backup: { + component: "backup", + redirect: "/config/backup", + }, supervisor_snapshots: { component: "backup", redirect: "/config/backup", From 92905c14331f482fac8fe50f4967c733355b920d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 5 May 2025 13:29:24 -0400 Subject: [PATCH 34/36] Clean up network browser nav (#25321) * Clean up network browser nav * Add ha-md-list --- .../config/network/ha-config-network-dhcp.ts | 66 ----------------- .../config/network/ha-config-network-ssdp.ts | 66 ----------------- .../network/ha-config-network-zeroconf.ts | 70 ------------------- .../network/ha-config-section-network.ts | 62 ++++++++++------ src/translations/en.json | 11 ++- 5 files changed, 47 insertions(+), 228 deletions(-) delete mode 100644 src/panels/config/network/ha-config-network-dhcp.ts delete mode 100644 src/panels/config/network/ha-config-network-ssdp.ts delete mode 100644 src/panels/config/network/ha-config-network-zeroconf.ts diff --git a/src/panels/config/network/ha-config-network-dhcp.ts b/src/panels/config/network/ha-config-network-dhcp.ts deleted file mode 100644 index ef70d11903..0000000000 --- a/src/panels/config/network/ha-config-network-dhcp.ts +++ /dev/null @@ -1,66 +0,0 @@ -import "@material/mwc-button/mwc-button"; -import type { CSSResultGroup } from "lit"; -import { css, html, LitElement } from "lit"; -import { customElement, property } from "lit/decorators"; -import "../../../components/ha-button"; -import "../../../components/ha-card"; -import { haStyle } from "../../../resources/styles"; -import type { HomeAssistant } from "../../../types"; - -@customElement("ha-config-network-dhcp") -class ConfigNetworkDHCP extends LitElement { - @property({ attribute: false }) public hass!: HomeAssistant; - - protected render() { - return html` - -
-

- ${this.hass.localize("ui.panel.config.network.discovery.dhcp_info")} -

-
-
- - `; - } - - static get styles(): CSSResultGroup { - return [ - haStyle, - css` - ha-settings-row { - padding: 0; - } - - .card-actions { - display: flex; - flex-direction: row-reverse; - justify-content: space-between; - align-items: center; - } - `, // row-reverse so we tab first to "save" - ]; - } -} - -declare global { - interface HTMLElementTagNameMap { - "ha-config-network-dhcp": ConfigNetworkDHCP; - } -} diff --git a/src/panels/config/network/ha-config-network-ssdp.ts b/src/panels/config/network/ha-config-network-ssdp.ts deleted file mode 100644 index bd7bf83568..0000000000 --- a/src/panels/config/network/ha-config-network-ssdp.ts +++ /dev/null @@ -1,66 +0,0 @@ -import "@material/mwc-button/mwc-button"; -import type { CSSResultGroup } from "lit"; -import { css, html, LitElement } from "lit"; -import { customElement, property } from "lit/decorators"; -import "../../../components/ha-button"; -import "../../../components/ha-card"; -import { haStyle } from "../../../resources/styles"; -import type { HomeAssistant } from "../../../types"; - -@customElement("ha-config-network-ssdp") -class ConfigNetworkSSDP extends LitElement { - @property({ attribute: false }) public hass!: HomeAssistant; - - protected render() { - return html` - -
-

- ${this.hass.localize("ui.panel.config.network.discovery.ssdp_info")} -

-
- -
- `; - } - - static get styles(): CSSResultGroup { - return [ - haStyle, - css` - ha-settings-row { - padding: 0; - } - - .card-actions { - display: flex; - flex-direction: row-reverse; - justify-content: space-between; - align-items: center; - } - `, // row-reverse so we tab first to "save" - ]; - } -} - -declare global { - interface HTMLElementTagNameMap { - "ha-config-network-ssdp": ConfigNetworkSSDP; - } -} diff --git a/src/panels/config/network/ha-config-network-zeroconf.ts b/src/panels/config/network/ha-config-network-zeroconf.ts deleted file mode 100644 index db97f0d019..0000000000 --- a/src/panels/config/network/ha-config-network-zeroconf.ts +++ /dev/null @@ -1,70 +0,0 @@ -import "@material/mwc-button/mwc-button"; -import type { CSSResultGroup } from "lit"; -import { css, html, LitElement } from "lit"; -import { customElement, property } from "lit/decorators"; -import "../../../components/ha-button"; -import "../../../components/ha-card"; -import { haStyle } from "../../../resources/styles"; -import type { HomeAssistant } from "../../../types"; - -@customElement("ha-config-network-zeroconf") -class ConfigNetworkZeroconf extends LitElement { - @property({ attribute: false }) public hass!: HomeAssistant; - - protected render() { - return html` - -
-

- ${this.hass.localize( - "ui.panel.config.network.discovery.zeroconf_info" - )} -

-
- -
- `; - } - - static get styles(): CSSResultGroup { - return [ - haStyle, - css` - ha-settings-row { - padding: 0; - } - - .card-actions { - display: flex; - flex-direction: row-reverse; - justify-content: space-between; - align-items: center; - } - `, // row-reverse so we tab first to "save" - ]; - } -} - -declare global { - interface HTMLElementTagNameMap { - "ha-config-network-zeroconf": ConfigNetworkZeroconf; - } -} diff --git a/src/panels/config/network/ha-config-section-network.ts b/src/panels/config/network/ha-config-section-network.ts index c9a3df311d..567c18eb0f 100644 --- a/src/panels/config/network/ha-config-section-network.ts +++ b/src/panels/config/network/ha-config-section-network.ts @@ -3,15 +3,18 @@ import { css, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import "../../../layouts/hass-subpage"; +import "../../../components/ha-card"; +import "../../../components/ha-md-list"; +import "../../../components/ha-md-list-item"; +import "../../../components/ha-icon-next"; import type { HomeAssistant, Route } from "../../../types"; import "./ha-config-network"; -import "./ha-config-network-dhcp"; -import "./ha-config-network-ssdp"; -import "./ha-config-network-zeroconf"; import "./ha-config-url-form"; import "./supervisor-hostname"; import "./supervisor-network"; +const NETWORK_BROWSERS = ["dhcp", "ssdp", "zeroconf"] as const; + @customElement("ha-config-section-network") class HaConfigSectionNetwork extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -38,20 +41,38 @@ class HaConfigSectionNetwork extends LitElement { : ""} - ${isComponentLoaded(this.hass, "dhcp") - ? html`` - : ""} - ${isComponentLoaded(this.hass, "ssdp") - ? html`` - : ""} - ${isComponentLoaded(this.hass, "zeroconf") - ? html`` + ${NETWORK_BROWSERS.some((component) => + isComponentLoaded(this.hass, component) + ) + ? html` + + + ${NETWORK_BROWSERS.map( + (domain) => html` + +
+ ${this.hass.localize( + `ui.panel.config.network.discovery.${domain}` + )} +
+
+ ${this.hass.localize( + `ui.panel.config.network.discovery.${domain}_info` + )} +
+ +
+ ` + )} +
+
+ ` : ""} @@ -68,14 +89,15 @@ class HaConfigSectionNetwork extends LitElement { supervisor-network, ha-config-url-form, ha-config-network, - ha-config-network-dhcp, - ha-config-network-ssdp, - ha-config-network-zeroconf { + .discovery-card { display: block; margin: 0 auto; margin-bottom: 24px; max-width: 600px; } + .discovery-card ha-md-list { + padding-top: 0; + } `; } diff --git a/src/translations/en.json b/src/translations/en.json index af02f16e77..4ceef74c9b 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -6399,15 +6399,14 @@ } }, "discovery": { + "title": "Network discovery", + "description": "Explore what data Home Assistant can see on the network.", "dhcp": "DHCP browser", - "dhcp_info": "The DHCP browser shows devices detected by Home Assistant using methods like DHCP, ARP+PTR lookups, and router-based device trackers. DHCP (Dynamic Host Configuration Protocol) data is received when devices join the network and request an IP address, allowing Home Assistant to discover them automatically. All devices found through these methods will appear here.", - "dhcp_browser": "View DHCP browser", + "dhcp_info": "Show devices detected by Home Assistant using methods like DHCP, ARP+PTR lookups, and router-based device trackers. DHCP (Dynamic Host Configuration Protocol) data is received when devices join the network and request an IP address.", "ssdp": "SSDP browser", - "ssdp_info": "The SSDP browser shows devices discovered by Home Assistant using SSDP/UPnP. Devices that Home Assistant has discovered will appear here.", - "ssdp_browser": "View SSDP browser", + "ssdp_info": "Show devices discovered by Home Assistant using SSDP/UPnP. Devices that Home Assistant has discovered will appear here.", "zeroconf": "Zeroconf browser", - "zeroconf_info": "The Zeroconf browser shows devices discovered by Home Assistant using mDNS. Only devices that Home Assistant is actively searching for will appear here.", - "zeroconf_browser": "View Zeroconf browser" + "zeroconf_info": "Show devices discovered by Home Assistant using mDNS. Only devices that Home Assistant is actively searching for will appear here." }, "network_adapter": "Network adapter", "network_adapter_info": "Configure which network adapters integrations will use. Currently this setting only affects multicast traffic. A restart is required for these settings to apply.", From 37671fd613c45800958f2304ac5854e8461f8680 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 5 May 2025 13:48:29 -0400 Subject: [PATCH 35/36] Populate integration domain My link (#25322) * Populate integration domain My link * break out of loop * Actually just return from function * Consolidate code --- src/state/quick-bar-mixin.ts | 60 +++++++++++++++++------------------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/src/state/quick-bar-mixin.ts b/src/state/quick-bar-mixin.ts index 6e931a9bd7..f0effc7f03 100644 --- a/src/state/quick-bar-mixin.ts +++ b/src/state/quick-bar-mixin.ts @@ -16,6 +16,7 @@ import { extractSearchParamsObject } from "../common/url/search-params"; import { showVoiceCommandDialog } from "../dialogs/voice-command-dialog/show-ha-voice-command-dialog"; import { canOverrideAlphanumericInput } from "../common/dom/can-override-input"; import { showShortcutsDialog } from "../dialogs/shortcuts/show-shortcuts-dialog"; +import type { Redirects } from "../panels/my/ha-panel-my"; declare global { interface HASSDomEvents { @@ -143,49 +144,44 @@ export default >(superClass: T) => e.preventDefault(); const targetPath = mainWindow.location.pathname; - const isHassio = isComponentLoaded(this.hass, "hassio"); const myParams = new URLSearchParams(); - if (isHassio && targetPath.startsWith("/hassio")) { + let redirects: Redirects; + + if (targetPath.startsWith("/hassio")) { const myPanelSupervisor = await import( "../../hassio/src/hassio-my-redirect" ); - for (const [slug, redirect] of Object.entries( - myPanelSupervisor.REDIRECTS - )) { - if (targetPath.startsWith(redirect.redirect)) { - myParams.append("redirect", slug); - if (redirect.redirect === "/hassio/addon") { - myParams.append("addon", targetPath.split("/")[3]); - } - window.open( - `https://my.home-assistant.io/create-link/?${myParams.toString()}`, - "_blank" - ); - return; - } - } + redirects = myPanelSupervisor.REDIRECTS; + } else { + const myPanel = await import("../panels/my/ha-panel-my"); + redirects = myPanel.getMyRedirects(); } - const myPanel = await import("../panels/my/ha-panel-my"); + for (const [slug, redirect] of Object.entries(redirects)) { + if (!targetPath.startsWith(redirect.redirect)) { + continue; + } + myParams.append("redirect", slug); - for (const [slug, redirect] of Object.entries(myPanel.getMyRedirects())) { - if (targetPath.startsWith(redirect.redirect)) { - myParams.append("redirect", slug); - if (redirect.params) { - const params = extractSearchParamsObject(); - for (const key of Object.keys(redirect.params)) { - if (key in params) { - myParams.append(key, params[key]); - } + if (redirect.params) { + const params = extractSearchParamsObject(); + for (const key of Object.keys(redirect.params)) { + if (key in params) { + myParams.append(key, params[key]); } } - window.open( - `https://my.home-assistant.io/create-link/?${myParams.toString()}`, - "_blank" - ); - return; } + if (redirect.redirect === "/config/integrations/integration") { + myParams.append("domain", targetPath.split("/")[4]); + } else if (redirect.redirect === "/hassio/addon") { + myParams.append("addon", targetPath.split("/")[3]); + } + window.open( + `https://my.home-assistant.io/create-link/?${myParams.toString()}`, + "_blank" + ); + return; } showToast(this, { message: this.hass.localize( From b7f866faffbe81a621f0fefc17320ec0df45a734 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 5 May 2025 20:12:59 +0200 Subject: [PATCH 36/36] Bumped version to 20250502.1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7f9b269a72..8d2173dca9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20250502.0" +version = "20250502.1" license = "Apache-2.0" license-files = ["LICENSE*"] description = "The Home Assistant frontend"