Skip to content

Commit

Permalink
Add visibility option to sections (conditional section) (#20805)
Browse files Browse the repository at this point in the history
* Add first version of section visibility option

* Move visibility logic into view

* Simplify section view structure

* Don't add hidden section to dom

* Move visilibity logic to hui-section

* Setup section editor

* Add visibility view

* Add basic settings editor

* Improve visibility editor

* Update conditional base

* Feedbacks

* Better typings
  • Loading branch information
piitaya committed May 21, 2024
1 parent 4cc5d2d commit a2a8950
Show file tree
Hide file tree
Showing 19 changed files with 747 additions and 171 deletions.
4 changes: 3 additions & 1 deletion src/common/dom/media_query.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export type MediaQueriesListener = () => void;

/**
* Attach a media query. Listener is called right away and when it matches.
* @param mediaQuery media query to match.
Expand All @@ -7,7 +9,7 @@
export const listenMediaQuery = (
mediaQuery: string,
matchesChanged: (matches: boolean) => void
) => {
): MediaQueriesListener => {
const mql = matchMedia(mediaQuery);
const listener = (e) => matchesChanged(e.matches);
mql.addListener(listener);
Expand Down
45 changes: 26 additions & 19 deletions src/components/chart/ha-chart-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,31 +313,38 @@ export class HaChartBase extends LitElement {
`;
}

private _loading = false;

private async _setupChart() {
if (this._loading) return;
const ctx: CanvasRenderingContext2D = this.renderRoot
.querySelector("canvas")!
.getContext("2d")!;
this._loading = true;
try {
const ChartConstructor = (await import("../../resources/chartjs")).Chart;

const ChartConstructor = (await import("../../resources/chartjs")).Chart;

const computedStyles = getComputedStyle(this);
const computedStyles = getComputedStyle(this);

ChartConstructor.defaults.borderColor =
computedStyles.getPropertyValue("--divider-color");
ChartConstructor.defaults.color = computedStyles.getPropertyValue(
"--secondary-text-color"
);
ChartConstructor.defaults.font.family =
computedStyles.getPropertyValue("--mdc-typography-body1-font-family") ||
computedStyles.getPropertyValue("--mdc-typography-font-family") ||
"Roboto, Noto, sans-serif";

this.chart = new ChartConstructor(ctx, {
type: this.chartType,
data: this.data,
options: this._createOptions(),
plugins: this._createPlugins(),
});
ChartConstructor.defaults.borderColor =
computedStyles.getPropertyValue("--divider-color");
ChartConstructor.defaults.color = computedStyles.getPropertyValue(
"--secondary-text-color"
);
ChartConstructor.defaults.font.family =
computedStyles.getPropertyValue("--mdc-typography-body1-font-family") ||
computedStyles.getPropertyValue("--mdc-typography-font-family") ||
"Roboto, Noto, sans-serif";

this.chart = new ChartConstructor(ctx, {
type: this.chartType,
data: this.data,
options: this._createOptions(),
plugins: this._createPlugins(),
});
} finally {
this._loading = false;
}
}

private _createOptions() {
Expand Down
14 changes: 11 additions & 3 deletions src/components/map/ha-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,16 +178,24 @@ export class HaMap extends ReactiveElement {
map!.classList.toggle("forced-light", this.themeMode === "light");
}

private _loading = false;

private async _loadMap(): Promise<void> {
if (this._loading) return;
let map = this.shadowRoot!.getElementById("map");
if (!map) {
map = document.createElement("div");
map.id = "map";
this.shadowRoot!.append(map);
}
[this.leafletMap, this.Leaflet] = await setupLeafletMap(map);
this._updateMapStyle();
this._loaded = true;
this._loading = true;
try {
[this.leafletMap, this.Leaflet] = await setupLeafletMap(map);
this._updateMapStyle();
this._loaded = true;
} finally {
this._loading = false;
}
}

public fitMap(options?: { zoom?: number; pad?: number }): void {
Expand Down
2 changes: 2 additions & 0 deletions src/data/lovelace/config/section.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import type { Condition } from "../../../panels/lovelace/common/validate-condition";
import type { LovelaceCardConfig } from "./card";
import type { LovelaceStrategyConfig } from "./strategy";

export interface LovelaceBaseSectionConfig {
title?: string;
visibility?: Condition[];
}

export interface LovelaceSectionConfig extends LovelaceBaseSectionConfig {
Expand Down
46 changes: 46 additions & 0 deletions src/panels/lovelace/common/validate-condition.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { ensureArray } from "../../../common/array/ensure-array";
import {
MediaQueriesListener,
listenMediaQuery,
} from "../../../common/dom/media_query";
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
import { UNAVAILABLE } from "../../../data/entity";
import { HomeAssistant } from "../../../types";
Expand Down Expand Up @@ -308,3 +312,45 @@ export function addEntityToCondition(
}
return condition;
}

export function extractMediaQueries(conditions: Condition[]): string[] {
return conditions.reduce<string[]>((array, c) => {
if ("conditions" in c && c.conditions) {
array.push(...extractMediaQueries(c.conditions));
}
if (c.condition === "screen" && c.media_query) {
array.push(c.media_query);
}
return array;
}, []);
}

export function attachConditionMediaQueriesListeners(
conditions: Condition[],
hass: HomeAssistant,
onChange: (visibility: boolean) => void
): MediaQueriesListener[] {
// For performance, if there is only one condition and it's a screen condition, set the visibility directly
if (
conditions.length === 1 &&
conditions[0].condition === "screen" &&
conditions[0].media_query
) {
const listener = listenMediaQuery(conditions[0].media_query, (matches) => {
onChange(matches);
});
return [listener];
}

const mediaQueries = extractMediaQueries(conditions);

const listeners = mediaQueries.map((query) => {
const listener = listenMediaQuery(query, () => {
const visibility = checkConditionsMet(conditions, hass);
onChange(visibility);
});
return listener;
});

return listeners;
}
61 changes: 19 additions & 42 deletions src/panels/lovelace/components/hui-conditional-base.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,19 @@
import { PropertyValues, ReactiveElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { listenMediaQuery } from "../../../common/dom/media_query";
import { MediaQueriesListener } from "../../../common/dom/media_query";
import { deepEqual } from "../../../common/util/deep-equal";
import { HomeAssistant } from "../../../types";
import { ConditionalCardConfig } from "../cards/types";
import {
Condition,
LegacyCondition,
checkConditionsMet,
attachConditionMediaQueriesListeners,
extractMediaQueries,
validateConditionalConfig,
} from "../common/validate-condition";
import { ConditionalRowConfig, LovelaceRow } from "../entity-rows/types";
import { LovelaceCard } from "../types";

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 {
@property({ attribute: false }) public hass?: HomeAssistant;
Expand All @@ -37,7 +24,7 @@ export class HuiConditionalBase extends ReactiveElement {

protected _element?: LovelaceCard | LovelaceRow;

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

private _mediaQueries: string[] = [];

Expand Down Expand Up @@ -79,41 +66,31 @@ export class HuiConditionalBase extends ReactiveElement {
}

private _clearMediaQueries() {
this._mediaQueries = [];
while (this._mediaQueriesListeners.length) {
this._mediaQueriesListeners.pop()!();
}
this._listeners.forEach((unsub) => unsub());
this._listeners = [];
}

private _listenMediaQueries() {
if (!this._config) {
if (!this._config || !this.hass) {
return;
}

const mediaQueries = extractMediaQueries(this._config.conditions);
const supportedConditions = this._config.conditions.filter(
(c) => "condition" in c
) as Condition[];
const mediaQueries = extractMediaQueries(supportedConditions);

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

this._mediaQueries = mediaQueries;
while (this._mediaQueriesListeners.length) {
this._mediaQueriesListeners.pop()!();
}
this._clearMediaQueries();

mediaQueries.forEach((query) => {
const listener = listenMediaQuery(query, (matches) => {
// 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;
}
this._updateVisibility();
});
this._mediaQueriesListeners.push(listener);
});
this._listeners = attachConditionMediaQueriesListeners(
supportedConditions,
this.hass,
(visibility) => {
this._setVisibility(visibility);
}
);
}

protected update(changed: PropertyValues): void {
Expand Down
16 changes: 9 additions & 7 deletions src/panels/lovelace/editor/card-editor/hui-dialog-create-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ import {
computeCards,
computeSection,
} from "../../common/generate-lovelace-config";
import {
findLovelaceContainer,
parseLovelaceContainerPath,
} from "../lovelace-path";
import "./hui-card-picker";
import "./hui-entity-picker-table";
import { CreateCardDialogParams } from "./show-create-card-dialog";
import { showEditCardDialog } from "./show-edit-card-dialog";
import { showSuggestCardDialog } from "./show-suggest-card-dialog";
import {
findLovelaceContainer,
parseLovelaceContainerPath,
} from "../lovelace-path";

declare global {
interface HASSDomEvents {
Expand Down Expand Up @@ -274,15 +274,17 @@ export class HuiCreateDialogCard

let sectionOptions: Partial<LovelaceSectionConfig> = {};

const { sectionIndex } = parseLovelaceContainerPath(this._params!.path);
const { viewIndex, sectionIndex } = parseLovelaceContainerPath(
this._params!.path
);
const isSection = sectionIndex !== undefined;

// If we are in a section, we want to keep the section options for the preview
if (isSection) {
const containerConfig = findLovelaceContainer(
this._params!.lovelaceConfig!,
this._params!.path!
) as LovelaceSectionConfig;
[viewIndex, sectionIndex]
);
if (!isStrategySection(containerConfig)) {
const { cards, title, ...rest } = containerConfig;
sectionOptions = rest;
Expand Down
28 changes: 3 additions & 25 deletions src/panels/lovelace/editor/conditions/ha-card-conditions-editor.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,8 @@
import { mdiPlus } from "@mdi/js";
import {
CSSResultGroup,
LitElement,
PropertyValues,
css,
html,
nothing,
} from "lit";
import { CSSResultGroup, LitElement, PropertyValues, css, html } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import { stopPropagation } from "../../../../common/dom/stop_propagation";
import "../../../../components/ha-alert";
import "../../../../components/ha-button";
import "../../../../components/ha-list-item";
import type { HaSelect } from "../../../../components/ha-select";
Expand All @@ -21,12 +13,12 @@ import { Condition, LegacyCondition } from "../../common/validate-condition";
import "./ha-card-condition-editor";
import type { HaCardConditionEditor } from "./ha-card-condition-editor";
import { LovelaceConditionEditorConstructor } from "./types";
import "./types/ha-card-condition-and";
import "./types/ha-card-condition-numeric_state";
import "./types/ha-card-condition-or";
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",
Expand All @@ -46,8 +38,6 @@ export class HaCardConditionsEditor extends LitElement {
| LegacyCondition
)[];

@property({ type: Boolean }) public nested = false;

private _focusLastConditionOnChange = false;

protected firstUpdated() {
Expand Down Expand Up @@ -83,15 +73,6 @@ export class HaCardConditionsEditor extends LitElement {
protected render() {
return html`
<div class="conditions">
${!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
Expand Down Expand Up @@ -172,9 +153,6 @@ export class HaCardConditionsEditor extends LitElement {
static get styles(): CSSResultGroup {
return [
css`
mwc-tab-bar {
border-bottom: 1px solid var(--divider-color);
}
ha-alert {
display: block;
margin-top: 12px;
Expand Down

0 comments on commit a2a8950

Please sign in to comment.