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:
parent
5dc05129ef
commit
5fec881c39
1
src/data/entity.ts
Normal file
1
src/data/entity.ts
Normal file
@ -0,0 +1 @@
|
||||
export const UNAVAILABLE = "unavailable";
|
@ -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);
|
175
src/panels/lovelace/cards/hui-picture-entity-card.ts
Normal file
175
src/panels/lovelace/cards/hui-picture-entity-card.ts
Normal 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);
|
@ -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";
|
||||
|
Loading…
Reference in New Issue
Block a user