From 7e5a85dbe75801520a8d49e0cc0a3409be7cc71b Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 30 May 2023 21:32:06 +0200 Subject: [PATCH] Allow to change switch as x (#16671) --- src/data/entity_registry.ts | 5 + .../settings/entity-settings-helper-tab.ts | 7 +- .../entity-registry-settings-editor.ts | 266 +++++++++++++----- .../entities/entity-registry-settings.ts | 18 +- src/translations/en.json | 6 +- 5 files changed, 223 insertions(+), 79 deletions(-) diff --git a/src/data/entity_registry.ts b/src/data/entity_registry.ts index cf6530b946..9063a8eade 100644 --- a/src/data/entity_registry.ts +++ b/src/data/entity_registry.ts @@ -95,12 +95,17 @@ export interface WeatherEntityOptions { wind_speed_unit?: string | null; } +export interface SwitchAsXEntityOptions { + entity_id: string; +} + export interface EntityRegistryOptions { number?: NumberEntityOptions; sensor?: SensorEntityOptions; lock?: LockEntityOptions; weather?: WeatherEntityOptions; light?: LightEntityOptions; + switch_as_x?: SwitchAsXEntityOptions; conversation?: Record; "cloud.alexa"?: Record; "cloud.google_assistant"?: Record; diff --git a/src/panels/config/entities/editor-tabs/settings/entity-settings-helper-tab.ts b/src/panels/config/entities/editor-tabs/settings/entity-settings-helper-tab.ts index 95659ca947..26ba5b9639 100644 --- a/src/panels/config/entities/editor-tabs/settings/entity-settings-helper-tab.ts +++ b/src/panels/config/entities/editor-tabs/settings/entity-settings-helper-tab.ts @@ -16,6 +16,7 @@ import { } from "../../../../../data/entity_registry"; import { HELPERS_CRUD } from "../../../../../data/helpers_crud"; import { showConfirmationDialog } from "../../../../../dialogs/generic/show-dialog-box"; +import { hideMoreInfoDialog } from "../../../../../dialogs/more-info/show-ha-more-info-dialog"; import { haStyle } from "../../../../../resources/styles"; import type { HomeAssistant } from "../../../../../types"; import type { Helper } from "../../../helpers/const"; @@ -148,8 +149,10 @@ export class EntityRegistrySettingsHelper extends LitElement { this._item ); } - await this._registryEditor!.updateEntry(); - fireEvent(this, "close-dialog"); + const result = await this._registryEditor!.updateEntry(); + if (result.close) { + hideMoreInfoDialog(this); + } } catch (err: any) { this._error = err.message || "Unknown error"; } finally { diff --git a/src/panels/config/entities/entity-registry-settings-editor.ts b/src/panels/config/entities/entity-registry-settings-editor.ts index c41f1fb68f..84764efc74 100644 --- a/src/panels/config/entities/entity-registry-settings-editor.ts +++ b/src/panels/config/entities/entity-registry-settings-editor.ts @@ -43,7 +43,7 @@ import { STREAM_TYPE_HLS, updateCameraPrefs, } from "../../../data/camera"; -import { ConfigEntry } from "../../../data/config_entries"; +import { ConfigEntry, deleteConfigEntry } from "../../../data/config_entries"; import { createConfigFlow, handleConfigFlowStep, @@ -57,10 +57,10 @@ import { EntityRegistryEntry, EntityRegistryEntryUpdateParams, ExtEntityRegistryEntry, - fetchEntityRegistry, - SensorEntityOptions, - updateEntityRegistryEntry, LockEntityOptions, + SensorEntityOptions, + subscribeEntityRegistry, + updateEntityRegistryEntry, } from "../../../data/entity_registry"; import { domainToName } from "../../../data/integration"; import { getNumberDeviceClassConvertibleUnits } from "../../../data/number"; @@ -307,6 +307,13 @@ export class EntityRegistrySettingsEditor extends LitElement { this._weatherConvertibleUnits = undefined; } } + if (changedProps.has("helperConfigEntry")) { + if (this.helperConfigEntry?.domain === "switch_as_x") { + this._switchAs = computeDomain(this.entry.entity_id); + } else { + this._switchAs = "switch"; + } + } } protected render() { @@ -366,6 +373,74 @@ export class EntityRegistrySettingsEditor extends LitElement { : undefined} .disabled=${this.disabled} >`} + ${domain === "switch" + ? html` + + ${domainToName(this.hass.localize, "switch")} + + + ${this.hass.localize( + "ui.dialogs.entity_registry.editor.device_classes.switch.outlet" + )} + +
  • + ${this._switchAsDomainsSorted( + SWITCH_AS_DOMAINS, + this.hass.localize + ).map( + (entry) => html` + + ${entry.label} + + ` + )} +
    ` + : this.helperConfigEntry?.domain === "switch_as_x" + ? html` + + ${domainToName(this.hass.localize, "switch")} + + + ${domainToName(this.hass.localize, domain)} + +
  • + ${this._switchAsDomainsSorted( + SWITCH_AS_DOMAINS, + this.hass.localize + ).map((entry) => + domain === entry.domain + ? nothing + : html` + + ${entry.label} + + ` + )} +
    ` + : nothing} ${this._deviceClassOptions ? html` ` : ""} - ${domain === "switch" - ? html` - - ${this.hass.localize( - "ui.dialogs.entity_registry.editor.device_classes.switch.switch" - )} - - - ${this.hass.localize( - "ui.dialogs.entity_registry.editor.device_classes.switch.outlet" - )} - -
  • - ${this._switchAsDomainsSorted( - SWITCH_AS_DOMAINS, - this.hass.localize - ).map( - (entry) => html` - - ${entry.label} - - ` - )} -
    ` - : ""} { - const parent = (this.getRootNode() as ShadowRoot).host as HTMLElement; + public async updateEntry(): Promise<{ + close: boolean; + entry: ExtEntityRegistryEntry; + }> { + let close = true; + // eslint-disable-next-line @typescript-eslint/no-this-alias + let parent: HTMLElement = this; + while (parent?.localName !== "home-assistant") { + parent = (parent.getRootNode() as ShadowRoot).host as HTMLElement; + } const params: Partial = { name: this._name.trim() || null, @@ -979,13 +1023,18 @@ export class EntityRegistrySettingsEditor extends LitElement { }); } - if (this._switchAs !== "switch") { + if (domain === "switch" && this._switchAs !== "switch") { + // generate config flow for switch_as_x if ( await showConfirmationDialog(this, { text: this.hass!.localize( "ui.dialogs.entity_registry.editor.switch_as_x_confirm", - "domain", - this._switchAs + { + domain: domainToName( + this.hass.localize, + this._switchAs + ).toLowerCase(), + } ), }) ) { @@ -999,24 +1048,107 @@ export class EntityRegistrySettingsEditor extends LitElement { } )) as DataEntryFlowStepCreateEntry; if (configFlowResult.result?.entry_id) { - const unsub = await this.hass.connection.subscribeEvents(() => { - unsub(); - fetchEntityRegistry(this.hass.connection).then((entityRegistry) => { - const entity = entityRegistry.find( - (reg) => - reg.config_entry_id === configFlowResult.result!.entry_id + try { + const entry = await this._waitForEntityRegistryUpdate( + configFlowResult.result.entry_id + ); + showMoreInfoDialog(parent, { entityId: entry.entity_id }); + close = false; + } catch (err) { + // ignore + } + } + } + } else if ( + this.helperConfigEntry?.domain === "switch_as_x" && + this._switchAs !== domain + ) { + // change a current switch as x to something else + if ( + await showConfirmationDialog(this, { + text: + this._switchAs === "switch" + ? this.hass!.localize( + "ui.dialogs.entity_registry.editor.switch_as_x_remove_confirm", + { + domain: domainToName( + this.hass.localize, + domain + ).toLowerCase(), + } + ) + : this.hass!.localize( + "ui.dialogs.entity_registry.editor.switch_as_x_change_confirm", + { + domain_1: domainToName( + this.hass.localize, + domain + ).toLowerCase(), + domain_2: domainToName( + this.hass.localize, + this._switchAs + ).toLowerCase(), + } + ), + }) + ) { + const origEntityId = this.entry.options?.switch_as_x?.entity_id; + // remove current helper + await deleteConfigEntry(this.hass, this.helperConfigEntry.entry_id); + + if (!origEntityId) { + // should not happen, guard for types + } else if (this._switchAs === "switch") { + // done, original switch is back + showMoreInfoDialog(parent, { entityId: origEntityId }); + close = false; + } else { + const configFlow = await createConfigFlow(this.hass, "switch_as_x"); + const configFlowResult = (await handleConfigFlowStep( + this.hass, + configFlow.flow_id, + { + entity_id: origEntityId, + target_domain: this._switchAs, + } + )) as DataEntryFlowStepCreateEntry; + if (configFlowResult.result?.entry_id) { + try { + const entry = await this._waitForEntityRegistryUpdate( + configFlowResult.result.entry_id ); - if (!entity) { - return; - } - showMoreInfoDialog(parent, { entityId: entity.entity_id }); - }); - }, "entity_registry_updated"); + showMoreInfoDialog(parent, { entityId: entry.entity_id }); + close = false; + } catch (err) { + // ignore + } + } } } } - return result.entity_entry; + return { close, entry: result.entity_entry }; + } + + private async _waitForEntityRegistryUpdate(config_entry_id: string) { + return new Promise((resolve, reject) => { + const timeout = setTimeout(reject, 5000); + const unsub = subscribeEntityRegistry( + this.hass.connection, + (entityRegistry) => { + const entity = entityRegistry.find( + (reg) => reg.config_entry_id === config_entry_id + ); + if (entity) { + clearTimeout(timeout); + unsub(); + resolve(entity); + } + } + ); + // @ts-ignore Force refresh + this.hass.connection._entityRegistry?.refresh(); + }); } private _nameChanged(ev): void { @@ -1090,7 +1222,11 @@ export class EntityRegistrySettingsEditor extends LitElement { const switchAs = ev.target.value === "outlet" ? "switch" : ev.target.value; this._switchAs = switchAs; - if (ev.target.value === "outlet" || ev.target.value === "switch") { + if ( + (computeDomain(this.entry.entity_id) === "switch" && + ev.target.value === "outlet") || + ev.target.value === "switch" + ) { this._deviceClass = ev.target.value; } } @@ -1177,9 +1313,9 @@ export class EntityRegistrySettingsEditor extends LitElement { private _switchAsDomainsSorted = memoizeOne( (domains: string[], localize: LocalizeFunc) => domains - .map((entry) => ({ - domain: entry, - label: domainToName(localize, entry), + .map((domain) => ({ + domain, + label: domainToName(localize, domain), })) .sort((a, b) => stringCompare(a.label, b.label, this.hass.locale.language) diff --git a/src/panels/config/entities/entity-registry-settings.ts b/src/panels/config/entities/entity-registry-settings.ts index 1671dd5225..f0d0052ee9 100644 --- a/src/panels/config/entities/entity-registry-settings.ts +++ b/src/panels/config/entities/entity-registry-settings.ts @@ -9,7 +9,7 @@ import "../../../components/ha-alert"; import { ConfigEntry, deleteConfigEntry, - getConfigEntries, + getConfigEntry, } from "../../../data/config_entries"; import { updateDeviceRegistryEntry } from "../../../data/device_registry"; import { @@ -21,6 +21,7 @@ import { showAlertDialog, showConfirmationDialog, } from "../../../dialogs/generic/show-dialog-box"; +import { hideMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info-dialog"; import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { haStyle } from "../../../resources/styles"; import type { HomeAssistant } from "../../../types"; @@ -48,13 +49,8 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) { protected firstUpdated(changedProps: PropertyValues): void { super.firstUpdated(changedProps); if (this.entry.config_entry_id) { - getConfigEntries(this.hass, { - type: ["helper"], - domain: this.entry.platform, - }).then((entries) => { - this._helperConfigEntry = entries.find( - (ent) => ent.entry_id === this.entry.config_entry_id - ); + getConfigEntry(this.hass, this.entry.config_entry_id).then((entry) => { + this._helperConfigEntry = entry.config_entry; }); } } @@ -183,8 +179,10 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) { private async _updateEntry(): Promise { this._submitting = true; try { - await this._registryEditor!.updateEntry(); - fireEvent(this, "close-dialog"); + const result = await this._registryEditor!.updateEntry(); + if (result.close) { + hideMoreInfoDialog(this); + } } catch (err: any) { this._error = err.message || "Unknown error"; } finally { diff --git a/src/translations/en.json b/src/translations/en.json index 411cef44d4..769cb458e9 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1002,6 +1002,7 @@ "visibility_unit": "Visibility unit", "wind_speed_unit": "Wind speed unit", "device_class": "Show as", + "switch_as_x": "Show switch as", "device_classes": { "binary_sensor": { "door": "Door", @@ -1046,8 +1047,7 @@ "shutter": "Shutter" }, "switch": { - "outlet": "Outlet", - "switch": "Switch" + "outlet": "Outlet" } }, "unavailable": "This entity is unavailable.", @@ -1063,6 +1063,8 @@ "enable_entity": "Enable", "open_device_settings": "Open device settings", "switch_as_x_confirm": "This switch will be hidden and a new {domain} will be added. Your existing configurations using the switch will continue to work.", + "switch_as_x_remove_confirm": "This {domain} will be removed and the original switch will be visible again. Your existing configurations using the {domain} will no longer work!", + "switch_as_x_change_confirm": "This {domain_1} will be removed and will be replaced by a new {domain_2}. Your existing configurations using the {domain_1} will no longer work!", "enabled_description": "Disabled entities will not be added to Home Assistant.", "enabled_delay_confirm": "The enabled entities will be added to Home Assistant in {delay} seconds", "enabled_restart_confirm": "Restart Home Assistant to finish enabling the entities",