From 5dd507fe5fef093d1b3917950381ae92540a2905 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Fri, 22 Nov 2024 17:53:38 +0100 Subject: [PATCH] Align state display with Home Assistant (#1592) * Use state display and attribute display * Remove format date * Remove blank before percent * Fix number duration format --- .../chips/alarm-control-panel-chip.ts | 11 +- src/cards/chips-card/chips/entity-chip.ts | 13 +- src/cards/chips-card/chips/light-chip.ts | 11 +- src/cards/chips-card/chips/weather-chip.ts | 23 +- src/cards/climate-card/climate-card.ts | 20 +- src/cards/cover-card/cover-card.ts | 19 +- src/cards/fan-card/fan-card.ts | 19 +- src/cards/humidifier-card/humidifier-card.ts | 19 +- src/cards/light-card/light-card.ts | 24 +- .../media-player-card/media-player-card.ts | 27 +- src/cards/media-player-card/utils.ts | 11 +- src/cards/number-card/number-card.ts | 25 +- .../controls/select-option-control.ts | 13 +- src/ha/common/datetime/duration.ts | 19 -- src/ha/common/datetime/format_date.ts | 193 -------------- src/ha/common/datetime/format_date_time.ts | 114 --------- src/ha/common/datetime/format_time.ts | 90 ------- .../datetime/milliseconds_to_duration.ts | 25 -- src/ha/common/datetime/use_am_pm.ts | 16 -- src/ha/common/entity/compute_state_display.ts | 235 ------------------ .../translations/blank_before_percent.ts | 18 -- src/ha/index.ts | 6 - src/ha/types.ts | 2 +- src/shared/input-number.ts | 2 +- src/utils/base-card.ts | 12 +- 25 files changed, 71 insertions(+), 896 deletions(-) delete mode 100644 src/ha/common/datetime/duration.ts delete mode 100644 src/ha/common/datetime/format_date.ts delete mode 100644 src/ha/common/datetime/format_date_time.ts delete mode 100644 src/ha/common/datetime/format_time.ts delete mode 100644 src/ha/common/datetime/milliseconds_to_duration.ts delete mode 100644 src/ha/common/datetime/use_am_pm.ts delete mode 100644 src/ha/common/translations/blank_before_percent.ts diff --git a/src/cards/chips-card/chips/alarm-control-panel-chip.ts b/src/cards/chips-card/chips/alarm-control-panel-chip.ts index 8638c56b6..828057921 100644 --- a/src/cards/chips-card/chips/alarm-control-panel-chip.ts +++ b/src/cards/chips-card/chips/alarm-control-panel-chip.ts @@ -7,7 +7,6 @@ import { actionHandler, ActionHandlerEvent, computeRTL, - computeStateDisplay, handleAction, hasAction, HomeAssistant, @@ -82,15 +81,7 @@ export class AlarmControlPanelChip extends LitElement implements LovelaceChip { const iconColor = getStateColor(stateObj.state); const iconPulse = shouldPulse(stateObj.state); - const stateDisplay = this.hass.formatEntityState - ? this.hass.formatEntityState(stateObj) - : computeStateDisplay( - this.hass.localize, - stateObj, - this.hass.locale, - this.hass.config, - this.hass.entities - ); + const stateDisplay = this.hass.formatEntityState(stateObj); const iconStyle = {}; if (iconColor) { diff --git a/src/cards/chips-card/chips/entity-chip.ts b/src/cards/chips-card/chips/entity-chip.ts index f894fe571..712f9a3a7 100644 --- a/src/cards/chips-card/chips/entity-chip.ts +++ b/src/cards/chips-card/chips/entity-chip.ts @@ -1,3 +1,4 @@ +import { HassEntity } from "home-assistant-js-websocket"; import { css, CSSResultGroup, @@ -13,7 +14,6 @@ import { actionHandler, ActionHandlerEvent, computeRTL, - computeStateDisplay, getEntityPicture, handleAction, hasAction, @@ -31,7 +31,6 @@ import { LovelaceChip, } from "../../../utils/lovelace/chip/types"; import { LovelaceChipEditor } from "../../../utils/lovelace/types"; -import { HassEntity } from "home-assistant-js-websocket"; @customElement(computeChipComponentName("entity")) export class EntityChip extends LitElement implements LovelaceChip { @@ -84,15 +83,7 @@ export class EntityChip extends LitElement implements LovelaceChip { ? getEntityPicture(stateObj) : undefined; - const stateDisplay = this.hass.formatEntityState - ? this.hass.formatEntityState(stateObj) - : computeStateDisplay( - this.hass.localize, - stateObj, - this.hass.locale, - this.hass.config, - this.hass.entities - ); + const stateDisplay = this.hass.formatEntityState(stateObj); const active = isActive(stateObj); diff --git a/src/cards/chips-card/chips/light-chip.ts b/src/cards/chips-card/chips/light-chip.ts index 500a501e1..3d7239aef 100644 --- a/src/cards/chips-card/chips/light-chip.ts +++ b/src/cards/chips-card/chips/light-chip.ts @@ -6,7 +6,6 @@ import { actionHandler, ActionHandlerEvent, computeRTL, - computeStateDisplay, handleAction, hasAction, HomeAssistant, @@ -80,15 +79,7 @@ export class LightChip extends LitElement implements LovelaceChip { const name = this._config.name || stateObj.attributes.friendly_name || ""; const icon = this._config.icon; - const stateDisplay = this.hass.formatEntityState - ? this.hass.formatEntityState(stateObj) - : computeStateDisplay( - this.hass.localize, - stateObj, - this.hass.locale, - this.hass.config, - this.hass.entities - ); + const stateDisplay = this.hass.formatEntityState(stateObj); const active = isActive(stateObj); diff --git a/src/cards/chips-card/chips/weather-chip.ts b/src/cards/chips-card/chips/weather-chip.ts index de540c01c..f4174d0d5 100644 --- a/src/cards/chips-card/chips/weather-chip.ts +++ b/src/cards/chips-card/chips/weather-chip.ts @@ -1,10 +1,10 @@ +import { HassEntity } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { actionHandler, ActionHandlerEvent, computeRTL, - computeStateDisplay, formatNumber, handleAction, hasAction, @@ -20,7 +20,6 @@ import { } from "../../../utils/lovelace/chip/types"; import { LovelaceChipEditor } from "../../../utils/lovelace/types"; import { getWeatherStateSVG, weatherSVGStyles } from "../../../utils/weather"; -import { HassEntity } from "home-assistant-js-websocket"; @customElement(computeChipComponentName("weather")) export class WeatherChip extends LitElement implements LovelaceChip { @@ -71,23 +70,15 @@ export class WeatherChip extends LitElement implements LovelaceChip { const displayLabels: string[] = []; if (this._config.show_conditions) { - const stateDisplay = this.hass.formatEntityState - ? this.hass.formatEntityState(stateObj) - : computeStateDisplay( - this.hass.localize, - stateObj, - this.hass.locale, - this.hass.config, - this.hass.entities - ); + const stateDisplay = this.hass.formatEntityState(stateObj); displayLabels.push(stateDisplay); } if (this._config.show_temperature) { - const temperatureDisplay = `${formatNumber( - stateObj.attributes.temperature, - this.hass.locale - )} ${this.hass.config.unit_system.temperature}`; + const temperatureDisplay = this.hass.formatEntityAttributeValue( + stateObj, + "temperature" + ); displayLabels.push(temperatureDisplay); } @@ -104,7 +95,7 @@ export class WeatherChip extends LitElement implements LovelaceChip { > ${weatherIcon} ${displayLabels.length > 0 - ? html`${displayLabels.join(" / ")}` + ? html`${displayLabels.join(" ⸱ ")}` : nothing} `; diff --git a/src/cards/climate-card/climate-card.ts b/src/cards/climate-card/climate-card.ts index 7bdf551e0..52fc59ab0 100644 --- a/src/cards/climate-card/climate-card.ts +++ b/src/cards/climate-card/climate-card.ts @@ -14,7 +14,6 @@ import { ActionHandlerEvent, ClimateEntity, computeRTL, - computeStateDisplay, formatNumber, handleAction, hasAction, @@ -167,22 +166,13 @@ export class ClimateCard const appearance = computeAppearance(this._config); const picture = computeEntityPicture(stateObj, appearance.icon_type); - let stateDisplay = this.hass.formatEntityState - ? this.hass.formatEntityState(stateObj) - : computeStateDisplay( - this.hass.localize, - stateObj, - this.hass.locale, - this.hass.config, - this.hass.entities - ); + let stateDisplay = this.hass.formatEntityState(stateObj); if (stateObj.attributes.current_temperature !== null) { - const temperature = formatNumber( - stateObj.attributes.current_temperature, - this.hass.locale + const temperature = this.hass.formatEntityAttributeValue( + stateObj, + "current_temperature" ); - const unit = this.hass.config.unit_system.temperature; - stateDisplay += ` - ${temperature} ${unit}`; + stateDisplay += ` ⸱ ${temperature}`; } const rtl = computeRTL(this.hass); diff --git a/src/cards/cover-card/cover-card.ts b/src/cards/cover-card/cover-card.ts index 0efcccc92..8769f283f 100644 --- a/src/cards/cover-card/cover-card.ts +++ b/src/cards/cover-card/cover-card.ts @@ -12,9 +12,7 @@ import { styleMap } from "lit/directives/style-map.js"; import { actionHandler, ActionHandlerEvent, - blankBeforePercent, computeRTL, - computeStateDisplay, CoverEntity, handleAction, hasAction, @@ -195,17 +193,14 @@ export class CoverCard const appearance = computeAppearance(this._config); const picture = computeEntityPicture(stateObj, appearance.icon_type); - let stateDisplay = this.hass.formatEntityState - ? this.hass.formatEntityState(stateObj) - : computeStateDisplay( - this.hass.localize, - stateObj, - this.hass.locale, - this.hass.config, - this.hass.entities - ); + let stateDisplay = this.hass.formatEntityState(stateObj); if (this.position) { - stateDisplay += ` - ${this.position}${blankBeforePercent(this.hass.locale)}%`; + const position = this.hass.formatEntityAttributeValue( + stateObj, + "current_position", + this.position + ); + stateDisplay += ` ⸱ ${position}`; } const rtl = computeRTL(this.hass); diff --git a/src/cards/fan-card/fan-card.ts b/src/cards/fan-card/fan-card.ts index 3ea139248..8f6fd3805 100644 --- a/src/cards/fan-card/fan-card.ts +++ b/src/cards/fan-card/fan-card.ts @@ -13,9 +13,7 @@ import { styleMap } from "lit/directives/style-map.js"; import { actionHandler, ActionHandlerEvent, - blankBeforePercent, computeRTL, - computeStateDisplay, handleAction, hasAction, HomeAssistant, @@ -137,17 +135,14 @@ export class FanCard const appearance = computeAppearance(this._config); const picture = computeEntityPicture(stateObj, appearance.icon_type); - let stateDisplay = this.hass.formatEntityState - ? this.hass.formatEntityState(stateObj) - : computeStateDisplay( - this.hass.localize, - stateObj, - this.hass.locale, - this.hass.config, - this.hass.entities - ); + let stateDisplay = this.hass.formatEntityState(stateObj); if (this.percentage != null && stateObj.state === "on") { - stateDisplay = `${this.percentage}${blankBeforePercent(this.hass.locale)}%`; + const percentage = this.hass.formatEntityAttributeValue( + stateObj, + "percentage", + this.percentage + ); + stateDisplay = percentage; } const rtl = computeRTL(this.hass); diff --git a/src/cards/humidifier-card/humidifier-card.ts b/src/cards/humidifier-card/humidifier-card.ts index 9939c3526..3dac3656f 100644 --- a/src/cards/humidifier-card/humidifier-card.ts +++ b/src/cards/humidifier-card/humidifier-card.ts @@ -4,9 +4,7 @@ import { classMap } from "lit/directives/class-map.js"; import { actionHandler, ActionHandlerEvent, - blankBeforePercent, computeRTL, - computeStateDisplay, handleAction, hasAction, HomeAssistant, @@ -110,17 +108,14 @@ export class HumidifierCard const appearance = computeAppearance(this._config); const picture = computeEntityPicture(stateObj, appearance.icon_type); - let stateDisplay = this.hass.formatEntityState - ? this.hass.formatEntityState(stateObj) - : computeStateDisplay( - this.hass.localize, - stateObj, - this.hass.locale, - this.hass.config, - this.hass.entities - ); + let stateDisplay = this.hass.formatEntityState(stateObj); if (this.humidity) { - stateDisplay = `${this.humidity}${blankBeforePercent(this.hass.locale)}%`; + const humidity = this.hass.formatEntityAttributeValue( + stateObj, + "current_humidity", + this.humidity + ); + stateDisplay = humidity; } const rtl = computeRTL(this.hass); diff --git a/src/cards/light-card/light-card.ts b/src/cards/light-card/light-card.ts index 7d3a6bffd..0a0ed1d4e 100644 --- a/src/cards/light-card/light-card.ts +++ b/src/cards/light-card/light-card.ts @@ -12,9 +12,7 @@ import { styleMap } from "lit/directives/style-map.js"; import { actionHandler, ActionHandlerEvent, - blankBeforePercent, computeRTL, - computeStateDisplay, handleAction, hasAction, HomeAssistant, @@ -46,7 +44,6 @@ import "./controls/light-color-control"; import "./controls/light-color-temp-control"; import { LightCardConfig } from "./light-card-config"; import { - getBrightness, getRGBColor, isColorLight, isColorSuperLight, @@ -158,12 +155,12 @@ export class LightCard const stateObj = this._stateObj; if (!stateObj) return; - this.brightness = getBrightness(stateObj); + this.brightness = stateObj.attributes.brightness; } private onCurrentBrightnessChange(e: CustomEvent<{ value?: number }>): void { if (e.detail.value != null) { - this.brightness = e.detail.value; + this.brightness = (e.detail.value * 255) / 100; } } @@ -196,17 +193,14 @@ export class LightCard const appearance = computeAppearance(this._config); const picture = computeEntityPicture(stateObj, appearance.icon_type); - let stateDisplay = this.hass.formatEntityState - ? this.hass.formatEntityState(stateObj) - : computeStateDisplay( - this.hass.localize, - stateObj, - this.hass.locale, - this.hass.config, - this.hass.entities - ); + let stateDisplay = this.hass.formatEntityState(stateObj); if (this.brightness != null) { - stateDisplay = `${this.brightness}${blankBeforePercent(this.hass.locale)}%`; + const brightness = this.hass.formatEntityAttributeValue( + stateObj, + "brightness", + this.brightness + ); + stateDisplay = brightness; } const rtl = computeRTL(this.hass); diff --git a/src/cards/media-player-card/media-player-card.ts b/src/cards/media-player-card/media-player-card.ts index 4c14ba10c..93b8009a5 100644 --- a/src/cards/media-player-card/media-player-card.ts +++ b/src/cards/media-player-card/media-player-card.ts @@ -11,7 +11,6 @@ import { classMap } from "lit/directives/class-map.js"; import { actionHandler, ActionHandlerEvent, - blankBeforePercent, computeRTL, handleAction, hasAction, @@ -45,7 +44,6 @@ import { computeMediaIcon, computeMediaNameDisplay, computeMediaStateDisplay, - getVolumeLevel, } from "./utils"; type MediaPlayerCardControl = "media_control" | "volume_control"; @@ -137,13 +135,12 @@ export class MediaPlayerCard const stateObj = this._stateObj; if (!stateObj) return; - const volume = getVolumeLevel(stateObj); - this.volume = volume != null ? Math.round(volume) : volume; + this.volume = stateObj.attributes.volume_level; } private onCurrentVolumeChange(e: CustomEvent<{ value?: number }>): void { if (e.detail.value != null) { - this.volume = e.detail.value; + this.volume = e.detail.value / 100; } } @@ -173,18 +170,22 @@ export class MediaPlayerCard const icon = computeMediaIcon(this._config, stateObj); const nameDisplay = computeMediaNameDisplay(this._config, stateObj); - const stateDisplay = computeMediaStateDisplay( + const appearance = computeAppearance(this._config); + const picture = computeEntityPicture(stateObj, appearance.icon_type); + let stateDisplay = computeMediaStateDisplay( this._config, stateObj, this.hass ); - const appearance = computeAppearance(this._config); - const picture = computeEntityPicture(stateObj, appearance.icon_type); - const stateValue = - this.volume != null && this._config.show_volume_level - ? `${stateDisplay} - ${this.volume}${blankBeforePercent(this.hass.locale)}%` - : stateDisplay; + if (this.volume != null && this._config.show_volume_level) { + const volume = this.hass.formatEntityAttributeValue( + stateObj, + "volume_level", + this.volume + ); + stateDisplay += ` ⸱ ${volume}`; + } const rtl = computeRTL(this.hass); @@ -216,7 +217,7 @@ export class MediaPlayerCard stateObj, appearance, nameDisplay, - stateValue + stateDisplay )}; ${isControlVisible diff --git a/src/cards/media-player-card/utils.ts b/src/cards/media-player-card/utils.ts index 58359d268..3a8cfc8ed 100644 --- a/src/cards/media-player-card/utils.ts +++ b/src/cards/media-player-card/utils.ts @@ -15,7 +15,6 @@ import { UNAVAILABLE, UNKNOWN, computeMediaDescription, - computeStateDisplay, supportsFeature, } from "../../ha"; import { @@ -56,15 +55,7 @@ export function computeMediaStateDisplay( entity: MediaPlayerEntity, hass: HomeAssistant ): string { - let state = hass.formatEntityState - ? hass.formatEntityState(entity) - : computeStateDisplay( - hass.localize, - entity, - hass.locale, - hass.config, - hass.entities - ); + let state = hass.formatEntityState(entity); if ( ![UNAVAILABLE, UNKNOWN, OFF].includes(entity.state) && config.use_media_info diff --git a/src/cards/number-card/number-card.ts b/src/cards/number-card/number-card.ts index 5ef2e9d46..f223f7a69 100644 --- a/src/cards/number-card/number-card.ts +++ b/src/cards/number-card/number-card.ts @@ -14,10 +14,6 @@ import { actionHandler, ActionHandlerEvent, computeRTL, - computeStateDisplay, - formatNumber, - getDefaultFormatOptions, - getNumberFormatOptions, handleAction, hasAction, HomeAssistant, @@ -124,25 +120,12 @@ export class NumberCard const appearance = computeAppearance(this._config); const picture = computeEntityPicture(stateObj, appearance.icon_type); - let stateDisplay = this.hass.formatEntityState - ? this.hass.formatEntityState(stateObj) - : computeStateDisplay( - this.hass.localize, - stateObj, - this.hass.locale, - this.hass.config, - this.hass.entities - ); + let stateDisplay = this.hass.formatEntityState(stateObj); if (this.value !== undefined) { - const numberValue = formatNumber( - this.value, - this.hass.locale, - getNumberFormatOptions( - stateObj, - this.hass.entities[stateObj.entity_id] - ) ?? getDefaultFormatOptions(stateObj.state) + stateDisplay = this.hass.formatEntityState( + stateObj, + this.value.toString() ); - stateDisplay = `${numberValue} ${stateObj.attributes.unit_of_measurement ?? ""}`; } const rtl = computeRTL(this.hass); diff --git a/src/cards/select-card/controls/select-option-control.ts b/src/cards/select-card/controls/select-option-control.ts index 56110b034..23dbc3d5a 100644 --- a/src/cards/select-card/controls/select-option-control.ts +++ b/src/cards/select-card/controls/select-option-control.ts @@ -1,7 +1,7 @@ import { HassEntity } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators.js"; -import { computeStateDisplay, HomeAssistant } from "../../../ha"; +import { HomeAssistant } from "../../../ha"; import "../../../shared/form/mushroom-select"; import { getCurrentOption, getOptions } from "../utils"; @@ -47,16 +47,7 @@ export class SelectOptionControl extends LitElement { ${options.map((option) => { return html` - ${this.hass.formatEntityState - ? this.hass.formatEntityState(this.entity, option) - : computeStateDisplay( - this.hass.localize, - this.entity, - this.hass.locale, - this.hass.config, - this.hass.entities, - option - )} + ${this.hass.formatEntityState(this.entity, option)} `; })} diff --git a/src/ha/common/datetime/duration.ts b/src/ha/common/datetime/duration.ts deleted file mode 100644 index 36fef1902..000000000 --- a/src/ha/common/datetime/duration.ts +++ /dev/null @@ -1,19 +0,0 @@ -import millisecondsToDuration from "./milliseconds_to_duration"; - -const DAY_IN_MILLISECONDS = 86400000; -const HOUR_IN_MILLISECONDS = 3600000; -const MINUTE_IN_MILLISECONDS = 60000; -const SECOND_IN_MILLISECONDS = 1000; - -export const UNIT_TO_MILLISECOND_CONVERT = { - ms: 1, - s: SECOND_IN_MILLISECONDS, - min: MINUTE_IN_MILLISECONDS, - h: HOUR_IN_MILLISECONDS, - d: DAY_IN_MILLISECONDS, -}; - -export const formatDuration = (duration: string, units: string): string => - millisecondsToDuration( - parseFloat(duration) * UNIT_TO_MILLISECOND_CONVERT[units] - ) || "0"; diff --git a/src/ha/common/datetime/format_date.ts b/src/ha/common/datetime/format_date.ts deleted file mode 100644 index 95ca1a94c..000000000 --- a/src/ha/common/datetime/format_date.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { HassConfig } from "home-assistant-js-websocket"; -import memoizeOne from "memoize-one"; -import { FrontendLocaleData, DateFormat } from "../../data/translation"; - -// Tuesday, August 10 -export const formatDateWeekdayDay = ( - dateObj: Date, - locale: FrontendLocaleData, - config: HassConfig -) => formatDateWeekdayDayMem(locale, config.time_zone).format(dateObj); - -const formatDateWeekdayDayMem = memoizeOne( - (locale: FrontendLocaleData, serverTimeZone: string) => - new Intl.DateTimeFormat(locale.language, { - weekday: "long", - month: "long", - day: "numeric", - timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, - }) -); - -// August 10, 2021 -export const formatDate = ( - dateObj: Date, - locale: FrontendLocaleData, - config: HassConfig -) => formatDateMem(locale, config.time_zone).format(dateObj); - -const formatDateMem = memoizeOne( - (locale: FrontendLocaleData, serverTimeZone: string) => - new Intl.DateTimeFormat(locale.language, { - year: "numeric", - month: "long", - day: "numeric", - timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, - }) -); - -// 10/08/2021 -export const formatDateNumeric = ( - dateObj: Date, - locale: FrontendLocaleData, - config: HassConfig -) => { - const formatter = formatDateNumericMem(locale, config.time_zone); - - if ( - locale.date_format === DateFormat.language || - locale.date_format === DateFormat.system - ) { - return formatter.format(dateObj); - } - - const parts = formatter.formatToParts(dateObj); - - const literal = parts.find((value) => value.type === "literal")?.value; - const day = parts.find((value) => value.type === "day")?.value; - const month = parts.find((value) => value.type === "month")?.value; - const year = parts.find((value) => value.type === "year")?.value; - - const lastPart = parts[parts.length - 1]; - let lastLiteral = lastPart?.type === "literal" ? lastPart?.value : ""; - - if (locale.language === "bg" && locale.date_format === DateFormat.YMD) { - lastLiteral = ""; - } - - const formats = { - [DateFormat.DMY]: `${day}${literal}${month}${literal}${year}${lastLiteral}`, - [DateFormat.MDY]: `${month}${literal}${day}${literal}${year}${lastLiteral}`, - [DateFormat.YMD]: `${year}${literal}${month}${literal}${day}${lastLiteral}`, - }; - - return formats[locale.date_format]; -}; - -const formatDateNumericMem = memoizeOne( - (locale: FrontendLocaleData, serverTimeZone: string) => { - const localeString = - locale.date_format === DateFormat.system ? undefined : locale.language; - - if ( - locale.date_format === DateFormat.language || - locale.date_format === DateFormat.system - ) { - return new Intl.DateTimeFormat(localeString, { - year: "numeric", - month: "numeric", - day: "numeric", - timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, - }); - } - - return new Intl.DateTimeFormat(localeString, { - year: "numeric", - month: "numeric", - day: "numeric", - timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, - }); - } -); - -// Aug 10 -export const formatDateShort = ( - dateObj: Date, - locale: FrontendLocaleData, - config: HassConfig -) => formatDateShortMem(locale, config.time_zone).format(dateObj); - -const formatDateShortMem = memoizeOne( - (locale: FrontendLocaleData, serverTimeZone: string) => - new Intl.DateTimeFormat(locale.language, { - day: "numeric", - month: "short", - timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, - }) -); - -// August 2021 -export const formatDateMonthYear = ( - dateObj: Date, - locale: FrontendLocaleData, - config: HassConfig -) => formatDateMonthYearMem(locale, config.time_zone).format(dateObj); - -const formatDateMonthYearMem = memoizeOne( - (locale: FrontendLocaleData, serverTimeZone: string) => - new Intl.DateTimeFormat(locale.language, { - month: "long", - year: "numeric", - timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, - }) -); - -// August -export const formatDateMonth = ( - dateObj: Date, - locale: FrontendLocaleData, - config: HassConfig -) => formatDateMonthMem(locale, config.time_zone).format(dateObj); - -const formatDateMonthMem = memoizeOne( - (locale: FrontendLocaleData, serverTimeZone: string) => - new Intl.DateTimeFormat(locale.language, { - month: "long", - timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, - }) -); - -// 2021 -export const formatDateYear = ( - dateObj: Date, - locale: FrontendLocaleData, - config: HassConfig -) => formatDateYearMem(locale, config.time_zone).format(dateObj); - -const formatDateYearMem = memoizeOne( - (locale: FrontendLocaleData, serverTimeZone: string) => - new Intl.DateTimeFormat(locale.language, { - year: "numeric", - timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, - }) -); - -// Monday -export const formatDateWeekday = ( - dateObj: Date, - locale: FrontendLocaleData, - config: HassConfig -) => formatDateWeekdayMem(locale, config.time_zone).format(dateObj); - -const formatDateWeekdayMem = memoizeOne( - (locale: FrontendLocaleData, serverTimeZone: string) => - new Intl.DateTimeFormat(locale.language, { - weekday: "long", - timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, - }) -); - -// Mon -export const formatDateWeekdayShort = ( - dateObj: Date, - locale: FrontendLocaleData, - config: HassConfig -) => formatDateWeekdayShortMem(locale, config.time_zone).format(dateObj); - -const formatDateWeekdayShortMem = memoizeOne( - (locale: FrontendLocaleData, serverTimeZone: string) => - new Intl.DateTimeFormat(locale.language, { - weekday: "short", - timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, - }) -); diff --git a/src/ha/common/datetime/format_date_time.ts b/src/ha/common/datetime/format_date_time.ts deleted file mode 100644 index f70c685a1..000000000 --- a/src/ha/common/datetime/format_date_time.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { HassConfig } from "home-assistant-js-websocket"; -import memoizeOne from "memoize-one"; -import { FrontendLocaleData } from "../../data/translation"; -import { formatDateNumeric } from "./format_date"; -import { formatTime } from "./format_time"; -import { useAmPm } from "./use_am_pm"; - -// August 9, 2021, 8:23 AM -export const formatDateTime = ( - dateObj: Date, - locale: FrontendLocaleData, - config: HassConfig -) => formatDateTimeMem(locale, config.time_zone).format(dateObj); - -const formatDateTimeMem = memoizeOne( - (locale: FrontendLocaleData, serverTimeZone: string) => - new Intl.DateTimeFormat( - locale.language === "en" && !useAmPm(locale) - ? "en-u-hc-h23" - : locale.language, - { - year: "numeric", - month: "long", - day: "numeric", - hour: useAmPm(locale) ? "numeric" : "2-digit", - minute: "2-digit", - hour12: useAmPm(locale), - timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, - } - ) -); - -// Aug 9, 2021, 8:23 AM -export const formatShortDateTimeWithYear = ( - dateObj: Date, - locale: FrontendLocaleData, - config: HassConfig -) => formatShortDateTimeWithYearMem(locale, config.time_zone).format(dateObj); - -const formatShortDateTimeWithYearMem = memoizeOne( - (locale: FrontendLocaleData, serverTimeZone: string) => - new Intl.DateTimeFormat( - locale.language === "en" && !useAmPm(locale) - ? "en-u-hc-h23" - : locale.language, - { - year: "numeric", - month: "short", - day: "numeric", - hour: useAmPm(locale) ? "numeric" : "2-digit", - minute: "2-digit", - hour12: useAmPm(locale), - timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, - } - ) -); - -// Aug 9, 8:23 AM -export const formatShortDateTime = ( - dateObj: Date, - locale: FrontendLocaleData, - config: HassConfig -) => formatShortDateTimeMem(locale, config.time_zone).format(dateObj); - -const formatShortDateTimeMem = memoizeOne( - (locale: FrontendLocaleData, serverTimeZone: string) => - new Intl.DateTimeFormat( - locale.language === "en" && !useAmPm(locale) - ? "en-u-hc-h23" - : locale.language, - { - month: "short", - day: "numeric", - hour: useAmPm(locale) ? "numeric" : "2-digit", - minute: "2-digit", - hour12: useAmPm(locale), - timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, - } - ) -); - -// August 9, 2021, 8:23:15 AM -export const formatDateTimeWithSeconds = ( - dateObj: Date, - locale: FrontendLocaleData, - config: HassConfig -) => formatDateTimeWithSecondsMem(locale, config.time_zone).format(dateObj); - -const formatDateTimeWithSecondsMem = memoizeOne( - (locale: FrontendLocaleData, serverTimeZone: string) => - new Intl.DateTimeFormat( - locale.language === "en" && !useAmPm(locale) - ? "en-u-hc-h23" - : locale.language, - { - year: "numeric", - month: "long", - day: "numeric", - hour: useAmPm(locale) ? "numeric" : "2-digit", - minute: "2-digit", - second: "2-digit", - hour12: useAmPm(locale), - timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, - } - ) -); - -// 9/8/2021, 8:23 AM -export const formatDateTimeNumeric = ( - dateObj: Date, - locale: FrontendLocaleData, - config: HassConfig -) => - `${formatDateNumeric(dateObj, locale, config)}, ${formatTime(dateObj, locale, config)}`; diff --git a/src/ha/common/datetime/format_time.ts b/src/ha/common/datetime/format_time.ts deleted file mode 100644 index 3bda190f5..000000000 --- a/src/ha/common/datetime/format_time.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { HassConfig } from "home-assistant-js-websocket"; -import memoizeOne from "memoize-one"; -import { FrontendLocaleData } from "../../data/translation"; -import { useAmPm } from "./use_am_pm"; - -// 9:15 PM || 21:15 -export const formatTime = ( - dateObj: Date, - locale: FrontendLocaleData, - config: HassConfig -) => formatTimeMem(locale, config.time_zone).format(dateObj); - -const formatTimeMem = memoizeOne( - (locale: FrontendLocaleData, serverTimeZone: string) => - new Intl.DateTimeFormat( - locale.language === "en" && !useAmPm(locale) - ? "en-u-hc-h23" - : locale.language, - { - hour: "numeric", - minute: "2-digit", - hour12: useAmPm(locale), - timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, - } - ) -); - -// 9:15:24 PM || 21:15:24 -export const formatTimeWithSeconds = ( - dateObj: Date, - locale: FrontendLocaleData, - config: HassConfig -) => formatTimeWithSecondsMem(locale, config.time_zone).format(dateObj); - -const formatTimeWithSecondsMem = memoizeOne( - (locale: FrontendLocaleData, serverTimeZone: string) => - new Intl.DateTimeFormat( - locale.language === "en" && !useAmPm(locale) - ? "en-u-hc-h23" - : locale.language, - { - hour: useAmPm(locale) ? "numeric" : "2-digit", - minute: "2-digit", - second: "2-digit", - hour12: useAmPm(locale), - timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, - } - ) -); - -// Tuesday 7:00 PM || Tuesday 19:00 -export const formatTimeWeekday = ( - dateObj: Date, - locale: FrontendLocaleData, - config: HassConfig -) => formatTimeWeekdayMem(locale, config.time_zone).format(dateObj); - -const formatTimeWeekdayMem = memoizeOne( - (locale: FrontendLocaleData, serverTimeZone: string) => - new Intl.DateTimeFormat( - locale.language === "en" && !useAmPm(locale) - ? "en-u-hc-h23" - : locale.language, - { - weekday: "long", - hour: useAmPm(locale) ? "numeric" : "2-digit", - minute: "2-digit", - hour12: useAmPm(locale), - timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, - } - ) -); - -// 21:15 -export const formatTime24h = ( - dateObj: Date, - locale: FrontendLocaleData, - config: HassConfig -) => formatTime24hMem(locale, config.time_zone).format(dateObj); - -const formatTime24hMem = memoizeOne( - (locale: FrontendLocaleData, serverTimeZone: string) => - // en-GB to fix Chrome 24:59 to 0:59 https://stackoverflow.com/a/60898146 - new Intl.DateTimeFormat("en-GB", { - hour: "numeric", - minute: "2-digit", - hour12: false, - timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, - }) -); diff --git a/src/ha/common/datetime/milliseconds_to_duration.ts b/src/ha/common/datetime/milliseconds_to_duration.ts deleted file mode 100644 index 384a8a770..000000000 --- a/src/ha/common/datetime/milliseconds_to_duration.ts +++ /dev/null @@ -1,25 +0,0 @@ -const leftPad = (num: number, digits = 2) => { - let paddedNum = "" + num; - for (let i = 1; i < digits; i++) { - paddedNum = parseInt(paddedNum) < 10 ** i ? `0${paddedNum}` : paddedNum; - } - return paddedNum; -}; - -export default function millisecondsToDuration(d: number) { - const h = Math.floor(d / 1000 / 3600); - const m = Math.floor(((d / 1000) % 3600) / 60); - const s = Math.floor(((d / 1000) % 3600) % 60); - const ms = Math.floor(d % 1000); - - if (h > 0) { - return `${h}:${leftPad(m)}:${leftPad(s)}`; - } - if (m > 0) { - return `${m}:${leftPad(s)}`; - } - if (s > 0 || ms > 0) { - return `${s}${ms > 0 ? `.${leftPad(ms, 3)}` : ``}`; - } - return null; -} diff --git a/src/ha/common/datetime/use_am_pm.ts b/src/ha/common/datetime/use_am_pm.ts deleted file mode 100644 index 97bf2911b..000000000 --- a/src/ha/common/datetime/use_am_pm.ts +++ /dev/null @@ -1,16 +0,0 @@ -import memoizeOne from "memoize-one"; -import { FrontendLocaleData, TimeFormat } from "../../data/translation"; - -export const useAmPm = memoizeOne((locale: FrontendLocaleData): boolean => { - if ( - locale.time_format === TimeFormat.language || - locale.time_format === TimeFormat.system - ) { - const testLanguage = - locale.time_format === TimeFormat.language ? locale.language : undefined; - const test = new Date().toLocaleString(testLanguage); - return test.includes("AM") || test.includes("PM"); - } - - return locale.time_format === TimeFormat.am_pm; -}); diff --git a/src/ha/common/entity/compute_state_display.ts b/src/ha/common/entity/compute_state_display.ts index 26bc0e830..e69de29bb 100644 --- a/src/ha/common/entity/compute_state_display.ts +++ b/src/ha/common/entity/compute_state_display.ts @@ -1,235 +0,0 @@ -import { HassConfig, HassEntity } from "home-assistant-js-websocket"; -import { UNAVAILABLE, UNKNOWN } from "../../data/entity"; -import { - updateIsInstallingFromAttributes, - UPDATE_SUPPORT_PROGRESS, -} from "../../data/update"; -import { EntityRegistryDisplayEntry, HomeAssistant } from "../../types"; -import { FrontendLocaleData, TimeZone } from "../../data/translation"; -import { - UNIT_TO_MILLISECOND_CONVERT, - formatDuration, -} from "../datetime/duration"; -import { formatDate } from "../datetime/format_date"; -import { formatDateTime } from "../datetime/format_date_time"; -import { formatTime } from "../datetime/format_time"; -import { - formatNumber, - getNumberFormatOptions, - isNumericFromAttributes, -} from "../number/format_number"; -import { blankBeforePercent } from "../translations/blank_before_percent"; -import { LocalizeFunc } from "../translations/localize"; -import { computeDomain } from "./compute_domain"; -import { supportsFeatureFromAttributes } from "./supports-feature"; - -export const computeStateDisplaySingleEntity = ( - localize: LocalizeFunc, - stateObj: HassEntity, - locale: FrontendLocaleData, - config: HassConfig, - entity: EntityRegistryDisplayEntry | undefined, - state?: string -): string => - computeStateDisplayFromEntityAttributes( - localize, - locale, - config, - entity, - stateObj.entity_id, - stateObj.attributes, - state !== undefined ? state : stateObj.state - ); - -export const computeStateDisplay = ( - localize: LocalizeFunc, - stateObj: HassEntity, - locale: FrontendLocaleData, - config: HassConfig, - entities: HomeAssistant["entities"], - state?: string -): string => { - const entity = entities[stateObj.entity_id] as - | EntityRegistryDisplayEntry - | undefined; - - return computeStateDisplayFromEntityAttributes( - localize, - locale, - config, - entity, - stateObj.entity_id, - stateObj.attributes, - state !== undefined ? state : stateObj.state - ); -}; - -export const computeStateDisplayFromEntityAttributes = ( - localize: LocalizeFunc, - locale: FrontendLocaleData, - config: HassConfig, - entity: EntityRegistryDisplayEntry | undefined, - entityId: string, - attributes: any, - state: string -): string => { - if (state === UNKNOWN || state === UNAVAILABLE) { - return localize(`state.default.${state}`); - } - - // Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber` - if (isNumericFromAttributes(attributes)) { - // state is duration - if ( - attributes.device_class === "duration" && - attributes.unit_of_measurement && - UNIT_TO_MILLISECOND_CONVERT[attributes.unit_of_measurement] - ) { - try { - return formatDuration(state, attributes.unit_of_measurement); - } catch (_err) { - // fallback to default - } - } - if (attributes.device_class === "monetary") { - try { - return formatNumber(state, locale, { - style: "currency", - currency: attributes.unit_of_measurement, - minimumFractionDigits: 2, - // Override monetary options with number format - ...getNumberFormatOptions( - { state, attributes } as HassEntity, - entity - ), - }); - } catch (_err) { - // fallback to default - } - } - const unit = !attributes.unit_of_measurement - ? "" - : attributes.unit_of_measurement === "%" - ? blankBeforePercent(locale) + "%" - : ` ${attributes.unit_of_measurement}`; - return `${formatNumber( - state, - locale, - getNumberFormatOptions({ state, attributes } as HassEntity, entity) - )}${unit}`; - } - - const domain = computeDomain(entityId); - - if (domain === "datetime") { - const time = new Date(state); - return formatDateTime(time, locale, config); - } - - if (["date", "input_datetime", "time"].includes(domain)) { - // If trying to display an explicit state, need to parse the explicit state to `Date` then format. - // Attributes aren't available, we have to use `state`. - - // These are timezone agnostic, so we should NOT use the system timezone here. - try { - const components = state.split(" "); - if (components.length === 2) { - // Date and time. - return formatDateTime( - new Date(components.join("T")), - { ...locale, time_zone: TimeZone.local }, - config - ); - } - if (components.length === 1) { - if (state.includes("-")) { - // Date only. - return formatDate( - new Date(`${state}T00:00`), - { ...locale, time_zone: TimeZone.local }, - config - ); - } - if (state.includes(":")) { - // Time only. - const now = new Date(); - return formatTime( - new Date(`${now.toISOString().split("T")[0]}T${state}`), - { ...locale, time_zone: TimeZone.local }, - config - ); - } - } - return state; - } catch (_e) { - // Formatting methods may throw error if date parsing doesn't go well, - // just return the state string in that case. - return state; - } - } - - // `counter` `number` and `input_number` domains do not have a unit of measurement but should still use `formatNumber` - if ( - domain === "counter" || - domain === "number" || - domain === "input_number" - ) { - // Format as an integer if the value and step are integers - return formatNumber( - state, - locale, - getNumberFormatOptions({ state, attributes } as HassEntity, entity) - ); - } - - // state is a timestamp - if ( - ["button", "event", "input_button", "scene", "stt", "tts"].includes( - domain - ) || - (domain === "sensor" && attributes.device_class === "timestamp") - ) { - try { - return formatDateTime(new Date(state), locale, config); - } catch (_err) { - return state; - } - } - - if (domain === "update") { - // When updating, and entity does not support % show "Installing" - // When updating, and entity does support % show "Installing (xx%)" - // When update available, show the version - // When the latest version is skipped, show the latest version - // When update is not available, show "Up-to-date" - // When update is not available and there is no latest_version show "Unavailable" - return state === "on" - ? updateIsInstallingFromAttributes(attributes) - ? supportsFeatureFromAttributes(attributes, UPDATE_SUPPORT_PROGRESS) && - typeof attributes.in_progress === "number" - ? localize("ui.card.update.installing_with_progress", { - progress: attributes.in_progress, - }) - : localize("ui.card.update.installing") - : attributes.latest_version - : attributes.skipped_version === attributes.latest_version - ? attributes.latest_version ?? localize("state.default.unavailable") - : localize("ui.card.update.up_to_date"); - } - - return ( - (entity?.translation_key && - localize( - `component.${entity.platform}.entity.${domain}.${entity.translation_key}.state.${state}` - )) || - // Return device class translation - (attributes.device_class && - localize( - `component.${domain}.entity_component.${attributes.device_class}.state.${state}` - )) || - // Return default translation - localize(`component.${domain}.entity_component._.state.${state}`) || - // We don't know! Return the raw state. - state - ); -}; diff --git a/src/ha/common/translations/blank_before_percent.ts b/src/ha/common/translations/blank_before_percent.ts deleted file mode 100644 index 4c489c96c..000000000 --- a/src/ha/common/translations/blank_before_percent.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { FrontendLocaleData } from "../../data/translation"; - -// Logic based on https://en.wikipedia.org/wiki/Percent_sign#Form_and_spacing -export const blankBeforePercent = ( - localeOptions: FrontendLocaleData -): string => { - switch (localeOptions.language) { - case "cz": - case "de": - case "fi": - case "fr": - case "sk": - case "sv": - return " "; - default: - return ""; - } -}; diff --git a/src/ha/index.ts b/src/ha/index.ts index 93065225d..b0ba350f6 100644 --- a/src/ha/index.ts +++ b/src/ha/index.ts @@ -1,19 +1,13 @@ export * from "./common/const"; -export * from "./common/datetime/format_date"; -export * from "./common/datetime/format_date_time"; -export * from "./common/datetime/format_time"; -export * from "./common/datetime/use_am_pm"; export * from "./common/dom/fire_event"; export * from "./common/dom/get_main_window"; export * from "./common/entity/compute_domain"; -export * from "./common/entity/compute_state_display"; export * from "./common/entity/compute_state_domain"; export * from "./common/entity/supports-feature"; export * from "./common/number/clamp"; export * from "./common/number/format_number"; export * from "./common/number/round"; export * from "./common/structs/handle-errors"; -export * from "./common/translations/blank_before_percent"; export * from "./common/translations/localize"; export * from "./common/util/compute_rtl"; export * from "./common/util/debounce"; diff --git a/src/ha/types.ts b/src/ha/types.ts index 1b8282f24..5511dcc0f 100644 --- a/src/ha/types.ts +++ b/src/ha/types.ts @@ -224,7 +224,7 @@ export interface HomeAssistant { formatEntityAttributeValue( stateObj: HassEntity, attribute: string, - value?: string + value?: any ): string; formatEntityAttributeName(stateObj: HassEntity, attribute: string): string; } diff --git a/src/shared/input-number.ts b/src/shared/input-number.ts index baa7eca12..8bfd9bd6c 100644 --- a/src/shared/input-number.ts +++ b/src/shared/input-number.ts @@ -45,7 +45,7 @@ export class InputNumber extends LitElement { @property({ type: Number }) public max?: number; - @property({ attribute: "false" }) + @property({ attribute: false }) public formatOptions: Intl.NumberFormatOptions = {}; @state() pending = false; diff --git a/src/utils/base-card.ts b/src/utils/base-card.ts index ed5e93b8d..f86e2b3ce 100644 --- a/src/utils/base-card.ts +++ b/src/utils/base-card.ts @@ -4,7 +4,6 @@ import { property, state } from "lit/decorators.js"; import { classMap } from "lit/directives/class-map.js"; import { computeRTL, - computeStateDisplay, HomeAssistant, isActive, isAvailable, @@ -293,15 +292,8 @@ export class MushroomBaseCard< name: string, state?: string ): TemplateResult | null { - const defaultState = this.hass.formatEntityState - ? this.hass.formatEntityState(stateObj) - : computeStateDisplay( - this.hass.localize, - stateObj, - this.hass.locale, - this.hass.config, - this.hass.entities - ); + const defaultState = this.hass.formatEntityState(stateObj); + const displayState = state ?? defaultState; const primary = computeInfoDisplay(