mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-21 08:16:36 +00:00
Add support for entity cards
This commit is contained in:
parent
1fb3f2bbf4
commit
09d0e529ed
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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
@ -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-entities-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>
|
|
||||||
</template>
|
|
||||||
<template is='dom-repeat' items='[[column]]' as='domain'>
|
|
||||||
<div class='zone-card'>
|
|
||||||
<ha-entities-card
|
|
||||||
states='[[computeStatesOfCard(cards, domain)]]'
|
|
||||||
group-entity='[[computeGroupEntityOfCard(cards, domain)]]'
|
|
||||||
></ha-entities-card>
|
></ha-entities-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-entities-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;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user