diff --git a/src/data/entity.ts b/src/data/entity.ts
new file mode 100644
index 0000000000..69028008b9
--- /dev/null
+++ b/src/data/entity.ts
@@ -0,0 +1 @@
+export const UNAVAILABLE = "unavailable";
diff --git a/src/panels/lovelace/cards/hui-picture-entity-card.js b/src/panels/lovelace/cards/hui-picture-entity-card.js
deleted file mode 100644
index 03d6fa488c..0000000000
--- a/src/panels/lovelace/cards/hui-picture-entity-card.js
+++ /dev/null
@@ -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`
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `;
- }
-
- 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);
diff --git a/src/panels/lovelace/cards/hui-picture-entity-card.ts b/src/panels/lovelace/cards/hui-picture-entity-card.ts
new file mode 100644
index 0000000000..8c2e1cde12
--- /dev/null
+++ b/src/panels/lovelace/cards/hui-picture-entity-card.ts
@@ -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`
+
+ `;
+ } else if (this._config.show_name) {
+ footer = html`
+
+ `;
+ } else if (this._config.show_state) {
+ footer = html`
+
+ `;
+ }
+
+ return html`
+ ${this.renderStyle()}
+
+
+ ${footer}
+
+ `;
+ }
+
+ private renderStyle(): TemplateResult {
+ return html`
+
+ `;
+ }
+
+ 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);
diff --git a/src/panels/lovelace/common/handle-click.ts b/src/panels/lovelace/common/handle-click.ts
index 714f4217eb..f6f654f744 100644
--- a/src/panels/lovelace/common/handle-click.ts
+++ b/src/panels/lovelace/common/handle-click.ts
@@ -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";