Merge pull request #63 from home-assistant/inline-javascript

Clean up - step 2: Break up app in core + ui and inline some scripts
This commit is contained in:
Paulus Schoutsen 2016-05-28 16:15:07 -07:00
commit c1cbd33d41
59 changed files with 839 additions and 835 deletions

View File

@ -2,6 +2,15 @@
"extends": "airbnb-base",
"globals": {
"__DEV__": false,
"__DEMO__": false
"Polymer": true
},
"rules": {
"new-cap": 0,
"prefer-template": 0,
"object-shorthand": 0,
"func-names": 0,
"prefer-arrow-callback": 0,
"no-underscore-dangle": 0,
"no-var": 0
}
}

23
src/app-core.js Normal file
View File

@ -0,0 +1,23 @@
import moment from 'moment';
import HomeAssistant from 'home-assistant-js';
window.moment = moment;
// While we figure out how ha-entity-marker can keep it's references
window.hass = new HomeAssistant();
window.validateAuth = function validateAuth(hass, authToken, rememberAuth) {
hass.authActions.validate(authToken, {
rememberAuth,
useStreaming: hass.localStoragePreferences.useStreaming,
});
};
window.removeInitMsg = function removeInitMessage() {
// remove the HTML init message
const initMsg = document.getElementById('ha-init-skeleton');
if (initMsg) {
initMsg.parentElement.removeChild(initMsg);
}
};

View File

@ -4,7 +4,6 @@ import dynamicContentUpdater from '../util/dynamic-content-updater';
require('./ha-camera-card');
require('./ha-entities-card');
require('./ha-introduction-card');
require('./ha-media_player-card');
export default new Polymer({

View File

@ -1,7 +1,6 @@
import Polymer from '../polymer';
import canToggle from '../util/can-toggle';
require('../components/ha-card');
require('../components/entity/ha-entity-toggle');
require('../state-summary/state-card-content');

View File

@ -30,7 +30,7 @@
<template>
<ha-card header="Welcome Home!">
<div class='content'>
<template is='dom-if' if='[[showInstallInstruction]]'>
<template is='dom-if' if='[[hass.demo]]'>
To install Home Assistant, run:<br />
<code class='install'>
pip3 install homeassistant<br />
@ -39,7 +39,7 @@
</template>
Here are some resources to get started.
<ul>
<template is='dom-if' if='[[showInstallInstruction]]'>
<template is='dom-if' if='[[hass.demo]]'>
<li><a href='https://home-assistant.io/getting-started/'>Home Assistant website</a></li>
<li><a href='https://home-assistant.io/getting-started/'>Installation instructions</a></li>
<li><a href='https://home-assistant.io/getting-started/troubleshooting/'>Troubleshooting your installation</a></li>
@ -64,3 +64,20 @@
</ha-card>
</template>
</dom-module>
<script>
Polymer({
is: 'ha-introduction-card',
properties: {
hass: {
type: Object,
},
showHideInstruction: {
type: Boolean,
value: true,
},
},
});
</script>

View File

@ -1,18 +0,0 @@
import Polymer from '../polymer';
require('../components/ha-card');
export default new Polymer({
is: 'ha-introduction-card',
properties: {
showInstallInstruction: {
type: Boolean,
value: __DEMO__,
},
showHideInstruction: {
type: Boolean,
value: true,
},
},
});

View File

@ -25,3 +25,34 @@
</ul>
</template>
</dom-module>
<script>
Polymer({
is: 'entity-list',
behaviors: [window.hassBehavior],
properties: {
hass: {
type: Object,
},
entities: {
type: Array,
bindNuclear: function (hass) {
return [
hass.entityGetters.entityMap,
function (map) {
return map.valueSeq().sortBy(function (entity) { return entity.entityId; }).toArray();
},
];
},
},
},
entitySelected: function (ev) {
ev.preventDefault();
this.fire('entity-selected', { entityId: ev.model.entity.entityId });
},
});
</script>

View File

@ -1,26 +0,0 @@
import Polymer from '../polymer';
export default new Polymer({
is: 'entity-list',
behaviors: [window.hassBehavior],
properties: {
hass: {
type: Object,
},
entities: {
type: Array,
bindNuclear: hass => [
hass.entityGetters.entityMap,
(map) => map.valueSeq().sortBy((entity) => entity.entityId).toArray(),
],
},
},
entitySelected(ev) {
ev.preventDefault();
this.fire('entity-selected', { entityId: ev.model.entity.entityId });
},
});

View File

@ -1,7 +1,5 @@
import Polymer from '../../polymer';
require('../../components/ha-label-badge');
/*
Leaflet clones this element before adding it to the map. This messes up
our Poylmer object and we lose the reference to the `hass` object.

View File

@ -2,8 +2,6 @@ import Polymer from '../../polymer';
import domainIcon from '../../util/domain-icon';
import stateIcon from '../../util/state-icon';
require('../../components/ha-label-badge');
export default new Polymer({
is: 'ha-state-label-badge',

View File

@ -28,3 +28,34 @@
</ul>
</template>
</dom-module>
<script>
Polymer({
is: 'events-list',
behaviors: [window.hassBehavior],
properties: {
hass: {
type: Object,
},
events: {
type: Array,
bindNuclear: function (hass) {
return [
hass.eventGetters.entityMap,
function (map) {
return map.valueSeq().sortBy(function (event) { return event.event; }).toArray();
},
];
},
},
},
eventSelected: function (ev) {
ev.preventDefault();
this.fire('event-selected', { eventType: ev.model.event.event });
},
});
</script>

View File

@ -1,26 +0,0 @@
import Polymer from '../polymer';
export default new Polymer({
is: 'events-list',
behaviors: [window.hassBehavior],
properties: {
hass: {
type: Object,
},
events: {
type: Array,
bindNuclear: hass => [
hass.eventGetters.entityMap,
(map) => map.valueSeq().sortBy((event) => event.event).toArray(),
],
},
},
eventSelected(ev) {
ev.preventDefault();
this.fire('event-selected', { eventType: ev.model.event.event });
},
});

View File

@ -25,3 +25,23 @@
<content></content>
</template>
</dom-module>
<script>
Polymer({
is: 'ha-card',
properties: {
header: {
type: String,
},
/**
* The z-depth of the card, from 0-5.
*/
elevation: {
type: Number,
value: 1,
reflectToAttribute: true,
},
},
});
</script>

