diff --git a/js/editor/automation.js b/js/editor/automation.js index 58c3f64290..9234b34967 100644 --- a/js/editor/automation.js +++ b/js/editor/automation.js @@ -1,6 +1,7 @@ import { h, Component } from 'preact'; import Trigger from './trigger'; +import Condition from './condition'; import Script from './script'; export default class Automation extends Component { @@ -9,6 +10,7 @@ export default class Automation extends Component { this.onChange = this.onChange.bind(this); this.triggerChanged = this.triggerChanged.bind(this); + this.conditionChanged = this.conditionChanged.bind(this); this.actionChanged = this.actionChanged.bind(this); } @@ -26,6 +28,13 @@ export default class Automation extends Component { }); } + conditionChanged(condition) { + this.props.onChange({ + ...this.props.automation, + condition, + }); + } + actionChanged(action) { this.props.onChange({ ...this.props.automation, @@ -83,12 +92,7 @@ export default class Automation extends Component { Learn more about conditions.

- -
- Conditions are not supported yet. -
{JSON.stringify(condition, null, 2)}
-
-
+ } diff --git a/js/editor/condition/condition_edit.js b/js/editor/condition/condition_edit.js new file mode 100644 index 0000000000..4d64042948 --- /dev/null +++ b/js/editor/condition/condition_edit.js @@ -0,0 +1,71 @@ +import { h, Component } from 'preact'; + +import NumericStateCondition from './numeric_state'; +import StateCondition from './state'; +import SunCondition from './sun'; +import TemplateCondition from './template'; +import TimeCondition from './time'; +import ZoneCondition from './zone'; + +const TYPES = { + state: StateCondition, + numeric_state: NumericStateCondition, + sun: SunCondition, + template: TemplateCondition, + time: TimeCondition, + zone: ZoneCondition, +}; + +const OPTIONS = Object.keys(TYPES).sort(); + +export default class ConditionRow extends Component { + constructor() { + super(); + + this.typeChanged = this.typeChanged.bind(this); + } + + typeChanged(ev) { + const type = ev.target.selectedItem.innerHTML; + + if (type !== this.props.condition.condition) { + this.props.onChange(this.props.index, { + condition: type, + ...TYPES[type].defaultConfig + }); + } + } + + render({ index, condition, onChange }) { + const Comp = TYPES[condition.condition]; + const selected = OPTIONS.indexOf(condition.condition); + + if (!Comp) { + return ( +
+ Unsupported condition: {condition.condition} +
{JSON.stringify(condition, null, 2)}
+
+ ); + } + + return ( +
+ + + {OPTIONS.map(opt => {opt})} + + + +
+ ); + } +} diff --git a/js/editor/condition/condition_row.js b/js/editor/condition/condition_row.js new file mode 100644 index 0000000000..a879ec421a --- /dev/null +++ b/js/editor/condition/condition_row.js @@ -0,0 +1,45 @@ +import { h, Component } from 'preact'; + +import ConditionEdit from './condition_edit'; + +export default class ConditionRow extends Component { + constructor() { + super(); + + this.onDelete = this.onDelete.bind(this); + } + + onDelete() { + // eslint-disable-next-line + if (confirm('Sure you want to delete?')) { + this.props.onChange(this.props.index, null); + } + } + + render(props) { + return ( + +
+ + + + Duplicate + Delete + + +
+
+ +
+
+ ); + } +} diff --git a/js/editor/condition/index.js b/js/editor/condition/index.js new file mode 100644 index 0000000000..c2cdf70950 --- /dev/null +++ b/js/editor/condition/index.js @@ -0,0 +1,50 @@ +import { h, Component } from 'preact'; + +import ConditionRow from './condition_row'; + +export default class Condition extends Component { + constructor() { + super(); + + this.addCondition = this.addCondition.bind(this); + this.conditionChanged = this.conditionChanged.bind(this); + } + + addCondition() { + const condition = this.props.condition.concat({ + condition: 'state', + }); + + this.props.onChange(condition); + } + + conditionChanged(index, newValue) { + const condition = this.props.condition.concat(); + + if (newValue === null) { + condition.splice(index, 1); + } else { + condition[index] = newValue; + } + + this.props.onChange(condition); + } + + render({ condition }) { + return ( +
+ {condition.map((cnd, idx) => ( + ))} + +
+ Add condition +
+
+
+ ); + } +} diff --git a/js/editor/condition/numeric_state.js b/js/editor/condition/numeric_state.js new file mode 100644 index 0000000000..7849c8a9db --- /dev/null +++ b/js/editor/condition/numeric_state.js @@ -0,0 +1,51 @@ +import { h, Component } from 'preact'; + +import { onChangeEvent } from '../util'; + +export default class NumericStateCondition extends Component { + constructor() { + super(); + + this.onChange = onChangeEvent.bind(this, 'condition'); + } + + /* eslint-disable camelcase */ + render({ condition }) { + const { value_template, entity_id, below, above } = condition; + return ( +
+ + + + +
+ ); + } +} + +NumericStateCondition.defaultConfig = { + entity_id: '', + above: '', + below: '', + value_template: '', +}; diff --git a/js/editor/condition/state.js b/js/editor/condition/state.js new file mode 100644 index 0000000000..0eafaca80d --- /dev/null +++ b/js/editor/condition/state.js @@ -0,0 +1,39 @@ +import { h, Component } from 'preact'; + +import { onChangeEvent } from '../util'; + +export default class StateCondition extends Component { + constructor() { + super(); + + this.onChange = onChangeEvent.bind(this, 'condition'); + } + + /* eslint-disable camelcase */ + render({ condition }) { + const { entity_id, state } = condition; + const cndFor = condition.for; + return ( +
+ + + {cndFor &&
For: {JSON.stringify(cndFor, null, 2)}
} +
+ ); + } +} + +StateCondition.defaultConfig = { + entity_id: '', + state: '', +}; diff --git a/js/editor/condition/sun.js b/js/editor/condition/sun.js new file mode 100644 index 0000000000..d234d2757c --- /dev/null +++ b/js/editor/condition/sun.js @@ -0,0 +1,74 @@ +import { h, Component } from 'preact'; + +import { onChangeEvent } from '../util'; + +export default class SunCondition extends Component { + constructor() { + super(); + + this.onChange = onChangeEvent.bind(this, 'condition'); + this.afterPicked = this.radioGroupPicked.bind(this, 'after'); + this.beforePicked = this.radioGroupPicked.bind(this, 'before'); + } + + radioGroupPicked(key, ev) { + const condition = { ...this.props.condition }; + + if (ev.target.selected) { + condition[key] = ev.target.value; + } else { + delete condition[key]; + } + + this.props.onChange(this.props.index, condition); + } + + render({ condition }) { + /* eslint-disable camelcase */ + const { after, after_offset, before, before_offset } = condition; + return ( +
+ + + Sunrise + Sunset + + + + + + + Sunrise + Sunset + + + +
+ ); + } +} + +SunCondition.defaultConfig = { +}; diff --git a/js/editor/condition/template.js b/js/editor/condition/template.js new file mode 100644 index 0000000000..2427879d34 --- /dev/null +++ b/js/editor/condition/template.js @@ -0,0 +1,30 @@ +import { h, Component } from 'preact'; + +import { onChangeEvent } from '../util'; + +export default class TemplateCondition extends Component { + constructor() { + super(); + + this.onChange = onChangeEvent.bind(this, 'condition'); + } + + render({ condition }) { + /* eslint-disable camelcase */ + const { value_template } = condition; + return ( +
+ +
+ ); + } +} + +TemplateCondition.defaultConfig = { + value_template: '', +}; diff --git a/js/editor/condition/time.js b/js/editor/condition/time.js new file mode 100644 index 0000000000..b6efe42aa7 --- /dev/null +++ b/js/editor/condition/time.js @@ -0,0 +1,37 @@ +import { h, Component } from 'preact'; + +import { onChangeEvent } from '../util'; + +export default class StateCondition extends Component { + constructor() { + super(); + + this.onChange = onChangeEvent.bind(this, 'condition'); + } + + /* eslint-disable camelcase */ + render({ condition }) { + const { after, before } = condition; + return ( +
+ + +
+ ); + } +} + +StateCondition.defaultConfig = { + after: '', + before: '', +}; diff --git a/js/editor/condition/zone.js b/js/editor/condition/zone.js new file mode 100644 index 0000000000..10a2da8c04 --- /dev/null +++ b/js/editor/condition/zone.js @@ -0,0 +1,37 @@ +import { h, Component } from 'preact'; + +import { onChangeEvent } from '../util'; + +export default class ZoneCondition extends Component { + constructor() { + super(); + + this.onChange = onChangeEvent.bind(this, 'condition'); + } + + /* eslint-disable camelcase */ + render({ condition }) { + const { entity_id, zone } = condition; + return ( +
+ + +
+ ); + } +} + +ZoneCondition.defaultConfig = { + entity_id: '', + zone: '', +}; diff --git a/js/editor/json_textarea.js b/js/editor/json_textarea.js index d8b6399ed2..0c5204c7c0 100644 --- a/js/editor/json_textarea.js +++ b/js/editor/json_textarea.js @@ -38,7 +38,7 @@ export default class JSONTextArea extends Component { }); } - render(props, { value, isValid }) { + render({ label }, { value, isValid }) { const style = { minWidth: 300, width: '100%', @@ -47,10 +47,11 @@ export default class JSONTextArea extends Component { style.border = '1px solid red'; } return ( - ); } diff --git a/js/editor/script/action_edit.js b/js/editor/script/action_edit.js new file mode 100644 index 0000000000..b470308899 --- /dev/null +++ b/js/editor/script/action_edit.js @@ -0,0 +1,78 @@ +import { h, Component } from 'preact'; + +import CallServiceAction from './call_service'; +import ConditionAction from './condition'; +import DelayAction from './delay'; +import EventAction from './event'; +import WaitAction from './wait'; + +const TYPES = { + 'Call Service': CallServiceAction, + Delay: DelayAction, + Wait: WaitAction, + Condition: ConditionAction, + 'Fire Event': EventAction, +}; + +const OPTIONS = Object.keys(TYPES).sort(); + +function getType(action) { + const keys = Object.keys(TYPES); + for (let i = 0; i < keys.length; i++) { + if (TYPES[keys[i]].configKey in action) { + return keys[i]; + } + } + return null; +} + +export default class Action extends Component { + constructor() { + super(); + + this.typeChanged = this.typeChanged.bind(this); + } + + typeChanged(ev) { + const newType = ev.target.selectedItem.innerHTML; + const oldType = getType(this.props.action); + + if (oldType !== newType) { + this.props.onChange(this.props.index, TYPES[newType].defaultConfig); + } + } + + render({ index, action, onChange }) { + const type = getType(action); + const Comp = type && TYPES[type]; + const selected = OPTIONS.indexOf(type); + + if (!Comp) { + return ( +
+ Unsupported action +
{JSON.stringify(action, null, 2)}
+
+ ); + } + return ( +
+ + + {OPTIONS.map(opt => {opt})} + + + +
+ ); + } + +} diff --git a/js/editor/script/action_row.js b/js/editor/script/action_row.js new file mode 100644 index 0000000000..235fa4e337 --- /dev/null +++ b/js/editor/script/action_row.js @@ -0,0 +1,46 @@ +import { h, Component } from 'preact'; + +import ActionEdit from './action_edit'; + +export default class Action extends Component { + constructor() { + super(); + + this.onDelete = this.onDelete.bind(this); + } + + onDelete() { + // eslint-disable-next-line + if (confirm('Sure you want to delete?')) { + this.props.onChange(this.props.index, null); + } + } + + render(props) { + return ( + +
+ + + + Duplicate + Delete + + +
+
+ +
+
+ ); + } + +} diff --git a/js/editor/script/call_service.js b/js/editor/script/call_service.js index bca7aca6ff..2112c3b84f 100644 --- a/js/editor/script/call_service.js +++ b/js/editor/script/call_service.js @@ -1,23 +1,16 @@ import { h, Component } from 'preact'; import JSONTextArea from '../json_textarea'; +import { onChangeEvent } from '../util'; export default class CallServiceAction extends Component { constructor() { super(); - this.onChange = this.onChange.bind(this); + this.onChange = onChangeEvent.bind(this, 'action'); this.serviceDataChanged = this.serviceDataChanged.bind(this); } - onChange(ev) { - this.props.onChange(this.props.index, { - ...this.props.action, - [ev.target.name]: ev.target.value - }); - } - - /* eslint-disable camelcase */ serviceDataChanged(data) { this.props.onChange(this.props.index, { ...this.props.action, @@ -41,8 +34,8 @@ export default class CallServiceAction extends Component { value={service} onChange={this.onChange} /> - Service Data
@@ -50,3 +43,10 @@ export default class CallServiceAction extends Component { ); } } + +CallServiceAction.configKey = 'service'; +CallServiceAction.defaultConfig = { + alias: '', + service: '', + data: {} +}; diff --git a/js/editor/script/condition.js b/js/editor/script/condition.js new file mode 100644 index 0000000000..815c303aa7 --- /dev/null +++ b/js/editor/script/condition.js @@ -0,0 +1,23 @@ +import { h, Component } from 'preact'; + +import StateCondition from '../condition/state'; +import ConditionEdit from '../condition/condition_edit'; + +export default class ConditionAction extends Component { + // eslint-disable-next-line + render({ action, index, onChange }) { + return ( + + ); + } +} + +ConditionAction.configKey = 'condition'; +ConditionAction.defaultConfig = { + condition: 'state', + ...StateCondition.defaultConfig, +}; diff --git a/js/editor/script/delay.js b/js/editor/script/delay.js new file mode 100644 index 0000000000..a20d29722b --- /dev/null +++ b/js/editor/script/delay.js @@ -0,0 +1,29 @@ +import { h, Component } from 'preact'; +import { onChangeEvent } from '../util'; + +export default class DelayAction extends Component { + constructor() { + super(); + + this.onChange = onChangeEvent.bind(this, 'action'); + } + + render({ action }) { + const { delay } = action; + return ( +
+ +
+ ); + } +} + +DelayAction.configKey = 'delay'; +DelayAction.defaultConfig = { + delay: '', +}; diff --git a/js/editor/script/event.js b/js/editor/script/event.js new file mode 100644 index 0000000000..c554a1a579 --- /dev/null +++ b/js/editor/script/event.js @@ -0,0 +1,46 @@ +import { h, Component } from 'preact'; + +import JSONTextArea from '../json_textarea'; +import { onChangeEvent } from '../util'; + +export default class EventAction extends Component { + constructor() { + super(); + + this.onChange = onChangeEvent.bind(this, 'action'); + this.serviceDataChanged = this.serviceDataChanged.bind(this); + } + + serviceDataChanged(data) { + this.props.onChange(this.props.index, { + ...this.props.action, + data, + }); + } + + render({ action }) { + /* eslint-disable camelcase */ + const { event, event_data } = action; + return ( +
+ + +
+ ); + } +} + +EventAction.configKey = 'event'; +EventAction.defaultConfig = { + event: '', + event_data: {}, +}; diff --git a/js/editor/script/index.js b/js/editor/script/index.js index b4299a3309..c5382cc38e 100644 --- a/js/editor/script/index.js +++ b/js/editor/script/index.js @@ -1,6 +1,6 @@ import { h, Component } from 'preact'; -import ScriptAction from './script_action'; +import ActionRow from './action_row'; export default class Script extends Component { constructor() { @@ -34,7 +34,7 @@ export default class Script extends Component { return (
{script.map((act, idx) => ( - - - - {OPTIONS.map(opt => {opt})} - - - -
- ); - } else { - content = ( -
- Unsupported action -
{JSON.stringify(action, null, 2)}
-
- ); - } - - return ( - -
- - - - Duplicate - Delete - - -
-
{content}
-
- ); - } - -} diff --git a/js/editor/script/wait.js b/js/editor/script/wait.js new file mode 100644 index 0000000000..a473d2277b --- /dev/null +++ b/js/editor/script/wait.js @@ -0,0 +1,47 @@ +import { h, Component } from 'preact'; +import { onChangeEvent } from '../util'; + +export default class WaitAction extends Component { + constructor() { + super(); + + this.onChange = onChangeEvent.bind(this, 'action'); + this.onTemplateChange = this.onTemplateChange.bind(this); + } + + // Gets fired on mount. If empty, onChangeEvent removes attribute. + // Without the attribute this action is no longer matched to this component. + onTemplateChange(ev) { + this.props.onChange(this.props.index, { + ...this.props.trigger, + [ev.target.name]: ev.target.value, + }); + } + + render({ action }) { + /* eslint-disable camelcase */ + const { wait_template, timeout } = action; + return ( +
+ + +
+ ); + } +} + +WaitAction.configKey = 'wait_template'; +WaitAction.defaultConfig = { + wait_template: '', + timeout: '', +}; diff --git a/js/editor/trigger/event.js b/js/editor/trigger/event.js index 2a99d17e90..11d032206d 100644 --- a/js/editor/trigger/event.js +++ b/js/editor/trigger/event.js @@ -2,13 +2,13 @@ import { h, Component } from 'preact'; import JSONTextArea from '../json_textarea'; -import { onChange } from './util'; +import { onChangeEvent } from '../util'; export default class EventTrigger extends Component { constructor() { super(); - this.onChange = onChange.bind(this); + this.onChange = onChangeEvent.bind(this, 'trigger'); this.eventDataChanged = this.eventDataChanged.bind(this); } @@ -30,8 +30,8 @@ export default class EventTrigger extends Component { value={event_type} onChange={this.onChange} /> - Event Data @@ -39,3 +39,8 @@ export default class EventTrigger extends Component { ); } } + +EventTrigger.defaultConfig = { + event_type: '', + event_data: {}, +}; diff --git a/js/editor/trigger/homeassistant.js b/js/editor/trigger/homeassistant.js new file mode 100644 index 0000000000..6d43225a89 --- /dev/null +++ b/js/editor/trigger/homeassistant.js @@ -0,0 +1,38 @@ +import { h, Component } from 'preact'; + +export default class HassTrigger extends Component { + constructor() { + super(); + + this.radioGroupPicked = this.radioGroupPicked.bind(this); + } + + radioGroupPicked(ev) { + this.props.onChange(this.props.index, { + ...this.props.trigger, + event: ev.target.selected, + }); + } + + /* eslint-disable camelcase */ + render({ trigger }) { + const { event } = trigger; + return ( +
+ + + Start + Shutdown + +
+ ); + } +} + +HassTrigger.defaultConfig = { + event: 'start' +}; diff --git a/js/editor/trigger/index.js b/js/editor/trigger/index.js index b180f16eb2..25a5976e75 100644 --- a/js/editor/trigger/index.js +++ b/js/editor/trigger/index.js @@ -1,6 +1,7 @@ import { h, Component } from 'preact'; import TriggerRow from './trigger_row'; +import StateTrigger from './state'; export default class Trigger extends Component { constructor() { @@ -12,7 +13,8 @@ export default class Trigger extends Component { addTrigger() { const trigger = this.props.trigger.concat({ - platform: 'event', + platform: 'state', + ...StateTrigger.defaultConfig, }); this.props.onChange(trigger); diff --git a/js/editor/trigger/mqtt.js b/js/editor/trigger/mqtt.js new file mode 100644 index 0000000000..59540ea92c --- /dev/null +++ b/js/editor/trigger/mqtt.js @@ -0,0 +1,37 @@ +import { h, Component } from 'preact'; + +import { onChangeEvent } from '../util'; + +export default class MQTTTrigger extends Component { + constructor() { + super(); + + this.onChange = onChangeEvent.bind(this, 'trigger'); + } + + /* eslint-disable camelcase */ + render({ trigger }) { + const { topic, payload } = trigger; + return ( +
+ + +
+ ); + } +} + +MQTTTrigger.defaultConfig = { + topic: '', + payload: '', +}; diff --git a/js/editor/trigger/numeric_state.js b/js/editor/trigger/numeric_state.js index c08bc9181c..ff7f4e5c41 100644 --- a/js/editor/trigger/numeric_state.js +++ b/js/editor/trigger/numeric_state.js @@ -1,12 +1,12 @@ import { h, Component } from 'preact'; -import { onChange } from './util'; +import { onChangeEvent } from '../util'; export default class NumericStateTrigger extends Component { constructor() { super(); - this.onChange = onChange.bind(this); + this.onChange = onChangeEvent.bind(this, 'trigger'); } /* eslint-disable camelcase */ @@ -32,14 +32,20 @@ export default class NumericStateTrigger extends Component { value={below} onChange={this.onChange} /> - Value template (optional)
-