mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-13 20:36:35 +00:00
Merge pull request #4 from balloob/ui-2015
Revamp how states are displayed
This commit is contained in:
commit
0035b14304
16
bower.json
16
bower.json
@ -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": {
|
||||
|
14
package.json
14
package.json
@ -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"
|
||||
}
|
||||
}
|
||||
|
13
src/cards/ha-badges-card.html
Normal file
13
src/cards/ha-badges-card.html
Normal 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>
|
13
src/cards/ha-badges-card.js
Normal file
13
src/cards/ha-badges-card.js
Normal 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,
|
||||
},
|
||||
},
|
||||
});
|
35
src/cards/ha-domain-card.html
Normal file
35
src/cards/ha-domain-card.html
Normal 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>
|
28
src/cards/ha-domain-card.js
Normal file
28
src/cards/ha-domain-card.js
Normal 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);
|
||||
},
|
||||
});
|
17
src/cards/ha-getting-started-card.html
Normal file
17
src/cards/ha-getting-started-card.html
Normal 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>
|
7
src/cards/ha-getting-started-card.js
Normal file
7
src/cards/ha-getting-started-card.js
Normal file
@ -0,0 +1,7 @@
|
||||
import Polymer from '../polymer';
|
||||
|
||||
require('../components/ha-card');
|
||||
|
||||
export default new Polymer({
|
||||
is: 'ha-getting-started-card',
|
||||
});
|
22
src/components/entity/ha-state-domain-icon.html
Normal file
22
src/components/entity/ha-state-domain-icon.html
Normal 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>
|
33
src/components/entity/ha-state-domain-icon.js
Normal file
33
src/components/entity/ha-state-domain-icon.js
Normal 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;
|
||||
}
|
||||
},
|
||||
|
||||
});
|
32
src/components/entity/ha-state-label-badge.html
Normal file
32
src/components/entity/ha-state-label-badge.html
Normal 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>
|
116
src/components/entity/ha-state-label-badge.js
Normal file
116
src/components/entity/ha-state-label-badge.js
Normal 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();
|
||||
},
|
||||
});
|
@ -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>
|
@ -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',
|
35
src/components/ha-card.html
Normal file
35
src/components/ha-card.html
Normal 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
14
src/components/ha-card.js
Normal file
@ -0,0 +1,14 @@
|
||||
import Polymer from '../polymer';
|
||||
|
||||
export default new Polymer({
|
||||
is: 'ha-card',
|
||||
|
||||
properties: {
|
||||
title: {
|
||||
type: String,
|
||||
},
|
||||
header: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
});
|
89
src/components/ha-label-badge.html
Normal file
89
src/components/ha-label-badge.html
Normal 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>
|
28
src/components/ha-label-badge.js
Normal file
28
src/components/ha-label-badge.js
Normal 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',
|
||||
},
|
||||
},
|
||||
});
|
@ -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>
|
||||
|
||||
|
@ -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;
|
||||
},
|
||||
});
|
||||
|
76
src/components/ha-zone-cards.html
Normal file
76
src/components/ha-zone-cards.html
Normal 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>
|
97
src/components/ha-zone-cards.js
Normal file
97
src/components/ha-zone-cards.js
Normal 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];
|
||||
},
|
||||
});
|
@ -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]);
|
||||
|
@ -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">
|
||||
|
@ -1,6 +1,6 @@
|
||||
import Polymer from '../polymer';
|
||||
|
||||
require('./state-badge');
|
||||
require('./entity/state-badge');
|
||||
require('./relative-ha-datetime');
|
||||
|
||||
export default new Polymer({
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
},
|
||||
|
||||
|
@ -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>
|
||||
</div>
|
||||
</partial-base>
|
||||
</template>
|
||||
|
||||
</dom-module>
|
@ -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
9
src/util/can-toggle.js
Normal 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));
|
||||
}
|
@ -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;
|
||||
}));
|
||||
}, []);
|
||||
|
@ -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';
|
||||
|
Loading…
x
Reference in New Issue
Block a user