mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-09 18:36:35 +00:00
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:
parent
5f44009177
commit
1a9af5595f
@ -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);
|
||||
|
@ -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');
|
||||
}
|
||||
|
||||
|
4
src/panels/lovelace/common/is-valid-object.js
Normal file
4
src/panels/lovelace/common/is-valid-object.js
Normal 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);
|
||||
}
|
143
src/panels/lovelace/components/hui-image.js
Normal file
143
src/panels/lovelace/components/hui-image.js
Normal 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);
|
Loading…
x
Reference in New Issue
Block a user