mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-19 10:57:19 +00:00
parent
2f71369dae
commit
a1057681f1
@ -2,7 +2,9 @@
|
||||
"extends": "airbnb-base",
|
||||
"globals": {
|
||||
"__DEV__": false,
|
||||
"Polymer": true
|
||||
"__DEMO__": false,
|
||||
"Polymer": true,
|
||||
"webkitSpeechRecognition": false,
|
||||
},
|
||||
"env": {
|
||||
"browser": true
|
||||
@ -19,7 +21,10 @@
|
||||
"prefer-spread": 0,
|
||||
"no-plusplus": 0,
|
||||
"no-bitwise": 0,
|
||||
"comma-dangle": 0
|
||||
"comma-dangle": 0,
|
||||
"vars-on-top": 0,
|
||||
"no-continue": 0,
|
||||
"no-param-reassign": 0
|
||||
},
|
||||
plugins: [
|
||||
"html"
|
||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
||||
[submodule "home-assistant-js"]
|
||||
path = home-assistant-js
|
||||
url = https://github.com/home-assistant/home-assistant-js.git
|
@ -1 +0,0 @@
|
||||
Subproject commit e5b4d6e5a3d1348c82a581dc728709a44a5fcc4a
|
@ -30,11 +30,7 @@
|
||||
"license": "Apache-2.0",
|
||||
"_depComment": "keymirror, nuclear-js, object-assign, ha-js-ws are for ha-js",
|
||||
"dependencies": {
|
||||
"classnames": "^2.2.5",
|
||||
"home-assistant-js-websocket": "0.5.0",
|
||||
"keymirror": "^0.1.1",
|
||||
"nuclear-js": "^1.4.0",
|
||||
"object-assign": "^4.1.1"
|
||||
"home-assistant-js-websocket": "^0.7.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bower": "^1.8.0",
|
||||
|
@ -1,6 +1,5 @@
|
||||
<link rel="import" href="../../bower_components/polymer/polymer.html">
|
||||
|
||||
<link rel="import" href="../../src/util/hass-behavior.html">
|
||||
|
||||
<dom-module id="events-list">
|
||||
<template>
|
||||
@ -24,7 +23,7 @@
|
||||
<template is='dom-repeat' items='[[events]]' as='event'>
|
||||
<li>
|
||||
<a href='#' on-click='eventSelected'>{{event.event}}</a>
|
||||
<span> (</span><span>{{event.listenerCount}}</span><span> listeners)</span>
|
||||
<span> (</span><span>{{event.listener_count}}</span><span> listeners)</span>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
@ -35,8 +34,6 @@
|
||||
Polymer({
|
||||
is: 'events-list',
|
||||
|
||||
behaviors: [window.hassBehavior],
|
||||
|
||||
properties: {
|
||||
hass: {
|
||||
type: Object,
|
||||
@ -44,15 +41,13 @@
|
||||
|
||||
events: {
|
||||
type: Array,
|
||||
bindNuclear: function (hass) {
|
||||
return [
|
||||
hass.eventGetters.entityMap,
|
||||
function (map) {
|
||||
return map.valueSeq().sortBy(function (event) { return event.event; }).toArray();
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
|
||||
attached: function () {
|
||||
this.hass.callApi('GET', 'events').then(function (events) {
|
||||
this.events = events;
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
eventSelected: function (ev) {
|
||||
|
@ -116,7 +116,12 @@ Polymer({
|
||||
return;
|
||||
}
|
||||
|
||||
this.hass.eventActions.fireEvent(this.eventType, eventData);
|
||||
this.hass.callApi('POST', 'events/' + this.eventType, eventData)
|
||||
.then(function () {
|
||||
this.fire('hass-notification', {
|
||||
message: 'Event ' + this.eventType + ' successful fired!',
|
||||
});
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
computeFormClasses: function (narrow) {
|
||||
|
@ -7,7 +7,6 @@
|
||||
<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/util/hass-behavior.html">
|
||||
<link rel="import" href="../../src/resources/ha-style.html">
|
||||
|
||||
<dom-module id="ha-panel-dev-info">
|
||||
@ -71,10 +70,10 @@
|
||||
<p class='version'>
|
||||
<a href='https://home-assistant.io'><img src="/static/icons/favicon-192x192.png" height="192" /></a><br />
|
||||
Home Assistant<br />
|
||||
[[hassVersion]]
|
||||
[[hass.config.core.version]]
|
||||
</p>
|
||||
<p>
|
||||
Path to configuration.yaml: [[hassConfigDir]]
|
||||
Path to configuration.yaml: [[hass.config.core.config_dir]]
|
||||
</p>
|
||||
<p class='develop'>
|
||||
<a href='https://home-assistant.io/developers/credits/' target='_blank'>
|
||||
@ -92,7 +91,6 @@
|
||||
Built using
|
||||
<a href='https://www.python.org'>Python 3</a>,
|
||||
<a href='https://www.polymer-project.org' target='_blank'>Polymer [[polymerVersion]]</a>,
|
||||
<a href='https://optimizely.github.io/nuclear-js/' target='_blank'>NuclearJS [[nuclearVersion]]</a><br />
|
||||
Icons by <a href='https://www.google.com/design/icons/' target='_blank'>Google</a> and <a href='https://MaterialDesignIcons.com' target='_blank'>MaterialDesignIcons.com</a>.
|
||||
</p>
|
||||
</div>
|
||||
@ -110,8 +108,6 @@
|
||||
Polymer({
|
||||
is: 'ha-panel-dev-info',
|
||||
|
||||
behaviors: [window.hassBehavior],
|
||||
|
||||
properties: {
|
||||
hass: {
|
||||
type: Object,
|
||||
@ -127,30 +123,11 @@ Polymer({
|
||||
value: false,
|
||||
},
|
||||
|
||||
hassVersion: {
|
||||
type: String,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.configGetters.serverVersion;
|
||||
},
|
||||
},
|
||||
|
||||
hassConfigDir: {
|
||||
type: String,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.configGetters.configDir;
|
||||
},
|
||||
},
|
||||
|
||||
polymerVersion: {
|
||||
type: String,
|
||||
value: Polymer.version,
|
||||
},
|
||||
|
||||
nuclearVersion: {
|
||||
type: String,
|
||||
value: '1.4.0',
|
||||
},
|
||||
|
||||
errorLog: {
|
||||
type: String,
|
||||
value: '',
|
||||
@ -166,7 +143,7 @@ Polymer({
|
||||
|
||||
this.errorLog = 'Loading error log…';
|
||||
|
||||
this.hass.errorLogActions.fetchErrorLog().then(
|
||||
this.hass.callApi('GET', 'error_log').then(
|
||||
function (log) {
|
||||
this.errorLog = log || 'No errors have been reported.';
|
||||
}.bind(this));
|
||||
|
@ -12,7 +12,6 @@
|
||||
|
||||
<link rel='import' href='../../src/components/ha-menu-button.html'>
|
||||
<link rel='import' href='../../src/resources/ha-style.html'>
|
||||
<link rel='import' href='../../src/util/hass-behavior.html'>
|
||||
|
||||
<dom-module id='ha-panel-dev-service'>
|
||||
<template>
|
||||
@ -142,8 +141,6 @@
|
||||
Polymer({
|
||||
is: 'ha-panel-dev-service',
|
||||
|
||||
behaviors: [window.hassBehavior],
|
||||
|
||||
properties: {
|
||||
hass: {
|
||||
type: Object,
|
||||
@ -178,56 +175,37 @@ Polymer({
|
||||
|
||||
_attributes: {
|
||||
type: Array,
|
||||
computed: 'computeAttributesArray(hass, domain, service)',
|
||||
computed: 'computeAttributesArray(serviceDomains, domain, service)',
|
||||
},
|
||||
|
||||
serviceDomains: {
|
||||
type: Array,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.serviceGetters.entityMap;
|
||||
},
|
||||
type: Object,
|
||||
computed: 'computeServiceDomains(hass)',
|
||||
},
|
||||
},
|
||||
|
||||
computeAttributesArray: function (hass, domain, service) {
|
||||
return hass.reactor.evaluate([
|
||||
hass.serviceGetters.entityMap,
|
||||
function (map) {
|
||||
if (map.has(domain) && map.get(domain).get('services').has(service)) {
|
||||
return map
|
||||
.get(domain)
|
||||
.get('services')
|
||||
.get(service)
|
||||
.get('fields')
|
||||
.map(function (field, key) {
|
||||
var fieldCopy = field.toJS();
|
||||
fieldCopy.key = key;
|
||||
return fieldCopy;
|
||||
})
|
||||
.toArray();
|
||||
}
|
||||
return [];
|
||||
computeServiceDomains: function (hass) {
|
||||
return hass.config.services;
|
||||
},
|
||||
]);
|
||||
|
||||
computeAttributesArray: function (serviceDomains, domain, service) {
|
||||
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 });
|
||||
});
|
||||
},
|
||||
|
||||
computeDomains: function (serviceDomains) {
|
||||
return serviceDomains
|
||||
.valueSeq()
|
||||
.map(function (domain) { return domain.domain; })
|
||||
.sort()
|
||||
.toJS();
|
||||
return Object.keys(serviceDomains).sort();
|
||||
},
|
||||
|
||||
computeServices: function (serviceDomains, domain) {
|
||||
if (domain) {
|
||||
return serviceDomains
|
||||
.get(domain)
|
||||
.get('services')
|
||||
.keySeq()
|
||||
.toArray();
|
||||
}
|
||||
return '';
|
||||
if (!(domain in serviceDomains)) return [];
|
||||
|
||||
return Object.keys(serviceDomains[domain]).sort();
|
||||
},
|
||||
|
||||
domainChanged: function () {
|
||||
@ -250,7 +228,7 @@ Polymer({
|
||||
return;
|
||||
}
|
||||
|
||||
this.hass.serviceActions.callService(this.domain, this.service, serviceData);
|
||||
this.hass.callService(this.domain, this.service, serviceData);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -10,7 +10,6 @@
|
||||
<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/util/hass-behavior.html">
|
||||
<link rel="import" href="../../src/resources/ha-style.html">
|
||||
|
||||
<dom-module id="ha-panel-dev-state">
|
||||
@ -82,7 +81,7 @@
|
||||
</tr>
|
||||
<template is='dom-repeat' items='[[_entities]]' as='entity'>
|
||||
<tr>
|
||||
<td><a href='#' on-tap='entitySelected'>[[entity.entityId]]</a></td>
|
||||
<td><a href='#' on-tap='entitySelected'>[[entity.entity_id]]</a></td>
|
||||
<td>[[entity.state]]</td>
|
||||
<template is='dom-if' if='[[computeShowAttributes(narrow, _showAttributes)]]'>
|
||||
<td>[[attributeString(entity)]]</td>
|
||||
@ -99,8 +98,6 @@
|
||||
Polymer({
|
||||
is: 'ha-panel-dev-state',
|
||||
|
||||
behaviors: [window.hassBehavior],
|
||||
|
||||
properties: {
|
||||
hass: {
|
||||
type: Object,
|
||||
@ -138,20 +135,13 @@ Polymer({
|
||||
|
||||
_entities: {
|
||||
type: Array,
|
||||
bindNuclear: function (hass) {
|
||||
return [
|
||||
hass.entityGetters.entityMap,
|
||||
function (map) {
|
||||
return map.valueSeq().sortBy(function (entity) { return entity.entityId; }).toArray();
|
||||
},
|
||||
];
|
||||
},
|
||||
computed: 'computeEntities(hass)',
|
||||
},
|
||||
},
|
||||
|
||||
entitySelected: function (ev) {
|
||||
var state = ev.model.entity;
|
||||
this._entityId = state.entityId;
|
||||
this._entityId = state.entity_id;
|
||||
this._state = state.state;
|
||||
this._stateAttributes = JSON.stringify(state.attributes, null, ' ');
|
||||
ev.preventDefault();
|
||||
@ -168,13 +158,25 @@ Polymer({
|
||||
return;
|
||||
}
|
||||
|
||||
this.hass.entityActions.save({
|
||||
entityId: this._entityId,
|
||||
this.hass.callApi('POST', 'states/' + this._entityId, {
|
||||
state: this._state,
|
||||
attributes: attr,
|
||||
});
|
||||
},
|
||||
|
||||
computeEntities: function (hass) {
|
||||
return Object.keys(hass.states).map(function (key) { return hass.states[key]; })
|
||||
.sort(function (entityA, entityB) {
|
||||
if (entityA.entity_id < entityB.entity_id) {
|
||||
return -1;
|
||||
}
|
||||
if (entityB.entity_id > entityA.entity_id) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
},
|
||||
|
||||
computeShowAttributes: function (narrow, _showAttributes) {
|
||||
return !narrow && _showAttributes;
|
||||
},
|
||||
|
@ -8,7 +8,6 @@
|
||||
<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/util/hass-behavior.html">
|
||||
<link rel="import" href="../../src/resources/ha-style.html">
|
||||
|
||||
<dom-module id="ha-panel-dev-template">
|
||||
@ -93,8 +92,6 @@
|
||||
Polymer({
|
||||
is: 'ha-panel-dev-template',
|
||||
|
||||
behaviors: [window.hassBehavior],
|
||||
|
||||
properties: {
|
||||
hass: {
|
||||
type: Object,
|
||||
@ -170,7 +167,8 @@ Polymer({
|
||||
renderTemplate: function () {
|
||||
this.rendering = true;
|
||||
|
||||
this.hass.templateActions.render(this.template).then(function (processed) {
|
||||
this.hass.callApi('POST', 'template', { template: this.template })
|
||||
.then(function (processed) {
|
||||
this.processed = processed;
|
||||
this.rendering = false;
|
||||
}.bind(this), function (error) {
|
||||
|
@ -9,7 +9,7 @@
|
||||
<link rel="import" href="../../src/components/state-history-charts.html">
|
||||
<link rel="import" href="../../src/resources/pikaday-js.html">
|
||||
<link rel="import" href="../../src/components/ha-menu-button.html">
|
||||
<link rel="import" href="../../src/util/hass-behavior.html">
|
||||
<link rel="import" href="../../src/data/ha-state-history-data.html">
|
||||
<link rel="import" href="../../src/resources/ha-style.html">
|
||||
|
||||
<dom-module id="ha-panel-history">
|
||||
@ -24,15 +24,19 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<ha-state-history-data
|
||||
hass='[[hass]]'
|
||||
filter-type='[[_filterType]]'
|
||||
filter-value='[[_computeFilterDate(_selectedDate)]]'
|
||||
data='{{stateHistory}}'
|
||||
isLoading='{{isLoadingData}}'
|
||||
></ha-state-history-data>
|
||||
|
||||
<app-header-layout has-scrolling-region>
|
||||
<app-header fixed>
|
||||
<app-toolbar>
|
||||
<ha-menu-button narrow='[[narrow]]' show-menu='[[showMenu]]'></ha-menu-button>
|
||||
<div main-title>History</div>
|
||||
<paper-icon-button
|
||||
icon="mdi:refresh"
|
||||
on-tap="handleRefreshClick"
|
||||
></paper-icon-button>
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
|
||||
@ -40,11 +44,11 @@
|
||||
<paper-input
|
||||
label='Showing entries for'
|
||||
id='datePicker'
|
||||
value='[[selectedDateStr]]'
|
||||
value='[[_computeDateDisplay(_selectedDate)]]'
|
||||
on-focus='datepickerFocus'
|
||||
></paper-input>
|
||||
|
||||
<state-history-charts state-history="[[stateHistory]]"
|
||||
<state-history-charts history-data="[[stateHistory]]"
|
||||
is-loading-data="[[isLoadingData]]"></state-history-charts>
|
||||
</div>
|
||||
</app-header-layout>
|
||||
@ -55,8 +59,6 @@
|
||||
Polymer({
|
||||
is: 'ha-panel-history',
|
||||
|
||||
behaviors: [window.hassBehavior],
|
||||
|
||||
properties: {
|
||||
hass: {
|
||||
type: Object,
|
||||
@ -71,63 +73,29 @@ Polymer({
|
||||
value: false,
|
||||
},
|
||||
|
||||
isDataLoaded: {
|
||||
type: Boolean,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.entityHistoryGetters.hasDataForCurrentDate;
|
||||
},
|
||||
observer: 'isDataLoadedChanged',
|
||||
},
|
||||
|
||||
stateHistory: {
|
||||
type: Object,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.entityHistoryGetters.entityHistoryForCurrentDate;
|
||||
},
|
||||
value: null,
|
||||
},
|
||||
|
||||
isLoadingData: {
|
||||
type: Boolean,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.entityHistoryGetters.isLoadingEntityHistory;
|
||||
value: false,
|
||||
},
|
||||
|
||||
_selectedDate: {
|
||||
type: Date,
|
||||
value: function () {
|
||||
return new Date();
|
||||
},
|
||||
},
|
||||
|
||||
selectedDate: {
|
||||
_filterType: {
|
||||
type: String,
|
||||
value: null,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.entityHistoryGetters.currentDate;
|
||||
value: 'date',
|
||||
},
|
||||
},
|
||||
|
||||
selectedDateStr: {
|
||||
type: String,
|
||||
value: null,
|
||||
bindNuclear: function (hass) {
|
||||
return [
|
||||
hass.entityHistoryGetters.currentDate,
|
||||
function (currentDate) {
|
||||
var dateObj = new Date(currentDate);
|
||||
return window.hassUtil.formatDate(dateObj);
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
isDataLoadedChanged: function (newVal) {
|
||||
if (!newVal) {
|
||||
this.async(function () {
|
||||
this.hass.entityHistoryActions.fetchSelectedDate();
|
||||
}.bind(this), 1);
|
||||
}
|
||||
},
|
||||
|
||||
handleRefreshClick: function () {
|
||||
this.hass.entityHistoryActions.fetchSelectedDate();
|
||||
},
|
||||
|
||||
datepickerFocus: function () {
|
||||
this.datePicker.adjustPosition();
|
||||
},
|
||||
@ -138,15 +106,30 @@ Polymer({
|
||||
// field value with its internal formatting.
|
||||
field: document.createElement('input'),
|
||||
trigger: this.$.datePicker.inputElement,
|
||||
onSelect: this.hass.entityHistoryActions.changeCurrentDate,
|
||||
onSelect: function (newDate) {
|
||||
newDate.setDate(newDate.getDate() + 1);
|
||||
|
||||
if (newDate > new Date()) {
|
||||
newDate = new Date();
|
||||
}
|
||||
|
||||
this._selectedDate = newDate;
|
||||
}.bind(this),
|
||||
});
|
||||
// Set the initial datePicker date, without triggering onSelect handler.
|
||||
this.datePicker.setDate(this.selectedDate, true);
|
||||
this.datePicker.setDate(this._selectedDate, true);
|
||||
},
|
||||
|
||||
detached: function () {
|
||||
this.datePicker.destroy();
|
||||
},
|
||||
|
||||
_computeDateDisplay: function (date) {
|
||||
return window.hassUtil.formatDate(new Date(date));
|
||||
},
|
||||
|
||||
_computeFilterDate: function (_selectedDate) {
|
||||
return _selectedDate.toISOString();
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
70
panels/logbook/ha-logbook-data.html
Normal file
70
panels/logbook/ha-logbook-data.html
Normal file
@ -0,0 +1,70 @@
|
||||
<link rel="import" href="../../bower_components/polymer/polymer.html">
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var DATE_CACHE = {};
|
||||
|
||||
Polymer({
|
||||
is: 'ha-logbook-data',
|
||||
|
||||
properties: {
|
||||
hass: {
|
||||
type: Object,
|
||||
observer: 'hassChanged',
|
||||
},
|
||||
|
||||
filterDate: {
|
||||
type: String,
|
||||
observer: 'filterDateChanged',
|
||||
},
|
||||
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
readOnly: true,
|
||||
notify: true,
|
||||
},
|
||||
|
||||
entries: {
|
||||
type: Object,
|
||||
value: null,
|
||||
readOnly: true,
|
||||
notify: true,
|
||||
},
|
||||
},
|
||||
|
||||
hassChanged: function (newHass, oldHass) {
|
||||
if (!oldHass && this.filterDate) {
|
||||
this.filterDateChanged(this.filterDate);
|
||||
}
|
||||
},
|
||||
|
||||
filterDateChanged: function (filterDate) {
|
||||
if (!this.hass) return;
|
||||
|
||||
this._setIsLoading(true);
|
||||
|
||||
this.getDate(filterDate).then(function (logbookEntries) {
|
||||
this._setEntries(logbookEntries);
|
||||
this._setIsLoading(false);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
getDate: function (date) {
|
||||
if (!DATE_CACHE[date]) {
|
||||
DATE_CACHE[date] = this.hass.callApi('GET', 'logbook/' + date).then(
|
||||
function (logbookEntries) {
|
||||
return logbookEntries;
|
||||
},
|
||||
function () {
|
||||
DATE_CACHE[date] = false;
|
||||
return null;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return DATE_CACHE[date];
|
||||
},
|
||||
});
|
||||
}());
|
||||
</script>
|
@ -47,10 +47,10 @@
|
||||
<div class='time'>[[formatTime(item.when)]]</div>
|
||||
<domain-icon domain="[[item.domain]]" class='icon'></domain-icon>
|
||||
<div class='message' flex>
|
||||
<template is='dom-if' if="[[!item.entityId]]">
|
||||
<template is='dom-if' if="[[!item.entity_id]]">
|
||||
<span class='name'>[[item.name]]</span>
|
||||
</template>
|
||||
<template is='dom-if' if="[[item.entityId]]">
|
||||
<template is='dom-if' if="[[item.entity_id]]">
|
||||
<a href='#' on-tap="entityClicked" class='name'>[[item.name]]</a>
|
||||
</template>
|
||||
<span> </span>
|
||||
@ -76,13 +76,13 @@ Polymer({
|
||||
},
|
||||
},
|
||||
|
||||
formatTime: function (dateObj) {
|
||||
return window.hassUtil.formatTime(dateObj);
|
||||
formatTime: function (date) {
|
||||
return window.hassUtil.formatTime(new Date(date));
|
||||
},
|
||||
|
||||
entityClicked: function (ev) {
|
||||
ev.preventDefault();
|
||||
this.hass.moreInfoActions.selectEntity(ev.model.item.entityId);
|
||||
this.fire('hass-more-info', { entityId: ev.model.item.entity_id });
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -10,10 +10,10 @@
|
||||
|
||||
<link rel="import" href="../../src/components/ha-menu-button.html">
|
||||
<link rel="import" href="../../src/resources/pikaday-js.html">
|
||||
<link rel="import" href="../../src/util/hass-behavior.html">
|
||||
<link rel="import" href="../../src/resources/ha-style.html">
|
||||
|
||||
<link rel="import" href="./ha-logbook.html">
|
||||
<link rel="import" href="./ha-logbook-data.html">
|
||||
|
||||
<dom-module id="ha-panel-logbook">
|
||||
<template>
|
||||
@ -31,15 +31,18 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<ha-logbook-data
|
||||
hass='[[hass]]'
|
||||
is-loading='{{isLoading}}'
|
||||
entries='{{entries}}'
|
||||
filter-date='[[_computeFilterDate(_selectedDate)]]'
|
||||
></ha-logbook-data>
|
||||
|
||||
<app-header-layout has-scrolling-region>
|
||||
<app-header fixed>
|
||||
<app-toolbar>
|
||||
<ha-menu-button narrow='[[narrow]]' show-menu='[[showMenu]]'></ha-menu-button>
|
||||
<div main-title>Logbook</div>
|
||||
<paper-icon-button
|
||||
icon="mdi:refresh"
|
||||
on-tap="handleRefresh"
|
||||
></paper-icon-button>
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
|
||||
@ -48,7 +51,7 @@
|
||||
<paper-input
|
||||
label='Showing entries for'
|
||||
id='datePicker'
|
||||
value='[[selectedDateStr]]'
|
||||
value='[[_computeDateDisplay(_selectedDate)]]'
|
||||
on-focus='datepickerFocus'
|
||||
></paper-input>
|
||||
|
||||
@ -68,8 +71,6 @@
|
||||
Polymer({
|
||||
is: 'ha-panel-logbook',
|
||||
|
||||
behaviors: [window.hassBehavior],
|
||||
|
||||
properties: {
|
||||
hass: {
|
||||
type: Object,
|
||||
@ -85,50 +86,19 @@ Polymer({
|
||||
value: false,
|
||||
},
|
||||
|
||||
selectedDate: {
|
||||
_selectedDate: {
|
||||
type: String,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.logbookGetters.currentDate;
|
||||
},
|
||||
},
|
||||
|
||||
selectedDateStr: {
|
||||
type: String,
|
||||
value: null,
|
||||
bindNuclear: function (hass) {
|
||||
return [
|
||||
hass.logbookGetters.currentDate,
|
||||
function (currentDate) {
|
||||
var dateObj = new Date(currentDate);
|
||||
return window.hassUtil.formatDate(dateObj);
|
||||
},
|
||||
];
|
||||
value: function () {
|
||||
return new Date();
|
||||
},
|
||||
},
|
||||
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.logbookGetters.isLoadingEntries;
|
||||
},
|
||||
},
|
||||
|
||||
isStale: {
|
||||
type: Boolean,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.logbookGetters.isCurrentStale;
|
||||
},
|
||||
observer: 'isStaleChanged',
|
||||
},
|
||||
|
||||
entries: {
|
||||
type: Array,
|
||||
bindNuclear: function (hass) {
|
||||
return [
|
||||
hass.logbookGetters.currentEntries,
|
||||
function (entries) { return entries.reverse().toArray(); },
|
||||
];
|
||||
},
|
||||
},
|
||||
|
||||
datePicker: {
|
||||
@ -136,18 +106,6 @@ Polymer({
|
||||
},
|
||||
},
|
||||
|
||||
isStaleChanged: function (newVal) {
|
||||
if (newVal) {
|
||||
this.async(function () {
|
||||
this.hass.logbookActions.fetchDate(this.selectedDate);
|
||||
}.bind(this), 1);
|
||||
}
|
||||
},
|
||||
|
||||
handleRefresh: function () {
|
||||
this.hass.logbookActions.fetchDate(this.selectedDate);
|
||||
},
|
||||
|
||||
datepickerFocus: function () {
|
||||
this.datePicker.adjustPosition();
|
||||
},
|
||||
@ -158,7 +116,15 @@ Polymer({
|
||||
// field value with its internal formatting.
|
||||
field: document.createElement('input'),
|
||||
trigger: this.$.datePicker.inputElement,
|
||||
onSelect: this.hass.logbookActions.changeCurrentDate,
|
||||
onSelect: function (newDate) {
|
||||
newDate.setDate(newDate.getDate() + 1);
|
||||
|
||||
if (newDate > new Date()) {
|
||||
newDate = new Date();
|
||||
}
|
||||
|
||||
this._selectedDate = newDate;
|
||||
}.bind(this),
|
||||
});
|
||||
// Set the initial datePicker date, without triggering onSelect handler.
|
||||
this.datePicker.setDate(this.selectedDate, true);
|
||||
@ -168,5 +134,12 @@ Polymer({
|
||||
this.datePicker.destroy();
|
||||
},
|
||||
|
||||
_computeDateDisplay: function (date) {
|
||||
return window.hassUtil.formatDate(new Date(date));
|
||||
},
|
||||
|
||||
_computeFilterDate: function (_selectedDate) {
|
||||
return _selectedDate.toISOString();
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -27,27 +27,24 @@
|
||||
</style>
|
||||
|
||||
<div class='marker'>
|
||||
<template is='dom-if' if='[[icon]]'>
|
||||
<iron-icon icon='[[icon]]'></iron-icon>
|
||||
</template>
|
||||
<template is='dom-if' if='[[value]]'>[[value]]</template>
|
||||
<template is='dom-if' if='[[image]]'>
|
||||
<iron-image sizing='cover' class='fit' src='[[image]]'></iron-image>
|
||||
<template is='dom-if' if='[[entityName]]'>[[entityName]]</template>
|
||||
<template is='dom-if' if='[[entityPicture]]'>
|
||||
<iron-image sizing='cover' class='fit' src='[[entityPicture]]'></iron-image>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
/*
|
||||
Leaflet clones this element before adding it to the map. This messes up
|
||||
our Poylmer object and we lose the reference to the `hass` object.
|
||||
|
||||
That's why we refer here to window.hass instead of the hass property.
|
||||
*/
|
||||
Polymer({
|
||||
is: 'ha-entity-marker',
|
||||
|
||||
hostAttributes: {
|
||||
entityId: null,
|
||||
entityName: null,
|
||||
entityPicture: null,
|
||||
},
|
||||
|
||||
properties: {
|
||||
hass: {
|
||||
type: Object,
|
||||
@ -56,28 +53,17 @@ Polymer({
|
||||
entityId: {
|
||||
type: String,
|
||||
value: '',
|
||||
reflectToAttribute: true,
|
||||
},
|
||||
|
||||
state: {
|
||||
type: Object,
|
||||
computed: 'computeState(entityId)',
|
||||
},
|
||||
|
||||
icon: {
|
||||
type: Object,
|
||||
computed: 'computeIcon(state)',
|
||||
},
|
||||
|
||||
image: {
|
||||
type: Object,
|
||||
computed: 'computeImage(state)',
|
||||
},
|
||||
|
||||
value: {
|
||||
entityName: {
|
||||
type: String,
|
||||
computed: 'computeValue(state)',
|
||||
value: null,
|
||||
},
|
||||
|
||||
entityPicture: {
|
||||
type: String,
|
||||
value: null,
|
||||
}
|
||||
},
|
||||
|
||||
listeners: {
|
||||
@ -87,29 +73,8 @@ Polymer({
|
||||
badgeTap: function (ev) {
|
||||
ev.stopPropagation();
|
||||
if (this.entityId) {
|
||||
this.async(function () {
|
||||
window.hass.moreInfoActions.selectEntity(this.entityId);
|
||||
}, 1);
|
||||
this.fire('hass-more-info', { entityId: this.entityId });
|
||||
}
|
||||
},
|
||||
|
||||
computeState: function (entityId) {
|
||||
return entityId && window.hass.reactor.evaluate(window.hass.entityGetters.byId(entityId));
|
||||
},
|
||||
|
||||
computeIcon: function (state) {
|
||||
return !state && 'home';
|
||||
},
|
||||
|
||||
computeImage: function (state) {
|
||||
return state && state.attributes.entity_picture;
|
||||
},
|
||||
|
||||
computeValue: function (state) {
|
||||
return state &&
|
||||
state.entityDisplay.split(' ').map(function (part) {
|
||||
return part.substr(0, 1);
|
||||
}).join('');
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -8,7 +8,6 @@
|
||||
<link rel="stylesheet" href="../../bower_components/leaflet/dist/leaflet.css" />
|
||||
|
||||
<link rel="import" href="../../src/components/ha-menu-button.html">
|
||||
<link rel="import" href="../../src/util/hass-behavior.html">
|
||||
|
||||
<link rel="import" href="./ha-entity-marker.html">
|
||||
|
||||
@ -37,41 +36,9 @@ window.L.Icon.Default.imagePath = '/static/images/leaflet';
|
||||
Polymer({
|
||||
is: 'ha-panel-map',
|
||||
|
||||
behaviors: [window.hassBehavior],
|
||||
|
||||
properties: {
|
||||
hass: {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
locationGPS: {
|
||||
type: Number,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.configGetters.locationGPS;
|
||||
},
|
||||
},
|
||||
|
||||
locationName: {
|
||||
type: String,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.configGetters.locationName;
|
||||
},
|
||||
},
|
||||
|
||||
locationEntities: {
|
||||
type: Array,
|
||||
bindNuclear: function (hass) {
|
||||
return [
|
||||
hass.entityGetters.entityMap,
|
||||
function (entities) {
|
||||
return entities.valueSeq().filter(
|
||||
function (entity) {
|
||||
return 'latitude' in entity.attributes;
|
||||
}
|
||||
);
|
||||
},
|
||||
];
|
||||
},
|
||||
observer: 'drawEntities',
|
||||
},
|
||||
|
||||
@ -96,7 +63,7 @@ Polymer({
|
||||
}
|
||||
).addTo(map);
|
||||
|
||||
this.drawEntities(this.locationEntities);
|
||||
this.drawEntities(this.hass);
|
||||
|
||||
this.async(function () {
|
||||
map.invalidateSize();
|
||||
@ -118,7 +85,7 @@ Polymer({
|
||||
this._map.fitBounds(bounds.pad(0.5));
|
||||
},
|
||||
|
||||
drawEntities: function (entities) {
|
||||
drawEntities: function (hass) {
|
||||
/* eslint-disable vars-on-top */
|
||||
var map = this._map;
|
||||
if (!map) return;
|
||||
@ -128,10 +95,21 @@ Polymer({
|
||||
}
|
||||
var mapItems = this._mapItems = [];
|
||||
|
||||
entities.forEach(function (entity) {
|
||||
Object.keys(hass.states).forEach(function (entityId) {
|
||||
var entity = hass.states[entityId];
|
||||
var title = window.hassUtil.computeStateName(entity);
|
||||
|
||||
if ((entity.attributes.hidden &&
|
||||
window.hassUtil.computeDomain(entity) !== 'zone') ||
|
||||
entity.state === 'home' ||
|
||||
!('latitude' in entity.attributes) ||
|
||||
!('longitude' in entity.attributes)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var icon;
|
||||
|
||||
if (entity.domain === 'zone') {
|
||||
if (window.hassUtil.computeDomain(entity) === 'zone') {
|
||||
// DRAW ZONE
|
||||
if (entity.attributes.passive) return;
|
||||
|
||||
@ -141,7 +119,7 @@ Polymer({
|
||||
iconHTML = (
|
||||
"<iron-icon icon='" + entity.attributes.icon + "'></iron-icon>");
|
||||
} else {
|
||||
iconHTML = entity.entityDisplay;
|
||||
iconHTML = title;
|
||||
}
|
||||
|
||||
icon = window.L.divIcon({
|
||||
@ -156,7 +134,7 @@ Polymer({
|
||||
{
|
||||
icon,
|
||||
interactive: false,
|
||||
title: entity.entityDisplay,
|
||||
title: title,
|
||||
}
|
||||
).addTo(map));
|
||||
|
||||
@ -173,13 +151,14 @@ Polymer({
|
||||
return;
|
||||
}
|
||||
|
||||
// Filter out entities at home
|
||||
if (entity.state === 'home' || entity.attributes.hidden) return;
|
||||
|
||||
// DRAW ENTITY
|
||||
// create icon
|
||||
var entityPicture = entity.attributes.entity_picture || '';
|
||||
var entityName = title.split(' ').map(function (part) { return part.substr(0, 1); }).join('');
|
||||
/* Leaflet clones this element before adding it to the map. This messes up
|
||||
our Poylmer object and we can't pass data through. Thus we hack like this. */
|
||||
icon = window.L.divIcon({
|
||||
html: "<ha-entity-marker entity-id='" + entity.entityId + "'></ha-entity-marker>",
|
||||
html: "<ha-entity-marker entity-id='" + entity.entity_id + "' entity-name='" + entityName + "' entity-picture='" + entityPicture + "'></ha-entity-marker>",
|
||||
iconSize: [45, 45],
|
||||
className: '',
|
||||
});
|
||||
@ -189,7 +168,7 @@ Polymer({
|
||||
[entity.attributes.latitude, entity.attributes.longitude],
|
||||
{
|
||||
icon,
|
||||
title: entity.entityDisplay,
|
||||
title: window.hassUtil.computeStateName(entity),
|
||||
}
|
||||
).addTo(map));
|
||||
|
||||
@ -212,7 +191,7 @@ Polymer({
|
||||
},
|
||||
|
||||
toggleMenu: function () {
|
||||
this.fire('open-menu');
|
||||
this.fire('hass-open-menu');
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -18,11 +18,10 @@ const plugins = [
|
||||
__DEMO__: JSON.stringify(DEMO),
|
||||
},
|
||||
}),
|
||||
|
||||
buble(),
|
||||
];
|
||||
|
||||
if (!DEV) {
|
||||
plugins.push(buble());
|
||||
plugins.push(uglify());
|
||||
}
|
||||
|
||||
|
@ -1,41 +1,28 @@
|
||||
import HomeAssistant from '../home-assistant-js/src/index';
|
||||
import * as HAWS from 'home-assistant-js-websocket';
|
||||
|
||||
const hass = new HomeAssistant();
|
||||
window.HAWS = HAWS;
|
||||
window.HASS_DEMO = __DEMO__;
|
||||
|
||||
window.validateAuth = function validateAuth(authToken, rememberAuth) {
|
||||
hass.authActions.validate(authToken, {
|
||||
rememberAuth,
|
||||
useStreaming: hass.localStoragePreferences.useStreaming,
|
||||
const init = window.createHassConnection = function (password) {
|
||||
const proto = window.location.protocol === 'https:' ? 'wss' : 'ws';
|
||||
const url = `${proto}://${window.location.host}/api/websocket`;
|
||||
const options = {};
|
||||
if (password !== undefined) {
|
||||
options.authToken = password;
|
||||
}
|
||||
|
||||
return HAWS.createConnection(url, options)
|
||||
.then(function (conn) {
|
||||
HAWS.subscribeEntities(conn);
|
||||
HAWS.subscribeConfig(conn);
|
||||
return conn;
|
||||
});
|
||||
};
|
||||
|
||||
window.removeInitMsg = function removeInitMessage() {
|
||||
// remove the HTML init message
|
||||
const initMsg = document.getElementById('ha-init-skeleton');
|
||||
if (initMsg) {
|
||||
initMsg.parentElement.removeChild(initMsg);
|
||||
}
|
||||
};
|
||||
|
||||
hass.reactor.batch(function () {
|
||||
hass.navigationActions.showSidebar(
|
||||
hass.localStoragePreferences.showSidebar);
|
||||
|
||||
// if auth was given, tell the backend
|
||||
if (window.noAuth) {
|
||||
window.validateAuth('', false);
|
||||
} else if (hass.localStoragePreferences.authToken) {
|
||||
window.validateAuth(hass.localStoragePreferences.authToken, true);
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(hass.startLocalStoragePreferencesSync, 5000);
|
||||
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', function () {
|
||||
navigator.serviceWorker.register('/service_worker.js');
|
||||
});
|
||||
if (window.noAuth) {
|
||||
window.hassConnection = init();
|
||||
} else if (window.localStorage.authToken) {
|
||||
window.hassConnection = init(window.localStorage.authToken);
|
||||
} else {
|
||||
window.hassConnection = null;
|
||||
}
|
||||
|
||||
// While we figure out how ha-entity-marker can keep it's references
|
||||
window.hass = hass;
|
||||
|
@ -38,9 +38,9 @@
|
||||
</style>
|
||||
|
||||
<img src='[[cameraFeedSrc]]' class='camera-feed' hidden$='[[!imageLoaded]]'
|
||||
on-load='imageLoadSuccess' on-error='imageLoadFail' alt='[[stateObj.entityDisplay]]'>
|
||||
on-load='imageLoadSuccess' on-error='imageLoadFail' alt='[[computeStateName(stateObj)]]'>
|
||||
<div class='caption'>
|
||||
[[stateObj.entityDisplay]]
|
||||
[[computeStateName(stateObj)]]
|
||||
<template is='dom-if' if='[[!imageLoaded]]'>
|
||||
(Error loading image)
|
||||
</template>
|
||||
@ -99,9 +99,7 @@ Polymer({
|
||||
},
|
||||
|
||||
cardTapped: function () {
|
||||
this.async(function () {
|
||||
this.hass.moreInfoActions.selectEntity(this.stateObj.entityId);
|
||||
}.bind(this), 1);
|
||||
this.fire('hass-more-info', { entityId: this.stateObj.entity_id });
|
||||
},
|
||||
|
||||
updateCameraFeedSrc: function (stateObj) {
|
||||
@ -117,5 +115,9 @@ Polymer({
|
||||
imageLoadFail: function () {
|
||||
this.imageLoaded = false;
|
||||
},
|
||||
|
||||
computeStateName: function (stateObj) {
|
||||
return window.hassUtil.computeStateName(stateObj);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -75,14 +75,16 @@ Polymer({
|
||||
states: {
|
||||
type: Array,
|
||||
},
|
||||
|
||||
groupEntity: {
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
|
||||
computeTitle: function (states, groupEntity) {
|
||||
return groupEntity ? groupEntity.entityDisplay :
|
||||
states[0].domain.replace(/_/g, ' ');
|
||||
return groupEntity ?
|
||||
window.hassUtil.computeStateName(groupEntity) :
|
||||
window.hassUtil.computeDomain(states[0]).replace(/_/g, ' ');
|
||||
},
|
||||
|
||||
computeTitleClass: function (groupEntity) {
|
||||
@ -105,26 +107,32 @@ Polymer({
|
||||
ev.stopPropagation();
|
||||
|
||||
if (ev.model) {
|
||||
entityId = ev.model.item.entityId;
|
||||
entityId = ev.model.item.entity_id;
|
||||
} else {
|
||||
entityId = this.groupEntity.entityId;
|
||||
entityId = this.groupEntity.entity_id;
|
||||
}
|
||||
this.async(function () { this.hass.moreInfoActions.selectEntity(entityId); }.bind(this), 1);
|
||||
this.fire('hass-more-info', { entityId: entityId });
|
||||
},
|
||||
|
||||
showGroupToggle: function (groupEntity, states) {
|
||||
var canToggleCount;
|
||||
|
||||
if (!groupEntity || !states || groupEntity.attributes.control === 'hidden' ||
|
||||
(groupEntity.state !== 'on' && groupEntity.state !== 'off')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// only show if we can toggle 2+ entities in group
|
||||
canToggleCount = states.reduce(
|
||||
function (sum, state) {
|
||||
return sum + window.hassUtil.canToggle(this.hass, state.entityId);
|
||||
}, 0);
|
||||
var canToggleCount = 0;
|
||||
for (var i = 0; i < states.length; i++) {
|
||||
if (!window.hassUtil.canToggleState(this.hass, states[i])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
canToggleCount++;
|
||||
|
||||
if (canToggleCount > 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return canToggleCount > 1;
|
||||
},
|
||||
|
@ -6,6 +6,8 @@
|
||||
<link rel='import' href='../../bower_components/paper-icon-button/paper-icon-button.html'>
|
||||
<link rel='import' href='../../bower_components/paper-progress/paper-progress.html'>
|
||||
|
||||
<link rel='import' href='../util/media-player-model.html'>
|
||||
|
||||
<dom-module id='ha-media_player-card'>
|
||||
<template>
|
||||
<style include="paper-material iron-flex iron-flex-alignment iron-positioning">
|
||||
@ -149,7 +151,7 @@
|
||||
<div class='cover' id='cover'></div>
|
||||
|
||||
<div class='caption'>
|
||||
[[stateObj.entityDisplay]]
|
||||
[[computeStateName(stateObj)]]
|
||||
<div class='title'>[[playerObj.primaryText]]</div>
|
||||
[[playerObj.secondaryText]]<br />
|
||||
</div>
|
||||
@ -217,7 +219,7 @@ Polymer({
|
||||
|
||||
playerObj: {
|
||||
type: Object,
|
||||
computed: 'computePlayerObj(stateObj)',
|
||||
computed: 'computePlayerObj(hass, stateObj)',
|
||||
observer: 'playerObjChanged',
|
||||
},
|
||||
|
||||
@ -290,8 +292,8 @@ Polymer({
|
||||
return playerObj.isOff ? !playerObj.supportsTurnOn : !playerObj.supportsTurnOff;
|
||||
},
|
||||
|
||||
computePlayerObj: function (stateObj) {
|
||||
return stateObj.domainModel(this.hass);
|
||||
computePlayerObj: function (hass, stateObj) {
|
||||
return new window.MediaPlayerEntity(hass, stateObj);
|
||||
},
|
||||
|
||||
computePlaybackControlIcon: function (playerObj) {
|
||||
@ -303,6 +305,10 @@ Polymer({
|
||||
return '';
|
||||
},
|
||||
|
||||
computeStateName: function (stateObj) {
|
||||
return window.hassUtil.computeStateName(stateObj);
|
||||
},
|
||||
|
||||
handleNext: function (ev) {
|
||||
ev.stopPropagation();
|
||||
this.playerObj.nextTrack();
|
||||
@ -310,9 +316,7 @@ Polymer({
|
||||
|
||||
handleOpenMoreInfo: function (ev) {
|
||||
ev.stopPropagation();
|
||||
this.async(function () {
|
||||
this.hass.moreInfoActions.selectEntity(this.stateObj.entityId);
|
||||
}, 1);
|
||||
this.fire('hass-more-info', { entityId: this.stateObj.entity_id });
|
||||
},
|
||||
|
||||
handlePlaybackControl: function (ev) {
|
||||
|
@ -49,7 +49,8 @@ Polymer({
|
||||
],
|
||||
|
||||
computeTitle: function (stateObj) {
|
||||
return stateObj.attributes.title || stateObj.entityDisplay;
|
||||
return (stateObj.attributes.title ||
|
||||
window.hassUtil.computeStateName(stateObj));
|
||||
},
|
||||
|
||||
loadScript: function () {
|
||||
@ -86,7 +87,7 @@ Polymer({
|
||||
dismissTap: function (ev) {
|
||||
ev.preventDefault();
|
||||
|
||||
this.hass.entityActions.delete(this.stateObj);
|
||||
this.hass.callApi('DELETE', 'states/' + this.stateObj.entity_id);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -110,24 +110,26 @@ Polymer({
|
||||
// result in the entity to be turned on. Since the state is not changing,
|
||||
// the resync is not called automatic.
|
||||
callService: function (turnOn) {
|
||||
var domain;
|
||||
var stateDomain = window.hassUtil.computeDomain(this.stateObj);
|
||||
var serviceDomain;
|
||||
var service;
|
||||
var currentState;
|
||||
|
||||
if (this.stateObj.domain === 'lock') {
|
||||
domain = 'lock';
|
||||
if (stateDomain === 'lock') {
|
||||
serviceDomain = 'lock';
|
||||
service = turnOn ? 'lock' : 'unlock';
|
||||
} else if (this.stateObj.domain === 'garage_door') {
|
||||
domain = 'garage_door';
|
||||
} else if (stateDomain === 'cover') {
|
||||
serviceDomain = 'cover';
|
||||
service = turnOn ? 'open' : 'close';
|
||||
} else {
|
||||
domain = 'homeassistant';
|
||||
serviceDomain = 'homeassistant';
|
||||
service = turnOn ? 'turn_on' : 'turn_off';
|
||||
}
|
||||
|
||||
currentState = this.stateObj;
|
||||
this.hass.serviceActions.callService(domain, service,
|
||||
{ entity_id: this.stateObj.entityId })
|
||||
this.hass.callService(
|
||||
serviceDomain, service,
|
||||
{ entity_id: this.stateObj.entity_id })
|
||||
.then(function () {
|
||||
setTimeout(function () {
|
||||
// If after 2 seconds we have not received a state update
|
||||
|
@ -57,13 +57,11 @@ Polymer({
|
||||
|
||||
badgeTap: function (ev) {
|
||||
ev.stopPropagation();
|
||||
this.async(function () {
|
||||
this.hass.moreInfoActions.selectEntity(this.state.entityId);
|
||||
}, 1);
|
||||
this.fire('hass-more-info', { entityId: this.state.entity_id });
|
||||
},
|
||||
|
||||
computeClasses: function (state) {
|
||||
switch (state.domain) {
|
||||
switch (window.hassUtil.computeDomain(state)) {
|
||||
case 'binary_sensor':
|
||||
case 'updater':
|
||||
return 'blue';
|
||||
@ -73,7 +71,7 @@ Polymer({
|
||||
},
|
||||
|
||||
computeValue: function (state) {
|
||||
switch (state.domain) {
|
||||
switch (window.hassUtil.computeDomain(state)) {
|
||||
case 'binary_sensor':
|
||||
case 'device_tracker':
|
||||
case 'updater':
|
||||
@ -90,7 +88,7 @@ Polymer({
|
||||
if (state.state === 'unavailable') {
|
||||
return null;
|
||||
}
|
||||
switch (state.domain) {
|
||||
switch (window.hassUtil.computeDomain(state)) {
|
||||
case 'alarm_control_panel':
|
||||
if (state.state === 'pending') {
|
||||
return 'mdi:clock-fast';
|
||||
@ -123,7 +121,7 @@ Polymer({
|
||||
if (state.state === 'unavailable') {
|
||||
return 'unavai';
|
||||
}
|
||||
switch (state.domain) {
|
||||
switch (window.hassUtil.computeDomain(state)) {
|
||||
case 'device_tracker':
|
||||
return state.state === 'not_home' ? 'Away' : state.state;
|
||||
case 'alarm_control_panel':
|
||||
@ -142,7 +140,7 @@ Polymer({
|
||||
},
|
||||
|
||||
computeDescription: function (state) {
|
||||
return state.entityDisplay;
|
||||
return window.hassUtil.computeStateName(state);
|
||||
},
|
||||
|
||||
stateChanged: function () {
|
||||
|
@ -38,7 +38,7 @@
|
||||
<ha-state-icon
|
||||
id='icon'
|
||||
state-obj='[[stateObj]]'
|
||||
data-domain$='[[stateObj.domain]]'
|
||||
data-domain$='[[computeDomain(stateObj)]]'
|
||||
data-state$='[[stateObj.state]]'
|
||||
></ha-state-icon>
|
||||
</template>
|
||||
@ -55,6 +55,10 @@ Polymer({
|
||||
},
|
||||
},
|
||||
|
||||
computeDomain: function (stateObj) {
|
||||
return window.hassUtil.computeDomain(stateObj);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when an attribute changes that influences the color of the icon.
|
||||
*/
|
||||
@ -71,7 +75,8 @@ Polymer({
|
||||
|
||||
// for domain light, set color of icon to light color if available and it is
|
||||
// not very white (sum rgb colors < 730)
|
||||
if (newVal.domain === 'light' && newVal.state === 'on' &&
|
||||
if (window.hassUtil.computeDomain(newVal) === 'light' &&
|
||||
newVal.state === 'on' &&
|
||||
newVal.attributes.rgb_color &&
|
||||
newVal.attributes.rgb_color.reduce(function (cur, tot) { return cur + tot; }, 0) < 730) {
|
||||
this.$.icon.style.color = 'rgb(' + newVal.attributes.rgb_color.join(',') + ')';
|
||||
|
@ -40,7 +40,7 @@
|
||||
<state-badge state-obj='[[stateObj]]'></state-badge>
|
||||
|
||||
<div class='info'>
|
||||
<div class='name' in-dialog$='[[inDialog]]'>[[stateObj.entityDisplay]]</div>
|
||||
<div class='name' in-dialog$='[[inDialog]]'>[[computeStateName(stateObj)]]</div>
|
||||
|
||||
<template is='dom-if' if='[[inDialog]]'>
|
||||
<div class='time-ago'>
|
||||
@ -71,5 +71,9 @@ Polymer({
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
|
||||
computeStateName: function (stateObj) {
|
||||
return window.hassUtil.computeStateName(stateObj);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -88,11 +88,18 @@
|
||||
weather: 4,
|
||||
};
|
||||
|
||||
// 4 types:
|
||||
// badges: 0 .. 10
|
||||
// before groups < 0
|
||||
// groups: X
|
||||
// rest: 100
|
||||
|
||||
var PRIORITY = {
|
||||
// before groups < 0
|
||||
configurator: -20,
|
||||
persistent_notification: -15,
|
||||
group: -10,
|
||||
a: -1,
|
||||
|
||||
// badges have priority >= 0
|
||||
updater: 0,
|
||||
sun: 1,
|
||||
device_tracker: 2,
|
||||
@ -102,14 +109,40 @@
|
||||
};
|
||||
|
||||
function getPriority(domain) {
|
||||
return (domain in PRIORITY) ? PRIORITY[domain] : 30;
|
||||
return (domain in PRIORITY) ? PRIORITY[domain] : 100;
|
||||
}
|
||||
|
||||
function entitySortBy(entity) {
|
||||
return entity.domain === 'group' ? entity.attributes.order :
|
||||
entity.entityDisplay.toLowerCase();
|
||||
function sortPriority(domainA, domainB) {
|
||||
return domainA.priority - domainB.priority;
|
||||
}
|
||||
|
||||
function entitySortBy(entityA, entityB) {
|
||||
var nameA = (entityA.attributes.friendly_name ||
|
||||
entityA.entity_id).toLowerCase();
|
||||
var nameB = (entityB.attributes.friendly_name ||
|
||||
entityB.entity_id).toLowerCase();
|
||||
|
||||
if (nameA < nameB) {
|
||||
return -1;
|
||||
}
|
||||
if (nameB > nameA) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function iterateDomainSorted(collection, func) {
|
||||
Object.keys(collection)
|
||||
.map(function (key) { return collection[key]; })
|
||||
.sort(sortPriority)
|
||||
.forEach(function (domain) {
|
||||
domain.states.sort(entitySortBy);
|
||||
func(domain);
|
||||
});
|
||||
}
|
||||
|
||||
var computeDomain = window.hassUtil.computeDomain;
|
||||
|
||||
Polymer({
|
||||
is: 'ha-cards',
|
||||
|
||||
@ -138,6 +171,7 @@
|
||||
|
||||
viewVisible: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
cards: {
|
||||
@ -160,13 +194,11 @@
|
||||
if (this.panelVisible && this.viewVisible) {
|
||||
this.cards = this.computeCards(columns, states, showIntroduction);
|
||||
}
|
||||
}.bind(this));
|
||||
}.bind(this), 10);
|
||||
},
|
||||
|
||||
computeCards: function (columns, states, showIntroduction) {
|
||||
var hass = this.hass;
|
||||
var byDomain = states.groupBy(function (entity) { return entity.domain; });
|
||||
var hasGroup = {};
|
||||
|
||||
var cards = {
|
||||
demo: false,
|
||||
@ -174,17 +206,12 @@
|
||||
columns: [],
|
||||
};
|
||||
var entityCount = [];
|
||||
var expandGroup;
|
||||
var i;
|
||||
for (i = 0; i < columns; i++) {
|
||||
cards.columns.push([]);
|
||||
entityCount.push(0);
|
||||
}
|
||||
|
||||
function filterGrouped(entities) {
|
||||
return entities.filter(function (entity) { return !(entity.entityId in hasGroup); });
|
||||
}
|
||||
|
||||
// Find column with < 5 entities, else column with lowest count
|
||||
function getIndex(size) {
|
||||
var minIndex = 0;
|
||||
@ -206,7 +233,7 @@
|
||||
cards.columns[getIndex(5)].push({
|
||||
hass: hass,
|
||||
cardType: 'introduction',
|
||||
showHideInstruction: states.size > 0 && !hass.demo,
|
||||
showHideInstruction: states.size > 0 && !window.HASS_DEMO,
|
||||
});
|
||||
}
|
||||
|
||||
@ -223,9 +250,11 @@
|
||||
size = 0;
|
||||
|
||||
entities.forEach(function (entity) {
|
||||
if (entity.domain in DOMAINS_WITH_CARD) {
|
||||
var domain = computeDomain(entity);
|
||||
|
||||
if (domain in DOMAINS_WITH_CARD) {
|
||||
owncard.push(entity);
|
||||
size += DOMAINS_WITH_CARD[entity.domain];
|
||||
size += DOMAINS_WITH_CARD[domain];
|
||||
} else {
|
||||
other.push(entity);
|
||||
size++;
|
||||
@ -249,43 +278,71 @@
|
||||
owncard.forEach(function (entity) {
|
||||
cards.columns[curIndex].push({
|
||||
hass: hass,
|
||||
cardType: entity.domain,
|
||||
cardType: computeDomain(entity),
|
||||
stateObj: entity,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
expandGroup = this.hass.util.expandGroup;
|
||||
var sorted = window.HAWS.splitByGroups(states);
|
||||
|
||||
byDomain.keySeq().sortBy(function (domain) { return getPriority(domain); })
|
||||
.forEach(function (domain) {
|
||||
var priority;
|
||||
var badgesColl = {};
|
||||
var beforeGroupColl = {};
|
||||
var afterGroupedColl = {};
|
||||
|
||||
Object.keys(sorted.ungrouped).forEach(function (key) {
|
||||
var state = sorted.ungrouped[key];
|
||||
var domain = computeDomain(state);
|
||||
|
||||
if (domain === 'a') {
|
||||
cards.demo = true;
|
||||
return;
|
||||
}
|
||||
|
||||
priority = getPriority(domain);
|
||||
var priority = getPriority(domain);
|
||||
var coll;
|
||||
|
||||
if (priority >= 0 && priority < 10) {
|
||||
cards.badges.push.apply(
|
||||
cards.badges, filterGrouped(byDomain.get(domain)).sortBy(
|
||||
entitySortBy).toArray());
|
||||
} else if (domain === 'group') {
|
||||
byDomain.get(domain).sortBy(entitySortBy)
|
||||
.forEach(function (groupState) {
|
||||
var entities = expandGroup(groupState, states);
|
||||
entities.forEach(function (entity) { hasGroup[entity.entityId] = true; });
|
||||
addEntitiesCard(groupState.entityId, entities.toArray(), groupState);
|
||||
}
|
||||
);
|
||||
if (priority < 0) {
|
||||
coll = beforeGroupColl;
|
||||
} else if (priority < 10) {
|
||||
coll = badgesColl;
|
||||
} else {
|
||||
coll = afterGroupedColl;
|
||||
}
|
||||
|
||||
if (!(domain in coll)) {
|
||||
coll[domain] = {
|
||||
domain: domain,
|
||||
priority: priority,
|
||||
states: [],
|
||||
};
|
||||
}
|
||||
|
||||
coll[domain].states.push(state);
|
||||
});
|
||||
|
||||
iterateDomainSorted(badgesColl, function (domain) {
|
||||
cards.badges.push.apply(cards.badges, domain.states);
|
||||
});
|
||||
|
||||
iterateDomainSorted(beforeGroupColl, function (domain) {
|
||||
addEntitiesCard(domain.domain, domain.states);
|
||||
});
|
||||
|
||||
sorted.groups.forEach(function (groupState) {
|
||||
var entities = window.HAWS.getGroupEntities(states, groupState);
|
||||
addEntitiesCard(
|
||||
domain, filterGrouped(byDomain.get(domain)).sortBy(entitySortBy).toArray());
|
||||
}
|
||||
}
|
||||
groupState.entity_id,
|
||||
Object.keys(entities).map(function (key) {
|
||||
return entities[key];
|
||||
}),
|
||||
groupState
|
||||
);
|
||||
});
|
||||
|
||||
iterateDomainSorted(afterGroupedColl, function (domain) {
|
||||
addEntitiesCard(domain.domain, domain.states);
|
||||
});
|
||||
|
||||
// Remove empty columns
|
||||
cards.columns = cards.columns.filter(function (val) {
|
||||
|
@ -36,8 +36,9 @@ Polymer({
|
||||
return !narrow && showMenu ? 'invisible' : '';
|
||||
},
|
||||
|
||||
toggleMenu: function () {
|
||||
this.fire('open-menu');
|
||||
toggleMenu: function (ev) {
|
||||
ev.stopPropagation();
|
||||
this.fire('hass-open-menu');
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
134
src/components/ha-push-notifications-toggle.html
Normal file
134
src/components/ha-push-notifications-toggle.html
Normal file
@ -0,0 +1,134 @@
|
||||
<link rel="import" href="../../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../../bower_components/paper-toggle-button/paper-toggle-button.html">
|
||||
|
||||
<dom-module id='ha-push-notifications-toggle'>
|
||||
<template>
|
||||
<paper-toggle-button
|
||||
hidden$='[[!pushSupported]]'
|
||||
disabled='[[loading]]'
|
||||
on-change='handlePushChange'
|
||||
checked='[[pushActive]]'
|
||||
></paper-toggle-button>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
Polymer({
|
||||
is: 'ha-push-notifications-toggle',
|
||||
properties: {
|
||||
hass: { type: Object, value: null },
|
||||
pushSupported: {
|
||||
type: Boolean,
|
||||
readOnly: true,
|
||||
notify: true,
|
||||
value: (
|
||||
'PushManager' in window &&
|
||||
(document.location.protocol === 'https:' ||
|
||||
document.location.hostname === 'localhost' ||
|
||||
document.location.hostname === '127.0.0.1')
|
||||
)
|
||||
},
|
||||
pushActive: {
|
||||
type: Boolean,
|
||||
value: 'Notification' in window && Notification.permission === 'granted'
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
}
|
||||
},
|
||||
attached: function () {
|
||||
if (!this.pushSupported) return;
|
||||
|
||||
var el = this;
|
||||
|
||||
navigator.serviceWorker.ready.then(
|
||||
function (reg) {
|
||||
reg.pushManager.getSubscription().then(function (subscription) {
|
||||
el.loading = false;
|
||||
el.pushActive = !!subscription;
|
||||
});
|
||||
},
|
||||
function () {
|
||||
// no service worker.
|
||||
el._setPushSupported(false);
|
||||
});
|
||||
},
|
||||
handlePushChange: function (ev) {
|
||||
if (ev.target.checked) {
|
||||
this.subscribePushNotifications();
|
||||
} else {
|
||||
this.unsubscribePushNotifications();
|
||||
}
|
||||
},
|
||||
subscribePushNotifications: function () {
|
||||
var el = this;
|
||||
|
||||
navigator.serviceWorker.ready
|
||||
.then(function (reg) {
|
||||
return reg.pushManager.subscribe({ userVisibleOnly: true });
|
||||
})
|
||||
.then(
|
||||
function (sub) {
|
||||
var browserName;
|
||||
if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1) {
|
||||
browserName = 'firefox';
|
||||
} else {
|
||||
browserName = 'chrome';
|
||||
}
|
||||
|
||||
return el.hass.callApi('POST', 'notify.html5', {
|
||||
subscription: sub,
|
||||
browser: browserName
|
||||
}).then(function () {
|
||||
el.pushActive = true;
|
||||
});
|
||||
},
|
||||
function (err) {
|
||||
var message;
|
||||
if (err.message && err.message.indexOf('gcm_sender_id') !== -1) {
|
||||
message = 'Please setup the notify.html5 platform.';
|
||||
} else {
|
||||
message = 'Notification registration failed.';
|
||||
}
|
||||
|
||||
/* eslint-disable no-console */
|
||||
console.error(err);
|
||||
/* eslint-enable no-console */
|
||||
|
||||
el.fire('hass-notification', { message: message });
|
||||
el.pushActive = false;
|
||||
}
|
||||
);
|
||||
},
|
||||
unsubscribePushNotifications: function () {
|
||||
var el = this;
|
||||
|
||||
navigator.serviceWorker.ready
|
||||
.then(function (reg) {
|
||||
return reg.pushManager.getSubscription();
|
||||
})
|
||||
.then(function (sub) {
|
||||
if (!sub) return Promise.resolve();
|
||||
|
||||
return el.hass
|
||||
.callApi('DELETE', 'notify.html5', { subscription: sub })
|
||||
.then(function () {
|
||||
sub.unsubscribe();
|
||||
});
|
||||
})
|
||||
.then(function () {
|
||||
el.pushActive = false;
|
||||
})
|
||||
.catch(function (err) {
|
||||
/* eslint-disable no-console */
|
||||
console.error('Error in unsub push', err);
|
||||
/* eslint-enable no-console */
|
||||
|
||||
el.fire('hass-notification', {
|
||||
message: 'Failed unsubscribing for push notifications.'
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
@ -3,13 +3,12 @@
|
||||
<link rel='import' href='../../bower_components/iron-icon/iron-icon.html'>
|
||||
<link rel='import' href='../../bower_components/paper-menu/paper-menu.html'>
|
||||
<link rel='import' href='../../bower_components/paper-item/paper-item.html'>
|
||||
<link rel='import' href='../../bower_components/paper-toggle-button/paper-toggle-button.html'>
|
||||
<link rel='import' href='../../bower_components/paper-item/paper-icon-item.html'>
|
||||
<link rel='import' href='../../bower_components/paper-icon-button/paper-icon-button.html'>
|
||||
|
||||
<link rel="import" href="../../bower_components/app-layout/app-toolbar/app-toolbar.html">
|
||||
|
||||
<link rel='import' href='../util/hass-behavior.html'>
|
||||
<link rel='import' href='./ha-push-notifications-toggle.html'>
|
||||
|
||||
<dom-module id='ha-sidebar'>
|
||||
<template>
|
||||
@ -98,13 +97,13 @@
|
||||
<paper-icon-button icon='mdi:chevron-left' hidden$='[[narrow]]' on-tap='toggleMenu'></paper-icon-button>
|
||||
</app-toolbar>
|
||||
|
||||
<paper-menu attr-for-selected='data-panel' selected='[[selected]]' on-iron-select='menuSelect'>
|
||||
<paper-menu attr-for-selected='data-panel' selected='[[hass.currentPanel]]' on-iron-select='menuSelect'>
|
||||
<paper-icon-item on-tap='menuClicked' data-panel='states'>
|
||||
<iron-icon item-icon icon='mdi:apps'></iron-icon>
|
||||
<span class='item-text'>States</span>
|
||||
</paper-icon-item>
|
||||
|
||||
<template is='dom-repeat' items='[[computePanels(panels)]]'>
|
||||
<template is='dom-repeat' items='[[panels]]'>
|
||||
<paper-icon-item on-tap='menuClicked' data-panel$='[[item.url_path]]'>
|
||||
<iron-icon item-icon icon='[[item.icon]]'></iron-icon>
|
||||
<span class='item-text'>[[item.title]]</span>
|
||||
@ -118,15 +117,15 @@
|
||||
</paper-menu>
|
||||
|
||||
<div>
|
||||
<template is='dom-if' if='[[supportPush]]'>
|
||||
<template is='dom-if' if='[[pushSupported]]'>
|
||||
<div class='divider'></div>
|
||||
|
||||
<paper-item class='horizontal layout justified'>
|
||||
<div class='setting'>Push Notifications</div>
|
||||
<paper-toggle-button
|
||||
on-change='handlePushChange'
|
||||
checked='{{pushToggleChecked}}'
|
||||
></paper-toggle-button>
|
||||
<ha-push-notifications-toggle
|
||||
hass='[[hass]]'
|
||||
push-supported='{{pushSupported}}'
|
||||
></ha-push-notifications-toggle>
|
||||
</paper-item>
|
||||
</template>
|
||||
|
||||
@ -164,8 +163,6 @@
|
||||
Polymer({
|
||||
is: 'ha-sidebar',
|
||||
|
||||
behaviors: [window.hassBehavior],
|
||||
|
||||
properties: {
|
||||
hass: {
|
||||
type: Object,
|
||||
@ -183,36 +180,14 @@ Polymer({
|
||||
type: Boolean,
|
||||
},
|
||||
|
||||
selected: {
|
||||
type: String,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.navigationGetters.activePanelName;
|
||||
},
|
||||
},
|
||||
|
||||
panels: {
|
||||
type: Array,
|
||||
bindNuclear: function (hass) {
|
||||
return [
|
||||
hass.navigationGetters.panels,
|
||||
function (res) { return res.toJS(); },
|
||||
];
|
||||
},
|
||||
computed: 'computePanels(hass)',
|
||||
},
|
||||
|
||||
supportPush: {
|
||||
pushSupported: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.pushNotificationGetters.isSupported;
|
||||
},
|
||||
},
|
||||
|
||||
pushToggleChecked: {
|
||||
type: Boolean,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.pushNotificationGetters.isActive;
|
||||
},
|
||||
value: true,
|
||||
},
|
||||
},
|
||||
|
||||
@ -220,7 +195,8 @@ Polymer({
|
||||
this._boundUpdateStyles = this.updateStyles.bind(this);
|
||||
},
|
||||
|
||||
computePanels: function (panels) {
|
||||
computePanels: function (hass) {
|
||||
var panels = hass.config.panels;
|
||||
var sortValue = {
|
||||
map: 1,
|
||||
logbook: 2,
|
||||
@ -280,36 +256,22 @@ Polymer({
|
||||
},
|
||||
|
||||
toggleMenu: function () {
|
||||
this.fire('close-menu');
|
||||
this.fire('hass-close-menu');
|
||||
},
|
||||
|
||||
selectPanel: function (newChoice) {
|
||||
if (newChoice === this.selected) {
|
||||
if (newChoice === this.hass.currentPanel) {
|
||||
return;
|
||||
} else if (newChoice === 'logout') {
|
||||
this.handleLogOut();
|
||||
return;
|
||||
}
|
||||
this.hass.navigationActions.navigate.apply(null, newChoice.split('/'));
|
||||
this.fire('hass-navigate', { panel: newChoice });
|
||||
this.debounce('updateStyles', this._boundUpdateStyles, 1);
|
||||
},
|
||||
|
||||
handlePushChange: function (ev) {
|
||||
if (ev.target.checked) {
|
||||
this.hass.pushNotificationActions.subscribePushNotifications()
|
||||
.then(function (success) {
|
||||
this.pushToggleChecked = success;
|
||||
}.bind(this));
|
||||
} else {
|
||||
this.hass.pushNotificationActions.unsubscribePushNotifications()
|
||||
.then(function (success) {
|
||||
this.pushToggleChecked = !success;
|
||||
}.bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
handleLogOut: function () {
|
||||
this.hass.authActions.logOut();
|
||||
this.fire('hass-logout');
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
39
src/components/ha-start-voice-button.html
Normal file
39
src/components/ha-start-voice-button.html
Normal file
@ -0,0 +1,39 @@
|
||||
<link rel="import" href="../../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../../bower_components/paper-icon-button/paper-icon-button.html">
|
||||
|
||||
<dom-module id='ha-start-voice-button'>
|
||||
<template>
|
||||
<paper-icon-button
|
||||
icon="mdi:microphone" hidden$='[[!canListen]]'
|
||||
on-tap="handleListenClick"
|
||||
></paper-icon-button>
|
||||
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
Polymer({
|
||||
is: 'ha-start-voice-button',
|
||||
|
||||
properties: {
|
||||
hass: {
|
||||
type: Object,
|
||||
value: null,
|
||||
},
|
||||
|
||||
canListen: {
|
||||
type: Boolean,
|
||||
computed: 'computeCanListen(hass)',
|
||||
},
|
||||
},
|
||||
|
||||
computeCanListen: function (hass) {
|
||||
return ('webkitSpeechRecognition' in window &&
|
||||
window.hassUtil.isComponentLoaded(hass, 'conversation'));
|
||||
},
|
||||
|
||||
handleListenClick: function () {
|
||||
this.fire('hass-start-voice');
|
||||
},
|
||||
});
|
||||
</script>
|
@ -112,7 +112,7 @@
|
||||
}
|
||||
|
||||
startTime = new Date(Math.min.apply(null, deviceStates.map(function (states) {
|
||||
return states[0].lastChangedAsDate;
|
||||
return new Date(states[0].last_changed);
|
||||
})));
|
||||
|
||||
endTime = new Date(startTime);
|
||||
@ -123,8 +123,8 @@
|
||||
|
||||
dataTables = deviceStates.map(function (states) {
|
||||
var last = states[states.length - 1];
|
||||
var domain = last.domain;
|
||||
var name = last.entityDisplay;
|
||||
var domain = window.hassUtil.computeDomain(last);
|
||||
var name = window.hassUtil.computeStateName(last);
|
||||
var data = [];
|
||||
var dataTable = new window.google.visualization.DataTable();
|
||||
// array containing [time, value1, value2, etc]
|
||||
@ -168,7 +168,9 @@
|
||||
var curTemp = saveParseFloat(state.attributes.current_temperature);
|
||||
var targetHigh = saveParseFloat(state.attributes.target_temp_high);
|
||||
var targetLow = saveParseFloat(state.attributes.target_temp_low);
|
||||
pushData([state.lastUpdatedAsDate, curTemp, targetHigh, targetLow], noInterpolations);
|
||||
pushData(
|
||||
[new Date(state.last_updated), curTemp, targetHigh, targetLow],
|
||||
noInterpolations);
|
||||
};
|
||||
} else {
|
||||
dataTable.addColumn('number', name + ' target temperature');
|
||||
@ -178,7 +180,7 @@
|
||||
processState = function (state) {
|
||||
var curTemp = saveParseFloat(state.attributes.current_temperature);
|
||||
var target = saveParseFloat(state.attributes.temperature);
|
||||
pushData([state.lastUpdatedAsDate, curTemp, target], noInterpolations);
|
||||
pushData([new Date(state.last_updated), curTemp, target], noInterpolations);
|
||||
};
|
||||
}
|
||||
|
||||
@ -203,7 +205,9 @@
|
||||
var curTemp = saveParseFloat(state.attributes.current_temperature);
|
||||
var targetHigh = saveParseFloat(state.attributes.target_temp_high);
|
||||
var targetLow = saveParseFloat(state.attributes.target_temp_low);
|
||||
pushData([state.lastUpdatedAsDate, curTemp, targetHigh, targetLow], noInterpolations);
|
||||
pushData(
|
||||
[new Date(state.last_updated), curTemp, targetHigh, targetLow],
|
||||
noInterpolations);
|
||||
};
|
||||
} else {
|
||||
dataTable.addColumn('number', name + ' target temperature');
|
||||
@ -213,7 +217,7 @@
|
||||
processState = function (state) {
|
||||
var curTemp = saveParseFloat(state.attributes.current_temperature);
|
||||
var target = saveParseFloat(state.attributes.temperature);
|
||||
pushData([state.lastUpdatedAsDate, curTemp, target], noInterpolations);
|
||||
pushData([new Date(state.last_updated), curTemp, target], noInterpolations);
|
||||
};
|
||||
}
|
||||
|
||||
@ -226,7 +230,7 @@
|
||||
|
||||
states.forEach(function (state) {
|
||||
var value = saveParseFloat(state.state);
|
||||
pushData([state.lastChangedAsDate, value], noInterpolations);
|
||||
pushData([new Date(state.last_changed), value], noInterpolations);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -73,7 +73,7 @@ Polymer({
|
||||
startTime = new Date(
|
||||
stateHistory.reduce(
|
||||
function (minTime, stateInfo) {
|
||||
return Math.min(minTime, stateInfo[0].lastChangedAsDate);
|
||||
return Math.min(minTime, new Date(stateInfo[0].last_changed));
|
||||
}, new Date()));
|
||||
|
||||
// end time is Math.min(curTime, start time + 1 day)
|
||||
@ -93,11 +93,11 @@ Polymer({
|
||||
|
||||
if (stateInfo.length === 0) return;
|
||||
|
||||
entityDisplay = stateInfo[0].entityDisplay;
|
||||
entityDisplay = window.hassUtil.computeStateName(stateInfo[0]);
|
||||
|
||||
stateInfo.forEach(function (state) {
|
||||
if (prevState !== null && state.state !== prevState) {
|
||||
newLastChanged = state.lastChangedAsDate;
|
||||
newLastChanged = new Date(state.last_changed);
|
||||
|
||||
addRow(entityDisplay, prevState, prevLastChanged, newLastChanged);
|
||||
|
||||
@ -105,7 +105,7 @@ Polymer({
|
||||
prevLastChanged = newLastChanged;
|
||||
} else if (prevState === null) {
|
||||
prevState = state.state;
|
||||
prevLastChanged = state.lastChangedAsDate;
|
||||
prevLastChanged = new Date(state.last_changed);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -24,30 +24,31 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<google-legacy-loader on-api-load="googleApiLoaded"></google-legacy-loader>
|
||||
<google-legacy-loader on-api-load="_googleApiLoaded"></google-legacy-loader>
|
||||
|
||||
<div hidden$="[[!isLoading]]" class='loading-container'>
|
||||
<template is='dom-if' if='[[_isLoading]]'>
|
||||
<div class='loading-container'>
|
||||
<paper-spinner active alt='Updating history data'></paper-spinner>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class$='[[computeContentClasses(isLoading)]]'>
|
||||
<template is='dom-if' if='[[computeIsEmpty(stateHistory)]]'>
|
||||
<template is='dom-if' if='[[!_isLoading]]'>
|
||||
<template is='dom-if' if='[[_computeIsEmpty(historyData)]]'>
|
||||
No state history found.
|
||||
</template>
|
||||
|
||||
<state-history-chart-timeline
|
||||
data='[[groupedStateHistory.timeline]]'
|
||||
is-single-device='[[isSingleDevice]]'>
|
||||
data='[[historyData.timeline]]'>
|
||||
</state-history-chart-timeline>
|
||||
|
||||
<template is='dom-repeat' items='[[groupedStateHistory.line]]'>
|
||||
<template is='dom-repeat' items='[[historyData.line]]'>
|
||||
<state-history-chart-line
|
||||
unit='[[item.unit]]'
|
||||
data='[[item.data]]'
|
||||
is-single-device='[[isSingleDevice]]'>
|
||||
is-single-device='[[_computeIsSingleLineChart(historyData)]]'>
|
||||
</state-history-chart-line>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
@ -56,101 +57,48 @@ Polymer({
|
||||
is: 'state-history-charts',
|
||||
|
||||
properties: {
|
||||
stateHistory: {
|
||||
historyData: {
|
||||
type: Object,
|
||||
value: null,
|
||||
},
|
||||
|
||||
isLoadingData: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
value: true,
|
||||
},
|
||||
|
||||
apiLoaded: {
|
||||
_apiLoaded: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
isLoading: {
|
||||
_isLoading: {
|
||||
type: Boolean,
|
||||
computed: 'computeIsLoading(isLoadingData, apiLoaded)',
|
||||
},
|
||||
|
||||
groupedStateHistory: {
|
||||
type: Object,
|
||||
computed: 'computeGroupedStateHistory(isLoading, stateHistory)',
|
||||
},
|
||||
|
||||
isSingleDevice: {
|
||||
type: Boolean,
|
||||
computed: 'computeIsSingleDevice(stateHistory)',
|
||||
computed: '_computeIsLoading(isLoadingData, _apiLoaded)',
|
||||
},
|
||||
},
|
||||
|
||||
computeIsSingleDevice: function (stateHistory) {
|
||||
return stateHistory && stateHistory.size === 1;
|
||||
_computeIsSingleLineChart: function (historyData) {
|
||||
return historyData && historyData.line.length === 1;
|
||||
},
|
||||
|
||||
computeGroupedStateHistory: function (isLoading, stateHistory) {
|
||||
var lineChartDevices = {};
|
||||
var timelineDevices = [];
|
||||
var unitStates;
|
||||
|
||||
if (isLoading || !stateHistory) {
|
||||
return { line: [], timeline: [] };
|
||||
}
|
||||
|
||||
stateHistory.forEach(function (stateInfo) {
|
||||
var stateWithUnit;
|
||||
var unit;
|
||||
|
||||
if (!stateInfo || stateInfo.size === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
stateWithUnit = stateInfo.find(
|
||||
function (state) { return 'unit_of_measurement' in state.attributes; });
|
||||
|
||||
unit = stateWithUnit ?
|
||||
stateWithUnit.attributes.unit_of_measurement : false;
|
||||
|
||||
if (!unit) {
|
||||
timelineDevices.push(stateInfo.toArray());
|
||||
} else if (unit in lineChartDevices) {
|
||||
lineChartDevices[unit].push(stateInfo.toArray());
|
||||
} else {
|
||||
lineChartDevices[unit] = [stateInfo.toArray()];
|
||||
}
|
||||
});
|
||||
|
||||
timelineDevices = timelineDevices.length > 0 && timelineDevices;
|
||||
|
||||
unitStates = Object.keys(lineChartDevices).map(
|
||||
function (unit) {
|
||||
return { unit: unit, data: lineChartDevices[unit] };
|
||||
});
|
||||
|
||||
return { line: unitStates, timeline: timelineDevices };
|
||||
},
|
||||
|
||||
googleApiLoaded: function () {
|
||||
_googleApiLoaded: function () {
|
||||
window.google.load('visualization', '1', {
|
||||
packages: ['timeline', 'corechart'],
|
||||
callback: function () {
|
||||
this.apiLoaded = true;
|
||||
this._apiLoaded = true;
|
||||
}.bind(this),
|
||||
});
|
||||
},
|
||||
|
||||
computeContentClasses: function (isLoading) {
|
||||
return isLoading ? 'loading' : '';
|
||||
_computeIsLoading: function (_isLoadingData, _apiLoaded) {
|
||||
return _isLoadingData || !_apiLoaded;
|
||||
},
|
||||
|
||||
computeIsLoading: function (isLoadingData, apiLoaded) {
|
||||
return isLoadingData || !apiLoaded;
|
||||
},
|
||||
|
||||
computeIsEmpty: function (stateHistory) {
|
||||
return stateHistory && stateHistory.size === 0;
|
||||
_computeIsEmpty: function (historyData) {
|
||||
return (historyData &&
|
||||
historyData.timeline.length === 0 &&
|
||||
historyData.line.length === 0);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
162
src/data/ha-state-history-data.html
Normal file
162
src/data/ha-state-history-data.html
Normal file
@ -0,0 +1,162 @@
|
||||
<link rel="import" href="../../bower_components/polymer/polymer.html">
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var RECENT_THRESHOLD = 60000; // 1 minute
|
||||
var DATE_CACHE = {};
|
||||
var RECENT_CACHE = {};
|
||||
|
||||
function computeHistory(stateHistory) {
|
||||
var lineChartDevices = {};
|
||||
var timelineDevices = [];
|
||||
var unitStates;
|
||||
|
||||
if (!stateHistory) {
|
||||
return { line: [], timeline: [] };
|
||||
}
|
||||
|
||||
stateHistory.forEach(function (stateInfo) {
|
||||
var stateWithUnit;
|
||||
var unit;
|
||||
|
||||
if (stateInfo.size === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
stateWithUnit = stateInfo.find(
|
||||
function (state) { return 'unit_of_measurement' in state.attributes; });
|
||||
|
||||
unit = stateWithUnit ?
|
||||
stateWithUnit.attributes.unit_of_measurement : false;
|
||||
|
||||
if (!unit) {
|
||||
timelineDevices.push(stateInfo);
|
||||
} else if (unit in lineChartDevices) {
|
||||
lineChartDevices[unit].push(stateInfo);
|
||||
} else {
|
||||
lineChartDevices[unit] = [stateInfo];
|
||||
}
|
||||
});
|
||||
|
||||
timelineDevices = timelineDevices.length > 0 && timelineDevices;
|
||||
|
||||
unitStates = Object.keys(lineChartDevices).map(
|
||||
function (unit) {
|
||||
return { unit: unit, data: lineChartDevices[unit] };
|
||||
});
|
||||
|
||||
return { line: unitStates, timeline: timelineDevices };
|
||||
}
|
||||
|
||||
Polymer({
|
||||
is: 'ha-state-history-data',
|
||||
|
||||
properties: {
|
||||
hass: {
|
||||
type: Object,
|
||||
observer: 'hassChanged',
|
||||
},
|
||||
|
||||
filterType: {
|
||||
type: String,
|
||||
},
|
||||
|
||||
filterValue: {
|
||||
type: String,
|
||||
},
|
||||
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
readOnly: true,
|
||||
notify: true,
|
||||
},
|
||||
|
||||
data: {
|
||||
type: Object,
|
||||
value: null,
|
||||
readOnly: true,
|
||||
notify: true,
|
||||
},
|
||||
},
|
||||
|
||||
observers: [
|
||||
'filterChanged(filterType, filterValue)',
|
||||
],
|
||||
|
||||
hassChanged: function (newHass, oldHass) {
|
||||
if (!oldHass && this.filterType && this.filterValue) {
|
||||
this.filterChanged(this.filterType, this.filterValue);
|
||||
}
|
||||
},
|
||||
|
||||
filterChanged: function (filterType, filterValue) {
|
||||
if (!this.hass) return;
|
||||
|
||||
var data;
|
||||
|
||||
if (filterType === 'date') {
|
||||
data = this.getDate(filterValue);
|
||||
} else if (filterType === 'recent-entity') {
|
||||
data = this.getRecent(filterValue);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
this._setIsLoading(true);
|
||||
|
||||
data.then(function (stateHistory) {
|
||||
this._setData(stateHistory);
|
||||
this._setIsLoading(false);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
getRecent: function (entityId) {
|
||||
var cache = RECENT_CACHE[entityId];
|
||||
|
||||
if (cache && Date.now() - cache.created < RECENT_THRESHOLD) {
|
||||
return cache.data;
|
||||
}
|
||||
|
||||
var url = 'history/period';
|
||||
|
||||
if (entityId) {
|
||||
url += '?filter_entity_id=' + entityId;
|
||||
}
|
||||
|
||||
var prom = this.hass.callApi('GET', url).then(
|
||||
function (stateHistory) {
|
||||
return computeHistory(stateHistory);
|
||||
},
|
||||
function () {
|
||||
RECENT_CACHE[entityId] = false;
|
||||
return null;
|
||||
}
|
||||
);
|
||||
|
||||
RECENT_CACHE[entityId] = {
|
||||
created: Date.now(),
|
||||
data: prom,
|
||||
};
|
||||
|
||||
return prom;
|
||||
},
|
||||
|
||||
getDate: function (date) {
|
||||
if (!DATE_CACHE[date]) {
|
||||
DATE_CACHE[date] = this.hass.callApi('GET', 'history/period/' + date).then(
|
||||
function (stateHistory) {
|
||||
return computeHistory(stateHistory);
|
||||
},
|
||||
function () {
|
||||
DATE_CACHE[date] = false;
|
||||
return null;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return DATE_CACHE[date];
|
||||
},
|
||||
});
|
||||
}());
|
||||
</script>
|
@ -5,8 +5,6 @@
|
||||
<link rel="import" href="../../bower_components/iron-icon/iron-icon.html">
|
||||
<link rel="import" href="../../bower_components/paper-spinner/paper-spinner.html">
|
||||
|
||||
<link rel='import' href='../util/hass-behavior.html'>
|
||||
|
||||
<dom-module id="ha-voice-command-dialog">
|
||||
<template>
|
||||
<style>
|
||||
@ -29,6 +27,10 @@
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.interimTranscript {
|
||||
color: darkgrey;
|
||||
}
|
||||
@ -54,11 +56,14 @@
|
||||
<iron-icon icon="mdi:text-to-speech" hidden$="[[isTransmitting]]"></iron-icon>
|
||||
<paper-spinner active$="[[isTransmitting]]" hidden$="[[!isTransmitting]]"></paper-spinner>
|
||||
</div>
|
||||
<div class='text'>
|
||||
<span>{{finalTranscript}}</span>
|
||||
<span class='interimTranscript'>[[interimTranscript]]</span>
|
||||
<div class='text' hidden$='[[hasError]]'>
|
||||
<span>{{results.final}}</span>
|
||||
<span class='interimTranscript'>[[results.interim]]</span>
|
||||
…
|
||||
</div>
|
||||
<div class='text red' hidden$='[[!hasError]]'>
|
||||
An error occurred. Unable to fulfill request.
|
||||
</div>
|
||||
</div>
|
||||
</paper-dialog>
|
||||
</template>
|
||||
@ -69,8 +74,6 @@
|
||||
Polymer({
|
||||
is: 'ha-voice-command-dialog',
|
||||
|
||||
behaviors: [window.hassBehavior],
|
||||
|
||||
properties: {
|
||||
hass: {
|
||||
type: Object,
|
||||
@ -82,32 +85,23 @@ Polymer({
|
||||
observer: 'dialogOpenChanged',
|
||||
},
|
||||
|
||||
finalTranscript: {
|
||||
type: String,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.voiceGetters.finalTranscript;
|
||||
},
|
||||
},
|
||||
|
||||
interimTranscript: {
|
||||
type: String,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.voiceGetters.extraInterimTranscript;
|
||||
},
|
||||
results: {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
isTransmitting: {
|
||||
type: Boolean,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.voiceGetters.isTransmitting;
|
||||
},
|
||||
value: false,
|
||||
},
|
||||
|
||||
isListening: {
|
||||
type: Boolean,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.voiceGetters.isListening;
|
||||
value: false,
|
||||
},
|
||||
|
||||
hasError: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
showListenInterface: {
|
||||
@ -117,13 +111,72 @@ Polymer({
|
||||
},
|
||||
},
|
||||
|
||||
initRecognition: function () {
|
||||
/* eslint-disable new-cap */
|
||||
this.recognition = new webkitSpeechRecognition();
|
||||
/* eslint-enable new-cap */
|
||||
|
||||
this.recognition.onstart = function () {
|
||||
this.isListening = true;
|
||||
this.isTransmitting = false;
|
||||
this.hasError = false;
|
||||
this.results = {
|
||||
final: '',
|
||||
interim: '',
|
||||
};
|
||||
}.bind(this);
|
||||
this.recognition.onerror = function () {
|
||||
this.recognition.abort();
|
||||
this.hasError = true;
|
||||
}.bind(this);
|
||||
this.recognition.onend = function () {
|
||||
this.isListening = false;
|
||||
this.isTransmitting = true;
|
||||
var text = this.results.final || this.results.interim;
|
||||
|
||||
var listeningDone = function () {
|
||||
this.isTransmitting = false;
|
||||
}.bind(this);
|
||||
|
||||
this.hass.callService('conversation', 'process', { text: text })
|
||||
.then(listeningDone, listeningDone);
|
||||
}.bind(this);
|
||||
|
||||
this.recognition.onresult = function (event) {
|
||||
var oldResults = this.results;
|
||||
var finalTranscript = '';
|
||||
var interimTranscript = '';
|
||||
|
||||
for (var ind = event.resultIndex; ind < event.results.length; ind++) {
|
||||
if (event.results[ind].isFinal) {
|
||||
finalTranscript += event.results[ind][0].transcript;
|
||||
} else {
|
||||
interimTranscript += event.results[ind][0].transcript;
|
||||
}
|
||||
}
|
||||
|
||||
this.results = {
|
||||
interim: interimTranscript,
|
||||
final: oldResults.final + finalTranscript,
|
||||
};
|
||||
}.bind(this);
|
||||
},
|
||||
|
||||
startListening: function () {
|
||||
if (!this.recognition) {
|
||||
this.initRecognition();
|
||||
}
|
||||
|
||||
this.recognition.start();
|
||||
},
|
||||
|
||||
computeShowListenInterface: function (isListening, isTransmitting) {
|
||||
return isListening || isTransmitting;
|
||||
},
|
||||
|
||||
dialogOpenChanged: function (newVal) {
|
||||
if (!newVal && this.isListening) {
|
||||
this.hass.voiceActions.stop();
|
||||
this.recognition.abort();
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -8,8 +8,7 @@
|
||||
<link rel="import" href="../state-summary/state-card-content.html">
|
||||
<link rel="import" href="../components/state-history-charts.html">
|
||||
<link rel="import" href="../more-infos/more-info-content.html">
|
||||
|
||||
<link rel='import' href='../util/hass-behavior.html'>
|
||||
<link rel="import" href="../data/ha-state-history-data.html">
|
||||
|
||||
<dom-module id="more-info-dialog">
|
||||
<template>
|
||||
@ -51,15 +50,24 @@
|
||||
</style>
|
||||
|
||||
<!-- entry-animation='slide-up-animation' exit-animation='slide-down-animation' -->
|
||||
<paper-dialog id="dialog" with-backdrop opened='{{dialogOpen}}' data-domain$='[[stateObj.domain]]'>
|
||||
<paper-dialog id="dialog" with-backdrop opened='{{dialogOpen}}' data-domain$='[[computeDomain(stateObj)]]'>
|
||||
<h2>
|
||||
<state-card-content
|
||||
state-obj="[[stateObj]]"
|
||||
hass='[[hass]]' in-dialog></state-card-content>
|
||||
</h2>
|
||||
<template is='dom-if' if="[[showHistoryComponent]]">
|
||||
<state-history-charts state-history="[[stateHistory]]"
|
||||
<div>
|
||||
<ha-state-history-data
|
||||
hass='[[hass]]'
|
||||
filter-type='[[_filterType]]'
|
||||
filter-value='[[stateObj.entity_id]]'
|
||||
data='{{stateHistory}}'
|
||||
is-loading='{{stateHistoryLoading}}'
|
||||
></ha-state-history-data>
|
||||
<state-history-charts history-data="[[stateHistory]]"
|
||||
is-loading-data="[[isLoadingHistoryData]]"></state-history-charts>
|
||||
</div>
|
||||
</template>
|
||||
<paper-dialog-scrollable id='scrollable'>
|
||||
<more-info-content
|
||||
@ -73,8 +81,6 @@
|
||||
Polymer({
|
||||
is: 'more-info-dialog',
|
||||
|
||||
behaviors: [window.hassBehavior],
|
||||
|
||||
properties: {
|
||||
hass: {
|
||||
type: Object,
|
||||
@ -82,48 +88,26 @@ Polymer({
|
||||
|
||||
stateObj: {
|
||||
type: Object,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.moreInfoGetters.currentEntity;
|
||||
},
|
||||
computed: 'computeStateObj(hass)',
|
||||
observer: 'stateObjChanged',
|
||||
},
|
||||
|
||||
stateHistory: {
|
||||
type: Object,
|
||||
bindNuclear: function (hass) {
|
||||
return [
|
||||
hass.moreInfoGetters.currentEntityHistory,
|
||||
function (history) { return history ? [history] : false; },
|
||||
];
|
||||
},
|
||||
|
||||
stateHistoryLoading: {
|
||||
type: Boolean,
|
||||
},
|
||||
|
||||
isLoadingHistoryData: {
|
||||
type: Boolean,
|
||||
computed: 'computeIsLoadingHistoryData(delayedDialogOpen, isLoadingEntityHistoryData)',
|
||||
},
|
||||
|
||||
isLoadingEntityHistoryData: {
|
||||
type: Boolean,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.entityHistoryGetters.isLoadingEntityHistory;
|
||||
},
|
||||
computed: 'computeIsLoadingHistoryData(delayedDialogOpen, stateHistoryLoading)',
|
||||
},
|
||||
|
||||
hasHistoryComponent: {
|
||||
type: Boolean,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.configGetters.isComponentLoaded('history');
|
||||
},
|
||||
observer: 'fetchHistoryData',
|
||||
},
|
||||
|
||||
shouldFetchHistory: {
|
||||
type: Boolean,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.moreInfoGetters.isCurrentEntityHistoryStale;
|
||||
},
|
||||
observer: 'fetchHistoryData',
|
||||
computed: 'computeHasHistoryComponent(hass)',
|
||||
},
|
||||
|
||||
showHistoryComponent: {
|
||||
@ -142,32 +126,43 @@ Polymer({
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
_filterType: {
|
||||
type: String,
|
||||
value: 'recent-entity',
|
||||
},
|
||||
},
|
||||
|
||||
ready: function () {
|
||||
this.$.scrollable.dialogElement = this.$.dialog;
|
||||
},
|
||||
|
||||
computeDomain: function (stateObj) {
|
||||
return stateObj ? window.hassUtil.computeDomain(stateObj) : '';
|
||||
},
|
||||
|
||||
computeStateObj: function (hass) {
|
||||
return hass.states[hass.moreInfoEntityId] || null;
|
||||
},
|
||||
|
||||
/**
|
||||
* We depend on a delayed dialogOpen value to tell the chart component
|
||||
* that the data is there. Otherwise the chart component will render
|
||||
* before the dialog is attached to the screen and is unable to determine
|
||||
* graph size resulting in scroll bars.
|
||||
*/
|
||||
computeIsLoadingHistoryData: function (delayedDialogOpen, isLoadingEntityHistoryData) {
|
||||
return !delayedDialogOpen || isLoadingEntityHistoryData;
|
||||
computeIsLoadingHistoryData: function (delayedDialogOpen, stateHistoryLoading) {
|
||||
return !delayedDialogOpen || stateHistoryLoading;
|
||||
},
|
||||
|
||||
computeHasHistoryComponent: function (hass) {
|
||||
return window.hassUtil.isComponentLoaded(hass, 'history');
|
||||
},
|
||||
|
||||
computeShowHistoryComponent: function (hasHistoryComponent, stateObj) {
|
||||
return this.hasHistoryComponent && stateObj &&
|
||||
window.hassUtil.DOMAINS_WITH_NO_HISTORY.indexOf(stateObj.domain) === -1;
|
||||
},
|
||||
|
||||
fetchHistoryData: function () {
|
||||
if (this.stateObj && this.hasHistoryComponent &&
|
||||
this.shouldFetchHistory) {
|
||||
this.hass.entityHistoryActions.fetchRecent(this.stateObj.entityId);
|
||||
}
|
||||
window.hassUtil.DOMAINS_WITH_NO_HISTORY.indexOf(
|
||||
window.hassUtil.computeDomain(stateObj)) === -1;
|
||||
},
|
||||
|
||||
stateObjChanged: function (newVal) {
|
||||
@ -177,8 +172,6 @@ Polymer({
|
||||
}
|
||||
|
||||
this.async(function () {
|
||||
// Firing action while other action is happening confuses nuclear
|
||||
this.fetchHistoryData();
|
||||
// allow dialog to render content before showing it so it is
|
||||
// positioned correctly.
|
||||
this.dialogOpen = true;
|
||||
@ -189,7 +182,7 @@ Polymer({
|
||||
if (newVal) {
|
||||
this.async(function () { this.delayedDialogOpen = true; }.bind(this), 10);
|
||||
} else if (!newVal && this.stateObj) {
|
||||
this.async(function () { this.hass.moreInfoActions.deselectEntity(); }.bind(this), 10);
|
||||
this.fire('hass-more-info', { entityId: null });
|
||||
this.delayedDialogOpen = false;
|
||||
}
|
||||
},
|
||||
|
@ -6,28 +6,47 @@
|
||||
<link rel='import' href='../bower_components/iron-flex-layout/iron-flex-layout-classes.html'>
|
||||
|
||||
<link rel='import' href='./util/hass-util.html'>
|
||||
<link rel='import' href='./util/hass-behavior.html'>
|
||||
<link rel='import' href='./util/ha-pref-storage.html'>
|
||||
<link rel='import' href='./util/hass-call-api.html'>
|
||||
<link rel='import' href='./layouts/login-form.html'>
|
||||
<link rel='import' href='./layouts/home-assistant-main.html'>
|
||||
<link rel='import' href='./resources/ha-style.html'>
|
||||
<link rel="import" href="./resources/panel-imports.html">
|
||||
<link rel='import' href='./managers/notification-manager.html'>
|
||||
|
||||
<dom-module id='home-assistant'>
|
||||
<template>
|
||||
<template is='dom-if' if='[[loaded]]'>
|
||||
<home-assistant-main hass='[[hass]]'></home-assistant-main>
|
||||
<ha-pref-storage hass='[[hass]]' id='storage'></ha-pref-storage>
|
||||
<notification-manager id='notifications' hass='[[hass]]'></notification-manager>
|
||||
<template is='dom-if' if='[[showMain]]' restamp>
|
||||
<home-assistant-main
|
||||
on-hass-more-info='handleMoreInfo'
|
||||
on-hass-navigate='handleNavigate'
|
||||
on-hass-dock-sidebar='handleDockSidebar'
|
||||
on-hass-notification='handleNotification'
|
||||
on-hass-logout='handleLogout'
|
||||
hass='[[hass]]'
|
||||
></home-assistant-main>
|
||||
</template>
|
||||
|
||||
<template is='dom-if' if='[[!loaded]]'>
|
||||
<template is='dom-if' if='[[!showMain]]'>
|
||||
<login-form
|
||||
hass='[[hass]]'
|
||||
force-show-loading='[[computeForceShowLoading(dataLoaded, iconsLoaded)]]'>
|
||||
connection-promise='{{connectionPromise}}'
|
||||
show-loading='[[computeShowLoading(connectionPromise, iconsLoaded)]]'>
|
||||
</login-form>
|
||||
</template>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
window.removeInitMsg = function () {
|
||||
var initMsg = document.getElementById('ha-init-skeleton');
|
||||
if (initMsg) {
|
||||
initMsg.parentElement.removeChild(initMsg);
|
||||
}
|
||||
};
|
||||
|
||||
Polymer({
|
||||
is: 'home-assistant',
|
||||
|
||||
@ -35,36 +54,40 @@ Polymer({
|
||||
icons: null,
|
||||
},
|
||||
|
||||
behaviors: [window.hassBehavior],
|
||||
|
||||
properties: {
|
||||
connectionPromise: {
|
||||
type: Object,
|
||||
value: window.hassConnection || null,
|
||||
observer: 'handleConnectionPromise',
|
||||
},
|
||||
connection: {
|
||||
type: Object,
|
||||
value: null,
|
||||
observer: 'connectionChanged',
|
||||
},
|
||||
hass: {
|
||||
type: Object,
|
||||
value: window.hass,
|
||||
value: null,
|
||||
},
|
||||
icons: {
|
||||
type: String,
|
||||
},
|
||||
dataLoaded: {
|
||||
type: Boolean,
|
||||
bindNuclear: function (hass) { return hass.syncGetters.isDataLoaded; },
|
||||
},
|
||||
iconsLoaded: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
loaded: {
|
||||
showMain: {
|
||||
type: Boolean,
|
||||
computed: 'computeLoaded(dataLoaded, iconsLoaded)',
|
||||
computed: 'computeShowMain(hass, iconsLoaded)',
|
||||
},
|
||||
},
|
||||
|
||||
computeLoaded: function (dataLoaded, iconsLoaded) {
|
||||
return dataLoaded && iconsLoaded;
|
||||
computeShowMain: function (hass, iconsLoaded) {
|
||||
return hass && hass.states && hass.config && iconsLoaded;
|
||||
},
|
||||
|
||||
computeForceShowLoading: function (dataLoaded, iconsLoaded) {
|
||||
return dataLoaded && !iconsLoaded;
|
||||
computeShowLoading: function (connectionPromise) {
|
||||
return connectionPromise != null;
|
||||
},
|
||||
|
||||
loadIcons: function () {
|
||||
@ -81,8 +104,125 @@ Polymer({
|
||||
});
|
||||
},
|
||||
|
||||
connectionChanged: function (conn) {
|
||||
if (!conn) {
|
||||
this.hass = null;
|
||||
return;
|
||||
}
|
||||
var notifications = this.$.notifications;
|
||||
this.hass = Object.assign({
|
||||
connection: conn,
|
||||
connected: true,
|
||||
states: null,
|
||||
config: null,
|
||||
dockedSidebar: false,
|
||||
currentPanel: 'states',
|
||||
currentView: null,
|
||||
moreInfoEntityId: null,
|
||||
callService: function (domain, service, serviceData) {
|
||||
return conn.callService(domain, service, serviceData || {})
|
||||
.then(function () {
|
||||
var message;
|
||||
if (service === 'turn_on' && serviceData.entity_id) {
|
||||
message = 'Turned on ' + serviceData.entity_id + '.';
|
||||
} else if (service === 'turn_off' && serviceData.entity_id) {
|
||||
message = 'Turned off ' + serviceData.entity_id + '.';
|
||||
} else {
|
||||
message = 'Service ' + domain + '/' + service + ' called.';
|
||||
}
|
||||
notifications.showNotification(message);
|
||||
},
|
||||
function () {
|
||||
notifications.showNotification(
|
||||
'Failed to call service ' + domain + '/' + service);
|
||||
return Promise.reject();
|
||||
});
|
||||
},
|
||||
callApi: function (method, path, parameters) {
|
||||
var host = window.location.protocol + '//' + window.location.host;
|
||||
return window.hassCallApi(host, {}, method, path, parameters);
|
||||
},
|
||||
}, this.$.storage.getStoredState());
|
||||
|
||||
conn.addEventListener('ready', function () {
|
||||
this.hass = Object.assign({}, this.hass, { connected: true });
|
||||
}.bind(this));
|
||||
|
||||
conn.addEventListener('disconnected', function () {
|
||||
this.hass = Object.assign({}, this.hass, { connected: false });
|
||||
}.bind(this));
|
||||
|
||||
window.HAWS.subscribeEntities(conn, function (states) {
|
||||
this.hass = Object.assign({}, this.hass, { states: states });
|
||||
}.bind(this));
|
||||
|
||||
window.HAWS.subscribeConfig(conn, function (config) {
|
||||
this.hass = Object.assign({}, this.hass, { config: config });
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
handleConnectionPromise: function (prom) {
|
||||
if (!prom) return;
|
||||
|
||||
var el = this;
|
||||
|
||||
prom.then(function (conn) {
|
||||
el.connection = conn;
|
||||
}, function () {
|
||||
el.connectionPromise = null;
|
||||
});
|
||||
},
|
||||
|
||||
handleMoreInfo: function (ev) {
|
||||
ev.stopPropagation();
|
||||
|
||||
this.hass = Object.assign(
|
||||
{}, this.hass,
|
||||
{ moreInfoEntityId: ev.detail.entityId });
|
||||
},
|
||||
|
||||
handleNavigate: function (ev) {
|
||||
ev.stopPropagation();
|
||||
|
||||
var hass = Object.assign({}, this.hass);
|
||||
|
||||
if ('panel' in ev.detail) {
|
||||
hass.currentPanel = ev.detail.panel;
|
||||
}
|
||||
|
||||
if ('view' in ev.detail) {
|
||||
hass.currentView = ev.detail.view;
|
||||
}
|
||||
|
||||
this.hass = hass;
|
||||
},
|
||||
|
||||
handleDockSidebar: function (ev) {
|
||||
ev.stopPropagation();
|
||||
this.hass = Object.assign(
|
||||
{}, this.hass,
|
||||
{ dockedSidebar: ev.detail.dock });
|
||||
this.$.storage.storeState();
|
||||
},
|
||||
|
||||
handleNotification: function (ev) {
|
||||
this.$.notifications.showNotification(ev.detail.message);
|
||||
},
|
||||
|
||||
handleLogout: function () {
|
||||
this.connection.close();
|
||||
delete localStorage.authToken;
|
||||
this.connection = null;
|
||||
this.connectionPromise = null;
|
||||
this.hass = null;
|
||||
},
|
||||
|
||||
ready: function () {
|
||||
this.loadIcons();
|
||||
|
||||
if (this.connectionPromise !== null) {
|
||||
this.handleConnectionPromise(this.connectionPromise);
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -5,22 +5,25 @@
|
||||
|
||||
<link rel='import' href='../layouts/partial-cards.html'>
|
||||
<link rel='import' href='../layouts/partial-panel-resolver.html'>
|
||||
<link rel='import' href='../managers/notification-manager.html'>
|
||||
<link rel="import" href="../dialogs/more-info-dialog.html">
|
||||
<link rel="import" href="../dialogs/ha-voice-command-dialog.html">
|
||||
<link rel='import' href='../util/ha-url-sync.html'>
|
||||
|
||||
<link rel='import' href='../components/ha-sidebar.html'>
|
||||
|
||||
<dom-module id='home-assistant-main'>
|
||||
<template>
|
||||
<notification-manager hass='[[hass]]'></notification-manager>
|
||||
<more-info-dialog hass='[[hass]]'></more-info-dialog>
|
||||
<ha-voice-command-dialog hass='[[hass]]'></ha-voice-command-dialog>
|
||||
<ha-url-sync hass='[[hass]]'></ha-url-sync>
|
||||
<ha-voice-command-dialog
|
||||
hass='[[hass]]'
|
||||
id='voiceDialog'
|
||||
></ha-voice-command-dialog>
|
||||
<iron-media-query query="(max-width: 870px)" query-matches="{{narrow}}">
|
||||
</iron-media-query>
|
||||
|
||||
<paper-drawer-panel id='drawer'
|
||||
force-narrow='[[computeForceNarrow(narrow, showSidebar)]]'
|
||||
force-narrow='[[computeForceNarrow(narrow, dockedSidebar)]]'
|
||||
responsive-width='0' disable-swipe='[[isSelectedMap]]'
|
||||
disable-edge-swipe='[[isSelectedMap]]'>
|
||||
<ha-sidebar drawer narrow='[[narrow]]' hass='[[hass]]'></ha-sidebar>
|
||||
@ -29,21 +32,21 @@
|
||||
main
|
||||
attr-for-selected='id'
|
||||
fallback-selection='panel-resolver'
|
||||
selected='[[activePanel]]'
|
||||
selected='[[currentPanel]]'
|
||||
selected-attribute='panel-visible'
|
||||
>
|
||||
<partial-cards
|
||||
id='states'
|
||||
narrow='[[narrow]]'
|
||||
hass='[[hass]]'
|
||||
show-menu='[[showSidebar]]'
|
||||
show-menu='[[dockedSidebar]]'
|
||||
></partial-cards>
|
||||
|
||||
<partial-panel-resolver
|
||||
id='panel-resolver'
|
||||
narrow='[[narrow]]'
|
||||
hass='[[hass]]'
|
||||
show-menu='[[showSidebar]]'
|
||||
show-menu='[[dockedSidebar]]'
|
||||
></partial-panel-resolver>
|
||||
|
||||
</iron-pages>
|
||||
@ -56,56 +59,55 @@
|
||||
Polymer({
|
||||
is: 'home-assistant-main',
|
||||
|
||||
behaviors: [window.hassBehavior],
|
||||
|
||||
properties: {
|
||||
hass: {
|
||||
type: Object,
|
||||
value: null,
|
||||
},
|
||||
|
||||
narrow: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
},
|
||||
|
||||
activePanel: {
|
||||
currentPanel: {
|
||||
type: String,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.navigationGetters.activePanelName;
|
||||
},
|
||||
observer: 'activePanelChanged',
|
||||
computed: 'computeCurrentPanel(hass)',
|
||||
observer: 'currentPanelChanged',
|
||||
},
|
||||
|
||||
showSidebar: {
|
||||
dockedSidebar: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.navigationGetters.showSidebar;
|
||||
},
|
||||
computed: 'computeDockedSidebar(hass)',
|
||||
},
|
||||
},
|
||||
|
||||
listeners: {
|
||||
'open-menu': 'openMenu',
|
||||
'close-menu': 'closeMenu',
|
||||
'hass-open-menu': 'handleOpenMenu',
|
||||
'hass-close-menu': 'handleCloseMenu',
|
||||
'hass-start-voice': 'handleStartVoice',
|
||||
},
|
||||
|
||||
openMenu: function () {
|
||||
handleStartVoice: function (ev) {
|
||||
ev.stopPropagation();
|
||||
this.$.voiceDialog.startListening();
|
||||
},
|
||||
|
||||
handleOpenMenu: function () {
|
||||
if (this.narrow) {
|
||||
this.$.drawer.openDrawer();
|
||||
} else {
|
||||
this.hass.navigationActions.showSidebar(true);
|
||||
this.fire('hass-dock-sidebar', { dock: true });
|
||||
}
|
||||
},
|
||||
|
||||
closeMenu: function () {
|
||||
handleCloseMenu: function () {
|
||||
this.$.drawer.closeDrawer();
|
||||
if (this.showSidebar) {
|
||||
this.hass.navigationActions.showSidebar(false);
|
||||
if (this.dockedSidebar) {
|
||||
this.fire('hass-dock-sidebar', { dock: false });
|
||||
}
|
||||
},
|
||||
|
||||
activePanelChanged: function () {
|
||||
currentPanelChanged: function () {
|
||||
if (this.narrow) {
|
||||
this.$.drawer.closeDrawer();
|
||||
}
|
||||
@ -113,15 +115,18 @@ Polymer({
|
||||
|
||||
attached: function () {
|
||||
window.removeInitMsg();
|
||||
this.hass.startUrlSync();
|
||||
},
|
||||
|
||||
computeForceNarrow: function (narrow, showSidebar) {
|
||||
return narrow || !showSidebar;
|
||||
computeForceNarrow: function (narrow, dockedSidebar) {
|
||||
return narrow || !dockedSidebar;
|
||||
},
|
||||
|
||||
detached: function () {
|
||||
this.hass.stopUrlSync();
|
||||
computeCurrentPanel: function (hass) {
|
||||
return hass.currentPanel;
|
||||
},
|
||||
|
||||
computeDockedSidebar: function (hass) {
|
||||
return hass.dockedSidebar;
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -9,8 +9,6 @@
|
||||
<link rel="import" href="../../bower_components/iron-input/iron-input.html">
|
||||
<link rel="import" href="../../bower_components/paper-spinner/paper-spinner.html">
|
||||
|
||||
<link rel='import' href='../util/hass-behavior.html'>
|
||||
|
||||
<dom-module id="login-form">
|
||||
<template>
|
||||
<style is="custom-style" include="iron-flex iron-positioning"></style>
|
||||
@ -51,10 +49,18 @@
|
||||
<a href="#" id="hideKeyboardOnFocus"></a>
|
||||
<div class='interact'>
|
||||
<div id='loginform' hidden$="[[showLoading]]">
|
||||
<paper-input-container id="passwordDecorator" invalid="[[isInvalid]]">
|
||||
<paper-input-container
|
||||
id="passwordDecorator"
|
||||
invalid="[[errorMessage]]"
|
||||
>
|
||||
<label>Password</label>
|
||||
<input is="iron-input" type="password" id="passwordInput" />
|
||||
<paper-input-error invalid="[[isInvalid]]">[[errorMessage]]</paper-input-error>
|
||||
<input
|
||||
is="iron-input"
|
||||
type="password"
|
||||
id="passwordInput"
|
||||
bind-value="{{password}}"
|
||||
/>
|
||||
<paper-input-error invalid="[[errorMessage]]">[[errorMessage]]</paper-input-error>
|
||||
</paper-input-container>
|
||||
<div class="layout horizontal center">
|
||||
<paper-checkbox for id='rememberLogin'>Remember</paper-checkbox>
|
||||
@ -74,31 +80,25 @@
|
||||
Polymer({
|
||||
is: 'login-form',
|
||||
|
||||
behaviors: [window.hassBehavior],
|
||||
|
||||
properties: {
|
||||
hass: {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
errorMessage: {
|
||||
type: String,
|
||||
bindNuclear: function (hass) { return hass.authGetters.attemptErrorMessage; },
|
||||
connectionPromise: {
|
||||
type: Object,
|
||||
notify: true,
|
||||
observer: 'handleConnectionPromiseChanged',
|
||||
},
|
||||
|
||||
isInvalid: {
|
||||
type: Boolean,
|
||||
bindNuclear: function (hass) { return hass.authGetters.isInvalidAttempt; },
|
||||
errorMessage: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
|
||||
isValidating: {
|
||||
type: Boolean,
|
||||
observer: 'isValidatingChanged',
|
||||
bindNuclear: function (hass) { return hass.authGetters.isValidating; },
|
||||
},
|
||||
|
||||
loadingResources: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
@ -111,6 +111,11 @@ Polymer({
|
||||
type: Boolean,
|
||||
computed: 'computeShowSpinner(forceShowLoading, isValidating)',
|
||||
},
|
||||
|
||||
password: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
},
|
||||
|
||||
listeners: {
|
||||
@ -118,10 +123,6 @@ Polymer({
|
||||
'loginButton.tap': 'validatePassword',
|
||||
},
|
||||
|
||||
observers: [
|
||||
'validatingChanged(isValidating, isInvalid)',
|
||||
],
|
||||
|
||||
attached: function () {
|
||||
window.removeInitMsg();
|
||||
},
|
||||
@ -130,15 +131,11 @@ Polymer({
|
||||
return forceShowLoading || isValidating;
|
||||
},
|
||||
|
||||
validatingChanged: function (isValidating, isInvalid) {
|
||||
if (!isValidating && !isInvalid) {
|
||||
this.$.passwordInput.value = '';
|
||||
}
|
||||
},
|
||||
|
||||
isValidatingChanged: function (newVal) {
|
||||
if (!newVal) {
|
||||
this.async(function () { this.$.passwordInput.focus(); }.bind(this), 10);
|
||||
this.async(function () {
|
||||
this.$.passwordInput.focus();
|
||||
}.bind(this), 10);
|
||||
}
|
||||
},
|
||||
|
||||
@ -148,16 +145,46 @@ Polymer({
|
||||
this.validatePassword();
|
||||
ev.preventDefault();
|
||||
// clear error after we start typing again
|
||||
} else if (this.isInvalid) {
|
||||
this.isInvalid = false;
|
||||
} else if (this.errorMessage) {
|
||||
this.errorMessage = '';
|
||||
}
|
||||
},
|
||||
|
||||
validatePassword: function () {
|
||||
var auth = this.password;
|
||||
this.$.hideKeyboardOnFocus.focus();
|
||||
this.connectionPromise = window.createHassConnection(auth);
|
||||
|
||||
window.validateAuth(this.$.passwordInput.value,
|
||||
this.$.rememberLogin.checked);
|
||||
if (this.$.rememberLogin.checked) {
|
||||
this.connectionPromise.then(function () {
|
||||
localStorage.authToken = auth;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
handleConnectionPromiseChanged: function (newVal) {
|
||||
if (!newVal) return;
|
||||
|
||||
var el = this;
|
||||
this.isValidating = true;
|
||||
|
||||
this.connectionPromise.then(
|
||||
function () {
|
||||
el.isValidating = false;
|
||||
el.password = '';
|
||||
},
|
||||
function (errCode) {
|
||||
el.isValidating = false;
|
||||
|
||||
if (errCode === window.HAWS.ERR_CANNOT_CONNECT) {
|
||||
el.errorMessage = 'Unable to connect';
|
||||
} else if (errCode === window.HAWS.ERR_INVALID_AUTH) {
|
||||
el.errorMessage = 'Invalid password';
|
||||
} else {
|
||||
el.errorMessage = 'Unknown error: ' + errCode;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -4,7 +4,6 @@
|
||||
<link rel="import" href="../../bower_components/iron-icon/iron-icon.html">
|
||||
<link rel="import" href="../../bower_components/iron-pages/iron-pages.html">
|
||||
|
||||
<link rel="import" href="../../bower_components/paper-icon-button/paper-icon-button.html">
|
||||
<link rel="import" href="../../bower_components/paper-tabs/paper-tabs.html">
|
||||
<link rel="import" href="../../bower_components/paper-tabs/paper-tab.html">
|
||||
|
||||
@ -14,8 +13,8 @@
|
||||
<link rel="import" href="../../bower_components/app-layout/app-toolbar/app-toolbar.html">
|
||||
|
||||
<link rel="import" href="../components/ha-menu-button.html">
|
||||
<link rel="import" href="../components/ha-start-voice-button.html">
|
||||
<link rel="import" href="../components/ha-cards.html">
|
||||
<link rel="import" href="../util/hass-behavior.html">
|
||||
|
||||
<dom-module id="partial-cards">
|
||||
<template>
|
||||
@ -42,9 +41,7 @@
|
||||
<app-toolbar>
|
||||
<ha-menu-button narrow='[[narrow]]' show-menu='[[showMenu]]'></ha-menu-button>
|
||||
<div main-title>[[computeTitle(views, locationName)]]</div>
|
||||
<paper-icon-button
|
||||
icon="mdi:microphone" hidden$='[[!canListen]]'
|
||||
on-tap="handleListenClick"></paper-icon-button>
|
||||
<ha-start-voice-button hass='[[hass]]'></ha-start-voice-button>
|
||||
</app-toolbar>
|
||||
|
||||
<div sticky hidden$='[[!views.length]]'>
|
||||
@ -60,14 +57,14 @@
|
||||
>[[locationName]]</paper-tab>
|
||||
<template is='dom-repeat' items='[[views]]'>
|
||||
<paper-tab
|
||||
data-entity$='[[item.entityId]]'
|
||||
data-entity$='[[item.entity_id]]'
|
||||
on-tap='scrollToTop'
|
||||
>
|
||||
<template is='dom-if' if='[[item.attributes.icon]]'>
|
||||
<iron-icon icon='[[item.attributes.icon]]'></iron-icon>
|
||||
</template>
|
||||
<template is='dom-if' if='[[!item.attributes.icon]]'>
|
||||
[[item.entityDisplay]]
|
||||
[[computeStateName(item)]]
|
||||
</template>
|
||||
</paper-tab>
|
||||
</template>
|
||||
@ -82,8 +79,8 @@
|
||||
>
|
||||
<ha-cards
|
||||
data-view=''
|
||||
show-introduction='[[computeShowIntroduction(currentView, introductionLoaded, states)]]'
|
||||
states='[[states]]'
|
||||
show-introduction='[[computeShowIntroduction(currentView, introductionLoaded, viewStates)]]'
|
||||
states='[[viewStates]]'
|
||||
columns='[[_columns]]'
|
||||
hass='[[hass]]'
|
||||
panel-visible='[[panelVisible]]'
|
||||
@ -91,9 +88,9 @@
|
||||
|
||||
<template is='dom-repeat' items='[[views]]'>
|
||||
<ha-cards
|
||||
data-view$='[[item.entityId]]'
|
||||
show-introduction='[[computeShowIntroduction(currentView, introductionLoaded, states)]]'
|
||||
states='[[states]]'
|
||||
data-view$='[[item.entity_id]]'
|
||||
show-introduction='[[computeShowIntroduction(currentView, introductionLoaded, viewStates)]]'
|
||||
states='[[viewStates]]'
|
||||
columns='[[_columns]]'
|
||||
hass='[[hass]]'
|
||||
panel-visible='[[panelVisible]]'
|
||||
@ -108,13 +105,15 @@
|
||||
|
||||
<script>
|
||||
Polymer({
|
||||
is: 'partial-cards',
|
||||
DEFAULT_VIEW_ENTITY_ID: 'group.default_view',
|
||||
ALWAYS_SHOW_DOMAIN: ['persistent_notification', 'configurator'],
|
||||
|
||||
behaviors: [window.hassBehavior],
|
||||
is: 'partial-cards',
|
||||
|
||||
properties: {
|
||||
hass: {
|
||||
type: Object,
|
||||
value: null,
|
||||
},
|
||||
|
||||
narrow: {
|
||||
@ -124,7 +123,6 @@ Polymer({
|
||||
|
||||
showMenu: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
observer: 'handleWindowChange',
|
||||
},
|
||||
|
||||
@ -138,62 +136,30 @@ Polymer({
|
||||
value: 1,
|
||||
},
|
||||
|
||||
canListen: {
|
||||
type: Boolean,
|
||||
bindNuclear: function (hass) {
|
||||
return [
|
||||
hass.voiceGetters.isVoiceSupported,
|
||||
hass.configGetters.isComponentLoaded('conversation'),
|
||||
function (isVoiceSupported, componentLoaded) {
|
||||
return isVoiceSupported && componentLoaded;
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
|
||||
introductionLoaded: {
|
||||
type: Boolean,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.configGetters.isComponentLoaded('introduction');
|
||||
},
|
||||
computed: 'computeIntroductionLoaded(hass)',
|
||||
},
|
||||
|
||||
locationName: {
|
||||
type: String,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.configGetters.locationName;
|
||||
},
|
||||
value: '',
|
||||
computed: 'computeLocationName(hass)',
|
||||
},
|
||||
|
||||
currentView: {
|
||||
type: String,
|
||||
bindNuclear: function (hass) {
|
||||
return [
|
||||
hass.viewGetters.currentView,
|
||||
function (view) { return view || ''; },
|
||||
];
|
||||
},
|
||||
computed: 'computeCurrentView(hass)',
|
||||
},
|
||||
|
||||
views: {
|
||||
type: Array,
|
||||
bindNuclear: function (hass) {
|
||||
return [
|
||||
hass.viewGetters.views,
|
||||
function (views) {
|
||||
return views.valueSeq()
|
||||
.sortBy(function (view) { return view.attributes.order; })
|
||||
.toArray();
|
||||
},
|
||||
];
|
||||
},
|
||||
computed: 'computeViews(hass)',
|
||||
},
|
||||
|
||||
states: {
|
||||
viewStates: {
|
||||
type: Object,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.viewGetters.currentViewEntities;
|
||||
},
|
||||
computed: 'computeViewStates(currentView, hass)',
|
||||
},
|
||||
},
|
||||
|
||||
@ -259,17 +225,11 @@ Polymer({
|
||||
}).call(this);
|
||||
},
|
||||
|
||||
handleListenClick: function () {
|
||||
this.hass.voiceActions.listen();
|
||||
},
|
||||
|
||||
handleViewSelected: function (ev) {
|
||||
var view = ev.detail.item.getAttribute('data-entity') || null;
|
||||
var current = this.currentView || null;
|
||||
if (view !== current) {
|
||||
this.async(function () {
|
||||
this.hass.viewActions.selectView(view);
|
||||
}.bind(this), 0);
|
||||
this.fire('hass-navigate', { view: view });
|
||||
}
|
||||
},
|
||||
|
||||
@ -280,5 +240,72 @@ Polymer({
|
||||
computeShowIntroduction: function (currentView, introductionLoaded, states) {
|
||||
return currentView === '' && (introductionLoaded || states.size === 0);
|
||||
},
|
||||
|
||||
computeStateName: function (stateObj) {
|
||||
return window.hassUtil.computeStateName(stateObj);
|
||||
},
|
||||
|
||||
computeLocationName: function (hass) {
|
||||
return window.hassUtil.computeLocationName(hass);
|
||||
},
|
||||
|
||||
computeIntroductionLoaded: function (hass) {
|
||||
return window.hassUtil.isComponentLoaded(hass, 'introduction');
|
||||
},
|
||||
|
||||
computeViews: function (hass) {
|
||||
return window.HAWS.extractViews(hass.states);
|
||||
},
|
||||
|
||||
/*
|
||||
Compute the states to show for current view.
|
||||
|
||||
Will make sure we always show entities from ALWAYS_SHOW_DOMAINS domains.
|
||||
*/
|
||||
computeViewStates: function (currentView, hass) {
|
||||
var i;
|
||||
var entityId;
|
||||
var state;
|
||||
var states;
|
||||
var entityIds = Object.keys(hass.states);
|
||||
|
||||
// If we base off all entities, only have to filter out hidden
|
||||
if (!currentView && !(this.DEFAULT_VIEW_ENTITY_ID in hass.states)) {
|
||||
states = {};
|
||||
for (i = 0; i < entityIds.length; i++) {
|
||||
entityId = entityIds[i];
|
||||
state = hass.states[entityId];
|
||||
|
||||
// We can filter out hidden and domain at the same time.
|
||||
if (!state.attributes.hidden) {
|
||||
states[entityId] = state;
|
||||
}
|
||||
}
|
||||
return states;
|
||||
}
|
||||
|
||||
if (currentView) {
|
||||
states = window.HAWS.getViewEntities(hass.states, hass.states[currentView]);
|
||||
} else {
|
||||
states = window.HAWS.getViewEntities(
|
||||
hass.states, hass.states[this.DEFAULT_VIEW_ENTITY_ID]);
|
||||
}
|
||||
|
||||
// Make sure certain domains are always shown.
|
||||
for (i = 0; i < entityIds.length; i++) {
|
||||
entityId = entityIds[i];
|
||||
state = hass.states[entityId];
|
||||
|
||||
if (this.ALWAYS_SHOW_DOMAIN.indexOf(window.hassUtil.computeDomain(state)) !== -1) {
|
||||
states[entityId] = state;
|
||||
}
|
||||
}
|
||||
|
||||
return states;
|
||||
},
|
||||
|
||||
computeCurrentView: function (hass) {
|
||||
return hass.currentView || '';
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -5,7 +5,6 @@
|
||||
<link rel="import" href="../../bower_components/app-layout/app-toolbar/app-toolbar.html">
|
||||
|
||||
<link rel='import' href='../components/ha-menu-button.html'>
|
||||
<link rel='import' href='../util/hass-behavior.html'>
|
||||
<link rel="import" href="../resources/ha-style.html">
|
||||
|
||||
<dom-module id='partial-panel-resolver'>
|
||||
@ -46,8 +45,6 @@
|
||||
Polymer({
|
||||
is: 'partial-panel-resolver',
|
||||
|
||||
behaviors: [window.hassBehavior],
|
||||
|
||||
properties: {
|
||||
hass: {
|
||||
type: Object,
|
||||
@ -78,13 +75,15 @@ Polymer({
|
||||
|
||||
panel: {
|
||||
type: Object,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.navigationGetters.activePanel;
|
||||
},
|
||||
computed: 'computeCurrentPanel(hass)',
|
||||
observer: 'panelChanged',
|
||||
},
|
||||
},
|
||||
|
||||
computeCurrentPanel: function (hass) {
|
||||
return hass.config.panels[hass.currentPanel];
|
||||
},
|
||||
|
||||
panelChanged: function (panel) {
|
||||
if (!panel) {
|
||||
if (this.$.panel.lastChild) {
|
||||
@ -97,15 +96,15 @@ Polymer({
|
||||
this.errorLoading = false;
|
||||
|
||||
this.importHref(
|
||||
panel.get('url'),
|
||||
panel.url,
|
||||
|
||||
function success() {
|
||||
window.hassUtil.dynamicContentUpdater(
|
||||
this.$.panel, 'ha-panel-' + panel.get('component_name'), {
|
||||
this.$.panel, 'ha-panel-' + panel.component_name, {
|
||||
hass: this.hass,
|
||||
narrow: this.narrow,
|
||||
showMenu: this.showMenu,
|
||||
panel: panel.toJS(),
|
||||
panel: panel,
|
||||
});
|
||||
this.resolved = true;
|
||||
}.bind(this),
|
||||
|
@ -2,8 +2,6 @@
|
||||
|
||||
<link rel="import" href="../../bower_components/paper-toast/paper-toast.html">
|
||||
|
||||
<link rel='import' href='../util/hass-behavior.html'>
|
||||
|
||||
<dom-module id="notification-manager">
|
||||
<template>
|
||||
<style>
|
||||
@ -14,8 +12,8 @@
|
||||
|
||||
<paper-toast
|
||||
id="toast"
|
||||
text='{{text}}'
|
||||
no-cancel-on-outside-click='[[neg]]'
|
||||
text='[[_text]]'
|
||||
no-cancel-on-outside-click='[[_cancelOnOutsideClick]]'
|
||||
></paper-toast>
|
||||
<paper-toast
|
||||
id='connToast'
|
||||
@ -30,8 +28,6 @@
|
||||
Polymer({
|
||||
is: 'notification-manager',
|
||||
|
||||
behaviors: [window.hassBehavior],
|
||||
|
||||
properties: {
|
||||
hass: {
|
||||
type: Object,
|
||||
@ -39,21 +35,17 @@ Polymer({
|
||||
|
||||
isStreaming: {
|
||||
type: Boolean,
|
||||
bindNuclear: function (hass) { return hass.streamGetters.isStreamingEvents; },
|
||||
computed: 'computeIsStreaming(hass)',
|
||||
},
|
||||
|
||||
// Otherwise we cannot close a modal when a notification is being shown.
|
||||
neg: {
|
||||
_cancelOnOutsideClick: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
text: {
|
||||
_text: {
|
||||
type: String,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.notificationGetters.lastNotificationMessage;
|
||||
},
|
||||
observer: 'showNotification',
|
||||
readOnly: true,
|
||||
},
|
||||
|
||||
toastClass: {
|
||||
@ -62,6 +54,10 @@ Polymer({
|
||||
},
|
||||
},
|
||||
|
||||
computeIsStreaming: function (hass) {
|
||||
return !hass || hass.connected;
|
||||
},
|
||||
|
||||
created: function () {
|
||||
this.handleWindowChange = this.handleWindowChange.bind(this);
|
||||
this._mediaq = window.matchMedia('(max-width: 599px)');
|
||||
@ -81,10 +77,9 @@ Polymer({
|
||||
this.$.connToast.classList.toggle('fit-bottom', ev.matches);
|
||||
},
|
||||
|
||||
showNotification: function (newText) {
|
||||
if (newText) {
|
||||
showNotification: function (message) {
|
||||
this._set_text(message);
|
||||
this.$.toast.show();
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -129,8 +129,8 @@ Polymer({
|
||||
|
||||
callService: function (service, data) {
|
||||
var serviceData = data || {};
|
||||
serviceData.entity_id = this.stateObj.entityId;
|
||||
this.hass.serviceActions.callService('alarm_control_panel', service, serviceData)
|
||||
serviceData.entity_id = this.stateObj.entity_id;
|
||||
this.hass.callService('alarm_control_panel', service, serviceData)
|
||||
.then(function () {
|
||||
this.enteredCode = '';
|
||||
}.bind(this));
|
||||
|
@ -42,8 +42,8 @@ Polymer({
|
||||
},
|
||||
|
||||
handleTriggerTapped: function () {
|
||||
this.hass.serviceActions.callService('automation', 'trigger', {
|
||||
entity_id: this.stateObj.entityId,
|
||||
this.hass.callService('automation', 'trigger', {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -13,7 +13,7 @@
|
||||
</style>
|
||||
|
||||
<img class='camera-image' src="[[computeCameraImageUrl(hass, stateObj, isVisible)]]"
|
||||
on-load='imageLoaded' alt='[[stateObj.entityDisplay]]' />
|
||||
on-load='imageLoaded' alt='[[computeStateName(stateObj)]]' />
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
@ -40,11 +40,15 @@ Polymer({
|
||||
this.fire('iron-resize');
|
||||
},
|
||||
|
||||
computeStateName: function (stateObj) {
|
||||
return window.hassUtil.computeStateName(stateObj);
|
||||
},
|
||||
|
||||
computeCameraImageUrl: function (hass, stateObj, isVisible) {
|
||||
if (hass.demo) {
|
||||
return '/demo/webcam.jpg';
|
||||
} else if (stateObj && isVisible) {
|
||||
return '/api/camera_proxy_stream/' + stateObj.entityId +
|
||||
return '/api/camera_proxy_stream/' + stateObj.entity_id +
|
||||
'?token=' + stateObj.attributes.access_token;
|
||||
}
|
||||
// Return an empty image if no stateObj (= dialog not open) or in cleanup mode.
|
||||
|
@ -376,9 +376,9 @@ Polymer({
|
||||
// result in the entity to be turned on. Since the state is not changing,
|
||||
// the resync is not called automatic.
|
||||
/* eslint-disable no-param-reassign */
|
||||
data.entity_id = this.stateObj.entityId;
|
||||
data.entity_id = this.stateObj.entity_id;
|
||||
/* eslint-enable no-param-reassign */
|
||||
this.hass.serviceActions.callService('climate', service, data)
|
||||
this.hass.callService('climate', service, data)
|
||||
.then(function () {
|
||||
this.stateObjChanged(this.stateObj);
|
||||
}.bind(this));
|
||||
|
@ -7,8 +7,6 @@
|
||||
<link rel='import' href='../../bower_components/paper-button/paper-button.html'>
|
||||
<link rel='import' href='../../bower_components/paper-input/paper-input-container.html'>
|
||||
|
||||
<link rel='import' href='../util/hass-behavior.html'>
|
||||
|
||||
<dom-module id='more-info-configurator'>
|
||||
<template>
|
||||
<style is="custom-style" include="iron-flex"></style>
|
||||
@ -94,8 +92,6 @@
|
||||
Polymer({
|
||||
is: 'more-info-configurator',
|
||||
|
||||
behaviors: [window.hassBehavior],
|
||||
|
||||
properties: {
|
||||
stateObj: {
|
||||
type: Object,
|
||||
@ -106,13 +102,6 @@ Polymer({
|
||||
value: 'display',
|
||||
},
|
||||
|
||||
isStreaming: {
|
||||
type: Boolean,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.streamGetters.isStreamingEvents;
|
||||
},
|
||||
},
|
||||
|
||||
isConfigurable: {
|
||||
type: Boolean,
|
||||
computed: 'computeIsConfigurable(stateObj)',
|
||||
@ -154,13 +143,9 @@ Polymer({
|
||||
|
||||
this.isConfiguring = true;
|
||||
|
||||
this.hass.serviceActions.callService('configurator', 'configure', data).then(
|
||||
this.hass.callService('configurator', 'configure', data).then(
|
||||
function () {
|
||||
this.isConfiguring = false;
|
||||
|
||||
if (!this.isStreaming) {
|
||||
this.hass.syncActions.fetchAll();
|
||||
}
|
||||
}.bind(this),
|
||||
function () {
|
||||
this.isConfiguring = false;
|
||||
|
@ -84,15 +84,15 @@ Polymer({
|
||||
},
|
||||
|
||||
coverPositionSliderChanged: function (ev) {
|
||||
this.hass.serviceActions.callService('cover', 'set_cover_position', {
|
||||
entity_id: this.stateObj.entityId,
|
||||
this.hass.callService('cover', 'set_cover_position', {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
position: ev.target.value,
|
||||
});
|
||||
},
|
||||
|
||||
coverTiltPositionSliderChanged: function (ev) {
|
||||
this.hass.serviceActions.callService('cover', 'set_cover_tilt_position', {
|
||||
entity_id: this.stateObj.entityId,
|
||||
this.hass.callService('cover', 'set_cover_tilt_position', {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
tilt_position: ev.target.value,
|
||||
});
|
||||
},
|
||||
@ -106,18 +106,18 @@ Polymer({
|
||||
},
|
||||
|
||||
onOpenTiltTap: function () {
|
||||
this.hass.serviceActions.callService('cover', 'open_cover_tilt',
|
||||
{ entity_id: this.stateObj.entityId });
|
||||
this.hass.callService('cover', 'open_cover_tilt',
|
||||
{ entity_id: this.stateObj.entity_id });
|
||||
},
|
||||
|
||||
onCloseTiltTap: function () {
|
||||
this.hass.serviceActions.callService('cover', 'close_cover_tilt',
|
||||
{ entity_id: this.stateObj.entityId });
|
||||
this.hass.callService('cover', 'close_cover_tilt',
|
||||
{ entity_id: this.stateObj.entity_id });
|
||||
},
|
||||
|
||||
onStopTiltTap: function () {
|
||||
this.hass.serviceActions.callService('cover', 'stop_cover_tilt',
|
||||
{ entity_id: this.stateObj.entityId });
|
||||
this.hass.callService('cover', 'stop_cover_tilt',
|
||||
{ entity_id: this.stateObj.entity_id });
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -114,8 +114,8 @@ Polymer({
|
||||
speedInput = this.stateObj.attributes.speed_list[speedIndex];
|
||||
if (speedInput === this.stateObj.attributes.speed) return;
|
||||
|
||||
this.hass.serviceActions.callService('fan', 'turn_on', {
|
||||
entity_id: this.stateObj.entityId,
|
||||
this.hass.callService('fan', 'turn_on', {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
speed: speedInput,
|
||||
});
|
||||
},
|
||||
@ -126,22 +126,22 @@ Polymer({
|
||||
|
||||
if (oldVal === newVal) return;
|
||||
|
||||
this.hass.serviceActions.callService('fan', 'oscillate', {
|
||||
entity_id: this.stateObj.entityId,
|
||||
this.hass.callService('fan', 'oscillate', {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
oscillating: newVal,
|
||||
});
|
||||
},
|
||||
|
||||
onDirectionLeft: function () {
|
||||
this.hass.serviceActions.callService('fan', 'set_direction', {
|
||||
entity_id: this.stateObj.entityId,
|
||||
this.hass.callService('fan', 'set_direction', {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
direction: 'left'
|
||||
});
|
||||
},
|
||||
|
||||
onDirectionRight: function () {
|
||||
this.hass.serviceActions.callService('fan', 'set_direction', {
|
||||
entity_id: this.stateObj.entityId,
|
||||
this.hass.callService('fan', 'set_direction', {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
direction: 'right'
|
||||
});
|
||||
},
|
||||
|
@ -2,8 +2,6 @@
|
||||
|
||||
<link rel="import" href="../state-summary/state-card-content.html">
|
||||
|
||||
<link rel='import' href='../util/hass-behavior.html'>
|
||||
|
||||
<dom-module id="more-info-group">
|
||||
<template>
|
||||
<style>
|
||||
@ -29,8 +27,6 @@
|
||||
Polymer({
|
||||
is: 'more-info-group',
|
||||
|
||||
behaviors: [window.hassBehavior],
|
||||
|
||||
properties: {
|
||||
hass: {
|
||||
type: Object,
|
||||
@ -42,20 +38,7 @@ Polymer({
|
||||
|
||||
states: {
|
||||
type: Array,
|
||||
bindNuclear: function (hass) {
|
||||
return [
|
||||
hass.moreInfoGetters.currentEntity,
|
||||
hass.entityGetters.entityMap,
|
||||
function (currentEntity, entities) {
|
||||
// weird bug??
|
||||
if (!currentEntity) {
|
||||
return [];
|
||||
}
|
||||
return currentEntity.attributes.entity_id.map(
|
||||
entities.get.bind(entities));
|
||||
},
|
||||
];
|
||||
},
|
||||
computed: 'computeStates(stateObj, hass)',
|
||||
},
|
||||
},
|
||||
|
||||
@ -63,6 +46,21 @@ Polymer({
|
||||
'statesChanged(stateObj, states)',
|
||||
],
|
||||
|
||||
computeStates: function (stateObj, hass) {
|
||||
var states = [];
|
||||
var entIds = stateObj.attributes.entity_id;
|
||||
|
||||
for (var i = 0; i < entIds.length; i++) {
|
||||
var state = hass.states[entIds[i]];
|
||||
|
||||
if (state) {
|
||||
states.push(state);
|
||||
}
|
||||
}
|
||||
|
||||
return states;
|
||||
},
|
||||
|
||||
statesChanged: function (stateObj, states) {
|
||||
var groupDomainStateObj = false;
|
||||
var baseStateObj;
|
||||
@ -73,15 +71,18 @@ Polymer({
|
||||
if (states && states.length > 0) {
|
||||
baseStateObj = states[0];
|
||||
|
||||
groupDomainStateObj = baseStateObj.set('entityId', stateObj.entityId).set(
|
||||
'attributes', Object.assign({}, baseStateObj.attributes));
|
||||
groupDomainStateObj = Object.assign(baseStateObj, {
|
||||
entity_id: stateObj.entity_id,
|
||||
attributes: Object.assign({}, baseStateObj.attributes)
|
||||
});
|
||||
var groupDomain = window.hassUtil.computeDomain(groupDomainStateObj);
|
||||
|
||||
for (i = 0; i < states.length; i++) {
|
||||
state = states[i];
|
||||
if (state && state.domain) {
|
||||
if (groupDomainStateObj.domain !== state.domain) {
|
||||
|
||||
if (groupDomain !== window.hassUtil.computeDomain(state)) {
|
||||
groupDomainStateObj = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -165,8 +165,8 @@ Polymer({
|
||||
effectInput = this.stateObj.attributes.effect_list[effectIndex];
|
||||
if (effectInput === this.stateObj.attributes.effect) return;
|
||||
|
||||
this.hass.serviceActions.callService('light', 'turn_on', {
|
||||
entity_id: this.stateObj.entityId,
|
||||
this.hass.callService('light', 'turn_on', {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
effect: effectInput,
|
||||
});
|
||||
},
|
||||
@ -177,10 +177,12 @@ Polymer({
|
||||
if (isNaN(bri)) return;
|
||||
|
||||
if (bri === 0) {
|
||||
this.hass.serviceActions.callTurnOff(this.stateObj.entityId);
|
||||
this.hass.callService('light', 'turn_off', {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
});
|
||||
} else {
|
||||
this.hass.serviceActions.callService('light', 'turn_on', {
|
||||
entity_id: this.stateObj.entityId,
|
||||
this.hass.callService('light', 'turn_on', {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
brightness: bri,
|
||||
});
|
||||
}
|
||||
@ -191,8 +193,8 @@ Polymer({
|
||||
|
||||
if (isNaN(ct)) return;
|
||||
|
||||
this.hass.serviceActions.callService('light', 'turn_on', {
|
||||
entity_id: this.stateObj.entityId,
|
||||
this.hass.callService('light', 'turn_on', {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
color_temp: ct,
|
||||
});
|
||||
},
|
||||
@ -202,14 +204,14 @@ Polymer({
|
||||
|
||||
if (isNaN(wv)) return;
|
||||
|
||||
this.hass.serviceActions.callService('light', 'turn_on', {
|
||||
entity_id: this.stateObj.entityId,
|
||||
this.hass.callService('light', 'turn_on', {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
white_value: wv,
|
||||
});
|
||||
},
|
||||
|
||||
serviceChangeColor: function (hass, entityId, color) {
|
||||
hass.serviceActions.callService('light', 'turn_on', {
|
||||
hass.callService('light', 'turn_on', {
|
||||
entity_id: entityId,
|
||||
rgb_color: [color.r, color.g, color.b],
|
||||
});
|
||||
@ -227,14 +229,14 @@ Polymer({
|
||||
|
||||
this.color = ev.detail.rgb;
|
||||
|
||||
this.serviceChangeColor(this.hass, this.stateObj.entityId, this.color);
|
||||
this.serviceChangeColor(this.hass, this.stateObj.entity_id, this.color);
|
||||
|
||||
this.colorChanged = false;
|
||||
this.skipColorPicked = true;
|
||||
|
||||
this.colorDebounce = setTimeout(function () {
|
||||
if (this.colorChanged) {
|
||||
this.serviceChangeColor(this.hass, this.stateObj.entityId, this.color);
|
||||
this.serviceChangeColor(this.hass, this.stateObj.entity_id, this.color);
|
||||
}
|
||||
this.skipColorPicked = false;
|
||||
}.bind(this), 500);
|
||||
|
@ -58,8 +58,8 @@ Polymer({
|
||||
|
||||
callService: function (service, data) {
|
||||
var serviceData = data || {};
|
||||
serviceData.entity_id = this.stateObj.entityId;
|
||||
this.hass.serviceActions.callService('lock', service, serviceData);
|
||||
serviceData.entity_id = this.stateObj.entity_id;
|
||||
this.hass.callService('lock', service, serviceData);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -10,8 +10,6 @@
|
||||
<link rel='import' href='../../bower_components/paper-menu/paper-menu.html'>
|
||||
<link rel='import' href='../../bower_components/paper-item/paper-item.html'>
|
||||
|
||||
<link rel='import' href='../util/hass-behavior.html'>
|
||||
|
||||
<dom-module id='more-info-media_player'>
|
||||
<template>
|
||||
<style is="custom-style" include="iron-flex iron-flex-alignment"></style>
|
||||
@ -115,14 +113,10 @@
|
||||
Polymer({
|
||||
is: 'more-info-media_player',
|
||||
|
||||
behaviors: [window.hassBehavior],
|
||||
|
||||
properties: {
|
||||
ttsLoaded: {
|
||||
type: Boolean,
|
||||
bindNuclear: function (hass) {
|
||||
return hass.configGetters.isComponentLoaded('tts');
|
||||
},
|
||||
computed: 'computeTTSLoaded(hass)',
|
||||
},
|
||||
|
||||
hass: {
|
||||
@ -307,6 +301,10 @@ Polymer({
|
||||
return !ttsLoaded || !supportsPlayMedia;
|
||||
},
|
||||
|
||||
computeTTSLoaded: function (hass) {
|
||||
return window.hassUtil.isComponentLoaded(hass, 'tts');
|
||||
},
|
||||
|
||||
handleTogglePower: function () {
|
||||
this.callService(this.isOff ? 'turn_on' : 'turn_off');
|
||||
},
|
||||
@ -376,15 +374,14 @@ Polymer({
|
||||
},
|
||||
|
||||
sendTTS: function () {
|
||||
var services = this.hass.reactor.evaluate(
|
||||
this.hass.serviceGetters.entityMap).get('tts').get('services').keySeq()
|
||||
.toArray();
|
||||
var services = this.hass.config.services.tts;
|
||||
var serviceKeys = Object.keys(services).sort();
|
||||
var service;
|
||||
var i;
|
||||
|
||||
for (i = 0; i < services.length; i++) {
|
||||
if (services[i].indexOf('_say') !== -1) {
|
||||
service = services[i];
|
||||
for (i = 0; i < serviceKeys.length; i++) {
|
||||
if (services[serviceKeys[i]].indexOf('_say') !== -1) {
|
||||
service = services[serviceKeys[i]];
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -393,8 +390,8 @@ Polymer({
|
||||
return;
|
||||
}
|
||||
|
||||
this.hass.serviceActions.callService('tts', service, {
|
||||
entity_id: this.stateObj.entityId,
|
||||
this.hass.callService('tts', service, {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
message: this.ttsMessage,
|
||||
});
|
||||
this.ttsMessage = '';
|
||||
@ -403,8 +400,8 @@ Polymer({
|
||||
|
||||
callService: function (service, data) {
|
||||
var serviceData = data || {};
|
||||
serviceData.entity_id = this.stateObj.entityId;
|
||||
this.hass.serviceActions.callService('media_player', service, serviceData);
|
||||
serviceData.entity_id = this.stateObj.entity_id;
|
||||
this.hass.callService('media_player', service, serviceData);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -36,12 +36,12 @@ Polymer({
|
||||
],
|
||||
|
||||
inputChanged: function (hass, inDialog, stateObj) {
|
||||
if (!stateObj) return;
|
||||
if (!stateObj || !hass) return;
|
||||
|
||||
window.hassUtil.dynamicContentUpdater(
|
||||
this,
|
||||
('STATE-CARD-' +
|
||||
window.hassUtil.stateCardType(this.hass, stateObj).toUpperCase()),
|
||||
window.hassUtil.stateCardType(hass, stateObj).toUpperCase()),
|
||||
{
|
||||
hass: hass,
|
||||
stateObj: stateObj,
|
||||
|
@ -68,18 +68,18 @@ Polymer({
|
||||
},
|
||||
|
||||
onOpenTap: function () {
|
||||
this.hass.serviceActions.callService('cover', 'open_cover',
|
||||
{ entity_id: this.stateObj.entityId });
|
||||
this.hass.callService('cover', 'open_cover',
|
||||
{ entity_id: this.stateObj.entity_id });
|
||||
},
|
||||
|
||||
onCloseTap: function () {
|
||||
this.hass.serviceActions.callService('cover', 'close_cover',
|
||||
{ entity_id: this.stateObj.entityId });
|
||||
this.hass.callService('cover', 'close_cover',
|
||||
{ entity_id: this.stateObj.entity_id });
|
||||
},
|
||||
|
||||
onStopTap: function () {
|
||||
this.hass.serviceActions.callService('cover', 'stop_cover',
|
||||
{ entity_id: this.stateObj.entityId });
|
||||
this.hass.callService('cover', 'stop_cover',
|
||||
{ entity_id: this.stateObj.entity_id });
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -20,7 +20,7 @@
|
||||
|
||||
<div class='horizontal justified layout'>
|
||||
<state-info state-obj="[[stateObj]]" in-dialog='[[inDialog]]'></state-info>
|
||||
<div class='state'>[[stateObj.stateDisplay]]</div>
|
||||
<div class='state'>[[computeStateDisplay(stateObj)]]</div>
|
||||
</div>
|
||||
</template>
|
||||
</dom-module>
|
||||
@ -39,5 +39,9 @@ Polymer({
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
|
||||
computeStateDisplay: function (stateObj) {
|
||||
return window.hassUtil.computeStateState(stateObj);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -27,7 +27,7 @@
|
||||
<paper-dropdown-menu
|
||||
on-tap='stopPropagation'
|
||||
selected-item-label='{{selectedOption}}'
|
||||
label='[[stateObj.entityDisplay]]'>
|
||||
label='[[computeStateName(stateObj)]]'>
|
||||
<paper-menu class="dropdown-content" selected="[[computeSelected(stateObj)]]">
|
||||
<template is='dom-repeat' items='[[stateObj.attributes.options]]'>
|
||||
<paper-item>[[item]]</paper-item>
|
||||
@ -61,6 +61,10 @@ Polymer({
|
||||
},
|
||||
},
|
||||
|
||||
computeStateName: function (stateObj) {
|
||||
return window.hassUtil.computeStateName(stateObj);
|
||||
},
|
||||
|
||||
computeSelected: function (stateObj) {
|
||||
return stateObj.attributes.options.indexOf(stateObj.state);
|
||||
},
|
||||
@ -70,9 +74,9 @@ Polymer({
|
||||
if (option === '' || option === this.stateObj.state) {
|
||||
return;
|
||||
}
|
||||
this.hass.serviceActions.callService('input_select', 'select_option', {
|
||||
this.hass.callService('input_select', 'select_option', {
|
||||
option: option,
|
||||
entity_id: this.stateObj.entityId,
|
||||
entity_id: this.stateObj.entity_id,
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -68,9 +68,9 @@ Polymer({
|
||||
if (this.value === Number(this.stateObj.state)) {
|
||||
return;
|
||||
}
|
||||
this.hass.serviceActions.callService('input_slider', 'select_value', {
|
||||
this.hass.callService('input_slider', 'select_value', {
|
||||
value: this.value,
|
||||
entity_id: this.stateObj.entityId,
|
||||
entity_id: this.stateObj.entity_id,
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -38,8 +38,8 @@
|
||||
<div class='horizontal justified layout'>
|
||||
<state-info state-obj="[[stateObj]]" in-dialog='[[inDialog]]'></state-info>
|
||||
<div class='state'>
|
||||
<div class='main-text' take-height$='[[!secondaryText]]'>[[computePrimaryText(stateObj, isPlaying)]]</div>
|
||||
<div class='secondary-text'>[[secondaryText]]</div>
|
||||
<div class='main-text' take-height$='[[!playerObj.secondaryText]]'>[[playerObj.primaryText]]</div>
|
||||
<div class='secondary-text'>[[playerObj.secondaryText]]</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -51,6 +51,10 @@ Polymer({
|
||||
is: 'state-card-media_player',
|
||||
|
||||
properties: {
|
||||
hass: {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
inDialog: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
@ -60,41 +64,14 @@ Polymer({
|
||||
type: Object,
|
||||
},
|
||||
|
||||
isPlaying: {
|
||||
type: Boolean,
|
||||
computed: 'computeIsPlaying(stateObj)',
|
||||
},
|
||||
|
||||
secondaryText: {
|
||||
type: String,
|
||||
computed: 'computeSecondaryText(stateObj)',
|
||||
playerObj: {
|
||||
type: Object,
|
||||
computed: 'computePlayerObj(hass, stateObj)',
|
||||
},
|
||||
},
|
||||
|
||||
computeIsPlaying: function (stateObj) {
|
||||
return this.PLAYING_STATES.indexOf(stateObj.state) !== -1;
|
||||
},
|
||||
|
||||
computePrimaryText: function (stateObj, isPlaying) {
|
||||
return isPlaying ? stateObj.attributes.media_title : stateObj.stateDisplay;
|
||||
},
|
||||
|
||||
computeSecondaryText: function (stateObj) {
|
||||
var text;
|
||||
|
||||
if (stateObj.attributes.media_content_type === 'music') {
|
||||
return stateObj.attributes.media_artist;
|
||||
} else if (stateObj.attributes.media_content_type === 'tvshow') {
|
||||
text = stateObj.attributes.media_series_title;
|
||||
|
||||
if (stateObj.attributes.media_season && stateObj.attributes.media_episode) {
|
||||
text += ' S' + stateObj.attributes.media_season + 'E' + stateObj.attributes.media_episode;
|
||||
}
|
||||
return text;
|
||||
} else if (stateObj.attributes.app_name) {
|
||||
return stateObj.attributes.app_name;
|
||||
}
|
||||
return '';
|
||||
computePlayerObj: function (hass, stateObj) {
|
||||
return new window.MediaPlayerEntity(hass, stateObj);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -47,7 +47,9 @@ Polymer({
|
||||
|
||||
activateScene: function (ev) {
|
||||
ev.stopPropagation();
|
||||
this.hass.serviceActions.callTurnOn(this.stateObj.entityId);
|
||||
this.hass.callService(
|
||||
'scene', 'turn_on',
|
||||
{ entity_id: this.stateObj.entity_id });
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -53,7 +53,9 @@ Polymer({
|
||||
|
||||
fireScript: function (ev) {
|
||||
ev.stopPropagation();
|
||||
this.hass.serviceActions.callTurnOn(this.stateObj.entityId);
|
||||
this.hass.callService(
|
||||
'script', 'turn_on',
|
||||
{ entity_id: this.stateObj.entity_id });
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -20,7 +20,7 @@
|
||||
</style>
|
||||
|
||||
<state-badge state-obj='[[stateObj]]' in-dialog='[[inDialog]]'></state-badge>
|
||||
<a href$='[[stateObj.state]]' target='_blank' class='name' id='link'>[[stateObj.entityDisplay]]</a>
|
||||
<a href$='[[stateObj.state]]' target='_blank' class='name' id='link'>[[computeStateName(stateObj)]]</a>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
@ -43,6 +43,10 @@ Polymer({
|
||||
tap: 'onTap',
|
||||
},
|
||||
|
||||
computeStateName: function (stateObj) {
|
||||
return window.hassUtil.computeStateName(stateObj);
|
||||
},
|
||||
|
||||
onTap: function (ev) {
|
||||
ev.stopPropagation();
|
||||
if (ev.target === this.$.link) {
|
||||
|
48
src/util/ha-pref-storage.html
Normal file
48
src/util/ha-pref-storage.html
Normal file
@ -0,0 +1,48 @@
|
||||
<script>
|
||||
(function () {
|
||||
var STORED_STATE = [
|
||||
'dockedSidebar',
|
||||
];
|
||||
|
||||
Polymer({
|
||||
is: 'ha-pref-storage',
|
||||
|
||||
properties: {
|
||||
hass: {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
storage: {
|
||||
type: Object,
|
||||
value: window.localStorage || {},
|
||||
},
|
||||
},
|
||||
|
||||
storeState: function () {
|
||||
if (!this.hass) return;
|
||||
|
||||
try {
|
||||
for (var i = 0; i < STORED_STATE.length; i++) {
|
||||
var key = STORED_STATE[i];
|
||||
this.storage[key] = JSON.stringify(this.hass[key]);
|
||||
}
|
||||
} catch (err) {
|
||||
// Safari throws exception in private mode
|
||||
}
|
||||
},
|
||||
|
||||
getStoredState: function () {
|
||||
var state = {};
|
||||
|
||||
for (var i = 0; i < STORED_STATE.length; i++) {
|
||||
var key = STORED_STATE[i];
|
||||
if (key in this.storage) {
|
||||
state[key] = JSON.parse(this.storage[key]);
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
});
|
||||
}());
|
||||
</script>
|
89
src/util/ha-url-sync.html
Normal file
89
src/util/ha-url-sync.html
Normal file
@ -0,0 +1,89 @@
|
||||
<script>
|
||||
(function () {
|
||||
var PAGE_TITLE = 'Home Assistant';
|
||||
|
||||
function pageState(panel, view) {
|
||||
var state = { panel: panel };
|
||||
if (panel === 'states') {
|
||||
state.view = view || null;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
function pageUrl(pane, view) {
|
||||
return pane === 'states' && view ?
|
||||
'/' + pane + '/' + view : '/' + pane;
|
||||
}
|
||||
|
||||
Polymer({
|
||||
is: 'ha-url-sync',
|
||||
|
||||
properties: {
|
||||
hass: {
|
||||
type: Object,
|
||||
observer: 'hassChanged',
|
||||
},
|
||||
},
|
||||
|
||||
hassChanged: function (newHass, oldHass) {
|
||||
if (!oldHass) {
|
||||
return;
|
||||
} else if (newHass.currentPanel === oldHass.currentPanel &&
|
||||
newHass.currentView === oldHass.currentView) {
|
||||
// did the more info entity change?
|
||||
if (oldHass.moreInfoEntityId !== newHass.moreInfoEntityId) {
|
||||
if (newHass.moreInfoEntityId) {
|
||||
// push same state so that back button works.
|
||||
history.pushState(history.state, PAGE_TITLE, window.location.pathname);
|
||||
} else if (this.ignoreNextDeselectEntity) {
|
||||
this.ignoreNextDeselectEntity = false;
|
||||
} else {
|
||||
history.back();
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
} else if (this.ignoreNextNav) {
|
||||
this.ignoreNextNav = false;
|
||||
return;
|
||||
}
|
||||
|
||||
history.pushState(
|
||||
pageState(newHass.currentPanel, newHass.currentView), PAGE_TITLE,
|
||||
pageUrl(newHass.currentPanel, newHass.currentView));
|
||||
},
|
||||
|
||||
popstateChangeListener: function (ev) {
|
||||
if (this.hass.moreInfoEntityId) {
|
||||
this.ignoreNextDeselectEntity = true;
|
||||
this.fire('hass-more-info', { entityId: null });
|
||||
} else if (this.hass.currentPanel !== ev.state.panel ||
|
||||
this.hass.currentView !== ev.state.view) {
|
||||
this.ignoreNextNav = true;
|
||||
this.fire('hass-navigate', ev.state);
|
||||
}
|
||||
},
|
||||
|
||||
// initial url sync
|
||||
attached: function () {
|
||||
this.popstateChangeListener = this.popstateChangeListener.bind(this);
|
||||
|
||||
// keep state in sync when url changes via forward/back buttons
|
||||
window.addEventListener('popstate', this.popstateChangeListener);
|
||||
|
||||
// store current view / panel
|
||||
if (window.location.pathname === '/') {
|
||||
var currentPanel = this.hass.currentPanel;
|
||||
var currentView = this.hass.currentView;
|
||||
|
||||
history.replaceState(
|
||||
pageState(currentPanel, currentView), PAGE_TITLE,
|
||||
pageUrl(currentPanel, currentView));
|
||||
} else {
|
||||
var parts = window.location.pathname.substr(1).split('/');
|
||||
this.fire('hass-navigate', pageState(parts[0], parts[1]));
|
||||
}
|
||||
}
|
||||
});
|
||||
}());
|
||||
</script>
|
@ -1,40 +0,0 @@
|
||||
<script>
|
||||
/** @polymerBehavior */
|
||||
window.hassBehavior = {
|
||||
attached: function attached() {
|
||||
var hass = this.hass;
|
||||
|
||||
if (!hass) {
|
||||
throw new Error('No hass property found on ' + this.nodeName);
|
||||
}
|
||||
|
||||
this.nuclearUnwatchFns = Object.keys(this.properties).reduce(
|
||||
function bindGetters(unwatchFns, key) {
|
||||
var getter;
|
||||
|
||||
if (!('bindNuclear' in this.properties[key])) {
|
||||
return unwatchFns;
|
||||
}
|
||||
|
||||
getter = this.properties[key].bindNuclear(hass);
|
||||
|
||||
if (!getter) {
|
||||
throw new Error('Undefined getter specified for key ' + key +
|
||||
' on ' + this.nodeName);
|
||||
}
|
||||
|
||||
this[key] = hass.reactor.evaluate(getter);
|
||||
|
||||
return unwatchFns.concat(hass.reactor.observe(getter, function updateAttribute(val) {
|
||||
this[key] = val;
|
||||
}.bind(this)));
|
||||
}.bind(this), []);
|
||||
},
|
||||
|
||||
detached: function detached() {
|
||||
while (this.nuclearUnwatchFns.length) {
|
||||
this.nuclearUnwatchFns.shift()();
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
81
src/util/hass-call-api.html
Normal file
81
src/util/hass-call-api.html
Normal file
@ -0,0 +1,81 @@
|
||||
<script>
|
||||
window.hassCallApi = function (host, auth, method, path, parameters) {
|
||||
var url = host + '/api/' + path;
|
||||
|
||||
if (window.HASS_DEMO) {
|
||||
var component = path.split('/', 1)[0];
|
||||
var data;
|
||||
switch (component) {
|
||||
case 'bootstrap':
|
||||
data = window.hassDemoData.bootstrap;
|
||||
break;
|
||||
case 'logbook':
|
||||
data = window.hassDemoData.logbook;
|
||||
break;
|
||||
case 'history':
|
||||
data = window.hassDemoData.stateHistory;
|
||||
break;
|
||||
default:
|
||||
data = false;
|
||||
}
|
||||
return new Promise(function (resolve, reject) {
|
||||
if (data) {
|
||||
resolve(data);
|
||||
} else {
|
||||
reject('Request not allowed in demo mode.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
var req = new XMLHttpRequest();
|
||||
req.open(method, url, true);
|
||||
|
||||
if (auth.authToken) {
|
||||
req.setRequestHeader('X-HA-access', auth.authToken);
|
||||
}
|
||||
|
||||
req.onload = function () {
|
||||
var body = req.responseText;
|
||||
|
||||
if (req.getResponseHeader('content-type') === 'application/json') {
|
||||
try {
|
||||
body = JSON.parse(req.responseText);
|
||||
} catch (err) {
|
||||
reject({
|
||||
error: 'Unable to parse JSON response',
|
||||
status_code: req.status,
|
||||
body: body,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (req.status > 199 && req.status < 300) {
|
||||
resolve(body);
|
||||
} else {
|
||||
reject({
|
||||
error: 'Response error: ' + req.status,
|
||||
status_code: req.status,
|
||||
body: body
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
req.onerror = function () {
|
||||
reject({
|
||||
error: 'Request error',
|
||||
status_code: req.status,
|
||||
body: req.responseText,
|
||||
});
|
||||
};
|
||||
|
||||
if (parameters) {
|
||||
req.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
|
||||
req.send(JSON.stringify(parameters));
|
||||
} else {
|
||||
req.send();
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
@ -46,8 +46,28 @@ window.hassUtil.attributeClassNames = function (stateObj, attributes) {
|
||||
).join(' ');
|
||||
};
|
||||
|
||||
window.hassUtil.canToggle = function (hass, entityId) {
|
||||
return hass.reactor.evaluate(hass.serviceGetters.canToggleEntity(entityId));
|
||||
window.hassUtil.canToggleState = function (hass, stateObj) {
|
||||
var domain = window.hassUtil.computeDomain(stateObj);
|
||||
if (domain === 'group') {
|
||||
return stateObj.state === 'on' || stateObj.state === 'off';
|
||||
}
|
||||
|
||||
return window.hassUtil.canToggleDomain(hass, domain);
|
||||
};
|
||||
|
||||
window.hassUtil.canToggleDomain = function (hass, domain) {
|
||||
var turnOnService;
|
||||
var services = hass.config.services[domain];
|
||||
|
||||
if (domain === 'lock') {
|
||||
turnOnService = 'lock';
|
||||
} else if (domain === 'cover') {
|
||||
turnOnService = 'open_cover';
|
||||
} else {
|
||||
turnOnService = 'turn_on';
|
||||
}
|
||||
|
||||
return services && turnOnService in services;
|
||||
};
|
||||
|
||||
// Update root's child element to be newElementTag replacing another existing child if any.
|
||||
@ -168,22 +188,29 @@ window.hassUtil.relativeTime.tests = [
|
||||
7, 'day',
|
||||
];
|
||||
|
||||
window.hassUtil.stateCardType = function (hass, state) {
|
||||
if (state.state === 'unavailable') {
|
||||
window.hassUtil.stateCardType = function (hass, stateObj) {
|
||||
if (stateObj.state === 'unavailable') {
|
||||
return 'display';
|
||||
} else if (window.hassUtil.DOMAINS_WITH_CARD.indexOf(state.domain) !== -1) {
|
||||
return state.domain;
|
||||
} else if (window.hassUtil.canToggle(hass, state.entityId) && state.attributes.control !== 'hidden') {
|
||||
}
|
||||
|
||||
var domain = window.hassUtil.computeDomain(stateObj);
|
||||
|
||||
if (window.hassUtil.DOMAINS_WITH_CARD.indexOf(domain) !== -1) {
|
||||
return domain;
|
||||
} else if (window.hassUtil.canToggleState(hass, stateObj) &&
|
||||
stateObj.attributes.control !== 'hidden') {
|
||||
return 'toggle';
|
||||
}
|
||||
return 'display';
|
||||
};
|
||||
|
||||
window.hassUtil.stateMoreInfoType = function (state) {
|
||||
if (window.hassUtil.DOMAINS_WITH_MORE_INFO.indexOf(state.domain) !== -1) {
|
||||
return state.domain;
|
||||
window.hassUtil.stateMoreInfoType = function (stateObj) {
|
||||
var domain = window.hassUtil.computeDomain(stateObj);
|
||||
|
||||
if (window.hassUtil.DOMAINS_WITH_MORE_INFO.indexOf(domain) !== -1) {
|
||||
return domain;
|
||||
}
|
||||
if (window.hassUtil.HIDE_MORE_INFO.indexOf(state.domain) !== -1) {
|
||||
if (window.hassUtil.HIDE_MORE_INFO.indexOf(domain) !== -1) {
|
||||
return 'hidden';
|
||||
}
|
||||
return 'default';
|
||||
@ -318,27 +345,65 @@ window.hassUtil.binarySensorIcon = function (state) {
|
||||
};
|
||||
|
||||
window.hassUtil.stateIcon = function (state) {
|
||||
var unit;
|
||||
|
||||
if (!state) {
|
||||
return window.hassUtil.DEFAULT_ICON;
|
||||
} else if (state.attributes.icon) {
|
||||
return state.attributes.icon;
|
||||
}
|
||||
|
||||
unit = state.attributes.unit_of_measurement;
|
||||
var unit = state.attributes.unit_of_measurement;
|
||||
var domain = window.hassUtil.computeDomain(state);
|
||||
|
||||
if (unit && state.domain === 'sensor') {
|
||||
if (unit && domain === 'sensor') {
|
||||
if (unit === '°C' || unit === '°F') {
|
||||
return 'mdi:thermometer';
|
||||
} else if (unit === 'Mice') {
|
||||
return 'mdi:mouse-variant';
|
||||
}
|
||||
} else if (state.domain === 'binary_sensor') {
|
||||
} else if (domain === 'binary_sensor') {
|
||||
return window.hassUtil.binarySensorIcon(state);
|
||||
}
|
||||
|
||||
return window.hassUtil.domainIcon(state.domain, state.state);
|
||||
return window.hassUtil.domainIcon(domain, state.state);
|
||||
};
|
||||
|
||||
window.hassUtil.computeDomain = function (stateObj) {
|
||||
if (!stateObj._domain) {
|
||||
stateObj._domain = window.HAWS.extractDomain(stateObj.entity_id);
|
||||
}
|
||||
|
||||
return stateObj._domain;
|
||||
};
|
||||
|
||||
window.hassUtil.computeStateName = function (stateObj) {
|
||||
if (!stateObj._entityDisplay) {
|
||||
stateObj._entityDisplay = (
|
||||
stateObj.attributes.friendly_name ||
|
||||
window.HAWS.extractObjectId(stateObj.entity_id)
|
||||
.replace(/_/g, ' '));
|
||||
}
|
||||
|
||||
return stateObj._entityDisplay;
|
||||
};
|
||||
|
||||
window.hassUtil.computeStateState = function (stateObj) {
|
||||
if (!stateObj._stateDisplay) {
|
||||
stateObj._stateDisplay = stateObj.state.replace(/_/g, ' ');
|
||||
|
||||
if (stateObj.attributes.unit_of_measurement) {
|
||||
stateObj._stateDisplay += ` ${stateObj.attributes.unit_of_measurement}`;
|
||||
}
|
||||
}
|
||||
|
||||
return stateObj._stateDisplay;
|
||||
};
|
||||
|
||||
window.hassUtil.isComponentLoaded = function (hass, component) {
|
||||
return hass.config.core.components.indexOf(component) !== -1;
|
||||
};
|
||||
|
||||
window.hassUtil.computeLocationName = function (hass) {
|
||||
return hass.config.core.location_name;
|
||||
};
|
||||
|
||||
</script>
|
||||
|
195
src/util/media-player-model.html
Normal file
195
src/util/media-player-model.html
Normal file
@ -0,0 +1,195 @@
|
||||
<script>
|
||||
(function () {
|
||||
window.MediaPlayerEntity = function (hass, stateObj) {
|
||||
this.hass = hass;
|
||||
this.stateObj = stateObj;
|
||||
};
|
||||
|
||||
function addGetter(name, getter) {
|
||||
Object.defineProperty(window.MediaPlayerEntity.prototype, name,
|
||||
{ get: getter });
|
||||
}
|
||||
|
||||
addGetter('isOff', function () {
|
||||
return this.stateObj.state === 'off';
|
||||
});
|
||||
|
||||
addGetter('isIdle', function () {
|
||||
return this.stateObj.state === 'idle';
|
||||
});
|
||||
|
||||
addGetter('isMuted', function () {
|
||||
return this.stateObj.attributes.is_volume_muted;
|
||||
});
|
||||
|
||||
addGetter('isPaused', function () {
|
||||
return this.stateObj.state === 'paused';
|
||||
});
|
||||
|
||||
addGetter('isPlaying', function () {
|
||||
return this.stateObj.state === 'playing';
|
||||
});
|
||||
|
||||
addGetter('isMusic', function () {
|
||||
return this.stateObj.attributes.media_content_type === 'music';
|
||||
});
|
||||
|
||||
addGetter('isTVShow', function () {
|
||||
return this.stateObj.attributes.media_content_type === 'tvshow';
|
||||
});
|
||||
|
||||
addGetter('hasMediaControl', function () {
|
||||
return ['playing', 'paused', 'unknown'].indexOf(
|
||||
this.stateObj.state) !== -1;
|
||||
});
|
||||
|
||||
addGetter('volumeSliderValue', function () {
|
||||
return this.stateObj.attributes.volume_level * 100;
|
||||
});
|
||||
|
||||
addGetter('showProgress', function () {
|
||||
return (
|
||||
(this.isPlaying || this.isPaused) &&
|
||||
'media_position' in this.stateObj.attributes &&
|
||||
'media_position_updated_at' in this.stateObj.attributes);
|
||||
});
|
||||
|
||||
addGetter('currentProgress', function () {
|
||||
return (
|
||||
this.stateObj.attributes.media_position +
|
||||
((Date.now() -
|
||||
new Date(this.stateObj.attributes.media_position_updated_at)) / 1000));
|
||||
});
|
||||
|
||||
/* eslint-disable no-bitwise */
|
||||
|
||||
addGetter('supportsPause', function () {
|
||||
return (this.stateObj.attributes.supported_media_commands & 1) !== 0;
|
||||
});
|
||||
|
||||
addGetter('supportsVolumeSet', function () {
|
||||
return (this.stateObj.attributes.supported_media_commands & 4) !== 0;
|
||||
});
|
||||
|
||||
addGetter('supportsVolumeMute', function () {
|
||||
return (this.stateObj.attributes.supported_media_commands & 8) !== 0;
|
||||
});
|
||||
|
||||
addGetter('supportsPreviousTrack', function () {
|
||||
return (this.stateObj.attributes.supported_media_commands & 16) !== 0;
|
||||
});
|
||||
|
||||
addGetter('supportsNextTrack', function () {
|
||||
return (this.stateObj.attributes.supported_media_commands & 32) !== 0;
|
||||
});
|
||||
|
||||
addGetter('supportsTurnOn', function () {
|
||||
return (this.stateObj.attributes.supported_media_commands & 128) !== 0;
|
||||
});
|
||||
|
||||
addGetter('supportsTurnOff', function () {
|
||||
return (this.stateObj.attributes.supported_media_commands & 256) !== 0;
|
||||
});
|
||||
|
||||
addGetter('supportsPlayMedia', function () {
|
||||
return (this.stateObj.attributes.supported_media_commands & 512) !== 0;
|
||||
});
|
||||
|
||||
addGetter('supportsVolumeButtons', function () {
|
||||
return (this.stateObj.attributes.supported_media_commands & 1024) !== 0;
|
||||
});
|
||||
|
||||
addGetter('supportsPlay', function () {
|
||||
return (this.stateObj.attributes.supported_media_commands & 16384) !== 0;
|
||||
});
|
||||
|
||||
/* eslint-enable no-bitwise */
|
||||
|
||||
addGetter('primaryText', function () {
|
||||
return this.stateObj.attributes.media_title ||
|
||||
window.hassUtil.computeStateState(this.stateObj);
|
||||
});
|
||||
|
||||
addGetter('secondaryText', function () {
|
||||
if (this.isMusic) {
|
||||
return this.stateObj.attributes.media_artist;
|
||||
} else if (this.isTVShow) {
|
||||
var text = this.stateObj.attributes.media_series_title;
|
||||
|
||||
if (this.stateObj.attributes.media_season) {
|
||||
text += ' S' + this.stateObj.attributes.media_season;
|
||||
|
||||
if (this.stateObj.attributes.media_episode) {
|
||||
text += 'E' + this.stateObj.attributes.media_episode;
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
} else if (this.stateObj.attributes.app_name) {
|
||||
return this.stateObj.attributes.app_name;
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
Object.assign(window.MediaPlayerEntity.prototype, {
|
||||
mediaPlayPause() {
|
||||
this.callService('media_play_pause');
|
||||
},
|
||||
|
||||
nextTrack() {
|
||||
this.callService('media_next_track');
|
||||
},
|
||||
|
||||
playbackControl() {
|
||||
this.callService('media_play_pause');
|
||||
},
|
||||
|
||||
previousTrack() {
|
||||
this.callService('media_previous_track');
|
||||
},
|
||||
|
||||
setVolume(volume) {
|
||||
this.callService('volume_set', { volume_level: volume });
|
||||
},
|
||||
|
||||
togglePower() {
|
||||
if (this.isOff) {
|
||||
this.turnOn();
|
||||
} else {
|
||||
this.turnOff();
|
||||
}
|
||||
},
|
||||
|
||||
turnOff() {
|
||||
this.callService('turn_off');
|
||||
},
|
||||
|
||||
turnOn() {
|
||||
this.callService('turn_on');
|
||||
},
|
||||
|
||||
volumeDown() {
|
||||
this.callService('volume_down');
|
||||
},
|
||||
|
||||
volumeMute(mute) {
|
||||
if (!this.supportsVolumeMute) {
|
||||
throw new Error('Muting volume not supported');
|
||||
}
|
||||
this.callService('volume_mute', { is_volume_muted: mute });
|
||||
},
|
||||
|
||||
volumeUp() {
|
||||
this.callService('volume_down');
|
||||
},
|
||||
|
||||
// helper method
|
||||
|
||||
callService(service, data) {
|
||||
var serviceData = data || {};
|
||||
serviceData.entity_id = this.stateObj.entity_id;
|
||||
this.hass.callService('media_player', service, serviceData);
|
||||
},
|
||||
});
|
||||
}());
|
||||
</script>
|
30
yarn.lock
30
yarn.lock
@ -905,10 +905,6 @@ class-extend@^0.1.0, class-extend@^0.1.1:
|
||||
dependencies:
|
||||
object-assign "^2.0.0"
|
||||
|
||||
classnames@^2.2.5:
|
||||
version "2.2.5"
|
||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d"
|
||||
|
||||
clean-css@3.4.x:
|
||||
version "3.4.24"
|
||||
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-3.4.24.tgz#89f5a5e9da37ae02394fe049a41388abbe72c3b5"
|
||||
@ -2806,9 +2802,9 @@ hoek@2.x.x:
|
||||
version "2.16.3"
|
||||
resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
|
||||
|
||||
home-assistant-js-websocket@0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/home-assistant-js-websocket/-/home-assistant-js-websocket-0.5.0.tgz#f78b31d160d7bdd083cf53ae01328483e7046ef5"
|
||||
home-assistant-js-websocket@^0.7.3:
|
||||
version "0.7.3"
|
||||
resolved "https://registry.yarnpkg.com/home-assistant-js-websocket/-/home-assistant-js-websocket-0.7.3.tgz#e65ac99b59a0b2c623048457c259808d67030c7f"
|
||||
|
||||
homedir-polyfill@^1.0.0:
|
||||
version "1.0.1"
|
||||
@ -2934,10 +2930,6 @@ ignore@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.2.0.tgz#8d88f03c3002a0ac52114db25d2c673b0bf1e435"
|
||||
|
||||
immutable@^3.8.1:
|
||||
version "3.8.1"
|
||||
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.1.tgz#200807f11ab0f72710ea485542de088075f68cd2"
|
||||
|
||||
imurmurhash@^0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
|
||||
@ -3336,10 +3328,6 @@ keep-alive-agent@^0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/keep-alive-agent/-/keep-alive-agent-0.0.1.tgz#44847ca394ce8d6b521ae85816bd64509942b385"
|
||||
|
||||
keymirror@^0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/keymirror/-/keymirror-0.1.1.tgz#918889ea13f8d0a42e7c557250eee713adc95c35"
|
||||
|
||||
kind-of@^3.0.2:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.1.0.tgz#475d698a5e49ff5e53d14e3e732429dc8bf4cf47"
|
||||
@ -3997,12 +3985,6 @@ nth-check@~1.0.0:
|
||||
dependencies:
|
||||
boolbase "~1.0.0"
|
||||
|
||||
nuclear-js@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/nuclear-js/-/nuclear-js-1.4.0.tgz#6c9c001b0673f0ae9d8f8b188c4da04ed693a7be"
|
||||
dependencies:
|
||||
immutable "^3.8.1"
|
||||
|
||||
number-is-nan@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
|
||||
@ -4027,7 +4009,7 @@ object-assign@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2"
|
||||
|
||||
object-assign@^4.0.0, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
|
||||
object-assign@^4.0.0, object-assign@^4.0.1, object-assign@^4.1.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
|
||||
@ -5535,9 +5517,9 @@ ternary-stream@^2.0.1:
|
||||
merge-stream "^1.0.0"
|
||||
through2 "^2.0.1"
|
||||
|
||||
"test-fixture@github:polymerelements/test-fixture":
|
||||
test-fixture@PolymerElements/test-fixture:
|
||||
version "2.0.1"
|
||||
resolved "https://codeload.github.com/polymerelements/test-fixture/tar.gz/f72f0ff4e83b83b0157afed664f560da91d20f31"
|
||||
resolved "https://codeload.github.com/PolymerElements/test-fixture/tar.gz/f72f0ff4e83b83b0157afed664f560da91d20f31"
|
||||
|
||||
test-value@^1.1.0:
|
||||
version "1.1.0"
|
||||
|
Loading…
x
Reference in New Issue
Block a user