Merge pull request #4 from balloob/ui-2015

Revamp how states are displayed
This commit is contained in:
Paulus Schoutsen 2015-08-23 01:30:14 -07:00
commit 0035b14304
32 changed files with 756 additions and 135 deletions

View File

@ -11,20 +11,20 @@
"bower_components"
],
"devDependencies": {
"polymer": "Polymer/polymer#^1.0.0",
"webcomponentsjs": "Polymer/webcomponentsjs#^0.7",
"polymer": "Polymer/polymer#^1.1.0",
"webcomponentsjs": "Polymer/webcomponentsjs#^0.7.12",
"paper-header-panel": "PolymerElements/paper-header-panel#^1.0.0",
"paper-toolbar": "PolymerElements/paper-toolbar#^1.0.0",
"paper-menu": "PolymerElements/paper-menu#^1.0.0",
"iron-input": "PolymerElements/iron-input#^1.0.0",
"iron-icons": "PolymerElements/iron-icons#^1.0.0",
"paper-toolbar": "PolymerElements/paper-toolbar#^1.0.4",
"paper-menu": "PolymerElements/paper-menu#^1.1.0",
"iron-input": "PolymerElements/iron-input#^1.0.5",
"iron-icons": "PolymerElements/iron-icons#^1.0.3",
"iron-image": "PolymerElements/iron-image#^1.0.0",
"paper-toast": "PolymerElements/paper-toast#^1.0.0",
"paper-dialog": "PolymerElements/paper-dialog#^1.0.0",
"paper-dialog-scrollable": "polymerelements/paper-dialog-scrollable#^1.0.0",
"paper-spinner": "PolymerElements/paper-spinner#^1.0.0",
"paper-button": "PolymerElements/paper-button#^1.0.0",
"paper-input": "PolymerElements/paper-input#^1.0.0",
"paper-input": "PolymerElements/paper-input#^1.0.12",
"paper-toggle-button": "PolymerElements/paper-toggle-button#^1.0.0",
"paper-icon-button": "PolymerElements/paper-icon-button#^1.0.0",
"paper-item": "PolymerElements/paper-item#^1.0.0",
@ -34,7 +34,7 @@
"paper-scroll-header-panel": "polymerelements/paper-scroll-header-panel#^1.0.0",
"google-apis": "GoogleWebComponents/google-apis#0.8-preview",
"layout": "Polymer/layout",
"paper-styles": "polymerelements/paper-styles#^1.0.0",
"paper-styles": "polymerelements/paper-styles#^1.0.11",
"pikaday": "~1.3.2"
},
"resolutions": {

View File

@ -16,20 +16,20 @@
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
"license": "MIT",
"dependencies": {
"home-assistant-js": "git+https://github.com/balloob/home-assistant-js.git#7bc70c0b75c274166ddba0d647356b6d0b7d696e",
"home-assistant-js": "git+https://github.com/balloob/home-assistant-js.git#66517cdf9d8ff1aed2f8653b8dc5fadc096b406d",
"lodash": "^3.10.1",
"moment": "^2.10.6"
},
"devDependencies": {
"babel-core": "^5.8.21",
"babel-eslint": "^4.0.5",
"babel-core": "^5.8.22",
"babel-eslint": "^4.0.10",
"babel-loader": "^5.3.2",
"bower": "^1.4.1",
"eslint": "^1.1.0",
"eslint-config-airbnb": "0.0.7",
"eslint-plugin-react": "^3.2.1",
"eslint": "^1.2.1",
"eslint-config-airbnb": "0.0.8",
"eslint-plugin-react": "^3.2.3",
"html-minifier": "^0.7.2",
"vulcanize": "^1.10.2",
"vulcanize": "^1.10.3",
"webpack": "^1.11.0"
}
}

View File

@ -0,0 +1,13 @@
<link rel='import' href='../../bower_components/polymer/polymer.html'>
<link rel='import' href='../components/entity/ha-state-label-badge.html'>
<dom-module id='ha-badges-card'>
<style>
</style>
<template>
<template is='dom-repeat' items='[[states]]'>
<ha-state-label-badge state='[[item]]'></ha-state-label-badge>
</template>
</template>
</dom-module>

