mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +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 {
|
||||
mdiAccount,
|
||||
mdiAmpersand,
|
||||
mdiGateOr,
|
||||
mdiNumeric,
|
||||
mdiResponsive,
|
||||
mdiStateMachine,
|
||||
@ -11,4 +13,6 @@ export const ICON_CONDITION: Record<Condition["condition"], string> = {
|
||||
state: mdiStateMachine,
|
||||
screen: mdiResponsive,
|
||||
user: mdiAccount,
|
||||
and: mdiAmpersand,
|
||||
or: mdiGateOr,
|
||||
};
|
||||
|
@ -6,7 +6,9 @@ export type Condition =
|
||||
| NumericStateCondition
|
||||
| ScreenCondition
|
||||
| StateCondition
|
||||
| UserCondition;
|
||||
| UserCondition
|
||||
| OrCondition
|
||||
| AndCondition;
|
||||
|
||||
export type LegacyCondition = {
|
||||
entity?: string;
|
||||
@ -38,6 +40,16 @@ export type UserCondition = {
|
||||
users?: string[];
|
||||
};
|
||||
|
||||
export type OrCondition = {
|
||||
condition: "or";
|
||||
conditions?: Condition[];
|
||||
};
|
||||
|
||||
export type AndCondition = {
|
||||
condition: "and";
|
||||
conditions?: Condition[];
|
||||
};
|
||||
|
||||
function checkStateCondition(
|
||||
condition: StateCondition | LegacyCondition,
|
||||
hass: HomeAssistant
|
||||
@ -87,6 +99,16 @@ function checkUserCondition(condition: UserCondition, hass: HomeAssistant) {
|
||||
: 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(
|
||||
conditions: (Condition | LegacyCondition)[],
|
||||
hass: HomeAssistant
|
||||
@ -100,6 +122,10 @@ export function checkConditionsMet(
|
||||
return checkUserCondition(c, hass);
|
||||
case "numeric_state":
|
||||
return checkStateNumericCondition(c, hass);
|
||||
case "and":
|
||||
return checkAndCondition(c, hass);
|
||||
case "or":
|
||||
return checkOrCondition(c, hass);
|
||||
default:
|
||||
return checkStateCondition(c, hass);
|
||||
}
|
||||
@ -123,6 +149,14 @@ function validateUserCondition(condition: UserCondition) {
|
||||
return condition.users != null;
|
||||
}
|
||||
|
||||
function validateAndCondition(condition: AndCondition) {
|
||||
return condition.conditions != null;
|
||||
}
|
||||
|
||||
function validateOrCondition(condition: OrCondition) {
|
||||
return condition.conditions != null;
|
||||
}
|
||||
|
||||
function validateNumericStateCondition(condition: NumericStateCondition) {
|
||||
return (
|
||||
condition.entity != null &&
|
||||
@ -142,6 +176,10 @@ export function validateConditionalConfig(
|
||||
return validateUserCondition(c);
|
||||
case "numeric_state":
|
||||
return validateNumericStateCondition(c);
|
||||
case "and":
|
||||
return validateAndCondition(c);
|
||||
case "or":
|
||||
return validateOrCondition(c);
|
||||
default:
|
||||
return validateStateCondition(c);
|
||||
}
|
||||
|
@ -1,16 +1,31 @@
|
||||
import { PropertyValues, ReactiveElement } from "lit";
|
||||
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 { ConditionalCardConfig } from "../cards/types";
|
||||
import {
|
||||
ScreenCondition,
|
||||
Condition,
|
||||
LegacyCondition,
|
||||
checkConditionsMet,
|
||||
validateConditionalConfig,
|
||||
} from "../common/validate-condition";
|
||||
import { ConditionalRowConfig, LovelaceRow } from "../entity-rows/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")
|
||||
export class HuiConditionalBase extends ReactiveElement {
|
||||
@ -77,13 +92,7 @@ export class HuiConditionalBase extends ReactiveElement {
|
||||
return;
|
||||
}
|
||||
|
||||
const conditions = this._config.conditions.filter(
|
||||
(c) => "condition" in c && c.condition === "screen"
|
||||
) as ScreenCondition[];
|
||||
|
||||
const mediaQueries = conditions
|
||||
.filter((c) => c.media_query)
|
||||
.map((c) => c.media_query as string);
|
||||
const mediaQueries = extractMediaQueries(this._config.conditions);
|
||||
|
||||
if (deepEqual(mediaQueries, this._mediaQueries)) return;
|
||||
|
||||
@ -91,10 +100,15 @@ export class HuiConditionalBase extends ReactiveElement {
|
||||
while (this._mediaQueriesListeners.length) {
|
||||
this._mediaQueriesListeners.pop()!();
|
||||
}
|
||||
|
||||
mediaQueries.forEach((query) => {
|
||||
const listener = listenMediaQuery(query, (matches) => {
|
||||
// For performance, if there is only one condition, set the visibility directly
|
||||
if (this._config!.conditions.length === 1) {
|
||||
// For performance, if there is only one condition and it's a screen condition, set the visibility directly
|
||||
if (
|
||||
this._config!.conditions.length === 1 &&
|
||||
"condition" in this._config!.conditions[0] &&
|
||||
this._config!.conditions[0].condition === "screen"
|
||||
) {
|
||||
this._setVisibility(matches);
|
||||
return;
|
||||
}
|
||||
@ -128,6 +142,7 @@ export class HuiConditionalBase extends ReactiveElement {
|
||||
this._config!.conditions,
|
||||
this.hass!
|
||||
);
|
||||
|
||||
this._setVisibility(conditionMet);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,12 @@
|
||||
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 { fireEvent } from "../../../../common/dom/fire_event";
|
||||
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-state";
|
||||
import "./types/ha-card-condition-user";
|
||||
import "./types/ha-card-condition-or";
|
||||
import "./types/ha-card-condition-and";
|
||||
|
||||
const UI_CONDITION = [
|
||||
"numeric_state",
|
||||
"state",
|
||||
"screen",
|
||||
"user",
|
||||
"and",
|
||||
"or",
|
||||
] as const satisfies readonly Condition["condition"][];
|
||||
|
||||
@customElement("ha-card-conditions-editor")
|
||||
@ -35,6 +46,8 @@ export class HaCardConditionsEditor extends LitElement {
|
||||
| LegacyCondition
|
||||
)[];
|
||||
|
||||
@property({ attribute: true, type: Boolean }) public nested?: boolean;
|
||||
|
||||
private _focusLastConditionOnChange = false;
|
||||
|
||||
protected firstUpdated() {
|
||||
@ -70,11 +83,15 @@ export class HaCardConditionsEditor extends LitElement {
|
||||
protected render() {
|
||||
return html`
|
||||
<div class="conditions">
|
||||
<ha-alert alert-type="info">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.condition-editor.explanation"
|
||||
)}
|
||||
</ha-alert>
|
||||
${!this.nested
|
||||
? html`
|
||||
<ha-alert alert-type="info">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.condition-editor.explanation"
|
||||
)}
|
||||
</ha-alert>
|
||||
`
|
||||
: nothing}
|
||||
${this.conditions.map(
|
||||
(cond, idx) => html`
|
||||
<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": {
|
||||
"label": "User"
|
||||
},
|
||||
"or": {
|
||||
"label": "Or"
|
||||
},
|
||||
"and": {
|
||||
"label": "And"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user