Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions src/components/ha-condition-icon.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
12 changes: 11 additions & 1 deletion src/data/automation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { LocalizeKeys } from "../common/translations/localize";
import { createSearchParam } from "../common/url/search-params";
import type { Context, HomeAssistant } from "../types";
import type { BlueprintInput } from "./blueprint";
import type { ConditionDescription } from "./condition";
import { CONDITION_BUILDING_BLOCKS } from "./condition";
import type { DeviceCondition, DeviceTrigger } from "./device_automation";
import type { Action, Field, MODES } from "./script";
Expand Down Expand Up @@ -236,6 +237,12 @@ interface BaseCondition {
condition: string;
alias?: string;
enabled?: boolean;
options?: Record<string, unknown>;
}

export interface PlatformCondition extends BaseCondition {
condition: Exclude<string, LegacyCondition["condition"]>;
target?: HassServiceTarget;
}

export interface LogicalCondition extends BaseCondition {
Expand Down Expand Up @@ -320,7 +327,7 @@ export type AutomationElementGroup = Record<
{ icon?: string; members?: AutomationElementGroup }
>;

export type Condition =
export type LegacyCondition =
| StateCondition
| NumericStateCondition
| SunCondition
Expand All @@ -331,6 +338,8 @@ export type Condition =
| LogicalCondition
| TriggerCondition;

export type Condition = LegacyCondition | PlatformCondition;

export type ConditionWithShorthand =
| Condition
| ShorthandAndConditionList
Expand Down Expand Up @@ -608,6 +617,7 @@ export interface ConditionSidebarConfig extends BaseSidebarConfig {
insertAfter: (value: Condition | Condition[]) => boolean;
toggleYamlMode: () => void;
config: Condition;
description?: ConditionDescription;
yamlMode: boolean;
uiSupported: boolean;
}
Expand Down
51 changes: 42 additions & 9 deletions src/data/automation_i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@ import {
} from "../common/string/format-list";
import { hasTemplate } from "../common/string/has-template";
import type { HomeAssistant } from "../types";
import type { Condition, ForDict, LegacyTrigger, Trigger } from "./automation";
import type {
Condition,
ForDict,
LegacyCondition,
LegacyTrigger,
Trigger,
} from "./automation";
import { getConditionDomain, getConditionObjectId } from "./condition";
import type { DeviceCondition, DeviceTrigger } from "./device_automation";
import {
localizeDeviceAutomationCondition,
Expand Down Expand Up @@ -896,6 +903,39 @@ const tryDescribeCondition = (
}
}

const description = describeLegacyCondition(
condition as LegacyCondition,
hass,
entityRegistry
);

if (description) {
return description;
}

const conditionType = condition.condition;

const domain = getConditionDomain(condition.condition);
const type = getConditionObjectId(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") {
const conditions = ensureArray(condition.conditions);

Expand Down Expand Up @@ -1287,12 +1327,5 @@ const tryDescribeCondition = (
);
}

return (
hass.localize(
`ui.panel.config.automation.editor.conditions.type.${condition.condition}.label`
) ||
hass.localize(
`ui.panel.config.automation.editor.conditions.unknown_condition`
)
);
return undefined;
};
65 changes: 36 additions & 29 deletions src/data/condition.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,15 @@
import {
mdiAmpersand,
mdiClockOutline,
mdiCodeBraces,
mdiDevices,
mdiGateOr,
mdiIdentifier,
mdiMapClock,
mdiMapMarkerRadius,
mdiNotEqualVariant,
mdiNumeric,
mdiShape,
mdiStateMachine,
mdiWeatherSunny,
} from "@mdi/js";
import { mdiMapClock, mdiShape } from "@mdi/js";
import { computeDomain } from "../common/entity/compute_domain";
import { computeObjectId } from "../common/entity/compute_object_id";
import type { HomeAssistant } from "../types";
import type { AutomationElementGroupCollection } from "./automation";

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,
};
import type { Selector, TargetSelector } from "./selector";

export const CONDITION_COLLECTIONS: AutomationElementGroupCollection[] = [
{
groups: {
device: {},
dynamicGroups: {},
entity: { icon: mdiShape, members: { state: {}, numeric_state: {} } },
time_location: {
icon: mdiMapClock,
Expand Down Expand Up @@ -62,3 +39,33 @@ export const COLLAPSIBLE_CONDITION_ELEMENTS = [
"ha-automation-condition-not",
"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) : "_";
48 changes: 46 additions & 2 deletions src/data/icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import type {

import { mdiHomeAssistant } from "../resources/home-assistant-logo-svg";
import { getTriggerDomain, getTriggerObjectId } from "./trigger";
import { getConditionDomain, getConditionObjectId } from "./condition";

/** Icon to use when no icon specified for service. */
export const DEFAULT_SERVICE_ICON = mdiRoomService;
Expand Down Expand Up @@ -138,15 +139,25 @@ const resources: {
all?: Promise<Record<string, TriggerIcons>>;
domains: Record<string, TriggerIcons | Promise<TriggerIcons>>;
};
conditions: {
all?: Promise<Record<string, ConditionIcons>>;
domains: Record<string, ConditionIcons | Promise<ConditionIcons>>;
};
} = {
entity: {},
entity_component: {},
services: { domains: {} },
triggers: { domains: {} },
conditions: { domains: {} },
};

interface IconResources<
T extends ComponentIcons | PlatformIcons | ServiceIcons | TriggerIcons,
T extends
| ComponentIcons
| PlatformIcons
| ServiceIcons
| TriggerIcons
| ConditionIcons,
> {
resources: Record<string, T>;
}
Expand Down Expand Up @@ -195,17 +206,24 @@ type TriggerIcons = Record<
{ trigger: string; sections?: Record<string, string> }
>;

type ConditionIcons = Record<
string,
{ condition: string; sections?: Record<string, string> }
>;

export type IconCategory =
| "entity"
| "entity_component"
| "services"
| "triggers";
| "triggers"
| "conditions";

interface CategoryType {
entity: PlatformIcons;
entity_component: ComponentIcons;
services: ServiceIcons;
triggers: TriggerIcons;
conditions: ConditionIcons;
}

export const getHassIcons = async <T extends IconCategory>(
Expand Down Expand Up @@ -327,6 +345,13 @@ export const getTriggerIcons = async (
): Promise<TriggerIcons | Record<string, TriggerIcons> | undefined> =>
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
const sortedRangeCache = new WeakMap<Record<string, string>, number[]>();

Expand Down Expand Up @@ -526,6 +551,25 @@ export const triggerIcon = async (
return icon;
};

export const conditionIcon = async (
hass: HomeAssistant,
condition: string
): Promise<string | undefined> => {
let icon: string | undefined;

const domain = getConditionDomain(condition);
const conditionIcons = await getConditionIcons(hass, domain);
if (conditionIcons) {
const conditionName = getConditionObjectId(condition);
const condIcon = conditionIcons[conditionName] as ConditionIcons[string];
icon = condIcon?.condition;
}
if (!icon) {
icon = await domainIcon(hass, domain);
}
return icon;
};

export const serviceIcon = async (
hass: HomeAssistant,
service: string
Expand Down
3 changes: 2 additions & 1 deletion src/data/translation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ export type TranslationCategory =
| "preview_features"
| "selector"
| "services"
| "triggers";
| "triggers"
| "conditions";

export const subscribeTranslationPreferences = (
hass: HomeAssistant,
Expand Down
Loading
Loading