Support entity reg id in device automations (#15387)

This commit is contained in:
Bram Kragten 2023-05-24 14:37:45 +02:00 committed by GitHub
parent 85acafe8a8
commit dde27c3524
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 279 additions and 104 deletions

View File

@ -63,7 +63,7 @@ export class DemoAutomationDescribeCondition extends LitElement {
<div class="condition"> <div class="condition">
<span> <span>
${this._condition ${this._condition
? describeCondition(this._condition, this.hass) ? describeCondition(this._condition, this.hass, [])
: "<invalid YAML>"} : "<invalid YAML>"}
</span> </span>
<ha-yaml-editor <ha-yaml-editor
@ -76,7 +76,7 @@ export class DemoAutomationDescribeCondition extends LitElement {
${conditions.map( ${conditions.map(
(conf) => html` (conf) => html`
<div class="condition"> <div class="condition">
<span>${describeCondition(conf as any, this.hass)}</span> <span>${describeCondition(conf as any, this.hass, [])}</span>
<pre>${dump(conf)}</pre> <pre>${dump(conf)}</pre>
</div> </div>
` `

View File

@ -74,7 +74,7 @@ export class DemoAutomationDescribeTrigger extends LitElement {
<div class="trigger"> <div class="trigger">
<span> <span>
${this._trigger ${this._trigger
? describeTrigger(this._trigger, this.hass) ? describeTrigger(this._trigger, this.hass, [])
: "<invalid YAML>"} : "<invalid YAML>"}
</span> </span>
<ha-yaml-editor <ha-yaml-editor
@ -86,7 +86,7 @@ export class DemoAutomationDescribeTrigger extends LitElement {
${triggers.map( ${triggers.map(
(conf) => html` (conf) => html`
<div class="trigger"> <div class="trigger">
<span>${describeTrigger(conf as any, this.hass)}</span> <span>${describeTrigger(conf as any, this.hass, [])}</span>
<pre>${dump(conf)}</pre> <pre>${dump(conf)}</pre>
</div> </div>
` `

View File

@ -1,12 +1,15 @@
import { consume } from "@lit-labs/context";
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { property, state } from "lit/decorators"; import { property, state } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event";
import { fullEntitiesContext } from "../../data/context";
import { import {
DeviceAutomation, DeviceAutomation,
deviceAutomationsEqual, deviceAutomationsEqual,
sortDeviceAutomations, sortDeviceAutomations,
} from "../../data/device_automation"; } from "../../data/device_automation";
import { EntityRegistryEntry } from "../../data/entity_registry";
import { HomeAssistant } from "../../types"; import { HomeAssistant } from "../../types";
import "../ha-select"; import "../ha-select";
@ -30,6 +33,10 @@ export abstract class HaDeviceAutomationPicker<
// paper-listbox does not like changing things around. // paper-listbox does not like changing things around.
@state() private _renderEmpty = false; @state() private _renderEmpty = false;
@state()
@consume({ context: fullEntitiesContext, subscribe: true })
_entityReg!: EntityRegistryEntry[];
protected get NO_AUTOMATION_TEXT() { protected get NO_AUTOMATION_TEXT() {
return this.hass.localize( return this.hass.localize(
"ui.panel.config.devices.automation.actions.no_actions" "ui.panel.config.devices.automation.actions.no_actions"
@ -44,6 +51,7 @@ export abstract class HaDeviceAutomationPicker<
private _localizeDeviceAutomation: ( private _localizeDeviceAutomation: (
hass: HomeAssistant, hass: HomeAssistant,
entityRegistry: EntityRegistryEntry[],
automation: T automation: T
) => string; ) => string;
@ -75,7 +83,7 @@ export abstract class HaDeviceAutomationPicker<
} }
const idx = this._automations.findIndex((automation) => const idx = this._automations.findIndex((automation) =>
deviceAutomationsEqual(automation, this.value!) deviceAutomationsEqual(this._entityReg, automation, this.value!)
); );
if (idx === -1) { if (idx === -1) {
@ -110,7 +118,11 @@ export abstract class HaDeviceAutomationPicker<
${this._automations.map( ${this._automations.map(
(automation, idx) => html` (automation, idx) => html`
<mwc-list-item .value=${`${automation.device_id}_${idx}`}> <mwc-list-item .value=${`${automation.device_id}_${idx}`}>
${this._localizeDeviceAutomation(this.hass, automation)} ${this._localizeDeviceAutomation(
this.hass,
this._entityReg,
automation
)}
</mwc-list-item> </mwc-list-item>
` `
)} )}
@ -161,7 +173,10 @@ export abstract class HaDeviceAutomationPicker<
} }
private _setValue(automation: T) { private _setValue(automation: T) {
if (this.value && deviceAutomationsEqual(automation, this.value)) { if (
this.value &&
deviceAutomationsEqual(this._entityReg, automation, this.value)
) {
return; return;
} }
const value = { ...automation }; const value = { ...automation };

View File

@ -15,6 +15,7 @@ import {
computeAttributeValueDisplay, computeAttributeValueDisplay,
} from "../common/entity/compute_attribute_display"; } from "../common/entity/compute_attribute_display";
import { computeStateDisplay } from "../common/entity/compute_state_display"; import { computeStateDisplay } from "../common/entity/compute_state_display";
import { EntityRegistryEntry } from "./entity_registry";
const describeDuration = (forTime: number | string | ForDict) => { const describeDuration = (forTime: number | string | ForDict) => {
let duration: string | null; let duration: string | null;
@ -48,6 +49,7 @@ const ordinalSuffix = (n: number) => {
export const describeTrigger = ( export const describeTrigger = (
trigger: Trigger, trigger: Trigger,
hass: HomeAssistant, hass: HomeAssistant,
entityRegistry: EntityRegistryEntry[],
ignoreAlias = false ignoreAlias = false
) => { ) => {
if (trigger.alias && !ignoreAlias) { if (trigger.alias && !ignoreAlias) {
@ -561,7 +563,11 @@ export const describeTrigger = (
return "Device trigger"; return "Device trigger";
} }
const config = trigger as DeviceTrigger; const config = trigger as DeviceTrigger;
const localized = localizeDeviceAutomationTrigger(hass, config); const localized = localizeDeviceAutomationTrigger(
hass,
entityRegistry,
config
);
if (localized) { if (localized) {
return localized; return localized;
} }
@ -579,6 +585,7 @@ export const describeTrigger = (
export const describeCondition = ( export const describeCondition = (
condition: Condition, condition: Condition,
hass: HomeAssistant, hass: HomeAssistant,
entityRegistry: EntityRegistryEntry[],
ignoreAlias = false ignoreAlias = false
) => { ) => {
if (condition.alias && !ignoreAlias) { if (condition.alias && !ignoreAlias) {
@ -873,7 +880,11 @@ export const describeCondition = (
return "Device condition"; return "Device condition";
} }
const config = condition as DeviceCondition; const config = condition as DeviceCondition;
const localized = localizeDeviceAutomationCondition(hass, config); const localized = localizeDeviceAutomationCondition(
hass,
entityRegistry,
config
);
if (localized) { if (localized) {
return localized; return localized;
} }

View File

@ -20,5 +20,5 @@ export const userDataContext =
createContext<HomeAssistant["userData"]>("userData"); createContext<HomeAssistant["userData"]>("userData");
export const panelsContext = createContext<HomeAssistant["panels"]>("panels"); export const panelsContext = createContext<HomeAssistant["panels"]>("panels");
export const extendedEntitiesContext = export const fullEntitiesContext =
createContext<EntityRegistryEntry[]>("extendedEntities"); createContext<EntityRegistryEntry[]>("extendedEntities");

View File

@ -2,6 +2,12 @@ import { computeStateName } from "../common/entity/compute_state_name";
import type { HaFormSchema } from "../components/ha-form/types"; import type { HaFormSchema } from "../components/ha-form/types";
import { HomeAssistant } from "../types"; import { HomeAssistant } from "../types";
import { BaseTrigger } from "./automation"; import { BaseTrigger } from "./automation";
import {
computeEntityRegistryName,
entityRegistryByEntityId,
entityRegistryById,
EntityRegistryEntry,
} from "./entity_registry";
export interface DeviceAutomation { export interface DeviceAutomation {
alias?: string; alias?: string;
@ -89,6 +95,7 @@ const deviceAutomationIdentifiers = [
]; ];
export const deviceAutomationsEqual = ( export const deviceAutomationsEqual = (
entityRegistry: EntityRegistryEntry[],
a: DeviceAutomation, a: DeviceAutomation,
b: DeviceAutomation b: DeviceAutomation
) => { ) => {
@ -100,6 +107,22 @@ export const deviceAutomationsEqual = (
if (!deviceAutomationIdentifiers.includes(property)) { if (!deviceAutomationIdentifiers.includes(property)) {
continue; continue;
} }
if (
property === "entity_id" &&
a[property]?.includes(".") !== b[property]?.includes(".")
) {
// both entity_id and entity_reg_id could be used, we should compare the entity_reg_id
if (
!compareEntityIdWithEntityRegId(
entityRegistry,
a[property],
b[property]
)
) {
return false;
}
continue;
}
if (!Object.is(a[property], b[property])) { if (!Object.is(a[property], b[property])) {
return false; return false;
} }
@ -108,6 +131,22 @@ export const deviceAutomationsEqual = (
if (!deviceAutomationIdentifiers.includes(property)) { if (!deviceAutomationIdentifiers.includes(property)) {
continue; continue;
} }
if (
property === "entity_id" &&
a[property]?.includes(".") !== b[property]?.includes(".")
) {
// both entity_id and entity_reg_id could be used, we should compare the entity_reg_id
if (
!compareEntityIdWithEntityRegId(
entityRegistry,
a[property],
b[property]
)
) {
return false;
}
continue;
}
if (!Object.is(a[property], b[property])) { if (!Object.is(a[property], b[property])) {
return false; return false;
} }
@ -116,71 +155,99 @@ export const deviceAutomationsEqual = (
return true; return true;
}; };
const compareEntityIdWithEntityRegId = (
entityRegistry: EntityRegistryEntry[],
entityIdA?: string,
entityIdB?: string
) => {
if (!entityIdA || !entityIdB) {
return false;
}
if (entityIdA.includes(".")) {
entityIdA = entityRegistryByEntityId(entityRegistry)[entityIdA].id;
}
if (entityIdB.includes(".")) {
entityIdB = entityRegistryByEntityId(entityRegistry)[entityIdB].id;
}
return entityIdA === entityIdB;
};
const getEntityName = (
hass: HomeAssistant,
entityRegistry: EntityRegistryEntry[],
entityId: string | undefined
): string => {
if (!entityId) {
return "<unknown entity>";
}
if (entityId.includes(".")) {
const state = hass.states[entityId];
if (state) {
return computeStateName(state);
}
return entityId;
}
const entityReg = entityRegistryById(entityRegistry)[entityId];
if (entityReg) {
return computeEntityRegistryName(hass, entityReg) || entityId;
}
return "<unknown entity>";
};
export const localizeDeviceAutomationAction = ( export const localizeDeviceAutomationAction = (
hass: HomeAssistant, hass: HomeAssistant,
entityRegistry: EntityRegistryEntry[],
action: DeviceAction action: DeviceAction
): string => { ): string =>
const state = action.entity_id ? hass.states[action.entity_id] : undefined; hass.localize(
return ( `component.${action.domain}.device_automation.action_type.${action.type}`,
hass.localize( "entity_name",
`component.${action.domain}.device_automation.action_type.${action.type}`, getEntityName(hass, entityRegistry, action.entity_id),
"entity_name", "subtype",
state ? computeStateName(state) : action.entity_id || "<unknown>", action.subtype
"subtype", ? hass.localize(
action.subtype `component.${action.domain}.device_automation.action_subtype.${action.subtype}`
? hass.localize( ) || action.subtype
`component.${action.domain}.device_automation.action_subtype.${action.subtype}` : ""
) || action.subtype ) || (action.subtype ? `"${action.subtype}" ${action.type}` : action.type!);
: ""
) || (action.subtype ? `"${action.subtype}" ${action.type}` : action.type!)
);
};
export const localizeDeviceAutomationCondition = ( export const localizeDeviceAutomationCondition = (
hass: HomeAssistant, hass: HomeAssistant,
entityRegistry: EntityRegistryEntry[],
condition: DeviceCondition condition: DeviceCondition
): string => { ): string =>
const state = condition.entity_id hass.localize(
? hass.states[condition.entity_id] `component.${condition.domain}.device_automation.condition_type.${condition.type}`,
: undefined; "entity_name",
return ( getEntityName(hass, entityRegistry, condition.entity_id),
hass.localize( "subtype",
`component.${condition.domain}.device_automation.condition_type.${condition.type}`, condition.subtype
"entity_name", ? hass.localize(
state ? computeStateName(state) : condition.entity_id || "<unknown>", `component.${condition.domain}.device_automation.condition_subtype.${condition.subtype}`
"subtype", ) || condition.subtype
condition.subtype : ""
? hass.localize( ) ||
`component.${condition.domain}.device_automation.condition_subtype.${condition.subtype}` (condition.subtype
) || condition.subtype ? `"${condition.subtype}" ${condition.type}`
: "" : condition.type!);
) ||
(condition.subtype
? `"${condition.subtype}" ${condition.type}`
: condition.type!)
);
};
export const localizeDeviceAutomationTrigger = ( export const localizeDeviceAutomationTrigger = (
hass: HomeAssistant, hass: HomeAssistant,
entityRegistry: EntityRegistryEntry[],
trigger: DeviceTrigger trigger: DeviceTrigger
): string => { ): string =>
const state = trigger.entity_id ? hass.states[trigger.entity_id] : undefined; hass.localize(
return ( `component.${trigger.domain}.device_automation.trigger_type.${trigger.type}`,
hass.localize( "entity_name",
`component.${trigger.domain}.device_automation.trigger_type.${trigger.type}`, getEntityName(hass, entityRegistry, trigger.entity_id),
"entity_name", "subtype",
state ? computeStateName(state) : trigger.entity_id || "<unknown>", trigger.subtype
"subtype", ? hass.localize(
trigger.subtype `component.${trigger.domain}.device_automation.trigger_subtype.${trigger.subtype}`
? hass.localize( ) || trigger.subtype
`component.${trigger.domain}.device_automation.trigger_subtype.${trigger.subtype}` : ""
) || trigger.subtype ) ||
: "" (trigger.subtype ? `"${trigger.subtype}" ${trigger.type}` : trigger.type!);
) ||
(trigger.subtype ? `"${trigger.subtype}" ${trigger.type}` : trigger.type!)
);
};
export const sortDeviceAutomations = ( export const sortDeviceAutomations = (
automationA: DeviceAutomation, automationA: DeviceAutomation,

View File

@ -186,7 +186,7 @@ export const describeAction = <T extends ActionType>(
return "Wait for a trigger"; return "Wait for a trigger";
} }
return `Wait for ${triggers return `Wait for ${triggers
.map((trigger) => describeTrigger(trigger, hass)) .map((trigger) => describeTrigger(trigger, hass, entityRegistry))
.join(", ")}`; .join(", ")}`;
} }
@ -208,7 +208,7 @@ export const describeAction = <T extends ActionType>(
} }
if (actionType === "check_condition") { if (actionType === "check_condition") {
return describeCondition(action as Condition, hass); return describeCondition(action as Condition, hass, entityRegistry);
} }
if (actionType === "stop") { if (actionType === "stop") {
@ -226,7 +226,7 @@ export const describeAction = <T extends ActionType>(
: ensureArray(config.if).length > 1 : ensureArray(config.if).length > 1
? `${ensureArray(config.if).length} conditions` ? `${ensureArray(config.if).length} conditions`
: ensureArray(config.if).length : ensureArray(config.if).length
? describeCondition(ensureArray(config.if)[0], hass) ? describeCondition(ensureArray(config.if)[0], hass, entityRegistry)
: "" : ""
}${config.else ? " (or else!)" : ""}`; }${config.else ? " (or else!)" : ""}`;
} }
@ -252,11 +252,11 @@ export const describeAction = <T extends ActionType>(
base += ` ${count} time${Number(count) === 1 ? "" : "s"}`; base += ` ${count} time${Number(count) === 1 ? "" : "s"}`;
} else if ("while" in config.repeat) { } else if ("while" in config.repeat) {
base += ` while ${ensureArray(config.repeat.while) base += ` while ${ensureArray(config.repeat.while)
.map((condition) => describeCondition(condition, hass)) .map((condition) => describeCondition(condition, hass, entityRegistry))
.join(", ")} is true`; .join(", ")} is true`;
} else if ("until" in config.repeat) { } else if ("until" in config.repeat) {
base += ` until ${ensureArray(config.repeat.until) base += ` until ${ensureArray(config.repeat.until)
.map((condition) => describeCondition(condition, hass)) .map((condition) => describeCondition(condition, hass, entityRegistry))
.join(", ")} is true`; .join(", ")} is true`;
} else if ("for_each" in config.repeat) { } else if ("for_each" in config.repeat) {
base += ` for every item: ${ensureArray(config.repeat.for_each) base += ` for every item: ${ensureArray(config.repeat.for_each)
@ -267,7 +267,11 @@ export const describeAction = <T extends ActionType>(
} }
if (actionType === "check_condition") { if (actionType === "check_condition") {
return `Test ${describeCondition(action as Condition, hass)}`; return `Test ${describeCondition(
action as Condition,
hass,
entityRegistry
)}`;
} }
if (actionType === "device_action") { if (actionType === "device_action") {
@ -275,7 +279,11 @@ export const describeAction = <T extends ActionType>(
if (!config.device_id) { if (!config.device_id) {
return "Device action"; return "Device action";
} }
const localized = localizeDeviceAutomationAction(hass, config); const localized = localizeDeviceAutomationAction(
hass,
entityRegistry,
config
);
if (localized) { if (localized) {
return localized; return localized;
} }

View File

@ -1,3 +1,4 @@
import { consume } from "@lit-labs/context";
import { css, html, LitElement } from "lit"; import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
@ -5,12 +6,14 @@ import { fireEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/device/ha-device-action-picker"; import "../../../../../components/device/ha-device-action-picker";
import "../../../../../components/device/ha-device-picker"; import "../../../../../components/device/ha-device-picker";
import "../../../../../components/ha-form/ha-form"; import "../../../../../components/ha-form/ha-form";
import { fullEntitiesContext } from "../../../../../data/context";
import { import {
DeviceAction, DeviceAction,
deviceAutomationsEqual, deviceAutomationsEqual,
DeviceCapabilities, DeviceCapabilities,
fetchDeviceActionCapabilities, fetchDeviceActionCapabilities,
} from "../../../../../data/device_automation"; } from "../../../../../data/device_automation";
import { EntityRegistryEntry } from "../../../../../data/entity_registry";
import { HomeAssistant } from "../../../../../types"; import { HomeAssistant } from "../../../../../types";
@customElement("ha-automation-action-device_id") @customElement("ha-automation-action-device_id")
@ -25,6 +28,10 @@ export class HaDeviceAction extends LitElement {
@state() private _capabilities?: DeviceCapabilities; @state() private _capabilities?: DeviceCapabilities;
@state()
@consume({ context: fullEntitiesContext, subscribe: true })
_entityReg!: EntityRegistryEntry[];
private _origAction?: DeviceAction; private _origAction?: DeviceAction;
public static get defaultConfig() { public static get defaultConfig() {
@ -98,7 +105,10 @@ export class HaDeviceAction extends LitElement {
protected updated(changedPros) { protected updated(changedPros) {
const prevAction = changedPros.get("action"); const prevAction = changedPros.get("action");
if (prevAction && !deviceAutomationsEqual(prevAction, this.action)) { if (
prevAction &&
!deviceAutomationsEqual(this._entityReg, prevAction, this.action)
) {
this._deviceId = undefined; this._deviceId = undefined;
this._getCapabilities(); this._getCapabilities();
} }
@ -123,7 +133,10 @@ export class HaDeviceAction extends LitElement {
private _deviceActionPicked(ev) { private _deviceActionPicked(ev) {
ev.stopPropagation(); ev.stopPropagation();
let action = ev.detail.value; let action = ev.detail.value;
if (this._origAction && deviceAutomationsEqual(this._origAction, action)) { if (
this._origAction &&
deviceAutomationsEqual(this._entityReg, this._origAction, action)
) {
action = this._origAction; action = this._origAction;
} }
fireEvent(this, "value-changed", { value: action }); fireEvent(this, "value-changed", { value: action });

View File

@ -1,3 +1,4 @@
import { consume } from "@lit-labs/context";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { import {
@ -28,6 +29,8 @@ import type { Clipboard } from "../../../../data/automation";
import { describeCondition } from "../../../../data/automation_i18n"; import { describeCondition } from "../../../../data/automation_i18n";
import { CONDITION_TYPES } from "../../../../data/condition"; import { CONDITION_TYPES } from "../../../../data/condition";
import { validateConfig } from "../../../../data/config"; import { validateConfig } from "../../../../data/config";
import { fullEntitiesContext } from "../../../../data/context";
import { EntityRegistryEntry } from "../../../../data/entity_registry";
import { import {
showAlertDialog, showAlertDialog,
showConfirmationDialog, showConfirmationDialog,
@ -90,6 +93,10 @@ export default class HaAutomationConditionRow extends LitElement {
@state() private _testingResult?: boolean; @state() private _testingResult?: boolean;
@state()
@consume({ context: fullEntitiesContext, subscribe: true })
_entityReg!: EntityRegistryEntry[];
protected render() { protected render() {
if (!this.condition) { if (!this.condition) {
return nothing; return nothing;
@ -111,7 +118,7 @@ export default class HaAutomationConditionRow extends LitElement {
.path=${CONDITION_TYPES[this.condition.condition]} .path=${CONDITION_TYPES[this.condition.condition]}
></ha-svg-icon> ></ha-svg-icon>
${capitalizeFirstLetter( ${capitalizeFirstLetter(
describeCondition(this.condition, this.hass) describeCondition(this.condition, this.hass, this._entityReg)
)} )}
</h3> </h3>
@ -458,7 +465,7 @@ export default class HaAutomationConditionRow extends LitElement {
), ),
inputType: "string", inputType: "string",
placeholder: capitalizeFirstLetter( placeholder: capitalizeFirstLetter(
describeCondition(this.condition, this.hass, true) describeCondition(this.condition, this.hass, this._entityReg, true)
), ),
defaultValue: this.condition.alias, defaultValue: this.condition.alias,
confirmText: this.hass.localize("ui.common.submit"), confirmText: this.hass.localize("ui.common.submit"),

View File

@ -1,3 +1,4 @@
import { consume } from "@lit-labs/context";
import { css, html, LitElement } from "lit"; import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
@ -5,12 +6,14 @@ import { fireEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/device/ha-device-condition-picker"; import "../../../../../components/device/ha-device-condition-picker";
import "../../../../../components/device/ha-device-picker"; import "../../../../../components/device/ha-device-picker";
import "../../../../../components/ha-form/ha-form"; import "../../../../../components/ha-form/ha-form";
import { fullEntitiesContext } from "../../../../../data/context";
import { import {
deviceAutomationsEqual, deviceAutomationsEqual,
DeviceCapabilities, DeviceCapabilities,
DeviceCondition, DeviceCondition,
fetchDeviceConditionCapabilities, fetchDeviceConditionCapabilities,
} from "../../../../../data/device_automation"; } from "../../../../../data/device_automation";
import { EntityRegistryEntry } from "../../../../../data/entity_registry";
import type { HomeAssistant } from "../../../../../types"; import type { HomeAssistant } from "../../../../../types";
@customElement("ha-automation-condition-device") @customElement("ha-automation-condition-device")
@ -25,6 +28,10 @@ export class HaDeviceCondition extends LitElement {
@state() private _capabilities?: DeviceCapabilities; @state() private _capabilities?: DeviceCapabilities;
@state()
@consume({ context: fullEntitiesContext, subscribe: true })
_entityReg!: EntityRegistryEntry[];
private _origCondition?: DeviceCondition; private _origCondition?: DeviceCondition;
public static get defaultConfig() { public static get defaultConfig() {
@ -100,7 +107,7 @@ export class HaDeviceCondition extends LitElement {
const prevCondition = changedPros.get("condition"); const prevCondition = changedPros.get("condition");
if ( if (
prevCondition && prevCondition &&
!deviceAutomationsEqual(prevCondition, this.condition) !deviceAutomationsEqual(this._entityReg, prevCondition, this.condition)
) { ) {
this._getCapabilities(); this._getCapabilities();
} }
@ -129,7 +136,7 @@ export class HaDeviceCondition extends LitElement {
let condition = ev.detail.value; let condition = ev.detail.value;
if ( if (
this._origCondition && this._origCondition &&
deviceAutomationsEqual(this._origCondition, condition) deviceAutomationsEqual(this._entityReg, this._origCondition, condition)
) { ) {
condition = this._origCondition; condition = this._origCondition;
} }

View File

@ -1,3 +1,4 @@
import { consume } from "@lit-labs/context";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { import {
@ -32,6 +33,8 @@ import { HaYamlEditor } from "../../../../components/ha-yaml-editor";
import { subscribeTrigger, Trigger } from "../../../../data/automation"; import { subscribeTrigger, Trigger } from "../../../../data/automation";
import { describeTrigger } from "../../../../data/automation_i18n"; import { describeTrigger } from "../../../../data/automation_i18n";
import { validateConfig } from "../../../../data/config"; import { validateConfig } from "../../../../data/config";
import { fullEntitiesContext } from "../../../../data/context";
import { EntityRegistryEntry } from "../../../../data/entity_registry";
import { TRIGGER_TYPES } from "../../../../data/trigger"; import { TRIGGER_TYPES } from "../../../../data/trigger";
import { import {
showAlertDialog, showAlertDialog,
@ -106,6 +109,10 @@ export default class HaAutomationTriggerRow extends LitElement {
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor; @query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
@state()
@consume({ context: fullEntitiesContext, subscribe: true })
_entityReg!: EntityRegistryEntry[];
private _triggerUnsub?: Promise<UnsubscribeFunc>; private _triggerUnsub?: Promise<UnsubscribeFunc>;
protected render() { protected render() {
@ -133,7 +140,9 @@ export default class HaAutomationTriggerRow extends LitElement {
class="trigger-icon" class="trigger-icon"
.path=${TRIGGER_TYPES[this.trigger.platform]} .path=${TRIGGER_TYPES[this.trigger.platform]}
></ha-svg-icon> ></ha-svg-icon>
${capitalizeFirstLetter(describeTrigger(this.trigger, this.hass))} ${capitalizeFirstLetter(
describeTrigger(this.trigger, this.hass, this._entityReg)
)}
</h3> </h3>
<slot name="icons" slot="icons"></slot> <slot name="icons" slot="icons"></slot>
@ -565,7 +574,7 @@ export default class HaAutomationTriggerRow extends LitElement {
), ),
inputType: "string", inputType: "string",
placeholder: capitalizeFirstLetter( placeholder: capitalizeFirstLetter(
describeTrigger(this.trigger, this.hass, true) describeTrigger(this.trigger, this.hass, this._entityReg, true)
), ),
defaultValue: this.trigger.alias, defaultValue: this.trigger.alias,
confirmText: this.hass.localize("ui.common.submit"), confirmText: this.hass.localize("ui.common.submit"),

View File

@ -1,3 +1,4 @@
import { consume } from "@lit-labs/context";
import { css, html, LitElement } from "lit"; import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
@ -5,12 +6,14 @@ import { fireEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/device/ha-device-picker"; import "../../../../../components/device/ha-device-picker";
import "../../../../../components/device/ha-device-trigger-picker"; import "../../../../../components/device/ha-device-trigger-picker";
import "../../../../../components/ha-form/ha-form"; import "../../../../../components/ha-form/ha-form";
import { fullEntitiesContext } from "../../../../../data/context";
import { import {
deviceAutomationsEqual, deviceAutomationsEqual,
DeviceCapabilities, DeviceCapabilities,
DeviceTrigger, DeviceTrigger,
fetchDeviceTriggerCapabilities, fetchDeviceTriggerCapabilities,
} from "../../../../../data/device_automation"; } from "../../../../../data/device_automation";
import { EntityRegistryEntry } from "../../../../../data/entity_registry";
import { HomeAssistant } from "../../../../../types"; import { HomeAssistant } from "../../../../../types";
@customElement("ha-automation-trigger-device") @customElement("ha-automation-trigger-device")
@ -25,6 +28,10 @@ export class HaDeviceTrigger extends LitElement {
@state() private _capabilities?: DeviceCapabilities; @state() private _capabilities?: DeviceCapabilities;
@state()
@consume({ context: fullEntitiesContext, subscribe: true })
_entityReg!: EntityRegistryEntry[];
private _origTrigger?: DeviceTrigger; private _origTrigger?: DeviceTrigger;
public static get defaultConfig() { public static get defaultConfig() {
@ -56,7 +63,7 @@ export class HaDeviceTrigger extends LitElement {
@value-changed=${this._devicePicked} @value-changed=${this._devicePicked}
.hass=${this.hass} .hass=${this.hass}
.disabled=${this.disabled} .disabled=${this.disabled}
label=${this.hass.localize( .label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.device.label" "ui.panel.config.automation.editor.triggers.type.device.label"
)} )}
></ha-device-picker> ></ha-device-picker>
@ -66,7 +73,7 @@ export class HaDeviceTrigger extends LitElement {
@value-changed=${this._deviceTriggerPicked} @value-changed=${this._deviceTriggerPicked}
.hass=${this.hass} .hass=${this.hass}
.disabled=${this.disabled} .disabled=${this.disabled}
label=${this.hass.localize( .label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.device.trigger" "ui.panel.config.automation.editor.triggers.type.device.trigger"
)} )}
></ha-device-trigger-picker> ></ha-device-trigger-picker>
@ -96,12 +103,15 @@ export class HaDeviceTrigger extends LitElement {
} }
} }
protected updated(changedPros) { protected updated(changedProps) {
if (!changedPros.has("trigger")) { if (!changedProps.has("trigger")) {
return; return;
} }
const prevTrigger = changedPros.get("trigger"); const prevTrigger = changedProps.get("trigger");
if (prevTrigger && !deviceAutomationsEqual(prevTrigger, this.trigger)) { if (
prevTrigger &&
!deviceAutomationsEqual(this._entityReg, prevTrigger, this.trigger)
) {
this._getCapabilities(); this._getCapabilities();
} }
} }
@ -129,7 +139,7 @@ export class HaDeviceTrigger extends LitElement {
let trigger = ev.detail.value; let trigger = ev.detail.value;
if ( if (
this._origTrigger && this._origTrigger &&
deviceAutomationsEqual(this._origTrigger, trigger) deviceAutomationsEqual(this._entityReg, this._origTrigger, trigger)
) { ) {
trigger = this._origTrigger; trigger = this._origTrigger;
} }

View File

@ -1,13 +1,16 @@
import { consume } from "@lit-labs/context";
import { css, html, LitElement, nothing } from "lit"; import { css, html, LitElement, nothing } from "lit";
import { property, state } from "lit/decorators"; import { property, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-chip"; import "../../../../components/ha-chip";
import "../../../../components/ha-chip-set"; import "../../../../components/ha-chip-set";
import { showAutomationEditor } from "../../../../data/automation"; import { showAutomationEditor } from "../../../../data/automation";
import { fullEntitiesContext } from "../../../../data/context";
import { import {
DeviceAction, DeviceAction,
DeviceAutomation, DeviceAutomation,
} from "../../../../data/device_automation"; } from "../../../../data/device_automation";
import { EntityRegistryEntry } from "../../../../data/entity_registry";
import { showScriptEditor } from "../../../../data/script"; import { showScriptEditor } from "../../../../data/script";
import { buttonLinkStyle } from "../../../../resources/styles"; import { buttonLinkStyle } from "../../../../resources/styles";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
@ -31,12 +34,17 @@ export abstract class HaDeviceAutomationCard<
@state() public _showSecondary = false; @state() public _showSecondary = false;
@state()
@consume({ context: fullEntitiesContext, subscribe: true })
_entityReg!: EntityRegistryEntry[];
abstract headerKey: Parameters<typeof this.hass.localize>[0]; abstract headerKey: Parameters<typeof this.hass.localize>[0];
abstract type: "action" | "condition" | "trigger"; abstract type: "action" | "condition" | "trigger";
private _localizeDeviceAutomation: ( private _localizeDeviceAutomation: (
hass: HomeAssistant, hass: HomeAssistant,
entityRegistry: EntityRegistryEntry[],
automation: T automation: T
) => string; ) => string;
@ -79,7 +87,11 @@ export abstract class HaDeviceAutomationCard<
@click=${this._handleAutomationClicked} @click=${this._handleAutomationClicked}
class=${automation.metadata?.secondary ? "secondary" : ""} class=${automation.metadata?.secondary ? "secondary" : ""}
> >
${this._localizeDeviceAutomation(this.hass, automation)} ${this._localizeDeviceAutomation(
this.hass,
this._entityReg,
automation
)}
</ha-chip> </ha-chip>
` `
)} )}

View File

@ -1,3 +1,4 @@
import { ContextProvider } from "@lit-labs/context";
import { import {
mdiAccount, mdiAccount,
mdiBackupRestore, mdiBackupRestore,
@ -28,13 +29,21 @@ import {
mdiViewDashboard, mdiViewDashboard,
} from "@mdi/js"; } from "@mdi/js";
import { PolymerElement } from "@polymer/polymer"; import { PolymerElement } from "@polymer/polymer";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { PropertyValues } from "lit"; import { PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { isComponentLoaded } from "../../common/config/is_component_loaded"; import { isComponentLoaded } from "../../common/config/is_component_loaded";
import { listenMediaQuery } from "../../common/dom/media_query"; import { listenMediaQuery } from "../../common/dom/media_query";
import { CloudStatus, fetchCloudStatus } from "../../data/cloud"; import { CloudStatus, fetchCloudStatus } from "../../data/cloud";
import { fullEntitiesContext } from "../../data/context";
import {
entityRegistryByEntityId,
entityRegistryById,
subscribeEntityRegistry,
} from "../../data/entity_registry";
import { HassRouterPage, RouterOptions } from "../../layouts/hass-router-page"; import { HassRouterPage, RouterOptions } from "../../layouts/hass-router-page";
import { PageNavigation } from "../../layouts/hass-tabs-subpage"; import { PageNavigation } from "../../layouts/hass-tabs-subpage";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import { HomeAssistant, Route } from "../../types"; import { HomeAssistant, Route } from "../../types";
declare global { declare global {
@ -349,13 +358,26 @@ export const configSections: { [name: string]: PageNavigation[] } = {
}; };
@customElement("ha-panel-config") @customElement("ha-panel-config")
class HaPanelConfig extends HassRouterPage { class HaPanelConfig extends SubscribeMixin(HassRouterPage) {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property() public narrow!: boolean; @property() public narrow!: boolean;
@property() public route!: Route; @property() public route!: Route;
private _entitiesContext = new ContextProvider(this, {
context: fullEntitiesContext,
initialValue: [],
});
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeEntityRegistry(this.hass.connection!, (entities) => {
this._entitiesContext.setValue(entities);
}),
];
}
protected routerOptions: RouterOptions = { protected routerOptions: RouterOptions = {
defaultPage: "dashboard", defaultPage: "dashboard",
routes: { routes: {
@ -545,6 +567,8 @@ class HaPanelConfig extends HassRouterPage {
while (this._listeners.length) { while (this._listeners.length) {
this._listeners.pop()!(); this._listeners.pop()!();
} }
entityRegistryByEntityId.clear();
entityRegistryById.clear();
} }
protected firstUpdated(changedProps: PropertyValues) { protected firstUpdated(changedProps: PropertyValues) {

View File

@ -1,19 +1,17 @@
import { HassEntities, UnsubscribeFunc } from "home-assistant-js-websocket"; import { consume } from "@lit-labs/context";
import { HassEntities } from "home-assistant-js-websocket";
import { PropertyValues } from "lit"; import { PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { computeStateDomain } from "../../../common/entity/compute_state_domain"; import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import { debounce } from "../../../common/util/debounce"; import { debounce } from "../../../common/util/debounce";
import { import { fullEntitiesContext } from "../../../data/context";
EntityRegistryEntry, import { EntityRegistryEntry } from "../../../data/entity_registry";
subscribeEntityRegistry,
} from "../../../data/entity_registry";
import { ScriptEntity } from "../../../data/script"; import { ScriptEntity } from "../../../data/script";
import { import {
HassRouterPage, HassRouterPage,
RouterOptions, RouterOptions,
} from "../../../layouts/hass-router-page"; } from "../../../layouts/hass-router-page";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import "./ha-script-editor"; import "./ha-script-editor";
import "./ha-script-picker"; import "./ha-script-picker";
@ -26,7 +24,7 @@ const equal = (a: ScriptEntity[], b: ScriptEntity[]): boolean => {
}; };
@customElement("ha-config-script") @customElement("ha-config-script")
class HaConfigScript extends SubscribeMixin(HassRouterPage) { class HaConfigScript extends HassRouterPage {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property() public narrow!: boolean; @property() public narrow!: boolean;
@ -37,15 +35,9 @@ class HaConfigScript extends SubscribeMixin(HassRouterPage) {
@property() public scripts: ScriptEntity[] = []; @property() public scripts: ScriptEntity[] = [];
@state() private _entityReg: EntityRegistryEntry[] = []; @state()
@consume({ context: fullEntitiesContext, subscribe: true })
public hassSubscribe(): UnsubscribeFunc[] { _entityReg!: EntityRegistryEntry[];
return [
subscribeEntityRegistry(this.hass.connection!, (entities) => {
this._entityReg = entities;
}),
];
}
protected routerOptions: RouterOptions = { protected routerOptions: RouterOptions = {
defaultPage: "dashboard", defaultPage: "dashboard",