From a78608bfb4a3e7ea75bfbdbf2e8d826319c153f9 Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Mon, 29 Apr 2024 08:48:01 -0700 Subject: [PATCH 01/14] Reorderable card-feature modes (#20647) * Reorderable card-feature modes * unused var in getStubConfig --- .../card-features/common/filter-modes.ts | 7 +++ .../hui-climate-fan-modes-card-feature.ts | 45 +++++++++---------- .../hui-climate-hvac-modes-card-feature.ts | 38 ++++++++-------- .../hui-climate-preset-modes-card-feature.ts | 45 +++++++++---------- .../hui-climate-swing-modes-card-feature.ts | 45 +++++++++---------- .../hui-fan-preset-modes-card-feature.ts | 45 +++++++++---------- .../hui-humidifier-modes-card-feature.ts | 45 +++++++++---------- ...ter-heater-operation-modes-card-feature.ts | 28 ++++++------ ...i-climate-fan-modes-card-feature-editor.ts | 1 + ...-climate-hvac-modes-card-feature-editor.ts | 1 + ...limate-preset-modes-card-feature-editor.ts | 1 + ...climate-swing-modes-card-feature-editor.ts | 1 + ...ui-fan-preset-modes-card-feature-editor.ts | 1 + ...ui-humidifier-modes-card-feature-editor.ts | 1 + ...ter-operation-modes-card-feature-editor.ts | 1 + 15 files changed, 150 insertions(+), 155 deletions(-) create mode 100644 src/panels/lovelace/card-features/common/filter-modes.ts diff --git a/src/panels/lovelace/card-features/common/filter-modes.ts b/src/panels/lovelace/card-features/common/filter-modes.ts new file mode 100644 index 000000000000..e3d1d50acd35 --- /dev/null +++ b/src/panels/lovelace/card-features/common/filter-modes.ts @@ -0,0 +1,7 @@ +export const filterModes = ( + supportedModes: string[] | undefined, + selectedModes: string[] | undefined +): string[] => + (selectedModes || []).length + ? selectedModes!.filter((mode) => (supportedModes || []).includes(mode)) + : supportedModes || []; diff --git a/src/panels/lovelace/card-features/hui-climate-fan-modes-card-feature.ts b/src/panels/lovelace/card-features/hui-climate-fan-modes-card-feature.ts index 78de1e6a4dcc..70c81e98ae9b 100644 --- a/src/panels/lovelace/card-features/hui-climate-fan-modes-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-climate-fan-modes-card-feature.ts @@ -15,6 +15,7 @@ import { UNAVAILABLE } from "../../../data/entity"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; import { ClimateFanModesCardFeatureConfig } from "./types"; +import { filterModes } from "./common/filter-modes"; export const supportsClimateFanModesCardFeature = (stateObj: HassEntity) => { const domain = computeDomain(stateObj.entity_id); @@ -40,14 +41,11 @@ class HuiClimateFanModesCardFeature @query("ha-control-select-menu", true) private _haSelect?: HaControlSelectMenu; - static getStubConfig( - _, - stateObj?: HassEntity - ): ClimateFanModesCardFeatureConfig { + static getStubConfig(): ClimateFanModesCardFeatureConfig { return { type: "climate-fan-modes", style: "dropdown", - fan_modes: stateObj?.attributes.fan_modes || [], + fan_modes: [], }; } @@ -122,25 +120,24 @@ class HuiClimateFanModesCardFeature const stateObj = this.stateObj; - const modes = stateObj.attributes.fan_modes || []; - - const options = modes - .filter((mode) => (this._config!.fan_modes || []).includes(mode)) - .map((mode) => ({ - value: mode, - label: this.hass!.formatEntityAttributeValue( - this.stateObj!, - "fan_mode", - mode - ), - icon: html``, - })); + const options = filterModes( + stateObj.attributes.fan_modes, + this._config!.fan_modes + ).map((mode) => ({ + value: mode, + label: this.hass!.formatEntityAttributeValue( + this.stateObj!, + "fan_mode", + mode + ), + icon: html``, + })); if (this._config.style === "icons") { return html` diff --git a/src/panels/lovelace/card-features/hui-climate-hvac-modes-card-feature.ts b/src/panels/lovelace/card-features/hui-climate-hvac-modes-card-feature.ts index 2cd020cc2207..949bf3b0c5a9 100644 --- a/src/panels/lovelace/card-features/hui-climate-hvac-modes-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-climate-hvac-modes-card-feature.ts @@ -20,6 +20,7 @@ import { UNAVAILABLE } from "../../../data/entity"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; import { ClimateHvacModesCardFeatureConfig } from "./types"; +import { filterModes } from "./common/filter-modes"; export const supportsClimateHvacModesCardFeature = (stateObj: HassEntity) => { const domain = computeDomain(stateObj.entity_id); @@ -42,13 +43,10 @@ class HuiClimateHvacModesCardFeature @query("ha-control-select-menu", true) private _haSelect?: HaControlSelectMenu; - static getStubConfig( - _, - stateObj?: HassEntity - ): ClimateHvacModesCardFeatureConfig { + static getStubConfig(): ClimateHvacModesCardFeatureConfig { return { type: "climate-hvac-modes", - hvac_modes: stateObj?.attributes.hvac_modes || [], + hvac_modes: [], }; } @@ -122,21 +120,21 @@ class HuiClimateHvacModesCardFeature const color = stateColorCss(this.stateObj); - const modes = this._config.hvac_modes || []; - - const options = modes - .filter((mode) => this.stateObj?.attributes.hvac_modes.includes(mode)) - .sort(compareClimateHvacModes) - .map((mode) => ({ - value: mode, - label: this.hass!.formatEntityState(this.stateObj!, mode), - icon: html` - - `, - })); + const options = filterModes( + [...(this.stateObj?.attributes.hvac_modes || [])].sort( + compareClimateHvacModes + ), + this._config.hvac_modes + ).map((mode) => ({ + value: mode, + label: this.hass!.formatEntityState(this.stateObj!, mode), + icon: html` + + `, + })); if (this._config.style === "dropdown") { return html` diff --git a/src/panels/lovelace/card-features/hui-climate-preset-modes-card-feature.ts b/src/panels/lovelace/card-features/hui-climate-preset-modes-card-feature.ts index 410f8ce942a0..84e3d61acf85 100644 --- a/src/panels/lovelace/card-features/hui-climate-preset-modes-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-climate-preset-modes-card-feature.ts @@ -15,6 +15,7 @@ import { UNAVAILABLE } from "../../../data/entity"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; import { ClimatePresetModesCardFeatureConfig } from "./types"; +import { filterModes } from "./common/filter-modes"; export const supportsClimatePresetModesCardFeature = (stateObj: HassEntity) => { const domain = computeDomain(stateObj.entity_id); @@ -40,14 +41,11 @@ class HuiClimatePresetModesCardFeature @query("ha-control-select-menu", true) private _haSelect?: HaControlSelectMenu; - static getStubConfig( - _, - stateObj?: HassEntity - ): ClimatePresetModesCardFeatureConfig { + static getStubConfig(): ClimatePresetModesCardFeatureConfig { return { type: "climate-preset-modes", style: "dropdown", - preset_modes: stateObj?.attributes.preset_modes || [], + preset_modes: [], }; } @@ -124,25 +122,24 @@ class HuiClimatePresetModesCardFeature const stateObj = this.stateObj; - const modes = stateObj.attributes.preset_modes || []; - - const options = modes - .filter((mode) => (this._config!.preset_modes || []).includes(mode)) - .map((mode) => ({ - value: mode, - label: this.hass!.formatEntityAttributeValue( - this.stateObj!, - "preset_mode", - mode - ), - icon: html``, - })); + const options = filterModes( + stateObj.attributes.preset_modes, + this._config!.preset_modes + ).map((mode) => ({ + value: mode, + label: this.hass!.formatEntityAttributeValue( + this.stateObj!, + "preset_mode", + mode + ), + icon: html``, + })); if (this._config.style === "icons") { return html` diff --git a/src/panels/lovelace/card-features/hui-climate-swing-modes-card-feature.ts b/src/panels/lovelace/card-features/hui-climate-swing-modes-card-feature.ts index ec7dadaa8496..6d5399f3e260 100644 --- a/src/panels/lovelace/card-features/hui-climate-swing-modes-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-climate-swing-modes-card-feature.ts @@ -15,6 +15,7 @@ import { UNAVAILABLE } from "../../../data/entity"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; import { ClimateSwingModesCardFeatureConfig } from "./types"; +import { filterModes } from "./common/filter-modes"; export const supportsClimateSwingModesCardFeature = (stateObj: HassEntity) => { const domain = computeDomain(stateObj.entity_id); @@ -40,14 +41,11 @@ class HuiClimateSwingModesCardFeature @query("ha-control-select-menu", true) private _haSelect?: HaControlSelectMenu; - static getStubConfig( - _, - stateObj?: HassEntity - ): ClimateSwingModesCardFeatureConfig { + static getStubConfig(): ClimateSwingModesCardFeatureConfig { return { type: "climate-swing-modes", style: "dropdown", - swing_modes: stateObj?.attributes.swing_modes || [], + swing_modes: [], }; } @@ -124,25 +122,24 @@ class HuiClimateSwingModesCardFeature const stateObj = this.stateObj; - const modes = stateObj.attributes.swing_modes || []; - - const options = modes - .filter((mode) => (this._config!.swing_modes || []).includes(mode)) - .map((mode) => ({ - value: mode, - label: this.hass!.formatEntityAttributeValue( - this.stateObj!, - "swing_mode", - mode - ), - icon: html``, - })); + const options = filterModes( + stateObj.attributes.swing_modes, + this._config!.swing_modes + ).map((mode) => ({ + value: mode, + label: this.hass!.formatEntityAttributeValue( + this.stateObj!, + "swing_mode", + mode + ), + icon: html``, + })); if (this._config.style === "icons") { return html` diff --git a/src/panels/lovelace/card-features/hui-fan-preset-modes-card-feature.ts b/src/panels/lovelace/card-features/hui-fan-preset-modes-card-feature.ts index b4701dab84ac..42066f146b23 100644 --- a/src/panels/lovelace/card-features/hui-fan-preset-modes-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-fan-preset-modes-card-feature.ts @@ -15,6 +15,7 @@ import { UNAVAILABLE } from "../../../data/entity"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; import { FanPresetModesCardFeatureConfig } from "./types"; +import { filterModes } from "./common/filter-modes"; export const supportsFanPresetModesCardFeature = (stateObj: HassEntity) => { const domain = computeDomain(stateObj.entity_id); @@ -39,14 +40,11 @@ class HuiFanPresetModesCardFeature @query("ha-control-select-menu", true) private _haSelect?: HaControlSelectMenu; - static getStubConfig( - _, - stateObj?: HassEntity - ): FanPresetModesCardFeatureConfig { + static getStubConfig(): FanPresetModesCardFeatureConfig { return { type: "fan-preset-modes", style: "dropdown", - preset_modes: stateObj?.attributes.preset_modes || [], + preset_modes: [], }; } @@ -121,25 +119,24 @@ class HuiFanPresetModesCardFeature const stateObj = this.stateObj; - const modes = stateObj.attributes.preset_modes || []; - - const options = modes - .filter((mode) => (this._config!.preset_modes || []).includes(mode)) - .map((mode) => ({ - value: mode, - label: this.hass!.formatEntityAttributeValue( - this.stateObj!, - "preset_mode", - mode - ), - icon: html``, - })); + const options = filterModes( + stateObj.attributes.preset_modes, + this._config!.preset_modes + ).map((mode) => ({ + value: mode, + label: this.hass!.formatEntityAttributeValue( + this.stateObj!, + "preset_mode", + mode + ), + icon: html``, + })); if (this._config.style === "icons") { return html` diff --git a/src/panels/lovelace/card-features/hui-humidifier-modes-card-feature.ts b/src/panels/lovelace/card-features/hui-humidifier-modes-card-feature.ts index 94a366712ad2..1f9c970194fd 100644 --- a/src/panels/lovelace/card-features/hui-humidifier-modes-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-humidifier-modes-card-feature.ts @@ -18,6 +18,7 @@ import { import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; import { HumidifierModesCardFeatureConfig } from "./types"; +import { filterModes } from "./common/filter-modes"; export const supportsHumidifierModesCardFeature = (stateObj: HassEntity) => { const domain = computeDomain(stateObj.entity_id); @@ -43,14 +44,11 @@ class HuiHumidifierModesCardFeature @query("ha-control-select-menu", true) private _haSelect?: HaControlSelectMenu; - static getStubConfig( - _, - stateObj?: HassEntity - ): HumidifierModesCardFeatureConfig { + static getStubConfig(): HumidifierModesCardFeatureConfig { return { type: "humidifier-modes", style: "dropdown", - modes: stateObj?.attributes.available_modes || [], + modes: [], }; } @@ -125,25 +123,24 @@ class HuiHumidifierModesCardFeature const stateObj = this.stateObj; - const modes = stateObj.attributes.available_modes || []; - - const options = modes - .filter((mode) => (this._config!.modes || []).includes(mode)) - .map((mode) => ({ - value: mode, - label: this.hass!.formatEntityAttributeValue( - this.stateObj!, - "mode", - mode - ), - icon: html``, - })); + const options = filterModes( + stateObj.attributes.available_modes, + this._config!.modes + ).map((mode) => ({ + value: mode, + label: this.hass!.formatEntityAttributeValue( + this.stateObj!, + "mode", + mode + ), + icon: html``, + })); if (this._config.style === "icons") { return html` diff --git a/src/panels/lovelace/card-features/hui-water-heater-operation-modes-card-feature.ts b/src/panels/lovelace/card-features/hui-water-heater-operation-modes-card-feature.ts index 344a4ad7ba50..3dbb61162208 100644 --- a/src/panels/lovelace/card-features/hui-water-heater-operation-modes-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-water-heater-operation-modes-card-feature.ts @@ -19,6 +19,7 @@ import { import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; import { WaterHeaterOperationModesCardFeatureConfig } from "./types"; +import { filterModes } from "./common/filter-modes"; export const supportsWaterHeaterOperationModesCardFeature = ( stateObj: HassEntity @@ -40,13 +41,10 @@ class HuiWaterHeaterOperationModeCardFeature @state() _currentOperationMode?: OperationMode; - static getStubConfig( - _, - stateObj?: HassEntity - ): WaterHeaterOperationModesCardFeatureConfig { + static getStubConfig(): WaterHeaterOperationModesCardFeatureConfig { return { type: "water-heater-operation-modes", - operation_modes: stateObj?.attributes.operation_list || [], + operation_modes: [], }; } @@ -107,16 +105,16 @@ class HuiWaterHeaterOperationModeCardFeature const color = stateColorCss(this.stateObj); - const modes = this._config.operation_modes || []; - - const options = modes - .filter((mode) => this.stateObj?.attributes.operation_list.includes(mode)) - .sort(compareWaterHeaterOperationMode) - .map((mode) => ({ - value: mode, - label: this.hass!.formatEntityState(this.stateObj!, mode), - path: computeOperationModeIcon(mode), - })); + const options = filterModes( + [...(this.stateObj?.attributes.operation_list || [])].sort( + compareWaterHeaterOperationMode + ), + this._config.operation_modes + ).map((mode) => ({ + value: mode, + label: this.hass!.formatEntityState(this.stateObj!, mode), + path: computeOperationModeIcon(mode as OperationMode), + })); return html`
diff --git a/src/panels/lovelace/editor/config-elements/hui-climate-fan-modes-card-feature-editor.ts b/src/panels/lovelace/editor/config-elements/hui-climate-fan-modes-card-feature-editor.ts index 3316c26b5c47..dafd27ec1289 100644 --- a/src/panels/lovelace/editor/config-elements/hui-climate-fan-modes-card-feature-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-climate-fan-modes-card-feature-editor.ts @@ -59,6 +59,7 @@ export class HuiClimateFanModesCardFeatureEditor selector: { select: { multiple: true, + reorder: true, mode: "list", options: stateObj?.attributes.fan_modes?.map((mode) => ({ diff --git a/src/panels/lovelace/editor/config-elements/hui-climate-hvac-modes-card-feature-editor.ts b/src/panels/lovelace/editor/config-elements/hui-climate-hvac-modes-card-feature-editor.ts index 228a43740d22..1d6aa6774f81 100644 --- a/src/panels/lovelace/editor/config-elements/hui-climate-hvac-modes-card-feature-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-climate-hvac-modes-card-feature-editor.ts @@ -57,6 +57,7 @@ export class HuiClimateHvacModesCardFeatureEditor selector: { select: { multiple: true, + reorder: true, mode: "list", options: HVAC_MODES.filter((mode) => stateObj?.attributes.hvac_modes?.includes(mode) diff --git a/src/panels/lovelace/editor/config-elements/hui-climate-preset-modes-card-feature-editor.ts b/src/panels/lovelace/editor/config-elements/hui-climate-preset-modes-card-feature-editor.ts index 52955c162714..db33052af105 100644 --- a/src/panels/lovelace/editor/config-elements/hui-climate-preset-modes-card-feature-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-climate-preset-modes-card-feature-editor.ts @@ -59,6 +59,7 @@ export class HuiClimatePresetModesCardFeatureEditor selector: { select: { multiple: true, + reorder: true, mode: "list", options: stateObj?.attributes.preset_modes?.map((mode) => ({ diff --git a/src/panels/lovelace/editor/config-elements/hui-climate-swing-modes-card-feature-editor.ts b/src/panels/lovelace/editor/config-elements/hui-climate-swing-modes-card-feature-editor.ts index 812c5c03cba6..a490a86fc5f1 100644 --- a/src/panels/lovelace/editor/config-elements/hui-climate-swing-modes-card-feature-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-climate-swing-modes-card-feature-editor.ts @@ -59,6 +59,7 @@ export class HuiClimateSwingModesCardFeatureEditor selector: { select: { multiple: true, + reorder: true, mode: "list", options: stateObj?.attributes.swing_modes?.map((mode) => ({ diff --git a/src/panels/lovelace/editor/config-elements/hui-fan-preset-modes-card-feature-editor.ts b/src/panels/lovelace/editor/config-elements/hui-fan-preset-modes-card-feature-editor.ts index 35cfd7b8a6a0..5c4d688f672e 100644 --- a/src/panels/lovelace/editor/config-elements/hui-fan-preset-modes-card-feature-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-fan-preset-modes-card-feature-editor.ts @@ -59,6 +59,7 @@ export class HuiFanPresetModesCardFeatureEditor selector: { select: { multiple: true, + reorder: true, mode: "list", options: stateObj?.attributes.preset_modes?.map((mode) => ({ diff --git a/src/panels/lovelace/editor/config-elements/hui-humidifier-modes-card-feature-editor.ts b/src/panels/lovelace/editor/config-elements/hui-humidifier-modes-card-feature-editor.ts index a462f19e49b1..33df27d2dc5e 100644 --- a/src/panels/lovelace/editor/config-elements/hui-humidifier-modes-card-feature-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-humidifier-modes-card-feature-editor.ts @@ -59,6 +59,7 @@ export class HuiHumidifierModesCardFeatureEditor selector: { select: { multiple: true, + reorder: true, mode: "list", options: stateObj?.attributes.available_modes?.map((mode) => ({ diff --git a/src/panels/lovelace/editor/config-elements/hui-water-heater-operation-modes-card-feature-editor.ts b/src/panels/lovelace/editor/config-elements/hui-water-heater-operation-modes-card-feature-editor.ts index 6e1a5f43ab1b..a5763b3401cd 100644 --- a/src/panels/lovelace/editor/config-elements/hui-water-heater-operation-modes-card-feature-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-water-heater-operation-modes-card-feature-editor.ts @@ -37,6 +37,7 @@ export class HuiWaterHeaterOperationModesCardFeatureEditor selector: { select: { multiple: true, + reorder: true, mode: "list", options: OPERATION_MODES.filter((mode) => stateObj?.attributes.operation_list?.includes(mode) From 8cca233b7cbe9bdf851c145b85d298b5e5ae4be3 Mon Sep 17 00:00:00 2001 From: krazos Date: Mon, 29 Apr 2024 14:53:33 -0400 Subject: [PATCH 02/14] Update unlock icon for tile card lock features (#20667) Update unlock icon for tile card lock features so it's easier to see the difference between lock and unlock buttons --- .../lovelace/card-features/hui-lock-commands-card-feature.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/panels/lovelace/card-features/hui-lock-commands-card-feature.ts b/src/panels/lovelace/card-features/hui-lock-commands-card-feature.ts index b0763a207422..2d02345bbf00 100644 --- a/src/panels/lovelace/card-features/hui-lock-commands-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-lock-commands-card-feature.ts @@ -1,4 +1,4 @@ -import { mdiLock, mdiLockOpen } from "@mdi/js"; +import { mdiLock, mdiLockOpenVariant } from "@mdi/js"; import { HassEntity } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; @@ -90,7 +90,7 @@ class HuiLockCommandsCardFeature pulse: isLocking(this.stateObj) || isUnlocking(this.stateObj), })} > - + `; From 1bc33a30ec0f864016eabf1a4140892639c1024b Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Tue, 30 Apr 2024 03:23:20 -0700 Subject: [PATCH 03/14] Display version info for custom integrations (#20652) * Display version info for custom integrations * no width --- src/data/integration.ts | 1 + .../config/integrations/ha-config-integration-page.ts | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/src/data/integration.ts b/src/data/integration.ts index ce75f49c92e6..7eb60bfb4435 100644 --- a/src/data/integration.ts +++ b/src/data/integration.ts @@ -44,6 +44,7 @@ export interface IntegrationManifest { | "local_polling" | "local_push"; single_config_entry?: boolean; + version?: string; } export interface IntegrationSetup { domain: string; diff --git a/src/panels/config/integrations/ha-config-integration-page.ts b/src/panels/config/integrations/ha-config-integration-page.ts index 674a720c056b..746d625a74de 100644 --- a/src/panels/config/integrations/ha-config-integration-page.ts +++ b/src/panels/config/integrations/ha-config-integration-page.ts @@ -269,6 +269,9 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) { @error=${this._onImageError} />
+ ${this._manifest?.version != null + ? html`
${this._manifest.version}
` + : nothing} ${this._manifest?.is_built_in === false ? html` Date: Tue, 30 Apr 2024 05:25:45 -0500 Subject: [PATCH 04/14] Fallback to raw config entry reason if localize returns an empty string (#20668) Show config entry reason if localize returns an empty string --- .../integrations/ha-config-integration-page.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/panels/config/integrations/ha-config-integration-page.ts b/src/panels/config/integrations/ha-config-integration-page.ts index 746d625a74de..7eabbeb93e7e 100644 --- a/src/panels/config/integrations/ha-config-integration-page.ts +++ b/src/panels/config/integrations/ha-config-integration-page.ts @@ -557,18 +557,22 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) { if (item.error_reason_translation_key) { const lokalisePromExc = this.hass .loadBackendTranslation("exceptions", item.domain) - .then((localize) => - localize( - `component.${item.domain}.exceptions.${item.error_reason_translation_key}.message`, - item.error_reason_translation_placeholders ?? undefined - ) + .then( + (localize) => + localize( + `component.${item.domain}.exceptions.${item.error_reason_translation_key}.message`, + item.error_reason_translation_placeholders ?? undefined + ) || item.reason ); stateTextExtra = html`${until(lokalisePromExc)}`; } else { const lokalisePromError = this.hass .loadBackendTranslation("config", item.domain) - .then((localize) => - localize(`component.${item.domain}.config.error.${item.reason}`) + .then( + (localize) => + localize( + `component.${item.domain}.config.error.${item.reason}` + ) || item.reason ); stateTextExtra = html`${until(lokalisePromError, item.reason)}`; } From c99e0e846b0ea999923730729e1a225029bf0690 Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Tue, 30 Apr 2024 03:32:32 -0700 Subject: [PATCH 05/14] More config/entities status filters (#20638) --- .../config/entities/ha-config-entities.ts | 102 +++++++++++------- 1 file changed, 64 insertions(+), 38 deletions(-) diff --git a/src/panels/config/entities/ha-config-entities.ts b/src/panels/config/entities/ha-config-entities.ts index 2260effc44ab..e90e6465cee0 100644 --- a/src/panels/config/entities/ha-config-entities.ts +++ b/src/panels/config/entities/ha-config-entities.ts @@ -28,6 +28,7 @@ import { customElement, property, query, state } from "lit/decorators"; import { ifDefined } from "lit/directives/if-defined"; import { styleMap } from "lit/directives/style-map"; import memoize from "memoize-one"; +import { stringCompare } from "../../../common/string/compare"; import { computeCssColor } from "../../../common/color/compute-color"; import { storage } from "../../../common/decorators/storage"; import type { HASSDomEvent } from "../../../common/dom/fire_event"; @@ -196,24 +197,40 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { } }; - private _states = memoize((localize: LocalizeFunc) => [ - { - value: "disabled", - label: localize("ui.panel.config.entities.picker.status.disabled"), - }, - { - value: "hidden", - label: localize("ui.panel.config.entities.picker.status.hidden"), - }, - { - value: "unavailable", - label: localize("ui.panel.config.entities.picker.status.unavailable"), - }, - { - value: "readonly", - label: localize("ui.panel.config.entities.picker.status.readonly"), - }, - ]); + private _states = memoize((localize: LocalizeFunc) => + [ + { + value: "available", + label: localize("ui.panel.config.entities.picker.status.available"), + }, + { + value: "disabled", + label: localize("ui.panel.config.entities.picker.status.disabled"), + }, + { + value: "hidden", + label: localize("ui.panel.config.entities.picker.status.hidden"), + }, + { + value: "unavailable", + label: localize("ui.panel.config.entities.picker.status.unavailable"), + }, + { + value: "readonly", + label: localize("ui.panel.config.entities.picker.status.readonly"), + }, + { + value: "restored", + label: localize("ui.panel.config.entities.picker.status.restored"), + }, + ].sort((a, b) => + stringCompare( + a.label || a.value, + b.label || b.value, + this.hass.locale.language + ) + ) + ); private _columns = memoize( ( @@ -397,10 +414,12 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { !stateFilters?.length || stateFilters.includes("hidden"); const showUnavailable = !stateFilters?.length || stateFilters.includes("unavailable"); + const showAvailable = + !stateFilters?.length || stateFilters.includes("available"); + const showRestored = + !stateFilters?.length || stateFilters.includes("restored"); - let filteredEntities = showReadOnly - ? entities.concat(stateEntities) - : entities; + let filteredEntities = entities.concat(stateEntities); let filteredConfigEntry: ConfigEntry | undefined; const filteredDomains = new Set(); @@ -459,26 +478,33 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { } }); - if (!showDisabled) { - filteredEntities = filteredEntities.filter( - (entity) => !entity.disabled_by - ); - } - - if (!showHidden) { - filteredEntities = filteredEntities.filter( - (entity) => !entity.hidden_by - ); - } - for (const entry of filteredEntities) { const entity = this.hass.states[entry.entity_id]; const unavailable = entity?.state === UNAVAILABLE; const restored = entity?.attributes.restored === true; const areaId = entry.area_id ?? devices[entry.device_id!]?.area_id; const area = areaId ? areas[areaId] : undefined; + const hidden = !!entry.hidden_by; + const disabled = !!entry.disabled_by; + const readonly = entry.readonly; + const available = !( + unavailable || + readonly || + restored || + hidden || + disabled + ); - if (!showUnavailable && unavailable) { + if ( + !( + (showAvailable && available) || + (showUnavailable && unavailable) || + (showRestored && restored) || + (showHidden && hidden) || + (showDisabled && disabled) || + (showReadOnly && readonly) + ) + ) { continue; } @@ -503,11 +529,11 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { ? localize("ui.panel.config.entities.picker.status.restored") : unavailable ? localize("ui.panel.config.entities.picker.status.unavailable") - : entry.disabled_by + : disabled ? localize("ui.panel.config.entities.picker.status.disabled") - : entry.hidden_by + : hidden ? localize("ui.panel.config.entities.picker.status.hidden") - : entry.readonly + : readonly ? localize( "ui.panel.config.entities.picker.status.readonly" ) @@ -861,7 +887,7 @@ ${ protected firstUpdated() { this._filters = { "ha-filter-states": { - value: ["unavailable", "readonly"], + value: ["unavailable", "readonly", "restored", "available"], items: undefined, }, }; From bcb72d83b88e5367d05f580b2ea2414011fad7d4 Mon Sep 17 00:00:00 2001 From: Nicooow <46629108+Nicooow@users.noreply.github.com> Date: Tue, 30 Apr 2024 14:03:19 +0200 Subject: [PATCH 06/14] Fix an inconsistency in dark mode (#20671) * add app-theme-color var * Fix Prettier format * Fix regression on default dark theme * prevent duplicate calculation --- src/common/dom/apply_themes_on_element.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/common/dom/apply_themes_on_element.ts b/src/common/dom/apply_themes_on_element.ts index cf431b6cb8ad..ee977cb49806 100644 --- a/src/common/dom/apply_themes_on_element.ts +++ b/src/common/dom/apply_themes_on_element.ts @@ -61,11 +61,8 @@ export const applyThemesOnElement = ( const accentColor = themeSettings?.accentColor; if (darkMode && primaryColor) { - themeRules["app-header-background-color"] = hexBlend( - primaryColor, - "#121212", - 8 - ); + themeRules["app-theme-color"] = hexBlend(primaryColor, "#121212", 8); + themeRules["app-header-background-color"] = themeRules["app-theme-color"]; } if (primaryColor) { From 334c245b652e9f36249eab7b9cb769e318f2e654 Mon Sep 17 00:00:00 2001 From: Adam Kapos Date: Tue, 30 Apr 2024 16:07:54 +0300 Subject: [PATCH 07/14] Fix visual differences between regular and energy dashboards (#20654) * Fix visual differences between regular and energy dashboards * Order padding properties the same way between energy and lovelace * Change from code review --- src/panels/energy/ha-panel-energy.ts | 20 +++++++++----------- src/panels/lovelace/hui-root.ts | 2 -- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/panels/energy/ha-panel-energy.ts b/src/panels/energy/ha-panel-energy.ts index c033f7c2f3d6..328e25cf4cd5 100644 --- a/src/panels/energy/ha-panel-energy.ts +++ b/src/panels/energy/ha-panel-energy.ts @@ -112,14 +112,14 @@ class PanelEnergy extends LitElement { - +
+ +
`; } @@ -401,12 +401,10 @@ class PanelEnergy extends LitElement { min-height: 100vh; box-sizing: border-box; padding-left: env(safe-area-inset-left); - padding-inline-start: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); + padding-inline-start: env(safe-area-inset-left); padding-inline-end: env(safe-area-inset-right); padding-bottom: env(safe-area-inset-bottom); - } - hui-view { background: var( --lovelace-background, var(--primary-background-color) diff --git a/src/panels/lovelace/hui-root.ts b/src/panels/lovelace/hui-root.ts index 6226897809a3..e95cdf1861dd 100644 --- a/src/panels/lovelace/hui-root.ts +++ b/src/panels/lovelace/hui-root.ts @@ -1013,8 +1013,6 @@ class HUIRoot extends LitElement { padding-inline-start: env(safe-area-inset-left); padding-inline-end: env(safe-area-inset-right); padding-bottom: env(safe-area-inset-bottom); - } - hui-view { background: var( --lovelace-background, var(--primary-background-color) From 7120ad99b9bdd3d6dc648deb6ae5285d986f8744 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Tue, 30 Apr 2024 18:38:51 +0200 Subject: [PATCH 08/14] Add customize mode option to card features with modes (#20670) * Add customize mode options to card features with modes * Better type * Fix water heater and humidifier * Clean schema --- .../card-features/common/filter-modes.ts | 4 +- .../hui-climate-fan-modes-card-feature.ts | 1 - .../hui-climate-hvac-modes-card-feature.ts | 11 ++- .../hui-climate-preset-modes-card-feature.ts | 1 - .../hui-climate-swing-modes-card-feature.ts | 1 - .../hui-fan-preset-modes-card-feature.ts | 1 - .../hui-humidifier-modes-card-feature.ts | 1 - ...ter-heater-operation-modes-card-feature.ts | 9 +- ...i-climate-fan-modes-card-feature-editor.ts | 66 +++++++++---- ...-climate-hvac-modes-card-feature-editor.ts | 79 ++++++++++++---- ...limate-preset-modes-card-feature-editor.ts | 70 ++++++++++---- ...climate-swing-modes-card-feature-editor.ts | 70 ++++++++++---- ...ui-fan-preset-modes-card-feature-editor.ts | 70 ++++++++++---- ...ui-humidifier-modes-card-feature-editor.ts | 66 +++++++++---- ...ter-operation-modes-card-feature-editor.ts | 94 ++++++++++++++----- src/translations/en.json | 13 ++- 16 files changed, 403 insertions(+), 154 deletions(-) diff --git a/src/panels/lovelace/card-features/common/filter-modes.ts b/src/panels/lovelace/card-features/common/filter-modes.ts index e3d1d50acd35..d1e81385df16 100644 --- a/src/panels/lovelace/card-features/common/filter-modes.ts +++ b/src/panels/lovelace/card-features/common/filter-modes.ts @@ -2,6 +2,6 @@ export const filterModes = ( supportedModes: string[] | undefined, selectedModes: string[] | undefined ): string[] => - (selectedModes || []).length - ? selectedModes!.filter((mode) => (supportedModes || []).includes(mode)) + selectedModes + ? selectedModes.filter((mode) => (supportedModes || []).includes(mode)) : supportedModes || []; diff --git a/src/panels/lovelace/card-features/hui-climate-fan-modes-card-feature.ts b/src/panels/lovelace/card-features/hui-climate-fan-modes-card-feature.ts index 70c81e98ae9b..3cfa5624b54c 100644 --- a/src/panels/lovelace/card-features/hui-climate-fan-modes-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-climate-fan-modes-card-feature.ts @@ -45,7 +45,6 @@ class HuiClimateFanModesCardFeature return { type: "climate-fan-modes", style: "dropdown", - fan_modes: [], }; } diff --git a/src/panels/lovelace/card-features/hui-climate-hvac-modes-card-feature.ts b/src/panels/lovelace/card-features/hui-climate-hvac-modes-card-feature.ts index 949bf3b0c5a9..3eab79b85ea3 100644 --- a/src/panels/lovelace/card-features/hui-climate-hvac-modes-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-climate-hvac-modes-card-feature.ts @@ -19,8 +19,8 @@ import { import { UNAVAILABLE } from "../../../data/entity"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; -import { ClimateHvacModesCardFeatureConfig } from "./types"; import { filterModes } from "./common/filter-modes"; +import { ClimateHvacModesCardFeatureConfig } from "./types"; export const supportsClimateHvacModesCardFeature = (stateObj: HassEntity) => { const domain = computeDomain(stateObj.entity_id); @@ -46,7 +46,6 @@ class HuiClimateHvacModesCardFeature static getStubConfig(): ClimateHvacModesCardFeatureConfig { return { type: "climate-hvac-modes", - hvac_modes: [], }; } @@ -120,10 +119,12 @@ class HuiClimateHvacModesCardFeature const color = stateColorCss(this.stateObj); + const ordererHvacModes = (this.stateObj.attributes.hvac_modes || []) + .concat() + .sort(compareClimateHvacModes); + const options = filterModes( - [...(this.stateObj?.attributes.hvac_modes || [])].sort( - compareClimateHvacModes - ), + ordererHvacModes, this._config.hvac_modes ).map((mode) => ({ value: mode, diff --git a/src/panels/lovelace/card-features/hui-climate-preset-modes-card-feature.ts b/src/panels/lovelace/card-features/hui-climate-preset-modes-card-feature.ts index 84e3d61acf85..5cd1e233e53c 100644 --- a/src/panels/lovelace/card-features/hui-climate-preset-modes-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-climate-preset-modes-card-feature.ts @@ -45,7 +45,6 @@ class HuiClimatePresetModesCardFeature return { type: "climate-preset-modes", style: "dropdown", - preset_modes: [], }; } diff --git a/src/panels/lovelace/card-features/hui-climate-swing-modes-card-feature.ts b/src/panels/lovelace/card-features/hui-climate-swing-modes-card-feature.ts index 6d5399f3e260..adc88c1948d0 100644 --- a/src/panels/lovelace/card-features/hui-climate-swing-modes-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-climate-swing-modes-card-feature.ts @@ -45,7 +45,6 @@ class HuiClimateSwingModesCardFeature return { type: "climate-swing-modes", style: "dropdown", - swing_modes: [], }; } diff --git a/src/panels/lovelace/card-features/hui-fan-preset-modes-card-feature.ts b/src/panels/lovelace/card-features/hui-fan-preset-modes-card-feature.ts index 42066f146b23..d1cfb0fcc296 100644 --- a/src/panels/lovelace/card-features/hui-fan-preset-modes-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-fan-preset-modes-card-feature.ts @@ -44,7 +44,6 @@ class HuiFanPresetModesCardFeature return { type: "fan-preset-modes", style: "dropdown", - preset_modes: [], }; } diff --git a/src/panels/lovelace/card-features/hui-humidifier-modes-card-feature.ts b/src/panels/lovelace/card-features/hui-humidifier-modes-card-feature.ts index 1f9c970194fd..ee5176f9adba 100644 --- a/src/panels/lovelace/card-features/hui-humidifier-modes-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-humidifier-modes-card-feature.ts @@ -48,7 +48,6 @@ class HuiHumidifierModesCardFeature return { type: "humidifier-modes", style: "dropdown", - modes: [], }; } diff --git a/src/panels/lovelace/card-features/hui-water-heater-operation-modes-card-feature.ts b/src/panels/lovelace/card-features/hui-water-heater-operation-modes-card-feature.ts index 3dbb61162208..f676b16ca699 100644 --- a/src/panels/lovelace/card-features/hui-water-heater-operation-modes-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-water-heater-operation-modes-card-feature.ts @@ -44,7 +44,6 @@ class HuiWaterHeaterOperationModeCardFeature static getStubConfig(): WaterHeaterOperationModesCardFeatureConfig { return { type: "water-heater-operation-modes", - operation_modes: [], }; } @@ -105,10 +104,12 @@ class HuiWaterHeaterOperationModeCardFeature const color = stateColorCss(this.stateObj); + const orderedModes = (this.stateObj.attributes.operation_list || []) + .concat() + .sort(compareWaterHeaterOperationMode); + const options = filterModes( - [...(this.stateObj?.attributes.operation_list || [])].sort( - compareWaterHeaterOperationMode - ), + orderedModes, this._config.operation_modes ).map((mode) => ({ value: mode, diff --git a/src/panels/lovelace/editor/config-elements/hui-climate-fan-modes-card-feature-editor.ts b/src/panels/lovelace/editor/config-elements/hui-climate-fan-modes-card-feature-editor.ts index dafd27ec1289..85c505913525 100644 --- a/src/panels/lovelace/editor/config-elements/hui-climate-fan-modes-card-feature-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-climate-fan-modes-card-feature-editor.ts @@ -17,6 +17,10 @@ import { } from "../../card-features/types"; import type { LovelaceCardFeatureEditor } from "../../types"; +type ClimateFanModesCardFeatureData = ClimateFanModesCardFeatureConfig & { + customize_modes: boolean; +}; + @customElement("hui-climate-fan-modes-card-feature-editor") export class HuiClimateFanModesCardFeatureEditor extends LitElement @@ -36,7 +40,8 @@ export class HuiClimateFanModesCardFeatureEditor ( localize: LocalizeFunc, formatEntityAttributeValue: FormatEntityAttributeValueFunc, - stateObj?: HassEntity + stateObj: HassEntity | undefined, + customizeModes: boolean ) => [ { @@ -55,20 +60,33 @@ export class HuiClimateFanModesCardFeatureEditor }, }, { - name: "fan_modes", + name: "customize_modes", selector: { - select: { - multiple: true, - reorder: true, - mode: "list", - options: - stateObj?.attributes.fan_modes?.map((mode) => ({ - value: mode, - label: formatEntityAttributeValue(stateObj, "fan_mode", mode), - })) || [], - }, + boolean: {}, }, }, + ...(customizeModes + ? ([ + { + name: "fan_modes", + selector: { + select: { + multiple: true, + reorder: true, + options: + stateObj?.attributes.fan_modes?.map((mode) => ({ + value: mode, + label: formatEntityAttributeValue( + stateObj, + "fan_mode", + mode + ), + })) || [], + }, + }, + }, + ] as const satisfies readonly HaFormSchema[]) + : []), ] as const satisfies readonly HaFormSchema[] ); @@ -81,16 +99,17 @@ export class HuiClimateFanModesCardFeatureEditor ? this.hass.states[this.context?.entity_id] : undefined; - const data: ClimateFanModesCardFeatureConfig = { + const data: ClimateFanModesCardFeatureData = { style: "dropdown", - fan_modes: [], ...this._config, + customize_modes: this._config.fan_modes !== undefined, }; const schema = this._schema( this.hass.localize, this.hass.formatEntityAttributeValue, - stateObj + stateObj, + data.customize_modes ); return html` @@ -105,7 +124,21 @@ export class HuiClimateFanModesCardFeatureEditor } private _valueChanged(ev: CustomEvent): void { - fireEvent(this, "config-changed", { config: ev.detail.value }); + const { customize_modes, ...config } = ev.detail + .value as ClimateFanModesCardFeatureData; + + const stateObj = this.context?.entity_id + ? this.hass!.states[this.context?.entity_id] + : undefined; + + if (customize_modes && !config.fan_modes) { + config.fan_modes = stateObj?.attributes.fan_modes || []; + } + if (!customize_modes && config.fan_modes) { + delete config.fan_modes; + } + + fireEvent(this, "config-changed", { config: config }); } private _computeLabelCallback = ( @@ -114,6 +147,7 @@ export class HuiClimateFanModesCardFeatureEditor switch (schema.name) { case "style": case "fan_modes": + case "customize_modes": return this.hass!.localize( `ui.panel.lovelace.editor.features.types.climate-fan-modes.${schema.name}` ); diff --git a/src/panels/lovelace/editor/config-elements/hui-climate-hvac-modes-card-feature-editor.ts b/src/panels/lovelace/editor/config-elements/hui-climate-hvac-modes-card-feature-editor.ts index 1d6aa6774f81..87663228aabb 100644 --- a/src/panels/lovelace/editor/config-elements/hui-climate-hvac-modes-card-feature-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-climate-hvac-modes-card-feature-editor.ts @@ -6,8 +6,11 @@ import { fireEvent } from "../../../../common/dom/fire_event"; import type { FormatEntityStateFunc } from "../../../../common/translations/entity-state"; import type { LocalizeFunc } from "../../../../common/translations/localize"; import "../../../../components/ha-form/ha-form"; -import type { SchemaUnion } from "../../../../components/ha-form/types"; -import { HVAC_MODES } from "../../../../data/climate"; +import type { + HaFormSchema, + SchemaUnion, +} from "../../../../components/ha-form/types"; +import { compareClimateHvacModes } from "../../../../data/climate"; import type { HomeAssistant } from "../../../../types"; import { ClimateHvacModesCardFeatureConfig, @@ -15,6 +18,10 @@ import { } from "../../card-features/types"; import type { LovelaceCardFeatureEditor } from "../../types"; +type ClimateHvacModesCardFeatureData = ClimateHvacModesCardFeatureConfig & { + customize_modes: boolean; +}; + @customElement("hui-climate-hvac-modes-card-feature-editor") export class HuiClimateHvacModesCardFeatureEditor extends LitElement @@ -34,7 +41,8 @@ export class HuiClimateHvacModesCardFeatureEditor ( localize: LocalizeFunc, formatEntityState: FormatEntityStateFunc, - stateObj?: HassEntity + stateObj: HassEntity | undefined, + customizeModes: boolean ) => [ { @@ -53,22 +61,34 @@ export class HuiClimateHvacModesCardFeatureEditor }, }, { - name: "hvac_modes", + name: "customize_modes", selector: { - select: { - multiple: true, - reorder: true, - mode: "list", - options: HVAC_MODES.filter((mode) => - stateObj?.attributes.hvac_modes?.includes(mode) - ).map((mode) => ({ - value: mode, - label: stateObj ? formatEntityState(stateObj, mode) : mode, - })), - }, + boolean: {}, }, }, - ] as const + ...(customizeModes + ? ([ + { + name: "hvac_modes", + selector: { + select: { + reorder: true, + multiple: true, + options: (stateObj?.attributes.hvac_modes || []) + .concat() + .sort(compareClimateHvacModes) + .map((mode) => ({ + value: mode, + label: stateObj + ? formatEntityState(stateObj, mode) + : mode, + })), + }, + }, + }, + ] as const satisfies readonly HaFormSchema[]) + : []), + ] as const satisfies readonly HaFormSchema[] ); protected render() { @@ -80,16 +100,17 @@ export class HuiClimateHvacModesCardFeatureEditor ? this.hass.states[this.context?.entity_id] : undefined; - const data: ClimateHvacModesCardFeatureConfig = { + const data: ClimateHvacModesCardFeatureData = { style: "icons", - hvac_modes: [], ...this._config, + customize_modes: this._config.hvac_modes !== undefined, }; const schema = this._schema( this.hass.localize, this.hass.formatEntityState, - stateObj + stateObj, + data.customize_modes ); return html` @@ -104,7 +125,24 @@ export class HuiClimateHvacModesCardFeatureEditor } private _valueChanged(ev: CustomEvent): void { - fireEvent(this, "config-changed", { config: ev.detail.value }); + const { customize_modes, ...config } = ev.detail + .value as ClimateHvacModesCardFeatureData; + + const stateObj = this.context?.entity_id + ? this.hass!.states[this.context?.entity_id] + : undefined; + + if (customize_modes && !config.hvac_modes) { + const ordererHvacModes = (stateObj?.attributes.hvac_modes || []) + .concat() + .sort(compareClimateHvacModes); + config.hvac_modes = ordererHvacModes; + } + if (!customize_modes && config.hvac_modes) { + delete config.hvac_modes; + } + + fireEvent(this, "config-changed", { config: config }); } private _computeLabelCallback = ( @@ -113,6 +151,7 @@ export class HuiClimateHvacModesCardFeatureEditor switch (schema.name) { case "hvac_modes": case "style": + case "customize_modes": return this.hass!.localize( `ui.panel.lovelace.editor.features.types.climate-hvac-modes.${schema.name}` ); diff --git a/src/panels/lovelace/editor/config-elements/hui-climate-preset-modes-card-feature-editor.ts b/src/panels/lovelace/editor/config-elements/hui-climate-preset-modes-card-feature-editor.ts index db33052af105..344fc4da8491 100644 --- a/src/panels/lovelace/editor/config-elements/hui-climate-preset-modes-card-feature-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-climate-preset-modes-card-feature-editor.ts @@ -17,6 +17,10 @@ import { } from "../../card-features/types"; import type { LovelaceCardFeatureEditor } from "../../types"; +type ClimatePresetModesCardFeatureData = ClimatePresetModesCardFeatureConfig & { + customize_modes: boolean; +}; + @customElement("hui-climate-preset-modes-card-feature-editor") export class HuiClimatePresetModesCardFeatureEditor extends LitElement @@ -36,7 +40,8 @@ export class HuiClimatePresetModesCardFeatureEditor ( localize: LocalizeFunc, formatEntityAttributeValue: FormatEntityAttributeValueFunc, - stateObj?: HassEntity + stateObj: HassEntity | undefined, + customizeModes: boolean ) => [ { @@ -55,24 +60,33 @@ export class HuiClimatePresetModesCardFeatureEditor }, }, { - name: "preset_modes", + name: "customize_modes", selector: { - select: { - multiple: true, - reorder: true, - mode: "list", - options: - stateObj?.attributes.preset_modes?.map((mode) => ({ - value: mode, - label: formatEntityAttributeValue( - stateObj, - "preset_mode", - mode - ), - })) || [], - }, + boolean: {}, }, }, + ...(customizeModes + ? ([ + { + name: "preset_modes", + selector: { + select: { + reorder: true, + multiple: true, + options: + stateObj?.attributes.preset_modes?.map((mode) => ({ + value: mode, + label: formatEntityAttributeValue( + stateObj, + "preset_mode", + mode + ), + })) || [], + }, + }, + }, + ] as const satisfies readonly HaFormSchema[]) + : []), ] as const satisfies readonly HaFormSchema[] ); @@ -85,16 +99,17 @@ export class HuiClimatePresetModesCardFeatureEditor ? this.hass.states[this.context?.entity_id] : undefined; - const data: ClimatePresetModesCardFeatureConfig = { + const data: ClimatePresetModesCardFeatureData = { style: "dropdown", - preset_modes: [], ...this._config, + customize_modes: this._config.preset_modes !== undefined, }; const schema = this._schema( this.hass.localize, this.hass.formatEntityAttributeValue, - stateObj + stateObj, + data.customize_modes ); return html` @@ -109,7 +124,21 @@ export class HuiClimatePresetModesCardFeatureEditor } private _valueChanged(ev: CustomEvent): void { - fireEvent(this, "config-changed", { config: ev.detail.value }); + const { customize_modes, ...config } = ev.detail + .value as ClimatePresetModesCardFeatureData; + + const stateObj = this.context?.entity_id + ? this.hass!.states[this.context?.entity_id] + : undefined; + + if (customize_modes && !config.preset_modes) { + config.preset_modes = stateObj?.attributes.preset_modes || []; + } + if (!customize_modes && config.preset_modes) { + delete config.preset_modes; + } + + fireEvent(this, "config-changed", { config: config }); } private _computeLabelCallback = ( @@ -118,6 +147,7 @@ export class HuiClimatePresetModesCardFeatureEditor switch (schema.name) { case "style": case "preset_modes": + case "customize_modes": return this.hass!.localize( `ui.panel.lovelace.editor.features.types.climate-preset-modes.${schema.name}` ); diff --git a/src/panels/lovelace/editor/config-elements/hui-climate-swing-modes-card-feature-editor.ts b/src/panels/lovelace/editor/config-elements/hui-climate-swing-modes-card-feature-editor.ts index a490a86fc5f1..a8c4f4c09c3c 100644 --- a/src/panels/lovelace/editor/config-elements/hui-climate-swing-modes-card-feature-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-climate-swing-modes-card-feature-editor.ts @@ -17,6 +17,10 @@ import { } from "../../card-features/types"; import type { LovelaceCardFeatureEditor } from "../../types"; +type ClimateSwingModesCardFeatureData = ClimateSwingModesCardFeatureConfig & { + customize_modes: boolean; +}; + @customElement("hui-climate-swing-modes-card-feature-editor") export class HuiClimateSwingModesCardFeatureEditor extends LitElement @@ -36,7 +40,8 @@ export class HuiClimateSwingModesCardFeatureEditor ( localize: LocalizeFunc, formatEntityAttributeValue: FormatEntityAttributeValueFunc, - stateObj?: HassEntity + stateObj: HassEntity | undefined, + customizeModes: boolean ) => [ { @@ -55,24 +60,33 @@ export class HuiClimateSwingModesCardFeatureEditor }, }, { - name: "swing_modes", + name: "customize_modes", selector: { - select: { - multiple: true, - reorder: true, - mode: "list", - options: - stateObj?.attributes.swing_modes?.map((mode) => ({ - value: mode, - label: formatEntityAttributeValue( - stateObj, - "swing_mode", - mode - ), - })) || [], - }, + boolean: {}, }, }, + ...(customizeModes + ? ([ + { + name: "swing_modes", + selector: { + select: { + reorder: true, + multiple: true, + options: + stateObj?.attributes.swing_modes?.map((mode) => ({ + value: mode, + label: formatEntityAttributeValue( + stateObj, + "swing_mode", + mode + ), + })) || [], + }, + }, + }, + ] as const satisfies readonly HaFormSchema[]) + : []), ] as const satisfies readonly HaFormSchema[] ); @@ -85,16 +99,17 @@ export class HuiClimateSwingModesCardFeatureEditor ? this.hass.states[this.context?.entity_id] : undefined; - const data: ClimateSwingModesCardFeatureConfig = { + const data: ClimateSwingModesCardFeatureData = { style: "dropdown", - swing_modes: [], ...this._config, + customize_modes: this._config.swing_modes !== undefined, }; const schema = this._schema( this.hass.localize, this.hass.formatEntityAttributeValue, - stateObj + stateObj, + data.customize_modes ); return html` @@ -109,7 +124,21 @@ export class HuiClimateSwingModesCardFeatureEditor } private _valueChanged(ev: CustomEvent): void { - fireEvent(this, "config-changed", { config: ev.detail.value }); + const { customize_modes, ...config } = ev.detail + .value as ClimateSwingModesCardFeatureData; + + const stateObj = this.context?.entity_id + ? this.hass!.states[this.context?.entity_id] + : undefined; + + if (customize_modes && !config.swing_modes) { + config.swing_modes = stateObj?.attributes.swing_modes || []; + } + if (!customize_modes && config.swing_modes) { + delete config.swing_modes; + } + + fireEvent(this, "config-changed", { config: config }); } private _computeLabelCallback = ( @@ -118,6 +147,7 @@ export class HuiClimateSwingModesCardFeatureEditor switch (schema.name) { case "style": case "swing_modes": + case "customize_modes": return this.hass!.localize( `ui.panel.lovelace.editor.features.types.climate-swing-modes.${schema.name}` ); diff --git a/src/panels/lovelace/editor/config-elements/hui-fan-preset-modes-card-feature-editor.ts b/src/panels/lovelace/editor/config-elements/hui-fan-preset-modes-card-feature-editor.ts index 5c4d688f672e..acc3cc18319d 100644 --- a/src/panels/lovelace/editor/config-elements/hui-fan-preset-modes-card-feature-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-fan-preset-modes-card-feature-editor.ts @@ -17,6 +17,10 @@ import { } from "../../card-features/types"; import type { LovelaceCardFeatureEditor } from "../../types"; +type FanPresetModesCardFeatureData = FanPresetModesCardFeatureConfig & { + customize_modes: boolean; +}; + @customElement("hui-fan-preset-modes-card-feature-editor") export class HuiFanPresetModesCardFeatureEditor extends LitElement @@ -36,7 +40,8 @@ export class HuiFanPresetModesCardFeatureEditor ( localize: LocalizeFunc, formatEntityAttributeValue: FormatEntityAttributeValueFunc, - stateObj?: HassEntity + stateObj: HassEntity | undefined, + customizeModes: boolean ) => [ { @@ -55,24 +60,33 @@ export class HuiFanPresetModesCardFeatureEditor }, }, { - name: "preset_modes", + name: "customize_modes", selector: { - select: { - multiple: true, - reorder: true, - mode: "list", - options: - stateObj?.attributes.preset_modes?.map((mode) => ({ - value: mode, - label: formatEntityAttributeValue( - stateObj, - "preset_mode", - mode - ), - })) || [], - }, + boolean: {}, }, }, + ...(customizeModes + ? ([ + { + name: "preset_modes", + selector: { + select: { + reorder: true, + multiple: true, + options: + stateObj?.attributes.preset_modes?.map((mode) => ({ + value: mode, + label: formatEntityAttributeValue( + stateObj, + "preset_mode", + mode + ), + })) || [], + }, + }, + }, + ] as const satisfies readonly HaFormSchema[]) + : []), ] as const satisfies readonly HaFormSchema[] ); @@ -85,16 +99,17 @@ export class HuiFanPresetModesCardFeatureEditor ? this.hass.states[this.context?.entity_id] : undefined; - const data: FanPresetModesCardFeatureConfig = { + const data: FanPresetModesCardFeatureData = { style: "dropdown", - preset_modes: [], ...this._config, + customize_modes: this._config.preset_modes !== undefined, }; const schema = this._schema( this.hass.localize, this.hass.formatEntityAttributeValue, - stateObj + stateObj, + data.customize_modes ); return html` @@ -109,7 +124,21 @@ export class HuiFanPresetModesCardFeatureEditor } private _valueChanged(ev: CustomEvent): void { - fireEvent(this, "config-changed", { config: ev.detail.value }); + const { customize_modes, ...config } = ev.detail + .value as FanPresetModesCardFeatureData; + + const stateObj = this.context?.entity_id + ? this.hass!.states[this.context?.entity_id] + : undefined; + + if (customize_modes && !config.preset_modes) { + config.preset_modes = stateObj?.attributes.preset_modes || []; + } + if (!customize_modes && config.preset_modes) { + delete config.preset_modes; + } + + fireEvent(this, "config-changed", { config: config }); } private _computeLabelCallback = ( @@ -118,6 +147,7 @@ export class HuiFanPresetModesCardFeatureEditor switch (schema.name) { case "style": case "preset_modes": + case "customize_modes": return this.hass!.localize( `ui.panel.lovelace.editor.features.types.fan-preset-modes.${schema.name}` ); diff --git a/src/panels/lovelace/editor/config-elements/hui-humidifier-modes-card-feature-editor.ts b/src/panels/lovelace/editor/config-elements/hui-humidifier-modes-card-feature-editor.ts index 33df27d2dc5e..92e6f778bb50 100644 --- a/src/panels/lovelace/editor/config-elements/hui-humidifier-modes-card-feature-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-humidifier-modes-card-feature-editor.ts @@ -17,6 +17,10 @@ import { } from "../../card-features/types"; import type { LovelaceCardFeatureEditor } from "../../types"; +type HumidifierModesCardFeatureData = HumidifierModesCardFeatureConfig & { + customize_modes: boolean; +}; + @customElement("hui-humidifier-modes-card-feature-editor") export class HuiHumidifierModesCardFeatureEditor extends LitElement @@ -36,7 +40,8 @@ export class HuiHumidifierModesCardFeatureEditor ( localize: LocalizeFunc, formatEntityAttributeValue: FormatEntityAttributeValueFunc, - stateObj?: HassEntity + stateObj: HassEntity | undefined, + customizeModes: boolean ) => [ { @@ -55,20 +60,33 @@ export class HuiHumidifierModesCardFeatureEditor }, }, { - name: "modes", + name: "customize_modes", selector: { - select: { - multiple: true, - reorder: true, - mode: "list", - options: - stateObj?.attributes.available_modes?.map((mode) => ({ - value: mode, - label: formatEntityAttributeValue(stateObj, "mode", mode), - })) || [], - }, + boolean: {}, }, }, + ...(customizeModes + ? ([ + { + name: "modes", + selector: { + select: { + reorder: true, + multiple: true, + options: + stateObj?.attributes.available_modes?.map((mode) => ({ + value: mode, + label: formatEntityAttributeValue( + stateObj, + "mode", + mode + ), + })) || [], + }, + }, + }, + ] as const satisfies readonly HaFormSchema[]) + : []), ] as const satisfies readonly HaFormSchema[] ); @@ -81,16 +99,17 @@ export class HuiHumidifierModesCardFeatureEditor ? this.hass.states[this.context?.entity_id] : undefined; - const data: HumidifierModesCardFeatureConfig = { + const data: HumidifierModesCardFeatureData = { style: "dropdown", - modes: [], ...this._config, + customize_modes: this._config.modes !== undefined, }; const schema = this._schema( this.hass.localize, this.hass.formatEntityAttributeValue, - stateObj + stateObj, + data.customize_modes ); return html` @@ -105,7 +124,21 @@ export class HuiHumidifierModesCardFeatureEditor } private _valueChanged(ev: CustomEvent): void { - fireEvent(this, "config-changed", { config: ev.detail.value }); + const { customize_modes, ...config } = ev.detail + .value as HumidifierModesCardFeatureData; + + const stateObj = this.context?.entity_id + ? this.hass!.states[this.context?.entity_id] + : undefined; + + if (customize_modes && !config.modes) { + config.modes = stateObj?.attributes.available_modes || []; + } + if (!customize_modes && config.modes) { + delete config.modes; + } + + fireEvent(this, "config-changed", { config: config }); } private _computeLabelCallback = ( @@ -114,6 +147,7 @@ export class HuiHumidifierModesCardFeatureEditor switch (schema.name) { case "style": case "modes": + case "customize_modes": return this.hass!.localize( `ui.panel.lovelace.editor.features.types.humidifier-modes.${schema.name}` ); diff --git a/src/panels/lovelace/editor/config-elements/hui-water-heater-operation-modes-card-feature-editor.ts b/src/panels/lovelace/editor/config-elements/hui-water-heater-operation-modes-card-feature-editor.ts index a5763b3401cd..aad60f9034c6 100644 --- a/src/panels/lovelace/editor/config-elements/hui-water-heater-operation-modes-card-feature-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-water-heater-operation-modes-card-feature-editor.ts @@ -5,14 +5,22 @@ import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../common/dom/fire_event"; import type { FormatEntityStateFunc } from "../../../../common/translations/entity-state"; import "../../../../components/ha-form/ha-form"; -import type { SchemaUnion } from "../../../../components/ha-form/types"; +import type { + HaFormSchema, + SchemaUnion, +} from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; import { WaterHeaterOperationModesCardFeatureConfig, LovelaceCardFeatureContext, } from "../../card-features/types"; import type { LovelaceCardFeatureEditor } from "../../types"; -import { OPERATION_MODES } from "../../../../data/water_heater"; +import { compareWaterHeaterOperationMode } from "../../../../data/water_heater"; + +type WaterHeaterOperationModesCardFeatureData = + WaterHeaterOperationModesCardFeatureConfig & { + customize_modes: boolean; + }; @customElement("hui-water-heater-operation-modes-card-feature-editor") export class HuiWaterHeaterOperationModesCardFeatureEditor @@ -30,25 +38,41 @@ export class HuiWaterHeaterOperationModesCardFeatureEditor } private _schema = memoizeOne( - (formatEntityState: FormatEntityStateFunc, stateObj?: HassEntity) => + ( + formatEntityState: FormatEntityStateFunc, + stateObj: HassEntity | undefined, + customizeModes: boolean + ) => [ { - name: "operation_modes", + name: "customize_modes", selector: { - select: { - multiple: true, - reorder: true, - mode: "list", - options: OPERATION_MODES.filter((mode) => - stateObj?.attributes.operation_list?.includes(mode) - ).map((mode) => ({ - value: mode, - label: stateObj ? formatEntityState(stateObj, mode) : mode, - })), - }, + boolean: {}, }, }, - ] as const + ...(customizeModes + ? ([ + { + name: "operation_modes", + selector: { + select: { + reorder: true, + multiple: true, + options: (stateObj?.attributes.operation_list || []) + .concat() + .sort(compareWaterHeaterOperationMode) + .map((mode) => ({ + value: mode, + label: stateObj + ? formatEntityState(stateObj, mode) + : mode, + })), + }, + }, + }, + ] as const satisfies readonly HaFormSchema[]) + : []), + ] as const satisfies readonly HaFormSchema[] ); protected render() { @@ -60,12 +84,21 @@ export class HuiWaterHeaterOperationModesCardFeatureEditor ? this.hass.states[this.context?.entity_id] : undefined; - const schema = this._schema(this.hass.formatEntityState, stateObj); + const data: WaterHeaterOperationModesCardFeatureData = { + ...this._config, + customize_modes: this._config.operation_modes !== undefined, + }; + + const schema = this._schema( + this.hass.formatEntityState, + stateObj, + data.customize_modes + ); return html` { switch (schema.name) { case "operation_modes": + case "customize_modes": return this.hass!.localize( - `ui.panel.lovelace.editor.features.types.water-heater-modes.${schema.name}` + `ui.panel.lovelace.editor.features.types.water-heater-operation-modes.${schema.name}` ); default: - return this.hass!.localize( - `ui.panel.lovelace.editor.card.generic.${schema.name}` - ); + return ""; } }; } diff --git a/src/translations/en.json b/src/translations/en.json index e6c7dadff19d..a92e37fc5a18 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -5996,16 +5996,18 @@ "dropdown": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::dropdown%]", "icons": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::icons%]" }, + "customize_modes": "Customize fan modes", "fan_modes": "Fan modes" }, "climate-swing-modes": { "label": "Climate swing modes", + "swing_modes": "Swing modes", "style": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style%]", "style_list": { "dropdown": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::dropdown%]", "icons": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::icons%]" }, - "swing_modes": "Swing modes" + "customize_modes": "Customize swing modes" }, "climate-hvac-modes": { "label": "Climate HVAC modes", @@ -6014,7 +6016,8 @@ "style_list": { "dropdown": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::dropdown%]", "icons": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::icons%]" - } + }, + "customize_modes": "Customize HVAC modes" }, "climate-preset-modes": { "label": "Climate preset modes", @@ -6023,6 +6026,7 @@ "dropdown": "Dropdown", "icons": "Icons" }, + "customize_modes": "Customize preset modes", "preset_modes": "Preset modes" }, "fan-preset-modes": { @@ -6032,6 +6036,7 @@ "dropdown": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::dropdown%]", "icons": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::icons%]" }, + "customize_modes": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::customize_modes%]", "preset_modes": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::preset_modes%]" }, "humidifier-toggle": { @@ -6044,6 +6049,7 @@ "dropdown": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::dropdown%]", "icons": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::icons%]" }, + "customize_modes": "Customize modes", "modes": "Modes" }, "select-options": { @@ -6065,7 +6071,8 @@ }, "water-heater-operation-modes": { "label": "Water heater operation modes", - "operation_modes": "Operation modes" + "operation_modes": "Operation modes", + "customize_modes": "Customize operation modes" }, "lawn-mower-commands": { "label": "Lawn mower commands", From af9417f2a6bd642c4d1ae737dc796b7ec1d4aca7 Mon Sep 17 00:00:00 2001 From: Adam Kapos Date: Tue, 30 Apr 2024 22:12:36 +0300 Subject: [PATCH 09/14] Add theme support for dialog surface background (#20653) * Add theme support for dialog surface background * Change from review * Change from review Co-authored-by: Bram Kragten * Run prettier --------- Co-authored-by: Bram Kragten --- src/components/ha-dialog.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/ha-dialog.ts b/src/components/ha-dialog.ts index 4d8980a0378e..5beea10a8a18 100644 --- a/src/components/ha-dialog.ts +++ b/src/components/ha-dialog.ts @@ -127,6 +127,10 @@ export class HaDialog extends DialogBase { border-radius: var(--ha-dialog-border-radius, 28px); -webkit-backdrop-filter: var(--ha-dialog-surface-backdrop-filter, none); backdrop-filter: var(--ha-dialog-surface-backdrop-filter, none); + background: var( + --ha-dialog-surface-background, + var(--mdc-theme-surface, #fff) + ); } :host([flexContent]) .mdc-dialog .mdc-dialog__content { display: flex; From 86a7e69812b3fbb08c321b0ee2172cde65825326 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Tue, 30 Apr 2024 21:14:49 +0200 Subject: [PATCH 10/14] Allow to reorder and filter options in select options card feature (#20675) --- .../hui-select-options-card-feature.ts | 17 ++- src/panels/lovelace/card-features/types.ts | 1 + .../hui-card-features-editor.ts | 11 +- .../hui-select-options-card-feature-editor.ts | 140 ++++++++++++++++++ src/translations/en.json | 4 +- 5 files changed, 165 insertions(+), 8 deletions(-) create mode 100644 src/panels/lovelace/editor/config-elements/hui-select-options-card-feature-editor.ts diff --git a/src/panels/lovelace/card-features/hui-select-options-card-feature.ts b/src/panels/lovelace/card-features/hui-select-options-card-feature.ts index 5cb20609ce49..60b99116e44e 100644 --- a/src/panels/lovelace/card-features/hui-select-options-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-select-options-card-feature.ts @@ -9,8 +9,9 @@ import { UNAVAILABLE } from "../../../data/entity"; import { InputSelectEntity } from "../../../data/input_select"; import { SelectEntity } from "../../../data/select"; import { HomeAssistant } from "../../../types"; -import { LovelaceCardFeature } from "../types"; +import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; import { SelectOptionsCardFeatureConfig } from "./types"; +import { filterModes } from "./common/filter-modes"; export const supportsSelectOptionsCardFeature = (stateObj: HassEntity) => { const domain = computeDomain(stateObj.entity_id); @@ -41,6 +42,13 @@ class HuiSelectOptionsCardFeature }; } + public static async getConfigElement(): Promise { + await import( + "../editor/config-elements/hui-select-options-card-feature-editor" + ); + return document.createElement("hui-select-options-card-feature-editor"); + } + public setConfig(config: SelectOptionsCardFeatureConfig): void { if (!config) { throw new Error("Invalid configuration"); @@ -105,6 +113,11 @@ class HuiSelectOptionsCardFeature const stateObj = this.stateObj; + const options = filterModes( + this.stateObj.attributes.options, + this._config.options + ); + return html`
- ${stateObj.attributes.options!.map( + ${options.map( (option) => html` ${this.hass!.formatEntityState(stateObj, option)} diff --git a/src/panels/lovelace/card-features/types.ts b/src/panels/lovelace/card-features/types.ts index 82fa311876a5..04a0f1df5fa7 100644 --- a/src/panels/lovelace/card-features/types.ts +++ b/src/panels/lovelace/card-features/types.ts @@ -75,6 +75,7 @@ export interface ClimatePresetModesCardFeatureConfig { export interface SelectOptionsCardFeatureConfig { type: "select-options"; + options?: string[]; } export interface NumericInputCardFeatureConfig { diff --git a/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts b/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts index c4b938230a4c..288a6976b814 100644 --- a/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts @@ -21,9 +21,9 @@ import { import { HomeAssistant } from "../../../../types"; import { supportsAlarmModesCardFeature } from "../../card-features/hui-alarm-modes-card-feature"; import { supportsClimateFanModesCardFeature } from "../../card-features/hui-climate-fan-modes-card-feature"; -import { supportsClimateSwingModesCardFeature } from "../../card-features/hui-climate-swing-modes-card-feature"; import { supportsClimateHvacModesCardFeature } from "../../card-features/hui-climate-hvac-modes-card-feature"; import { supportsClimatePresetModesCardFeature } from "../../card-features/hui-climate-preset-modes-card-feature"; +import { supportsClimateSwingModesCardFeature } from "../../card-features/hui-climate-swing-modes-card-feature"; import { supportsCoverOpenCloseCardFeature } from "../../card-features/hui-cover-open-close-card-feature"; import { supportsCoverPositionCardFeature } from "../../card-features/hui-cover-position-card-feature"; import { supportsCoverTiltCardFeature } from "../../card-features/hui-cover-tilt-card-feature"; @@ -53,13 +53,13 @@ type SupportsFeature = (stateObj: HassEntity) => boolean; const UI_FEATURE_TYPES = [ "alarm-modes", "climate-fan-modes", - "climate-swing-modes", "climate-hvac-modes", "climate-preset-modes", + "climate-swing-modes", "cover-open-close", "cover-position", - "cover-tilt", "cover-tilt-position", + "cover-tilt", "fan-preset-modes", "fan-speed", "humidifier-modes", @@ -82,14 +82,15 @@ type UiFeatureTypes = (typeof UI_FEATURE_TYPES)[number]; const EDITABLES_FEATURE_TYPES = new Set([ "alarm-modes", - "climate-hvac-modes", "climate-fan-modes", - "climate-swing-modes", + "climate-hvac-modes", "climate-preset-modes", + "climate-swing-modes", "fan-preset-modes", "humidifier-modes", "lawn-mower-commands", "numeric-input", + "select-options", "update-actions", "vacuum-commands", "water-heater-operation-modes", diff --git a/src/panels/lovelace/editor/config-elements/hui-select-options-card-feature-editor.ts b/src/panels/lovelace/editor/config-elements/hui-select-options-card-feature-editor.ts new file mode 100644 index 000000000000..65b4dc6faf12 --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/hui-select-options-card-feature-editor.ts @@ -0,0 +1,140 @@ +import { HassEntity } from "home-assistant-js-websocket"; +import { html, LitElement, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { FormatEntityStateFunc } from "../../../../common/translations/entity-state"; +import "../../../../components/ha-form/ha-form"; +import type { + HaFormSchema, + SchemaUnion, +} from "../../../../components/ha-form/types"; +import type { HomeAssistant } from "../../../../types"; +import { + LovelaceCardFeatureContext, + SelectOptionsCardFeatureConfig, +} from "../../card-features/types"; +import type { LovelaceCardFeatureEditor } from "../../types"; + +type SelectOptionsCardFeatureData = SelectOptionsCardFeatureConfig & { + customize_options: boolean; +}; + +@customElement("hui-select-options-card-feature-editor") +export class HuiSelectOptionsCardFeatureEditor + extends LitElement + implements LovelaceCardFeatureEditor +{ + @property({ attribute: false }) public hass?: HomeAssistant; + + @property({ attribute: false }) public context?: LovelaceCardFeatureContext; + + @state() private _config?: SelectOptionsCardFeatureConfig; + + public setConfig(config: SelectOptionsCardFeatureConfig): void { + this._config = config; + } + + private _schema = memoizeOne( + ( + formatEntityState: FormatEntityStateFunc, + stateObj: HassEntity | undefined, + customizeOptions: boolean + ) => + [ + { + name: "customize_options", + selector: { + boolean: {}, + }, + }, + ...(customizeOptions + ? ([ + { + name: "options", + selector: { + select: { + multiple: true, + reorder: true, + options: + stateObj?.attributes.options?.map((option) => ({ + value: option, + label: formatEntityState(stateObj, option), + })) || [], + }, + }, + }, + ] as const satisfies readonly HaFormSchema[]) + : []), + ] as const satisfies readonly HaFormSchema[] + ); + + protected render() { + if (!this.hass || !this._config) { + return nothing; + } + + const stateObj = this.context?.entity_id + ? this.hass.states[this.context?.entity_id] + : undefined; + + const data: SelectOptionsCardFeatureData = { + ...this._config, + customize_options: this._config.options !== undefined, + }; + + const schema = this._schema( + this.hass.formatEntityState, + stateObj, + data.customize_options + ); + + return html` + + `; + } + + private _valueChanged(ev: CustomEvent): void { + const { customize_options, ...config } = ev.detail + .value as SelectOptionsCardFeatureData; + + const stateObj = this.context?.entity_id + ? this.hass!.states[this.context?.entity_id] + : undefined; + + if (customize_options && !config.options) { + config.options = stateObj?.attributes.options || []; + } + if (!customize_options && config.options) { + delete config.options; + } + + fireEvent(this, "config-changed", { config: config }); + } + + private _computeLabelCallback = ( + schema: SchemaUnion> + ) => { + switch (schema.name) { + case "options": + case "customize_options": + return this.hass!.localize( + `ui.panel.lovelace.editor.features.types.select-options.${schema.name}` + ); + default: + return ""; + } + }; +} + +declare global { + interface HTMLElementTagNameMap { + "hui-select-options-card-feature-editor": HuiSelectOptionsCardFeatureEditor; + } +} diff --git a/src/translations/en.json b/src/translations/en.json index a92e37fc5a18..f7365a2c70d6 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -6053,7 +6053,9 @@ "modes": "Modes" }, "select-options": { - "label": "Select options" + "label": "Select options", + "options": "Options", + "customize_options": "Customize options" }, "numeric-input": { "label": "Numeric input", From d0df029ff17e7d8a23e371f2a4d99acf91444ad6 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Tue, 30 Apr 2024 21:21:30 +0200 Subject: [PATCH 11/14] Update check update icon and add toast when checking update (#20677) * Update check update icon * Add toast when checking for update --- hassio/src/dashboard/hassio-dashboard.ts | 16 ++++++++-------- src/data/update.ts | 4 ++++ .../config/core/ha-config-section-updates.ts | 10 +++++----- .../config/dashboard/ha-config-dashboard.ts | 4 ++-- src/translations/en.json | 1 + 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/hassio/src/dashboard/hassio-dashboard.ts b/hassio/src/dashboard/hassio-dashboard.ts index e678a4299285..1eaf53947ddc 100644 --- a/hassio/src/dashboard/hassio-dashboard.ts +++ b/hassio/src/dashboard/hassio-dashboard.ts @@ -1,19 +1,19 @@ -import { mdiStorePlus, mdiUpdate } from "@mdi/js"; -import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { mdiRefresh, mdiStorePlus } from "@mdi/js"; +import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit"; import { customElement, property } from "lit/decorators"; import { atLeastVersion } from "../../../src/common/config/version"; +import { fireEvent } from "../../../src/common/dom/fire_event"; import "../../../src/components/ha-fab"; +import { reloadHassioAddons } from "../../../src/data/hassio/addon"; +import { extractApiErrorMessage } from "../../../src/data/hassio/common"; import { Supervisor } from "../../../src/data/supervisor/supervisor"; +import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box"; +import "../../../src/layouts/hass-subpage"; import "../../../src/layouts/hass-tabs-subpage"; import { haStyle } from "../../../src/resources/styles"; import { HomeAssistant, Route } from "../../../src/types"; import { supervisorTabs } from "../hassio-tabs"; import "./hassio-addons"; -import "../../../src/layouts/hass-subpage"; -import { reloadHassioAddons } from "../../../src/data/hassio/addon"; -import { extractApiErrorMessage } from "../../../src/data/hassio/common"; -import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box"; -import { fireEvent } from "../../../src/common/dom/fire_event"; @customElement("hassio-dashboard") class HassioDashboard extends LitElement { @@ -43,7 +43,7 @@ class HassioDashboard extends LitElement { ( diff --git a/src/panels/config/core/ha-config-section-updates.ts b/src/panels/config/core/ha-config-section-updates.ts index cdf7d896fa84..9f3191b7be48 100644 --- a/src/panels/config/core/ha-config-section-updates.ts +++ b/src/panels/config/core/ha-config-section-updates.ts @@ -1,7 +1,7 @@ import { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item"; -import { mdiDotsVertical, mdiUpdate } from "@mdi/js"; +import { mdiDotsVertical, mdiRefresh } from "@mdi/js"; import { HassEntities } from "home-assistant-js-websocket"; -import { css, html, LitElement, TemplateResult } from "lit"; +import { LitElement, TemplateResult, css, html } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; @@ -14,11 +14,11 @@ import "../../../components/ha-check-list-item"; import "../../../components/ha-metric"; import { extractApiErrorMessage } from "../../../data/hassio/common"; import { - fetchHassioSupervisorInfo, HassioSupervisorInfo, + SupervisorOptions, + fetchHassioSupervisorInfo, reloadSupervisor, setSupervisorOption, - SupervisorOptions, } from "../../../data/hassio/supervisor"; import { checkForEntityUpdates, @@ -66,7 +66,7 @@ class HaConfigSectionUpdates extends LitElement { .label=${this.hass.localize( "ui.panel.config.updates.check_updates" )} - .path=${mdiUpdate} + .path=${mdiRefresh} @click=${this._checkUpdates} > diff --git a/src/panels/config/dashboard/ha-config-dashboard.ts b/src/panels/config/dashboard/ha-config-dashboard.ts index 9e4562f9a4ec..6b11299a6418 100644 --- a/src/panels/config/dashboard/ha-config-dashboard.ts +++ b/src/panels/config/dashboard/ha-config-dashboard.ts @@ -4,7 +4,7 @@ import { mdiDotsVertical, mdiMagnify, mdiPower, - mdiUpdate, + mdiRefresh, } from "@mdi/js"; import { HassEntities, UnsubscribeFunc } from "home-assistant-js-websocket"; import { @@ -206,7 +206,7 @@ class HaConfigDashboard extends SubscribeMixin(LitElement) { ${this.hass.localize("ui.panel.config.updates.check_updates")} - + diff --git a/src/translations/en.json b/src/translations/en.json index f7365a2c70d6..66a1bc51d72e 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1889,6 +1889,7 @@ "check_updates": "Check for updates", "no_new_updates": "No new updates found", "updates_refreshed": "{count} {count, plural,\n one {update}\n other {updates}\n} refreshed", + "checking_updates": "Checking for updates...", "title": "{count} {count, plural,\n one {update}\n other {updates}\n}", "unable_to_fetch": "Unable to load updates", "more_updates": "Show all updates", From 17dcc906380f1e03aac76c8aba0ba40efd658f03 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 30 Apr 2024 23:04:48 +0200 Subject: [PATCH 12/14] Update entity status filter and grouping (#20679) --- .../config/entities/ha-config-entities.ts | 142 ++++++++++-------- .../config/helpers/ha-config-helpers.ts | 2 +- src/translations/en.json | 13 +- 3 files changed, 92 insertions(+), 65 deletions(-) diff --git a/src/panels/config/entities/ha-config-entities.ts b/src/panels/config/entities/ha-config-entities.ts index e90e6465cee0..d4f96fb04fac 100644 --- a/src/panels/config/entities/ha-config-entities.ts +++ b/src/panels/config/entities/ha-config-entities.ts @@ -28,7 +28,6 @@ import { customElement, property, query, state } from "lit/decorators"; import { ifDefined } from "lit/directives/if-defined"; import { styleMap } from "lit/directives/style-map"; import memoize from "memoize-one"; -import { stringCompare } from "../../../common/string/compare"; import { computeCssColor } from "../../../common/color/compute-color"; import { storage } from "../../../common/decorators/storage"; import type { HASSDomEvent } from "../../../common/dom/fire_event"; @@ -117,6 +116,9 @@ export interface EntityRow extends StateEntity { localized_platform: string; domain: string; label_entries: LabelRegistryEntry[]; + enabled: string; + visible: string; + available: string; } @customElement("ha-config-entities") @@ -197,40 +199,40 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { } }; - private _states = memoize((localize: LocalizeFunc) => - [ - { - value: "available", - label: localize("ui.panel.config.entities.picker.status.available"), - }, - { - value: "disabled", - label: localize("ui.panel.config.entities.picker.status.disabled"), - }, - { - value: "hidden", - label: localize("ui.panel.config.entities.picker.status.hidden"), - }, - { - value: "unavailable", - label: localize("ui.panel.config.entities.picker.status.unavailable"), - }, - { - value: "readonly", - label: localize("ui.panel.config.entities.picker.status.readonly"), - }, - { - value: "restored", - label: localize("ui.panel.config.entities.picker.status.restored"), - }, - ].sort((a, b) => - stringCompare( - a.label || a.value, - b.label || b.value, - this.hass.locale.language - ) - ) - ); + private _states = memoize((localize: LocalizeFunc) => [ + { + value: "available", + label: localize("ui.panel.config.entities.picker.status.available"), + }, + { + value: "unavailable", + label: localize("ui.panel.config.entities.picker.status.unavailable"), + }, + { + value: "enabled", + label: localize("ui.panel.config.entities.picker.status.enabled"), + }, + { + value: "disabled", + label: localize("ui.panel.config.entities.picker.status.disabled"), + }, + { + value: "visible", + label: localize("ui.panel.config.entities.picker.status.visible"), + }, + { + value: "hidden", + label: localize("ui.panel.config.entities.picker.status.hidden"), + }, + { + value: "readonly", + label: localize("ui.panel.config.entities.picker.status.unmanageable"), + }, + { + value: "restored", + label: localize("ui.panel.config.entities.picker.status.not_provided"), + }, + ]); private _columns = memoize( ( @@ -327,7 +329,6 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { type: "icon", sortable: true, filterable: true, - groupable: true, width: "68px", template: (entry) => entry.unavailable || @@ -356,7 +357,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { ${entry.restored ? this.hass.localize( - "ui.panel.config.entities.picker.status.restored" + "ui.panel.config.entities.picker.status.not_provided" ) : entry.unavailable ? this.hass.localize( @@ -371,13 +372,31 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { "ui.panel.config.entities.picker.status.hidden" ) : this.hass.localize( - "ui.panel.config.entities.picker.status.readonly" + "ui.panel.config.entities.picker.status.unmanageable" )}
` : "—", }, + available: { + title: localize("ui.panel.config.entities.picker.headers.availability"), + sortable: true, + groupable: true, + hidden: true, + }, + visible: { + title: localize("ui.panel.config.entities.picker.headers.visibility"), + sortable: true, + groupable: true, + hidden: true, + }, + enabled: { + title: localize("ui.panel.config.entities.picker.headers.enabled"), + sortable: true, + groupable: true, + hidden: true, + }, labels: { title: "", hidden: true, @@ -406,18 +425,22 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { const stateFilters = filters["ha-filter-states"]?.value; - const showReadOnly = - !stateFilters?.length || stateFilters.includes("readonly"); + const showEnabled = + !stateFilters?.length || stateFilters.includes("enabled"); const showDisabled = !stateFilters?.length || stateFilters.includes("disabled"); + const showVisible = + !stateFilters?.length || stateFilters.includes("visible"); const showHidden = !stateFilters?.length || stateFilters.includes("hidden"); - const showUnavailable = - !stateFilters?.length || stateFilters.includes("unavailable"); const showAvailable = !stateFilters?.length || stateFilters.includes("available"); + const showUnavailable = + !stateFilters?.length || stateFilters.includes("unavailable"); const showRestored = !stateFilters?.length || stateFilters.includes("restored"); + const showReadOnly = + !stateFilters?.length || stateFilters.includes("readonly"); let filteredEntities = entities.concat(stateEntities); @@ -487,21 +510,17 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { const hidden = !!entry.hidden_by; const disabled = !!entry.disabled_by; const readonly = entry.readonly; - const available = !( - unavailable || - readonly || - restored || - hidden || - disabled - ); + const available = entity?.state && entity.state !== UNAVAILABLE; if ( !( (showAvailable && available) || (showUnavailable && unavailable) || (showRestored && restored) || + (showVisible && !hidden) || (showHidden && hidden) || (showDisabled && disabled) || + (showEnabled && !disabled) || (showReadOnly && readonly) ) ) { @@ -526,7 +545,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { area: area ? area.name : "—", domain: domainToName(localize, computeDomain(entry.entity_id)), status: restored - ? localize("ui.panel.config.entities.picker.status.restored") + ? localize("ui.panel.config.entities.picker.status.not_provided") : unavailable ? localize("ui.panel.config.entities.picker.status.unavailable") : disabled @@ -535,12 +554,21 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { ? localize("ui.panel.config.entities.picker.status.hidden") : readonly ? localize( - "ui.panel.config.entities.picker.status.readonly" + "ui.panel.config.entities.picker.status.unmanageable" ) : localize( "ui.panel.config.entities.picker.status.available" ), label_entries: labelsEntries, + available: unavailable + ? localize("ui.panel.config.entities.picker.status.unavailable") + : localize("ui.panel.config.entities.picker.status.available"), + enabled: disabled + ? localize("ui.panel.config.entities.picker.status.disabled") + : localize("ui.panel.config.entities.picker.status.enabled"), + visible: hidden + ? localize("ui.panel.config.entities.picker.status.hidden") + : localize("ui.panel.config.entities.picker.status.visible"), }); } @@ -887,7 +915,7 @@ ${ protected firstUpdated() { this._filters = { "ha-filter-states": { - value: ["unavailable", "readonly", "restored", "available"], + value: ["enabled"], items: undefined, }, }; @@ -902,10 +930,7 @@ ${ this._filters = { ...this._filters, "ha-filter-states": { - value: [ - ...(this._filters["ha-filter-states"]?.value || []), - "disabled", - ], + value: [], items: undefined, }, "ha-filter-integrations": { @@ -918,10 +943,7 @@ ${ this._filters = { ...this._filters, "ha-filter-states": { - value: [ - ...(this._filters["ha-filter-states"]?.value || []), - "disabled", - ], + value: [], items: undefined, }, config_entry: { diff --git a/src/panels/config/helpers/ha-config-helpers.ts b/src/panels/config/helpers/ha-config-helpers.ts index 6be09330eec9..b90c12b9d8d3 100644 --- a/src/panels/config/helpers/ha-config-helpers.ts +++ b/src/panels/config/helpers/ha-config-helpers.ts @@ -309,7 +309,7 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) { ${this.hass.localize( - "ui.panel.config.entities.picker.status.readonly" + "ui.panel.config.entities.picker.status.unmanageable" )} diff --git a/src/translations/en.json b/src/translations/en.json index 66a1bc51d72e..3bde569c1b51 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -4062,12 +4062,14 @@ "search": "Search {number} entities", "unnamed_entity": "Unnamed entity", "status": { - "restored": "Restored", "available": "Available", "unavailable": "Unavailable", + "enabled": "Enabled", "disabled": "Disabled", - "readonly": "Read-only", - "hidden": "Hidden" + "visible": "Visible", + "hidden": "Hidden", + "not_provided": "Not provided", + "unmanageable": "Unmanageable" }, "headers": { "state_icon": "State icon", @@ -4077,7 +4079,10 @@ "area": "Area", "disabled_by": "Disabled by", "status": "Status", - "domain": "Domain" + "domain": "Domain", + "availability": "Availability", + "visibility": "Visibility", + "enabled": "Enabled" }, "selected": "{number} selected", "enable_selected": { From ab01633069c10d1d55ac2cbcd685baba4a250807 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Tue, 30 Apr 2024 23:12:53 +0200 Subject: [PATCH 13/14] Fix ha settings row display in more info settings (#20680) --- src/components/ha-settings-row.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ha-settings-row.ts b/src/components/ha-settings-row.ts index a516ff6c3bfd..4ba42277e319 100644 --- a/src/components/ha-settings-row.ts +++ b/src/components/ha-settings-row.ts @@ -54,7 +54,7 @@ export class HaSettingsRow extends LitElement { .body[three-line] { min-height: var(--paper-item-body-three-line-min-height, 88px); } - :host(:not([wrap-heading])) > * { + :host(:not([wrap-heading])) body > * { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; From 627e06663b3b2d22f17d5802273ada6c71c8e31b Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 30 Apr 2024 23:44:32 +0200 Subject: [PATCH 14/14] Bumped version to 20240430.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d9646cbb5307..b56f852c165c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20240429.0" +version = "20240430.0" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md"