diff --git a/src/components/entity/ha-state-label-badge.js b/src/components/entity/ha-state-label-badge.ts similarity index 60% rename from src/components/entity/ha-state-label-badge.js rename to src/components/entity/ha-state-label-badge.ts index bbd6f4dcac..c9308e48e6 100644 --- a/src/components/entity/ha-state-label-badge.js +++ b/src/components/entity/ha-state-label-badge.ts @@ -1,25 +1,206 @@ -import { html } from "@polymer/polymer/lib/utils/html-tag"; -import { PolymerElement } from "@polymer/polymer/polymer-element"; - -import "../ha-label-badge"; +import { + LitElement, + html, + PropertyValues, + PropertyDeclarations, +} from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; +import { HassEntity } from "home-assistant-js-websocket"; +import { classMap } from "lit-html/directives/classMap"; import computeStateDomain from "../../common/entity/compute_state_domain"; import computeStateName from "../../common/entity/compute_state_name"; import domainIcon from "../../common/entity/domain_icon"; import stateIcon from "../../common/entity/state_icon"; import timerTimeRemaining from "../../common/entity/timer_time_remaining"; -import attributeClassNames from "../../common/entity/attribute_class_names"; import secondsToDuration from "../../common/datetime/seconds_to_duration"; +import { fireEvent } from "../../common/dom/fire_event"; +import { hassLocalizeLitMixin } from "../../mixins/lit-localize-mixin"; -import EventsMixin from "../../mixins/events-mixin"; -import LocalizeMixin from "../../mixins/localize-mixin"; +import "../ha-label-badge"; /* * @appliesMixin LocalizeMixin * @appliesMixin EventsMixin */ -class HaStateLabelBadge extends LocalizeMixin(EventsMixin(PolymerElement)) { - static get template() { +export class HaStateLabelBadge extends hassLocalizeLitMixin(LitElement) { + public state?: HassEntity; + private _connected?: boolean; + private _updateRemaining?: number; + private _timerTimeRemaining?: number; + + public connectedCallback(): void { + super.connectedCallback(); + this._connected = true; + this.startInterval(this.state); + } + + public disconnectedCallback(): void { + super.disconnectedCallback(); + this._connected = false; + this.clearInterval(); + } + + protected render(): TemplateResult { + const state = this.state; + + if (!state) { + return html` + ${this.renderStyle()} + + `; + } + + const domain = computeStateDomain(state); + + return html` + ${this.renderStyle()} + + `; + } + + static get properties(): PropertyDeclarations { + return { + hass: {}, + state: {}, + _timerTimeRemaining: {}, + }; + } + + protected firstUpdated(changedProperties: PropertyValues): void { + super.firstUpdated(changedProperties); + this.addEventListener("click", (ev) => { + ev.stopPropagation(); + fireEvent(this, "hass-more-info", { entityId: this.state!.entity_id }); + }); + } + + protected updated(changedProperties: PropertyValues): void { + super.updated(changedProperties); + + if (this._connected && changedProperties.has("state")) { + this.startInterval(this.state); + } + } + + private _computeValue(domain: string, state: HassEntity) { + switch (domain) { + case "binary_sensor": + case "device_tracker": + case "updater": + case "sun": + case "alarm_control_panel": + case "timer": + return null; + case "sensor": + default: + return state.state === "unknown" + ? "-" + : this.localize(`component.${domain}.state.${state.state}`) || + state.state; + } + } + + private _computeIcon(domain: string, state: HassEntity) { + if (state.state === "unavailable") { + return null; + } + switch (domain) { + case "alarm_control_panel": + if (state.state === "pending") { + return "hass:clock-fast"; + } + if (state.state === "armed_away") { + return "hass:nature"; + } + if (state.state === "armed_home") { + return "hass:home-variant"; + } + if (state.state === "armed_night") { + return "hass:weather-night"; + } + if (state.state === "armed_custom_bypass") { + return "hass:security-home"; + } + if (state.state === "triggered") { + return "hass:alert-circle"; + } + // state == 'disarmed' + return domainIcon(domain, state.state); + case "binary_sensor": + case "device_tracker": + case "updater": + return stateIcon(state); + case "sun": + return state.state === "above_horizon" + ? domainIcon(domain) + : "hass:brightness-3"; + case "timer": + return state.state === "active" ? "hass:timer" : "hass:timer-off"; + default: + return null; + } + } + + private _computeLabel(domain, state, _timerTimeRemaining) { + if ( + state.state === "unavailable" || + ["device_tracker", "alarm_control_panel"].includes(domain) + ) { + // Localize the state with a special state_badge namespace, which has variations of + // the state translations that are truncated to fit within the badge label. Translations + // are only added for device_tracker and alarm_control_panel. + return ( + this.localize(`state_badge.${domain}.${state.state}`) || + this.localize(`state_badge.default.${state.state}`) || + state.state + ); + } + if (domain === "timer") { + return secondsToDuration(_timerTimeRemaining); + } + return state.attributes.unit_of_measurement || null; + } + + private clearInterval() { + if (this._updateRemaining) { + clearInterval(this._updateRemaining); + this._updateRemaining = undefined; + } + } + + private startInterval(stateObj) { + this.clearInterval(); + if (computeStateDomain(stateObj) === "timer") { + this.calculateTimerRemaining(stateObj); + + if (stateObj.state === "active") { + this._updateRemaining = window.setInterval( + () => this.calculateTimerRemaining(this.state), + 1000 + ); + } + } + } + + private calculateTimerRemaining(stateObj) { + this._timerTimeRemaining = timerTimeRemaining(stateObj); + } + + private renderStyle(): TemplateResult { return html` - - `; } +} - static get properties() { - return { - hass: Object, - state: { - type: Object, - observer: "stateChanged", - }, - _timerTimeRemaining: { - type: Number, - value: 0, - }, - }; - } - - connectedCallback() { - super.connectedCallback(); - this.startInterval(this.state); - } - - disconnectedCallback() { - super.disconnectedCallback(); - this.clearInterval(); - } - - ready() { - super.ready(); - this.addEventListener("click", (ev) => this.badgeTap(ev)); - } - - badgeTap(ev) { - ev.stopPropagation(); - this.fire("hass-more-info", { entityId: this.state.entity_id }); - } - - computeClassNames(state) { - const classes = [computeStateDomain(state)]; - classes.push(attributeClassNames(state, ["unit_of_measurement"])); - return classes.join(" "); - } - - computeValue(localize, state) { - const domain = computeStateDomain(state); - switch (domain) { - case "binary_sensor": - case "device_tracker": - case "updater": - case "sun": - case "alarm_control_panel": - case "timer": - return null; - case "sensor": - default: - return state.state === "unknown" - ? "-" - : localize(`component.${domain}.state.${state.state}`) || state.state; - } - } - - computeIcon(state) { - if (state.state === "unavailable") { - return null; - } - const domain = computeStateDomain(state); - switch (domain) { - case "alarm_control_panel": - if (state.state === "pending") { - return "hass:clock-fast"; - } - if (state.state === "armed_away") { - return "hass:nature"; - } - if (state.state === "armed_home") { - return "hass:home-variant"; - } - if (state.state === "armed_night") { - return "hass:weather-night"; - } - if (state.state === "armed_custom_bypass") { - return "hass:security-home"; - } - if (state.state === "triggered") { - return "hass:alert-circle"; - } - // state == 'disarmed' - return domainIcon(domain, state.state); - case "binary_sensor": - case "device_tracker": - case "updater": - return stateIcon(state); - case "sun": - return state.state === "above_horizon" - ? domainIcon(domain) - : "hass:brightness-3"; - case "timer": - return state.state === "active" ? "hass:timer" : "hass:timer-off"; - default: - return null; - } - } - - computeImage(state) { - return state.attributes.entity_picture || null; - } - - computeLabel(localize, state, _timerTimeRemaining) { - const domain = computeStateDomain(state); - if ( - state.state === "unavailable" || - ["device_tracker", "alarm_control_panel"].includes(domain) - ) { - // Localize the state with a special state_badge namespace, which has variations of - // the state translations that are truncated to fit within the badge label. Translations - // are only added for device_tracker and alarm_control_panel. - return ( - localize(`state_badge.${domain}.${state.state}`) || - localize(`state_badge.default.${state.state}`) || - state.state - ); - } - if (domain === "timer") { - return secondsToDuration(_timerTimeRemaining); - } - return state.attributes.unit_of_measurement || null; - } - - computeDescription(state) { - return computeStateName(state); - } - - stateChanged(stateObj) { - this.updateStyles(); - this.startInterval(stateObj); - } - - clearInterval() { - if (this._updateRemaining) { - clearInterval(this._updateRemaining); - this._updateRemaining = null; - } - } - - startInterval(stateObj) { - this.clearInterval(); - if (computeStateDomain(stateObj) === "timer") { - this.calculateTimerRemaining(stateObj); - - if (stateObj.state === "active") { - this._updateRemaining = setInterval( - () => this.calculateTimerRemaining(this.state), - 1000 - ); - } - } - } - - calculateTimerRemaining(stateObj) { - this._timerTimeRemaining = timerTimeRemaining(stateObj); +declare global { + interface HTMLElementTagNameMap { + "ha-state-label-badge": HaStateLabelBadge; } } diff --git a/src/mixins/localize-base-mixin.ts b/src/mixins/localize-base-mixin.ts index f7084902e8..a70821e6be 100644 --- a/src/mixins/localize-base-mixin.ts +++ b/src/mixins/localize-base-mixin.ts @@ -1,4 +1,5 @@ import IntlMessageFormat from "intl-messageformat/src/main"; +import { HomeAssistant } from "../types"; /** * Adapted from Polymer app-localize-behavior. @@ -32,6 +33,7 @@ export interface FormatsType { export type LocalizeFunc = (key: string, ...args: any[]) => string; export interface LocalizeMixin { + hass?: HomeAssistant; localize: LocalizeFunc; } diff --git a/src/panels/lovelace/components/hui-theme-select-editor.ts b/src/panels/lovelace/components/hui-theme-select-editor.ts index 206919b213..68c452c3d2 100644 --- a/src/panels/lovelace/components/hui-theme-select-editor.ts +++ b/src/panels/lovelace/components/hui-theme-select-editor.ts @@ -19,7 +19,7 @@ declare global { export class HuiThemeSelectionEditor extends hassLocalizeLitMixin(LitElement) { public value?: string; - protected hass?: HomeAssistant; + public hass?: HomeAssistant; static get properties(): PropertyDeclarations { return { diff --git a/src/panels/lovelace/editor/hui-dialog-save-config.ts b/src/panels/lovelace/editor/hui-dialog-save-config.ts index 7e00454cfa..295d9ae87c 100644 --- a/src/panels/lovelace/editor/hui-dialog-save-config.ts +++ b/src/panels/lovelace/editor/hui-dialog-save-config.ts @@ -14,7 +14,7 @@ import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; import { SaveDialogParams } from "./show-save-config-dialog"; export class HuiSaveConfig extends hassLocalizeLitMixin(LitElement) { - protected hass?: HomeAssistant; + public hass?: HomeAssistant; private _params?: SaveDialogParams; private _saving: boolean; diff --git a/src/panels/lovelace/editor/view-editor/hui-edit-view.ts b/src/panels/lovelace/editor/view-editor/hui-edit-view.ts index 733ec46a39..8b27068812 100644 --- a/src/panels/lovelace/editor/view-editor/hui-edit-view.ts +++ b/src/panels/lovelace/editor/view-editor/hui-edit-view.ts @@ -30,7 +30,7 @@ import { deleteView, addView, replaceView } from "../config-util"; export class HuiEditView extends hassLocalizeLitMixin(LitElement) { public lovelace?: Lovelace; public viewIndex?: number; - protected hass?: HomeAssistant; + public hass?: HomeAssistant; private _config?: LovelaceViewConfig; private _badges?: EntityConfig[]; private _cards?: LovelaceCardConfig[]; diff --git a/src/panels/lovelace/hui-view.ts b/src/panels/lovelace/hui-view.ts index 52093225a1..8fc3cd6d01 100644 --- a/src/panels/lovelace/hui-view.ts +++ b/src/panels/lovelace/hui-view.ts @@ -5,9 +5,11 @@ import { PropertyDeclarations, } from "@polymer/lit-element"; import { TemplateResult } from "lit-html"; -import { PolymerElement } from "@polymer/polymer"; import "../../components/entity/ha-state-label-badge"; +// This one is for types +// tslint:disable-next-line +import { HaStateLabelBadge } from "../../components/entity/ha-state-label-badge"; import applyThemesOnElement from "../../common/dom/apply_themes_on_element"; @@ -28,7 +30,7 @@ export class HUIView extends hassLocalizeLitMixin(LitElement) { public columns?: number; public index?: number; private _cards: LovelaceCard[]; - private _badges: Array<{ element: PolymerElement; entityId: string }>; + private _badges: Array<{ element: HaStateLabelBadge; entityId: string }>; static get properties(): PropertyDeclarations { return { @@ -158,10 +160,8 @@ export class HUIView extends hassLocalizeLitMixin(LitElement) { } else if (changedProperties.has("hass")) { this._badges.forEach((badge) => { const { element, entityId } = badge; - element.setProperties({ - hass: this.hass, - state: this.hass!.states[entityId], - }); + element.hass = this.hass!; + element.state = this.hass!.states[entityId]; }); } @@ -196,17 +196,9 @@ export class HUIView extends hassLocalizeLitMixin(LitElement) { const elements: HUIView["_badges"] = []; for (const entityId of config.badges) { - if (!(entityId in this.hass!.states)) { - continue; - } - - const element = document.createElement( - "ha-state-label-badge" - ) as PolymerElement; - element.setProperties({ - hass: this.hass, - state: this.hass!.states[entityId], - }); + const element = document.createElement("ha-state-label-badge"); + element.hass = this.hass; + element.state = this.hass!.states[entityId]; elements.push({ element, entityId }); root.appendChild(element); }