View File

@ -1,19 +0,0 @@
import Polymer from '../polymer';
export default new Polymer({
is: 'ha-card',
properties: {
header: {
type: String,
},
/**
* The z-depth of the card, from 0-5.
*/
elevation: {
type: Number,
value: 1,
reflectToAttribute: true,
},
},
});

View File

@ -1,6 +1,5 @@
import Polymer from '../polymer';
require('.//ha-demo-badge');
require('../cards/ha-badges-card');
require('../cards/ha-card-chooser');
@ -109,8 +108,9 @@ export default new Polymer({
}
if (showIntroduction) {
cards.columns[getIndex(5)].push({
hass,
cardType: 'introduction',
showHideInstruction: states.size > 0 && !__DEMO__,
showHideInstruction: states.size > 0 && !hass.demo,
});
}

View File

@ -10,3 +10,134 @@
<canvas width='[[width]]' height='[[height]]'></canvas>
</template>
</dom-module>
<script>
/**
* Color-picker custom element
* Originally created by bbrewer97202 (Ben Brewer). MIT Licensed.
* https://github.com/bbrewer97202/color-picker-element
*
* Adapted to work with Polymer.
*/
Polymer({
is: 'ha-color-picker',
properties: {
color: {
type: Object,
},
width: {
type: Number,
},
height: {
type: Number,
},
},
listeners: {
mousedown: 'onMouseDown',
mouseup: 'onMouseUp',
touchstart: 'onTouchStart',
touchend: 'onTouchEnd',
},
onMouseDown: function (ev) {
this.onMouseMove(ev);
this.addEventListener('mousemove', this.onMouseMove);
},
onMouseUp: function () {
this.removeEventListener('mousemove', this.onMouseMove);
},
onTouchStart: function (ev) {
this.onTouchMove(ev);
this.addEventListener('touchmove', this.onTouchMove);
},
onTouchEnd: function () {
this.removeEventListener('touchmove', this.onTouchMove);
},
onTouchMove: function (ev) {
if (!this.mouseMoveIsThrottled) {
return;
}
this.mouseMoveIsThrottled = false;
this.processColorSelect(ev.touches[0]);
this.async(function () { this.mouseMoveIsThrottled = true; }.bind(this), 100);
},
onMouseMove: function (ev) {
if (!this.mouseMoveIsThrottled) {
return;
}
this.mouseMoveIsThrottled = false;
this.processColorSelect(ev);
this.async(function () { this.mouseMoveIsThrottled = true; }.bind(this), 100);
},
processColorSelect: function (ev) {
var rect = this.canvas.getBoundingClientRect();
// boundary check because people can move off-canvas.
if (ev.clientX < rect.left || ev.clientX >= rect.left + rect.width ||
ev.clientY < rect.top || ev.clientY >= rect.top + rect.height) {
return;
}
this.onColorSelect(ev.clientX - rect.left, ev.clientY - rect.top);
},
onColorSelect: function (x, y) {
var data = this.context.getImageData(x, y, 1, 1).data;
this.setColor({ r: data[0], g: data[1], b: data[2] });
},
setColor: function (rgb) {
this.color = rgb;
this.fire('colorselected', { rgb: this.color });
},
ready: function () {
this.setColor = this.setColor.bind(this);
this.mouseMoveIsThrottled = true;
this.canvas = this.children[0];
this.context = this.canvas.getContext('2d');
this.drawGradient();
},
drawGradient: function () {
var style;
if (!this.width || !this.height) {
style = getComputedStyle(this);
}
var width = this.width || parseInt(style.width, 10);
var height = this.height || parseInt(style.height, 10);
var 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)');
colorGradient.addColorStop(0.48, 'rgb(0,255,255)');
colorGradient.addColorStop(0.64, 'rgb(0,255,0)');
colorGradient.addColorStop(0.80, 'rgb(255,255,0)');
colorGradient.addColorStop(1, 'rgb(255,0,0)');
this.context.fillStyle = colorGradient;
this.context.fillRect(0, 0, width, height);
var 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)');
bwGradient.addColorStop(1, 'rgba(0,0,0,1)');
this.context.fillStyle = bwGradient;
this.context.fillRect(0, 0, width, height);
},
});
</script>

View File

