diff --git a/src/panels/lovelace/card-features/hui-date-set-card-feature.ts b/src/panels/lovelace/card-features/hui-date-set-card-feature.ts new file mode 100644 index 0000000000..b4041ebbb9 --- /dev/null +++ b/src/panels/lovelace/card-features/hui-date-set-card-feature.ts @@ -0,0 +1,143 @@ +import { html, LitElement, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { computeDomain } from "../../../common/entity/compute_domain"; +import "../../../components/ha-control-slider"; +import type { HomeAssistant } from "../../../types"; +import type { LovelaceCardFeature } from "../types"; +import { cardFeatureStyles } from "./common/card-feature-styles"; +import type { + LovelaceCardFeatureContext, + DateSetCardFeatureConfig, +} from "./types"; +import { fireEvent } from "../../../common/dom/fire_event"; +import type { DatePickerDialogParams } from "../../../components/ha-date-input"; +import { firstWeekdayIndex } from "../../../common/datetime/first_weekday"; +import "../../../components/ha-control-button"; +import "../../../components/ha-control-button-group"; + +const loadDatePickerDialog = () => + import("../../../components/ha-dialog-date-picker"); + +export const supportsDateSetCardFeature = ( + hass: HomeAssistant, + context: LovelaceCardFeatureContext +) => { + const stateObj = context.entity_id + ? hass.states[context.entity_id] + : undefined; + if (!stateObj) return false; + const domain = computeDomain(stateObj.entity_id); + return ( + (domain === "input_datetime" && stateObj.attributes.has_date) || + ["datetime", "date"].includes(domain) + ); +}; + +@customElement("hui-date-set-card-feature") +class HuiDateSetCardFeature extends LitElement implements LovelaceCardFeature { + @property({ attribute: false }) public hass?: HomeAssistant; + + @property({ attribute: false }) public context?: LovelaceCardFeatureContext; + + @property({ attribute: false }) public color?: string; + + @state() private _config?: DateSetCardFeatureConfig; + + private get _stateObj() { + if (!this.hass || !this.context || !this.context.entity_id) { + return undefined; + } + return this.hass.states[this.context.entity_id!] ?? undefined; + } + + private _pressButton() { + if (!this.hass || !this._stateObj) return; + + const dialogParams: DatePickerDialogParams = { + min: "1970-01-01", + value: this._stateObj.state, + onChange: (value) => this._dateChanged(value), + locale: this.hass.locale.language, + firstWeekday: firstWeekdayIndex(this.hass.locale), + }; + + fireEvent(this, "show-dialog", { + dialogTag: "ha-dialog-date-picker", + dialogImport: loadDatePickerDialog, + dialogParams, + }); + } + + private _dateChanged(value: string | undefined) { + if (!this.hass || !this._stateObj || !value) return; + + const domain = computeDomain(this._stateObj.entity_id); + const service = domain === "input_datetime" ? "set_datetime" : "set_value"; + + // datetime requires a full datetime string + if (domain === "datetime") { + const dateObj = new Date(this._stateObj.state); + const selectedDate = new Date(`${value}T00:00:00`); + dateObj.setFullYear( + selectedDate.getFullYear(), + selectedDate.getMonth(), + selectedDate.getDate() + ); + + this.hass.callService(domain, service, { + entity_id: this._stateObj.entity_id, + datetime: dateObj.toISOString(), + }); + } else { + this.hass.callService(domain, service, { + entity_id: this._stateObj.entity_id, + date: value, + }); + } + } + + static getStubConfig(): DateSetCardFeatureConfig { + return { + type: "date-set", + }; + } + + public setConfig(config: DateSetCardFeatureConfig): void { + if (!config) { + throw new Error("Invalid configuration"); + } + this._config = config; + } + + protected render() { + if ( + !this._config || + !this.hass || + !this.context || + !this._stateObj || + !supportsDateSetCardFeature(this.hass, this.context) + ) { + return nothing; + } + + return html` + + + ${this.hass.localize("ui.card.date.set_date")} + + + `; + } + + static styles = cardFeatureStyles; +} + +declare global { + interface HTMLElementTagNameMap { + "hui-date-set-card-feature": HuiDateSetCardFeature; + } +} diff --git a/src/panels/lovelace/card-features/types.ts b/src/panels/lovelace/card-features/types.ts index 0b15db11d5..a31e533c1c 100644 --- a/src/panels/lovelace/card-features/types.ts +++ b/src/panels/lovelace/card-features/types.ts @@ -101,6 +101,10 @@ export interface CounterActionsCardFeatureConfig { actions?: CounterActions[]; } +export interface DateSetCardFeatureConfig { + type: "date-set"; +} + export interface SelectOptionsCardFeatureConfig { type: "select-options"; options?: string[]; @@ -213,6 +217,7 @@ export type LovelaceCardFeatureConfig = | CoverPositionCardFeatureConfig | CoverTiltPositionCardFeatureConfig | CoverTiltCardFeatureConfig + | DateSetCardFeatureConfig | FanDirectionCardFeatureConfig | FanPresetModesCardFeatureConfig | FanSpeedCardFeatureConfig diff --git a/src/panels/lovelace/create-element/create-card-feature-element.ts b/src/panels/lovelace/create-element/create-card-feature-element.ts index bdbc44999d..bd12120d59 100644 --- a/src/panels/lovelace/create-element/create-card-feature-element.ts +++ b/src/panels/lovelace/create-element/create-card-feature-element.ts @@ -10,6 +10,7 @@ import "../card-features/hui-cover-open-close-card-feature"; import "../card-features/hui-cover-position-card-feature"; import "../card-features/hui-cover-tilt-card-feature"; import "../card-features/hui-cover-tilt-position-card-feature"; +import "../card-features/hui-date-set-card-feature"; import "../card-features/hui-fan-direction-card-feature"; import "../card-features/hui-fan-preset-modes-card-feature"; import "../card-features/hui-fan-speed-card-feature"; @@ -53,6 +54,7 @@ const TYPES = new Set([ "cover-position", "cover-tilt-position", "cover-tilt", + "date-set", "fan-direction", "fan-preset-modes", "fan-speed", diff --git a/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts b/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts index 3b09645c39..678f04a9e7 100644 --- a/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts @@ -30,6 +30,7 @@ import { supportsCoverOpenCloseCardFeature } from "../../card-features/hui-cover import { supportsCoverPositionCardFeature } from "../../card-features/hui-cover-position-card-feature"; import { supportsCoverTiltCardFeature } from "../../card-features/hui-cover-tilt-card-feature"; import { supportsCoverTiltPositionCardFeature } from "../../card-features/hui-cover-tilt-position-card-feature"; +import { supportsDateSetCardFeature } from "../../card-features/hui-date-set-card-feature"; import { supportsFanDirectionCardFeature } from "../../card-features/hui-fan-direction-card-feature"; import { supportsFanPresetModesCardFeature } from "../../card-features/hui-fan-preset-modes-card-feature"; import { supportsFanSpeedCardFeature } from "../../card-features/hui-fan-speed-card-feature"; @@ -78,6 +79,7 @@ const UI_FEATURE_TYPES = [ "cover-position", "cover-tilt-position", "cover-tilt", + "date-set", "fan-direction", "fan-preset-modes", "fan-speed", @@ -141,6 +143,7 @@ const SUPPORTS_FEATURE_TYPES: Record< "cover-position": supportsCoverPositionCardFeature, "cover-tilt-position": supportsCoverTiltPositionCardFeature, "cover-tilt": supportsCoverTiltCardFeature, + "date-set": supportsDateSetCardFeature, "fan-direction": supportsFanDirectionCardFeature, "fan-preset-modes": supportsFanPresetModesCardFeature, "fan-speed": supportsFanSpeedCardFeature, diff --git a/src/translations/en.json b/src/translations/en.json index fa899bfa8a..e5d394505b 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -147,6 +147,9 @@ "close_tilt_cover": "Close cover tilt", "stop_cover": "Stop cover" }, + "date": { + "set_date": "Set date" + }, "fan": { "preset_mode": "Preset mode", "oscillate": "Oscillate", @@ -7861,6 +7864,9 @@ "cover-tilt-position": { "label": "Cover tilt position" }, + "date-set": { + "label": "Set date" + }, "fan-direction": { "label": "Fan direction" },