mirror of
https://github.com/home-assistant/frontend.git
synced 2025-12-20 15:07:21 +00:00
Add dynamic condition support
This commit is contained in:
84
src/components/ha-condition-icon.ts
Normal file
84
src/components/ha-condition-icon.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import {
|
||||||
|
mdiAmpersand,
|
||||||
|
mdiClockOutline,
|
||||||
|
mdiCodeBraces,
|
||||||
|
mdiDevices,
|
||||||
|
mdiGateOr,
|
||||||
|
mdiIdentifier,
|
||||||
|
mdiMapMarkerRadius,
|
||||||
|
mdiNotEqualVariant,
|
||||||
|
mdiNumeric,
|
||||||
|
mdiStateMachine,
|
||||||
|
mdiWeatherSunny,
|
||||||
|
} from "@mdi/js";
|
||||||
|
import { html, LitElement, nothing } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { until } from "lit/directives/until";
|
||||||
|
import { computeDomain } from "../common/entity/compute_domain";
|
||||||
|
import { conditionIcon, FALLBACK_DOMAIN_ICONS } from "../data/icons";
|
||||||
|
import type { HomeAssistant } from "../types";
|
||||||
|
import "./ha-icon";
|
||||||
|
import "./ha-svg-icon";
|
||||||
|
|
||||||
|
export const CONDITION_ICONS = {
|
||||||
|
device: mdiDevices,
|
||||||
|
and: mdiAmpersand,
|
||||||
|
or: mdiGateOr,
|
||||||
|
not: mdiNotEqualVariant,
|
||||||
|
state: mdiStateMachine,
|
||||||
|
numeric_state: mdiNumeric,
|
||||||
|
sun: mdiWeatherSunny,
|
||||||
|
template: mdiCodeBraces,
|
||||||
|
time: mdiClockOutline,
|
||||||
|
trigger: mdiIdentifier,
|
||||||
|
zone: mdiMapMarkerRadius,
|
||||||
|
};
|
||||||
|
|
||||||
|
@customElement("ha-condition-icon")
|
||||||
|
export class HaConditionIcon extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() public condition?: string;
|
||||||
|
|
||||||
|
@property() public icon?: string;
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (this.icon) {
|
||||||
|
return html`<ha-icon .icon=${this.icon}></ha-icon>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.condition) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.hass) {
|
||||||
|
return this._renderFallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
const icon = conditionIcon(this.hass, this.condition).then((icn) => {
|
||||||
|
if (icn) {
|
||||||
|
return html`<ha-icon .icon=${icn}></ha-icon>`;
|
||||||
|
}
|
||||||
|
return this._renderFallback();
|
||||||
|
});
|
||||||
|
|
||||||
|
return html`${until(icon)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderFallback() {
|
||||||
|
const domain = computeDomain(this.condition!);
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-svg-icon
|
||||||
|
.path=${CONDITION_ICONS[this.condition!] ||
|
||||||
|
FALLBACK_DOMAIN_ICONS[domain]}
|
||||||
|
></ha-svg-icon>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-condition-icon": HaConditionIcon;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import type { LocalizeKeys } from "../common/translations/localize";
|
|||||||
import { createSearchParam } from "../common/url/search-params";
|
import { createSearchParam } from "../common/url/search-params";
|
||||||
import type { Context, HomeAssistant } from "../types";
|
import type { Context, HomeAssistant } from "../types";
|
||||||
import type { BlueprintInput } from "./blueprint";
|
import type { BlueprintInput } from "./blueprint";
|
||||||
|
import type { ConditionDescription } from "./condition";
|
||||||
import { CONDITION_BUILDING_BLOCKS } from "./condition";
|
import { CONDITION_BUILDING_BLOCKS } from "./condition";
|
||||||
import type { DeviceCondition, DeviceTrigger } from "./device_automation";
|
import type { DeviceCondition, DeviceTrigger } from "./device_automation";
|
||||||
import type { Action, Field, MODES } from "./script";
|
import type { Action, Field, MODES } from "./script";
|
||||||
@@ -236,6 +237,12 @@ interface BaseCondition {
|
|||||||
condition: string;
|
condition: string;
|
||||||
alias?: string;
|
alias?: string;
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
|
options?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlatformCondition extends BaseCondition {
|
||||||
|
condition: Exclude<string, LegacyCondition["condition"]>;
|
||||||
|
target?: HassServiceTarget;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LogicalCondition extends BaseCondition {
|
export interface LogicalCondition extends BaseCondition {
|
||||||
@@ -320,7 +327,7 @@ export type AutomationElementGroup = Record<
|
|||||||
{ icon?: string; members?: AutomationElementGroup }
|
{ icon?: string; members?: AutomationElementGroup }
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type Condition =
|
export type LegacyCondition =
|
||||||
| StateCondition
|
| StateCondition
|
||||||
| NumericStateCondition
|
| NumericStateCondition
|
||||||
| SunCondition
|
| SunCondition
|
||||||
@@ -331,6 +338,8 @@ export type Condition =
|
|||||||
| LogicalCondition
|
| LogicalCondition
|
||||||
| TriggerCondition;
|
| TriggerCondition;
|
||||||
|
|
||||||
|
export type Condition = LegacyCondition | PlatformCondition;
|
||||||
|
|
||||||
export type ConditionWithShorthand =
|
export type ConditionWithShorthand =
|
||||||
| Condition
|
| Condition
|
||||||
| ShorthandAndConditionList
|
| ShorthandAndConditionList
|
||||||
@@ -608,6 +617,7 @@ export interface ConditionSidebarConfig extends BaseSidebarConfig {
|
|||||||
insertAfter: (value: Condition | Condition[]) => boolean;
|
insertAfter: (value: Condition | Condition[]) => boolean;
|
||||||
toggleYamlMode: () => void;
|
toggleYamlMode: () => void;
|
||||||
config: Condition;
|
config: Condition;
|
||||||
|
description?: ConditionDescription;
|
||||||
yamlMode: boolean;
|
yamlMode: boolean;
|
||||||
uiSupported: boolean;
|
uiSupported: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,13 @@ import {
|
|||||||
} from "../common/string/format-list";
|
} from "../common/string/format-list";
|
||||||
import { hasTemplate } from "../common/string/has-template";
|
import { hasTemplate } from "../common/string/has-template";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
import type { Condition, ForDict, LegacyTrigger, Trigger } from "./automation";
|
import type {
|
||||||
|
Condition,
|
||||||
|
ForDict,
|
||||||
|
LegacyCondition,
|
||||||
|
LegacyTrigger,
|
||||||
|
Trigger,
|
||||||
|
} from "./automation";
|
||||||
import type { DeviceCondition, DeviceTrigger } from "./device_automation";
|
import type { DeviceCondition, DeviceTrigger } from "./device_automation";
|
||||||
import {
|
import {
|
||||||
localizeDeviceAutomationCondition,
|
localizeDeviceAutomationCondition,
|
||||||
@@ -896,6 +902,39 @@ const tryDescribeCondition = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const description = describeLegacyCondition(
|
||||||
|
condition as LegacyCondition,
|
||||||
|
hass,
|
||||||
|
entityRegistry
|
||||||
|
);
|
||||||
|
|
||||||
|
if (description) {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
const conditionType = condition.condition;
|
||||||
|
|
||||||
|
const domain = getTriggerDomain(condition.condition);
|
||||||
|
const type = getTriggerObjectId(condition.condition);
|
||||||
|
|
||||||
|
return (
|
||||||
|
hass.localize(
|
||||||
|
`component.${domain}.conditions.${type}.description_configured`
|
||||||
|
) ||
|
||||||
|
hass.localize(
|
||||||
|
`ui.panel.config.automation.editor.conditions.type.${conditionType as LegacyCondition["condition"]}.label`
|
||||||
|
) ||
|
||||||
|
hass.localize(
|
||||||
|
`ui.panel.config.automation.editor.conditions.unknown_condition`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const describeLegacyCondition = (
|
||||||
|
condition: LegacyCondition,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entityRegistry: EntityRegistryEntry[]
|
||||||
|
) => {
|
||||||
if (condition.condition === "or") {
|
if (condition.condition === "or") {
|
||||||
const conditions = ensureArray(condition.conditions);
|
const conditions = ensureArray(condition.conditions);
|
||||||
|
|
||||||
@@ -1287,12 +1326,5 @@ const tryDescribeCondition = (
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return undefined;
|
||||||
hass.localize(
|
|
||||||
`ui.panel.config.automation.editor.conditions.type.${condition.condition}.label`
|
|
||||||
) ||
|
|
||||||
hass.localize(
|
|
||||||
`ui.panel.config.automation.editor.conditions.unknown_condition`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,38 +1,15 @@
|
|||||||
import {
|
import { mdiMapClock, mdiShape } from "@mdi/js";
|
||||||
mdiAmpersand,
|
import { computeDomain } from "../common/entity/compute_domain";
|
||||||
mdiClockOutline,
|
import { computeObjectId } from "../common/entity/compute_object_id";
|
||||||
mdiCodeBraces,
|
import type { HomeAssistant } from "../types";
|
||||||
mdiDevices,
|
|
||||||
mdiGateOr,
|
|
||||||
mdiIdentifier,
|
|
||||||
mdiMapClock,
|
|
||||||
mdiMapMarkerRadius,
|
|
||||||
mdiNotEqualVariant,
|
|
||||||
mdiNumeric,
|
|
||||||
mdiShape,
|
|
||||||
mdiStateMachine,
|
|
||||||
mdiWeatherSunny,
|
|
||||||
} from "@mdi/js";
|
|
||||||
import type { AutomationElementGroupCollection } from "./automation";
|
import type { AutomationElementGroupCollection } from "./automation";
|
||||||
|
import type { Selector, TargetSelector } from "./selector";
|
||||||
export const CONDITION_ICONS = {
|
|
||||||
device: mdiDevices,
|
|
||||||
and: mdiAmpersand,
|
|
||||||
or: mdiGateOr,
|
|
||||||
not: mdiNotEqualVariant,
|
|
||||||
state: mdiStateMachine,
|
|
||||||
numeric_state: mdiNumeric,
|
|
||||||
sun: mdiWeatherSunny,
|
|
||||||
template: mdiCodeBraces,
|
|
||||||
time: mdiClockOutline,
|
|
||||||
trigger: mdiIdentifier,
|
|
||||||
zone: mdiMapMarkerRadius,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CONDITION_COLLECTIONS: AutomationElementGroupCollection[] = [
|
export const CONDITION_COLLECTIONS: AutomationElementGroupCollection[] = [
|
||||||
{
|
{
|
||||||
groups: {
|
groups: {
|
||||||
device: {},
|
device: {},
|
||||||
|
dynamicGroups: {},
|
||||||
entity: { icon: mdiShape, members: { state: {}, numeric_state: {} } },
|
entity: { icon: mdiShape, members: { state: {}, numeric_state: {} } },
|
||||||
time_location: {
|
time_location: {
|
||||||
icon: mdiMapClock,
|
icon: mdiMapClock,
|
||||||
@@ -62,3 +39,33 @@ export const COLLAPSIBLE_CONDITION_ELEMENTS = [
|
|||||||
"ha-automation-condition-not",
|
"ha-automation-condition-not",
|
||||||
"ha-automation-condition-or",
|
"ha-automation-condition-or",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export interface ConditionDescription {
|
||||||
|
target?: TargetSelector["target"];
|
||||||
|
fields: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
example?: string | boolean | number;
|
||||||
|
default?: unknown;
|
||||||
|
required?: boolean;
|
||||||
|
selector?: Selector;
|
||||||
|
context?: Record<string, string>;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ConditionDescriptions = Record<string, ConditionDescription>;
|
||||||
|
|
||||||
|
export const subscribeConditions = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
callback: (conditions: ConditionDescriptions) => void
|
||||||
|
) =>
|
||||||
|
hass.connection.subscribeMessage<ConditionDescriptions>(callback, {
|
||||||
|
type: "condition_platforms/subscribe",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getConditionDomain = (condition: string) =>
|
||||||
|
condition.includes(".") ? computeDomain(condition) : condition;
|
||||||
|
|
||||||
|
export const getConditionObjectId = (condition: string) =>
|
||||||
|
condition.includes(".") ? computeObjectId(condition) : "_";
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ import type {
|
|||||||
|
|
||||||
import { mdiHomeAssistant } from "../resources/home-assistant-logo-svg";
|
import { mdiHomeAssistant } from "../resources/home-assistant-logo-svg";
|
||||||
import { getTriggerDomain, getTriggerObjectId } from "./trigger";
|
import { getTriggerDomain, getTriggerObjectId } from "./trigger";
|
||||||
|
import { getConditionDomain, getConditionObjectId } from "./condition";
|
||||||
|
|
||||||
/** Icon to use when no icon specified for service. */
|
/** Icon to use when no icon specified for service. */
|
||||||
export const DEFAULT_SERVICE_ICON = mdiRoomService;
|
export const DEFAULT_SERVICE_ICON = mdiRoomService;
|
||||||
@@ -138,15 +139,25 @@ const resources: {
|
|||||||
all?: Promise<Record<string, TriggerIcons>>;
|
all?: Promise<Record<string, TriggerIcons>>;
|
||||||
domains: Record<string, TriggerIcons | Promise<TriggerIcons>>;
|
domains: Record<string, TriggerIcons | Promise<TriggerIcons>>;
|
||||||
};
|
};
|
||||||
|
conditions: {
|
||||||
|
all?: Promise<Record<string, ConditionIcons>>;
|
||||||
|
domains: Record<string, ConditionIcons | Promise<ConditionIcons>>;
|
||||||
|
};
|
||||||
} = {
|
} = {
|
||||||
entity: {},
|
entity: {},
|
||||||
entity_component: {},
|
entity_component: {},
|
||||||
services: { domains: {} },
|
services: { domains: {} },
|
||||||
triggers: { domains: {} },
|
triggers: { domains: {} },
|
||||||
|
conditions: { domains: {} },
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IconResources<
|
interface IconResources<
|
||||||
T extends ComponentIcons | PlatformIcons | ServiceIcons | TriggerIcons,
|
T extends
|
||||||
|
| ComponentIcons
|
||||||
|
| PlatformIcons
|
||||||
|
| ServiceIcons
|
||||||
|
| TriggerIcons
|
||||||
|
| ConditionIcons,
|
||||||
> {
|
> {
|
||||||
resources: Record<string, T>;
|
resources: Record<string, T>;
|
||||||
}
|
}
|
||||||
@@ -195,17 +206,24 @@ type TriggerIcons = Record<
|
|||||||
{ trigger: string; sections?: Record<string, string> }
|
{ trigger: string; sections?: Record<string, string> }
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
type ConditionIcons = Record<
|
||||||
|
string,
|
||||||
|
{ condition: string; sections?: Record<string, string> }
|
||||||
|
>;
|
||||||
|
|
||||||
export type IconCategory =
|
export type IconCategory =
|
||||||
| "entity"
|
| "entity"
|
||||||
| "entity_component"
|
| "entity_component"
|
||||||
| "services"
|
| "services"
|
||||||
| "triggers";
|
| "triggers"
|
||||||
|
| "conditions";
|
||||||
|
|
||||||
interface CategoryType {
|
interface CategoryType {
|
||||||
entity: PlatformIcons;
|
entity: PlatformIcons;
|
||||||
entity_component: ComponentIcons;
|
entity_component: ComponentIcons;
|
||||||
services: ServiceIcons;
|
services: ServiceIcons;
|
||||||
triggers: TriggerIcons;
|
triggers: TriggerIcons;
|
||||||
|
conditions: ConditionIcons;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getHassIcons = async <T extends IconCategory>(
|
export const getHassIcons = async <T extends IconCategory>(
|
||||||
@@ -327,6 +345,13 @@ export const getTriggerIcons = async (
|
|||||||
): Promise<TriggerIcons | Record<string, TriggerIcons> | undefined> =>
|
): Promise<TriggerIcons | Record<string, TriggerIcons> | undefined> =>
|
||||||
getCategoryIcons(hass, "triggers", domain, force);
|
getCategoryIcons(hass, "triggers", domain, force);
|
||||||
|
|
||||||
|
export const getConditionIcons = async (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
domain?: string,
|
||||||
|
force = false
|
||||||
|
): Promise<ConditionIcons | Record<string, ConditionIcons> | undefined> =>
|
||||||
|
getCategoryIcons(hass, "conditions", domain, force);
|
||||||
|
|
||||||
// Cache for sorted range keys
|
// Cache for sorted range keys
|
||||||
const sortedRangeCache = new WeakMap<Record<string, string>, number[]>();
|
const sortedRangeCache = new WeakMap<Record<string, string>, number[]>();
|
||||||
|
|
||||||
@@ -526,6 +551,26 @@ export const triggerIcon = async (
|
|||||||
return icon;
|
return icon;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const conditionIcon = async (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
condition: string
|
||||||
|
): Promise<string | undefined> => {
|
||||||
|
let icon: string | undefined;
|
||||||
|
|
||||||
|
const domain = getConditionDomain(condition);
|
||||||
|
const conditionName = getConditionObjectId(condition);
|
||||||
|
|
||||||
|
const conditionIcons = await getConditionIcons(hass, domain);
|
||||||
|
if (conditionIcons) {
|
||||||
|
const condIcon = conditionIcons[conditionName] as ConditionIcons[string];
|
||||||
|
icon = condIcon?.condition;
|
||||||
|
}
|
||||||
|
if (!icon) {
|
||||||
|
icon = await domainIcon(hass, domain);
|
||||||
|
}
|
||||||
|
return icon;
|
||||||
|
};
|
||||||
|
|
||||||
export const serviceIcon = async (
|
export const serviceIcon = async (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
service: string
|
service: string
|
||||||
|
|||||||
@@ -75,7 +75,8 @@ export type TranslationCategory =
|
|||||||
| "preview_features"
|
| "preview_features"
|
||||||
| "selector"
|
| "selector"
|
||||||
| "services"
|
| "services"
|
||||||
| "triggers";
|
| "triggers"
|
||||||
|
| "conditions";
|
||||||
|
|
||||||
export const subscribeTranslationPreferences = (
|
export const subscribeTranslationPreferences = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
|||||||
@@ -1,19 +1,29 @@
|
|||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, query } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
import { stringCompare } from "../../../../../common/string/compare";
|
|
||||||
import { stopPropagation } from "../../../../../common/dom/stop_propagation";
|
import { stopPropagation } from "../../../../../common/dom/stop_propagation";
|
||||||
|
import { stringCompare } from "../../../../../common/string/compare";
|
||||||
import type { LocalizeFunc } from "../../../../../common/translations/localize";
|
import type { LocalizeFunc } from "../../../../../common/translations/localize";
|
||||||
|
import { CONDITION_ICONS } from "../../../../../components/ha-condition-icon";
|
||||||
import "../../../../../components/ha-list-item";
|
import "../../../../../components/ha-list-item";
|
||||||
import "../../../../../components/ha-select";
|
import "../../../../../components/ha-select";
|
||||||
import type { HaSelect } from "../../../../../components/ha-select";
|
import type { HaSelect } from "../../../../../components/ha-select";
|
||||||
import type { Condition } from "../../../../../data/automation";
|
import {
|
||||||
|
DYNAMIC_PREFIX,
|
||||||
|
getValueFromDynamic,
|
||||||
|
isDynamic,
|
||||||
|
type Condition,
|
||||||
|
} from "../../../../../data/automation";
|
||||||
|
import type { ConditionDescriptions } from "../../../../../data/condition";
|
||||||
import {
|
import {
|
||||||
CONDITION_BUILDING_BLOCKS,
|
CONDITION_BUILDING_BLOCKS,
|
||||||
CONDITION_ICONS,
|
getConditionDomain,
|
||||||
|
getConditionObjectId,
|
||||||
|
subscribeConditions,
|
||||||
} from "../../../../../data/condition";
|
} from "../../../../../data/condition";
|
||||||
import type { Entries, HomeAssistant } from "../../../../../types";
|
import { SubscribeMixin } from "../../../../../mixins/subscribe-mixin";
|
||||||
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
import "../../condition/ha-automation-condition-editor";
|
import "../../condition/ha-automation-condition-editor";
|
||||||
import type HaAutomationConditionEditor from "../../condition/ha-automation-condition-editor";
|
import type HaAutomationConditionEditor from "../../condition/ha-automation-condition-editor";
|
||||||
import "../../condition/types/ha-automation-condition-and";
|
import "../../condition/types/ha-automation-condition-and";
|
||||||
@@ -30,7 +40,10 @@ import "../../condition/types/ha-automation-condition-zone";
|
|||||||
import type { ActionElement } from "../ha-automation-action-row";
|
import type { ActionElement } from "../ha-automation-action-row";
|
||||||
|
|
||||||
@customElement("ha-automation-action-condition")
|
@customElement("ha-automation-action-condition")
|
||||||
export class HaConditionAction extends LitElement implements ActionElement {
|
export class HaConditionAction
|
||||||
|
extends SubscribeMixin(LitElement)
|
||||||
|
implements ActionElement
|
||||||
|
{
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
@@ -43,6 +56,8 @@ export class HaConditionAction extends LitElement implements ActionElement {
|
|||||||
|
|
||||||
@property({ type: Boolean, attribute: "indent" }) public indent = false;
|
@property({ type: Boolean, attribute: "indent" }) public indent = false;
|
||||||
|
|
||||||
|
@state() private _conditionDescriptions: ConditionDescriptions = {};
|
||||||
|
|
||||||
@query("ha-automation-condition-editor")
|
@query("ha-automation-condition-editor")
|
||||||
private _conditionEditor?: HaAutomationConditionEditor;
|
private _conditionEditor?: HaAutomationConditionEditor;
|
||||||
|
|
||||||
@@ -50,6 +65,21 @@ export class HaConditionAction extends LitElement implements ActionElement {
|
|||||||
return { condition: "state" };
|
return { condition: "state" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected hassSubscribe() {
|
||||||
|
return [
|
||||||
|
subscribeConditions(this.hass, (conditions) =>
|
||||||
|
this._addConditions(conditions)
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addConditions(conditions: ConditionDescriptions) {
|
||||||
|
this._conditionDescriptions = {
|
||||||
|
...this._conditionDescriptions,
|
||||||
|
...conditions,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
const buildingBlock = CONDITION_BUILDING_BLOCKS.includes(
|
const buildingBlock = CONDITION_BUILDING_BLOCKS.includes(
|
||||||
this.action.condition
|
this.action.condition
|
||||||
@@ -64,19 +94,25 @@ export class HaConditionAction extends LitElement implements ActionElement {
|
|||||||
"ui.panel.config.automation.editor.conditions.type_select"
|
"ui.panel.config.automation.editor.conditions.type_select"
|
||||||
)}
|
)}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.value=${this.action.condition}
|
.value=${this.action.condition in this._conditionDescriptions
|
||||||
|
? `${DYNAMIC_PREFIX}${this.action.condition}`
|
||||||
|
: this.action.condition}
|
||||||
naturalMenuWidth
|
naturalMenuWidth
|
||||||
@selected=${this._typeChanged}
|
@selected=${this._typeChanged}
|
||||||
@closed=${stopPropagation}
|
@closed=${stopPropagation}
|
||||||
>
|
>
|
||||||
${this._processedTypes(this.hass.localize).map(
|
${this._processedTypes(
|
||||||
([opt, label, icon]) => html`
|
this._conditionDescriptions,
|
||||||
|
this.hass.localize
|
||||||
|
).map(
|
||||||
|
([opt, label, condition]) => html`
|
||||||
<ha-list-item .value=${opt} graphic="icon">
|
<ha-list-item .value=${opt} graphic="icon">
|
||||||
${label}<ha-svg-icon
|
${label}
|
||||||
|
<ha-condition-icon
|
||||||
slot="graphic"
|
slot="graphic"
|
||||||
.path=${icon}
|
.condition=${condition}
|
||||||
></ha-svg-icon
|
></ha-condition-icon>
|
||||||
></ha-list-item>
|
</ha-list-item>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</ha-select>
|
</ha-select>
|
||||||
@@ -88,11 +124,14 @@ export class HaConditionAction extends LitElement implements ActionElement {
|
|||||||
? html`
|
? html`
|
||||||
<ha-automation-condition-editor
|
<ha-automation-condition-editor
|
||||||
.condition=${this.action}
|
.condition=${this.action}
|
||||||
|
.description=${this._conditionDescriptions[this.action.condition]}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@value-changed=${this._conditionChanged}
|
@value-changed=${this._conditionChanged}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
.uiSupported=${this._uiSupported(this.action.condition)}
|
.uiSupported=${this._uiSupported(
|
||||||
|
this._getType(this.action, this._conditionDescriptions)
|
||||||
|
)}
|
||||||
.indent=${this.indent}
|
.indent=${this.indent}
|
||||||
action
|
action
|
||||||
></ha-automation-condition-editor>
|
></ha-automation-condition-editor>
|
||||||
@@ -102,19 +141,46 @@ export class HaConditionAction extends LitElement implements ActionElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _processedTypes = memoizeOne(
|
private _processedTypes = memoizeOne(
|
||||||
(localize: LocalizeFunc): [string, string, string][] =>
|
(
|
||||||
(Object.entries(CONDITION_ICONS) as Entries<typeof CONDITION_ICONS>)
|
conditionDescriptions: ConditionDescriptions,
|
||||||
.map(
|
localize: LocalizeFunc
|
||||||
([condition, icon]) =>
|
): [string, string, string][] => {
|
||||||
[
|
const legacy = (
|
||||||
condition,
|
Object.keys(CONDITION_ICONS) as (keyof typeof CONDITION_ICONS)[]
|
||||||
localize(
|
).map(
|
||||||
`ui.panel.config.automation.editor.conditions.type.${condition}.label`
|
(condition) =>
|
||||||
),
|
[
|
||||||
icon,
|
condition,
|
||||||
] as [string, string, string]
|
localize(
|
||||||
)
|
`ui.panel.config.automation.editor.conditions.type.${condition}.label`
|
||||||
.sort((a, b) => stringCompare(a[1], b[1], this.hass.locale.language))
|
),
|
||||||
|
condition,
|
||||||
|
] as [string, string, string]
|
||||||
|
);
|
||||||
|
const platform = Object.keys(conditionDescriptions).map((condition) => {
|
||||||
|
const domain = getConditionDomain(condition);
|
||||||
|
const conditionObjId = getConditionObjectId(condition);
|
||||||
|
return [
|
||||||
|
`${DYNAMIC_PREFIX}${condition}`,
|
||||||
|
localize(`component.${domain}.conditions.${conditionObjId}.name`) ||
|
||||||
|
condition,
|
||||||
|
condition,
|
||||||
|
] as [string, string, string];
|
||||||
|
});
|
||||||
|
return [...legacy, ...platform].sort((a, b) =>
|
||||||
|
stringCompare(a[1], b[1], this.hass.locale.language)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
private _getType = memoizeOne(
|
||||||
|
(condition: Condition, conditionDescriptions: ConditionDescriptions) => {
|
||||||
|
if (condition.condition in conditionDescriptions) {
|
||||||
|
return "platform";
|
||||||
|
}
|
||||||
|
|
||||||
|
return condition.condition;
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
private _conditionChanged(ev: CustomEvent) {
|
private _conditionChanged(ev: CustomEvent) {
|
||||||
@@ -132,6 +198,18 @@ export class HaConditionAction extends LitElement implements ActionElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isDynamic(type)) {
|
||||||
|
const value = getValueFromDynamic(type);
|
||||||
|
if (value !== this.action.condition) {
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: {
|
||||||
|
condition: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const elClass = customElements.get(
|
const elClass = customElements.get(
|
||||||
`ha-automation-condition-${type}`
|
`ha-automation-condition-${type}`
|
||||||
) as CustomElementConstructor & {
|
) as CustomElementConstructor & {
|
||||||
|
|||||||
@@ -56,12 +56,19 @@ import {
|
|||||||
type AutomationElementGroup,
|
type AutomationElementGroup,
|
||||||
type AutomationElementGroupCollection,
|
type AutomationElementGroupCollection,
|
||||||
} from "../../../data/automation";
|
} from "../../../data/automation";
|
||||||
|
import type { ConditionDescriptions } from "../../../data/condition";
|
||||||
import {
|
import {
|
||||||
CONDITION_BUILDING_BLOCKS_GROUP,
|
CONDITION_BUILDING_BLOCKS_GROUP,
|
||||||
CONDITION_COLLECTIONS,
|
CONDITION_COLLECTIONS,
|
||||||
CONDITION_ICONS,
|
getConditionDomain,
|
||||||
|
getConditionObjectId,
|
||||||
|
subscribeConditions,
|
||||||
} from "../../../data/condition";
|
} from "../../../data/condition";
|
||||||
import { getServiceIcons, getTriggerIcons } from "../../../data/icons";
|
import {
|
||||||
|
getConditionIcons,
|
||||||
|
getServiceIcons,
|
||||||
|
getTriggerIcons,
|
||||||
|
} from "../../../data/icons";
|
||||||
import type { IntegrationManifest } from "../../../data/integration";
|
import type { IntegrationManifest } from "../../../data/integration";
|
||||||
import {
|
import {
|
||||||
domainToName,
|
domainToName,
|
||||||
@@ -82,6 +89,7 @@ import { isMac } from "../../../util/is_mac";
|
|||||||
import { showToast } from "../../../util/toast";
|
import { showToast } from "../../../util/toast";
|
||||||
import type { AddAutomationElementDialogParams } from "./show-add-automation-element-dialog";
|
import type { AddAutomationElementDialogParams } from "./show-add-automation-element-dialog";
|
||||||
import { PASTE_VALUE } from "./show-add-automation-element-dialog";
|
import { PASTE_VALUE } from "./show-add-automation-element-dialog";
|
||||||
|
import { CONDITION_ICONS } from "../../../components/ha-condition-icon";
|
||||||
|
|
||||||
const TYPES = {
|
const TYPES = {
|
||||||
trigger: { collections: TRIGGER_COLLECTIONS, icons: TRIGGER_ICONS },
|
trigger: { collections: TRIGGER_COLLECTIONS, icons: TRIGGER_ICONS },
|
||||||
@@ -152,6 +160,8 @@ class DialogAddAutomationElement
|
|||||||
|
|
||||||
@state() private _triggerDescriptions: TriggerDescriptions = {};
|
@state() private _triggerDescriptions: TriggerDescriptions = {};
|
||||||
|
|
||||||
|
@state() private _conditionDescriptions: ConditionDescriptions = {};
|
||||||
|
|
||||||
@query(".items ha-md-list ha-md-list-item")
|
@query(".items ha-md-list ha-md-list-item")
|
||||||
private _itemsListFirstElement?: HaMdList;
|
private _itemsListFirstElement?: HaMdList;
|
||||||
|
|
||||||
@@ -169,6 +179,8 @@ class DialogAddAutomationElement
|
|||||||
|
|
||||||
this.addKeyboardShortcuts();
|
this.addKeyboardShortcuts();
|
||||||
|
|
||||||
|
this._unsubscribe();
|
||||||
|
|
||||||
if (this._params?.type === "action") {
|
if (this._params?.type === "action") {
|
||||||
this.hass.loadBackendTranslation("services");
|
this.hass.loadBackendTranslation("services");
|
||||||
this._fetchManifests();
|
this._fetchManifests();
|
||||||
@@ -186,6 +198,18 @@ class DialogAddAutomationElement
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (this._params?.type === "condition") {
|
||||||
|
this.hass.loadBackendTranslation("conditions");
|
||||||
|
this._fetchManifests();
|
||||||
|
getConditionIcons(this.hass);
|
||||||
|
this._unsub = subscribeConditions(this.hass, (conditions) => {
|
||||||
|
this._conditionDescriptions = {
|
||||||
|
...this._conditionDescriptions,
|
||||||
|
...conditions,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this._fullScreen = matchMedia(
|
this._fullScreen = matchMedia(
|
||||||
"all and (max-width: 450px), all and (max-height: 500px)"
|
"all and (max-width: 450px), all and (max-height: 500px)"
|
||||||
).matches;
|
).matches;
|
||||||
@@ -199,10 +223,7 @@ class DialogAddAutomationElement
|
|||||||
|
|
||||||
public closeDialog() {
|
public closeDialog() {
|
||||||
this.removeKeyboardShortcuts();
|
this.removeKeyboardShortcuts();
|
||||||
if (this._unsub) {
|
this._unsubscribe();
|
||||||
this._unsub.then((unsub) => unsub());
|
|
||||||
this._unsub = undefined;
|
|
||||||
}
|
|
||||||
if (this._params) {
|
if (this._params) {
|
||||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||||
}
|
}
|
||||||
@@ -219,6 +240,13 @@ class DialogAddAutomationElement
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _unsubscribe() {
|
||||||
|
if (this._unsub) {
|
||||||
|
this._unsub.then((unsub) => unsub());
|
||||||
|
this._unsub = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _getGroups = (
|
private _getGroups = (
|
||||||
type: AddAutomationElementDialogParams["type"],
|
type: AddAutomationElementDialogParams["type"],
|
||||||
group?: string,
|
group?: string,
|
||||||
@@ -349,6 +377,11 @@ class DialogAddAutomationElement
|
|||||||
...this._triggers(localize, this._triggerDescriptions, manifests)
|
...this._triggers(localize, this._triggerDescriptions, manifests)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (type === "condition") {
|
||||||
|
items.push(
|
||||||
|
...this._conditions(localize, this._conditionDescriptions, manifests)
|
||||||
|
);
|
||||||
|
}
|
||||||
if (type === "action") {
|
if (type === "action") {
|
||||||
items.push(...this._services(localize, services, manifests));
|
items.push(...this._services(localize, services, manifests));
|
||||||
}
|
}
|
||||||
@@ -372,6 +405,7 @@ class DialogAddAutomationElement
|
|||||||
localize: LocalizeFunc,
|
localize: LocalizeFunc,
|
||||||
services: HomeAssistant["services"],
|
services: HomeAssistant["services"],
|
||||||
triggerDescriptions: TriggerDescriptions,
|
triggerDescriptions: TriggerDescriptions,
|
||||||
|
conditionDescriptions: ConditionDescriptions,
|
||||||
manifests?: DomainManifestLookup
|
manifests?: DomainManifestLookup
|
||||||
): {
|
): {
|
||||||
titleKey?: LocalizeKeys;
|
titleKey?: LocalizeKeys;
|
||||||
@@ -433,6 +467,31 @@ class DialogAddAutomationElement
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
type === "condition" &&
|
||||||
|
Object.keys(collection.groups).some((item) =>
|
||||||
|
ACTION_SERVICE_KEYWORDS.includes(item)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
groups.push(
|
||||||
|
...this._conditionGroups(
|
||||||
|
localize,
|
||||||
|
conditionDescriptions,
|
||||||
|
manifests,
|
||||||
|
domains,
|
||||||
|
collection.groups.dynamicGroups
|
||||||
|
? undefined
|
||||||
|
: collection.groups.helpers
|
||||||
|
? "helper"
|
||||||
|
: "other"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
collectionGroups = collectionGroups.filter(
|
||||||
|
([key]) => !ACTION_SERVICE_KEYWORDS.includes(key)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
groups.push(
|
groups.push(
|
||||||
...collectionGroups.map(([key, options]) =>
|
...collectionGroups.map(([key, options]) =>
|
||||||
this._convertToItem(key, options, type, localize)
|
this._convertToItem(key, options, type, localize)
|
||||||
@@ -500,6 +559,15 @@ class DialogAddAutomationElement
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type === "condition" && isDynamic(group)) {
|
||||||
|
return this._conditions(
|
||||||
|
localize,
|
||||||
|
this._conditionDescriptions,
|
||||||
|
manifests,
|
||||||
|
group
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const groups = this._getGroups(type, group, collectionIndex);
|
const groups = this._getGroups(type, group, collectionIndex);
|
||||||
|
|
||||||
const result = Object.entries(groups).map(([key, options]) =>
|
const result = Object.entries(groups).map(([key, options]) =>
|
||||||
@@ -688,6 +756,102 @@ class DialogAddAutomationElement
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
private _conditionGroups = (
|
||||||
|
localize: LocalizeFunc,
|
||||||
|
conditions: ConditionDescriptions,
|
||||||
|
manifests: DomainManifestLookup | undefined,
|
||||||
|
domains: Set<string> | undefined,
|
||||||
|
type: "helper" | "other" | undefined
|
||||||
|
): ListItem[] => {
|
||||||
|
if (!conditions || !manifests) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const result: ListItem[] = [];
|
||||||
|
const addedDomains = new Set<string>();
|
||||||
|
Object.keys(conditions).forEach((condition) => {
|
||||||
|
const domain = getConditionDomain(condition);
|
||||||
|
|
||||||
|
if (addedDomains.has(domain)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
addedDomains.add(domain);
|
||||||
|
|
||||||
|
const manifest = manifests[domain];
|
||||||
|
const domainUsed = !domains ? true : domains.has(domain);
|
||||||
|
|
||||||
|
if (
|
||||||
|
(type === undefined &&
|
||||||
|
(ENTITY_DOMAINS_MAIN.has(domain) ||
|
||||||
|
(manifest?.integration_type === "entity" &&
|
||||||
|
domainUsed &&
|
||||||
|
!ENTITY_DOMAINS_OTHER.has(domain)))) ||
|
||||||
|
(type === "helper" && manifest?.integration_type === "helper") ||
|
||||||
|
(type === "other" &&
|
||||||
|
!ENTITY_DOMAINS_MAIN.has(domain) &&
|
||||||
|
(ENTITY_DOMAINS_OTHER.has(domain) ||
|
||||||
|
(!domainUsed && manifest?.integration_type === "entity") ||
|
||||||
|
!["helper", "entity"].includes(manifest?.integration_type || "")))
|
||||||
|
) {
|
||||||
|
result.push({
|
||||||
|
icon: html`
|
||||||
|
<ha-domain-icon
|
||||||
|
.hass=${this.hass}
|
||||||
|
.domain=${domain}
|
||||||
|
brand-fallback
|
||||||
|
></ha-domain-icon>
|
||||||
|
`,
|
||||||
|
key: `${DYNAMIC_PREFIX}${domain}`,
|
||||||
|
name: domainToName(localize, domain, manifest),
|
||||||
|
description: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result.sort((a, b) =>
|
||||||
|
stringCompare(a.name, b.name, this.hass.locale.language)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
private _conditions = memoizeOne(
|
||||||
|
(
|
||||||
|
localize: LocalizeFunc,
|
||||||
|
conditions: ConditionDescriptions,
|
||||||
|
_manifests: DomainManifestLookup | undefined,
|
||||||
|
group?: string
|
||||||
|
): ListItem[] => {
|
||||||
|
if (!conditions) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const result: ListItem[] = [];
|
||||||
|
|
||||||
|
for (const condition of Object.keys(conditions)) {
|
||||||
|
const domain = getConditionDomain(condition);
|
||||||
|
const conditionName = getConditionObjectId(condition);
|
||||||
|
|
||||||
|
if (group && group !== `${DYNAMIC_PREFIX}${domain}`) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push({
|
||||||
|
icon: html`
|
||||||
|
<ha-condition-icon
|
||||||
|
.hass=${this.hass}
|
||||||
|
.condition=${condition}
|
||||||
|
></ha-condition-icon>
|
||||||
|
`,
|
||||||
|
key: `${DYNAMIC_PREFIX}${condition}`,
|
||||||
|
name:
|
||||||
|
localize(`component.${domain}.conditions.${conditionName}.name`) ||
|
||||||
|
condition,
|
||||||
|
description:
|
||||||
|
localize(
|
||||||
|
`component.${domain}.conditions.${conditionName}.description`
|
||||||
|
) || condition,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
private _services = memoizeOne(
|
private _services = memoizeOne(
|
||||||
(
|
(
|
||||||
localize: LocalizeFunc,
|
localize: LocalizeFunc,
|
||||||
@@ -832,6 +996,7 @@ class DialogAddAutomationElement
|
|||||||
this.hass.localize,
|
this.hass.localize,
|
||||||
this.hass.services,
|
this.hass.services,
|
||||||
this._triggerDescriptions,
|
this._triggerDescriptions,
|
||||||
|
this._conditionDescriptions,
|
||||||
this._manifests
|
this._manifests
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1136,6 +1301,7 @@ class DialogAddAutomationElement
|
|||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
window.removeEventListener("resize", this._updateNarrow);
|
window.removeEventListener("resize", this._updateNarrow);
|
||||||
this._removeSearchKeybindings();
|
this._removeSearchKeybindings();
|
||||||
|
this._unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _updateNarrow = () => {
|
private _updateNarrow = () => {
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ import "../../../../components/ha-yaml-editor";
|
|||||||
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
|
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
|
||||||
import type { Condition } from "../../../../data/automation";
|
import type { Condition } from "../../../../data/automation";
|
||||||
import { expandConditionWithShorthand } from "../../../../data/automation";
|
import { expandConditionWithShorthand } from "../../../../data/automation";
|
||||||
|
import type { ConditionDescription } from "../../../../data/condition";
|
||||||
import { COLLAPSIBLE_CONDITION_ELEMENTS } from "../../../../data/condition";
|
import { COLLAPSIBLE_CONDITION_ELEMENTS } from "../../../../data/condition";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import "../ha-automation-editor-warning";
|
import "../ha-automation-editor-warning";
|
||||||
import { editorStyles, indentStyle } from "../styles";
|
import { editorStyles, indentStyle } from "../styles";
|
||||||
import type { ConditionElement } from "./ha-automation-condition-row";
|
import type { ConditionElement } from "./ha-automation-condition-row";
|
||||||
|
import "./types/ha-automation-condition-platform";
|
||||||
|
|
||||||
@customElement("ha-automation-condition-editor")
|
@customElement("ha-automation-condition-editor")
|
||||||
export default class HaAutomationConditionEditor extends LitElement {
|
export default class HaAutomationConditionEditor extends LitElement {
|
||||||
@@ -35,6 +37,8 @@ export default class HaAutomationConditionEditor extends LitElement {
|
|||||||
@property({ type: Boolean, attribute: "supported" }) public uiSupported =
|
@property({ type: Boolean, attribute: "supported" }) public uiSupported =
|
||||||
false;
|
false;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public description?: ConditionDescription;
|
||||||
|
|
||||||
@query("ha-yaml-editor") public yamlEditor?: HaYamlEditor;
|
@query("ha-yaml-editor") public yamlEditor?: HaYamlEditor;
|
||||||
|
|
||||||
@query(COLLAPSIBLE_CONDITION_ELEMENTS.join(", "))
|
@query(COLLAPSIBLE_CONDITION_ELEMENTS.join(", "))
|
||||||
@@ -83,16 +87,23 @@ export default class HaAutomationConditionEditor extends LitElement {
|
|||||||
`
|
`
|
||||||
: html`
|
: html`
|
||||||
<div @value-changed=${this._onUiChanged}>
|
<div @value-changed=${this._onUiChanged}>
|
||||||
${dynamicElement(
|
${this.description
|
||||||
`ha-automation-condition-${condition.condition}`,
|
? html`<ha-automation-condition-platform
|
||||||
{
|
.hass=${this.hass}
|
||||||
hass: this.hass,
|
.condition=${this.condition}
|
||||||
condition: condition,
|
.description=${this.description}
|
||||||
disabled: this.disabled,
|
.disabled=${this.disabled}
|
||||||
optionsInSidebar: this.indent,
|
></ha-automation-condition-platform>`
|
||||||
narrow: this.narrow,
|
: dynamicElement(
|
||||||
}
|
`ha-automation-condition-${condition.condition}`,
|
||||||
)}
|
{
|
||||||
|
hass: this.hass,
|
||||||
|
condition: condition,
|
||||||
|
disabled: this.disabled,
|
||||||
|
optionsInSidebar: this.indent,
|
||||||
|
narrow: this.narrow,
|
||||||
|
}
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
`}
|
`}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import { copyToClipboard } from "../../../../common/util/copy-clipboard";
|
|||||||
import "../../../../components/ha-automation-row";
|
import "../../../../components/ha-automation-row";
|
||||||
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
|
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
|
||||||
import "../../../../components/ha-card";
|
import "../../../../components/ha-card";
|
||||||
|
import "../../../../components/ha-condition-icon";
|
||||||
import "../../../../components/ha-expansion-panel";
|
import "../../../../components/ha-expansion-panel";
|
||||||
import "../../../../components/ha-icon-button";
|
import "../../../../components/ha-icon-button";
|
||||||
import "../../../../components/ha-md-button-menu";
|
import "../../../../components/ha-md-button-menu";
|
||||||
@@ -44,10 +45,8 @@ import type {
|
|||||||
} from "../../../../data/automation";
|
} from "../../../../data/automation";
|
||||||
import { isCondition, testCondition } from "../../../../data/automation";
|
import { isCondition, testCondition } from "../../../../data/automation";
|
||||||
import { describeCondition } from "../../../../data/automation_i18n";
|
import { describeCondition } from "../../../../data/automation_i18n";
|
||||||
import {
|
import type { ConditionDescriptions } from "../../../../data/condition";
|
||||||
CONDITION_BUILDING_BLOCKS,
|
import { CONDITION_BUILDING_BLOCKS } from "../../../../data/condition";
|
||||||
CONDITION_ICONS,
|
|
||||||
} from "../../../../data/condition";
|
|
||||||
import { validateConfig } from "../../../../data/config";
|
import { validateConfig } from "../../../../data/config";
|
||||||
import { fullEntitiesContext } from "../../../../data/context";
|
import { fullEntitiesContext } from "../../../../data/context";
|
||||||
import type { EntityRegistryEntry } from "../../../../data/entity_registry";
|
import type { EntityRegistryEntry } from "../../../../data/entity_registry";
|
||||||
@@ -130,6 +129,9 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||||||
|
|
||||||
@state() private _warnings?: string[];
|
@state() private _warnings?: string[];
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
public conditionDescriptions: ConditionDescriptions = {};
|
||||||
|
|
||||||
@property({ type: Boolean, attribute: "sidebar" })
|
@property({ type: Boolean, attribute: "sidebar" })
|
||||||
public optionsInSidebar = false;
|
public optionsInSidebar = false;
|
||||||
|
|
||||||
@@ -179,11 +181,11 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||||||
|
|
||||||
private _renderRow() {
|
private _renderRow() {
|
||||||
return html`
|
return html`
|
||||||
<ha-svg-icon
|
<ha-condition-icon
|
||||||
slot="leading-icon"
|
slot="leading-icon"
|
||||||
class="condition-icon"
|
.hass=${this.hass}
|
||||||
.path=${CONDITION_ICONS[this.condition.condition]}
|
.condition=${this.condition.condition}
|
||||||
></ha-svg-icon>
|
></ha-condition-icon>
|
||||||
<h3 slot="header">
|
<h3 slot="header">
|
||||||
${capitalizeFirstLetter(
|
${capitalizeFirstLetter(
|
||||||
describeCondition(this.condition, this.hass, this._entityReg)
|
describeCondition(this.condition, this.hass, this._entityReg)
|
||||||
@@ -395,9 +397,14 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||||||
<ha-automation-condition-editor
|
<ha-automation-condition-editor
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.condition=${this.condition}
|
.condition=${this.condition}
|
||||||
|
.description=${this.conditionDescriptions[
|
||||||
|
this.condition.condition
|
||||||
|
]}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.yamlMode=${this._yamlMode}
|
.yamlMode=${this._yamlMode}
|
||||||
.uiSupported=${this._uiSupported(this.condition.condition)}
|
.uiSupported=${this._uiSupported(
|
||||||
|
this._getType(this.condition, this.conditionDescriptions)
|
||||||
|
)}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
@ui-mode-not-available=${this._handleUiModeNotAvailable}
|
@ui-mode-not-available=${this._handleUiModeNotAvailable}
|
||||||
></ha-automation-condition-editor>`
|
></ha-automation-condition-editor>`
|
||||||
@@ -476,7 +483,9 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.condition=${this.condition}
|
.condition=${this.condition}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.uiSupported=${this._uiSupported(this.condition.condition)}
|
.uiSupported=${this._uiSupported(
|
||||||
|
this._getType(this.condition, this.conditionDescriptions)
|
||||||
|
)}
|
||||||
indent
|
indent
|
||||||
.selected=${this._selected}
|
.selected=${this._selected}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
@@ -786,7 +795,10 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||||||
cut: this._cutCondition,
|
cut: this._cutCondition,
|
||||||
test: this._testCondition,
|
test: this._testCondition,
|
||||||
config: sidebarCondition,
|
config: sidebarCondition,
|
||||||
uiSupported: this._uiSupported(sidebarCondition.condition),
|
uiSupported: this._uiSupported(
|
||||||
|
this._getType(sidebarCondition, this.conditionDescriptions)
|
||||||
|
),
|
||||||
|
description: this.conditionDescriptions[sidebarCondition.condition],
|
||||||
yamlMode: this._yamlMode,
|
yamlMode: this._yamlMode,
|
||||||
} satisfies ConditionSidebarConfig);
|
} satisfies ConditionSidebarConfig);
|
||||||
this._selected = true;
|
this._selected = true;
|
||||||
@@ -802,6 +814,16 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _getType = memoizeOne(
|
||||||
|
(condition: Condition, conditionDescriptions: ConditionDescriptions) => {
|
||||||
|
if (condition.condition in conditionDescriptions) {
|
||||||
|
return "platform";
|
||||||
|
}
|
||||||
|
|
||||||
|
return condition.condition;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
private _uiSupported = memoizeOne(
|
private _uiSupported = memoizeOne(
|
||||||
(type: string) =>
|
(type: string) =>
|
||||||
customElements.get(`ha-automation-condition-${type}`) !== undefined
|
customElements.get(`ha-automation-condition-${type}`) !== undefined
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { PropertyValues } from "lit";
|
|||||||
import { html, LitElement, nothing } from "lit";
|
import { html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, queryAll, state } from "lit/decorators";
|
import { customElement, property, queryAll, state } from "lit/decorators";
|
||||||
import { repeat } from "lit/directives/repeat";
|
import { repeat } from "lit/directives/repeat";
|
||||||
|
import { ensureArray } from "../../../../common/array/ensure-array";
|
||||||
import { storage } from "../../../../common/decorators/storage";
|
import { storage } from "../../../../common/decorators/storage";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||||
@@ -12,11 +13,18 @@ import "../../../../components/ha-button";
|
|||||||
import "../../../../components/ha-button-menu";
|
import "../../../../components/ha-button-menu";
|
||||||
import "../../../../components/ha-sortable";
|
import "../../../../components/ha-sortable";
|
||||||
import "../../../../components/ha-svg-icon";
|
import "../../../../components/ha-svg-icon";
|
||||||
import type {
|
import {
|
||||||
AutomationClipboard,
|
getValueFromDynamic,
|
||||||
Condition,
|
isDynamic,
|
||||||
|
type AutomationClipboard,
|
||||||
|
type Condition,
|
||||||
} from "../../../../data/automation";
|
} from "../../../../data/automation";
|
||||||
import { CONDITION_BUILDING_BLOCKS } from "../../../../data/condition";
|
import type { ConditionDescriptions } from "../../../../data/condition";
|
||||||
|
import {
|
||||||
|
CONDITION_BUILDING_BLOCKS,
|
||||||
|
subscribeConditions,
|
||||||
|
} from "../../../../data/condition";
|
||||||
|
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import {
|
import {
|
||||||
PASTE_VALUE,
|
PASTE_VALUE,
|
||||||
@@ -25,10 +33,9 @@ import {
|
|||||||
import { automationRowsStyles } from "../styles";
|
import { automationRowsStyles } from "../styles";
|
||||||
import "./ha-automation-condition-row";
|
import "./ha-automation-condition-row";
|
||||||
import type HaAutomationConditionRow from "./ha-automation-condition-row";
|
import type HaAutomationConditionRow from "./ha-automation-condition-row";
|
||||||
import { ensureArray } from "../../../../common/array/ensure-array";
|
|
||||||
|
|
||||||
@customElement("ha-automation-condition")
|
@customElement("ha-automation-condition")
|
||||||
export default class HaAutomationCondition extends LitElement {
|
export default class HaAutomationCondition extends SubscribeMixin(LitElement) {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ attribute: false }) public conditions!: Condition[];
|
@property({ attribute: false }) public conditions!: Condition[];
|
||||||
@@ -46,6 +53,8 @@ export default class HaAutomationCondition extends LitElement {
|
|||||||
|
|
||||||
@state() private _rowSortSelected?: number;
|
@state() private _rowSortSelected?: number;
|
||||||
|
|
||||||
|
@state() private _conditionDescriptions: ConditionDescriptions = {};
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "automationClipboard",
|
key: "automationClipboard",
|
||||||
@@ -64,6 +73,26 @@ export default class HaAutomationCondition extends LitElement {
|
|||||||
|
|
||||||
private _conditionKeys = new WeakMap<Condition, string>();
|
private _conditionKeys = new WeakMap<Condition, string>();
|
||||||
|
|
||||||
|
protected hassSubscribe() {
|
||||||
|
return [
|
||||||
|
subscribeConditions(this.hass, (conditions) =>
|
||||||
|
this._addConditions(conditions)
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addConditions(conditions: ConditionDescriptions) {
|
||||||
|
this._conditionDescriptions = {
|
||||||
|
...this._conditionDescriptions,
|
||||||
|
...conditions,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
this.hass.loadBackendTranslation("conditions");
|
||||||
|
}
|
||||||
|
|
||||||
protected updated(changedProperties: PropertyValues) {
|
protected updated(changedProperties: PropertyValues) {
|
||||||
if (!changedProperties.has("conditions")) {
|
if (!changedProperties.has("conditions")) {
|
||||||
return;
|
return;
|
||||||
@@ -168,6 +197,7 @@ export default class HaAutomationCondition extends LitElement {
|
|||||||
.last=${idx === this.conditions.length - 1}
|
.last=${idx === this.conditions.length - 1}
|
||||||
.totalConditions=${this.conditions.length}
|
.totalConditions=${this.conditions.length}
|
||||||
.condition=${cond}
|
.condition=${cond}
|
||||||
|
.conditionDescriptions=${this._conditionDescriptions}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
@duplicate=${this._duplicateCondition}
|
@duplicate=${this._duplicateCondition}
|
||||||
@@ -237,6 +267,10 @@ export default class HaAutomationCondition extends LitElement {
|
|||||||
conditions = this.conditions.concat(
|
conditions = this.conditions.concat(
|
||||||
deepClone(this._clipboard!.condition)
|
deepClone(this._clipboard!.condition)
|
||||||
);
|
);
|
||||||
|
} else if (isDynamic(value)) {
|
||||||
|
conditions = this.conditions.concat({
|
||||||
|
condition: getValueFromDynamic(value),
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
const condition = value as Condition["condition"];
|
const condition = value as Condition["condition"];
|
||||||
const elClass = customElements.get(
|
const elClass = customElements.get(
|
||||||
|
|||||||
@@ -0,0 +1,416 @@
|
|||||||
|
import { mdiHelpCircle } from "@mdi/js";
|
||||||
|
import type { PropertyValues } from "lit";
|
||||||
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
|
import { computeDomain } from "../../../../../common/entity/compute_domain";
|
||||||
|
import "../../../../../components/ha-checkbox";
|
||||||
|
import "../../../../../components/ha-selector/ha-selector";
|
||||||
|
import "../../../../../components/ha-settings-row";
|
||||||
|
import type { PlatformCondition } from "../../../../../data/automation";
|
||||||
|
import {
|
||||||
|
getConditionDomain,
|
||||||
|
getConditionObjectId,
|
||||||
|
type ConditionDescription,
|
||||||
|
} from "../../../../../data/condition";
|
||||||
|
import type { IntegrationManifest } from "../../../../../data/integration";
|
||||||
|
import { fetchIntegrationManifest } from "../../../../../data/integration";
|
||||||
|
import type { TargetSelector } from "../../../../../data/selector";
|
||||||
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
|
import { documentationUrl } from "../../../../../util/documentation-url";
|
||||||
|
|
||||||
|
const showOptionalToggle = (field: ConditionDescription["fields"][string]) =>
|
||||||
|
field.selector &&
|
||||||
|
!field.required &&
|
||||||
|
!("boolean" in field.selector && field.default);
|
||||||
|
|
||||||
|
@customElement("ha-automation-condition-platform")
|
||||||
|
export class HaPlatformCondition extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public condition!: PlatformCondition;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public description?: ConditionDescription;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
@state() private _checkedKeys = new Set();
|
||||||
|
|
||||||
|
@state() private _manifest?: IntegrationManifest;
|
||||||
|
|
||||||
|
public static get defaultConfig(): PlatformCondition {
|
||||||
|
return { condition: "" };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected willUpdate(changedProperties: PropertyValues<this>) {
|
||||||
|
super.willUpdate(changedProperties);
|
||||||
|
if (!this.hasUpdated) {
|
||||||
|
this.hass.loadBackendTranslation("conditions");
|
||||||
|
this.hass.loadBackendTranslation("selector");
|
||||||
|
}
|
||||||
|
if (!changedProperties.has("condition")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const oldValue = changedProperties.get("condition") as
|
||||||
|
| undefined
|
||||||
|
| this["condition"];
|
||||||
|
|
||||||
|
// Fetch the manifest if we have a condition selected and the condition domain changed.
|
||||||
|
// If no condition is selected, clear the manifest.
|
||||||
|
if (this.condition?.condition) {
|
||||||
|
const domain = getConditionDomain(this.condition.condition);
|
||||||
|
|
||||||
|
const oldDomain = getConditionDomain(oldValue?.condition || "");
|
||||||
|
|
||||||
|
if (domain !== oldDomain) {
|
||||||
|
this._fetchManifest(domain);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._manifest = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const domain = getConditionDomain(this.condition.condition);
|
||||||
|
const conditionName = getConditionObjectId(this.condition.condition);
|
||||||
|
|
||||||
|
const description = this.hass.localize(
|
||||||
|
`component.${domain}.conditions.${conditionName}.description`
|
||||||
|
);
|
||||||
|
|
||||||
|
const conditionDesc = this.description;
|
||||||
|
|
||||||
|
const shouldRenderDataYaml = !conditionDesc?.fields;
|
||||||
|
|
||||||
|
const hasOptional = Boolean(
|
||||||
|
conditionDesc?.fields &&
|
||||||
|
Object.values(conditionDesc.fields).some((field) =>
|
||||||
|
showOptionalToggle(field)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="description">
|
||||||
|
${description ? html`<p>${description}</p>` : nothing}
|
||||||
|
${this._manifest
|
||||||
|
? html`<a
|
||||||
|
href=${this._manifest.is_built_in
|
||||||
|
? documentationUrl(
|
||||||
|
this.hass,
|
||||||
|
`/integrations/${this._manifest.domain}`
|
||||||
|
)
|
||||||
|
: this._manifest.documentation}
|
||||||
|
title=${this.hass.localize(
|
||||||
|
"ui.components.service-control.integration_doc"
|
||||||
|
)}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
<ha-icon-button
|
||||||
|
.path=${mdiHelpCircle}
|
||||||
|
class="help-icon"
|
||||||
|
></ha-icon-button>
|
||||||
|
</a>`
|
||||||
|
: nothing}
|
||||||
|
</div>
|
||||||
|
${conditionDesc && "target" in conditionDesc
|
||||||
|
? html`<ha-settings-row narrow>
|
||||||
|
${hasOptional
|
||||||
|
? html`<div slot="prefix" class="checkbox-spacer"></div>`
|
||||||
|
: nothing}
|
||||||
|
<span slot="heading"
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.components.service-control.target"
|
||||||
|
)}</span
|
||||||
|
>
|
||||||
|
<span slot="description"
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.components.service-control.target_secondary"
|
||||||
|
)}</span
|
||||||
|
><ha-selector
|
||||||
|
.hass=${this.hass}
|
||||||
|
.selector=${this._targetSelector(conditionDesc.target)}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
@value-changed=${this._targetChanged}
|
||||||
|
.value=${this.condition?.target}
|
||||||
|
></ha-selector
|
||||||
|
></ha-settings-row>`
|
||||||
|
: nothing}
|
||||||
|
${shouldRenderDataYaml
|
||||||
|
? html`<ha-yaml-editor
|
||||||
|
.hass=${this.hass}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.components.service-control.action_data"
|
||||||
|
)}
|
||||||
|
.name=${"data"}
|
||||||
|
.readOnly=${this.disabled}
|
||||||
|
.defaultValue=${this.condition?.options}
|
||||||
|
@value-changed=${this._dataChanged}
|
||||||
|
></ha-yaml-editor>`
|
||||||
|
: Object.entries(conditionDesc.fields).map(([fieldName, dataField]) =>
|
||||||
|
this._renderField(
|
||||||
|
fieldName,
|
||||||
|
dataField,
|
||||||
|
hasOptional,
|
||||||
|
domain,
|
||||||
|
conditionName
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _targetSelector = memoizeOne(
|
||||||
|
(targetSelector: TargetSelector["target"] | null | undefined) =>
|
||||||
|
targetSelector ? { target: { ...targetSelector } } : { target: {} }
|
||||||
|
);
|
||||||
|
|
||||||
|
private _renderField = (
|
||||||
|
fieldName: string,
|
||||||
|
dataField: ConditionDescription["fields"][string],
|
||||||
|
hasOptional: boolean,
|
||||||
|
domain: string | undefined,
|
||||||
|
conditionName: string | undefined
|
||||||
|
) => {
|
||||||
|
const selector = dataField?.selector ?? { text: null };
|
||||||
|
|
||||||
|
const showOptional = showOptionalToggle(dataField);
|
||||||
|
|
||||||
|
return dataField.selector
|
||||||
|
? html`<ha-settings-row narrow>
|
||||||
|
${!showOptional
|
||||||
|
? hasOptional
|
||||||
|
? html`<div slot="prefix" class="checkbox-spacer"></div>`
|
||||||
|
: nothing
|
||||||
|
: html`<ha-checkbox
|
||||||
|
.key=${fieldName}
|
||||||
|
.checked=${this._checkedKeys.has(fieldName) ||
|
||||||
|
(this.condition?.options &&
|
||||||
|
this.condition.options[fieldName] !== undefined)}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
@change=${this._checkboxChanged}
|
||||||
|
slot="prefix"
|
||||||
|
></ha-checkbox>`}
|
||||||
|
<span slot="heading"
|
||||||
|
>${this.hass.localize(
|
||||||
|
`component.${domain}.conditions.${conditionName}.fields.${fieldName}.name`
|
||||||
|
) || conditionName}</span
|
||||||
|
>
|
||||||
|
<span slot="description"
|
||||||
|
>${this.hass.localize(
|
||||||
|
`component.${domain}.conditions.${conditionName}.fields.${fieldName}.description`
|
||||||
|
)}</span
|
||||||
|
>
|
||||||
|
<ha-selector
|
||||||
|
.disabled=${this.disabled ||
|
||||||
|
(showOptional &&
|
||||||
|
!this._checkedKeys.has(fieldName) &&
|
||||||
|
(!this.condition?.options ||
|
||||||
|
this.condition.options[fieldName] === undefined))}
|
||||||
|
.hass=${this.hass}
|
||||||
|
.selector=${selector}
|
||||||
|
.context=${this._generateContext(dataField)}
|
||||||
|
.key=${fieldName}
|
||||||
|
@value-changed=${this._dataChanged}
|
||||||
|
.value=${this.condition?.options
|
||||||
|
? this.condition.options[fieldName]
|
||||||
|
: undefined}
|
||||||
|
.placeholder=${dataField.default}
|
||||||
|
.localizeValue=${this._localizeValueCallback}
|
||||||
|
></ha-selector>
|
||||||
|
</ha-settings-row>`
|
||||||
|
: nothing;
|
||||||
|
};
|
||||||
|
|
||||||
|
private _generateContext(
|
||||||
|
field: ConditionDescription["fields"][string]
|
||||||
|
): Record<string, any> | undefined {
|
||||||
|
if (!field.context) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const context = {};
|
||||||
|
for (const [context_key, data_key] of Object.entries(field.context)) {
|
||||||
|
context[context_key] =
|
||||||
|
data_key === "target"
|
||||||
|
? this.condition.target
|
||||||
|
: this.condition.options?.[data_key];
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _dataChanged(ev: CustomEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
if (ev.detail.isValid === false) {
|
||||||
|
// Don't clear an object selector that returns invalid YAML
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const key = (ev.currentTarget as any).key;
|
||||||
|
const value = ev.detail.value;
|
||||||
|
if (
|
||||||
|
this.condition?.options?.[key] === value ||
|
||||||
|
((!this.condition?.options || !(key in this.condition.options)) &&
|
||||||
|
(value === "" || value === undefined))
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = { ...this.condition?.options, [key]: value };
|
||||||
|
|
||||||
|
if (
|
||||||
|
value === "" ||
|
||||||
|
value === undefined ||
|
||||||
|
(typeof value === "object" && !Object.keys(value).length)
|
||||||
|
) {
|
||||||
|
delete options[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: {
|
||||||
|
...this.condition,
|
||||||
|
options,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _targetChanged(ev: CustomEvent): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: {
|
||||||
|
...this.condition,
|
||||||
|
target: ev.detail.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _checkboxChanged(ev) {
|
||||||
|
const checked = ev.currentTarget.checked;
|
||||||
|
const key = ev.currentTarget.key;
|
||||||
|
let options;
|
||||||
|
|
||||||
|
if (checked) {
|
||||||
|
this._checkedKeys.add(key);
|
||||||
|
const field =
|
||||||
|
this.description &&
|
||||||
|
Object.entries(this.description).find(([k, _value]) => k === key)?.[1];
|
||||||
|
let defaultValue = field?.default;
|
||||||
|
|
||||||
|
if (
|
||||||
|
defaultValue == null &&
|
||||||
|
field?.selector &&
|
||||||
|
"constant" in field.selector
|
||||||
|
) {
|
||||||
|
defaultValue = field.selector.constant?.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
defaultValue == null &&
|
||||||
|
field?.selector &&
|
||||||
|
"boolean" in field.selector
|
||||||
|
) {
|
||||||
|
defaultValue = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultValue != null) {
|
||||||
|
options = {
|
||||||
|
...this.condition?.options,
|
||||||
|
[key]: defaultValue,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._checkedKeys.delete(key);
|
||||||
|
options = { ...this.condition?.options };
|
||||||
|
delete options[key];
|
||||||
|
}
|
||||||
|
if (options) {
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: {
|
||||||
|
...this.condition,
|
||||||
|
options,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.requestUpdate("_checkedKeys");
|
||||||
|
}
|
||||||
|
|
||||||
|
private _localizeValueCallback = (key: string) => {
|
||||||
|
if (!this.condition?.condition) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return this.hass.localize(
|
||||||
|
`component.${computeDomain(this.condition.condition)}.selector.${key}`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
private async _fetchManifest(integration: string) {
|
||||||
|
this._manifest = undefined;
|
||||||
|
try {
|
||||||
|
this._manifest = await fetchIntegrationManifest(this.hass, integration);
|
||||||
|
} catch (_err: any) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`Unable to fetch integration manifest for ${integration}`);
|
||||||
|
// Ignore if loading manifest fails. Probably bad JSON in manifest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
ha-settings-row {
|
||||||
|
padding: 0 var(--ha-space-4);
|
||||||
|
}
|
||||||
|
ha-settings-row[narrow] {
|
||||||
|
padding-bottom: var(--ha-space-2);
|
||||||
|
}
|
||||||
|
ha-settings-row {
|
||||||
|
--settings-row-content-width: 100%;
|
||||||
|
--settings-row-prefix-display: contents;
|
||||||
|
border-top: var(
|
||||||
|
--service-control-items-border-top,
|
||||||
|
1px solid var(--divider-color)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ha-service-picker,
|
||||||
|
ha-entity-picker,
|
||||||
|
ha-yaml-editor {
|
||||||
|
display: block;
|
||||||
|
margin: 0 var(--ha-space-4);
|
||||||
|
}
|
||||||
|
ha-yaml-editor {
|
||||||
|
padding: var(--ha-space-4) 0;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin: 0 var(--ha-space-4);
|
||||||
|
padding: var(--ha-space-4) 0;
|
||||||
|
}
|
||||||
|
:host([hide-picker]) p {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
.checkbox-spacer {
|
||||||
|
width: 32px;
|
||||||
|
}
|
||||||
|
ha-checkbox {
|
||||||
|
margin-left: calc(var(--ha-space-4) * -1);
|
||||||
|
margin-inline-start: calc(var(--ha-space-4) * -1);
|
||||||
|
margin-inline-end: initial;
|
||||||
|
}
|
||||||
|
.help-icon {
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
.description {
|
||||||
|
justify-content: space-between;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-right: 2px;
|
||||||
|
padding-inline-end: 2px;
|
||||||
|
padding-inline-start: initial;
|
||||||
|
}
|
||||||
|
.description p {
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-automation-condition-platform": HaPlatformCondition;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,11 +16,16 @@ import { classMap } from "lit/directives/class-map";
|
|||||||
import { keyed } from "lit/directives/keyed";
|
import { keyed } from "lit/directives/keyed";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import { handleStructError } from "../../../../common/structs/handle-errors";
|
import { handleStructError } from "../../../../common/structs/handle-errors";
|
||||||
import {
|
import type {
|
||||||
testCondition,
|
LegacyCondition,
|
||||||
type ConditionSidebarConfig,
|
ConditionSidebarConfig,
|
||||||
} from "../../../../data/automation";
|
} from "../../../../data/automation";
|
||||||
import { CONDITION_BUILDING_BLOCKS } from "../../../../data/condition";
|
import { testCondition } from "../../../../data/automation";
|
||||||
|
import {
|
||||||
|
CONDITION_BUILDING_BLOCKS,
|
||||||
|
getConditionDomain,
|
||||||
|
getConditionObjectId,
|
||||||
|
} from "../../../../data/condition";
|
||||||
import { validateConfig } from "../../../../data/config";
|
import { validateConfig } from "../../../../data/config";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import { isMac } from "../../../../util/is_mac";
|
import { isMac } from "../../../../util/is_mac";
|
||||||
@@ -84,14 +89,25 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
|||||||
"ui.panel.config.automation.editor.conditions.condition"
|
"ui.panel.config.automation.editor.conditions.condition"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const domain =
|
||||||
|
"condition" in this.config.config &&
|
||||||
|
getConditionDomain(this.config.config.condition);
|
||||||
|
const conditionName =
|
||||||
|
"condition" in this.config.config &&
|
||||||
|
getConditionObjectId(this.config.config.condition);
|
||||||
|
|
||||||
const title =
|
const title =
|
||||||
this.hass.localize(
|
this.hass.localize(
|
||||||
`ui.panel.config.automation.editor.conditions.type.${type}.label`
|
`ui.panel.config.automation.editor.conditions.type.${type as LegacyCondition["condition"]}.label`
|
||||||
) || type;
|
) ||
|
||||||
|
this.hass.localize(
|
||||||
|
`component.${domain}.conditions.${conditionName}.name`
|
||||||
|
) ||
|
||||||
|
type;
|
||||||
|
|
||||||
const description = isBuildingBlock
|
const description = isBuildingBlock
|
||||||
? this.hass.localize(
|
? this.hass.localize(
|
||||||
`ui.panel.config.automation.editor.conditions.type.${type}.description.picker`
|
`ui.panel.config.automation.editor.conditions.type.${type as LegacyCondition["condition"]}.description.picker`
|
||||||
)
|
)
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
@@ -282,6 +298,7 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
|||||||
class="sidebar-editor"
|
class="sidebar-editor"
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.condition=${this.config.config}
|
.condition=${this.config.config}
|
||||||
|
.description=${this.config.description}
|
||||||
.yamlMode=${this.yamlMode}
|
.yamlMode=${this.yamlMode}
|
||||||
.uiSupported=${this.config.uiSupported}
|
.uiSupported=${this.config.uiSupported}
|
||||||
@value-changed=${this._valueChangedSidebar}
|
@value-changed=${this._valueChangedSidebar}
|
||||||
|
|||||||
Reference in New Issue
Block a user