Automation update (#382)

* Add missing triggers

* Move onChangeEvent to generic util

* Generate dev service worker in dev mode

* Add actions

* Lint

* Add condition support

* Add condition actions
This commit is contained in:
Paulus Schoutsen 2017-08-09 00:44:17 -07:00 committed by GitHub
parent 77f934b738
commit f02d42d79c
36 changed files with 1099 additions and 215 deletions

View File

@ -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.
</a></p>
</span>
<paper-card>
<div class='card-content'>
Conditions are not supported yet.
<pre>{JSON.stringify(condition, null, 2)}</pre>
</div>
</paper-card>
<Condition condition={condition} onChange={this.conditionChanged} />
</ha-config-section>}
<ha-config-section is-wide={isWide}>

View File

@ -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 (
<div>
Unsupported condition: {condition.condition}
<pre>{JSON.stringify(condition, null, 2)}</pre>
</div>
);
}
return (
<div>
<paper-dropdown-menu-light label="Condition Type" no-animations>
<paper-listbox
slot="dropdown-content"
selected={selected}
oniron-select={this.typeChanged}
>
{OPTIONS.map(opt => <paper-item>{opt}</paper-item>)}
</paper-listbox>
</paper-dropdown-menu-light>
<Comp
index={index}
condition={condition}
onChange={onChange}
/>
</div>
);
}
}

View File

@ -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 (
<paper-card>
<div class='card-menu'>
<paper-menu-button
no-animations
horizontal-align="right"
horizontal-offset="-5"
vertical-offset="-5"
>
<paper-icon-button
icon="mdi:dots-vertical"
slot="dropdown-trigger"
/>
<paper-listbox slot="dropdown-content">
<paper-item disabled>Duplicate</paper-item>
<paper-item onTap={this.onDelete}>Delete</paper-item>
</paper-listbox>
</paper-menu-button>
</div>
<div class='card-content'>
<ConditionEdit {...props} />
</div>
</paper-card>
);
}
}

View File

@ -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 (
<div class="triggers">
{condition.map((cnd, idx) => (
<ConditionRow
index={idx}
condition={cnd}
onChange={this.conditionChanged}
/>))}
<paper-card>
<div class='card-actions add-card'>
<paper-button onTap={this.addCondition}>Add condition</paper-button>
</div>
</paper-card>
</div>
);
}
}

View File

@ -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 (
<div>
<paper-input
label="Entity Id"
name="entity_id"
value={entity_id}
onChange={this.onChange}
/>
<paper-input
label="Above"
name="above"
value={above}
onChange={this.onChange}
/>
<paper-input
label="Below"
name="below"
value={below}
onChange={this.onChange}
/>
<paper-textarea
label="Value template (optional)"
name="value_template"
value={value_template}
onvalue-changed={this.onChange}
/>
</div>
);
}
}
NumericStateCondition.defaultConfig = {
entity_id: '',
above: '',
below: '',
value_template: '',
};

View File

@ -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 (
<div>
<paper-input
label="Entity Id"
name="entity_id"
value={entity_id}
onChange={this.onChange}
/>
<paper-input
label="State"
name="state"
value={state}
onChange={this.onChange}
/>
{cndFor && <pre>For: {JSON.stringify(cndFor, null, 2)}</pre>}
</div>
);
}
}
StateCondition.defaultConfig = {
entity_id: '',
state: '',
};

View File

@ -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 (
<div>
<label id="beforelabel">Before:</label>
<paper-radio-group
allow-empty-selection
selected={before}
aria-labelledby="beforelabel"
onpaper-radio-group-changed={this.beforePicked}
>
<paper-radio-button name="sunrise">Sunrise</paper-radio-button>
<paper-radio-button name="sunset">Sunset</paper-radio-button>
</paper-radio-group>
<paper-input
label="Before offset (optional)"
name="before_offset"
value={before_offset}
onChange={this.onChange}
disabled={before === undefined}
/>
<label id="afterlabel">After:</label>
<paper-radio-group
allow-empty-selection
selected={after}
aria-labelledby="afterlabel"
onpaper-radio-group-changed={this.afterPicked}
>
<paper-radio-button name="sunrise">Sunrise</paper-radio-button>
<paper-radio-button name="sunset">Sunset</paper-radio-button>
</paper-radio-group>
<paper-input
label="After offset (optional)"
name="after_offset"
value={after_offset}
onChange={this.onChange}
disabled={after === undefined}
/>
</div>
);
}
}
SunCondition.defaultConfig = {
};

