Major cleanup frontend

This commit is contained in:
Paulus Schoutsen 2014-11-01 15:04:44 -07:00
parent 990ac057db
commit b686f04121
12 changed files with 8169 additions and 1813 deletions

View File

@ -356,14 +356,15 @@ class RequestHandler(SimpleHTTPRequestHandler):
"<link rel='shortcut icon' href='/static/favicon.ico' />" "<link rel='shortcut icon' href='/static/favicon.ico' />"
"<link rel='icon' type='image/png' " "<link rel='icon' type='image/png' "
" href='/static/favicon-192x192.png' sizes='192x192'>" " href='/static/favicon-192x192.png' sizes='192x192'>"
"<script"
" src='/static/polymer_platform.js'></script>"
"<link rel='import' href='/static/{}' />"
"<meta name='viewport' content='width=device-width, " "<meta name='viewport' content='width=device-width, "
" user-scalable=no, initial-scale=1.0, " " user-scalable=no, initial-scale=1.0, "
" minimum-scale=1.0, maximum-scale=1.0' />" " minimum-scale=1.0, maximum-scale=1.0' />"
"</head>" "</head>"
"<body fullbleed>" "<body fullbleed>"
"<h3 id='init' align='center'>Initializing Home Assistant</h3>"
"<script"
" src='/static/polymer_platform.js'></script>"
"<link rel='import' href='/static/{}' />"
"<splash-login auth='{}'></splash-login>" "<splash-login auth='{}'></splash-login>"
"</body></html>").format(app_url, data.get('api_password', ''))) "</body></html>").format(app_url, data.get('api_password', '')))

View File

