mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 01:36:49 +00:00
Move picking new integration into dialog (#3110)
This commit is contained in:
parent
ff2f573dd0
commit
2f36304f06
@ -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");
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
67
src/dialogs/config-flow/step-flow-pick-handler.ts
Normal file
67
src/dialogs/config-flow/step-flow-pick-handler.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user