View File

@ -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 (
<div>
<paper-textarea
label="Value Template"
name="value_template"
value={value_template}
onvalue-changed={this.onChange}
/>
</div>
);
}
}
TemplateCondition.defaultConfig = {
value_template: '',
};

View File

@ -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 (
<div>
<paper-input
label="After"
name="after"
value={after}
onChange={this.onChange}
/>
<paper-input
label="Before"
name="before"
value={before}
onChange={this.onChange}
/>
</div>
);
}
}
StateCondition.defaultConfig = {
after: '',
before: '',
};

View File

@ -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 (
<div>
<paper-input
label="Entity Id"
name="entity_id"
value={entity_id}
onChange={this.onChange}
/>
<paper-input
label="Zone entity id"
name="zone"
value={zone}
onChange={this.onChange}
/>
</div>
);
}
}
ZoneCondition.defaultConfig = {
entity_id: '',
zone: '',
};

View File

@ -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 (
<iron-autogrow-textarea
<paper-textarea
label={label}
value={value}
style={style}
onValue-Changed={this.onChange}
onvalue-changed={this.onChange}
/>
);
}

View File

@ -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 (
<div>
Unsupported action
<pre>{JSON.stringify(action, null, 2)}</pre>
</div>
);
}
return (
<div>
<paper-dropdown-menu-light label="Action Type" no-animations>
<paper-listbox
slot="dropdown-content"
selected={selected}
oniron-select={this.typeChanged}
>
{OPTIONS.map(opt => <paper-item>{opt}</paper-item>)}
</paper-listbox>
</paper-dropdown-menu-light>
<Comp
index={index}
action={action}
onChange={onChange}
/>
</div>
);
}
}

View File

@ -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 (
<paper-card>
<div class='card-menu'>
<paper-menu-button
no-animations
horizontal-align="right"
horizontal-offset="-5"
vertical-offset="-5"
>
<paper-icon-button
icon="mdi:dots-vertical"
slot="dropdown-trigger"
/>
<paper-listbox slot="dropdown-content">
<paper-item disabled>Duplicate</paper-item>
<paper-item onTap={this.onDelete}>Delete</paper-item>
</paper-listbox>
</paper-menu-button>
</div>
<div class='card-content'>
<ActionEdit {...props} />
</div>
</paper-card>
);
}
}

View File

@ -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<br />
<JSONTextArea
label="Service Data"
value={data}
onChange={this.serviceDataChanged}
/>
@ -50,3 +43,10 @@ export default class CallServiceAction extends Component {
);
}
}
CallServiceAction.configKey = 'service';
CallServiceAction.defaultConfig = {
alias: '',
service: '',
data: {}
};

View File

@ -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 (
<ConditionEdit
condition={action}
onChange={onChange}
index={index}
/>
);
}
}
ConditionAction.configKey = 'condition';
ConditionAction.defaultConfig = {
condition: 'state',
...StateCondition.defaultConfig,
};

29
js/editor/script/delay.js Normal file
View File

@ -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 (
<div>
<paper-input
label="Delay"
name="delay"
value={delay}
onChange={this.onChange}
/>
</div>
);
}
}
DelayAction.configKey = 'delay';
DelayAction.defaultConfig = {
delay: '',
};

46
js/editor/script/event.js Normal file
View File

@ -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 (
<div>
<paper-input
label="Event"
name="event"
value={event}
onChange={this.onChange}
/>
<JSONTextArea
label="Service Data"
value={event_data}
onChange={this.serviceDataChanged}
/>
</div>
);
}
}
EventAction.configKey = 'event';
EventAction.defaultConfig = {
event: '',
event_data: {},
};

View File

