mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 18:26:35 +00:00
Add numeric state condition for conditional card (#18288)
* Add numeric state condition for conditional card * Add validate ui * Clean entity data * Check for numeric state
This commit is contained in:
parent
463a3244cf
commit
fdddc18291
@ -1,7 +1,13 @@
|
|||||||
import { mdiAccount, mdiResponsive, mdiStateMachine } from "@mdi/js";
|
import {
|
||||||
|
mdiAccount,
|
||||||
|
mdiNumeric,
|
||||||
|
mdiResponsive,
|
||||||
|
mdiStateMachine,
|
||||||
|
} from "@mdi/js";
|
||||||
import { Condition } from "./validate-condition";
|
import { Condition } from "./validate-condition";
|
||||||
|
|
||||||
export const ICON_CONDITION: Record<Condition["condition"], string> = {
|
export const ICON_CONDITION: Record<Condition["condition"], string> = {
|
||||||
|
numeric_state: mdiNumeric,
|
||||||
state: mdiStateMachine,
|
state: mdiStateMachine,
|
||||||
screen: mdiResponsive,
|
screen: mdiResponsive,
|
||||||
user: mdiAccount,
|
user: mdiAccount,
|
||||||
|
@ -2,7 +2,11 @@ import { ensureArray } from "../../../common/array/ensure-array";
|
|||||||
import { UNAVAILABLE } from "../../../data/entity";
|
import { UNAVAILABLE } from "../../../data/entity";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
|
|
||||||
export type Condition = StateCondition | ScreenCondition | UserCondition;
|
export type Condition =
|
||||||
|
| NumericStateCondition
|
||||||
|
| ScreenCondition
|
||||||
|
| StateCondition
|
||||||
|
| UserCondition;
|
||||||
|
|
||||||
export type LegacyCondition = {
|
export type LegacyCondition = {
|
||||||
entity?: string;
|
entity?: string;
|
||||||
@ -10,6 +14,13 @@ export type LegacyCondition = {
|
|||||||
state_not?: string | string[];
|
state_not?: string | string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type NumericStateCondition = {
|
||||||
|
condition: "numeric_state";
|
||||||
|
entity?: string;
|
||||||
|
below?: number;
|
||||||
|
above?: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type StateCondition = {
|
export type StateCondition = {
|
||||||
condition: "state";
|
condition: "state";
|
||||||
entity?: string;
|
entity?: string;
|
||||||
@ -41,6 +52,29 @@ function checkStateCondition(
|
|||||||
: ensureArray(condition.state_not).includes(state);
|
: ensureArray(condition.state_not).includes(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkStateNumericCondition(
|
||||||
|
condition: NumericStateCondition,
|
||||||
|
hass: HomeAssistant
|
||||||
|
) {
|
||||||
|
const entity =
|
||||||
|
(condition.entity ? hass.states[condition.entity] : undefined) ?? undefined;
|
||||||
|
|
||||||
|
if (!entity) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const numericState = Number(entity.state);
|
||||||
|
|
||||||
|
if (isNaN(numericState)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
(condition.above == null || condition.above < numericState) &&
|
||||||
|
(condition.below == null || condition.below >= numericState)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function checkScreenCondition(condition: ScreenCondition, _: HomeAssistant) {
|
function checkScreenCondition(condition: ScreenCondition, _: HomeAssistant) {
|
||||||
return condition.media_query
|
return condition.media_query
|
||||||
? matchMedia(condition.media_query).matches
|
? matchMedia(condition.media_query).matches
|
||||||
@ -64,6 +98,8 @@ export function checkConditionsMet(
|
|||||||
return checkScreenCondition(c, hass);
|
return checkScreenCondition(c, hass);
|
||||||
case "user":
|
case "user":
|
||||||
return checkUserCondition(c, hass);
|
return checkUserCondition(c, hass);
|
||||||
|
case "numeric_state":
|
||||||
|
return checkStateNumericCondition(c, hass);
|
||||||
default:
|
default:
|
||||||
return checkStateCondition(c, hass);
|
return checkStateCondition(c, hass);
|
||||||
}
|
}
|
||||||
@ -87,6 +123,13 @@ function validateUserCondition(condition: UserCondition) {
|
|||||||
return condition.users != null;
|
return condition.users != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validateNumericStateCondition(condition: NumericStateCondition) {
|
||||||
|
return (
|
||||||
|
condition.entity != null &&
|
||||||
|
(condition.above != null || condition.below != null)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function validateConditionalConfig(
|
export function validateConditionalConfig(
|
||||||
conditions: (Condition | LegacyCondition)[]
|
conditions: (Condition | LegacyCondition)[]
|
||||||
): boolean {
|
): boolean {
|
||||||
@ -97,6 +140,8 @@ export function validateConditionalConfig(
|
|||||||
return validateScreenCondition(c);
|
return validateScreenCondition(c);
|
||||||
case "user":
|
case "user":
|
||||||
return validateUserCondition(c);
|
return validateUserCondition(c);
|
||||||
|
case "numeric_state":
|
||||||
|
return validateNumericStateCondition(c);
|
||||||
default:
|
default:
|
||||||
return validateStateCondition(c);
|
return validateStateCondition(c);
|
||||||
}
|
}
|
||||||
|
@ -12,11 +12,13 @@ import { ICON_CONDITION } from "../../common/icon-condition";
|
|||||||
import { Condition, LegacyCondition } from "../../common/validate-condition";
|
import { Condition, LegacyCondition } from "../../common/validate-condition";
|
||||||
import "./ha-card-condition-editor";
|
import "./ha-card-condition-editor";
|
||||||
import { LovelaceConditionEditorConstructor } from "./types";
|
import { LovelaceConditionEditorConstructor } from "./types";
|
||||||
|
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";
|
||||||
|
|
||||||
const UI_CONDITION = [
|
const UI_CONDITION = [
|
||||||
|
"numeric_state",
|
||||||
"state",
|
"state",
|
||||||
"screen",
|
"screen",
|
||||||
"user",
|
"user",
|
||||||
|
@ -0,0 +1,113 @@
|
|||||||
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
|
import { html, LitElement } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import { assert, literal, number, object, optional, string } from "superstruct";
|
||||||
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
|
import "../../../../../components/ha-form/ha-form";
|
||||||
|
import type { SchemaUnion } from "../../../../../components/ha-form/types";
|
||||||
|
import { HaFormSchema } from "../../../../../components/ha-form/types";
|
||||||
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
|
import {
|
||||||
|
NumericStateCondition,
|
||||||
|
StateCondition,
|
||||||
|
} from "../../../common/validate-condition";
|
||||||
|
|
||||||
|
const numericStateConditionStruct = object({
|
||||||
|
condition: literal("numeric_state"),
|
||||||
|
entity: optional(string()),
|
||||||
|
above: optional(number()),
|
||||||
|
below: optional(number()),
|
||||||
|
});
|
||||||
|
|
||||||
|
@customElement("ha-card-condition-numeric_state")
|
||||||
|
export class HaCardConditionNumericState extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public condition!: NumericStateCondition;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
public static get defaultConfig(): NumericStateCondition {
|
||||||
|
return { condition: "numeric_state", entity: "" };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static validateUIConfig(condition: StateCondition) {
|
||||||
|
return assert(condition, numericStateConditionStruct);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _schema = memoizeOne(
|
||||||
|
(stateObj?: HassEntity) =>
|
||||||
|
[
|
||||||
|
{ name: "entity", selector: { entity: {} } },
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
type: "grid",
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
name: "above",
|
||||||
|
selector: {
|
||||||
|
number: {
|
||||||
|
mode: "box",
|
||||||
|
unit_of_measurement: stateObj?.attributes.unit_of_measurement,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "below",
|
||||||
|
selector: {
|
||||||
|
number: {
|
||||||
|
mode: "box",
|
||||||
|
unit_of_measurement: stateObj?.attributes.unit_of_measurement,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] as const satisfies readonly HaFormSchema[]
|
||||||
|
);
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const stateObj = this.condition.entity
|
||||||
|
? this.hass.states[this.condition.entity]
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-form
|
||||||
|
.hass=${this.hass}
|
||||||
|
.data=${this.condition}
|
||||||
|
.schema=${this._schema(stateObj)}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
.computeLabel=${this._computeLabelCallback}
|
||||||
|
></ha-form>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(ev: CustomEvent): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const condition = ev.detail.value as NumericStateCondition;
|
||||||
|
fireEvent(this, "value-changed", { value: condition });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computeLabelCallback = (
|
||||||
|
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||||
|
): string => {
|
||||||
|
switch (schema.name) {
|
||||||
|
case "entity":
|
||||||
|
return this.hass.localize("ui.components.entity.entity-picker.entity");
|
||||||
|
case "below":
|
||||||
|
return "Below";
|
||||||
|
case "above":
|
||||||
|
return "Above";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-card-condition-numeric_state": HaCardConditionNumericState;
|
||||||
|
}
|
||||||
|
}
|
@ -19,7 +19,7 @@ const stateConditionStruct = object({
|
|||||||
|
|
||||||
type StateConditionData = {
|
type StateConditionData = {
|
||||||
condition: "state";
|
condition: "state";
|
||||||
entity: string;
|
entity?: string;
|
||||||
invert: "true" | "false";
|
invert: "true" | "false";
|
||||||
state?: string | string[];
|
state?: string | string[];
|
||||||
};
|
};
|
||||||
@ -101,12 +101,9 @@ export class HaCardConditionState extends LitElement {
|
|||||||
|
|
||||||
const data: StateConditionData = {
|
const data: StateConditionData = {
|
||||||
...content,
|
...content,
|
||||||
entity: this.condition.entity ?? "",
|
entity: this.condition.entity,
|
||||||
invert: this.condition.state_not ? "true" : "false",
|
invert: this.condition.state_not ? "true" : "false",
|
||||||
state:
|
state: this.condition.state_not ?? this.condition.state,
|
||||||
(this.condition.state_not as string | string[] | undefined) ??
|
|
||||||
(this.condition.state as string | string[] | undefined) ??
|
|
||||||
"",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
@ -125,12 +122,11 @@ export class HaCardConditionState extends LitElement {
|
|||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const data = ev.detail.value as StateConditionData;
|
const data = ev.detail.value as StateConditionData;
|
||||||
|
|
||||||
const { invert, state, entity, condition: _, ...content } = data;
|
const { invert, state, condition: _, ...content } = data;
|
||||||
|
|
||||||
const condition: StateCondition = {
|
const condition: StateCondition = {
|
||||||
condition: "state",
|
condition: "state",
|
||||||
...content,
|
...content,
|
||||||
entity: entity ?? "",
|
|
||||||
state: invert === "false" ? state ?? "" : undefined,
|
state: invert === "false" ? state ?? "" : undefined,
|
||||||
state_not: invert === "true" ? state ?? "" : undefined,
|
state_not: invert === "true" ? state ?? "" : undefined,
|
||||||
};
|
};
|
||||||
|
@ -4758,6 +4758,9 @@
|
|||||||
"explanation": "The card will be shown when ALL conditions below are fulfilled.",
|
"explanation": "The card will be shown when ALL conditions below are fulfilled.",
|
||||||
"add": "Add condition",
|
"add": "Add condition",
|
||||||
"condition": {
|
"condition": {
|
||||||
|
"numeric_state": {
|
||||||
|
"label": "Entity numeric state"
|
||||||
|
},
|
||||||
"screen": {
|
"screen": {
|
||||||
"label": "Screen",
|
"label": "Screen",
|
||||||
"breakpoints": "Screen sizes",
|
"breakpoints": "Screen sizes",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user