Add visibility option to heading entities (#22064)

* Add visibility option to heading entity

* Fix types
This commit is contained in:
Paul Bottein 2024-09-24 17:12:04 +02:00 committed by GitHub
parent c4a700a55c
commit c30e4a6935
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 144 additions and 7 deletions

View File

@ -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<typeof this>): 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);

View File

@ -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 {
<div class="entities">
${this._config.entities.map(
(config) => html`
<hui-heading-entity .config=${config} .hass=${this.hass}>
<hui-heading-entity
.config=${config}
.hass=${this.hass}
.preview=${this.preview}
>
</hui-heading-entity>
`
)}

View File

@ -508,6 +508,7 @@ export interface HeadingEntityConfig {
content?: string | string[];
icon?: string;
tap_action?: ActionConfig;
visibility?: Condition[];
}
export interface HeadingCardConfig extends LovelaceCardConfig {

View File

@ -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`
<ha-form
.hass=${this.hass}
@ -87,6 +102,27 @@ export class HuiHeadingEntityEditor
.computeLabel=${this._computeLabelCallback}
@value-changed=${this._valueChanged}
></ha-form>
<ha-expansion-panel outlined>
<h3 slot="header">
<ha-svg-icon .path=${mdiEye}></ha-svg-icon>
${this.hass!.localize(
"ui.panel.lovelace.editor.card.heading.entity_config.visibility"
)}
</h3>
<div class="content">
<p class="intro">
${this.hass.localize(
"ui.panel.lovelace.editor.card.heading.entity_config.visibility_explanation"
)}
</p>
<ha-card-conditions-editor
.hass=${this.hass}
.conditions=${conditions}
@value-changed=${this._conditionChanged}
>
</ha-card-conditions-editor>
</div>
</ha-expansion-panel>
`;
}
@ -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<ReturnType<typeof this._schema>>
) => {
@ -128,6 +183,11 @@ export class HuiHeadingEntityEditor
display: block;
margin-bottom: 24px;
}
.intro {
margin: 0;
color: var(--secondary-text-color);
margin-bottom: 8px;
}
`,
];
}

View File

@ -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"
},