Add entity filter section strategy

pull/24607/head
Paul Bottein 2025-03-12 18:14:39 +01:00
parent 91e9836423
commit 99cd67c857
No known key found for this signature in database
5 changed files with 166 additions and 8 deletions

View File

@ -5,6 +5,7 @@ import type { LovelaceStrategyConfig } from "./strategy";
export interface LovelaceBaseSectionConfig {
visibility?: Condition[];
column_span?: number;
hidden?: boolean;
row_span?: number;
/**
* @deprecated Use heading card instead.

View File

@ -80,7 +80,7 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
const cardsConfig = this._config?.cards ?? [];
const editMode = Boolean(this.lovelace?.editMode && !this.isStrategy);
const editMode = Boolean(this.lovelace?.editMode);
const sortableOptions = this.importOnly
? IMPORT_MODE_CARD_SORTABLE_OPTIONS
@ -88,7 +88,7 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
return html`
<ha-sortable
.disabled=${!editMode}
.disabled=${!(editMode && !this.isStrategy)}
@drag-start=${this._dragStart}
@drag-end=${this._dragEnd}
draggable-selector=".card"
@ -103,6 +103,7 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
class="container ${classMap({
"edit-mode": editMode,
"import-only": this.importOnly,
"is-strategy": this.isStrategy,
})}"
>
${repeat(
@ -133,7 +134,7 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
})}"
.sortableData=${cardPath}
>
${editMode
${editMode && !this.isStrategy
? html`
<hui-card-edit-mode
.hass=${this.hass}
@ -151,7 +152,7 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
`;
}
)}
${editMode && !this.importOnly
${editMode && !this.importOnly && !this.isStrategy
? html`
<button
class="add"
@ -246,6 +247,10 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
min-height: var(--row-height);
}
.container.edit-mode.is-strategy {
border-style: solid;
}
.container.import-only {
border: none;
padding: 0 !important;

View File

@ -55,6 +55,8 @@ export class HuiSection extends ReactiveElement {
@state() private _cards: HuiCard[] = [];
@state() private _config?: LovelaceSectionConfig;
private _layoutElementType?: string;
private _layoutElement?: LovelaceSectionElement;
@ -195,6 +197,8 @@ export class HuiSection extends ReactiveElement {
type: sectionConfig.type || DEFAULT_SECTION_LAYOUT,
};
this._config = sectionConfig;
// Create a new layout element if necessary.
let addLayoutElement = false;
@ -223,14 +227,16 @@ export class HuiSection extends ReactiveElement {
}
private _updateElement(forceVisible?: boolean) {
if (!this._layoutElement) {
if (!this._layoutElement || !this._config) {
return;
}
const visible =
forceVisible ||
this.preview ||
!this.config.visibility ||
checkConditionsMet(this.config.visibility, this.hass);
(!this._config.hidden &&
(!this.config.visibility ||
checkConditionsMet(this.config.visibility, this.hass)));
if (this.hidden !== !visible) {
this.style.setProperty("display", visible ? "" : "none");

View File

@ -0,0 +1,143 @@
import { ReactiveElement } from "lit";
import { customElement } from "lit/decorators";
import {
generateEntityFilter,
type EntityFilter,
} from "../../../../common/entity/entity_filter";
import type { TileCardConfig, HeadingCardConfig } from "../../cards/types";
import type { HomeAssistant } from "../../../../types";
import type { LovelaceSectionConfig } from "../../../../data/lovelace/config/section";
import type { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
import { ensureArray } from "../../../../common/array/ensure-array";
interface EntityFilterConfig {
title?: string;
icon?: string;
filter?: EntityFilter | EntityFilter[];
include_entities?: string[];
exclude_entities?: string[];
order?: string[];
}
export type EntitiesFilterSectionStrategyConfig = EntityFilterConfig & {
type: "entities-filter";
groups?: EntityFilterConfig[];
};
const getEntities = (hass: HomeAssistant, config: EntityFilterConfig) => {
let entitiesIds =
config.filter || config.exclude_entities ? Object.keys(hass.states) : [];
if (config.exclude_entities) {
entitiesIds = entitiesIds.filter(
(entityId) => !config.exclude_entities!.includes(entityId)
);
}
if (config.filter) {
const filters = ensureArray(config.filter);
const entityFilters = filters.map((filter) =>
generateEntityFilter(hass, filter)
);
entitiesIds = entitiesIds.filter((entityId) =>
entityFilters.some((filter) => filter(entityId))
);
}
if (config.include_entities) {
entitiesIds.push(...config.include_entities);
}
if (config.order) {
entitiesIds.sort((a, b) => {
const aIndex = config.order!.indexOf(a);
const bIndex = config.order!.indexOf(b);
if (aIndex === -1 && bIndex === -1) return 0;
if (aIndex === -1) return 1;
if (bIndex === -1) return -1;
return aIndex - bIndex;
});
}
return entitiesIds;
};
@customElement("entities-filter-section-strategy")
export class EntitiesFilterSectionStrategy extends ReactiveElement {
static async generate(
config: EntitiesFilterSectionStrategyConfig,
hass: HomeAssistant
): Promise<LovelaceSectionConfig> {
const cards: LovelaceCardConfig[] = [];
let isEmpty = true;
if (config.title) {
const headingCard: HeadingCardConfig = {
type: "heading",
heading: config.title,
heading_style: "title",
icon: config.icon,
};
cards.push(headingCard);
}
const entities = getEntities(hass, config);
if (entities.length > 0) {
isEmpty = false;
for (const entityId of entities) {
const tileCard: TileCardConfig = {
type: "tile",
entity: entityId,
};
cards.push(tileCard);
}
}
if (config.groups) {
for (const group of config.groups) {
const groupEntities = getEntities(hass, group);
if (groupEntities.length > 0) {
isEmpty = false;
if (group.title) {
cards.push({
type: "heading",
heading: group.title,
heading_style: "subtitle",
icon: group.icon,
});
}
for (const entityId of groupEntities) {
const tileCard: TileCardConfig = {
type: "tile",
entity: entityId,
};
cards.push(tileCard);
}
}
}
}
if (isEmpty) {
cards.push({
type: "markdown",
content: "No entities found.",
});
}
return {
type: "grid",
cards: cards,
hidden: isEmpty,
};
}
}
declare global {
interface HTMLElementTagNameMap {
"entities-filter-section-strategy": EntitiesFilterSectionStrategy;
}
}

View File

@ -35,7 +35,10 @@ const STRATEGIES: Record<LovelaceStrategyConfigType, Record<string, any>> = {
area: () => import("./area/area-view-strategy"),
areas: () => import("./areas/areas-view-strategy"),
},
section: {},
section: {
"entities-filter": () =>
import("./entities-filter/entities-filter-section-strategy"),
},
};
export type LovelaceStrategyConfigType = "dashboard" | "view" | "section";