mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-23 09:16:38 +00:00
Improv external flow (#22878)
* WIP improv external flow * Update external_messaging.ts * use name instead, start flow * make copy * Update ha-config-integrations-dashboard.ts * Update * rename command * Use a map instead of array for deduping
This commit is contained in:
parent
2899388395
commit
6b8068a22a
@ -13,6 +13,7 @@ import type {
|
|||||||
EMIncomingMessageBarCodeScanAborted,
|
EMIncomingMessageBarCodeScanAborted,
|
||||||
EMIncomingMessageBarCodeScanResult,
|
EMIncomingMessageBarCodeScanResult,
|
||||||
EMIncomingMessageCommands,
|
EMIncomingMessageCommands,
|
||||||
|
ImprovDiscoveredDevice,
|
||||||
} from "./external_messaging";
|
} from "./external_messaging";
|
||||||
|
|
||||||
const barCodeListeners = new Set<
|
const barCodeListeners = new Set<
|
||||||
@ -113,6 +114,22 @@ const handleExternalMessage = (
|
|||||||
success: true,
|
success: true,
|
||||||
result: null,
|
result: null,
|
||||||
});
|
});
|
||||||
|
} else if (msg.command === "improv/discovered_device") {
|
||||||
|
fireEvent(window, "improv-discovered-device", msg.payload);
|
||||||
|
bus.fireMessage({
|
||||||
|
id: msg.id,
|
||||||
|
type: "result",
|
||||||
|
success: true,
|
||||||
|
result: null,
|
||||||
|
});
|
||||||
|
} else if (msg.command === "improv/device_setup_done") {
|
||||||
|
fireEvent(window, "improv-device-setup-done");
|
||||||
|
bus.fireMessage({
|
||||||
|
id: msg.id,
|
||||||
|
type: "result",
|
||||||
|
success: true,
|
||||||
|
result: null,
|
||||||
|
});
|
||||||
} else if (msg.command === "bar_code/scan_result") {
|
} else if (msg.command === "bar_code/scan_result") {
|
||||||
barCodeListeners.forEach((listener) => listener(msg));
|
barCodeListeners.forEach((listener) => listener(msg));
|
||||||
bus.fireMessage({
|
bus.fireMessage({
|
||||||
@ -135,3 +152,10 @@ const handleExternalMessage = (
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HASSDomEvents {
|
||||||
|
"improv-discovered-device": ImprovDiscoveredDevice;
|
||||||
|
"improv-device-setup-done": undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -134,10 +134,18 @@ interface EMOutgoingMessageAssistShow extends EMMessage {
|
|||||||
start_listening: boolean;
|
start_listening: boolean;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EMOutgoingMessageImprovScan extends EMMessage {
|
interface EMOutgoingMessageImprovScan extends EMMessage {
|
||||||
type: "improv/scan";
|
type: "improv/scan";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface EMOutgoingMessageImprovConfigureDevice extends EMMessage {
|
||||||
|
type: "improv/configure_device";
|
||||||
|
payload: {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface EMOutgoingMessageThreadStoreInPlatformKeychain extends EMMessage {
|
interface EMOutgoingMessageThreadStoreInPlatformKeychain extends EMMessage {
|
||||||
type: "thread/store_in_platform_keychain";
|
type: "thread/store_in_platform_keychain";
|
||||||
payload: {
|
payload: {
|
||||||
@ -167,7 +175,8 @@ type EMOutgoingMessageWithoutAnswer =
|
|||||||
| EMOutgoingMessageTagWrite
|
| EMOutgoingMessageTagWrite
|
||||||
| EMOutgoingMessageThemeUpdate
|
| EMOutgoingMessageThemeUpdate
|
||||||
| EMOutgoingMessageThreadStoreInPlatformKeychain
|
| EMOutgoingMessageThreadStoreInPlatformKeychain
|
||||||
| EMOutgoingMessageImprovScan;
|
| EMOutgoingMessageImprovScan
|
||||||
|
| EMOutgoingMessageImprovConfigureDevice;
|
||||||
|
|
||||||
interface EMIncomingMessageRestart {
|
interface EMIncomingMessageRestart {
|
||||||
id: number;
|
id: number;
|
||||||
@ -237,6 +246,23 @@ export interface EMIncomingMessageBarCodeScanAborted {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ImprovDiscoveredDevice {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EMIncomingMessageImprovDeviceDiscovered extends EMMessage {
|
||||||
|
id: number;
|
||||||
|
type: "command";
|
||||||
|
command: "improv/discovered_device";
|
||||||
|
payload: ImprovDiscoveredDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EMIncomingMessageImprovDeviceSetupDone extends EMMessage {
|
||||||
|
id: number;
|
||||||
|
type: "command";
|
||||||
|
command: "improv/device_setup_done";
|
||||||
|
}
|
||||||
|
|
||||||
export type EMIncomingMessageCommands =
|
export type EMIncomingMessageCommands =
|
||||||
| EMIncomingMessageRestart
|
| EMIncomingMessageRestart
|
||||||
| EMIncomingMessageShowNotifications
|
| EMIncomingMessageShowNotifications
|
||||||
@ -244,7 +270,9 @@ export type EMIncomingMessageCommands =
|
|||||||
| EMIncomingMessageShowSidebar
|
| EMIncomingMessageShowSidebar
|
||||||
| EMIncomingMessageShowAutomationEditor
|
| EMIncomingMessageShowAutomationEditor
|
||||||
| EMIncomingMessageBarCodeScanResult
|
| EMIncomingMessageBarCodeScanResult
|
||||||
| EMIncomingMessageBarCodeScanAborted;
|
| EMIncomingMessageBarCodeScanAborted
|
||||||
|
| EMIncomingMessageImprovDeviceDiscovered
|
||||||
|
| EMIncomingMessageImprovDeviceSetupDone;
|
||||||
|
|
||||||
type EMIncomingMessage =
|
type EMIncomingMessage =
|
||||||
| EMMessageResultSuccess
|
| EMMessageResultSuccess
|
||||||
|
@ -124,6 +124,17 @@ export class HaConfigFlowCard extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _continueFlow() {
|
private _continueFlow() {
|
||||||
|
if (this.flow.flow_id === "external") {
|
||||||
|
this.hass.auth.external!.fireMessage({
|
||||||
|
type: "improv/configure_device",
|
||||||
|
payload: {
|
||||||
|
name:
|
||||||
|
this.flow.localized_title ||
|
||||||
|
this.flow.context.title_placeholders.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
showConfigFlowDialog(this, {
|
showConfigFlowDialog(this, {
|
||||||
continueFlowId: this.flow.flow_id,
|
continueFlowId: this.flow.flow_id,
|
||||||
dialogClosedCallback: () => {
|
dialogClosedCallback: () => {
|
||||||
|
@ -69,6 +69,7 @@ import type { HaIntegrationCard } from "./ha-integration-card";
|
|||||||
import "./ha-integration-overflow-menu";
|
import "./ha-integration-overflow-menu";
|
||||||
import { showAddIntegrationDialog } from "./show-add-integration-dialog";
|
import { showAddIntegrationDialog } from "./show-add-integration-dialog";
|
||||||
import { fetchEntitySourcesWithCache } from "../../../data/entity_sources";
|
import { fetchEntitySourcesWithCache } from "../../../data/entity_sources";
|
||||||
|
import type { ImprovDiscoveredDevice } from "../../../external_app/external_messaging";
|
||||||
|
|
||||||
export interface ConfigEntryExtended extends Omit<ConfigEntry, "entry_id"> {
|
export interface ConfigEntryExtended extends Omit<ConfigEntry, "entry_id"> {
|
||||||
entry_id?: string;
|
entry_id?: string;
|
||||||
@ -105,6 +106,9 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
public configEntriesInProgress?: DataEntryFlowProgressExtended[];
|
public configEntriesInProgress?: DataEntryFlowProgressExtended[];
|
||||||
|
|
||||||
|
@state() private _improvDiscovered: Map<string, ImprovDiscoveredDevice> =
|
||||||
|
new Map();
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private _entityRegistryEntries: EntityRegistryEntry[] = [];
|
private _entityRegistryEntries: EntityRegistryEntry[] = [];
|
||||||
|
|
||||||
@ -131,6 +135,18 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
|
|||||||
[integration: string]: IntegrationLogInfo;
|
[integration: string]: IntegrationLogInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public disconnectedCallback(): void {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
window.removeEventListener(
|
||||||
|
"improv-discovered-device",
|
||||||
|
this._handleImprovDiscovered
|
||||||
|
);
|
||||||
|
window.removeEventListener(
|
||||||
|
"improv-device-setup-done",
|
||||||
|
this._reScanImprovDevices
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public hassSubscribe(): Array<UnsubscribeFunc | Promise<UnsubscribeFunc>> {
|
public hassSubscribe(): Array<UnsubscribeFunc | Promise<UnsubscribeFunc>> {
|
||||||
return [
|
return [
|
||||||
subscribeEntityRegistry(this.hass.connection, (entries) => {
|
subscribeEntityRegistry(this.hass.connection, (entries) => {
|
||||||
@ -244,8 +260,38 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
|
|||||||
private _filterConfigEntriesInProgress = memoizeOne(
|
private _filterConfigEntriesInProgress = memoizeOne(
|
||||||
(
|
(
|
||||||
configEntriesInProgress: DataEntryFlowProgressExtended[],
|
configEntriesInProgress: DataEntryFlowProgressExtended[],
|
||||||
|
improvDiscovered: Map<string, ImprovDiscoveredDevice>,
|
||||||
filter?: string
|
filter?: string
|
||||||
): DataEntryFlowProgressExtended[] => {
|
): DataEntryFlowProgressExtended[] => {
|
||||||
|
let inProgress = [...configEntriesInProgress];
|
||||||
|
|
||||||
|
const improvDiscoveredArray = Array.from(improvDiscovered.values());
|
||||||
|
|
||||||
|
if (improvDiscoveredArray.length) {
|
||||||
|
// filter out native flows that have been discovered by both mobile and local bluetooth
|
||||||
|
inProgress = inProgress.filter(
|
||||||
|
(flow) =>
|
||||||
|
!improvDiscoveredArray.some(
|
||||||
|
(discovered) => discovered.name === flow.localized_title
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// add mobile flows to the list
|
||||||
|
improvDiscovered.forEach((discovered) => {
|
||||||
|
inProgress.push({
|
||||||
|
flow_id: "external",
|
||||||
|
handler: "improv_ble",
|
||||||
|
context: {
|
||||||
|
title_placeholders: {
|
||||||
|
name: discovered.name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
step_id: "bluetooth_confirm",
|
||||||
|
localized_title: discovered.name,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let filteredEntries: DataEntryFlowProgressExtended[];
|
let filteredEntries: DataEntryFlowProgressExtended[];
|
||||||
if (filter) {
|
if (filter) {
|
||||||
const options: IFuseOptions<DataEntryFlowProgressExtended> = {
|
const options: IFuseOptions<DataEntryFlowProgressExtended> = {
|
||||||
@ -255,12 +301,12 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
|
|||||||
threshold: 0.2,
|
threshold: 0.2,
|
||||||
getFn: getStripDiacriticsFn,
|
getFn: getStripDiacriticsFn,
|
||||||
};
|
};
|
||||||
const fuse = new Fuse(configEntriesInProgress, options);
|
const fuse = new Fuse(inProgress, options);
|
||||||
filteredEntries = fuse
|
filteredEntries = fuse
|
||||||
.search(stripDiacritics(filter))
|
.search(stripDiacritics(filter))
|
||||||
.map((result) => result.item);
|
.map((result) => result.item);
|
||||||
} else {
|
} else {
|
||||||
filteredEntries = configEntriesInProgress;
|
filteredEntries = inProgress;
|
||||||
}
|
}
|
||||||
return filteredEntries.sort((a, b) =>
|
return filteredEntries.sort((a, b) =>
|
||||||
caseInsensitiveStringCompare(
|
caseInsensitiveStringCompare(
|
||||||
@ -280,6 +326,8 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
|
|||||||
this._handleAdd();
|
this._handleAdd();
|
||||||
}
|
}
|
||||||
this._scanUSBDevices();
|
this._scanUSBDevices();
|
||||||
|
this._scanImprovDevices();
|
||||||
|
|
||||||
if (isComponentLoaded(this.hass, "diagnostics")) {
|
if (isComponentLoaded(this.hass, "diagnostics")) {
|
||||||
fetchDiagnosticHandlers(this.hass).then((infos) => {
|
fetchDiagnosticHandlers(this.hass).then((infos) => {
|
||||||
const handlers = {};
|
const handlers = {};
|
||||||
@ -334,6 +382,7 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
|
|||||||
);
|
);
|
||||||
const configEntriesInProgress = this._filterConfigEntriesInProgress(
|
const configEntriesInProgress = this._filterConfigEntriesInProgress(
|
||||||
this.configEntriesInProgress,
|
this.configEntriesInProgress,
|
||||||
|
this._improvDiscovered,
|
||||||
this._filter
|
this._filter
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -608,6 +657,43 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
|
|||||||
await scanUSBDevices(this.hass);
|
await scanUSBDevices(this.hass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _scanImprovDevices() {
|
||||||
|
if (!this.hass.auth.external?.config.canSetupImprov) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener(
|
||||||
|
"improv-discovered-device",
|
||||||
|
this._handleImprovDiscovered
|
||||||
|
);
|
||||||
|
|
||||||
|
window.addEventListener(
|
||||||
|
"improv-device-setup-done",
|
||||||
|
this._reScanImprovDevices
|
||||||
|
);
|
||||||
|
|
||||||
|
this.hass.auth.external!.fireMessage({
|
||||||
|
type: "improv/scan",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _reScanImprovDevices = () => {
|
||||||
|
if (!this.hass.auth.external?.config.canSetupImprov) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._improvDiscovered = new Map();
|
||||||
|
this.hass.auth.external!.fireMessage({
|
||||||
|
type: "improv/scan",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private _handleImprovDiscovered = (ev) => {
|
||||||
|
this._fetchManifests(["improv_ble"]);
|
||||||
|
this._improvDiscovered.set(ev.detail.name, ev.detail);
|
||||||
|
// copy for memoize and reactive updates
|
||||||
|
this._improvDiscovered = new Map(Array.from(this._improvDiscovered));
|
||||||
|
};
|
||||||
|
|
||||||
private async _fetchEntitySources() {
|
private async _fetchEntitySources() {
|
||||||
const entitySources = await fetchEntitySourcesWithCache(this.hass);
|
const entitySources = await fetchEntitySourcesWithCache(this.hass);
|
||||||
|
|
||||||
@ -657,6 +743,7 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
private _handleFlowUpdated() {
|
private _handleFlowUpdated() {
|
||||||
getConfigFlowInProgressCollection(this.hass.connection).refresh();
|
getConfigFlowInProgressCollection(this.hass.connection).refresh();
|
||||||
|
this._reScanImprovDevices();
|
||||||
this._fetchManifests();
|
this._fetchManifests();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -664,11 +751,6 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
|
|||||||
showAddIntegrationDialog(this, {
|
showAddIntegrationDialog(this, {
|
||||||
initialFilter: this._filter,
|
initialFilter: this._filter,
|
||||||
});
|
});
|
||||||
if (this.hass.auth.external?.config.canSetupImprov) {
|
|
||||||
this.hass.auth.external!.fireMessage({
|
|
||||||
type: "improv/scan",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleMenuAction(ev: CustomEvent<ActionDetail>) {
|
private _handleMenuAction(ev: CustomEvent<ActionDetail>) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user