Update frontend to use NuclearJS

This commit is contained in:
Paulus Schoutsen 2015-06-22 00:25:56 -07:00
parent 4221eef428
commit 7ef0dec185
34 changed files with 1691 additions and 1542 deletions

View file

@ -102,8 +102,8 @@ def setup(hass, config):
{
"auto": True,
ATTR_ENTITY_ID: [
"device_tracker.Paulus",
"device_tracker.Anne_Therese"
"device_tracker.paulus",
"device_tracker.anne_therese"
]
})

View file

@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """
VERSION = "010d9683fa9d210abd199b3cde4edbc0"
VERSION = "18d02dc9820b907ac4159da09cd20c4b"

File diff suppressed because one or more lines are too long

View file

@ -37,7 +37,8 @@
"layout": "Polymer/layout",
"color-picker-element": "~0.0.3",
"paper-styles": "polymerelements/paper-styles#^1.0.0",
"paper-date-picker": "vsimonian/paper-date-picker#master"
"paper-date-picker": "vsimonian/paper-date-picker#master",
"lodash": "~3.9.3"
},
"resolutions": {
"polymer": "^1.0.0",

View file

@ -26,7 +26,7 @@
<script>
(function(){
var uiActions = window.hass.uiActions;
var moreInfoActions = window.hass.moreInfoActions;
Polymer({
is: 'state-card',
@ -44,7 +44,7 @@
cardTapped: function(ev) {
ev.stopPropagation();
this.debounce('show-more-info-dialog', function() {
uiActions.showMoreInfoDialog(this.stateObj.entityId);
moreInfoActions.selectEntity(this.stateObj.entityId);
}.bind(this), 100);
},
});

View file

@ -20,7 +20,7 @@
<template>
<ul>
<template is='dom-repeat' items='[[entities]]' as='entity'>
<li><a href='#' on-click='entitySelected'>[[entity]]</a></li>
<li><a href='#' on-click='entitySelected'>[[entity.entityId]]</a></li>
</template>
</ul>
</template>
@ -28,25 +28,30 @@
<script>
(function() {
var entityGetters = window.hass.entityGetters;
Polymer({
is: 'entity-list',
behaviors: [StoreListenerBehavior],
behaviors: [nuclearObserver],
properties: {
entities: {
type: Array,
value: [],
bindNuclear: [
entityGetters.entityMap,
function(map) {
return map.valueSeq().
sortBy(function(entity) { return entity.entityId; })
.toArray();
},
],
},
stateStoreChanged: function(stateStore) {
this.entities = stateStore.entityIDs.toArray();
},
entitySelected: function(ev) {
ev.preventDefault();
this.fire('entity-selected', {entityId: ev.model.entity});
this.fire('entity-selected', {entityId: ev.model.entity.entityId});
},
});
})();

View file

@ -31,22 +31,27 @@
<script>
(function() {
var eventGetters = window.hass.eventGetters;
Polymer({
is: 'events-list',
behaviors: [StoreListenerBehavior],
behaviors: [nuclearObserver],
properties: {
events: {
type: Array,
value: [],
bindNuclear: [
eventGetters.entityMap,
function(map) {
return map.valueSeq()
.sortBy(function(event) { return event.event; })
.toArray();
}
],
},
},
eventStoreChanged: function(eventStore) {
this.events = eventStore.all.toArray();
},
eventSelected: function(ev) {
ev.preventDefault();
this.fire('event-selected', {eventType: ev.model.event.event});

View file

@ -54,14 +54,14 @@
<script>
(function() {
var uiActions = window.hass.uiActions;
var moreInfoActions = window.hass.moreInfoActions;
Polymer({
is: 'logbook-entry',
entityClicked: function(ev) {
ev.preventDefault();
uiActions.showMoreInfoDialog(this.entryObj.entityId);
moreInfoActions.selectEntity(this.entryObj.entityId);
}
});

View file

@ -23,10 +23,10 @@
<template>
<ul>
<template is='dom-repeat' items="[[domains]]" as="domain">
<template is='dom-repeat' items="[[computeServices(domain)]]" as="service">
<template is='dom-repeat' items="[[serviceDomains]]" as="domain">
<template is='dom-repeat' items="[[domain.services]]" as="service">
<li><a href='#' on-click='serviceClicked'>
<span>[[domain]]</span>/<span>[[service]]</span>
<span>[[domain.domain]]</span>/<span>[[service]]</span>
</a></li>
</template>
</template>
@ -36,19 +36,24 @@
<script>
(function() {
var serviceGetters = window.hass.serviceGetters;
Polymer({
is: 'services-list',
behaviors: [StoreListenerBehavior],
behaviors: [nuclearObserver],
properties: {
domains: {
serviceDomains: {
type: Array,
value: [],
bindNuclear: [
serviceGetters.entityMap,
function(map) {
return map.valueSeq()
.sortBy(function(domain) { return domain.domain; })
.toJS();
},
services: {
type: Object,
],
},
},
@ -56,15 +61,10 @@
return this.services.get(domain).toArray();
},
serviceStoreChanged: function(serviceStore) {
this.services = serviceStore.all;
this.domains = this.services.keySeq().sort().toArray();
},
serviceClicked: function(ev) {
ev.preventDefault();
this.fire(
'service-selected', {domain: ev.model.domain, service: ev.model.service});
'service-selected', {domain: ev.model.domain.domain, service: ev.model.service});
},
});
})();

View file

@ -1,5 +1,7 @@
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="../resources/lodash.html">
<script>
(function() {
Polymer({

View file

@ -39,12 +39,11 @@
if (!this.isAttached) {
return;
}
var root = Polymer.dom(this);
var stateHistory = this.data;
while (root.lastChild) {
root.removeChild(root.lastChild);
while (root.node.lastChild) {
root.node.removeChild(root.node.lastChild);
}
if (!stateHistory || stateHistory.length === 0) {
@ -64,11 +63,12 @@
dataTable.addRow([entityDisplay, stateStr, start, end]);
};
var startTime = new Date(stateHistory.map(function(stateInfo) {
return stateInfo[0].lastChangedAsDate;
}).reduce(function(prev, cur) {
return Math.min(prev, cur);
}, new Date()));
var startTime = new Date(
stateHistory.reduce(function(minTime, stateInfo) {
return Math.min(
minTime, stateInfo[0].lastChangedAsDate);
}, new Date())
);
// end time is Math.min(curTime, start time + 1 day)
var endTime = new Date(startTime);

View file

@ -34,7 +34,8 @@
No state history found.
</template>
<state-history-chart-timeline data='[[groupedStateHistory.timeline]]'
<state-history-chart-timeline
data='[[groupedStateHistory.timeline]]'
is-single-device='[[isSingleDevice]]'>
</state-history-chart-timeline>
@ -84,36 +85,35 @@
},
computeIsSingleDevice: function(stateHistory) {
return stateHistory && stateHistory.length == 1;
return stateHistory && stateHistory.size == 1;
},
computeGroupedStateHistory: function(isLoading, stateHistory) {
if (isLoading || !stateHistory) {
return {line: [], timeline: []};
}
var lineChartDevices = {};
var timelineDevices = [];
if (isLoading || !stateHistory) {
return {line: unitStates, timeline: timelineDevices};
}
stateHistory.forEach(function(stateInfo) {
if (!stateInfo || stateInfo.length === 0) {
if (!stateInfo || stateInfo.size === 0) {
return;
}
var unit;
var stateWithUnit = stateInfo.find(function(state) {
return 'unit_of_measurement' in state.attributes;
});
for (var i = 0; i < stateInfo.length && !unit; i++) {
unit = stateInfo[i].attributes.unit_of_measurement;
}
var unit = stateWithUnit ?
stateWithUnit.attributes.unit_of_measurement : false;
if (unit) {
if (!(unit in lineChartDevices)) {
lineChartDevices[unit] = [stateInfo];
if (!unit) {
timelineDevices.push(stateInfo.toArray());
} else if(unit in lineChartDevices) {
lineChartDevices[unit].push(stateInfo.toArray());
} else {
lineChartDevices[unit].push(stateInfo);
}
} else {
timelineDevices.push(stateInfo);
lineChartDevices[unit] = [stateInfo.toArray()];
}
});
@ -143,7 +143,7 @@
},
computeIsEmpty: function(stateHistory) {
return stateHistory && stateHistory.length === 0;
return stateHistory && stateHistory.size === 0;
},
extractUnit: function(arr) {

View file

@ -17,42 +17,37 @@
}
</style>
<template>
<iron-icon icon="warning" hidden$="{{!hasError}}"></iron-icon>
<paper-toggle-button id="toggle" on-change='toggleChanged' hidden$="{{hasError}}"></paper-toggle-button>
<iron-icon icon="warning" hidden$="[[!hasError]]"></iron-icon>
<paper-toggle-button id="toggle" on-change='toggleChanged' checked$='[[isStreaming]]' hidden$="[[hasError]]"></paper-toggle-button>
</template>
</dom-module>
<script>
var streamGetters = window.hass.streamGetters;
var streamActions = window.hass.streamActions;
var authStore = window.hass.authStore;
Polymer({
is: 'stream-status',
behaviors: [StoreListenerBehavior],
behaviors: [nuclearObserver],
properties: {
isStreaming: {
type: Boolean,
value: false,
bindNuclear: streamGetters.isStreamingEvents,
},
hasError: {
type: Boolean,
value: false,
bindNuclear: streamGetters.hasStreamingEventsError,
},
},
streamStoreChanged: function(streamStore) {
this.hasError = streamStore.hasError;
this.$.toggle.checked = this.isStreaming = streamStore.isStreaming;
},
toggleChanged: function(ev) {
toggleChanged: function() {
if (this.isStreaming) {
streamActions.stop();
} else {
streamActions.start(authStore.authToken);
streamActions.start();
}
},
});

View file

@ -49,36 +49,51 @@
<script>
(function() {
var stateStore = window.hass.stateStore;
var stateHistoryStore = window.hass.stateHistoryStore;
var stateHistoryActions = window.hass.stateHistoryActions;
var configGetters = window.hass.configGetters;
var entityHistoryGetters = window.hass.entityHistoryGetters;
var entityHistoryActions = window.hass.entityHistoryActions;
var moreInfoGetters = window.hass.moreInfoGetters;
var moreInfoActions = window.hass.moreInfoActions;
Polymer({
is: 'more-info-dialog',
behaviors: [StoreListenerBehavior],
behaviors: [nuclearObserver],
properties: {
entityId: {
type: String,
},
stateObj: {
type: Object,
bindNuclear: moreInfoGetters.currentEntity,
observer: 'fetchHistoryData',
},
stateHistory: {
type: Object,
bindNuclear: [
moreInfoGetters.currentEntityHistory,
function(history) {
return history ? [history] : false;
},
],
},
isLoadingHistoryData: {
type: Boolean,
value: false,
bindNuclear: entityHistoryGetters.isLoadingEntityHistory,
},
hasHistoryComponent: {
type: Boolean,
value: false,
bindNuclear: configGetters.isComponentLoaded('history'),
observer: 'fetchHistoryData',
},
shouldFetchHistory: {
type: Boolean,
bindNuclear: moreInfoGetters.isCurrentEntityHistoryStale,
observer: 'fetchHistoryData',
},
dialogOpen: {
@ -92,31 +107,10 @@
'iron-overlay-closed': 'onIronOverlayClosed'
},
componentStoreChanged: function(componentStore) {
this.hasHistoryComponent = componentStore.isLoaded('history');
},
stateStoreChanged: function() {
var newState = this.entityId ? stateStore.get(this.entityId) : null;
if (newState !== this.stateObj) {
this.stateObj = newState;
}
},
stateHistoryStoreChanged: function() {
var newHistory;
if (this.hasHistoryComponent && this.entityId) {
newHistory = [stateHistoryStore.get(this.entityId)];
} else {
newHistory = null;
}
this.isLoadingHistoryData = false;
if (newHistory !== this.stateHistory) {
this.stateHistory = newHistory;
fetchHistoryData: function(newVal) {
if (this.stateObj && this.hasHistoryComponent &&
this.shouldFetchHistory) {
entityHistoryActions.fetchRecent(this.stateObj.entityId);
}
},
@ -126,30 +120,13 @@
onIronOverlayClosed: function() {
this.dialogOpen = false;
moreInfoActions.deselectEntity();
},
changeEntityId: function(entityId) {
if (entityId == this.entityId) {
return;
}
this.entityId = entityId;
this.stateStoreChanged();
this.stateHistoryStoreChanged();
if (this.hasHistoryComponent && stateHistoryStore.shouldFetchEntity(entityId)) {
this.isLoadingHistoryData = true;
stateHistoryActions.fetch(entityId);
}
},
show: function(entityId) {
this.changeEntityId(entityId);
show: function() {
this.debounce('showDialogAfterRender', function() {
this.$.dialog.toggle();
}.bind(this));
this.$.dialog.open();
}.bind(this), 1);
},
});
})();

@ -1 +1 @@
Subproject commit 5a7165b272fe2ed3e1b1432e2e621c3b971cc4bf
Subproject commit 53941ad076fa5d453370cb6922cf6770202dc76e

View file

@ -21,9 +21,8 @@
}
</style>
<home-assistant-icons></home-assistant-icons>
<template>
<home-assistant-icons></home-assistant-icons>
<template is='dom-if' if='[[!loaded]]'>
<login-form></login-form>
</template>
@ -37,10 +36,10 @@
<script>
(function() {
var storeListenerMixIn = window.hass.storeListenerMixIn,
uiActions = window.hass.uiActions,
preferenceStore = window.hass.preferenceStore;
var uiActions = window.hass.uiActions;
var authGetters = window.hass.authGetters;
var syncGetters = window.hass.syncGetters;
var uiPreferences = window.hass.uiPreferences;
Polymer({
is: 'home-assistant',
@ -49,15 +48,24 @@
auth: null,
},
behaviors: [StoreListenerBehavior],
behaviors: [nuclearObserver],
properties: {
auth: {
type: String,
},
loaded: {
type: Boolean,
value: false,
observer: 'loadedChanged',
bindNuclear: syncGetters.isDataLoaded,
},
},
loadedChanged: function(newVal, oldVal) {
console.log("Loaded changed", newVal);
},
ready: function() {
// remove the HTML init message
document.getElementById('init').remove();
@ -65,13 +73,11 @@
// if auth was given, tell the backend
if(this.auth) {
uiActions.validateAuth(this.auth, false);
} else if (preferenceStore.hasAuthToken) {
uiActions.validateAuth(preferenceStore.authToken, false);
} else if (uiPreferences.authToken) {
uiActions.validateAuth(uiPreferences.authToken, false);
}
},
syncStoreChanged: function(syncStore) {
this.loaded = syncStore.initialLoadDone;
uiPreferences.startSync();
},
});

View file

@ -76,7 +76,7 @@
<iron-icon item-icon icon='apps'></iron-icon> States
</paper-icon-item>
<template is='dom-repeat' items='{{activeFilters}}'>
<template is='dom-repeat' items='{{possibleFilters}}'>
<paper-icon-item data-panel$='[[filterType(item)]]'>
<iron-icon item-icon icon='[[filterIcon(item)]]'></iron-icon>
<span>[[filterName(item)]]</span>
@ -110,25 +110,22 @@
<div class='text label divider'>Developer Tools</div>
<div class='dev-tools layout horizontal justified'>
<paper-icon-button
icon='settings-remote' data-panel$='[[selectedDevService]]'
icon='settings-remote' data-panel='devService'
on-click='handleDevClick'></paper-icon-button>
<paper-icon-button
icon='settings-ethernet' data-panel$='[[selectedDevState]]'
icon='settings-ethernet' data-panel='devState'
on-click='handleDevClick'></paper-icon-button>
<paper-icon-button
icon='settings-input-antenna' data-panel$='[[selectedDevEvent]]'
icon='settings-input-antenna' data-panel='devEvent'
on-click='handleDevClick'></paper-icon-button>
</div>
</paper-menu>
</paper-header-panel>
<template is='dom-if' if='[[!hideStates]]'>
<partial-states
main narrow='[[narrow]]'
filter='[[stateFilter]]'>
<template is='dom-if' if='[[isSelectedStates]]'>
<partial-states main narrow='[[narrow]]'>
</partial-states>
</template>
<template is='dom-if' if='[[isSelectedLogbook]]'>
<partial-logbook main narrow='[[narrow]]'></partial-logbook>
</template>
@ -151,117 +148,86 @@
<script>
(function() {
var configGetters = window.hass.configGetters;
var entityGetters = window.hass.entityGetters;
var navigationGetters = window.hass.navigationGetters;
var authActions = window.hass.authActions;
var navigationActions = window.hass.navigationActions;
var uiUtil = window.hass.uiUtil;
var uiConstants = window.hass.uiConstants;
var entityDomainFilters = window.hass.util.entityDomainFilters;
Polymer({
is: 'home-assistant-main',
behaviors: [StoreListenerBehavior],
behaviors: [nuclearObserver],
properties: {
selected: {
type: String,
value: 'states',
},
stateFilter: {
type: String,
value: null,
},
narrow: {
type: Boolean,
},
activeFilters: {
selected: {
type: String,
bindNuclear: [
navigationGetters.activePane,
navigationGetters.activeFilter,
function(pane, filter) {
return filter ? pane + '/' + filter : pane;
},
],
observer: 'selectedChanged',
},
possibleFilters: {
type: Array,
value: [],
bindNuclear: [
navigationGetters.possibleEntityDomainFilters,
function(domains) { return domains.toArray(); }
],
},
hasHistoryComponent: {
type: Boolean,
value: false,
bindNuclear: configGetters.isComponentLoaded('history'),
},
hasLogbookComponent: {
type: Boolean,
value: false,
bindNuclear: configGetters.isComponentLoaded('logbook'),
},
isStreaming: {
isSelectedStates: {
type: Boolean,
value: false,
},
hasStreamError: {
type: Boolean,
value: false,
},
hideStates: {
type: Boolean,
value: false,
},
selectedHistory: {
type: String,
value: 'history',
readOnly: true,
bindNuclear: navigationGetters.isActivePane('states'),
},
isSelectedHistory: {
type: Boolean,
computed: 'computeIsSelected(selected, selectedHistory)',
},
selectedLogbook: {
type: String,
value: 'logbook',
readOnly: true,
bindNuclear: navigationGetters.isActivePane('history'),
},
isSelectedLogbook: {
type: Boolean,
computed: 'computeIsSelected(selected, selectedLogbook)',
},
selectedDevEvent: {
type: String,
value: 'devEvent',
readOnly: true,
bindNuclear: navigationGetters.isActivePane('logbook'),
},
isSelectedDevEvent: {
type: Boolean,
computed: 'computeIsSelected(selected, selectedDevEvent)',
},
selectedDevState: {
type: String,
value: 'devState',
readOnly: true,
bindNuclear: navigationGetters.isActivePane('devEvent'),
},
isSelectedDevState: {
type: Boolean,
computed: 'computeIsSelected(selected, selectedDevState)',
},
selectedDevService: {
type: String,
value: 'devService',
readOnly: true,
bindNuclear: navigationGetters.isActivePane('devState'),
},
isSelectedDevService: {
type: Boolean,
computed: 'computeIsSelected(selected, selectedDevService)',
bindNuclear: navigationGetters.isActivePane('devService'),
},
},
listeners: {
@ -269,17 +235,6 @@
'open-menu': 'openDrawer',
},
stateStoreChanged: function(stateStore) {
this.activeFilters = stateStore.domains.filter(function(domain) {
return domain in uiConstants.STATE_FILTERS;
}).toArray();
},
componentStoreChanged: function(componentStore) {
this.hasHistoryComponent = componentStore.isLoaded('history');
this.hasLogbookComponent = componentStore.isLoaded('logbook');
},
menuSelect: function(ev, detail, sender) {
this.selectPanel(this.$.menu.selected);
},
@ -299,15 +254,9 @@
}
this.closeDrawer();
this.selected = newChoice;
if (newChoice.substr(0, 7) === 'states_') {
this.hideStates = false;
this.stateFilter = newChoice.substr(7);
} else {
this.hideStates = newChoice !== 'states';
this.stateFilter = null;
}
navigationActions.navigate.apply(
null, newChoice.split('/'));
},
openDrawer: function() {
@ -322,21 +271,41 @@
authActions.logOut();
},
computeIsSelected: function(selected, selectedType) {
return selected === selectedType;
},
filterIcon: function(filter) {
return uiUtil.domainIcon(filter);
},
filterName: function(filter) {
return uiConstants.STATE_FILTERS[filter];
return entityDomainFilters[filter];
},
filterType: function(filter) {
return 'states_' + filter;
return 'states/' + filter;
},
hashChanged: function(ev) {
var parts = ev.newURL.split('#');
if (parts[1]) {
this.selectPanel(parts[1]);
}
},
selectedChanged: function(newVal) {
window.location.hash = newVal;
},
ready: function() {
this.hashChanged({newURL: window.location.toString()});
},
attached: function() {
this.hashChanged = this.hashChanged.bind(this);
window.addEventListener('hashchange', this.hashChanged);
},
detached: function() {
window.removeEventListener('hashchange', this.hashChanged);
},
});
})();
</script>

View file

@ -86,27 +86,29 @@
<script>
(function() {
var uiActions = window.hass.uiActions;
var authGetters = window.hass.authGetters;
Polymer({
is: 'login-form',
behaviors: [StoreListenerBehavior],
behaviors: [nuclearObserver],
properties: {
isValidating: {
type: Boolean,
value: false,
observer: 'isValidatingChanged',
bindNuclear: authGetters.isValidating,
},
isInvalid: {
type: Boolean,
value: false,
bindNuclear: authGetters.isInvalidAttempt,
},
errorMessage: {
type: String,
value: '',
}
bindNuclear: authGetters.attemptErrorMessage,
},
},
listeners: {
@ -114,19 +116,12 @@
'loginButton.click': 'validatePassword',
},
attached: function() {
this.focusPassword();
},
// attached: function() {
// this.focusPassword();
// },
authStoreChanged: function(authStore) {
this.isValidating = authStore.isValidating;
if (authStore.lastAttemptInvalid) {
this.errorMessage = authStore.lastAttemptMessage;
this.isInvalid = true;
}
if (!this.isValidating) {
isValidatingChanged: function(newVal) {
if (!newVal) {
setTimeout(this.focusPassword.bind(this), 0);
}
},

View file

@ -80,7 +80,7 @@
return;
}
eventActions.fire(this.eventType, eventData);
eventActions.fireEvent(this.eventType, eventData);
},
computeFormClasses: function(narrow) {

View file

@ -50,8 +50,9 @@
<script>
(function() {
var stateStore = window.hass.stateStore;
var stateActions = window.hass.stateActions;
var reactor = window.hass.reactor;
var entityGetters = window.hass.entityGetters;
var entityActions = window.hass.entityActions;
Polymer({
is: 'partial-dev-set-state',
@ -83,7 +84,7 @@
},
entitySelected: function(ev) {
var state = stateStore.get(ev.detail.entityId);
var state = reactor.evaluate(entityGetters.byId(ev.detail.entityId));
this.entityId = state.entityId;
this.state = state.state;
@ -99,7 +100,11 @@
return;
}
stateActions.set(this.entityId, this.state, attr);
entityActions.save({
entityId: this.entityId,
state: this.state,
attributes: attr,
});
},
computeFormClasses: function(narrow) {

View file

@ -33,7 +33,7 @@
<div class$="[[computeContentClasses(narrow)]]">
<paper-input label='Showing entries for' on-click='handleShowDatePicker'
value='[[computeDateCaption(selectedDate)]]'></paper-input>
value='[[selectedDate]]'></paper-input>
<state-history-charts state-history="[[stateHistory]]"
is-loading-data="[[isLoadingData]]"></state-history-charts>
@ -43,68 +43,57 @@
</dom-module>
<script>
(function() {
var stateHistoryActions = window.hass.stateHistoryActions;
var stateHistoryStore = window.hass.stateHistoryStore;
var entityHistoryGetters = window.hass.entityHistoryGetters;
var entityHistoryActions = window.hass.entityHistoryActions;
var uiActions = window.hass.uiActions;
function date_to_str(date) {
return date.getFullYear() + '-' + (date.getMonth()+1) + '-' + date.getDate();
}
Polymer({
is: 'partial-history',
behaviors: [StoreListenerBehavior],
behaviors: [nuclearObserver],
properties: {
narrow: {
type: Boolean,
},
isDataLoaded: {
type: Boolean,
bindNuclear: entityHistoryGetters.hasDataForCurrentDate,
observer: 'isDataLoadedChanged',
},
stateHistory: {
type: Object,
bindNuclear: entityHistoryGetters.entityHistoryForCurrentDate,
},
isLoadingData: {
type: Boolean,
value: false,
bindNuclear: entityHistoryGetters.isLoadingEntityHistory,
},
selectedDate: {
type: String,
value: null,
observer: 'fetchIfNeeded',
bindNuclear: entityHistoryGetters.currentDate,
},
},
stateHistoryStoreChanged: function() {
this.isLoadingData = this.fetchIfNeeded();
this.stateHistory = this.isLoadingData ?
[] : stateHistoryStore.all(this.selectedDate);
},
computeDateCaption: function(selectedDate) {
return selectedDate || 'today';
},
fetchIfNeeded: function() {
if (stateHistoryStore.shouldFetch(this.selectedDate)) {
this.isLoadingData = true;
stateHistoryActions.fetchAll(this.selectedDate);
return true;
isDataLoadedChanged: function(newVal) {
if (!newVal) {
entityHistoryActions.fetchSelectedDate();
}
return false;
},
handleRefreshClick: function() {
this.isLoadingData = true;
stateHistoryActions.fetchAll(this.selectedDate);
entityHistoryActions.fetchSelectedDate();
},
handleShowDatePicker: function() {
uiActions.showDatePicker(function(selectedDate) {
this.selectedDate = date_to_str(selectedDate);
}.bind(this), this.selectedDate);
uiActions.showDatePicker(
entityHistoryActions.changeCurrentDate,
this.selectedDate);
},
computeContentClasses: function(narrow) {

View file

@ -28,7 +28,7 @@
<div>
<div class='selected-date-container'>
<paper-input label='Showing entries for' on-click='handleShowDatePicker'
value='[[computeDateCaption(selectedDate)]]'></paper-input>
value='[[selectedDate]]'></paper-input>
<loading-box hidden$='[[!isLoading]]'>Loading logbook entries</loading-box>
</div>
@ -40,19 +40,15 @@
<script>
(function() {
var storeListenerMixIn = window.hass.storeListenerMixIn;
var logbookGetters = window.hass.logbookGetters;
var logbookActions = window.hass.logbookActions;
var logbookStore = window.hass.logbookStore;
var uiActions = window.hass.uiActions;
function date_to_str(date) {
return date.getFullYear() + '-' + (date.getMonth()+1) + '-' + date.getDate();
}
var dateToStr = window.hass.util.dateToStr;
Polymer({
is: 'partial-logbook',
behaviors: [StoreListenerBehavior],
behaviors: [nuclearObserver],
properties: {
narrow: {
@ -62,48 +58,46 @@
selectedDate: {
type: String,
value: null,
observer: 'fetchIfNeeded',
bindNuclear: logbookGetters.currentDate,
},
isLoading: {
type: Boolean,
value: true,
bindNuclear: logbookGetters.isLoadingEntries,
},
isStale: {
type: Boolean,
bindNuclear: logbookGetters.isCurrentStale,
observer: 'isStaleChanged',
},
entries: {
type: Array,
value: null,
bindNuclear: [
logbookGetters.currentEntries,
function(entries) { return entries.toArray(); },
],
},
},
logbookStoreChanged: function() {
this.isLoading = this.fetchIfNeeded();
var entries = logbookStore.all.toArray();
this.entries = entries.length > 0 ? entries : false;
},
computeDateCaption: function(selectedDate) {
return selectedDate || 'today';
},
fetchIfNeeded: function() {
if (logbookStore.shouldFetch(this.selectedDate)) {
this.isLoading = true;
logbookActions.fetch(this.selectedDate);
return true;
isStaleChanged: function(newVal) {
if (newVal) {
// isLoading wouldn't update without debounce <_<
this.debounce('fetch-logbook-entries', function() {
logbookActions.fetchDate(this.selectedDate);
}, 0);
}
return false;
},
handleShowDatePicker: function() {
uiActions.showDatePicker(function(selectedDate) {
this.selectedDate = date_to_str(selectedDate);
}.bind(this), this.selectedDate);
uiActions.showDatePicker(
logbookActions.changeCurrentDate,
this.selectedDate);
},
handleRefresh: function() {
logbookActions.fetch(this.selectedDate);
logbookActions.fetchDate(this.selectedDate);
},
});
})();

View file

@ -41,12 +41,17 @@
<template>
<partial-base narrow="[[narrow]]">
<span header-title>{{headerTitle}}</span>
<span header-title>[[computeHeaderTitle(filter)]]</span>
<span header-buttons>
<paper-icon-button icon="refresh" class$="[[computeRefreshButtonClass(isFetching)]]"
on-click="handleRefresh" hidden$="[[isStreaming]]"></paper-icon-button>
<paper-icon-button icon="[[listenButtonIcon]]" hidden$={{!canListen}}
<paper-icon-button
icon="refresh"
class$="[[computeRefreshButtonClass(isFetching)]]"
on-click="handleRefresh" hidden$="[[isStreaming]]"
></paper-icon-button>
<paper-icon-button
icon="[[computeListenButtonIcon(isListening)]]"
hidden$='[[!canListen]]'
on-click="handleListenClick"></paper-icon-button>
</span>
@ -75,139 +80,93 @@
<script>
(function(){
var configGetters = window.hass.configGetters;
var entityGetters = window.hass.entityGetters;
var navigationGetters = window.hass.navigationGetters;
var voiceGetters = window.hass.voiceGetters;
var streamGetters = window.hass.streamGetters;
var serviceGetters = window.hass.serviceGetters;
var syncGetters = window.hass.syncGetters;
var syncActions = window.hass.syncActions;
var voiceActions = window.hass.voiceActions;
var stateStore = window.hass.stateStore;
var uiConstants = window.hass.uiConstants;
var entityDomainFilters = window.hass.util.entityDomainFilters;
Polymer({
is: 'partial-states',
behaviors: [StoreListenerBehavior],
behaviors: [nuclearObserver],
properties: {
/**
* Title to show in the header
*/
headerTitle: {
type: String,
value: 'States',
},
/**
* If header is to be shown in narrow mode.
*/
narrow: {
type: Boolean,
value: false,
},
filter: {
type: String,
value: null,
observer: 'filterChanged',
},
voiceSupported: {
type: Boolean,
value: voiceActions.isSupported(),
},
isFetching: {
type: Boolean,
value: false,
bindNuclear: syncGetters.isFetching,
},
isStreaming: {
type: Boolean,
value: false,
bindNuclear: streamGetters.isStreamingEvents,
},
canListen: {
type: Boolean,
value: false,
bindNuclear: [
voiceGetters.isVoiceSupported,
configGetters.isComponentLoaded('conversation'),
function(isVoiceSupported, componentLoaded) {
return isVoiceSupported && componentLoaded;
}
]
},
isListening: {
type: Boolean,
value: false,
bindNuclear: voiceGetters.isListening,
},
isTransmitting: {
type: Boolean,
value: false,
bindNuclear: voiceGetters.isTransmitting,
},
interimTranscript: {
type: String,
value: '',
bindNuclear: voiceGetters.extraInterimTranscript,
},
finalTranscript: {
type: String,
value: '',
},
listenButtonIcon: {
type: String,
computed: 'computeListenButtonIcon(isListening)'
bindNuclear: voiceGetters.finalTranscript,
},
showListenInterface: {
type: Boolean,
computed: 'computeShowListenInterface(isListening,isTransmitting)'
}
bindNuclear: [
voiceGetters.isListening,
voiceGetters.isTransmitting,
function(isListening, isTransmitting) {
return isListening || isTransmitting;
},
],
},
componentStoreChanged: function(componentStore) {
this.canListen = this.voiceSupported &&
componentStore.isLoaded('conversation');
states: {
type: Array,
bindNuclear: [
navigationGetters.filteredStates,
// are here so a change to services causes a re-render.
// we need this to decide if we show toggles for states.
serviceGetters.entityMap,
function(states) { return states.toArray(); },
],
},
stateStoreChanged: function() {
this.refreshStates();
},
syncStoreChanged: function(syncStore) {
this.isFetching = syncStore.isFetching;
},
streamStoreChanged: function(streamStore) {
this.isStreaming = streamStore.isStreaming;
},
voiceStoreChanged: function(voiceStore) {
this.isListening = voiceStore.isListening;
this.isTransmitting = voiceStore.isTransmitting;
this.finalTranscript = voiceStore.finalTranscript;
this.interimTranscript = voiceStore.interimTranscript.slice(
this.finalTranscript.length);
},
filterChanged: function() {
this.refreshStates();
this.headerTitle = uiConstants.STATE_FILTERS[this.filter] || 'States';
},
refreshStates: function() {
var states;
if (this.filter) {
var filter = this.filter;
states = stateStore.all.filter(function(state) {
return state.domain === filter;
});
} else {
// all but the STATE_FILTER keys
states = stateStore.all.filter(function(state) {
return !(state.domain in uiConstants.STATE_FILTERS);
});
}
this.states = states.toArray().filter(
function (el) {return !el.attributes.hidden;});
},
handleRefresh: function() {
@ -222,12 +181,12 @@
}
},
computeListenButtonIcon: function(isListening) {
return isListening ? 'av:mic-off' : 'av:mic';
computeHeaderTitle: function(filter) {
return filter ? entityDomainFilters[filter] : 'States';
},
computeShowListenInterface: function(isListening,isTransmitting) {
return isListening || isTransmitting;
computeListenButtonIcon: function(isListening) {
return isListening ? 'av:mic-off' : 'av:mic';
},
computeRefreshButtonClass: function(isFetching) {

View file

@ -14,33 +14,38 @@
<script>
(function() {
var uiConstants = window.hass.uiConstants,
dispatcher = window.hass.dispatcher;
var moreInfoGetters = window.hass.moreInfoGetters;
var uiActions = window.hass.uiActions;
Polymer({
is: 'modal-manager',
behaviors: [nuclearObserver],
properties: {
moreInfoEntityId: {
type: String,
observer: 'moreInfoEntityIdChanged',
bindNuclear: moreInfoGetters.currentEntityId,
},
datePickerCallback: {
type: Function,
value: null,
},
},
ready: function() {
dispatcher.register(function(payload) {
switch (payload.actionType) {
case uiConstants.ACTION_SHOW_DIALOG_MORE_INFO:
this.$.moreInfoDialog.show(payload.entityId);
break;
case uiConstants.ACTION_SHOW_DATE_PICKER:
this.datePickerCallback = payload.dateSelectedCallback;
this.$.date = payload.date;
this.$.datePicker.toggle();
break;
moreInfoEntityIdChanged: function(newVal) {
if (newVal) {
this.$.moreInfoDialog.show(newVal);
}
}.bind(this));
},
ready: function() {
uiActions.showDatePicker = function(dateSelectedCallback, startDate) {
this.datePickerCallback = dateSelectedCallback;
this.$.date = startDate;
this.$.datePicker.toggle();
}.bind(this);
},
datePickerValueChanged: function(ev) {

View file

@ -15,33 +15,26 @@
<script>
(function() {
var notificationGetters = window.hass.notificationGetters;
Polymer({
is: 'notification-manager',
behaviors: [StoreListenerBehavior],
behaviors: [nuclearObserver],
properties: {
text: {
type: String,
value: '',
},
lastId: {
type: Number,
bindNuclear: notificationGetters.lastNotificationMessage,
observer: 'showNotification',
},
},
notificationStoreChanged: function(notificationStore) {
if (notificationStore.hasNewNotifications(this.lastId)) {
var notification = notificationStore.lastNotification;
this.lastId = notification.id;
this.text = notification.message;
showNotification: function(newText) {
if (newText) {
this.$.toast.show();
}
},
}
});
})();
</script>

View file

@ -0,0 +1,40 @@
<link rel="import" href="../bower_components/polymer/polymer.html">
<script>
(function() {
var authGetters = window.hass.authGetters;
var streamGetters = window.hass.streamGetters;
Polymer({
is: 'preferences-manager',
behaviors: [nuclearObserver],
properties: {
authToken: {
type: String,
bindNuclear: authGetters.currentAuthToken,
observer: 'updateStorage',
},
useStreaming: {
type: String,
bindNuclear: ,
observer: 'updateStorage',
},
},
updateStorage: function() {
if (!('localStorage' in window)) {
return;
}
var storage = localStorage;
Object.keys(this.properties).forEach(function(prop) {
storage[prop] = this.prop;
});
},
});
})();
</script>

View file

@ -52,13 +52,14 @@
<script>
(function() {
var streamGetters = window.hass.streamGetters;
var syncActions = window.hass.syncActions;
var serviceActions = window.hass.serviceActions;
Polymer({
is: 'more-info-configurator',
behaviors: [StoreListenerBehavior],
behaviors: [nuclearObserver],
properties: {
stateObj: {
@ -72,7 +73,7 @@
isStreaming: {
type: Boolean,
value: false,
bindNuclear: streamGetters.isStreamingEvents,
},
isConfigurable: {
@ -99,10 +100,6 @@
return stateObj.attributes.submit_caption || 'Set configuration';
},
streamStoreChanged: function(streamStore) {
this.isStreaming = streamStore.isStreaming;
},
submitClicked: function() {
this.isConfiguring = true;

View file

@ -23,28 +23,36 @@
<script>
(function() {
var stateStore = window.hass.stateStore;
var entityGetters = window.hass.entityGetters;
var moreInfoGetters = window.hass.moreInfoGetters;
Polymer({
is: 'more-info-group',
behaviors: [StoreListenerBehavior],
behaviors: [nuclearObserver],
properties: {
stateObj: {
type: Object,
observer: 'updateStates',
},
states: {
type: Array,
value: [],
bindNuclear: [
moreInfoGetters.currentEntity,
entityGetters.entityMap,
function(currentEntity, entities) {
// weird bug??
if (!currentEntity) {
return;
}
return currentEntity.attributes.entity_id.map(
entities.get.bind(entities));
},
],
},
},
stateStoreChanged: function() {
this.updateStates();
},
updateStates: function() {
this.states = this.stateObj && this.stateObj.attributes.entity_id ?

View file

@ -41,7 +41,7 @@
<script>
(function() {
var constants = window.hass.constants;
var temperatureUnits = window.hass.util.temperatureUnits;
var serviceActions = window.hass.serviceActions;
var uiUtil = window.hass.uiUtil;
var ATTRIBUTE_CLASSES = ['away_mode'];
@ -76,7 +76,8 @@
this.targetTemperatureSliderValue = this.stateObj.state;
this.awayToggleChecked = this.stateObj.attributes.away_mode == 'on';
if (this.stateObj.attributes.unit_of_measurement === constants.UNIT_TEMP_F) {
if (this.stateObj.attributes.unit_of_measurement ===
temperatureUnits.UNIT_TEMP_F) {
this.tempMin = 45;
this.tempMax = 95;
} else {

View file

@ -0,0 +1,51 @@
<script>
(function() {
var reactor = window.hass.reactor;
var authGetters = window.hass.authGetters;
var streamGetters = window.hass.streamGetters;
var storage = 'localStorage' in window ? localStorage : {};
var observe = {
authToken: {
bindNuclear: [
authGetters.currentAuthToken,
authGetters.rememberAuth,
function(authToken, rememberAuth) {
return rememberAuth ? authToken : null;
},
],
defaultValue: null,
},
useStreaming: {
bindNuclear: streamGetters.useStreaming,
defaultValue: true,
},
};
var uiPreferences = {};
Object.keys(observe).forEach(function(prop) {
if (!(prop in storage)) {
storage[prop] = observe[prop].defaultValue;
}
Object.defineProperty(uiPreferences, prop, {
get: function() { return JSON.parse(storage[prop]); }
});
});
uiPreferences.startSync = function startSync() {
Object.keys(observe).forEach(function(prop) {
var getter = observe[prop].bindNuclear;
var valueChanged = function valueChanged(value) {
storage[prop] = JSON.stringify(value);
};
reactor.observe(getter, valueChanged);
valueChanged(reactor.evaluate(getter));
});
};
window.hass.uiPreferences = uiPreferences;
})();
</script>

View file

@ -1,4 +1,5 @@
<script src="../home-assistant-js/dist/homeassistant.min.js"></script>
<link rel="import" href="./ha-preferences.html">
<script>
(function() {
@ -10,63 +11,16 @@
'sensor',
];
// Add some frontend specific helpers to the models
Object.defineProperties(window.hass.stateModel.prototype, {
// how to render the card for this state
cardType: {
get: function() {
console.warn('Deprecated method. Please use hass.uiUtil.stateCardType');
return window.hass.uiUtil.stateCardType(this);
}
},
// how to render the more info of this state
moreInfoType: {
get: function() {
console.warn('Deprecated method. Please use hass.uiUtil.stateMoreInfoType');
return window.hass.uiUtil.stateMoreInfoType(this);
}
},
});
var dispatcher = window.hass.dispatcher,
constants = window.hass.constants,
preferenceStore = window.hass.preferenceStore,
authActions = window.hass.authActions;
window.hass.uiConstants = {
ACTION_SHOW_DIALOG_MORE_INFO: 'ACTION_SHOW_DIALOG_MORE_INFO',
ACTION_SHOW_DATE_PICKER: 'ACTION_SHOW_DATE_PICKER',
STATE_FILTERS: {
'group': 'Groups',
'script': 'Scripts',
'scene': 'Scenes',
},
};
var reactor = window.hass.reactor;
var serviceGetters = window.hass.serviceGetters;
var authActions = window.hass.authActions;
var uiPreferences = window.hass.uiPreferences;
window.hass.uiActions = {
showMoreInfoDialog: function(entityId) {
dispatcher.dispatch({
actionType: window.hass.uiConstants.ACTION_SHOW_DIALOG_MORE_INFO,
entityId: entityId,
});
},
showDatePicker: function(dateSelectedCallback, startDate) {
startDate = startDate || null;
dispatcher.dispatch({
actionType: window.hass.uiConstants.ACTION_SHOW_DATE_PICKER,
dateSelectedCallback: dateSelectedCallback,
startDate: startDate,
});
},
validateAuth: function(authToken, rememberLogin) {
validateAuth: function(authToken, rememberAuth) {
authActions.validate(authToken, {
useStreaming: preferenceStore.useStreaming,
rememberLogin: rememberLogin,
useStreaming: uiPreferences.useStreaming,
rememberAuth: rememberAuth,
});
},
};
@ -76,7 +30,7 @@
stateCardType: function(state) {
if(DOMAINS_WITH_CARD.indexOf(state.domain) !== -1) {
return state.domain;
} else if(state.canToggle) {
} else if(reactor.evaluate(serviceGetters.canToggle(state.entityId))) {
return "toggle";
} else {
return "display";

View file

@ -0,0 +1,5 @@
<!--
Wrapping JS in an HTML file will prevent it from being loaded twice.
-->
<script src="../bower_components/lodash/lodash.min.js"></script>

View file

@ -1,21 +1,42 @@
<script>
(function() {
var StoreListenerMixIn = window.hass.storeListenerMixIn;
window.StoreListenerBehavior = {
var NuclearObserver = function NuclearObserver(reactor) {
return {
attached: function() {
StoreListenerMixIn.listenToStores(true, this);
var component = this;
this.__unwatchFns = Object.keys(component.properties).reduce(
function(unwatchFns, key) {
if (!('bindNuclear' in component.properties[key])) {
return unwatchFns;
}
var getter = component.properties[key].bindNuclear;
if (!getter) {
throw 'Undefined getter specified for key ' + key;
}
// console.log(key, getter);
component[key] = reactor.evaluate(getter);
return unwatchFns.concat(reactor.observe(getter, function(val) {
// console.log('New value for', key, val);
component[key] = val;
}));
}, []);
},
detached: function() {
StoreListenerMixIn.stopListeningToStores(this);
while (this.__unwatchFns.length) {
this.__unwatchFns.shift()();
}
},
};
};
window.nuclearObserver = NuclearObserver(window.hass.reactor);
})();
</script>

File diff suppressed because one or more lines are too long