From 500dd429b2cb17c8db3fea2d7c3a149943f58ada Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 5 Dec 2024 13:56:28 +0100 Subject: [PATCH 1/9] Add base support for sub entries --- demo/src/stubs/config_entries.ts | 1 + gallery/src/pages/misc/integration-card.ts | 1 + src/data/config_entries.ts | 26 + src/data/device_registry.ts | 1 + src/data/entity_registry.ts | 1 + src/data/sub_config_flow.ts | 45 ++ .../show-dialog-sub-config-flow.ts | 266 ++++++++ .../devices/ha-config-devices-dashboard.ts | 53 +- .../config/entities/ha-config-entities.ts | 49 +- .../ha-config-integration-page.ts | 624 +++++++++++------- .../ha-config-integrations-dashboard.ts | 1 + 11 files changed, 824 insertions(+), 244 deletions(-) create mode 100644 src/data/sub_config_flow.ts create mode 100644 src/dialogs/config-flow/show-dialog-sub-config-flow.ts diff --git a/demo/src/stubs/config_entries.ts b/demo/src/stubs/config_entries.ts index 583ab7d773e2..f8245d45444c 100644 --- a/demo/src/stubs/config_entries.ts +++ b/demo/src/stubs/config_entries.ts @@ -11,6 +11,7 @@ export const mockConfigEntries = (hass: MockHomeAssistant) => { supports_remove_device: false, supports_unload: true, supports_reconfigure: true, + supports_subentries: false, pref_disable_new_entities: false, pref_disable_polling: false, disabled_by: null, diff --git a/gallery/src/pages/misc/integration-card.ts b/gallery/src/pages/misc/integration-card.ts index bf71736163a3..0f543aedf775 100644 --- a/gallery/src/pages/misc/integration-card.ts +++ b/gallery/src/pages/misc/integration-card.ts @@ -32,6 +32,7 @@ const createConfigEntry = ( supports_remove_device: false, supports_unload: true, supports_reconfigure: true, + supports_subentries: false, disabled_by: null, pref_disable_new_entities: false, pref_disable_polling: false, diff --git a/src/data/config_entries.ts b/src/data/config_entries.ts index 3558d6e10946..94d6173e5c41 100644 --- a/src/data/config_entries.ts +++ b/src/data/config_entries.ts @@ -19,14 +19,40 @@ export interface ConfigEntry { supports_remove_device: boolean; supports_unload: boolean; supports_reconfigure: boolean; + supports_subentries: boolean; + num_subentries: number; pref_disable_new_entities: boolean; pref_disable_polling: boolean; disabled_by: "user" | null; reason: string | null; error_reason_translation_key: string | null; error_reason_translation_placeholders: Record | null; + subentries: SubConfigEntry[]; } +export interface SubConfigEntry { + subentry_id: string; + title: string; + unique_id: string; +} + +export const getSubConfigEntries = (hass: HomeAssistant, entry_id: string) => + hass.callWS({ + type: "config_entries/subentries/list", + entry_id, + }); + +export const deleteSubConfigEntry = ( + hass: HomeAssistant, + entry_id: string, + subentry_id: string +) => + hass.callWS({ + type: "config_entries/subentries/delete", + entry_id, + subentry_id, + }); + export type ConfigEntryMutableParams = Partial< Pick< ConfigEntry, diff --git a/src/data/device_registry.ts b/src/data/device_registry.ts index 958da7c634c1..7cd6b609bf51 100644 --- a/src/data/device_registry.ts +++ b/src/data/device_registry.ts @@ -17,6 +17,7 @@ export { export interface DeviceRegistryEntry extends RegistryEntry { id: string; config_entries: string[]; + config_subentries: { [configEntryId: string]: (string | null)[] }; connections: Array<[string, string]>; identifiers: Array<[string, string]>; manufacturer: string | null; diff --git a/src/data/entity_registry.ts b/src/data/entity_registry.ts index 05659e276628..0b9144146649 100644 --- a/src/data/entity_registry.ts +++ b/src/data/entity_registry.ts @@ -50,6 +50,7 @@ export interface EntityRegistryEntry extends RegistryEntry { icon: string | null; platform: string; config_entry_id: string | null; + config_subentry_id: string | null; device_id: string | null; area_id: string | null; labels: string[]; diff --git a/src/data/sub_config_flow.ts b/src/data/sub_config_flow.ts new file mode 100644 index 000000000000..91eae96aab6b --- /dev/null +++ b/src/data/sub_config_flow.ts @@ -0,0 +1,45 @@ +import type { HomeAssistant } from "../types"; +import type { DataEntryFlowStep } from "./data_entry_flow"; + +const HEADERS = { + "HA-Frontend-Base": `${location.protocol}//${location.host}`, +}; + +export const createSubConfigFlow = ( + hass: HomeAssistant, + handler: string, + entry_id?: string +) => + hass.callApi( + "POST", + "config/config_entries/subentries/flow", + { + handler, + show_advanced_options: Boolean(hass.userData?.showAdvanced), + entry_id, + }, + HEADERS + ); + +export const fetchSubConfigFlow = (hass: HomeAssistant, flowId: string) => + hass.callApi( + "GET", + `config/config_entries/subentries/flow/${flowId}`, + undefined, + HEADERS + ); + +export const handleSubConfigFlowStep = ( + hass: HomeAssistant, + flowId: string, + data: Record +) => + hass.callApi( + "POST", + `config/config_entries/subentries/flow/${flowId}`, + data, + HEADERS + ); + +export const deleteSubConfigFlow = (hass: HomeAssistant, flowId: string) => + hass.callApi("DELETE", `config/config_entries/subentries/flow/${flowId}`); diff --git a/src/dialogs/config-flow/show-dialog-sub-config-flow.ts b/src/dialogs/config-flow/show-dialog-sub-config-flow.ts new file mode 100644 index 000000000000..fbf47a50f687 --- /dev/null +++ b/src/dialogs/config-flow/show-dialog-sub-config-flow.ts @@ -0,0 +1,266 @@ +import { html } from "lit"; +import { + createSubConfigFlow, + deleteSubConfigFlow, + fetchSubConfigFlow, + handleSubConfigFlowStep, +} from "../../data/sub_config_flow"; +import { domainToName } from "../../data/integration"; +import type { DataEntryFlowDialogParams } from "./show-dialog-data-entry-flow"; +import { + loadDataEntryFlowDialog, + showFlowDialog, +} from "./show-dialog-data-entry-flow"; + +export const loadSubConfigFlowDialog = loadDataEntryFlowDialog; + +export const showSubConfigFlowDialog = ( + element: HTMLElement, + dialogParams: Omit +): void => + showFlowDialog(element, dialogParams, { + flowType: "config_flow", + showDevices: true, + createFlow: async (hass, handler) => { + const [step] = await Promise.all([ + createSubConfigFlow(hass, handler, dialogParams.entryId), + hass.loadFragmentTranslation("config"), + hass.loadBackendTranslation("config", handler), + hass.loadBackendTranslation("selector", handler), + // Used as fallback if no header defined for step + hass.loadBackendTranslation("title", handler), + ]); + return step; + }, + fetchFlow: async (hass, flowId) => { + const step = await fetchSubConfigFlow(hass, flowId); + await hass.loadFragmentTranslation("config"); + await hass.loadBackendTranslation("config", step.handler); + await hass.loadBackendTranslation("selector", step.handler); + return step; + }, + handleFlowStep: handleSubConfigFlowStep, + deleteFlow: deleteSubConfigFlow, + + renderAbortDescription(hass, step) { + const description = hass.localize( + `component.${step.translation_domain || step.handler}.config.abort.${step.reason}`, + step.description_placeholders + ); + + return description + ? html` + + ` + : step.reason; + }, + + renderShowFormStepHeader(hass, step) { + return ( + hass.localize( + `component.${step.translation_domain || step.handler}.config.step.${step.step_id}.title`, + step.description_placeholders + ) || hass.localize(`component.${step.handler}.title`) + ); + }, + + renderShowFormStepDescription(hass, step) { + const description = hass.localize( + `component.${step.translation_domain || step.handler}.config.step.${step.step_id}.description`, + step.description_placeholders + ); + return description + ? html` + + ` + : ""; + }, + + renderShowFormStepFieldLabel(hass, step, field, options) { + if (field.type === "expandable") { + return hass.localize( + `component.${step.handler}.config.step.${step.step_id}.sections.${field.name}.name` + ); + } + + const prefix = options?.path?.[0] ? `sections.${options.path[0]}.` : ""; + + return ( + hass.localize( + `component.${step.handler}.config.step.${step.step_id}.${prefix}data.${field.name}` + ) || field.name + ); + }, + + renderShowFormStepFieldHelper(hass, step, field, options) { + if (field.type === "expandable") { + return hass.localize( + `component.${step.translation_domain || step.handler}.config.step.${step.step_id}.sections.${field.name}.description` + ); + } + + const prefix = options?.path?.[0] ? `sections.${options.path[0]}.` : ""; + + const description = hass.localize( + `component.${step.translation_domain || step.handler}.config.step.${step.step_id}.${prefix}data_description.${field.name}`, + step.description_placeholders + ); + + return description + ? html`` + : ""; + }, + + renderShowFormStepFieldError(hass, step, error) { + return ( + hass.localize( + `component.${step.translation_domain || step.translation_domain || step.handler}.config.error.${error}`, + step.description_placeholders + ) || error + ); + }, + + renderShowFormStepFieldLocalizeValue(hass, step, key) { + return hass.localize(`component.${step.handler}.selector.${key}`); + }, + + renderShowFormStepSubmitButton(hass, step) { + return ( + hass.localize( + `component.${step.handler}.config.step.${step.step_id}.submit` + ) || + hass.localize( + `ui.panel.config.integrations.config_flow.${ + step.last_step === false ? "next" : "submit" + }` + ) + ); + }, + + renderExternalStepHeader(hass, step) { + return ( + hass.localize( + `component.${step.handler}.config.step.${step.step_id}.title` + ) || + hass.localize( + "ui.panel.config.integrations.config_flow.external_step.open_site" + ) + ); + }, + + renderExternalStepDescription(hass, step) { + const description = hass.localize( + `component.${step.translation_domain || step.handler}.config.${step.step_id}.description`, + step.description_placeholders + ); + + return html` +

+ ${hass.localize( + "ui.panel.config.integrations.config_flow.external_step.description" + )} +

+ ${description + ? html` + + ` + : ""} + `; + }, + + renderCreateEntryDescription(hass, step) { + const description = hass.localize( + `component.${step.translation_domain || step.handler}.config.create_entry.${ + step.description || "default" + }`, + step.description_placeholders + ); + + return html` + ${description + ? html` + + ` + : ""} +

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

+ `; + }, + + renderShowFormProgressHeader(hass, step) { + return ( + hass.localize( + `component.${step.handler}.config.step.${step.step_id}.title` + ) || hass.localize(`component.${step.handler}.title`) + ); + }, + + renderShowFormProgressDescription(hass, step) { + const description = hass.localize( + `component.${step.translation_domain || step.handler}.config.progress.${step.progress_action}`, + step.description_placeholders + ); + return description + ? html` + + ` + : ""; + }, + + renderMenuHeader(hass, step) { + return ( + hass.localize( + `component.${step.handler}.config.step.${step.step_id}.title` + ) || hass.localize(`component.${step.handler}.title`) + ); + }, + + renderMenuDescription(hass, step) { + const description = hass.localize( + `component.${step.translation_domain || step.handler}.config.step.${step.step_id}.description`, + step.description_placeholders + ); + return description + ? html` + + ` + : ""; + }, + + renderMenuOption(hass, step, option) { + return hass.localize( + `component.${step.translation_domain || step.handler}.config.step.${step.step_id}.menu_options.${option}`, + step.description_placeholders + ); + }, + + renderLoadingDescription(hass, reason, handler, step) { + if (reason !== "loading_flow" && reason !== "loading_step") { + return ""; + } + const domain = step?.handler || handler; + return hass.localize( + `ui.panel.config.integrations.config_flow.loading.${reason}`, + { + integration: domain + ? domainToName(hass.localize, domain) + : // when we are continuing a config flow, we only know the ID and not the domain + hass.localize( + "ui.panel.config.integrations.config_flow.loading.fallback_title" + ), + } + ); + }, + }); diff --git a/src/panels/config/devices/ha-config-devices-dashboard.ts b/src/panels/config/devices/ha-config-devices-dashboard.ts index c0ba1f330ebf..d7e6d7ea296a 100644 --- a/src/panels/config/devices/ha-config-devices-dashboard.ts +++ b/src/panels/config/devices/ha-config-devices-dashboard.ts @@ -51,8 +51,11 @@ import "../../../components/ha-icon-button"; import "../../../components/ha-md-menu-item"; import "../../../components/ha-sub-menu"; import { createAreaRegistryEntry } from "../../../data/area_registry"; -import type { ConfigEntry } from "../../../data/config_entries"; -import { sortConfigEntries } from "../../../data/config_entries"; +import type { ConfigEntry, SubConfigEntry } from "../../../data/config_entries"; +import { + getSubConfigEntries, + sortConfigEntries, +} from "../../../data/config_entries"; import { fullEntitiesContext } from "../../../data/context"; import type { DataTableFilters } from "../../../data/data_table_filters"; import { @@ -108,6 +111,8 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) { @property({ attribute: false }) public entries!: ConfigEntry[]; + @state() private _subConfigEntries?: SubConfigEntry[]; + @state() @consume({ context: fullEntitiesContext, subscribe: true }) entities!: EntityRegistryEntry[]; @@ -219,6 +224,7 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) { private _setFiltersFromUrl() { const domain = this._searchParms.get("domain"); const configEntry = this._searchParms.get("config_entry"); + const subConfigEntry = this._searchParms.get("sub_entry"); const label = this._searchParms.has("label"); if (!domain && !configEntry && !label) { @@ -243,6 +249,10 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) { value: configEntry ? [configEntry] : [], items: undefined, }, + sub_config_entry: { + value: subConfigEntry ? [subConfigEntry] : [], + items: undefined, + }, }; this._filterLabel(); } @@ -334,6 +344,30 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) { if (configEntries.length === 1) { filteredConfigEntry = configEntries[0]; } + } else if ( + key === "sub_config_entry" && + Array.isArray(filter.value) && + filter.value.length + ) { + if ( + !( + Array.isArray(this._filters.config_entry?.value) && + this._filters.config_entry.value.length === 1 + ) + ) { + return; + } + const configEntryId = this._filters.config_entry.value[0]; + outputDevices = outputDevices.filter( + (device) => + device.config_subentries[configEntryId] && + (filter.value as string[]).some((subEntryId) => + device.config_subentries[configEntryId].includes(subEntryId) + ) + ); + if (!this._subConfigEntries) { + this._loadSubConfigEntries(configEntryId); + } } else if ( key === "ha-filter-integrations" && Array.isArray(filter.value) && @@ -755,7 +789,16 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) { ${this.entries?.find( (entry) => entry.entry_id === this._filters.config_entry!.value![0] - )?.title || this._filters.config_entry.value[0]} + )?.title || this._filters.config_entry.value[0]}${this._filters + .config_entry.value.length === 1 && + Array.isArray(this._filters.sub_config_entry?.value) && + this._filters.sub_config_entry.value.length + ? html` (${this._subConfigEntries?.find( + (entry) => + entry.subentry_id === + this._filters.sub_config_entry!.value![0] + )?.title || this._filters.sub_config_entry!.value![0]})` + : nothing} ` : nothing} + entity.config_subentry_id && + (filter as string[]).includes(entity.config_subentry_id) + ); + if (!this._subConfigEntries) { + this._loadSubConfigEntries(this._filters.config_entry[0]); + } } else if ( key === "ha-filter-integrations" && Array.isArray(filter) && @@ -902,14 +928,22 @@ ${ ${ Array.isArray(this._filters.config_entry) && - this._filters.config_entry?.length + this._filters.config_entry.length ? html` ${this.hass.localize( "ui.panel.config.entities.picker.filtering_by_config_entry" )} ${this._entries?.find( (entry) => entry.entry_id === this._filters.config_entry![0] - )?.title || this._filters.config_entry[0]} + )?.title || this._filters.config_entry[0]}${this._filters + .config_entry.length === 1 && + Array.isArray(this._filters.sub_config_entry) && + this._filters.sub_config_entry.length + ? html` (${this._subConfigEntries?.find( + (entry) => + entry.subentry_id === this._filters.sub_config_entry![0] + )?.title || this._filters.sub_config_entry[0]})` + : nothing} ` : nothing } @@ -1022,6 +1056,7 @@ ${ private _setFiltersFromUrl() { const domain = this._searchParms.get("domain"); const configEntry = this._searchParms.get("config_entry"); + const subConfigEntry = this._searchParms.get("sub_entry"); const label = this._searchParms.has("label"); if (!domain && !configEntry && !label) { @@ -1034,6 +1069,7 @@ ${ "ha-filter-states": [], "ha-filter-integrations": domain ? [domain] : [], config_entry: configEntry ? [configEntry] : [], + sub_config_entry: subConfigEntry ? [subConfigEntry] : [], }; this._filterLabel(); } @@ -1091,6 +1127,7 @@ ${ hidden_by: null, area_id: null, config_entry_id: null, + config_subentry_id: null, device_id: null, icon: null, readonly: true, @@ -1382,6 +1419,10 @@ ${rejected this._entries = await getConfigEntries(this.hass); } + private async _loadSubConfigEntries(entryId: string) { + this._subConfigEntries = await getSubConfigEntries(this.hass, entryId); + } + private _addDevice() { const { filteredConfigEntry, filteredDomains } = this._filteredEntitiesAndDomains( diff --git a/src/panels/config/integrations/ha-config-integration-page.ts b/src/panels/config/integrations/ha-config-integration-page.ts index 65968928cac7..8b3c73c843b4 100644 --- a/src/panels/config/integrations/ha-config-integration-page.ts +++ b/src/panels/config/integrations/ha-config-integration-page.ts @@ -16,6 +16,7 @@ import { mdiOpenInNew, mdiPackageVariant, mdiPlayCircleOutline, + mdiPlus, mdiProgressHelper, mdiReload, mdiReloadAlert, @@ -52,14 +53,17 @@ import { getSignedPath } from "../../../data/auth"; import type { ConfigEntry, DisableConfigEntryResult, + SubConfigEntry, } from "../../../data/config_entries"; import { ERROR_STATES, RECOVERABLE_STATES, deleteConfigEntry, + deleteSubConfigEntry, disableConfigEntry, enableConfigEntry, getConfigEntries, + getSubConfigEntries, reloadConfigEntry, updateConfigEntry, } from "../../../data/config_entries"; @@ -106,6 +110,7 @@ import { fileDownload } from "../../../util/file_download"; import type { DataEntryFlowProgressExtended } from "./ha-config-integrations"; import { showAddIntegrationDialog } from "./show-add-integration-dialog"; import { QUALITY_SCALE_MAP } from "../../../data/integration_quality_scale"; +import { showSubConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-sub-config-flow"; export const renderConfigEntryError = ( hass: HomeAssistant, @@ -172,6 +177,8 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) { @state() private _domainEntities: Record = {}; + @state() private _subEntries: Record = {}; + private _configPanel = memoizeOne( (domain: string, panels: HomeAssistant["panels"]): string | undefined => Object.values(panels).find( @@ -219,6 +226,12 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) { this._fetchDiagnostics(); this._fetchEntitySources(); } + if ( + changedProperties.has("configEntries") || + changedProperties.has("_extraConfigEntries") + ) { + this._fetchSubEntries(); + } } private async _fetchEntitySources() { @@ -573,7 +586,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) { const attention = ATTENTION_SOURCES.includes( flow.context.source ); - return html` ${flow.localized_title} @@ -673,6 +686,73 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) { ev.target.style.display = "none"; } + private _renderDeviceLine( + item: ConfigEntry, + devices: DeviceRegistryEntry[], + services: DeviceRegistryEntry[], + entities: EntityRegistryEntry[], + subItem?: SubConfigEntry + ) { + let devicesLine: (TemplateResult | string)[] = []; + for (const [items, localizeKey] of [ + [devices, "devices"], + [services, "services"], + ] as const) { + if (items.length === 0) { + continue; + } + const url = + items.length === 1 + ? `/config/devices/device/${items[0].id}` + : `/config/devices/dashboard?historyBack=1&config_entry=${item.entry_id}${subItem ? `&sub_entry=${subItem.subentry_id}` : ""}`; + devicesLine.push( + // no white space before/after template on purpose + html`${this.hass.localize( + `ui.panel.config.integrations.config_entry.${localizeKey}`, + { count: items.length } + )}` + ); + } + + if (entities.length) { + devicesLine.push( + // no white space before/after template on purpose + html`${this.hass.localize( + "ui.panel.config.integrations.config_entry.entities", + { count: entities.length } + )}` + ); + } + + if (devicesLine.length === 0) { + devicesLine = [ + this.hass.localize( + "ui.panel.config.integrations.config_entry.no_devices_or_entities" + ), + ]; + } else if (devicesLine.length === 2) { + devicesLine = [ + devicesLine[0], + ` ${this.hass.localize("ui.common.and")} `, + devicesLine[1], + ]; + } else if (devicesLine.length === 3) { + devicesLine = [ + devicesLine[0], + ", ", + devicesLine[1], + ` ${this.hass.localize("ui.common.and")} `, + devicesLine[2], + ]; + } + return devicesLine; + } + private _renderConfigEntry(item: ConfigEntry) { let stateText: Parameters | undefined; let stateTextExtra: TemplateResult | string | undefined; @@ -720,274 +800,285 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) { )}.`); } } else { - for (const [items, localizeKey] of [ - [devices, "devices"], - [services, "services"], - ] as const) { - if (items.length === 0) { - continue; - } - const url = - items.length === 1 - ? `/config/devices/device/${items[0].id}` - : `/config/devices/dashboard?historyBack=1&config_entry=${item.entry_id}`; - devicesLine.push( - // no white space before/after template on purpose - html`${this.hass.localize( - `ui.panel.config.integrations.config_entry.${localizeKey}`, - { count: items.length } - )}` - ); - } - - if (entities.length) { - devicesLine.push( - // no white space before/after template on purpose - html`${this.hass.localize( - "ui.panel.config.integrations.config_entry.entities", - { count: entities.length } - )}` - ); - } - - if (devicesLine.length === 0) { - devicesLine = [ - this.hass.localize( - "ui.panel.config.integrations.config_entry.no_devices_or_entities" - ), - ]; - } else if (devicesLine.length === 2) { - devicesLine = [ - devicesLine[0], - ` ${this.hass.localize("ui.common.and")} `, - devicesLine[1], - ]; - } else if (devicesLine.length === 3) { - devicesLine = [ - devicesLine[0], - ", ", - devicesLine[1], - ` ${this.hass.localize("ui.common.and")} `, - devicesLine[2], - ]; - } + devicesLine = this._renderDeviceLine(item, devices, services, entities); } const configPanel = this._configPanel(item.domain, this.hass.panels); + const subEntries = this._subEntries[item.entry_id] || []; + return html` -
- ${item.title || domainToName(this.hass.localize, item.domain)} -
-
-
${devicesLine}
- ${stateText - ? html` -
- -
- ${this.hass.localize(...stateText)}${stateTextExtra - ? html`: ${stateTextExtra}` - : nothing} -
-
- ` - : nothing} -
- ${item.disabled_by === "user" - ? html` - ${this.hass.localize("ui.common.enable")} - ` - : configPanel && - (item.domain !== "matter" || - isDevVersion(this.hass.config.version)) && - !stateText - ? html` - ${this.hass.localize( - "ui.panel.config.integrations.config_entry.configure" - )} - ` - : item.supports_options + class=${classMap({ + config_entry: true, + "state-not-loaded": item!.state === "not_loaded", + "state-failed-unload": item!.state === "failed_unload", + "state-setup": item!.state === "setup_in_progress", + "state-error": ERROR_STATES.includes(item!.state), + "state-disabled": item.disabled_by !== null, + })} + data-entry-id=${item.entry_id} + .configEntry=${item} + > +
+ ${item.title || domainToName(this.hass.localize, item.domain)} +
+
+
${devicesLine}
+ ${stateText ? html` - +
+ +
+ ${this.hass.localize(...stateText)}${stateTextExtra + ? html`: ${stateTextExtra}` + : nothing} +
+
+ ` + : nothing} +
+ ${item.disabled_by === "user" + ? html` + ${this.hass.localize("ui.common.enable")} + ` + : configPanel && + (item.domain !== "matter" || + isDevVersion(this.hass.config.version)) && + !stateText + ? html` ${this.hass.localize( "ui.panel.config.integrations.config_entry.configure" )} - + ` + : item.supports_options + ? html` + + ${this.hass.localize( + "ui.panel.config.integrations.config_entry.configure" + )} + + ` + : nothing} + + + ${item.disabled_by && devices.length + ? html` + + + ${this.hass.localize( + `ui.panel.config.integrations.config_entry.devices`, + { count: devices.length } + )} + + ` : nothing} - - - ${item.disabled_by && devices.length - ? html` - - - ${this.hass.localize( - `ui.panel.config.integrations.config_entry.devices`, - { count: devices.length } - )} - - - ` - : nothing} - ${item.disabled_by && services.length - ? html` - - ${this.hass.localize( - `ui.panel.config.integrations.config_entry.services`, - { count: services.length } - )} - - ` - : nothing} - ${item.disabled_by && entities.length - ? html` - ${this.hass.localize( - `ui.panel.config.integrations.config_entry.entities`, - { count: entities.length } + `ui.panel.config.integrations.config_entry.services`, + { count: services.length } )} - - ` - : nothing} - ${!item.disabled_by && - RECOVERABLE_STATES.includes(item.state) && - item.supports_unload && - item.source !== "system" - ? html` - - - ${this.hass.localize( - "ui.panel.config.integrations.config_entry.reload" - )} - - ` - : nothing} - - - - ${this.hass.localize( - "ui.panel.config.integrations.config_entry.rename" - )} - - - + ` + : nothing} + ${item.disabled_by && entities.length + ? html` + + + ${this.hass.localize( + `ui.panel.config.integrations.config_entry.entities`, + { count: entities.length } + )} + + + ` + : nothing} + ${!item.disabled_by && + RECOVERABLE_STATES.includes(item.state) && + item.supports_unload && + item.source !== "system" + ? html` + + + ${this.hass.localize( + "ui.panel.config.integrations.config_entry.reload" + )} + + ` + : nothing} - ${this._diagnosticHandler && item.state === "loaded" - ? html` - + + ${this.hass.localize( + "ui.panel.config.integrations.config_entry.rename" + )} + + + ${item.supports_subentries + ? html` - - ${this.hass.localize( - "ui.panel.config.integrations.config_entry.download_diagnostics" - )} - - ` - : nothing} - ${!item.disabled_by && - item.supports_reconfigure && - item.source !== "system" - ? html` - - - ${this.hass.localize( - "ui.panel.config.integrations.config_entry.reconfigure" - )} - - ` - : nothing} + + Add sub entry` + : nothing} - - - ${this.hass.localize( - "ui.panel.config.integrations.config_entry.system_options" - )} - - ${item.disabled_by === "user" - ? html` - - - ${this.hass.localize("ui.common.enable")} - - ` - : item.source !== "system" + + + ${this._diagnosticHandler && item.state === "loaded" ? html` + + ${this.hass.localize( + "ui.panel.config.integrations.config_entry.download_diagnostics" + )} + + ` + : nothing} + ${!item.disabled_by && + item.supports_reconfigure && + item.source !== "system" + ? html` + + + ${this.hass.localize( + "ui.panel.config.integrations.config_entry.reconfigure" + )} + + ` + : nothing} + + + + ${this.hass.localize( + "ui.panel.config.integrations.config_entry.system_options" + )} + + ${item.disabled_by === "user" + ? html` + + ${this.hass.localize("ui.common.enable")} + + ` + : item.source !== "system" + ? html` + + + ${this.hass.localize("ui.common.disable")} + + ` + : nothing} + ${item.source !== "system" + ? html` + + - ${this.hass.localize("ui.common.disable")} + ${this.hass.localize( + "ui.panel.config.integrations.config_entry.delete" + )} ` : nothing} - ${item.source !== "system" - ? html` - - - ${this.hass.localize( - "ui.panel.config.integrations.config_entry.delete" - )} - - ` - : nothing} + +
+ ${subEntries.map((subEntry) => this._renderSubEntry(item, subEntry))}`; + } + + private _renderSubEntry(configEntry: ConfigEntry, subEntry: SubConfigEntry) { + const devices = this._getConfigEntryDevices(configEntry).filter((device) => + device.config_subentries[configEntry.entry_id]?.includes( + subEntry.subentry_id + ) + ); + const services = this._getConfigEntryServices(configEntry).filter( + (device) => + device.config_subentries[configEntry.entry_id]?.includes( + subEntry.subentry_id + ) + ); + const entities = this._getConfigEntryEntities(configEntry).filter( + (entity) => entity.config_subentry_id === subEntry.subentry_id + ); + + return html` + ${subEntry.title} + ${this._renderDeviceLine( + configEntry, + devices, + services, + entities, + subEntry + )} + + + + + ${this.hass.localize( + "ui.panel.config.integrations.config_entry.delete" + )} + `; } @@ -1030,6 +1121,27 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) { } } + private async _fetchSubEntries() { + const subEntriesPromises = ( + this._extraConfigEntries || this.configEntries + )?.map((entry) => + entry.num_subentries + ? getSubConfigEntries(this.hass, entry.entry_id).then((subEntries) => ({ + entry_id: entry.entry_id, + subEntries, + })) + : undefined + ); + if (subEntriesPromises) { + const subEntries = await Promise.all(subEntriesPromises); + this._subEntries = {}; + subEntries.forEach((entry) => { + if (!entry) return; + this._subEntries[entry.entry_id] = entry.subEntries; + }); + } + } + private async _fetchDiagnostics() { if (!this.domain || !isComponentLoaded(this.hass, "diagnostics")) { return; @@ -1177,6 +1289,35 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) { ); } + private async _handleDeleteSub(ev: Event): Promise { + const configEntry = ( + (ev.target as HTMLElement).closest(".sub-entry") as any + ).configEntry; + const subEntry = ((ev.target as HTMLElement).closest(".sub-entry") as any) + .subConfigEntry; + const confirmed = await showConfirmationDialog(this, { + title: this.hass.localize( + "ui.panel.config.integrations.config_entry.delete_confirm_title", + { title: configEntry.title } + ), + text: this.hass.localize( + "ui.panel.config.integrations.config_entry.delete_confirm_text" + ), + confirmText: this.hass!.localize("ui.common.delete"), + dismissText: this.hass!.localize("ui.common.cancel"), + destructive: true, + }); + + if (!confirmed) { + return; + } + await deleteSubConfigEntry( + this.hass, + configEntry.entry_id, + subEntry.subentry_id + ); + } + private _handleDisable(ev: Event): void { this._disableIntegration( ((ev.target as HTMLElement).closest(".config_entry") as any).configEntry @@ -1454,6 +1595,12 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) { }); } + private async _addSubEntry(ev) { + showSubConfigFlowDialog(this, { + startFlowHandler: ev.target.entryId, + }); + } + static get styles(): CSSResultGroup { return [ haStyle, @@ -1583,6 +1730,9 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) { pointer-events: none; content: ""; } + ha-md-list-item.sub-entry { + --md-list-item-leading-space: 50px; + } a { text-decoration: none; } diff --git a/src/panels/config/integrations/ha-config-integrations-dashboard.ts b/src/panels/config/integrations/ha-config-integrations-dashboard.ts index c3f7950f3f15..c81040f875dd 100644 --- a/src/panels/config/integrations/ha-config-integrations-dashboard.ts +++ b/src/panels/config/integrations/ha-config-integrations-dashboard.ts @@ -204,6 +204,7 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) { supports_remove_device: false, supports_unload: false, supports_reconfigure: false, + supports_subentries: false, pref_disable_new_entities: false, pref_disable_polling: false, disabled_by: null, From 0b1fb2c6cf0339a3e7c160576ae4cf40959a017a Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 5 Dec 2024 14:10:43 +0100 Subject: [PATCH 2/9] add demo types --- demo/src/ha-demo.ts | 2 ++ gallery/src/pages/components/ha-form.ts | 3 +++ gallery/src/pages/components/ha-selector.ts | 3 +++ gallery/src/pages/misc/integration-card.ts | 3 +++ src/data/config_entries.ts | 1 - .../config/integrations/ha-config-integrations-dashboard.ts | 1 + 6 files changed, 12 insertions(+), 1 deletion(-) diff --git a/demo/src/ha-demo.ts b/demo/src/ha-demo.ts index d133aaa5248f..560fef69c30a 100644 --- a/demo/src/ha-demo.ts +++ b/demo/src/ha-demo.ts @@ -65,6 +65,7 @@ export class HaDemo extends HomeAssistantAppEl { mockEntityRegistry(hass, [ { config_entry_id: "co2signal", + config_subentry_id: null, device_id: "co2signal", area_id: null, disabled_by: null, @@ -85,6 +86,7 @@ export class HaDemo extends HomeAssistantAppEl { }, { config_entry_id: "co2signal", + config_subentry_id: null, device_id: "co2signal", area_id: null, disabled_by: null, diff --git a/gallery/src/pages/components/ha-form.ts b/gallery/src/pages/components/ha-form.ts index 2f9b01287734..67520f060313 100644 --- a/gallery/src/pages/components/ha-form.ts +++ b/gallery/src/pages/components/ha-form.ts @@ -48,6 +48,7 @@ const DEVICES: DeviceRegistryEntry[] = [ area_id: "bedroom", configuration_url: null, config_entries: ["config_entry_1"], + config_subentries: {}, connections: [], disabled_by: null, entry_type: null, @@ -71,6 +72,7 @@ const DEVICES: DeviceRegistryEntry[] = [ area_id: "backyard", configuration_url: null, config_entries: ["config_entry_2"], + config_subentries: {}, connections: [], disabled_by: null, entry_type: null, @@ -94,6 +96,7 @@ const DEVICES: DeviceRegistryEntry[] = [ area_id: null, configuration_url: null, config_entries: ["config_entry_3"], + config_subentries: {}, connections: [], disabled_by: null, entry_type: null, diff --git a/gallery/src/pages/components/ha-selector.ts b/gallery/src/pages/components/ha-selector.ts index f9eb0e6d0e7f..fc9a9b6be8fb 100644 --- a/gallery/src/pages/components/ha-selector.ts +++ b/gallery/src/pages/components/ha-selector.ts @@ -47,6 +47,7 @@ const DEVICES: DeviceRegistryEntry[] = [ area_id: "bedroom", configuration_url: null, config_entries: ["config_entry_1"], + config_subentries: {}, connections: [], disabled_by: null, entry_type: null, @@ -70,6 +71,7 @@ const DEVICES: DeviceRegistryEntry[] = [ area_id: "backyard", configuration_url: null, config_entries: ["config_entry_2"], + config_subentries: {}, connections: [], disabled_by: null, entry_type: null, @@ -93,6 +95,7 @@ const DEVICES: DeviceRegistryEntry[] = [ area_id: null, configuration_url: null, config_entries: ["config_entry_3"], + config_subentries: {}, connections: [], disabled_by: null, entry_type: null, diff --git a/gallery/src/pages/misc/integration-card.ts b/gallery/src/pages/misc/integration-card.ts index 0f543aedf775..6c0a6da6af59 100644 --- a/gallery/src/pages/misc/integration-card.ts +++ b/gallery/src/pages/misc/integration-card.ts @@ -33,6 +33,7 @@ const createConfigEntry = ( supports_unload: true, supports_reconfigure: true, supports_subentries: false, + num_subentries: 0, disabled_by: null, pref_disable_new_entities: false, pref_disable_polling: false, @@ -189,6 +190,7 @@ const createEntityRegistryEntries = ( ): EntityRegistryEntry[] => [ { config_entry_id: item.entry_id, + config_subentry_id: null, device_id: "mock-device-id", area_id: null, disabled_by: null, @@ -215,6 +217,7 @@ const createDeviceRegistryEntries = ( { entry_type: null, config_entries: [item.entry_id], + config_subentries: {}, connections: [], manufacturer: "ESPHome", model: "Mock Device", diff --git a/src/data/config_entries.ts b/src/data/config_entries.ts index 94d6173e5c41..0ac58004fbd5 100644 --- a/src/data/config_entries.ts +++ b/src/data/config_entries.ts @@ -27,7 +27,6 @@ export interface ConfigEntry { reason: string | null; error_reason_translation_key: string | null; error_reason_translation_placeholders: Record | null; - subentries: SubConfigEntry[]; } export interface SubConfigEntry { diff --git a/src/panels/config/integrations/ha-config-integrations-dashboard.ts b/src/panels/config/integrations/ha-config-integrations-dashboard.ts index c81040f875dd..89df8d4875dd 100644 --- a/src/panels/config/integrations/ha-config-integrations-dashboard.ts +++ b/src/panels/config/integrations/ha-config-integrations-dashboard.ts @@ -205,6 +205,7 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) { supports_unload: false, supports_reconfigure: false, supports_subentries: false, + num_subentries: 0, pref_disable_new_entities: false, pref_disable_polling: false, disabled_by: null, From 83117c2e510dc11c1f5e400cf713dd52710296f7 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 6 Dec 2024 20:21:15 +0100 Subject: [PATCH 3/9] fix translations --- src/data/data_entry_flow.ts | 6 +- src/data/translation.ts | 1 + .../previews/flow-preview-generic.ts | 2 +- .../previews/flow-preview-template.ts | 2 +- .../show-dialog-sub-config-flow.ts | 63 ++++++++++--------- .../ha-config-integration-page.ts | 6 +- 6 files changed, 45 insertions(+), 35 deletions(-) diff --git a/src/data/data_entry_flow.ts b/src/data/data_entry_flow.ts index 33b49424e33a..ded6ecf806d9 100644 --- a/src/data/data_entry_flow.ts +++ b/src/data/data_entry_flow.ts @@ -2,7 +2,11 @@ import type { Connection } from "home-assistant-js-websocket"; import type { HaFormSchema } from "../components/ha-form/types"; import type { ConfigEntry } from "./config_entries"; -export type FlowType = "config_flow" | "options_flow" | "repair_flow"; +export type FlowType = + | "config_flow" + | "config_subentries_flow" + | "options_flow" + | "repair_flow"; export interface DataEntryFlowProgressedEvent { type: "data_entry_flow_progressed"; diff --git a/src/data/translation.ts b/src/data/translation.ts index 5db27ac98708..c3b10f42864e 100644 --- a/src/data/translation.ts +++ b/src/data/translation.ts @@ -63,6 +63,7 @@ export type TranslationCategory = | "entity_component" | "exceptions" | "config" + | "config_subentries" | "config_panel" | "options" | "device_automation" diff --git a/src/dialogs/config-flow/previews/flow-preview-generic.ts b/src/dialogs/config-flow/previews/flow-preview-generic.ts index bec40add9174..b04845ecb53b 100644 --- a/src/dialogs/config-flow/previews/flow-preview-generic.ts +++ b/src/dialogs/config-flow/previews/flow-preview-generic.ts @@ -77,7 +77,7 @@ export class FlowPreviewGeneric extends LitElement { (await this._unsub)(); this._unsub = undefined; } - if (this.flowType === "repair_flow") { + if (this.flowType !== "config_flow" && this.flowType !== "options_flow") { return; } try { diff --git a/src/dialogs/config-flow/previews/flow-preview-template.ts b/src/dialogs/config-flow/previews/flow-preview-template.ts index e4c8c3eafe06..5697128a3db6 100644 --- a/src/dialogs/config-flow/previews/flow-preview-template.ts +++ b/src/dialogs/config-flow/previews/flow-preview-template.ts @@ -147,7 +147,7 @@ class FlowPreviewTemplate extends LitElement { (await this._unsub)(); this._unsub = undefined; } - if (this.flowType === "repair_flow") { + if (this.flowType !== "config_flow" && this.flowType !== "options_flow") { return; } try { diff --git a/src/dialogs/config-flow/show-dialog-sub-config-flow.ts b/src/dialogs/config-flow/show-dialog-sub-config-flow.ts index fbf47a50f687..a54a76626308 100644 --- a/src/dialogs/config-flow/show-dialog-sub-config-flow.ts +++ b/src/dialogs/config-flow/show-dialog-sub-config-flow.ts @@ -1,11 +1,12 @@ import { html } from "lit"; +import type { ConfigEntry } from "../../data/config_entries"; +import { domainToName } from "../../data/integration"; import { createSubConfigFlow, deleteSubConfigFlow, fetchSubConfigFlow, handleSubConfigFlowStep, } from "../../data/sub_config_flow"; -import { domainToName } from "../../data/integration"; import type { DataEntryFlowDialogParams } from "./show-dialog-data-entry-flow"; import { loadDataEntryFlowDialog, @@ -16,27 +17,31 @@ export const loadSubConfigFlowDialog = loadDataEntryFlowDialog; export const showSubConfigFlowDialog = ( element: HTMLElement, + configEntry: ConfigEntry, dialogParams: Omit ): void => showFlowDialog(element, dialogParams, { - flowType: "config_flow", + flowType: "config_subentries_flow", showDevices: true, createFlow: async (hass, handler) => { const [step] = await Promise.all([ createSubConfigFlow(hass, handler, dialogParams.entryId), hass.loadFragmentTranslation("config"), - hass.loadBackendTranslation("config", handler), - hass.loadBackendTranslation("selector", handler), + hass.loadBackendTranslation("config_subentries", configEntry.domain), + hass.loadBackendTranslation("selector", configEntry.domain), // Used as fallback if no header defined for step - hass.loadBackendTranslation("title", handler), + hass.loadBackendTranslation("title", configEntry.domain), ]); return step; }, fetchFlow: async (hass, flowId) => { const step = await fetchSubConfigFlow(hass, flowId); await hass.loadFragmentTranslation("config"); - await hass.loadBackendTranslation("config", step.handler); - await hass.loadBackendTranslation("selector", step.handler); + await hass.loadBackendTranslation( + "config_subentries", + configEntry.domain + ); + await hass.loadBackendTranslation("selector", configEntry.domain); return step; }, handleFlowStep: handleSubConfigFlowStep, @@ -44,7 +49,7 @@ export const showSubConfigFlowDialog = ( renderAbortDescription(hass, step) { const description = hass.localize( - `component.${step.translation_domain || step.handler}.config.abort.${step.reason}`, + `component.${step.translation_domain || configEntry.domain}.config_subentries.abort.${step.reason}`, step.description_placeholders ); @@ -58,15 +63,15 @@ export const showSubConfigFlowDialog = ( renderShowFormStepHeader(hass, step) { return ( hass.localize( - `component.${step.translation_domain || step.handler}.config.step.${step.step_id}.title`, + `component.${step.translation_domain || configEntry.domain}.config_subentries.step.${step.step_id}.title`, step.description_placeholders - ) || hass.localize(`component.${step.handler}.title`) + ) || hass.localize(`component.${configEntry.domain}.title`) ); }, renderShowFormStepDescription(hass, step) { const description = hass.localize( - `component.${step.translation_domain || step.handler}.config.step.${step.step_id}.description`, + `component.${step.translation_domain || configEntry.domain}.config_subentries.step.${step.step_id}.description`, step.description_placeholders ); return description @@ -79,7 +84,7 @@ export const showSubConfigFlowDialog = ( renderShowFormStepFieldLabel(hass, step, field, options) { if (field.type === "expandable") { return hass.localize( - `component.${step.handler}.config.step.${step.step_id}.sections.${field.name}.name` + `component.${configEntry.domain}.config_subentries.step.${step.step_id}.sections.${field.name}.name` ); } @@ -87,7 +92,7 @@ export const showSubConfigFlowDialog = ( return ( hass.localize( - `component.${step.handler}.config.step.${step.step_id}.${prefix}data.${field.name}` + `component.${configEntry.domain}.config_subentries.step.${step.step_id}.${prefix}data.${field.name}` ) || field.name ); }, @@ -95,14 +100,14 @@ export const showSubConfigFlowDialog = ( renderShowFormStepFieldHelper(hass, step, field, options) { if (field.type === "expandable") { return hass.localize( - `component.${step.translation_domain || step.handler}.config.step.${step.step_id}.sections.${field.name}.description` + `component.${step.translation_domain || configEntry.domain}.config_subentries.step.${step.step_id}.sections.${field.name}.description` ); } const prefix = options?.path?.[0] ? `sections.${options.path[0]}.` : ""; const description = hass.localize( - `component.${step.translation_domain || step.handler}.config.step.${step.step_id}.${prefix}data_description.${field.name}`, + `component.${step.translation_domain || configEntry.domain}.config_subentries.step.${step.step_id}.${prefix}data_description.${field.name}`, step.description_placeholders ); @@ -114,20 +119,20 @@ export const showSubConfigFlowDialog = ( renderShowFormStepFieldError(hass, step, error) { return ( hass.localize( - `component.${step.translation_domain || step.translation_domain || step.handler}.config.error.${error}`, + `component.${step.translation_domain || step.translation_domain || configEntry.domain}.config_subentries.error.${error}`, step.description_placeholders ) || error ); }, - renderShowFormStepFieldLocalizeValue(hass, step, key) { - return hass.localize(`component.${step.handler}.selector.${key}`); + renderShowFormStepFieldLocalizeValue(hass, _step, key) { + return hass.localize(`component.${configEntry.domain}.selector.${key}`); }, renderShowFormStepSubmitButton(hass, step) { return ( hass.localize( - `component.${step.handler}.config.step.${step.step_id}.submit` + `component.${configEntry.domain}.config_subentries.step.${step.step_id}.submit` ) || hass.localize( `ui.panel.config.integrations.config_flow.${ @@ -140,7 +145,7 @@ export const showSubConfigFlowDialog = ( renderExternalStepHeader(hass, step) { return ( hass.localize( - `component.${step.handler}.config.step.${step.step_id}.title` + `component.${configEntry.domain}.config_subentries.step.${step.step_id}.title` ) || hass.localize( "ui.panel.config.integrations.config_flow.external_step.open_site" @@ -150,7 +155,7 @@ export const showSubConfigFlowDialog = ( renderExternalStepDescription(hass, step) { const description = hass.localize( - `component.${step.translation_domain || step.handler}.config.${step.step_id}.description`, + `component.${step.translation_domain || configEntry.domain}.config_subentries.${step.step_id}.description`, step.description_placeholders ); @@ -174,7 +179,7 @@ export const showSubConfigFlowDialog = ( renderCreateEntryDescription(hass, step) { const description = hass.localize( - `component.${step.translation_domain || step.handler}.config.create_entry.${ + `component.${step.translation_domain || configEntry.domain}.config_subentries.create_entry.${ step.description || "default" }`, step.description_placeholders @@ -202,14 +207,14 @@ export const showSubConfigFlowDialog = ( renderShowFormProgressHeader(hass, step) { return ( hass.localize( - `component.${step.handler}.config.step.${step.step_id}.title` - ) || hass.localize(`component.${step.handler}.title`) + `component.${configEntry.domain}.config_subentries.step.${step.step_id}.title` + ) || hass.localize(`component.${configEntry.domain}.title`) ); }, renderShowFormProgressDescription(hass, step) { const description = hass.localize( - `component.${step.translation_domain || step.handler}.config.progress.${step.progress_action}`, + `component.${step.translation_domain || configEntry.domain}.config_subentries.progress.${step.progress_action}`, step.description_placeholders ); return description @@ -222,14 +227,14 @@ export const showSubConfigFlowDialog = ( renderMenuHeader(hass, step) { return ( hass.localize( - `component.${step.handler}.config.step.${step.step_id}.title` - ) || hass.localize(`component.${step.handler}.title`) + `component.${configEntry.domain}.config_subentries.step.${step.step_id}.title` + ) || hass.localize(`component.${configEntry.domain}.title`) ); }, renderMenuDescription(hass, step) { const description = hass.localize( - `component.${step.translation_domain || step.handler}.config.step.${step.step_id}.description`, + `component.${step.translation_domain || configEntry.domain}.config_subentries.step.${step.step_id}.description`, step.description_placeholders ); return description @@ -241,7 +246,7 @@ export const showSubConfigFlowDialog = ( renderMenuOption(hass, step, option) { return hass.localize( - `component.${step.translation_domain || step.handler}.config.step.${step.step_id}.menu_options.${option}`, + `component.${step.translation_domain || configEntry.domain}.config_subentries.step.${step.step_id}.menu_options.${option}`, step.description_placeholders ); }, diff --git a/src/panels/config/integrations/ha-config-integration-page.ts b/src/panels/config/integrations/ha-config-integration-page.ts index 8b3c73c843b4..f8cde5cb241a 100644 --- a/src/panels/config/integrations/ha-config-integration-page.ts +++ b/src/panels/config/integrations/ha-config-integration-page.ts @@ -943,7 +943,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) { ${item.supports_subentries ? html` @@ -1596,8 +1596,8 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) { } private async _addSubEntry(ev) { - showSubConfigFlowDialog(this, { - startFlowHandler: ev.target.entryId, + showSubConfigFlowDialog(this, ev.target.entry, { + startFlowHandler: ev.target.entry.entry_id, }); } From 3bacd7f66f840cf69f822d9d84ed01dcd5ef269b Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 9 Dec 2024 09:25:45 +0100 Subject: [PATCH 4/9] Use sub entry name when deleting --- src/panels/config/integrations/ha-config-integration-page.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/config/integrations/ha-config-integration-page.ts b/src/panels/config/integrations/ha-config-integration-page.ts index f8cde5cb241a..b268fee57cb0 100644 --- a/src/panels/config/integrations/ha-config-integration-page.ts +++ b/src/panels/config/integrations/ha-config-integration-page.ts @@ -1298,7 +1298,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) { const confirmed = await showConfirmationDialog(this, { title: this.hass.localize( "ui.panel.config.integrations.config_entry.delete_confirm_title", - { title: configEntry.title } + { title: subEntry.title } ), text: this.hass.localize( "ui.panel.config.integrations.config_entry.delete_confirm_text" From e1cb5253df4835a5385ab006a992e4725635d9f7 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 9 Dec 2024 09:27:53 +0100 Subject: [PATCH 5/9] Update show-dialog-sub-config-flow.ts --- src/dialogs/config-flow/show-dialog-sub-config-flow.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dialogs/config-flow/show-dialog-sub-config-flow.ts b/src/dialogs/config-flow/show-dialog-sub-config-flow.ts index a54a76626308..80783a8fc270 100644 --- a/src/dialogs/config-flow/show-dialog-sub-config-flow.ts +++ b/src/dialogs/config-flow/show-dialog-sub-config-flow.ts @@ -227,7 +227,8 @@ export const showSubConfigFlowDialog = ( renderMenuHeader(hass, step) { return ( hass.localize( - `component.${configEntry.domain}.config_subentries.step.${step.step_id}.title` + `component.${configEntry.domain}.config_subentries.step.${step.step_id}.title`, + step.description_placeholders ) || hass.localize(`component.${configEntry.domain}.title`) ); }, From f53d3e4174285b6427b327410286705bd20808e8 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 10 Dec 2024 13:09:50 +0100 Subject: [PATCH 6/9] adjust for multiple sub types --- demo/src/stubs/config_entries.ts | 2 +- gallery/src/pages/misc/integration-card.ts | 2 +- src/data/config_entries.ts | 2 +- src/data/sub_config_flow.ts | 7 ++-- .../show-dialog-sub-config-flow.ts | 37 ++++++++++--------- .../ha-config-integration-page.ts | 12 +++--- .../ha-config-integrations-dashboard.ts | 2 +- 7 files changed, 33 insertions(+), 31 deletions(-) diff --git a/demo/src/stubs/config_entries.ts b/demo/src/stubs/config_entries.ts index f8245d45444c..9f23475d435a 100644 --- a/demo/src/stubs/config_entries.ts +++ b/demo/src/stubs/config_entries.ts @@ -11,7 +11,7 @@ export const mockConfigEntries = (hass: MockHomeAssistant) => { supports_remove_device: false, supports_unload: true, supports_reconfigure: true, - supports_subentries: false, + supported_subentries: [], pref_disable_new_entities: false, pref_disable_polling: false, disabled_by: null, diff --git a/gallery/src/pages/misc/integration-card.ts b/gallery/src/pages/misc/integration-card.ts index 6c0a6da6af59..3e3af36030a8 100644 --- a/gallery/src/pages/misc/integration-card.ts +++ b/gallery/src/pages/misc/integration-card.ts @@ -32,7 +32,7 @@ const createConfigEntry = ( supports_remove_device: false, supports_unload: true, supports_reconfigure: true, - supports_subentries: false, + supported_subentries: [], num_subentries: 0, disabled_by: null, pref_disable_new_entities: false, diff --git a/src/data/config_entries.ts b/src/data/config_entries.ts index 0ac58004fbd5..82b1cc5d8094 100644 --- a/src/data/config_entries.ts +++ b/src/data/config_entries.ts @@ -19,7 +19,7 @@ export interface ConfigEntry { supports_remove_device: boolean; supports_unload: boolean; supports_reconfigure: boolean; - supports_subentries: boolean; + supported_subentries: string[]; num_subentries: number; pref_disable_new_entities: boolean; pref_disable_polling: boolean; diff --git a/src/data/sub_config_flow.ts b/src/data/sub_config_flow.ts index 91eae96aab6b..1dbad1cc098e 100644 --- a/src/data/sub_config_flow.ts +++ b/src/data/sub_config_flow.ts @@ -7,16 +7,15 @@ const HEADERS = { export const createSubConfigFlow = ( hass: HomeAssistant, - handler: string, - entry_id?: string + configEntryId: string, + subFlowType: string ) => hass.callApi( "POST", "config/config_entries/subentries/flow", { - handler, + handler: [configEntryId, subFlowType], show_advanced_options: Boolean(hass.userData?.showAdvanced), - entry_id, }, HEADERS ); diff --git a/src/dialogs/config-flow/show-dialog-sub-config-flow.ts b/src/dialogs/config-flow/show-dialog-sub-config-flow.ts index 80783a8fc270..74071427b2a0 100644 --- a/src/dialogs/config-flow/show-dialog-sub-config-flow.ts +++ b/src/dialogs/config-flow/show-dialog-sub-config-flow.ts @@ -18,6 +18,7 @@ export const loadSubConfigFlowDialog = loadDataEntryFlowDialog; export const showSubConfigFlowDialog = ( element: HTMLElement, configEntry: ConfigEntry, + flowType: string, dialogParams: Omit ): void => showFlowDialog(element, dialogParams, { @@ -25,7 +26,7 @@ export const showSubConfigFlowDialog = ( showDevices: true, createFlow: async (hass, handler) => { const [step] = await Promise.all([ - createSubConfigFlow(hass, handler, dialogParams.entryId), + createSubConfigFlow(hass, handler, flowType), hass.loadFragmentTranslation("config"), hass.loadBackendTranslation("config_subentries", configEntry.domain), hass.loadBackendTranslation("selector", configEntry.domain), @@ -49,7 +50,7 @@ export const showSubConfigFlowDialog = ( renderAbortDescription(hass, step) { const description = hass.localize( - `component.${step.translation_domain || configEntry.domain}.config_subentries.abort.${step.reason}`, + `component.${step.translation_domain || configEntry.domain}.config_subentries.${flowType}.abort.${step.reason}`, step.description_placeholders ); @@ -63,7 +64,7 @@ export const showSubConfigFlowDialog = ( renderShowFormStepHeader(hass, step) { return ( hass.localize( - `component.${step.translation_domain || configEntry.domain}.config_subentries.step.${step.step_id}.title`, + `component.${step.translation_domain || configEntry.domain}.config_subentries.${flowType}.step.${step.step_id}.title`, step.description_placeholders ) || hass.localize(`component.${configEntry.domain}.title`) ); @@ -71,7 +72,7 @@ export const showSubConfigFlowDialog = ( renderShowFormStepDescription(hass, step) { const description = hass.localize( - `component.${step.translation_domain || configEntry.domain}.config_subentries.step.${step.step_id}.description`, + `component.${step.translation_domain || configEntry.domain}.config_subentries.${flowType}.step.${step.step_id}.description`, step.description_placeholders ); return description @@ -84,7 +85,7 @@ export const showSubConfigFlowDialog = ( renderShowFormStepFieldLabel(hass, step, field, options) { if (field.type === "expandable") { return hass.localize( - `component.${configEntry.domain}.config_subentries.step.${step.step_id}.sections.${field.name}.name` + `component.${configEntry.domain}.config_subentries.${flowType}.step.${step.step_id}.sections.${field.name}.name` ); } @@ -92,7 +93,7 @@ export const showSubConfigFlowDialog = ( return ( hass.localize( - `component.${configEntry.domain}.config_subentries.step.${step.step_id}.${prefix}data.${field.name}` + `component.${configEntry.domain}.config_subentries.${flowType}.step.${step.step_id}.${prefix}data.${field.name}` ) || field.name ); }, @@ -100,14 +101,14 @@ export const showSubConfigFlowDialog = ( renderShowFormStepFieldHelper(hass, step, field, options) { if (field.type === "expandable") { return hass.localize( - `component.${step.translation_domain || configEntry.domain}.config_subentries.step.${step.step_id}.sections.${field.name}.description` + `component.${step.translation_domain || configEntry.domain}.config_subentries.${flowType}.step.${step.step_id}.sections.${field.name}.description` ); } const prefix = options?.path?.[0] ? `sections.${options.path[0]}.` : ""; const description = hass.localize( - `component.${step.translation_domain || configEntry.domain}.config_subentries.step.${step.step_id}.${prefix}data_description.${field.name}`, + `component.${step.translation_domain || configEntry.domain}.config_subentries.${flowType}.step.${step.step_id}.${prefix}data_description.${field.name}`, step.description_placeholders ); @@ -119,7 +120,7 @@ export const showSubConfigFlowDialog = ( renderShowFormStepFieldError(hass, step, error) { return ( hass.localize( - `component.${step.translation_domain || step.translation_domain || configEntry.domain}.config_subentries.error.${error}`, + `component.${step.translation_domain || step.translation_domain || configEntry.domain}.config_subentries.${flowType}.error.${error}`, step.description_placeholders ) || error ); @@ -132,7 +133,7 @@ export const showSubConfigFlowDialog = ( renderShowFormStepSubmitButton(hass, step) { return ( hass.localize( - `component.${configEntry.domain}.config_subentries.step.${step.step_id}.submit` + `component.${configEntry.domain}.config_subentries.${flowType}.step.${step.step_id}.submit` ) || hass.localize( `ui.panel.config.integrations.config_flow.${ @@ -145,7 +146,7 @@ export const showSubConfigFlowDialog = ( renderExternalStepHeader(hass, step) { return ( hass.localize( - `component.${configEntry.domain}.config_subentries.step.${step.step_id}.title` + `component.${configEntry.domain}.config_subentries.${flowType}.step.${step.step_id}.title` ) || hass.localize( "ui.panel.config.integrations.config_flow.external_step.open_site" @@ -155,7 +156,7 @@ export const showSubConfigFlowDialog = ( renderExternalStepDescription(hass, step) { const description = hass.localize( - `component.${step.translation_domain || configEntry.domain}.config_subentries.${step.step_id}.description`, + `component.${step.translation_domain || configEntry.domain}.config_subentries.${flowType}.step.${step.step_id}.description`, step.description_placeholders ); @@ -179,7 +180,7 @@ export const showSubConfigFlowDialog = ( renderCreateEntryDescription(hass, step) { const description = hass.localize( - `component.${step.translation_domain || configEntry.domain}.config_subentries.create_entry.${ + `component.${step.translation_domain || configEntry.domain}.config_subentries.${flowType}.create_entry.${ step.description || "default" }`, step.description_placeholders @@ -207,14 +208,14 @@ export const showSubConfigFlowDialog = ( renderShowFormProgressHeader(hass, step) { return ( hass.localize( - `component.${configEntry.domain}.config_subentries.step.${step.step_id}.title` + `component.${configEntry.domain}.config_subentries.${flowType}.step.${step.step_id}.title` ) || hass.localize(`component.${configEntry.domain}.title`) ); }, renderShowFormProgressDescription(hass, step) { const description = hass.localize( - `component.${step.translation_domain || configEntry.domain}.config_subentries.progress.${step.progress_action}`, + `component.${step.translation_domain || configEntry.domain}.config_subentries.${flowType}.progress.${step.progress_action}`, step.description_placeholders ); return description @@ -227,7 +228,7 @@ export const showSubConfigFlowDialog = ( renderMenuHeader(hass, step) { return ( hass.localize( - `component.${configEntry.domain}.config_subentries.step.${step.step_id}.title`, + `component.${configEntry.domain}.config_subentries.${flowType}.step.${step.step_id}.title`, step.description_placeholders ) || hass.localize(`component.${configEntry.domain}.title`) ); @@ -235,7 +236,7 @@ export const showSubConfigFlowDialog = ( renderMenuDescription(hass, step) { const description = hass.localize( - `component.${step.translation_domain || configEntry.domain}.config_subentries.step.${step.step_id}.description`, + `component.${step.translation_domain || configEntry.domain}.config_subentries.${flowType}.step.${step.step_id}.description`, step.description_placeholders ); return description @@ -247,7 +248,7 @@ export const showSubConfigFlowDialog = ( renderMenuOption(hass, step, option) { return hass.localize( - `component.${step.translation_domain || configEntry.domain}.config_subentries.step.${step.step_id}.menu_options.${option}`, + `component.${step.translation_domain || configEntry.domain}.config_subentries.${flowType}.step.${step.step_id}.menu_options.${option}`, step.description_placeholders ); }, diff --git a/src/panels/config/integrations/ha-config-integration-page.ts b/src/panels/config/integrations/ha-config-integration-page.ts index b268fee57cb0..7073e1507d53 100644 --- a/src/panels/config/integrations/ha-config-integration-page.ts +++ b/src/panels/config/integrations/ha-config-integration-page.ts @@ -940,16 +940,18 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) { )} - ${item.supports_subentries - ? html` + html` - Add sub entry` - : nothing} + )} @@ -1596,7 +1598,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) { } private async _addSubEntry(ev) { - showSubConfigFlowDialog(this, ev.target.entry, { + showSubConfigFlowDialog(this, ev.target.entry, ev.target.flowType, { startFlowHandler: ev.target.entry.entry_id, }); } diff --git a/src/panels/config/integrations/ha-config-integrations-dashboard.ts b/src/panels/config/integrations/ha-config-integrations-dashboard.ts index 89df8d4875dd..ca4ec46f1dda 100644 --- a/src/panels/config/integrations/ha-config-integrations-dashboard.ts +++ b/src/panels/config/integrations/ha-config-integrations-dashboard.ts @@ -204,7 +204,7 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) { supports_remove_device: false, supports_unload: false, supports_reconfigure: false, - supports_subentries: false, + supported_subentries: [], num_subentries: 0, pref_disable_new_entities: false, pref_disable_polling: false, From 7908c065ba660de5104c47d61925ec690306881a Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 6 Jan 2025 14:59:39 +0100 Subject: [PATCH 7/9] WIP, not functional --- demo/src/stubs/config_entries.ts | 2 +- gallery/src/pages/misc/integration-card.ts | 2 +- src/data/config_entries.ts | 4 ++- src/data/sub_config_flow.ts | 5 ++- .../config-flow/show-dialog-config-flow.ts | 4 ++- .../show-dialog-data-entry-flow.ts | 1 - .../show-dialog-sub-config-flow.ts | 6 ++-- .../ha-config-integration-page.ts | 31 ++++++++++++++++++- .../ha-config-integrations-dashboard.ts | 2 +- 9 files changed, 47 insertions(+), 10 deletions(-) diff --git a/demo/src/stubs/config_entries.ts b/demo/src/stubs/config_entries.ts index 9f23475d435a..bed9a11c1557 100644 --- a/demo/src/stubs/config_entries.ts +++ b/demo/src/stubs/config_entries.ts @@ -11,7 +11,7 @@ export const mockConfigEntries = (hass: MockHomeAssistant) => { supports_remove_device: false, supports_unload: true, supports_reconfigure: true, - supported_subentries: [], + supported_subentry_flows: [], pref_disable_new_entities: false, pref_disable_polling: false, disabled_by: null, diff --git a/gallery/src/pages/misc/integration-card.ts b/gallery/src/pages/misc/integration-card.ts index 3e3af36030a8..a3135d2098d0 100644 --- a/gallery/src/pages/misc/integration-card.ts +++ b/gallery/src/pages/misc/integration-card.ts @@ -32,7 +32,7 @@ const createConfigEntry = ( supports_remove_device: false, supports_unload: true, supports_reconfigure: true, - supported_subentries: [], + supported_subentry_flows: [], num_subentries: 0, disabled_by: null, pref_disable_new_entities: false, diff --git a/src/data/config_entries.ts b/src/data/config_entries.ts index 82b1cc5d8094..b1b4e6f19893 100644 --- a/src/data/config_entries.ts +++ b/src/data/config_entries.ts @@ -19,7 +19,9 @@ export interface ConfigEntry { supports_remove_device: boolean; supports_unload: boolean; supports_reconfigure: boolean; - supported_subentries: string[]; + supported_subentry_flows: { + [key: string]: { supports_reconfigure: boolean }; + }; num_subentries: number; pref_disable_new_entities: boolean; pref_disable_polling: boolean; diff --git a/src/data/sub_config_flow.ts b/src/data/sub_config_flow.ts index 1dbad1cc098e..3e78bde395ec 100644 --- a/src/data/sub_config_flow.ts +++ b/src/data/sub_config_flow.ts @@ -8,7 +8,8 @@ const HEADERS = { export const createSubConfigFlow = ( hass: HomeAssistant, configEntryId: string, - subFlowType: string + subFlowType: string, + subentry_id?: string ) => hass.callApi( "POST", @@ -16,6 +17,8 @@ export const createSubConfigFlow = ( { handler: [configEntryId, subFlowType], show_advanced_options: Boolean(hass.userData?.showAdvanced), + subentry_id, + source: subentry_id ? "reconfigure" : "user", }, HEADERS ); diff --git a/src/dialogs/config-flow/show-dialog-config-flow.ts b/src/dialogs/config-flow/show-dialog-config-flow.ts index dd0aaa438e0b..f94d1687d3a2 100644 --- a/src/dialogs/config-flow/show-dialog-config-flow.ts +++ b/src/dialogs/config-flow/show-dialog-config-flow.ts @@ -16,7 +16,9 @@ export const loadConfigFlowDialog = loadDataEntryFlowDialog; export const showConfigFlowDialog = ( element: HTMLElement, - dialogParams: Omit + dialogParams: Omit & { + entryId?: string; + } ): void => showFlowDialog(element, dialogParams, { flowType: "config_flow", diff --git a/src/dialogs/config-flow/show-dialog-data-entry-flow.ts b/src/dialogs/config-flow/show-dialog-data-entry-flow.ts index bb1d2bbba226..3d67b2e9c556 100644 --- a/src/dialogs/config-flow/show-dialog-data-entry-flow.ts +++ b/src/dialogs/config-flow/show-dialog-data-entry-flow.ts @@ -148,7 +148,6 @@ export interface DataEntryFlowDialogParams { }) => void; flowConfig: FlowConfig; showAdvanced?: boolean; - entryId?: string; dialogParentElement?: HTMLElement; } diff --git a/src/dialogs/config-flow/show-dialog-sub-config-flow.ts b/src/dialogs/config-flow/show-dialog-sub-config-flow.ts index 74071427b2a0..bc886985b272 100644 --- a/src/dialogs/config-flow/show-dialog-sub-config-flow.ts +++ b/src/dialogs/config-flow/show-dialog-sub-config-flow.ts @@ -19,14 +19,16 @@ export const showSubConfigFlowDialog = ( element: HTMLElement, configEntry: ConfigEntry, flowType: string, - dialogParams: Omit + dialogParams: Omit & { + subEntryId?: string; + } ): void => showFlowDialog(element, dialogParams, { flowType: "config_subentries_flow", showDevices: true, createFlow: async (hass, handler) => { const [step] = await Promise.all([ - createSubConfigFlow(hass, handler, flowType), + createSubConfigFlow(hass, handler, flowType, dialogParams.subEntryId), hass.loadFragmentTranslation("config"), hass.loadBackendTranslation("config_subentries", configEntry.domain), hass.loadBackendTranslation("selector", configEntry.domain), diff --git a/src/panels/config/integrations/ha-config-integration-page.ts b/src/panels/config/integrations/ha-config-integration-page.ts index 7073e1507d53..69c206a597d2 100644 --- a/src/panels/config/integrations/ha-config-integration-page.ts +++ b/src/panels/config/integrations/ha-config-integration-page.ts @@ -940,7 +940,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) { )} - ${item.supported_subentries.map( + ${Object.keys(item.supported_subentry_flows).map( (flowType) => html` + ${configEntry.supported_subentry_flows.add_entity?.supports_reconfigure + ? html` + + ${this.hass.localize( + "ui.panel.config.integrations.config_entry.configure" + )} + + ` + : nothing} { + const configEntry = ( + (ev.target as HTMLElement).closest(".sub-entry") as any + ).configEntry; + const subEntry = ((ev.target as HTMLElement).closest(".sub-entry") as any) + .subConfigEntry; + + // const flow = configEntry.supported_subentry_flows[subEntry.subentry_id]; + + showSubConfigFlowDialog( + this, + configEntry, + subEntry.flowType || "add_entity", + { + startFlowHandler: configEntry.entry_id, + subEntryId: subEntry.subentry_id, + } + ); + } + private async _handleDeleteSub(ev: Event): Promise { const configEntry = ( (ev.target as HTMLElement).closest(".sub-entry") as any diff --git a/src/panels/config/integrations/ha-config-integrations-dashboard.ts b/src/panels/config/integrations/ha-config-integrations-dashboard.ts index ca4ec46f1dda..455f889f6471 100644 --- a/src/panels/config/integrations/ha-config-integrations-dashboard.ts +++ b/src/panels/config/integrations/ha-config-integrations-dashboard.ts @@ -204,7 +204,7 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) { supports_remove_device: false, supports_unload: false, supports_reconfigure: false, - supported_subentries: [], + supported_subentry_flows: [], num_subentries: 0, pref_disable_new_entities: false, pref_disable_polling: false, From 321a3ec6022b0e71ad3453fdf34d27c2c51c7d0a Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 7 Jan 2025 12:48:08 +0100 Subject: [PATCH 8/9] add subentry_type --- src/data/config_entries.ts | 1 + src/data/sub_config_flow.ts | 1 - .../config/integrations/ha-config-integration-page.ts | 7 +++---- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/data/config_entries.ts b/src/data/config_entries.ts index b1b4e6f19893..a00e9aa2810f 100644 --- a/src/data/config_entries.ts +++ b/src/data/config_entries.ts @@ -33,6 +33,7 @@ export interface ConfigEntry { export interface SubConfigEntry { subentry_id: string; + subentry_type: string; title: string; unique_id: string; } diff --git a/src/data/sub_config_flow.ts b/src/data/sub_config_flow.ts index 3e78bde395ec..388435e2906f 100644 --- a/src/data/sub_config_flow.ts +++ b/src/data/sub_config_flow.ts @@ -18,7 +18,6 @@ export const createSubConfigFlow = ( handler: [configEntryId, subFlowType], show_advanced_options: Boolean(hass.userData?.showAdvanced), subentry_id, - source: subentry_id ? "reconfigure" : "user", }, HEADERS ); diff --git a/src/panels/config/integrations/ha-config-integration-page.ts b/src/panels/config/integrations/ha-config-integration-page.ts index 69c206a597d2..546542ce585b 100644 --- a/src/panels/config/integrations/ha-config-integration-page.ts +++ b/src/panels/config/integrations/ha-config-integration-page.ts @@ -1065,7 +1065,8 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) { subEntry )} - ${configEntry.supported_subentry_flows.add_entity?.supports_reconfigure + ${configEntry.supported_subentry_flows[subEntry.subentry_type] + ?.supports_reconfigure ? html` ${this.hass.localize( @@ -1307,12 +1308,10 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) { const subEntry = ((ev.target as HTMLElement).closest(".sub-entry") as any) .subConfigEntry; - // const flow = configEntry.supported_subentry_flows[subEntry.subentry_id]; - showSubConfigFlowDialog( this, configEntry, - subEntry.flowType || "add_entity", + subEntry.flowType || subEntry.subentry_type, { startFlowHandler: configEntry.entry_id, subEntryId: subEntry.subentry_id, From 465a81df2d5056ec93a4cf283c6cb2fddab1d1ab Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 7 Jan 2025 13:07:52 +0100 Subject: [PATCH 9/9] rename to supported_subentry_types --- demo/src/stubs/config_entries.ts | 2 +- gallery/src/pages/misc/integration-card.ts | 2 +- src/data/config_entries.ts | 2 +- src/panels/config/integrations/ha-config-integration-page.ts | 4 ++-- .../config/integrations/ha-config-integrations-dashboard.ts | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/demo/src/stubs/config_entries.ts b/demo/src/stubs/config_entries.ts index bed9a11c1557..01685f3848a3 100644 --- a/demo/src/stubs/config_entries.ts +++ b/demo/src/stubs/config_entries.ts @@ -11,7 +11,7 @@ export const mockConfigEntries = (hass: MockHomeAssistant) => { supports_remove_device: false, supports_unload: true, supports_reconfigure: true, - supported_subentry_flows: [], + supported_subentry_types: {}, pref_disable_new_entities: false, pref_disable_polling: false, disabled_by: null, diff --git a/gallery/src/pages/misc/integration-card.ts b/gallery/src/pages/misc/integration-card.ts index a3135d2098d0..4c17ed0f6577 100644 --- a/gallery/src/pages/misc/integration-card.ts +++ b/gallery/src/pages/misc/integration-card.ts @@ -32,7 +32,7 @@ const createConfigEntry = ( supports_remove_device: false, supports_unload: true, supports_reconfigure: true, - supported_subentry_flows: [], + supported_subentry_types: {}, num_subentries: 0, disabled_by: null, pref_disable_new_entities: false, diff --git a/src/data/config_entries.ts b/src/data/config_entries.ts index a00e9aa2810f..216ad9765d32 100644 --- a/src/data/config_entries.ts +++ b/src/data/config_entries.ts @@ -19,7 +19,7 @@ export interface ConfigEntry { supports_remove_device: boolean; supports_unload: boolean; supports_reconfigure: boolean; - supported_subentry_flows: { + supported_subentry_types: { [key: string]: { supports_reconfigure: boolean }; }; num_subentries: number; diff --git a/src/panels/config/integrations/ha-config-integration-page.ts b/src/panels/config/integrations/ha-config-integration-page.ts index 546542ce585b..0f2fa47cdc9d 100644 --- a/src/panels/config/integrations/ha-config-integration-page.ts +++ b/src/panels/config/integrations/ha-config-integration-page.ts @@ -940,7 +940,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) { )} - ${Object.keys(item.supported_subentry_flows).map( + ${Object.keys(item.supported_subentry_types).map( (flowType) => html` - ${configEntry.supported_subentry_flows[subEntry.subentry_type] + ${configEntry.supported_subentry_types[subEntry.subentry_type] ?.supports_reconfigure ? html` diff --git a/src/panels/config/integrations/ha-config-integrations-dashboard.ts b/src/panels/config/integrations/ha-config-integrations-dashboard.ts index 455f889f6471..a6779ae8fe11 100644 --- a/src/panels/config/integrations/ha-config-integrations-dashboard.ts +++ b/src/panels/config/integrations/ha-config-integrations-dashboard.ts @@ -204,7 +204,7 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) { supports_remove_device: false, supports_unload: false, supports_reconfigure: false, - supported_subentry_flows: [], + supported_subentry_types: {}, num_subentries: 0, pref_disable_new_entities: false, pref_disable_polling: false,