Skip to content

Commit

Permalink
Add screen condition to conditional card. (#18041)
Browse files Browse the repository at this point in the history
  • Loading branch information
piitaya authored Oct 9, 2023
1 parent 5a6d6dc commit 86c014b
Show file tree
Hide file tree
Showing 10 changed files with 831 additions and 145 deletions.
9 changes: 9 additions & 0 deletions src/common/array/combinations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export function getAllCombinations<T>(arr: T[]) {
return arr.reduce<T[][]>(
(combinations, element) =>
combinations.concat(
combinations.map((combination) => [...combination, element])
),
[[]]
);
}
7 changes: 7 additions & 0 deletions src/panels/lovelace/common/icon-condition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { mdiResponsive, mdiStateMachine } from "@mdi/js";
import { Condition } from "./validate-condition";

export const ICON_CONDITION: Record<Condition["condition"], string> = {
state: mdiStateMachine,
screen: mdiResponsive,
};
68 changes: 57 additions & 11 deletions src/panels/lovelace/common/validate-condition.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,75 @@
import { UNAVAILABLE } from "../../../data/entity";
import { HomeAssistant } from "../../../types";

export interface Condition {
entity: string;
export type Condition = StateCondition | ScreenCondition;

export type LegacyCondition = {
entity?: string;
state?: string;
state_not?: string;
};

export type StateCondition = {
condition: "state";
entity?: string;
state?: string;
state_not?: string;
};

export type ScreenCondition = {
condition: "screen";
media_query?: string;
};

function checkStateCondition(condition: StateCondition, hass: HomeAssistant) {
const state =
condition.entity && hass.states[condition.entity]
? hass.states[condition.entity].state
: UNAVAILABLE;

return condition.state != null
? state === condition.state
: state !== condition.state_not;
}

function checkScreenCondition(
condition: ScreenCondition,
_hass: HomeAssistant
) {
return condition.media_query
? matchMedia(condition.media_query).matches
: false;
}

export function checkConditionsMet(
conditions: Condition[],
hass: HomeAssistant
): boolean {
return conditions.every((c) => {
const state = hass.states[c.entity]
? hass!.states[c.entity].state
: UNAVAILABLE;
if (c.condition === "screen") {
return checkScreenCondition(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 != null &&
(condition.state != null || condition.state_not != null)
);
}

function validateScreenCondition(condition: ScreenCondition) {
return condition.media_query != null;
}

export function validateConditionalConfig(conditions: Condition[]): boolean {
return conditions.every((c) => {
if (c.condition === "screen") {
return validateScreenCondition(c);
}
return valideStateCondition(c);
});
}
90 changes: 84 additions & 6 deletions src/panels/lovelace/components/hui-conditional-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ import { customElement, property } from "lit/decorators";
import { HomeAssistant } from "../../../types";
import { ConditionalCardConfig } from "../cards/types";
import {
ScreenCondition,
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 {
Expand All @@ -21,6 +24,10 @@ export class HuiConditionalBase extends ReactiveElement {

protected _element?: LovelaceCard | LovelaceRow;

private _mediaQueriesListeners: Array<() => void> = [];

private _mediaQueries: string[] = [];

protected createRenderRoot() {
return this;
}
Expand All @@ -47,27 +54,98 @@ 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 === "screen"
) as ScreenCondition[];

const mediaQueries = conditions
.filter((c) => c.media_query)
.map((c) => c.media_query as string);

if (deepEqual(mediaQueries, this._mediaQueries)) return;

this._mediaQueries = mediaQueries;
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) {
this._setVisibility(matches);
return;
}
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;

const visible =
this.editMode || checkConditionsMet(this._config.conditions, this.hass);
this.hidden = !visible;
const conditionMet = checkConditionsMet(
this._config!.conditions,
this.hass!
);
this._setVisibility(conditionMet);
}

private _setVisibility(conditionMet: boolean) {
if (!this._element || !this.hass) {
return;
}
const visible = this.editMode || conditionMet;
this.hidden = !visible;
this.style.setProperty("display", visible ? "" : "none");

if (visible) {
this._element.hass = this.hass;
if (!this._element.parentElement) {
this.appendChild(this._element);
if (!this._element!.parentElement) {
this.appendChild(this._element!);
}
} else if (this._element.parentElement) {
this.removeChild(this._element);
this.removeChild(this._element!);
}
}
}
Expand Down
Loading

0 comments on commit 86c014b

Please sign in to comment.