@ -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 (
<div class="script">
{script.map((act, idx) => (
<ScriptAction
<ActionRow
index={idx}
action={act}
onChange={this.actionChanged}

View File

@ -1,106 +0,0 @@
import { h, Component } from 'preact';
import CallService from './call_service';
function getType(action) {
if ('service' in action) {
return 'Call Service';
}
return null;
}
const TYPES = {
'Call Service': CallService,
Delay: null,
'Templated Delay': null,
Condition: null,
'Fire Event': null,
};
const OPTIONS = Object.keys(TYPES).sort();
export default class Action extends Component {
constructor() {
super();
this.typeChanged = this.typeChanged.bind(this);
this.onDelete = this.onDelete.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, {
platform: newType,
});
}
}
onDelete() {
// eslint-disable-next-line
if (confirm('Sure you want to delete?')) {
this.props.onChange(this.props.index, null);
}
}
render({ index, action, onChange }) {
const type = getType(action);
const Comp = TYPES[type];
const selected = OPTIONS.indexOf(type);
let content;
if (Comp) {
content = (
<div>
<paper-dropdown-menu-light label="Action Type" no-animations>
<paper-listbox
slot="dropdown-content"
selected={selected}
oniron-select={this.typeChanged}
>
{OPTIONS.map(opt => <paper-item>{opt}</paper-item>)}
</paper-listbox>
</paper-dropdown-menu-light>
<Comp
index={index}
action={action}
onChange={onChange}
/>
</div>
);
} else {
content = (
<div>
Unsupported action
<pre>{JSON.stringify(action, null, 2)}</pre>
</div>
);
}
return (
<paper-card>
<div class='card-menu'>
<paper-menu-button
no-animations
horizontal-align="right"
horizontal-offset="-5"
vertical-offset="-5"
>
<paper-icon-button
icon="mdi:dots-vertical"
slot="dropdown-trigger"
/>
<paper-listbox slot="dropdown-content">
<paper-item disabled>Duplicate</paper-item>
<paper-item onTap={this.onDelete}>Delete</paper-item>
</paper-listbox>
</paper-menu-button>
</div>
<div class='card-content'>{content}</div>
</paper-card>
);
}
}

47
js/editor/script/wait.js Normal file
View File

@ -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 (
<div>
<paper-textarea
label="Wait Template"
name="wait_template"
value={wait_template}
onvalue-changed={this.onTemplateChange}
/>
<paper-input
label="Timeout (Optional)"
name="timeout"
value={timeout}
onChange={this.onChange}
/>
</div>
);
}
}
WaitAction.configKey = 'wait_template';
WaitAction.defaultConfig = {
wait_template: '',
timeout: '',
};

View File

@ -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
<JSONTextArea
label="Event Data"
value={event_data}
onChange={this.eventDataChanged}
/>
@ -39,3 +39,8 @@ export default class EventTrigger extends Component {
);
}
}
EventTrigger.defaultConfig = {
event_type: '',
event_data: {},
};

View File

@ -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 (
<div>
<label id="eventlabel">Event:</label>
<paper-radio-group
selected={event}
aria-labelledby="eventlabel"
onpaper-radio-group-changed={this.radioGroupPicked}
>
<paper-radio-button name="start">Start</paper-radio-button>
<paper-radio-button name="shutdown">Shutdown</paper-radio-button>
</paper-radio-group>
</div>
);
}
}
HassTrigger.defaultConfig = {
event: 'start'
};

View File

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

37
js/editor/trigger/mqtt.js Normal file
View File

@ -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 (
<div>
<paper-input
label="Topic"
name="topic"
value={topic}
onChange={this.onChange}
/>
<paper-input
label="Payload (Optional)"
name="payload"
value={payload}
onChange={this.onChange}
/>
</div>
);
}
}
MQTTTrigger.defaultConfig = {
topic: '',
payload: '',
};

View File

@ -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)<br />
<textarea
<paper-textarea
label="Value template (optional)"
name="value_template"
value={value_template}
style={{ width: '100%', height: 100 }}
onChange={this.onChange}
onvalue-changed={this.onChange}
/>
</div>
);
}
}
NumericStateTrigger.defaultConfig = {
entity_id: '',
above: '',
below: '',
value_template: '',
};

View File

@ -1,12 +1,12 @@
import { h, Component } from 'preact';
import { onChange } from './util';
import { onChangeEvent } from '../util';
export default class StateTrigger extends Component {
constructor() {
super();
this.onChange = onChange.bind(this);
this.onChange = onChangeEvent.bind(this, 'trigger');
}
/* eslint-disable camelcase */
@ -39,3 +39,9 @@ export default class StateTrigger extends Component {
);
}
}
StateTrigger.defaultConfig = {
entity_id: '',
from: '',
to: '',
};

49
js/editor/trigger/sun.js Normal file
View File

