mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-27 11:16:35 +00:00
Improve warning messages in conditional card (#18272)
* Improve warning messages in conditional card * Update src/panels/lovelace/editor/conditions/ha-card-condition-editor.ts Co-authored-by: Bram Kragten <mail@bramkragten.nl> --------- Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
parent
c48b620e03
commit
c6be4d6f4d
@ -13,45 +13,33 @@ export const handleStructError = (
|
|||||||
for (const failure of err.failures()) {
|
for (const failure of err.failures()) {
|
||||||
if (failure.value === undefined) {
|
if (failure.value === undefined) {
|
||||||
errors.push(
|
errors.push(
|
||||||
hass.localize(
|
hass.localize("ui.errors.config.key_missing", {
|
||||||
"ui.errors.config.key_missing",
|
key: failure.path.join("."),
|
||||||
"key",
|
})
|
||||||
failure.path.join(".")
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
} else if (failure.type === "never") {
|
} else if (failure.type === "never") {
|
||||||
warnings.push(
|
warnings.push(
|
||||||
hass.localize(
|
hass.localize("ui.errors.config.key_not_expected", {
|
||||||
"ui.errors.config.key_not_expected",
|
key: failure.path.join("."),
|
||||||
"key",
|
})
|
||||||
failure.path.join(".")
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
} else if (failure.type === "union") {
|
} else if (failure.type === "union") {
|
||||||
continue;
|
continue;
|
||||||
} else if (failure.type === "enums") {
|
} else if (failure.type === "enums") {
|
||||||
warnings.push(
|
warnings.push(
|
||||||
hass.localize(
|
hass.localize("ui.errors.config.key_wrong_type", {
|
||||||
"ui.errors.config.key_wrong_type",
|
key: failure.path.join("."),
|
||||||
"key",
|
type_correct: failure.message.replace("Expected ", "").split(", ")[0],
|
||||||
failure.path.join("."),
|
type_wrong: JSON.stringify(failure.value),
|
||||||
"type_correct",
|
})
|
||||||
failure.message.replace("Expected ", "").split(", ")[0],
|
|
||||||
"type_wrong",
|
|
||||||
JSON.stringify(failure.value)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
warnings.push(
|
warnings.push(
|
||||||
hass.localize(
|
hass.localize("ui.errors.config.key_wrong_type", {
|
||||||
"ui.errors.config.key_wrong_type",
|
key: failure.path.join("."),
|
||||||
"key",
|
type_correct: failure.refinement || failure.type,
|
||||||
failure.path.join("."),
|
type_wrong: JSON.stringify(failure.value),
|
||||||
"type_correct",
|
})
|
||||||
failure.refinement || failure.type,
|
|
||||||
"type_wrong",
|
|
||||||
JSON.stringify(failure.value)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Statistic, StatisticType } from "../../../data/recorder";
|
import { Statistic, StatisticType } from "../../../data/recorder";
|
||||||
import { ActionConfig, LovelaceCardConfig } from "../../../data/lovelace";
|
import { ActionConfig, LovelaceCardConfig } from "../../../data/lovelace";
|
||||||
import { FullCalendarView, TranslationDict } from "../../../types";
|
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 { HuiImage } from "../components/hui-image";
|
||||||
import { LovelaceElementConfig } from "../elements/types";
|
import { LovelaceElementConfig } from "../elements/types";
|
||||||
import {
|
import {
|
||||||
@ -37,7 +37,7 @@ export interface CalendarCardConfig extends LovelaceCardConfig {
|
|||||||
|
|
||||||
export interface ConditionalCardConfig extends LovelaceCardConfig {
|
export interface ConditionalCardConfig extends LovelaceCardConfig {
|
||||||
card: LovelaceCardConfig;
|
card: LovelaceCardConfig;
|
||||||
conditions: Condition[];
|
conditions: (Condition | LegacyCondition)[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EmptyStateCardConfig extends LovelaceCardConfig {
|
export interface EmptyStateCardConfig extends LovelaceCardConfig {
|
||||||
|
@ -21,7 +21,10 @@ export type ScreenCondition = {
|
|||||||
media_query?: string;
|
media_query?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function checkStateCondition(condition: StateCondition, hass: HomeAssistant) {
|
function checkStateCondition(
|
||||||
|
condition: StateCondition | LegacyCondition,
|
||||||
|
hass: HomeAssistant
|
||||||
|
) {
|
||||||
const state =
|
const state =
|
||||||
condition.entity && hass.states[condition.entity]
|
condition.entity && hass.states[condition.entity]
|
||||||
? hass.states[condition.entity].state
|
? hass.states[condition.entity].state
|
||||||
@ -42,19 +45,20 @@ function checkScreenCondition(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function checkConditionsMet(
|
export function checkConditionsMet(
|
||||||
conditions: Condition[],
|
conditions: (Condition | LegacyCondition)[],
|
||||||
hass: HomeAssistant
|
hass: HomeAssistant
|
||||||
): boolean {
|
): boolean {
|
||||||
return conditions.every((c) => {
|
return conditions.every((c) => {
|
||||||
if (c.condition === "screen") {
|
if ("condition" in c) {
|
||||||
return checkScreenCondition(c, hass);
|
if (c.condition === "screen") {
|
||||||
|
return checkScreenCondition(c, hass);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return checkStateCondition(c, hass);
|
return checkStateCondition(c, hass);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function valideStateCondition(condition: StateCondition) {
|
function valideStateCondition(condition: StateCondition | LegacyCondition) {
|
||||||
return (
|
return (
|
||||||
condition.entity != null &&
|
condition.entity != null &&
|
||||||
(condition.state != null || condition.state_not != null)
|
(condition.state != null || condition.state_not != null)
|
||||||
@ -65,10 +69,14 @@ function validateScreenCondition(condition: ScreenCondition) {
|
|||||||
return condition.media_query != null;
|
return condition.media_query != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validateConditionalConfig(conditions: Condition[]): boolean {
|
export function validateConditionalConfig(
|
||||||
|
conditions: (Condition | LegacyCondition)[]
|
||||||
|
): boolean {
|
||||||
return conditions.every((c) => {
|
return conditions.every((c) => {
|
||||||
if (c.condition === "screen") {
|
if ("condition" in c) {
|
||||||
return validateScreenCondition(c);
|
if (c.condition === "screen") {
|
||||||
|
return validateScreenCondition(c);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return valideStateCondition(c);
|
return valideStateCondition(c);
|
||||||
});
|
});
|
||||||
|
@ -78,7 +78,7 @@ export class HuiConditionalBase extends ReactiveElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const conditions = this._config.conditions.filter(
|
const conditions = this._config.conditions.filter(
|
||||||
(c) => c.condition === "screen"
|
(c) => "condition" in c && c.condition === "screen"
|
||||||
) as ScreenCondition[];
|
) as ScreenCondition[];
|
||||||
|
|
||||||
const mediaQueries = conditions
|
const mediaQueries = conditions
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { preventDefault } from "@fullcalendar/core/internal";
|
import { preventDefault } from "@fullcalendar/core/internal";
|
||||||
import { ActionDetail } from "@material/mwc-list";
|
import { ActionDetail } from "@material/mwc-list";
|
||||||
import { mdiCheck, mdiDelete, mdiDotsVertical } from "@mdi/js";
|
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 { customElement, property, state } from "lit/decorators";
|
||||||
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
|
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
@ -13,33 +13,59 @@ import "../../../../components/ha-svg-icon";
|
|||||||
import "../../../../components/ha-yaml-editor";
|
import "../../../../components/ha-yaml-editor";
|
||||||
import { haStyle } from "../../../../resources/styles";
|
import { haStyle } from "../../../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
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 { 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")
|
@customElement("ha-card-condition-editor")
|
||||||
export default class HaCardConditionEditor extends LitElement {
|
export default class HaCardConditionEditor extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ attribute: false }) condition!: Condition | LegacyCondition;
|
@property({ attribute: false }) condition!: Condition;
|
||||||
|
|
||||||
@state() public _yamlMode = false;
|
@state() public _yamlMode = false;
|
||||||
|
|
||||||
protected render() {
|
@state() public _uiAvailable = false;
|
||||||
const condition: Condition = {
|
|
||||||
condition: "state",
|
@state() public _uiWarnings: string[] = [];
|
||||||
...this.condition,
|
|
||||||
};
|
private get _editor() {
|
||||||
const element = customElements.get(
|
const element = customElements.get(
|
||||||
`ha-card-condition-${condition.condition}`
|
`ha-card-condition-${this.condition.condition}`
|
||||||
) as LovelaceConditionEditorConstructor | undefined;
|
) as LovelaceConditionEditorConstructor | undefined;
|
||||||
const supported = element !== undefined;
|
|
||||||
|
|
||||||
const valid =
|
return element;
|
||||||
element &&
|
}
|
||||||
(!element.validateUIConfig || element.validateUIConfig(condition));
|
|
||||||
|
|
||||||
const yamlMode = this._yamlMode || !supported || !valid;
|
protected willUpdate(changedProperties: PropertyValues): void {
|
||||||
|
if (changedProperties.has("condition")) {
|
||||||
|
const validator = 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 = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._uiAvailable && !this._yamlMode) {
|
||||||
|
this._yamlMode = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const condition = this.condition;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="header">
|
<div class="header">
|
||||||
@ -68,9 +94,9 @@ export default class HaCardConditionEditor extends LitElement {
|
|||||||
>
|
>
|
||||||
</ha-icon-button>
|
</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")}
|
${this.hass.localize("ui.panel.lovelace.editor.edit_card.edit_ui")}
|
||||||
${!yamlMode
|
${!this._yamlMode
|
||||||
? html`
|
? html`
|
||||||
<ha-svg-icon
|
<ha-svg-icon
|
||||||
class="selected_menu_item"
|
class="selected_menu_item"
|
||||||
@ -85,7 +111,7 @@ export default class HaCardConditionEditor extends LitElement {
|
|||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.lovelace.editor.edit_card.edit_yaml"
|
"ui.panel.lovelace.editor.edit_card.edit_yaml"
|
||||||
)}
|
)}
|
||||||
${yamlMode
|
${this._yamlMode
|
||||||
? html`
|
? html`
|
||||||
<ha-svg-icon
|
<ha-svg-icon
|
||||||
class="selected_menu_item"
|
class="selected_menu_item"
|
||||||
@ -108,15 +134,30 @@ export default class HaCardConditionEditor extends LitElement {
|
|||||||
</ha-list-item>
|
</ha-list-item>
|
||||||
</ha-button-menu>
|
</ha-button-menu>
|
||||||
</div>
|
</div>
|
||||||
${!valid
|
${!this._uiAvailable
|
||||||
? html`
|
? html`
|
||||||
<ha-alert alert-type="warning">
|
<ha-alert
|
||||||
${this.hass.localize("ui.errors.config.editor_not_supported")}
|
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>
|
</ha-alert>
|
||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
<div class="content">
|
<div class="content">
|
||||||
${yamlMode
|
${this._yamlMode
|
||||||
? html`
|
? html`
|
||||||
<ha-yaml-editor
|
<ha-yaml-editor
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
import { HomeAssistant } from "../../../../types";
|
||||||
import { Condition } from "../../common/validate-condition";
|
import { Condition } from "../../common/validate-condition";
|
||||||
|
|
||||||
export interface LovelaceConditionEditorConstructor {
|
export interface LovelaceConditionEditorConstructor {
|
||||||
defaultConfig?: Condition;
|
defaultConfig?: Condition;
|
||||||
validateUIConfig?: (condition: Condition) => boolean;
|
validateUIConfig?: (condition: Condition, hass: HomeAssistant) => void;
|
||||||
}
|
}
|
||||||
|
@ -103,10 +103,17 @@ export class HaCardConditionScreen extends LitElement {
|
|||||||
return { condition: "screen", media_query: "" };
|
return { condition: "screen", media_query: "" };
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static validateUIConfig(condition: ScreenCondition) {
|
protected static validateUIConfig(
|
||||||
return (
|
condition: ScreenCondition,
|
||||||
!condition.media_query || mediaQueryReverseMap.get(condition.media_query)
|
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(
|
private _schema = memoizeOne(
|
||||||
|
@ -12,7 +12,7 @@ import { StateCondition } from "../../../common/validate-condition";
|
|||||||
|
|
||||||
const stateConditionStruct = object({
|
const stateConditionStruct = object({
|
||||||
condition: literal("state"),
|
condition: literal("state"),
|
||||||
entity: string(),
|
entity: optional(string()),
|
||||||
state: optional(string()),
|
state: optional(string()),
|
||||||
state_not: optional(string()),
|
state_not: optional(string()),
|
||||||
});
|
});
|
||||||
@ -36,6 +36,10 @@ export class HaCardConditionState extends LitElement {
|
|||||||
return { condition: "state", entity: "", state: "" };
|
return { condition: "state", entity: "", state: "" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static validateUIConfig(condition: StateCondition) {
|
||||||
|
return assert(condition, stateConditionStruct);
|
||||||
|
}
|
||||||
|
|
||||||
protected willUpdate(changedProperties: PropertyValues): void {
|
protected willUpdate(changedProperties: PropertyValues): void {
|
||||||
if (!changedProperties.has("condition")) {
|
if (!changedProperties.has("condition")) {
|
||||||
return;
|
return;
|
||||||
|
@ -163,21 +163,27 @@ export class HuiConditionalCardEditor
|
|||||||
`
|
`
|
||||||
: html`
|
: html`
|
||||||
<div class="conditions">
|
<div class="conditions">
|
||||||
${this.hass!.localize(
|
<ha-alert alert-type="info">
|
||||||
"ui.panel.lovelace.editor.card.conditional.condition_explanation"
|
${this.hass!.localize(
|
||||||
)}
|
"ui.panel.lovelace.editor.card.conditional.condition_explanation"
|
||||||
${this._config.conditions.map(
|
)}
|
||||||
(cond, idx) => html`
|
</ha-alert>
|
||||||
|
${this._config.conditions.map((cond, idx) => {
|
||||||
|
const condition: Condition = {
|
||||||
|
condition: "state",
|
||||||
|
...cond,
|
||||||
|
};
|
||||||
|
return html`
|
||||||
<div class="condition">
|
<div class="condition">
|
||||||
<ha-card-condition-editor
|
<ha-card-condition-editor
|
||||||
.index=${idx}
|
.index=${idx}
|
||||||
@value-changed=${this._conditionChanged}
|
@value-changed=${this._conditionChanged}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.condition=${cond}
|
.condition=${condition}
|
||||||
></ha-card-condition-editor>
|
></ha-card-condition-editor>
|
||||||
</div>
|
</div>
|
||||||
`
|
`;
|
||||||
)}
|
})}
|
||||||
<div>
|
<div>
|
||||||
<ha-button-menu
|
<ha-button-menu
|
||||||
@action=${this._addCondition}
|
@action=${this._addCondition}
|
||||||
|
@ -1488,7 +1488,8 @@
|
|||||||
"key_missing": "Required key ''{key}'' is missing.",
|
"key_missing": "Required key ''{key}'' is missing.",
|
||||||
"key_not_expected": "Key ''{key}'' is not expected or not supported by the visual editor.",
|
"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}).",
|
"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": {
|
"supervisor": {
|
||||||
"title": "Could not load the Supervisor panel!",
|
"title": "Could not load the Supervisor panel!",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user