Move picking new integration into dialog (#3110)

This commit is contained in:
Paulus Schoutsen 2019-04-24 12:51:41 -07:00 committed by GitHub
parent ff2f573dd0
commit 2f36304f06
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 199 additions and 50 deletions

View File

@ -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;
@ -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");

View File

@ -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";
@ -39,6 +40,7 @@ import {
fetchDeviceRegistry,
} from "../../data/device_registry";
import { AreaRegistryEntry, fetchAreaRegistry } from "../../data/area_registry";
import { HomeAssistant } from "../../types";
let instance = 0;
@ -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;
@ -62,7 +67,11 @@ 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[];
@ -70,25 +79,35 @@ class ConfigFlowDialog extends LitElement {
@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;
// Create a new config flow. Show picker
if (!params.continueFlowId) {
this._step = null;
if (!fetchStep) {
throw new Error(`Pass in either continueFlowId or newFlorForHandler`);
// 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) {
@ -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 {
@ -113,7 +132,7 @@ 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>
`
@ -121,18 +140,26 @@ class ConfigFlowDialog extends LitElement {
? // 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
@ -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>
@ -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);
});
}
@ -170,6 +198,10 @@ class ConfigFlowDialog extends LitElement {
}
}
private _scheduleCenterDialog() {
setTimeout(() => this._dialog.center(), 0);
}
private get _dialog(): HaPaperDialog {
return this.shadowRoot!.querySelector("ha-paper-dialog")!;
}
@ -177,17 +209,29 @@ class ConfigFlowDialog extends LitElement {
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;
@ -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({
@ -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;
}
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -1,6 +1,7 @@
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import "@polymer/paper-tooltip/paper-tooltip";
import "@material/mwc-button";
import "@polymer/paper-fab/paper-fab";
import "@polymer/paper-card/paper-card";
import "@polymer/iron-icon/iron-icon";
import "@polymer/paper-item/paper-item";
@ -13,6 +14,7 @@ import "../../../layouts/hass-subpage";
import "../../../resources/ha-style";
import "../../../components/ha-icon-next";
import { computeRTL } from "../../../common/util/compute_rtl";
import "../ha-config-section";
import EventsMixin from "../../../mixins/events-mixin";
import LocalizeMixin from "../../../mixins/localize-mixin";
@ -50,6 +52,28 @@ class HaConfigManagerDashboard extends LocalizeMixin(
color: var(--primary-text-color);
text-decoration: none;
}
paper-fab {
position: fixed;
bottom: 16px;
right: 16px;
z-index: 1;
}
paper-fab[is-wide] {
bottom: 24px;
right: 24px;
}
paper-fab[rtl] {
right: auto;
left: 16px;
}
paper-fab[rtl][is-wide] {
bottom: 24px;
right: auto;
left: 24px;
}
</style>
<hass-subpage
@ -119,23 +143,13 @@ class HaConfigManagerDashboard extends LocalizeMixin(
</paper-card>
</ha-config-section>
<ha-config-section>
<span slot="header"
>[[localize('ui.panel.config.integrations.new')]]</span
>
<paper-card>
<template is="dom-repeat" items="[[handlers]]">
<div class="config-entry-row">
<paper-item-body>
[[_computeIntegrationTitle(localize, item)]]
</paper-item-body>
<mwc-button on-click="_createFlow"
>[[localize('ui.panel.config.integrations.configure')]]</mwc-button
>
</div>
</template>
</paper-card>
</ha-config-section>
<paper-fab
icon="hass:plus"
title="[[localize('ui.panel.config.integrations.new')]]"
on-click="_createFlow"
is-wide$="[[isWide]]"
rtl$="[[rtl]]"
></paper-fab>
</hass-subpage>
`;
}
@ -162,6 +176,12 @@ class HaConfigManagerDashboard extends LocalizeMixin(
progress: Array,
handlers: Array,
rtl: {
type: Boolean,
reflectToAttribute: true,
computed: "_computeRTL(hass)",
},
};
}
@ -170,17 +190,14 @@ class HaConfigManagerDashboard extends LocalizeMixin(
loadConfigFlowDialog();
}
_createFlow(ev) {
_createFlow() {
showConfigFlowDialog(this, {
hass: this.hass,
newFlowForHandler: ev.model.item,
dialogClosedCallback: () => this.fire("hass-reload-entries"),
});
}
_continueFlow(ev) {
showConfigFlowDialog(this, {
hass: this.hass,
continueFlowId: ev.model.item.flow_id,
dialogClosedCallback: () => this.fire("hass-reload-entries"),
});
@ -230,6 +247,10 @@ class HaConfigManagerDashboard extends LocalizeMixin(
_handleMoreInfo(ev) {
this.fire("hass-more-info", { entityId: ev.model.item.entity_id });
}
_computeRTL(hass) {
return computeRTL(hass);
}
}
customElements.define("ha-config-entries-dashboard", HaConfigManagerDashboard);