1
mirror of https://github.com/home-assistant/frontend synced 2024-09-28 00:43:28 +02:00

Convert hui-picture-entity-card to TypeScript/LitElement (#2168)

* Convert hui-picture-entity-card to TypeScript/LitElement

click/hold are not working on my Chrome dev env

* typo

* Address review comments

Still having issues with clicks/holds on lots of cards on my system

* Add explicit navigate option

* Fixed after testing with touchevents

* Simplify
This commit is contained in:
Ian Richardson 2018-12-04 03:04:59 -06:00 committed by Paulus Schoutsen
parent 5dc05129ef
commit 5fec881c39
4 changed files with 178 additions and 198 deletions

1
src/data/entity.ts Normal file
View File

@ -0,0 +1 @@
export const UNAVAILABLE = "unavailable";

View File

@ -1,197 +0,0 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../components/ha-card";
import "../components/hui-image";
import computeDomain from "../../../common/entity/compute_domain";
import computeStateDisplay from "../../../common/entity/compute_state_display";
import computeStateName from "../../../common/entity/compute_state_name";
import EventsMixin from "../../../mixins/events-mixin";
import LocalizeMixin from "../../../mixins/localize-mixin";
import { toggleEntity } from "../common/entity/toggle-entity";
import { longPressBind } from "../common/directives/long-press-directive";
const UNAVAILABLE = "Unavailable";
/*
* @appliesMixin LocalizeMixin
* @appliesMixin EventsMixin
*/
class HuiPictureEntityCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
static get template() {
return html`
<style>
ha-card {
min-height: 75px;
overflow: hidden;
position: relative;
}
ha-card.canInteract {
cursor: pointer;
}
.footer {
@apply --paper-font-common-nowrap;
position: absolute;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.3);
padding: 16px;
font-size: 16px;
line-height: 16px;
color: white;
}
.both {
display: flex;
justify-content: space-between;
}
.state {
text-align: right;
}
</style>
<ha-card id="card">
<hui-image
hass="[[hass]]"
image="[[_config.image]]"
state-image="[[_config.state_image]]"
camera-image="[[_getCameraImage(_config)]]"
entity="[[_config.entity]]"
aspect-ratio="[[_config.aspect_ratio]]"
></hui-image>
<template is="dom-if" if="[[_showNameAndState(_config)]]">
<div class="footer both">
<div>[[_name]]</div>
<div>[[_state]]</div>
</div>
</template>
<template is="dom-if" if="[[_showName(_config)]]">
<div class="footer">[[_name]]</div>
</template>
<template is="dom-if" if="[[_showState(_config)]]">
<div class="footer state">[[_state]]</div>
</template>
</ha-card>
`;
}
static get properties() {
return {
hass: {
type: Object,
observer: "_hassChanged",
},
_config: Object,
_name: String,
_state: String,
};
}
getCardSize() {
return 3;
}
setConfig(config) {
if (!config || !config.entity) {
throw new Error("Error in card configuration.");
}
this._entityDomain = computeDomain(config.entity);
if (
this._entityDomain !== "camera" &&
(!config.image && !config.state_image && !config.camera_image)
) {
throw new Error("No image source configured.");
}
this._config = config;
}
ready() {
super.ready();
const card = this.shadowRoot.querySelector("#card");
longPressBind(card);
card.addEventListener("ha-click", () => this._cardClicked(false));
card.addEventListener("ha-hold", () => this._cardClicked(true));
}
_hassChanged(hass) {
const config = this._config;
const entityId = config.entity;
const stateObj = hass.states[entityId];
// Nothing changed
if (
(!stateObj && this._oldState === UNAVAILABLE) ||
(stateObj && stateObj.state === this._oldState)
) {
return;
}
let name;
let state;
let stateLabel;
let available;
if (stateObj) {
name = config.name || computeStateName(stateObj);
state = stateObj.state;
stateLabel = computeStateDisplay(this.localize, stateObj);
available = true;
} else {
name = config.name || entityId;
state = UNAVAILABLE;
stateLabel = this.localize("state.default.unavailable");
available = false;
}
this.setProperties({
_name: name,
_state: stateLabel,
_oldState: state,
});
this.$.card.classList.toggle("canInteract", available);
}
_showNameAndState(config) {
return config.show_name !== false && config.show_state !== false;
}
_showName(config) {
return config.show_name !== false && config.show_state === false;
}
_showState(config) {
return config.show_name === false && config.show_state !== false;
}
_cardClicked(hold) {
const config = this._config;
const entityId = config.entity;
if (!(entityId in this.hass.states)) return;
const action = hold ? config.hold_action : config.tap_action || "more-info";
switch (action) {
case "toggle":
toggleEntity(this.hass, entityId);
break;
case "more-info":
this.fire("hass-more-info", { entityId });
break;
default:
}
}
_getCameraImage(config) {
return this._entityDomain === "camera"
? config.entity
: config.camera_image;
}
}
customElements.define("hui-picture-entity-card", HuiPictureEntityCard);

