mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-18 23:06:40 +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/no-unresolved": 0,
|
||||
"import/extensions": [2, "ignorePackages"],
|
||||
"object-curly-newline": 0,
|
||||
"react/jsx-no-bind": [2, { "ignoreRefs": true }],
|
||||
"react/jsx-no-duplicate-props": 2,
|
||||
"react/self-closing-comp": 2,
|
||||
|
@ -51,7 +51,7 @@
|
||||
"paper-toast": "PolymerElements/paper-toast#^2.0.0",
|
||||
"paper-toggle-button": "PolymerElements/paper-toggle-button#^2.0.0",
|
||||
"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",
|
||||
"web-animations-js": "^2.2.5",
|
||||
"webcomponentsjs": "^1.0.10"
|
||||
|
@ -42,7 +42,7 @@ export default class Automation extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
render({ automation, isWide }) {
|
||||
render({ automation, isWide, hass }) {
|
||||
const {
|
||||
alias, trigger, condition, action
|
||||
} = automation;
|
||||
@ -77,7 +77,11 @@ export default class Automation extends Component {
|
||||
Learn more about triggers.
|
||||
</a></p>
|
||||
</span>
|
||||
<Trigger trigger={trigger} onChange={this.triggerChanged} />
|
||||
<Trigger
|
||||
trigger={trigger}
|
||||
onChange={this.triggerChanged}
|
||||
hass={hass}
|
||||
/>
|
||||
</ha-config-section>
|
||||
|
||||
<ha-config-section is-wide={isWide}>
|
||||
@ -93,7 +97,11 @@ export default class Automation extends Component {
|
||||
Learn more about conditions.
|
||||
</a></p>
|
||||
</span>
|
||||
<Condition condition={condition || []} onChange={this.conditionChanged} />
|
||||
<Condition
|
||||
condition={condition || []}
|
||||
onChange={this.conditionChanged}
|
||||
hass={hass}
|
||||
/>
|
||||
</ha-config-section>
|
||||
|
||||
<ha-config-section is-wide={isWide}>
|
||||
@ -104,7 +112,11 @@ export default class Automation extends Component {
|
||||
Learn more about actions.
|
||||
</a></p>
|
||||
</span>
|
||||
<Script script={action} onChange={this.actionChanged} />
|
||||
<Script
|
||||
script={action}
|
||||
onChange={this.actionChanged}
|
||||
hass={hass}
|
||||
/>
|
||||
</ha-config-section>
|
||||
</div>
|
||||
);
|
||||
|
@ -32,7 +32,7 @@ export default class Trigger extends Component {
|
||||
this.props.onChange(trigger);
|
||||
}
|
||||
|
||||
render({ trigger }) {
|
||||
render({ trigger, hass }) {
|
||||
return (
|
||||
<div class="triggers">
|
||||
{trigger.map((trg, idx) => (
|
||||
@ -40,6 +40,7 @@ export default class Trigger extends Component {
|
||||
index={idx}
|
||||
trigger={trg}
|
||||
onChange={this.triggerChanged}
|
||||
hass={hass}
|
||||
/>))}
|
||||
<paper-card>
|
||||
<div class='card-actions add-card'>
|
||||
|
@ -7,20 +7,29 @@ export default class NumericStateTrigger extends Component {
|
||||
super();
|
||||
|
||||
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 */
|
||||
render({ trigger }) {
|
||||
render({ trigger, hass }) {
|
||||
const {
|
||||
value_template, entity_id, below, above
|
||||
} = trigger;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<paper-input
|
||||
label="Entity Id"
|
||||
name="entity_id"
|
||||
<ha-entity-picker
|
||||
value={entity_id}
|
||||
onChange={this.onChange}
|
||||
onChange={this.entityPicked}
|
||||
hass={hass}
|
||||
allowCustomEntity
|
||||
/>
|
||||
<paper-input
|
||||
label="Above"
|
||||
|
@ -7,20 +7,28 @@ export default class StateTrigger extends Component {
|
||||
super();
|
||||
|
||||
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 */
|
||||
render({ trigger }) {
|
||||
render({ trigger, hass }) {
|
||||
const { entity_id, to } = trigger;
|
||||
const trgFrom = trigger.from;
|
||||
const trgFor = trigger.for;
|
||||
return (
|
||||
<div>
|
||||
<paper-input
|
||||
label="Entity Id"
|
||||
name="entity_id"
|
||||
<ha-entity-picker
|
||||
value={entity_id}
|
||||
onChange={this.onChange}
|
||||
onChange={this.entityPicked}
|
||||
hass={hass}
|
||||
allowCustomEntity
|
||||
/>
|
||||
<paper-input
|
||||
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 selected = OPTIONS.indexOf(trigger.platform);
|
||||
|
||||
@ -69,6 +69,7 @@ export default class TriggerEdit extends Component {
|
||||
index={index}
|
||||
trigger={trigger}
|
||||
onChange={onChange}
|
||||
hass={hass}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,6 +1,12 @@
|
||||
import { h, Component } from 'preact';
|
||||
|
||||
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 {
|
||||
constructor() {
|
||||
@ -8,6 +14,22 @@ export default class ZoneTrigger extends Component {
|
||||
|
||||
this.onChange = onChangeEvent.bind(this, 'trigger');
|
||||
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) {
|
||||
@ -18,21 +40,25 @@ export default class ZoneTrigger extends Component {
|
||||
}
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
render({ trigger }) {
|
||||
render({ trigger, hass }) {
|
||||
const { entity_id, zone, event } = trigger;
|
||||
return (
|
||||
<div>
|
||||
<paper-input
|
||||
label="Entity Id"
|
||||
name="entity_id"
|
||||
<ha-entity-picker
|
||||
label='Entity with location'
|
||||
value={entity_id}
|
||||
onChange={this.onChange}
|
||||
onChange={this.entityPicked}
|
||||
hass={hass}
|
||||
allowCustomEntity
|
||||
entityFilter={zoneAndLocationFilter}
|
||||
/>
|
||||
<paper-input
|
||||
label="Zone"
|
||||
name="zone"
|
||||
<ha-entity-picker
|
||||
label='Zone'
|
||||
value={zone}
|
||||
onChange={this.onChange}
|
||||
onChange={this.zonePicked}
|
||||
hass={hass}
|
||||
allowCustomEntity
|
||||
domainFilter='zone'
|
||||
/>
|
||||
<label id="eventlabel">Event:</label>
|
||||
<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 selected = OPTIONS.indexOf(condition.condition);
|
||||
|
||||
@ -64,6 +64,7 @@ export default class ConditionRow extends Component {
|
||||
index={index}
|
||||
condition={condition}
|
||||
onChange={onChange}
|
||||
hass={hass}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -30,7 +30,7 @@ export default class Condition extends Component {
|
||||
this.props.onChange(condition);
|
||||
}
|
||||
|
||||
render({ condition }) {
|
||||
render({ condition, hass }) {
|
||||
return (
|
||||
<div class="triggers">
|
||||
{condition.map((cnd, idx) => (
|
||||
@ -38,6 +38,7 @@ export default class Condition extends Component {
|
||||
index={idx}
|
||||
condition={cnd}
|
||||
onChange={this.conditionChanged}
|
||||
hass={hass}
|
||||
/>))}
|
||||
<paper-card>
|
||||
<div class='card-actions add-card'>
|
||||
|
@ -7,20 +7,28 @@ export default class NumericStateCondition extends Component {
|
||||
super();
|
||||
|
||||
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 */
|
||||
render({ condition }) {
|
||||
render({ condition, hass }) {
|
||||
const {
|
||||
value_template, entity_id, below, above
|
||||
} = condition;
|
||||
return (
|
||||
<div>
|
||||
<paper-input
|
||||
label="Entity Id"
|
||||
name="entity_id"
|
||||
<ha-entity-picker
|
||||
value={entity_id}
|
||||
onChange={this.onChange}
|
||||
onChange={this.entityPicked}
|
||||
hass={hass}
|
||||
allowCustomEntity
|
||||
/>
|
||||
<paper-input
|
||||
label="Above"
|
||||
|
@ -7,19 +7,27 @@ export default class StateCondition extends Component {
|
||||
super();
|
||||
|
||||
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 */
|
||||
render({ condition }) {
|
||||
render({ condition, hass }) {
|
||||
const { entity_id, state } = condition;
|
||||
const cndFor = condition.for;
|
||||
return (
|
||||
<div>
|
||||
<paper-input
|
||||
label="Entity Id"
|
||||
name="entity_id"
|
||||
<ha-entity-picker
|
||||
value={entity_id}
|
||||
onChange={this.onChange}
|
||||
onChange={this.entityPicked}
|
||||
hass={hass}
|
||||
allowCustomEntity
|
||||
/>
|
||||
<paper-input
|
||||
label="State"
|
||||
|
@ -1,30 +1,56 @@
|
||||
import { h, Component } from 'preact';
|
||||
|
||||
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 {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
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 */
|
||||
render({ condition }) {
|
||||
render({ condition, hass }) {
|
||||
const { entity_id, zone } = condition;
|
||||
return (
|
||||
<div>
|
||||
<paper-input
|
||||
label="Entity Id"
|
||||
name="entity_id"
|
||||
<ha-entity-picker
|
||||
label='Entity with location'
|
||||
value={entity_id}
|
||||
onChange={this.onChange}
|
||||
onChange={this.entityPicked}
|
||||
hass={hass}
|
||||
allowCustomEntity
|
||||
entityFilter={zoneAndLocationFilter}
|
||||
/>
|
||||
<paper-input
|
||||
label="Zone entity id"
|
||||
name="zone"
|
||||
<ha-entity-picker
|
||||
label='Zone'
|
||||
value={zone}
|
||||
onChange={this.onChange}
|
||||
onChange={this.zonePicked}
|
||||
hass={hass}
|
||||
allowCustomEntity
|
||||
domainFilter='zone'
|
||||
/>
|
||||
</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 Comp = type && TYPES[type];
|
||||
const selected = OPTIONS.indexOf(type);
|
||||
@ -70,6 +70,7 @@ export default class Action extends Component {
|
||||
index={index}
|
||||
action={action}
|
||||
onChange={onChange}
|
||||
hass={hass}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,16 +1,22 @@
|
||||
import { h, Component } from 'preact';
|
||||
|
||||
import JSONTextArea from '../json_textarea.js';
|
||||
import { onChangeEvent } from '../../util/event.js';
|
||||
|
||||
export default class CallServiceAction extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.onChange = onChangeEvent.bind(this, 'action');
|
||||
this.serviceChanged = this.serviceChanged.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) {
|
||||
this.props.onChange(this.props.index, {
|
||||
...this.props.action,
|
||||
@ -18,21 +24,15 @@ export default class CallServiceAction extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
render({ action }) {
|
||||
const { alias, service, data } = action;
|
||||
render({ action, hass }) {
|
||||
const { service, data } = action;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<paper-input
|
||||
label="Alias"
|
||||
name="alias"
|
||||
value={alias}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
<paper-input
|
||||
label="Service"
|
||||
name="service"
|
||||
<ha-service-picker
|
||||
hass={hass}
|
||||
value={service}
|
||||
onChange={this.onChange}
|
||||
onChange={this.serviceChanged}
|
||||
/>
|
||||
<JSONTextArea
|
||||
label="Service Data"
|
||||
|
@ -5,12 +5,13 @@ import ConditionEdit from '../condition/condition_edit.js';
|
||||
|
||||
export default class ConditionAction extends Component {
|
||||
// eslint-disable-next-line
|
||||
render({ action, index, onChange }) {
|
||||
render({ action, index, onChange, hass }) {
|
||||
return (
|
||||
<ConditionEdit
|
||||
condition={action}
|
||||
onChange={onChange}
|
||||
index={index}
|
||||
hass={hass}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ export default class Script extends Component {
|
||||
this.props.onChange(script);
|
||||
}
|
||||
|
||||
render({ script }) {
|
||||
render({ script, hass }) {
|
||||
return (
|
||||
<div class="script">
|
||||
{script.map((act, idx) => (
|
||||
@ -38,6 +38,7 @@ export default class Script extends Component {
|
||||
index={idx}
|
||||
action={act}
|
||||
onChange={this.actionChanged}
|
||||
hass={hass}
|
||||
/>))}
|
||||
<paper-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/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="../ha-config-section.html">
|
||||
@ -117,6 +120,7 @@ class HaAutomationEditor extends window.hassMixins.EventsMixin(Polymer.Element)
|
||||
return {
|
||||
hass: {
|
||||
type: Object,
|
||||
observer: '_updateComponent',
|
||||
},
|
||||
|
||||
narrow: {
|
||||
@ -160,7 +164,7 @@ class HaAutomationEditor extends window.hassMixins.EventsMixin(Polymer.Element)
|
||||
|
||||
isWide: {
|
||||
type: Boolean,
|
||||
observer: 'isWideChanged',
|
||||
observer: '_updateComponent',
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -169,6 +173,7 @@ class HaAutomationEditor extends window.hassMixins.EventsMixin(Polymer.Element)
|
||||
super.ready();
|
||||
this.configChanged = this.configChanged.bind(this);
|
||||
this._rendered = null;
|
||||
this._renderScheduled = false;
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
@ -184,13 +189,13 @@ class HaAutomationEditor extends window.hassMixins.EventsMixin(Polymer.Element)
|
||||
this.config = config;
|
||||
this.errors = null;
|
||||
this.dirty = true;
|
||||
this._updateComponent(config);
|
||||
this._updateComponent();
|
||||
}
|
||||
|
||||
automationChanged(newVal, oldVal) {
|
||||
if (!newVal) return;
|
||||
if (!this.hass) {
|
||||
setTimeout(this.automationChanged.bind(this, newVal, oldVal), 0);
|
||||
setTimeout(() => this.automationChanged(newVal, oldVal), 0);
|
||||
return;
|
||||
}
|
||||
if (oldVal && oldVal.attributes.id === newVal.attributes.id) {
|
||||
@ -230,11 +235,6 @@ class HaAutomationEditor extends window.hassMixins.EventsMixin(Polymer.Element)
|
||||
this._updateComponent();
|
||||
}
|
||||
|
||||
isWideChanged() {
|
||||
if (this.config === null) return;
|
||||
this._updateComponent();
|
||||
}
|
||||
|
||||
backTapped() {
|
||||
if (this.dirty &&
|
||||
// eslint-disable-next-line
|
||||
@ -245,11 +245,17 @@ class HaAutomationEditor extends window.hassMixins.EventsMixin(Polymer.Element)
|
||||
}
|
||||
|
||||
_updateComponent() {
|
||||
this._rendered = window.AutomationEditor(this.$.root, {
|
||||
automation: this.config,
|
||||
onChange: this.configChanged,
|
||||
isWide: this.isWide,
|
||||
}, this._rendered);
|
||||
if (this._renderScheduled || !this.hass || !this.config) return;
|
||||
this._renderScheduled = true;
|
||||
Promise.resolve().then(() => {
|
||||
this._rendered = window.AutomationEditor(this.$.root, {
|
||||
automation: this.config,
|
||||
onChange: this.configChanged,
|
||||
isWide: this.isWide,
|
||||
hass: this.hass,
|
||||
}, this._rendered);
|
||||
this._renderScheduled = false;
|
||||
});
|
||||
}
|
||||
|
||||
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-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/entity/ha-entity-picker.html'>
|
||||
<link rel='import' href='../../src/components/ha-service-picker.html'>
|
||||
<link rel='import' href='../../src/resources/ha-style.html'>
|
||||
|
||||
<dom-module id='ha-panel-dev-service'>
|
||||
@ -28,7 +28,7 @@
|
||||
|
||||
.ha-form {
|
||||
margin-right: 16px;
|
||||
max-width: 500px;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.description {
|
||||
@ -68,6 +68,14 @@
|
||||
h1 {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: var(--google-red-500);
|
||||
}
|
||||
</style>
|
||||
|
||||
<app-header-layout has-scrolling-region>
|
||||
@ -79,69 +87,77 @@
|
||||
</app-header>
|
||||
|
||||
<app-localstorage-document
|
||||
key='panel-dev-service-state-domain'
|
||||
data='{{domain}}'>
|
||||
key='panel-dev-service-state-domain-service'
|
||||
data='{{domainService}}'>
|
||||
</app-localstorage-document>
|
||||
<app-localstorage-document
|
||||
key='[[computeServiceKey(domain)]]'
|
||||
data='{{service}}'>
|
||||
</app-localstorage-document>
|
||||
<app-localstorage-document
|
||||
key='[[computeServicedataKey(domain, service)]]'
|
||||
key='[[_computeServicedataKey(domainService)]]'
|
||||
data='{{serviceData}}'>
|
||||
</app-localstorage-document>
|
||||
|
||||
<div class='content'>
|
||||
<p>
|
||||
Call a service from a component.
|
||||
The service dev tool allows you to call any available service in Home Assistant.
|
||||
</p>
|
||||
|
||||
<div class='ha-form'>
|
||||
<vaadin-combo-box label='Domain' items='[[computeDomains(serviceDomains)]]' value='{{domain}}'></vaadin-combo-box>
|
||||
<vaadin-combo-box label='Service' items='[[computeServices(serviceDomains, domain)]]' value='{{service}}'></vaadin-combo-box>
|
||||
<ha-service-picker
|
||||
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
|
||||
always-float-label
|
||||
label='Service Data (JSON, optional)'
|
||||
value='{{serviceData}}'
|
||||
></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>
|
||||
|
||||
<template is='dom-if' if='[[!domain]]'>
|
||||
<h1>Select a domain and service to see the description</h1>
|
||||
<template is='dom-if' if='[[!domainService]]'>
|
||||
<h1>Select a service to see the description</h1>
|
||||
</template>
|
||||
|
||||
<template is='dom-if' if='[[domain]]'>
|
||||
<template is='dom-if' if='[[!service]]'>
|
||||
<h1>Select a service to see the description</h1>
|
||||
<template is='dom-if' if='[[domainService]]'>
|
||||
<template is='dom-if' if='[[!_description]]'>
|
||||
<h1>No description is available</h1>
|
||||
</template>
|
||||
</template>
|
||||
<template is='dom-if' if='[[_description]]'>
|
||||
<h3>[[_description]]</h3>
|
||||
|
||||
<template is='dom-if' if='[[domain]]'>
|
||||
<template is='dom-if' if='[[service]]'>
|
||||
<template is='dom-if' if='[[!_description]]'>
|
||||
<h1>No description is available</h1>
|
||||
</template>
|
||||
<template is='dom-if' if='[[_description]]'>
|
||||
<h3>[[_description]]</h3>
|
||||
</template>
|
||||
<template is='dom-if' if='[[_attributes.length]]'>
|
||||
<h1>Valid Parameters</h1>
|
||||
<table class='attributes'>
|
||||
<table class='attributes'>
|
||||
<tr>
|
||||
<th>Parameter</th>
|
||||
<th>Description</th>
|
||||
<th>Example</th>
|
||||
</tr>
|
||||
<template is='dom-if' if='[[!_attributes.length]]'>
|
||||
<tr><td colspan='3'>This service takes no parameters.</td></tr>
|
||||
</template>
|
||||
<template is='dom-repeat' items='[[_attributes]]' as='attribute'>
|
||||
<tr>
|
||||
<th>Parameter</th>
|
||||
<th>Description</th>
|
||||
<th>Example</th>
|
||||
<td><pre>[[attribute.key]]</pre></td>
|
||||
<td>[[attribute.description]]</td>
|
||||
<td>[[attribute.example]]</td>
|
||||
</tr>
|
||||
<template is='dom-repeat' items='[[_attributes]]' as='attribute'>
|
||||
<tr>
|
||||
<td><pre>[[attribute.key]]</pre></td>
|
||||
<td>[[attribute.description]]</td>
|
||||
<td>[[attribute.example]]</td>
|
||||
</tr>
|
||||
</template>
|
||||
</table>
|
||||
</template>
|
||||
</template>
|
||||
</table>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
@ -151,128 +167,143 @@
|
||||
</dom-module>
|
||||
|
||||
<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() {
|
||||
return {
|
||||
hass: {
|
||||
type: Object,
|
||||
},
|
||||
static get properties() {
|
||||
return {
|
||||
hass: {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
narrow: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
narrow: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
showMenu: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
showMenu: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
domain: {
|
||||
type: String,
|
||||
value: '',
|
||||
observer: 'domainChanged',
|
||||
},
|
||||
domainService: {
|
||||
type: String,
|
||||
observer: '_domainServiceChanged',
|
||||
},
|
||||
|
||||
service: {
|
||||
type: String,
|
||||
value: '',
|
||||
observer: 'serviceChanged',
|
||||
},
|
||||
_domain: {
|
||||
type: String,
|
||||
computed: '_computeDomain(domainService)',
|
||||
},
|
||||
|
||||
serviceData: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
_service: {
|
||||
type: String,
|
||||
computed: '_computeService(domainService)',
|
||||
},
|
||||
|
||||
_attributes: {
|
||||
type: Array,
|
||||
computed: 'computeAttributesArray(serviceDomains, domain, service)',
|
||||
},
|
||||
serviceData: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
|
||||
_description: {
|
||||
type: String,
|
||||
computed: 'computeDescription(serviceDomains, domain, service)',
|
||||
},
|
||||
parsedJSON: {
|
||||
type: Object,
|
||||
computed: '_computeParsedServiceData(serviceData)'
|
||||
},
|
||||
|
||||
serviceDomains: {
|
||||
type: Object,
|
||||
computed: 'computeServiceDomains(hass)',
|
||||
},
|
||||
};
|
||||
}
|
||||
validJSON: {
|
||||
type: Boolean,
|
||||
computed: '_computeValidJSON(parsedJSON)',
|
||||
},
|
||||
|
||||
computeServiceDomains(hass) {
|
||||
return hass.config.services;
|
||||
}
|
||||
_attributes: {
|
||||
type: Array,
|
||||
computed: '_computeAttributesArray(hass, _domain, _service)',
|
||||
},
|
||||
|
||||
computeAttributesArray(serviceDomains, domain, service) {
|
||||
if (!serviceDomains) return [];
|
||||
if (!(domain in serviceDomains)) return [];
|
||||
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;
|
||||
_description: {
|
||||
type: String,
|
||||
computed: '_computeDescription(hass, _domain, _service)',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
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>
|
||||
|
@ -10,7 +10,7 @@
|
||||
<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/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">
|
||||
|
||||
<dom-module id="ha-panel-dev-state">
|
||||
@ -26,9 +26,9 @@
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
ha-entity-dropdown {
|
||||
ha-entity-picker, .state-input, paper-textarea {
|
||||
display: block;
|
||||
max-width: 300px;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.entities th {
|
||||
@ -72,12 +72,18 @@
|
||||
This will not communicate with the actual device.
|
||||
</p>
|
||||
|
||||
<ha-entity-dropdown
|
||||
<ha-entity-picker
|
||||
autofocus
|
||||
hass="[[hass]]"
|
||||
value="{{_entityId}}"
|
||||
></ha-entity-dropdown>
|
||||
<paper-input label="State" required value='{{_state}}'></paper-input>
|
||||
allow-custom-entity
|
||||
></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-button on-tap='handleSetState' raised>Set State</paper-button>
|
||||
</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-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/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 */
|
||||
body {
|
||||
font-size: 14px;
|
||||
height: 100vh;
|
||||
|
||||
/* for paper-toggle-button */
|
||||
--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