@@ -193,8 +184,6 @@ export class HaTracePathDetails extends LitElement {
${describeAction(
this.hass,
this._entityReg,
- this._labelReg,
- this._floorReg,
currentDetail
)}
`
diff --git a/src/components/trace/hat-trace-timeline.ts b/src/components/trace/hat-trace-timeline.ts
index 44c3df45ad..38a0bf7cb4 100644
--- a/src/components/trace/hat-trace-timeline.ts
+++ b/src/components/trace/hat-trace-timeline.ts
@@ -15,14 +15,8 @@ import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_tim
import { relativeTime } from "../../common/datetime/relative_time";
import { fireEvent } from "../../common/dom/fire_event";
import { toggleAttribute } from "../../common/dom/toggle_attribute";
-import {
- floorsContext,
- fullEntitiesContext,
- labelsContext,
-} from "../../data/context";
+import { fullEntitiesContext } from "../../data/context";
import type { EntityRegistryEntry } from "../../data/entity/entity_registry";
-import type { FloorRegistryEntry } from "../../data/floor_registry";
-import type { LabelRegistryEntry } from "../../data/label/label_registry";
import type { LogbookEntry } from "../../data/logbook";
import type {
ChooseAction,
@@ -197,8 +191,6 @@ class ActionRenderer {
constructor(
private hass: HomeAssistant,
private entityReg: EntityRegistryEntry[],
- private labelReg: LabelRegistryEntry[],
- private floorReg: Record
,
private entries: TemplateResult[],
private trace: AutomationTraceExtended,
private logbookRenderer: LogbookRenderer,
@@ -313,14 +305,7 @@ class ActionRenderer {
this._renderEntry(
path,
- describeAction(
- this.hass,
- this.entityReg,
- this.labelReg,
- this.floorReg,
- data,
- actionType
- ),
+ describeAction(this.hass, this.entityReg, data, actionType),
undefined,
data.enabled === false
);
@@ -485,13 +470,7 @@ class ActionRenderer {
const name =
repeatConfig.alias ||
- describeAction(
- this.hass,
- this.entityReg,
- this.labelReg,
- this.floorReg,
- repeatConfig
- );
+ describeAction(this.hass, this.entityReg, repeatConfig);
this._renderEntry(repeatPath, name, undefined, disabled);
@@ -585,14 +564,7 @@ class ActionRenderer {
this._renderEntry(
sequencePath,
sequenceConfig.alias ||
- describeAction(
- this.hass,
- this.entityReg,
- this.labelReg,
- this.floorReg,
- sequenceConfig,
- "sequence"
- ),
+ describeAction(this.hass, this.entityReg, sequenceConfig, "sequence"),
undefined,
sequenceConfig.enabled === false
);
@@ -683,14 +655,6 @@ export class HaAutomationTracer extends LitElement {
@consume({ context: fullEntitiesContext, subscribe: true })
_entityReg!: EntityRegistryEntry[];
- @state()
- @consume({ context: labelsContext, subscribe: true })
- _labelReg!: LabelRegistryEntry[];
-
- @state()
- @consume({ context: floorsContext, subscribe: true })
- _floorReg!: Record;
-
protected render() {
if (!this.trace) {
return nothing;
@@ -707,8 +671,6 @@ export class HaAutomationTracer extends LitElement {
const actionRenderer = new ActionRenderer(
this.hass,
this._entityReg,
- this._labelReg,
- this._floorReg,
entries,
this.trace,
logbookRenderer,
diff --git a/src/data/script_i18n.ts b/src/data/script_i18n.ts
index ecc1959ab9..b9c711f6f6 100644
--- a/src/data/script_i18n.ts
+++ b/src/data/script_i18n.ts
@@ -1,7 +1,6 @@
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";
@@ -10,13 +9,7 @@ import type { Condition } from "./automation";
import { describeCondition } from "./automation_i18n";
import { localizeDeviceAutomationAction } from "./device/device_automation";
import type { EntityRegistryEntry } from "./entity/entity_registry";
-import {
- computeEntityRegistryName,
- entityRegistryById,
-} from "./entity/entity_registry";
-import type { FloorRegistryEntry } from "./floor_registry";
import { domainToName } from "./integration";
-import type { LabelRegistryEntry } from "./label/label_registry";
import type {
ActionType,
ActionTypes,
@@ -41,8 +34,6 @@ const actionTranslationBaseKey =
export const describeAction = (
hass: HomeAssistant,
entityRegistry: EntityRegistryEntry[],
- labelRegistry: LabelRegistryEntry[],
- floorRegistry: Record,
action: ActionTypes[T],
actionType?: T,
ignoreAlias = false
@@ -51,8 +42,6 @@ export const describeAction = (
const description = tryDescribeAction(
hass,
entityRegistry,
- labelRegistry,
- floorRegistry,
action,
actionType,
ignoreAlias
@@ -75,8 +64,6 @@ export const describeAction = (
const tryDescribeAction = (
hass: HomeAssistant,
entityRegistry: EntityRegistryEntry[],
- labelRegistry: LabelRegistryEntry[],
- floorRegistry: Record,
action: ActionTypes[T],
actionType?: T,
ignoreAlias = false
@@ -100,107 +87,6 @@ const tryDescribeAction = (
{ 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 (
@@ -229,26 +115,20 @@ const tryDescribeAction = (
if (config.metadata) {
return hass.localize(
- targets.length
- ? `${actionTranslationBaseKey}.service.description.service_name`
- : `${actionTranslationBaseKey}.service.description.service_name_no_targets`,
+ `${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`,
+ `${actionTranslationBaseKey}.service.description.service_based_on_name_no_targets`,
{
name: service
? `${domainToName(hass.localize, domain)}: ${service}`
: config.action,
- targets: formatListWithAnds(hass.locale, targets),
}
);
}
diff --git a/src/panels/config/automation/action/ha-automation-action-row.ts b/src/panels/config/automation/action/ha-automation-action-row.ts
index 16d9b5ac3a..6f7f150688 100644
--- a/src/panels/config/automation/action/ha-automation-action-row.ts
+++ b/src/panels/config/automation/action/ha-automation-action-row.ts
@@ -17,6 +17,7 @@ import {
mdiStopCircleOutline,
} from "@mdi/js";
import deepClone from "deep-clone-simple";
+import type { HassServiceTarget } from "home-assistant-js-websocket";
import { dump } from "js-yaml";
import type { PropertyValues, TemplateResult } from "lit";
import { LitElement, html, nothing } from "lit";
@@ -53,18 +54,13 @@ import type {
} from "../../../../data/automation";
import { CONDITION_BUILDING_BLOCKS } from "../../../../data/condition";
import { validateConfig } from "../../../../data/config";
-import {
- floorsContext,
- fullEntitiesContext,
- labelsContext,
-} from "../../../../data/context";
+import { fullEntitiesContext } from "../../../../data/context";
import type { EntityRegistryEntry } from "../../../../data/entity/entity_registry";
-import type { FloorRegistryEntry } from "../../../../data/floor_registry";
-import type { LabelRegistryEntry } from "../../../../data/label/label_registry";
import type {
Action,
NonConditionAction,
RepeatAction,
+ ServiceAction,
} from "../../../../data/script";
import { getActionType, isAction } from "../../../../data/script";
import { describeAction } from "../../../../data/script_i18n";
@@ -78,6 +74,7 @@ import { isMac } from "../../../../util/is_mac";
import { showToast } from "../../../../util/toast";
import "../ha-automation-editor-warning";
import { overflowStyles, rowStyles } from "../styles";
+import "../target/ha-automation-row-targets";
import "./ha-automation-action-editor";
import type HaAutomationActionEditor from "./ha-automation-action-editor";
import "./types/ha-automation-action-choose";
@@ -176,14 +173,6 @@ export default class HaAutomationActionRow extends LitElement {
@consume({ context: fullEntitiesContext, subscribe: true })
_entityReg!: EntityRegistryEntry[];
- @state()
- @consume({ context: labelsContext, subscribe: true })
- _labelReg!: LabelRegistryEntry[];
-
- @state()
- @consume({ context: floorsContext, subscribe: true })
- _floorReg!: Record;
-
@state() private _uiModeAvailable = true;
@state() private _yamlMode = false;
@@ -263,14 +252,11 @@ export default class HaAutomationActionRow extends LitElement {
`}
${capitalizeFirstLetter(
- describeAction(
- this.hass,
- this._entityReg,
- this._labelReg,
- this._floorReg,
- this.action
- )
+ describeAction(this.hass, this._entityReg, this.action)
)}
+ ${type === "service" && "target" in this.action
+ ? this._renderTargets((this.action as ServiceAction).target)
+ : nothing}
@@ -556,6 +542,14 @@ export default class HaAutomationActionRow extends LitElement {
`;
}
+ private _renderTargets = memoizeOne(
+ (target?: HassServiceTarget) =>
+ html``
+ );
+
private _onValueChange(event: CustomEvent) {
// reload sidebar if sort, deleted,... happend
if (this._selected && this.optionsInSidebar) {
@@ -668,15 +662,7 @@ export default class HaAutomationActionRow extends LitElement {
),
inputType: "string",
placeholder: capitalizeFirstLetter(
- describeAction(
- this.hass,
- this._entityReg,
- this._labelReg,
- this._floorReg,
- this.action,
- undefined,
- true
- )
+ describeAction(this.hass, this._entityReg, this.action, undefined, true)
),
defaultValue: this.action.alias,
confirmText: this.hass.localize("ui.common.submit"),
diff --git a/src/panels/config/automation/add-automation-element-dialog.ts b/src/panels/config/automation/add-automation-element-dialog.ts
index c97a7c861b..bb446b5e76 100644
--- a/src/panels/config/automation/add-automation-element-dialog.ts
+++ b/src/panels/config/automation/add-automation-element-dialog.ts
@@ -18,7 +18,6 @@ import memoizeOne from "memoize-one";
import { fireEvent } from "../../../common/dom/fire_event";
import { mainWindow } from "../../../common/dom/get_main_window";
import { computeAreaName } from "../../../common/entity/compute_area_name";
-import { computeDeviceName } from "../../../common/entity/compute_device_name";
import { computeDomain } from "../../../common/entity/compute_domain";
import { computeEntityNameList } from "../../../common/entity/compute_entity_name_display";
import { computeFloorName } from "../../../common/entity/compute_floor_name";
@@ -123,6 +122,7 @@ import "./add-automation-element/ha-automation-add-items";
import "./add-automation-element/ha-automation-add-search";
import type { AddAutomationElementDialogParams } from "./show-add-automation-element-dialog";
import { PASTE_VALUE } from "./show-add-automation-element-dialog";
+import { getTargetText } from "./target/get_target_text";
const TYPES = {
trigger: { collections: TRIGGER_COLLECTIONS, icons: TRIGGER_ICONS },
@@ -1393,8 +1393,8 @@ class DialogAddAutomationElement
}
);
- private _getLabel = memoizeOne((labelId) =>
- this._labelRegistry?.find(({ label_id }) => label_id === labelId)
+ private _getLabel = memoizeOne((id: string) =>
+ this._labelRegistry?.find(({ label_id }) => label_id === id)
);
private _getDomainType(domain: string) {
@@ -1926,32 +1926,12 @@ class DialogAddAutomationElement
}
if (targetId) {
- if (targetType === "floor") {
- return computeFloorName(this.hass.floors[targetId]) || targetId;
- }
- if (targetType === "area") {
- return computeAreaName(this.hass.areas[targetId]) || targetId;
- }
- if (targetType === "device") {
- return computeDeviceName(this.hass.devices[targetId]) || targetId;
- }
- if (targetType === "entity" && this.hass.states[targetId]) {
- const stateObj = this.hass.states[targetId];
- const [entityName, deviceName] = computeEntityNameList(
- stateObj,
- [{ type: "entity" }, { type: "device" }, { type: "area" }],
- this.hass.entities,
- this.hass.devices,
- this.hass.areas,
- this.hass.floors
- );
-
- return entityName || deviceName || targetId;
- }
- if (targetType === "label") {
- const label = this._getLabel(targetId);
- return label?.name || targetId;
- }
+ return getTargetText(
+ this.hass,
+ targetType as "floor" | "area" | "device" | "entity" | "label",
+ targetId,
+ this._getLabel
+ );
}
return undefined;
diff --git a/src/panels/config/automation/add-automation-element/ha-automation-add-items.ts b/src/panels/config/automation/add-automation-element/ha-automation-add-items.ts
index 510b8d9067..1018f4fc2a 100644
--- a/src/panels/config/automation/add-automation-element/ha-automation-add-items.ts
+++ b/src/panels/config/automation/add-automation-element/ha-automation-add-items.ts
@@ -1,10 +1,5 @@
-import {
- mdiInformationOutline,
- mdiLabel,
- mdiPlus,
- mdiTextureBox,
-} from "@mdi/js";
-import { LitElement, css, html, nothing, type TemplateResult } from "lit";
+import { mdiInformationOutline, mdiPlus } from "@mdi/js";
+import { LitElement, css, html, nothing } from "lit";
import {
customElement,
eventOptions,
@@ -17,17 +12,15 @@ import { repeat } from "lit/directives/repeat";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../common/dom/fire_event";
import { stopPropagation } from "../../../../common/dom/stop_propagation";
-import "../../../../components/entity/state-badge";
-import "../../../../components/ha-domain-icon";
-import "../../../../components/ha-floor-icon";
-import "../../../../components/ha-icon-next";
import "../../../../components/ha-md-list";
import "../../../../components/ha-md-list-item";
import "../../../../components/ha-svg-icon";
import "../../../../components/ha-tooltip";
import type { ConfigEntry } from "../../../../data/config_entries";
+import type { LabelRegistryEntry } from "../../../../data/label/label_registry";
import type { HomeAssistant } from "../../../../types";
import type { AddAutomationElementListItem } from "../add-automation-element-dialog";
+import { getTargetIcon } from "../target/get_target_icon";
type Target = [string, string | undefined, string | undefined];
@@ -50,7 +43,7 @@ export class HaAutomationAddItems extends LitElement {
@property({ attribute: false }) public getLabel!: (
id: string
- ) => { name: string; icon?: string } | undefined;
+ ) => LabelRegistryEntry | undefined;
@property({ attribute: false }) public configEntryLookup: Record<
string,
@@ -164,72 +157,17 @@ export class HaAutomationAddItems extends LitElement {
}
return html`
- ${this._getSelectedTargetIcon(target[0], target[1])}
+ ${getTargetIcon(
+ this.hass,
+ target[0],
+ target[1],
+ this.configEntryLookup,
+ this.getLabel
+ )}
${target[2]}
`;
});
- private _getSelectedTargetIcon(
- targetType: string,
- targetId: string | undefined
- ): TemplateResult | typeof nothing {
- if (!targetId) {
- return nothing;
- }
-
- if (targetType === "floor") {
- return html``;
- }
-
- if (targetType === "area" && this.hass.areas[targetId]) {
- const area = this.hass.areas[targetId];
- if (area.icon) {
- return html``;
- }
- return html``;
- }
-
- if (targetType === "device" && this.hass.devices[targetId]) {
- const device = this.hass.devices[targetId];
- const configEntry = device.primary_config_entry
- ? this.configEntryLookup[device.primary_config_entry]
- : undefined;
- const domain = configEntry?.domain;
-
- if (domain) {
- return html``;
- }
- }
-
- if (targetType === "entity" && this.hass.states[targetId]) {
- const stateObj = this.hass.states[targetId];
- if (stateObj) {
- return html``;
- }
- }
-
- if (targetType === "label") {
- const label = this.getLabel(targetId);
- if (label?.icon) {
- return html``;
- }
- return html``;
- }
-
- return nothing;
- }
-
private _selected(ev) {
const item = ev.currentTarget;
fireEvent(this, "value-changed", {
@@ -335,10 +273,6 @@ export class HaAutomationAddItems extends LitElement {
border-bottom: 1px solid var(--ha-color-border-neutral-quiet);
}
- ha-icon-next {
- width: var(--ha-space-6);
- }
-
ha-svg-icon.plus {
color: var(--primary-color);
}
@@ -362,16 +296,11 @@ export class HaAutomationAddItems extends LitElement {
.selected-target ha-icon,
.selected-target ha-svg-icon,
- .selected-target state-badge,
.selected-target ha-domain-icon {
display: flex;
padding: var(--ha-space-1) 0;
}
- .selected-target state-badge {
- --mdc-icon-size: 24px;
- }
- .selected-target state-badge,
.selected-target ha-floor-icon {
display: flex;
height: 32px;
diff --git a/src/panels/config/automation/condition/ha-automation-condition-row.ts b/src/panels/config/automation/condition/ha-automation-condition-row.ts
index ee41c0074a..a3314d53f9 100644
--- a/src/panels/config/automation/condition/ha-automation-condition-row.ts
+++ b/src/panels/config/automation/condition/ha-automation-condition-row.ts
@@ -16,6 +16,7 @@ import {
mdiStopCircleOutline,
} from "@mdi/js";
import deepClone from "deep-clone-simple";
+import type { HassServiceTarget } from "home-assistant-js-websocket";
import { dump } from "js-yaml";
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { LitElement, css, html, nothing } from "lit";
@@ -43,6 +44,7 @@ import type {
AutomationClipboard,
Condition,
ConditionSidebarConfig,
+ PlatformCondition,
} from "../../../../data/automation";
import { isCondition, testCondition } from "../../../../data/automation";
import { describeCondition } from "../../../../data/automation_i18n";
@@ -60,6 +62,7 @@ import { isMac } from "../../../../util/is_mac";
import { showToast } from "../../../../util/toast";
import "../ha-automation-editor-warning";
import { overflowStyles, rowStyles } from "../styles";
+import "../target/ha-automation-row-targets";
import "./ha-automation-condition-editor";
import type HaAutomationConditionEditor from "./ha-automation-condition-editor";
import "./types/ha-automation-condition-and";
@@ -191,6 +194,10 @@ export default class HaAutomationConditionRow extends LitElement {
${capitalizeFirstLetter(
describeCondition(this.condition, this.hass, this._entityReg)
)}
+ ${"target" in
+ (this.conditionDescriptions[this.condition.condition] || {})
+ ? this._renderTargets((this.condition as PlatformCondition).target)
+ : nothing}
@@ -475,6 +482,14 @@ export default class HaAutomationConditionRow extends LitElement {
`;
}
+ private _renderTargets = memoizeOne(
+ (target?: HassServiceTarget) =>
+ html``
+ );
+
protected firstUpdated(changedProperties: PropertyValues): void {
super.firstUpdated(changedProperties);
diff --git a/src/panels/config/automation/styles.ts b/src/panels/config/automation/styles.ts
index 0158869d14..9928d664d7 100644
--- a/src/panels/config/automation/styles.ts
+++ b/src/panels/config/automation/styles.ts
@@ -14,6 +14,12 @@ export const rowStyles = css`
h3 {
font-size: inherit;
font-weight: inherit;
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: var(--ha-space-2);
+ padding: var(--ha-space-2) 0;
+ min-height: 32px;
}
ha-card {
diff --git a/src/panels/config/automation/target/get_target_icon.ts b/src/panels/config/automation/target/get_target_icon.ts
new file mode 100644
index 0000000000..454d4a9113
--- /dev/null
+++ b/src/panels/config/automation/target/get_target_icon.ts
@@ -0,0 +1,69 @@
+import { mdiLabel, mdiTextureBox } from "@mdi/js";
+import { html, nothing, type TemplateResult } from "lit";
+import "../../../../components/ha-domain-icon";
+import "../../../../components/ha-floor-icon";
+import "../../../../components/ha-icon";
+import "../../../../components/ha-state-icon";
+import "../../../../components/ha-svg-icon";
+import type { ConfigEntry } from "../../../../data/config_entries";
+import type { LabelRegistryEntry } from "../../../../data/label/label_registry";
+import type { HomeAssistant } from "../../../../types";
+
+export const getTargetIcon = (
+ hass: HomeAssistant,
+ targetType: string,
+ targetId: string | undefined,
+ configEntryLookup: Record,
+ getLabel?: (id: string) => LabelRegistryEntry | undefined
+): TemplateResult | typeof nothing => {
+ if (!targetId) {
+ return nothing;
+ }
+
+ if (targetType === "floor" && hass.floors[targetId]) {
+ return html``;
+ }
+
+ if (targetType === "area") {
+ const area = hass.areas[targetId];
+ if (area?.icon) {
+ return html``;
+ }
+ return html``;
+ }
+
+ if (targetType === "device" && hass.devices[targetId]) {
+ const device = hass.devices[targetId];
+ const configEntry = device.primary_config_entry
+ ? configEntryLookup[device.primary_config_entry]
+ : undefined;
+ const domain = configEntry?.domain;
+
+ if (domain) {
+ return html``;
+ }
+ }
+
+ if (targetType === "entity" && hass.states[targetId]) {
+ return html``;
+ }
+
+ if (targetType === "label" && getLabel) {
+ const label = getLabel(targetId);
+ if (label?.icon) {
+ return html``;
+ }
+ return html``;
+ }
+
+ return nothing;
+};
diff --git a/src/panels/config/automation/target/get_target_text.ts b/src/panels/config/automation/target/get_target_text.ts
new file mode 100644
index 0000000000..fbe2951b4f
--- /dev/null
+++ b/src/panels/config/automation/target/get_target_text.ts
@@ -0,0 +1,68 @@
+import { computeAreaName } from "../../../../common/entity/compute_area_name";
+import { computeDeviceName } from "../../../../common/entity/compute_device_name";
+import { computeEntityNameList } from "../../../../common/entity/compute_entity_name_display";
+import { computeFloorName } from "../../../../common/entity/compute_floor_name";
+import type { LabelRegistryEntry } from "../../../../data/label/label_registry";
+import type { HomeAssistant } from "../../../../types";
+
+export const getTargetText = (
+ hass: HomeAssistant,
+ targetType: "floor" | "area" | "device" | "entity" | "label",
+ targetId: string,
+ getLabel?: (id: string) => LabelRegistryEntry | undefined
+): string => {
+ if (targetType === "floor") {
+ return (
+ (hass.floors[targetId] && computeFloorName(hass.floors[targetId])) ||
+ hass.localize(
+ "ui.panel.config.automation.editor.actions.type.service.description.target_unknown_floor"
+ )
+ );
+ }
+ if (targetType === "area") {
+ return (
+ (hass.areas[targetId] && computeAreaName(hass.areas[targetId])) ||
+ hass.localize(
+ "ui.panel.config.automation.editor.actions.type.service.description.target_unknown_area"
+ )
+ );
+ }
+ if (targetType === "device") {
+ return (
+ (hass.devices[targetId] && computeDeviceName(hass.devices[targetId])) ||
+ hass.localize(
+ "ui.panel.config.automation.editor.actions.type.service.description.target_unknown_device"
+ )
+ );
+ }
+ if (targetType === "entity" && hass.states[targetId]) {
+ const stateObj = hass.states[targetId];
+ const [entityName, deviceName] = computeEntityNameList(
+ stateObj,
+ [{ type: "entity" }, { type: "device" }, { type: "area" }],
+ hass.entities,
+ hass.devices,
+ hass.areas,
+ hass.floors
+ );
+
+ return entityName || deviceName || targetId;
+ }
+ if (targetType === "entity") {
+ return hass.localize(
+ "ui.panel.config.automation.editor.actions.type.service.description.target_unknown_entity"
+ );
+ }
+
+ if (targetType === "label" && getLabel) {
+ const label = getLabel(targetId);
+ return (
+ label?.name ||
+ hass.localize(
+ "ui.panel.config.automation.editor.actions.type.service.description.target_unknown_label"
+ )
+ );
+ }
+
+ return targetId;
+};
diff --git a/src/panels/config/automation/target/ha-automation-row-targets.ts b/src/panels/config/automation/target/ha-automation-row-targets.ts
new file mode 100644
index 0000000000..8a3e681a1c
--- /dev/null
+++ b/src/panels/config/automation/target/ha-automation-row-targets.ts
@@ -0,0 +1,260 @@
+import { consume } from "@lit/context";
+import { mdiAlert, mdiFormatListBulleted, mdiShape } from "@mdi/js";
+import type { HassServiceTarget } from "home-assistant-js-websocket";
+import { LitElement, css, html, nothing, type TemplateResult } from "lit";
+import { customElement, property, state } from "lit/decorators";
+import { until } from "lit/directives/until";
+import { ensureArray } from "../../../../common/array/ensure-array";
+import "../../../../components/ha-svg-icon";
+import {
+ getConfigEntries,
+ type ConfigEntry,
+} from "../../../../data/config_entries";
+import {
+ areasContext,
+ devicesContext,
+ floorsContext,
+ labelsContext,
+ localizeContext,
+ statesContext,
+} from "../../../../data/context";
+import type { LabelRegistryEntry } from "../../../../data/label/label_registry";
+import type { HomeAssistant } from "../../../../types";
+import { getTargetIcon } from "./get_target_icon";
+import { getTargetText } from "./get_target_text";
+
+@customElement("ha-automation-row-targets")
+export class HaAutomationRowTargets extends LitElement {
+ @property({ attribute: false })
+ public hass!: HomeAssistant;
+
+ @property({ attribute: false })
+ public target?: HassServiceTarget;
+
+ @state()
+ @consume({ context: localizeContext, subscribe: true })
+ private localize!: HomeAssistant["localize"];
+
+ @state()
+ @consume({ context: floorsContext, subscribe: true })
+ private floors!: HomeAssistant["floors"];
+
+ @state()
+ @consume({ context: areasContext, subscribe: true })
+ private areas!: HomeAssistant["areas"];
+
+ @state()
+ @consume({ context: devicesContext, subscribe: true })
+ private devices!: HomeAssistant["devices"];
+
+ @state()
+ @consume({ context: statesContext, subscribe: true })
+ private states!: HomeAssistant["states"];
+
+ @state()
+ @consume({ context: labelsContext, subscribe: true })
+ private _labelRegistry!: LabelRegistryEntry[];
+
+ private _configEntryLookup?: Record;
+
+ protected render() {
+ const length = Object.keys(this.target || {}).length;
+ if (!length) {
+ return html`
+
+ ${this.localize(
+ "ui.panel.config.automation.editor.target_summary.no_target"
+ )}
+
+ `;
+ }
+ const totalLength = Object.values(this.target || {}).reduce(
+ (acc, val) => acc + ensureArray(val).length,
+ 0
+ );
+
+ if (totalLength <= 5) {
+ const targets = Object.entries(this.target!).reduce<
+ ["floor" | "area" | "device" | "entity" | "label", string][]
+ >((acc, [targetType, targetId]) => {
+ const type = targetType.replace("_id", "") as
+ | "floor"
+ | "area"
+ | "device"
+ | "entity"
+ | "label";
+ return [
+ ...acc,
+ ...ensureArray(targetId).map((id): [typeof type, string] => [
+ type,
+ id,
+ ]),
+ ];
+ }, []);
+
+ return targets.map(
+ ([targetType, targetId]) =>
+ html`
+ ${this._renderTarget(targetType, targetId)}
+ `
+ );
+ }
+
+ return html`
+
+
+ ${this.localize(
+ "ui.panel.config.automation.editor.target_summary.targets",
+ {
+ count: totalLength,
+ }
+ )}
+
+ `;
+ }
+
+ private _getLabel = (id: string) =>
+ this._labelRegistry?.find(({ label_id }) => label_id === id);
+
+ private _checkTargetExists(
+ targetType: "floor" | "area" | "device" | "entity" | "label",
+ targetId: string
+ ): boolean {
+ if (targetType === "floor") {
+ return !!this.floors[targetId];
+ }
+ if (targetType === "area") {
+ return !!this.areas[targetId];
+ }
+ if (targetType === "device") {
+ return !!this.devices[targetId];
+ }
+ if (targetType === "entity") {
+ return !!this.states[targetId];
+ }
+ if (targetType === "label") {
+ return !!this._getLabel(targetId);
+ }
+ return false;
+ }
+
+ private _renderTargetBadge(
+ icon: TemplateResult | typeof nothing,
+ label: string,
+ alert = false
+ ) {
+ return html``;
+ }
+
+ private async _loadConfigEntries() {
+ const configEntries = await getConfigEntries(this.hass);
+ this._configEntryLookup = Object.fromEntries(
+ configEntries.map((entry) => [entry.entry_id, entry])
+ );
+ }
+
+ private _renderTarget(
+ targetType: "floor" | "area" | "device" | "entity" | "label",
+ targetId: string
+ ) {
+ if (targetType === "entity" && ["all", "none"].includes(targetId)) {
+ return this._renderTargetBadge(
+ html``,
+ this.localize(
+ `ui.panel.config.automation.editor.target_summary.${targetId as "all" | "none"}_entities`
+ )
+ );
+ }
+
+ const exists = this._checkTargetExists(targetType, targetId);
+ if (!exists) {
+ return this._renderTargetBadge(
+ html``,
+ getTargetText(this.hass, targetType, targetId, this._getLabel),
+ true
+ );
+ }
+
+ if (targetType === "device" && !this._configEntryLookup) {
+ const loadConfigEntries = this._loadConfigEntries().then(() =>
+ this._renderTargetBadge(
+ getTargetIcon(
+ this.hass,
+ targetType,
+ targetId,
+ this._configEntryLookup!
+ ),
+ getTargetText(this.hass, targetType, targetId)
+ )
+ );
+
+ return html`${until(loadConfigEntries, nothing)}`;
+ }
+
+ return this._renderTargetBadge(
+ getTargetIcon(
+ this.hass,
+ targetType,
+ targetId,
+ this._configEntryLookup || {},
+ this._getLabel
+ ),
+ getTargetText(this.hass, targetType, targetId, this._getLabel)
+ );
+ }
+
+ static styles = css`
+ :host {
+ display: contents;
+ min-height: 32px;
+ }
+ .target-wrapper {
+ display: inline-flex;
+ align-items: flex-end;
+ gap: var(--ha-space-1);
+ }
+ .target {
+ display: inline-flex;
+ gap: var(--ha-space-1);
+ justify-content: center;
+ align-items: center;
+ border-radius: var(--ha-border-radius-md);
+ background: var(--ha-color-fill-neutral-normal-resting);
+ padding: 0 var(--ha-space-2) 0 var(--ha-space-1);
+ color: var(--ha-color-on-neutral-normal);
+ overflow: hidden;
+ height: 32px;
+ }
+ .target.alert {
+ background: var(--ha-color-fill-warning-normal-resting);
+ color: var(--ha-color-on-warning-normal);
+ }
+ .target .label {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .target ha-icon,
+ .target ha-svg-icon,
+ .target ha-domain-icon {
+ display: flex;
+ padding: var(--ha-space-1) 0;
+ }
+
+ .target ha-floor-icon {
+ display: flex;
+ height: 32px;
+ align-items: center;
+ }
+ `;
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-automation-row-targets": HaAutomationRowTargets;
+ }
+}
diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts
index 1930b55fc4..99fa2ba44a 100644
--- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts
+++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts
@@ -15,7 +15,10 @@ import {
mdiRenameBox,
mdiStopCircleOutline,
} from "@mdi/js";
-import type { UnsubscribeFunc } from "home-assistant-js-websocket";
+import type {
+ HassServiceTarget,
+ UnsubscribeFunc,
+} from "home-assistant-js-websocket";
import { dump } from "js-yaml";
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { LitElement, css, html, nothing } from "lit";
@@ -44,6 +47,7 @@ import "../../../../components/ha-svg-icon";
import { TRIGGER_ICONS } from "../../../../components/ha-trigger-icon";
import type {
AutomationClipboard,
+ PlatformTrigger,
Trigger,
TriggerList,
TriggerSidebarConfig,
@@ -64,6 +68,7 @@ import { isMac } from "../../../../util/is_mac";
import { showToast } from "../../../../util/toast";
import "../ha-automation-editor-warning";
import { overflowStyles, rowStyles } from "../styles";
+import "../target/ha-automation-row-targets";
import "./ha-automation-trigger-editor";
import type HaAutomationTriggerEditor from "./ha-automation-trigger-editor";
import "./types/ha-automation-trigger-calendar";
@@ -205,6 +210,11 @@ export default class HaAutomationTriggerRow extends LitElement {
>`}
${describeTrigger(this.trigger, this.hass, this._entityReg)}
+ ${type === "platform" &&
+ "target" in
+ this.triggerDescriptions[(this.trigger as PlatformTrigger).trigger]
+ ? this._renderTargets((this.trigger as PlatformTrigger).target)
+ : nothing}
@@ -450,6 +460,14 @@ export default class HaAutomationTriggerRow extends LitElement {
`;
}
+ private _renderTargets = memoizeOne(
+ (target?: HassServiceTarget) =>
+ html``
+ );
+
protected willUpdate(changedProperties) {
// on yaml toggle --> clear warnings
if (changedProperties.has("yamlMode")) {
diff --git a/src/translations/en.json b/src/translations/en.json
index 1c45b6a134..05a1383dbc 100644
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -4077,6 +4077,13 @@
"services": "Services",
"helpers": "Helpers",
"entity_hidden": "[%key:ui::panel::config::devices::entities::hidden%]",
+ "target_summary": {
+ "no_target": "No target set",
+ "targets": "{count} {count, plural,\n one {target}\n other {targets}\n}",
+ "invalid": "Invalid target",
+ "all_entities": "All entities",
+ "none_entities": "No entities"
+ },
"triggers": {
"name": "Triggers",
"header": "When",
@@ -4588,11 +4595,11 @@
"service": "Perform an action",
"target_template": "templated {name}",
"target_every_entity": "every entity",
- "target_unknown_entity": "unknown entity",
- "target_unknown_device": "unknown device",
- "target_unknown_area": "unknown area",
- "target_unknown_floor": "unknown floor",
- "target_unknown_label": "unknown label"
+ "target_unknown_entity": "Unknown entity",
+ "target_unknown_device": "Unknown device",
+ "target_unknown_area": "Unknown area",
+ "target_unknown_floor": "Unknown floor",
+ "target_unknown_label": "Unknown label"
}
},
"play_media": {