From dcad2f2bf01c96f431590dc73aa4304d26a27f4b Mon Sep 17 00:00:00 2001 From: Ian Richardson Date: Mon, 2 Sep 2019 23:19:16 -0500 Subject: [PATCH 1/3] Option to display last changed in glance-card Closes https://github.com/home-assistant/ui-schema/issues/110 --- src/common/entity/compute_state_display.ts | 136 ++++++++++--------- src/panels/lovelace/cards/hui-glance-card.ts | 3 +- src/panels/lovelace/cards/types.ts | 1 + 3 files changed, 75 insertions(+), 65 deletions(-) diff --git a/src/common/entity/compute_state_display.ts b/src/common/entity/compute_state_display.ts index 4ca6fbae7ee0..1116b779e52e 100644 --- a/src/common/entity/compute_state_display.ts +++ b/src/common/entity/compute_state_display.ts @@ -3,83 +3,91 @@ import computeStateDomain from "./compute_state_domain"; import formatDateTime from "../datetime/format_date_time"; import formatDate from "../datetime/format_date"; import formatTime from "../datetime/format_time"; +import relativeTime from "../datetime/relative_time"; import { LocalizeFunc } from "../translations/localize"; export default ( localize: LocalizeFunc, stateObj: HassEntity, - language: string + language: string, + lastChanged: boolean = false ): string => { let display: string | undefined; const domain = computeStateDomain(stateObj); - if (domain === "binary_sensor") { - // Try device class translation, then default binary sensor translation - if (stateObj.attributes.device_class) { - display = localize( - `state.${domain}.${stateObj.attributes.device_class}.${stateObj.state}` - ); - } + if (lastChanged) { + display = relativeTime(new Date(stateObj.last_changed), localize); + } else { + if (domain === "binary_sensor") { + // Try device class translation, then default binary sensor translation + if (stateObj.attributes.device_class) { + display = localize( + `state.${domain}.${stateObj.attributes.device_class}.${ + stateObj.state + }` + ); + } - if (!display) { - display = localize(`state.${domain}.default.${stateObj.state}`); - } - } else if ( - stateObj.attributes.unit_of_measurement && - !["unknown", "unavailable"].includes(stateObj.state) - ) { - display = stateObj.state + " " + stateObj.attributes.unit_of_measurement; - } else if (domain === "input_datetime") { - let date: Date; - if (!stateObj.attributes.has_time) { - date = new Date( - stateObj.attributes.year, - stateObj.attributes.month - 1, - stateObj.attributes.day - ); - display = formatDate(date, language); - } else if (!stateObj.attributes.has_date) { - const now = new Date(); - date = new Date( - // Due to bugs.chromium.org/p/chromium/issues/detail?id=797548 - // don't use artificial 1970 year. - now.getFullYear(), - now.getMonth(), - now.getDay(), - stateObj.attributes.hour, - stateObj.attributes.minute - ); - display = formatTime(date, language); - } else { - date = new Date( - stateObj.attributes.year, - stateObj.attributes.month - 1, - stateObj.attributes.day, - stateObj.attributes.hour, - stateObj.attributes.minute - ); - display = formatDateTime(date, language); - } - } else if (domain === "zwave") { - if (["initializing", "dead"].includes(stateObj.state)) { - display = localize( - `state.zwave.query_stage.${stateObj.state}`, - "query_stage", - stateObj.attributes.query_stage - ); + if (!display) { + display = localize(`state.${domain}.default.${stateObj.state}`); + } + } else if ( + stateObj.attributes.unit_of_measurement && + !["unknown", "unavailable"].includes(stateObj.state) + ) { + display = stateObj.state + " " + stateObj.attributes.unit_of_measurement; + } else if (domain === "input_datetime") { + let date: Date; + if (!stateObj.attributes.has_time) { + date = new Date( + stateObj.attributes.year, + stateObj.attributes.month - 1, + stateObj.attributes.day + ); + display = formatDate(date, language); + } else if (!stateObj.attributes.has_date) { + const now = new Date(); + date = new Date( + // Due to bugs.chromium.org/p/chromium/issues/detail?id=797548 + // don't use artificial 1970 year. + now.getFullYear(), + now.getMonth(), + now.getDay(), + stateObj.attributes.hour, + stateObj.attributes.minute + ); + display = formatTime(date, language); + } else { + date = new Date( + stateObj.attributes.year, + stateObj.attributes.month - 1, + stateObj.attributes.day, + stateObj.attributes.hour, + stateObj.attributes.minute + ); + display = formatDateTime(date, language); + } + } else if (domain === "zwave") { + if (["initializing", "dead"].includes(stateObj.state)) { + display = localize( + `state.zwave.query_stage.${stateObj.state}`, + "query_stage", + stateObj.attributes.query_stage + ); + } else { + display = localize(`state.zwave.default.${stateObj.state}`); + } } else { - display = localize(`state.zwave.default.${stateObj.state}`); + display = localize(`state.${domain}.${stateObj.state}`); } - } else { - display = localize(`state.${domain}.${stateObj.state}`); - } - // Fall back to default, component backend translation, or raw state if nothing else matches. - if (!display) { - display = - localize(`state.default.${stateObj.state}`) || - localize(`component.${domain}.state.${stateObj.state}`) || - stateObj.state; + // Fall back to default, component backend translation, or raw state if nothing else matches. + if (!display) { + display = + localize(`state.default.${stateObj.state}`) || + localize(`component.${domain}.state.${stateObj.state}`) || + stateObj.state; + } } return display; diff --git a/src/panels/lovelace/cards/hui-glance-card.ts b/src/panels/lovelace/cards/hui-glance-card.ts index 69dd4a319922..cba60b0b2f03 100644 --- a/src/panels/lovelace/cards/hui-glance-card.ts +++ b/src/panels/lovelace/cards/hui-glance-card.ts @@ -210,7 +210,8 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard { ${computeStateDisplay( this.hass!.localize, stateObj, - this.hass!.language + this.hass!.language, + this._config!.show_last_changed )} ` diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index 12f0a441c34b..f9233acc3754 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -86,6 +86,7 @@ export interface GlanceCardConfig extends LovelaceCardConfig { show_name?: boolean; show_state?: boolean; show_icon?: boolean; + show_last_changed?: boolean; title?: string; theme?: string; entities: ConfigEntity[]; From 872a193cf676c5855c107289465e5a6c6cfcc07d Mon Sep 17 00:00:00 2001 From: Ian Richardson Date: Tue, 3 Sep 2019 11:06:28 -0500 Subject: [PATCH 2/3] move show_last_changed to entity-level --- src/panels/lovelace/cards/hui-glance-card.ts | 2 +- src/panels/lovelace/cards/types.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/panels/lovelace/cards/hui-glance-card.ts b/src/panels/lovelace/cards/hui-glance-card.ts index cba60b0b2f03..70508b73f7f5 100644 --- a/src/panels/lovelace/cards/hui-glance-card.ts +++ b/src/panels/lovelace/cards/hui-glance-card.ts @@ -211,7 +211,7 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard { this.hass!.localize, stateObj, this.hass!.language, - this._config!.show_last_changed + entityConf.show_last_changed )} ` diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index f9233acc3754..e441c3c51eec 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -80,13 +80,13 @@ export interface GaugeCardConfig extends LovelaceCardConfig { export interface ConfigEntity extends EntityConfig { tap_action?: ActionConfig; hold_action?: ActionConfig; + show_last_changed?: boolean; } export interface GlanceCardConfig extends LovelaceCardConfig { show_name?: boolean; show_state?: boolean; show_icon?: boolean; - show_last_changed?: boolean; title?: string; theme?: string; entities: ConfigEntity[]; From acf50b43e022e69717c37d94c93baa898c38cb3f Mon Sep 17 00:00:00 2001 From: Ian Richardson Date: Tue, 3 Sep 2019 14:44:26 -0500 Subject: [PATCH 3/3] address review comments --- src/common/entity/compute_state_display.ts | 136 +++++++++---------- src/panels/lovelace/cards/hui-glance-card.ts | 27 ++-- src/panels/lovelace/cards/types.ts | 3 + 3 files changed, 83 insertions(+), 83 deletions(-) diff --git a/src/common/entity/compute_state_display.ts b/src/common/entity/compute_state_display.ts index 1116b779e52e..4ca6fbae7ee0 100644 --- a/src/common/entity/compute_state_display.ts +++ b/src/common/entity/compute_state_display.ts @@ -3,91 +3,83 @@ import computeStateDomain from "./compute_state_domain"; import formatDateTime from "../datetime/format_date_time"; import formatDate from "../datetime/format_date"; import formatTime from "../datetime/format_time"; -import relativeTime from "../datetime/relative_time"; import { LocalizeFunc } from "../translations/localize"; export default ( localize: LocalizeFunc, stateObj: HassEntity, - language: string, - lastChanged: boolean = false + language: string ): string => { let display: string | undefined; const domain = computeStateDomain(stateObj); - if (lastChanged) { - display = relativeTime(new Date(stateObj.last_changed), localize); - } else { - if (domain === "binary_sensor") { - // Try device class translation, then default binary sensor translation - if (stateObj.attributes.device_class) { - display = localize( - `state.${domain}.${stateObj.attributes.device_class}.${ - stateObj.state - }` - ); - } - - if (!display) { - display = localize(`state.${domain}.default.${stateObj.state}`); - } - } else if ( - stateObj.attributes.unit_of_measurement && - !["unknown", "unavailable"].includes(stateObj.state) - ) { - display = stateObj.state + " " + stateObj.attributes.unit_of_measurement; - } else if (domain === "input_datetime") { - let date: Date; - if (!stateObj.attributes.has_time) { - date = new Date( - stateObj.attributes.year, - stateObj.attributes.month - 1, - stateObj.attributes.day - ); - display = formatDate(date, language); - } else if (!stateObj.attributes.has_date) { - const now = new Date(); - date = new Date( - // Due to bugs.chromium.org/p/chromium/issues/detail?id=797548 - // don't use artificial 1970 year. - now.getFullYear(), - now.getMonth(), - now.getDay(), - stateObj.attributes.hour, - stateObj.attributes.minute - ); - display = formatTime(date, language); - } else { - date = new Date( - stateObj.attributes.year, - stateObj.attributes.month - 1, - stateObj.attributes.day, - stateObj.attributes.hour, - stateObj.attributes.minute - ); - display = formatDateTime(date, language); - } - } else if (domain === "zwave") { - if (["initializing", "dead"].includes(stateObj.state)) { - display = localize( - `state.zwave.query_stage.${stateObj.state}`, - "query_stage", - stateObj.attributes.query_stage - ); - } else { - display = localize(`state.zwave.default.${stateObj.state}`); - } - } else { - display = localize(`state.${domain}.${stateObj.state}`); + if (domain === "binary_sensor") { + // Try device class translation, then default binary sensor translation + if (stateObj.attributes.device_class) { + display = localize( + `state.${domain}.${stateObj.attributes.device_class}.${stateObj.state}` + ); } - // Fall back to default, component backend translation, or raw state if nothing else matches. if (!display) { - display = - localize(`state.default.${stateObj.state}`) || - localize(`component.${domain}.state.${stateObj.state}`) || - stateObj.state; + display = localize(`state.${domain}.default.${stateObj.state}`); + } + } else if ( + stateObj.attributes.unit_of_measurement && + !["unknown", "unavailable"].includes(stateObj.state) + ) { + display = stateObj.state + " " + stateObj.attributes.unit_of_measurement; + } else if (domain === "input_datetime") { + let date: Date; + if (!stateObj.attributes.has_time) { + date = new Date( + stateObj.attributes.year, + stateObj.attributes.month - 1, + stateObj.attributes.day + ); + display = formatDate(date, language); + } else if (!stateObj.attributes.has_date) { + const now = new Date(); + date = new Date( + // Due to bugs.chromium.org/p/chromium/issues/detail?id=797548 + // don't use artificial 1970 year. + now.getFullYear(), + now.getMonth(), + now.getDay(), + stateObj.attributes.hour, + stateObj.attributes.minute + ); + display = formatTime(date, language); + } else { + date = new Date( + stateObj.attributes.year, + stateObj.attributes.month - 1, + stateObj.attributes.day, + stateObj.attributes.hour, + stateObj.attributes.minute + ); + display = formatDateTime(date, language); } + } else if (domain === "zwave") { + if (["initializing", "dead"].includes(stateObj.state)) { + display = localize( + `state.zwave.query_stage.${stateObj.state}`, + "query_stage", + stateObj.attributes.query_stage + ); + } else { + display = localize(`state.zwave.default.${stateObj.state}`); + } + } else { + display = localize(`state.${domain}.${stateObj.state}`); + } + + // Fall back to default, component backend translation, or raw state if nothing else matches. + if (!display) { + display = + localize(`state.default.${stateObj.state}`) || + localize(`component.${domain}.state.${stateObj.state}`) || + stateObj.state; } return display; diff --git a/src/panels/lovelace/cards/hui-glance-card.ts b/src/panels/lovelace/cards/hui-glance-card.ts index 70508b73f7f5..be7ee5d4f67d 100644 --- a/src/panels/lovelace/cards/hui-glance-card.ts +++ b/src/panels/lovelace/cards/hui-glance-card.ts @@ -13,6 +13,7 @@ import { classMap } from "lit-html/directives/class-map"; import computeStateDisplay from "../../../common/entity/compute_state_display"; import computeStateName from "../../../common/entity/compute_state_name"; import applyThemesOnElement from "../../../common/dom/apply_themes_on_element"; +import relativeTime from "../../../common/datetime/relative_time"; import "../../../components/entity/state-badge"; import "../../../components/ha-card"; @@ -24,7 +25,7 @@ import { LovelaceCard, LovelaceCardEditor } from "../types"; import { longPress } from "../common/directives/long-press-directive"; import { processConfigEntities } from "../common/process-config-entities"; import { handleClick } from "../common/handle-click"; -import { GlanceCardConfig, ConfigEntity } from "./types"; +import { GlanceCardConfig, GlanceConfigEntity } from "./types"; @customElement("hui-glance-card") export class HuiGlanceCard extends LitElement implements LovelaceCard { @@ -41,7 +42,7 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard { @property() private _config?: GlanceCardConfig; - private _configEntities?: ConfigEntity[]; + private _configEntities?: GlanceConfigEntity[]; public getCardSize(): number { return ( @@ -52,7 +53,7 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard { public setConfig(config: GlanceCardConfig): void { this._config = { theme: "default", ...config }; - const entities = processConfigEntities(config.entities); + const entities = processConfigEntities(config.entities); for (const entity of entities) { if ( @@ -207,12 +208,16 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard { ${this._config!.show_state !== false ? html`
- ${computeStateDisplay( - this.hass!.localize, - stateObj, - this.hass!.language, - entityConf.show_last_changed - )} + ${entityConf.show_last_changed + ? relativeTime( + new Date(stateObj.last_changed), + this.hass!.localize + ) + : computeStateDisplay( + this.hass!.localize, + stateObj, + this.hass!.language + )}
` : ""} @@ -221,12 +226,12 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard { } private _handleTap(ev: MouseEvent): void { - const config = (ev.currentTarget as any).entityConf as ConfigEntity; + const config = (ev.currentTarget as any).entityConf as GlanceConfigEntity; handleClick(this, this.hass!, config, false); } private _handleHold(ev: MouseEvent): void { - const config = (ev.currentTarget as any).entityConf as ConfigEntity; + const config = (ev.currentTarget as any).entityConf as GlanceConfigEntity; handleClick(this, this.hass!, config, true); } } diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index e441c3c51eec..ce1d21ad2520 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -80,6 +80,9 @@ export interface GaugeCardConfig extends LovelaceCardConfig { export interface ConfigEntity extends EntityConfig { tap_action?: ActionConfig; hold_action?: ActionConfig; +} + +export interface GlanceConfigEntity extends ConfigEntity { show_last_changed?: boolean; }