Skip to content

Commit

Permalink
Improve warning messages in conditional card
Browse files Browse the repository at this point in the history
  • Loading branch information
piitaya committed Oct 18, 2023
1 parent b2cb0d8 commit 2db4f93
Show file tree
Hide file tree
Showing 10 changed files with 133 additions and 77 deletions.
44 changes: 16 additions & 28 deletions src/common/structs/handle-errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
})
);
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/panels/lovelace/cards/types.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
26 changes: 17 additions & 9 deletions src/panels/lovelace/common/validate-condition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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);
});
Expand Down
2 changes: 1 addition & 1 deletion src/panels/lovelace/components/hui-conditional-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
85 changes: 63 additions & 22 deletions src/panels/lovelace/editor/conditions/ha-card-condition-editor.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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`
<div class="header">
Expand Down Expand Up @@ -68,9 +94,9 @@ export default class HaCardConditionEditor extends LitElement {
>
</ha-icon-button>
<ha-list-item graphic="icon" .disabled=${!supported || !valid}>
<ha-list-item graphic="icon" .disabled=${!this._uiAvailable}>
${this.hass.localize("ui.panel.lovelace.editor.edit_card.edit_ui")}
${!yamlMode
${!this._yamlMode
? html`
<ha-svg-icon
class="selected_menu_item"
Expand All @@ -85,7 +111,7 @@ export default class HaCardConditionEditor extends LitElement {
${this.hass.localize(
"ui.panel.lovelace.editor.edit_card.edit_yaml"
)}
${yamlMode
${this._yamlMode
? html`
<ha-svg-icon
class="selected_menu_item"
Expand All @@ -108,15 +134,30 @@ export default class HaCardConditionEditor extends LitElement {
</ha-list-item>
</ha-button-menu>
</div>
${!valid
${!this._uiAvailable
? html`
<ha-alert alert-type="warning">
${this.hass.localize("ui.errors.config.editor_not_supported")}
<ha-alert
alert-type="warning"
.title=${this.hass.localize(
"ui.errors.config.editor_not_supported"
)}
>
${this._uiWarnings!.length > 0 &&
this._uiWarnings![0] !== undefined
? html`
<ul>
${this._uiWarnings!.map(
(warning) => html`<li>${warning}</li>`
)}
</ul>
`
: nothing}
${this.hass.localize("ui.errors.config.edit_in_yaml_supported")}
</ha-alert>
`
: nothing}
<div class="content">
${yamlMode
${this._yamlMode
? html`
<ha-yaml-editor
.hass=${this.hass}
Expand Down
3 changes: 2 additions & 1 deletion src/panels/lovelace/editor/conditions/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { HomeAssistant } from "../../../../types";
import { Condition } from "../../common/validate-condition";

export interface LovelaceConditionEditorConstructor {
defaultConfig?: Condition;
validateUIConfig?: (condition: Condition) => boolean;
validateUIConfig?: (condition: Condition, hass: HomeAssistant) => void;
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,17 @@ export class HaCardConditionScreen extends LitElement {
return { condition: "screen", media_query: "" };
}

protected static validateUIConfig(condition: ScreenCondition) {
return (
!condition.media_query || mediaQueryReverseMap.get(condition.media_query)
);
protected static validateUIConfig(
condition: ScreenCondition,
hass: HomeAssistant
) {
const valid =
!condition.media_query || mediaQueryReverseMap.has(condition.media_query);
if (!valid) {
throw new Error(
hass.localize("ui.errors.config.media_query_not_supported")
);
}
}

private _schema = memoizeOne(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { StateCondition } from "../../../common/validate-condition";

const stateConditionStruct = object({
condition: literal("state"),
entity: string(),
entity: optional(string()),
state: optional(string()),
state_not: optional(string()),
});
Expand All @@ -36,6 +36,10 @@ export class HaCardConditionState extends LitElement {
return { condition: "state", entity: "", state: "" };
}

protected static validateUIConfig(condition: StateCondition) {
return assert(condition, stateConditionStruct);
}

protected willUpdate(changedProperties: PropertyValues): void {
if (!changedProperties.has("condition")) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,21 +163,27 @@ export class HuiConditionalCardEditor
`
: html`
<div class="conditions">
${this.hass!.localize(
"ui.panel.lovelace.editor.card.conditional.condition_explanation"
)}
${this._config.conditions.map(
(cond, idx) => html`
<ha-alert alert-type="info">
${this.hass!.localize(
"ui.panel.lovelace.editor.card.conditional.condition_explanation"
)}
</ha-alert>
${this._config.conditions.map((cond, idx) => {
const condition: Condition = {
condition: "state",
...cond,
};
return html`
<div class="condition">
<ha-card-condition-editor
.index=${idx}
@value-changed=${this._conditionChanged}
.hass=${this.hass}
.condition=${cond}
.condition=${condition}
></ha-card-condition-editor>
</div>
`
)}
`;
})}
<div>
<ha-button-menu
@action=${this._addCondition}
Expand Down
3 changes: 2 additions & 1 deletion src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1488,7 +1488,8 @@
"key_missing": "Required key ''{key}'' is missing.",
"key_not_expected": "Key ''{key}'' is not expected or not supported by the visual editor.",
"key_wrong_type": "The provided value for ''{key}'' is not supported by the visual editor. We support ({type_correct}) but received ({type_wrong}).",
"no_template_editor_support": "Templates not supported in visual editor"
"no_template_editor_support": "Templates not supported in visual editor",
"media_query_not_supported": "This media query is not supported by the visual editor."
},
"supervisor": {
"title": "Could not load the Supervisor panel!",
Expand Down

0 comments on commit 2db4f93

Please sign in to comment.