YAML support for automation triggers (#4289)

* WIP: Add yaml editors to automation

* Fix form overwriting yaml on switching back

* Finish triggers

* prettier
This commit is contained in:
Bram Kragten 2019-12-02 11:20:09 +01:00 committed by GitHub
parent 4b56db5255
commit 7b2be54f8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 162 additions and 49 deletions

View File

@ -7,8 +7,11 @@
// export function onChangeEvent(this: OnChangeComponent, prop, ev) { // export function onChangeEvent(this: OnChangeComponent, prop, ev) {
export function onChangeEvent(this: any, prop, ev) { export function onChangeEvent(this: any, prop, ev) {
const origData = this.props[prop]; if (!this.initialized) {
return;
}
const origData = this.props[prop];
if (ev.target.value === origData[ev.target.name]) { if (ev.target.value === origData[ev.target.name]) {
return; return;
} }

View File

@ -0,0 +1,23 @@
import { h, Component, ComponentChild } from "preact";
export class AutomationComponent extends Component<any, any> {
// @ts-ignore
protected initialized: boolean;
constructor(props?, context?) {
super(props, context);
this.initialized = false;
}
public componentDidMount() {
this.initialized = true;
}
public componentWillUnmount() {
this.initialized = false;
}
public render(_props?, _state?, _context?: any): ComponentChild {
return <div />;
}
}

View File

@ -1,4 +1,4 @@
import { h, Component } from "preact"; import { h } from "preact";
import "../../../../components/device/ha-device-picker"; import "../../../../components/device/ha-device-picker";
import "../../../../components/device/ha-device-trigger-picker"; import "../../../../components/device/ha-device-trigger-picker";
@ -9,7 +9,9 @@ import {
deviceAutomationsEqual, deviceAutomationsEqual,
} from "../../../../data/device_automation"; } from "../../../../data/device_automation";
export default class DeviceTrigger extends Component<any, any> { import { AutomationComponent } from "../automation-component";
export default class DeviceTrigger extends AutomationComponent {
private _origTrigger; private _origTrigger;
constructor() { constructor() {
@ -21,10 +23,16 @@ export default class DeviceTrigger extends Component<any, any> {
} }
public devicePicked(ev) { public devicePicked(ev) {
if (!this.initialized) {
return;
}
this.setState({ ...this.state, device_id: ev.target.value }); this.setState({ ...this.state, device_id: ev.target.value });
} }
public deviceTriggerPicked(ev) { public deviceTriggerPicked(ev) {
if (!this.initialized) {
return;
}
let trigger = ev.target.value; let trigger = ev.target.value;
if ( if (
this._origTrigger && this._origTrigger &&
@ -75,6 +83,7 @@ export default class DeviceTrigger extends Component<any, any> {
} }
public componentDidMount() { public componentDidMount() {
this.initialized = true;
if (!this.state.capabilities) { if (!this.state.capabilities) {
this._getCapabilities(); this._getCapabilities();
} }
@ -99,6 +108,9 @@ export default class DeviceTrigger extends Component<any, any> {
} }
private _extraFieldsChanged(ev) { private _extraFieldsChanged(ev) {
if (!this.initialized) {
return;
}
this.props.onChange(this.props.index, { this.props.onChange(this.props.index, {
...this.props.trigger, ...this.props.trigger,
...ev.detail.value, ...ev.detail.value,

View File

@ -1,10 +1,12 @@
import { h, Component } from "preact"; import { h } from "preact";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import YAMLTextArea from "../yaml_textarea"; import YAMLTextArea from "../yaml_textarea";
import { onChangeEvent } from "../../../../common/preact/event"; import { onChangeEvent } from "../../../../common/preact/event";
import { AutomationComponent } from "../automation-component";
export default class EventTrigger extends Component<any> { export default class EventTrigger extends AutomationComponent {
private onChange: (obj: any) => void; private onChange: (obj: any) => void;
constructor() { constructor() {
super(); super();

View File

@ -1,11 +1,13 @@
import { h, Component } from "preact"; import { h } from "preact";
import "@polymer/paper-radio-button/paper-radio-button"; import "@polymer/paper-radio-button/paper-radio-button";
import "@polymer/paper-radio-group/paper-radio-group"; import "@polymer/paper-radio-group/paper-radio-group";
import "../../../../components/entity/ha-entity-picker"; import "../../../../components/entity/ha-entity-picker";
import { onChangeEvent } from "../../../../common/preact/event"; import { onChangeEvent } from "../../../../common/preact/event";
import { AutomationComponent } from "../automation-component";
export default class GeolocationTrigger extends Component<any> { export default class GeolocationTrigger extends AutomationComponent {
private onChange: (obj: any) => void; private onChange: (obj: any) => void;
constructor() { constructor() {
super(); super();

View File

@ -1,8 +1,11 @@
import { h, Component } from "preact"; import { h } from "preact";
import "@polymer/paper-radio-button/paper-radio-button"; import "@polymer/paper-radio-button/paper-radio-button";
import "@polymer/paper-radio-group/paper-radio-group"; import "@polymer/paper-radio-group/paper-radio-group";
export default class HassTrigger extends Component<any> { import { AutomationComponent } from "../automation-component";
export default class HassTrigger extends AutomationComponent {
constructor() { constructor() {
super(); super();
@ -10,6 +13,9 @@ export default class HassTrigger extends Component<any> {
} }
public radioGroupPicked(ev) { public radioGroupPicked(ev) {
if (!this.initialized) {
return;
}
this.props.onChange(this.props.index, { this.props.onChange(this.props.index, {
...this.props.trigger, ...this.props.trigger,
event: ev.target.selected, event: ev.target.selected,

View File

@ -1,12 +1,14 @@
import { h, Component } from "preact"; import { h } from "preact";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { onChangeEvent } from "../../../../common/preact/event"; import { onChangeEvent } from "../../../../common/preact/event";
import { AutomationComponent } from "../automation-component";
export default class MQTTTrigger extends Component<any> { export default class MQTTTrigger extends AutomationComponent {
private onChange: (obj: any) => void; private onChange: (obj: any) => void;
constructor() { constructor(props) {
super(); super(props);
this.onChange = onChangeEvent.bind(this, "trigger"); this.onChange = onChangeEvent.bind(this, "trigger");
} }

View File

@ -1,15 +1,17 @@
import { h, Component } from "preact"; import { h } from "preact";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import "../../../../components/ha-textarea"; import "../../../../components/ha-textarea";
import "../../../../components/entity/ha-entity-picker"; import "../../../../components/entity/ha-entity-picker";
import { onChangeEvent } from "../../../../common/preact/event"; import { onChangeEvent } from "../../../../common/preact/event";
import { AutomationComponent } from "../automation-component";
export default class NumericStateTrigger extends Component<any> { export default class NumericStateTrigger extends AutomationComponent {
private onChange: (obj: any) => void; private onChange: (obj: any) => void;
constructor() { constructor(props) {
super(); super(props);
this.onChange = onChangeEvent.bind(this, "trigger"); this.onChange = onChangeEvent.bind(this, "trigger");
this.entityPicked = this.entityPicked.bind(this); this.entityPicked = this.entityPicked.bind(this);

View File

@ -1,20 +1,23 @@
import { h, Component } from "preact"; import { h } from "preact";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import "../../../../components/entity/ha-entity-picker"; import "../../../../components/entity/ha-entity-picker";
import { onChangeEvent } from "../../../../common/preact/event"; import { onChangeEvent } from "../../../../common/preact/event";
import { AutomationComponent } from "../automation-component";
export default class StateTrigger extends Component<any> { export default class StateTrigger extends AutomationComponent {
private onChange: (obj: any) => void; private onChange: (obj: any) => void;
constructor() { constructor(props) {
super(); super(props);
this.onChange = onChangeEvent.bind(this, "trigger"); this.onChange = onChangeEvent.bind(this, "trigger");
this.entityPicked = this.entityPicked.bind(this); this.entityPicked = this.entityPicked.bind(this);
} }
public entityPicked(ev) { public entityPicked(ev) {
if (!this.initialized) {
return;
}
this.props.onChange(this.props.index, { this.props.onChange(this.props.index, {
...this.props.trigger, ...this.props.trigger,
entity_id: ev.target.value, entity_id: ev.target.value,
@ -23,8 +26,7 @@ export default class StateTrigger extends Component<any> {
/* eslint-disable camelcase */ /* eslint-disable camelcase */
public render({ trigger, hass, localize }) { public render({ trigger, hass, localize }) {
const { entity_id, to } = trigger; const { entity_id, to, from } = trigger;
const trgFrom = trigger.from;
let trgFor = trigger.for; let trgFor = trigger.for;
if (trgFor && (trgFor.hours || trgFor.minutes || trgFor.seconds)) { if (trgFor && (trgFor.hours || trgFor.minutes || trgFor.seconds)) {
@ -50,7 +52,7 @@ export default class StateTrigger extends Component<any> {
"ui.panel.config.automation.editor.triggers.type.state.from" "ui.panel.config.automation.editor.triggers.type.state.from"
)} )}
name="from" name="from"
value={trgFrom} value={from}
onvalue-changed={this.onChange} onvalue-changed={this.onChange}
/> />
<paper-input <paper-input

View File

@ -1,12 +1,13 @@
import { h, Component } from "preact"; import { h } from "preact";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import "@polymer/paper-radio-button/paper-radio-button"; import "@polymer/paper-radio-button/paper-radio-button";
import "@polymer/paper-radio-group/paper-radio-group"; import "@polymer/paper-radio-group/paper-radio-group";
import { onChangeEvent } from "../../../../common/preact/event"; import { onChangeEvent } from "../../../../common/preact/event";
import { AutomationComponent } from "../automation-component";
export default class SunTrigger extends Component<any> { export default class SunTrigger extends AutomationComponent {
private onChange: (obj: any) => void; private onChange: (obj: any) => void;
constructor() { constructor() {
super(); super();
@ -16,6 +17,9 @@ export default class SunTrigger extends Component<any> {
} }
public radioGroupPicked(ev) { public radioGroupPicked(ev) {
if (!this.initialized) {
return;
}
this.props.onChange(this.props.index, { this.props.onChange(this.props.index, {
...this.props.trigger, ...this.props.trigger,
event: ev.target.selected, event: ev.target.selected,

View File

@ -1,10 +1,11 @@
import { h, Component } from "preact"; import { h } from "preact";
import "../../../../components/ha-textarea"; import "../../../../components/ha-textarea";
import { onChangeEvent } from "../../../../common/preact/event"; import { onChangeEvent } from "../../../../common/preact/event";
import { AutomationComponent } from "../automation-component";
export default class TemplateTrigger extends Component<any> { export default class TemplateTrigger extends AutomationComponent {
private onChange: (obj: any) => void; private onChange: (obj: any) => void;
constructor() { constructor() {
super(); super();

View File

@ -1,10 +1,11 @@
import { h, Component } from "preact"; import { h } from "preact";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { onChangeEvent } from "../../../../common/preact/event"; import { onChangeEvent } from "../../../../common/preact/event";
import { AutomationComponent } from "../automation-component";
export default class TimeTrigger extends Component<any> { export default class TimeTrigger extends AutomationComponent {
private onChange: (obj: any) => void; private onChange: (obj: any) => void;
constructor() { constructor() {
super(); super();

View File

@ -1,10 +1,11 @@
import { h, Component } from "preact"; import { h } from "preact";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { onChangeEvent } from "../../../../common/preact/event"; import { onChangeEvent } from "../../../../common/preact/event";
import { AutomationComponent } from "../automation-component";
export default class TimePatternTrigger extends Component<any> { export default class TimePatternTrigger extends AutomationComponent {
private onChange: (obj: any) => void; private onChange: (obj: any) => void;
constructor() { constructor() {
super(); super();

View File

@ -4,6 +4,10 @@ import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-listbox/paper-listbox";
import "../../../../components/ha-code-editor";
import YAMLTextArea from "../yaml_textarea";
import DeviceTrigger from "./device"; import DeviceTrigger from "./device";
import EventTrigger from "./event"; import EventTrigger from "./event";
import GeolocationTrigger from "./geo_location"; import GeolocationTrigger from "./geo_location";
@ -41,25 +45,31 @@ export default class TriggerEdit extends Component<any> {
super(); super();
this.typeChanged = this.typeChanged.bind(this); this.typeChanged = this.typeChanged.bind(this);
this.onYamlChange = this.onYamlChange.bind(this);
} }
public render({ index, trigger, onChange, hass, localize }) { public render({ index, trigger, onChange, hass, localize, yamlMode }) {
// tslint:disable-next-line: variable-name // tslint:disable-next-line: variable-name
const Comp = TYPES[trigger.platform]; const Comp = TYPES[trigger.platform];
const selected = OPTIONS.indexOf(trigger.platform); const selected = OPTIONS.indexOf(trigger.platform);
if (!Comp) { if (yamlMode || !Comp) {
return ( return (
<div> <div style="margin-right: 24px;">
{localize( {!Comp && (
"ui.panel.config.automation.editor.triggers.unsupported_platform", <div>
"platform", {localize(
trigger.platform "ui.panel.config.automation.editor.triggers.unsupported_platform",
"platform",
trigger.platform
)}
</div>
)} )}
<pre>{JSON.stringify(trigger, null, 2)}</pre> <YAMLTextArea value={trigger} onChange={this.onYamlChange} />
</div> </div>
); );
} }
return ( return (
<div> <div>
<paper-dropdown-menu-light <paper-dropdown-menu-light
@ -103,4 +113,8 @@ export default class TriggerEdit extends Component<any> {
}); });
} }
} }
private onYamlChange(trigger) {
this.props.onChange(this.props.index, trigger);
}
} }

View File

@ -8,28 +8,44 @@ import "../../../../components/ha-card";
import TriggerEdit from "./trigger_edit"; import TriggerEdit from "./trigger_edit";
export default class TriggerRow extends Component<any> { export default class TriggerRow extends Component<any> {
public state: { yamlMode: boolean };
constructor() { constructor() {
super(); super();
this.state = {
yamlMode: false,
};
this.onDelete = this.onDelete.bind(this); this.onDelete = this.onDelete.bind(this);
this.switchYamlMode = this.switchYamlMode.bind(this);
} }
public render(props) { public render(props, { yamlMode }) {
return ( return (
<ha-card> <ha-card>
<div class="card-content"> <div class="card-content">
<div class="card-menu"> <div class="card-menu" style="z-index: 3">
<paper-menu-button <paper-menu-button
no-animations no-animations
horizontal-align="right" horizontal-align="right"
horizontal-offset="-5" horizontal-offset="-5"
vertical-offset="-5" vertical-offset="-5"
close-on-activate
> >
<paper-icon-button <paper-icon-button
icon="hass:dots-vertical" icon="hass:dots-vertical"
slot="dropdown-trigger" slot="dropdown-trigger"
/> />
<paper-listbox slot="dropdown-content"> <paper-listbox slot="dropdown-content">
<paper-item onTap={this.switchYamlMode}>
{yamlMode
? props.localize(
"ui.panel.config.automation.editor.edit_ui"
)
: props.localize(
"ui.panel.config.automation.editor.edit_yaml"
)}
</paper-item>
<paper-item disabled> <paper-item disabled>
{props.localize( {props.localize(
"ui.panel.config.automation.editor.triggers.duplicate" "ui.panel.config.automation.editor.triggers.duplicate"
@ -43,7 +59,7 @@ export default class TriggerRow extends Component<any> {
</paper-listbox> </paper-listbox>
</paper-menu-button> </paper-menu-button>
</div> </div>
<TriggerEdit {...props} /> <TriggerEdit {...props} yamlMode={yamlMode} />
</div> </div>
</ha-card> </ha-card>
); );
@ -61,4 +77,10 @@ export default class TriggerRow extends Component<any> {
this.props.onChange(this.props.index, null); this.props.onChange(this.props.index, null);
} }
} }
private switchYamlMode() {
this.setState({
yamlMode: !this.state.yamlMode,
});
}
} }

View File

@ -1,8 +1,11 @@
import { h, Component } from "preact"; import { h } from "preact";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { onChangeEvent } from "../../../../common/preact/event"; import { onChangeEvent } from "../../../../common/preact/event";
export default class WebhookTrigger extends Component<any> { import { AutomationComponent } from "../automation-component";
export default class WebhookTrigger extends AutomationComponent {
private onChange: (obj: any) => void; private onChange: (obj: any) => void;
constructor() { constructor() {
super(); super();

View File

@ -1,16 +1,18 @@
import { h, Component } from "preact"; import { h } from "preact";
import "@polymer/paper-radio-button/paper-radio-button"; import "@polymer/paper-radio-button/paper-radio-button";
import "@polymer/paper-radio-group/paper-radio-group"; import "@polymer/paper-radio-group/paper-radio-group";
import "../../../../components/entity/ha-entity-picker"; import "../../../../components/entity/ha-entity-picker";
import { hasLocation } from "../../../../common/entity/has_location"; import { hasLocation } from "../../../../common/entity/has_location";
import { computeStateDomain } from "../../../../common/entity/compute_state_domain"; import { computeStateDomain } from "../../../../common/entity/compute_state_domain";
import { AutomationComponent } from "../automation-component";
function zoneAndLocationFilter(stateObj) { function zoneAndLocationFilter(stateObj) {
return hasLocation(stateObj) && computeStateDomain(stateObj) !== "zone"; return hasLocation(stateObj) && computeStateDomain(stateObj) !== "zone";
} }
export default class ZoneTrigger extends Component<any> { export default class ZoneTrigger extends AutomationComponent {
constructor() { constructor() {
super(); super();
@ -70,6 +72,9 @@ export default class ZoneTrigger extends Component<any> {
} }
private entityPicked(ev) { private entityPicked(ev) {
if (!this.initialized) {
return;
}
this.props.onChange(this.props.index, { this.props.onChange(this.props.index, {
...this.props.trigger, ...this.props.trigger,
entity_id: ev.target.value, entity_id: ev.target.value,
@ -77,6 +82,9 @@ export default class ZoneTrigger extends Component<any> {
} }
private zonePicked(ev) { private zonePicked(ev) {
if (!this.initialized) {
return;
}
this.props.onChange(this.props.index, { this.props.onChange(this.props.index, {
...this.props.trigger, ...this.props.trigger,
zone: ev.target.value, zone: ev.target.value,
@ -84,6 +92,9 @@ export default class ZoneTrigger extends Component<any> {
} }
private radioGroupPicked(ev) { private radioGroupPicked(ev) {
if (!this.initialized) {
return;
}
this.props.onChange(this.props.index, { this.props.onChange(this.props.index, {
...this.props.trigger, ...this.props.trigger,
event: ev.target.selected, event: ev.target.selected,

View File

@ -66,7 +66,7 @@ export default class YAMLTextArea extends Component<any, any> {
}; };
return ( return (
<div> <div>
<p>{label}</p> {label && <p>{label}</p>}
<ha-code-editor <ha-code-editor
mode="yaml" mode="yaml"
style={style} style={style}

View File

@ -786,6 +786,8 @@
"label": "Description", "label": "Description",
"placeholder": "Optional description" "placeholder": "Optional description"
}, },
"edit_yaml": "Edit as YAML",
"edit_ui": "Edit with UI",
"triggers": { "triggers": {
"name": "Trigger", "name": "Trigger",
"header": "Triggers", "header": "Triggers",