Merge pull request #30 from balloob/show-camera-inline

Show camera feeds inline
This commit is contained in:
Paulus Schoutsen 2016-02-16 23:46:53 -08:00
commit 4cdefac2fe
11 changed files with 217 additions and 90 deletions

View File

@ -0,0 +1,37 @@
<link rel='import' href='../../bower_components/polymer/polymer.html'>
<link rel="import" href="../../bower_components/paper-material/paper-material.html">
<dom-module id='ha-camera-card'>
<style include="paper-material">
:host {
display: block;
position: relative;
font-size: 0px;
border-radius: 2px;
cursor: pointer;
}
.camera-feed {
width: 100%;
height: auto;
}
.caption {
position: absolute;
left: 0px;
right: 0px;
bottom: 0px;
background-color: rgba(0, 0, 0, 0.3);
padding: 16px;
text-transform: capitalize;
font-size: 16px;
font-weight: 500;
line-height: 16px;
color: white;
}
</style>
<template>
<img src='[[cameraFeedSrc]]' class='camera-feed'>
<div class='caption'>[[stateObj.entityDisplay]]</div>
</template>
</dom-module>

View File

@ -0,0 +1,52 @@
import Polymer from '../polymer';
import hass from '../util/home-assistant-js-instance';
const { moreInfoActions } = hass;
const UPDATE_INTERVAL = 10000; // ms
export default new Polymer({
is: 'ha-camera-card',
properties: {
stateObj: {
type: Object,
observer: 'updateCameraFeedSrc',
},
cameraFeedSrc: {
type: String,
},
/**
* 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(() => moreInfoActions.selectEntity(this.stateObj.entityId), 1);
},
updateCameraFeedSrc(stateObj) {
const time = (new Date()).getTime();
this.cameraFeedSrc = `${stateObj.attributes.entity_picture}?time=${time}`;
},
});

View File

@ -0,0 +1,5 @@
<link rel='import' href='../../bower_components/polymer/polymer.html'>
<link rel='import' href='./ha-camera-card.html'>
<link rel='import' href='./ha-entities-card.html'>
<link rel='import' href='./ha-introduction-card.html'>

View File

@ -0,0 +1,53 @@
import Polymer from '../polymer';
require('./ha-camera-card');
require('./ha-entities-card');
require('./ha-introduction-card');
export default new Polymer({
is: 'ha-card-chooser',
properties: {
cardData: {
type: Object,
observer: 'cardDataChanged',
},
},
cardDataChanged(newData, oldData) {
const root = Polymer.dom(this);
if (!newData) {
if (root.lastChild) {
root.removeChild(root.lastChild);
}
return;
}
const newElement = !oldData || oldData.cardType !== newData.cardType;
let card;
if (newElement) {
if (root.lastChild) {
root.removeChild(root.lastChild);
}
card = document.createElement(`ha-${newData.cardType}-card`);
} else {
card = root.lastChild;
}
Object.keys(newData).forEach(key => card[key] = newData[key]);
if (oldData) {
Object.keys(oldData).forEach(key => {
if (!(key in newData)) {
card[key] = undefined;
}
});
}
if (newElement) {
root.appendChild(card);
}
},
});

View File

@ -4,7 +4,7 @@
<link rel='import' href='../components/entity/ha-entity-toggle.html'> <link rel='import' href='../components/entity/ha-entity-toggle.html'>
<link rel='import' href='../state-summary/state-card-content.html'> <link rel='import' href='../state-summary/state-card-content.html'>
<dom-module id='ha-domain-card'> <dom-module id='ha-entities-card'>
<style> <style>
.states { .states {
padding-bottom: 16px; padding-bottom: 16px;
@ -29,7 +29,7 @@
<template> <template>
<ha-card> <ha-card>
<div class='header horizontal layout center'> <div class='header horizontal layout center'>
<div class='flex name'>[[computeDomainTitle(domain)]]</div> <div class='flex name'>[[computeTitle(states, groupEntity)]]</div>
<template is='dom-if' if='[[showGroupToggle(groupEntity, states)]]'> <template is='dom-if' if='[[showGroupToggle(groupEntity, states)]]'>
<ha-entity-toggle state-obj='[[groupEntity]]'></ha-entity-toggle> <ha-entity-toggle state-obj='[[groupEntity]]'></ha-entity-toggle>
</template> </template>

View File

@ -9,12 +9,9 @@ require('../state-summary/state-card-content');
const { moreInfoActions } = hass; const { moreInfoActions } = hass;
export default new Polymer({ export default new Polymer({
is: 'ha-domain-card', is: 'ha-entities-card',
properties: { properties: {
domain: {
type: String,
},
states: { states: {
type: Array, type: Array,
}, },
@ -23,8 +20,9 @@ export default new Polymer({
}, },
}, },
computeDomainTitle(domain) { computeTitle(states, groupEntity) {
return domain.replace(/_/g, ' '); return groupEntity ? groupEntity.entityDisplay :
states[0].domain.replace(/_/g, ' ');
}, },
entityTapped(ev) { entityTapped(ev) {
@ -43,6 +41,6 @@ export default new Polymer({
} }
// only show if we can toggle 2+ entities in group // only show if we can toggle 2+ entities in group
return states.reduce((sum, state) => sum + canToggle(state.entityId), 0) > 1; return states.reduce((sum, state) => sum + canToggle(state.entityId)) > 1;
}, },
}); });

View File

@ -1,17 +0,0 @@
<link rel='import' href='../../bower_components/polymer/polymer.html'>
<link rel='import' href='../components/ha-card.html'>
<dom-module id='ha-getting-started-card'>
<template>
<ha-card>
<h3>Hi there!</h3>
<p>
It looks like we have nothing to show you right now. It could be that we have not yet discovered all your devices but it is more likely that you have not configured Home Assistant yet.
</p>
<p>
Please see the <a href='https://home-assistant.io/getting-started/' target='_blank'>Getting Started</a> section on how to setup your devices.
</p>
</ha-card>
</template>
</dom-module>

View File

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

View File

@ -2,8 +2,7 @@
<link rel="import" href="./ha-demo-badge.html"> <link rel="import" href="./ha-demo-badge.html">
<link rel="import" href="../cards/ha-badges-card.html"> <link rel="import" href="../cards/ha-badges-card.html">
<link rel="import" href="../cards/ha-domain-card.html"> <link rel="import" href="../cards/ha-card-chooser.html">
<link rel="import" href="../cards/ha-introduction-card.html">
<dom-module id="ha-cards"> <dom-module id="ha-cards">
<style> <style>
@ -53,7 +52,7 @@
<template> <template>
<div class='main'> <div class='main'>
<template is='dom-if' if='[[cards._badges.length]]'> <template is='dom-if' if='[[cards._badges]]'>
<div class='badges'> <div class='badges'>
<template is='dom-if' if='[[cards._demo]]'> <template is='dom-if' if='[[cards._demo]]'>
<ha-demo-badge></ha-demo-badge> <ha-demo-badge></ha-demo-badge>
@ -62,30 +61,21 @@
<ha-badges-card states='[[cards._badges]]'></ha-badges-card> <ha-badges-card states='[[cards._badges]]'></ha-badges-card>
</div> </div>
</template> </template>
<template is='dom-if' if='[[!cards._badges.length]]'> <template is='dom-if' if='[[!cards._badges]]'>
<div class='no-badges'> </div> <div class='no-badges'> </div>
</template> </template>
<div class='horizontal layout'> <div class='horizontal layout'>
<template is='dom-repeat' items='[[cards._columns]]' as='column'> <template is='dom-repeat' items='[[cards._columns]]' as='column'>
<template is='dom-if'
if='[[computeShouldRenderColumn(index, column)]]'>
<div class='column flex-1'> <div class='column flex-1'>
<template is='dom-if' if='[[computeShowIntroduction(index, showIntroduction, cards)]]'> <template is='dom-repeat' items='[[column]]' as='card'>
<div class='zone-card'> <div class='zone-card'>
<ha-introduction-card show-hide-instruction='[[computeShowHideInstruction(states, cards)]]'></ha-introduction-card> <ha-card-chooser card-data='[[computeCardDataOfCard(cards, card)]]'
</div> ></ha-entities-card>
</template>
<template is='dom-repeat' items='[[column]]' as='domain'>
<div class='zone-card'>
<ha-domain-card domain='[[domain]]'
states='[[computeStatesOfCard(cards, domain)]]'
group-entity='[[computeGroupEntityOfCard(cards, domain)]]'
></ha-domain-card>
</div> </div>
</template> </template>
</div> </div>
</template> </template>
</template>
</div> </div>
</template> </template>

View File

@ -3,10 +3,12 @@ import hass from '../util/home-assistant-js-instance';
require('.//ha-demo-badge'); require('.//ha-demo-badge');
require('../cards/ha-badges-card'); require('../cards/ha-badges-card');
require('../cards/ha-domain-card'); require('../cards/ha-card-chooser');
require('../cards/ha-introduction-card');
const { util } = hass; const { util } = hass;
const DOMAINS_WITH_CARD = ['camera'];
const PRIORITY = { const PRIORITY = {
configurator: -20, configurator: -20,
group: -10, group: -10,
@ -15,7 +17,6 @@ const PRIORITY = {
sun: 1, sun: 1,
device_tracker: 2, device_tracker: 2,
alarm_control_panel: 3, alarm_control_panel: 3,
camera: 4,
sensor: 5, sensor: 5,
binary_sensor: 6, binary_sensor: 6,
scene: 7, scene: 7,
@ -77,15 +78,45 @@ export default new Polymer({
return old; return old;
} }
if (showIntroduction) { if (showIntroduction) {
increaseIndex(); cards._columns[increaseIndex()].push('ha-introduction');
cards['ha-introduction'] = {
cardType: 'introduction',
showHideInstruction: states.size > 0 && !__DEMO__,
};
} }
function pushCard(name, entities, groupEntity = false) { function addEntitiesCard(name, entities, groupEntity = false) {
if (entities.length === 0) { if (entities.length === 0) return;
return;
const owncard = [];
const other = [];
entities.forEach(entity => {
if (DOMAINS_WITH_CARD.indexOf(entity.domain) === -1) {
other.push(entity);
} else {
owncard.push(entity);
} }
cards._columns[increaseIndex()].push(name); });
cards[name] = { entities, groupEntity };
const curIndex = increaseIndex();
if (other.length > 0) {
cards._columns[curIndex].push(name);
cards[name] = {
cardType: 'entities',
states: other,
groupEntity,
};
}
owncard.forEach(entity => {
cards._columns[curIndex].push(entity.entityId);
cards[entity.entityId] = {
cardType: entity.domain,
stateObj: entity,
};
});
} }
byDomain.keySeq().sortBy(domain => getPriority(domain)) byDomain.keySeq().sortBy(domain => getPriority(domain))
@ -106,34 +137,19 @@ export default new Polymer({
.forEach(groupState => { .forEach(groupState => {
const entities = util.expandGroup(groupState, states); const entities = util.expandGroup(groupState, states);
entities.forEach(entity => hasGroup[entity.entityId] = true); entities.forEach(entity => hasGroup[entity.entityId] = true);
pushCard(groupState.entityDisplay, entities.toArray(), groupState); addEntitiesCard(groupState.entityId, entities.toArray(), groupState);
} }
); );
} else { } else {
pushCard(domain, filterGrouped(byDomain.get(domain)).sortBy(entitySortBy).toArray()); addEntitiesCard(
domain, filterGrouped(byDomain.get(domain)).sortBy(entitySortBy).toArray());
} }
} }
); );
return cards; return cards;
}, },
computeShouldRenderColumn(index, items) { computeCardDataOfCard(cards, card) {
return index === 0 || items.length; return cards[card];
},
computeShowIntroduction(index, showIntroduction, cards) {
return index === 0 && (showIntroduction || cards._demo);
},
computeShowHideInstruction(states, cards) {
return states.size > 0 && !__DEMO__ && !cards._demo;
},
computeGroupEntityOfCard(cards, card) {
return card in cards && cards[card].groupEntity;
},
computeStatesOfCard(cards, card) {
return card in cards && cards[card].entities;
}, },
}); });

View File

@ -15,7 +15,6 @@ const {
moreInfoActions, moreInfoActions,
} = hass; } = hass;
// if you don't want the history component to show add the domain to this array
const DOMAINS_WITH_NO_HISTORY = ['camera', 'configurator', 'scene']; const DOMAINS_WITH_NO_HISTORY = ['camera', 'configurator', 'scene'];
export default new Polymer({ export default new Polymer({
@ -63,6 +62,7 @@ export default new Polymer({
showHistoryComponent: { showHistoryComponent: {
type: Boolean, type: Boolean,
value: false, value: false,
computed: 'computeShowHistoryComponent(hasHistoryComponent, stateObj)',
}, },
dialogOpen: { dialogOpen: {
@ -87,6 +87,11 @@ export default new Polymer({
return !_delayedDialogOpen || _isLoadingHistoryData; return !_delayedDialogOpen || _isLoadingHistoryData;
}, },
computeShowHistoryComponent(hasHistoryComponent, stateObj) {
return this.hasHistoryComponent && stateObj &&
DOMAINS_WITH_NO_HISTORY.indexOf(stateObj.domain) === -1;
},
fetchHistoryData() { fetchHistoryData() {
if (this.stateObj && this.hasHistoryComponent && if (this.stateObj && this.hasHistoryComponent &&
this.shouldFetchHistory) { this.shouldFetchHistory) {
@ -100,11 +105,6 @@ export default new Polymer({
return; return;
} }
this.showHistoryComponent = (
this.hasHistoryComponent &&
DOMAINS_WITH_NO_HISTORY.indexOf(this.stateObj.domain) === -1
);
this.async(() => { this.async(() => {
// Firing action while other action is happening confuses nuclear // Firing action while other action is happening confuses nuclear
this.fetchHistoryData(); this.fetchHistoryData();