mirror of
https://github.com/home-assistant/frontend.git
synced 2026-06-09 01:42:11 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 030e8c122c | |||
| db7da2b521 | |||
| d8e7ade6e1 | |||
| 7f79075c27 | |||
| b4635b907b | |||
| cc37adfe7b | |||
| 7f9e2f0d48 | |||
| 3c06ef46ad | |||
| a159e746e2 | |||
| 7981d028cd | |||
| 36f7b17465 | |||
| 2b0c3c4c22 | |||
| 80e610bc50 | |||
| b0832f3c27 |
@@ -0,0 +1,448 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { formatDateWeekdayShort } from "../../../common/datetime/format_date";
|
||||
import { formatTime } from "../../../common/datetime/format_time";
|
||||
import { formatNumber } from "../../../common/number/format_number";
|
||||
import { DragScrollController } from "../../../common/controllers/drag-scroll-controller";
|
||||
import type {
|
||||
ForecastAttribute,
|
||||
ForecastEvent,
|
||||
ForecastType,
|
||||
ModernForecastType,
|
||||
WeatherEntity,
|
||||
} from "../../../data/weather";
|
||||
import {
|
||||
getDefaultForecastType,
|
||||
getForecast,
|
||||
getWeatherStateIcon,
|
||||
subscribeForecast,
|
||||
weatherSVGStyles,
|
||||
} from "../../../data/weather";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
|
||||
import type {
|
||||
LovelaceCardFeatureContext,
|
||||
LovelaceCardFeaturePosition,
|
||||
WeatherForecastCardFeatureConfig,
|
||||
} from "./types";
|
||||
import { DEFAULT_FORECAST_SLOTS } from "../editor/config-elements/hui-weather-forecast-card-feature-editor";
|
||||
|
||||
export const supportsWeatherForecastCardFeature = (
|
||||
hass: HomeAssistant,
|
||||
context: LovelaceCardFeatureContext
|
||||
) => {
|
||||
const stateObj = context.entity_id
|
||||
? (hass.states[context.entity_id] as WeatherEntity | undefined)
|
||||
: undefined;
|
||||
|
||||
if (!stateObj || computeDomain(stateObj.entity_id) !== "weather") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Boolean(
|
||||
getDefaultForecastType(stateObj) ||
|
||||
(stateObj.attributes.forecast?.length || 0) > 2
|
||||
);
|
||||
};
|
||||
|
||||
@customElement("hui-weather-forecast-card-feature")
|
||||
class HuiWeatherForecastCardFeature
|
||||
extends LitElement
|
||||
implements LovelaceCardFeature
|
||||
{
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
|
||||
|
||||
@property({ reflect: true })
|
||||
public position?: LovelaceCardFeaturePosition;
|
||||
|
||||
@state() private _config?: WeatherForecastCardFeatureConfig;
|
||||
|
||||
@state() private _forecastEvent?: ForecastEvent;
|
||||
|
||||
@state() private _forecastType?: ForecastType;
|
||||
|
||||
@state() private _subscribed?: Promise<() => void>;
|
||||
|
||||
private _dragScrollController = new DragScrollController(this, {
|
||||
selector: ".forecast",
|
||||
enabled: false,
|
||||
});
|
||||
|
||||
static getStubConfig(): WeatherForecastCardFeatureConfig {
|
||||
return {
|
||||
type: "weather-forecast",
|
||||
};
|
||||
}
|
||||
|
||||
public static async getConfigElement(): Promise<LovelaceCardFeatureEditor> {
|
||||
await import("../editor/config-elements/hui-weather-forecast-card-feature-editor");
|
||||
return document.createElement("hui-weather-forecast-card-feature-editor");
|
||||
}
|
||||
|
||||
public setConfig(config: WeatherForecastCardFeatureConfig): void {
|
||||
if (!config) {
|
||||
throw new Error("Invalid configuration");
|
||||
}
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
if (this.hasUpdated) {
|
||||
this._subscribeForecastEvents();
|
||||
}
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._unsubscribeForecastEvents();
|
||||
}
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues): void {
|
||||
super.willUpdate(changedProps);
|
||||
|
||||
const nextForecastType = this._effectiveForecastType;
|
||||
const forecastTypeChanged = nextForecastType !== this._forecastType;
|
||||
if (forecastTypeChanged) {
|
||||
this._forecastType = nextForecastType;
|
||||
}
|
||||
|
||||
if (
|
||||
changedProps.has("context") ||
|
||||
changedProps.has("_config") ||
|
||||
forecastTypeChanged ||
|
||||
!this._subscribed
|
||||
) {
|
||||
this._subscribeForecastEvents();
|
||||
}
|
||||
|
||||
this._dragScrollController.enabled = Boolean(this._forecast?.length);
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (
|
||||
!this._config ||
|
||||
!this.hass ||
|
||||
!this.context ||
|
||||
!this._stateObj ||
|
||||
!supportsWeatherForecastCardFeature(this.hass, this.context)
|
||||
) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const forecast = this._forecast;
|
||||
|
||||
if (!forecast?.length) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const temperatureFormatOptions = this._config.round_temperature
|
||||
? { maximumFractionDigits: 0 }
|
||||
: undefined;
|
||||
|
||||
const forecastType = getForecast(
|
||||
this._stateObj.attributes,
|
||||
this._forecastEvent,
|
||||
this._forecastType
|
||||
)?.type;
|
||||
const hourly = forecastType === "hourly";
|
||||
const dayNight = forecastType === "twice_daily";
|
||||
const todayKey = this._dayKeyFromDate(new Date());
|
||||
|
||||
return html`
|
||||
<div
|
||||
class=${classMap({
|
||||
forecast: true,
|
||||
dragging: this._dragScrollController.scrolling,
|
||||
})}
|
||||
>
|
||||
${forecast.map(
|
||||
(item, index) => html`
|
||||
${this._renderDayGroupLabel(
|
||||
item,
|
||||
index,
|
||||
forecast,
|
||||
dayNight,
|
||||
hourly,
|
||||
todayKey
|
||||
)}
|
||||
<div class="item">
|
||||
<div class="label">
|
||||
${this._labelForForecast(item, hourly, dayNight)}
|
||||
</div>
|
||||
${item.condition
|
||||
? html`
|
||||
<div class="icon">
|
||||
${getWeatherStateIcon(
|
||||
item.condition,
|
||||
this,
|
||||
!(item.is_daytime || item.is_daytime === undefined)
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
<div class="temp">
|
||||
${item.temperature !== undefined && item.temperature !== null
|
||||
? `${formatNumber(
|
||||
item.temperature,
|
||||
this.hass!.locale,
|
||||
temperatureFormatOptions
|
||||
)}°`
|
||||
: "—"}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderDayGroupLabel(
|
||||
item: ForecastAttribute,
|
||||
index: number,
|
||||
forecast: ForecastAttribute[],
|
||||
dayNight: boolean,
|
||||
hourly: boolean,
|
||||
todayKey: string
|
||||
) {
|
||||
if (!dayNight && !hourly) {
|
||||
return nothing;
|
||||
}
|
||||
const previousItem = forecast[index - 1];
|
||||
const itemDayKey = this._dayKeyForForecast(item);
|
||||
const dayChanged =
|
||||
!previousItem || itemDayKey !== this._dayKeyForForecast(previousItem);
|
||||
if (!dayChanged || itemDayKey === todayKey) {
|
||||
return nothing;
|
||||
}
|
||||
return html`<div class="item label-only">
|
||||
<div class="label">${this._dayLabelForForecast(item)}</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
private get _stateObj() {
|
||||
if (!this.hass || !this.context?.entity_id) {
|
||||
return undefined;
|
||||
}
|
||||
return this.hass.states[this.context.entity_id] as
|
||||
| WeatherEntity
|
||||
| undefined;
|
||||
}
|
||||
|
||||
private get _forecast() {
|
||||
const stateObj = this._stateObj;
|
||||
if (!stateObj) {
|
||||
return undefined;
|
||||
}
|
||||
return getForecast(
|
||||
stateObj.attributes,
|
||||
this._forecastEvent,
|
||||
this._forecastType
|
||||
)?.forecast?.slice(
|
||||
0,
|
||||
this._config?.forecast_slots ?? DEFAULT_FORECAST_SLOTS
|
||||
);
|
||||
}
|
||||
|
||||
private get _effectiveForecastType(): ForecastType | undefined {
|
||||
const stateObj = this._stateObj;
|
||||
if (!stateObj) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (this._config?.forecast_type !== undefined) {
|
||||
return this._config.forecast_type;
|
||||
}
|
||||
|
||||
return (
|
||||
getDefaultForecastType(stateObj) ??
|
||||
((stateObj.attributes.forecast?.length || 0) > 2 ? "legacy" : undefined)
|
||||
);
|
||||
}
|
||||
|
||||
private get _modernForecastType(): ModernForecastType | undefined {
|
||||
return this._forecastType !== "legacy" ? this._forecastType : undefined;
|
||||
}
|
||||
|
||||
private _unsubscribeForecastEvents() {
|
||||
if (this._subscribed) {
|
||||
this._subscribed.then((unsub) => unsub());
|
||||
this._subscribed = undefined;
|
||||
}
|
||||
this._forecastEvent = undefined;
|
||||
}
|
||||
|
||||
private _subscribeForecastEvents() {
|
||||
this._unsubscribeForecastEvents();
|
||||
const modernForecastType = this._modernForecastType;
|
||||
|
||||
if (
|
||||
!this.isConnected ||
|
||||
!this.hass ||
|
||||
!this._stateObj ||
|
||||
!modernForecastType
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._subscribed = subscribeForecast(
|
||||
this.hass,
|
||||
this._stateObj.entity_id,
|
||||
modernForecastType,
|
||||
(event) => {
|
||||
this._forecastEvent = event;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _labelForForecast(
|
||||
item: ForecastAttribute,
|
||||
hourly: boolean,
|
||||
dayNight: boolean
|
||||
) {
|
||||
if (hourly) {
|
||||
return formatTime(
|
||||
new Date(item.datetime),
|
||||
this.hass!.locale,
|
||||
this.hass!.config
|
||||
);
|
||||
}
|
||||
if (dayNight) {
|
||||
return item.is_daytime !== false
|
||||
? this.hass!.localize("ui.card.weather.day")
|
||||
: this.hass!.localize("ui.card.weather.night");
|
||||
}
|
||||
return this._dayLabelForForecast(item);
|
||||
}
|
||||
|
||||
private _dayLabelForForecast(item: ForecastAttribute) {
|
||||
return formatDateWeekdayShort(
|
||||
new Date(item.datetime),
|
||||
this.hass!.locale,
|
||||
this.hass!.config
|
||||
);
|
||||
}
|
||||
|
||||
private _dayKeyForForecast(item: ForecastAttribute) {
|
||||
return this._dayKeyFromDate(new Date(item.datetime));
|
||||
}
|
||||
|
||||
private _dayKeyFromDate(date: Date) {
|
||||
return `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
weatherSVGStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
width: calc(100% + 16px);
|
||||
margin: 0 -8px;
|
||||
pointer-events: auto;
|
||||
--icon-size: 28px;
|
||||
}
|
||||
|
||||
:host([position="inline"]) {
|
||||
--icon-size: 20px;
|
||||
}
|
||||
|
||||
.forecast {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
max-width: 100%;
|
||||
overflow: auto;
|
||||
scrollbar-color: var(--scrollbar-thumb-color) transparent;
|
||||
scrollbar-width: none;
|
||||
mask-image: linear-gradient(
|
||||
90deg,
|
||||
transparent 0%,
|
||||
black 16px,
|
||||
black calc(100% - 16px),
|
||||
transparent 100%
|
||||
);
|
||||
user-select: none;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.forecast.dragging {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.forecast.dragging * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.forecast::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.forecast::before,
|
||||
.forecast::after {
|
||||
content: "";
|
||||
position: relative;
|
||||
display: block;
|
||||
min-width: 8px;
|
||||
height: 1px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
min-width: 40px;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
gap: var(--ha-space-1);
|
||||
}
|
||||
|
||||
.item.label-only {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.item.label-only .label {
|
||||
color: var(--secondary-text-color);
|
||||
font-weight: var(--ha-font-weight-bold);
|
||||
}
|
||||
|
||||
.label,
|
||||
.temp {
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: var(--secondary-text-color);
|
||||
font-size: var(--ha-font-size-s);
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: var(--icon-size);
|
||||
height: var(--icon-size);
|
||||
}
|
||||
|
||||
.icon > * {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
--mdc-icon-size: var(--icon-size);
|
||||
}
|
||||
|
||||
.temp {
|
||||
font-size: var(--ha-font-size-m);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-weather-forecast-card-feature": HuiWeatherForecastCardFeature;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { AlarmMode } from "../../../data/alarm_control_panel";
|
||||
import type { HvacMode } from "../../../data/climate";
|
||||
import type { ForecastType } from "../../../data/weather";
|
||||
import type { OperationMode } from "../../../data/water_heater";
|
||||
|
||||
export type ButtonCardData = Record<string, any>;
|
||||
@@ -226,6 +227,13 @@ export interface TrendGraphCardFeatureConfig {
|
||||
detail?: boolean;
|
||||
}
|
||||
|
||||
export interface WeatherForecastCardFeatureConfig {
|
||||
type: "weather-forecast";
|
||||
forecast_type?: ForecastType;
|
||||
forecast_slots?: number;
|
||||
round_temperature?: boolean;
|
||||
}
|
||||
|
||||
export const AREA_CONTROL_DOMAINS = [
|
||||
"light",
|
||||
"fan",
|
||||
@@ -299,6 +307,7 @@ export type LovelaceCardFeatureConfig =
|
||||
| TargetHumidityCardFeatureConfig
|
||||
| TargetTemperatureCardFeatureConfig
|
||||
| ToggleCardFeatureConfig
|
||||
| WeatherForecastCardFeatureConfig
|
||||
| UpdateActionsCardFeatureConfig
|
||||
| VacuumCommandsCardFeatureConfig
|
||||
| ValveOpenCloseCardFeatureConfig
|
||||
|
||||
@@ -332,6 +332,7 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
.context=${this._featureContext}
|
||||
.color=${this._config.color}
|
||||
.features=${features}
|
||||
.position=${featurePosition}
|
||||
></hui-card-features>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
@@ -40,6 +40,7 @@ import "../card-features/hui-vacuum-commands-card-feature";
|
||||
import "../card-features/hui-valve-open-close-card-feature";
|
||||
import "../card-features/hui-valve-position-favorite-card-feature";
|
||||
import "../card-features/hui-valve-position-card-feature";
|
||||
import "../card-features/hui-weather-forecast-card-feature";
|
||||
import "../card-features/hui-water-heater-operation-modes-card-feature";
|
||||
import "../card-features/hui-area-controls-card-feature";
|
||||
import "../card-features/hui-bar-gauge-card-feature";
|
||||
@@ -97,6 +98,7 @@ const TYPES = new Set<LovelaceCardFeatureConfig["type"]>([
|
||||
"valve-open-close",
|
||||
"valve-position-favorite",
|
||||
"valve-position",
|
||||
"weather-forecast",
|
||||
"water-heater-operation-modes",
|
||||
]);
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ import { supportsVacuumCommandsCardFeature } from "../../card-features/hui-vacuu
|
||||
import { supportsValveOpenCloseCardFeature } from "../../card-features/hui-valve-open-close-card-feature";
|
||||
import { supportsValvePositionFavoriteCardFeature } from "../../card-features/hui-valve-position-favorite-card-feature";
|
||||
import { supportsValvePositionCardFeature } from "../../card-features/hui-valve-position-card-feature";
|
||||
import { supportsWeatherForecastCardFeature } from "../../card-features/hui-weather-forecast-card-feature";
|
||||
import { supportsWaterHeaterOperationModesCardFeature } from "../../card-features/hui-water-heater-operation-modes-card-feature";
|
||||
import type {
|
||||
LovelaceCardFeatureConfig,
|
||||
@@ -129,6 +130,7 @@ const UI_FEATURE_TYPES = [
|
||||
"valve-open-close",
|
||||
"valve-position-favorite",
|
||||
"valve-position",
|
||||
"weather-forecast",
|
||||
"water-heater-operation-modes",
|
||||
] as const satisfies readonly FeatureType[];
|
||||
|
||||
@@ -158,6 +160,7 @@ const EDITABLES_FEATURE_TYPES = new Set<UiFeatureTypes>([
|
||||
"update-actions",
|
||||
"vacuum-commands",
|
||||
"valve-position-favorite",
|
||||
"weather-forecast",
|
||||
"water-heater-operation-modes",
|
||||
]);
|
||||
|
||||
@@ -211,6 +214,7 @@ const SUPPORTS_FEATURE_TYPES: Record<
|
||||
"valve-open-close": supportsValveOpenCloseCardFeature,
|
||||
"valve-position-favorite": supportsValvePositionFavoriteCardFeature,
|
||||
"valve-position": supportsValvePositionCardFeature,
|
||||
"weather-forecast": supportsWeatherForecastCardFeature,
|
||||
"water-heater-operation-modes": supportsWaterHeaterOperationModesCardFeature,
|
||||
};
|
||||
|
||||
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { supportsFeature } from "../../../../common/entity/supports-feature";
|
||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import type { SchemaUnion } from "../../../../components/ha-form/types";
|
||||
import type {
|
||||
ForecastType,
|
||||
ModernForecastType,
|
||||
WeatherEntity,
|
||||
} from "../../../../data/weather";
|
||||
import { WeatherEntityFeature } from "../../../../data/weather";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type {
|
||||
LovelaceCardFeatureContext,
|
||||
WeatherForecastCardFeatureConfig,
|
||||
} from "../../card-features/types";
|
||||
import type { LovelaceCardFeatureEditor } from "../../types";
|
||||
|
||||
export const DEFAULT_FORECAST_SLOTS = 12;
|
||||
export const MAX_FORECAST_SLOTS = 48;
|
||||
|
||||
@customElement("hui-weather-forecast-card-feature-editor")
|
||||
export class HuiWeatherForecastCardFeatureEditor
|
||||
extends LitElement
|
||||
implements LovelaceCardFeatureEditor
|
||||
{
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
|
||||
|
||||
@state() private _config?: WeatherForecastCardFeatureConfig;
|
||||
|
||||
public setConfig(config: WeatherForecastCardFeatureConfig): void {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
private get _stateObj(): WeatherEntity | undefined {
|
||||
if (!this.hass || !this.context?.entity_id) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.hass.states[this.context.entity_id] as
|
||||
| WeatherEntity
|
||||
| undefined;
|
||||
}
|
||||
|
||||
private _forecastSupported(forecastType: ForecastType): boolean {
|
||||
const stateObj = this._stateObj;
|
||||
if (!stateObj) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (forecastType === "daily") {
|
||||
return supportsFeature(stateObj, WeatherEntityFeature.FORECAST_DAILY);
|
||||
}
|
||||
if (forecastType === "hourly") {
|
||||
return supportsFeature(stateObj, WeatherEntityFeature.FORECAST_HOURLY);
|
||||
}
|
||||
if (forecastType === "twice_daily") {
|
||||
return supportsFeature(
|
||||
stateObj,
|
||||
WeatherEntityFeature.FORECAST_TWICE_DAILY
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private get _defaultForecastType(): ModernForecastType | undefined {
|
||||
if (this._forecastSupported("daily")) {
|
||||
return "daily";
|
||||
}
|
||||
if (this._forecastSupported("hourly")) {
|
||||
return "hourly";
|
||||
}
|
||||
if (this._forecastSupported("twice_daily")) {
|
||||
return "twice_daily";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(
|
||||
localize: LocalizeFunc,
|
||||
hasDaily: boolean,
|
||||
hasHourly: boolean,
|
||||
hasTwiceDaily: boolean
|
||||
) => {
|
||||
const forecastTypeOptions = [
|
||||
...(hasDaily
|
||||
? ([
|
||||
{
|
||||
value: "daily",
|
||||
label: localize(
|
||||
"ui.panel.lovelace.editor.features.types.weather-forecast.type_list.daily"
|
||||
),
|
||||
},
|
||||
] as const)
|
||||
: []),
|
||||
...(hasHourly
|
||||
? ([
|
||||
{
|
||||
value: "hourly",
|
||||
label: localize(
|
||||
"ui.panel.lovelace.editor.features.types.weather-forecast.type_list.hourly"
|
||||
),
|
||||
},
|
||||
] as const)
|
||||
: []),
|
||||
...(hasTwiceDaily
|
||||
? ([
|
||||
{
|
||||
value: "twice_daily",
|
||||
label: localize(
|
||||
"ui.panel.lovelace.editor.features.types.weather-forecast.type_list.twice_daily"
|
||||
),
|
||||
},
|
||||
] as const)
|
||||
: []),
|
||||
];
|
||||
|
||||
return [
|
||||
...(forecastTypeOptions.length
|
||||
? ([
|
||||
{
|
||||
name: "forecast_type",
|
||||
default: forecastTypeOptions[0].value,
|
||||
selector: {
|
||||
select: {
|
||||
options: forecastTypeOptions,
|
||||
},
|
||||
},
|
||||
},
|
||||
] as const)
|
||||
: []),
|
||||
{
|
||||
name: "forecast_slots",
|
||||
default: DEFAULT_FORECAST_SLOTS,
|
||||
selector: {
|
||||
number: {
|
||||
min: 1,
|
||||
max: MAX_FORECAST_SLOTS,
|
||||
mode: "slider",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "round_temperature",
|
||||
selector: { boolean: {} },
|
||||
},
|
||||
] as const;
|
||||
}
|
||||
);
|
||||
|
||||
protected render() {
|
||||
if (!this.hass || !this._config) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const defaultForecastType = this._defaultForecastType;
|
||||
|
||||
const schema = this._schema(
|
||||
this.hass.localize,
|
||||
this._forecastSupported("daily"),
|
||||
this._forecastSupported("hourly"),
|
||||
this._forecastSupported("twice_daily")
|
||||
);
|
||||
|
||||
const data: WeatherForecastCardFeatureConfig = {
|
||||
...this._config,
|
||||
forecast_slots: this._config.forecast_slots ?? DEFAULT_FORECAST_SLOTS,
|
||||
forecast_type: this._config.forecast_type ?? defaultForecastType,
|
||||
type: "weather-forecast",
|
||||
};
|
||||
|
||||
return html`
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${data}
|
||||
.schema=${schema}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
fireEvent(this, "config-changed", { config: ev.detail.value });
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (
|
||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
) =>
|
||||
this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.features.types.weather-forecast.${schema.name}`
|
||||
);
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-weather-forecast-card-feature-editor": HuiWeatherForecastCardFeatureEditor;
|
||||
}
|
||||
}
|
||||
@@ -9923,6 +9923,17 @@
|
||||
"target-humidity": {
|
||||
"label": "Target humidity"
|
||||
},
|
||||
"weather-forecast": {
|
||||
"label": "Weather forecast",
|
||||
"forecast_type": "[%key:ui::panel::lovelace::editor::card::weather-forecast::forecast_type%]",
|
||||
"forecast_slots": "[%key:ui::panel::lovelace::editor::card::weather-forecast::forecast_slots%]",
|
||||
"round_temperature": "[%key:ui::panel::lovelace::editor::card::generic::round_temperature%]",
|
||||
"type_list": {
|
||||
"daily": "[%key:ui::panel::lovelace::editor::card::weather-forecast::daily%]",
|
||||
"hourly": "[%key:ui::panel::lovelace::editor::card::weather-forecast::hourly%]",
|
||||
"twice_daily": "[%key:ui::panel::lovelace::editor::card::weather-forecast::twice_daily%]"
|
||||
}
|
||||
},
|
||||
"water-heater-operation-modes": {
|
||||
"label": "Water heater operation modes",
|
||||
"operation_modes": "Operation modes",
|
||||
|
||||
Reference in New Issue
Block a user