View File

@ -0,0 +1,13 @@
import Polymer from '../polymer';
require('../components/entity/ha-state-label-badge');
export default new Polymer({
is: 'ha-badges-card',
properties: {
states: {
type: Array,
},
},
});

View File

@ -0,0 +1,35 @@
<link rel='import' href='../../bower_components/polymer/polymer.html'>
<link rel='import' href='../components/ha-card.html'>
<link rel='import' href='../state-summary/state-card-content.html'>
<dom-module id='ha-domain-card'>
<style>
.domain-title {
font-size: 24px;
padding: 24px 16px 16px;
border-bottom: 2px solid #E8E8E8;
text-transform: capitalize;
}
.states {
padding: 8px 0;
}
.state {
padding: 8px 16px;
}
state-card-content {
cursor: pointer;
}
</style>
<template>
<ha-card title='[[computeDomainTitle(domain)]]'>
<div class='states'>
<template is='dom-repeat' items="[[states]]">
<div class='state'>
<state-card-content class="state-card" state-obj="[[item]]" on-tap='entityTapped'></state-card-content>
</div>
</template>
</div>
</ha-card>
</template>
</dom-module>

View File

@ -0,0 +1,28 @@
import Polymer from '../polymer';
import { moreInfoActions } from '../util/home-assistant-js-instance';
require('../components/ha-card');
require('../state-summary/state-card-content');
export default new Polymer({
is: 'ha-domain-card',
properties: {
domain: {
type: String,
},
states: {
type: Array,
},
},
computeDomainTitle(domain) {
return domain.replace(/_/g, ' ');
},
entityTapped(ev) {
ev.stopPropagation();
const entityId = ev.currentTarget.stateObj.entityId;
this.async(() => moreInfoActions.selectEntity(entityId), 1);
},
});

View File

@ -0,0 +1,17 @@
<link rel='import' href='../../bower_components/polymer/polymer.html'>
<link rel='import' href='../components/ha-card.html'>
<dom-module id='ha-getting-started-card'>
<template>
<ha-card>
<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>
</ha-card>
</template>
</dom-module>

View File

@ -0,0 +1,7 @@
import Polymer from '../polymer';
require('../components/ha-card');
export default new Polymer({
is: 'ha-getting-started-card',
});

View File

@ -0,0 +1,22 @@
<link rel='import' href='../../../bower_components/polymer/polymer.html'>
<link rel='import' href='../../../bower_components/iron-image/iron-image.html'>
<link rel='import' href='../domain-icon.html'>
<dom-module id='ha-state-domain-icon'>
<style>
/* Color the icon if light or sun is on */
domain-icon[data-domain=light][data-state=on],
domain-icon[data-domain=switch][data-state=on],
domain-icon[data-domain=sun][data-state=above_horizon] {
color: #fff176;
}
</style>
<template>
<domain-icon id='icon'
domain='[[stateObj.domain]]' data-domain$='[[stateObj.domain]]'
state='[[stateObj.state]]' data-state$='[[stateObj.state]]'>
</domain-icon>
</template>
</dom-module>

View File

@ -0,0 +1,33 @@
import Polymer from '../../polymer';
import xyBriToRgb from '../../util/xybri-to-rgb';
require('./domain-icon');
export default new Polymer({
is: 'ha-state-domain-icon',
properties: {
stateObj: {
type: Object,
observer: 'updateIconColor',
},
},
/**
* Called when an attribute changes that influences the color of the icon.
*/
updateIconColor(newVal) {
// for domain light, set color of icon to light color if available
if (newVal.domain === 'light' && newVal.state === 'on' &&
newVal.attributes.brightness && newVal.attributes.xy_color) {
const rgb = xyBriToRgb(newVal.attributes.xy_color[0],
newVal.attributes.xy_color[1],
newVal.attributes.brightness);
this.$.icon.style.color = 'rgb(' + rgb.map(Math.floor).join(',') + ')';
} else {
this.$.icon.style.color = null;
}
},
});

