More ES6->ES5 conversion

This commit is contained in:
Paulus Schoutsen 2016-07-17 23:18:48 -07:00
parent ff0e24fecb
commit 111b6c6f48
74 changed files with 1851 additions and 1768 deletions

View File

@ -16,5 +16,8 @@
"no-underscore-dangle": 0,
"no-var": 0,
"strict": 0
}
},
plugins: [
"html"
]
}

View File

@ -24,7 +24,7 @@
"watch_ru_core": "rollup --config rollup/core.js --watch",
"watch_ru_ui": "rollup --config rollup/ui.js --watch",
"watch_ru_demo": "rollup --config rollup/demo.js --watch",
"test": "eslint src"
"test": "eslint src panels --ext html"
},
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
"license": "MIT",
@ -39,8 +39,10 @@
"bower": "^1.7.9",
"eslint": "^3.1.0",
"eslint-config-airbnb-base": "^4.0.2",
"eslint-plugin-import": "^1.10.3",
"eslint-plugin-html": "^1.5.1",
"eslint-plugin-import": "^1.11.0",
"html-minifier": "^3.0.1",
"polymer-cli": "^0.12.0",
"rollup": "^0.34.1",
"rollup-plugin-babel": "^2.6.1",
"rollup-plugin-buble": "^0.12.1",

View File

@ -1,23 +1,23 @@
<!-- <link rel="import" href="../../bower_components/polymer/polymer.html"> -->
<dom-module id="events-list">
<style>
ul {
margin: 0;
padding: 0;
}
li {
list-style: none;
line-height: 2em;
}
a {
color: var(--dark-primary-color);
}
</style>
<template>
<style>
ul {
margin: 0;
padding: 0;
}
li {
list-style: none;
line-height: 2em;
}
a {
color: var(--dark-primary-color);
}
</style>
<ul>
<template is='dom-repeat' items='[[events]]' as='event'>
<li>

View File

@ -10,28 +10,28 @@
<link rel="import" href="./events-list.html">
<dom-module id="ha-panel-dev-event">
<style is="custom-style" include="iron-flex iron-positioning"></style>
<style>
.content {
@apply(--paper-font-body1);
margin-top: 64px;
padding: 24px;
background-color: white;
-ms-user-select: initial;
-webkit-user-select: initial;
-moz-user-select: initial;
}
.ha-form {
margin-right: 16px;
}
.header {
@apply(--paper-font-title);
}
</style>
<template>
<style is="custom-style" include="iron-flex iron-positioning"></style>
<style>
.content {
@apply(--paper-font-body1);
margin-top: 64px;
padding: 24px;
background-color: white;
-ms-user-select: initial;
-webkit-user-select: initial;
-moz-user-select: initial;
}
.ha-form {
margin-right: 16px;
}
.header {
@apply(--paper-font-title);
}
</style>
<partial-base narrow="{{narrow}}" show-menu='[[showMenu]]'>
<span header-title>Events</span>

View File

@ -6,51 +6,52 @@
<link rel="import" href="./partial-base.html">
-->
<dom-module id="ha-panel-dev-info">
<style is="custom-style" include="iron-positioning"></style>
<style>
.content {
margin-top: 64px;
padding: 24px;
background-color: white;
-ms-user-select: initial;
-webkit-user-select: initial;
-moz-user-select: initial;
}
.about {
text-align: center;
line-height: 2em;
}
.version {
@apply(--paper-font-headline);
}
.develop {
@apply(--paper-font-subhead);
}
.about a {
color: var(--dark-primary-color);
}
.error-log-intro {
margin-top: 16px;
border-top: 1px solid var(--light-primary-color);
padding-top: 16px;
}
paper-icon-button {
float: right;
}
.error-log {
@apply(--paper-font-code1)
clear: both;
white-space: pre-wrap;
}
</style>
<template>
<style is="custom-style" include="iron-positioning"></style>
<style>
.content {
margin-top: 64px;
padding: 24px;
background-color: white;
-ms-user-select: initial;
-webkit-user-select: initial;
-moz-user-select: initial;
}
.about {
text-align: center;
line-height: 2em;
}
.version {
@apply(--paper-font-headline);
}
.develop {
@apply(--paper-font-subhead);
}
.about a {
color: var(--dark-primary-color);
}
.error-log-intro {
margin-top: 16px;
border-top: 1px solid var(--light-primary-color);
padding-top: 16px;
}
paper-icon-button {
float: right;
}
.error-log {
@apply(--paper-font-code1)
clear: both;
white-space: pre-wrap;
}
</style>
<partial-base narrow="[[narrow]]" show-menu='[[showMenu]]'>
<span header-title>About</span>

View File

@ -10,32 +10,33 @@
<link rel="import" href="./services-list.html">
<dom-module id="ha-panel-dev-service">
<style is="custom-style" include="iron-flex iron-positioning"></style>
<style>
.content {
@apply(--paper-font-body1);
margin-top: 64px;
padding: 24px;
background-color: white;
-ms-user-select: initial;
-webkit-user-select: initial;
-moz-user-select: initial;
}
.ha-form {
margin-right: 16px;
}
.description {
margin-top: 24px;
white-space: pre-wrap;
}
.header {
@apply(--paper-font-title);
}
</style>
<template>
<style is="custom-style" include="iron-flex iron-positioning"></style>
<style>
.content {
@apply(--paper-font-body1);
margin-top: 64px;
padding: 24px;
background-color: white;
-ms-user-select: initial;
-webkit-user-select: initial;
-moz-user-select: initial;
}
.ha-form {
margin-right: 16px;
}
.description {
margin-top: 24px;
white-space: pre-wrap;
}
.header {
@apply(--paper-font-title);
}
</style>
<partial-base narrow="[[narrow]]" show-menu='[[showMenu]]'>
<span header-title>Services</span>

View File

@ -1,23 +1,23 @@
<!-- <link rel="import" href="../../bower_components/polymer/polymer.html"> -->
<dom-module id="services-list">
<style>
ul {
margin: 0;
padding: 0;
}
li {
list-style: none;
line-height: 2em;
}
a {
color: var(--dark-primary-color);
}
</style>
<template>
<style>
ul {
margin: 0;
padding: 0;
}
li {
list-style: none;
line-height: 2em;
}
a {
color: var(--dark-primary-color);
}
</style>
<ul>
<template is='dom-repeat' items="[[computeDomains(serviceDomains)]]" as="domain">
<template is='dom-repeat' items="[[computeServices(serviceDomains, domain)]]" as="service">

View File

@ -1,23 +1,23 @@
<!-- <link rel="import" href="../../bower_components/polymer/polymer.html"> -->
<dom-module id="entity-list">
<style>
ul {
margin: 0;
padding: 0;
}
li {
list-style: none;
line-height: 2em;
}
a {
color: var(--dark-primary-color);
}
</style>
<template>
<style>
ul {
margin: 0;
padding: 0;
}
li {
list-style: none;
line-height: 2em;
}
a {
color: var(--dark-primary-color);
}
</style>
<ul>
<template is='dom-repeat' items='[[entities]]' as='entity'>
<li><a href='#' on-click='entitySelected'>[[entity.entityId]]</a></li>

View File

@ -10,28 +10,28 @@
<link rel="import" href="./entity-list.html">
<dom-module id="ha-panel-dev-state">
<style is="custom-style" include="iron-flex iron-positioning"></style>
<style>
.content {
@apply(--paper-font-body1);
margin-top: 64px;
padding: 24px;
background-color: white;
-ms-user-select: initial;
-webkit-user-select: initial;
-moz-user-select: initial;
}
.ha-form {
margin-right: 16px;
}
.header {
@apply(--paper-font-title);
}
</style>
<template>
<style is="custom-style" include="iron-flex iron-positioning"></style>
<style>
.content {
@apply(--paper-font-body1);
margin-top: 64px;
padding: 24px;
background-color: white;
-ms-user-select: initial;
-webkit-user-select: initial;
-moz-user-select: initial;
}
.ha-form {
margin-right: 16px;
}
.header {
@apply(--paper-font-title);
}
</style>
<partial-base narrow="[[narrow]]" show-menu='[[showMenu]]'>
<span header-title>States</span>

View File

@ -8,52 +8,53 @@
-->
<dom-module id="ha-panel-dev-template">
<style is="custom-style" include="iron-flex iron-positioning"></style>
<style>
.content {
@apply(--paper-font-body1);
margin-top: 64px;
padding: 16px;
background-color: white;
-ms-user-select: initial;
-webkit-user-select: initial;
-moz-user-select: initial;
}
.edit-pane {
margin-right: 16px;
}
.edit-pane a {
color: var(--dark-primary-color);
}
.horizontal .edit-pane {
max-width: 50%;
}
.render-pane {
position: relative;
max-width: 50%;
}
.render-spinner {
position: absolute;
top: 8px;
right: 8px;
}
.rendered {
@apply(--paper-font-code1)
clear: both;
white-space: pre-wrap;
}
.rendered.error {
color: red;
}
</style>
<template>
<style is="custom-style" include="iron-flex iron-positioning"></style>
<style>
.content {
@apply(--paper-font-body1);
margin-top: 64px;
padding: 16px;
background-color: white;
-ms-user-select: initial;
-webkit-user-select: initial;
-moz-user-select: initial;
}
.edit-pane {
margin-right: 16px;
}
.edit-pane a {
color: var(--dark-primary-color);
}
.horizontal .edit-pane {
max-width: 50%;
}
.render-pane {
position: relative;
max-width: 50%;
}
.render-spinner {
position: absolute;
top: 8px;
right: 8px;
}
.rendered {
@apply(--paper-font-code1)
clear: both;
white-space: pre-wrap;
}
.rendered.error {
color: red;
}
</style>
<partial-base narrow="[[narrow]]" show-menu='[[showMenu]]'>
<span header-title>Template Editor</span>

View File

@ -10,25 +10,26 @@
<link rel="import" href="../../src/resources/pikaday-js.html">
<dom-module id="ha-panel-history">
<style is="custom-style" include="iron-flex"></style>
<style>
.content {
background-color: white;
}
.content.wide {
padding: 8px;
}
paper-input {
max-width: 200px;
}
.narrow paper-input {
margin-left: 8px;
}
</style>
<template>
<style is="custom-style" include="iron-flex"></style>
<style>
.content {
background-color: white;
}
.content.wide {
padding: 8px;
}
paper-input {
max-width: 200px;
}
.narrow paper-input {
margin-left: 8px;
}
</style>
<partial-base narrow="[[narrow]]" show-menu='[[showMenu]]'>
<span header-title>History</span>

View File

@ -1,12 +1,13 @@
<dom-module id='ha-panel-iframe'>
<style>
iframe {
border: 0;
width: 100%;
height: 100%;
}
</style>
<template>
<style>
iframe {
border: 0;
width: 100%;
height: 100%;
}
</style>
<partial-base narrow="[[narrow]]" show-menu='[[showMenu]]'>
<span header-title>[[panel.title]]</span>

