Love: Added <hui-image> element for handling different image sources (#1365)

* Added hui-image element. Converted picture-glance and picture-entity.

* Fixed hui-image data bindings

* Hide image if no source. Error message l10n

* Pass entire state object to hui-image

* Renamed isObject function. Fix camera image updating.
This commit is contained in:
Jerad Meisner 2018-07-01 08:26:41 -07:00 committed by Paulus Schoutsen
parent 5f44009177
commit 1a9af5595f
4 changed files with 169 additions and 20 deletions

View File

@ -2,8 +2,8 @@ import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import '../../../components/ha-card.js';
import '../components/hui-image.js';
import { STATES_OFF } from '../../../common/const.js';
import computeDomain from '../../../common/entity/compute_domain.js';
import computeStateDisplay from '../../../common/entity/compute_state_display.js';
import computeStateDomain from '../../../common/entity/compute_state_domain.js';
@ -22,14 +22,10 @@ class HuiPictureEntityCard extends LocalizeMixin(PolymerElement) {
return html`
<style>
ha-card {
position: relative;
cursor: pointer;
min-height: 75px;
overflow: hidden;
}
img {
display: block;
width: 100%;
height: auto;
position: relative;
}
.box {
@apply --paper-font-common-nowrap;
@ -51,7 +47,13 @@ class HuiPictureEntityCard extends LocalizeMixin(PolymerElement) {
</style>
<ha-card on-click="_cardClicked">
<img id="image" src="">
<hui-image
hass="[[hass]]"
image="[[_config.image]]"
state-image="[[_config.state_image]]"
camera-image="[[_config.camera_image]]"
state="[[_getStateObj(_oldState)]]"
></hui-image>
<div class="box">
<div id="title"></div>
<div id="state"></div>
@ -97,12 +99,7 @@ class HuiPictureEntityCard extends LocalizeMixin(PolymerElement) {
_updateState(hass, entityId, config) {
const state = entityId in hass.states ? hass.states[entityId].state : OFFLINE;
const stateImg = config.state_image &&
(config.state_image[state] || config.state_image.default);
this.$.image.src = stateImg || config.image;
this.$.image.style.filter = stateImg || (!STATES_OFF.includes(state) && state !== OFFLINE) ?
'' : 'grayscale(100%)';
this.$.title.innerText = config.title || (state === OFFLINE ?
entityId : computeStateName(hass.states[entityId]));
this.$.state.innerText = state === OFFLINE ?
@ -137,6 +134,10 @@ class HuiPictureEntityCard extends LocalizeMixin(PolymerElement) {
toggleEntity(this.hass, entityId);
}
}
_getStateObj() {
return this.hass && this.hass.states[this._config.entity];
}
}
customElements.define('hui-picture-entity-card', HuiPictureEntityCard);

View File

@ -3,6 +3,7 @@ import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import '@polymer/paper-icon-button/paper-icon-button.js';
import '../../../components/ha-card.js';
import '../components/hui-image.js';
import { STATES_OFF } from '../../../common/const.js';
import canToggleState from '../../../common/entity/can_toggle_state.js';
@ -27,11 +28,6 @@ class HuiPictureGlanceCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
min-height: 48px;
overflow: hidden;
}
img {
display: block;
width: 100%;
height: auto;
}
.box {
@apply --paper-font-common-nowrap;
position: absolute;
@ -61,7 +57,11 @@ class HuiPictureGlanceCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
</style>
<ha-card>
<img src="[[_config.image]]">
<hui-image
hass="[[hass]]"
image="[[_config.image]]"
camera-image="[[_config.camera_image]]"
></hui-image>
<div class="box">
<div class="title">[[_config.title]]</div>
<div>
@ -114,7 +114,8 @@ class HuiPictureGlanceCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
}
setConfig(config) {
if (!config || !config.entities || !Array.isArray(config.entities) || !config.image) {
if (!config || !config.entities || !Array.isArray(config.entities) ||
!(config.image || config.camera_image)) {
throw new Error('Invalid card configuration');
}

View File

@ -0,0 +1,4 @@
// Check if given obj is a JS object and optionally contains all required keys
export default function isValidObject(obj, requiredKeys = []) {
return obj && typeof obj === 'object' && !Array.isArray(obj) && requiredKeys.every(k => k in obj);
}

View File

@ -0,0 +1,143 @@
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import '@polymer/paper-toggle-button/paper-toggle-button.js';
import { STATES_OFF } from '../../../common/const.js';
import LocalizeMixin from '../../../mixins/localize-mixin.js';
import isValidObject from '../common/is-valid-object';
const UPDATE_INTERVAL = 10000;
/*
* @appliesMixin LocalizeMixin
*/
class HuiImage extends LocalizeMixin(PolymerElement) {
static get template() {
return html`
<style>
.state-off {
filter: grayscale(100%);
}
img {
display: block;
height: auto;
transition: filter .2s linear;
width: 100%;
}
.error {
width: 100%;
height: auto;
text-align: center;
}
</style>
<template is="dom-if" if="[[_imageSrc]]">
<img
src="[[_imageSrc]]"
on-error="_onImageError"
on-load="_onImageLoad"
class$="[[_imageClass]]" />
</template>
<template is="dom-if" if="[[_error]]">
<div class="error">[[localize('ui.card.camera.not_available')]]</div>
</template>
`;
}
static get properties() {
return {
hass: Object,
state: {
type: Object,
value: null,
observer: '_stateChanged'
},
image: String,
stateImage: Object,
cameraImage: String,
_error: {
type: Boolean,
value: false
},
_imageClass: String,
_imageSrc: String
};
}
static get observers() {
return ['_configChanged(image, stateImage, cameraImage)'];
}
connectedCallback() {
super.connectedCallback();
if (this.cameraImage) {
this.timer = setInterval(() => this._updateCameraImageSrc(), UPDATE_INTERVAL);
}
}
disconnectedCallback() {
super.disconnectedCallback();
clearInterval(this.timer);
}
_configChanged(image, stateImage, cameraImage) {
if (cameraImage) {
this._updateCameraImageSrc();
} else if (image && !stateImage) {
this._imageSrc = image;
}
}
_onImageError() {
this.setProperties({
_imageSrc: null,
_error: true
});
}
_onImageLoad() {
this._error = false;
}
_stateChanged(state) {
if (this.cameraImage) {
return;
}
if (!this.stateImage) {
this._imageClass = (!isValidObject(state, ['state']) || STATES_OFF.includes(state.state)) ? 'state-off' : '';
return;
}
const stateImg = isValidObject(state, ['state']) ? this.stateImage[state.state] : this.stateImage.offline;
this.setProperties({
_imageSrc: stateImg || this.stateImage.default || this.image,
_imageClass: ''
});
}
_updateCameraImageSrc() {
this.hass.connection.sendMessagePromise({
type: 'camera_thumbnail',
entity_id: this.cameraImage,
}).then((resp) => {
if (resp.success) {
this.setProperties({
_imageSrc: `data:${resp.result.content_type};base64, ${resp.result.content}`,
_error: false
});
} else {
this.setProperties({
_imageSrc: null,
_error: true
});
}
});
}
}
customElements.define('hui-image', HuiImage);