Compare commits

...
Author SHA1 Message Date
Petar Petrov a61e11bccb Check for a newer add integration load after loading translations 2026-10-08 08:08:32 +03:00
Petar Petrov e6df427444 Show only serial integrations when setting up an ESPHome serial port
The add integration dialog gets an integrationFilter param with a list of
domains and trims the integration descriptions to them. The ESPHome serial
Set up button fetches usb/list_serial_integrations and passes it. If that
fails, it opens the unfiltered dialog as before.
2026-10-07 17:45:04 +03:00
5 changed files with 86 additions and 9 deletions
+30
View File
@@ -39,6 +39,10 @@ export interface IntegrationDescriptions {
};
}
export interface IntegrationFilter {
domains: string[];
}
export const getIntegrationDescriptions = (
hass: HomeAssistant
): Promise<IntegrationDescriptions> =>
@@ -46,6 +50,32 @@ export const getIntegrationDescriptions = (
type: "integration/descriptions",
});
export const filterIntegrationsByDomains = <T extends Brands>(
integrations: T,
domains: string[]
): T => {
const filtered: Brands = {};
for (const [domain, integration] of Object.entries(integrations)) {
if ("integration_type" in integration) {
if (domains.includes(domain)) {
filtered[domain] = integration;
}
continue;
}
const subIntegrations = Object.entries(
integration.integrations ?? {}
).filter(([subDomain]) => domains.includes(subDomain));
if (subIntegrations.length) {
const { iot_standards: _iotStandards, ...brand } = integration;
filtered[domain] = {
...brand,
integrations: Object.fromEntries(subIntegrations),
};
}
}
return filtered as T;
};
export const findIntegration = (
integrations: Brands | undefined,
domain: string
+3
View File
@@ -46,3 +46,6 @@ export const listSerialPortsWithUsage = (hass: { callWS: CallWS }) =>
type: "usb/list_serial_ports",
include_usage: true,
});
export const listSerialIntegrations = (hass: { callWS: CallWS }) =>
hass.callWS<string[]>({ type: "usb/list_serial_integrations" });
@@ -64,7 +64,11 @@ import {
startHassioAddon,
} from "../../data/hassio/addon";
import { extractApiErrorMessage } from "../../data/hassio/common";
import { listSerialPortsWithUsage, type SerialPortUsage } from "../../data/usb";
import {
listSerialIntegrations,
listSerialPortsWithUsage,
type SerialPortUsage,
} from "../../data/usb";
import { showAddIntegrationDialog } from "../../panels/config/integrations/show-add-integration-dialog";
import { haStyle, haStyleDialog } from "../../resources/styles";
import type { HomeAssistant, HomeAssistantUI } from "../../types";
@@ -712,9 +716,21 @@ class DialogESPHomeDeviceSetup extends DialogMixin<ESPHomeDeviceSetupDialogParam
`;
}
private _setupSerialPort = (ev: Event) => {
private _setupSerialPort = async (ev: Event) => {
ev.stopPropagation();
showAddIntegrationDialog(this);
if (!this._api) {
return;
}
const domains = await listSerialIntegrations(this._api).catch(
() => undefined
);
if (!this.isConnected) {
return;
}
showAddIntegrationDialog(
this,
domains ? { integrationFilter: { domains } } : undefined
);
};
private _dialogTitle(): string {
@@ -45,9 +45,11 @@ import type {
Brand,
Brands,
Integration,
IntegrationFilter,
Integrations,
} from "../../../data/integrations";
import {
filterIntegrationsByDomains,
findIntegration,
getIntegrationDescriptions,
} from "../../../data/integrations";
@@ -95,6 +97,8 @@ class AddIntegrationDialog extends LitElement {
@state() private _filter?: string;
@state() private _integrationFilter?: IntegrationFilter;
@state() private _pickedBrand?: string;
@state() private _prevPickedBrand?: string;
@@ -117,8 +121,10 @@ class AddIntegrationDialog extends LitElement {
private _height?: number;
private _loadId = 0;
public async showDialog(params?: AddIntegrationDialogParams): Promise<void> {
const loadPromise = this._load();
const loadPromise = this._load(params?.integrationFilter);
if (params?.domain) {
// If we get here we clicked the button to add an entry for a specific integration
@@ -150,6 +156,7 @@ class AddIntegrationDialog extends LitElement {
: params?.domain || params?.brand;
this._openedDirectly = !!(params?.brand || params?.domain);
this._initialFilter = params?.initialFilter;
this._integrationFilter = params?.integrationFilter;
this._navigateToResult = params?.navigateToResult ?? false;
this._narrow = matchMedia(
"all and (max-width: 450px), all and (max-height: 500px)"
@@ -171,8 +178,10 @@ class AddIntegrationDialog extends LitElement {
this._openedDirectly = false;
this._navigateToResult = false;
this._filter = undefined;
this._integrationFilter = undefined;
this._width = undefined;
this._height = undefined;
this._loadId++;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
@@ -213,11 +222,12 @@ class AddIntegrationDialog extends LitElement {
components: HassConfig["components"],
localize: LocalizeFunc,
discoveredFlowsCount: number,
filter?: string
filter?: string,
integrationFilter?: IntegrationFilter
): IntegrationListItem[] => {
// Create a single discovered devices row if there are any discovered flows
const discoveredRows: IntegrationListItem[] =
discoveredFlowsCount > 0
!integrationFilter && discoveredFlowsCount > 0
? [
{
id: "_discovered",
@@ -235,7 +245,7 @@ class AddIntegrationDialog extends LitElement {
: [];
const addDeviceRows: IntegrationListItem[] = PROTOCOL_INTEGRATIONS.filter(
(domain) => components.includes(domain)
(domain) => !integrationFilter && components.includes(domain)
)
.map((domain) => ({
id: `device_${domain}`,
@@ -386,7 +396,8 @@ class AddIntegrationDialog extends LitElement {
this.hass.config.components,
this.hass.localize,
this._flowsInProgress?.length ?? 0,
this._filter
this._filter,
this._integrationFilter
);
}
@@ -608,7 +619,8 @@ class AddIntegrationDialog extends LitElement {
`;
};
private async _load() {
private async _load(integrationFilter?: IntegrationFilter) {
const loadId = ++this._loadId;
const [descriptions, flowsInProgress] = await Promise.all([
getIntegrationDescriptions(this.hass),
fetchConfigFlowInProgress(this.hass.connection),
@@ -627,6 +639,10 @@ class AddIntegrationDialog extends LitElement {
await this.hass.loadBackendTranslation("title", discoveredHandlers, true);
}
if (loadId !== this._loadId) {
return;
}
for (const integration in descriptions.custom.integration) {
if (
!Object.prototype.hasOwnProperty.call(
@@ -657,6 +673,16 @@ class AddIntegrationDialog extends LitElement {
...descriptions.core.helper,
...descriptions.custom.helper,
};
if (integrationFilter) {
this._integrations = filterIntegrationsByDomains(
this._integrations,
integrationFilter.domains
);
this._helpers = filterIntegrationsByDomains(
this._helpers,
integrationFilter.domains
);
}
this.hass.loadBackendTranslation(
"title",
descriptions.core.translated_name,
@@ -1,10 +1,12 @@
import { fireEvent } from "../../../common/dom/fire_event";
import type { IntegrationManifest } from "../../../data/integration";
import type { IntegrationFilter } from "../../../data/integrations";
export interface AddIntegrationDialogParams {
brand?: string;
domain?: string;
initialFilter?: string;
integrationFilter?: IntegrationFilter;
navigateToResult?: boolean;
}