@ -1,130 +0,0 @@
/**
* Color-picker custom element
* Originally created by bbrewer97202 (Ben Brewer). MIT Licensed.
* https://github.com/bbrewer97202/color-picker-element
*
* Adapted to work with Polymer.
*/
import Polymer from '../polymer';
export default new Polymer({
is: 'ha-color-picker',
properties: {
color: {
type: Object,
},
width: {
type: Number,
},
height: {
type: Number,
},
},
listeners: {
mousedown: 'onMouseDown',
mouseup: 'onMouseUp',
touchstart: 'onTouchStart',
touchend: 'onTouchEnd',
},
onMouseDown(ev) {
this.onMouseMove(ev);
this.addEventListener('mousemove', this.onMouseMove);
},
onMouseUp() {
this.removeEventListener('mousemove', this.onMouseMove);
},
onTouchStart(ev) {
this.onTouchMove(ev);
this.addEventListener('touchmove', this.onTouchMove);
},
onTouchEnd() {
this.removeEventListener('touchmove', this.onTouchMove);
},
onTouchMove(ev) {
if (!this.mouseMoveIsThrottled) {
return;
}
this.mouseMoveIsThrottled = false;
this.processColorSelect(ev.touches[0]);
this.async(() => { this.mouseMoveIsThrottled = true; }, 100);
},
onMouseMove(ev) {
if (!this.mouseMoveIsThrottled) {
return;
}
this.mouseMoveIsThrottled = false;
this.processColorSelect(ev);
this.async(() => { this.mouseMoveIsThrottled = true; }, 100);
},
processColorSelect(ev) {
const rect = this.canvas.getBoundingClientRect();
// boundary check because people can move off-canvas.
if (ev.clientX < rect.left || ev.clientX >= rect.left + rect.width ||
ev.clientY < rect.top || ev.clientY >= rect.top + rect.height) {
return;
}
this.onColorSelect(ev.clientX - rect.left, ev.clientY - rect.top);
},
onColorSelect(x, y) {
const data = this.context.getImageData(x, y, 1, 1).data;
this.setColor({ r: data[0], g: data[1], b: data[2] });
},
setColor(rgb) {
this.color = rgb;
this.fire('colorselected', { rgb: this.color });
},
ready() {
this.setColor = this.setColor.bind(this);
this.mouseMoveIsThrottled = true;
this.canvas = this.children[0];
this.context = this.canvas.getContext('2d');
this.drawGradient();
},
drawGradient() {
let style;
if (!this.width || !this.height) {
style = getComputedStyle(this);
}
const width = this.width || parseInt(style.width, 10);
const height = this.height || parseInt(style.height, 10);
const 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)');
colorGradient.addColorStop(0.48, 'rgb(0,255,255)');
colorGradient.addColorStop(0.64, 'rgb(0,255,0)');
colorGradient.addColorStop(0.80, 'rgb(255,255,0)');
colorGradient.addColorStop(1, 'rgb(255,0,0)');
this.context.fillStyle = colorGradient;
this.context.fillRect(0, 0, width, height);
const 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)');
bwGradient.addColorStop(1, 'rgba(0,0,0,1)');
this.context.fillStyle = bwGradient;
this.context.fillRect(0, 0, width, height);
},
});

View File

@ -16,3 +16,9 @@
></ha-label-badge>
</template>
</dom-module>
<script>
Polymer({
is: 'ha-demo-badge',
});
</script>

View File

@ -1,7 +0,0 @@
import Polymer from '../polymer';
require('./ha-label-badge');
export default new Polymer({
is: 'ha-demo-badge',
});

View File

@ -83,3 +83,54 @@
</div>
</template>
</dom-module>
<script>
// Beware: Polymer will not call computeHideIcon and computeHideValue if any of
// the parameters are undefined. Set to null if not using.
Polymer({
is: 'ha-label-badge',
properties: {
value: {
type: String,
value: null,
},
icon: {
type: String,
value: null,
},
label: {
type: String,
value: null,
},
description: {
type: String,
},
image: {
type: String,
value: null,
observer: 'imageChanged',
},
},
computeClasses: function (value) {
return value && value.length > 4 ? 'value big' : 'value';
},
computeHideIcon: function (icon, value, image) {
return !icon || value || image;
},
computeHideValue: function (value, image) {
return !value || image;
},
imageChanged: function (newVal) {
this.$.badge.style.backgroundImage = newVal ? `url(${newVal})` : '';
},
});
</script>

View File

@ -1,50 +0,0 @@
import Polymer from '../polymer';
// Beware: Polymer will not call computeHideIcon and computeHideValue if any of
// the parameters are undefined. Set to null if not using.
export default new Polymer({
is: 'ha-label-badge',
properties: {
value: {
type: String,
value: null,
},
icon: {
type: String,
value: null,
},
label: {
type: String,
value: null,
},
description: {
type: String,
},
image: {
type: String,
value: null,
observer: 'imageChanged',
},
},
computeClasses(value) {
return value && value.length > 4 ? 'value big' : 'value';
},
computeHideIcon(icon, value, image) {
return !icon || value || image;
},
computeHideValue(value, image) {
return !value || image;
},
imageChanged(newVal) {
this.$.badge.style.backgroundImage = newVal ? `url(${newVal})` : '';
},
});

View File

@ -14,7 +14,28 @@
No logbook entries found.
</template>
<template is='dom-repeat' items="[[entries]]">
<logbook-entry entry-obj="[[item]]"></logbook-entry>
<logbook-entry entry-obj="[[item]]" hass='[[hass]]'></logbook-entry>
</template>
</template>
</dom-module>
<script>
Polymer({
is: 'ha-logbook',
properties: {
hass: {
type: Object,
},
entries: {
type: Array,
value: [],
},
},
noEntries: function (entries) {
return !entries.length;
},
});
</script>

