Frontend now build on top of home-assistant-js
This commit is contained in:
parent
fbae2ef725
commit
115be859b6
36 changed files with 467 additions and 829 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -10,3 +10,6 @@
|
|||
[submodule "homeassistant/external/noop"]
|
||||
path = homeassistant/external/noop
|
||||
url = https://github.com/balloob/noop.git
|
||||
[submodule "homeassistant/components/frontend/www_static/polymer/home-assistant-js"]
|
||||
path = homeassistant/components/frontend/www_static/polymer/home-assistant-js
|
||||
url = https://github.com/balloob/home-assistant-js.git
|
||||
|
|
|
@ -58,7 +58,7 @@ def _handle_get_root(handler, path_match, data):
|
|||
handler.end_headers()
|
||||
|
||||
if handler.server.development:
|
||||
app_url = "polymer/splash-login.html"
|
||||
app_url = "polymer/home-assistant.html"
|
||||
else:
|
||||
app_url = "frontend-{}.html".format(version.VERSION)
|
||||
|
||||
|
@ -83,7 +83,7 @@ def _handle_get_root(handler, path_match, data):
|
|||
"<script"
|
||||
" src='/static/webcomponents.min.js'></script>"
|
||||
"<link rel='import' href='/static/{}' />"
|
||||
"<splash-login auth='{}'></splash-login>"
|
||||
"<home-assistant auth='{}'></home-assistant>"
|
||||
"</body></html>").format(app_url, auth))
|
||||
|
||||
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
||||
VERSION = "954620894f13782f17ae7443f0df4ffc"
|
||||
VERSION = "dae175210dab23f9c383a593d8e97d9b"
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -34,7 +34,6 @@
|
|||
"paper-dropdown": "polymer/paper-dropdown#~0.5.4",
|
||||
"paper-item": "polymer/paper-item#~0.5.4",
|
||||
"paper-slider": "polymer/paper-slider#~0.5.4",
|
||||
"moment": "~2.8.4",
|
||||
"color-picker-element": "~0.0.2",
|
||||
"google-apis": "GoogleWebComponents/google-apis#~0.4.2"
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
<script src="../bower_components/moment/moment.js"></script>
|
||||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<link rel="import" href="../components/state-info.html">
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<link rel="import" href="state-card-thermostat.html">
|
||||
<link rel="import" href="state-card-configurator.html">
|
||||
|
||||
<polymer-element name="state-card-content" attributes="api stateObj">
|
||||
<polymer-element name="state-card-content" attributes="stateObj">
|
||||
<template>
|
||||
<style>
|
||||
:host {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
<script src="../bower_components/moment/moment.js"></script>
|
||||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<link rel="import" href="../components/state-info.html">
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
<script src="../bower_components/moment/moment.js"></script>
|
||||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<link rel="import" href="../components/state-info.html">
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
<script src="../bower_components/moment/moment.js"></script>
|
||||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../bower_components/paper-toggle-button/paper-toggle-button.html">
|
||||
|
||||
|
@ -38,6 +37,10 @@
|
|||
'stateObj.state': 'stateChanged'
|
||||
},
|
||||
|
||||
ready: function() {
|
||||
this.forceStateChange = this.forceStateChange.bind(this);
|
||||
},
|
||||
|
||||
// prevent the event from propegating
|
||||
toggleClicked: function(ev) {
|
||||
ev.stopPropagation();
|
||||
|
@ -60,16 +63,17 @@
|
|||
this.toggleChecked = newVal === "on";
|
||||
},
|
||||
|
||||
forceStateChange: function() {
|
||||
this.stateChanged(this.stateObj.state, this.stateObj.state);
|
||||
},
|
||||
|
||||
turn_on: function() {
|
||||
// We call stateChanged after a successful call to re-sync the toggle
|
||||
// with the state. It will be out of sync if our service call did not
|
||||
// result in the entity to be turned on. Since the state is not changing,
|
||||
// the resync is not called automatic.
|
||||
this.api.turn_on(this.stateObj.entity_id, {
|
||||
success: function() {
|
||||
this.stateChanged(this.stateObj.state, this.stateObj.state);
|
||||
}.bind(this)
|
||||
});
|
||||
window.hass.serviceActions.callTurnOn(this.stateObj.entity_id)
|
||||
.then(this.forceStateChange);
|
||||
},
|
||||
|
||||
turn_off: function() {
|
||||
|
@ -77,11 +81,8 @@
|
|||
// with the state. It will be out of sync if our service call did not
|
||||
// result in the entity to be turned on. Since the state is not changing,
|
||||
// the resync is not called automatic.
|
||||
this.api.turn_off(this.stateObj.entity_id, {
|
||||
success: function() {
|
||||
this.stateChanged(this.stateObj.state, this.stateObj.state);
|
||||
}.bind(this)
|
||||
});
|
||||
window.hass.serviceActions.callTurnOff(this.stateObj.entity_id)
|
||||
.then(this.forceStateChange);
|
||||
},
|
||||
|
||||
});
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
Polymer({
|
||||
|
||||
cardClicked: function() {
|
||||
this.api.showmoreInfoDialog(this.stateObj.entity_id);
|
||||
window.hass.uiActions.showMoreInfoDialog(this.stateObj.entity_id);
|
||||
},
|
||||
|
||||
});
|
||||
|
|
|
@ -35,13 +35,18 @@
|
|||
cbEventClicked: null,
|
||||
states: [],
|
||||
|
||||
domReady: function() {
|
||||
this.api.addEventListener('states-updated', this.statesUpdated.bind(this))
|
||||
this.statesUpdated()
|
||||
ready: function() {
|
||||
this.statesUpdated = this.statesUpdated.bind(this);
|
||||
window.hass.stateStore.addChangeListener(this.statesUpdated);
|
||||
this.statesUpdated();
|
||||
},
|
||||
|
||||
detached: function() {
|
||||
window.hass.stateStore.removeChangeListener(this.statesUpdated);
|
||||
},
|
||||
|
||||
statesUpdated: function() {
|
||||
this.states = this.api.states;
|
||||
this.states = window.hass.stateStore.all();
|
||||
},
|
||||
|
||||
handleClick: function(ev) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<polymer-element name="events-list" attributes="api cbEventClicked">
|
||||
<polymer-element name="events-list" attributes="cbEventClicked">
|
||||
<template>
|
||||
<style>
|
||||
:host {
|
||||
|
@ -37,14 +37,18 @@
|
|||
cbEventClicked: null,
|
||||
events: [],
|
||||
|
||||
domReady: function() {
|
||||
this.events = this.api.events
|
||||
ready: function() {
|
||||
this.eventsUpdated = this.eventsUpdated.bind(this);
|
||||
window.hass.eventStore.addChangeListener(this.eventsUpdated);
|
||||
this.eventsUpdated();
|
||||
},
|
||||
|
||||
this.api.addEventListener('events-updated', this.eventsUpdated.bind(this))
|
||||
detached: function() {
|
||||
window.hass.eventStore.removeChangeListener(this.eventsUpdated);
|
||||
},
|
||||
|
||||
eventsUpdated: function() {
|
||||
this.events = this.api.events;
|
||||
this.events = window.hass.eventStore.all();
|
||||
},
|
||||
|
||||
handleClick: function(ev) {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<link rel="import" href="../bower_components/core-style/core-style.html">
|
||||
<link rel="import" href="./loading-box.html">
|
||||
|
||||
<polymer-element name="recent-states" attributes="api stateObj">
|
||||
<polymer-element name="recent-states" attributes="stateObj">
|
||||
<template>
|
||||
<core-style ref='ha-data-table'></core-style>
|
||||
|
||||
|
@ -36,13 +36,12 @@
|
|||
stateObjChanged: function() {
|
||||
this.recentStates = null;
|
||||
|
||||
this.api.call_api('GET', 'history/entity/' + this.stateObj.entity_id + '/recent_states', {}, this.newStates.bind(this));
|
||||
window.hass.callApi(
|
||||
'GET', 'history/entity/' + this.stateObj.entity_id + '/recent_states').then(
|
||||
function(states) {
|
||||
this.recentStates = states.slice(1);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
newStates: function(states) {
|
||||
// cut off the first one (which is the current)
|
||||
this.recentStates = states.slice(1);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</polymer-element>
|
||||
|
|
|
@ -51,18 +51,22 @@
|
|||
services: [],
|
||||
cbServiceClicked: null,
|
||||
|
||||
domReady: function() {
|
||||
this.services = this.api.services
|
||||
ready: function() {
|
||||
this.servicesUpdated = this.servicesUpdated.bind(this);
|
||||
window.hass.serviceStore.addChangeListener(this.servicesUpdated);
|
||||
this.servicesUpdated();
|
||||
},
|
||||
|
||||
this.api.addEventListener('services-updated', this.servicesUpdated.bind(this))
|
||||
detached: function() {
|
||||
window.hass.serviceStore.removeChangeListener(this.servicesUpdated);
|
||||
},
|
||||
|
||||
getIcon: function(domain) {
|
||||
return (new DomainIcon).icon(domain);
|
||||
return (new DomainIcon()).icon(domain);
|
||||
},
|
||||
|
||||
servicesUpdated: function() {
|
||||
this.services = this.api.services;
|
||||
this.services = window.hass.serviceStore.all();
|
||||
},
|
||||
|
||||
serviceClicked: function(ev) {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<link rel="import" href="../cards/state-card.html">
|
||||
|
||||
<polymer-element name="state-cards" attributes="api filter">
|
||||
<polymer-element name="state-cards" attributes="states">
|
||||
<template>
|
||||
<style>
|
||||
:host {
|
||||
|
@ -47,7 +47,7 @@
|
|||
<div horizontal layout wrap>
|
||||
|
||||
<template repeat="{{states as state}}">
|
||||
<state-card class="state-card" stateObj={{state}} api={{api}}></state-card>
|
||||
<state-card class="state-card" stateObj={{state}}></state-card>
|
||||
</template>
|
||||
|
||||
<template if="{{states.length == 0}}">
|
||||
|
@ -60,24 +60,6 @@
|
|||
</template>
|
||||
<script>
|
||||
Polymer({
|
||||
filter: null,
|
||||
states: [],
|
||||
|
||||
observe: {
|
||||
'api.states': 'filterChanged'
|
||||
},
|
||||
|
||||
filterChanged: function() {
|
||||
if(this.filter === 'customgroup') {
|
||||
this.states = this.api.getCustomGroups();
|
||||
} else {
|
||||
// if no filter, return all non-group states
|
||||
this.states = this.api.states.filter(function(state) {
|
||||
return state.domain != 'group';
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
</script>
|
||||
</polymer-element>
|
||||
|
|
|
@ -39,10 +39,6 @@
|
|||
},
|
||||
|
||||
fetchData: function() {
|
||||
if (!this.api) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.stateData = null;
|
||||
|
||||
var url = 'history/period';
|
||||
|
@ -51,11 +47,10 @@
|
|||
url += '?filter_entity_id=' + this.stateObj.entity_id;
|
||||
}
|
||||
|
||||
this.api.call_api('GET', url, {}, function(stateData) {
|
||||
window.hass.callApi('GET', url).then(function(stateData) {
|
||||
this.stateData = stateData;
|
||||
this.drawChart();
|
||||
}.bind(this)
|
||||
);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
drawChart: function() {
|
||||
|
@ -75,7 +70,7 @@
|
|||
var stateTimeToDate = function(time) {
|
||||
if (!time) return new Date();
|
||||
|
||||
return ha.util.parseTime(time).toDate();
|
||||
return window.hass.util.parseTime(time).toDate();
|
||||
};
|
||||
|
||||
var addRow = function(baseState, state, tillState) {
|
||||
|
@ -89,7 +84,7 @@
|
|||
|
||||
// this.stateData is a list of lists of sorted state objects
|
||||
this.stateData.forEach(function(stateInfo) {
|
||||
var baseState = new this.api.State(stateInfo[0], this.api);
|
||||
var baseState = new window.hass.stateModel(stateInfo[0]);
|
||||
|
||||
var prevRow = null;
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ Polymer({
|
|||
},
|
||||
|
||||
setEventData: function(eventData) {
|
||||
this.$.inputData.value = eventData;
|
||||
this.$.inputData.value = eventData || "";
|
||||
// this.$.inputDataWrapper.update();
|
||||
},
|
||||
|
||||
|
@ -75,7 +75,7 @@ Polymer({
|
|||
|
||||
clickFireEvent: function() {
|
||||
try {
|
||||
this.api.fire_event(
|
||||
window.hass.eventActions.fire(
|
||||
this.$.inputType.value,
|
||||
this.$.inputData.value ? JSON.parse(this.$.inputData.value) : {});
|
||||
this.$.dialog.close();
|
||||
|
|
|
@ -16,10 +16,10 @@
|
|||
</style>
|
||||
|
||||
<div>
|
||||
<state-card-content stateObj="{{stateObj}}" api="{{api}}" class='title-card'>
|
||||
<state-card-content stateObj="{{stateObj}}" class='title-card'>
|
||||
</state-card-content>
|
||||
<state-timeline stateObj="{{stateObj}}" api="{{api}}"></state-timeline>
|
||||
<more-info-content stateObj="{{stateObj}}" api="{{api}}"></more-info-content>
|
||||
<state-timeline stateObj="{{stateObj}}"></state-timeline>
|
||||
<more-info-content stateObj="{{stateObj}}"></more-info-content>
|
||||
</div>
|
||||
|
||||
<paper-button dismissive on-click={{editClicked}}>Debug</paper-button>
|
||||
|
@ -56,7 +56,7 @@ Polymer({
|
|||
},
|
||||
|
||||
editClicked: function(ev) {
|
||||
this.api.showEditStateDialog(this.stateObj.entity_id);
|
||||
window.hass.uiActions.showSetStateDialog(this.stateObj.entity_id);
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
@ -53,7 +53,7 @@ Polymer({
|
|||
|
||||
show: function(domain, service, serviceData) {
|
||||
this.setService(domain, service);
|
||||
this.$.inputData.value = serviceData;
|
||||
this.$.inputData.value = serviceData || "";
|
||||
// this.$.inputDataWrapper.update();
|
||||
this.job('showDialogAfterRender', function() {
|
||||
this.$.dialog.toggle();
|
||||
|
@ -71,10 +71,10 @@ Polymer({
|
|||
|
||||
clickCallService: function() {
|
||||
try {
|
||||
this.api.call_service(
|
||||
this.$.inputDomain.value,
|
||||
this.$.inputService.value,
|
||||
this.$.inputData.value ? JSON.parse(this.$.inputData.value) : {});
|
||||
window.hass.serviceActions.callService(
|
||||
this.$.inputDomain.value,
|
||||
this.$.inputService.value,
|
||||
this.$.inputData.value ? JSON.parse(this.$.inputData.value) : {});
|
||||
|
||||
this.$.dialog.close();
|
||||
|
||||
|
|
|
@ -82,14 +82,14 @@ Polymer({
|
|||
entitySelected: function(entityId) {
|
||||
this.setEntityId(entityId);
|
||||
|
||||
var state = this.api.getState(entityId);
|
||||
var state = window.hass.stateStore.get(entityId);
|
||||
this.setState(state.state);
|
||||
this.setStateData(state.attributes);
|
||||
},
|
||||
|
||||
clickSetState: function(ev) {
|
||||
try {
|
||||
this.api.set_state(
|
||||
window.hass.stateActions.set(
|
||||
this.$.inputEntityID.value,
|
||||
this.$.inputState.value,
|
||||
this.$.inputData.value ? JSON.parse(this.$.inputData.value) : {}
|
||||
|
|
|
@ -1,508 +1,130 @@
|
|||
<script src="bower_components/moment/moment.js"></script>
|
||||
<link rel="import" href="./bower_components/paper-toast/paper-toast.html">
|
||||
|
||||
<link rel="import" href="bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="bower_components/paper-toast/paper-toast.html">
|
||||
<link rel="import" href="./dialogs/event-fire-dialog.html">
|
||||
<link rel="import" href="./dialogs/service-call-dialog.html">
|
||||
<link rel="import" href="./dialogs/state-set-dialog.html">
|
||||
<link rel="import" href="./dialogs/more-info-dialog.html">
|
||||
<link rel="import" href="./dialogs/history-dialog.html">
|
||||
|
||||
<link rel="import" href="dialogs/event-fire-dialog.html">
|
||||
<link rel="import" href="dialogs/service-call-dialog.html">
|
||||
<link rel="import" href="dialogs/state-set-dialog.html">
|
||||
<link rel="import" href="dialogs/more-info-dialog.html">
|
||||
<link rel="import" href="dialogs/history-dialog.html">
|
||||
<script src="./home-assistant-js/dist/homeassistant.min.js"></script>
|
||||
|
||||
<script>
|
||||
var ha = {};
|
||||
ha.util = {};
|
||||
|
||||
ha.util.parseTime = function(timeString) {
|
||||
return moment(timeString, "HH:mm:ss DD-MM-YYYY");
|
||||
};
|
||||
|
||||
ha.util.relativeTime = function(timeString) {
|
||||
return ha.util.parseTime(timeString).fromNow();
|
||||
};
|
||||
|
||||
// Register some polymer filters
|
||||
PolymerExpressions.prototype.relativeHATime = function(timeString) {
|
||||
return ha.util.relativeTime(timeString);
|
||||
return window.hass.util.relativeTime(timeString);
|
||||
};
|
||||
|
||||
PolymerExpressions.prototype.HATimeStripDate = function(timeString) {
|
||||
return (timeString || "").split(' ')[0];
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<polymer-element name="home-assistant-api" attributes="auth">
|
||||
<template>
|
||||
<paper-toast id="toast" role="alert" text=""></paper-toast>
|
||||
<event-fire-dialog id="eventDialog" api={{api}}></event-fire-dialog>
|
||||
<service-call-dialog id="serviceDialog" api={{api}}></service-call-dialog>
|
||||
<state-set-dialog id="stateSetDialog" api={{api}}></state-set-dialog>
|
||||
<more-info-dialog id="moreInfoDialog" api={{api}}></more-info-dialog>
|
||||
<history-dialog id="historyDialog" api={{api}}></history-dialog>
|
||||
<event-fire-dialog id="eventDialog"></event-fire-dialog>
|
||||
<service-call-dialog id="serviceDialog"></service-call-dialog>
|
||||
<state-set-dialog id="stateSetDialog"></state-set-dialog>
|
||||
<more-info-dialog id="moreInfoDialog"></more-info-dialog>
|
||||
<history-dialog id="historyDialog"></history-dialog>
|
||||
</template>
|
||||
<script>
|
||||
var domainsWithCard = ['thermostat', 'configurator'];
|
||||
var domainsWithMoreInfo = ['light', 'group', 'sun', 'configurator'];
|
||||
|
||||
State = function(json, api) {
|
||||
this.api = api;
|
||||
|
||||
this.attributes = json.attributes;
|
||||
|
||||
this.entity_id = json.entity_id;
|
||||
var parts = json.entity_id.split(".");
|
||||
this.domain = parts[0];
|
||||
this.object_id = parts[1];
|
||||
|
||||
if(this.attributes.friendly_name) {
|
||||
this.entityDisplay = this.attributes.friendly_name;
|
||||
} else {
|
||||
this.entityDisplay = this.object_id.replace(/_/g, " ");
|
||||
}
|
||||
|
||||
this.state = json.state;
|
||||
this.last_changed = json.last_changed;
|
||||
};
|
||||
|
||||
Object.defineProperties(State.prototype, {
|
||||
stateDisplay: {
|
||||
get: function() {
|
||||
var state = this.state.replace(/_/g, " ");
|
||||
if(this.attributes.unit_of_measurement) {
|
||||
return state + " " + this.attributes.unit_of_measurement;
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
isCustomGroup: {
|
||||
get: function() {
|
||||
return this.domain == "group" && !this.attributes.auto;
|
||||
}
|
||||
},
|
||||
|
||||
canToggle: {
|
||||
get: function() {
|
||||
// groups that have the on/off state or if there is a turn_on service
|
||||
return ((this.domain == 'group' &&
|
||||
(this.state == 'on' || this.state == 'off')) ||
|
||||
this.api.hasService(this.domain, 'turn_on'));
|
||||
}
|
||||
},
|
||||
|
||||
// how to render the card for this state
|
||||
cardType: {
|
||||
get: function() {
|
||||
if(domainsWithCard.indexOf(this.domain) !== -1) {
|
||||
return this.domain;
|
||||
} else if(this.canToggle) {
|
||||
return "toggle";
|
||||
} else {
|
||||
return "display";
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// how to render the more info of this state
|
||||
moreInfoType: {
|
||||
get: function() {
|
||||
if(domainsWithMoreInfo.indexOf(this.domain) !== -1) {
|
||||
return this.domain;
|
||||
} else {
|
||||
return 'default';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
relativeLastChanged: {
|
||||
get: function() {
|
||||
return ha.util.relativeTime(this.last_changed);
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
Polymer({
|
||||
auth: "not-set",
|
||||
states: [],
|
||||
services: [],
|
||||
events: [],
|
||||
stateUpdateTimeout: null,
|
||||
ready: function() {
|
||||
var state,
|
||||
actions = window.hass.actions,
|
||||
dispatcher = window.hass.dispatcher;
|
||||
|
||||
// available classes
|
||||
State: State,
|
||||
var uiActions = window.hass.uiActions = {
|
||||
ACTION_SHOW_TOAST: actions.ACTION_SHOW_TOAST,
|
||||
ACTION_SHOW_DIALOG_CALL_SERVICE: 'ACTION_SHOW_DIALOG_CALL_SERVICE',
|
||||
ACTION_SHOW_DIALOG_FIRE_EVENT: 'ACTION_SHOW_DIALOG_FIRE_EVENT',
|
||||
ACTION_SHOW_DIALOG_SET_STATE: 'ACTION_SHOW_DIALOG_SET_STATE',
|
||||
ACTION_SHOW_DIALOG_HISTORY: 'ACTION_SHOW_DIALOG_HISTORY',
|
||||
ACTION_SHOW_DIALOG_MORE_INFO: 'ACTION_SHOW_DIALOG_MORE_INFO',
|
||||
|
||||
// Polymer lifecycle methods
|
||||
created: function() {
|
||||
this.api = this;
|
||||
showMoreInfoDialog: function(entityId) {
|
||||
dispatcher.dispatch({
|
||||
actionType: this.ACTION_SHOW_DIALOG_MORE_INFO,
|
||||
entityId: entityId,
|
||||
});
|
||||
},
|
||||
|
||||
// so we can pass these methods safely as callbacks
|
||||
this.turn_on = this.turn_on.bind(this);
|
||||
this.turn_off = this.turn_off.bind(this);
|
||||
},
|
||||
showSetStateDialog: function(entityId) {
|
||||
dispatcher.dispatch({
|
||||
actionType: this.ACTION_SHOW_DIALOG_SET_STATE,
|
||||
entityId: entityId
|
||||
});
|
||||
},
|
||||
|
||||
// local methods
|
||||
removeState: function(entityId) {
|
||||
var state = this.getState(entityId);
|
||||
showFireEventDialog: function() {
|
||||
dispatcher.dispatch({
|
||||
actionType: this.ACTION_SHOW_DIALOG_FIRE_EVENT,
|
||||
});
|
||||
},
|
||||
|
||||
if (state !== null) {
|
||||
this.states.splice(this.states.indexOf(state), 1);
|
||||
}
|
||||
},
|
||||
showCallServiceDialog: function() {
|
||||
dispatcher.dispatch({
|
||||
actionType: this.ACTION_SHOW_DIALOG_CALL_SERVICE,
|
||||
});
|
||||
},
|
||||
|
||||
getState: function(entityId) {
|
||||
var found = this.states.filter(function(state) {
|
||||
return state.entity_id == entityId;
|
||||
}, this);
|
||||
showHistoryDialog: function() {
|
||||
dispatcher.dispatch({
|
||||
actionType: this.ACTION_SHOW_DIALOG_HISTORY,
|
||||
});
|
||||
},
|
||||
|
||||
return found.length > 0 ? found[0] : null;
|
||||
},
|
||||
};
|
||||
|
||||
getStates: function(entityIds) {
|
||||
var states = [];
|
||||
var state;
|
||||
for(var i = 0; i < entityIds.length; i++) {
|
||||
state = this.getState(entityIds[i]);
|
||||
var getState = function(entityId) {
|
||||
return window.hass.stateStore.get(entityId);
|
||||
};
|
||||
|
||||
dispatcher.register(function(payload) {
|
||||
switch (payload.actionType) {
|
||||
case actions.ACTION_SHOW_TOAST:
|
||||
this.$.toast.text = payload.message;
|
||||
this.$.toast.show();
|
||||
break;
|
||||
|
||||
case uiActions.ACTION_SHOW_DIALOG_HISTORY:
|
||||
this.$.historyDialog.show();
|
||||
break;
|
||||
|
||||
case uiActions.ACTION_SHOW_DIALOG_MORE_INFO:
|
||||
state = getState(payload.entityId);
|
||||
|
||||
this.$.moreInfoDialog.show(state);
|
||||
break;
|
||||
|
||||
case uiActions.ACTION_SHOW_DIALOG_SET_STATE:
|
||||
if (payload.entityId) {
|
||||
state = getState(payload.entityId);
|
||||
|
||||
this.$.stateSetDialog.show(
|
||||
state.entity_id, state.state, state.attributes);
|
||||
} else {
|
||||
this.$.stateSetDialog.show("", "");
|
||||
}
|
||||
break;
|
||||
|
||||
case uiActions.ACTION_SHOW_DIALOG_FIRE_EVENT:
|
||||
this.$.eventDialog.show();
|
||||
break;
|
||||
|
||||
case uiActions.ACTION_SHOW_DIALOG_CALL_SERVICE:
|
||||
this.$.serviceDialog.show();
|
||||
break;
|
||||
|
||||
if(state !== null) {
|
||||
states.push(state);
|
||||
}
|
||||
}
|
||||
return states;
|
||||
},
|
||||
|
||||
getEntityIDs: function() {
|
||||
return this.states.map(
|
||||
function(state) { return state.entity_id; });
|
||||
},
|
||||
|
||||
hasService: function(domain, service) {
|
||||
var found = this.services.filter(function(serv) {
|
||||
return serv.domain == domain && serv.services.indexOf(service) !== -1;
|
||||
}, this);
|
||||
|
||||
return found.length > 0;
|
||||
},
|
||||
|
||||
hasComponent: function(component) {
|
||||
return this.components.indexOf(component) !== -1;
|
||||
},
|
||||
|
||||
getCustomGroups: function() {
|
||||
return this.states.filter(function(state) { return state.isCustomGroup;});
|
||||
},
|
||||
|
||||
_laterFetchStates: function() {
|
||||
if(this.stateUpdateTimeout) {
|
||||
clearTimeout(this.stateUpdateTimeout);
|
||||
}
|
||||
|
||||
// update states in 60 seconds
|
||||
this.stateUpdateTimeout = setTimeout(this.fetchStates.bind(this), 60000);
|
||||
},
|
||||
|
||||
_sortStates: function() {
|
||||
this.states.sort(function(one, two) {
|
||||
if (one.entity_id > two.entity_id) {
|
||||
return 1;
|
||||
} else if (one.entity_id < two.entity_id) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Pushes a new state to the state machine.
|
||||
* Will resort the states after a push and fire states-updated event.
|
||||
*/
|
||||
_pushNewState: function(new_state) {
|
||||
if (this.__pushNewState(new_state)) {
|
||||
this._sortStates();
|
||||
}
|
||||
|
||||
this.fire('states-updated');
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates or updates a state. Returns if a new state was added.
|
||||
*/
|
||||
__pushNewState: function(new_state) {
|
||||
var curState = this.getState(new_state.entity_id);
|
||||
|
||||
if (curState === null) {
|
||||
this.states.push(new State(new_state, this));
|
||||
|
||||
return true;
|
||||
} else {
|
||||
curState.attributes = new_state.attributes;
|
||||
curState.last_changed = new_state.last_changed;
|
||||
curState.state = new_state.state;
|
||||
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
_pushNewStates: function(newStates, removeNonPresent) {
|
||||
removeNonPresent = !!removeNonPresent;
|
||||
var currentEntityIds = removeNonPresent ? this.getEntityIDs() : [];
|
||||
|
||||
var hasNew = newStates.reduce(function(hasNew, newState) {
|
||||
var isNewState = this.__pushNewState(newState);
|
||||
|
||||
if (isNewState) {
|
||||
return true;
|
||||
} else if(removeNonPresent) {
|
||||
currentEntityIds.splice(currentEntityIds.indexOf(newState.entity_id), 1);
|
||||
}
|
||||
|
||||
return hasNew;
|
||||
}.bind(this), false);
|
||||
|
||||
currentEntityIds.forEach(function(entityId) {
|
||||
this.removeState(entityId);
|
||||
}.bind(this));
|
||||
|
||||
if (hasNew) {
|
||||
this._sortStates();
|
||||
}
|
||||
|
||||
this.fire('states-updated');
|
||||
},
|
||||
|
||||
// call api methods
|
||||
fetchAll: function() {
|
||||
this.fetchStates();
|
||||
this.fetchServices();
|
||||
this.fetchEvents();
|
||||
this.fetchComponents();
|
||||
},
|
||||
|
||||
fetchState: function(entityId) {
|
||||
var successStateUpdate = function(new_state) {
|
||||
this._pushNewState(new_state);
|
||||
};
|
||||
|
||||
this.call_api("GET", "states/" + entityId, null, successStateUpdate.bind(this));
|
||||
},
|
||||
|
||||
fetchStates: function(onSuccess, onError) {
|
||||
var successStatesUpdate = function(newStates) {
|
||||
this._pushNewStates(newStates, true);
|
||||
|
||||
this._laterFetchStates();
|
||||
|
||||
if(onSuccess) {
|
||||
onSuccess(this.states);
|
||||
}
|
||||
};
|
||||
|
||||
this.call_api(
|
||||
"GET", "states", null, successStatesUpdate.bind(this), onError);
|
||||
},
|
||||
|
||||
fetchEvents: function(onSuccess, onError) {
|
||||
var successEventsUpdated = function(events) {
|
||||
this.events = events;
|
||||
|
||||
this.fire('events-updated');
|
||||
|
||||
if(onSuccess) {
|
||||
onSuccess(events);
|
||||
}
|
||||
};
|
||||
|
||||
this.call_api(
|
||||
"GET", "events", null, successEventsUpdated.bind(this), onError);
|
||||
},
|
||||
|
||||
fetchServices: function(onSuccess, onError) {
|
||||
var successServicesUpdated = function(services) {
|
||||
this.services = services;
|
||||
|
||||
this.fire('services-updated');
|
||||
|
||||
if(onSuccess) {
|
||||
onSuccess(this.services);
|
||||
}
|
||||
};
|
||||
|
||||
this.call_api(
|
||||
"GET", "services", null, successServicesUpdated.bind(this), onError);
|
||||
},
|
||||
|
||||
fetchComponents: function(onSuccess, onError) {
|
||||
var successComponentsUpdated = function(components) {
|
||||
this.components = components;
|
||||
|
||||
this.fire('components-updated');
|
||||
|
||||
if(onSuccess) {
|
||||
onSuccess(this.components);
|
||||
}
|
||||
};
|
||||
|
||||
this.call_api(
|
||||
"GET", "components", null,
|
||||
successComponentsUpdated.bind(this), onError);
|
||||
},
|
||||
|
||||
turn_on: function(entity_id, options) {
|
||||
this.call_service(
|
||||
"homeassistant", "turn_on", {entity_id: entity_id}, options);
|
||||
},
|
||||
|
||||
turn_off: function(entity_id, options) {
|
||||
this.call_service(
|
||||
"homeassistant", "turn_off", {entity_id: entity_id}, options);
|
||||
},
|
||||
|
||||
set_state: function(entity_id, state, attributes) {
|
||||
var payload = {state: state};
|
||||
|
||||
if(attributes) {
|
||||
payload.attributes = attributes;
|
||||
}
|
||||
|
||||
var successToast = function(new_state) {
|
||||
this.showToast("State of "+entity_id+" set to "+state+".");
|
||||
this._pushNewState(new_state);
|
||||
};
|
||||
|
||||
this.call_api("POST", "states/" + entity_id,
|
||||
payload, successToast.bind(this));
|
||||
},
|
||||
|
||||
call_service: function(domain, service, parameters, options) {
|
||||
parameters = parameters || {};
|
||||
options = options || {};
|
||||
|
||||
var successHandler = function(changed_states) {
|
||||
if(service == "turn_on" && parameters.entity_id) {
|
||||
this.showToast("Turned on " + parameters.entity_id + '.');
|
||||
} else if(service == "turn_off" && parameters.entity_id) {
|
||||
this.showToast("Turned off " + parameters.entity_id + '.');
|
||||
} else {
|
||||
this.showToast("Service "+domain+"/"+service+" called.");
|
||||
}
|
||||
|
||||
this._pushNewStates(changed_states);
|
||||
|
||||
if(options.success) {
|
||||
options.success();
|
||||
}
|
||||
};
|
||||
|
||||
var errorHandler = function(error_data) {
|
||||
if(options.error) {
|
||||
options.error(error_data);
|
||||
}
|
||||
};
|
||||
|
||||
this.call_api("POST", "services/" + domain + "/" + service,
|
||||
parameters, successHandler.bind(this), errorHandler);
|
||||
},
|
||||
|
||||
fire_event: function(eventType, eventData) {
|
||||
eventData = eventData || {};
|
||||
|
||||
var successToast = function() {
|
||||
this.showToast("Event "+eventType+" fired.");
|
||||
};
|
||||
|
||||
this.call_api("POST", "events/" + eventType,
|
||||
eventData, successToast.bind(this));
|
||||
},
|
||||
|
||||
call_api: function(method, path, parameters, onSuccess, onError) {
|
||||
var url = "/api/" + path;
|
||||
|
||||
// set to true to generate a frontend to be used as demo on the website
|
||||
if (false) {
|
||||
if (path === "states" || path === "services" || path === "events") {
|
||||
url = "/demo/" + path + ".json";
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
var req = new XMLHttpRequest();
|
||||
req.open(method, url, true);
|
||||
req.setRequestHeader("X-HA-access", this.auth);
|
||||
|
||||
req.onreadystatechange = function() {
|
||||
|
||||
if(req.readyState == 4) {
|
||||
if(req.status > 199 && req.status < 300) {
|
||||
if(onSuccess) {
|
||||
onSuccess(JSON.parse(req.responseText));
|
||||
}
|
||||
} else {
|
||||
if(onError) {
|
||||
var data = req.responseText ? JSON.parse(req.responseText) : {};
|
||||
onError(data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}.bind(this);
|
||||
|
||||
if(parameters) {
|
||||
req.send(JSON.stringify(parameters));
|
||||
} else {
|
||||
req.send();
|
||||
// if auth was given, tell the backend
|
||||
if(this.auth) {
|
||||
window.hass.authActions.validate(this.auth);
|
||||
}
|
||||
},
|
||||
|
||||
// show dialogs
|
||||
showHistoryDialog: function() {
|
||||
this.$.historyDialog.show();
|
||||
},
|
||||
|
||||
showmoreInfoDialog: function(entityId) {
|
||||
this.$.moreInfoDialog.show(this.getState(entityId));
|
||||
},
|
||||
|
||||
showEditStateDialog: function(entityId) {
|
||||
var state = this.getState(entityId);
|
||||
|
||||
this.showSetStateDialog(entityId, state.state, state.attributes);
|
||||
},
|
||||
|
||||
showSetStateDialog: function(entityId, state, stateAttributes) {
|
||||
entityId = entityId || "";
|
||||
state = state || "";
|
||||
stateAttributes = stateAttributes || null;
|
||||
|
||||
this.$.stateSetDialog.show(entityId, state, stateAttributes);
|
||||
},
|
||||
|
||||
showFireEventDialog: function(eventType, eventData) {
|
||||
eventType = eventType || "";
|
||||
eventData = eventData || "";
|
||||
|
||||
this.$.eventDialog.show(eventType, eventData);
|
||||
},
|
||||
|
||||
showCallServiceDialog: function(domain, service, serviceData) {
|
||||
domain = domain || "";
|
||||
service = service || "";
|
||||
serviceData = serviceData || "";
|
||||
|
||||
this.$.serviceDialog.show(domain, service, serviceData);
|
||||
},
|
||||
|
||||
showToast: function(message) {
|
||||
this.$.toast.text = message;
|
||||
this.$.toast.show();
|
||||
},
|
||||
|
||||
logOut: function() {
|
||||
this.auth = "";
|
||||
}
|
||||
|
||||
});
|
||||
</script>
|
||||
</polymer-element>
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Subproject commit ebaaf741b55a22bcc913fc571065d6bb04cb5979
|
|
@ -0,0 +1,48 @@
|
|||
<link rel="import" href="bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="bower_components/font-roboto/roboto.html">
|
||||
|
||||
<link rel="import" href="home-assistant-api.html">
|
||||
<link rel="import" href="layouts/login-form.html">
|
||||
<link rel="import" href="layouts/home-assistant-main.html">
|
||||
<link rel="import" href="resources/home-assistant-style.html">
|
||||
|
||||
<polymer-element name="home-assistant" attributes="auth">
|
||||
<template>
|
||||
<style>
|
||||
|
||||
:host {
|
||||
font-family: RobotoDraft, 'Helvetica Neue', Helvetica, Arial;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<home-assistant-style></home-assistant-style>
|
||||
<home-assistant-api auth="{{auth}}"></home-assistant-api>
|
||||
|
||||
<template if="{{!loaded}}">
|
||||
<login-form></login-form>
|
||||
</template>
|
||||
|
||||
<template if="{{loaded}}">
|
||||
<home-assistant-main></home-assistant-main>
|
||||
</template>
|
||||
|
||||
</template>
|
||||
<script>
|
||||
Polymer({
|
||||
loaded: window.hass.syncStore.initialLoadDone(),
|
||||
|
||||
ready: function() {
|
||||
// remove the HTML init message
|
||||
document.getElementById('init').remove();
|
||||
|
||||
// listen if we are fully loaded
|
||||
window.hass.syncStore.addChangeListener(this.updateLoadStatus.bind(this));
|
||||
},
|
||||
|
||||
updateLoadStatus: function() {
|
||||
this.loaded = window.hass.syncStore.initialLoadDone();
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</polymer-element>
|
|
@ -1,15 +1,15 @@
|
|||
<link rel="import" href="bower_components/core-header-panel/core-header-panel.html">
|
||||
<link rel="import" href="bower_components/core-toolbar/core-toolbar.html">
|
||||
<link rel="import" href="bower_components/core-icon/core-icon.html">
|
||||
<link rel="import" href="bower_components/paper-tabs/paper-tabs.html">
|
||||
<link rel="import" href="bower_components/paper-tabs/paper-tab.html">
|
||||
<link rel="import" href="bower_components/paper-icon-button/paper-icon-button.html">
|
||||
<link rel="import" href="bower_components/paper-menu-button/paper-menu-button.html">
|
||||
<link rel="import" href="bower_components/paper-dropdown/paper-dropdown.html">
|
||||
<link rel="import" href="bower_components/core-menu/core-menu.html">
|
||||
<link rel="import" href="bower_components/paper-item/paper-item.html">
|
||||
<link rel="import" href="../bower_components/core-header-panel/core-header-panel.html">
|
||||
<link rel="import" href="../bower_components/core-toolbar/core-toolbar.html">
|
||||
<link rel="import" href="../bower_components/core-icon/core-icon.html">
|
||||
<link rel="import" href="../bower_components/paper-tabs/paper-tabs.html">
|
||||
<link rel="import" href="../bower_components/paper-tabs/paper-tab.html">
|
||||
<link rel="import" href="../bower_components/paper-icon-button/paper-icon-button.html">
|
||||
<link rel="import" href="../bower_components/paper-menu-button/paper-menu-button.html">
|
||||
<link rel="import" href="../bower_components/paper-dropdown/paper-dropdown.html">
|
||||
<link rel="import" href="../bower_components/core-menu/core-menu.html">
|
||||
<link rel="import" href="../bower_components/paper-item/paper-item.html">
|
||||
|
||||
<link rel="import" href="components/state-cards.html">
|
||||
<link rel="import" href="../layouts/partial-states.html">
|
||||
|
||||
<polymer-element name="home-assistant-main" attributes="api">
|
||||
<template>
|
||||
|
@ -69,7 +69,7 @@
|
|||
|
||||
</style>
|
||||
|
||||
<core-header-panel fit mode="{{hasCustomGroups && 'waterfall-tall'}}">
|
||||
<core-header-panel fit>
|
||||
|
||||
<core-toolbar>
|
||||
<div flex>Home Assistant</div>
|
||||
|
@ -107,84 +107,39 @@
|
|||
</core-menu>
|
||||
</paper-dropdown>
|
||||
</paper-menu-button>
|
||||
|
||||
<template if="{{hasCustomGroups}}">
|
||||
<div class="bottom fit" horizontal layout>
|
||||
<paper-tabs id="tabsHolder" noink flex
|
||||
selected="0" on-core-select="{{tabClicked}}">
|
||||
|
||||
<paper-tab>ALL</paper-tab>
|
||||
<paper-tab data-filter='customgroup'>GROUPS</paper-tab>
|
||||
|
||||
</paper-tabs>
|
||||
</div>
|
||||
</template>
|
||||
</core-toolbar>
|
||||
|
||||
<state-cards
|
||||
api="{{api}}"
|
||||
filter="{{selectedFilter}}"
|
||||
class="content">
|
||||
<h3>Hi there!</h3>
|
||||
<p>
|
||||
It looks like we have nothing to show you right now. It could be that we have not yet discovered all your devices but it is more likely that you have not configured Home Assistant yet.
|
||||
</p>
|
||||
<p>
|
||||
Please see the <a href='https://home-assistant.io/getting-started/' target='_blank'>Getting Started</a> section on how to setup your devices.
|
||||
</p>
|
||||
</state-cards>
|
||||
|
||||
<partial-states></partial-states>
|
||||
</core-header-panel>
|
||||
|
||||
</template>
|
||||
<script>
|
||||
Polymer({
|
||||
selectedFilter: null,
|
||||
hasCustomGroups: false,
|
||||
|
||||
observe: {
|
||||
'api.states': 'updateHasCustomGroup'
|
||||
},
|
||||
|
||||
// computed: {
|
||||
// hasCustomGroups: "api.getCustomGroups().length > 0"
|
||||
// },
|
||||
|
||||
tabClicked: function(ev) {
|
||||
if(ev.detail.isSelected) {
|
||||
// will be null for ALL tab
|
||||
this.selectedFilter = ev.detail.item.getAttribute('data-filter');
|
||||
}
|
||||
},
|
||||
|
||||
handleRefreshClick: function() {
|
||||
this.api.fetchAll();
|
||||
window.hass.syncActions.sync();
|
||||
},
|
||||
|
||||
handleHistoryClick: function() {
|
||||
this.api.showHistoryDialog();
|
||||
window.hass.uiActions.showHistoryDialog();
|
||||
},
|
||||
|
||||
handleEventClick: function() {
|
||||
this.api.showFireEventDialog();
|
||||
window.hass.uiActions.showFireEventDialog();
|
||||
},
|
||||
|
||||
handleServiceClick: function() {
|
||||
this.api.showCallServiceDialog();
|
||||
window.hass.uiActions.showCallServiceDialog();
|
||||
},
|
||||
|
||||
handleAddStateClick: function() {
|
||||
this.api.showSetStateDialog();
|
||||
window.hass.uiActions.showSetStateDialog();
|
||||
},
|
||||
|
||||
handleLogOutClick: function() {
|
||||
this.api.logOut();
|
||||
window.hass.authActions.logOut();
|
||||
},
|
||||
|
||||
updateHasCustomGroup: function() {
|
||||
this.hasCustomGroups = this.api.getCustomGroups().length > 0;
|
||||
}
|
||||
|
||||
});
|
||||
</script>
|
||||
</polymer-element>
|
|
@ -0,0 +1,129 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../bower_components/paper-button/paper-button.html">
|
||||
<link rel="import" href="../bower_components/paper-input/paper-input-decorator.html">
|
||||
<link rel="import" href="../bower_components/core-input/core-input.html">
|
||||
<link rel="import" href="../bower_components/paper-spinner/paper-spinner.html">
|
||||
|
||||
<polymer-element name="login-form">
|
||||
<template>
|
||||
<style>
|
||||
paper-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.login paper-button {
|
||||
margin-left: 242px;
|
||||
}
|
||||
|
||||
.login .interact {
|
||||
height: 125px;
|
||||
}
|
||||
|
||||
#validatebox {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.validatemessage {
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div layout horizontal center fit class='login' id="splash">
|
||||
<div layout vertical center flex>
|
||||
|
||||
<img src="/static/favicon-192x192.png" />
|
||||
<h1>Home Assistant</h1>
|
||||
|
||||
<a href="#" id="hideKeyboardOnFocus"></a>
|
||||
|
||||
<div class='interact' layout vertical>
|
||||
|
||||
<div id='loginform' hidden?="{{isValidating || isLoggedIn}}">
|
||||
<paper-input-decorator label="Password" id="passwordDecorator">
|
||||
<input is="core-input" type="password" id="passwordInput"
|
||||
value="{{authToken}}" on-keyup="{{passwordKeyup}}">
|
||||
</paper-input-decorator>
|
||||
<paper-button on-click={{validatePassword}}>Log In</paper-button>
|
||||
</div>
|
||||
|
||||
<div id="validatebox" hidden?="{{!(isValidating || isLoggedIn)}}">
|
||||
<paper-spinner active="true"></paper-spinner><br />
|
||||
<div class="validatemessage">{{spinnerMessage}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
Polymer({
|
||||
MSG_VALIDATING: "Validating password…",
|
||||
MSG_LOADING_DATA: "Loading data…",
|
||||
|
||||
authToken: "",
|
||||
|
||||
isValidating: false,
|
||||
isLoggedIn: false,
|
||||
|
||||
spinnerMessage: "",
|
||||
|
||||
ready: function() {
|
||||
this.syncStore = window.hass.syncStore;
|
||||
this.authStore = window.hass.authStore;
|
||||
|
||||
this.authChangeListener = this.authChangeListener.bind(this);
|
||||
|
||||
this.authStore.addChangeListener(this.authChangeListener);
|
||||
|
||||
this.authChangeListener();
|
||||
},
|
||||
|
||||
attached: function() {
|
||||
this.focusPassword();
|
||||
},
|
||||
|
||||
detached: function() {
|
||||
this.authStore.removeChangeListener(this.authChangeListener);
|
||||
},
|
||||
|
||||
authChangeListener: function() {
|
||||
this.isValidating = this.authStore.isValidating();
|
||||
this.isLoggedIn = this.authStore.isLoggedIn();
|
||||
this.spinnerMessage = this.isValidating ? this.MSG_VALIDATING : this.MSG_LOADING_DATA;
|
||||
|
||||
if (this.authStore.wasLastAttemptInvalid()) {
|
||||
this.$.passwordDecorator.error = this.authStore.getLastAttemptMessage();
|
||||
this.$.passwordDecorator.isInvalid = true;
|
||||
}
|
||||
|
||||
if (!(this.isValidating && this.isLoggedIn)) {
|
||||
this.job('focusPasswordBox', this.focusPassword.bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
focusPassword: function() {
|
||||
this.$.passwordInput.focus();
|
||||
},
|
||||
|
||||
passwordKeyup: function(ev) {
|
||||
// validate on enter
|
||||
if(ev.keyCode === 13) {
|
||||
this.validatePassword();
|
||||
|
||||
// clear error after we start typing again
|
||||
} else if(this.$.passwordDecorator.isInvalid) {
|
||||
this.$.passwordDecorator.isInvalid = false;
|
||||
}
|
||||
},
|
||||
|
||||
validatePassword: function() {
|
||||
this.$.hideKeyboardOnFocus.focus();
|
||||
|
||||
window.hass.authActions.validate(this.authToken);
|
||||
},
|
||||
|
||||
});
|
||||
</script>
|
||||
</polymer-element>
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<link rel="import" href="../components/state-cards.html">
|
||||
|
||||
<polymer-element name="partial-states">
|
||||
<template>
|
||||
<state-cards states="{{states}}">
|
||||
<h3>Hi there!</h3>
|
||||
<p>
|
||||
It looks like we have nothing to show you right now. It could be that we have not yet discovered all your devices but it is more likely that you have not configured Home Assistant yet.
|
||||
</p>
|
||||
<p>
|
||||
Please see the <a href='https://home-assistant.io/getting-started/' target='_blank'>Getting Started</a> section on how to setup your devices.
|
||||
</p>
|
||||
</state-cards>
|
||||
|
||||
</template>
|
||||
<script>
|
||||
Polymer({
|
||||
states: [],
|
||||
|
||||
ready: function() {
|
||||
this.stateStoreChanged = this.stateStoreChanged.bind(this);
|
||||
window.hass.stateStore.addChangeListener(this.stateStoreChanged);
|
||||
|
||||
this.stateStoreChanged();
|
||||
},
|
||||
|
||||
detached: function() {
|
||||
window.hass.stateStore.removeChangeListener(this.stateStoreChanged);
|
||||
},
|
||||
|
||||
stateStoreChanged: function() {
|
||||
this.states = _.filter(window.hass.stateStore.all(), function(state) {
|
||||
return state.domain !== 'group';
|
||||
});
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</polymer>
|
|
@ -76,15 +76,16 @@
|
|||
configure_id: this.stateObj.attributes.configure_id
|
||||
};
|
||||
|
||||
this.api.call_service('configurator', 'configure', data, {
|
||||
success: function() {
|
||||
window.hass.serviceActions.callService('configurator', 'configure', data).then(
|
||||
|
||||
function() {
|
||||
this.action = 'display';
|
||||
this.api.fetchAll();
|
||||
window.hass.syncActions.sync();
|
||||
}.bind(this),
|
||||
error: function() {
|
||||
|
||||
function() {
|
||||
this.action = 'display';
|
||||
}.bind(this)
|
||||
});
|
||||
}.bind(this));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<link rel="import" href="more-info-sun.html">
|
||||
<link rel="import" href="more-info-configurator.html">
|
||||
|
||||
<polymer-element name="more-info-content" attributes="api stateObj">
|
||||
<polymer-element name="more-info-content" attributes="stateObj">
|
||||
<template>
|
||||
<style>
|
||||
:host {
|
||||
|
@ -30,7 +30,6 @@ Polymer({
|
|||
}
|
||||
|
||||
var moreInfo = document.createElement("more-info-" + this.stateObj.moreInfoType);
|
||||
moreInfo.api = this.api;
|
||||
moreInfo.stateObj = this.stateObj;
|
||||
this.$.moreInfo.appendChild(moreInfo);
|
||||
},
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<link rel="import" href="../cards/state-card-content.html">
|
||||
|
||||
<polymer-element name="more-info-group" attributes="stateObj api">
|
||||
<polymer-element name="more-info-group" attributes="stateObj">
|
||||
<template>
|
||||
<style>
|
||||
.child-card {
|
||||
|
@ -15,7 +15,7 @@
|
|||
</style>
|
||||
|
||||
<template repeat="{{states as state}}">
|
||||
<state-card-content stateObj="{{state}}" api="{{api}}" class='child-card'>
|
||||
<state-card-content stateObj="{{state}}" class='child-card'>
|
||||
</state-card-content>
|
||||
</template>
|
||||
</template>
|
||||
|
@ -26,7 +26,7 @@ Polymer({
|
|||
},
|
||||
|
||||
updateStates: function() {
|
||||
this.states = this.api.getStates(this.stateObj.attributes.entity_id);
|
||||
this.states = window.hass.stateStore.gets(this.stateObj.attributes.entity_id);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -56,9 +56,8 @@
|
|||
<script>
|
||||
Polymer({
|
||||
|
||||
// on-change is unpredictable so using on-core-change this has side effect
|
||||
// that it fires if changed by brightnessChanged(), thus an ignore boolean.
|
||||
ignoreNextBrightnessEvent: false,
|
||||
// initial change should be ignored
|
||||
ignoreNextBrightnessEvent: true,
|
||||
|
||||
observe: {
|
||||
'stateObj.attributes.brightness': 'brightnessChanged',
|
||||
|
@ -86,9 +85,9 @@ Polymer({
|
|||
if(isNaN(bri)) return;
|
||||
|
||||
if(bri === 0) {
|
||||
this.api.turn_off(this.stateObj.entity_id);
|
||||
window.hass.serviceActions.callTurnOff(this.stateObj.entity_id);
|
||||
} else {
|
||||
this.api.call_service("light", "turn_on", {
|
||||
window.hass.serviceActions.callService("light", "turn_on", {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
brightness: bri
|
||||
});
|
||||
|
@ -98,7 +97,7 @@ Polymer({
|
|||
colorPicked: function(ev) {
|
||||
var color = ev.detail.rgb;
|
||||
|
||||
this.api.call_service("light", "turn_on", {
|
||||
window.hass.serviceActions.callService("light", "turn_on", {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
rgb_color: [color.r, color.g, color.b]
|
||||
});
|
||||
|
|
|
@ -31,8 +31,8 @@
|
|||
Polymer({
|
||||
|
||||
stateObjChanged: function() {
|
||||
var rising = ha.util.parseTime(this.stateObj.attributes.next_rising);
|
||||
var setting = ha.util.parseTime(this.stateObj.attributes.next_setting);
|
||||
var rising = window.hass.parseTime(this.stateObj.attributes.next_rising);
|
||||
var setting = window.hass.parseTime(this.stateObj.attributes.next_setting);
|
||||
|
||||
if(rising > setting) {
|
||||
this.$.sunData.appendChild(this.$.rising);
|
||||
|
|
|
@ -1,157 +0,0 @@
|
|||
<link rel="import" href="bower_components/font-roboto/roboto.html">
|
||||
<link rel="import" href="bower_components/paper-button/paper-button.html">
|
||||
<link rel="import" href="bower_components/paper-input/paper-input-decorator.html">
|
||||
<link rel="import" href="bower_components/core-input/core-input.html">
|
||||
<link rel="import" href="bower_components/paper-spinner/paper-spinner.html">
|
||||
|
||||
<link rel="import" href="home-assistant-main.html">
|
||||
<link rel="import" href="home-assistant-api.html">
|
||||
<link rel="import" href="resources/home-assistant-style.html">
|
||||
|
||||
<polymer-element name="splash-login" attributes="auth">
|
||||
<template>
|
||||
<style>
|
||||
|
||||
:host {
|
||||
font-family: RobotoDraft, 'Helvetica Neue', Helvetica, Arial;
|
||||
}
|
||||
|
||||
paper-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.login paper-button {
|
||||
margin-left: 242px;
|
||||
}
|
||||
|
||||
.login .interact {
|
||||
height: 125px;
|
||||
}
|
||||
|
||||
#validatebox {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#validatemessage {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<home-assistant-style></home-assistant-style>
|
||||
<home-assistant-api auth="{{auth}}" id="api"></home-assistant-api>
|
||||
|
||||
<div layout horizontal center fit class='login' id="splash">
|
||||
<div layout vertical center flex>
|
||||
<img src="/static/favicon-192x192.png" />
|
||||
<h1>Home Assistant</h1>
|
||||
<a href="#" id="hideKeyboardOnFocus"></a>
|
||||
<div class='interact' layout vertical>
|
||||
<div id='loginform'>
|
||||
<paper-input-decorator label="Password" id="passwordDecorator">
|
||||
<input is="core-input" type="password" id="passwordInput"
|
||||
value="{{auth}}" on-keyup="{{passwordKeyup}}" autofocus>
|
||||
</paper-input-decorator>
|
||||
<paper-button on-click={{validatePassword}}>Log In</paper-button>
|
||||
</div>
|
||||
|
||||
<div id="validatebox" hidden>
|
||||
<paper-spinner active="true"></paper-spinner><br />
|
||||
<div id="validatemessage">Validating password...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<home-assistant-main api="{{api}}" hidden id="main"></home-assistant-main>
|
||||
|
||||
</template>
|
||||
<script>
|
||||
Polymer({
|
||||
|
||||
// can be no_auth, valid_auth
|
||||
state: "no_auth",
|
||||
auth: "",
|
||||
|
||||
ready: function() {
|
||||
this.api = this.$.api;
|
||||
},
|
||||
|
||||
domReady: function() {
|
||||
document.getElementById('init').remove();
|
||||
|
||||
if(this.auth) {
|
||||
this.validatePassword();
|
||||
}
|
||||
},
|
||||
|
||||
authChanged: function(oldVal, newVal) {
|
||||
// log out functionality
|
||||
if(newVal === "" && this.state === "valid_auth") {
|
||||
this.state = "no_auth";
|
||||
}
|
||||
},
|
||||
|
||||
stateChanged: function(oldVal, newVal) {
|
||||
if(newVal === "no_auth") {
|
||||
// set login box showing
|
||||
this.$.loginform.removeAttribute('hidden');
|
||||
this.$.validatebox.setAttribute('hidden', null);
|
||||
|
||||
// reset to initial message
|
||||
this.$.validatemessage.innerHTML = "Validating password...";
|
||||
|
||||
// show splash
|
||||
this.$.splash.removeAttribute('hidden');
|
||||
this.$.main.setAttribute('hidden', null);
|
||||
} else { // valid_auth
|
||||
this.$.splash.setAttribute('hidden', null);
|
||||
this.$.main.removeAttribute('hidden');
|
||||
}
|
||||
},
|
||||
|
||||
passwordKeyup: function(ev) {
|
||||
// validate on enter
|
||||
if(ev.keyCode === 13) {
|
||||
this.validatePassword();
|
||||
|
||||
// clear error after we start typing again
|
||||
} else if(this.$.passwordDecorator.isInvalid) {
|
||||
this.$.passwordDecorator.isInvalid = false;
|
||||
}
|
||||
},
|
||||
|
||||
validatePassword: function() {
|
||||
this.$.loginform.setAttribute('hidden', null);
|
||||
this.$.validatebox.removeAttribute('hidden');
|
||||
this.$.hideKeyboardOnFocus.focus();
|
||||
|
||||
var passwordValid = function(result) {
|
||||
this.$.validatemessage.innerHTML = "Loading data…";
|
||||
this.api.fetchEvents();
|
||||
this.api.fetchComponents();
|
||||
|
||||
this.api.fetchStates(function() {
|
||||
this.state = "valid_auth";
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
var passwordInvalid = function(result) {
|
||||
if(result && result.message) {
|
||||
this.$.passwordDecorator.error = result.message;
|
||||
} else {
|
||||
this.$.passwordDecorator.error = "Unexpected result from API";
|
||||
}
|
||||
this.auth = null;
|
||||
this.$.passwordDecorator.isInvalid = true;
|
||||
this.$.loginform.removeAttribute('hidden');
|
||||
this.$.validatebox.setAttribute('hidden', null);
|
||||
this.$.passwordInput.focus();
|
||||
};
|
||||
|
||||
this.api.fetchServices(passwordValid.bind(this), passwordInvalid.bind(this));
|
||||
}
|
||||
|
||||
});
|
||||
</script>
|
||||
</polymer-element>
|
|
@ -15,7 +15,7 @@ cp polymer/bower_components/webcomponentsjs/webcomponents.min.js .
|
|||
# Let Polymer refer to the minified JS version before we compile
|
||||
sed -i.bak 's/polymer\.js/polymer\.min\.js/' polymer/bower_components/polymer/polymer.html
|
||||
|
||||
vulcanize -o frontend.html --inline --strip polymer/splash-login.html
|
||||
vulcanize -o frontend.html --inline --strip polymer/home-assistant.html
|
||||
|
||||
# Revert back the change to the Polymer component
|
||||
rm polymer/bower_components/polymer/polymer.html
|
||||
|
|
9
scripts/build_js
Executable file
9
scripts/build_js
Executable file
|
@ -0,0 +1,9 @@
|
|||
# If current pwd is scripts, go 1 up.
|
||||
if [ ${PWD##*/} == "scripts" ]; then
|
||||
cd ..
|
||||
fi
|
||||
|
||||
cd homeassistant/components/frontend/www_static/polymer/home-assistant-js
|
||||
|
||||
npm install
|
||||
npm run prod
|
9
scripts/dev_js
Executable file
9
scripts/dev_js
Executable file
|
@ -0,0 +1,9 @@
|
|||
# If current pwd is scripts, go 1 up.
|
||||
if [ ${PWD##*/} == "scripts" ]; then
|
||||
cd ..
|
||||
fi
|
||||
|
||||
cd homeassistant/components/frontend/www_static/polymer/home-assistant-js
|
||||
|
||||
npm install
|
||||
npm run dev
|
Loading…
Add table
Add a link
Reference in a new issue