diff --git a/src/data/automation.ts b/src/data/automation.ts index 49d23f069f08..a85089bff0ff 100644 --- a/src/data/automation.ts +++ b/src/data/automation.ts @@ -256,6 +256,13 @@ export interface ZoneCondition extends BaseCondition { type Weekday = "sun" | "mon" | "tue" | "wed" | "thu" | "fri" | "sat"; +export interface DateCondition extends BaseCondition { + condition: "date"; + after?: string; + before?: string; + weekday?: Weekday | Weekday[]; +} + export interface TimeCondition extends BaseCondition { condition: "time"; after?: string; @@ -263,6 +270,13 @@ export interface TimeCondition extends BaseCondition { weekday?: Weekday | Weekday[]; } +export interface DatetimeCondition extends BaseCondition { + condition: "datetime"; + after?: string; + before?: string; + weekday?: Weekday | Weekday[]; +} + export interface TemplateCondition extends BaseCondition { condition: "template"; value_template: string; @@ -300,7 +314,9 @@ export type Condition = | NumericStateCondition | SunCondition | ZoneCondition + | DateCondition | TimeCondition + | DatetimeCondition | TemplateCondition | DeviceCondition | LogicalCondition diff --git a/src/panels/config/automation/condition/ha-automation-condition-editor.ts b/src/panels/config/automation/condition/ha-automation-condition-editor.ts index a4734a1e8249..81346e699a02 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-editor.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-editor.ts @@ -9,6 +9,8 @@ import { expandConditionWithShorthand } from "../../../../data/automation"; import { haStyle } from "../../../../resources/styles"; import type { HomeAssistant } from "../../../../types"; import "./types/ha-automation-condition-and"; +import "./types/ha-automation-condition-date"; +import "./types/ha-automation-condition-datetime"; import "./types/ha-automation-condition-device"; import "./types/ha-automation-condition-not"; import "./types/ha-automation-condition-numeric_state"; diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-date.ts b/src/panels/config/automation/condition/types/ha-automation-condition-date.ts new file mode 100644 index 000000000000..95e9166dc355 --- /dev/null +++ b/src/panels/config/automation/condition/types/ha-automation-condition-date.ts @@ -0,0 +1,193 @@ +import { html, LitElement } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; +import { firstWeekdayIndex } from "../../../../../common/datetime/first_weekday"; +import { fireEvent } from "../../../../../common/dom/fire_event"; +import type { LocalizeFunc } from "../../../../../common/translations/localize"; +import "../../../../../components/ha-form/ha-form"; +import type { SchemaUnion } from "../../../../../components/ha-form/types"; +import type { DateCondition } from "../../../../../data/automation"; +import type { FrontendLocaleData } from "../../../../../data/translation"; +import type { HomeAssistant } from "../../../../../types"; +import type { ConditionElement } from "../ha-automation-condition-row"; + +const DAYS = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"] as const; + +@customElement("ha-automation-condition-date") +export class HaDateCondition extends LitElement implements ConditionElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public condition!: DateCondition; + + @state() private _inputModeBefore?: boolean; + + @state() private _inputModeAfter?: boolean; + + @property({ type: Boolean }) public disabled = false; + + public static get defaultConfig(): DateCondition { + return { condition: "date" }; + } + + private _schema = memoizeOne( + ( + localize: LocalizeFunc, + locale: FrontendLocaleData, + inputModeAfter?: boolean, + inputModeBefore?: boolean + ) => { + const dayIndex = firstWeekdayIndex(locale); + const sortedDays = DAYS.slice(dayIndex, DAYS.length).concat( + DAYS.slice(0, dayIndex) + ); + return [ + { + name: "mode_after", + type: "select", + required: true, + options: [ + [ + "value", + localize( + "ui.panel.config.automation.editor.conditions.type.date.type_value" + ), + ], + [ + "input", + localize( + "ui.panel.config.automation.editor.conditions.type.date.type_input" + ), + ], + ], + }, + { + name: "after", + selector: inputModeAfter + ? { + entity: { + filter: [ + { domain: "input_datetime" }, + { domain: "sensor", device_class: "timestamp" }, + ], + }, + } + : { date: {} }, + }, + { + name: "mode_before", + type: "select", + required: true, + options: [ + [ + "value", + localize( + "ui.panel.config.automation.editor.conditions.type.date.type_value" + ), + ], + [ + "input", + localize( + "ui.panel.config.automation.editor.conditions.type.date.type_input" + ), + ], + ], + }, + { + name: "before", + selector: inputModeBefore + ? { + entity: { + filter: [ + { domain: "input_datetime" }, + { domain: "sensor", device_class: "timestamp" }, + ], + }, + } + : { date: {} }, + }, + { + type: "multi_select", + name: "weekday", + options: sortedDays.map( + (day) => + [ + day, + localize( + `ui.panel.config.automation.editor.conditions.type.date.weekdays.${day}` + ), + ] as const + ), + }, + ] as const; + } + ); + + protected render() { + const inputModeBefore = + this._inputModeBefore ?? + (this.condition.before?.startsWith("input_datetime.") || + this.condition.before?.startsWith("sensor.")); + const inputModeAfter = + this._inputModeAfter ?? + (this.condition.after?.startsWith("input_datetime.") || + this.condition.after?.startsWith("sensor.")); + + const schema = this._schema( + this.hass.localize, + this.hass.locale, + inputModeAfter, + inputModeBefore + ); + + const data = { + mode_before: inputModeBefore ? "input" : "value", + mode_after: inputModeAfter ? "input" : "value", + ...this.condition, + }; + + return html` + + `; + } + + private _valueChanged(ev: CustomEvent): void { + ev.stopPropagation(); + const newValue = ev.detail.value; + + this._inputModeAfter = newValue.mode_after === "input"; + this._inputModeBefore = newValue.mode_before === "input"; + + delete newValue.mode_after; + delete newValue.mode_before; + + Object.keys(newValue).forEach((key) => + newValue[key] === undefined || + newValue[key] === "" || + (Array.isArray(newValue[key]) && newValue[key].length === 0) + ? delete newValue[key] + : {} + ); + + fireEvent(this, "value-changed", { value: newValue }); + } + + private _computeLabelCallback = ( + schema: SchemaUnion> + ): string => + this.hass.localize( + `ui.panel.config.automation.editor.conditions.type.date.${schema.name}` + ); +} + +declare global { + interface HTMLElementTagNameMap { + "ha-automation-condition-date": HaDateCondition; + } +} diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-datetime.ts b/src/panels/config/automation/condition/types/ha-automation-condition-datetime.ts new file mode 100644 index 000000000000..efe417255685 --- /dev/null +++ b/src/panels/config/automation/condition/types/ha-automation-condition-datetime.ts @@ -0,0 +1,196 @@ +import { html, LitElement } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; +import { firstWeekdayIndex } from "../../../../../common/datetime/first_weekday"; +import { fireEvent } from "../../../../../common/dom/fire_event"; +import type { LocalizeFunc } from "../../../../../common/translations/localize"; +import "../../../../../components/ha-form/ha-form"; +import type { SchemaUnion } from "../../../../../components/ha-form/types"; +import type { DatetimeCondition } from "../../../../../data/automation"; +import type { FrontendLocaleData } from "../../../../../data/translation"; +import type { HomeAssistant } from "../../../../../types"; +import type { ConditionElement } from "../ha-automation-condition-row"; + +const DAYS = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"] as const; + +@customElement("ha-automation-condition-datetime") +export class HaDatetimeCondition + extends LitElement + implements ConditionElement +{ + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public condition!: DatetimeCondition; + + @state() private _inputModeBefore?: boolean; + + @state() private _inputModeAfter?: boolean; + + @property({ type: Boolean }) public disabled = false; + + public static get defaultConfig(): DatetimeCondition { + return { condition: "datetime" }; + } + + private _schema = memoizeOne( + ( + localize: LocalizeFunc, + locale: FrontendLocaleData, + inputModeAfter?: boolean, + inputModeBefore?: boolean + ) => { + const dayIndex = firstWeekdayIndex(locale); + const sortedDays = DAYS.slice(dayIndex, DAYS.length).concat( + DAYS.slice(0, dayIndex) + ); + return [ + { + name: "mode_after", + type: "select", + required: true, + options: [ + [ + "value", + localize( + "ui.panel.config.automation.editor.conditions.type.datetime.type_value" + ), + ], + [ + "input", + localize( + "ui.panel.config.automation.editor.conditions.type.datetime.type_input" + ), + ], + ], + }, + { + name: "after", + selector: inputModeAfter + ? { + entity: { + filter: [ + { domain: "input_datetime" }, + { domain: "sensor", device_class: "timestamp" }, + ], + }, + } + : { datetime: {} }, + }, + { + name: "mode_before", + type: "select", + required: true, + options: [ + [ + "value", + localize( + "ui.panel.config.automation.editor.conditions.type.datetime.type_value" + ), + ], + [ + "input", + localize( + "ui.panel.config.automation.editor.conditions.type.datetime.type_input" + ), + ], + ], + }, + { + name: "before", + selector: inputModeBefore + ? { + entity: { + filter: [ + { domain: "input_datetime" }, + { domain: "sensor", device_class: "timestamp" }, + ], + }, + } + : { datetime: {} }, + }, + { + type: "multi_select", + name: "weekday", + options: sortedDays.map( + (day) => + [ + day, + localize( + `ui.panel.config.automation.editor.conditions.type.datetime.weekdays.${day}` + ), + ] as const + ), + }, + ] as const; + } + ); + + protected render() { + const inputModeBefore = + this._inputModeBefore ?? + (this.condition.before?.startsWith("input_datetime.") || + this.condition.before?.startsWith("sensor.")); + const inputModeAfter = + this._inputModeAfter ?? + (this.condition.after?.startsWith("input_datetime.") || + this.condition.after?.startsWith("sensor.")); + + const schema = this._schema( + this.hass.localize, + this.hass.locale, + inputModeAfter, + inputModeBefore + ); + + const data = { + mode_before: inputModeBefore ? "input" : "value", + mode_after: inputModeAfter ? "input" : "value", + ...this.condition, + }; + + return html` + + `; + } + + private _valueChanged(ev: CustomEvent): void { + ev.stopPropagation(); + const newValue = ev.detail.value; + + this._inputModeAfter = newValue.mode_after === "input"; + this._inputModeBefore = newValue.mode_before === "input"; + + delete newValue.mode_after; + delete newValue.mode_before; + + Object.keys(newValue).forEach((key) => + newValue[key] === undefined || + newValue[key] === "" || + (Array.isArray(newValue[key]) && newValue[key].length === 0) + ? delete newValue[key] + : {} + ); + + fireEvent(this, "value-changed", { value: newValue }); + } + + private _computeLabelCallback = ( + schema: SchemaUnion> + ): string => + this.hass.localize( + `ui.panel.config.automation.editor.conditions.type.datetime.${schema.name}` + ); +} + +declare global { + interface HTMLElementTagNameMap { + "ha-automation-condition-datetime": HaDatetimeCondition; + } +} diff --git a/src/translations/en.json b/src/translations/en.json index c658e7933818..e5e503ee03de 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3327,6 +3327,29 @@ "full": "If template renders a value equal to true" } }, + "date": { + "type_value": "[%key:ui::panel::config::automation::editor::triggers::type::time::type_value%]", + "type_input": "[%key:ui::panel::config::automation::editor::triggers::type::time::type_input%]", + "label": "Date", + "after": "After", + "before": "Before", + "weekday": "Weekdays", + "mode_after": "[%key:ui::panel::config::automation::editor::conditions::type::date::after%]", + "mode_before": "[%key:ui::panel::config::automation::editor::conditions::type::date::before%]", + "weekdays": { + "mon": "[%key:ui::weekdays::monday%]", + "tue": "[%key:ui::weekdays::tuesday%]", + "wed": "[%key:ui::weekdays::wednesday%]", + "thu": "[%key:ui::weekdays::thursday%]", + "fri": "[%key:ui::weekdays::friday%]", + "sat": "[%key:ui::weekdays::saturday%]", + "sun": "[%key:ui::weekdays::sunday%]" + }, + "description": { + "picker": "If the current date is before or after a specified date.", + "full": "Confirm the {hasTime, select, \n after {date is after {time_after}}\n before {date is before {date_before}}\n after_before {date is after {time_after} and before {date_before}} \n other {}\n }{hasTimeAndDay, select, \n true { and the }\n other {}\n}{hasDay, select, \n true { day is {day}}\n other {}\n}" + } + }, "time": { "type_value": "[%key:ui::panel::config::automation::editor::triggers::type::time::type_value%]", "type_input": "[%key:ui::panel::config::automation::editor::triggers::type::time::type_input%]", @@ -3337,19 +3360,42 @@ "mode_after": "[%key:ui::panel::config::automation::editor::conditions::type::time::after%]", "mode_before": "[%key:ui::panel::config::automation::editor::conditions::type::time::before%]", "weekdays": { - "mon": "Monday", - "tue": "Tuesday", - "wed": "Wednesday", - "thu": "Thursday", - "fri": "Friday", - "sat": "Saturday", - "sun": "Sunday" + "mon": "[%key:ui::weekdays::monday%]", + "tue": "[%key:ui::weekdays::tuesday%]", + "wed": "[%key:ui::weekdays::wednesday%]", + "thu": "[%key:ui::weekdays::thursday%]", + "fri": "[%key:ui::weekdays::friday%]", + "sat": "[%key:ui::weekdays::saturday%]", + "sun": "[%key:ui::weekdays::sunday%]" }, "description": { "picker": "If the current time is before or after a specified time.", "full": "If the {hasTime, select, \n after {time is after {time_after}}\n before {time is before {time_before}}\n after_before {time is after {time_after} and before {time_before}} \n other {}\n }{hasTimeAndDay, select, \n true { and the }\n other {}\n}{hasDay, select, \n true { day is {day}}\n other {}\n}" } }, + "datetime": { + "type_value": "[%key:ui::panel::config::automation::editor::triggers::type::time::type_value%]", + "type_input": "[%key:ui::panel::config::automation::editor::triggers::type::time::type_input%]", + "label": "Date and time", + "after": "After", + "before": "Before", + "weekday": "Weekdays", + "mode_after": "[%key:ui::panel::config::automation::editor::conditions::type::datetime::after%]", + "mode_before": "[%key:ui::panel::config::automation::editor::conditions::type::datetime::before%]", + "weekdays": { + "mon": "[%key:ui::weekdays::monday%]", + "tue": "[%key:ui::weekdays::tuesday%]", + "wed": "[%key:ui::weekdays::wednesday%]", + "thu": "[%key:ui::weekdays::thursday%]", + "fri": "[%key:ui::weekdays::friday%]", + "sat": "[%key:ui::weekdays::saturday%]", + "sun": "[%key:ui::weekdays::sunday%]" + }, + "description": { + "picker": "If the current datetime is before or after a specified datetime.", + "full": "Confirm the {hasTime, select, \n after {time is after {time_after}}\n before {time is before {time_before}}\n after_before {time is after {time_after} and before {time_before}} \n other {}\n }{hasTimeAndDay, select, \n true { and the }\n other {}\n}{hasDay, select, \n true { day is {day}}\n other {}\n}" + } + }, "trigger": { "label": "Triggered by", "no_triggers": "No triggers have an ID set. Use the three-dot menu on a trigger and select Edit ID to assign one.",