mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-10 02:46:38 +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 { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||||
|
|
||||||
import '../../../components/ha-card.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 computeDomain from '../../../common/entity/compute_domain.js';
|
||||||
import computeStateDisplay from '../../../common/entity/compute_state_display.js';
|
import computeStateDisplay from '../../../common/entity/compute_state_display.js';
|
||||||
import computeStateDomain from '../../../common/entity/compute_state_domain.js';
|
import computeStateDomain from '../../../common/entity/compute_state_domain.js';
|
||||||
@ -22,14 +22,10 @@ class HuiPictureEntityCard extends LocalizeMixin(PolymerElement) {
|
|||||||
return html`
|
return html`
|
||||||
<style>
|
<style>
|
||||||
ha-card {
|
ha-card {
|
||||||
position: relative;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
min-height: 75px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
position: relative;
|
||||||
img {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
}
|
}
|
||||||
.box {
|
.box {
|
||||||
@apply --paper-font-common-nowrap;
|
@apply --paper-font-common-nowrap;
|
||||||
@ -51,7 +47,13 @@ class HuiPictureEntityCard extends LocalizeMixin(PolymerElement) {
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<ha-card on-click="_cardClicked">
|
<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 class="box">
|
||||||
<div id="title"></div>
|
<div id="title"></div>
|
||||||
<div id="state"></div>
|
<div id="state"></div>
|
||||||
@ -97,12 +99,7 @@ class HuiPictureEntityCard extends LocalizeMixin(PolymerElement) {
|
|||||||
|
|
||||||
_updateState(hass, entityId, config) {
|
_updateState(hass, entityId, config) {
|
||||||
const state = entityId in hass.states ? hass.states[entityId].state : OFFLINE;
|
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 ?
|
this.$.title.innerText = config.title || (state === OFFLINE ?
|
||||||
entityId : computeStateName(hass.states[entityId]));
|
entityId : computeStateName(hass.states[entityId]));
|
||||||
this.$.state.innerText = state === OFFLINE ?
|
this.$.state.innerText = state === OFFLINE ?
|
||||||
@ -137,6 +134,10 @@ class HuiPictureEntityCard extends LocalizeMixin(PolymerElement) {
|
|||||||
toggleEntity(this.hass, entityId);
|
toggleEntity(this.hass, entityId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_getStateObj() {
|
||||||
|
return this.hass && this.hass.states[this._config.entity];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define('hui-picture-entity-card', HuiPictureEntityCard);
|
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 '@polymer/paper-icon-button/paper-icon-button.js';
|
||||||
|
|
||||||
import '../../../components/ha-card.js';
|
import '../../../components/ha-card.js';
|
||||||
|
import '../components/hui-image.js';
|
||||||
|
|
||||||
import { STATES_OFF } from '../../../common/const.js';
|
import { STATES_OFF } from '../../../common/const.js';
|
||||||
import canToggleState from '../../../common/entity/can_toggle_state.js';
|
import canToggleState from '../../../common/entity/can_toggle_state.js';
|
||||||
@ -27,11 +28,6 @@ class HuiPictureGlanceCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
min-height: 48px;
|
min-height: 48px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
img {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
.box {
|
.box {
|
||||||
@apply --paper-font-common-nowrap;
|
@apply --paper-font-common-nowrap;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -61,7 +57,11 @@ class HuiPictureGlanceCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<ha-card>
|
<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="box">
|
||||||
<div class="title">[[_config.title]]</div>
|
<div class="title">[[_config.title]]</div>
|
||||||
<div>
|
<div>
|
||||||
@ -114,7 +114,8 @@ class HuiPictureGlanceCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setConfig(config) {
|
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');
|
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