import "@material/mwc-button"; import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable"; import "../../components/ha-icon-button"; import "../../components/ha-circular-progress"; import "@polymer/paper-tooltip/paper-tooltip"; import type { UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, CSSResultArray, customElement, html, LitElement, property, PropertyValues, TemplateResult, } from "lit-element"; import "../../components/ha-dialog"; import "../../components/ha-form/ha-form"; import "../../components/ha-markdown"; import { AreaRegistryEntry, subscribeAreaRegistry, } from "../../data/area_registry"; import type { DataEntryFlowStep } from "../../data/data_entry_flow"; import { DeviceRegistryEntry, subscribeDeviceRegistry, } from "../../data/device_registry"; import { haStyleDialog } from "../../resources/styles"; import type { HomeAssistant } from "../../types"; import { DataEntryFlowDialogParams } from "./show-dialog-data-entry-flow"; import "./step-flow-abort"; import "./step-flow-create-entry"; import "./step-flow-external"; import "./step-flow-form"; import "./step-flow-loading"; import "./step-flow-pick-handler"; let instance = 0; declare global { // for fire event interface HASSDomEvents { "flow-update": { step?: DataEntryFlowStep; stepPromise?: Promise; }; } } @customElement("dialog-data-entry-flow") class DataEntryFlowDialog extends LitElement { public hass!: HomeAssistant; @property() private _params?: DataEntryFlowDialogParams; @property() private _loading = true; private _instance = instance; @property() private _step: | DataEntryFlowStep | undefined // Null means we need to pick a config flow | null; @property() private _devices?: DeviceRegistryEntry[]; @property() private _areas?: AreaRegistryEntry[]; @property() private _handlers?: string[]; private _unsubAreas?: UnsubscribeFunc; private _unsubDevices?: UnsubscribeFunc; public async showDialog(params: DataEntryFlowDialogParams): Promise { this._params = params; this._instance = instance++; // Create a new config flow. Show picker if (!params.continueFlowId && !params.startFlowHandler) { if (!params.flowConfig.getFlowHandlers) { throw new Error("No getFlowHandlers defined in flow config"); } this._step = null; // We only load the handlers once if (this._handlers === undefined) { this._loading = true; try { this._handlers = await params.flowConfig.getFlowHandlers(this.hass); } finally { this._loading = false; } } await this.updateComplete; return; } this._loading = true; const curInstance = this._instance; const step = await (params.continueFlowId ? params.flowConfig.fetchFlow(this.hass, params.continueFlowId) : params.flowConfig.createFlow(this.hass, params.startFlowHandler!)); // Happens if second showDialog called if (curInstance !== this._instance) { return; } this._processStep(step); this._loading = false; } protected render(): TemplateResult { if (!this._params) { return html``; } return html`
${this._loading || (this._step === null && this._handlers === undefined) ? html` ` : this._step === undefined ? // When we are going to next step, we render 1 round of empty // to reset the element. "" : html` ${this._step === null ? // Show handler picker html` ` : this._step.type === "form" ? html` ` : this._step.type === "external" ? html` ` : this._step.type === "abort" ? html` ` : this._devices === undefined || this._areas === undefined ? // When it's a create entry result, we will fetch device & area registry html` ` : html` `} `}
`; } protected firstUpdated(changedProps: PropertyValues) { super.firstUpdated(changedProps); this.addEventListener("flow-update", (ev) => { const { step, stepPromise } = (ev as any).detail; this._processStep(step || stepPromise); }); } protected updated(changedProps: PropertyValues) { if ( changedProps.has("_step") && this._step && this._step.type === "create_entry" ) { if (this._params!.flowConfig.loadDevicesAndAreas) { this._fetchDevices(this._step.result); this._fetchAreas(); } else { this._devices = []; this._areas = []; } } } private async _fetchDevices(configEntryId) { this._unsubDevices = subscribeDeviceRegistry( this.hass.connection, (devices) => { this._devices = devices.filter((device) => device.config_entries.includes(configEntryId) ); } ); } private async _fetchAreas() { this._unsubAreas = subscribeAreaRegistry(this.hass.connection, (areas) => { this._areas = areas; }); } private async _processStep( step: DataEntryFlowStep | undefined | Promise ): Promise { if (step instanceof Promise) { this._loading = true; try { this._step = await step; } finally { this._loading = false; } return; } if (step === undefined) { this._flowDone(); return; } this._step = undefined; await this.updateComplete; this._step = step; } private _flowDone(): void { if (!this._params) { return; } const flowFinished = Boolean( this._step && ["create_entry", "abort"].includes(this._step.type) ); // If we created this flow, delete it now. if (this._step && !flowFinished && !this._params.continueFlowId) { this._params.flowConfig.deleteFlow(this.hass, this._step.flow_id); } if (this._params.dialogClosedCallback) { this._params.dialogClosedCallback({ flowFinished, }); } this._step = undefined; this._params = undefined; this._devices = undefined; if (this._unsubAreas) { this._unsubAreas(); this._unsubAreas = undefined; } if (this._unsubDevices) { this._unsubDevices(); this._unsubDevices = undefined; } } private _close(): void { if (this._step) { this._flowDone(); } else if (this._step === null) { // Flow aborted during picking flow this._step = undefined; this._params = undefined; } } static get styles(): CSSResultArray { return [ haStyleDialog, css` ha-dialog { --dialog-content-padding: 0; } ha-icon-button { padding: 16px; position: absolute; top: 0; right: 0; } `, ]; } } declare global { interface HTMLElementTagNameMap { "dialog-data-entry-flow": DataEntryFlowDialog; } }