View File

@ -5,13 +5,14 @@
<link rel="import" href="./logbook-entry.html">
<dom-module id="ha-logbook">
<style>
:host {
display: block;
padding: 16px;
}
</style>
<template>
<style>
:host {
display: block;
padding: 16px;
}
</style>
<template is='dom-if' if='[[!entries.length]]'>
No logbook entries found.
</template>

View File

@ -14,16 +14,17 @@
<link rel="import" href="../../src/resources/pikaday-js.html">
<dom-module id="ha-panel-logbook">
<style>
.selected-date-container {
padding: 0 16px;
}
paper-input {
max-width: 200px;
}
</style>
<template>
<style>
.selected-date-container {
padding: 0 16px;
}
paper-input {
max-width: 200px;
}
</style>
<partial-base narrow="[[narrow]]" show-menu='[[showMenu]]'>
<span header-title>Logbook</span>

View File

@ -7,39 +7,40 @@
-->
<dom-module id="logbook-entry">
<style is="custom-style" include="iron-flex"></style>
<style>
:host {
@apply(--paper-font-body1);
display: block;
line-height: 2em;
}
display-time {
width: 55px;
font-size: .8em;
color: var(--secondary-text-color);
}
domain-icon {
margin: 0 8px 0 16px;
color: var(--primary-text-color);
}
.name {
text-transform: capitalize;
}
.message {
color: var(--primary-text-color);
}
a {
color: var(--primary-color);
}
</style>
<template>
<style is="custom-style" include="iron-flex"></style>
<style>
:host {
@apply(--paper-font-body1);
display: block;
line-height: 2em;
}
display-time {
width: 55px;
font-size: .8em;
color: var(--secondary-text-color);
}
domain-icon {
margin: 0 8px 0 16px;
color: var(--primary-text-color);
}
.name {
text-transform: capitalize;
}
.message {
color: var(--primary-text-color);
}
a {
color: var(--primary-color);
}
</style>
<div class='horizontal layout'>
<display-time date-obj="[[entryObj.when]]"></display-time>
<domain-icon domain="[[entryObj.domain]]" class='icon'></domain-icon>

View File

@ -5,28 +5,29 @@
-->
<dom-module id='ha-entity-marker'>
<style is="custom-style" include="iron-positioning"></style>
<style>
.marker {
vertical-align: top;
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-marker-color, --default-primary-color);
color: rgb(76, 76, 76);
background-color: white;
}
iron-image {
border-radius: 50%;
}
</style>
<template>
<style is="custom-style" include="iron-positioning"></style>
<style>
.marker {
vertical-align: top;
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-marker-color, --default-primary-color);
color: rgb(76, 76, 76);
background-color: white;
}
iron-image {
border-radius: 50%;
}
</style>
<div class='marker'>
<template is='dom-if' if='[[icon]]'>
<iron-icon icon='[[icon]]'></iron-icon>

View File

@ -16,20 +16,21 @@
.leaflet-top, .leaflet-bottom {
z-index: 0;
}
.menu-icon {
margin-right: 24px;
}
</style>
<dom-module id="ha-panel-map">
<style is="custom-style" include="iron-flex iron-positioning"></style>
<style>
.map {
position: relative;
}
</style>
<template>
<style is="custom-style" include="iron-flex iron-positioning"></style>
<style>
.map {
position: relative;
}
.menu-icon {
margin-right: 24px;
}
</style>
<div class='layout vertical fit'>
<paper-toolbar>
<paper-icon-button icon='mdi:menu' class$='[[computeMenuButtonClass(narrow, showMenu)]]' on-tap='toggleMenu'></paper-icon-button>

View File

@ -11,3 +11,19 @@
</template>
</template>
</dom-module>
<script>
Polymer({
is: 'ha-badges-card',
properties: {
hass: {
type: Object,
},
states: {
type: Array,
},
},
});
</script>

View File

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

View File

@ -46,3 +46,75 @@
</div>
</template>
</dom-module>
<script>
Polymer({
is: 'ha-camera-card',
UPDATE_INTERVAL: 10000, // ms
properties: {
hass: {
type: Object,
},
stateObj: {
type: Object,
observer: 'updateCameraFeedSrc',
},
cameraFeedSrc: {
type: String,
},
imageLoaded: {
type: Boolean,
value: true,
},
/**
* The z-depth of the card, from 0-5.
*/
elevation: {
type: Number,
value: 1,
reflectToAttribute: true,
},
},
listeners: {
tap: 'cardTapped',
},
attached: function () {
this.timer = setInterval(
function () {
this.updateCameraFeedSrc(this.stateObj);
}.bind(this),
this.UPDATE_INTERVAL);
},
detached: function () {
clearInterval(this.timer);
},
cardTapped: function () {
this.async(function () {
this.hass.moreInfoActions.selectEntity(this.stateObj.entityId);
}.bind(this), 1);
},
updateCameraFeedSrc: function (stateObj) {
const attr = stateObj.attributes;
const time = (new Date()).getTime();
this.cameraFeedSrc = attr.entity_picture + '&time=' + time;
},
imageLoadSuccess: function () {
this.imageLoaded = true;
},
imageLoadFail: function () {
this.imageLoaded = false;
},
});
</script>

View File

@ -1,67 +0,0 @@
import Polymer from '../polymer';
const UPDATE_INTERVAL = 10000; // ms
export default new Polymer({
is: 'ha-camera-card',
properties: {
hass: {
type: Object,
},
stateObj: {
type: Object,
observer: 'updateCameraFeedSrc',
},
cameraFeedSrc: {
type: String,
},
imageLoaded: {
type: Boolean,
value: true,
},
/**
* The z-depth of the card, from 0-5.
*/
elevation: {
type: Number,
value: 1,
reflectToAttribute: true,
},
},
listeners: {
tap: 'cardTapped',
},
attached() {
this.timer = setInterval(() => this.updateCameraFeedSrc(this.stateObj),
UPDATE_INTERVAL);
},
detached() {
clearInterval(this.timer);
},
cardTapped() {
this.async(() => this.hass.moreInfoActions.selectEntity(this.stateObj.entityId), 1);
},
updateCameraFeedSrc(stateObj) {
const attr = stateObj.attributes;
const time = (new Date()).getTime();
this.cameraFeedSrc = `${attr.entity_picture}&time=${time}`;
},
imageLoadSuccess() {
this.imageLoaded = true;
},
imageLoadFail() {
this.imageLoaded = false;
},
});

View File

@ -5,3 +5,23 @@
<link rel='import' href='./ha-introduction-card.html'>
<link rel='import' href='./ha-media_player-card.html'>
<link rel='import' href='./ha-persistent_notification-card.html'>
<script>
Polymer({
is: 'ha-card-chooser',
properties: {
cardData: {
type: Object,
observer: 'cardDataChanged',
},
},
cardDataChanged: function (newData) {
if (!newData) return;
window.hassUtil.dynamicContentUpdater(this, 'HA-' + newData.cardType.toUpperCase() + '-CARD',
newData);
},
});
</script>

View File

@ -1,25 +0,0 @@
import Polymer from '../polymer';
import dynamicContentUpdater from '../util/dynamic-content-updater';
import './ha-camera-card';
import './ha-entities-card';
import './ha-media_player-card';
export default new Polymer({
is: 'ha-card-chooser',
properties: {
cardData: {
type: Object,
observer: 'cardDataChanged',
},
},
cardDataChanged(newData) {
if (!newData) return;
dynamicContentUpdater(this, `HA-${newData.cardType.toUpperCase()}-CARD`,
newData);
},
});

View File

@ -56,3 +56,69 @@
</ha-card>
</template>
</dom-module>
<script>
Polymer({
is: 'ha-entities-card',
properties: {
hass: {
type: Object,
},
states: {
type: Array,
},
groupEntity: {
type: Object,
},
},
computeTitle: function (states, groupEntity) {
return groupEntity ? groupEntity.entityDisplay :
states[0].domain.replace(/_/g, ' ');
},
computeTitleClass: function (groupEntity) {
var classes = 'header horizontal layout center ';
if (groupEntity) {
classes += 'header-more-info';
}
return classes;
},
entityTapped: function (ev) {
var entityId;
if (ev.target.classList.contains('paper-toggle-button') ||
ev.target.classList.contains('paper-icon-button') ||
(!ev.model && !this.groupEntity)) {
return;
}
ev.stopPropagation();
if (ev.model) {
entityId = ev.model.item.entityId;
} else {
entityId = this.groupEntity.entityId;
}
this.async(function () { this.hass.moreInfoActions.selectEntity(entityId); }.bind(this), 1);
},
showGroupToggle: function (groupEntity, states) {
var canToggleCount;
if (!groupEntity || !states ||
(groupEntity.state !== 'on' && groupEntity.state !== 'off')) {
return false;
}
// only show if we can toggle 2+ entities in group
canToggleCount = states.reduce(
function (sum, state) {
return sum + window.hassUtil.canToggle(this.hass, state.entityId);
}, 0);
return canToggleCount > 1;
},
});
</script>

View File

@ -1,60 +0,0 @@
import Polymer from '../polymer';
import canToggle from '../util/can-toggle';
import '../components/entity/ha-entity-toggle';
import '../state-summary/state-card-content';
export default new Polymer({
is: 'ha-entities-card',
properties: {
hass: {
type: Object,
},
states: {
type: Array,
},
groupEntity: {
type: Object,
},
},
computeTitle(states, groupEntity) {
return groupEntity ? groupEntity.entityDisplay :
states[0].domain.replace(/_/g, ' ');
},
computeTitleClass(groupEntity) {
let classes = 'header horizontal layout center ';
if (groupEntity) {
classes += 'header-more-info';
}
return classes;
},
entityTapped(ev) {
if (ev.target.classList.contains('paper-toggle-button') ||
ev.target.classList.contains('paper-icon-button') ||
(!ev.model && !this.groupEntity)) {
return;
}
ev.stopPropagation();
let entityId;
if (ev.model) {
entityId = ev.model.item.entityId;
} else {
entityId = this.groupEntity.entityId;
}
this.async(() => this.hass.moreInfoActions.selectEntity(entityId), 1);
},
showGroupToggle(groupEntity, states) {
if (!groupEntity || !states ||
(groupEntity.state !== 'on' && groupEntity.state !== 'off')) {
return false;
}
// only show if we can toggle 2+ entities in group
return states.reduce((sum, state) => sum + canToggle(this.hass, state.entityId), 0) > 1;
},
});

View File

@ -178,3 +178,108 @@
</div>
</template>
</dom-module>
<script>
Polymer({
is: 'ha-media_player-card',
properties: {
hass: {
type: Object,
},
stateObj: {
type: Object,
},
playerObj: {
type: Object,
computed: 'computePlayerObj(stateObj)',
observer: 'playerObjChanged',
},
playbackControlIcon: {
type: String,
computed: 'computePlaybackControlIcon(playerObj)',
},
/**
* The z-depth of the card, from 0-5.
*/
elevation: {
type: Number,
value: 1,
reflectToAttribute: true,
},
},
playerObjChanged: function (playerObj) {
if (!playerObj.isOff && !playerObj.isIdle) {
this.$.cover.style.backgroundImage = playerObj.stateObj.attributes.entity_picture ?
'url(' + playerObj.stateObj.attributes.entity_picture + ')' : '';
}
},
computeBannerClasses: function (playerObj) {
var cls = 'banner';
if (playerObj.isOff || playerObj.isIdle) {
cls += ' is-off';
}
if (!playerObj.stateObj.attributes.entity_picture) {
cls += ' no-cover';
}
return cls;
},
computeHidePowerOnButton: function (playerObj) {
return !playerObj.isOff || !playerObj.supportsTurnOn;
},
computePlayerObj: function (stateObj) {
return stateObj.domainModel(this.hass);
},
computePlaybackControlIcon: function (playerObj) {
if (playerObj.isPlaying) {
return playerObj.supportsPause ? 'mdi:pause' : 'mdi:stop';
} else if (playerObj.isPaused || playerObj.isOff) {
return 'mdi:play';
}
return '';
},
computeShowControls: function (playerObj) {
return !playerObj.isOff;
},
handleNext: function (ev) {
ev.stopPropagation();
this.playerObj.nextTrack();
},
handleOpenMoreInfo: function (ev) {
ev.stopPropagation();
this.async(function () {
this.hass.moreInfoActions.selectEntity(this.stateObj.entityId);
}, 1);
},
handlePlaybackControl: function (ev) {
ev.stopPropagation();
this.playerObj.mediaPlayPause();
},
handlePrevious: function (ev) {
ev.stopPropagation();
this.playerObj.previousTrack();
},
handleTogglePower: function (ev) {
ev.stopPropagation();
this.playerObj.togglePower();
},
});
</script>

View File

@ -1,98 +0,0 @@
import classnames from 'classnames';
import Polymer from '../polymer';
export default new Polymer({
is: 'ha-media_player-card',
properties: {
hass: {
type: Object,
},
stateObj: {
type: Object,
},
playerObj: {
type: Object,
computed: 'computePlayerObj(stateObj)',
observer: 'playerObjChanged',
},
playbackControlIcon: {
type: String,
computed: 'computePlaybackControlIcon(playerObj)',
},
/**
* The z-depth of the card, from 0-5.
*/
elevation: {
type: Number,
value: 1,
reflectToAttribute: true,
},
},
playerObjChanged(playerObj) {
if (!playerObj.isOff && !playerObj.isIdle) {
this.$.cover.style.backgroundImage = playerObj.stateObj.attributes.entity_picture ?
`url(${playerObj.stateObj.attributes.entity_picture})` : '';
}
},
computeBannerClasses(playerObj) {
return classnames({
banner: true,
'is-off': playerObj.isOff || playerObj.isIdle,
'no-cover': !playerObj.stateObj.attributes.entity_picture,
});
},
computeHidePowerOnButton(playerObj) {
return !playerObj.isOff || !playerObj.supportsTurnOn;
},
computePlayerObj(stateObj) {
return stateObj.domainModel(this.hass);
},
computePlaybackControlIcon(playerObj) {
if (playerObj.isPlaying) {
return playerObj.supportsPause ? 'mdi:pause' : 'mdi:stop';
} else if (playerObj.isPaused || playerObj.isOff) {
return 'mdi:play';
}
return '';
},
computeShowControls(playerObj) {
return !playerObj.isOff;
},
handleNext(ev) {
ev.stopPropagation();
this.playerObj.nextTrack();
},
handleOpenMoreInfo(ev) {
ev.stopPropagation();
this.async(() => this.hass.moreInfoActions.selectEntity(this.stateObj.entityId), 1);
},
handlePlaybackControl(ev) {
ev.stopPropagation();
this.playerObj.mediaPlayPause();
},
handlePrevious(ev) {
ev.stopPropagation();
this.playerObj.previousTrack();
},
handleTogglePower(ev) {
ev.stopPropagation();
this.playerObj.togglePower();
},
});

View File

@ -25,7 +25,7 @@ Polymer({
},
computeIcon: function (domain, state) {
return window.domainIcon(domain, state);
return window.hassUtil.domainIcon(domain, state);
},
});
</script>

View File

@ -32,3 +32,109 @@
</template>
</template>
</dom-module>
<script>
Polymer({
is: 'ha-entity-toggle',
properties: {
hass: {
type: Object,
},
stateObj: {
type: Object,
},
toggleChecked: {
type: Boolean,
value: false,
},
isOn: {
type: Boolean,
computed: 'computeIsOn(stateObj)',
observer: 'isOnChanged',
},
},
listeners: {
tap: 'onTap',
},
onTap: function (ev) {
ev.stopPropagation();
},
ready: function () {
this.forceStateChange();
},
toggleChanged: function (ev) {
var newVal = ev.target.checked;
if (newVal && !this.isOn) {
this.callService(true);
} else if (!newVal && this.isOn) {
this.callService(false);
}
},
isOnChanged: function (newVal) {
this.toggleChecked = newVal;
},
forceStateChange: function () {
if (this.toggleChecked === this.isOn) {
this.toggleChecked = !this.toggleChecked;
}
this.toggleChecked = this.isOn;
},
turnOn: function () {
this.callService(true);
},
turnOff: function () {
this.callService(false);
},
computeIsOn: function (stateObj) {
return stateObj && window.hassUtil.OFF_STATES.indexOf(stateObj.state) === -1;
},
// We call updateToggle after a successful call to re-sync the toggle
// with the state. It will be out of sync if our service call did not
// result in the entity to be turned on. Since the state is not changing,
// the resync is not called automatic.
callService: function (turnOn) {
var domain;
var service;
var currentState;
if (this.stateObj.domain === 'lock') {
domain = 'lock';
service = turnOn ? 'lock' : 'unlock';
} else if (this.stateObj.domain === 'garage_door') {
domain = 'garage_door';
service = turnOn ? 'open' : 'close';
} else {
domain = 'homeassistant';
service = turnOn ? 'turn_on' : 'turn_off';
}
currentState = this.stateObj;
this.hass.serviceActions.callService(domain, service,
{ entity_id: this.stateObj.entityId })
.then(function () {
setTimeout(function () {
// If after 2 seconds we have not received a state update
// reset the switch to it's original state.
if (this.stateObj === currentState) {
this.forceStateChange();
}
}.bind(this), 2000);
}.bind(this));
},
});
</script>

View File

@ -1,104 +0,0 @@
import Polymer from '../../polymer';
import OFF_STATES from '../../util/off-states';
export default new Polymer({
is: 'ha-entity-toggle',
properties: {
hass: {
type: Object,
},
stateObj: {
type: Object,
},
toggleChecked: {
type: Boolean,
value: false,
},
isOn: {
type: Boolean,
computed: 'computeIsOn(stateObj)',
observer: 'isOnChanged',
},
},
listeners: {
tap: 'onTap',
},
onTap(ev) {
ev.stopPropagation();
},
ready() {
this.forceStateChange();
},
toggleChanged(ev) {
const newVal = ev.target.checked;
if (newVal && !this.isOn) {
this.callService(true);
} else if (!newVal && this.isOn) {
this.callService(false);
}
},
isOnChanged(newVal) {
this.toggleChecked = newVal;
},
forceStateChange() {
if (this.toggleChecked === this.isOn) {
this.toggleChecked = !this.toggleChecked;
}
this.toggleChecked = this.isOn;
},
turnOn() {
this.callService(true);
},
turnOff() {
this.callService(false);
},
computeIsOn(stateObj) {
return stateObj && OFF_STATES.indexOf(stateObj.state) === -1;
},
// We call updateToggle after a successful call to re-sync the toggle
// with the state. It will be out of sync if our service call did not
// result in the entity to be turned on. Since the state is not changing,
// the resync is not called automatic.
callService(turnOn) {
let domain;
let service;
if (this.stateObj.domain === 'lock') {
domain = 'lock';
service = turnOn ? 'lock' : 'unlock';
} else if (this.stateObj.domain === 'garage_door') {
domain = 'garage_door';
service = turnOn ? 'open' : 'close';
} else {
domain = 'homeassistant';
service = turnOn ? 'turn_on' : 'turn_off';
}
const currentState = this.stateObj;
this.hass.serviceActions.callService(domain, service,
{ entity_id: this.stateObj.entityId })
.then(() => setTimeout(() => {
// If after 2 seconds we have not received a state update
// reset the switch to it's original state.
if (this.stateObj === currentState) {
this.forceStateChange();
}
}, 2000));
},
});

View File

@ -7,3 +7,19 @@
<iron-icon icon="[[computeIcon(stateObj)]]"></iron-icon>
</template>
</dom-module>
<script>
Polymer({
is: 'ha-state-icon',
properties: {
stateObj: {
type: Object,
},
},
computeIcon: function (stateObj) {
return window.hassUtil.stateIcon(stateObj);
},
});
</script>

View File

@ -1,17 +0,0 @@
import Polymer from '../../polymer';
import stateIcon from '../../util/state-icon';
export default new Polymer({
is: 'ha-state-icon',
properties: {
stateObj: {
type: Object,
},
},
computeIcon(stateObj) {
return stateIcon(stateObj);
},
});

View File

@ -34,3 +34,114 @@
></ha-label-badge>
</template>
</dom-module>
<script>
Polymer({
is: 'ha-state-label-badge',
properties: {
hass: {
type: Object,
},
state: {
type: Object,
observer: 'stateChanged',
},
},
listeners: {
tap: 'badgeTap',
},
badgeTap: function (ev) {
ev.stopPropagation();
this.async(function () {
this.hass.moreInfoActions.selectEntity(this.state.entityId);
}, 1);
},
computeClasses: function (state) {
switch (state.domain) {
case 'binary_sensor':
case 'updater':
return 'blue';
default:
return '';
}
},
computeValue: function (state) {
switch (state.domain) {
case 'binary_sensor':
case 'device_tracker':
case 'updater':
case 'sun':
case 'alarm_control_panel':
return null;
case 'sensor':
default:
return state.state === 'unknown' ? '-' : state.state;
}
},
computeIcon: function (state) {
if (state.state === 'unavailable') {
return null;
}
switch (state.domain) {
case 'alarm_control_panel':
if (state.state === 'pending') {
return 'mdi:clock-fast';
} else if (state.state === 'armed_away') {
return 'mdi:nature';
} else if (state.state === 'armed_home') {
return 'mdi:home-variant';
}
// state == 'disarmed'
return window.hassUtil.domainIcon(state.domain, state.state);
case 'binary_sensor':
case 'device_tracker':
case 'updater':
return window.hassUtil.stateIcon(state);
case 'sun':
return state.state === 'above_horizon' ?
window.hassUtil.domainIcon(state.domain) : 'mdi:brightness-3';
default:
return null;
}
},
computeImage: function (state) {
return state.attributes.entity_picture || null;
},
computeLabel: function (state) {
if (state.state === 'unavailable') {
return 'unavai';
}
switch (state.domain) {
case 'device_tracker':
return state.state === 'not_home' ? 'Away' : state.state;
case 'alarm_control_panel':
if (state.state === 'pending') {
return 'pend';
} else if (state.state === 'armed_away' || state.state === 'armed_home') {
return 'armed';
}
// state == 'disarmed'
return 'disarm';
default:
return state.attributes.unit_of_measurement || null;
}
},
computeDescription: function (state) {
return state.entityDisplay;
},
stateChanged: function () {
this.updateStyles();
},
});
</script>