@ -0,0 +1,49 @@
import { h, Component } from 'preact';
import { onChangeEvent } from '../util';
export default class SunTrigger extends Component {
constructor() {
super();
this.onChange = onChangeEvent.bind(this, 'trigger');
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 { offset, event } = trigger;
return (
<div>
<label id="eventlabel">Event:</label>
<paper-radio-group
selected={event}
aria-labelledby="eventlabel"
onpaper-radio-group-changed={this.radioGroupPicked}
>
<paper-radio-button name="sunrise">Sunrise</paper-radio-button>
<paper-radio-button name="sunset">Sunset</paper-radio-button>
</paper-radio-group>
<paper-input
label="Offset (optional)"
name="offset"
value={offset}
onChange={this.onChange}
/>
</div>
);
}
}
SunTrigger.defaultConfig = {
event: 'sunrise',
offset: '',
};

View File

@ -0,0 +1,30 @@
import { h, Component } from 'preact';
import { onChangeEvent } from '../util';
export default class TemplateTrigger extends Component {
constructor() {
super();
this.onChange = onChangeEvent.bind(this, 'trigger');
}
render({ trigger }) {
/* eslint-disable camelcase */
const { value_template } = trigger;
return (
<div>
<paper-textarea
label="Value Template"
name="value_template"
value={value_template}
onvalue-changed={this.onChange}
/>
</div>
);
}
}
TemplateTrigger.defaultConfig = {
value_template: '',
};

30
js/editor/trigger/time.js Normal file
View File

@ -0,0 +1,30 @@
import { h, Component } from 'preact';
import { onChangeEvent } from '../util';
export default class TimeTrigger extends Component {
constructor() {
super();
this.onChange = onChangeEvent.bind(this, 'trigger');
}
/* eslint-disable camelcase */
render({ trigger }) {
const { at } = trigger;
return (
<div>
<paper-input
label="At"
name="at"
value={at}
onChange={this.onChange}
/>
</div>
);
}
}
TimeTrigger.defaultConfig = {
at: '',
};

View File

@ -0,0 +1,76 @@
import { h, Component } from 'preact';
import EventTrigger from './event';
import HassTrigger from './homeassistant';
import MQTTTrigger from './mqtt';
import NumericStateTrigger from './numeric_state';
import StateTrigger from './state';
import SunTrigger from './sun';
import TemplateTrigger from './template';
import TimeTrigger from './time';
import ZoneTrigger from './zone';
const TYPES = {
event: EventTrigger,
state: StateTrigger,
homeassistant: HassTrigger,
mqtt: MQTTTrigger,
numeric_state: NumericStateTrigger,
sun: SunTrigger,
template: TemplateTrigger,
time: TimeTrigger,
zone: ZoneTrigger,
};
const OPTIONS = Object.keys(TYPES).sort();
export default class TriggerEdit extends Component {
constructor() {
super();
this.typeChanged = this.typeChanged.bind(this);
}
typeChanged(ev) {
const type = ev.target.selectedItem.innerHTML;
if (type !== this.props.trigger.platform) {
this.props.onChange(this.props.index, {
platform: type,
...TYPES[type].defaultConfig
});
}
}
render({ index, trigger, onChange }) {
const Comp = TYPES[trigger.platform];
const selected = OPTIONS.indexOf(trigger.platform);
if (!Comp) {
return (
<div>
Unsupported platform: {trigger.platform}
<pre>{JSON.stringify(trigger, null, 2)}</pre>
</div>
);
}
return (
<div>
<paper-dropdown-menu-light label="Trigger Type" no-animations>
<paper-listbox
slot="dropdown-content"
selected={selected}
oniron-select={this.typeChanged}
>
{OPTIONS.map(opt => <paper-item>{opt}</paper-item>)}
</paper-listbox>
</paper-dropdown-menu-light>
<Comp
index={index}
trigger={trigger}
onChange={onChange}
/>
</div>
);
}
}

View File

