mirror of
https://github.com/home-assistant/frontend.git
synced 2025-12-07 16:47:20 +00:00
499 lines
15 KiB
TypeScript
499 lines
15 KiB
TypeScript
import { ensureArray } from "../common/array/ensure-array";
|
|
import { formatNumericDuration } from "../common/datetime/format_duration";
|
|
import secondsToDuration from "../common/datetime/seconds_to_duration";
|
|
import { computeDeviceNameDisplay } from "../common/entity/compute_device_name";
|
|
import { computeStateName } from "../common/entity/compute_state_name";
|
|
import { formatListWithAnds } from "../common/string/format-list";
|
|
import { isTemplate } from "../common/string/has-template";
|
|
import type { HomeAssistant } from "../types";
|
|
import type { Condition } from "./automation";
|
|
import { describeCondition } from "./automation_i18n";
|
|
import { localizeDeviceAutomationAction } from "./device_automation";
|
|
import type { EntityRegistryEntry } from "./entity_registry";
|
|
import {
|
|
computeEntityRegistryName,
|
|
entityRegistryById,
|
|
} from "./entity_registry";
|
|
import type { FloorRegistryEntry } from "./floor_registry";
|
|
import { domainToName } from "./integration";
|
|
import type { LabelRegistryEntry } from "./label_registry";
|
|
import type {
|
|
ActionType,
|
|
ActionTypes,
|
|
ChooseAction,
|
|
DelayAction,
|
|
DeviceAction,
|
|
EventAction,
|
|
IfAction,
|
|
ParallelAction,
|
|
RepeatAction,
|
|
SequenceAction,
|
|
SetConversationResponseAction,
|
|
StopAction,
|
|
VariablesAction,
|
|
WaitForTriggerAction,
|
|
} from "./script";
|
|
import { getActionType } from "./script";
|
|
|
|
const actionTranslationBaseKey =
|
|
"ui.panel.config.automation.editor.actions.type";
|
|
|
|
export const describeAction = <T extends ActionType>(
|
|
hass: HomeAssistant,
|
|
entityRegistry: EntityRegistryEntry[],
|
|
labelRegistry: LabelRegistryEntry[],
|
|
floorRegistry: Record<string, FloorRegistryEntry>,
|
|
action: ActionTypes[T],
|
|
actionType?: T,
|
|
ignoreAlias = false
|
|
): string => {
|
|
try {
|
|
const description = tryDescribeAction(
|
|
hass,
|
|
entityRegistry,
|
|
labelRegistry,
|
|
floorRegistry,
|
|
action,
|
|
actionType,
|
|
ignoreAlias
|
|
);
|
|
if (typeof description !== "string") {
|
|
throw new Error(String(description));
|
|
}
|
|
return description;
|
|
} catch (error: any) {
|
|
// eslint-disable-next-line no-console
|
|
console.error(error);
|
|
let msg = "Error in describing action";
|
|
if (error.message) {
|
|
msg += ": " + error.message;
|
|
}
|
|
return msg;
|
|
}
|
|
};
|
|
|
|
const tryDescribeAction = <T extends ActionType>(
|
|
hass: HomeAssistant,
|
|
entityRegistry: EntityRegistryEntry[],
|
|
labelRegistry: LabelRegistryEntry[],
|
|
floorRegistry: Record<string, FloorRegistryEntry>,
|
|
action: ActionTypes[T],
|
|
actionType?: T,
|
|
ignoreAlias = false
|
|
): string => {
|
|
if (action.alias && !ignoreAlias) {
|
|
return action.alias;
|
|
}
|
|
if (!actionType) {
|
|
actionType = getActionType(action) as T;
|
|
}
|
|
|
|
if (actionType === "service") {
|
|
const config = action as ActionTypes["service"];
|
|
|
|
const targets: string[] = [];
|
|
const targetOrData = config.target || config.data;
|
|
if (typeof targetOrData === "string" && isTemplate(targetOrData)) {
|
|
targets.push(
|
|
hass.localize(
|
|
`${actionTranslationBaseKey}.service.description.target_template`,
|
|
{ name: "target" }
|
|
)
|
|
);
|
|
} else if (targetOrData) {
|
|
for (const [key, name] of Object.entries({
|
|
area_id: "areas",
|
|
device_id: "devices",
|
|
entity_id: "entities",
|
|
floor_id: "floors",
|
|
label_id: "labels",
|
|
})) {
|
|
if (!(key in targetOrData)) {
|
|
continue;
|
|
}
|
|
const keyConf: string[] = ensureArray(targetOrData[key]) || [];
|
|
|
|
for (const targetThing of keyConf) {
|
|
if (isTemplate(targetThing)) {
|
|
targets.push(
|
|
hass.localize(
|
|
`${actionTranslationBaseKey}.service.description.target_template`,
|
|
{ name }
|
|
)
|
|
);
|
|
break;
|
|
} else if (key === "entity_id") {
|
|
if (targetThing.includes(".")) {
|
|
const state = hass.states[targetThing];
|
|
if (state) {
|
|
targets.push(computeStateName(state));
|
|
} else {
|
|
targets.push(targetThing);
|
|
}
|
|
} else {
|
|
const entityReg = entityRegistryById(entityRegistry)[targetThing];
|
|
if (entityReg) {
|
|
targets.push(
|
|
computeEntityRegistryName(hass, entityReg) || targetThing
|
|
);
|
|
} else if (targetThing === "all") {
|
|
targets.push(
|
|
hass.localize(
|
|
`${actionTranslationBaseKey}.service.description.target_every_entity`
|
|
)
|
|
);
|
|
} else {
|
|
targets.push(
|
|
hass.localize(
|
|
`${actionTranslationBaseKey}.service.description.target_unknown_entity`
|
|
)
|
|
);
|
|
}
|
|
}
|
|
} else if (key === "device_id") {
|
|
const device = hass.devices[targetThing];
|
|
if (device) {
|
|
targets.push(computeDeviceNameDisplay(device, hass));
|
|
} else {
|
|
targets.push(
|
|
hass.localize(
|
|
`${actionTranslationBaseKey}.service.description.target_unknown_device`
|
|
)
|
|
);
|
|
}
|
|
} else if (key === "area_id") {
|
|
const area = hass.areas[targetThing];
|
|
if (area?.name) {
|
|
targets.push(area.name);
|
|
} else {
|
|
targets.push(
|
|
hass.localize(
|
|
`${actionTranslationBaseKey}.service.description.target_unknown_area`
|
|
)
|
|
);
|
|
}
|
|
} else if (key === "floor_id") {
|
|
const floor = floorRegistry[targetThing] ?? undefined;
|
|
if (floor?.name) {
|
|
targets.push(floor.name);
|
|
} else {
|
|
targets.push(
|
|
hass.localize(
|
|
`${actionTranslationBaseKey}.service.description.target_unknown_floor`
|
|
)
|
|
);
|
|
}
|
|
} else if (key === "label_id") {
|
|
const label = labelRegistry.find(
|
|
(lbl) => lbl.label_id === targetThing
|
|
);
|
|
if (label?.name) {
|
|
targets.push(label.name);
|
|
} else {
|
|
targets.push(
|
|
hass.localize(
|
|
`${actionTranslationBaseKey}.service.description.target_unknown_label`
|
|
)
|
|
);
|
|
}
|
|
} else {
|
|
targets.push(targetThing);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (
|
|
config.service_template ||
|
|
(config.action && isTemplate(config.action))
|
|
) {
|
|
return hass.localize(
|
|
targets.length
|
|
? `${actionTranslationBaseKey}.service.description.service_based_on_template`
|
|
: `${actionTranslationBaseKey}.service.description.service_based_on_template_no_targets`,
|
|
{
|
|
targets: formatListWithAnds(hass.locale, targets),
|
|
}
|
|
);
|
|
}
|
|
|
|
if (config.action) {
|
|
const [domain, serviceName] = config.action.split(".", 2);
|
|
const descriptionPlaceholders =
|
|
hass.services[domain]?.[serviceName]?.description_placeholders;
|
|
const service =
|
|
hass.localize(
|
|
`component.${domain}.services.${serviceName}.name`,
|
|
descriptionPlaceholders
|
|
) || hass.services[domain]?.[serviceName]?.name;
|
|
|
|
if (config.metadata) {
|
|
return hass.localize(
|
|
targets.length
|
|
? `${actionTranslationBaseKey}.service.description.service_name`
|
|
: `${actionTranslationBaseKey}.service.description.service_name_no_targets`,
|
|
{
|
|
domain: domainToName(hass.localize, domain),
|
|
name: service || config.action,
|
|
targets: formatListWithAnds(hass.locale, targets),
|
|
}
|
|
);
|
|
}
|
|
|
|
return hass.localize(
|
|
targets.length
|
|
? `${actionTranslationBaseKey}.service.description.service_based_on_name`
|
|
: `${actionTranslationBaseKey}.service.description.service_based_on_name_no_targets`,
|
|
{
|
|
name: service
|
|
? `${domainToName(hass.localize, domain)}: ${service}`
|
|
: config.action,
|
|
targets: formatListWithAnds(hass.locale, targets),
|
|
}
|
|
);
|
|
}
|
|
return hass.localize(
|
|
`${actionTranslationBaseKey}.service.description.service`
|
|
);
|
|
}
|
|
|
|
if (actionType === "delay") {
|
|
const config = action as DelayAction;
|
|
|
|
let duration: string;
|
|
if (typeof config.delay === "number") {
|
|
duration = hass.localize(
|
|
`${actionTranslationBaseKey}.delay.description.duration_string`,
|
|
{
|
|
string: secondsToDuration(config.delay)!,
|
|
}
|
|
);
|
|
} else if (typeof config.delay === "string") {
|
|
duration = isTemplate(config.delay)
|
|
? hass.localize(
|
|
`${actionTranslationBaseKey}.delay.description.duration_template`
|
|
)
|
|
: hass.localize(
|
|
`${actionTranslationBaseKey}.delay.description.duration_string`,
|
|
{
|
|
string:
|
|
config.delay ||
|
|
hass.localize(
|
|
`${actionTranslationBaseKey}.delay.description.duration_unknown`
|
|
),
|
|
}
|
|
);
|
|
} else if (config.delay) {
|
|
duration = hass.localize(
|
|
`${actionTranslationBaseKey}.delay.description.duration_string`,
|
|
{
|
|
string: formatNumericDuration(hass.locale, config.delay),
|
|
}
|
|
);
|
|
} else {
|
|
duration = hass.localize(
|
|
`${actionTranslationBaseKey}.delay.description.duration_string`,
|
|
{
|
|
string: hass.localize(
|
|
`${actionTranslationBaseKey}.delay.description.duration_unknown`
|
|
),
|
|
}
|
|
);
|
|
}
|
|
|
|
return hass.localize(`${actionTranslationBaseKey}.delay.description.full`, {
|
|
duration: duration,
|
|
});
|
|
}
|
|
|
|
if (actionType === "wait_for_trigger") {
|
|
const config = action as WaitForTriggerAction;
|
|
const triggers = ensureArray(config.wait_for_trigger);
|
|
if (!triggers || triggers.length === 0) {
|
|
return hass.localize(
|
|
`${actionTranslationBaseKey}.wait_for_trigger.description.wait_for_a_trigger`
|
|
);
|
|
}
|
|
return hass.localize(
|
|
`${actionTranslationBaseKey}.wait_for_trigger.description.wait_for_triggers`,
|
|
{ count: triggers.length }
|
|
);
|
|
}
|
|
|
|
if (actionType === "variables") {
|
|
const config = action as VariablesAction;
|
|
return hass.localize(
|
|
`${actionTranslationBaseKey}.variables.description.full`,
|
|
{
|
|
names: formatListWithAnds(hass.locale, Object.keys(config.variables)),
|
|
}
|
|
);
|
|
}
|
|
|
|
if (actionType === "fire_event") {
|
|
const config = action as EventAction;
|
|
if (isTemplate(config.event)) {
|
|
return hass.localize(
|
|
`${actionTranslationBaseKey}.event.description.full`,
|
|
{
|
|
name: hass.localize(
|
|
`${actionTranslationBaseKey}.event.description.template`
|
|
),
|
|
}
|
|
);
|
|
}
|
|
return hass.localize(`${actionTranslationBaseKey}.event.description.full`, {
|
|
name: config.event,
|
|
});
|
|
}
|
|
|
|
if (actionType === "wait_template") {
|
|
return hass.localize(
|
|
`${actionTranslationBaseKey}.wait_template.description.full`
|
|
);
|
|
}
|
|
|
|
if (actionType === "stop") {
|
|
const config = action as StopAction;
|
|
return hass.localize(`${actionTranslationBaseKey}.stop.description.full`, {
|
|
hasReason: config.stop !== undefined ? "true" : "false",
|
|
reason: config.stop,
|
|
});
|
|
}
|
|
|
|
if (actionType === "if") {
|
|
const config = action as IfAction;
|
|
|
|
if (config.else !== undefined) {
|
|
return hass.localize(
|
|
`${actionTranslationBaseKey}.if.description.if_else`
|
|
);
|
|
}
|
|
|
|
return hass.localize(`${actionTranslationBaseKey}.if.description.if`);
|
|
}
|
|
|
|
if (actionType === "choose") {
|
|
const config = action as ChooseAction;
|
|
if (config.choose) {
|
|
const numActions =
|
|
ensureArray(config.choose).length + (config.default ? 1 : 0);
|
|
return hass.localize(
|
|
`${actionTranslationBaseKey}.choose.description.full`,
|
|
{ number: numActions }
|
|
);
|
|
}
|
|
return hass.localize(
|
|
`${actionTranslationBaseKey}.choose.description.no_action`
|
|
);
|
|
}
|
|
|
|
if (actionType === "repeat") {
|
|
const config = action as RepeatAction;
|
|
|
|
let chosenAction = "";
|
|
if ("count" in config.repeat) {
|
|
const count = config.repeat.count;
|
|
chosenAction = hass.localize(
|
|
`${actionTranslationBaseKey}.repeat.description.count`,
|
|
{ count: count }
|
|
);
|
|
} else if ("while" in config.repeat) {
|
|
const conditions = ensureArray(config.repeat.while);
|
|
chosenAction = hass.localize(
|
|
`${actionTranslationBaseKey}.repeat.description.while_count`,
|
|
{ count: conditions.length }
|
|
);
|
|
} else if ("until" in config.repeat) {
|
|
const conditions = ensureArray(config.repeat.until);
|
|
chosenAction = hass.localize(
|
|
`${actionTranslationBaseKey}.repeat.description.until_count`,
|
|
{ count: conditions.length }
|
|
);
|
|
} else if ("for_each" in config.repeat) {
|
|
const items = ensureArray(config.repeat.for_each).map((item) =>
|
|
JSON.stringify(item)
|
|
);
|
|
chosenAction = hass.localize(
|
|
`${actionTranslationBaseKey}.repeat.description.for_each`,
|
|
{ items: formatListWithAnds(hass.locale, items) }
|
|
);
|
|
}
|
|
return hass.localize(
|
|
`${actionTranslationBaseKey}.repeat.description.full`,
|
|
{ chosenAction: chosenAction }
|
|
);
|
|
}
|
|
|
|
if (actionType === "check_condition") {
|
|
return hass.localize(
|
|
`${actionTranslationBaseKey}.check_condition.description.full`,
|
|
{
|
|
condition: describeCondition(action as Condition, hass, entityRegistry),
|
|
}
|
|
);
|
|
}
|
|
|
|
if (actionType === "device_action") {
|
|
const config = action as DeviceAction;
|
|
if (!config.device_id) {
|
|
return hass.localize(
|
|
`${actionTranslationBaseKey}.device_id.description.no_device`
|
|
);
|
|
}
|
|
const localized = localizeDeviceAutomationAction(
|
|
hass,
|
|
entityRegistry,
|
|
config
|
|
);
|
|
if (localized) {
|
|
return localized;
|
|
}
|
|
const stateObj = hass.states[config.entity_id];
|
|
if (config.type) {
|
|
return `${config.type} ${
|
|
stateObj ? computeStateName(stateObj) : config.entity_id
|
|
}`;
|
|
}
|
|
return hass.localize(
|
|
`${actionTranslationBaseKey}.device_id.description.perform_device_action`,
|
|
{
|
|
device: stateObj ? computeStateName(stateObj) : config.entity_id,
|
|
}
|
|
);
|
|
}
|
|
|
|
if (actionType === "sequence") {
|
|
const config = action as SequenceAction;
|
|
const numActions = ensureArray(config.sequence).length;
|
|
return hass.localize(
|
|
`${actionTranslationBaseKey}.sequence.description.full`,
|
|
{ number: numActions }
|
|
);
|
|
}
|
|
|
|
if (actionType === "parallel") {
|
|
const config = action as ParallelAction;
|
|
const numActions = ensureArray(config.parallel).length;
|
|
return hass.localize(
|
|
`${actionTranslationBaseKey}.parallel.description.full`,
|
|
{ number: numActions }
|
|
);
|
|
}
|
|
|
|
if (actionType === "set_conversation_response") {
|
|
const config = action as SetConversationResponseAction;
|
|
if (isTemplate(config.set_conversation_response)) {
|
|
return hass.localize(
|
|
`${actionTranslationBaseKey}.set_conversation_response.description.template`
|
|
);
|
|
}
|
|
return hass.localize(
|
|
`${actionTranslationBaseKey}.set_conversation_response.description.full`,
|
|
{ response: config.set_conversation_response }
|
|
);
|
|
}
|
|
|
|
return actionType;
|
|
};
|