From c30e4a693514f935790853ecfc82ab68ee7eb5f6 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Tue, 24 Sep 2024 17:12:04 +0200 Subject: [PATCH] Add visibility option to heading entities (#22064) * Add visibility option to heading entity * Fix types --- .../cards/heading/hui-heading-entity.ts | 70 ++++++++++++++++++- src/panels/lovelace/cards/hui-heading-card.ts | 8 ++- src/panels/lovelace/cards/types.ts | 1 + .../hui-heading-entity-editor.ts | 68 ++++++++++++++++-- src/translations/en.json | 4 +- 5 files changed, 144 insertions(+), 7 deletions(-) diff --git a/src/panels/lovelace/cards/heading/hui-heading-entity.ts b/src/panels/lovelace/cards/heading/hui-heading-entity.ts index f18e0c3481..f64ae58448 100644 --- a/src/panels/lovelace/cards/heading/hui-heading-entity.ts +++ b/src/panels/lovelace/cards/heading/hui-heading-entity.ts @@ -1,7 +1,15 @@ -import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; +import { + CSSResultGroup, + LitElement, + PropertyValues, + css, + html, + nothing, +} from "lit"; import { customElement, property } from "lit/decorators"; import { ifDefined } from "lit/directives/if-defined"; import memoizeOne from "memoize-one"; +import { MediaQueriesListener } from "../../../../common/dom/media_query"; import "../../../../components/ha-card"; import "../../../../components/ha-icon"; import "../../../../components/ha-icon-next"; @@ -12,6 +20,10 @@ import { HomeAssistant } from "../../../../types"; import { actionHandler } from "../../common/directives/action-handler-directive"; import { handleAction } from "../../common/handle-action"; import { hasAction } from "../../common/has-action"; +import { + attachConditionMediaQueriesListeners, + checkConditionsMet, +} from "../../common/validate-condition"; import type { HeadingEntityConfig } from "../types"; @customElement("hui-heading-entity") @@ -20,6 +32,10 @@ export class HuiHeadingEntity extends LitElement { @property({ attribute: false }) public config!: HeadingEntityConfig | string; + @property({ type: Boolean }) public preview = false; + + private _listeners: MediaQueriesListener[] = []; + private _handleAction(ev: ActionHandlerEvent) { const config: HeadingEntityConfig = { tap_action: { @@ -46,6 +62,58 @@ export class HuiHeadingEntity extends LitElement { } ); + public disconnectedCallback() { + super.disconnectedCallback(); + this._clearMediaQueries(); + } + + public connectedCallback() { + super.connectedCallback(); + this._listenMediaQueries(); + this._updateVisibility(); + } + + protected update(changedProps: PropertyValues): void { + super.update(changedProps); + if (changedProps.has("hass") || changedProps.has("preview")) { + this._updateVisibility(); + } + } + + private _updateVisibility(forceVisible?: boolean) { + const config = this._config(this.config); + const visible = + forceVisible || + this.preview || + !config.visibility || + checkConditionsMet(config.visibility, this.hass); + this.toggleAttribute("hidden", !visible); + } + + private _clearMediaQueries() { + this._listeners.forEach((unsub) => unsub()); + this._listeners = []; + } + + private _listenMediaQueries() { + const config = this._config(this.config); + if (!config?.visibility) { + return; + } + const conditions = config.visibility; + const hasOnlyMediaQuery = + conditions.length === 1 && + conditions[0].condition === "screen" && + !!conditions[0].media_query; + + this._listeners = attachConditionMediaQueriesListeners( + config.visibility, + (matches) => { + this._updateVisibility(hasOnlyMediaQuery && matches); + } + ); + } + protected render() { const config = this._config(this.config); diff --git a/src/panels/lovelace/cards/hui-heading-card.ts b/src/panels/lovelace/cards/hui-heading-card.ts index 4e3b569d98..63bad9a61f 100644 --- a/src/panels/lovelace/cards/hui-heading-card.ts +++ b/src/panels/lovelace/cards/hui-heading-card.ts @@ -36,6 +36,8 @@ export class HuiHeadingCard extends LitElement implements LovelaceCard { @property({ attribute: false }) public hass?: HomeAssistant; + @property({ type: Boolean }) public preview = false; + @state() private _config?: HeadingCardConfig; public setConfig(config: HeadingCardConfig): void { @@ -94,7 +96,11 @@ export class HuiHeadingCard extends LitElement implements LovelaceCard {
${this._config.entities.map( (config) => html` - + ` )} diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index a6e41ffd98..ca844ae039 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -508,6 +508,7 @@ export interface HeadingEntityConfig { content?: string | string[]; icon?: string; tap_action?: ActionConfig; + visibility?: Condition[]; } export interface HeadingCardConfig extends LovelaceCardConfig { diff --git a/src/panels/lovelace/editor/heading-entity/hui-heading-entity-editor.ts b/src/panels/lovelace/editor/heading-entity/hui-heading-entity-editor.ts index 7d6f63a3c2..e331227463 100644 --- a/src/panels/lovelace/editor/heading-entity/hui-heading-entity-editor.ts +++ b/src/panels/lovelace/editor/heading-entity/hui-heading-entity-editor.ts @@ -1,17 +1,28 @@ -import { mdiGestureTap } from "@mdi/js"; +import { mdiEye, mdiGestureTap } from "@mdi/js"; import { css, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; -import { array, assert, object, optional, string, union } from "superstruct"; +import { + any, + array, + assert, + object, + optional, + string, + union, +} from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; +import "../../../../components/ha-expansion-panel"; import "../../../../components/ha-form/ha-form"; import type { HaFormSchema, SchemaUnion, } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; -import type { HeadingCardConfig, HeadingEntityConfig } from "../../cards/types"; +import type { HeadingEntityConfig } from "../../cards/types"; +import { Condition } from "../../common/validate-condition"; import type { LovelaceGenericElementEditor } from "../../types"; +import "../conditions/ha-card-conditions-editor"; import { configElementStyle } from "../config-elements/config-elements-style"; import { actionConfigStruct } from "../structs/action-struct"; @@ -20,6 +31,7 @@ const entityConfigStruct = object({ content: optional(union([string(), array(string())])), icon: optional(string()), tap_action: optional(actionConfigStruct), + visibility: optional(array(any())), }); @customElement("hui-heading-entity-editor") @@ -29,6 +41,8 @@ export class HuiHeadingEntityEditor { @property({ attribute: false }) public hass?: HomeAssistant; + @property({ type: Boolean }) public preview = false; + @state() private _config?: HeadingEntityConfig; public setConfig(config: HeadingEntityConfig): void { @@ -79,6 +93,7 @@ export class HuiHeadingEntityEditor const schema = this._schema(); + const conditions = this._config.visibility ?? []; return html` + +

+ + ${this.hass!.localize( + "ui.panel.lovelace.editor.card.heading.entity_config.visibility" + )} +

+
+

+ ${this.hass.localize( + "ui.panel.lovelace.editor.card.heading.entity_config.visibility_explanation" + )} +

+ + +
+
`; } @@ -96,11 +132,30 @@ export class HuiHeadingEntityEditor return; } - const config = ev.detail.value as HeadingCardConfig; + const config = ev.detail.value as HeadingEntityConfig; fireEvent(this, "config-changed", { config }); } + private _conditionChanged(ev: CustomEvent): void { + ev.stopPropagation(); + if (!this._config || !this.hass) { + return; + } + + const conditions = ev.detail.value as Condition[]; + + const newConfig: HeadingEntityConfig = { + ...this._config, + visibility: conditions, + }; + if (newConfig.visibility?.length === 0) { + delete newConfig.visibility; + } + + fireEvent(this, "config-changed", { config: newConfig }); + } + private _computeLabelCallback = ( schema: SchemaUnion> ) => { @@ -128,6 +183,11 @@ export class HuiHeadingEntityEditor display: block; margin-bottom: 24px; } + .intro { + margin: 0; + color: var(--secondary-text-color); + margin-bottom: 8px; + } `, ]; } diff --git a/src/translations/en.json b/src/translations/en.json index 31b311ba27..46af0984bf 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -6007,7 +6007,9 @@ }, "entities": "Entities", "entity_config": { - "content": "Content" + "content": "Content", + "visibility": "Visibility", + "visibility_explanation": "The entity will be shown when ALL conditions below are fulfilled. If no conditions are set, the entity will always be shown." }, "default_heading": "Kitchen" },