mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-21 16:26:43 +00:00
Entity dropdown improvement (#674)
* Ignore hass changes while dropdown is open * Upgrade vaadin-combo-box * Fix styling on dev-service panel * Fix styling for ha-entity-dropdown * Fix height vaadin-combo-box dropdown * Rename ha-entity-dropdown to ha-entity-picker * More entity improvement (#675) * Update script and automation editor to use entity picker * Add entity and service picker to service dev panel * Lint
This commit is contained in:
parent
28457747e7
commit
0707528bd7
@ -48,6 +48,7 @@
|
|||||||
"import/prefer-default-export": 0,
|
"import/prefer-default-export": 0,
|
||||||
"import/no-unresolved": 0,
|
"import/no-unresolved": 0,
|
||||||
"import/extensions": [2, "ignorePackages"],
|
"import/extensions": [2, "ignorePackages"],
|
||||||
|
"object-curly-newline": 0,
|
||||||
"react/jsx-no-bind": [2, { "ignoreRefs": true }],
|
"react/jsx-no-bind": [2, { "ignoreRefs": true }],
|
||||||
"react/jsx-no-duplicate-props": 2,
|
"react/jsx-no-duplicate-props": 2,
|
||||||
"react/self-closing-comp": 2,
|
"react/self-closing-comp": 2,
|
||||||
|
@ -51,7 +51,7 @@
|
|||||||
"paper-toast": "PolymerElements/paper-toast#^2.0.0",
|
"paper-toast": "PolymerElements/paper-toast#^2.0.0",
|
||||||
"paper-toggle-button": "PolymerElements/paper-toggle-button#^2.0.0",
|
"paper-toggle-button": "PolymerElements/paper-toggle-button#^2.0.0",
|
||||||
"polymer": "^2.1.1",
|
"polymer": "^2.1.1",
|
||||||
"vaadin-combo-box": "vaadin/vaadin-combo-box#^2.0.0",
|
"vaadin-combo-box": "vaadin/vaadin-combo-box#^3.0.2",
|
||||||
"vaadin-date-picker": "vaadin/vaadin-date-picker#^2.0.0",
|
"vaadin-date-picker": "vaadin/vaadin-date-picker#^2.0.0",
|
||||||
"web-animations-js": "^2.2.5",
|
"web-animations-js": "^2.2.5",
|
||||||
"webcomponentsjs": "^1.0.10"
|
"webcomponentsjs": "^1.0.10"
|
||||||
|
@ -42,7 +42,7 @@ export default class Automation extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render({ automation, isWide }) {
|
render({ automation, isWide, hass }) {
|
||||||
const {
|
const {
|
||||||
alias, trigger, condition, action
|
alias, trigger, condition, action
|
||||||
} = automation;
|
} = automation;
|
||||||
@ -77,7 +77,11 @@ export default class Automation extends Component {
|
|||||||
Learn more about triggers.
|
Learn more about triggers.
|
||||||
</a></p>
|
</a></p>
|
||||||
</span>
|
</span>
|
||||||
<Trigger trigger={trigger} onChange={this.triggerChanged} />
|
<Trigger
|
||||||
|
trigger={trigger}
|
||||||
|
onChange={this.triggerChanged}
|
||||||
|
hass={hass}
|
||||||
|
/>
|
||||||
</ha-config-section>
|
</ha-config-section>
|
||||||
|
|
||||||
<ha-config-section is-wide={isWide}>
|
<ha-config-section is-wide={isWide}>
|
||||||
@ -93,7 +97,11 @@ export default class Automation extends Component {
|
|||||||
Learn more about conditions.
|
Learn more about conditions.
|
||||||
</a></p>
|
</a></p>
|
||||||
</span>
|
</span>
|
||||||
<Condition condition={condition || []} onChange={this.conditionChanged} />
|
<Condition
|
||||||
|
condition={condition || []}
|
||||||
|
onChange={this.conditionChanged}
|
||||||
|
hass={hass}
|
||||||
|
/>
|
||||||
</ha-config-section>
|
</ha-config-section>
|
||||||
|
|
||||||
<ha-config-section is-wide={isWide}>
|
<ha-config-section is-wide={isWide}>
|
||||||
@ -104,7 +112,11 @@ export default class Automation extends Component {
|
|||||||
Learn more about actions.
|
Learn more about actions.
|
||||||
</a></p>
|
</a></p>
|
||||||
</span>
|
</span>
|
||||||
<Script script={action} onChange={this.actionChanged} />
|
<Script
|
||||||
|
script={action}
|
||||||
|
onChange={this.actionChanged}
|
||||||
|
hass={hass}
|
||||||
|
/>
|
||||||
</ha-config-section>
|
</ha-config-section>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -32,7 +32,7 @@ export default class Trigger extends Component {
|
|||||||
this.props.onChange(trigger);
|
this.props.onChange(trigger);
|
||||||
}
|
}
|
||||||
|
|
||||||
render({ trigger }) {
|
render({ trigger, hass }) {
|
||||||
return (
|
return (
|
||||||
<div class="triggers">
|
<div class="triggers">
|
||||||
{trigger.map((trg, idx) => (
|
{trigger.map((trg, idx) => (
|
||||||
@ -40,6 +40,7 @@ export default class Trigger extends Component {
|
|||||||
index={idx}
|
index={idx}
|
||||||
trigger={trg}
|
trigger={trg}
|
||||||
onChange={this.triggerChanged}
|
onChange={this.triggerChanged}
|
||||||
|
hass={hass}
|
||||||
/>))}
|
/>))}
|
||||||
<paper-card>
|
<paper-card>
|
||||||
<div class='card-actions add-card'>
|
<div class='card-actions add-card'>
|
||||||
|
@ -7,20 +7,29 @@ export default class NumericStateTrigger extends Component {
|
|||||||
super();
|
super();
|
||||||
|
|
||||||
this.onChange = onChangeEvent.bind(this, 'trigger');
|
this.onChange = onChangeEvent.bind(this, 'trigger');
|
||||||
|
this.entityPicked = this.entityPicked.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
entityPicked(ev) {
|
||||||
|
this.props.onChange(this.props.index, {
|
||||||
|
...this.props.trigger,
|
||||||
|
entity_id: ev.target.value,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
render({ trigger }) {
|
render({ trigger, hass }) {
|
||||||
const {
|
const {
|
||||||
value_template, entity_id, below, above
|
value_template, entity_id, below, above
|
||||||
} = trigger;
|
} = trigger;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<paper-input
|
<ha-entity-picker
|
||||||
label="Entity Id"
|
|
||||||
name="entity_id"
|
|
||||||
value={entity_id}
|
value={entity_id}
|
||||||
onChange={this.onChange}
|
onChange={this.entityPicked}
|
||||||
|
hass={hass}
|
||||||
|
allowCustomEntity
|
||||||
/>
|
/>
|
||||||
<paper-input
|
<paper-input
|
||||||
label="Above"
|
label="Above"
|
||||||
|
@ -7,20 +7,28 @@ export default class StateTrigger extends Component {
|
|||||||
super();
|
super();
|
||||||
|
|
||||||
this.onChange = onChangeEvent.bind(this, 'trigger');
|
this.onChange = onChangeEvent.bind(this, 'trigger');
|
||||||
|
this.entityPicked = this.entityPicked.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
entityPicked(ev) {
|
||||||
|
this.props.onChange(this.props.index, {
|
||||||
|
...this.props.trigger,
|
||||||
|
entity_id: ev.target.value,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
render({ trigger }) {
|
render({ trigger, hass }) {
|
||||||
const { entity_id, to } = trigger;
|
const { entity_id, to } = trigger;
|
||||||
const trgFrom = trigger.from;
|
const trgFrom = trigger.from;
|
||||||
const trgFor = trigger.for;
|
const trgFor = trigger.for;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<paper-input
|
<ha-entity-picker
|
||||||
label="Entity Id"
|
|
||||||
name="entity_id"
|
|
||||||
value={entity_id}
|
value={entity_id}
|
||||||
onChange={this.onChange}
|
onChange={this.entityPicked}
|
||||||
|
hass={hass}
|
||||||
|
allowCustomEntity
|
||||||
/>
|
/>
|
||||||
<paper-input
|
<paper-input
|
||||||
label="From"
|
label="From"
|
||||||
|
@ -42,7 +42,7 @@ export default class TriggerEdit extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render({ index, trigger, onChange }) {
|
render({ index, trigger, onChange, hass }) {
|
||||||
const Comp = TYPES[trigger.platform];
|
const Comp = TYPES[trigger.platform];
|
||||||
const selected = OPTIONS.indexOf(trigger.platform);
|
const selected = OPTIONS.indexOf(trigger.platform);
|
||||||
|
|
||||||
@ -69,6 +69,7 @@ export default class TriggerEdit extends Component {
|
|||||||
index={index}
|
index={index}
|
||||||
trigger={trigger}
|
trigger={trigger}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
hass={hass}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
import { h, Component } from 'preact';
|
import { h, Component } from 'preact';
|
||||||
|
|
||||||
import { onChangeEvent } from '../../common/util/event.js';
|
import { onChangeEvent } from '../../common/util/event.js';
|
||||||
|
import { hasLocation } from '../../common/util/location.js';
|
||||||
|
import computeDomain from '../../common/util/compute_domain.js';
|
||||||
|
|
||||||
|
function zoneAndLocationFilter(stateObj) {
|
||||||
|
return hasLocation(stateObj) && computeDomain(stateObj) !== 'zone';
|
||||||
|
}
|
||||||
|
|
||||||
export default class ZoneTrigger extends Component {
|
export default class ZoneTrigger extends Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -8,6 +14,22 @@ export default class ZoneTrigger extends Component {
|
|||||||
|
|
||||||
this.onChange = onChangeEvent.bind(this, 'trigger');
|
this.onChange = onChangeEvent.bind(this, 'trigger');
|
||||||
this.radioGroupPicked = this.radioGroupPicked.bind(this);
|
this.radioGroupPicked = this.radioGroupPicked.bind(this);
|
||||||
|
this.entityPicked = this.entityPicked.bind(this);
|
||||||
|
this.zonePicked = this.zonePicked.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
entityPicked(ev) {
|
||||||
|
this.props.onChange(this.props.index, {
|
||||||
|
...this.props.trigger,
|
||||||
|
entity_id: ev.target.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
zonePicked(ev) {
|
||||||
|
this.props.onChange(this.props.index, {
|
||||||
|
...this.props.trigger,
|
||||||
|
zone: ev.target.value,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
radioGroupPicked(ev) {
|
radioGroupPicked(ev) {
|
||||||
@ -18,21 +40,25 @@ export default class ZoneTrigger extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
render({ trigger }) {
|
render({ trigger, hass }) {
|
||||||
const { entity_id, zone, event } = trigger;
|
const { entity_id, zone, event } = trigger;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<paper-input
|
<ha-entity-picker
|
||||||
label="Entity Id"
|
label='Entity with location'
|
||||||
name="entity_id"
|
|
||||||
value={entity_id}
|
value={entity_id}
|
||||||
onChange={this.onChange}
|
onChange={this.entityPicked}
|
||||||
|
hass={hass}
|
||||||
|
allowCustomEntity
|
||||||
|
entityFilter={zoneAndLocationFilter}
|
||||||
/>
|
/>
|
||||||
<paper-input
|
<ha-entity-picker
|
||||||
label="Zone"
|
label='Zone'
|
||||||
name="zone"
|
|
||||||
value={zone}
|
value={zone}
|
||||||
onChange={this.onChange}
|
onChange={this.zonePicked}
|
||||||
|
hass={hass}
|
||||||
|
allowCustomEntity
|
||||||
|
domainFilter='zone'
|
||||||
/>
|
/>
|
||||||
<label id="eventlabel">Event:</label>
|
<label id="eventlabel">Event:</label>
|
||||||
<paper-radio-group
|
<paper-radio-group
|
||||||
|
@ -36,7 +36,7 @@ export default class ConditionRow extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render({ index, condition, onChange }) {
|
render({ index, condition, onChange, hass }) {
|
||||||
const Comp = TYPES[condition.condition];
|
const Comp = TYPES[condition.condition];
|
||||||
const selected = OPTIONS.indexOf(condition.condition);
|
const selected = OPTIONS.indexOf(condition.condition);
|
||||||
|
|
||||||
@ -64,6 +64,7 @@ export default class ConditionRow extends Component {
|
|||||||
index={index}
|
index={index}
|
||||||
condition={condition}
|
condition={condition}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
hass={hass}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -30,7 +30,7 @@ export default class Condition extends Component {
|
|||||||
this.props.onChange(condition);
|
this.props.onChange(condition);
|
||||||
}
|
}
|
||||||
|
|
||||||
render({ condition }) {
|
render({ condition, hass }) {
|
||||||
return (
|
return (
|
||||||
<div class="triggers">
|
<div class="triggers">
|
||||||
{condition.map((cnd, idx) => (
|
{condition.map((cnd, idx) => (
|
||||||
@ -38,6 +38,7 @@ export default class Condition extends Component {
|
|||||||
index={idx}
|
index={idx}
|
||||||
condition={cnd}
|
condition={cnd}
|
||||||
onChange={this.conditionChanged}
|
onChange={this.conditionChanged}
|
||||||
|
hass={hass}
|
||||||
/>))}
|
/>))}
|
||||||
<paper-card>
|
<paper-card>
|
||||||
<div class='card-actions add-card'>
|
<div class='card-actions add-card'>
|
||||||
|
@ -7,20 +7,28 @@ export default class NumericStateCondition extends Component {
|
|||||||
super();
|
super();
|
||||||
|
|
||||||
this.onChange = onChangeEvent.bind(this, 'condition');
|
this.onChange = onChangeEvent.bind(this, 'condition');
|
||||||
|
this.entityPicked = this.entityPicked.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
entityPicked(ev) {
|
||||||
|
this.props.onChange(this.props.index, {
|
||||||
|
...this.props.condition,
|
||||||
|
entity_id: ev.target.value,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
render({ condition }) {
|
render({ condition, hass }) {
|
||||||
const {
|
const {
|
||||||
value_template, entity_id, below, above
|
value_template, entity_id, below, above
|
||||||
} = condition;
|
} = condition;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<paper-input
|
<ha-entity-picker
|
||||||
label="Entity Id"
|
|
||||||
name="entity_id"
|
|
||||||
value={entity_id}
|
value={entity_id}
|
||||||
onChange={this.onChange}
|
onChange={this.entityPicked}
|
||||||
|
hass={hass}
|
||||||
|
allowCustomEntity
|
||||||
/>
|
/>
|
||||||
<paper-input
|
<paper-input
|
||||||
label="Above"
|
label="Above"
|
||||||
|
@ -7,19 +7,27 @@ export default class StateCondition extends Component {
|
|||||||
super();
|
super();
|
||||||
|
|
||||||
this.onChange = onChangeEvent.bind(this, 'condition');
|
this.onChange = onChangeEvent.bind(this, 'condition');
|
||||||
|
this.entityPicked = this.entityPicked.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
entityPicked(ev) {
|
||||||
|
this.props.onChange(this.props.index, {
|
||||||
|
...this.props.condition,
|
||||||
|
entity_id: ev.target.value,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
render({ condition }) {
|
render({ condition, hass }) {
|
||||||
const { entity_id, state } = condition;
|
const { entity_id, state } = condition;
|
||||||
const cndFor = condition.for;
|
const cndFor = condition.for;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<paper-input
|
<ha-entity-picker
|
||||||
label="Entity Id"
|
|
||||||
name="entity_id"
|
|
||||||
value={entity_id}
|
value={entity_id}
|
||||||
onChange={this.onChange}
|
onChange={this.entityPicked}
|
||||||
|
hass={hass}
|
||||||
|
allowCustomEntity
|
||||||
/>
|
/>
|
||||||
<paper-input
|
<paper-input
|
||||||
label="State"
|
label="State"
|
||||||
|
@ -1,30 +1,56 @@
|
|||||||
import { h, Component } from 'preact';
|
import { h, Component } from 'preact';
|
||||||
|
|
||||||
import { onChangeEvent } from '../../util/event.js';
|
import { onChangeEvent } from '../../util/event.js';
|
||||||
|
import { hasLocation } from '../../util/location.js';
|
||||||
|
import computeDomain from '../../util/compute_domain.js';
|
||||||
|
|
||||||
|
function zoneAndLocationFilter(stateObj) {
|
||||||
|
return hasLocation(stateObj) && computeDomain(stateObj) !== 'zone';
|
||||||
|
}
|
||||||
|
|
||||||
export default class ZoneCondition extends Component {
|
export default class ZoneCondition extends Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.onChange = onChangeEvent.bind(this, 'condition');
|
this.onChange = onChangeEvent.bind(this, 'condition');
|
||||||
|
this.entityPicked = this.entityPicked.bind(this);
|
||||||
|
this.zonePicked = this.zonePicked.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
entityPicked(ev) {
|
||||||
|
this.props.onChange(this.props.index, {
|
||||||
|
...this.props.condition,
|
||||||
|
entity_id: ev.target.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
zonePicked(ev) {
|
||||||
|
this.props.onChange(this.props.index, {
|
||||||
|
...this.props.condition,
|
||||||
|
zone: ev.target.value,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
render({ condition }) {
|
render({ condition, hass }) {
|
||||||
const { entity_id, zone } = condition;
|
const { entity_id, zone } = condition;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<paper-input
|
<ha-entity-picker
|
||||||
label="Entity Id"
|
label='Entity with location'
|
||||||
name="entity_id"
|
|
||||||
value={entity_id}
|
value={entity_id}
|
||||||
onChange={this.onChange}
|
onChange={this.entityPicked}
|
||||||
|
hass={hass}
|
||||||
|
allowCustomEntity
|
||||||
|
entityFilter={zoneAndLocationFilter}
|
||||||
/>
|
/>
|
||||||
<paper-input
|
<ha-entity-picker
|
||||||
label="Zone entity id"
|
label='Zone'
|
||||||
name="zone"
|
|
||||||
value={zone}
|
value={zone}
|
||||||
onChange={this.onChange}
|
onChange={this.zonePicked}
|
||||||
|
hass={hass}
|
||||||
|
allowCustomEntity
|
||||||
|
domainFilter='zone'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -42,7 +42,7 @@ export default class Action extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render({ index, action, onChange }) {
|
render({ index, action, onChange, hass }) {
|
||||||
const type = getType(action);
|
const type = getType(action);
|
||||||
const Comp = type && TYPES[type];
|
const Comp = type && TYPES[type];
|
||||||
const selected = OPTIONS.indexOf(type);
|
const selected = OPTIONS.indexOf(type);
|
||||||
@ -70,6 +70,7 @@ export default class Action extends Component {
|
|||||||
index={index}
|
index={index}
|
||||||
action={action}
|
action={action}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
hass={hass}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,16 +1,22 @@
|
|||||||
import { h, Component } from 'preact';
|
import { h, Component } from 'preact';
|
||||||
|
|
||||||
import JSONTextArea from '../json_textarea.js';
|
import JSONTextArea from '../json_textarea.js';
|
||||||
import { onChangeEvent } from '../../util/event.js';
|
|
||||||
|
|
||||||
export default class CallServiceAction extends Component {
|
export default class CallServiceAction extends Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.onChange = onChangeEvent.bind(this, 'action');
|
this.serviceChanged = this.serviceChanged.bind(this);
|
||||||
this.serviceDataChanged = this.serviceDataChanged.bind(this);
|
this.serviceDataChanged = this.serviceDataChanged.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
serviceChanged(ev) {
|
||||||
|
this.props.onChange(this.props.index, {
|
||||||
|
...this.props.action,
|
||||||
|
service: ev.target.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
serviceDataChanged(data) {
|
serviceDataChanged(data) {
|
||||||
this.props.onChange(this.props.index, {
|
this.props.onChange(this.props.index, {
|
||||||
...this.props.action,
|
...this.props.action,
|
||||||
@ -18,21 +24,15 @@ export default class CallServiceAction extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render({ action }) {
|
render({ action, hass }) {
|
||||||
const { alias, service, data } = action;
|
const { service, data } = action;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<paper-input
|
<ha-service-picker
|
||||||
label="Alias"
|
hass={hass}
|
||||||
name="alias"
|
|
||||||
value={alias}
|
|
||||||
onChange={this.onChange}
|
|
||||||
/>
|
|
||||||
<paper-input
|
|
||||||
label="Service"
|
|
||||||
name="service"
|
|
||||||
value={service}
|
value={service}
|
||||||
onChange={this.onChange}
|
onChange={this.serviceChanged}
|
||||||
/>
|
/>
|
||||||
<JSONTextArea
|
<JSONTextArea
|
||||||
label="Service Data"
|
label="Service Data"
|
||||||
|
@ -5,12 +5,13 @@ import ConditionEdit from '../condition/condition_edit.js';
|
|||||||
|
|
||||||
export default class ConditionAction extends Component {
|
export default class ConditionAction extends Component {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
render({ action, index, onChange }) {
|
render({ action, index, onChange, hass }) {
|
||||||
return (
|
return (
|
||||||
<ConditionEdit
|
<ConditionEdit
|
||||||
condition={action}
|
condition={action}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
index={index}
|
index={index}
|
||||||
|
hass={hass}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ export default class Script extends Component {
|
|||||||
this.props.onChange(script);
|
this.props.onChange(script);
|
||||||
}
|
}
|
||||||
|
|
||||||
render({ script }) {
|
render({ script, hass }) {
|
||||||
return (
|
return (
|
||||||
<div class="script">
|
<div class="script">
|
||||||
{script.map((act, idx) => (
|
{script.map((act, idx) => (
|
||||||
@ -38,6 +38,7 @@ export default class Script extends Component {
|
|||||||
index={idx}
|
index={idx}
|
||||||
action={act}
|
action={act}
|
||||||
onChange={this.actionChanged}
|
onChange={this.actionChanged}
|
||||||
|
hass={hass}
|
||||||
/>))}
|
/>))}
|
||||||
<paper-card>
|
<paper-card>
|
||||||
<div class='card-actions add-card'>
|
<div class='card-actions add-card'>
|
||||||
|
4
js/common/util/location.js
Normal file
4
js/common/util/location.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export function hasLocation(stateObj) {
|
||||||
|
return ('latitude' in stateObj.attributes &&
|
||||||
|
'longitude' in stateObj.attributes);
|
||||||
|
}
|
@ -16,6 +16,9 @@
|
|||||||
<link rel="import" href="../../../bower_components/paper-fab/paper-fab.html">
|
<link rel="import" href="../../../bower_components/paper-fab/paper-fab.html">
|
||||||
<link rel="import" href="../../../bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
|
<link rel="import" href="../../../bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
|
||||||
|
|
||||||
|
<link rel='import' href='../../../src/components/entity/ha-entity-picker.html'>
|
||||||
|
<link rel='import' href='../../../src/components/ha-combo-box.html'>
|
||||||
|
<link rel='import' href='../../../src/components/ha-service-picker.html'>
|
||||||
<link rel='import' href='../../../src/util/hass-mixins.html'>
|
<link rel='import' href='../../../src/util/hass-mixins.html'>
|
||||||
|
|
||||||
<link rel="import" href="../ha-config-section.html">
|
<link rel="import" href="../ha-config-section.html">
|
||||||
@ -117,6 +120,7 @@ class HaAutomationEditor extends window.hassMixins.EventsMixin(Polymer.Element)
|
|||||||
return {
|
return {
|
||||||
hass: {
|
hass: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
observer: '_updateComponent',
|
||||||
},
|
},
|
||||||
|
|
||||||
narrow: {
|
narrow: {
|
||||||
@ -160,7 +164,7 @@ class HaAutomationEditor extends window.hassMixins.EventsMixin(Polymer.Element)
|
|||||||
|
|
||||||
isWide: {
|
isWide: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
observer: 'isWideChanged',
|
observer: '_updateComponent',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -169,6 +173,7 @@ class HaAutomationEditor extends window.hassMixins.EventsMixin(Polymer.Element)
|
|||||||
super.ready();
|
super.ready();
|
||||||
this.configChanged = this.configChanged.bind(this);
|
this.configChanged = this.configChanged.bind(this);
|
||||||
this._rendered = null;
|
this._rendered = null;
|
||||||
|
this._renderScheduled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
@ -184,13 +189,13 @@ class HaAutomationEditor extends window.hassMixins.EventsMixin(Polymer.Element)
|
|||||||
this.config = config;
|
this.config = config;
|
||||||
this.errors = null;
|
this.errors = null;
|
||||||
this.dirty = true;
|
this.dirty = true;
|
||||||
this._updateComponent(config);
|
this._updateComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
automationChanged(newVal, oldVal) {
|
automationChanged(newVal, oldVal) {
|
||||||
if (!newVal) return;
|
if (!newVal) return;
|
||||||
if (!this.hass) {
|
if (!this.hass) {
|
||||||
setTimeout(this.automationChanged.bind(this, newVal, oldVal), 0);
|
setTimeout(() => this.automationChanged(newVal, oldVal), 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (oldVal && oldVal.attributes.id === newVal.attributes.id) {
|
if (oldVal && oldVal.attributes.id === newVal.attributes.id) {
|
||||||
@ -230,11 +235,6 @@ class HaAutomationEditor extends window.hassMixins.EventsMixin(Polymer.Element)
|
|||||||
this._updateComponent();
|
this._updateComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
isWideChanged() {
|
|
||||||
if (this.config === null) return;
|
|
||||||
this._updateComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
backTapped() {
|
backTapped() {
|
||||||
if (this.dirty &&
|
if (this.dirty &&
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
@ -245,11 +245,17 @@ class HaAutomationEditor extends window.hassMixins.EventsMixin(Polymer.Element)
|
|||||||
}
|
}
|
||||||
|
|
||||||
_updateComponent() {
|
_updateComponent() {
|
||||||
this._rendered = window.AutomationEditor(this.$.root, {
|
if (this._renderScheduled || !this.hass || !this.config) return;
|
||||||
automation: this.config,
|
this._renderScheduled = true;
|
||||||
onChange: this.configChanged,
|
Promise.resolve().then(() => {
|
||||||
isWide: this.isWide,
|
this._rendered = window.AutomationEditor(this.$.root, {
|
||||||
}, this._rendered);
|
automation: this.config,
|
||||||
|
onChange: this.configChanged,
|
||||||
|
isWide: this.isWide,
|
||||||
|
hass: this.hass,
|
||||||
|
}, this._rendered);
|
||||||
|
this._renderScheduled = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
saveAutomation() {
|
saveAutomation() {
|
||||||
|
@ -8,9 +8,9 @@
|
|||||||
<link rel='import' href='../../bower_components/app-layout/app-toolbar/app-toolbar.html'>
|
<link rel='import' href='../../bower_components/app-layout/app-toolbar/app-toolbar.html'>
|
||||||
<link rel="import" href="../../bower_components/app-storage/app-localstorage/app-localstorage-document.html">
|
<link rel="import" href="../../bower_components/app-storage/app-localstorage/app-localstorage-document.html">
|
||||||
|
|
||||||
<link rel="import" href="../../bower_components/vaadin-combo-box/vaadin-combo-box.html">
|
|
||||||
|
|
||||||
<link rel='import' href='../../src/components/ha-menu-button.html'>
|
<link rel='import' href='../../src/components/ha-menu-button.html'>
|
||||||
|
<link rel='import' href='../../src/components/entity/ha-entity-picker.html'>
|
||||||
|
<link rel='import' href='../../src/components/ha-service-picker.html'>
|
||||||
<link rel='import' href='../../src/resources/ha-style.html'>
|
<link rel='import' href='../../src/resources/ha-style.html'>
|
||||||
|
|
||||||
<dom-module id='ha-panel-dev-service'>
|
<dom-module id='ha-panel-dev-service'>
|
||||||
@ -28,7 +28,7 @@
|
|||||||
|
|
||||||
.ha-form {
|
.ha-form {
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
max-width: 500px;
|
max-width: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
@ -68,6 +68,14 @@
|
|||||||
h1 {
|
h1 {
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: var(--google-red-500);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<app-header-layout has-scrolling-region>
|
<app-header-layout has-scrolling-region>
|
||||||
@ -79,69 +87,77 @@
|
|||||||
</app-header>
|
</app-header>
|
||||||
|
|
||||||
<app-localstorage-document
|
<app-localstorage-document
|
||||||
key='panel-dev-service-state-domain'
|
key='panel-dev-service-state-domain-service'
|
||||||
data='{{domain}}'>
|
data='{{domainService}}'>
|
||||||
</app-localstorage-document>
|
</app-localstorage-document>
|
||||||
<app-localstorage-document
|
<app-localstorage-document
|
||||||
key='[[computeServiceKey(domain)]]'
|
key='[[_computeServicedataKey(domainService)]]'
|
||||||
data='{{service}}'>
|
|
||||||
</app-localstorage-document>
|
|
||||||
<app-localstorage-document
|
|
||||||
key='[[computeServicedataKey(domain, service)]]'
|
|
||||||
data='{{serviceData}}'>
|
data='{{serviceData}}'>
|
||||||
</app-localstorage-document>
|
</app-localstorage-document>
|
||||||
|
|
||||||
<div class='content'>
|
<div class='content'>
|
||||||
<p>
|
<p>
|
||||||
Call a service from a component.
|
The service dev tool allows you to call any available service in Home Assistant.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class='ha-form'>
|
<div class='ha-form'>
|
||||||
<vaadin-combo-box label='Domain' items='[[computeDomains(serviceDomains)]]' value='{{domain}}'></vaadin-combo-box>
|
<ha-service-picker
|
||||||
<vaadin-combo-box label='Service' items='[[computeServices(serviceDomains, domain)]]' value='{{service}}'></vaadin-combo-box>
|
hass='[[hass]]'
|
||||||
|
value='{{domainService}}'
|
||||||
|
></ha-service-picker>
|
||||||
|
<template is='dom-if' if='[[_computeHasEntity(_attributes)]]'>
|
||||||
|
<ha-entity-picker
|
||||||
|
hass='[[hass]]'
|
||||||
|
value='[[_computeEntityValue(parsedJSON)]]'
|
||||||
|
on-change='_entityPicked'
|
||||||
|
disabled='[[!validJSON]]'
|
||||||
|
domain-filter='[[_computeEntityDomainFilter(_domain)]]'
|
||||||
|
allow-custom-entity
|
||||||
|
></ha-entity-picker>
|
||||||
|
</template>
|
||||||
<paper-textarea
|
<paper-textarea
|
||||||
always-float-label
|
always-float-label
|
||||||
label='Service Data (JSON, optional)'
|
label='Service Data (JSON, optional)'
|
||||||
value='{{serviceData}}'
|
value='{{serviceData}}'
|
||||||
></paper-textarea>
|
></paper-textarea>
|
||||||
<paper-button on-tap='callService' raised>Call Service</paper-button>
|
<paper-button
|
||||||
|
on-tap='_callService'
|
||||||
|
raised
|
||||||
|
disabled='[[!validJSON]]'
|
||||||
|
>Call Service</paper-button>
|
||||||
|
<template is='dom-if' if='[[!validJSON]]'>
|
||||||
|
<span class='error'>Invalid JSON</span>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template is='dom-if' if='[[!domain]]'>
|
<template is='dom-if' if='[[!domainService]]'>
|
||||||
<h1>Select a domain and service to see the description</h1>
|
<h1>Select a service to see the description</h1>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template is='dom-if' if='[[domain]]'>
|
<template is='dom-if' if='[[domainService]]'>
|
||||||
<template is='dom-if' if='[[!service]]'>
|
<template is='dom-if' if='[[!_description]]'>
|
||||||
<h1>Select a service to see the description</h1>
|
<h1>No description is available</h1>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
<template is='dom-if' if='[[_description]]'>
|
||||||
|
<h3>[[_description]]</h3>
|
||||||
|
|
||||||
<template is='dom-if' if='[[domain]]'>
|
<table class='attributes'>
|
||||||
<template is='dom-if' if='[[service]]'>
|
<tr>
|
||||||
<template is='dom-if' if='[[!_description]]'>
|
<th>Parameter</th>
|
||||||
<h1>No description is available</h1>
|
<th>Description</th>
|
||||||
</template>
|
<th>Example</th>
|
||||||
<template is='dom-if' if='[[_description]]'>
|
</tr>
|
||||||
<h3>[[_description]]</h3>
|
<template is='dom-if' if='[[!_attributes.length]]'>
|
||||||
</template>
|
<tr><td colspan='3'>This service takes no parameters.</td></tr>
|
||||||
<template is='dom-if' if='[[_attributes.length]]'>
|
</template>
|
||||||
<h1>Valid Parameters</h1>
|
<template is='dom-repeat' items='[[_attributes]]' as='attribute'>
|
||||||
<table class='attributes'>
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>Parameter</th>
|
<td><pre>[[attribute.key]]</pre></td>
|
||||||
<th>Description</th>
|
<td>[[attribute.description]]</td>
|
||||||
<th>Example</th>
|
<td>[[attribute.example]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
<template is='dom-repeat' items='[[_attributes]]' as='attribute'>
|
</template>
|
||||||
<tr>
|
</table>
|
||||||
<td><pre>[[attribute.key]]</pre></td>
|
|
||||||
<td>[[attribute.description]]</td>
|
|
||||||
<td>[[attribute.example]]</td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
|
||||||
</table>
|
|
||||||
</template>
|
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@ -151,128 +167,143 @@
|
|||||||
</dom-module>
|
</dom-module>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
class HaPanelDevService extends Polymer.Element {
|
{
|
||||||
static get is() { return 'ha-panel-dev-service'; }
|
const ERROR_SENTINEL = {};
|
||||||
|
class HaPanelDevService extends Polymer.Element {
|
||||||
|
static get is() { return 'ha-panel-dev-service'; }
|
||||||
|
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
hass: {
|
hass: {
|
||||||
type: Object,
|
type: Object,
|
||||||
},
|
},
|
||||||
|
|
||||||
narrow: {
|
narrow: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
value: false,
|
value: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
showMenu: {
|
showMenu: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
value: false,
|
value: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
domain: {
|
domainService: {
|
||||||
type: String,
|
type: String,
|
||||||
value: '',
|
observer: '_domainServiceChanged',
|
||||||
observer: 'domainChanged',
|
},
|
||||||
},
|
|
||||||
|
|
||||||
service: {
|
_domain: {
|
||||||
type: String,
|
type: String,
|
||||||
value: '',
|
computed: '_computeDomain(domainService)',
|
||||||
observer: 'serviceChanged',
|
},
|
||||||
},
|
|
||||||
|
|
||||||
serviceData: {
|
_service: {
|
||||||
type: String,
|
type: String,
|
||||||
value: '',
|
computed: '_computeService(domainService)',
|
||||||
},
|
},
|
||||||
|
|
||||||
_attributes: {
|
serviceData: {
|
||||||
type: Array,
|
type: String,
|
||||||
computed: 'computeAttributesArray(serviceDomains, domain, service)',
|
value: '',
|
||||||
},
|
},
|
||||||
|
|
||||||
_description: {
|
parsedJSON: {
|
||||||
type: String,
|
type: Object,
|
||||||
computed: 'computeDescription(serviceDomains, domain, service)',
|
computed: '_computeParsedServiceData(serviceData)'
|
||||||
},
|
},
|
||||||
|
|
||||||
serviceDomains: {
|
validJSON: {
|
||||||
type: Object,
|
type: Boolean,
|
||||||
computed: 'computeServiceDomains(hass)',
|
computed: '_computeValidJSON(parsedJSON)',
|
||||||
},
|
},
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
computeServiceDomains(hass) {
|
_attributes: {
|
||||||
return hass.config.services;
|
type: Array,
|
||||||
}
|
computed: '_computeAttributesArray(hass, _domain, _service)',
|
||||||
|
},
|
||||||
|
|
||||||
computeAttributesArray(serviceDomains, domain, service) {
|
_description: {
|
||||||
if (!serviceDomains) return [];
|
type: String,
|
||||||
if (!(domain in serviceDomains)) return [];
|
computed: '_computeDescription(hass, _domain, _service)',
|
||||||
if (!(service in serviceDomains[domain])) return [];
|
},
|
||||||
|
};
|
||||||
var fields = serviceDomains[domain][service].fields;
|
|
||||||
return Object.keys(fields).map(function (field) {
|
|
||||||
return Object.assign({}, fields[field], { key: field });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
computeDescription(serviceDomains, domain, service) {
|
|
||||||
if (!serviceDomains) return undefined;
|
|
||||||
if (!(domain in serviceDomains)) return undefined;
|
|
||||||
if (!(service in serviceDomains[domain])) return undefined;
|
|
||||||
return serviceDomains[domain][service].description;
|
|
||||||
}
|
|
||||||
|
|
||||||
computeDomains(serviceDomains) {
|
|
||||||
return Object.keys(serviceDomains).sort();
|
|
||||||
}
|
|
||||||
|
|
||||||
computeServices(serviceDomains, domain) {
|
|
||||||
if (!(domain in serviceDomains)) return [];
|
|
||||||
|
|
||||||
return Object.keys(serviceDomains[domain]).sort();
|
|
||||||
}
|
|
||||||
|
|
||||||
computeServiceKey(domain) {
|
|
||||||
if (!domain) {
|
|
||||||
return 'panel-dev-service-state-service';
|
|
||||||
}
|
|
||||||
return 'panel-dev-service-state-service.' + domain;
|
|
||||||
}
|
|
||||||
|
|
||||||
computeServicedataKey(domain, service) {
|
|
||||||
if (!domain || !service) {
|
|
||||||
return 'panel-dev-service-state-servicedata';
|
|
||||||
}
|
|
||||||
return 'panel-dev-service-state-servicedata.' + domain + '.' + service;
|
|
||||||
}
|
|
||||||
|
|
||||||
domainChanged() {
|
|
||||||
this.service = '';
|
|
||||||
this.serviceData = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
serviceChanged() {
|
|
||||||
this.serviceData = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
callService() {
|
|
||||||
var serviceData;
|
|
||||||
try {
|
|
||||||
serviceData = this.serviceData ? JSON.parse(this.serviceData) : {};
|
|
||||||
} catch (err) {
|
|
||||||
/* eslint-disable no-alert */
|
|
||||||
alert('Error parsing JSON: ' + err);
|
|
||||||
/* eslint-enable no-alert */
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hass.callService(this.domain, this.service, serviceData);
|
_domainServiceChanged() {
|
||||||
|
this.serviceData = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
_computeAttributesArray(hass, domain, service) {
|
||||||
|
const serviceDomains = hass.config.services;
|
||||||
|
if (!(domain in serviceDomains)) return [];
|
||||||
|
if (!(service in serviceDomains[domain])) return [];
|
||||||
|
|
||||||
|
const fields = serviceDomains[domain][service].fields;
|
||||||
|
return Object.keys(fields).map(function (field) {
|
||||||
|
return Object.assign({ key: field }, fields[field]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_computeDescription(hass, domain, service) {
|
||||||
|
const serviceDomains = hass.config.services;
|
||||||
|
if (!(domain in serviceDomains)) return undefined;
|
||||||
|
if (!(service in serviceDomains[domain])) return undefined;
|
||||||
|
return serviceDomains[domain][service].description;
|
||||||
|
}
|
||||||
|
|
||||||
|
_computeServicedataKey(domainService) {
|
||||||
|
return `panel-dev-service-state-servicedata.${domainService}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
_computeDomain(domainService) {
|
||||||
|
return domainService.split('.', 1)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
_computeService(domainService) {
|
||||||
|
return domainService.split('.', 2)[1] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_computeParsedServiceData(serviceData) {
|
||||||
|
try {
|
||||||
|
return serviceData ? JSON.parse(serviceData) : {};
|
||||||
|
} catch (err) {
|
||||||
|
return ERROR_SENTINEL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_computeValidJSON(parsedJSON) {
|
||||||
|
return parsedJSON !== ERROR_SENTINEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
_computeHasEntity(attributes) {
|
||||||
|
return attributes.some(attr => attr.key === 'entity_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
_computeEntityValue(parsedJSON) {
|
||||||
|
return parsedJSON === ERROR_SENTINEL ? '' : parsedJSON.entity_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
_computeEntityDomainFilter(domain) {
|
||||||
|
return domain === 'homeassistant' ? null : domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
_callService() {
|
||||||
|
if (this.parsedJSON === ERROR_SENTINEL) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
alert(`Error parsing JSON: ${this.serviceData}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hass.callService(this.domain, this.service, this.parsedJSON);
|
||||||
|
}
|
||||||
|
|
||||||
|
_entityPicked(ev) {
|
||||||
|
this.serviceData = JSON.stringify(Object.assign({}, this.parsedJSON, {
|
||||||
|
entity_id: ev.target.value
|
||||||
|
}), null, 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
customElements.define(HaPanelDevService.is, HaPanelDevService);
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define(HaPanelDevService.is, HaPanelDevService);
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
<link rel="import" href="../../bower_components/app-layout/app-toolbar/app-toolbar.html">
|
<link rel="import" href="../../bower_components/app-layout/app-toolbar/app-toolbar.html">
|
||||||
|
|
||||||
<link rel="import" href="../../src/components/ha-menu-button.html">
|
<link rel="import" href="../../src/components/ha-menu-button.html">
|
||||||
<link rel="import" href="../../src/components/entity/ha-entity-dropdown.html">
|
<link rel="import" href="../../src/components/entity/ha-entity-picker.html">
|
||||||
<link rel="import" href="../../src/resources/ha-style.html">
|
<link rel="import" href="../../src/resources/ha-style.html">
|
||||||
|
|
||||||
<dom-module id="ha-panel-dev-state">
|
<dom-module id="ha-panel-dev-state">
|
||||||
@ -26,9 +26,9 @@
|
|||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-entity-dropdown {
|
ha-entity-picker, .state-input, paper-textarea {
|
||||||
display: block;
|
display: block;
|
||||||
max-width: 300px;
|
max-width: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.entities th {
|
.entities th {
|
||||||
@ -72,12 +72,18 @@
|
|||||||
This will not communicate with the actual device.
|
This will not communicate with the actual device.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<ha-entity-dropdown
|
<ha-entity-picker
|
||||||
autofocus
|
autofocus
|
||||||
hass="[[hass]]"
|
hass="[[hass]]"
|
||||||
value="{{_entityId}}"
|
value="{{_entityId}}"
|
||||||
></ha-entity-dropdown>
|
allow-custom-entity
|
||||||
<paper-input label="State" required value='{{_state}}'></paper-input>
|
></ha-entity-picker>
|
||||||
|
<paper-input
|
||||||
|
label="State"
|
||||||
|
required
|
||||||
|
value='{{_state}}'
|
||||||
|
class='state-input'
|
||||||
|
></paper-input>
|
||||||
<paper-textarea label="State attributes (JSON, optional)" value='{{_stateAttributes}}'></paper-textarea>
|
<paper-textarea label="State attributes (JSON, optional)" value='{{_stateAttributes}}'></paper-textarea>
|
||||||
<paper-button on-tap='handleSetState' raised>Set State</paper-button>
|
<paper-button on-tap='handleSetState' raised>Set State</paper-button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -12,8 +12,6 @@
|
|||||||
<link rel='import' href='../../bower_components/app-layout/app-toolbar/app-toolbar.html'>
|
<link rel='import' href='../../bower_components/app-layout/app-toolbar/app-toolbar.html'>
|
||||||
<link rel="import" href="../../bower_components/app-storage/app-localstorage/app-localstorage-document.html">
|
<link rel="import" href="../../bower_components/app-storage/app-localstorage/app-localstorage-document.html">
|
||||||
|
|
||||||
<link rel="import" href="../../bower_components/vaadin-combo-box/vaadin-combo-box.html">
|
|
||||||
|
|
||||||
<link rel='import' href='../../src/components/ha-menu-button.html'>
|
<link rel='import' href='../../src/components/ha-menu-button.html'>
|
||||||
<link rel='import' href='../../src/resources/ha-style.html'>
|
<link rel='import' href='../../src/resources/ha-style.html'>
|
||||||
|
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
<link rel="import" href="../../../bower_components/polymer/polymer-element.html">
|
|
||||||
<link rel="import" href="../../../bower_components/vaadin-combo-box/vaadin-combo-box.html">
|
|
||||||
<link rel="import" href="../../../bower_components/paper-listbox/paper-listbox.html">
|
|
||||||
<link rel="import" href="../../../bower_components/paper-item/paper-icon-item.html">
|
|
||||||
<link rel="import" href="../../../bower_components/paper-item/paper-item-body.html">
|
|
||||||
|
|
||||||
<link rel="import" href="./state-badge.html">
|
|
||||||
|
|
||||||
<dom-module id="ha-entity-dropdown">
|
|
||||||
<template>
|
|
||||||
<vaadin-combo-box
|
|
||||||
autofocus="[[autofocus]]"
|
|
||||||
label="[[label]]"
|
|
||||||
items='[[computeStates(hass)]]'
|
|
||||||
item-value-path='entity_id'
|
|
||||||
item-label-path='entity_id'
|
|
||||||
value='{{value}}'
|
|
||||||
>
|
|
||||||
<template>
|
|
||||||
<style>
|
|
||||||
paper-icon-item {
|
|
||||||
margin: -13px -16px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<paper-icon-item>
|
|
||||||
<state-badge state-obj="[[item]]" slot='item-icon'></state-badge>
|
|
||||||
<paper-item-body two-line>
|
|
||||||
<div>[[computeStateName(item)]]</div>
|
|
||||||
<div secondary>[[item.entity_id]]</div>
|
|
||||||
</paper-item-body>
|
|
||||||
</paper-icon-item>
|
|
||||||
</template>
|
|
||||||
</vaadin-combo-box>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
</dom-module>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
class HaEntityDropdown extends Polymer.Element {
|
|
||||||
static get is() { return 'ha-entity-dropdown'; }
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: Object,
|
|
||||||
autofocus: Boolean,
|
|
||||||
label: {
|
|
||||||
type: String,
|
|
||||||
value: 'Entity',
|
|
||||||
},
|
|
||||||
value: {
|
|
||||||
type: String,
|
|
||||||
notify: true,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
computeStates(hass) {
|
|
||||||
return Object.keys(hass.states).sort().map(key => hass.states[key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
computeStateName(state) {
|
|
||||||
return window.hassUtil.computeStateName(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define(HaEntityDropdown.is, HaEntityDropdown);
|
|
||||||
</script>
|
|
152
src/components/entity/ha-entity-picker.html
Normal file
152
src/components/entity/ha-entity-picker.html
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
<link rel="import" href="../../../bower_components/polymer/polymer-element.html">
|
||||||
|
<link rel="import" href="../../../bower_components/vaadin-combo-box/vaadin-combo-box-light.html">
|
||||||
|
<link rel="import" href="../../../bower_components/paper-item/paper-icon-item.html">
|
||||||
|
<link rel="import" href="../../../bower_components/paper-item/paper-item-body.html">
|
||||||
|
|
||||||
|
<link rel="import" href="./state-badge.html">
|
||||||
|
|
||||||
|
<dom-module id="ha-entity-picker">
|
||||||
|
<template>
|
||||||
|
<style>
|
||||||
|
paper-input > paper-icon-button {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
padding: 2px;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<vaadin-combo-box-light
|
||||||
|
items='[[_states]]'
|
||||||
|
item-value-path='entity_id'
|
||||||
|
item-label-path='entity_id'
|
||||||
|
value='{{value}}'
|
||||||
|
opened='{{opened}}'
|
||||||
|
allow-custom-value='[[allowCustomEntity]]'
|
||||||
|
>
|
||||||
|
<paper-input
|
||||||
|
autofocus="[[autofocus]]"
|
||||||
|
label="[[label]]"
|
||||||
|
class="input"
|
||||||
|
disabled='[[disabled]]'
|
||||||
|
>
|
||||||
|
<paper-icon-button
|
||||||
|
slot="suffix"
|
||||||
|
class="clear-button"
|
||||||
|
icon="mdi:close"
|
||||||
|
no-ripple
|
||||||
|
hidden$='[[!value]]'
|
||||||
|
>Clear</paper-icon-button>
|
||||||
|
<paper-icon-button
|
||||||
|
slot="suffix"
|
||||||
|
class="toggle-button"
|
||||||
|
icon='[[_computeToggleIcon(opened)]]'
|
||||||
|
hidden='[[!_states.length]]'
|
||||||
|
>Toggle</paper-icon-button>
|
||||||
|
</paper-input>
|
||||||
|
<template>
|
||||||
|
<style>
|
||||||
|
paper-icon-item {
|
||||||
|
margin: -10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<paper-icon-item>
|
||||||
|
<state-badge state-obj="[[item]]" slot='item-icon'></state-badge>
|
||||||
|
<paper-item-body two-line>
|
||||||
|
<div>[[computeStateName(item)]]</div>
|
||||||
|
<div secondary>[[item.entity_id]]</div>
|
||||||
|
</paper-item-body>
|
||||||
|
</paper-icon-item>
|
||||||
|
</template>
|
||||||
|
</vaadin-combo-box-light>
|
||||||
|
</template>
|
||||||
|
</dom-module>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
class HaEntityPicker extends Polymer.Element {
|
||||||
|
static get is() { return 'ha-entity-picker'; }
|
||||||
|
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
allowCustomEntity: {
|
||||||
|
type: Boolean,
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
hass: {
|
||||||
|
type: Object,
|
||||||
|
observer: '_hassChanged',
|
||||||
|
},
|
||||||
|
_hass: Object,
|
||||||
|
_states: {
|
||||||
|
type: Array,
|
||||||
|
computed: '_computeStates(_hass, domainFilter, entityFilter)',
|
||||||
|
},
|
||||||
|
autofocus: Boolean,
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
value: 'Entity',
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
notify: true,
|
||||||
|
},
|
||||||
|
opened: {
|
||||||
|
type: Boolean,
|
||||||
|
value: false,
|
||||||
|
observer: '_openedChanged',
|
||||||
|
},
|
||||||
|
domainFilter: {
|
||||||
|
type: String,
|
||||||
|
value: null,
|
||||||
|
},
|
||||||
|
entityFilter: {
|
||||||
|
type: Function,
|
||||||
|
value: null,
|
||||||
|
},
|
||||||
|
disabled: Boolean,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_computeStates(hass, domainFilter, entityFilter) {
|
||||||
|
if (!hass) return [];
|
||||||
|
|
||||||
|
let entityIds = Object.keys(hass.states);
|
||||||
|
|
||||||
|
if (domainFilter) {
|
||||||
|
entityIds = entityIds.filter(eid => eid.substr(0, eid.indexOf('.')) === domainFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
let entities = entityIds.sort().map(key => hass.states[key]);
|
||||||
|
|
||||||
|
if (entityFilter) {
|
||||||
|
entities = entities.filter(entityFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
return entities;
|
||||||
|
}
|
||||||
|
|
||||||
|
computeStateName(state) {
|
||||||
|
return window.hassUtil.computeStateName(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
_openedChanged(newVal) {
|
||||||
|
if (!newVal) {
|
||||||
|
this._hass = this.hass;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_hassChanged(newVal) {
|
||||||
|
if (!this.opened) {
|
||||||
|
this._hass = newVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_computeToggleIcon(opened) {
|
||||||
|
return opened ? 'mdi:menu-up' : 'mdi:menu-down';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define(HaEntityPicker.is, HaEntityPicker);
|
||||||
|
</script>
|
106
src/components/ha-combo-box.html
Normal file
106
src/components/ha-combo-box.html
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
<link rel="import" href="../../bower_components/polymer/polymer-element.html">
|
||||||
|
<link rel="import" href="../../bower_components/vaadin-combo-box/vaadin-combo-box-light.html">
|
||||||
|
<link rel="import" href="../../bower_components/paper-item/paper-item.html">
|
||||||
|
|
||||||
|
<dom-module id="ha-combo-box">
|
||||||
|
<template>
|
||||||
|
<style>
|
||||||
|
paper-input > paper-icon-button {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
padding: 2px;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<vaadin-combo-box-light
|
||||||
|
items='[[items]]'
|
||||||
|
item-value-path='[[itemValuePath]]'
|
||||||
|
item-label-path='[[itemLabelPath]]'
|
||||||
|
value='{{value}}'
|
||||||
|
opened='{{opened}}'
|
||||||
|
allow-custom-value='[[allowCustomValue]]'
|
||||||
|
>
|
||||||
|
<paper-input
|
||||||
|
autofocus="[[autofocus]]"
|
||||||
|
label="[[label]]"
|
||||||
|
class="input"
|
||||||
|
>
|
||||||
|
<paper-icon-button
|
||||||
|
slot="suffix"
|
||||||
|
class="clear-button"
|
||||||
|
icon="mdi:close"
|
||||||
|
hidden$='[[!value]]'
|
||||||
|
>Clear</paper-icon-button>
|
||||||
|
<paper-icon-button
|
||||||
|
slot="suffix"
|
||||||
|
class="toggle-button"
|
||||||
|
icon='[[_computeToggleIcon(opened)]]'
|
||||||
|
hidden$='[[!items.length]]'
|
||||||
|
>Toggle</paper-icon-button>
|
||||||
|
</paper-input>
|
||||||
|
<template>
|
||||||
|
<style>
|
||||||
|
paper-item {
|
||||||
|
margin: -5px -10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<paper-item>[[_computeItemLabel(item, itemLabelPath)]]</paper-item>
|
||||||
|
</template>
|
||||||
|
</vaadin-combo-box-light>
|
||||||
|
</template>
|
||||||
|
</dom-module>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
class HaComboBox extends Polymer.Element {
|
||||||
|
static get is() { return 'ha-combo-box'; }
|
||||||
|
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
allowCustomValue: Boolean,
|
||||||
|
items: {
|
||||||
|
type: Object,
|
||||||
|
observer: '_itemsChanged',
|
||||||
|
},
|
||||||
|
_items: Object,
|
||||||
|
itemLabelPath: String,
|
||||||
|
itemValuePath: String,
|
||||||
|
autofocus: Boolean,
|
||||||
|
label: String,
|
||||||
|
opened: {
|
||||||
|
type: Boolean,
|
||||||
|
value: false,
|
||||||
|
observer: '_openedChanged',
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
notify: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_openedChanged(newVal) {
|
||||||
|
if (!newVal) {
|
||||||
|
this._items = this.items;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_itemsChanged(newVal) {
|
||||||
|
if (!this.opened) {
|
||||||
|
this._items = newVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_computeToggleIcon(opened) {
|
||||||
|
return opened ? 'mdi:menu-up' : 'mdi:menu-down';
|
||||||
|
}
|
||||||
|
|
||||||
|
_computeItemLabel(item, itemLabelPath) {
|
||||||
|
return itemLabelPath ? item[itemLabelPath] : item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define(HaComboBox.is, HaComboBox);
|
||||||
|
</script>
|
46
src/components/ha-service-picker.html
Normal file
46
src/components/ha-service-picker.html
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<link rel="import" href="../../bower_components/polymer/polymer-element.html">
|
||||||
|
|
||||||
|
<link rel="import" href="./ha-combo-box.html">
|
||||||
|
|
||||||
|
<dom-module id="ha-service-picker">
|
||||||
|
<template>
|
||||||
|
<ha-combo-box
|
||||||
|
label='Service'
|
||||||
|
items='[[_computeServices(hass)]]'
|
||||||
|
value='{{value}}'
|
||||||
|
allow-custom-value
|
||||||
|
></ha-combo-box>
|
||||||
|
</template>
|
||||||
|
</dom-module>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
class HaServicePicker extends Polymer.Element {
|
||||||
|
static get is() { return 'ha-service-picker'; }
|
||||||
|
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
hass: Object,
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
notify: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_computeServices(hass) {
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
Object.keys(hass.config.services).sort().forEach((domain) => {
|
||||||
|
const services = Object.keys(hass.config.services[domain]).sort();
|
||||||
|
|
||||||
|
for (let i = 0; i < services.length; i++) {
|
||||||
|
result.push(`${domain}.${services[i]}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define(HaServicePicker.is, HaServicePicker);
|
||||||
|
</script>
|
@ -4,6 +4,7 @@
|
|||||||
<style is="custom-style">/* remove is= on Polymer 2 */
|
<style is="custom-style">/* remove is= on Polymer 2 */
|
||||||
body {
|
body {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
height: 100vh;
|
||||||
|
|
||||||
/* for paper-toggle-button */
|
/* for paper-toggle-button */
|
||||||
--paper-grey-50: #fafafa;
|
--paper-grey-50: #fafafa;
|
||||||
|
38
test-mocha/common/util/location.js
Normal file
38
test-mocha/common/util/location.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { hasLocation } from '../../../js/common/util/location';
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
describe('hasLocation', () => {
|
||||||
|
it('flags states with location', () => {
|
||||||
|
const stateObj = {
|
||||||
|
attributes: {
|
||||||
|
latitude: 12.34,
|
||||||
|
longitude: 12.34
|
||||||
|
}
|
||||||
|
};
|
||||||
|
assert(hasLocation(stateObj));
|
||||||
|
});
|
||||||
|
it('does not flag states with only latitude', () => {
|
||||||
|
const stateObj = {
|
||||||
|
attributes: {
|
||||||
|
latitude: 12.34,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
assert(!hasLocation(stateObj));
|
||||||
|
});
|
||||||
|
it('does not flag states with only longitude', () => {
|
||||||
|
const stateObj = {
|
||||||
|
attributes: {
|
||||||
|
longitude: 12.34
|
||||||
|
}
|
||||||
|
};
|
||||||
|
assert(!hasLocation(stateObj));
|
||||||
|
});
|
||||||
|
it('does not flag states with no location', () => {
|
||||||
|
const stateObj = {
|
||||||
|
attributes: {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
assert(!hasLocation(stateObj));
|
||||||
|
});
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user