Add sensor offset to time trigger UI (#21957)

* Add sensor offset to time trigger UI

* refactor long expression

* memoize data

* fix for trigger platform migration
This commit is contained in:
karwosts 2024-10-11 12:36:44 -07:00 committed by GitHub
parent 82b50a1c5d
commit 79c71cbe48
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 120 additions and 52 deletions

View File

@ -167,7 +167,7 @@ export interface TagTrigger extends BaseTrigger {
export interface TimeTrigger extends BaseTrigger { export interface TimeTrigger extends BaseTrigger {
trigger: "time"; trigger: "time";
at: string; at: string | { entity_id: string; offset?: string };
} }
export interface TemplateTrigger extends BaseTrigger { export interface TemplateTrigger extends BaseTrigger {

View File

@ -8,6 +8,7 @@ import {
import secondsToDuration from "../common/datetime/seconds_to_duration"; import secondsToDuration from "../common/datetime/seconds_to_duration";
import { computeAttributeNameDisplay } from "../common/entity/compute_attribute_display"; import { computeAttributeNameDisplay } from "../common/entity/compute_attribute_display";
import { computeStateName } from "../common/entity/compute_state_name"; import { computeStateName } from "../common/entity/compute_state_name";
import { isValidEntityId } from "../common/entity/valid_entity_id";
import type { HomeAssistant } from "../types"; import type { HomeAssistant } from "../types";
import { Condition, ForDict, Trigger } from "./automation"; import { Condition, ForDict, Trigger } from "./automation";
import { import {
@ -371,13 +372,22 @@ const tryDescribeTrigger = (
// Time Trigger // Time Trigger
if (trigger.trigger === "time" && trigger.at) { if (trigger.trigger === "time" && trigger.at) {
const result = ensureArray(trigger.at).map((at) => const result = ensureArray(trigger.at).map((at) => {
typeof at !== "string" if (typeof at === "string") {
? at if (isValidEntityId(at)) {
: at.includes(".") return `entity ${hass.states[at] ? computeStateName(hass.states[at]) : at}`;
? `entity ${hass.states[at] ? computeStateName(hass.states[at]) : at}` }
: localizeTimeString(at, hass.locale, hass.config) return localizeTimeString(at, hass.locale, hass.config);
); }
const entityStr = `entity ${hass.states[at.entity_id] ? computeStateName(hass.states[at.entity_id]) : at.entity_id}`;
const offsetStr = at.offset
? " " +
hass.localize(`${triggerTranslationBaseKey}.time.offset_by`, {
offset: describeDuration(hass.locale, at.offset),
})
: "";
return `${entityStr}${offsetStr}`;
});
return hass.localize(`${triggerTranslationBaseKey}.time.description.full`, { return hass.localize(`${triggerTranslationBaseKey}.time.description.full`, {
time: formatListWithOrs(hass.locale, result), time: formatListWithOrs(hass.locale, result),

View File

@ -9,6 +9,9 @@ import type { TimeTrigger } from "../../../../../data/automation";
import type { HomeAssistant } from "../../../../../types"; import type { HomeAssistant } from "../../../../../types";
import type { TriggerElement } from "../ha-automation-trigger-row"; import type { TriggerElement } from "../ha-automation-trigger-row";
const MODE_TIME = "time";
const MODE_ENTITY = "entity";
@customElement("ha-automation-trigger-time") @customElement("ha-automation-trigger-time")
export class HaTimeTrigger extends LitElement implements TriggerElement { export class HaTimeTrigger extends LitElement implements TriggerElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@ -17,48 +20,60 @@ export class HaTimeTrigger extends LitElement implements TriggerElement {
@property({ type: Boolean }) public disabled = false; @property({ type: Boolean }) public disabled = false;
@state() private _inputMode?: boolean; @state() private _inputMode:
| undefined
| typeof MODE_TIME
| typeof MODE_ENTITY;
public static get defaultConfig(): TimeTrigger { public static get defaultConfig(): TimeTrigger {
return { trigger: "time", at: "" }; return { trigger: "time", at: "" };
} }
private _schema = memoizeOne( private _schema = memoizeOne(
(localize: LocalizeFunc, inputMode?: boolean) => { (
const atSelector = inputMode localize: LocalizeFunc,
? { inputMode: typeof MODE_TIME | typeof MODE_ENTITY,
entity: { showOffset: boolean
filter: [ ) =>
{ domain: "input_datetime" }, [
{ domain: "sensor", device_class: "timestamp" },
],
},
}
: { time: {} };
return [
{ {
name: "mode", name: "mode",
type: "select", type: "select",
required: true, required: true,
options: [ options: [
[ [
"value", MODE_TIME,
localize( localize(
"ui.panel.config.automation.editor.triggers.type.time.type_value" "ui.panel.config.automation.editor.triggers.type.time.type_value"
), ),
], ],
[ [
"input", MODE_ENTITY,
localize( localize(
"ui.panel.config.automation.editor.triggers.type.time.type_input" "ui.panel.config.automation.editor.triggers.type.time.type_input"
), ),
], ],
], ],
}, },
{ name: "at", selector: atSelector }, ...(inputMode === MODE_TIME
] as const; ? ([{ name: "time", selector: { time: {} } }] as const)
} : ([
{
name: "entity",
selector: {
entity: {
filter: [
{ domain: "input_datetime" },
{ domain: "sensor", device_class: "timestamp" },
],
},
},
},
] as const)),
...(showOffset
? ([{ name: "offset", selector: { text: {} } }] as const)
: ([] as const)),
] as const
); );
public willUpdate(changedProperties: PropertyValues) { public willUpdate(changedProperties: PropertyValues) {
@ -75,23 +90,46 @@ export class HaTimeTrigger extends LitElement implements TriggerElement {
} }
} }
private _data = memoizeOne(
(
inputMode: undefined | typeof MODE_ENTITY | typeof MODE_TIME,
at:
| string
| { entity_id: string | undefined; offset?: string | undefined }
): {
mode: typeof MODE_TIME | typeof MODE_ENTITY;
entity: string | undefined;
time: string | undefined;
offset: string | undefined;
} => {
const entity =
typeof at === "object"
? at.entity_id
: at?.startsWith("input_datetime.") || at?.startsWith("sensor.")
? at
: undefined;
const time = entity ? undefined : (at as string | undefined);
const offset = typeof at === "object" ? at.offset : undefined;
const mode = inputMode ?? (entity ? MODE_ENTITY : MODE_TIME);
return {
mode,
entity,
time,
offset,
};
}
);
protected render() { protected render() {
const at = this.trigger.at; const at = this.trigger.at;
if (Array.isArray(at)) { if (Array.isArray(at)) {
return nothing; return nothing;
} }
const data = this._data(this._inputMode, at);
const inputMode = const showOffset =
this._inputMode ?? data.mode === MODE_ENTITY && data.entity?.startsWith("sensor.");
(at?.startsWith("input_datetime.") || at?.startsWith("sensor.")); const schema = this._schema(this.hass.localize, data.mode, !!showOffset);
const schema = this._schema(this.hass.localize, inputMode);
const data = {
mode: inputMode ? "input" : "value",
...this.trigger,
};
return html` return html`
<ha-form <ha-form
@ -107,26 +145,43 @@ export class HaTimeTrigger extends LitElement implements TriggerElement {
private _valueChanged(ev: CustomEvent): void { private _valueChanged(ev: CustomEvent): void {
ev.stopPropagation(); ev.stopPropagation();
const newValue = ev.detail.value; const newValue = { ...ev.detail.value };
this._inputMode = newValue.mode;
this._inputMode = newValue.mode === "input"; if (newValue.mode === MODE_TIME) {
delete newValue.mode; delete newValue.entity;
delete newValue.offset;
Object.keys(newValue).forEach((key) => } else {
newValue[key] === undefined || newValue[key] === "" delete newValue.time;
? delete newValue[key] if (!newValue.entity?.startsWith("sensor.")) {
: {} delete newValue.offset;
); }
}
fireEvent(this, "value-changed", { value: newValue }); fireEvent(this, "value-changed", {
value: {
...this.trigger,
at: newValue.offset
? {
entity_id: newValue.entity,
offset: newValue.offset,
}
: newValue.entity || newValue.time,
},
});
} }
private _computeLabelCallback = ( private _computeLabelCallback = (
schema: SchemaUnion<ReturnType<typeof this._schema>> schema: SchemaUnion<ReturnType<typeof this._schema>>
): string => ): string => {
this.hass.localize( switch (schema.name) {
case "time":
return this.hass.localize(
`ui.panel.config.automation.editor.triggers.type.time.at`
);
}
return this.hass.localize(
`ui.panel.config.automation.editor.triggers.type.time.${schema.name}` `ui.panel.config.automation.editor.triggers.type.time.${schema.name}`
); );
};
} }
declare global { declare global {

View File

@ -3054,6 +3054,9 @@
"type_input": "Value of a date/time helper or timestamp-class sensor", "type_input": "Value of a date/time helper or timestamp-class sensor",
"label": "Time", "label": "Time",
"at": "At time", "at": "At time",
"offset": "[%key:ui::panel::config::automation::editor::triggers::type::sun::offset%]",
"entity": "Entity with timestamp",
"offset_by": "offset by {offset}",
"mode": "Mode", "mode": "Mode",
"description": { "description": {
"picker": "At a specific time, or on a specific date.", "picker": "At a specific time, or on a specific date.",