1
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:
Paulus Schoutsen 2018-06-18 09:53:37 -04:00 committed by GitHub
parent 4e608e6a2c
commit dd87502688
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 224 additions and 116 deletions

View File

@ -35,7 +35,13 @@ class HaForm extends EventsMixin(PolymerElement) {
</template>
<template is="dom-if" if="[[_equals(schema.type, &quot;string&quot;)]]" 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, &quot;integer&quot;)]]" 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, &quot;float&quot;)]]" 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, &quot;boolean&quot;)]]" restamp="">

View 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));
});
});
};

View File

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

View File

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

View File

@ -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, &quot;abort&quot;)]]">
<template is="dom-if" if="[[_equals(_step.type, 'abort')]]">
Aborted
</template>
<template is="dom-if" if="[[_equals(step.type, &quot;create_entry&quot;)]]">
<template is="dom-if" if="[[_equals(_step.type, 'create_entry')]]">
Success!
</template>
<template is="dom-if" if="[[_equals(step.type, &quot;form&quot;)]]">
[[_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, &quot;abort&quot;)]]">
<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, &quot;create_entry&quot;)]]">
<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, &quot;form&quot;)]]">
<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, &quot;abort&quot;)]]">
<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, &quot;create_entry&quot;)]]">
<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, &quot;form&quot;)]]">
<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();
}
}

View File

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