mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-27 19:26:36 +00:00
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:
parent
35face602b
commit
e9fef1f873
@ -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"];
|
||||||
|
@ -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"
|
||||||
</div>
|
.stateObj=${this.stateObj}
|
||||||
<div>
|
.hass=${this.hass}
|
||||||
${this.hass.formatEntityAttributeValue(
|
></ha-state-icon>
|
||||||
this.stateObj,
|
`}
|
||||||
"temperature"
|
</div>
|
||||||
)}
|
<div class="info">
|
||||||
</div>
|
<div class="name-state">
|
||||||
|
<div class="state">
|
||||||
|
${this.hass.formatEntityState(this.stateObj)}
|
||||||
</div>
|
</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 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
|
||||||
|
)} <span
|
||||||
|
>${getWeatherUnit(
|
||||||
|
this.hass.config,
|
||||||
|
this.stateObj,
|
||||||
|
"temperature"
|
||||||
|
)}</span
|
||||||
|
>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
</div>
|
||||||
|
<div class="attribute">
|
||||||
|
${getSecondaryWeatherAttribute(
|
||||||
|
this.hass,
|
||||||
|
this.stateObj,
|
||||||
|
forecast!
|
||||||
|
)}
|
||||||
|
</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}
|
||||||
${forecast.map((item) =>
|
<div class="forecast">
|
||||||
this._showValue(item.templow) || this._showValue(item.temperature)
|
${forecast.map((item) =>
|
||||||
? html`<div class="flex">
|
this._showValue(item.templow) ||
|
||||||
${item.condition
|
this._showValue(item.temperature)
|
||||||
? html`
|
? html`
|
||||||
<ha-svg-icon
|
<div>
|
||||||
.path=${weatherIcons[item.condition]}
|
<div>
|
||||||
></ha-svg-icon>
|
${dayNight
|
||||||
`
|
? html`
|
||||||
: ""}
|
${formatDateWeekdayShort(
|
||||||
<div class="main">
|
new Date(item.datetime),
|
||||||
${dayNight
|
this.hass!.locale,
|
||||||
? html`
|
this.hass!.config
|
||||||
${formatDateWeekdayDay(
|
)}
|
||||||
new Date(item.datetime),
|
<div class="daynight">
|
||||||
this.hass!.locale,
|
${item.is_daytime !== false
|
||||||
this.hass!.config
|
? this.hass!.localize("ui.card.weather.day")
|
||||||
)}
|
: this.hass!.localize(
|
||||||
(${item.is_daytime !== false
|
"ui.card.weather.night"
|
||||||
? this.hass!.localize("ui.card.weather.day")
|
)}<br />
|
||||||
: this.hass!.localize("ui.card.weather.night")})
|
</div>
|
||||||
`
|
`
|
||||||
: hourly
|
: hourly
|
||||||
|
? html`
|
||||||
|
${formatTime(
|
||||||
|
new Date(item.datetime),
|
||||||
|
this.hass!.locale,
|
||||||
|
this.hass!.config
|
||||||
|
)}
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
${formatDateWeekdayShort(
|
||||||
|
new Date(item.datetime),
|
||||||
|
this.hass!.locale,
|
||||||
|
this.hass!.config
|
||||||
|
)}
|
||||||
|
`}
|
||||||
|
</div>
|
||||||
|
${this._showValue(item.condition)
|
||||||
? html`
|
? html`
|
||||||
${formatTimeWeekday(
|
<div class="forecast-image-icon">
|
||||||
new Date(item.datetime),
|
${getWeatherStateIcon(
|
||||||
this.hass!.locale,
|
item.condition!,
|
||||||
this.hass!.config
|
this,
|
||||||
)}
|
!(
|
||||||
|
item.is_daytime ||
|
||||||
|
item.is_daytime === undefined
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
`
|
`
|
||||||
: html`
|
: nothing}
|
||||||
${formatDateWeekdayDay(
|
<div class="temp">
|
||||||
new Date(item.datetime),
|
${this._showValue(item.temperature)
|
||||||
this.hass!.locale,
|
? html`${formatNumber(
|
||||||
this.hass!.config
|
item.temperature,
|
||||||
)}
|
this.hass!.locale
|
||||||
`}
|
)}°`
|
||||||
</div>
|
: "—"}
|
||||||
<div class="templow">
|
</div>
|
||||||
${this._showValue(item.templow)
|
<div class="templow">
|
||||||
? this.hass.formatEntityAttributeValue(
|
${this._showValue(item.templow)
|
||||||
this.stateObj!,
|
? html`${formatNumber(
|
||||||
"templow",
|
item.templow!,
|
||||||
item.templow
|
this.hass!.locale
|
||||||
)
|
)}°`
|
||||||
: hourly
|
: hourly
|
||||||
? ""
|
? nothing
|
||||||
: "—"}
|
: "—"}
|
||||||
</div>
|
</div>
|
||||||
<div class="temp">
|
</div>
|
||||||
${this._showValue(item.temperature)
|
`
|
||||||
? this.hass.formatEntityAttributeValue(
|
: nothing
|
||||||
this.stateObj!,
|
)}
|
||||||
"temperature",
|
</div>
|
||||||
item.temperature
|
|
||||||
)
|
|
||||||
: "—"}
|
|
||||||
</div>
|
|
||||||
</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,56 +402,186 @@ class MoreInfoWeather extends LitElement {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static get styles(): CSSResultGroup {
|
||||||
ha-svg-icon {
|
return [
|
||||||
color: var(--paper-item-icon-color);
|
weatherSVGStyles,
|
||||||
margin-left: 8px;
|
css`
|
||||||
margin-inline-start: 8px;
|
ha-svg-icon {
|
||||||
margin-inline-end: initial;
|
color: var(--paper-item-icon-color);
|
||||||
}
|
margin-left: 8px;
|
||||||
|
margin-inline-start: 8px;
|
||||||
|
margin-inline-end: initial;
|
||||||
|
}
|
||||||
|
|
||||||
mwc-tab-bar {
|
mwc-tab-bar {
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section {
|
.section {
|
||||||
margin: 16px 0 8px 0;
|
margin: 16px 0 8px 0;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flex {
|
.flex {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.flex > div:last-child {
|
.flex > div:last-child {
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
margin-left: 24px;
|
margin-left: 24px;
|
||||||
margin-inline-start: 24px;
|
margin-inline-start: 24px;
|
||||||
margin-inline-end: initial;
|
margin-inline-end: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
.temp,
|
.attribution {
|
||||||
.templow {
|
text-align: center;
|
||||||
min-width: 48px;
|
margin-top: 16px;
|
||||||
text-align: right;
|
}
|
||||||
direction: ltr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.templow {
|
.time-ago,
|
||||||
margin: 0 16px;
|
.attribute {
|
||||||
color: var(--secondary-text-color);
|
white-space: nowrap;
|
||||||
}
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
.attribution {
|
.attribution,
|
||||||
color: var(--secondary-text-color);
|
.templow,
|
||||||
text-align: center;
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp-attribute .temp span {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 24px;
|
||||||
|
top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.state,
|
||||||
|
.temp-attribute .temp {
|
||||||
|
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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user