View File

@ -1,18 +0,0 @@
import Polymer from '../polymer';
require('./logbook-entry');
export default new Polymer({
is: 'ha-logbook',
properties: {
entries: {
type: Object,
value: [],
},
},
noEntries(entries) {
return !entries.length;
},
});

View File

@ -169,3 +169,86 @@
</template>
</dom-module>
<script>
Polymer({
is: 'ha-sidebar',
behaviors: [window.hassBehavior],
properties: {
hass: {
type: Object,
},
menuShown: {
type: Boolean,
},
menuSelected: {
type: String,
},
narrow: {
type: Boolean,
},
selected: {
type: String,
bindNuclear: function (hass) { return hass.navigationGetters.activePane; },
},
hasHistoryComponent: {
type: Boolean,
bindNuclear: function (hass) { return hass.configGetters.isComponentLoaded('history'); },
},
hasLogbookComponent: {
type: Boolean,
bindNuclear: function (hass) { return hass.configGetters.isComponentLoaded('logbook'); },
},
},
created: function () {
this._boundUpdateStyles = this.updateStyles.bind(this);
},
menuSelect: function () {
this.debounce('updateStyles', this._boundUpdateStyles, 1);
},
menuClicked: function (ev) {
var target = ev.target;
var checks = 5;
// find panel to select
while (checks && !target.getAttribute('data-panel')) {
target = target.parentElement;
checks--;
}
if (checks) {
this.selectPanel(target.getAttribute('data-panel'));
}
},
toggleMenu: function () {
this.fire('close-menu');
},
selectPanel: function (newChoice) {
if (newChoice === this.selected) {
return;
} else if (newChoice === 'logout') {
this.handleLogOut();
return;
}
this.hass.navigationActions.navigate.apply(null, newChoice.split('/'));
this.debounce('updateStyles', this._boundUpdateStyles, 1);
},
handleLogOut: function () {
this.hass.authActions.logOut();
},
});
</script>

View File

@ -1,80 +0,0 @@
import Polymer from '../polymer';
require('./stream-status');
export default new Polymer({
is: 'ha-sidebar',
behaviors: [window.hassBehavior],
properties: {
hass: {
type: Object,
},
menuShown: {
type: Boolean,
},
menuSelected: {
type: String,
},
narrow: {
type: Boolean,
},
selected: {
type: String,
bindNuclear: hass => hass.navigationGetters.activePane,
},
hasHistoryComponent: {
type: Boolean,
bindNuclear: hass => hass.configGetters.isComponentLoaded('history'),
},
hasLogbookComponent: {
type: Boolean,
bindNuclear: hass => hass.configGetters.isComponentLoaded('logbook'),
},
},
menuSelect() {
this.debounce('updateStyles', () => this.updateStyles(), 1);
},
menuClicked(ev) {
let target = ev.target;
let checks = 5;
// find panel to select
while (checks && !target.getAttribute('data-panel')) {
target = target.parentElement;
checks--;
}
if (checks) {
this.selectPanel(target.getAttribute('data-panel'));
}
},
toggleMenu() {
this.fire('close-menu');
},
selectPanel(newChoice) {
if (newChoice === this.selected) {
return;
} else if (newChoice === 'logout') {
this.handleLogOut();
return;
}
this.hass.navigationActions.navigate.apply(null, newChoice.split('/'));
this.debounce('updateStyles', () => this.updateStyles(), 1);
},
handleLogOut() {
this.hass.authActions.logOut();
},
});

View File

@ -23,3 +23,58 @@
</paper-tabs>
</template>
</dom-module>
<script>
Polymer({
is: 'ha-view-tabs',
behaviors: [window.hassBehavior],
properties: {
hass: {
type: Object,
},
locationName: {
type: String,
bindNuclear: function (hass) { return hass.configGetters.locationName; },
},
currentView: {
type: String,
bindNuclear: function (hass) {
return [
hass.viewGetters.currentView,
function (view) { return view || ''; },
];
},
},
views: {
type: Array,
bindNuclear: function (hass) {
return [
hass.viewGetters.views,
function (views) {
return views.valueSeq()
.sortBy(function (view) { return view.attributes.order; })
.toArray();
},
];
},
},
},
viewTapped() {
this.fire('view-tapped');
},
viewSelected(ev) {
var view = ev.detail.item.getAttribute('data-entity') || null;
var current = this.currentView || null;
if (view !== current) {
this.async(function () { this.hass.viewActions.selectView(view); }.bind(this), 0);
}
},
});
</script>

View File

@ -1,49 +0,0 @@
import Polymer from '../polymer';
export default new Polymer({
is: 'ha-view-tabs',
behaviors: [window.hassBehavior],
properties: {
hass: {
type: Object,
},
locationName: {
type: String,
bindNuclear: hass => hass.configGetters.locationName,
},
currentView: {
type: String,
bindNuclear: hass => [
hass.viewGetters.currentView,
view => view || '',
],
},
views: {
type: Array,
bindNuclear: hass => [
hass.viewGetters.views,
views => views.valueSeq()
.sortBy(view => view.attributes.order)
.toArray(),
],
},
},
viewTapped() {
this.fire('view-tapped');
},
viewSelected(ev) {
const view = ev.detail.item.getAttribute('data-entity') || null;
const current = this.currentView || null;
this.expectChange = true;
if (view !== current) {
this.async(() => this.hass.viewActions.selectView(view), 0);
}
},
});

View File

@ -18,3 +18,9 @@
</div>
</template>
</dom-module>
<script>
Polymer({
is: 'loading-box',
});
</script>

View File

@ -1,5 +0,0 @@
import Polymer from '../polymer';
export default new Polymer({
is: 'loading-box',
});

View File

@ -2,7 +2,6 @@ import Polymer from '../polymer';
require('./domain-icon');
require('./display-time');
require('./relative-ha-datetime');
export default new Polymer({
is: 'logbook-entry',

View File

@ -5,3 +5,60 @@
<span>[[relativeTime]]</span>
</template>
</dom-module>
<script>
Polymer({
is: 'relative-ha-datetime',
properties: {
datetime: {
type: String,
observer: 'datetimeChanged',
},
datetimeObj: {
type: Object,
observer: 'datetimeObjChanged',
},
parsedDateTime: {
type: Object,
},
relativeTime: {
type: String,
value: 'not set',
},
},
created: function () {
this.updateRelative = this.updateRelative.bind(this);
},
attached: function () {
// update every 60 seconds
this.updateInterval = setInterval(this.updateRelative, 60000);
},
detached: function () {
clearInterval(this.updateInterval);
},
datetimeChanged: function (newVal) {
this.parsedDateTime = newVal ? new Date(newVal) : null;
this.updateRelative();
},
datetimeObjChanged: function (newVal) {
this.parsedDateTime = newVal;
this.updateRelative();
},
updateRelative: function () {
this.relativeTime = this.parsedDateTime ?
window.moment(this.parsedDateTime).fromNow() : '';
},
});
</script>

View File

@ -1,57 +0,0 @@
import Polymer from '../polymer';
const UPDATE_INTERVAL = 60000; // 60 seconds
export default new Polymer({
is: 'relative-ha-datetime',
properties: {
datetime: {
type: String,
observer: 'datetimeChanged',
},
datetimeObj: {
type: Object,
observer: 'datetimeObjChanged',
},
parsedDateTime: {
type: Object,
},
relativeTime: {
type: String,
value: 'not set',
},
},
created() {
this.updateRelative = this.updateRelative.bind(this);
},
attached() {
this.updateInterval = setInterval(this.updateRelative, UPDATE_INTERVAL);
},
detached() {
clearInterval(this.updateInterval);
},
datetimeChanged(newVal) {
this.parsedDateTime = newVal ? new Date(newVal) : null;
this.updateRelative();
},
datetimeObjChanged(newVal) {
this.parsedDateTime = newVal;
this.updateRelative();
},
updateRelative() {
this.relativeTime = this.parsedDateTime ?
window.moment(this.parsedDateTime).fromNow() : '';
},
});

View File

@ -1,6 +1,5 @@
import Polymer from '../polymer';
require('./loading-box');
require('./state-history-chart-timeline');
require('./state-history-chart-line');

View File

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

View File

@ -25,3 +25,35 @@
<paper-toggle-button id="toggle" on-change='toggleChanged' checked$='[[isStreaming]]' hidden$="[[hasError]]"></paper-toggle-button>
</template>
</dom-module>
<script>
Polymer({
is: 'stream-status',
behaviors: [window.hassBehavior],
properties: {
hass: {
type: Object,
},
isStreaming: {
type: Boolean,
bindNuclear: function (hass) { return hass.streamGetters.isStreamingEvents; },
},
hasError: {
type: Boolean,
bindNuclear: function (hass) { return hass.streamGetters.hasStreamingEventsError; },
},
},
toggleChanged: function () {
if (this.isStreaming) {
this.hass.streamActions.stop();
} else {
this.hass.streamActions.start();
}
},
});
</script>

View File

@ -1,31 +0,0 @@
import Polymer from '../polymer';
export default new Polymer({
is: 'stream-status',
behaviors: [window.hassBehavior],
properties: {
hass: {
type: Object,
},
isStreaming: {
type: Boolean,
bindNuclear: hass => hass.streamGetters.isStreamingEvents,
},
hasError: {
type: Boolean,
bindNuclear: hass => hass.streamGetters.hasStreamingEventsError,
},
},
toggleChanged() {
if (this.isStreaming) {
this.hass.streamActions.stop();
} else {
this.hass.streamActions.start();
}
},
});

View File

@ -1,14 +1,17 @@
<link rel='import' href='../bower_components/polymer/polymer.html'>
<link rel='import' href='../bower_components/paper-spinner/paper-spinner.html'>
<script src='../build/_app_core_compiled.js'></script>
<link rel='import' href='../bower_components/iron-flex-layout/iron-flex-layout-classes.html'>
<link rel='import' href='./util/roboto.html'>
<link rel='import' href='../bower_components/paper-styles/typography.html'>
<link rel="import" href="../bower_components/iron-iconset-svg/iron-iconset-svg.html">
<link rel='import' href='./util/hass-behavior.html'>
<link rel='import' href='./layouts/login-form.html'>
<link rel='import' href='./layouts/home-assistant-main.html'>
<link rel='import' href='resources/home-assistant-style.html'>
<link rel='import' href='./resources/home-assistant-style.html'>
<dom-module id='home-assistant'>
<style>
@ -29,9 +32,94 @@
</template>
<template is='dom-if' if='[[loaded]]'>
<home-assistant-main hass='[[hass]]'></home-assistant-main>
<home-assistant-main hass='[[hass]]' hidden$='[[!loaded]]'></home-assistant-main>
</template>
</template>
</dom-module>
<script>
Polymer({
is: 'home-assistant',
hostAttributes: {
auth: null,
icons: null,
},
behaviors: [window.hassBehavior],
properties: {
hass: {
type: Object,
value: window.hass,
},
auth: {
type: String,
},
icons: {
type: String,
},
dataLoaded: {
type: Boolean,
bindNuclear: function (hass) { return hass.syncGetters.isDataLoaded; },
},
iconsLoaded: {
type: Boolean,
value: false,
},
loaded: {
type: Boolean,
computed: 'computeLoaded(dataLoaded, iconsLoaded)',
},
},
computeLoaded: function (dataLoaded, iconsLoaded) {
return dataLoaded && iconsLoaded;
},
computeForceShowLoading: function (dataLoaded, iconsLoaded) {
return dataLoaded && !iconsLoaded;
},
loadIcons: function () {
// If the import fails, we'll try to import again, must be a server glitch
// Since HTML imports only resolve once, we import another url.
var success = function () {
this.iconsLoaded = true;
}.bind(this);
this.importHref(`/static/mdi-${this.icons}.html`,
success,
function () {
this.importHref('/static/mdi.html', success, success);
});
},
created: function () {
if (!('serviceWorker' in navigator)) {
return;
}
navigator.serviceWorker.register('/service_worker.js');
},
ready: function () {
var hass = this.hass;
hass.reactor.batch(function () {
// if auth was given, tell the backend
if (this.auth) {
window.validateAuth(this.hass, this.auth, false);
} else if (hass.localStoragePreferences.authToken) {
window.validateAuth(this.hass, hass.localStoragePreferences.authToken, true);
}
hass.navigationActions.showSidebar(hass.localStoragePreferences.showSidebar);
});
hass.startLocalStoragePreferencesSync();
this.loadIcons();
},
});
</script>
<script src='../build/_app_compiled.js'></script>

View File

@ -1,99 +1 @@
import moment from 'moment';
import Polymer from './polymer';
import HomeAssistant from 'home-assistant-js';
import validateAuth from './util/validate-auth';
import hassBehavior from './util/hass-behavior';
window.hassBehavior = hassBehavior;
window.moment = moment;
require('./layouts/login-form');
require('./layouts/home-assistant-main');
// While we figure out how ha-entity-marker can keep it's references
window.hass = new HomeAssistant();
export default new Polymer({
is: 'home-assistant',
hostAttributes: {
auth: null,
icons: null,
},
behaviors: [window.hassBehavior],
properties: {
hass: {
type: Object,
value: window.hass,
},
auth: {
type: String,
},
icons: {
type: String,
},
dataLoaded: {
type: Boolean,
bindNuclear: hass => hass.syncGetters.isDataLoaded,
},
iconsLoaded: {
type: Boolean,
value: false,
},
loaded: {
type: Boolean,
computed: 'computeLoaded(dataLoaded, iconsLoaded)',
},
},
computeLoaded(dataLoaded, iconsLoaded) {
return dataLoaded && iconsLoaded;
},
computeForceShowLoading(dataLoaded, iconsLoaded) {
return dataLoaded && !iconsLoaded;
},
loadIcons() {
// If the import fails, we'll try to import again, must be a server glitch
// Since HTML imports only resolve once, we import another url.
const success = () => { this.iconsLoaded = true; };
this.importHref(`/static/mdi-${this.icons}.html`,
success,
() => this.importHref('/static/mdi.html', success, success));
},
created() {
if (!('serviceWorker' in navigator)) {
return;
}
navigator.serviceWorker.register('/service_worker.js').catch(err => {
if (__DEV__) {
/* eslint-disable no-console */
console.warn('Unable to register service worker', err);
/* eslint-enable no-console */
}
});
},
ready() {
const hass = this.hass;
hass.reactor.batch(() => {
// if auth was given, tell the backend
if (this.auth) {
validateAuth(this.hass, this.auth, false);
} else if (hass.localStoragePreferences.authToken) {
validateAuth(this.hass, hass.localStoragePreferences.authToken, true);
}
hass.navigationActions.showSidebar(hass.localStoragePreferences.showSidebar);
});
hass.startLocalStoragePreferencesSync();
this.loadIcons();
},
});

View File

@ -1,8 +1,5 @@
import Polymer from '../polymer';
import removeInitMsg from '../util/remove-init-message';
require('../components/ha-sidebar');
require('../layouts/partial-cards');
require('../layouts/partial-logbook');
require('../layouts/partial-history');
@ -16,13 +13,6 @@ require('../managers/notification-manager');
require('../dialogs/more-info-dialog');
require('../dialogs/ha-voice-command-dialog');
// const {
// navigationActions,
// navigationGetters,
// startUrlSync,
// stopUrlSync,
// } = hass;
export default new Polymer({
is: 'home-assistant-main',
@ -35,7 +25,7 @@ export default new Polymer({
narrow: {
type: Boolean,
value: false,
value: true,
},
activePane: {
@ -91,6 +81,7 @@ export default new Polymer({
showSidebar: {
type: Boolean,
value: false,
bindNuclear: hass => hass.navigationGetters.showSidebar,
},
},
@ -122,7 +113,7 @@ export default new Polymer({
},
attached() {
removeInitMsg();
window.removeInitMsg();
this.hass.startUrlSync();
},

View File

@ -65,3 +65,95 @@
</div>
</template>
</dom-module>
<script>
Polymer({
is: 'login-form',
behaviors: [window.hassBehavior],
properties: {
hass: {
type: Object,
},
errorMessage: {
type: String,
bindNuclear: function (hass) { return hass.authGetters.attemptErrorMessage; },
},
isInvalid: {
type: Boolean,
bindNuclear: function (hass) { return hass.authGetters.isInvalidAttempt; },
},
isValidating: {
type: Boolean,
observer: 'isValidatingChanged',
bindNuclear: function (hass) { return hass.authGetters.isValidating; },
},
loadingResources: {
type: Boolean,
value: false,
},
forceShowLoading: {
type: Boolean,
value: false,
},
showLoading: {
type: Boolean,
computed: 'computeShowSpinner(forceShowLoading, isValidating)',
},
},
listeners: {
keydown: 'passwordKeyDown',
'loginButton.tap': 'validatePassword',
},
observers: [
'validatingChanged(isValidating, isInvalid)',
],
attached: function () {
window.removeInitMsg();
},
computeShowSpinner: function (forceShowLoading, isValidating) {
return forceShowLoading || isValidating;
},
validatingChanged: function (isValidating, isInvalid) {
if (!isValidating && !isInvalid) {
this.$.passwordInput.value = '';
}
},
isValidatingChanged: function (newVal) {
if (!newVal) {
this.async(function () { this.$.passwordInput.focus(); }.bind(this), 10);
}
},
passwordKeyDown: function (ev) {
// validate on enter
if (ev.keyCode === 13) {
this.validatePassword();
ev.preventDefault();
// clear error after we start typing again
} else if (this.isInvalid) {
this.isInvalid = false;
}
},
validatePassword: function () {
this.$.hideKeyboardOnFocus.focus();
window.validateAuth(this.hass, this.$.passwordInput.value,
this.$.rememberLogin.checked);
},
});
</script>

View File

@ -1,93 +0,0 @@
import Polymer from '../polymer';
import validateAuth from '../util/validate-auth';
import removeInitMsg from '../util/remove-init-message';
export default new Polymer({
is: 'login-form',
behaviors: [window.hassBehavior],
properties: {
hass: {
type: Object,
},
errorMessage: {
type: String,
bindNuclear: hass => hass.authGetters.attemptErrorMessage,
},
isInvalid: {
type: Boolean,
bindNuclear: hass => hass.authGetters.isInvalidAttempt,
},
isValidating: {
type: Boolean,
observer: 'isValidatingChanged',
bindNuclear: hass => hass.authGetters.isValidating,
},
loadingResources: {
type: Boolean,
value: false,
},
forceShowLoading: {
type: Boolean,
value: false,
},
showLoading: {
type: Boolean,
computed: 'computeShowSpinner(forceShowLoading, isValidating)',
},
},
listeners: {
keydown: 'passwordKeyDown',
'loginButton.tap': 'validatePassword',
},
observers: [
'validatingChanged(isValidating, isInvalid)',
],
attached() {
removeInitMsg();
},
computeShowSpinner(forceShowLoading, isValidating) {
return forceShowLoading || isValidating;
},
validatingChanged(isValidating, isInvalid) {
if (!isValidating && !isInvalid) {
this.$.passwordInput.value = '';
}
},
isValidatingChanged(newVal) {
if (!newVal) {
this.async(() => this.$.passwordInput.focus(), 10);
}
},
passwordKeyDown(ev) {
// validate on enter
if (ev.keyCode === 13) {
this.validatePassword();
ev.preventDefault();
// clear error after we start typing again
} else if (this.isInvalid) {
this.isInvalid = false;
}
},
validatePassword() {
this.$.hideKeyboardOnFocus.focus();
validateAuth(this.hass, this.$.passwordInput.value, this.$.rememberLogin.checked);
},
});

View File

@ -2,7 +2,6 @@ import Polymer from '../polymer';
require('./partial-base');
require('../components/ha-cards');
require('../components/ha-view-tabs');
export default new Polymer({
is: 'partial-cards',

View File

@ -55,7 +55,7 @@
<div>
<div class='header'>Available services</div>
<services-list on-service-selected='serviceSelected'></services-list>
<services-list on-service-selected='serviceSelected' hass='[[hass]]'></services-list>
</div>
</div>
</partial-base>

View File

@ -49,7 +49,7 @@
<div>
<div class='header'>Available Events</div>
<events-list on-event-selected='eventSelected'></event-list>
<events-list on-event-selected='eventSelected' hass='[[hass]]'></event-list>
</div>
</div>
</partial-base>

View File

@ -1,7 +1,6 @@
import Polymer from '../polymer';
require('./partial-base');
require('../components/events-list');
export default new Polymer({
is: 'partial-dev-fire-event',

View File

@ -51,7 +51,7 @@
<div>
<div class='header'>Current entities</div>
<entity-list on-entity-selected='entitySelected'></entity-list>
<entity-list on-entity-selected='entitySelected' hass='[[hass]]'></entity-list>
</div>
</div>
</partial-base>

View File

@ -1,7 +1,6 @@
import Polymer from '../polymer';
require('./partial-base');
require('../components/entity-list');
export default new Polymer({
is: 'partial-dev-set-state',

View File

@ -34,7 +34,7 @@
<loading-box hidden$='[[!isLoading]]'>Loading logbook entries</loading-box>
</div>
<ha-logbook entries="[[entries]]" hidden$='[[isLoading]]'></ha-logbook>
<ha-logbook hass='[[hass]]' entries="[[entries]]" hidden$='[[isLoading]]'></ha-logbook>
</div>
</partial-base>
</template>

View File

@ -1,8 +1,7 @@
import Polymer from '../polymer';
require('./partial-base');
require('../components/ha-logbook');
require('../components/loading-box');
require('../components/logbook-entry');
export default new Polymer({
is: 'partial-logbook',

View File

@ -12,7 +12,38 @@
</style>
<template>
<img class='camera-image' src="[[computeCameraImageUrl(stateObj)]]"
<img class='camera-image' src="[[computeCameraImageUrl(hass, stateObj)]]"
on-load='imageLoaded' />
</template>
</dom-module>
<script>
Polymer({
is: 'more-info-camera',
properties: {
hass: {
type: Object,
},
stateObj: {
type: Object,
},
},
imageLoaded: function () {
this.fire('iron-resize');
},
computeCameraImageUrl: function (hass, stateObj) {
if (hass.demo) {
return '/demo/webcam.jpg';
} else if (stateObj) {
return `/api/camera_proxy_stream/${stateObj.entityId}` +
`?token=${stateObj.attributes.access_token}`;
}
// Return an empty image if no stateObj (= dialog not open)
return 'data:image/gif;base64,R0lGODlhAQABAAAAACw=';
},
});
</script>

View File

@ -1,26 +0,0 @@
import Polymer from '../polymer';
export default new Polymer({
is: 'more-info-camera',
properties: {
stateObj: {
type: Object,
},
},
imageLoaded() {
this.fire('iron-resize');
},
computeCameraImageUrl(stateObj) {
if (__DEMO__) {
return '/demo/webcam.jpg';
} else if (stateObj) {
return `/api/camera_proxy_stream/${stateObj.entityId}` +
`?token=${stateObj.attributes.access_token}`;
}
// Return an empty image if no stateObj (= dialog not open)
return 'data:image/gif;base64,R0lGODlhAQABAAAAACw=';
},
});

View File

@ -1,7 +1,5 @@
import Polymer from '../polymer';
require('../components/loading-box');
export default new Polymer({
is: 'more-info-configurator',

View File

@ -11,7 +11,6 @@ require('./more-info-thermostat');
require('./more-info-script');
require('./more-info-light');
require('./more-info-media_player');
require('./more-info-camera');
require('./more-info-updater');
require('./more-info-alarm_control_panel');
require('./more-info-lock');

View File

@ -1,8 +1,6 @@
import Polymer from '../polymer';
import attributeClassNames from '../util/attribute-class-names';
require('../components/ha-color-picker');
const ATTRIBUTE_CLASSES = ['brightness', 'rgb_color', 'color_temp'];
function pickColor(hass, entityId, color) {

View File

@ -0,0 +1,36 @@
<script>
window.hassBehavior = {
attached: function attached() {
var hass = this.hass;
if (!hass) {
throw new Error('No hass property found on ' + this.nodeName);
}
this.nuclearUnwatchFns = Object.keys(this.properties).reduce(
function bindGetters(unwatchFns, key) {
if (!('bindNuclear' in this.properties[key])) {
return unwatchFns;
}
var getter = this.properties[key].bindNuclear(hass);
if (!getter) {
throw new Error(`Undefined getter specified for key ${key}`);
}
this[key] = hass.reactor.evaluate(getter);
return unwatchFns.concat(hass.reactor.observe(getter, function updateAttribute(val) {
this[key] = val;
}.bind(this)));
}.bind(this), []);
},
detached: function detached() {
while (this.nuclearUnwatchFns.length) {
this.nuclearUnwatchFns.shift()();
}
},
};
</script>

View File

@ -1,44 +0,0 @@
export default {
attached() {
const hass = this.hass;
if (!hass) {
throw new Error(`No hass property found on ${this.nodeName}`);
}
this.nuclearUnwatchFns = Object.keys(this.properties).reduce(
(unwatchFns, key) => {
if (!('bindNuclear' in this.properties[key])) {
return unwatchFns;
}
let getter = this.properties[key].bindNuclear;
if (typeof getter !== 'function') {
/* eslint-disable no-console */
console.warn(`Component ${this.nodeName} uses old style bindNuclear`);
/* eslint-enable no-console */
} else {
getter = getter(hass);
}
if (!getter) {
throw new Error(`Undefined getter specified for key ${key}`);
}
this[key] = hass.reactor.evaluate(getter);
return unwatchFns.concat(hass.reactor.observe(getter, (val) => {
this[key] = val;
}));
}, []);
},
detached() {
while (this.nuclearUnwatchFns.length) {
this.nuclearUnwatchFns.shift()();
}
},
};

View File

@ -1,7 +0,0 @@
export default function removeInitMessage() {
// remove the HTML init message
const initMsg = document.getElementById('ha-init-skeleton');
if (initMsg) {
initMsg.parentElement.removeChild(initMsg);
}
}

View File

@ -1,6 +0,0 @@
export default function (hass, authToken, rememberAuth) {
hass.authActions.validate(authToken, {
rememberAuth,
useStreaming: hass.localStoragePreferences.useStreaming,
});
}

View File

@ -9,6 +9,7 @@ var definePlugin = new webpack.DefinePlugin({
module.exports = {
entry: {
_app_compiled: './src/home-assistant.js',
_app_core_compiled: './src/app-core.js',
},
output: {
path: 'build',