View File

@ -0,0 +1,32 @@
<link rel='import' href='../../../bower_components/polymer/polymer.html'>
<link rel='import' href='../ha-label-badge.html'>
<dom-module id='ha-state-label-badge'>
<style>
:host {
cursor: pointer;
}
ha-label-badge {
--ha-label-badge-color: rgb(223, 76, 30);
}
.blue {
--ha-label-badge-color: #039be5;
}
.grey {
--ha-label-badge-color: var(--paper-grey-500);
}
</style>
<template>
<ha-label-badge class$='[[computeClasses(state)]]'
value='[[computeValue(state)]]'
icon='[[computeIcon(state)]]'
image='[[computeImage(state)]]'
label='[[computeLabel(state)]]'
description='[[computeDescription(state)]]'
></ha-label-badge>
</template>
</dom-module>

View File

@ -0,0 +1,116 @@
import Polymer from '../../polymer';
import {
moreInfoActions,
serviceActions,
} from '../../util/home-assistant-js-instance';
import domainIcon from '../../util/domain-icon';
import canToggle from '../../util/can-toggle';
require('../../components/ha-label-badge');
export default new Polymer({
is: 'ha-state-label-badge',
properties: {
state: {
type: Object,
observer: 'stateChanged',
},
},
listeners: {
'tap': 'badgeTap',
},
badgeTap(ev) {
ev.stopPropagation();
if (!canToggle(this.state.entityId)) {
this.async(() => moreInfoActions.selectEntity(this.state.entityId), 1);
return;
}
if (this.state.domain === 'scene' && this.state.state === 'on' &&
!this.state.attributes.active_requested) {
// Scenes that are on but by virtue of other events then itself
// being turned on cannot be turned off.
return;
} else if (this.state.state === 'off') {
serviceActions.callTurnOn(this.state.entityId);
} else {
serviceActions.callTurnOff(this.state.entityId);
}
},
computeClasses(state) {
switch (state.domain) {
case 'scene':
case 'script':
return state.state === 'on' ? 'blue' : 'grey';
default:
return '';
}
},
computeGlow(state) {
switch (state.domain) {
case 'scene':
case 'script':
return state.state === 'on';
default:
return false;
}
},
computeValue(state) {
switch (state.domain) {
case 'device_tracker':
case 'sun':
case 'scene':
case 'script':
return undefined;
default:
return state.state;
}
},
computeIcon(state) {
switch (state.domain) {
case 'scene':
case 'script':
return domainIcon(state.domain);
case 'sun':
return state.state === 'above_horizon' ?
'image:wb-sunny' : 'image:brightness-3';
default:
return undefined;
}
},
computeImage(state) {
switch (state.domain) {
case 'device_tracker':
return state.attributes.entity_picture;
default:
return undefined;
}
},
computeLabel(state) {
switch (state.domain) {
case 'scene':
case 'script':
return state.domain;
case 'device_tracker':
return state.state === 'home' ? 'Home' : 'Away';
default:
return state.attributes.unit_of_measurement;
}
},
computeDescription(state) {
return state.entityDisplay;
},
stateChanged() {
this.updateStyles();
},
});

View File

@ -1,7 +1,7 @@
<link rel='import' href='../../bower_components/polymer/polymer.html'>
<link rel='import' href='../../bower_components/iron-image/iron-image.html'>
<link rel='import' href='../../../bower_components/polymer/polymer.html'>
<link rel='import' href='../../../bower_components/iron-image/iron-image.html'>
<link rel='import' href='domain-icon.html'>
<link rel='import' href='../domain-icon.html'>
<dom-module id='state-badge'>
<style>

View File

