Skip to content
Snippets Groups Projects
Commit 8a8097af authored by Paulus Schoutsen's avatar Paulus Schoutsen
Browse files

Initial commit Polymer interface

parent a0c12fe6
No related branches found
No related tags found
No related merge requests found
Showing
with 993 additions and 0 deletions
{
"name": "Home Assistant",
"version": "0.1.0",
"authors": [
"Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>"
],
"main": "index.htm",
"license": "MIT",
"private": true,
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"polymer": "Polymer/polymer#~0.4.2",
"font-roboto": "Polymer/font-roboto#~0.4.2",
"core-header-panel": "Polymer/core-header-panel#~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",
"core-ajax": "Polymer/core-ajax#~0.4.2",
"paper-toast": "Polymer/paper-toast#~0.4.2",
"paper-dialog": "Polymer/paper-dialog#~0.4.2",
"paper-button": "Polymer/paper-button#~0.4.2",
"core-tooltip": "Polymer/core-tooltip#~0.4.2"
}
}
<link rel="import" href="bower_components/polymer/polymer.html">
<polymer-element name="entity-list" attributes="api cbEntityClicked">
<template>
<style>
:host {
display: block;
}
.entityContainer {
font-size: 1rem;
}
</style>
<template if={{cbEntityClicked}}>
<style>
a {
text-decoration: underline;
cursor: pointer;
}
</style>
</template>
<div>
<template repeat="{{state in states}}">
<div class='eventContainer'>
<a on-click={{handleClick}}>{{state.entity_id}}</a>
</div>
</template>
</div>
</template>
<script>
Polymer({
cbEventClicked: null,
states: [],
domReady: function() {
this.api.addEventListener('states-updated', this.statesUpdated.bind(this))
this.statesUpdated()
},
statesUpdated: function() {
this.states = this.api.states;
},
handleClick: function(ev) {
if(this.cbEntityClicked) {
this.cbEntityClicked(ev.path[0].innerHTML);
}
},
});
</script>
</polymer-element>
<link rel="import" href="bower_components/polymer/polymer.html">
<link rel="import" href="bower_components/paper-dialog/paper-dialog.html">
<link rel="import" href="bower_components/paper-dialog/paper-dialog-transition.html">
<link rel="import" href="bower_components/paper-button/paper-button.html">
<link rel="import" href="bower_components/paper-input/paper-input.html">
<link rel="import" href="events-list.html">
<polymer-element name="event-fire-dialog" attributes="api">
<template>
<style>
paper-input:first-child {
padding-top: 0;
}
.eventContainer {
margin-left: 30px;
}
</style>
<paper-dialog id="dialog" heading="Fire Event" transition="paper-dialog-transition-bottom" backdrop="true">
<div layout horizontal>
<div>
<paper-input id="inputType" label="Event Type" floatingLabel="true" autofocus required></paper-input>
<paper-input id="inputData" label="Event Data (JSON, optional)" floatingLabel="true" multiline></paper-input>
</div>
<div class='eventContainer'>
<b>Available events:</b>
<events-list api={{api}} cbEventClicked={{eventSelected}}></event-list>
</div>
</div>
<paper-button dismissive>Cancel</paper-button>
<paper-button affirmative on-click={{clickFireEvent}}>Fire Event</paper-button>
</paper-dialog>
</template>
<script>
Polymer({
ready: function() {
// to ensure callback methods work..
this.eventSelected = this.eventSelected.bind(this)
},
show: function(eventType, eventData) {
this.setEventType(eventType);
this.setEventData(eventData);
this.$.dialog.toggle();
},
setEventType: function(eventType) {
this.$.inputType.value = eventType;
},
setEventData: function(eventData) {
this.$.inputData.value = eventData;
},
eventSelected: function(eventType) {
this.setEventType(eventType);
},
clickFireEvent: function() {
this.api.fire_event(
this.$.inputType.value,
this.$.inputData.value
)
}
});
</script>
</polymer-element>
<link rel="import" href="bower_components/polymer/polymer.html">
<polymer-element name="events-list" attributes="api cbEventClicked">
<template>
<style>
:host {
display: block;
}
.eventContainer {
font-size: 1rem;
}
</style>
<template if={{cbEventClicked}}>
<style>
a {
text-decoration: underline;
cursor: pointer;
}
</style>
</template>
<div>
<template repeat="{{event in events}}">
<div class='eventContainer'>
<a on-click={{handleClick}}>{{event.event}}</a>
({{event.listener_count}} listeners)
</div>
</template>
</div>
</template>
<script>
Polymer({
cbEventClicked: null,
events: [],
domReady: function() {
this.events = this.api.events
this.api.addEventListener('events-updated', this.eventsUpdated.bind(this))
},
eventsUpdated: function() {
this.events = this.api.events;
},
handleClick: function(ev) {
if(this.cbEventClicked) {
this.cbEventClicked(ev.path[0].innerHTML);
}
},
});
</script>
</polymer-element>
<link rel="import" href="bower_components/polymer/polymer.html">
<link rel="import" href="bower_components/core-ajax/core-ajax.html">
<link rel="import" href="bower_components/paper-toast/paper-toast.html">
<link rel="import" href="event-fire-dialog.html">
<link rel="import" href="service-call-dialog.html">
<link rel="import" href="state-set-dialog.html">
<polymer-element name="home-assistant-api" attributes="auth">
<template>
<style>
core-ajax {
display: none;
}
</style>
<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="stateDialog" api={{api}}></state-set-dialog>
<core-ajax id="statesAjax"
auto
method="GET"
url="/api/states"
headers='{"HA-access": "{{auth}}"}'
on-core-response="{{statesLoaded}}"
handleAs="json">
</core-ajax>
<core-ajax id="eventsAjax"
auto
method="GET"
url="/api/events"
headers='{"HA-access": "{{auth}}"}'
on-core-response="{{eventsLoaded}}"
handleAs="json">
</core-ajax>
<core-ajax id="servicesAjax"
auto
method="GET"
url="/api/services"
headers='{"HA-access": "{{auth}}"}'
on-core-response="{{servicesLoaded}}"
handleAs="json">
</core-ajax>
</template>
<script>
Polymer({
auth: "",
states: [],
services: {},
events: {},
stateUpdateTimeout: null,
created: function() {
this.api = this;
// 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);
},
_laterFetchStates: function() {
if(this.stateUpdateTimeout) {
clearTimeout(this.stateUpdateTimeout);
}
// update states in 60 seconds
this.stateUpdateTimeout = setTimeout(this.fetchStates.bind(this), 60000);
},
_sortStates: function(states) {
return 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;
}
})
},
statesLoaded: function() {
// Make a copy of the loaded data
this.states = this._sortStates(this.$.statesAjax.response.slice(0));
this.fire('states-updated')
this._laterFetchStates();
},
eventsLoaded: function() {
// Make a copy of the loaded data
this.events = this.$.eventsAjax.response;
this.fire('events-updated')
},
servicesLoaded: function() {
// Make a copy of the loaded data
this.services = this.$.servicesAjax.response;
this.fire('services-updated')
},
_pushNewState: function(new_state) {
var state;
var stateFound = false;
for(var i = 0; i < this.states.length; i++) {
if(this.states[i].entity_id == new_state.entity_id) {
state = this.states[i];
state.attributes = new_state.attributes;
state.last_changed = new_state.last_changed;
state.state = new_state.state;
stateFound = true;
break;
}
}
if(!stateFound) {
this.states.push(new_state);
this._sortStates(this.states);
}
},
fetchState: function(entity_id) {
var successStateUpdate = function(new_state) {
this._pushNewState(new_state);
}
this.call_api("GET", "states/" + entity_id, null, successStateUpdate.bind(this));
},
fetchStates: function() {
this.$.statesAjax.go();
},
getState: function(entityId) {
for(var i = 0; i < this.states.length; i++) {
if(this.states[i].entity_id == entityId) {
return this.states[i];
}
}
},
turn_on: function(entity_id) {
this.call_service("homeassistant", "turn_on", {entity_id: entity_id});
},
turn_off: function(entity_id) {
this.call_service("homeassistant", "turn_off", {entity_id: entity_id})
},
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+" successful set to "+state+".");
this._pushNewState(new_state);
}
this.call_api("POST", "states/" + entity_id,
payload, successToast.bind(this));
},
call_service: function(domain, service, parameters) {
var successToast = function() {
this.showToast("Service "+domain+"/"+service+" successful called.");
}
this.call_api("POST", "services/" + domain + "/" + service,
parameters, successToast.bind(this));
},
fire_event: function(eventType, eventData) {
eventData = eventData ? JSON.parse(eventData) : "";
var successToast = function() {
this.showToast("Event "+eventType+" successful fired.");
}
this.call_api("POST", "events/" + eventType,
eventData, successToast.bind(this));
},
call_api: function(method, path, parameters, callback) {
var req = new XMLHttpRequest();
req.open(method, "/api/" + path, true)
req.setRequestHeader("HA-access", this.auth);
req.onreadystatechange = function() {
if(req.readyState == 4 && req.status > 199 && req.status < 300) {
if(callback) {
callback(JSON.parse(req.responseText))
}
// if we targetted an entity id, update state after 2 seconds
if(parameters && parameters.entity_id) {
var updateCallback;
// if a string, update just that entity, otherwise update all
if(typeof(parameters.entity_id) == "string") {
updateCallback = function() {
this.fetchState(parameters.entity_id);
}
} else {
updateCallback = this.fetchStates();
}
setTimeout(updateCallback.bind(this), 2000);
}
}
}.bind(this)
if(parameters) {
req.send(JSON.stringify(parameters))
} else {
req.send()
}
},
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.$.stateDialog.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();
}
});
</script>
</polymer-element>
<link rel="import" href="bower_components/font-roboto/roboto.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-button/core-icon-button.html">
<link rel="import" href="bower_components/paper-fab/paper-fab.html">
<link rel="import" href="home-assistant-api.html">
<link rel="import" href="states-cards.html">
<polymer-element name="home-assistant-main" attributes="auth">
<template>
<style type="text/css">
:host {
font-family: 'RobotoDraft', sans-serif;
}
core-header-panel {
height: 100%;
overflow: auto;
-webkit-overflow-scrolling: touch;
}
core-toolbar {
background: #03a9f4;
font-size: 1.5rem;
color: white;
}
paper-fab {
position: absolute;
bottom: 10px;
right: 10px;
}
</style>
<home-assistant-api auth="{{auth}}" id="api"></home-assistant-api>
<core-header-panel layout>
<core-toolbar>
<div flex>
Home Assistant
</div>
<core-icon-button icon="developer-mode-tv" on-click="{{handleEventClick}}"></core-icon-button>
<core-icon-button icon="settings-remote" on-click="{{handleServiceClick}}"></core-icon-button>
</core-toolbar>
<div class="content" flex>
<states-cards api="{{api}}"></states-cards>
<paper-fab icon="add" on-click={{handleAddStateClick}}></paper-fab>
</div>
</core-header-panel>
</template>
<script>
Polymer({
ready: function() {
this.api = this.$.api;
},
handleEventClick: function() {
this.api.showFireEventDialog();
},
handleServiceClick: function() {
this.api.showCallServiceDialog();
},
handleAddStateClick: function() {
this.api.showSetStateDialog();
}
});
</script>
</polymer-element>
<link rel="import" href="bower_components/polymer/polymer.html">
<link rel="import" href="bower_components/paper-dialog/paper-dialog.html">
<link rel="import" href="bower_components/paper-dialog/paper-dialog-transition.html">
<link rel="import" href="bower_components/paper-button/paper-button.html">
<link rel="import" href="bower_components/paper-input/paper-input.html">
<link rel="import" href="services-list.html">
<polymer-element name="service-call-dialog" attributes="api">
<template>
<style>
paper-input:first-child {
padding-top: 0;
}
.serviceContainer {
margin-left: 30px;
}
</style>
<paper-dialog id="dialog" heading="Call Service" transition="paper-dialog-transition-bottom" backdrop="true">
<div layout horizontal>
<div>
<paper-input id="inputDomain" label="Domain" floatingLabel="true" autofocus required></paper-input>
<paper-input id="inputService" label="Service" floatingLabel="true" required></paper-input>
<paper-input id="inputData" label="Service Data (JSON, optional)" floatingLabel="true" multiline></paper-input>
</div>
<div class='serviceContainer'>
<b>Available services:</b>
<services-list api={{api}} cbServiceClicked={{serviceSelected}}></event-list>
</div>
</div>
<paper-button dismissive>Cancel</paper-button>
<paper-button affirmative on-click={{clickCallService}}>Call Service</paper-button>
</paper-dialog>
</template>
<script>
Polymer({
ready: function() {
// to ensure callback methods work..
this.serviceSelected = this.serviceSelected.bind(this)
},
show: function(domain, service, serviceData) {
this.setService(domain, service);
this.$.inputData.value = serviceData;
this.$.dialog.toggle();
},
setService: function(domain, service) {
this.$.inputDomain.value = domain;
this.$.inputService.value = service;
},
serviceSelected: function(domain, service) {
this.setService(domain, service);
},
clickCallService: function() {
this.api.call_service(
this.$.inputDomain.value,
this.$.inputService.value,
this.$.inputData.value
)
}
});
</script>
</polymer-element>
<link rel="import" href="bower_components/polymer/polymer.html">
<link rel="import" href="bower_components/core-menu/core-menu.html">
<link rel="import" href="bower_components/core-menu/core-submenu.html">
<link rel="import" href="bower_components/core-item/core-item.html">
<polymer-element name="services-list" attributes="api cbServiceClicked">
<template>
<style>
:host {
display: block;
}
core-menu {
margin-top: 0;
font-size: 1rem;
}
a {
display: block;
}
</style>
<template if={{cbServiceClicked}}>
<style>
a {
text-decoration: underline;
cursor: pointer;
}
</style>
</template>
<div>
<core-menu selected="0">
<template repeat="{{serv in services}}">
<core-submenu icon="settings" label="{{serv.domain}}">
<template repeat="{{service in serv.services}}">
<a on-click={{serviceClicked}} data-domain={{serv.domain}}>{{service}}</a>
</template>
</core-submenu>
</template>
</core-menu>
</div>
</template>
<script>
Polymer({
services: [],
cbServiceClicked: null,
domReady: function() {
this.services = this.api.services
this.api.addEventListener('services-updated', this.servicesUpdated.bind(this))
},
servicesUpdated: function() {
this.services = this.api.services;
},
serviceClicked: function(ev) {
if(this.cbServiceClicked) {
var target = ev.path[0];
var domain = target.getAttributeNode("data-domain").value;
var service = target.innerHTML;
this.cbServiceClicked(domain, service);
}
}
});
</script>
</polymer-element>
<script src="bower_components/moment/moment.js"></script>
<link rel="import" href="bower_components/polymer/polymer.html">
<link rel="import" href="bower_components/core-tooltip/core-tooltip.html">
<link rel="import" href="bower_components/paper-button/paper-button.html">
<polymer-element name="state-card"
attributes="entity state last_changed state_attr cb_turn_on, cb_turn_off cb_edit">
<template>
<style>
:host {
position: relative;
background-color: white;
padding: 20px 20px 50px 20px;
width: 100%;
font-weight: 300;
border-radius: 2px;
}
.header {
text-transform: capitalize;
font-size: 1.5rem;
}
.subheader {
margin-top: -5px;
color: darkgrey;
}
.content {
margin-top: 10px;
}
.actions {
position: absolute;
bottom: 10px;
left: 20px;
right: 20px;
text-align: right;
}
paper-button.toggle {
color: #03a9f4;
}
</style>
<div class="header" horizontal justified layout>
<span class="entity_id">{{entity_id | makeReadable}}</span>
<span class='state'>{{state | makeReadable}}</span>
</div>
<div class="subheader" horizontal justified layout>
<span class="domain">{{domain}}</span>
<core-tooltip label="{{last_changed}}" position="bottom">
<span class="last_changed_from_now">{{last_changed_from_now}}</span>
</core-tooltip>
</div>
<div class="content">
<template repeat="{{key in objectKeys(state_attr)}}">
<div>{{key | makeReadable}}: {{state_attr[key]}}</div>
</template>
</div>
<div class="actions">
<paper-button class='edit' on-click="{{editClicked}}">EDIT</paper-button>
<template if="{{state == 'on'}}">
<paper-button class="toggle" on-click="{{turn_off}}">TURN OFF</paper-button>
</template>
<template if="{{state == 'off'}}">
<paper-button class="toggle" on-click="{{turn_on}}">TURN ON</paper-button>
</template>
</div>
</template>
<script>
Polymer({
// attributes
entity: "",
state: "",
last_changed: "never",
state_attr: {},
cb_turn_on: null,
cb_turn_off: null,
cb_edit: null,
// computed
domain: "",
entity_id: "",
entityChanged: function(oldVal, newVal) {
var parts = newVal.split(".")
if(parts.length == 1) {
this.domain = ""
this.entity_id = parts[0]
} else {
this.domain = parts[0]
this.entity_id = parts.slice(1).join('.')
}
},
last_changedChanged: function(oldVal, newVal) {
this.last_changed_from_now = moment(this.last_changed, "HH:mm:ss DD-MM-YYYY").fromNow()
},
turn_on: function() {
if(this.cb_turn_on) {
this.cb_turn_on(this.entity);
}
},
turn_off: function() {
if(this.cb_turn_off) {
this.cb_turn_off(this.entity);
}
},
editClicked: function() {
if(this.cb_edit) {
this.cb_edit(this.entity);
}
},
// used as filter
makeReadable: function(value) {
return value.replace("_", " ")
},
objectKeys: function(obj) {
return obj ? Object.keys(obj) : [];
}
});
</script>
</polymer-element>
<link rel="import" href="bower_components/polymer/polymer.html">
<link rel="import" href="bower_components/paper-dialog/paper-dialog.html">
<link rel="import" href="bower_components/paper-dialog/paper-dialog-transition.html">
<link rel="import" href="bower_components/paper-button/paper-button.html">
<link rel="import" href="bower_components/paper-input/paper-input.html">
<link rel="import" href="entity-list.html">
<polymer-element name="state-set-dialog" attributes="api">
<template>
<style>
paper-input:first-child {
padding-top: 0;
}
.stateContainer {
margin-left: 30px;
}
</style>
<paper-dialog id="dialog" heading="Set State" transition="paper-dialog-transition-center" backdrop="true">
<div layout horizontal>
<div>
<paper-input id="inputEntityID" label="Entity ID" floatingLabel="true" autofocus required></paper-input>
<paper-input id="inputState" label="State" floatingLabel="true" required></paper-input>
<paper-input id="inputData" label="State attributes (JSON, optional)" floatingLabel="true" multiline></paper-input>
</div>
<div class='stateContainer'>
<b>Current entities:</b>
<entity-list api={{api}} cbEntityClicked={{entitySelected}}></entity-list>
</div>
</div>
<paper-button dismissive>Cancel</paper-button>
<paper-button affirmative on-click={{clickSetState}}>Set State</paper-button>
</paper-dialog>
</template>
<script>
Polymer({
ready: function() {
// to ensure callback methods work..
this.entitySelected = this.entitySelected.bind(this)
},
show: function(entityId, state, stateData) {
this.setEntityId(entityId);
this.setState(state);
this.setStateData(stateData);
this.$.dialog.toggle();
},
setEntityId: function(entityId) {
this.$.inputEntityID.value = entityId;
},
setState: function(state) {
this.$.inputState.value = state;
},
setStateData: function(stateData) {
var value = stateData ? JSON.stringify(stateData, null, ' ') : "";
this.$.inputData.value = value;
},
entitySelected: function(entityId) {
this.setEntityId(entityId);
var state = this.api.getState(entityId);
this.setState(state.state);
this.setStateData(state.attributes);
},
clickSetState: function() {
this.api.set_state(
this.$.inputEntityID.value,
this.$.inputState.value,
JSON.parse(this.$.inputData.value)
)
}
});
</script>
</polymer-element>
<link rel="import" href="bower_components/polymer/polymer.html">
<link rel="import" href="state-card.html">
<polymer-element name="states-cards" attributes="api">
<template>
<style>
:host {
display: block;
width: 100%;
}
state-card, state-add-card {
display: inline-block;
width: 350px;
margin: 10px 0 0 10px;
}
state-add-card {
cursor: pointer;
}
</style>
<div horizontal layout wrap>
<template repeat="{{state in states}}">
<state-card
entity="{{state.entity_id}}"
state="{{state.state}}"
last_changed="{{state.last_changed}}"
state_attr="{{state.attributes}}"
cb_turn_on="{{api.turn_on}}"
cb_turn_off="{{api.turn_off}}"
cb_edit={{editCallback}}>
</state-card>
</template>
</div>
</template>
<script>
Polymer({
states: [],
ready: function() {
this.editCallback = this.editCallback.bind(this);
},
domReady: function() {
this.states = this.api.states
this.api.addEventListener('states-updated', this.statesUpdated.bind(this))
},
statesUpdated: function() {
this.states = this.api.states;
},
editCallback: function(entityId) {
this.api.showEditStateDialog(entityId);
},
});
</script>
</polymer-element>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment