Update more-info dialog layout for weather entities (#22818)

* Update weather more-info style

* Cleanup unused var

* Use badges for attributes

* Remove unnecessary flex class

* CSS cleanup

* Wrap badges

* Revert "Cleanup unused var"

This reverts commit 89ab0f6ad05e1e669b84e69f4c263e3d302794f2.

* Revert badges for attributes

* Scroll long forecasts

* Use nothing instead of empty strings

* Cleanup
This commit is contained in:
Charles Garwood 2025-02-06 01:11:30 -05:00 committed by GitHub
parent 35face602b
commit e9fef1f873
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 346 additions and 134 deletions

View File

@ -33,6 +33,7 @@ export const DOMAINS_WITH_NEW_MORE_INFO = [
"switch", "switch",
"valve", "valve",
"water_heater", "water_heater",
"weather",
]; ];
/** Domains with full height more info dialog */ /** Domains with full height more info dialog */
export const DOMAINS_FULL_HEIGHT_MORE_INFO = ["update"]; export const DOMAINS_FULL_HEIGHT_MORE_INFO = ["update"];

View File

@ -1,18 +1,13 @@
import "@material/mwc-tab"; import "@material/mwc-tab";
import "@material/mwc-tab-bar"; import "@material/mwc-tab-bar";
import { import { mdiEye, mdiGauge, mdiWaterPercent, mdiWeatherWindy } from "@mdi/js";
mdiEye, import type { CSSResultGroup, PropertyValues } from "lit";
mdiGauge,
mdiThermometer,
mdiWaterPercent,
mdiWeatherWindy,
} from "@mdi/js";
import type { PropertyValues } from "lit";
import { LitElement, css, html, nothing } from "lit"; import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { formatDateWeekdayDay } from "../../../common/datetime/format_date"; import { formatDateWeekdayShort } from "../../../common/datetime/format_date";
import { formatTimeWeekday } from "../../../common/datetime/format_time"; import { formatTime } from "../../../common/datetime/format_time";
import { formatNumber } from "../../../common/number/format_number";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
import type { import type {
ForecastEvent, ForecastEvent,
@ -23,11 +18,16 @@ import {
getDefaultForecastType, getDefaultForecastType,
getForecast, getForecast,
getSupportedForecastTypes, getSupportedForecastTypes,
getSecondaryWeatherAttribute,
getWeatherStateIcon,
getWeatherUnit,
getWind, getWind,
subscribeForecast, subscribeForecast,
weatherIcons, weatherSVGStyles,
} from "../../../data/weather"; } from "../../../data/weather";
import type { HomeAssistant } from "../../../types"; import type { HomeAssistant } from "../../../types";
import "../../../components/ha-relative-time";
import "../../../components/ha-state-icon";
@customElement("more-info-weather") @customElement("more-info-weather")
class MoreInfoWeather extends LitElement { class MoreInfoWeather extends LitElement {
@ -137,23 +137,90 @@ class MoreInfoWeather extends LitElement {
const hourly = forecastData?.type === "hourly"; const hourly = forecastData?.type === "hourly";
const dayNight = forecastData?.type === "twice_daily"; const dayNight = forecastData?.type === "twice_daily";
const weatherStateIcon = getWeatherStateIcon(this.stateObj.state, this);
return html` return html`
${this._showValue(this.stateObj.attributes.temperature) <div class="content">
? html` <div class="icon-image">
<div class="flex"> ${weatherStateIcon ||
<ha-svg-icon .path=${mdiThermometer}></ha-svg-icon> html`
<div class="main"> <ha-state-icon
${this.hass.localize("ui.card.weather.attributes.temperature")} class="weather-icon"
.stateObj=${this.stateObj}
.hass=${this.hass}
></ha-state-icon>
`}
</div> </div>
<div class="info">
<div class="name-state">
<div class="state">
${this.hass.formatEntityState(this.stateObj)}
</div>
<div class="time-ago">
<ha-relative-time
id="last_changed"
.hass=${this.hass}
.datetime=${this.stateObj.last_changed}
capitalize
></ha-relative-time>
<simple-tooltip animation-delay="0" for="last_changed">
<div> <div>
${this.hass.formatEntityAttributeValue( <div class="row">
<span class="column-name">
${this.hass.localize(
"ui.dialogs.more_info_control.last_changed"
)}:
</span>
<ha-relative-time
.hass=${this.hass}
.datetime=${this.stateObj.last_changed}
capitalize
></ha-relative-time>
</div>
<div class="row">
<span>
${this.hass.localize(
"ui.dialogs.more_info_control.last_updated"
)}:
</span>
<ha-relative-time
.hass=${this.hass}
.datetime=${this.stateObj.last_updated}
capitalize
></ha-relative-time>
</div>
</div>
</simple-tooltip>
</div>
</div>
<div class="temp-attribute">
<div class="temp">
${this.stateObj.attributes.temperature !== undefined &&
this.stateObj.attributes.temperature !== null
? html`
${formatNumber(
this.stateObj.attributes.temperature,
this.hass.locale
)}&nbsp;<span
>${getWeatherUnit(
this.hass.config,
this.stateObj, this.stateObj,
"temperature" "temperature"
)}</span
>
`
: nothing}
</div>
<div class="attribute">
${getSecondaryWeatherAttribute(
this.hass,
this.stateObj,
forecast!
)} )}
</div> </div>
</div> </div>
` </div>
: ""} </div>
${this._showValue(this.stateObj.attributes.pressure) ${this._showValue(this.stateObj.attributes.pressure)
? html` ? html`
<div class="flex"> <div class="flex">
@ -169,7 +236,7 @@ class MoreInfoWeather extends LitElement {
</div> </div>
</div> </div>
` `
: ""} : nothing}
${this._showValue(this.stateObj.attributes.humidity) ${this._showValue(this.stateObj.attributes.humidity)
? html` ? html`
<div class="flex"> <div class="flex">
@ -185,7 +252,7 @@ class MoreInfoWeather extends LitElement {
</div> </div>
</div> </div>
` `
: ""} : nothing}
${this._showValue(this.stateObj.attributes.wind_speed) ${this._showValue(this.stateObj.attributes.wind_speed)
? html` ? html`
<div class="flex"> <div class="flex">
@ -203,7 +270,7 @@ class MoreInfoWeather extends LitElement {
</div> </div>
</div> </div>
` `
: ""} : nothing}
${this._showValue(this.stateObj.attributes.visibility) ${this._showValue(this.stateObj.attributes.visibility)
? html` ? html`
<div class="flex"> <div class="flex">
@ -219,7 +286,7 @@ class MoreInfoWeather extends LitElement {
</div> </div>
</div> </div>
` `
: ""} : nothing}
${forecast ${forecast
? html` ? html`
<div class="section"> <div class="section">
@ -242,76 +309,90 @@ class MoreInfoWeather extends LitElement {
)} )}
</mwc-tab-bar>` </mwc-tab-bar>`
: nothing} : nothing}
<div class="forecast">
${forecast.map((item) => ${forecast.map((item) =>
this._showValue(item.templow) || this._showValue(item.temperature) this._showValue(item.templow) ||
? html`<div class="flex"> this._showValue(item.temperature)
${item.condition
? html` ? html`
<ha-svg-icon <div>
.path=${weatherIcons[item.condition]} <div>
></ha-svg-icon>
`
: ""}
<div class="main">
${dayNight ${dayNight
? html` ? html`
${formatDateWeekdayDay( ${formatDateWeekdayShort(
new Date(item.datetime), new Date(item.datetime),
this.hass!.locale, this.hass!.locale,
this.hass!.config this.hass!.config
)} )}
(${item.is_daytime !== false <div class="daynight">
${item.is_daytime !== false
? this.hass!.localize("ui.card.weather.day") ? this.hass!.localize("ui.card.weather.day")
: this.hass!.localize("ui.card.weather.night")}) : this.hass!.localize(
"ui.card.weather.night"
)}<br />
</div>
` `
: hourly : hourly
? html` ? html`
${formatTimeWeekday( ${formatTime(
new Date(item.datetime), new Date(item.datetime),
this.hass!.locale, this.hass!.locale,
this.hass!.config this.hass!.config
)} )}
` `
: html` : html`
${formatDateWeekdayDay( ${formatDateWeekdayShort(
new Date(item.datetime), new Date(item.datetime),
this.hass!.locale, this.hass!.locale,
this.hass!.config this.hass!.config
)} )}
`} `}
</div> </div>
<div class="templow"> ${this._showValue(item.condition)
${this._showValue(item.templow) ? html`
? this.hass.formatEntityAttributeValue( <div class="forecast-image-icon">
this.stateObj!, ${getWeatherStateIcon(
"templow", item.condition!,
item.templow this,
!(
item.is_daytime ||
item.is_daytime === undefined
) )
: hourly )}
? ""
: "—"}
</div> </div>
`
: nothing}
<div class="temp"> <div class="temp">
${this._showValue(item.temperature) ${this._showValue(item.temperature)
? this.hass.formatEntityAttributeValue( ? html`${formatNumber(
this.stateObj!, item.temperature,
"temperature", this.hass!.locale
item.temperature )}°`
)
: "—"} : "—"}
</div> </div>
</div>` <div class="templow">
: "" ${this._showValue(item.templow)
)} ? html`${formatNumber(
item.templow!,
this.hass!.locale
)}°`
: hourly
? nothing
: "—"}
</div>
</div>
` `
: ""} : nothing
)}
</div>
`
: nothing}
${this.stateObj.attributes.attribution ${this.stateObj.attributes.attribution
? html` ? html`
<div class="attribution"> <div class="attribution">
${this.stateObj.attributes.attribution} ${this.stateObj.attributes.attribution}
</div> </div>
` `
: ""} : nothing}
`; `;
} }
@ -321,7 +402,10 @@ class MoreInfoWeather extends LitElement {
]; ];
} }
static styles = css` static get styles(): CSSResultGroup {
return [
weatherSVGStyles,
css`
ha-svg-icon { ha-svg-icon {
color: var(--paper-item-icon-color); color: var(--paper-item-icon-color);
margin-left: 8px; margin-left: 8px;
@ -354,23 +438,150 @@ class MoreInfoWeather extends LitElement {
margin-inline-end: initial; margin-inline-end: initial;
} }
.temp, .attribution {
.templow { text-align: center;
min-width: 48px; margin-top: 16px;
text-align: right; }
.time-ago,
.attribute {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.attribution,
.templow,
.daynight,
.attribute,
.time-ago {
color: var(--secondary-text-color);
}
.content {
display: flex;
flex-wrap: nowrap;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.icon-image {
display: flex;
align-items: center;
min-width: 64px;
margin-right: 16px;
margin-inline-end: 16px;
margin-inline-start: initial;
}
.icon-image > * {
flex: 0 0 64px;
height: 64px;
}
.weather-icon {
--mdc-icon-size: 64px;
}
.info {
display: flex;
justify-content: space-between;
flex-grow: 1;
overflow: hidden;
}
.temp-attribute {
text-align: var(--float-end);
}
.temp-attribute .temp {
position: relative;
margin-right: 24px;
direction: ltr; direction: ltr;
} }
.templow { .temp-attribute .temp span {
margin: 0 16px; position: absolute;
color: var(--secondary-text-color); font-size: 24px;
top: 1px;
} }
.attribution { .state,
color: var(--secondary-text-color); .temp-attribute .temp {
text-align: center; font-size: 28px;
line-height: 1.2;
}
.attribute {
font-size: 14px;
line-height: 1;
}
.name-state {
overflow: hidden;
padding-right: 12px;
padding-inline-end: 12px;
padding-inline-start: initial;
width: 100%;
}
.state {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.forecast {
display: flex;
justify-content: space-around;
padding: 16px;
padding-bottom: 0px;
overflow-x: auto;
scrollbar-color: var(--scrollbar-thumb-color) transparent;
scrollbar-width: thin;
mask-image: linear-gradient(
90deg,
transparent 0%,
black 5%,
black 94%,
transparent 100%
);
}
.forecast > div {
text-align: center;
padding: 0 10px;
}
.forecast .icon,
.forecast .temp {
margin: 4px 0;
}
.forecast .temp {
font-size: 16px;
}
.forecast-image-icon {
padding-top: 4px;
padding-bottom: 4px;
display: flex;
justify-content: center;
}
.forecast-image-icon > * {
width: 40px;
height: 40px;
--mdc-icon-size: 40px;
}
.forecast-icon {
--mdc-icon-size: 40px;
}
`,
];
} }
`;
private _showValue(item: number | string | undefined): boolean { private _showValue(item: number | string | undefined): boolean {
return typeof item !== "undefined" && item !== null; return typeof item !== "undefined" && item !== null;