Add automation editor (#275)
* Add automation editor * Build JS before running tests * Add browser warning * Re-order from/to in state
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
import { h, render } from 'preact';
|
||||
import Automation from '../../preact-src/automation';
|
||||
|
||||
window.AutomationEditor = function (mountEl, props, mergeEl) {
|
||||
return render(h(Automation, props), mountEl, mergeEl);
|
||||
};
|
||||
|
||||
window.unmountPreact = function (mountEl, mergeEl) {
|
||||
render(() => null, mountEl, mergeEl);
|
||||
};
|
||||
@@ -0,0 +1,214 @@
|
||||
<link rel="import" href="../../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../../bower_components/app-layout/app-header-layout/app-header-layout.html">
|
||||
<link rel="import" href="../../bower_components/app-layout/app-header/app-header.html">
|
||||
<link rel="import" href="../../bower_components/app-layout/app-toolbar/app-toolbar.html">
|
||||
<link rel="import" href="../../bower_components/paper-card/paper-card.html">
|
||||
<link rel="import" href="../../bower_components/paper-item/paper-item.html">
|
||||
<link rel="import" href="../../bower_components/paper-item/paper-item-body.html">
|
||||
<link rel="import" href="../../bower_components/paper-icon-button/paper-icon-button.html">
|
||||
<link rel="import" href="../../bower_components/paper-input/paper-input.html">
|
||||
<link rel="import" href="../../bower_components/paper-dropdown-menu/paper-dropdown-menu-light.html">
|
||||
<link rel="import" href="../../bower_components/paper-listbox/paper-listbox.html">
|
||||
<link rel="import" href="../../bower_components/paper-menu-button/paper-menu-button.html">
|
||||
|
||||
<link rel="import" href="../config/ha-config-section.html">
|
||||
|
||||
<script src='../../build/editor.js'></script>
|
||||
|
||||
<dom-module id="ha-automation-editor">
|
||||
<template>
|
||||
<style include="ha-style">
|
||||
.errors {
|
||||
padding: 20px;
|
||||
font-weight: bold;
|
||||
color: var(--google-red-500);
|
||||
}
|
||||
.content {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
paper-card {
|
||||
display: block;
|
||||
}
|
||||
.triggers,
|
||||
.script {
|
||||
margin-top: -16px;
|
||||
}
|
||||
.triggers paper-card,
|
||||
.script paper-card {
|
||||
margin-top: 16px;
|
||||
}
|
||||
.add-card paper-button {
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
.card-menu {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.card-menu paper-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
span[slot=introduction] a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
<app-header-layout has-scrolling-region>
|
||||
<app-header fixed>
|
||||
<app-toolbar>
|
||||
<paper-icon-button
|
||||
icon='mdi:arrow-left'
|
||||
on-tap='backTapped'
|
||||
></paper-icon-button>
|
||||
<div main-title>Automation [[name]]</div>
|
||||
<paper-icon-button
|
||||
icon='mdi:content-save'
|
||||
on-tap='saveAutomation'
|
||||
disabled='[[!dirty]]'
|
||||
></paper-icon-button>
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
|
||||
<div class='content'>
|
||||
<template is='dom-if' if='[[errors]]'>
|
||||
<div class='errors'>[[errors]]</div>
|
||||
</template>
|
||||
<div id='root'></div>
|
||||
</div>
|
||||
</app-header-layout>
|
||||
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
Polymer({
|
||||
is: 'ha-automation-editor',
|
||||
|
||||
properties: {
|
||||
hass: {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
narrow: {
|
||||
type: Boolean,
|
||||
},
|
||||
|
||||
showMenu: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
errors: {
|
||||
type: Object,
|
||||
value: null,
|
||||
},
|
||||
|
||||
dirty: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
config: {
|
||||
type: Object,
|
||||
value: null,
|
||||
},
|
||||
|
||||
automation: {
|
||||
type: Object,
|
||||
observer: 'automationChanged',
|
||||
},
|
||||
|
||||
name: {
|
||||
type: String,
|
||||
computed: 'computeName(automation)'
|
||||
},
|
||||
|
||||
isWide: {
|
||||
type: Boolean,
|
||||
observer: 'isWideChanged',
|
||||
},
|
||||
},
|
||||
|
||||
attached: function () {
|
||||
this.configChanged = this.configChanged.bind(this);
|
||||
},
|
||||
|
||||
detached: function () {
|
||||
if (this._rendered) {
|
||||
window.unmountPreact(this._rendered);
|
||||
}
|
||||
},
|
||||
|
||||
configChanged: function (config) {
|
||||
this.config = config;
|
||||
this.errors = null;
|
||||
this.dirty = true;
|
||||
this._updateComponent(config);
|
||||
},
|
||||
|
||||
automationChanged: function (newVal, oldVal) {
|
||||
if (!newVal) return;
|
||||
if (!this.hass) {
|
||||
setTimeout(this.automationChanged.bind(this, newVal, oldVal), 0);
|
||||
return;
|
||||
}
|
||||
if (oldVal && oldVal.attributes.id === newVal.attributes.id) {
|
||||
return;
|
||||
}
|
||||
this.hass.callApi('get', 'config/automation/config/' + newVal.attributes.id)
|
||||
.then(function (config) {
|
||||
// Normalize data: ensure trigger, action and condition are lists
|
||||
// Happens when people copy paste their automations into the config
|
||||
['trigger', 'condition', 'action'].forEach(function (key) {
|
||||
if (!Array.isArray(config[key])) {
|
||||
config[key] = [config[key]];
|
||||
}
|
||||
});
|
||||
this.dirty = false;
|
||||
this.config = config;
|
||||
this._updateComponent();
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
isWideChanged: function () {
|
||||
if (this.config === null) return;
|
||||
this._updateComponent();
|
||||
},
|
||||
|
||||
backTapped: function () {
|
||||
if (this.dirty &&
|
||||
// eslint-disable-next-line
|
||||
!confirm('You have unsaved changes. Are you sure you want to leave?')) {
|
||||
return;
|
||||
}
|
||||
this.fire('hass-automation-picked', { id: null });
|
||||
},
|
||||
|
||||
_updateComponent: function () {
|
||||
this._rendered = window.AutomationEditor(
|
||||
this.$.root, {
|
||||
automation: this.config,
|
||||
onChange: this.configChanged,
|
||||
isWide: this.isWide,
|
||||
}, this._rendered);
|
||||
},
|
||||
|
||||
saveAutomation: function () {
|
||||
this.hass.callApi(
|
||||
'post', 'config/automation/config/' + this.automation.attributes.id,
|
||||
this.config).then(function () {
|
||||
this.dirty = false;
|
||||
}.bind(this), function (errors) {
|
||||
this.errors = errors.body.message;
|
||||
throw errors;
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
computeName: function (automation) {
|
||||
return automation && window.hassUtil.computeStateName(automation);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,116 @@
|
||||
<link rel="import" href="../../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../../bower_components/app-layout/app-header-layout/app-header-layout.html">
|
||||
<link rel="import" href="../../bower_components/app-layout/app-header/app-header.html">
|
||||
<link rel="import" href="../../bower_components/app-layout/app-toolbar/app-toolbar.html">
|
||||
<link rel="import" href="../../bower_components/paper-card/paper-card.html">
|
||||
<link rel="import" href="../../bower_components/paper-item/paper-item.html">
|
||||
<link rel="import" href="../../bower_components/paper-item/paper-item-body.html">
|
||||
|
||||
<dom-module id="ha-automation-picker">
|
||||
<template>
|
||||
<style include="ha-style">
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
paper-card {
|
||||
display: block;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.content > paper-card:first-child {
|
||||
margin-bottom: 16px;
|
||||
color: var(--google-red-500);
|
||||
}
|
||||
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
<app-header-layout has-scrolling-region>
|
||||
<app-header fixed>
|
||||
<app-toolbar>
|
||||
<ha-menu-button narrow='[[narrow]]' show-menu='[[showMenu]]'></ha-menu-button>
|
||||
<div main-title>Automations Editor</div>
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
|
||||
<div class='content'>
|
||||
<paper-card>
|
||||
<div class='card-content'>
|
||||
Currently Chrome is the only supported browser.
|
||||
</div>
|
||||
</paper-card>
|
||||
|
||||
<paper-card heading='Pick automation to edit'>
|
||||
<template is='dom-if' if='[[!automations.length]]'>
|
||||
<div class='card-content'>
|
||||
We couldn't find any editable automations.
|
||||
</div>
|
||||
</template>
|
||||
<template is='dom-repeat' items='[[automations]]' as='automation'>
|
||||
<paper-item>
|
||||
<paper-item-body two-line on-tap='automationTapped'>
|
||||
<div>[[computeName(automation)]]</div>
|
||||
<div secondary>[[computeDescription(automation)]]</div>
|
||||
</paper-item-body>
|
||||
[[computeStatus(automation)]]
|
||||
</paper-item>
|
||||
</template>
|
||||
</paper-card>
|
||||
</div>
|
||||
</app-header-layout>
|
||||
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
Polymer({
|
||||
is: 'ha-automation-picker',
|
||||
|
||||
properties: {
|
||||
hass: {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
narrow: {
|
||||
type: Boolean,
|
||||
},
|
||||
|
||||
showMenu: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
automations: {
|
||||
type: Array,
|
||||
},
|
||||
},
|
||||
|
||||
automationTapped: function (ev) {
|
||||
this.fire('hass-automation-picked', {
|
||||
id: this.automations[ev.model.index].attributes.id,
|
||||
});
|
||||
},
|
||||
|
||||
computeName: function (automation) {
|
||||
return window.hassUtil.computeStateName(automation);
|
||||
},
|
||||
|
||||
// Still thinking of something to add here.
|
||||
// eslint-disable-next-line
|
||||
computeDescription: function (automation) {
|
||||
return '';
|
||||
},
|
||||
|
||||
computeStatus: function (automation) {
|
||||
return automation.state;
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,133 @@
|
||||
<link rel="import" href="../../bower_components/polymer/polymer.html">
|
||||
<link rel='import' href='../../bower_components/iron-media-query/iron-media-query.html'>
|
||||
|
||||
<link rel="import" href="./ha-automation-picker.html">
|
||||
<link rel="import" href="./ha-automation-editor.html">
|
||||
|
||||
<dom-module id="ha-panel-automation">
|
||||
<template>
|
||||
<style>
|
||||
ha-automation-picker,
|
||||
ha-automation-editor {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<iron-media-query query="(min-width: 1040px)" query-matches="{{wide}}">
|
||||
</iron-media-query>
|
||||
<iron-media-query query="(min-width: 1296px)" query-matches="{{wideSidebar}}">
|
||||
</iron-media-query>
|
||||
|
||||
<template is='dom-if' if='[[!automation]]'>
|
||||
<ha-automation-picker
|
||||
automations='[[automations]]'
|
||||
></ha-automation-picker>
|
||||
</template>
|
||||
|
||||
<template is='dom-if' if='[[automation]]' restamp>
|
||||
<ha-automation-editor
|
||||
hass='[[hass]]'
|
||||
automation='[[automation]]'
|
||||
is-wide='[[isWide]]'
|
||||
></ha-automation-editor>
|
||||
</template>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
Polymer({
|
||||
is: 'ha-panel-automation',
|
||||
|
||||
properties: {
|
||||
hass: {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
narrow: {
|
||||
type: Boolean,
|
||||
},
|
||||
|
||||
showMenu: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
automations: {
|
||||
type: Array,
|
||||
computed: 'computeAutomations(hass)',
|
||||
},
|
||||
|
||||
automationId: {
|
||||
type: String,
|
||||
value: null,
|
||||
},
|
||||
|
||||
automation: {
|
||||
type: Object,
|
||||
computed: 'computeAutomation(automations, automationId)',
|
||||
},
|
||||
|
||||
wide: {
|
||||
type: Boolean,
|
||||
},
|
||||
|
||||
wideSidebar: {
|
||||
type: Boolean,
|
||||
},
|
||||
|
||||
isWide: {
|
||||
type: Boolean,
|
||||
computed: 'computeIsWide(showMenu, wideSidebar, wide)'
|
||||
},
|
||||
},
|
||||
|
||||
listeners: {
|
||||
'hass-automation-picked': 'automationPicked',
|
||||
},
|
||||
|
||||
computeIsWide: function (showMenu, wideSidebar, wide) {
|
||||
return showMenu ? wideSidebar : wide;
|
||||
},
|
||||
|
||||
computeAutomation: function (automations, automationId) {
|
||||
for (var i = 0; i < automations.length; i++) {
|
||||
if (automations[i].attributes.id === automationId) {
|
||||
return automations[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
computeAutomations: function (hass) {
|
||||
var automations = [];
|
||||
|
||||
Object.keys(hass.states).forEach(function (key) {
|
||||
var entity = hass.states[key];
|
||||
|
||||
if (window.hassUtil.computeDomain(entity) === 'automation' &&
|
||||
'id' in entity.attributes) {
|
||||
automations.push(entity);
|
||||
}
|
||||
});
|
||||
|
||||
return automations.sort(function entitySortBy(entityA, entityB) {
|
||||
var nameA = (entityA.attributes.alias ||
|
||||
entityA.entity_id).toLowerCase();
|
||||
var nameB = (entityB.attributes.alias ||
|
||||
entityB.entity_id).toLowerCase();
|
||||
|
||||
if (nameA < nameB) {
|
||||
return -1;
|
||||
}
|
||||
if (nameA > nameB) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
},
|
||||
|
||||
automationPicked: function (ev) {
|
||||
this.automationId = ev.detail.id;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -7,12 +7,12 @@
|
||||
<link rel="import" href="./hassio-data.html">
|
||||
|
||||
<dom-module id="ha-panel-hassio">
|
||||
<style>
|
||||
iron-pages {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<style>
|
||||
iron-pages {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
<hassio-data
|
||||
id='data'
|
||||
hass='[[hass]]'
|
||||
|
||||
Reference in New Issue
Block a user