View File

@ -1,110 +0,0 @@
import Polymer from '../../polymer';
import domainIcon from '../../util/domain-icon';
import stateIcon from '../../util/state-icon';
export default new Polymer({
is: 'ha-state-label-badge',
properties: {
hass: {
type: Object,
},
state: {
type: Object,
observer: 'stateChanged',
},
},
listeners: {
tap: 'badgeTap',
},
badgeTap(ev) {
ev.stopPropagation();
this.async(() => this.hass.moreInfoActions.selectEntity(this.state.entityId), 1);
},
computeClasses(state) {
switch (state.domain) {
case 'binary_sensor':
case 'updater':
return 'blue';
default:
return '';
}
},
computeValue(state) {
switch (state.domain) {
case 'binary_sensor':
case 'device_tracker':
case 'updater':
case 'sun':
case 'alarm_control_panel':
return null;
case 'sensor':
default:
return state.state === 'unknown' ? '-' : state.state;
}
},
computeIcon(state) {
if (state.state === 'unavailable') {
return null;
}
switch (state.domain) {
case 'alarm_control_panel':
if (state.state === 'pending') {
return 'mdi:clock-fast';
} else if (state.state === 'armed_away') {
return 'mdi:nature';
} else if (state.state === 'armed_home') {
return 'mdi:home-variant';
}
// state == 'disarmed'
return domainIcon(state.domain, state.state);
case 'binary_sensor':
case 'device_tracker':
case 'updater':
return stateIcon(state);
case 'sun':
return state.state === 'above_horizon' ?
domainIcon(state.domain) : 'mdi:brightness-3';
default:
return null;
}
},
computeImage(state) {
return state.attributes.entity_picture || null;
},
computeLabel(state) {
if (state.state === 'unavailable') {
return 'unavai';
}
switch (state.domain) {
case 'device_tracker':
return state.state === 'not_home' ? 'Away' : state.state;
case 'alarm_control_panel':
if (state.state === 'pending') {
return 'pend';
} else if (state.state === 'armed_away' || state.state === 'armed_home') {
return 'armed';
}
// state == 'disarmed'
return 'disarm';
default:
return state.attributes.unit_of_measurement || null;
}
},
computeDescription(state) {
return state.entityDisplay;
},
stateChanged() {
this.updateStyles();
},
});

View File

@ -40,3 +40,42 @@
</ha-state-icon>
</template>
</dom-module>
<script>
Polymer({
is: 'state-badge',
properties: {
stateObj: {
type: Object,
observer: 'updateIconColor',
},
},
/**
* Called when an attribute changes that influences the color of the icon.
*/
updateIconColor: function (newVal) {
// hide icon if we have entity picture
if (newVal.attributes.entity_picture) {
this.style.backgroundImage = 'url(' + newVal.attributes.entity_picture + ')';
this.$.icon.style.display = 'none';
return;
}
this.style.backgroundImage = '';
this.$.icon.style.display = 'inline';
// for domain light, set color of icon to light color if available and it is
// not very white (sum rgb colors < 730)
if (newVal.domain === 'light' && newVal.state === 'on' &&
newVal.attributes.rgb_color &&
newVal.attributes.rgb_color.reduce(function (cur, tot) { return cur + tot; }, 0) < 730) {
this.$.icon.style.color = 'rgb(' + newVal.attributes.rgb_color.join(',') + ')';
} else {
this.$.icon.style.color = null;
}
},
});
</script>

View File

@ -1,40 +0,0 @@
import Polymer from '../../polymer';
import './ha-state-icon';
export default new Polymer({
is: 'state-badge',
properties: {
stateObj: {
type: Object,
observer: 'updateIconColor',
},
},
/**
* Called when an attribute changes that influences the color of the icon.
*/
updateIconColor(newVal) {
// hide icon if we have entity picture
if (newVal.attributes.entity_picture) {
this.style.backgroundImage = `url(${newVal.attributes.entity_picture})`;
this.$.icon.style.display = 'none';
return;
}
this.style.backgroundImage = '';
this.$.icon.style.display = 'inline';
// for domain light, set color of icon to light color if available and it is
// not very white (sum rgb colors < 730)
if (newVal.domain === 'light' && newVal.state === 'on' &&
newVal.attributes.rgb_color &&
newVal.attributes.rgb_color.reduce((cur, tot) => cur + tot, 0) < 730) {
this.$.icon.style.color = `rgb(${newVal.attributes.rgb_color.join(',')})`;
} else {
this.$.icon.style.color = null;
}
},
});

View File

@ -80,3 +80,209 @@
</template>
</dom-module>
<script>
(function () {
'use strict';
// mapping domain to size of the card.
var DOMAINS_WITH_CARD = {
camera: 4,
media_player: 3,
persistent_notification: 0,
};
var PRIORITY = {
configurator: -20,
persistent_notification: -15,
group: -10,
a: -1,
updater: 0,
sun: 1,
device_tracker: 2,
alarm_control_panel: 3,
sensor: 5,
binary_sensor: 6,
};
function getPriority(domain) {
return (domain in PRIORITY) ? PRIORITY[domain] : 30;
}
function entitySortBy(entity) {
return entity.domain === 'group' ? entity.attributes.order :
entity.entityDisplay.toLowerCase();
}
Polymer({
is: 'ha-cards',
properties: {
hass: {
type: Object,
},
showIntroduction: {
type: Boolean,
value: false,
},
columns: {
type: Number,
value: 2,
},
states: {
type: Object,
},
cards: {
type: Object,
},
},
observers: [
'updateCards(columns, states, showIntroduction)',
],
updateCards: function (columns, states, showIntroduction) {
this.debounce(
'updateCards',
function () { this.cards = this.computeCards(columns, states, showIntroduction); },
0
);
},
computeCards: function (columns, states, showIntroduction) {
var hass = this.hass;
var byDomain = states.groupBy(function (entity) { return entity.domain; });
var hasGroup = {};
var cards = {
demo: false,
badges: [],
columns: [],
};
var entityCount = [];
var expandGroup;
var i;
for (i = 0; i < columns; i++) {
cards.columns.push([]);
entityCount.push(0);
}
function filterGrouped(entities) {
return entities.filter(function (entity) { return !(entity.entityId in hasGroup); });
}
// Find column with < 5 entities, else column with lowest count
function getIndex(size) {
var minIndex = 0;
for (i = minIndex; i < entityCount.length; i++) {
if (entityCount[i] < 5) {
minIndex = i;
break;
}
if (entityCount[i] < entityCount[minIndex]) {
minIndex = i;
}
}
entityCount[minIndex] += size;
return minIndex;
}
if (showIntroduction) {
cards.columns[getIndex(5)].push({
hass: hass,
cardType: 'introduction',
showHideInstruction: states.size > 0 && !hass.demo,
});
}
function addEntitiesCard(name, entities, groupEntity) {
var owncard;
var other;
var size;
var curIndex;
if (entities.length === 0) return;
owncard = [];
other = [];
size = 0;
entities.forEach(function (entity) {
if (entity.domain in DOMAINS_WITH_CARD) {
owncard.push(entity);
size += DOMAINS_WITH_CARD[entity.domain];
} else {
other.push(entity);
size++;
}
});
// Add 1 to the size if we're rendering entities card
size += other.length > 1;
curIndex = getIndex(size);
if (other.length > 0) {
cards.columns[curIndex].push({
hass: hass,
cardType: 'entities',
states: other,
groupEntity: groupEntity || false,
});
}
owncard.forEach(function (entity) {
cards.columns[curIndex].push({
hass: hass,
cardType: entity.domain,
stateObj: entity,
});
});
}
expandGroup = this.hass.util.expandGroup;
byDomain.keySeq().sortBy(function (domain) { return getPriority(domain); })
.forEach(function (domain) {
var priority;
if (domain === 'a') {
cards.demo = true;
return;
}
priority = getPriority(domain);
if (priority >= 0 && priority < 10) {
cards.badges.push.apply(
cards.badges, filterGrouped(byDomain.get(domain)).sortBy(
entitySortBy).toArray());
} else if (domain === 'group') {
byDomain.get(domain).sortBy(entitySortBy)
.forEach(function (groupState) {
var entities = expandGroup(groupState, states);
entities.forEach(function (entity) { hasGroup[entity.entityId] = true; });
addEntitiesCard(groupState.entityId, entities.toArray(), groupState);
}
);
} else {
addEntitiesCard(
domain, filterGrouped(byDomain.get(domain)).sortBy(entitySortBy).toArray());
}
}
);
// Remove empty columns
cards.columns = cards.columns.filter(function (val) {
return val.length > 0;
});
return cards;
},
});
}());
</script>

View File

