diff --git a/src/data/bootstrap_integrations.ts b/src/data/bootstrap_integrations.ts new file mode 100644 index 0000000000..864cc2efef --- /dev/null +++ b/src/data/bootstrap_integrations.ts @@ -0,0 +1,16 @@ +import { HomeAssistant } from "../types"; + +export type BootstrapIntegrationsTimings = { [key: string]: number }; + +export const subscribeBootstrapIntegrations = ( + hass: HomeAssistant, + callback: (message: BootstrapIntegrationsTimings) => void +) => { + const unsubProm = hass.connection.subscribeMessage< + BootstrapIntegrationsTimings + >((message) => callback(message), { + type: "subscribe_bootstrap_integrations", + }); + + return unsubProm; +}; diff --git a/src/state/disconnect-toast-mixin.ts b/src/state/disconnect-toast-mixin.ts index 1bf402ce8f..09efcc3f89 100644 --- a/src/state/disconnect-toast-mixin.ts +++ b/src/state/disconnect-toast-mixin.ts @@ -2,13 +2,21 @@ import { STATE_NOT_RUNNING, STATE_RUNNING, STATE_STARTING, + UnsubscribeFunc, } from "home-assistant-js-websocket"; import { Constructor } from "../types"; import { showToast } from "../util/toast"; import { HassBaseEl } from "./hass-base-mixin"; +import { domainToName } from "../data/integration"; +import { + subscribeBootstrapIntegrations, + BootstrapIntegrationsTimings, +} from "../data/bootstrap_integrations"; export default >(superClass: T) => class extends superClass { + private _subscribedBootstrapIntegrations?: Promise; + protected firstUpdated(changedProps) { super.firstUpdated(changedProps); // Need to load in advance because when disconnected, can't dynamically load code. @@ -35,15 +43,19 @@ export default >(superClass: T) => action: { text: this.hass!.localize("ui.notification_toast.dismiss") || "Dismiss", - action: () => {}, + action: () => { + this._unsubscribeBootstrapIntergrations(); + }, }, }); + this._subscribeBootstrapIntergrations(); } else if ( oldHass?.config && oldHass.config.state === STATE_NOT_RUNNING && (this.hass!.config.state === STATE_STARTING || this.hass!.config.state === STATE_RUNNING) ) { + this._unsubscribeBootstrapIntergrations(); showToast(this, { message: this.hass!.localize("ui.notification_toast.started"), duration: 5000, @@ -68,4 +80,69 @@ export default >(superClass: T) => dismissable: false, }); } + + private _handleMessage(message: BootstrapIntegrationsTimings): void { + if (this.hass!.config.state !== STATE_NOT_RUNNING) { + return; + } + + if (Object.keys(message).length === 0) { + showToast(this, { + message: + this.hass!.localize("ui.notification_toast.wrapping_up_startup") || + `Wrapping up startup, not everything will be available until it is finished.`, + duration: 0, + dismissable: false, + action: { + text: + this.hass!.localize("ui.notification_toast.dismiss") || "Dismiss", + action: () => {}, + }, + }); + return; + } + + // Show the integration that has been starting for the longest time + const integration = Object.entries(message).sort( + ([, a], [, b]) => b - a + )[0][0]; + + showToast(this, { + message: + this.hass!.localize( + "ui.notification_toast.intergration_starting", + "integration", + domainToName(this.hass!.localize, integration) + ) || + `Starting ${integration}, not everything will be available until it is finished.`, + duration: 0, + dismissable: false, + action: { + text: + this.hass!.localize("ui.notification_toast.dismiss") || "Dismiss", + action: () => { + this._unsubscribeBootstrapIntergrations(); + }, + }, + }); + } + + private _unsubscribeBootstrapIntergrations() { + if (this._subscribedBootstrapIntegrations) { + this._subscribedBootstrapIntegrations.then((unsub) => unsub()); + this._subscribedBootstrapIntegrations = undefined; + } + } + + private _subscribeBootstrapIntergrations() { + if (!this.hass) { + return; + } + this._subscribedBootstrapIntegrations = subscribeBootstrapIntegrations( + this.hass!, + (message) => { + this._handleMessage(message); + } + ); + } }; diff --git a/src/translations/en.json b/src/translations/en.json index 143647766d..65ec334f84 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -850,6 +850,8 @@ "connection_lost": "Connection lost. Reconnecting…", "started": "Home Assistant has started!", "starting": "Home Assistant is starting, not everything will be available until it is finished.", + "wrapping_up_startup": "Wrapping up startup, not everything will be available until it is finished.", + "intergration_starting": "Starting {integration}, not everything will be available until it is finished.", "triggered": "Triggered {name}", "dismiss": "Dismiss" },