@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_polymer script """ """ DO NOT MODIFY. Auto-generated by build_polymer script """
VERSION = "5ae474d6905e074fa2a6aa0cfa2941be" VERSION = "835922d1958738763525d6670c11bfb5"

File diff suppressed because it is too large Load Diff

View File

@ -19,8 +19,6 @@
"font-roboto": "Polymer/font-roboto#~0.4.2", "font-roboto": "Polymer/font-roboto#~0.4.2",
"core-header-panel": "Polymer/core-header-panel#~0.4.2", "core-header-panel": "Polymer/core-header-panel#~0.4.2",
"core-toolbar": "Polymer/core-toolbar#~0.4.2", "core-toolbar": "Polymer/core-toolbar#~0.4.2",
"core-icon-button": "Polymer/core-icon-button#~0.4.2",
"paper-fab": "Polymer/paper-fab#~0.4.2",
"paper-toast": "Polymer/paper-toast#~0.4.2", "paper-toast": "Polymer/paper-toast#~0.4.2",
"paper-dialog": "Polymer/paper-dialog#~0.4.2", "paper-dialog": "Polymer/paper-dialog#~0.4.2",
"paper-button": "Polymer/paper-button#~0.4.2", "paper-button": "Polymer/paper-button#~0.4.2",
@ -31,6 +29,9 @@
"core-item": "Polymer/core-item#~0.4.2", "core-item": "Polymer/core-item#~0.4.2",
"core-icons": "polymer/core-icons#~0.4.2", "core-icons": "polymer/core-icons#~0.4.2",
"paper-toggle-button": "polymer/paper-toggle-button#~0.4.2", "paper-toggle-button": "polymer/paper-toggle-button#~0.4.2",
"paper-tabs": "polymer/paper-tabs#~0.4.2" "paper-tabs": "polymer/paper-tabs#~0.4.2",
"paper-icon-button": "polymer/paper-icon-button#~0.4.2",
"paper-menu-button": "polymer/paper-menu-button#~0.4.2",
"paper-item": "polymer/paper-item#~0.4.2"
} }
} }

View File

@ -6,15 +6,19 @@
<link rel="import" href="bower_components/core-icons/image-icons.html"> <link rel="import" href="bower_components/core-icons/image-icons.html">
<link rel="import" href="bower_components/core-icons/hardware-icons.html"> <link rel="import" href="bower_components/core-icons/hardware-icons.html">
<polymer-element name="domain-icon" attributes="domain"> <polymer-element name="domain-icon"
attributes="domain state" constructor="DomainIcon">
<template> <template>
<core-icon icon="{{icon(domain)}}"></core-icon> <core-icon icon="{{icon(domain, state)}}"></core-icon>
</template> </template>
<script> <script>
Polymer({ Polymer({
icon: function() { icon: function(domain, state) {
switch(this.domain) { switch(domain) {
case "homeassistant":
return "home";
case "group": case "group":
return "social:communities"; return "social:communities";
@ -25,8 +29,11 @@
return "settings-input-svideo"; return "settings-input-svideo";
case "chromecast": case "chromecast":
// hardware:cast-connected if(state && state != "idle") {
return "hardware:cast-connected";
} else {
return "hardware:cast"; return "hardware:cast";
}
case "process": case "process":
return "hardware:memory" return "hardware:memory"

View File

@ -58,7 +58,7 @@
}, },
_sortStates: function(states) { _sortStates: function(states) {
return states.sort(function(one, two) { states.sort(function(one, two) {
if (one.entity_id > two.entity_id) { if (one.entity_id > two.entity_id) {
return 1; return 1;
} else if (one.entity_id < two.entity_id) { } else if (one.entity_id < two.entity_id) {
@ -86,6 +86,7 @@
} }
if(!stateFound) { if(!stateFound) {
this._enhanceState(state);
this.states.push(new_state); this.states.push(new_state);
this._sortStates(this.states); this._sortStates(this.states);
} }
@ -93,6 +94,14 @@
this.fire('states-updated') this.fire('states-updated')
}, },
_enhanceState: function(state) {
var parts = state.entity_id.split(".");
state.domain = parts[0];
state.entity = parts[1];
state.entityDisplay = state.entity.replace(/_/g, " ");
state.stateDisplay = state.state.replace(/_/g, " ");
},
// call api methods // call api methods
fetchState: function(entityId) { fetchState: function(entityId) {
var successStateUpdate = function(new_state) { var successStateUpdate = function(new_state) {
@ -104,7 +113,9 @@
fetchStates: function(onSuccess, onError) { fetchStates: function(onSuccess, onError) {
var successStatesUpdate = function(newStates) { var successStatesUpdate = function(newStates) {
this.states = this._sortStates(newStates); this._sortStates(newStates);
newStates.map(this._enhanceState);
this.states = newStates;
this.fire('states-updated') this.fire('states-updated')
@ -155,7 +166,7 @@
} }
var successToast = function(new_state) { var successToast = function(new_state) {
this.showToast("State of "+entity_id+" successful set to "+state+"."); this.showToast("State of "+entity_id+" set to "+state+".");
this._pushNewState(new_state); this._pushNewState(new_state);
} }
@ -165,7 +176,13 @@
call_service: function(domain, service, parameters) { call_service: function(domain, service, parameters) {
var successToast = function() { var successToast = function() {
this.showToast("Service "+domain+"/"+service+" successful called."); if(service == "turn_on" && parameters.entity_id) {
this.showToast("Turned on " + parameters.entity_id + '.');
} else if(service == "turn_off") {
this.showToast("Turned off " + parameters.entity_id + '.');
} else {
this.showToast("Service "+domain+"/"+service+" called.");
}
// if we call a service on an entity_id, update the state // if we call a service on an entity_id, update the state
if(parameters && parameters.entity_id) { if(parameters && parameters.entity_id) {
@ -197,7 +214,7 @@
eventData = eventData ? JSON.parse(eventData) : ""; eventData = eventData ? JSON.parse(eventData) : "";
var successToast = function() { var successToast = function() {
this.showToast("Event "+eventType+" successful fired."); this.showToast("Event "+eventType+" fired.");
} }
this.call_api("POST", "events/" + eventType, this.call_api("POST", "events/" + eventType,
@ -267,6 +284,10 @@
showToast: function(message) { showToast: function(message) {
this.$.toast.text = message; this.$.toast.text = message;
this.$.toast.show(); this.$.toast.show();
},
logOut: function() {
this.auth = "";
} }
}); });

View File

@ -1,7 +1,8 @@
<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-button/core-icon-button.html"> <link rel="import" href="bower_components/paper-icon-button/paper-icon-button.html">
<link rel="import" href="bower_components/paper-fab/paper-fab.html"> <link rel="import" href="bower_components/paper-item/paper-item.html">
<link rel="import" href="bower_components/paper-menu-button/paper-menu-button.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">
@ -24,33 +25,41 @@
core-toolbar { core-toolbar {
background: #03a9f4; background: #03a9f4;
font-size: 1.4rem; font-size: 1.3rem;
color: white; color: white;
} }
.content { paper-tab {
padding-bottom: 75px; text-transform: uppercase;
padding-right: 10px;
} }
paper-fab { .content {
position: fixed; padding-bottom: 10px;
bottom: 10px; padding-right: 10px;
right: 10px;
} }
</style> </style>
<core-header-panel fullbleed>
<core-header-panel unresolved fullbleed>
<core-toolbar class='medium-tall'> <core-toolbar class='medium-tall'>
<div flex> <div flex>
Home Assistant Home Assistant
</div> </div>
<core-icon-button icon="refresh" on-click="{{handleRefreshClick}}"></core-icon-button> <paper-icon-button icon="refresh" on-click="{{handleRefreshClick}}"></paper-icon-button>
<core-icon-button icon="developer-mode-tv" on-click="{{handleEventClick}}"></core-icon-button> <paper-icon-button icon="settings-remote"
<core-icon-button icon="settings-remote" on-click="{{handleServiceClick}}"></core-icon-button> on-click="{{handleServiceClick}}"></paper-icon-button>
<paper-menu-button icon="more-vert" halign="right">
<paper-item label="Set State">
<a on-click={{handleAddStateClick}}></a>
</paper-item>
<paper-item label="Trigger Event">
<a on-click={{handleEventClick}}></a>
</paper-item>
<paper-item label="Log Out">
<a on-click={{handleLogOutClick}}></a>
</paper-item>
</paper-menu-button>
<div class="bottom fit" horizontal layout> <div class="bottom fit" horizontal layout>
<paper-tabs id="tabsHolder" noink flex <paper-tabs id="tabsHolder" noink flex
@ -59,8 +68,10 @@
<paper-tab>ALL</paper-tab> <paper-tab>ALL</paper-tab>
<template repeat="{{state in api.states}}"> <template repeat="{{state in api.states}}">
<template if="{{isCustomGroup(state)}}"> <template if="{{state.domain == 'group' && !state.attributes.auto}}">
<paper-tab data-entity="{{state.entity_id}}">{{state.entity_id | groupName}}</paper-tab> <paper-tab data-entity="{{state.entity_id}}">
{{state.entityDisplay}}
</paper-tab>
</template> </template>
</template> </template>
@ -70,7 +81,6 @@
<div class="content" flex> <div class="content" flex>
<states-cards api="{{api}}" filter="{{selectedTab}}"></states-cards> <states-cards api="{{api}}" filter="{{selectedTab}}"></states-cards>
<paper-fab icon="add" on-click={{handleAddStateClick}}></paper-fab>
</div> </div>
</core-header-panel> </core-header-panel>
@ -80,15 +90,6 @@
Polymer({ Polymer({
selectedTab: null, selectedTab: null,
isCustomGroup: function(state) {
return (state.entity_id.lastIndexOf('group.') == 0 &&
!state.attributes.auto);
},
groupName: function(entity_id) {
return entity_id.substring(6).toUpperCase().replace(/_/g, " ");
},
tabClicked: function(ev) { tabClicked: function(ev) {
if(ev.detail.isSelected) { if(ev.detail.isSelected) {
// will be null for ALL tab // will be null for ALL tab
@ -110,6 +111,10 @@
handleAddStateClick: function() { handleAddStateClick: function() {
this.api.showSetStateDialog(); this.api.showSetStateDialog();
},
handleLogOutClick: function() {
this.api.logOut();
} }
}); });

View File

@ -3,6 +3,8 @@
<link rel="import" href="bower_components/core-menu/core-submenu.html"> <link rel="import" href="bower_components/core-menu/core-submenu.html">
<link rel="import" href="bower_components/core-item/core-item.html"> <link rel="import" href="bower_components/core-item/core-item.html">
<link rel="import" href="domain-icon.html">
<polymer-element name="services-list" attributes="api cbServiceClicked"> <polymer-element name="services-list" attributes="api cbServiceClicked">
<template> <template>
<style> <style>
@ -22,7 +24,7 @@
<template if={{cbServiceClicked}}> <template if={{cbServiceClicked}}>
<style> <style>
a { a, core-submenu {
text-decoration: underline; text-decoration: underline;
cursor: pointer; cursor: pointer;
} }
@ -33,7 +35,7 @@
<core-menu selected="0"> <core-menu selected="0">
<template repeat="{{serv in services}}"> <template repeat="{{serv in services}}">
<core-submenu icon="settings" label="{{serv.domain}}"> <core-submenu icon="{{serv.domain | getIcon}}" label="{{serv.domain}}">
<template repeat="{{service in serv.services}}"> <template repeat="{{service in serv.services}}">
<a on-click={{serviceClicked}} data-domain={{serv.domain}}>{{service}}</a> <a on-click={{serviceClicked}} data-domain={{serv.domain}}>{{service}}</a>
</template> </template>
@ -55,6 +57,10 @@
this.api.addEventListener('services-updated', this.servicesUpdated.bind(this)) this.api.addEventListener('services-updated', this.servicesUpdated.bind(this))
}, },
getIcon: function(domain) {
return (new DomainIcon).icon(domain);
},
servicesUpdated: function() { servicesUpdated: function() {
this.services = this.api.services; this.services = this.api.services;
}, },

View File

@ -64,11 +64,20 @@
}, },
domReady: function() { domReady: function() {
document.getElementById('init').remove();
if(this.auth) { if(this.auth) {
this.validatePassword(); this.validatePassword();
} }
}, },
authChanged: function(oldVal, newVal) {
// log out functionality
if(newVal == "" && this.state == "valid_auth") {
this.state = "no_auth";
}
},
passwordKeyup: function(ev) { passwordKeyup: function(ev) {
if(ev.keyCode == 13) { if(ev.keyCode == 13) {
this.validatePassword(); this.validatePassword();

View File

@ -2,7 +2,7 @@
<link rel="import" href="domain-icon.html"> <link rel="import" href="domain-icon.html">
<polymer-element name="state-badge" attributes="domain state"> <polymer-element name="state-badge" attributes="stateObj" noscript>
<template> <template>
<style> <style>
:host { :host {
@ -12,6 +12,7 @@
color: white; color: white;
border-radius: 23px; border-radius: 23px;
} }
div { div {
height: 45px; height: 45px;
text-align: center; text-align: center;
@ -23,12 +24,9 @@
</style> </style>
<div horizontal layout center> <div horizontal layout center>
<domain-icon domain="{{domain}}"></domain-icon> <domain-icon domain="{{stateObj.domain}}" state="{{stateObj.state}}">
</domain-icon>
</div> </div>
</template> </template>
<script>
Polymer({
});
</script>
</polymer-element> </polymer-element>

View File

@ -7,7 +7,7 @@
<link rel="import" href="state-badge.html"> <link rel="import" href="state-badge.html">
<polymer-element name="state-card" <polymer-element name="state-card"
attributes="entity state last_changed state_attr cb_turn_on, cb_turn_off cb_edit"> attributes="stateObj cb_turn_on, cb_turn_off cb_edit">
<template> <template>
<style> <style>
:host { :host {
@ -80,43 +80,44 @@
<div class="entity"> <div class="entity">
<state-badge <state-badge
id="badge" id="badge"
domain="{{domain}}" stateObj="{{stateObj}}"
state="{{state}}" data-domain="{{stateObj.domain}}"
data-domain="{{domain}}" data-state="{{stateObj.state}}"
data-state="{{state}}"
on-click="{{editClicked}}"> on-click="{{editClicked}}">
</state-badge> </state-badge>
<div class='info'> <div class='info'>
<div class='name'> <div class='name'>
<template if="{{state_attr['friendly_name']}}">{{state_attr['friendly_name']}}</template> <template if="{{state_attr['friendly_name']}}">
<template if="{{!state_attr['friendly_name']}}">{{entity_id | makeReadable}}</template> {{state_attr['friendly_name']}}
</template>
<template if="{{!state_attr['friendly_name']}}">
{{stateObj.entityDisplay}}
</template>
</div> </div>
<div class="time-ago"> <div class="time-ago">
<core-tooltip label="{{last_changed}}" position="bottom"> <core-tooltip label="{{last_changed}}" position="bottom">
{{last_changed_from_now}} {{lastChangedFromNow}}
</core-tooltip> </core-tooltip>
</div> </div>
</div> </div>
</div> </div>
<template if="{{!state_unknown}}"> <template if="{{!stateUnknown}}">
<template if="{{state == 'on' || state == 'off'}}"> <template if="{{stateObj.state == 'on' || stateObj.state == 'off'}}">
<div class='state toggle' self-center flex> <div class='state toggle' self-center flex>
<paper-toggle-button checked="{{toggleChecked}}"> <paper-toggle-button checked="{{toggleChecked}}">
</paper-toggle-button> </paper-toggle-button>
</div> </div>
</template> </template>
<template if="{{state != 'on' && state != 'off'}}"> <template if="{{stateObj.state != 'on' && stateObj.state != 'off'}}">
<div class='state text'> <div class='state text'>{{stateObj.stateDisplay}}</div>
{{state | makeReadable}}
</div>
</template> </template>
</template> </template>
<template if="{{state_unknown}}"> <template if="{{stateUnknown}}">
<div class="state" self-center flex>Updating..</div> <div class="state" self-center flex>Updating..</div>
</template> </template>
@ -126,28 +127,20 @@
<script> <script>
Polymer({ Polymer({
// attributes // attributes
entity: "", stateObj: {},
state: "",
last_changed: "never",
state_attr: {},
cb_turn_on: null, cb_turn_on: null,
cb_turn_off: null, cb_turn_off: null,
cb_edit: null, cb_edit: null,
state_unknown: false, stateUnknown: false,
toggleChecked: -1, toggleChecked: -1,
computed: { computed: {
domain: "entity | parseDomain", lastChangedFromNow: "stateObj.last_changed | parseLastChangedFromNow",
entity_id: "entity | parseEntityId",
last_changed_from_now: "last_changed | parseLastChangedFromNow"
}, },
parseDomain: function(entity) { observe: {
return entity.split('.')[0]; 'stateObj.state': 'stateChanged'
},
parseEntityId: function(entity) {
return entity.split('.')[1];
}, },
parseLastChangedFromNow: function(lastChanged) { parseLastChangedFromNow: function(lastChanged) {
@ -160,24 +153,26 @@
return; return;
} }
if(newVal && this.state == "off") { if(newVal && this.stateObj.state == "off") {
this.turn_on(); this.turn_on();
} else if(!newVal && this.state == "on") { } else if(!newVal && this.stateObj.state == "on") {
this.turn_off(); this.turn_off();
} }
}, },
stateChanged: function(oldVal, newVal) { stateChanged: function(oldVal, newVal) {
this.state_unknown = newVal == null; this.stateUnknown = newVal == null;
this.toggleChecked = newVal == "on" this.toggleChecked = newVal == "on"
// for domain light, set color of icon to light color if available var state = this.stateObj;
if(this.domain == "light" && newVal == "on" &&
this.state_attr.brightness && this.state_attr.xy_color) {
var rgb = this.xyBriToRgb(this.state_attr.xy_color[0], // for domain light, set color of icon to light color if available
this.state_attr.xy_color[1], if(state.domain == "light" && newVal == "on" &&
this.state_attr.brightness); state.attributes.brightness && state.attributes.xy_color) {
var rgb = this.xyBriToRgb(state.attributes.xy_color[0],
state.attributes.xy_color[1],
state.attributes.brightness);
this.$.badge.style.color = "rgb(" + rgb.map(Math.floor).join(",") + ")"; this.$.badge.style.color = "rgb(" + rgb.map(Math.floor).join(",") + ")";
} else { } else {
this.$.badge.style.color = null; this.$.badge.style.color = null;
@ -186,11 +181,11 @@
turn_on: function() { turn_on: function() {
if(this.cb_turn_on) { if(this.cb_turn_on) {
this.cb_turn_on(this.entity); this.cb_turn_on(this.stateObj.entity_id);
// unset state while we wait for an update // unset state while we wait for an update
var delayUnsetSate = function() { var delayUnsetSate = function() {
this.state = null; this.stateObj.state = null;
} }
setTimeout(delayUnsetSate.bind(this), 500); setTimeout(delayUnsetSate.bind(this), 500);
} }
@ -198,11 +193,11 @@
turn_off: function() { turn_off: function() {
if(this.cb_turn_off) { if(this.cb_turn_off) {
this.cb_turn_off(this.entity); this.cb_turn_off(this.stateObj.entity_id);
// unset state while we wait for an update // unset state while we wait for an update
var delayUnsetSate = function() { var delayUnsetSate = function() {
this.state = null; this.stateObj.state = null;
} }
setTimeout(delayUnsetSate.bind(this), 500); setTimeout(delayUnsetSate.bind(this), 500);
} }
@ -210,20 +205,7 @@
editClicked: function() { editClicked: function() {
if(this.cb_edit) { if(this.cb_edit) {
this.cb_edit(this.entity); this.cb_edit(this.stateObj.entity_id);
}
},
// used as filter
makeReadable: function(value) {
if(typeof value == "string") {
return value.replace(/_/g, " ");
} else if(Array.isArray(value)) {
return value.join(", ");
} else {
return value;
} }
}, },

View File

@ -24,10 +24,7 @@
<div horizontal layout wrap> <div horizontal layout wrap>
<template repeat="{{state in states}}"> <template repeat="{{state in states}}">
<state-card <state-card
entity="{{state.entity_id}}" stateObj="{{state}}"
state="{{state.state}}"
last_changed="{{state.last_changed}}"
state_attr="{{state.attributes}}"
cb_turn_on="{{api.turn_on}}" cb_turn_on="{{api.turn_on}}"
cb_turn_off="{{api.turn_off}}" cb_turn_off="{{api.turn_off}}"
cb_edit={{editCallback}}> cb_edit={{editCallback}}>