diff --git a/public/static/images/weather/night.png b/public/static/images/weather/night.png index d7e79d2ac9..d7661fab3f 100644 Binary files a/public/static/images/weather/night.png and b/public/static/images/weather/night.png differ diff --git a/public/static/images/weather/partly-cloudy.png b/public/static/images/weather/partly-cloudy.png index a39a400ba1..6c59f6cc93 100644 Binary files a/public/static/images/weather/partly-cloudy.png and b/public/static/images/weather/partly-cloudy.png differ diff --git a/public/static/images/weather/sunny.png b/public/static/images/weather/sunny.png index 6c67835dce..671611b73b 100644 Binary files a/public/static/images/weather/sunny.png and b/public/static/images/weather/sunny.png differ diff --git a/src/data/weather.ts b/src/data/weather.ts index 3c7b2d351a..45b6423e37 100644 --- a/src/data/weather.ts +++ b/src/data/weather.ts @@ -1,24 +1,24 @@ -import { HomeAssistant } from "../types"; +import { HomeAssistant, WeatherEntity } from "../types"; export const weatherImages = { "clear-night": "/static/images/weather/night.png", cloudy: "/static/images/weather/cloudy.png", - fog: "/static/images/weather/cloudy.png", - hail: "/static/images/weather/rainy.png", lightning: "/static/images/weather/lightning.png", "lightning-rainy": "/static/images/weather/lightning-rainy.png", partlycloudy: "/static/images/weather/partly-cloudy.png", pouring: "/static/images/weather/pouring.png", rainy: "/static/images/weather/rainy.png", snowy: "/static/images/weather/snowy.png", - "snowy-rainy": "/static/images/weather/rainy.png", sunny: "/static/images/weather/sunny.png", windy: "/static/images/weather/windy.png", - "windy-variant": "/static/images/weather/windy.png", }; export const weatherIcons = { exceptional: "hass:alert-circle-outline", + fog: "hass:weather-fog", + hail: "hass:weather-hail", + "snowy-rainy": "hass:weather-snowy-rainy", + "windy-variant": "hass:weather-windy-variant", }; export const cardinalDirections = [ @@ -78,3 +78,89 @@ export const getWeatherUnit = ( return hass.config.unit_system[measure] || ""; } }; + +export const getSecondaryWeatherAttribute = ( + hass: HomeAssistant, + stateObj: WeatherEntity +): string | undefined => { + const extrema = getWeatherExtrema(hass, stateObj); + + if (extrema) { + return extrema; + } + + let value: number; + let attribute: string; + + if ( + stateObj.attributes.forecast?.length && + stateObj.attributes.forecast[0].precipitation !== undefined && + stateObj.attributes.forecast[0].precipitation !== null + ) { + value = stateObj.attributes.forecast[0].precipitation!; + attribute = "precipitation"; + } else if ("humidity" in stateObj.attributes) { + value = stateObj.attributes.humidity!; + attribute = "humidity"; + } else { + return undefined; + } + + return ` + ${hass!.localize( + `ui.card.weather.attributes.${attribute}` + )} ${value} ${getWeatherUnit(hass!, attribute)} + `; +}; + +const getWeatherExtrema = ( + hass: HomeAssistant, + stateObj: WeatherEntity +): string | undefined => { + if (!stateObj.attributes.forecast?.length) { + return undefined; + } + + let tempLow: number | undefined; + let tempHigh: number | undefined; + const today = new Date().getDate(); + + for (const forecast of stateObj.attributes.forecast!) { + if (new Date(forecast.datetime).getDate() !== today) { + break; + } + if (!tempHigh || forecast.temperature > tempHigh) { + tempHigh = forecast.temperature; + } + if (!tempLow || (forecast.templow && forecast.templow < tempLow)) { + tempLow = forecast.templow; + } + if (!forecast.templow && (!tempLow || forecast.temperature < tempLow)) { + tempLow = forecast.temperature; + } + } + + if (!tempLow && !tempHigh) { + return undefined; + } + + const unit = getWeatherUnit(hass!, "temperature"); + + return ` + ${ + tempHigh + ? ` + ${hass!.localize(`ui.card.weather.high`)} ${tempHigh} ${unit} + ` + : "" + } + ${tempLow && tempHigh ? " / " : ""} + ${ + tempLow + ? ` + ${hass!.localize(`ui.card.weather.low`)} ${tempLow} ${unit} + ` + : "" + } + `; +}; diff --git a/src/panels/lovelace/cards/hui-weather-forecast-card.ts b/src/panels/lovelace/cards/hui-weather-forecast-card.ts index 81e269089a..023633dd17 100644 --- a/src/panels/lovelace/cards/hui-weather-forecast-card.ts +++ b/src/panels/lovelace/cards/hui-weather-forecast-card.ts @@ -8,58 +8,32 @@ import { PropertyValues, TemplateResult, } from "lit-element"; -import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; -import { fireEvent } from "../../../common/dom/fire_event"; -import { toggleAttribute } from "../../../common/dom/toggle_attribute"; -import { computeStateName } from "../../../common/entity/compute_state_name"; -import { isValidEntityId } from "../../../common/entity/valid_entity_id"; -import { computeRTL } from "../../../common/util/compute_rtl"; + +import "../../../components/ha-icon"; import "../../../components/ha-card"; -import { HomeAssistant } from "../../../types"; -import { actionHandler } from "../common/directives/action-handler-directive"; +import "../components/hui-warning"; + +import { WeatherForecastCardConfig } from "./types"; +import { LovelaceCard, LovelaceCardEditor } from "../types"; +import { HomeAssistant, WeatherEntity } from "../../../types"; import { findEntities } from "../common/find-entites"; import { hasConfigOrEntityChanged } from "../common/has-changed"; -import "../components/hui-warning"; -import { LovelaceCard, LovelaceCardEditor } from "../types"; -import { WeatherForecastCardConfig } from "./types"; +import { actionHandler } from "../common/directives/action-handler-directive"; +import { isValidEntityId } from "../../../common/entity/valid_entity_id"; +import { computeStateName } from "../../../common/entity/compute_state_name"; +import { fireEvent } from "../../../common/dom/fire_event"; +import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; +import { debounce } from "../../../common/util/debounce"; +import { UNAVAILABLE } from "../../../data/entity"; +import { + weatherIcons, + getSecondaryWeatherAttribute, + getWeatherUnit, + weatherImages, +} from "../../../data/weather"; +import { stateIcon } from "../../../common/entity/state_icon"; -const cardinalDirections = [ - "N", - "NNE", - "NE", - "ENE", - "E", - "ESE", - "SE", - "SSE", - "S", - "SSW", - "SW", - "WSW", - "W", - "WNW", - "NW", - "NNW", - "N", -]; - -const weatherIcons = { - "clear-night": "hass:weather-night", - cloudy: "hass:weather-cloudy", - exceptional: "hass:alert-circle-outline", - fog: "hass:weather-fog", - hail: "hass:weather-hail", - lightning: "hass:weather-lightning", - "lightning-rainy": "hass:weather-lightning-rainy", - partlycloudy: "hass:weather-partly-cloudy", - pouring: "hass:weather-pouring", - rainy: "hass:weather-rainy", - snowy: "hass:weather-snowy", - "snowy-rainy": "hass:weather-snowy-rainy", - sunny: "hass:weather-sunny", - windy: "hass:weather-windy", - "windy-variant": "hass:weather-windy-variant", -}; +const DAY_IN_MILLISECONDS = 86400000; @customElement("hui-weather-forecast-card") class HuiWeatherForecastCard extends LitElement implements LovelaceCard { @@ -92,8 +66,18 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard { @property() private _config?: WeatherForecastCardConfig; + @property({ type: Boolean, reflect: true, attribute: "narrow" }) + private _narrow = false; + + private _resizeObserver?: ResizeObserver; + + public connectedCallback(): void { + super.connectedCallback(); + this.updateComplete.then(() => this._measureCard()); + } + public getCardSize(): number { - return 4; + return this._config?.show_forecast !== false ? 4 : 2; } public setConfig(config: WeatherForecastCardConfig): void { @@ -112,6 +96,7 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard { if (!this._config || !this.hass) { return; } + const oldHass = changedProps.get("hass") as HomeAssistant | undefined; const oldConfig = changedProps.get("_config") as | WeatherForecastCardConfig @@ -125,10 +110,6 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard { ) { applyThemesOnElement(this, this.hass.themes, this._config.theme); } - - if (changedProps.has("hass")) { - toggleAttribute(this, "rtl", computeRTL(this.hass!)); - } } protected render(): TemplateResult { @@ -136,7 +117,7 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard { return html``; } - const stateObj = this.hass.states[this._config.entity]; + const stateObj = this.hass.states[this._config.entity] as WeatherEntity; if (!stateObj) { return html` @@ -150,9 +131,33 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard { `; } - const forecast = stateObj.attributes.forecast - ? stateObj.attributes.forecast.slice(0, 5) - : undefined; + if (stateObj.state === UNAVAILABLE) { + return html` + + ${this.hass.localize( + "ui.panel.lovelace.warning.entity_unavailable", + "entity", + `${computeStateName(stateObj)} (${this._config.entity})` + )} + + `; + } + + const forecast = + this._config?.show_forecast !== false && + stateObj.attributes.forecast?.length + ? stateObj.attributes.forecast.slice(0, this._narrow ? 3 : 5) + : undefined; + + let hourly: boolean | undefined; + + if (forecast?.length && forecast?.length > 1) { + const date1 = new Date(forecast[0].datetime); + const date2 = new Date(forecast[1].datetime); + const timeDiff = date2.getTime() - date1.getTime(); + + hourly = timeDiff < DAY_IN_MILLISECONDS; + } return html` -
- ${this.hass.localize(`state.weather.${stateObj.state}`) || - stateObj.state} -
- ${(this._config && this._config.name) || computeStateName(stateObj)} -
-
-
-
- ${stateObj.state in weatherIcons - ? html` - - ` - : ""} -
- ${stateObj.attributes.temperature}${this.getUnit("temperature")} +
+ ${stateObj.state in weatherImages + ? html` + + ` + : html` + + `} +
+
+ ${this._config.name || computeStateName(stateObj)} +
+
+ ${this.hass.localize(`state.weather.${stateObj.state}`) || + stateObj.state}
-
- ${this._showValue(stateObj.attributes.pressure) - ? html` -
- ${this.hass.localize( - "ui.card.weather.attributes.air_pressure" - )}: - - ${stateObj.attributes.pressure} - ${this.getUnit("air_pressure")} - -
- ` - : ""} - ${this._showValue(stateObj.attributes.humidity) - ? html` -
- ${this.hass.localize( - "ui.card.weather.attributes.humidity" - )}: - ${stateObj.attributes.humidity} % -
- ` - : ""} - ${this._showValue(stateObj.attributes.wind_speed) - ? html` -
- ${this.hass.localize( - "ui.card.weather.attributes.wind_speed" - )}: - - ${stateObj.attributes.wind_speed} - ${this.getUnit("length")}/h - - ${this.getWindBearing(stateObj.attributes.wind_bearing)} -
- ` - : ""} +
+
+
+ ${stateObj.attributes.temperature}${getWeatherUnit(this.hass, "temperature")} +
+
+ ${getSecondaryWeatherAttribute(this.hass, stateObj)}
- ${forecast - ? html` -
- ${forecast.map( - (item) => html` -
-
- ${new Date(item.datetime).toLocaleDateString( - this.hass!.language, - { weekday: "short" } - )}
- ${!this._showValue(item.templow) - ? html` - ${new Date(item.datetime).toLocaleTimeString( - this.hass!.language, - { - hour: "numeric", - } - )} - ` - : ""} -
- ${this._showValue(item.condition) - ? html` -
- -
- ` - : ""} - ${this._showValue(item.temperature) - ? html` -
- ${item.temperature} - ${this.getUnit("temperature")} -
- ` - : ""} - ${this._showValue(item.templow) - ? html` -
- ${item.templow} ${this.getUnit("temperature")} -
- ` - : ""} - ${this._showValue(item.precipitation) - ? html` -
- ${item.precipitation} - ${this.getUnit("precipitation")} -
- ` - : ""} -
- ` - )} -
- ` - : ""}
+ ${forecast + ? html` +
+ ${forecast.map( + (item) => html` +
+
+ ${hourly + ? html` + ${new Date(item.datetime).toLocaleTimeString( + this.hass!.language, + { + hour: "numeric", + } + )} + ` + : html` + ${new Date(item.datetime).toLocaleDateString( + this.hass!.language, + { weekday: "short" } + )} + `} +
+ ${item.condition !== undefined && item.condition !== null + ? html` +
+ ${item.condition in weatherImages + ? html` + + ` + : item.condition in weatherIcons + ? html` + + ` + : ""} +
+ ` + : ""} + ${item.temperature !== undefined && + item.temperature !== null + ? html` +
+ ${item.temperature}° +
+ ` + : ""} + ${item.templow !== undefined && item.templow !== null + ? html` +
+ ${item.templow}° +
+ ` + : ""} +
+ ` + )} +
+ ` + : ""} `; } @@ -292,177 +274,239 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard { return hasConfigOrEntityChanged(this, changedProps); } + protected firstUpdated(): void { + this._attachObserver(); + } + private _handleAction(): void { fireEvent(this, "hass-more-info", { entityId: this._config!.entity }); } - private getUnit(measure: string): string { - const lengthUnit = this.hass!.config.unit_system.length || ""; - switch (measure) { - case "air_pressure": - return lengthUnit === "km" ? "hPa" : "inHg"; - case "length": - return lengthUnit; - case "precipitation": - return lengthUnit === "km" ? "mm" : "in"; - default: - return this.hass!.config.unit_system[measure] || ""; + private _attachObserver(): void { + if (typeof ResizeObserver !== "function") { + import("resize-observer").then((modules) => { + modules.install(); + this._attachObserver(); + }); + return; } + + this._resizeObserver = new ResizeObserver( + debounce(() => this._measureCard(), 250, false) + ); + + const card = this.shadowRoot!.querySelector("ha-card"); + // If we show an error or warning there is no ha-card + if (!card) { + return; + } + this._resizeObserver.observe(card); } - private windBearingToText(degree: string): string { - const degreenum = parseInt(degree, 10); - if (isFinite(degreenum)) { - // eslint-disable-next-line no-bitwise - return cardinalDirections[(((degreenum + 11.25) / 22.5) | 0) % 16]; + private _measureCard() { + this._narrow = this.offsetWidth < 375; + if (this.offsetWidth < 300) { + this.setAttribute("verynarrow", ""); + } else { + this.removeAttribute("verynarrow"); } - return degree; - } - - private getWindBearing(bearing: string): string { - if (bearing != null) { - const cardinalDirection = this.windBearingToText(bearing); - return `(${ - this.hass!.localize( - `ui.card.weather.cardinal_direction.${cardinalDirection.toLowerCase()}` - ) || cardinalDirection - })`; + if (this.offsetWidth < 200) { + this.setAttribute("veryverynarrow", ""); + } else { + this.removeAttribute("veryverynarrow"); } - return ``; - } - - private _showValue(item: string): boolean { - return typeof item !== "undefined" && item !== null; } static get styles(): CSSResult { return css` :host { + display: block; + } + + ha-card { cursor: pointer; + padding: 16px; } .content { - padding: 0 20px 20px; - } - - ha-icon { - color: var(--paper-item-icon-color); - } - - .header { - font-family: var(--paper-font-headline_-_font-family); - -webkit-font-smoothing: var( - --paper-font-headline_-_-webkit-font-smoothing - ); - font-size: var(--paper-font-headline_-_font-size); - font-weight: var(--paper-font-headline_-_font-weight); - letter-spacing: var(--paper-font-headline_-_letter-spacing); - line-height: var(--paper-font-headline_-_line-height); - text-rendering: var( - --paper-font-common-expensive-kerning_-_text-rendering - ); - opacity: var(--dark-primary-opacity); - padding: 24px 16px 16px; display: flex; - align-items: baseline; + flex-wrap: nowrap; + justify-content: space-between; + align-items: center; } - .name { - margin-left: 16px; - font-size: 16px; - color: var(--secondary-text-color); + .icon-info { + display: flex; + align-items: center; + min-width: 0; + flex: 1; } - :host([rtl]) .name { - margin-left: 0px; + .weather-image, + .weather-icon { + flex: 0 0 66px; margin-right: 16px; } - .now { + .weather-icon { + --iron-icon-width: 66px; + --iron-icon-height: 66px; + } + + .info { + overflow: hidden; + } + + .name { + font-size: 16px; + color: var(--secondary-text-color); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .state { + font-size: 28px; + line-height: 1.2; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .temp-attribute { display: flex; - justify-content: space-between; - align-items: center; - flex-wrap: wrap; + flex-direction: column; + align-items: flex-end; } - .main { - display: flex; - align-items: center; - margin-right: 32px; - } - - :host([rtl]) .main { - margin-right: 0px; - } - - .main ha-icon { - --iron-icon-height: 72px; - --iron-icon-width: 72px; - margin-right: 8px; - } - - :host([rtl]) .main ha-icon { - margin-right: 0px; - } - - .main .temp { - font-size: 52px; - line-height: 1em; + .temp-attribute .temp { position: relative; + font-size: 38px; + line-height: 1; + margin-right: 24px; } - :host([rtl]) .main .temp { - direction: ltr; - margin-right: 28px; - } - - .main .temp span { - font-size: 24px; - line-height: 1em; + .temp-attribute .temp span { position: absolute; + font-size: 24px; top: 4px; } - .measurand { - display: inline-block; - } - - :host([rtl]) .measurand { - direction: ltr; - } - .forecast { - margin-top: 16px; display: flex; justify-content: space-between; + padding-top: 16px; } - .forecast div { - flex: 0 0 auto; + .forecast > div { text-align: center; } - .forecast .icon { + .forecast .icon, + .forecast .temp { margin: 4px 0; - text-align: center; } - :host([rtl]) .forecast .temp { - direction: ltr; + .forecast .temp { + font-size: 16px; } - .weekday { - font-weight: bold; + .forecast-image-icon { + padding-top: 4px; + padding-bottom: 4px; } - .attributes, - .templow, - .precipitation { + .forecast-image { + width: 40px; + } + + .forecast-icon { + --iron-icon-width: 40px; + --iron-icon-height: 40px; + } + + .attribute { + line-height: 1; + } + + .attribute, + .templow { color: var(--secondary-text-color); } - :host([rtl]) .precipitation { - direction: ltr; + :host([narrow]) .weather-image { + flex: 0 0 58px; + } + + :host([narrow]) .weather-icon { + --iron-icon-width: 58px; + --iron-icon-height: 58px; + } + + :host([narrow]) .state { + font-size: 22px; + } + + :host([narrow]) .temp-attribute .temp { + font-size: 44px; + margin-right: 18px; + } + + :host([narrow]) .temp-attribute .temp span { + font-size: 18px; + top: 3px; + } + + :host([narrow]) .attribute { + display: none; + } + + :host([narrow]) .forecast { + justify-content: space-around; + } + + :host([veryVeryNarrow]) .content { + flex-wrap: wrap; + justify-content: center; + } + + :host([veryNarrow]) .icon-info { + flex: initial; + } + + :host([narrow]) .weather-image { + flex: 0 0 48px; + } + + :host([narrow]) .weather-icon { + --iron-icon-width: 48px; + --iron-icon-height: 48px; + } + + :host([veryNarrow]) .info { + display: none; + } + + :host([veryNarrow]) .temp-attribute .temp { + font-size: 36px; + } + + :host([veryNarrow]) .temp-attribute .temp span { + top: 2px; + } + + :host([veryVeryNarrow]) .temp-attribute { + padding-top: 4px; + } + + .unavailable { + height: 100px; + display: flex; + justify-content: center; + align-items: center; + font-size: 16px; + padding: 10px 20px; + text-align: center; } `; } diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index 233cc65e3a..5dd1c46ac1 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -274,4 +274,5 @@ export interface ThermostatCardConfig extends LovelaceCardConfig { export interface WeatherForecastCardConfig extends LovelaceCardConfig { entity: string; name?: string; + show_forecast?: boolean; } diff --git a/src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts index 2bf907a919..f8d8415bbe 100644 --- a/src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts @@ -7,12 +7,14 @@ import { } from "lit-element"; import { fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/entity/ha-entity-picker"; +import "../../../../components/ha-switch"; + +import { EntitiesEditorEvent, EditorTarget } from "../types"; import { HomeAssistant } from "../../../../types"; import { WeatherForecastCardConfig } from "../../cards/types"; import { struct } from "../../common/structs/struct"; import "../../components/hui-theme-select-editor"; import { LovelaceCardEditor } from "../../types"; -import { EditorTarget, EntitiesEditorEvent } from "../types"; import { configElementStyle } from "./config-elements-style"; const cardConfigStruct = struct({ @@ -20,6 +22,7 @@ const cardConfigStruct = struct({ entity: "string?", name: "string?", theme: "string?", + show_forecast: "boolean?", }); @customElement("hui-weather-forecast-card-editor") @@ -46,6 +49,10 @@ export class HuiWeatherForecastCardEditor extends LitElement return this._config!.theme || ""; } + get _show_forecast(): boolean { + return this._config!.show_forecast || true; + } + protected render(): TemplateResult { if (!this.hass || !this._config) { return html``; @@ -61,28 +68,36 @@ export class HuiWeatherForecastCardEditor extends LitElement "ui.panel.lovelace.editor.card.config.required" )})" .hass=${this.hass} - .value="${this._entity}" + .value=${this._entity} .configValue=${"entity"} - .include-domains=${["weather"]} - @change="${this._valueChanged}" + .includeDomains=${["weather"]} + @change=${this._valueChanged} allow-custom-entity > - - +
+ + +
+ Show forecast
`; } @@ -101,7 +116,8 @@ export class HuiWeatherForecastCardEditor extends LitElement } else { this._config = { ...this._config, - [target.configValue!]: target.value, + [target.configValue!]: + target.checked !== undefined ? target.checked : target.value, }; } } diff --git a/src/panels/lovelace/entity-rows/hui-weather-entity-row.ts b/src/panels/lovelace/entity-rows/hui-weather-entity-row.ts index 794630aa76..dc1e526322 100644 --- a/src/panels/lovelace/entity-rows/hui-weather-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-weather-entity-row.ts @@ -14,11 +14,11 @@ import { getWeatherUnit, weatherIcons, weatherImages, + getSecondaryWeatherAttribute, } from "../../../data/weather"; import { HomeAssistant, WeatherEntity } from "../../../types"; import { EntitiesCardEntityConfig } from "../cards/types"; import { hasConfigOrEntityChanged } from "../common/has-changed"; -import "../components/hui-warning"; import { LovelaceRow } from "./types"; @customElement("hui-weather-entity-row") @@ -77,95 +77,13 @@ class HuiWeatherEntityRow extends LitElement implements LovelaceRow { `}
- ${!UNAVAILABLE_STATES.includes(stateObj.state) - ? this._getSecondaryAttribute(stateObj) - : ""} + ${getSecondaryWeatherAttribute(this.hass!, stateObj)}
`; } - private _getSecondaryAttribute(stateObj: WeatherEntity): string | undefined { - const extrema = this._getExtrema(stateObj); - - if (extrema) { - return extrema; - } - - let value: number; - let attribute: string; - - if ( - stateObj.attributes.forecast?.length && - stateObj.attributes.forecast[0].precipitation !== undefined && - stateObj.attributes.forecast[0].precipitation !== null - ) { - value = stateObj.attributes.forecast[0].precipitation!; - attribute = "precipitation"; - } else if ("humidity" in stateObj.attributes) { - value = stateObj.attributes.humidity!; - attribute = "humidity"; - } else { - return undefined; - } - - return ` - ${this.hass!.localize( - `ui.card.weather.attributes.${attribute}` - )} ${value} ${getWeatherUnit(this.hass!, attribute)} - `; - } - - private _getExtrema(stateObj: WeatherEntity): string | undefined { - if (!stateObj.attributes.forecast?.length) { - return undefined; - } - - let tempLow: number | undefined; - let tempHigh: number | undefined; - const today = new Date().getDate(); - - for (const forecast of stateObj.attributes.forecast!) { - if (new Date(forecast.datetime).getDate() !== today) { - break; - } - if (!tempHigh || forecast.temperature > tempHigh) { - tempHigh = forecast.temperature; - } - if (!tempLow || (forecast.templow && forecast.templow < tempLow)) { - tempLow = forecast.templow; - } - if (!forecast.templow && (!tempLow || forecast.temperature < tempLow)) { - tempLow = forecast.temperature; - } - } - - if (!tempLow && !tempHigh) { - return undefined; - } - - const unit = getWeatherUnit(this.hass!, "temperature"); - - return ` - ${ - tempHigh - ? ` - ${this.hass!.localize(`ui.card.weather.high`)} ${tempHigh} ${unit} - ` - : "" - } - ${tempLow && tempHigh ? " / " : ""} - ${ - tempLow - ? ` - ${this.hass!.localize(`ui.card.weather.low`)} ${tempLow} ${unit} - ` - : "" - } - `; - } - static get styles(): CSSResult { return css` .attributes { diff --git a/src/translations/en.json b/src/translations/en.json index 26ddad1a5e..81f6e23278 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1907,7 +1907,8 @@ "warning": { "attribute_not_found": "Attribute {attribute} not available in: {entity}", "entity_not_found": "Entity not available: {entity}", - "entity_non_numeric": "Entity is non-numeric: {entity}" + "entity_non_numeric": "Entity is non-numeric: {entity}", + "entity_unavailable": "{entity} is currently unavailable" }, "changed_toast": { "message": "The Lovelace UI configuration for this dashboard was updated, refresh to see changes?", diff --git a/src/types.ts b/src/types.ts index 94672e5d4f..6a42dce227 100644 --- a/src/types.ts +++ b/src/types.ts @@ -259,6 +259,7 @@ interface ForecastAttribute { templow?: number; precipitation?: number; humidity?: number; + condition?: string; } export type WeatherEntity = HassEntityBase & {