mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-04 06:57:47 +00:00
Add responsive condition for conditional card
This commit is contained in:
parent
8e1e42cd50
commit
0a4401b417
@ -1,10 +1,48 @@
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
|
||||
export interface Condition {
|
||||
export type Condition = StateCondition | ResponsiveCondition;
|
||||
|
||||
export type StateCondition = {
|
||||
condition: "state";
|
||||
entity: string;
|
||||
state?: string;
|
||||
state_not?: string;
|
||||
};
|
||||
|
||||
export type ResponsiveCondition = {
|
||||
condition: "responsive";
|
||||
min_width?: number;
|
||||
max_width?: number;
|
||||
};
|
||||
|
||||
function checkStateCondition(condition: StateCondition, hass: HomeAssistant) {
|
||||
const state = hass.states[condition.entity]
|
||||
? hass!.states[condition.entity].state
|
||||
: UNAVAILABLE;
|
||||
|
||||
return condition.state != null
|
||||
? state === condition.state
|
||||
: state !== condition.state_not;
|
||||
}
|
||||
|
||||
export function buildMediaQuery(condition: ResponsiveCondition) {
|
||||
const queries: string[] = [];
|
||||
if (condition.min_width != null) {
|
||||
queries.push(`(min-width: ${condition.min_width}px)`);
|
||||
}
|
||||
if (condition.max_width != null) {
|
||||
queries.push(`(max-width: ${condition.max_width}px)`);
|
||||
}
|
||||
return queries.join(" and ");
|
||||
}
|
||||
|
||||
function checkResponsiveCondition(
|
||||
condition: ResponsiveCondition,
|
||||
_hass: HomeAssistant
|
||||
) {
|
||||
const query = buildMediaQuery(condition);
|
||||
return matchMedia(query).matches;
|
||||
}
|
||||
|
||||
export function checkConditionsMet(
|
||||
@ -12,18 +50,30 @@ export function checkConditionsMet(
|
||||
hass: HomeAssistant
|
||||
): boolean {
|
||||
return conditions.every((c) => {
|
||||
const state = hass.states[c.entity]
|
||||
? hass!.states[c.entity].state
|
||||
: UNAVAILABLE;
|
||||
if (c.condition === "responsive") {
|
||||
return checkResponsiveCondition(c, hass);
|
||||
}
|
||||
|
||||
return c.state != null ? state === c.state : state !== c.state_not;
|
||||
return checkStateCondition(c, hass);
|
||||
});
|
||||
}
|
||||
|
||||
export function validateConditionalConfig(conditions: Condition[]): boolean {
|
||||
return conditions.every(
|
||||
(c) =>
|
||||
(c.entity &&
|
||||
(c.state != null || c.state_not != null)) as unknown as boolean
|
||||
);
|
||||
function valideStateCondition(condition: StateCondition) {
|
||||
return (condition.entity &&
|
||||
(condition.state != null ||
|
||||
condition.state_not != null)) as unknown as boolean;
|
||||
}
|
||||
|
||||
function valideResponsiveCondition(condition: ResponsiveCondition) {
|
||||
return (condition.min_width != null ||
|
||||
condition.max_width != null) as unknown as boolean;
|
||||
}
|
||||
|
||||
export function validateConditionalConfig(conditions: Condition[]): boolean {
|
||||
return conditions.every((c) => {
|
||||
if (c.condition === "responsive") {
|
||||
return valideResponsiveCondition(c);
|
||||
}
|
||||
return valideStateCondition(c);
|
||||
});
|
||||
}
|
||||
|
@ -3,11 +3,15 @@ import { customElement, property } from "lit/decorators";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { ConditionalCardConfig } from "../cards/types";
|
||||
import {
|
||||
ResponsiveCondition,
|
||||
buildMediaQuery,
|
||||
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";
|
||||
|
||||
@customElement("hui-conditional-base")
|
||||
export class HuiConditionalBase extends ReactiveElement {
|
||||
@ -21,6 +25,10 @@ export class HuiConditionalBase extends ReactiveElement {
|
||||
|
||||
protected _element?: LovelaceCard | LovelaceRow;
|
||||
|
||||
private _mediaQueriesListeners: Array<() => void> = [];
|
||||
|
||||
private _mediaQueries: string[] = [];
|
||||
|
||||
protected createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
@ -47,27 +55,82 @@ export class HuiConditionalBase extends ReactiveElement {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._clearMediaQueries();
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._listenMediaQueries();
|
||||
this._updateVisibility();
|
||||
}
|
||||
|
||||
private _clearMediaQueries() {
|
||||
this._mediaQueries = [];
|
||||
while (this._mediaQueriesListeners.length) {
|
||||
this._mediaQueriesListeners.pop()!();
|
||||
}
|
||||
}
|
||||
|
||||
private _listenMediaQueries() {
|
||||
if (!this._config) {
|
||||
return;
|
||||
}
|
||||
|
||||
const conditions = this._config.conditions.filter(
|
||||
(c) => c.condition === "responsive"
|
||||
) as ResponsiveCondition[];
|
||||
|
||||
const mediaQueries = conditions.map((c) => buildMediaQuery(c));
|
||||
|
||||
if (deepEqual(mediaQueries, this._mediaQueries)) return;
|
||||
|
||||
this._mediaQueries = mediaQueries;
|
||||
while (this._mediaQueriesListeners.length) {
|
||||
this._mediaQueriesListeners.pop()!();
|
||||
}
|
||||
mediaQueries.forEach((query) => {
|
||||
const listener = listenMediaQuery(query, () => {
|
||||
this._updateVisibility();
|
||||
});
|
||||
this._mediaQueriesListeners.push(listener);
|
||||
});
|
||||
}
|
||||
|
||||
protected update(changed: PropertyValues): void {
|
||||
super.update(changed);
|
||||
|
||||
if (
|
||||
changed.has("_element") ||
|
||||
changed.has("_config") ||
|
||||
changed.has("hass")
|
||||
) {
|
||||
this._listenMediaQueries();
|
||||
this._updateVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
private _updateVisibility() {
|
||||
if (!this._element || !this.hass || !this._config) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._element.editMode = this.editMode;
|
||||
this._element!.editMode = this.editMode;
|
||||
|
||||
const visible =
|
||||
this.editMode || checkConditionsMet(this._config.conditions, this.hass);
|
||||
this.editMode || checkConditionsMet(this._config!.conditions, this.hass!);
|
||||
this.hidden = !visible;
|
||||
|
||||
this.style.setProperty("display", visible ? "" : "none");
|
||||
|
||||
if (visible) {
|
||||
this._element.hass = this.hass;
|
||||
if (!this._element.parentElement) {
|
||||
this.appendChild(this._element);
|
||||
this._element!.hass = this.hass;
|
||||
if (!this._element!.parentElement) {
|
||||
this.appendChild(this._element!);
|
||||
}
|
||||
} else if (this._element.parentElement) {
|
||||
this.removeChild(this._element);
|
||||
} else if (this._element!.parentElement) {
|
||||
this.removeChild(this._element!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,9 +11,12 @@ import {
|
||||
array,
|
||||
assert,
|
||||
assign,
|
||||
literal,
|
||||
number,
|
||||
object,
|
||||
optional,
|
||||
string,
|
||||
union,
|
||||
} from "superstruct";
|
||||
import { storage } from "../../../../common/decorators/storage";
|
||||
import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
|
||||
@ -36,17 +39,28 @@ import type { ConfigChangedEvent } from "../hui-element-editor";
|
||||
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
|
||||
import type { GUIModeChangedEvent } from "../types";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
import { StateCondition } from "../../common/validate-condition";
|
||||
|
||||
const conditionStruct = object({
|
||||
const stateConditionStruct = object({
|
||||
condition: optional(literal("state")),
|
||||
entity: string(),
|
||||
state: optional(string()),
|
||||
state_not: optional(string()),
|
||||
});
|
||||
|
||||
const responsiveConditionStruct = object({
|
||||
condition: literal("responsive"),
|
||||
max_width: optional(number()),
|
||||
min_width: optional(number()),
|
||||
});
|
||||
|
||||
const cardConfigStruct = assign(
|
||||
baseLovelaceCardConfig,
|
||||
object({
|
||||
card: any(),
|
||||
conditions: optional(array(conditionStruct)),
|
||||
conditions: optional(
|
||||
array(union([stateConditionStruct, responsiveConditionStruct]))
|
||||
),
|
||||
})
|
||||
);
|
||||
|
||||
@ -163,8 +177,10 @@ export class HuiConditionalCardEditor
|
||||
${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.card.conditional.condition_explanation"
|
||||
)}
|
||||
${this._config.conditions.map(
|
||||
(cond, idx) => html`
|
||||
${this._config.conditions.map((cond, idx) => {
|
||||
if (cond.condition && cond.condition !== "state")
|
||||
return nothing;
|
||||
return html`
|
||||
<div class="condition">
|
||||
<div class="entity">
|
||||
<ha-entity-picker
|
||||
@ -214,8 +230,8 @@ export class HuiConditionalCardEditor
|
||||
></ha-textfield>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
`;
|
||||
})}
|
||||
<div class="condition">
|
||||
<ha-entity-picker
|
||||
.hass=${this.hass}
|
||||
@ -296,6 +312,7 @@ export class HuiConditionalCardEditor
|
||||
}
|
||||
const conditions = [...this._config.conditions];
|
||||
conditions.push({
|
||||
condition: "state",
|
||||
entity: target.value,
|
||||
state: "",
|
||||
});
|
||||
@ -313,7 +330,8 @@ export class HuiConditionalCardEditor
|
||||
if (target.configValue === "entity" && target.value === "") {
|
||||
conditions.splice(target.idx, 1);
|
||||
} else {
|
||||
const condition = { ...conditions[target.idx] };
|
||||
// Only state condition are supported by the editor
|
||||
const condition = { ...conditions[target.idx] } as StateCondition;
|
||||
if (target.configValue === "entity") {
|
||||
condition.entity = target.value;
|
||||
} else if (target.configValue === "state") {
|
||||
|
Loading…
x
Reference in New Issue
Block a user