More material love for interface

This commit is contained in:
Paulus Schoutsen 2014-10-29 00:47:55 -07:00
parent 3bab3f4be1
commit 30ba447c64
14 changed files with 2567 additions and 816 deletions

View File

@ -9,9 +9,11 @@ Bare minimum what is needed for a component to be valid.
DOMAIN = "example"
# List of component names (string) your component depends upon
# If you are setting up a group but not using a group for anything, don't depend on group
# If you are setting up a group but not using a group for anything,
# don't depend on group
DEPENDENCIES = []
# pylint: disable=unused-argument
def setup(hass, config):
""" Register services or listen for events that your component needs. """

View File

@ -143,7 +143,8 @@ class DeviceTracker(object):
self.update_devices()
group.setup_group(hass, GROUP_NAME_ALL_DEVICES, self.device_entity_ids)
group.setup_group(
hass, GROUP_NAME_ALL_DEVICES, self.device_entity_ids, False)
@property
def device_entity_ids(self):

View File

@ -17,6 +17,8 @@ DEPENDENCIES = []
ENTITY_ID_FORMAT = DOMAIN + ".{}"
ATTR_AUTO = "auto"
_GROUP_TYPES = {
"on_off": (STATE_ON, STATE_OFF),
"home_not_home": (STATE_HOME, STATE_NOT_HOME)
@ -100,7 +102,7 @@ def setup(hass, config):
# pylint: disable=too-many-branches
def setup_group(hass, name, entity_ids):
def setup_group(hass, name, entity_ids, user_defined=True):
""" Sets up a group state that is the combined state of
several states. Supports ON/OFF and DEVICE_HOME/DEVICE_NOT_HOME. """
@ -161,7 +163,7 @@ def setup_group(hass, name, entity_ids):
else:
group_entity_id = ENTITY_ID_FORMAT.format(name)
state_attr = {ATTR_ENTITY_ID: entity_ids}
state_attr = {ATTR_ENTITY_ID: entity_ids, ATTR_AUTO: not user_defined}
# pylint: disable=unused-argument
def update_group_state(entity_id, old_state, new_state):

View File

@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_polymer script """
VERSION = "460fa7f075841b858b102678f13fb070"
VERSION = "57f41262ccbd90c2e988e5be0a5dab59"

File diff suppressed because it is too large Load Diff

View File

@ -29,6 +29,9 @@
"moment": "~2.8.3",
"paper-input": "Polymer/paper-input#~0.4.2",
"core-menu": "Polymer/core-menu#~0.4.2",
"core-item": "Polymer/core-item#~0.4.2"
"core-item": "Polymer/core-item#~0.4.2",
"core-icons": "polymer/core-icons#~0.4.2",
"paper-toggle-button": "polymer/paper-toggle-button#~0.4.2",
"paper-tabs": "polymer/paper-tabs#~0.4.2"
}
}

View File

@ -0,0 +1,47 @@
<link rel="import" href="bower_components/polymer/polymer.html">
<link rel="import" href="bower_components/core-icon/core-icon.html">
<link rel="import" href="bower_components/core-icons/device-icons.html">
<link rel="import" href="bower_components/core-icons/social-icons.html">
<link rel="import" href="bower_components/core-icons/image-icons.html">
<link rel="import" href="bower_components/core-icons/hardware-icons.html">
<polymer-element name="domain-icon" attributes="domain">
<template>
<core-icon icon="{{icon(domain)}}"></core-icon>
</template>
<script>
Polymer({
icon: function() {
switch(this.domain) {
case "group":
return "social:communities";
case "device_tracker":
return "social:person";
case "wemo":
return "settings-input-svideo";
case "chromecast":
// hardware:cast-connected
return "hardware:cast";
case "process":
return "hardware:memory"
case "sun":
return "device:brightness-low"
case "light":
return "image:wb-incandescent"
default:
return "bookmark-outline";
}
}
});
</script>
</polymer-element>

View File

@ -153,6 +153,12 @@
}
},
getCustomGroups: function() {
return this.states.filter(function(state) {
return state.entity_id.lastIndexOf("group.") == 0;
})
},
turn_on: function(entity_id) {
this.call_service("homeassistant", "turn_on", {entity_id: entity_id});
},

View File

@ -2,6 +2,8 @@
<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="bower_components/paper-tabs/paper-tabs.html">
<link rel="import" href="bower_components/paper-tabs/paper-tab.html">
<link rel="import" href="home-assistant-api.html">
<link rel="import" href="states-cards.html">
@ -25,6 +27,7 @@
background: #03a9f4;
font-size: 1.4rem;
color: white;
height: 95px;
}
.content {
@ -51,10 +54,25 @@
<core-icon-button icon="refresh" on-click="{{handleRefreshClick}}"></core-icon-button>
<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>
<div class="bottom fit" horizontal layout>
<paper-tabs id="tabsHolder" noink flex
selected="0" on-core-select="{{tabClicked}}">
<paper-tab>ALL</paper-tab>
<template repeat="{{state in api.states}}">
<template if="{{isCustomGroup(state)}}">
<paper-tab data-entity="{{state.entity_id}}">{{state.entity_id | groupName}}</paper-tab>
</template>
</template>
</paper-tabs>
</div>
</core-toolbar>
<div class="content" flex>
<states-cards api="{{api}}"></states-cards>
<states-cards api="{{api}}" filter="{{selectedTab}}"></states-cards>
<paper-fab icon="add" on-click={{handleAddStateClick}}></paper-fab>
</div>
@ -63,11 +81,28 @@
</template>
<script>
Polymer({
selectedTab: null,
ready: function() {
this.api = this.$.api;
},
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) {
if(ev.detail.isSelected) {
// will be null for ALL tab
this.selectedTab = ev.detail.item.getAttribute('data-entity');
}
},
handleRefreshClick: function() {
this.api.fetchStates();
},

View File

@ -0,0 +1,34 @@
<link rel="import" href="bower_components/polymer/polymer.html">
<link rel="import" href="domain-icon.html">
<polymer-element name="state-badge" attributes="domain state">
<template>
<style>
:host {
display: inline-block;
width: 45px;
background-color: #4fc3f7;
color: white;
border-radius: 23px;
}
div {
height: 45px;
text-align: center;
}
domain-icon {
margin: 0 auto;
}
</style>
<div horizontal layout center>
<domain-icon domain="{{domain}}"></domain-icon>
</div>
</template>
<script>
Polymer({
});
</script>
</polymer-element>

View File

@ -2,104 +2,106 @@
<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">
<link rel="import" href="bower_components/paper-toggle-button/paper-toggle-button.html">
<link rel="import" href="state-badge.html">
<polymer-element name="state-card"
attributes="entity state last_changed state_attr cb_turn_on, cb_turn_off cb_edit">
<template>
<style>
:host {
background-color: #fff;
border-radius: 2px;
box-shadow: rgba(0, 0, 0, 0.098) 0px 2px 4px, rgba(0, 0, 0, 0.098) 0px 0px 3px;
/* transition */
-webkit-transition: all 0.30s ease-out;
transition: all 0.30s ease-out;
position: relative;
background-color: white;
padding: 20px 20px 55px 20px;
padding: 15px;
width: 100%;
border-radius: 2px;
}
.header {
text-transform: capitalize;
font-weight: 300;
state-badge {
float: left;
}
.name, .state.text {
text-transform: capitalize;
font-weight: 300;
font-size: 1.5rem;
}
.header .state {
.state {
text-align: right;
}
.subheader {
margin-top: -5px;
.info {
margin-left: 60px;
}
.time-ago {
color: darkgrey;
margin-top: -2px;
}
.state-attributes {
margin-top: 10px;
font-size: 1rem;
/* the splash while enabling */
paper-toggle-button::shadow paper-radio-button::shadow #ink[checked] {
color: #0091ea;
}
.state-attributes .key {
white-space: nowrap;
width: 85px;
float: left;
clear: left;
overflow: hidden;
text-overflow: ellipsis;
}
.state-attributes .value {
margin-left: 95px;
}
.actions {
position: absolute;
bottom: 10px;
left: 20px;
right: 20px;
text-align: right;
}
paper-button.toggle {
color: #03a9f4;
/* filling of circle when checked */
paper-toggle-button::shadow paper-radio-button::shadow #onRadio {
background-color: #0091ea;
}
/* line when checked */
paper-toggle-button::shadow #toggleBar[checked] {
background-color: #0091ea;
}
</style>
<div class="header" horizontal justified layout>
<span class="entity_id">
<template if="{{state_attr['friendly_name']}}">{{state_attr['friendly_name']}}</template>
<template if="{{!state_attr['friendly_name']}}">{{entity_id | makeReadable}}</template>
</span>
<span class='state'>{{state | makeReadable}}</span>
</div>
<div horizontal justified layout>
<div class="entity">
<state-badge
domain="{{domain}}"
state="{{state}}"
on-click="{{editClicked}}">
</state-badge>
<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='info'>
<div class='name'>
<template if="{{state_attr['friendly_name']}}">{{state_attr['friendly_name']}}</template>
<template if="{{!state_attr['friendly_name']}}">{{entity_id | makeReadable}}</template>
</div>
<div class="state-attributes">
<template repeat="{{key in objectKeys(state_attr)}}">
<template if="{{key != 'friendly_name'}}">
<div class='key'>{{key | makeReadable}}</div>
<div class='value'>{{state_attr[key] | makeReadable}}</div>
</template>
<div class="time-ago">
<core-tooltip label="{{last_changed}}" position="bottom">
{{last_changed_from_now}}
</core-tooltip>
</div>
</div>
</div>
<template if="{{state == 'on' || state == 'off'}}">
<div class='state toggle' self-center flex>
<paper-toggle-button
id="toggleButton"
on-change="{{toggle}}">
</paper-toggle-button>
</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 if="{{state != 'on' && state != 'off'}}">
<div class='state text'>
{{state | makeReadable}}
</div>
</template>
<template if="{{state == 'off'}}">
<paper-button class="toggle" on-click="{{turn_on}}">TURN ON</paper-button>
</template>
</div>
</div>
</template>
<script>
@ -117,6 +119,12 @@
domain: "",
entity_id: "",
stateChanged: function() {
if(this.$.toggleButton) {
this.$.toggleButton.checked = this.state == 'on';
}
},
entityChanged: function(oldVal, newVal) {
var parts = newVal.split(".")
@ -133,6 +141,14 @@
this.last_changed_from_now = moment(this.last_changed, "HH:mm:ss DD-MM-YYYY").fromNow()
},
toggle: function(ev) {
if(this.$.toggleButton.checked) {
this.turn_on();
} else {
this.turn_off();
}
},
turn_on: function() {
if(this.cb_turn_on) {
this.cb_turn_on(this.entity);
@ -162,10 +178,6 @@
} else {
return value;
}
},
objectKeys: function(obj) {
return obj ? Object.keys(obj) : [];
}
});
</script>

View File

@ -1,7 +1,7 @@
<link rel="import" href="bower_components/polymer/polymer.html">
<link rel="import" href="state-card.html">
<polymer-element name="states-cards" attributes="api">
<polymer-element name="states-cards" attributes="api filter">
<template>
<style>
:host {
@ -22,6 +22,17 @@
</style>
<div horizontal layout wrap>
<template if="{{filter != null}}">
<state-card
entity="{{filter_state.entity_id}}"
state="{{filter_state.state}}"
last_changed="{{filter_state.last_changed}}"
state_attr="{{filter_state.attributes}}"
cb_turn_on="{{api.turn_on}}"
cb_turn_off="{{api.turn_off}}"
cb_edit={{editCallback}}>
</state-card>
</template>
<template repeat="{{state in states}}">
<state-card
@ -39,7 +50,15 @@
</template>
<script>
Polymer({
raw_states: [],
states: [],
filter: null,
filter_state: null,
filter_substates: null,
filterChanged: function(oldVal, newVal) {
this.refilterStates();
},
ready: function() {
this.editCallback = this.editCallback.bind(this);
@ -52,7 +71,24 @@
},
statesUpdated: function() {
this.states = this.api.states;
this.raw_states = this.api.states;
this.refilterStates();
},
refilterStates: function() {
if(this.filter == null) {
this.filter_state = null;
this.states = this.raw_states;
} else {
this.filter_state = this.api.getState(this.filter);
var map_states = function(entity_id) {
return this.api.getState(entity_id);
}.bind(this)
this.states = this.filter_state.attributes.entity_id.map(map_states)
}
},
editCallback: function(entityId) {

View File

@ -224,7 +224,8 @@ def setup(hass, config):
return False
# Track all lights in a group
group.setup_group(hass, GROUP_NAME_ALL_LIGHTS, light_to_ent.values())
group.setup_group(
hass, GROUP_NAME_ALL_LIGHTS, light_to_ent.values(), False)
# Load built-in profiles and custom profiles
profile_paths = [os.path.join(os.path.dirname(__file__),

View File

@ -139,8 +139,8 @@ def setup(hass, config):
update_wemos_state(None, True)
# Track all lights in a group
group.setup_group(hass, GROUP_NAME_ALL_WEMOS, sno_to_ent.values())
# Track all wemos in a group
group.setup_group(hass, GROUP_NAME_ALL_WEMOS, sno_to_ent.values(), False)
def handle_wemo_service(service):
""" Handles calls to the WeMo service. """