Frontend now build on top of home-assistant-js

This commit is contained in:
Paulus Schoutsen 2015-02-03 23:16:53 -08:00
parent fbae2ef725
commit 115be859b6
36 changed files with 467 additions and 829 deletions

3
.gitmodules vendored
View file

@ -10,3 +10,6 @@
[submodule "homeassistant/external/noop"] [submodule "homeassistant/external/noop"]
path = homeassistant/external/noop path = homeassistant/external/noop
url = https://github.com/balloob/noop.git 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

View file

@ -58,7 +58,7 @@ def _handle_get_root(handler, path_match, data):
handler.end_headers() handler.end_headers()
if handler.server.development: if handler.server.development:
app_url = "polymer/splash-login.html" app_url = "polymer/home-assistant.html"
else: else:
app_url = "frontend-{}.html".format(version.VERSION) app_url = "frontend-{}.html".format(version.VERSION)
@ -83,7 +83,7 @@ def _handle_get_root(handler, path_match, data):
"<script" "<script"
" src='/static/webcomponents.min.js'></script>" " src='/static/webcomponents.min.js'></script>"
"<link rel='import' href='/static/{}' />" "<link rel='import' href='/static/{}' />"
"<splash-login auth='{}'></splash-login>" "<home-assistant auth='{}'></home-assistant>"
"</body></html>").format(app_url, auth)) "</body></html>").format(app_url, auth))

View file

@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """ """ DO NOT MODIFY. Auto-generated by build_frontend script """
VERSION = "954620894f13782f17ae7443f0df4ffc" VERSION = "dae175210dab23f9c383a593d8e97d9b"

File diff suppressed because one or more lines are too long

View file

@ -34,7 +34,6 @@
"paper-dropdown": "polymer/paper-dropdown#~0.5.4", "paper-dropdown": "polymer/paper-dropdown#~0.5.4",
"paper-item": "polymer/paper-item#~0.5.4", "paper-item": "polymer/paper-item#~0.5.4",
"paper-slider": "polymer/paper-slider#~0.5.4", "paper-slider": "polymer/paper-slider#~0.5.4",
"moment": "~2.8.4",
"color-picker-element": "~0.0.2", "color-picker-element": "~0.0.2",
"google-apis": "GoogleWebComponents/google-apis#~0.4.2" "google-apis": "GoogleWebComponents/google-apis#~0.4.2"
} }

View file

@ -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/polymer/polymer.html">
<link rel="import" href="../components/state-info.html"> <link rel="import" href="../components/state-info.html">

View file

@ -5,7 +5,7 @@
<link rel="import" href="state-card-thermostat.html"> <link rel="import" href="state-card-thermostat.html">
<link rel="import" href="state-card-configurator.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> <template>
<style> <style>
:host { :host {

View file

@ -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/polymer/polymer.html">
<link rel="import" href="../components/state-info.html"> <link rel="import" href="../components/state-info.html">

View file

@ -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/polymer/polymer.html">
<link rel="import" href="../components/state-info.html"> <link rel="import" href="../components/state-info.html">

View file

@ -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/polymer/polymer.html">
<link rel="import" href="../bower_components/paper-toggle-button/paper-toggle-button.html"> <link rel="import" href="../bower_components/paper-toggle-button/paper-toggle-button.html">
@ -38,6 +37,10 @@
'stateObj.state': 'stateChanged' 'stateObj.state': 'stateChanged'
}, },
ready: function() {
this.forceStateChange = this.forceStateChange.bind(this);
},
// prevent the event from propegating // prevent the event from propegating
toggleClicked: function(ev) { toggleClicked: function(ev) {
ev.stopPropagation(); ev.stopPropagation();
@ -60,16 +63,17 @@
this.toggleChecked = newVal === "on"; this.toggleChecked = newVal === "on";
}, },
forceStateChange: function() {
this.stateChanged(this.stateObj.state, this.stateObj.state);
},
turn_on: function() { turn_on: function() {
// We call stateChanged after a successful call to re-sync the toggle // 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 // 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, // result in the entity to be turned on. Since the state is not changing,
// the resync is not called automatic. // the resync is not called automatic.
this.api.turn_on(this.stateObj.entity_id, { window.hass.serviceActions.callTurnOn(this.stateObj.entity_id)
success: function() { .then(this.forceStateChange);
this.stateChanged(this.stateObj.state, this.stateObj.state);
}.bind(this)
});
}, },
turn_off: function() { turn_off: function() {
@ -77,11 +81,8 @@
// with the state. It will be out of sync if our service call did not // 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, // result in the entity to be turned on. Since the state is not changing,
// the resync is not called automatic. // the resync is not called automatic.
this.api.turn_off(this.stateObj.entity_id, { window.hass.serviceActions.callTurnOff(this.stateObj.entity_id)
success: function() { .then(this.forceStateChange);
this.stateChanged(this.stateObj.state, this.stateObj.state);
}.bind(this)
});
}, },
}); });

View file

@ -25,7 +25,7 @@
Polymer({ Polymer({
cardClicked: function() { cardClicked: function() {
this.api.showmoreInfoDialog(this.stateObj.entity_id); window.hass.uiActions.showMoreInfoDialog(this.stateObj.entity_id);
}, },
}); });

View file

@ -35,13 +35,18 @@
cbEventClicked: null, cbEventClicked: null,
states: [], states: [],
domReady: function() { ready: function() {
this.api.addEventListener('states-updated', this.statesUpdated.bind(this)) this.statesUpdated = this.statesUpdated.bind(this);
this.statesUpdated() window.hass.stateStore.addChangeListener(this.statesUpdated);
this.statesUpdated();
},
detached: function() {
window.hass.stateStore.removeChangeListener(this.statesUpdated);
}, },
statesUpdated: function() { statesUpdated: function() {
this.states = this.api.states; this.states = window.hass.stateStore.all();
}, },
handleClick: function(ev) { handleClick: function(ev) {

View file

@ -1,6 +1,6 @@
<link rel="import" href="../bower_components/polymer/polymer.html"> <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> <template>
<style> <style>
:host { :host {
@ -37,14 +37,18 @@
cbEventClicked: null, cbEventClicked: null,
events: [], events: [],
domReady: function() { ready: function() {
this.events = this.api.events 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() { eventsUpdated: function() {
this.events = this.api.events; this.events = window.hass.eventStore.all();
}, },
handleClick: function(ev) { handleClick: function(ev) {

View file

@ -2,7 +2,7 @@
<link rel="import" href="../bower_components/core-style/core-style.html"> <link rel="import" href="../bower_components/core-style/core-style.html">
<link rel="import" href="./loading-box.html"> <link rel="import" href="./loading-box.html">
<polymer-element name="recent-states" attributes="api stateObj"> <polymer-element name="recent-states" attributes="stateObj">
<template> <template>
<core-style ref='ha-data-table'></core-style> <core-style ref='ha-data-table'></core-style>
@ -36,13 +36,12 @@
stateObjChanged: function() { stateObjChanged: function() {
this.recentStates = null; 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> </script>
</polymer-element> </polymer-element>

View file

@ -51,18 +51,22 @@
services: [], services: [],
cbServiceClicked: null, cbServiceClicked: null,
domReady: function() { ready: function() {
this.services = this.api.services 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) { getIcon: function(domain) {
return (new DomainIcon).icon(domain); return (new DomainIcon()).icon(domain);
}, },
servicesUpdated: function() { servicesUpdated: function() {
this.services = this.api.services; this.services = window.hass.serviceStore.all();
}, },
serviceClicked: function(ev) { serviceClicked: function(ev) {

View file

@ -2,7 +2,7 @@
<link rel="import" href="../cards/state-card.html"> <link rel="import" href="../cards/state-card.html">
<polymer-element name="state-cards" attributes="api filter"> <polymer-element name="state-cards" attributes="states">
<template> <template>
<style> <style>
:host { :host {
@ -47,7 +47,7 @@
<div horizontal layout wrap> <div horizontal layout wrap>
<template repeat="{{states as state}}"> <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>
<template if="{{states.length == 0}}"> <template if="{{states.length == 0}}">
@ -60,24 +60,6 @@
</template> </template>
<script> <script>
Polymer({ 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> </script>
</polymer-element> </polymer-element>

View file

@ -39,10 +39,6 @@
}, },
fetchData: function() { fetchData: function() {
if (!this.api) {
return;
}
this.stateData = null; this.stateData = null;
var url = 'history/period'; var url = 'history/period';
@ -51,11 +47,10 @@
url += '?filter_entity_id=' + this.stateObj.entity_id; 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.stateData = stateData;
this.drawChart(); this.drawChart();
}.bind(this) }.bind(this));
);
}, },
drawChart: function() { drawChart: function() {
@ -75,7 +70,7 @@
var stateTimeToDate = function(time) { var stateTimeToDate = function(time) {
if (!time) return new Date(); if (!time) return new Date();
return ha.util.parseTime(time).toDate(); return window.hass.util.parseTime(time).toDate();
}; };
var addRow = function(baseState, state, tillState) { var addRow = function(baseState, state, tillState) {
@ -89,7 +84,7 @@
// this.stateData is a list of lists of sorted state objects // this.stateData is a list of lists of sorted state objects
this.stateData.forEach(function(stateInfo) { 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; var prevRow = null;

View file

@ -65,7 +65,7 @@ Polymer({
}, },
setEventData: function(eventData) { setEventData: function(eventData) {
this.$.inputData.value = eventData; this.$.inputData.value = eventData || "";
// this.$.inputDataWrapper.update(); // this.$.inputDataWrapper.update();
}, },
@ -75,7 +75,7 @@ Polymer({
clickFireEvent: function() { clickFireEvent: function() {
try { try {
this.api.fire_event( window.hass.eventActions.fire(
this.$.inputType.value, this.$.inputType.value,
this.$.inputData.value ? JSON.parse(this.$.inputData.value) : {}); this.$.inputData.value ? JSON.parse(this.$.inputData.value) : {});
this.$.dialog.close(); this.$.dialog.close();

View file

@ -16,10 +16,10 @@
</style> </style>
<div> <div>
<state-card-content stateObj="{{stateObj}}" api="{{api}}" class='title-card'> <state-card-content stateObj="{{stateObj}}" class='title-card'>
</state-card-content> </state-card-content>
<state-timeline stateObj="{{stateObj}}" api="{{api}}"></state-timeline> <state-timeline stateObj="{{stateObj}}"></state-timeline>
<more-info-content stateObj="{{stateObj}}" api="{{api}}"></more-info-content> <more-info-content stateObj="{{stateObj}}"></more-info-content>
</div> </div>
<paper-button dismissive on-click={{editClicked}}>Debug</paper-button> <paper-button dismissive on-click={{editClicked}}>Debug</paper-button>
@ -56,7 +56,7 @@ Polymer({
}, },
editClicked: function(ev) { editClicked: function(ev) {
this.api.showEditStateDialog(this.stateObj.entity_id); window.hass.uiActions.showSetStateDialog(this.stateObj.entity_id);
} }
}); });

View file

@ -53,7 +53,7 @@ Polymer({
show: function(domain, service, serviceData) { show: function(domain, service, serviceData) {
this.setService(domain, service); this.setService(domain, service);
this.$.inputData.value = serviceData; this.$.inputData.value = serviceData || "";
// this.$.inputDataWrapper.update(); // this.$.inputDataWrapper.update();
this.job('showDialogAfterRender', function() { this.job('showDialogAfterRender', function() {
this.$.dialog.toggle(); this.$.dialog.toggle();
@ -71,10 +71,10 @@ Polymer({
clickCallService: function() { clickCallService: function() {
try { try {
this.api.call_service( window.hass.serviceActions.callService(
this.$.inputDomain.value, this.$.inputDomain.value,
this.$.inputService.value, this.$.inputService.value,
this.$.inputData.value ? JSON.parse(this.$.inputData.value) : {}); this.$.inputData.value ? JSON.parse(this.$.inputData.value) : {});
this.$.dialog.close(); this.$.dialog.close();

View file

@ -82,14 +82,14 @@ Polymer({
entitySelected: function(entityId) { entitySelected: function(entityId) {
this.setEntityId(entityId); this.setEntityId(entityId);
var state = this.api.getState(entityId); var state = window.hass.stateStore.get(entityId);
this.setState(state.state); this.setState(state.state);
this.setStateData(state.attributes); this.setStateData(state.attributes);
}, },
clickSetState: function(ev) { clickSetState: function(ev) {
try { try {
this.api.set_state( window.hass.stateActions.set(
this.$.inputEntityID.value, this.$.inputEntityID.value,
this.$.inputState.value, this.$.inputState.value,
this.$.inputData.value ? JSON.parse(this.$.inputData.value) : {} this.$.inputData.value ? JSON.parse(this.$.inputData.value) : {}

View file

@ -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="./dialogs/event-fire-dialog.html">
<link rel="import" href="bower_components/paper-toast/paper-toast.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"> <script src="./home-assistant-js/dist/homeassistant.min.js"></script>
<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> <script>
var ha = {}; // Register some polymer filters
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();
};
PolymerExpressions.prototype.relativeHATime = function(timeString) { PolymerExpressions.prototype.relativeHATime = function(timeString) {
return ha.util.relativeTime(timeString); return window.hass.util.relativeTime(timeString);
}; };
PolymerExpressions.prototype.HATimeStripDate = function(timeString) { PolymerExpressions.prototype.HATimeStripDate = function(timeString) {
return (timeString || "").split(' ')[0]; return (timeString || "").split(' ')[0];
}; };
</script> </script>
<polymer-element name="home-assistant-api" attributes="auth"> <polymer-element name="home-assistant-api" attributes="auth">
<template> <template>
<paper-toast id="toast" role="alert" text=""></paper-toast> <paper-toast id="toast" role="alert" text=""></paper-toast>
<event-fire-dialog id="eventDialog" api={{api}}></event-fire-dialog> <event-fire-dialog id="eventDialog"></event-fire-dialog>
<service-call-dialog id="serviceDialog" api={{api}}></service-call-dialog> <service-call-dialog id="serviceDialog"></service-call-dialog>
<state-set-dialog id="stateSetDialog" api={{api}}></state-set-dialog> <state-set-dialog id="stateSetDialog"></state-set-dialog>
<more-info-dialog id="moreInfoDialog" api={{api}}></more-info-dialog> <more-info-dialog id="moreInfoDialog"></more-info-dialog>
<history-dialog id="historyDialog" api={{api}}></history-dialog> <history-dialog id="historyDialog"></history-dialog>
</template> </template>
<script> <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({ Polymer({
auth: "not-set", ready: function() {
states: [], var state,
services: [], actions = window.hass.actions,
events: [], dispatcher = window.hass.dispatcher;
stateUpdateTimeout: null,
// available classes var uiActions = window.hass.uiActions = {
State: State, 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 showMoreInfoDialog: function(entityId) {
created: function() { dispatcher.dispatch({
this.api = this; actionType: this.ACTION_SHOW_DIALOG_MORE_INFO,
entityId: entityId,
});
},
// so we can pass these methods safely as callbacks showSetStateDialog: function(entityId) {
this.turn_on = this.turn_on.bind(this); dispatcher.dispatch({
this.turn_off = this.turn_off.bind(this); actionType: this.ACTION_SHOW_DIALOG_SET_STATE,
}, entityId: entityId
});
},
// local methods showFireEventDialog: function() {
removeState: function(entityId) { dispatcher.dispatch({
var state = this.getState(entityId); actionType: this.ACTION_SHOW_DIALOG_FIRE_EVENT,
});
},
if (state !== null) { showCallServiceDialog: function() {
this.states.splice(this.states.indexOf(state), 1); dispatcher.dispatch({
} actionType: this.ACTION_SHOW_DIALOG_CALL_SERVICE,
}, });
},
getState: function(entityId) { showHistoryDialog: function() {
var found = this.states.filter(function(state) { dispatcher.dispatch({
return state.entity_id == entityId; actionType: this.ACTION_SHOW_DIALOG_HISTORY,
}, this); });
},
return found.length > 0 ? found[0] : null; };
},
getStates: function(entityIds) { var getState = function(entityId) {
var states = []; return window.hass.stateStore.get(entityId);
var state; };
for(var i = 0; i < entityIds.length; i++) {
state = this.getState(entityIds[i]); 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)); }.bind(this));
if (hasNew) { // if auth was given, tell the backend
this._sortStates(); if(this.auth) {
} window.hass.authActions.validate(this.auth);
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();
} }
}, },
// 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> </script>
</polymer-element> </polymer-element>

@ -0,0 +1 @@
Subproject commit ebaaf741b55a22bcc913fc571065d6bb04cb5979

View file

@ -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>

View file

@ -1,15 +1,15 @@
<link rel="import" href="bower_components/core-header-panel/core-header-panel.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-toolbar/core-toolbar.html">
<link rel="import" href="bower_components/core-icon/core-icon.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-tabs.html">
<link rel="import" href="bower_components/paper-tabs/paper-tab.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-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-menu-button/paper-menu-button.html">
<link rel="import" href="bower_components/paper-dropdown/paper-dropdown.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/core-menu/core-menu.html">
<link rel="import" href="bower_components/paper-item/paper-item.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"> <polymer-element name="home-assistant-main" attributes="api">
<template> <template>
@ -69,7 +69,7 @@
</style> </style>
<core-header-panel fit mode="{{hasCustomGroups && 'waterfall-tall'}}"> <core-header-panel fit>
<core-toolbar> <core-toolbar>
<div flex>Home Assistant</div> <div flex>Home Assistant</div>
@ -107,84 +107,39 @@
</core-menu> </core-menu>
</paper-dropdown> </paper-dropdown>
</paper-menu-button> </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> </core-toolbar>
<state-cards <partial-states></partial-states>
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>
</core-header-panel> </core-header-panel>
</template> </template>
<script> <script>
Polymer({ 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() { handleRefreshClick: function() {
this.api.fetchAll(); window.hass.syncActions.sync();
}, },
handleHistoryClick: function() { handleHistoryClick: function() {
this.api.showHistoryDialog(); window.hass.uiActions.showHistoryDialog();
}, },
handleEventClick: function() { handleEventClick: function() {
this.api.showFireEventDialog(); window.hass.uiActions.showFireEventDialog();
}, },
handleServiceClick: function() { handleServiceClick: function() {
this.api.showCallServiceDialog(); window.hass.uiActions.showCallServiceDialog();
}, },
handleAddStateClick: function() { handleAddStateClick: function() {
this.api.showSetStateDialog(); window.hass.uiActions.showSetStateDialog();
}, },
handleLogOutClick: function() { handleLogOutClick: function() {
this.api.logOut(); window.hass.authActions.logOut();
}, },
updateHasCustomGroup: function() {
this.hasCustomGroups = this.api.getCustomGroups().length > 0;
}
}); });
</script> </script>
</polymer-element> </polymer-element>

View file

@ -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>

View file

@ -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>

View file

@ -76,15 +76,16 @@
configure_id: this.stateObj.attributes.configure_id configure_id: this.stateObj.attributes.configure_id
}; };
this.api.call_service('configurator', 'configure', data, { window.hass.serviceActions.callService('configurator', 'configure', data).then(
success: function() {
function() {
this.action = 'display'; this.action = 'display';
this.api.fetchAll(); window.hass.syncActions.sync();
}.bind(this), }.bind(this),
error: function() {
function() {
this.action = 'display'; this.action = 'display';
}.bind(this) }.bind(this));
});
} }
}); });
</script> </script>

View file

@ -6,7 +6,7 @@
<link rel="import" href="more-info-sun.html"> <link rel="import" href="more-info-sun.html">
<link rel="import" href="more-info-configurator.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> <template>
<style> <style>
:host { :host {
@ -30,7 +30,6 @@ Polymer({
} }
var moreInfo = document.createElement("more-info-" + this.stateObj.moreInfoType); var moreInfo = document.createElement("more-info-" + this.stateObj.moreInfoType);
moreInfo.api = this.api;
moreInfo.stateObj = this.stateObj; moreInfo.stateObj = this.stateObj;
this.$.moreInfo.appendChild(moreInfo); this.$.moreInfo.appendChild(moreInfo);
}, },

View file

@ -2,7 +2,7 @@
<link rel="import" href="../cards/state-card-content.html"> <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> <template>
<style> <style>
.child-card { .child-card {
@ -15,7 +15,7 @@
</style> </style>
<template repeat="{{states as state}}"> <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> </state-card-content>
</template> </template>
</template> </template>
@ -26,7 +26,7 @@ Polymer({
}, },
updateStates: function() { updateStates: function() {
this.states = this.api.getStates(this.stateObj.attributes.entity_id); this.states = window.hass.stateStore.gets(this.stateObj.attributes.entity_id);
} }
}); });
</script> </script>

View file

@ -56,9 +56,8 @@
<script> <script>
Polymer({ Polymer({
// on-change is unpredictable so using on-core-change this has side effect // initial change should be ignored
// that it fires if changed by brightnessChanged(), thus an ignore boolean. ignoreNextBrightnessEvent: true,
ignoreNextBrightnessEvent: false,
observe: { observe: {
'stateObj.attributes.brightness': 'brightnessChanged', 'stateObj.attributes.brightness': 'brightnessChanged',
@ -86,9 +85,9 @@ Polymer({
if(isNaN(bri)) return; if(isNaN(bri)) return;
if(bri === 0) { if(bri === 0) {
this.api.turn_off(this.stateObj.entity_id); window.hass.serviceActions.callTurnOff(this.stateObj.entity_id);
} else { } else {
this.api.call_service("light", "turn_on", { window.hass.serviceActions.callService("light", "turn_on", {
entity_id: this.stateObj.entity_id, entity_id: this.stateObj.entity_id,
brightness: bri brightness: bri
}); });
@ -98,7 +97,7 @@ Polymer({
colorPicked: function(ev) { colorPicked: function(ev) {
var color = ev.detail.rgb; 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, entity_id: this.stateObj.entity_id,
rgb_color: [color.r, color.g, color.b] rgb_color: [color.r, color.g, color.b]
}); });

View file

@ -31,8 +31,8 @@
Polymer({ Polymer({
stateObjChanged: function() { stateObjChanged: function() {
var rising = ha.util.parseTime(this.stateObj.attributes.next_rising); var rising = window.hass.parseTime(this.stateObj.attributes.next_rising);
var setting = ha.util.parseTime(this.stateObj.attributes.next_setting); var setting = window.hass.parseTime(this.stateObj.attributes.next_setting);
if(rising > setting) { if(rising > setting) {
this.$.sunData.appendChild(this.$.rising); this.$.sunData.appendChild(this.$.rising);

View file

@ -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>

View file

@ -15,7 +15,7 @@ cp polymer/bower_components/webcomponentsjs/webcomponents.min.js .
# Let Polymer refer to the minified JS version before we compile # 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 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 # Revert back the change to the Polymer component
rm polymer/bower_components/polymer/polymer.html rm polymer/bower_components/polymer/polymer.html

9
scripts/build_js Executable file
View 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
View 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