mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-23 09:16:38 +00:00
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 <mail@bramkragten.nl> * Reviews * Reviews * Comments Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
parent
9a00078169
commit
fdf7b516a0
Binary file not shown.
Before Width: | Height: | Size: 465 B After Width: | Height: | Size: 648 B |
Binary file not shown.
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
Before Width: | Height: | Size: 678 B After Width: | Height: | Size: 492 B |
@ -1,24 +1,24 @@
|
|||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant, WeatherEntity } from "../types";
|
||||||
|
|
||||||
export const weatherImages = {
|
export const weatherImages = {
|
||||||
"clear-night": "/static/images/weather/night.png",
|
"clear-night": "/static/images/weather/night.png",
|
||||||
cloudy: "/static/images/weather/cloudy.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: "/static/images/weather/lightning.png",
|
||||||
"lightning-rainy": "/static/images/weather/lightning-rainy.png",
|
"lightning-rainy": "/static/images/weather/lightning-rainy.png",
|
||||||
partlycloudy: "/static/images/weather/partly-cloudy.png",
|
partlycloudy: "/static/images/weather/partly-cloudy.png",
|
||||||
pouring: "/static/images/weather/pouring.png",
|
pouring: "/static/images/weather/pouring.png",
|
||||||
rainy: "/static/images/weather/rainy.png",
|
rainy: "/static/images/weather/rainy.png",
|
||||||
snowy: "/static/images/weather/snowy.png",
|
snowy: "/static/images/weather/snowy.png",
|
||||||
"snowy-rainy": "/static/images/weather/rainy.png",
|
|
||||||
sunny: "/static/images/weather/sunny.png",
|
sunny: "/static/images/weather/sunny.png",
|
||||||
windy: "/static/images/weather/windy.png",
|
windy: "/static/images/weather/windy.png",
|
||||||
"windy-variant": "/static/images/weather/windy.png",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const weatherIcons = {
|
export const weatherIcons = {
|
||||||
exceptional: "hass:alert-circle-outline",
|
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 = [
|
export const cardinalDirections = [
|
||||||
@ -78,3 +78,89 @@ export const getWeatherUnit = (
|
|||||||
return hass.config.unit_system[measure] || "";
|
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}
|
||||||
|
`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
@ -8,58 +8,32 @@ import {
|
|||||||
PropertyValues,
|
PropertyValues,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import "../../../components/ha-icon";
|
||||||
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-card";
|
import "../../../components/ha-card";
|
||||||
import { HomeAssistant } from "../../../types";
|
import "../components/hui-warning";
|
||||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
|
||||||
|
import { WeatherForecastCardConfig } from "./types";
|
||||||
|
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||||
|
import { HomeAssistant, WeatherEntity } from "../../../types";
|
||||||
import { findEntities } from "../common/find-entites";
|
import { findEntities } from "../common/find-entites";
|
||||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||||
import "../components/hui-warning";
|
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
|
||||||
import { WeatherForecastCardConfig } from "./types";
|
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 = [
|
const DAY_IN_MILLISECONDS = 86400000;
|
||||||
"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",
|
|
||||||
};
|
|
||||||
|
|
||||||
@customElement("hui-weather-forecast-card")
|
@customElement("hui-weather-forecast-card")
|
||||||
class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
||||||
@ -92,8 +66,18 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
|||||||
|
|
||||||
@property() private _config?: WeatherForecastCardConfig;
|
@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 {
|
public getCardSize(): number {
|
||||||
return 4;
|
return this._config?.show_forecast !== false ? 4 : 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
public setConfig(config: WeatherForecastCardConfig): void {
|
public setConfig(config: WeatherForecastCardConfig): void {
|
||||||
@ -112,6 +96,7 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
|||||||
if (!this._config || !this.hass) {
|
if (!this._config || !this.hass) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||||
const oldConfig = changedProps.get("_config") as
|
const oldConfig = changedProps.get("_config") as
|
||||||
| WeatherForecastCardConfig
|
| WeatherForecastCardConfig
|
||||||
@ -125,10 +110,6 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
|||||||
) {
|
) {
|
||||||
applyThemesOnElement(this, this.hass.themes, this._config.theme);
|
applyThemesOnElement(this, this.hass.themes, this._config.theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changedProps.has("hass")) {
|
|
||||||
toggleAttribute(this, "rtl", computeRTL(this.hass!));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
@ -136,7 +117,7 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
const stateObj = this.hass.states[this._config.entity];
|
const stateObj = this.hass.states[this._config.entity] as WeatherEntity;
|
||||||
|
|
||||||
if (!stateObj) {
|
if (!stateObj) {
|
||||||
return html`
|
return html`
|
||||||
@ -150,77 +131,74 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const forecast = stateObj.attributes.forecast
|
if (stateObj.state === UNAVAILABLE) {
|
||||||
? stateObj.attributes.forecast.slice(0, 5)
|
return html`
|
||||||
|
<ha-card class="unavailable" @click=${this._handleAction}>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.lovelace.warning.entity_unavailable",
|
||||||
|
"entity",
|
||||||
|
`${computeStateName(stateObj)} (${this._config.entity})`
|
||||||
|
)}
|
||||||
|
</ha-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const forecast =
|
||||||
|
this._config?.show_forecast !== false &&
|
||||||
|
stateObj.attributes.forecast?.length
|
||||||
|
? stateObj.attributes.forecast.slice(0, this._narrow ? 3 : 5)
|
||||||
: undefined;
|
: 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`
|
return html`
|
||||||
<ha-card
|
<ha-card
|
||||||
@action=${this._handleAction}
|
@action=${this._handleAction}
|
||||||
.actionHandler=${actionHandler()}
|
.actionHandler=${actionHandler()}
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<div class="header">
|
<div class="content">
|
||||||
|
<div class="icon-info">
|
||||||
|
${stateObj.state in weatherImages
|
||||||
|
? html`
|
||||||
|
<img
|
||||||
|
class="weather-image"
|
||||||
|
src="${weatherImages[stateObj.state]}"
|
||||||
|
/>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<ha-icon
|
||||||
|
class="weather-icon"
|
||||||
|
.icon=${weatherIcons[stateObj.state] || stateIcon(stateObj)}
|
||||||
|
></ha-icon>
|
||||||
|
`}
|
||||||
|
<div class="info">
|
||||||
|
<div class="name">
|
||||||
|
${this._config.name || computeStateName(stateObj)}
|
||||||
|
</div>
|
||||||
|
<div class="state">
|
||||||
${this.hass.localize(`state.weather.${stateObj.state}`) ||
|
${this.hass.localize(`state.weather.${stateObj.state}`) ||
|
||||||
stateObj.state}
|
stateObj.state}
|
||||||
<div class="name">
|
|
||||||
${(this._config && this._config.name) || computeStateName(stateObj)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
</div>
|
||||||
<div class="now">
|
<div class="temp-attribute">
|
||||||
<div class="main">
|
|
||||||
${stateObj.state in weatherIcons
|
|
||||||
? html`
|
|
||||||
<ha-icon icon="${weatherIcons[stateObj.state]}"></ha-icon>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
<div class="temp">
|
<div class="temp">
|
||||||
${stateObj.attributes.temperature}<span
|
${stateObj.attributes.temperature}<span
|
||||||
>${this.getUnit("temperature")}</span
|
>${getWeatherUnit(this.hass, "temperature")}</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="attribute">
|
||||||
|
${getSecondaryWeatherAttribute(this.hass, stateObj)}
|
||||||
</div>
|
</div>
|
||||||
<div class="attributes">
|
|
||||||
${this._showValue(stateObj.attributes.pressure)
|
|
||||||
? html`
|
|
||||||
<div>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.card.weather.attributes.air_pressure"
|
|
||||||
)}:
|
|
||||||
<span class="measurand">
|
|
||||||
${stateObj.attributes.pressure}
|
|
||||||
${this.getUnit("air_pressure")}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
${this._showValue(stateObj.attributes.humidity)
|
|
||||||
? html`
|
|
||||||
<div>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.card.weather.attributes.humidity"
|
|
||||||
)}:
|
|
||||||
<span class="measurand"
|
|
||||||
>${stateObj.attributes.humidity} %</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
${this._showValue(stateObj.attributes.wind_speed)
|
|
||||||
? html`
|
|
||||||
<div>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.card.weather.attributes.wind_speed"
|
|
||||||
)}:
|
|
||||||
<span class="measurand">
|
|
||||||
${stateObj.attributes.wind_speed}
|
|
||||||
${this.getUnit("length")}/h
|
|
||||||
</span>
|
|
||||||
${this.getWindBearing(stateObj.attributes.wind_bearing)}
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
${forecast
|
${forecast
|
||||||
@ -229,12 +207,8 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
|||||||
${forecast.map(
|
${forecast.map(
|
||||||
(item) => html`
|
(item) => html`
|
||||||
<div>
|
<div>
|
||||||
<div class="weekday">
|
<div>
|
||||||
${new Date(item.datetime).toLocaleDateString(
|
${hourly
|
||||||
this.hass!.language,
|
|
||||||
{ weekday: "short" }
|
|
||||||
)}<br />
|
|
||||||
${!this._showValue(item.templow)
|
|
||||||
? html`
|
? html`
|
||||||
${new Date(item.datetime).toLocaleTimeString(
|
${new Date(item.datetime).toLocaleTimeString(
|
||||||
this.hass!.language,
|
this.hass!.language,
|
||||||
@ -243,37 +217,46 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
`
|
`
|
||||||
: ""}
|
: html`
|
||||||
|
${new Date(item.datetime).toLocaleDateString(
|
||||||
|
this.hass!.language,
|
||||||
|
{ weekday: "short" }
|
||||||
|
)}
|
||||||
|
`}
|
||||||
</div>
|
</div>
|
||||||
${this._showValue(item.condition)
|
${item.condition !== undefined && item.condition !== null
|
||||||
|
? html`
|
||||||
|
<div class="forecast-image-icon">
|
||||||
|
${item.condition in weatherImages
|
||||||
|
? html`
|
||||||
|
<img
|
||||||
|
class="forecast-image"
|
||||||
|
src="${weatherImages[item.condition]}"
|
||||||
|
/>
|
||||||
|
`
|
||||||
|
: item.condition in weatherIcons
|
||||||
? html`
|
? html`
|
||||||
<div class="icon">
|
|
||||||
<ha-icon
|
<ha-icon
|
||||||
.icon="${weatherIcons[item.condition]}"
|
class="forecast-icon"
|
||||||
|
.icon=${weatherIcons[item.condition]}
|
||||||
></ha-icon>
|
></ha-icon>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
${this._showValue(item.temperature)
|
${item.temperature !== undefined &&
|
||||||
|
item.temperature !== null
|
||||||
? html`
|
? html`
|
||||||
<div class="temp">
|
<div class="temp">
|
||||||
${item.temperature}
|
${item.temperature}°
|
||||||
${this.getUnit("temperature")}
|
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
${this._showValue(item.templow)
|
${item.templow !== undefined && item.templow !== null
|
||||||
? html`
|
? html`
|
||||||
<div class="templow">
|
<div class="templow">
|
||||||
${item.templow} ${this.getUnit("temperature")}
|
${item.templow}°
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
${this._showValue(item.precipitation)
|
|
||||||
? html`
|
|
||||||
<div class="precipitation">
|
|
||||||
${item.precipitation}
|
|
||||||
${this.getUnit("precipitation")}
|
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
@ -283,7 +266,6 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
</div>
|
|
||||||
</ha-card>
|
</ha-card>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -292,177 +274,239 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
|||||||
return hasConfigOrEntityChanged(this, changedProps);
|
return hasConfigOrEntityChanged(this, changedProps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected firstUpdated(): void {
|
||||||
|
this._attachObserver();
|
||||||
|
}
|
||||||
|
|
||||||
private _handleAction(): void {
|
private _handleAction(): void {
|
||||||
fireEvent(this, "hass-more-info", { entityId: this._config!.entity });
|
fireEvent(this, "hass-more-info", { entityId: this._config!.entity });
|
||||||
}
|
}
|
||||||
|
|
||||||
private getUnit(measure: string): string {
|
private _attachObserver(): void {
|
||||||
const lengthUnit = this.hass!.config.unit_system.length || "";
|
if (typeof ResizeObserver !== "function") {
|
||||||
switch (measure) {
|
import("resize-observer").then((modules) => {
|
||||||
case "air_pressure":
|
modules.install();
|
||||||
return lengthUnit === "km" ? "hPa" : "inHg";
|
this._attachObserver();
|
||||||
case "length":
|
});
|
||||||
return lengthUnit;
|
return;
|
||||||
case "precipitation":
|
|
||||||
return lengthUnit === "km" ? "mm" : "in";
|
|
||||||
default:
|
|
||||||
return this.hass!.config.unit_system[measure] || "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private windBearingToText(degree: string): string {
|
this._resizeObserver = new ResizeObserver(
|
||||||
const degreenum = parseInt(degree, 10);
|
debounce(() => this._measureCard(), 250, false)
|
||||||
if (isFinite(degreenum)) {
|
);
|
||||||
// eslint-disable-next-line no-bitwise
|
|
||||||
return cardinalDirections[(((degreenum + 11.25) / 22.5) | 0) % 16];
|
const card = this.shadowRoot!.querySelector("ha-card");
|
||||||
|
// If we show an error or warning there is no ha-card
|
||||||
|
if (!card) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return degree;
|
this._resizeObserver.observe(card);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getWindBearing(bearing: string): string {
|
private _measureCard() {
|
||||||
if (bearing != null) {
|
this._narrow = this.offsetWidth < 375;
|
||||||
const cardinalDirection = this.windBearingToText(bearing);
|
if (this.offsetWidth < 300) {
|
||||||
return `(${
|
this.setAttribute("verynarrow", "");
|
||||||
this.hass!.localize(
|
} else {
|
||||||
`ui.card.weather.cardinal_direction.${cardinalDirection.toLowerCase()}`
|
this.removeAttribute("verynarrow");
|
||||||
) || cardinalDirection
|
|
||||||
})`;
|
|
||||||
}
|
}
|
||||||
return ``;
|
if (this.offsetWidth < 200) {
|
||||||
|
this.setAttribute("veryverynarrow", "");
|
||||||
|
} else {
|
||||||
|
this.removeAttribute("veryverynarrow");
|
||||||
}
|
}
|
||||||
|
|
||||||
private _showValue(item: string): boolean {
|
|
||||||
return typeof item !== "undefined" && item !== null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResult {
|
static get styles(): CSSResult {
|
||||||
return css`
|
return css`
|
||||||
:host {
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-card {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.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;
|
display: flex;
|
||||||
align-items: baseline;
|
flex-wrap: nowrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.name {
|
.icon-info {
|
||||||
margin-left: 16px;
|
display: flex;
|
||||||
font-size: 16px;
|
align-items: center;
|
||||||
color: var(--secondary-text-color);
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([rtl]) .name {
|
.weather-image,
|
||||||
margin-left: 0px;
|
.weather-icon {
|
||||||
|
flex: 0 0 66px;
|
||||||
margin-right: 16px;
|
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;
|
display: flex;
|
||||||
justify-content: space-between;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: flex-end;
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.main {
|
.temp-attribute .temp {
|
||||||
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;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
font-size: 38px;
|
||||||
|
line-height: 1;
|
||||||
|
margin-right: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([rtl]) .main .temp {
|
.temp-attribute .temp span {
|
||||||
direction: ltr;
|
|
||||||
margin-right: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main .temp span {
|
|
||||||
font-size: 24px;
|
|
||||||
line-height: 1em;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
font-size: 24px;
|
||||||
top: 4px;
|
top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.measurand {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host([rtl]) .measurand {
|
|
||||||
direction: ltr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.forecast {
|
.forecast {
|
||||||
margin-top: 16px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
padding-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.forecast div {
|
.forecast > div {
|
||||||
flex: 0 0 auto;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.forecast .icon {
|
.forecast .icon,
|
||||||
|
.forecast .temp {
|
||||||
margin: 4px 0;
|
margin: 4px 0;
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([rtl]) .forecast .temp {
|
.forecast .temp {
|
||||||
direction: ltr;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.weekday {
|
.forecast-image-icon {
|
||||||
font-weight: bold;
|
padding-top: 4px;
|
||||||
|
padding-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.attributes,
|
.forecast-image {
|
||||||
.templow,
|
width: 40px;
|
||||||
.precipitation {
|
}
|
||||||
|
|
||||||
|
.forecast-icon {
|
||||||
|
--iron-icon-width: 40px;
|
||||||
|
--iron-icon-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attribute {
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attribute,
|
||||||
|
.templow {
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([rtl]) .precipitation {
|
:host([narrow]) .weather-image {
|
||||||
direction: ltr;
|
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;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -274,4 +274,5 @@ export interface ThermostatCardConfig extends LovelaceCardConfig {
|
|||||||
export interface WeatherForecastCardConfig extends LovelaceCardConfig {
|
export interface WeatherForecastCardConfig extends LovelaceCardConfig {
|
||||||
entity: string;
|
entity: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
show_forecast?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -7,12 +7,14 @@ import {
|
|||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import "../../../../components/entity/ha-entity-picker";
|
import "../../../../components/entity/ha-entity-picker";
|
||||||
|
import "../../../../components/ha-switch";
|
||||||
|
|
||||||
|
import { EntitiesEditorEvent, EditorTarget } from "../types";
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../../../types";
|
||||||
import { WeatherForecastCardConfig } from "../../cards/types";
|
import { WeatherForecastCardConfig } from "../../cards/types";
|
||||||
import { struct } from "../../common/structs/struct";
|
import { struct } from "../../common/structs/struct";
|
||||||
import "../../components/hui-theme-select-editor";
|
import "../../components/hui-theme-select-editor";
|
||||||
import { LovelaceCardEditor } from "../../types";
|
import { LovelaceCardEditor } from "../../types";
|
||||||
import { EditorTarget, EntitiesEditorEvent } from "../types";
|
|
||||||
import { configElementStyle } from "./config-elements-style";
|
import { configElementStyle } from "./config-elements-style";
|
||||||
|
|
||||||
const cardConfigStruct = struct({
|
const cardConfigStruct = struct({
|
||||||
@ -20,6 +22,7 @@ const cardConfigStruct = struct({
|
|||||||
entity: "string?",
|
entity: "string?",
|
||||||
name: "string?",
|
name: "string?",
|
||||||
theme: "string?",
|
theme: "string?",
|
||||||
|
show_forecast: "boolean?",
|
||||||
});
|
});
|
||||||
|
|
||||||
@customElement("hui-weather-forecast-card-editor")
|
@customElement("hui-weather-forecast-card-editor")
|
||||||
@ -46,6 +49,10 @@ export class HuiWeatherForecastCardEditor extends LitElement
|
|||||||
return this._config!.theme || "";
|
return this._config!.theme || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get _show_forecast(): boolean {
|
||||||
|
return this._config!.show_forecast || true;
|
||||||
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this.hass || !this._config) {
|
if (!this.hass || !this._config) {
|
||||||
return html``;
|
return html``;
|
||||||
@ -61,29 +68,37 @@ export class HuiWeatherForecastCardEditor extends LitElement
|
|||||||
"ui.panel.lovelace.editor.card.config.required"
|
"ui.panel.lovelace.editor.card.config.required"
|
||||||
)})"
|
)})"
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value="${this._entity}"
|
.value=${this._entity}
|
||||||
.configValue=${"entity"}
|
.configValue=${"entity"}
|
||||||
.include-domains=${["weather"]}
|
.includeDomains=${["weather"]}
|
||||||
@change="${this._valueChanged}"
|
@change=${this._valueChanged}
|
||||||
allow-custom-entity
|
allow-custom-entity
|
||||||
></ha-entity-picker>
|
></ha-entity-picker>
|
||||||
|
<div class="side-by-side">
|
||||||
<paper-input
|
<paper-input
|
||||||
.label="${this.hass.localize(
|
.label="${this.hass.localize(
|
||||||
"ui.panel.lovelace.editor.card.generic.name"
|
"ui.panel.lovelace.editor.card.generic.name"
|
||||||
)} (${this.hass.localize(
|
)} (${this.hass.localize(
|
||||||
"ui.panel.lovelace.editor.card.config.optional"
|
"ui.panel.lovelace.editor.card.config.optional"
|
||||||
)})"
|
)})"
|
||||||
.value="${this._name}"
|
.value=${this._name}
|
||||||
.configValue="${"name"}"
|
.configValue=${"name"}
|
||||||
@value-changed="${this._valueChanged}"
|
@value-changed=${this._valueChanged}
|
||||||
></paper-input>
|
></paper-input>
|
||||||
<hui-theme-select-editor
|
<hui-theme-select-editor
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value="${this._theme}"
|
.value=${this._theme}
|
||||||
.configValue="${"theme"}"
|
.configValue=${"theme"}
|
||||||
@value-changed="${this._valueChanged}"
|
@value-changed=${this._valueChanged}
|
||||||
></hui-theme-select-editor>
|
></hui-theme-select-editor>
|
||||||
</div>
|
</div>
|
||||||
|
<ha-switch
|
||||||
|
.checked=${this._config!.show_forecast !== false}
|
||||||
|
.configValue=${"show_forecast"}
|
||||||
|
@change=${this._valueChanged}
|
||||||
|
>Show forecast</ha-switch
|
||||||
|
>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,7 +116,8 @@ export class HuiWeatherForecastCardEditor extends LitElement
|
|||||||
} else {
|
} else {
|
||||||
this._config = {
|
this._config = {
|
||||||
...this._config,
|
...this._config,
|
||||||
[target.configValue!]: target.value,
|
[target.configValue!]:
|
||||||
|
target.checked !== undefined ? target.checked : target.value,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,11 +14,11 @@ import {
|
|||||||
getWeatherUnit,
|
getWeatherUnit,
|
||||||
weatherIcons,
|
weatherIcons,
|
||||||
weatherImages,
|
weatherImages,
|
||||||
|
getSecondaryWeatherAttribute,
|
||||||
} from "../../../data/weather";
|
} from "../../../data/weather";
|
||||||
import { HomeAssistant, WeatherEntity } from "../../../types";
|
import { HomeAssistant, WeatherEntity } from "../../../types";
|
||||||
import { EntitiesCardEntityConfig } from "../cards/types";
|
import { EntitiesCardEntityConfig } from "../cards/types";
|
||||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||||
import "../components/hui-warning";
|
|
||||||
import { LovelaceRow } from "./types";
|
import { LovelaceRow } from "./types";
|
||||||
|
|
||||||
@customElement("hui-weather-entity-row")
|
@customElement("hui-weather-entity-row")
|
||||||
@ -77,95 +77,13 @@ class HuiWeatherEntityRow extends LitElement implements LovelaceRow {
|
|||||||
`}
|
`}
|
||||||
</div>
|
</div>
|
||||||
<div class="secondary">
|
<div class="secondary">
|
||||||
${!UNAVAILABLE_STATES.includes(stateObj.state)
|
${getSecondaryWeatherAttribute(this.hass!, stateObj)}
|
||||||
? this._getSecondaryAttribute(stateObj)
|
|
||||||
: ""}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</hui-generic-entity-row>
|
</hui-generic-entity-row>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
static get styles(): CSSResult {
|
||||||
return css`
|
return css`
|
||||||
.attributes {
|
.attributes {
|
||||||
|
@ -1907,7 +1907,8 @@
|
|||||||
"warning": {
|
"warning": {
|
||||||
"attribute_not_found": "Attribute {attribute} not available in: {entity}",
|
"attribute_not_found": "Attribute {attribute} not available in: {entity}",
|
||||||
"entity_not_found": "Entity not available: {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": {
|
"changed_toast": {
|
||||||
"message": "The Lovelace UI configuration for this dashboard was updated, refresh to see changes?",
|
"message": "The Lovelace UI configuration for this dashboard was updated, refresh to see changes?",
|
||||||
|
@ -259,6 +259,7 @@ interface ForecastAttribute {
|
|||||||
templow?: number;
|
templow?: number;
|
||||||
precipitation?: number;
|
precipitation?: number;
|
||||||
humidity?: number;
|
humidity?: number;
|
||||||
|
condition?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WeatherEntity = HassEntityBase & {
|
export type WeatherEntity = HassEntityBase & {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user