mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-04 15:07:48 +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 { UNAVAILABLE } from "../../../data/entity";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
|
|
||||||
export interface Condition {
|
export type Condition = StateCondition | ResponsiveCondition;
|
||||||
|
|
||||||
|
export type StateCondition = {
|
||||||
|
condition: "state";
|
||||||
entity: string;
|
entity: string;
|
||||||
state?: string;
|
state?: string;
|
||||||
state_not?: 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(
|
export function checkConditionsMet(
|
||||||
@ -12,18 +50,30 @@ export function checkConditionsMet(
|
|||||||
hass: HomeAssistant
|
hass: HomeAssistant
|
||||||
): boolean {
|
): boolean {
|
||||||
return conditions.every((c) => {
|
return conditions.every((c) => {
|
||||||
const state = hass.states[c.entity]
|
if (c.condition === "responsive") {
|
||||||
? hass!.states[c.entity].state
|
return checkResponsiveCondition(c, hass);
|
||||||
: UNAVAILABLE;
|
}
|
||||||
|
|
||||||
return c.state != null ? state === c.state : state !== c.state_not;
|
return checkStateCondition(c, hass);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validateConditionalConfig(conditions: Condition[]): boolean {
|
function valideStateCondition(condition: StateCondition) {
|
||||||
return conditions.every(
|
return (condition.entity &&
|
||||||
(c) =>
|
(condition.state != null ||
|
||||||
(c.entity &&
|
condition.state_not != null)) as unknown as boolean;
|
||||||
(c.state != null || c.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 { HomeAssistant } from "../../../types";
|
||||||
import { ConditionalCardConfig } from "../cards/types";
|
import { ConditionalCardConfig } from "../cards/types";
|
||||||
import {
|
import {
|
||||||
|
ResponsiveCondition,
|
||||||
|
buildMediaQuery,
|
||||||
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";
|
||||||
|
|
||||||
@customElement("hui-conditional-base")
|
@customElement("hui-conditional-base")
|
||||||
export class HuiConditionalBase extends ReactiveElement {
|
export class HuiConditionalBase extends ReactiveElement {
|
||||||
@ -21,6 +25,10 @@ export class HuiConditionalBase extends ReactiveElement {
|
|||||||
|
|
||||||
protected _element?: LovelaceCard | LovelaceRow;
|
protected _element?: LovelaceCard | LovelaceRow;
|
||||||
|
|
||||||
|
private _mediaQueriesListeners: Array<() => void> = [];
|
||||||
|
|
||||||
|
private _mediaQueries: string[] = [];
|
||||||
|
|
||||||
protected createRenderRoot() {
|
protected createRenderRoot() {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -47,27 +55,82 @@ export class HuiConditionalBase extends ReactiveElement {
|
|||||||
this._config = config;
|
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 {
|
protected update(changed: PropertyValues): void {
|
||||||
super.update(changed);
|
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) {
|
if (!this._element || !this.hass || !this._config) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._element.editMode = this.editMode;
|
this._element!.editMode = this.editMode;
|
||||||
|
|
||||||
const visible =
|
const visible =
|
||||||
this.editMode || checkConditionsMet(this._config.conditions, this.hass);
|
this.editMode || checkConditionsMet(this._config!.conditions, this.hass!);
|
||||||
this.hidden = !visible;
|
this.hidden = !visible;
|
||||||
|
|
||||||
this.style.setProperty("display", visible ? "" : "none");
|
this.style.setProperty("display", visible ? "" : "none");
|
||||||
|
|
||||||
if (visible) {
|
if (visible) {
|
||||||
this._element.hass = this.hass;
|
this._element!.hass = this.hass;
|
||||||
if (!this._element.parentElement) {
|
if (!this._element!.parentElement) {
|
||||||
this.appendChild(this._element);
|
this.appendChild(this._element!);
|
||||||
}
|
}
|
||||||
} else if (this._element.parentElement) {
|
} else if (this._element!.parentElement) {
|
||||||
this.removeChild(this._element);
|
this.removeChild(this._element!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,9 +11,12 @@ import {
|
|||||||
array,
|
array,
|
||||||
assert,
|
assert,
|
||||||
assign,
|
assign,
|
||||||
|
literal,
|
||||||
|
number,
|
||||||
object,
|
object,
|
||||||
optional,
|
optional,
|
||||||
string,
|
string,
|
||||||
|
union,
|
||||||
} from "superstruct";
|
} from "superstruct";
|
||||||
import { storage } from "../../../../common/decorators/storage";
|
import { storage } from "../../../../common/decorators/storage";
|
||||||
import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
|
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 { baseLovelaceCardConfig } from "../structs/base-card-struct";
|
||||||
import type { GUIModeChangedEvent } from "../types";
|
import type { GUIModeChangedEvent } from "../types";
|
||||||
import { configElementStyle } from "./config-elements-style";
|
import { configElementStyle } from "./config-elements-style";
|
||||||
|
import { StateCondition } from "../../common/validate-condition";
|
||||||
|
|
||||||
const conditionStruct = object({
|
const stateConditionStruct = object({
|
||||||
|
condition: optional(literal("state")),
|
||||||
entity: string(),
|
entity: string(),
|
||||||
state: optional(string()),
|
state: optional(string()),
|
||||||
state_not: optional(string()),
|
state_not: optional(string()),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const responsiveConditionStruct = object({
|
||||||
|
condition: literal("responsive"),
|
||||||
|
max_width: optional(number()),
|
||||||
|
min_width: optional(number()),
|
||||||
|
});
|
||||||
|
|
||||||
const cardConfigStruct = assign(
|
const cardConfigStruct = assign(
|
||||||
baseLovelaceCardConfig,
|
baseLovelaceCardConfig,
|
||||||
object({
|
object({
|
||||||
card: any(),
|
card: any(),
|
||||||
conditions: optional(array(conditionStruct)),
|
conditions: optional(
|
||||||
|
array(union([stateConditionStruct, responsiveConditionStruct]))
|
||||||
|
),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -163,8 +177,10 @@ export class HuiConditionalCardEditor
|
|||||||
${this.hass!.localize(
|
${this.hass!.localize(
|
||||||
"ui.panel.lovelace.editor.card.conditional.condition_explanation"
|
"ui.panel.lovelace.editor.card.conditional.condition_explanation"
|
||||||
)}
|
)}
|
||||||
${this._config.conditions.map(
|
${this._config.conditions.map((cond, idx) => {
|
||||||
(cond, idx) => html`
|
if (cond.condition && cond.condition !== "state")
|
||||||
|
return nothing;
|
||||||
|
return html`
|
||||||
<div class="condition">
|
<div class="condition">
|
||||||
<div class="entity">
|
<div class="entity">
|
||||||
<ha-entity-picker
|
<ha-entity-picker
|
||||||
@ -214,8 +230,8 @@ export class HuiConditionalCardEditor
|
|||||||
></ha-textfield>
|
></ha-textfield>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`
|
`;
|
||||||
)}
|
})}
|
||||||
<div class="condition">
|
<div class="condition">
|
||||||
<ha-entity-picker
|
<ha-entity-picker
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@ -296,6 +312,7 @@ export class HuiConditionalCardEditor
|
|||||||
}
|
}
|
||||||
const conditions = [...this._config.conditions];
|
const conditions = [...this._config.conditions];
|
||||||
conditions.push({
|
conditions.push({
|
||||||
|
condition: "state",
|
||||||
entity: target.value,
|
entity: target.value,
|
||||||
state: "",
|
state: "",
|
||||||
});
|
});
|
||||||
@ -313,7 +330,8 @@ export class HuiConditionalCardEditor
|
|||||||
if (target.configValue === "entity" && target.value === "") {
|
if (target.configValue === "entity" && target.value === "") {
|
||||||
conditions.splice(target.idx, 1);
|
conditions.splice(target.idx, 1);
|
||||||
} else {
|
} 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") {
|
if (target.configValue === "entity") {
|
||||||
condition.entity = target.value;
|
condition.entity = target.value;
|
||||||
} else if (target.configValue === "state") {
|
} else if (target.configValue === "state") {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user