mirror of
https://github.com/home-assistant/frontend
synced 2024-09-12 05:34:56 +02:00
Fix config entries (#1293)
* Fix config entries * Reset error msg when flow is closed
This commit is contained in:
parent
4e608e6a2c
commit
dd87502688
@ -35,7 +35,13 @@ class HaForm extends EventsMixin(PolymerElement) {
|
||||
</template>
|
||||
|
||||
<template is="dom-if" if="[[_equals(schema.type, "string")]]" restamp="">
|
||||
<paper-input label="[[computeLabel(schema)]]" value="{{data}}"></paper-input>
|
||||
<paper-input
|
||||
label="[[computeLabel(schema)]]"
|
||||
value="{{data}}"
|
||||
required="[[schema.required]]"
|
||||
auto-validate="[[schema.required]]"
|
||||
error-message='Required'
|
||||
></paper-input>
|
||||
</template>
|
||||
|
||||
<template is="dom-if" if="[[_equals(schema.type, "integer")]]" restamp="">
|
||||
@ -46,13 +52,26 @@ class HaForm extends EventsMixin(PolymerElement) {
|
||||
</div>
|
||||
</template>
|
||||
<template is="dom-if" if="[[!_isRange(schema)]]" restamp="">
|
||||
<paper-input label="[[computeLabel(schema)]]" value="{{data}}" type="number"></paper-input>
|
||||
<paper-input
|
||||
label="[[computeLabel(schema)]]"
|
||||
value="{{data}}"
|
||||
type="number"
|
||||
required="[[schema.required]]"
|
||||
auto-validate="[[schema.required]]"
|
||||
error-message='Required'
|
||||
></paper-input>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template is="dom-if" if="[[_equals(schema.type, "float")]]" restamp="">
|
||||
<!--TODO-->
|
||||
<paper-input label="[[computeLabel(schema)]]" value="{{data}}"></paper-input>
|
||||
<paper-input
|
||||
label="[[computeLabel(schema)]]"
|
||||
value="{{data}}"
|
||||
required="[[schema.required]]"
|
||||
auto-validate="[[schema.required]]"
|
||||
error-message='Required'
|
||||
></paper-input>
|
||||
</template>
|
||||
|
||||
<template is="dom-if" if="[[_equals(schema.type, "boolean")]]" restamp="">
|
||||
|
23
src/dialogs/dialog-manager.js
Normal file
23
src/dialogs/dialog-manager.js
Normal file
@ -0,0 +1,23 @@
|
||||
// Allows registering dialogs and makes sure they are appended to the root element.
|
||||
export default (root) => {
|
||||
root.addEventListener('register-dialog', (regEv) => {
|
||||
let loaded = null;
|
||||
|
||||
const {
|
||||
dialogShowEvent,
|
||||
dialogTag,
|
||||
dialogImport,
|
||||
} = regEv.detail;
|
||||
|
||||
root.addEventListener(dialogShowEvent, (showEv) => {
|
||||
if (!loaded) {
|
||||
loaded = dialogImport().then(() => {
|
||||
const dialogEl = document.createElement(dialogTag);
|
||||
root.shadowRoot.appendChild(dialogEl);
|
||||
return dialogEl;
|
||||
});
|
||||
}
|
||||
loaded.then(dialogEl => dialogEl.showDialog(showEv.detail));
|
||||
});
|
||||
});
|
||||
};
|
@ -25,6 +25,7 @@ import { getActiveTranslation, getTranslation } from '../util/hass-translation.j
|
||||
import '../util/legacy-support';
|
||||
import '../util/roboto.js';
|
||||
import hassCallApi from '../util/hass-call-api.js';
|
||||
import makeDialogManager from '../dialogs/dialog-manager.js';
|
||||
|
||||
import computeStateName from '../common/entity/compute_state_name.js';
|
||||
import applyThemesOnElement from '../common/dom/apply_themes_on_element.js';
|
||||
@ -96,6 +97,11 @@ class HomeAssistant extends LocalizeMixin(PolymerElement) {
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
makeDialogManager(this);
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
this.addEventListener('settheme', e => this.setTheme(e));
|
||||
|
@ -4,15 +4,18 @@ import '@polymer/paper-card/paper-card.js';
|
||||
import '@polymer/paper-item/paper-item-body.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import { Debouncer } from '@polymer/polymer/lib/utils/debounce.js';
|
||||
import { timeOut } from '@polymer/polymer/lib/utils/async.js';
|
||||
|
||||
import '../../../layouts/hass-subpage.js';
|
||||
import '../../../resources/ha-style.js';
|
||||
|
||||
import '../ha-config-section.js';
|
||||
import './ha-config-flow.js';
|
||||
import EventsMixin from '../../../mixins/events-mixin.js';
|
||||
import LocalizeMixin from '../../../mixins/localize-mixin.js';
|
||||
|
||||
let registeredDialog = false;
|
||||
|
||||
/*
|
||||
* @appliesMixin LocalizeMixin
|
||||
* @appliesMixin EventsMixin
|
||||
@ -39,7 +42,7 @@ class HaConfigManager extends
|
||||
|
||||
<hass-subpage header="Integrations">
|
||||
<template is="dom-if" if="[[_progress.length]]">
|
||||
<ha-config-section is-wide="[[isWide]]">
|
||||
<ha-config-section>
|
||||
<span slot="header">Discovered</span>
|
||||
<paper-card>
|
||||
<template is="dom-repeat" items="[[_progress]]">
|
||||
@ -54,23 +57,21 @@ class HaConfigManager extends
|
||||
</ha-config-section>
|
||||
</template>
|
||||
|
||||
<ha-config-section is-wide="[[isWide]]">
|
||||
<ha-config-section>
|
||||
<span slot="header">Configured</span>
|
||||
<paper-card>
|
||||
<template is="dom-if" if="[[!_entries.length]]">
|
||||
<div class="config-entry-row">
|
||||
<paper-item-body>
|
||||
Nothing configured yet
|
||||
<paper-item-body two-line>
|
||||
<div>Nothing configured yet</div>
|
||||
</paper-item-body>
|
||||
</div>
|
||||
</template>
|
||||
<template is="dom-repeat" items="[[_entries]]">
|
||||
<div class="config-entry-row">
|
||||
<paper-item-body three-line="">
|
||||
[[item.title]]
|
||||
<div secondary="">Integration: [[_computeIntegrationTitle(localize, item.domain)]]</div>
|
||||
<div secondary="">Added by: [[item.source]]</div>
|
||||
<div secondary="">State: [[item.state]]</div>
|
||||
<paper-item-body two-line>
|
||||
<div>[[_computeIntegrationTitle(localize, item.domain)]]: [[item.title]]</div>
|
||||
<div secondary>[[item.state]] – added by [[item.source]]</div>
|
||||
</paper-item-body>
|
||||
<paper-button on-click="_removeEntry">Remove</paper-button>
|
||||
</div>
|
||||
@ -78,7 +79,7 @@ class HaConfigManager extends
|
||||
</paper-card>
|
||||
</ha-config-section>
|
||||
|
||||
<ha-config-section is-wide="[[isWide]]">
|
||||
<ha-config-section>
|
||||
<span slot="header">Set up a new integration</span>
|
||||
<paper-card>
|
||||
<template is="dom-repeat" items="[[_handlers]]">
|
||||
@ -92,8 +93,6 @@ class HaConfigManager extends
|
||||
</paper-card>
|
||||
</ha-config-section>
|
||||
</hass-subpage>
|
||||
|
||||
<ha-config-flow hass="[[hass]]" flow-id="[[_flowId]]" step="{{_flowStep}}" on-flow-closed="_flowClose"></ha-config-flow>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -102,15 +101,6 @@ class HaConfigManager extends
|
||||
hass: Object,
|
||||
isWide: Boolean,
|
||||
|
||||
_flowId: {
|
||||
type: String,
|
||||
value: null,
|
||||
},
|
||||
/*
|
||||
* The step of the current selected flow, if available.
|
||||
*/
|
||||
_flowStep: Object,
|
||||
|
||||
/**
|
||||
* Existing entries.
|
||||
*/
|
||||
@ -131,22 +121,45 @@ class HaConfigManager extends
|
||||
this._loadData();
|
||||
}
|
||||
|
||||
_createFlow(ev) {
|
||||
this.hass.callApi('post', 'config/config_entries/flow', { handler: ev.model.item })
|
||||
.then((flow) => {
|
||||
this._userCreatedFlow = true;
|
||||
this.setProperties({
|
||||
_flowStep: flow,
|
||||
_flowId: flow.flow_id,
|
||||
});
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
if (!registeredDialog) {
|
||||
registeredDialog = true;
|
||||
this.fire('register-dialog', {
|
||||
dialogShowEvent: 'show-config-flow',
|
||||
dialogTag: 'ha-config-flow',
|
||||
dialogImport: () => import('./ha-config-flow.js'),
|
||||
});
|
||||
}
|
||||
|
||||
this.hass.connection.subscribeEvents(() => {
|
||||
this._debouncer = Debouncer.debounce(
|
||||
this._debouncer,
|
||||
timeOut.after(500),
|
||||
() => this._loadData()
|
||||
);
|
||||
}, 'config_entry_discovered').then((unsub) => { this._unsubEvents = unsub; });
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
if (this._unsubEvents) this._unsubEvents();
|
||||
}
|
||||
|
||||
_createFlow(ev) {
|
||||
this.fire('show-config-flow', {
|
||||
hass: this.hass,
|
||||
newFlowForHandler: ev.model.item,
|
||||
dialogClosedCallback: () => this._loadData(),
|
||||
});
|
||||
}
|
||||
|
||||
_continueFlow(ev) {
|
||||
this._userCreatedFlow = false;
|
||||
this.setProperties({
|
||||
_flowId: ev.model.item.flow_id,
|
||||
_flowStep: null,
|
||||
this.fire('show-config-flow', {
|
||||
hass: this.hass,
|
||||
continueFlowId: ev.model.item.flow_id,
|
||||
dialogClosedCallback: () => this._loadData(),
|
||||
});
|
||||
}
|
||||
|
||||
@ -164,19 +177,6 @@ class HaConfigManager extends
|
||||
});
|
||||
}
|
||||
|
||||
_flowClose(ev) {
|
||||
// Was the flow completed?
|
||||
if (ev.detail.flowFinished) {
|
||||
this._loadData();
|
||||
|
||||
// Remove a flow if it was not finished and was started by the user
|
||||
} else if (this._userCreatedFlow) {
|
||||
this.hass.callApi('delete', `config/config_entries/flow/${this._flowId}`);
|
||||
}
|
||||
|
||||
this._flowId = null;
|
||||
}
|
||||
|
||||
_loadData() {
|
||||
this._loadEntries();
|
||||
this._loadDiscovery();
|
||||
|
@ -1,6 +1,7 @@
|
||||
import '@polymer/paper-button/paper-button.js';
|
||||
import '@polymer/paper-dialog-scrollable/paper-dialog-scrollable.js';
|
||||
import '@polymer/paper-dialog/paper-dialog.js';
|
||||
import '@polymer/paper-spinner/paper-spinner.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
@ -11,6 +12,8 @@ import '../../../resources/ha-style.js';
|
||||
import EventsMixin from '../../../mixins/events-mixin.js';
|
||||
import LocalizeMixin from '../../../mixins/localize-mixin.js';
|
||||
|
||||
let instance = 0;
|
||||
|
||||
/*
|
||||
* @appliesMixin LocalizeMixin
|
||||
* @appliesMixin EventsMixin
|
||||
@ -30,50 +33,71 @@ class HaConfigFlow extends
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.init-spinner {
|
||||
padding: 10px 100px 34px;
|
||||
text-align: center;
|
||||
}
|
||||
.submit-spinner {
|
||||
margin-right: 16px;
|
||||
}
|
||||
</style>
|
||||
<paper-dialog id="dialog" with-backdrop="" opened="[[step]]" on-opened-changed="_openedChanged">
|
||||
<paper-dialog id="dialog" with-backdrop="" opened="{{_opened}}" on-opened-changed="_openedChanged">
|
||||
<h2>
|
||||
<template is="dom-if" if="[[_equals(step.type, "abort")]]">
|
||||
<template is="dom-if" if="[[_equals(_step.type, 'abort')]]">
|
||||
Aborted
|
||||
</template>
|
||||
<template is="dom-if" if="[[_equals(step.type, "create_entry")]]">
|
||||
<template is="dom-if" if="[[_equals(_step.type, 'create_entry')]]">
|
||||
Success!
|
||||
</template>
|
||||
<template is="dom-if" if="[[_equals(step.type, "form")]]">
|
||||
[[_computeStepTitle(localize, step)]]
|
||||
<template is="dom-if" if="[[_equals(_step.type, 'form')]]">
|
||||
[[_computeStepTitle(localize, _step)]]
|
||||
</template>
|
||||
</h2>
|
||||
<paper-dialog-scrollable>
|
||||
<template is="dom-if" if="[[!step]]">
|
||||
Loading flow.
|
||||
<template is="dom-if" if="[[_errorMsg]]">
|
||||
<div class='error'>[[_errorMsg]]</div>
|
||||
</template>
|
||||
<template is="dom-if" if="[[step]]">
|
||||
<template is="dom-if" if="[[_equals(step.type, "abort")]]">
|
||||
<ha-markdown content="[[_computeStepAbortedReason(localize, step)]]"></ha-markdown>
|
||||
<template is="dom-if" if="[[!_step]]">
|
||||
<div class='init-spinner'><paper-spinner active></paper-spinner></div>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_step]]">
|
||||
<template is="dom-if" if="[[_equals(_step.type, 'abort')]]">
|
||||
<ha-markdown content="[[_computeStepAbortedReason(localize, _step)]]"></ha-markdown>
|
||||
</template>
|
||||
|
||||
<template is="dom-if" if="[[_equals(step.type, "create_entry")]]">
|
||||
<p>Created config for [[step.title]]</p>
|
||||
<template is="dom-if" if="[[_equals(_step.type, 'create_entry')]]">
|
||||
<p>Created config for [[_step.title]]</p>
|
||||
</template>
|
||||
|
||||
<template is="dom-if" if="[[_equals(step.type, "form")]]">
|
||||
<template is="dom-if" if="[[_computeStepDescription(localize, step)]]">
|
||||
<ha-markdown content="[[_computeStepDescription(localize, step)]]"></ha-markdown>
|
||||
<template is="dom-if" if="[[_equals(_step.type, 'form')]]">
|
||||
<template is="dom-if" if="[[_computeStepDescription(localize, _step)]]">
|
||||
<ha-markdown content="[[_computeStepDescription(localize, _step)]]"></ha-markdown>
|
||||
</template>
|
||||
|
||||
<ha-form data="{{stepData}}" schema="[[step.data_schema]]" error="[[step.errors]]" compute-label="[[_computeLabelCallback(localize, step)]]" compute-error="[[_computeErrorCallback(localize, step)]]"></ha-form>
|
||||
<ha-form
|
||||
data="{{_stepData}}"
|
||||
schema="[[_step.data_schema]]"
|
||||
error="[[_step.errors]]"
|
||||
compute-label="[[_computeLabelCallback(localize, _step)]]"
|
||||
compute-error="[[_computeErrorCallback(localize, _step)]]"
|
||||
></ha-form>
|
||||
</template>
|
||||
</template>
|
||||
</paper-dialog-scrollable>
|
||||
<div class="buttons">
|
||||
<template is="dom-if" if="[[_equals(step.type, "abort")]]">
|
||||
<template is="dom-if" if="[[_equals(_step.type, 'abort')]]">
|
||||
<paper-button on-click="_flowDone">Close</paper-button>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_equals(step.type, "create_entry")]]">
|
||||
<template is="dom-if" if="[[_equals(_step.type, 'create_entry')]]">
|
||||
<paper-button on-click="_flowDone">Close</paper-button>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_equals(step.type, "form")]]">
|
||||
<paper-button on-click="_submitStep">Submit</paper-button>
|
||||
<template is="dom-if" if="[[_equals(_step.type, 'form')]]">
|
||||
<template is="dom-if" if="[[_loading]]">
|
||||
<div class='submit-spinner'><paper-spinner active></paper-spinner></div>
|
||||
</template>
|
||||
<template is="dom-if" if="[[!_loading]]">
|
||||
<paper-button on-click="_submitStep">Submit</paper-button>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</paper-dialog>
|
||||
@ -82,19 +106,32 @@ class HaConfigFlow extends
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
step: {
|
||||
_hass: Object,
|
||||
_dialogClosedCallback: Function,
|
||||
_instance: Number,
|
||||
|
||||
_loading: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
// Error message when can't talk to server etc
|
||||
_errorMsg: String,
|
||||
|
||||
_opened: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
_step: {
|
||||
type: Object,
|
||||
notify: true,
|
||||
},
|
||||
flowId: {
|
||||
type: String,
|
||||
observer: '_flowIdChanged'
|
||||
value: null,
|
||||
},
|
||||
|
||||
/*
|
||||
* Store user entered data.
|
||||
*/
|
||||
stepData: Object,
|
||||
_stepData: Object,
|
||||
};
|
||||
}
|
||||
|
||||
@ -105,55 +142,79 @@ class HaConfigFlow extends
|
||||
this._submitStep();
|
||||
}
|
||||
});
|
||||
// Fix for overlay showing on top of dialog.
|
||||
this.$.dialog.addEventListener('iron-overlay-opened', (ev) => {
|
||||
if (ev.target.withBackdrop) {
|
||||
ev.target.parentNode.insertBefore(ev.target.backdropElement, ev.target);
|
||||
}
|
||||
}
|
||||
|
||||
showDialog({ hass, continueFlowId, newFlowForHandler, dialogClosedCallback }) {
|
||||
this.hass = hass;
|
||||
this._instance = instance++;
|
||||
this._dialogClosedCallback = dialogClosedCallback;
|
||||
this._createdFromHandler = !!newFlowForHandler;
|
||||
this._loading = true;
|
||||
this._opened = true;
|
||||
|
||||
const fetchStep = continueFlowId ?
|
||||
this.hass.callApi('get', `config/config_entries/flow/${continueFlowId}`) :
|
||||
this.hass.callApi('post', 'config/config_entries/flow', { handler: newFlowForHandler });
|
||||
|
||||
const curInstance = this._instance;
|
||||
|
||||
fetchStep.then((step) => {
|
||||
if (curInstance !== this._instance) return;
|
||||
|
||||
this._processStep(step);
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
_flowIdChanged(flowId) {
|
||||
if (!flowId) {
|
||||
this.setProperties({
|
||||
step: null,
|
||||
stepData: {},
|
||||
});
|
||||
return;
|
||||
|
||||
// Check if parent passed in step data to use.
|
||||
} else if (this.step) {
|
||||
this._processStep(this.step);
|
||||
return;
|
||||
}
|
||||
|
||||
this.hass.callApi('get', `config/config_entries/flow/${flowId}`)
|
||||
.then((step) => {
|
||||
this._processStep(step);
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
|
||||
_submitStep() {
|
||||
this.hass.callApi('post', `config/config_entries/flow/${this.flowId}`, this.stepData)
|
||||
.then(step => this._processStep(step));
|
||||
this._loading = true;
|
||||
this._errorMsg = null;
|
||||
|
||||
const curInstance = this._instance;
|
||||
|
||||
this.hass.callApi('post', `config/config_entries/flow/${this._step.flow_id}`, this._stepData)
|
||||
.then(
|
||||
(step) => {
|
||||
if (curInstance !== this._instance) return;
|
||||
|
||||
this._processStep(step);
|
||||
this._loading = false;
|
||||
},
|
||||
(err) => {
|
||||
this._errorMsg = (err && err.body && err.body.message) || 'Unknown error occurred';
|
||||
this._loading = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_processStep(step) {
|
||||
if (!step.errors) step.errors = {};
|
||||
this.step = step;
|
||||
this._step = step;
|
||||
// We got a new form if there are no errors.
|
||||
if (Object.keys(step.errors).length === 0) {
|
||||
this.stepData = {};
|
||||
this._stepData = {};
|
||||
}
|
||||
}
|
||||
|
||||
_flowDone() {
|
||||
this.fire('flow-closed', {
|
||||
flowFinished: true
|
||||
this._opened = false;
|
||||
const flowFinished = this._step && ['success', 'abort'].includes(this._step.type);
|
||||
|
||||
if (this._step && !flowFinished && this._createdFromHandler) {
|
||||
this.hass.callApi('delete', `config/config_entries/flow/${this._step.flow_id}`);
|
||||
}
|
||||
|
||||
this._dialogClosedCallback({
|
||||
flowFinished,
|
||||
});
|
||||
|
||||
this._errorMsg = null;
|
||||
this._step = null;
|
||||
this._stepData = {};
|
||||
this._dialogClosedCallback = null;
|
||||
}
|
||||
|
||||
_equals(a, b) {
|
||||
@ -162,10 +223,8 @@ class HaConfigFlow extends
|
||||
|
||||
_openedChanged(ev) {
|
||||
// Closed dialog by clicking on the overlay
|
||||
if (this.step && !ev.detail.value) {
|
||||
this.fire('flow-closed', {
|
||||
flowFinished: ['success', 'abort'].includes(this.step.type)
|
||||
});
|
||||
if (this._step && !ev.detail.value) {
|
||||
this._flowDone();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,6 +48,7 @@ function createConfig(isProdBuild, latestBuild) {
|
||||
__BUILD__: JSON.stringify(latestBuild ? 'latest' : 'es5'),
|
||||
__VERSION__: JSON.stringify(VERSION),
|
||||
__PUBLIC_PATH__: JSON.stringify(publicPath),
|
||||
'process.env.NODE_ENV': JSON.stringify(isProdBuild ? 'production' : 'development'),
|
||||
}),
|
||||
new CopyWebpackPlugin(copyPluginOpts),
|
||||
// Ignore moment.js locales
|
||||
|
Loading…
Reference in New Issue
Block a user