From fdf7b516a0ae477355bc8a11bdb6e8452ec08378 Mon Sep 17 00:00:00 2001 From: Zack Arnett Date: Wed, 22 Apr 2020 06:13:32 -0400 Subject: [PATCH] Weather Card: Beautify (#5387) * Weather card * Updates * Remove Precipitation from forecast * Weather Card :) * Fix no breaking changes * Size styles * Space * Fix some overlap * Unavailable * New unavailable * Changed to check if less than day * Updates * oops * Little clean up * styling * Reviews * Fix merge * Lint * eslint * New images * Update src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts Co-Authored-By: Bram Kragten * Reviews * Reviews * Comments Co-authored-by: Bram Kragten --- public/static/images/weather/night.png | Bin 465 -> 648 bytes .../static/images/weather/partly-cloudy.png | Bin 1064 -> 1130 bytes public/static/images/weather/sunny.png | Bin 678 -> 492 bytes src/data/weather.ts | 96 ++- .../cards/hui-weather-forecast-card.ts | 628 ++++++++++-------- src/panels/lovelace/cards/types.ts | 1 + .../hui-weather-forecast-card-editor.ts | 58 +- .../entity-rows/hui-weather-entity-row.ts | 86 +-- src/translations/en.json | 3 +- src/types.ts | 1 + 10 files changed, 470 insertions(+), 403 deletions(-) diff --git a/public/static/images/weather/night.png b/public/static/images/weather/night.png index d7e79d2ac9a7426ee97ffcbb81e6d58ffe189306..d7661fab3f75aaa2835e75dcfc8ea2b527676040 100644 GIT binary patch delta 623 zcmcb}+`&3QrM@@7C&ZP3f#Jc||0nMLIdKPsj^F-$;`Vi>W=-vJr{)cN`zx@lBvmQ|i`ynhey*0?khn;t6ep1xK`*SM9Dm-R#wpD1j?PK_M zWYOXy_mtxg@8F4<@*}uo74y0yiDB$(B2Ar+DzZuYtNoGt82pFjSgXL}&M%e0Yj!Nj zX{%4~dtkaSY4R%GtfGVSnLclRw&+|)>@ptxw65tt4rzB^X{kv49JD6Ge#s$+=9v?j zHo7#Y8FR;OY&j&O5_Lx8+ru8SOI#TXI$c)ZdAFxM{YqWl#e;=^x82~ajrwn2aKZKR z^U1$9->kfK$JBq(@u2)`im;7VZG5*rT&+Xdc`{1YshXL1|zzqjAGQt|059wUuYRxLP;!xV| z>h-K@35TU~YPHURiD{y5!Wegj2CQLP6Xv*)YeN+4)F_pGfs*kZ&rdB0S$3{uwe32N zBO))u++q}^D@CR{>|wS%*}zy@CeOmGGBG*iRm|qH9?k7tai!AjQ|vr`KlpIT?e2!{ zZ>{HcG5FPg{^wZa^LO&wg*z|rQWcPywzly>ft|M8W54~s`#;wiN|>zw;q`2)+c|&F zCQTKd_1?!=ey)?xRF_}xva{FSzHqS&+l~k7`ne_InYvGT?2f+qx5;wZ>yv*OUMvf; U{nS%;9T>zopr0BO?MRsaA1 diff --git a/public/static/images/weather/partly-cloudy.png b/public/static/images/weather/partly-cloudy.png index a39a400ba1c6aa2898b237cac148797b9efc4330..6c59f6cc936b1d7b4ebb67e98e679c11e2ece5ef 100644 GIT binary patch literal 1130 zcmXw1X;4#F6b^*N27^F`F$FAvVX*{lKz|5?vL&)e;jvZ0s-SkPK!IVAH7rR8AW&%$ zMuP~&ZZcD9ktxe4Yq5m{Ww8(Ln4uYs$4GL z+}zyS+5#A4Pz6A#RH6bZf*@cE9zZxGYo|R9I39@56M)^7Cg7u1s{satL%CK6B%ZSW z1DU1*2)dv%Zs?>tdfuvzP_$dD8MIx7O+?hXg~|hW$@Sr zyefs?$zk5MxL>(cs-92Kh_1@u@tw8jszrusF;_Dig$RQXVIa+sv;T9U^ze`f%nQX1 zqE;z(-iwC(#m^e6vTcb3wM--!_?elU6k(*1yno$ZQ&F0KCjtugIB92n+|? z)ZHSOiFr!@X6nv3j?P#~ZA0W;{i9g~`<|bafnG)p$>cA(;^bF~L)95jbGT?Y=_amI z@NedAy0_npp%5MSRsRM$$J_^TE}OV}EzhSO{q)h84R1S_jpJ3Z^7-a7%S)jVE}eY^ zr-z}!J(gou-|`#B?@RAd`z*-k^arg<1lJ!A1(0D^ro++2>U1`F$h+@i&wOFF{zG;i zr=0IWbRPAJ?Y0`3tdF+fpW*ja1>Y*iPVQ2REGfEb*s9`hO_rQPi?L$kk96b}r|Q#oHF204UB zAAVQfbEf)c=ZUW(dq^C84Q-xuR%&7Agh{Ke=v^auMGp>Ea^K=l1(&$gy|L2mXCTCZ zbYd%ZspzwEJ_#mqV~?G?@6T<+wmz7XVD5VQ3FG?ts99E4#;FdhY?(0lQS9n$rC~aS zsx|u2X&z#v-;&4Yy*9I{3Hr5VQ4*e)MqY-TGej&xh;j-skbY~g!|VOW6kKymAU%ZE I;v38S4^xPX^8f$< delta 1043 zcmaFGv4UfQO1*M`Plzi65_tLYB@&MT$c2EvH*J985cbcXKjDlQFJ1t}zhAKi3jX-< zfA=dFJHc_ zU%&qGVbdp1o&YtRKYt#m`rEf}K(m0V-#$GI6o2^O&c{n0UoTm`KWlma{(Ydb-`8y} zZ#4oc{{R2~u3fv{ymW!qfKouafF{3v`}Y0& z_x10d?)&)hEaj?aro`Dn^m(M zB-%s+U1Fk|a+7;5&RF_#+LGV+?Z5G^>})J)i4WGCpS(w|e*OLb{K{eL zc1G3AeAj#Y@yXr2Sw1tr6kNKNT(<1+roP9W^A_5QvMg*#N?!7~r_Je!#-#k$DsS7F zf~K6F%+@HnTaz!Ue(f}+u)gvVtHtkyq8F@Ccy=>>?!j#;o+StW`c6puI5|VdTo}k&V44GX(9(#NAU`0 z7zGzNNPLK*Z)p&nCNI(cUXPr{fr|`+ZB$UX`SNFM*7bVAuNt9oDIrbEvpt|cE-oar7RbGZ41#BR^}%N{&`_|9LPtkEPo zFENF8PG}w1`=(hU22Ne!6~bIW_0Q7<+G-98Yn<>&5Mq;lw6pk`)a;e(T4`1oEu zV4Adh?lM!kp0N2-$NB!id3g$-?=I$Q+m`+O4ddy2ttFW)a_V*7Tfo(GiIJzf1=);T3K0RU>HGb;c9 diff --git a/public/static/images/weather/sunny.png b/public/static/images/weather/sunny.png index 6c67835dcede3fac5163abd0eaa24b5073e7226a..671611b73bbf096533db121736b2c08d044e2aa3 100644 GIT binary patch delta 466 zcmZ3+`i6OeN_|v-Plzi61H->tHvcc${k>`Z@21UPAp4fhUmydDK!Sg6+WZG`t^Z#) z1Ck)ATh_mCfK>j!ZT@J zbEECgkFO5win3}IUX+#QUTY^t2UitZ+#&){nmx5W{&t@CADoDHzQe?`Lp?TZR*z)S2QH5 zoZrmiyz|0ES)F62;|*jt?_vset+#O4#>#5us=rGnr_z1594FhYRF4T4-NZct-k2vj zO{(O+py0Wgb%RIJUZ(>oe3>d9OQk%s3K>4Cckl=Pkv`g~;+Og{RVXg~>$#>)Twb#l ztnfO$>r3K9yIj9NJLGgPJ)i4l&~wOJ?6}u#ox<7b*(UGrRo%0UNZf3Dsr9S+j=!zz zc7AR=94Pbs|B1&M_pD^v*Cnc|*}hM{zvkuRJNhT)@b&Pm_+AVQTLw>8KbLh*2~7ah CYu~5< delta 654 zcmaFEyo_~%N_~ESPlzi6Zt&-Z&5Psae{b3V8Ta>_{J3iUqz%%d;B{A09LXDgYv($i?fOH2yqBB_I$i3bhTDpVo`guli{(0MTLQm#!B)lt<4@3L_g7GU|>At z>Eaj?aro`^(`8Kt5-bTSLIO;I0(-KzR{l@Vkx*jf*ZBEzQTMqzJ9WXYFMLljeT)g7 z8?N@8tBJeV_Fbs{@57~5ljK^ZIxLn72zFF)et zdtzFzVCFhC)%y{Pu8U_S9Z}2S^pbbVVDY-owTAi0Da}d&Gvzmbt(O@zy$nBP`Q1TS z_rqJh6h@X?j1!oceq}l_PKwqIU{7#b!upB9r$g$&ijIkaEDiyUJwe-hg>+fz~zXBhuT&G=l% zRczhYpR;ULApz$DzVo#C(g zhXVFfatG${D_+cJ;Mf`c|8zdXJ6rXfmdmVbE^cRFIc~?skfGL6FEHnqn1Pes$3JuH zuCN|jFr(uCHu;L$hx2OZ#FzbM=sBRka`d--?8f}xUm3VF7##|j4TIwMe_`Cib { + 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 & {