mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-26 02:36:37 +00:00
Add and and or condition to conditional card (#18409)
This commit is contained in:
parent
acb5a2b283
commit
463cfb869f
@ -1,5 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
mdiAccount,
|
mdiAccount,
|
||||||
|
mdiAmpersand,
|
||||||
|
mdiGateOr,
|
||||||
mdiNumeric,
|
mdiNumeric,
|
||||||
mdiResponsive,
|
mdiResponsive,
|
||||||
mdiStateMachine,
|
mdiStateMachine,
|
||||||
@ -11,4 +13,6 @@ export const ICON_CONDITION: Record<Condition["condition"], string> = {
|
|||||||
state: mdiStateMachine,
|
state: mdiStateMachine,
|
||||||
screen: mdiResponsive,
|
screen: mdiResponsive,
|
||||||
user: mdiAccount,
|
user: mdiAccount,
|
||||||
|
and: mdiAmpersand,
|
||||||
|
or: mdiGateOr,
|
||||||
};
|
};
|
||||||
|
@ -6,7 +6,9 @@ export type Condition =
|
|||||||
| NumericStateCondition
|
| NumericStateCondition
|
||||||
| ScreenCondition
|
| ScreenCondition
|
||||||
| StateCondition
|
| StateCondition
|
||||||
| UserCondition;
|
| UserCondition
|
||||||
|
| OrCondition
|
||||||
|
| AndCondition;
|
||||||
|
|
||||||
export type LegacyCondition = {
|
export type LegacyCondition = {
|
||||||
entity?: string;
|
entity?: string;
|
||||||
@ -38,6 +40,16 @@ export type UserCondition = {
|
|||||||
users?: string[];
|
users?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type OrCondition = {
|
||||||
|
condition: "or";
|
||||||
|
conditions?: Condition[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AndCondition = {
|
||||||
|
condition: "and";
|
||||||
|
conditions?: Condition[];
|
||||||
|
};
|
||||||
|
|
||||||
function checkStateCondition(
|
function checkStateCondition(
|
||||||
condition: StateCondition | LegacyCondition,
|
condition: StateCondition | LegacyCondition,
|
||||||
hass: HomeAssistant
|
hass: HomeAssistant
|
||||||
@ -87,6 +99,16 @@ function checkUserCondition(condition: UserCondition, hass: HomeAssistant) {
|
|||||||
: false;
|
: false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkAndCondition(condition: AndCondition, hass: HomeAssistant) {
|
||||||
|
if (!condition.conditions) return true;
|
||||||
|
return checkConditionsMet(condition.conditions, hass);
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkOrCondition(condition: OrCondition, hass: HomeAssistant) {
|
||||||
|
if (!condition.conditions) return true;
|
||||||
|
return condition.conditions.some((c) => checkConditionsMet([c], hass));
|
||||||
|
}
|
||||||
|
|
||||||
export function checkConditionsMet(
|
export function checkConditionsMet(
|
||||||
conditions: (Condition | LegacyCondition)[],
|
conditions: (Condition | LegacyCondition)[],
|
||||||
hass: HomeAssistant
|
hass: HomeAssistant
|
||||||
@ -100,6 +122,10 @@ export function checkConditionsMet(
|
|||||||
return checkUserCondition(c, hass);
|
return checkUserCondition(c, hass);
|
||||||
case "numeric_state":
|
case "numeric_state":
|
||||||
return checkStateNumericCondition(c, hass);
|
return checkStateNumericCondition(c, hass);
|
||||||
|
case "and":
|
||||||
|
return checkAndCondition(c, hass);
|
||||||
|
case "or":
|
||||||
|
return checkOrCondition(c, hass);
|
||||||
default:
|
default:
|
||||||
return checkStateCondition(c, hass);
|
return checkStateCondition(c, hass);
|
||||||
}
|
}
|
||||||
@ -123,6 +149,14 @@ function validateUserCondition(condition: UserCondition) {
|
|||||||
return condition.users != null;
|
return condition.users != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validateAndCondition(condition: AndCondition) {
|
||||||
|
return condition.conditions != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateOrCondition(condition: OrCondition) {
|
||||||
|
return condition.conditions != null;
|
||||||
|
}
|
||||||
|
|
||||||
function validateNumericStateCondition(condition: NumericStateCondition) {
|
function validateNumericStateCondition(condition: NumericStateCondition) {
|
||||||
return (
|
return (
|
||||||
condition.entity != null &&
|
condition.entity != null &&
|
||||||
@ -142,6 +176,10 @@ export function validateConditionalConfig(
|
|||||||
return validateUserCondition(c);
|
return validateUserCondition(c);
|
||||||
case "numeric_state":
|
case "numeric_state":
|
||||||
return validateNumericStateCondition(c);
|
return validateNumericStateCondition(c);
|
||||||
|
case "and":
|
||||||
|
return validateAndCondition(c);
|
||||||
|
case "or":
|
||||||
|
return validateOrCondition(c);
|
||||||
default:
|
default:
|
||||||
return validateStateCondition(c);
|
return validateStateCondition(c);
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,31 @@
|
|||||||
import { PropertyValues, ReactiveElement } from "lit";
|
import { PropertyValues, ReactiveElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { listenMediaQuery } from "../../../common/dom/media_query";
|
||||||
|
import { deepEqual } from "../../../common/util/deep-equal";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { ConditionalCardConfig } from "../cards/types";
|
import { ConditionalCardConfig } from "../cards/types";
|
||||||
import {
|
import {
|
||||||
ScreenCondition,
|
Condition,
|
||||||
|
LegacyCondition,
|
||||||
checkConditionsMet,
|
checkConditionsMet,
|
||||||
validateConditionalConfig,
|
validateConditionalConfig,
|
||||||
} from "../common/validate-condition";
|
} from "../common/validate-condition";
|
||||||
import { ConditionalRowConfig, LovelaceRow } from "../entity-rows/types";
|
import { ConditionalRowConfig, LovelaceRow } from "../entity-rows/types";
|
||||||
import { LovelaceCard } from "../types";
|
import { LovelaceCard } from "../types";
|
||||||
import { listenMediaQuery } from "../../../common/dom/media_query";
|
|
||||||
import { deepEqual } from "../../../common/util/deep-equal";
|
function extractMediaQueries(
|
||||||
|
conditions: (Condition | LegacyCondition)[]
|
||||||
|
): string[] {
|
||||||
|
return conditions.reduce<string[]>((array, c) => {
|
||||||
|
if ("conditions" in c && c.conditions) {
|
||||||
|
array.push(...extractMediaQueries(c.conditions));
|
||||||
|
}
|
||||||
|
if ("condition" in c && c.condition === "screen" && c.media_query) {
|
||||||
|
array.push(c.media_query);
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
@customElement("hui-conditional-base")
|
@customElement("hui-conditional-base")
|
||||||
export class HuiConditionalBase extends ReactiveElement {
|
export class HuiConditionalBase extends ReactiveElement {
|
||||||
@ -77,13 +92,7 @@ export class HuiConditionalBase extends ReactiveElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const conditions = this._config.conditions.filter(
|
const mediaQueries = extractMediaQueries(this._config.conditions);
|
||||||
(c) => "condition" in c && c.condition === "screen"
|
|
||||||
) as ScreenCondition[];
|
|
||||||
|
|
||||||
const mediaQueries = conditions
|
|
||||||
.filter((c) => c.media_query)
|
|
||||||
.map((c) => c.media_query as string);
|
|
||||||
|
|
||||||
if (deepEqual(mediaQueries, this._mediaQueries)) return;
|
if (deepEqual(mediaQueries, this._mediaQueries)) return;
|
||||||
|
|
||||||
@ -91,10 +100,15 @@ export class HuiConditionalBase extends ReactiveElement {
|
|||||||
while (this._mediaQueriesListeners.length) {
|
while (this._mediaQueriesListeners.length) {
|
||||||
this._mediaQueriesListeners.pop()!();
|
this._mediaQueriesListeners.pop()!();
|
||||||
}
|
}
|
||||||
|
|
||||||
mediaQueries.forEach((query) => {
|
mediaQueries.forEach((query) => {
|
||||||
const listener = listenMediaQuery(query, (matches) => {
|
const listener = listenMediaQuery(query, (matches) => {
|
||||||
// For performance, if there is only one condition, set the visibility directly
|
// For performance, if there is only one condition and it's a screen condition, set the visibility directly
|
||||||
if (this._config!.conditions.length === 1) {
|
if (
|
||||||
|
this._config!.conditions.length === 1 &&
|
||||||
|
"condition" in this._config!.conditions[0] &&
|
||||||
|
this._config!.conditions[0].condition === "screen"
|
||||||
|
) {
|
||||||
this._setVisibility(matches);
|
this._setVisibility(matches);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -128,6 +142,7 @@ export class HuiConditionalBase extends ReactiveElement {
|
|||||||
this._config!.conditions,
|
this._config!.conditions,
|
||||||
this.hass!
|
this.hass!
|
||||||
);
|
);
|
||||||
|
|
||||||
this._setVisibility(conditionMet);
|
this._setVisibility(conditionMet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
import { mdiPlus } from "@mdi/js";
|
import { mdiPlus } from "@mdi/js";
|
||||||
import { CSSResultGroup, LitElement, PropertyValues, css, html } from "lit";
|
import {
|
||||||
|
CSSResultGroup,
|
||||||
|
LitElement,
|
||||||
|
PropertyValues,
|
||||||
|
css,
|
||||||
|
html,
|
||||||
|
nothing,
|
||||||
|
} from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
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";
|
||||||
@ -18,12 +25,16 @@ import "./types/ha-card-condition-numeric_state";
|
|||||||
import "./types/ha-card-condition-screen";
|
import "./types/ha-card-condition-screen";
|
||||||
import "./types/ha-card-condition-state";
|
import "./types/ha-card-condition-state";
|
||||||
import "./types/ha-card-condition-user";
|
import "./types/ha-card-condition-user";
|
||||||
|
import "./types/ha-card-condition-or";
|
||||||
|
import "./types/ha-card-condition-and";
|
||||||
|
|
||||||
const UI_CONDITION = [
|
const UI_CONDITION = [
|
||||||
"numeric_state",
|
"numeric_state",
|
||||||
"state",
|
"state",
|
||||||
"screen",
|
"screen",
|
||||||
"user",
|
"user",
|
||||||
|
"and",
|
||||||
|
"or",
|
||||||
] as const satisfies readonly Condition["condition"][];
|
] as const satisfies readonly Condition["condition"][];
|
||||||
|
|
||||||
@customElement("ha-card-conditions-editor")
|
@customElement("ha-card-conditions-editor")
|
||||||
@ -35,6 +46,8 @@ export class HaCardConditionsEditor extends LitElement {
|
|||||||
| LegacyCondition
|
| LegacyCondition
|
||||||
)[];
|
)[];
|
||||||
|
|
||||||
|
@property({ attribute: true, type: Boolean }) public nested?: boolean;
|
||||||
|
|
||||||
private _focusLastConditionOnChange = false;
|
private _focusLastConditionOnChange = false;
|
||||||
|
|
||||||
protected firstUpdated() {
|
protected firstUpdated() {
|
||||||
@ -70,11 +83,15 @@ export class HaCardConditionsEditor extends LitElement {
|
|||||||
protected render() {
|
protected render() {
|
||||||
return html`
|
return html`
|
||||||
<div class="conditions">
|
<div class="conditions">
|
||||||
<ha-alert alert-type="info">
|
${!this.nested
|
||||||
${this.hass!.localize(
|
? html`
|
||||||
"ui.panel.lovelace.editor.condition-editor.explanation"
|
<ha-alert alert-type="info">
|
||||||
)}
|
${this.hass!.localize(
|
||||||
</ha-alert>
|
"ui.panel.lovelace.editor.condition-editor.explanation"
|
||||||
|
)}
|
||||||
|
</ha-alert>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
${this.conditions.map(
|
${this.conditions.map(
|
||||||
(cond, idx) => html`
|
(cond, idx) => html`
|
||||||
<ha-card-condition-editor
|
<ha-card-condition-editor
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
import { html, LitElement } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { any, array, assert, literal, object, optional } from "superstruct";
|
||||||
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
|
import "../../../../../components/ha-form/ha-form";
|
||||||
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
|
import {
|
||||||
|
AndCondition,
|
||||||
|
Condition,
|
||||||
|
StateCondition,
|
||||||
|
} from "../../../common/validate-condition";
|
||||||
|
|
||||||
|
const andConditionStruct = object({
|
||||||
|
condition: literal("and"),
|
||||||
|
conditions: optional(array(any())),
|
||||||
|
});
|
||||||
|
|
||||||
|
@customElement("ha-card-condition-and")
|
||||||
|
export class HaCardConditionNumericAnd extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public condition!: AndCondition;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
public static get defaultConfig(): AndCondition {
|
||||||
|
return { condition: "and", conditions: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static validateUIConfig(condition: StateCondition) {
|
||||||
|
return assert(condition, andConditionStruct);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
return html`
|
||||||
|
<ha-card-conditions-editor
|
||||||
|
nested
|
||||||
|
.hass=${this.hass}
|
||||||
|
.conditions=${this.condition.conditions}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
>
|
||||||
|
</ha-card-conditions-editor>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(ev: CustomEvent): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const conditions = ev.detail.value as Condition[];
|
||||||
|
const condition = {
|
||||||
|
...this.condition,
|
||||||
|
conditions,
|
||||||
|
};
|
||||||
|
fireEvent(this, "value-changed", { value: condition });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-card-condition-and": HaCardConditionNumericAnd;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
import { html, LitElement } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { any, array, assert, literal, object, optional } from "superstruct";
|
||||||
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
|
import "../../../../../components/ha-form/ha-form";
|
||||||
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
|
import {
|
||||||
|
Condition,
|
||||||
|
OrCondition,
|
||||||
|
StateCondition,
|
||||||
|
} from "../../../common/validate-condition";
|
||||||
|
|
||||||
|
const orConditionStruct = object({
|
||||||
|
condition: literal("or"),
|
||||||
|
conditions: optional(array(any())),
|
||||||
|
});
|
||||||
|
|
||||||
|
@customElement("ha-card-condition-or")
|
||||||
|
export class HaCardConditionOr extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public condition!: OrCondition;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
public static get defaultConfig(): OrCondition {
|
||||||
|
return { condition: "or", conditions: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static validateUIConfig(condition: StateCondition) {
|
||||||
|
return assert(condition, orConditionStruct);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
return html`
|
||||||
|
<ha-card-conditions-editor
|
||||||
|
nested
|
||||||
|
.hass=${this.hass}
|
||||||
|
.conditions=${this.condition.conditions}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
>
|
||||||
|
</ha-card-conditions-editor>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(ev: CustomEvent): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const conditions = ev.detail.value as Condition[];
|
||||||
|
const condition = {
|
||||||
|
...this.condition,
|
||||||
|
conditions,
|
||||||
|
};
|
||||||
|
fireEvent(this, "value-changed", { value: condition });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-card-condition-or": HaCardConditionOr;
|
||||||
|
}
|
||||||
|
}
|
@ -4844,6 +4844,12 @@
|
|||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"label": "User"
|
"label": "User"
|
||||||
|
},
|
||||||
|
"or": {
|
||||||
|
"label": "Or"
|
||||||
|
},
|
||||||
|
"and": {
|
||||||
|
"label": "And"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user