Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/data/config_entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ export interface FieldSchema {
optional: boolean;
}

export interface ConfigFlowProgress {
flow_id: string;
handler: string;
context: { [key: string]: any };
}

export interface ConfigFlowStepForm {
type: "form";
flow_id: string;
Expand Down Expand Up @@ -62,3 +68,9 @@ export const handleConfigFlowStep = (

export const deleteConfigFlow = (hass: HomeAssistant, flowId: string) =>
hass.callApi("DELETE", `config/config_entries/flow/${flowId}`);

export const getConfigFlowsInProgress = (hass: HomeAssistant) =>
hass.callApi<ConfigFlowProgress[]>("GET", "config/config_entries/flow");

export const getConfigFlowHandlers = (hass: HomeAssistant) =>
hass.callApi<string[]>("GET", "config/config_entries/flow_handlers");
104 changes: 77 additions & 27 deletions src/dialogs/config-flow/dialog-config-flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,14 @@ import { HaPaperDialog } from "../../components/dialog/ha-paper-dialog";
import { haStyleDialog } from "../../resources/styles";
import {
fetchConfigFlow,
createConfigFlow,
ConfigFlowStep,
deleteConfigFlow,
getConfigFlowHandlers,
} from "../../data/config_entries";
import { PolymerChangedEvent } from "../../polymer-types";
import { HaConfigFlowParams } from "./show-dialog-config-flow";

import "./step-flow-pick-handler";
import "./step-flow-loading";
import "./step-flow-form";
import "./step-flow-abort";
Expand All @@ -39,6 +40,7 @@ import {
fetchDeviceRegistry,
} from "../../data/device_registry";
import { AreaRegistryEntry, fetchAreaRegistry } from "../../data/area_registry";
import { HomeAssistant } from "../../types";

let instance = 0;

Expand All @@ -47,12 +49,15 @@ declare global {
interface HASSDomEvents {
"flow-update": {
step?: ConfigFlowStep;
stepPromise?: Promise<ConfigFlowStep>;
};
}
}

@customElement("dialog-config-flow")
class ConfigFlowDialog extends LitElement {
public hass!: HomeAssistant;

@property()
private _params?: HaConfigFlowParams;

Expand All @@ -62,33 +67,47 @@ class ConfigFlowDialog extends LitElement {
private _instance = instance;

@property()
private _step?: ConfigFlowStep;
private _step:
| ConfigFlowStep
| undefined
// Null means we need to pick a config flow
| null;

@property()
private _devices?: DeviceRegistryEntry[];

@property()
private _areas?: AreaRegistryEntry[];

@property()
private _handlers?: string[];

public async showDialog(params: HaConfigFlowParams): Promise<void> {
this._params = params;
this._loading = true;
this._instance = instance++;

const fetchStep = params.continueFlowId
? fetchConfigFlow(params.hass, params.continueFlowId)
: params.newFlowForHandler
? createConfigFlow(params.hass, params.newFlowForHandler)
: undefined;

if (!fetchStep) {
throw new Error(`Pass in either continueFlowId or newFlorForHandler`);
// Create a new config flow. Show picker
if (!params.continueFlowId) {
this._step = null;

// We only load the handlers once
if (this._handlers === undefined) {
this._loading = true;
this.updateComplete.then(() => this._scheduleCenterDialog());
try {
this._handlers = await getConfigFlowHandlers(this.hass);
} finally {
this._loading = false;
}
}
await this.updateComplete;
this._scheduleCenterDialog();
return;
}

this._loading = true;
const curInstance = this._instance;

await this.updateComplete;
const step = await fetchStep;
const step = await fetchConfigFlow(this.hass, params.continueFlowId);

// Happens if second showDialog called
if (curInstance !== this._instance) {
Expand All @@ -99,7 +118,7 @@ class ConfigFlowDialog extends LitElement {
this._loading = false;
// When the flow changes, center the dialog.
// Don't do it on each step or else the dialog keeps bouncing.
setTimeout(() => this._dialog.center(), 0);
this._scheduleCenterDialog();
}

protected render(): TemplateResult | void {
Expand All @@ -113,26 +132,34 @@ class ConfigFlowDialog extends LitElement {
opened
@opened-changed=${this._openedChanged}
>
${this._loading
${this._loading || (this._step === null && this._handlers === undefined)
? html`
<step-flow-loading></step-flow-loading>
`
: this._step === undefined
? // When we are going to next step, we render 1 round of empty
// to reset the element.
""
: this._step === null
? // Show handler picker
html`
<step-flow-pick-handler
.hass=${this.hass}
.handlers=${this._handlers}
></step-flow-pick-handler>
`
: this._step.type === "form"
? html`
<step-flow-form
.step=${this._step}
.hass=${this._params.hass}
.hass=${this.hass}
></step-flow-form>
`
: this._step.type === "abort"
? html`
<step-flow-abort
.step=${this._step}
.hass=${this._params.hass}
.hass=${this.hass}
></step-flow-abort>
`
: this._devices === undefined || this._areas === undefined
Expand All @@ -143,7 +170,7 @@ class ConfigFlowDialog extends LitElement {
: html`
<step-flow-create-entry
.step=${this._step}
.hass=${this._params.hass}
.hass=${this.hass}
.devices=${this._devices}
.areas=${this._areas}
></step-flow-create-entry>
Expand All @@ -155,7 +182,8 @@ class ConfigFlowDialog extends LitElement {
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this.addEventListener("flow-update", (ev) => {
this._processStep((ev as any).detail.step);
const { step, stepPromise } = (ev as any).detail;
this._processStep(step || stepPromise);
});
}

Expand All @@ -170,24 +198,40 @@ class ConfigFlowDialog extends LitElement {
}
}

private _scheduleCenterDialog() {
setTimeout(() => this._dialog.center(), 0);
}

private get _dialog(): HaPaperDialog {
return this.shadowRoot!.querySelector("ha-paper-dialog")!;
}

private async _fetchDevices(configEntryId) {
// Wait 5 seconds to give integrations time to find devices
await new Promise((resolve) => setTimeout(resolve, 5000));
const devices = await fetchDeviceRegistry(this._params!.hass);
const devices = await fetchDeviceRegistry(this.hass);
this._devices = devices.filter((device) =>
device.config_entries.includes(configEntryId)
);
}

private async _fetchAreas() {
this._areas = await fetchAreaRegistry(this._params!.hass);
this._areas = await fetchAreaRegistry(this.hass);
}

private async _processStep(step: ConfigFlowStep): Promise<void> {
private async _processStep(
step: ConfigFlowStep | undefined | Promise<ConfigFlowStep>
): Promise<void> {
if (step instanceof Promise) {
this._loading = true;
try {
this._step = await step;
} finally {
this._loading = false;
}
return;
}

if (step === undefined) {
this._flowDone();
return;
Expand All @@ -206,8 +250,8 @@ class ConfigFlowDialog extends LitElement {
);

// If we created this flow, delete it now.
if (this._step && !flowFinished && this._params.newFlowForHandler) {
deleteConfigFlow(this._params.hass, this._step.flow_id);
if (this._step && !flowFinished && !this._params.continueFlowId) {
deleteConfigFlow(this.hass, this._step.flow_id);
}

this._params.dialogClosedCallback({
Expand All @@ -221,8 +265,14 @@ class ConfigFlowDialog extends LitElement {

private _openedChanged(ev: PolymerChangedEvent<boolean>): void {
// Closed dialog by clicking on the overlay
if (this._step && !ev.detail.value) {
this._flowDone();
if (!ev.detail.value) {
if (this._step) {
this._flowDone();
} else if (this._step === null) {
// Flow aborted during picking flow
this._step = undefined;
this._params = undefined;
}
}
}

Expand Down
3 changes: 0 additions & 3 deletions src/dialogs/config-flow/show-dialog-config-flow.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { HomeAssistant } from "../../types";
import { fireEvent } from "../../common/dom/fire_event";

export interface HaConfigFlowParams {
hass: HomeAssistant;
continueFlowId?: string;
newFlowForHandler?: string;
dialogClosedCallback: (params: { flowFinished: boolean }) => void;
}

Expand Down
2 changes: 2 additions & 0 deletions src/dialogs/config-flow/step-flow-form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ class StepFlowForm extends LitElement {
toSendData
);

// make sure we're still showing the same step as when we
// fired off request.
if (!this.step || flowId !== this.step.flow_id) {
return;
}
Expand Down
67 changes: 67 additions & 0 deletions src/dialogs/config-flow/step-flow-pick-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {
LitElement,
TemplateResult,
html,
css,
customElement,
CSSResult,
} from "lit-element";
import "@polymer/paper-spinner/paper-spinner-lite";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
import { HomeAssistant } from "../../types";
import { createConfigFlow } from "../../data/config_entries";
import { fireEvent } from "../../common/dom/fire_event";
import "../../components/ha-icon-next";

@customElement("step-flow-pick-handler")
class StepFlowPickHandler extends LitElement {
public hass!: HomeAssistant;
public handlers!: string[];

protected render(): TemplateResult | void {
return html`
<h2>${this.hass.localize("ui.panel.config.integrations.new")}</h2>
<div>
${this.handlers.map(
(handler) =>
html`
<paper-item @click=${this._handlerPicked} .handler=${handler}>
<paper-item-body>
${this.hass.localize(`component.${handler}.config.title`)}
</paper-item-body>
<ha-icon-next></ha-icon-next>
</paper-item>
`
)}
</div>
`;
}

private async _handlerPicked(ev) {
fireEvent(this, "flow-update", {
stepPromise: createConfigFlow(this.hass, ev.currentTarget.handler),
});
}

static get styles(): CSSResult {
return css`
h2 {
padding-left: 16px;
}
div {
overflow: auto;
max-height: 600px;
}
paper-item {
cursor: pointer;
}
`;
}
}

declare global {
interface HTMLElementTagNameMap {
"step-flow-pick-handler": StepFlowPickHandler;
}
}
Loading