View File

@ -0,0 +1,175 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
import { TemplateResult } from "lit-html/lib/shady-render";
import { classMap } from "lit-html/directives/classMap";
import "../../../components/ha-card";
import "../components/hui-image";
import computeDomain from "../../../common/entity/compute_domain";
import computeStateDisplay from "../../../common/entity/compute_state_display";
import computeStateName from "../../../common/entity/compute_state_name";
import { longPress } from "../common/directives/long-press-directive";
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
import { HomeAssistant } from "../../../types";
import { LovelaceCardConfig } from "../../../data/lovelace";
import { LovelaceCard } from "../types";
import { handleClick } from "../common/handle-click";
import { UNAVAILABLE } from "../../../data/entity";
interface Config extends LovelaceCardConfig {
entity: string;
name?: string;
navigation_path?: string;
image?: string;
camera_image?: string;
state_image?: {};
aspect_ratio?: string;
tap_action?: "toggle" | "call-service" | "more-info" | "navigate";
hold_action?: "toggle" | "call-service" | "more-info" | "navigate";
service?: string;
service_data?: object;
show_name?: boolean;
show_state?: boolean;
}
class HuiPictureEntityCard extends hassLocalizeLitMixin(LitElement)
implements LovelaceCard {
public hass?: HomeAssistant;
private _config?: Config;
static get properties(): PropertyDeclarations {
return {
hass: {},
_config: {},
};
}
public getCardSize(): number {
return 3;
}
public setConfig(config: Config): void {
if (!config || !config.entity) {
throw new Error("Invalid Configuration: 'entity' required");
}
if (
computeDomain(config.entity) !== "camera" &&
(!config.image && !config.state_image && !config.camera_image)
) {
throw new Error("No image source configured.");
}
this._config = { show_name: true, show_state: true, ...config };
}
protected render(): TemplateResult {
if (!this._config || !this.hass || !this.hass.states[this._config.entity]) {
return html``;
}
const stateObj = this.hass.states[this._config.entity];
const name = this._config.name || computeStateName(stateObj);
const state = computeStateDisplay(
this.localize,
stateObj,
this.hass.language
);
let footer: TemplateResult | string = "";
if (this._config.show_name && this._config.show_state) {
footer = html`
<div class="footer both">
<div>${name}</div>
<div>${state}</div>
</div>
`;
} else if (this._config.show_name) {
footer = html`
<div class="footer">${name}</div>
`;
} else if (this._config.show_state) {
footer = html`
<div class="footer state">${state}</div>
`;
}
return html`
${this.renderStyle()}
<ha-card>
<hui-image
.hass="${this.hass}"
.image="${this._config.image}"
.stateImage="${this._config.state_image}"
.cameraImage="${
computeDomain(this._config.entity) === "camera"
? this._config.entity
: this._config.camera_image
}"
.entity="${this._config.entity}"
.aspectRatio="${this._config.aspect_ratio}"
@ha-click="${this._handleClick}"
@ha-hold="${this._handleHold}"
.longPress="${longPress()}"
class="${
classMap({
clickable: stateObj.state !== UNAVAILABLE,
})
}"
></hui-image>
${footer}
</ha-card>
`;
}
private renderStyle(): TemplateResult {
return html`
<style>
ha-card {
min-height: 75px;
overflow: hidden;
position: relative;
}
hui-image.clickable {
cursor: pointer;
}
.footer {
@apply --paper-font-common-nowrap;
position: absolute;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.3);
padding: 16px;
font-size: 16px;
line-height: 16px;
color: white;
}
.both {
display: flex;
justify-content: space-between;
}
.state {
text-align: right;
}
</style>
`;
}
private _handleClick() {
handleClick(this, this.hass!, this._config!, false);
}
private _handleHold() {
handleClick(this, this.hass!, this._config!, true);
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-picture-entity-card": HuiPictureEntityCard;
}
}
customElements.define("hui-picture-entity-card", HuiPictureEntityCard);

View File

@ -3,11 +3,12 @@ import { LovelaceElementConfig } from "../elements/types";
import { fireEvent } from "../../../common/dom/fire_event";
import { navigate } from "../../../common/navigate";
import { toggleEntity } from "../../../../src/panels/lovelace/common/entity/toggle-entity";
import { LovelaceCardConfig } from "../../../data/lovelace";
export const handleClick = (
node: HTMLElement,
hass: HomeAssistant,
config: LovelaceElementConfig,
config: LovelaceElementConfig | LovelaceCardConfig,
hold: boolean
): void => {
let action = config.tap_action || "more-info";