@ -1,8 +1,8 @@
import Polymer from '../polymer';
import Polymer from '../../polymer';
import xyBriToRgb from '../util/xybri-to-rgb';
import xyBriToRgb from '../../util/xybri-to-rgb';
require('./domain-icon');
require('../domain-icon');
export default new Polymer({
is: 'state-badge',

View File

@ -0,0 +1,35 @@
<link rel='import' href='../../bower_components/polymer/polymer.html'>
<dom-module id='ha-card'>
<style>
.title {
padding: 8px 0;
text-transform: uppercase;
}
.card {
display: block;
border-radius: 2px;
box-shadow: rgba(0, 0, 0, 0.098) 0px 2px 4px, rgba(0, 0, 0, 0.098) 0px 0px 3px;
transition: all 0.30s ease-out;
background-color: white;
}
.header {
font-size: 24px;
padding: 24px 16px 16px;
border-bottom: 2px solid #E8E8E8;
text-transform: capitalize;
}
</style>
<template>
<template is='dom-if' if='[[title]]'>
<div class='title'>[[title]]</div>
</template>
<div class='card'>
<template is='dom-if' if='[[header]]'>
<div class='header'>[[header]]</div>
</template>
<content></content>
</div>
</template>
</dom-module>

14
src/components/ha-card.js Normal file
View File

@ -0,0 +1,14 @@
import Polymer from '../polymer';
export default new Polymer({
is: 'ha-card',
properties: {
title: {
type: String,
},
header: {
type: String,
},
},
});

View File

@ -0,0 +1,89 @@
<link rel='import' href='../../bower_components/polymer/polymer.html'>
<link rel='import' href='../../bower_components/layout/layout.html'>
<link rel='import' href='../../bower_components/iron-image/iron-image.html'>
<link rel='import' href='../../bower_components/iron-icon/iron-icon.html'>
<dom-module id='ha-label-badge'>
<style>
.badge-container {
display: inline-block;
text-align: center;
vertical-align: top;
margin-bottom: 16px;
}
.label-badge {
position: relative;
display: block;
margin: 0 auto;
width: 2.5em;
text-align: center;
height: 2.5em;
line-height: 2.5em;
font-size: 1.5em;
border-radius: 50%;
border: 0.1em solid var(--ha-label-badge-color, --default-primary-color);
color: rgb(76, 76, 76);
white-space: nowrap;
background-color: white;
transition: border .3s ease-in-out;
}
.label-badge .value {
overflow: hidden;
text-overflow: ellipsis;
}
.label-badge .label {
position: absolute;
bottom: -1em;
left: 0;
right: 0;
line-height: 1em;
font-size: 0.5em;
}
.label-badge .label span {
max-width: 80%;
display: inline-block;
background-color: var(--ha-label-badge-color, --default-primary-color);
color: white;
border-radius: 1em;
padding: 4px 8px;
font-weight: 400;
text-transform: uppercase;
overflow: hidden;
text-overflow: ellipsis;
transition: background-color .3s ease-in-out;
}
.badge-container .title {
margin-top: 1em;
font-size: .9em;
width: 5em;
font-weight: 300;
overflow: hidden;
text-overflow: ellipsis;
}
iron-image {
border-radius: 50%;
}
</style>
<template>
<div class='badge-container'>
<div class='label-badge' id='badge'>
<div class='value'>
<template is='dom-if' if='[[icon]]'>
<iron-icon icon='[[icon]]'></iron-icon>
</template>
<template is='dom-if' if='[[value]]'>[[value]]</template>
<template is='dom-if' if='[[image]]'>
<iron-image sizing='cover' class='fit'src='[[image]]'></iron-image>
</template>
</div>
<template is='dom-if' if='[[label]]'>
<div class='label'><span>[[label]]</span></div>
</template>
</div>
<div class='title'>[[description]]</div>
</div>
</template>
</dom-module>

View File

@ -0,0 +1,28 @@
import Polymer from '../polymer';
export default new Polymer({
is: 'ha-label-badge',
properties: {
value: {
type: String,
},
icon: {
type: String,
},
label: {
type: String,
},
description: {
type: String,
},
image: {
type: String,
observe: 'imageChanged',
},
},
});

View File

@ -3,9 +3,6 @@
<link rel='import' href='../../bower_components/paper-header-panel/paper-header-panel.html'>
<link rel='import' href='../../bower_components/paper-toolbar/paper-toolbar.html'>
<!--
Too broken for now.
<link rel='import' href='../../bower_components/paper-menu/paper-menu.html'> -->
<link rel='import' href='../../bower_components/iron-icon/iron-icon.html'>
<link rel='import' href='../../bower_components/paper-item/paper-item.html'>
<link rel='import' href='../../bower_components/paper-item/paper-icon-item.html'>
@ -27,11 +24,6 @@ Too broken for now.
-moz-user-select: none;
}
/*.sidenav paper-menu {
--paper-menu-color: var(--secondary-text-color);
--paper-menu-background-color: #fafafa;
}*/
div.title {
text-align: left;
}
@ -74,20 +66,11 @@ Too broken for now.
<paper-icon-button hidden></paper-icon-button>
<div class="title">Home Assistant</div>
</paper-toolbar>
<!-- <paper-menu id='menu' selected='{{menuSelected}}'
selectable='[data-panel]' attr-for-selected='data-panel'> -->
<div class='menu'>
<paper-icon-item on-click='menuClicked' data-panel='states'>
<iron-icon item-icon icon='apps'></iron-icon> States
</paper-icon-item>
<template is='dom-repeat' items='{{possibleFilters}}'>
<paper-icon-item on-click='menuClicked' data-panel$='[[filterType(item)]]'>
<iron-icon item-icon icon='[[filterIcon(item)]]'></iron-icon>
<span>[[filterName(item)]]</span>
</paper-icon-item>
</template>
<template is='dom-if' if='[[hasHistoryComponent]]'>
<paper-icon-item on-click='menuClicked' data-panel='history'>
<iron-icon item-icon icon='assessment'></iron-icon>
@ -124,7 +107,6 @@ Too broken for now.
icon='settings-input-antenna' data-panel='devEvent'
on-click='handleDevClick'></paper-icon-button>
</div>
<!-- </paper-menu> -->
</div>
</paper-header-panel>

View File

@ -3,17 +3,13 @@ import {
navigationGetters,
authActions,
navigationActions,
util
} from '../util/home-assistant-js-instance';
import Polymer from '../polymer';
import nuclearObserver from '../util/bound-nuclear-behavior';
import domainIcon from '../util/domain-icon';
require('./stream-status');
const { entityDomainFilters } = util;
export default new Polymer({
is: 'ha-sidebar',
@ -22,24 +18,14 @@ export default new Polymer({
properties: {
menuSelected: {
type: String,
// observer: 'menuSelectedChanged',
},
selected: {
type: String,
bindNuclear: navigationGetters.activePage,
bindNuclear: navigationGetters.activePane,
observer: 'selectedChanged',
},
possibleFilters: {
type: Array,
value: [],
bindNuclear: [
navigationGetters.possibleEntityDomainFilters,
(domains) => domains.toArray(),
],
},
hasHistoryComponent: {
type: Boolean,
bindNuclear: configGetters.isComponentLoaded('history'),
@ -51,16 +37,7 @@ export default new Polymer({
},
},
// menuSelectedChanged: function(newVal) {
// if (this.selected !== newVal) {
// this.selectPanel(newVal);
// }
// },
selectedChanged(newVal) {
// if (this.menuSelected !== newVal) {
// this.menuSelected = newVal;
// }
const menuItems = this.querySelectorAll('.menu [data-panel]');
for (let i = 0; i < menuItems.length; i++) {
@ -106,16 +83,4 @@ export default new Polymer({
handleLogOut() {
authActions.logOut();
},
filterIcon(filter) {
return domainIcon(filter);
},
filterName(filter) {
return entityDomainFilters[filter];
},
filterType(filter) {
return 'states/' + filter;
},
});

View File

@ -0,0 +1,76 @@
<link rel="import" href="../../bower_components/polymer/polymer.html">
<link rel="import" href="../cards/ha-badges-card.html">
<link rel="import" href="../cards/ha-domain-card.html">
<dom-module id="ha-zone-cards">
<style>
:host {
display: block;
padding-right: 8px;
}
.badges {
margin-top: 8px;
margin-left: 8px;
font-size: 80%;
text-align: center;
}
.zone-card {
margin-left: 8px;
margin-bottom: 8px;
}
</style>
<template>
<div class='main'>
<template is='dom-if' if='domains.badges'>
<div class='badges'>
<ha-badges-card states='[[cards._badges]]'></ha-badges-card>
</div>
</template>
<div class='horizontal layout'>
<div class='column flex-1'>
<template is='dom-repeat' items='[[cards._columns.0]]'>
<div class='zone-card'>
<ha-domain-card domain='[[item]]'
states='[[computeStatesOfCard(cards, item)]]'></ha-domain-card>
</div>
</template>
</div>
<template is='dom-if' if='[[cards._columns.1]]'>
<div class='column flex-1'>
<template is='dom-repeat' items='[[cards._columns.1]]'>
<div class='zone-card'>
<ha-domain-card domain='[[item]]'
states='[[computeStatesOfCard(cards, item)]]'></ha-domain-card>
</div>
</template>
</div>
</template>
<template is='dom-if' if='[[cards._columns.2]]'>
<div class='column flex-1'>
<template is='dom-repeat' items='[[cards._columns.2]]'>
<div class='zone-card'>
<ha-domain-card domain='[[item]]'
states='[[computeStatesOfCard(cards, item)]]'></ha-domain-card>
</div>
</template>
</div>
</template>
<template is='dom-if' if='[[cards._columns.3]]'>
<div class='column flex-1'>
<template is='dom-repeat' items='[[cards._columns.3]]'>
<div class='zone-card'>
<ha-domain-card domain='[[item]]'
states='[[computeStatesOfCard(cards, item)]]'></ha-domain-card>
</div>
</template>
</div>
</template>
</div>
</div>
</template>
</dom-module>

View File

@ -0,0 +1,97 @@
import Polymer from '../polymer';
import { util } from '../util/home-assistant-js-instance';
require('../cards/ha-domain-card');
require('../cards/ha-badges-card');
const PRIORITY = {
a: -1,
sun: 0,
device_tracker: 1,
sensor: 2,
scene: 3,
script: 4,
configurator: 10,
group: 20,
thermostat: 40,
media_player: 50,
camera: 60,
};
function getPriority(domain) {
return (domain in PRIORITY) ? PRIORITY[domain] : 30;
}
function entityDomainMap(entityMap) {
return entityMap.groupBy(entity => entity.domain);
}
export default new Polymer({
is: 'ha-zone-cards',
properties: {
columns: {
type: Number,
value: 2,
},
states: {
type: Object,
},
cards: {
type: Object,
computed: 'computeDomains(columns, states)',
},
},
computeDomains(columns, states) {
const byDomain = entityDomainMap(states);
const hasGroup = {};
const cards = {
_demo: false,
_badges: [],
_columns: {},
};
for (let i = 0; i < columns; i++) { cards._columns[i] = []; }
let index = 0;
function pushCard(name, entities, filterGrouped = true) {
const filtered = filterGrouped ?
entities.filter(entity => !(entity.entityId in hasGroup)) :
entities;
if (filtered.length === 0) {
return;
}
cards._columns[index].push(name);
index = (index + 1) % columns;
cards[name] = filtered;
}
byDomain.keySeq().sortBy(domain => getPriority(domain))
.forEach(domain => {
if (domain === 'a') {
cards._demo = true;
} else if (getPriority(domain) < 10) {
cards._badges.push.apply(cards._badges, byDomain.get(domain).toArray());
} else if (domain === 'group') {
byDomain.get(domain).filter(st => !st.attributes.auto)
.forEach(groupState => {
const entities = util.expandGroup(groupState, states);
entities.forEach(entity => hasGroup[entity.entityId] = true);
pushCard(groupState.entityDisplay, entities, false);
}
);
} else {
pushCard(domain, byDomain.get(domain).toArray());
}
}
);
return cards;
},
computeStatesOfCard(cards, card) {
return cards[card];
},
});

View File

@ -126,7 +126,7 @@ export default new Polymer({
let timeIndex = 1;
const endDate = new Date();
for (i = 0; i < times.length; i++) {
for (let i = 0; i < times.length; i++) {
// because we only have state changes we add an extra point at the same time
// that holds the previous state which makes the line display correctly
const beforePoint = new Date(times[i]);

View File

@ -1,6 +1,6 @@
<link rel="import" href="../../bower_components/polymer/polymer.html">
<link rel="import" href="state-badge.html">
<link rel="import" href="./entity/state-badge.html">
<link rel="import" href="relative-ha-datetime.html">
<dom-module id="state-info">

View File

@ -1,6 +1,6 @@
import Polymer from '../polymer';
require('./state-badge');
require('./entity/state-badge');
require('./relative-ha-datetime');
export default new Polymer({

View File

@ -2,7 +2,7 @@
<link rel='import' href='../../bower_components/layout/layout.html'>
<link rel='import' href='../../bower_components/paper-drawer-panel/paper-drawer-panel.html'>
<link rel='import' href='../layouts/partial-states.html'>
<link rel='import' href='../layouts/partial-zone.html'>
<link rel='import' href='../layouts/partial-logbook.html'>
<link rel='import' href='../layouts/partial-history.html'>
<link rel='import' href='../layouts/partial-dev-call-service.html'>
@ -22,8 +22,7 @@
<ha-sidebar drawer></ha-sidebar>
<template is='dom-if' if='[[isSelectedStates]]'>
<partial-states main narrow='[[narrow]]'>
</partial-states>
<partial-zone main narrow='[[narrow]]'></partial-zone>
</template>
<template is='dom-if' if='[[isSelectedLogbook]]'>
<partial-logbook main narrow='[[narrow]]'></partial-logbook>

View File

@ -7,7 +7,7 @@ import {
import nuclearObserver from '../util/bound-nuclear-behavior';
require('../components/ha-sidebar');
require('../layouts/partial-states');
require('../layouts/partial-zone');
require('../layouts/partial-logbook');
require('../layouts/partial-history');
require('../layouts/partial-dev-call-service');
@ -26,10 +26,10 @@ export default new Polymer({
type: Boolean,
},
activePage: {
activePane: {
type: String,
bindNuclear: navigationGetters.activePage,
observer: 'activePageChanged',
bindNuclear: navigationGetters.activePane,
observer: 'activePaneChanged',
},
isSelectedStates: {
@ -71,7 +71,7 @@ export default new Polymer({
this.$.drawer.openDrawer();
},
activePageChanged() {
activePaneChanged() {
this.$.drawer.closeDrawer();
},

View File

@ -2,15 +2,20 @@
<link rel="import" href="../../bower_components/paper-icon-button/paper-icon-button.html">
<link rel="import" href="./partial-base.html">
<link rel="import" href="../components/state-cards.html">
<link rel="import" href="../components/ha-voice-command-progress.html">
<link rel="import" href="../components/ha-zone-cards.html">
<dom-module id="partial-states">
<dom-module id="partial-zone">
<style>
.root {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.content-wrapper {
position: relative;
height: 100%;
overflow: scroll;
background-color: #E5E5E5;
}
@ -31,10 +36,11 @@
</style>
<template>
<partial-base narrow="[[narrow]]">
<span header-title>[[computeHeaderTitle(filter)]]</span>
<div class='root layout vertical'>
<paper-toolbar>
<paper-icon-button icon='menu' hidden$='[[!narrow]]' on-click='toggleMenu'></paper-icon-button>
<div class='title'>States</div>
<span header-buttons>
<paper-icon-button
icon="refresh"
class$="[[computeRefreshButtonClass(isFetching)]]"
@ -44,25 +50,17 @@
icon="[[computeListenButtonIcon(isListening)]]"
hidden$='[[!canListen]]'
on-click="handleListenClick"></paper-icon-button>
</span>
</paper-toolbar>
<div class='content-wrapper'>
<div class='content-wrapper flex'>
<div class='listening' hidden$="[[!showListenInterface]]"
on-click="handleListenClick">
<ha-voice-command-progress></ha-voice-command-progress>
</div>
<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>
<ha-zone-cards states='[[states]]' columns='[[columns]]'></ha-zone-cards>
</div>
</partial-base>
</div>
</template>
</dom-module>

View File

@ -1,26 +1,22 @@
import {
configGetters,
navigationGetters,
entityGetters,
voiceGetters,
streamGetters,
serviceGetters,
syncGetters,
syncActions,
voiceActions,
util
} from '../util/home-assistant-js-instance';
import Polymer from '../polymer';
import nuclearObserver from '../util/bound-nuclear-behavior';
const { entityDomainFilters } = util;
require('./partial-base');
require('../components/state-cards');
require('../components/ha-voice-command-progress');
require('../components/ha-zone-cards');
export default new Polymer({
is: 'partial-states',
is: 'partial-zone',
behaviors: [nuclearObserver],
@ -30,11 +26,6 @@ export default new Polymer({
value: false,
},
filter: {
type: String,
bindNuclear: navigationGetters.activeFilter,
},
isFetching: {
type: Boolean,
bindNuclear: syncGetters.isFetching,
@ -69,15 +60,38 @@ export default new Polymer({
},
states: {
type: Array,
bindNuclear: [
navigationGetters.filteredStates,
// are here so a change to services causes a re-render.
// we need this to decide if we show toggles for states.
serviceGetters.entityMap,
(states) => states.toArray(),
],
type: Object,
bindNuclear: entityGetters.visibleEntityMap,
},
columns: {
type: Number,
},
},
created() {
this.windowChange = this.windowChange.bind(this);
},
attached() {
const sizes = [];
for (let i = 0; i < 4; i++) {
sizes.push(940 + i * 350);
}
this.mqls = sizes.map(width => {
const mql = window.matchMedia(`(min-width: ${width}px)`);
mql.addListener(this.windowChange);
return mql;
});
this.windowChange();
},
detached() {
this.mqls.forEach(mql => mql.removeListener(this.windowChange));
},
windowChange() {
this.columns = this.mqls.reduce((cols, mql) => cols + mql.matches, 1);
},
handleRefresh() {
@ -92,8 +106,12 @@ export default new Polymer({
}
},
computeHeaderTitle(filter) {
return filter ? entityDomainFilters[filter] : 'States';
computeDomains(states) {
return states.keySeq().toArray();
},
computeStatesOfDomain(states, domain) {
return states.get(domain).toArray();
},
computeListenButtonIcon(isListening) {

9
src/util/can-toggle.js Normal file
View File

@ -0,0 +1,9 @@
import {
reactor,
serviceGetters
} from '../util/home-assistant-js-instance';
// Return boolean if entity can be toggled.
export default function canToggle(entityId) {
return reactor.evaluate(serviceGetters.canToggleEntity(entityId));
}

View File

@ -14,11 +14,6 @@ export default function NuclearObserver(reactor) {
this[key] = reactor.evaluate(getter);
return unwatchFns.concat(reactor.observe(getter, (val) => {
if (__DEV__) {
/* eslint-disable no-console */
console.log(this, key, val);
/* eslint-enable no-console */
}
this[key] = val;
}));
}, []);

View File

@ -1,4 +1,4 @@
import { reactor, serviceGetters } from '../util/home-assistant-js-instance';
import canToggle from './can-toggle';
const DOMAINS_WITH_CARD = [
'thermostat', 'configurator', 'scene', 'media_player'];
@ -6,7 +6,7 @@ const DOMAINS_WITH_CARD = [
export default function stateCardType(state) {
if (DOMAINS_WITH_CARD.indexOf(state.domain) !== -1) {
return state.domain;
} else if (reactor.evaluate(serviceGetters.canToggleEntity(state.entityId))) {
} else if (canToggle(state.entityId)) {
return 'toggle';
}
return 'display';