@ -1,195 +0,0 @@
import Polymer from '../polymer';
import '../cards/ha-badges-card';
import '../cards/ha-card-chooser';
// mapping domain to size of the card.
const DOMAINS_WITH_CARD = {
camera: 4,
media_player: 3,
persistent_notification: 0,
};
const PRIORITY = {
configurator: -20,
persistent_notification: -15,
group: -10,
a: -1,
updater: 0,
sun: 1,
device_tracker: 2,
alarm_control_panel: 3,
sensor: 5,
binary_sensor: 6,
};
function getPriority(domain) {
return (domain in PRIORITY) ? PRIORITY[domain] : 30;
}
function entitySortBy(entity) {
return entity.domain === 'group' ? entity.attributes.order :
entity.entityDisplay.toLowerCase();
}
export default new Polymer({
is: 'ha-cards',
properties: {
hass: {
type: Object,
},
showIntroduction: {
type: Boolean,
value: false,
},
columns: {
type: Number,
value: 2,
},
states: {
type: Object,
},
cards: {
type: Object,
},
},
observers: [
'updateCards(columns, states, showIntroduction)',
],
updateCards(columns, states, showIntroduction) {
this.debounce(
'updateCards',
() => { this.cards = this.computeCards(columns, states, showIntroduction); },
0
);
},
computeCards(columns, states, showIntroduction) {
const hass = this.hass;
const byDomain = states.groupBy(entity => entity.domain);
const hasGroup = {};
const cards = {
demo: false,
badges: [],
columns: [],
};
const entityCount = [];
for (let idx = 0; idx < columns; idx++) {
cards.columns.push([]);
entityCount.push(0);
}
function filterGrouped(entities) {
return entities.filter(entity => !(entity.entityId in hasGroup));
}
// Find column with < 5 entities, else column with lowest count
function getIndex(size) {
let minIndex = 0;
for (let i = minIndex; i < entityCount.length; i++) {
if (entityCount[i] < 5) {
minIndex = i;
break;
}
if (entityCount[i] < entityCount[minIndex]) {
minIndex = i;
}
}
entityCount[minIndex] += size;
return minIndex;
}
if (showIntroduction) {
cards.columns[getIndex(5)].push({
hass,
cardType: 'introduction',
showHideInstruction: states.size > 0 && !hass.demo,
});
}
function addEntitiesCard(name, entities, groupEntity = false) {
if (entities.length === 0) return;
const owncard = [];
const other = [];
let size = 0;
entities.forEach(entity => {
if (entity.domain in DOMAINS_WITH_CARD) {
owncard.push(entity);
size += DOMAINS_WITH_CARD[entity.domain];
} else {
other.push(entity);
size++;
}
});
// Add 1 to the size if we're rendering entities card
size += other.length > 1;
const curIndex = getIndex(size);
if (other.length > 0) {
cards.columns[curIndex].push({
hass,
cardType: 'entities',
states: other,
groupEntity,
});
}
owncard.forEach(entity => {
cards.columns[curIndex].push({
hass,
cardType: entity.domain,
stateObj: entity,
});
});
}
const expandGroup = this.hass.util.expandGroup;
byDomain.keySeq().sortBy(domain => getPriority(domain))
.forEach(domain => {
if (domain === 'a') {
cards.demo = true;
return;
}
const priority = getPriority(domain);
if (priority >= 0 && priority < 10) {
cards.badges.push.apply(
cards.badges, filterGrouped(byDomain.get(domain)).sortBy(
entitySortBy).toArray());
} else if (domain === 'group') {
byDomain.get(domain).sortBy(entitySortBy)
.forEach(groupState => {
const entities = expandGroup(groupState, states);
entities.forEach(entity => { hasGroup[entity.entityId] = true; });
addEntitiesCard(groupState.entityId, entities.toArray(), groupState);
}
);
} else {
addEntitiesCard(
domain, filterGrouped(byDomain.get(domain)).sortBy(entitySortBy).toArray());
}
}
);
// Remove empty columns
cards.columns = cards.columns.filter(val => val.length > 0);
return cards;
},
});

View File

