diff --git a/src/common/structs/handle-errors.ts b/src/common/structs/handle-errors.ts index 96c82081c565..ad5cb080d94f 100644 --- a/src/common/structs/handle-errors.ts +++ b/src/common/structs/handle-errors.ts @@ -13,45 +13,33 @@ export const handleStructError = ( for (const failure of err.failures()) { if (failure.value === undefined) { errors.push( - hass.localize( - "ui.errors.config.key_missing", - "key", - failure.path.join(".") - ) + hass.localize("ui.errors.config.key_missing", { + key: failure.path.join("."), + }) ); } else if (failure.type === "never") { warnings.push( - hass.localize( - "ui.errors.config.key_not_expected", - "key", - failure.path.join(".") - ) + hass.localize("ui.errors.config.key_not_expected", { + key: failure.path.join("."), + }) ); } else if (failure.type === "union") { continue; } else if (failure.type === "enums") { warnings.push( - hass.localize( - "ui.errors.config.key_wrong_type", - "key", - failure.path.join("."), - "type_correct", - failure.message.replace("Expected ", "").split(", ")[0], - "type_wrong", - JSON.stringify(failure.value) - ) + hass.localize("ui.errors.config.key_wrong_type", { + key: failure.path.join("."), + type_correct: failure.message.replace("Expected ", "").split(", ")[0], + type_wrong: JSON.stringify(failure.value), + }) ); } else { warnings.push( - hass.localize( - "ui.errors.config.key_wrong_type", - "key", - failure.path.join("."), - "type_correct", - failure.refinement || failure.type, - "type_wrong", - JSON.stringify(failure.value) - ) + hass.localize("ui.errors.config.key_wrong_type", { + key: failure.path.join("."), + type_correct: failure.refinement || failure.type, + type_wrong: JSON.stringify(failure.value), + }) ); } } diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index d1aae334080a..48d94d3758f5 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -1,7 +1,7 @@ import { Statistic, StatisticType } from "../../../data/recorder"; import { ActionConfig, LovelaceCardConfig } from "../../../data/lovelace"; import { FullCalendarView, TranslationDict } from "../../../types"; -import { Condition } from "../common/validate-condition"; +import { Condition, LegacyCondition } from "../common/validate-condition"; import { HuiImage } from "../components/hui-image"; import { LovelaceElementConfig } from "../elements/types"; import { @@ -37,7 +37,7 @@ export interface CalendarCardConfig extends LovelaceCardConfig { export interface ConditionalCardConfig extends LovelaceCardConfig { card: LovelaceCardConfig; - conditions: Condition[]; + conditions: (Condition | LegacyCondition)[]; } export interface EmptyStateCardConfig extends LovelaceCardConfig { diff --git a/src/panels/lovelace/common/validate-condition.ts b/src/panels/lovelace/common/validate-condition.ts index c68950509055..1d17f44b1047 100644 --- a/src/panels/lovelace/common/validate-condition.ts +++ b/src/panels/lovelace/common/validate-condition.ts @@ -21,7 +21,10 @@ export type ScreenCondition = { media_query?: string; }; -function checkStateCondition(condition: StateCondition, hass: HomeAssistant) { +function checkStateCondition( + condition: StateCondition | LegacyCondition, + hass: HomeAssistant +) { const state = condition.entity && hass.states[condition.entity] ? hass.states[condition.entity].state @@ -42,19 +45,20 @@ function checkScreenCondition( } export function checkConditionsMet( - conditions: Condition[], + conditions: (Condition | LegacyCondition)[], hass: HomeAssistant ): boolean { return conditions.every((c) => { - if (c.condition === "screen") { - return checkScreenCondition(c, hass); + if ("condition" in c) { + if (c.condition === "screen") { + return checkScreenCondition(c, hass); + } } - return checkStateCondition(c, hass); }); } -function valideStateCondition(condition: StateCondition) { +function valideStateCondition(condition: StateCondition | LegacyCondition) { return ( condition.entity != null && (condition.state != null || condition.state_not != null) @@ -65,10 +69,14 @@ function validateScreenCondition(condition: ScreenCondition) { return condition.media_query != null; } -export function validateConditionalConfig(conditions: Condition[]): boolean { +export function validateConditionalConfig( + conditions: (Condition | LegacyCondition)[] +): boolean { return conditions.every((c) => { - if (c.condition === "screen") { - return validateScreenCondition(c); + if ("condition" in c) { + if (c.condition === "screen") { + return validateScreenCondition(c); + } } return valideStateCondition(c); }); diff --git a/src/panels/lovelace/components/hui-conditional-base.ts b/src/panels/lovelace/components/hui-conditional-base.ts index eaec714cd36a..79fea6759e32 100644 --- a/src/panels/lovelace/components/hui-conditional-base.ts +++ b/src/panels/lovelace/components/hui-conditional-base.ts @@ -78,7 +78,7 @@ export class HuiConditionalBase extends ReactiveElement { } const conditions = this._config.conditions.filter( - (c) => c.condition === "screen" + (c) => "condition" in c && c.condition === "screen" ) as ScreenCondition[]; const mediaQueries = conditions diff --git a/src/panels/lovelace/editor/conditions/ha-card-condition-editor.ts b/src/panels/lovelace/editor/conditions/ha-card-condition-editor.ts index 7d7f322723d1..63f74d5c929f 100644 --- a/src/panels/lovelace/editor/conditions/ha-card-condition-editor.ts +++ b/src/panels/lovelace/editor/conditions/ha-card-condition-editor.ts @@ -1,7 +1,7 @@ import { preventDefault } from "@fullcalendar/core/internal"; import { ActionDetail } from "@material/mwc-list"; import { mdiCheck, mdiDelete, mdiDotsVertical } from "@mdi/js"; -import { LitElement, css, html, nothing } from "lit"; +import { LitElement, PropertyValues, css, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { dynamicElement } from "../../../../common/dom/dynamic-element-directive"; import { fireEvent } from "../../../../common/dom/fire_event"; @@ -13,33 +13,59 @@ import "../../../../components/ha-svg-icon"; import "../../../../components/ha-yaml-editor"; import { haStyle } from "../../../../resources/styles"; import type { HomeAssistant } from "../../../../types"; -import { Condition, LegacyCondition } from "../../common/validate-condition"; -import type { LovelaceConditionEditorConstructor } from "./types"; import { ICON_CONDITION } from "../../common/icon-condition"; +import { Condition } from "../../common/validate-condition"; +import type { LovelaceConditionEditorConstructor } from "./types"; +import { handleStructError } from "../../../../common/structs/handle-errors"; @customElement("ha-card-condition-editor") export default class HaCardConditionEditor extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ attribute: false }) condition!: Condition | LegacyCondition; + @property({ attribute: false }) condition!: Condition; @state() public _yamlMode = false; - protected render() { - const condition: Condition = { - condition: "state", - ...this.condition, - }; + @state() public _uiAvailable = false; + + @state() public _uiWarnings: string[] = []; + + private get _editor() { const element = customElements.get( - `ha-card-condition-${condition.condition}` + `ha-card-condition-${this.condition.condition}` ) as LovelaceConditionEditorConstructor | undefined; - const supported = element !== undefined; - const valid = - element && - (!element.validateUIConfig || element.validateUIConfig(condition)); + return element; + } + + protected willUpdate(changedProperties: PropertyValues): void { + if (changedProperties.has("condition")) { + const validator = this._editor && this._editor.validateUIConfig; + if (validator) { + try { + validator(this.condition, this.hass); + this._uiAvailable = true; + this._uiWarnings = []; + } catch (err) { + this._uiWarnings = handleStructError( + this.hass, + err as Error + ).warnings; + this._uiAvailable = false; + } + } else { + this._uiAvailable = false; + this._uiWarnings = []; + } - const yamlMode = this._yamlMode || !supported || !valid; + if (!this._uiAvailable && !this._yamlMode) { + this._yamlMode = true; + } + } + } + + protected render() { + const condition = this.condition; return html`