@ -1,41 +1,14 @@
import { h, Component } from 'preact';
import EventTrigger from './event';
import StateTrigger from './state';
import NumericStateTrigger from './numeric_state';
const TYPES = {
event: EventTrigger,
state: StateTrigger,
homeassistant: null,
mqtt: null,
numeric_state: NumericStateTrigger,
sun: null,
template: null,
time: null,
zone: null,
};
const OPTIONS = Object.keys(TYPES).sort();
import TriggerEdit from './trigger_edit';
export default class TriggerRow extends Component {
constructor() {
super();
this.typeChanged = this.typeChanged.bind(this);
this.onDelete = this.onDelete.bind(this);
}
typeChanged(ev) {
const type = ev.target.selectedItem.innerHTML;
if (type !== this.props.trigger.platform) {
this.props.onChange(this.props.index, {
platform: type,
});
}
}
onDelete() {
// eslint-disable-next-line
if (confirm('Sure you want to delete?')) {
@ -43,40 +16,7 @@ export default class TriggerRow extends Component {
}
}
render({ index, trigger, onChange }) {
const Comp = TYPES[trigger.platform];
const selected = OPTIONS.indexOf(trigger.platform);
let content;
if (Comp) {
content = (
<div>
<paper-dropdown-menu-light label="Trigger Type" no-animations>
<paper-listbox
slot="dropdown-content"
selected={selected}
oniron-select={this.typeChanged}
>
{OPTIONS.map(opt => <paper-item>{opt}</paper-item>)}
</paper-listbox>
</paper-dropdown-menu-light>
<Comp
index={index}
trigger={trigger}
onChange={onChange}
/>
</div>
);
} else {
content = (
<div>
Unsupported platform: {trigger.platform}
<pre>{JSON.stringify(trigger, null, 2)}</pre>
</div>
);
}
render(props) {
return (
<paper-card>
<div class='card-menu'>
@ -96,7 +36,9 @@ export default class TriggerRow extends Component {
</paper-listbox>
</paper-menu-button>
</div>
<div class='card-content'>{content}</div>
<div class='card-content'>
<TriggerEdit {...props} />
</div>
</paper-card>
);
}

View File

@ -1,11 +0,0 @@
export function onChange(ev) {
const trigger = { ...this.props.trigger };
if (ev.target.value) {
trigger[ev.target.name] = ev.target.value;
} else {
delete trigger[ev.target.name];
}
this.props.onChange(this.props.index, trigger);
}

55
js/editor/trigger/zone.js Normal file
View File

@ -0,0 +1,55 @@
import { h, Component } from 'preact';
import { onChangeEvent } from '../util';
export default class ZoneTrigger extends Component {
constructor() {
super();
this.onChange = onChangeEvent.bind(this, 'trigger');
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 { entity_id, zone, event } = trigger;
return (
<div>
<paper-input
label="Entity Id"
name="entity_id"
value={entity_id}
onChange={this.onChange}
/>
<paper-input
label="Zone"
name="zone"
value={zone}
onChange={this.onChange}
/>
<label id="eventlabel">Event:</label>
<paper-radio-group
selected={event}
aria-labelledby="eventlabel"
onpaper-radio-group-changed={this.radioGroupPicked}
>
<paper-radio-button name="enter">Enter</paper-radio-button>
<paper-radio-button name="leave">Leave</paper-radio-button>
</paper-radio-group>
</div>
);
}
}
ZoneTrigger.defaultConfig = {
entity_id: '',
zone: '',
event: 'enter',
};

View File

@ -1,3 +1,17 @@
export function validEntityId(entityId) {
return /^(\w+)\.(\w+)$/.test(entityId);
}
export function onChangeEvent(prop, ev) {
const data = { ...this.props[prop] };
if (ev.target.value === data[ev.target.name]) {
return;
} else if (ev.target.value) {
data[ev.target.name] = ev.target.value;
} else {
delete data[ev.target.name];
}
this.props.onChange(this.props.index, data);
}

View File

@ -11,8 +11,8 @@
"gulp": "gulp",
"build": "BUILD_DEV=0 gulp",
"build_demo": "BUILD_DEV=0 BUILD_DEMO=1 gulp",
"dev": "npm run gulp ru_all",
"dev-watch": "npm run gulp watch_ru_all",
"dev": "npm run gulp ru_all gen-service-worker",
"dev-watch": "npm run gulp watch_ru_all gen-service-worker",
"lint_js": "eslint src panels js --ext js,html",
"lint_html": "ls -1 src/home-assistant.html panels/**/ha-panel-*.html | xargs polymer lint --input",
"test": "npm run lint_js && npm run lint_html"

View File

@ -7,6 +7,9 @@
<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-input/paper-textarea.html">
<link rel="import" href="../../../bower_components/paper-radio-button/paper-radio-button.html">
<link rel="import" href="../../../bower_components/paper-radio-group/paper-radio-group.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">