@ -113,13 +113,17 @@ Polymer({
drawGradient: function () {
var style;
var width;
var height;
var colorGradient;
var bwGradient;
if (!this.width || !this.height) {
style = getComputedStyle(this);
}
var width = this.width || parseInt(style.width, 10);
var height = this.height || parseInt(style.height, 10);
width = this.width || parseInt(style.width, 10);
height = this.height || parseInt(style.height, 10);
var colorGradient = this.context.createLinearGradient(0, 0, width, 0);
colorGradient = this.context.createLinearGradient(0, 0, width, 0);
colorGradient.addColorStop(0, 'rgb(255,0,0)');
colorGradient.addColorStop(0.16, 'rgb(255,0,255)');
colorGradient.addColorStop(0.32, 'rgb(0,0,255)');
@ -130,7 +134,7 @@ Polymer({
this.context.fillStyle = colorGradient;
this.context.fillRect(0, 0, width, height);
var bwGradient = this.context.createLinearGradient(0, 0, 0, height);
bwGradient = this.context.createLinearGradient(0, 0, 0, height);
bwGradient.addColorStop(0, 'rgba(255,255,255,1)');
bwGradient.addColorStop(0.5, 'rgba(255,255,255,0)');
bwGradient.addColorStop(0.5, 'rgba(0,0,0,0)');

View File

@ -52,3 +52,20 @@
</div>
</template>
</dom-module>
<script>
Polymer({
is: 'state-info',
properties: {
detailed: {
type: Boolean,
value: false,
},
stateObj: {
type: Object,
},
},
});
</script>

View File

@ -1,18 +0,0 @@
import Polymer from '../polymer';
import './entity/state-badge';
export default new Polymer({
is: 'state-info',
properties: {
detailed: {
type: Boolean,
value: false,
},
stateObj: {
type: Object,
},
},
});

View File

@ -62,3 +62,75 @@
</template>
</dom-module>
<script>
Polymer({
is: 'ha-voice-command-dialog',
behaviors: [window.hassBehavior],
properties: {
hass: {
type: Object,
},
dialogOpen: {
type: Boolean,
value: false,
observer: 'dialogOpenChanged',
},
finalTranscript: {
type: String,
bindNuclear: function (hass) {
return hass.voiceGetters.finalTranscript;
},
},
interimTranscript: {
type: String,
bindNuclear: function (hass) {
return hass.voiceGetters.extraInterimTranscript;
},
},
isTransmitting: {
type: Boolean,
bindNuclear: function (hass) {
return hass.voiceGetters.isTransmitting;
},
},
isListening: {
type: Boolean,
bindNuclear: function (hass) {
return hass.voiceGetters.isListening;
},
},
showListenInterface: {
type: Boolean,
computed: 'computeShowListenInterface(isListening, isTransmitting)',
observer: 'showListenInterfaceChanged',
},
},
computeShowListenInterface: function (isListening, isTransmitting) {
return isListening || isTransmitting;
},
dialogOpenChanged: function (newVal) {
if (!newVal && this.isListening) {
this.hass.voiceActions.stop();
}
},
showListenInterfaceChanged: function (newVal) {
if (!newVal && this.dialogOpen) {
this.dialogOpen = false;
} else if (newVal) {
this.dialogOpen = true;
}
},
});
</script>

View File

@ -1,63 +0,0 @@
import Polymer from '../polymer';
export default new Polymer({
is: 'ha-voice-command-dialog',
behaviors: [window.hassBehavior],
properties: {
hass: {
type: Object,
},
dialogOpen: {
type: Boolean,
value: false,
observer: 'dialogOpenChanged',
},
finalTranscript: {
type: String,
bindNuclear: hass => hass.voiceGetters.finalTranscript,
},
interimTranscript: {
type: String,
bindNuclear: hass => hass.voiceGetters.extraInterimTranscript,
},
isTransmitting: {
type: Boolean,
bindNuclear: hass => hass.voiceGetters.isTransmitting,
},
isListening: {
type: Boolean,
bindNuclear: hass => hass.voiceGetters.isListening,
},
showListenInterface: {
type: Boolean,
computed: 'computeShowListenInterface(isListening, isTransmitting)',
observer: 'showListenInterfaceChanged',
},
},
computeShowListenInterface(isListening, isTransmitting) {
return isListening || isTransmitting;
},
dialogOpenChanged(newVal) {
if (!newVal && this.isListening) {
this.hass.voiceActions.stop();
}
},
showListenInterfaceChanged(newVal) {
if (!newVal && this.dialogOpen) {
this.dialogOpen = false;
} else if (newVal) {
this.dialogOpen = true;
}
},
});

View File

@ -58,3 +58,130 @@
</paper-dialog>
</template>
</dom-module>
<script>
Polymer({
is: 'more-info-dialog',
behaviors: [window.hassBehavior],
properties: {
hass: {
type: Object,
},
stateObj: {
type: Object,
bindNuclear: function (hass) {
return hass.moreInfoGetters.currentEntity;
},
observer: 'stateObjChanged',
},
stateHistory: {
type: Object,
bindNuclear: function (hass) {
return [
hass.moreInfoGetters.currentEntityHistory,
function (history) { return history ? [history] : false; },
];
},
},
isLoadingHistoryData: {
type: Boolean,
computed: 'computeIsLoadingHistoryData(delayedDialogOpen, isLoadingEntityHistoryData)',
},
isLoadingEntityHistoryData: {
type: Boolean,
bindNuclear: function (hass) {
return hass.entityHistoryGetters.isLoadingEntityHistory;
},
},
hasHistoryComponent: {
type: Boolean,
bindNuclear: function (hass) {
return hass.configGetters.isComponentLoaded('history');
},
observer: 'fetchHistoryData',
},
shouldFetchHistory: {
type: Boolean,
bindNuclear: function (hass) {
return hass.moreInfoGetters.isCurrentEntityHistoryStale;
},
observer: 'fetchHistoryData',
},
showHistoryComponent: {
type: Boolean,
value: false,
computed: 'computeShowHistoryComponent(hasHistoryComponent, stateObj)',
},
dialogOpen: {
type: Boolean,
value: false,
observer: 'dialogOpenChanged',
},
delayedDialogOpen: {
type: Boolean,
value: false,
},
},
ready: function () {
this.$.scrollable.dialogElement = this.$.dialog;
},
/**
* We depend on a delayed dialogOpen value to tell the chart component
* that the data is there. Otherwise the chart component will render
* before the dialog is attached to the screen and is unable to determine
* graph size resulting in scroll bars.
*/
computeIsLoadingHistoryData: function (delayedDialogOpen, isLoadingEntityHistoryData) {
return !delayedDialogOpen || isLoadingEntityHistoryData;
},
computeShowHistoryComponent: function (hasHistoryComponent, stateObj) {
return this.hasHistoryComponent && stateObj &&
window.hassUtil.DOMAINS_WITH_NO_HISTORY.indexOf(stateObj.domain) === -1;
},
fetchHistoryData: function () {
if (this.stateObj && this.hasHistoryComponent &&
this.shouldFetchHistory) {
this.hass.entityHistoryActions.fetchRecent(this.stateObj.entityId);
}
},
stateObjChanged: function (newVal) {
if (!newVal) {
this.dialogOpen = false;
return;
}
this.async(function () {
// Firing action while other action is happening confuses nuclear
this.fetchHistoryData();
// allow dialog to render content before showing it so it is
// positioned correctly.
this.dialogOpen = true;
}.bind(this), 10);
},
dialogOpenChanged: function (newVal) {
if (newVal) {
this.async(function () { this.delayedDialogOpen = true; }.bind(this), 10);
} else if (!newVal && this.stateObj) {
this.async(function () { this.hass.moreInfoActions.deselectEntity(); }.bind(this), 10);
this.delayedDialogOpen = false;
}
},
});
</script>

View File

@ -1,122 +0,0 @@
import Polymer from '../polymer';
import '../state-summary/state-card-content';
import '../components/state-history-charts';
import '../more-infos/more-info-content';
const DOMAINS_WITH_NO_HISTORY = ['camera', 'configurator', 'scene'];
export default new Polymer({
is: 'more-info-dialog',
behaviors: [window.hassBehavior],
properties: {
hass: {
type: Object,
},
stateObj: {
type: Object,
bindNuclear: hass => hass.moreInfoGetters.currentEntity,
observer: 'stateObjChanged',
},
stateHistory: {
type: Object,
bindNuclear: hass => [
hass.moreInfoGetters.currentEntityHistory,
(history) => (history ? [history] : false),
],
},
isLoadingHistoryData: {
type: Boolean,
computed: 'computeIsLoadingHistoryData(delayedDialogOpen, isLoadingEntityHistoryData)',
},
isLoadingEntityHistoryData: {
type: Boolean,
bindNuclear: hass => hass.entityHistoryGetters.isLoadingEntityHistory,
},
hasHistoryComponent: {
type: Boolean,
bindNuclear: hass => hass.configGetters.isComponentLoaded('history'),
observer: 'fetchHistoryData',
},
shouldFetchHistory: {
type: Boolean,
bindNuclear: hass => hass.moreInfoGetters.isCurrentEntityHistoryStale,
observer: 'fetchHistoryData',
},
showHistoryComponent: {
type: Boolean,
value: false,
computed: 'computeShowHistoryComponent(hasHistoryComponent, stateObj)',
},
dialogOpen: {
type: Boolean,
value: false,
observer: 'dialogOpenChanged',
},
delayedDialogOpen: {
type: Boolean,
value: false,
},
},
ready() {
this.$.scrollable.dialogElement = this.$.dialog;
},
/**
* We depend on a delayed dialogOpen value to tell the chart component
* that the data is there. Otherwise the chart component will render
* before the dialog is attached to the screen and is unable to determine
* graph size resulting in scroll bars.
*/
computeIsLoadingHistoryData(delayedDialogOpen, isLoadingEntityHistoryData) {
return !delayedDialogOpen || isLoadingEntityHistoryData;
},
computeShowHistoryComponent(hasHistoryComponent, stateObj) {
return this.hasHistoryComponent && stateObj &&
DOMAINS_WITH_NO_HISTORY.indexOf(stateObj.domain) === -1;
},
fetchHistoryData() {
if (this.stateObj && this.hasHistoryComponent &&
this.shouldFetchHistory) {
this.hass.entityHistoryActions.fetchRecent(this.stateObj.entityId);
}
},
stateObjChanged(newVal) {
if (!newVal) {
this.dialogOpen = false;
return;
}
this.async(() => {
// Firing action while other action is happening confuses nuclear
this.fetchHistoryData();
// allow dialog to render content before showing it so it is
// positioned correctly.
this.dialogOpen = true;
}, 10);
},
dialogOpenChanged(newVal) {
if (newVal) {
this.async(() => { this.delayedDialogOpen = true; }, 10);
} else if (!newVal && this.stateObj) {
this.async(() => this.hass.moreInfoActions.deselectEntity(), 10);
this.delayedDialogOpen = false;
}
},
});

View File

@ -1,4 +1,3 @@
import '../layouts/partial-cards';
import '../managers/notification-manager';
import '../dialogs/more-info-dialog';
import '../dialogs/ha-voice-command-dialog';
// components that still require update
import '../components/state-history-charts';
import '../more-infos/more-info-content';

View File

@ -7,6 +7,7 @@
<link rel='import' href='../bower_components/iron-flex-layout/iron-flex-layout-classes.html'>
<link rel="import" href="../bower_components/iron-iconset-svg/iron-iconset-svg.html">
<link rel='import' href='./util/hass-util.html'>
<link rel='import' href='./util/hass-behavior.html'>
<link rel='import' href='./layouts/login-form.html'>
<link rel='import' href='./entry-points/home-assistant-main.html'>

View File

@ -105,3 +105,203 @@
</template>
</dom-module>
<script>
Polymer({
is: 'partial-cards',
behaviors: [window.hassBehavior],
properties: {
hass: {
type: Object,
},
narrow: {
type: Boolean,
value: false,
},
isFetching: {
type: Boolean,
bindNuclear: function (hass) {
return hass.syncGetters.isFetching;
},
},
isStreaming: {
type: Boolean,
bindNuclear: function (hass) {
return hass.streamGetters.isStreamingEvents;
},
},
canListen: {
type: Boolean,
bindNuclear: function (hass) {
return [
hass.voiceGetters.isVoiceSupported,
hass.configGetters.isComponentLoaded('conversation'),
function (isVoiceSupported, componentLoaded) {
return isVoiceSupported && componentLoaded;
},
];
},
},
introductionLoaded: {
type: Boolean,
bindNuclear: function (hass) {
return hass.configGetters.isComponentLoaded('introduction');
},
},
locationName: {
type: String,
bindNuclear: function (hass) {
return hass.configGetters.locationName;
},
},
showMenu: {
type: Boolean,
value: false,
observer: 'windowChange',
},
currentView: {
type: String,
bindNuclear: function (hass) {
return [
hass.viewGetters.currentView,
function (view) { return view || ''; },
];
},
},
hasViews: {
type: Boolean,
bindNuclear: function (hass) {
return [
hass.viewGetters.views,
function (views) { return views.size > 0; },
];
},
},
states: {
type: Object,
bindNuclear: function (hass) {
return hass.viewGetters.currentViewEntities;
},
},
columns: {
type: Number,
value: 1,
},
},
created: function () {
var sizes = [];
var col;
this.windowChange = this.windowChange.bind(this);
for (col = 0; col < 5; col++) {
sizes.push(300 + (col * 300));
}
this.mqls = sizes.map(function (width) {
var mql = window.matchMedia('(min-width: ' + width + 'px)');
mql.addListener(this.windowChange);
return mql;
}.bind(this));
},
detached: function () {
this.mqls.forEach(function (mql) {
mql.removeListener(this.windowChange);
});
},
windowChange: function () {
var matchColumns = this.mqls.reduce(function (cols, mql) { return cols + mql.matches; }, 0);
// Do -1 column if the menu is docked and open
this.columns = Math.max(1, matchColumns - (!this.narrow && this.showMenu));
},
scrollToTop: function () {
this.$.panel.scrollToTop(true);
},
handleRefresh: function () {
this.hass.syncActions.fetchAll();
},
handleListenClick: function () {
this.hass.voiceActions.listen();
},
contentScroll: function () {
if (this.debouncedContentScroll) return;
this.debouncedContentScroll = this.async(function () {
this.checkRaised();
this.debouncedContentScroll = false;
}.bind(this), 100);
},
checkRaised: function () {
this.toggleClass(
'raised',
this.$.panel.scroller.scrollTop > (this.hasViews ? 56 : 0),
this.$.panel);
},
headerScrollAdjust: function (ev) {
if (!this.hasViews) return;
this.translate3d('0', '-' + ev.detail.y + 'px', '0', this.$.menu);
// this.toggleClass('condensed', ev.detail.y === 56, this.$.panel);
},
computeHeaderHeight: function (hasViews, narrow) {
if (hasViews) {
return 104;
} else if (narrow) {
return 56;
}
return 64;
},
computeCondensedHeaderHeight: function (hasViews, narrow) {
if (hasViews) {
return 48;
} else if (narrow) {
return 56;
}
return 64;
},
computeMenuButtonClass: function (narrow, showMenu) {
return !narrow && showMenu ? 'menu-icon invisible' : 'menu-icon';
},
computeRefreshButtonClass: function (isFetching) {
return isFetching ? 'ha-spin' : '';
},
computeTitle: function (hasViews, locationName) {
return hasViews ? 'Home Assistant' : locationName;
},
computeShowIntroduction: function (currentView, introductionLoaded, states) {
return currentView === '' && (introductionLoaded || states.size === 0);
},
computeHasViews: function (views) {
return views.length > 0;
},
toggleMenu: function () {
this.fire('open-menu');
},
});
</script>

View File

@ -1,180 +0,0 @@
import Polymer from '../polymer';
import '../components/ha-cards';
export default new Polymer({
is: 'partial-cards',
behaviors: [window.hassBehavior],
properties: {
hass: {
type: Object,
},
narrow: {
type: Boolean,
value: false,
},
isFetching: {
type: Boolean,
bindNuclear: hass => hass.syncGetters.isFetching,
},
isStreaming: {
type: Boolean,
bindNuclear: hass => hass.streamGetters.isStreamingEvents,
},
canListen: {
type: Boolean,
bindNuclear: hass => [
hass.voiceGetters.isVoiceSupported,
hass.configGetters.isComponentLoaded('conversation'),
(isVoiceSupported, componentLoaded) => isVoiceSupported && componentLoaded,
],
},
introductionLoaded: {
type: Boolean,
bindNuclear: hass => hass.configGetters.isComponentLoaded('introduction'),
},
locationName: {
type: String,
bindNuclear: hass => hass.configGetters.locationName,
},
showMenu: {
type: Boolean,
value: false,
observer: 'windowChange',
},
currentView: {
type: String,
bindNuclear: hass => [
hass.viewGetters.currentView,
view => view || '',
],
},
hasViews: {
type: Boolean,
bindNuclear: hass => [
hass.viewGetters.views,
views => views.size > 0,
],
},
states: {
type: Object,
bindNuclear: hass => hass.viewGetters.currentViewEntities,
},
columns: {
type: Number,
value: 1,
},
},
created() {
this.windowChange = this.windowChange.bind(this);
const sizes = [];
for (let col = 0; col < 5; col++) {
sizes.push(300 + (col * 300));
}
this.mqls = sizes.map(width => {
const mql = window.matchMedia(`(min-width: ${width}px)`);
mql.addListener(this.windowChange);
return mql;
});
},
detached() {
this.mqls.forEach(mql => mql.removeListener(this.windowChange));
},
windowChange() {
const matchColumns = this.mqls.reduce((cols, mql) => cols + mql.matches, 0);
// Do -1 column if the menu is docked and open
this.columns = Math.max(1, matchColumns - (!this.narrow && this.showMenu));
},
scrollToTop() {
this.$.panel.scrollToTop(true);
},
handleRefresh() {
this.hass.syncActions.fetchAll();
},
handleListenClick() {
this.hass.voiceActions.listen();
},
contentScroll() {
if (this.debouncedContentScroll) return;
this.debouncedContentScroll = this.async(() => {
this.checkRaised();
this.debouncedContentScroll = false;
}, 100);
},
checkRaised() {
this.toggleClass(
'raised',
this.$.panel.scroller.scrollTop > (this.hasViews ? 56 : 0),
this.$.panel);
},
headerScrollAdjust(ev) {
if (!this.hasViews) return;
this.translate3d('0', `-${ev.detail.y}px`, '0', this.$.menu);
// this.toggleClass('condensed', ev.detail.y === 56, this.$.panel);
},
computeHeaderHeight(hasViews, narrow) {
if (hasViews) {
return 104;
} else if (narrow) {
return 56;
}
return 64;
},
computeCondensedHeaderHeight(hasViews, narrow) {
if (hasViews) {
return 48;
} else if (narrow) {
return 56;
}
return 64;
},
computeMenuButtonClass(narrow, showMenu) {
return !narrow && showMenu ? 'menu-icon invisible' : 'menu-icon';
},
computeRefreshButtonClass(isFetching) {
return isFetching ? 'ha-spin' : '';
},
computeTitle(hasViews, locationName) {
return hasViews ? 'Home Assistant' : locationName;
},
computeShowIntroduction(currentView, introductionLoaded, states) {
return currentView === '' && (introductionLoaded || states.size === 0);
},
computeHasViews(views) {
return views.length > 0;
},
toggleMenu() {
this.fire('open-menu');
},
});

View File

@ -12,3 +12,37 @@
<paper-toast id="toast" text='{{text}}' no-cancel-on-outside-click='[[neg]]'></paper-toast>
</template>
</dom-module>
<script>
Polymer({
is: 'notification-manager',
behaviors: [window.hassBehavior],
properties: {
hass: {
type: Object,
},
// Otherwise we cannot close a modal when a notification is being shown.
neg: {
type: Boolean,
value: false,
},
text: {
type: String,
bindNuclear: function (hass) {
return hass.notificationGetters.lastNotificationMessage;
},
observer: 'showNotification',
},
},
showNotification: function (newText) {
if (newText) {
this.$.toast.show();
}
},
});
</script>

View File

@ -1,31 +0,0 @@
import Polymer from '../polymer';
export default new Polymer({
is: 'notification-manager',
behaviors: [window.hassBehavior],
properties: {
hass: {
type: Object,
},
// Otherwise we cannot close a modal when a notification is being shown.
neg: {
type: Boolean,
value: false,
},
text: {
type: String,
bindNuclear: hass => hass.notificationGetters.lastNotificationMessage,
observer: 'showNotification',
},
},
showNotification(newText) {
if (newText) {
this.$.toast.show();
}
},
});

View File

@ -1,8 +1,5 @@
import Polymer from '../polymer';
import dynamicContentUpdater from '../util/dynamic-content-updater';
import stateMoreInfoType from '../util/state-more-info-type';
import './more-info-group';
import './more-info-sun';
import './more-info-configurator';
@ -32,8 +29,8 @@ export default new Polymer({
stateObjChanged(stateObj) {
if (!stateObj) return;
dynamicContentUpdater(
this, `MORE-INFO-${stateMoreInfoType(stateObj).toUpperCase()}`,
window.hassUtil.dynamicContentUpdater(
this, `MORE-INFO-${window.hassUtil.stateMoreInfoType(stateObj).toUpperCase()}`,
{ hass: this.hass, stateObj });
},
});

View File

@ -1,8 +1,4 @@
import Polymer from '../polymer';
import dynamicContentUpdater from '../util/dynamic-content-updater';
import stateMoreInfoType from '../util/state-more-info-type';
import '../state-summary/state-card-content';
export default new Polymer({
is: 'more-info-group',
@ -64,9 +60,9 @@ export default new Polymer({
el.removeChild(el.lastChild);
}
} else {
dynamicContentUpdater(
window.hassUtil.dynamicContentUpdater(
this.$.groupedControlDetails,
`MORE-INFO-${stateMoreInfoType(groupDomainStateObj).toUpperCase()}`,
`MORE-INFO-${window.hassUtil.stateMoreInfoType(groupDomainStateObj).toUpperCase()}`,
{ stateObj: groupDomainStateObj });
}
},

View File

@ -1,5 +1,4 @@
import Polymer from '../polymer';
import attributeClassNames from '../util/attribute-class-names';
const ATTRIBUTE_CLASSES = [
'away_mode',
@ -80,7 +79,7 @@ export default new Polymer({
},
computeClassNames(stateObj) {
return `more-info-hvac ${attributeClassNames(stateObj, ATTRIBUTE_CLASSES)}`;
return `more-info-hvac ${window.hassUtil.attributeClassNames(stateObj, ATTRIBUTE_CLASSES)}`;
},
targetTemperatureSliderChanged(ev) {

View File

@ -1,5 +1,4 @@
import Polymer from '../polymer';
import attributeClassNames from '../util/attribute-class-names';
const ATTRIBUTE_CLASSES = ['brightness', 'rgb_color', 'color_temp'];
@ -44,7 +43,7 @@ export default new Polymer({
},
computeClassNames(stateObj) {
return attributeClassNames(stateObj, ATTRIBUTE_CLASSES);
return window.hassUtil.attributeClassNames(stateObj, ATTRIBUTE_CLASSES);
},
brightnessSliderChanged(ev) {

View File

@ -1,5 +1,4 @@
import Polymer from '../polymer';
import attributeClassNames from '../util/attribute-class-names';
const ATTRIBUTE_CLASSES = ['volume_level'];
@ -128,7 +127,7 @@ export default new Polymer({
},
computeClassNames(stateObj) {
return attributeClassNames(stateObj, ATTRIBUTE_CLASSES);
return window.hassUtil.attributeClassNames(stateObj, ATTRIBUTE_CLASSES);
},
computeIsOff(stateObj) {

View File

@ -1,7 +1,5 @@
import Polymer from '../polymer';
import formatTime from '../util/format-time';
export default new Polymer({
is: 'more-info-sun',
@ -42,6 +40,6 @@ export default new Polymer({
},
itemValue(type) {
return formatTime(this.itemDate(type));
return window.hassUtil.formatTime(this.itemDate(type));
},
});

View File

@ -1,5 +1,4 @@
import Polymer from '../polymer';
import attributeClassNames from '../util/attribute-class-names';
const ATTRIBUTE_CLASSES = ['away_mode'];
@ -42,7 +41,7 @@ export default new Polymer({
},
computeClassNames(stateObj) {
return attributeClassNames(stateObj, ATTRIBUTE_CLASSES);
return window.hassUtil.attributeClassNames(stateObj, ATTRIBUTE_CLASSES);
},
targetTemperatureSliderChanged(ev) {

View File

@ -12,3 +12,42 @@
<link rel="import" href="state-card-thermostat.html">
<link rel="import" href="state-card-toggle.html">
<link rel="import" href="state-card-weblink.html">
<script>
Polymer({
is: 'state-card-content',
properties: {
hass: {
type: Object,
},
inDialog: {
type: Boolean,
value: false,
},
stateObj: {
type: Object,
},
},
observers: [
'inputChanged(hass, inDialog, stateObj)',
],
inputChanged: function (hass, inDialog, stateObj) {
if (!stateObj) return;
window.hassUtil.dynamicContentUpdater(
this,
('STATE-CARD-' +
window.hassUtil.stateCardType(this.hass, stateObj).toUpperCase()),
{
hass: hass,
stateObj: stateObj,
inDialog: inDialog,
});
},
});
</script>

View File

@ -1,37 +0,0 @@
import Polymer from '../polymer';
import stateCardType from '../util/state-card-type';
import dynamicContentUpdater from '../util/dynamic-content-updater';
import '../components/state-info';
export default new Polymer({
is: 'state-card-content',
properties: {
hass: {
type: Object,
},
inDialog: {
type: Boolean,
value: false,
},
stateObj: {
type: Object,
},
},
observers: [
'inputChanged(hass, inDialog, stateObj)',
],
inputChanged(hass, inDialog, stateObj) {
if (!stateObj) return;
dynamicContentUpdater(
this, `STATE-CARD-${stateCardType(this.hass, stateObj).toUpperCase()}`,
{ hass, stateObj, inDialog });
},
});

View File

@ -1,6 +0,0 @@
export default function attributeClassNames(stateObj, attributes) {
if (!stateObj) return '';
return attributes.map(
(attribute) => (attribute in stateObj.attributes ? `has-${attribute}` : '')
).join(' ');
}

View File

@ -1,4 +0,0 @@
// Return boolean if entity can be toggled.
export default function canToggle(hass, entityId) {
return hass.reactor.evaluate(hass.serviceGetters.canToggleEntity(entityId));
}

View File

@ -1 +0,0 @@
export default 'mdi:bookmark';

View File

@ -1,103 +0,0 @@
import defaultIcon from './default-icon';
window.domainIcon = function (domain, state) {
switch (domain) {
case 'alarm_control_panel':
return state && state === 'disarmed' ? 'mdi:bell-outline' : 'mdi:bell';
case 'automation':
return 'mdi:playlist-play';
case 'binary_sensor':
return state && state === 'off' ? 'mdi:radiobox-blank' : 'mdi:checkbox-marked-circle';
case 'camera':
return 'mdi:video';
case 'configurator':
return 'mdi:settings';
case 'conversation':
return 'mdi:text-to-speech';
case 'device_tracker':
return 'mdi:account';
case 'garage_door':
return 'mdi:glassdoor';
case 'group':
return 'mdi:google-circles-communities';
case 'homeassistant':
return 'mdi:home';
case 'hvac':
return 'mdi:air-conditioner';
case 'input_boolean':
return 'mdi:drawing';
case 'input_select':
return 'mdi:format-list-bulleted';
case 'input_slider':
return 'mdi:ray-vertex';
case 'light':
return 'mdi:lightbulb';
case 'lock':
return state && state === 'unlocked' ? 'mdi:lock-open' : 'mdi:lock';
case 'media_player':
return state && state !== 'off' && state !== 'idle' ?
'mdi:cast-connected' : 'mdi:cast';
case 'notify':
return 'mdi:comment-alert';
case 'proximity':
return 'mdi:apple-safari';
case 'rollershutter':
return state && state === 'open' ? 'mdi:window-open' : 'mdi:window-closed';
case 'scene':
return 'mdi:google-pages';
case 'script':
return 'mdi:file-document';
case 'sensor':
return 'mdi:eye';
case 'simple_alarm':
return 'mdi:bell';
case 'sun':
return 'mdi:white-balance-sunny';
case 'switch':
return 'mdi:flash';
case 'thermostat':
return 'mdi:nest-thermostat';
case 'updater':
return 'mdi:cloud-upload';
case 'weblink':
return 'mdi:open-in-new';
default:
if (__DEV__) {
/* eslint-disable no-console */
console.warn(`Unable to find icon for domain ${domain} (${state})`);
/* eslint-enable no-console */
}
return defaultIcon;
}
};
export default window.domainIcon;

View File

@ -1,22 +0,0 @@
import Polymer from '../polymer';
export default function dynamicContentUpdater(root, newElementTag, attributes) {
const rootEl = Polymer.dom(root);
let customEl;
if (rootEl.lastChild && rootEl.lastChild.tagName === newElementTag) {
customEl = rootEl.lastChild;
} else {
if (rootEl.lastChild) {
rootEl.removeChild(rootEl.lastChild);
}
customEl = document.createElement(newElementTag);
}
Object.keys(attributes).forEach(key => { customEl[key] = attributes[key]; });
if (customEl.parentNode === null) {
rootEl.appendChild(customEl);
}
}

View File

@ -1,3 +0,0 @@
export default function formatDateTime(dateObj) {
return window.moment(dateObj).format('lll');
}

View File

@ -1,3 +0,0 @@
export default function formatDate(dateObj) {
return window.moment(dateObj).format('ll');
}

View File

@ -1,3 +0,0 @@
export default function formatTime(dateObj) {
return window.moment(dateObj).format('LT');
}

View File

@ -9,11 +9,13 @@ window.hassBehavior = {
this.nuclearUnwatchFns = Object.keys(this.properties).reduce(
function bindGetters(unwatchFns, key) {
var getter;
if (!('bindNuclear' in this.properties[key])) {
return unwatchFns;
}
var getter = this.properties[key].bindNuclear(hass);
getter = this.properties[key].bindNuclear(hass);
if (!getter) {
throw new Error('Undefined getter specified for key ' + key);

255
src/util/hass-util.html Normal file
View File

@ -0,0 +1,255 @@
<!--
collection of utility functions.
-->
<script>
window.hassUtil = window.hassUtil || {};
window.hassUtil.DEFAULT_ICON = 'mdi:bookmark';
window.hassUtil.OFF_STATES = ['off', 'closed', 'unlocked'];
window.hassUtil.DOMAINS_WITH_CARD = [
'configurator',
'hvac',
'input_select',
'input_slider',
'media_player',
'rollershutter',
'scene',
'script',
'thermostat',
'weblink',
];
window.hassUtil.DOMAINS_WITH_MORE_INFO = [
'light', 'group', 'sun', 'configurator', 'thermostat', 'script',
'media_player', 'camera', 'updater', 'alarm_control_panel', 'lock',
'hvac',
];
window.hassUtil.DOMAINS_WITH_NO_HISTORY = ['camera', 'configurator', 'scene'];
window.hassUtil.HIDE_MORE_INFO = [
'input_select', 'scene', 'script', 'input_slider',
];
window.hassUtil.attributeClassNames = function (stateObj, attributes) {
if (!stateObj) return '';
return attributes.map(
function (attribute) {
return attribute in stateObj.attributes ? 'has-' + attribute : '';
}
).join(' ');
};
window.hassUtil.canToggle = function (hass, entityId) {
return hass.reactor.evaluate(hass.serviceGetters.canToggleEntity(entityId));
};
window.hassUtil.dynamicContentUpdater = function (root, newElementTag, attributes) {
var rootEl = Polymer.dom(root);
var customEl;
if (rootEl.lastChild && rootEl.lastChild.tagName === newElementTag) {
customEl = rootEl.lastChild;
} else {
if (rootEl.lastChild) {
rootEl.removeChild(rootEl.lastChild);
}
customEl = document.createElement(newElementTag);
}
Object.keys(attributes).forEach(function (key) {
customEl[key] = attributes[key];
});
if (customEl.parentNode === null) {
rootEl.appendChild(customEl);
}
};
window.hassUtil.formatDateTime = function (dateObj) {
return window.moment(dateObj).format('lll');
};
window.hassUtil.formatDate = function (dateObj) {
return window.moment(dateObj).format('ll');
};
window.hassUtil.formatTime = function (dateObj) {
return window.moment(dateObj).format('LT');
};
window.hassUtil.stateCardType = function (hass, state) {
if (state.state === 'unavailable') {
return 'display';
} else if (window.hassUtil.DOMAINS_WITH_CARD.indexOf(state.domain) !== -1) {
return state.domain;
} else if (window.hassUtil.canToggle(hass, state.entityId)) {
return 'toggle';
}
return 'display';
};
window.hassUtil.stateMoreInfoType = function (state) {
if (window.hassUtil.DOMAINS_WITH_MORE_INFO.indexOf(state.domain) !== -1) {
return state.domain;
}
if (window.hassUtil.HIDE_MORE_INFO.indexOf(state.domain) !== -1) {
return 'hidden';
}
return 'default';
};
window.hassUtil.domainIcon = function (domain, state) {
switch (domain) {
case 'alarm_control_panel':
return state && state === 'disarmed' ? 'mdi:bell-outline' : 'mdi:bell';
case 'automation':
return 'mdi:playlist-play';
case 'binary_sensor':
return state && state === 'off' ? 'mdi:radiobox-blank' : 'mdi:checkbox-marked-circle';
case 'camera':
return 'mdi:video';
case 'configurator':
return 'mdi:settings';
case 'conversation':
return 'mdi:text-to-speech';
case 'device_tracker':
return 'mdi:account';
case 'garage_door':
return 'mdi:glassdoor';
case 'group':
return 'mdi:google-circles-communities';
case 'homeassistant':
return 'mdi:home';
case 'hvac':
return 'mdi:air-conditioner';
case 'input_boolean':
return 'mdi:drawing';
case 'input_select':
return 'mdi:format-list-bulleted';
case 'input_slider':
return 'mdi:ray-vertex';
case 'light':
return 'mdi:lightbulb';
case 'lock':
return state && state === 'unlocked' ? 'mdi:lock-open' : 'mdi:lock';
case 'media_player':
return state && state !== 'off' && state !== 'idle' ?
'mdi:cast-connected' : 'mdi:cast';
case 'notify':
return 'mdi:comment-alert';
case 'proximity':
return 'mdi:apple-safari';
case 'rollershutter':
return state && state === 'open' ? 'mdi:window-open' : 'mdi:window-closed';
case 'scene':
return 'mdi:google-pages';
case 'script':
return 'mdi:file-document';
case 'sensor':
return 'mdi:eye';
case 'simple_alarm':
return 'mdi:bell';
case 'sun':
return 'mdi:white-balance-sunny';
case 'switch':
return 'mdi:flash';
case 'thermostat':
return 'mdi:nest-thermostat';
case 'updater':
return 'mdi:cloud-upload';
case 'weblink':
return 'mdi:open-in-new';
default:
/* eslint-disable no-console */
console.warn(
'Unable to find icon for domain ' + domain + ' (' + state + ')');
/* eslint-enable no-console */
return window.hassUtil.DEFAULT_ICON;
}
};
window.hassUtil.binarySensorIcon = function (state) {
var activated = state.state && state.state === 'off';
switch (state.attributes.sensor_class) {
case 'opening':
return activated ? 'mdi:crop-square' : 'mdi:exit-to-app';
case 'moisture':
return activated ? 'mdi:water-off' : 'mdi:water';
case 'light':
return activated ? 'mdi:brightness-5' : 'mdi:brightness-7';
case 'sound':
return activated ? 'mdi:music-note-off' : 'mdi:music-note';
case 'vibration':
return activated ? 'mdi:crop-portrait' : 'mdi:vibrate';
case 'connectivity':
return activated ? 'mdi:server-network-off' : 'mdi:server-network';
case 'safety':
case 'gas':
case 'smoke':
case 'power':
return activated ? 'mdi:verified' : 'mdi:alert';
case 'motion':
return activated ? 'mdi:walk' : 'mdi:run';
case 'digital':
default:
return activated ? 'mdi:radiobox-blank' : 'mdi:checkbox-marked-circle';
}
};
window.hassUtil.stateIcon = function (state) {
var unit;
if (!state) {
return window.hassUtil.DEFAULT_ICON;
} else if (state.attributes.icon) {
return state.attributes.icon;
}
unit = state.attributes.unit_of_measurement;
if (unit && state.domain === 'sensor') {
if (unit === '°C' || unit === '°F') {
return 'mdi:thermometer';
} else if (unit === 'Mice') {
return 'mdi:mouse-variant';
}
} else if (state.domain === 'binary_sensor') {
return window.hassUtil.binarySensorIcon(state);
}
return window.hassUtil.domainIcon(state.domain, state.state);
};
</script>

View File

@ -1 +0,0 @@
export default ['off', 'closed', 'unlocked'];

View File

@ -1,25 +0,0 @@
import canToggle from './can-toggle';
const DOMAINS_WITH_CARD = [
'configurator',
'hvac',
'input_select',
'input_slider',
'media_player',
'rollershutter',
'scene',
'script',
'thermostat',
'weblink',
];
export default function stateCardType(hass, state) {
if (state.state === 'unavailable') {
return 'display';
} else if (DOMAINS_WITH_CARD.indexOf(state.domain) !== -1) {
return state.domain;
} else if (canToggle(hass, state.entityId)) {
return 'toggle';
}
return 'display';
}

View File

@ -1,52 +0,0 @@
import defaultIcon from './default-icon';
import domainIcon from './domain-icon.js';
function binarySensorIcon(state) {
const activated = state.state && state.state === 'off';
switch (state.attributes.sensor_class) {
case 'opening':
return activated ? 'mdi:crop-square' : 'mdi:exit-to-app';
case 'moisture':
return activated ? 'mdi:water-off' : 'mdi:water';
case 'light':
return activated ? 'mdi:brightness-5' : 'mdi:brightness-7';
case 'sound':
return activated ? 'mdi:music-note-off' : 'mdi:music-note';
case 'vibration':
return activated ? 'mdi:crop-portrait' : 'mdi:vibrate';
case 'connectivity':
return activated ? 'mdi:server-network-off' : 'mdi:server-network';
case 'safety':
case 'gas':
case 'smoke':
case 'power':
return activated ? 'mdi:verified' : 'mdi:alert';
case 'motion':
return activated ? 'mdi:walk' : 'mdi:run';
case 'digital':
default:
return activated ? 'mdi:radiobox-blank' : 'mdi:checkbox-marked-circle';
}
}
export default function stateIcon(state) {
if (!state) {
return defaultIcon;
} else if (state.attributes.icon) {
return state.attributes.icon;
}
const unit = state.attributes.unit_of_measurement;
if (unit && state.domain === 'sensor') {
if (unit === '°C' || unit === '°F') {
return 'mdi:thermometer';
} else if (unit === 'Mice') {
return 'mdi:mouse-variant';
}
} else if (state.domain === 'binary_sensor') {
return binarySensorIcon(state);
}
return domainIcon(state.domain, state.state);
}

View File

@ -1,19 +0,0 @@
const DOMAINS_WITH_MORE_INFO = [
'light', 'group', 'sun', 'configurator', 'thermostat', 'script',
'media_player', 'camera', 'updater', 'alarm_control_panel', 'lock',
'hvac',
];
const HIDE_MORE_INFO = [
'input_select', 'scene', 'script', 'input_slider',
];
export default function stateMoreInfoType(state) {
if (DOMAINS_WITH_MORE_INFO.indexOf(state.domain) !== -1) {
return state.domain;
}
if (HIDE_MORE_INFO.indexOf(state.domain) !== -1) {
return 'hidden';
}
return 'default';
}