Allow to change switch as x (#16671)

This commit is contained in:
Bram Kragten 2023-05-30 21:32:06 +02:00 committed by GitHub
parent 0771a780d9
commit 7e5a85dbe7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 223 additions and 79 deletions

View File

@ -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<string, unknown>;
"cloud.alexa"?: Record<string, unknown>;
"cloud.google_assistant"?: Record<string, unknown>;

View File

@ -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 {

View File

@ -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}
></ha-icon-picker>`}
${domain === "switch"
? html`<ha-select
.label=${this.hass.localize(
"ui.dialogs.entity_registry.editor.device_class"
)}
naturalMenuWidth
fixedMenuPosition
@selected=${this._switchAsChanged}
@closed=${stopPropagation}
>
<ha-list-item
value="switch"
.selected=${!this._deviceClass || this._deviceClass === "switch"}
>
${domainToName(this.hass.localize, "switch")}
</ha-list-item>
<ha-list-item
value="outlet"
.selected=${this._deviceClass === "outlet"}
>
${this.hass.localize(
"ui.dialogs.entity_registry.editor.device_classes.switch.outlet"
)}
</ha-list-item>
<li divider role="separator"></li>
${this._switchAsDomainsSorted(
SWITCH_AS_DOMAINS,
this.hass.localize
).map(
(entry) => html`
<ha-list-item .value=${entry.domain}>
${entry.label}
</ha-list-item>
`
)}
</ha-select>`
: this.helperConfigEntry?.domain === "switch_as_x"
? html`<ha-select
.label=${this.hass.localize(
"ui.dialogs.entity_registry.editor.switch_as_x"
)}
.value=${this._switchAs}
naturalMenuWidth
fixedMenuPosition
@selected=${this._switchAsChanged}
@closed=${stopPropagation}
>
<ha-list-item value="switch">
${domainToName(this.hass.localize, "switch")}
</ha-list-item>
<ha-list-item .value=${domain}>
${domainToName(this.hass.localize, domain)}
</ha-list-item>
<li divider role="separator"></li>
${this._switchAsDomainsSorted(
SWITCH_AS_DOMAINS,
this.hass.localize
).map((entry) =>
domain === entry.domain
? nothing
: html`
<ha-list-item .value=${entry.domain}>
${entry.label}
</ha-list-item>
`
)}
</ha-select>`
: nothing}
${this._deviceClassOptions
? html`
<ha-select
@ -599,45 +674,6 @@ export class EntityRegistrySettingsEditor extends LitElement {
</ha-select>
`
: ""}
${domain === "switch"
? html`<ha-select
.label=${this.hass.localize(
"ui.dialogs.entity_registry.editor.device_class"
)}
naturalMenuWidth
fixedMenuPosition
@selected=${this._switchAsChanged}
@closed=${stopPropagation}
>
<ha-list-item
value="switch"
.selected=${!this._deviceClass || this._deviceClass === "switch"}
>
${this.hass.localize(
"ui.dialogs.entity_registry.editor.device_classes.switch.switch"
)}
</ha-list-item>
<ha-list-item
value="outlet"
.selected=${this._deviceClass === "outlet"}
>
${this.hass.localize(
"ui.dialogs.entity_registry.editor.device_classes.switch.outlet"
)}
</ha-list-item>
<li divider role="separator"></li>
${this._switchAsDomainsSorted(
SWITCH_AS_DOMAINS,
this.hass.localize
).map(
(entry) => html`
<ha-list-item .value=${entry.domain}>
${entry.label}
</ha-list-item>
`
)}
</ha-select>`
: ""}
<ha-textfield
error-message="Domain needs to stay the same"
.value=${this._entityId}
@ -880,8 +916,16 @@ export class EntityRegistrySettingsEditor extends LitElement {
`;
}
public async updateEntry(): Promise<ExtEntityRegistryEntry> {
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<EntityRegistryEntryUpdateParams> = {
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<EntityRegistryEntry>((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)

View File

@ -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<void> {
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 {

View File

@ -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",