mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-10 19:06:36 +00:00
Merge pull request #30 from balloob/show-camera-inline
Show camera feeds inline
This commit is contained in:
commit
4cdefac2fe
37
src/cards/ha-camera-card.html
Normal file
37
src/cards/ha-camera-card.html
Normal 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>
|
52
src/cards/ha-camera-card.js
Normal file
52
src/cards/ha-camera-card.js
Normal 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}`;
|
||||||
|
},
|
||||||
|
});
|
5
src/cards/ha-card-chooser.html
Normal file
5
src/cards/ha-card-chooser.html
Normal 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'>
|
53
src/cards/ha-card-chooser.js
Normal file
53
src/cards/ha-card-chooser.js
Normal 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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
@ -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>
|
@ -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;
|
||||||
},
|
},
|
||||||
});
|
});
|
@ -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>
|
|
@ -1,7 +0,0 @@
|
|||||||
import Polymer from '../polymer';
|
|
||||||
|
|
||||||
require('../components/ha-card');
|
|
||||||
|
|
||||||
export default new Polymer({
|
|
||||||
is: 'ha-getting-started-card',
|
|
||||||
});
|
|
@ -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>
|
||||||
|
|
||||||
|
@ -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;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -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();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user