275 lines
7.3 KiB
TypeScript
275 lines
7.3 KiB
TypeScript
import { PropertyValues, ReactiveElement } from "lit";
|
|
import { customElement, property, state } from "lit/decorators";
|
|
import { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
|
import { HomeAssistant } from "../../../types";
|
|
import { computeCardSize } from "../common/compute-card-size";
|
|
import { evaluateStateFilter } from "../common/evaluate-filter";
|
|
import { findEntities } from "../common/find-entities";
|
|
import { processConfigEntities } from "../common/process-config-entities";
|
|
import {
|
|
addEntityToCondition,
|
|
checkConditionsMet,
|
|
extractConditionEntityIds,
|
|
} from "../common/validate-condition";
|
|
import { createCardElement } from "../create-element/create-card-element";
|
|
import { EntityFilterEntityConfig } from "../entity-rows/types";
|
|
import { LovelaceCard } from "../types";
|
|
import { EntityFilterCardConfig } from "./types";
|
|
|
|
@customElement("hui-entity-filter-card")
|
|
export class HuiEntityFilterCard
|
|
extends ReactiveElement
|
|
implements LovelaceCard
|
|
{
|
|
public static getStubConfig(
|
|
hass: HomeAssistant,
|
|
entities: string[],
|
|
entitiesFallback: string[]
|
|
): EntityFilterCardConfig {
|
|
const maxEntities = 3;
|
|
const foundEntities = findEntities(
|
|
hass,
|
|
maxEntities,
|
|
entities,
|
|
entitiesFallback,
|
|
["light", "switch", "sensor"]
|
|
);
|
|
|
|
return {
|
|
type: "entity-filter",
|
|
entities: foundEntities,
|
|
conditions: foundEntities[0]
|
|
? [
|
|
{
|
|
condition: "state",
|
|
state: hass.states[foundEntities[0]].state,
|
|
},
|
|
]
|
|
: [],
|
|
card: { type: "entities" },
|
|
};
|
|
}
|
|
|
|
@property({ attribute: false }) public hass?: HomeAssistant;
|
|
|
|
@property({ type: Boolean }) public isPanel = false;
|
|
|
|
@property({ type: Boolean }) public editMode = false;
|
|
|
|
@state() private _config?: EntityFilterCardConfig;
|
|
|
|
private _element?: LovelaceCard;
|
|
|
|
private _configEntities?: EntityFilterEntityConfig[];
|
|
|
|
private _baseCardConfig?: LovelaceCardConfig;
|
|
|
|
private _oldEntities?: EntityFilterEntityConfig[];
|
|
|
|
public getCardSize(): number | Promise<number> {
|
|
return this._element ? computeCardSize(this._element) : 1;
|
|
}
|
|
|
|
public setConfig(config: EntityFilterCardConfig): void {
|
|
if (
|
|
!config.entities ||
|
|
!config.entities.length ||
|
|
!Array.isArray(config.entities)
|
|
) {
|
|
throw new Error("Entities must be specified");
|
|
}
|
|
|
|
if (
|
|
!(
|
|
(config.conditions && Array.isArray(config.conditions)) ||
|
|
(config.state_filter && Array.isArray(config.state_filter))
|
|
) &&
|
|
!config.entities.every(
|
|
(entity) =>
|
|
typeof entity === "object" &&
|
|
entity.state_filter &&
|
|
Array.isArray(entity.state_filter)
|
|
)
|
|
) {
|
|
throw new Error("Incorrect filter config");
|
|
}
|
|
|
|
this._configEntities = processConfigEntities(config.entities);
|
|
this._config = config;
|
|
this._baseCardConfig = {
|
|
type: "entities",
|
|
entities: [],
|
|
...this._config.card,
|
|
};
|
|
|
|
if (this.lastChild) {
|
|
this.removeChild(this.lastChild);
|
|
}
|
|
|
|
this._element = this._createCardElement(this._baseCardConfig);
|
|
}
|
|
|
|
protected createRenderRoot() {
|
|
return this;
|
|
}
|
|
|
|
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
|
if (this._element) {
|
|
this._element.hass = this.hass;
|
|
this._element.editMode = this.editMode;
|
|
this._element.isPanel = this.isPanel;
|
|
}
|
|
|
|
if (changedProps.has("_config")) {
|
|
return true;
|
|
}
|
|
if (changedProps.has("hass")) {
|
|
return this._haveEntitiesChanged(
|
|
changedProps.get("hass") as HomeAssistant | null
|
|
);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
protected update(changedProps: PropertyValues) {
|
|
super.update(changedProps);
|
|
if (
|
|
!this.hass ||
|
|
!this._config ||
|
|
!this._configEntities ||
|
|
!this._element
|
|
) {
|
|
return;
|
|
}
|
|
|
|
const entitiesList = this._configEntities.filter((entityConf) => {
|
|
const stateObj = this.hass!.states[entityConf.entity];
|
|
if (!stateObj) return false;
|
|
|
|
const conditions = entityConf.conditions ?? this._config!.conditions;
|
|
if (conditions) {
|
|
const conditionWithEntity = conditions.map((condition) =>
|
|
addEntityToCondition(condition, entityConf.entity)
|
|
);
|
|
return checkConditionsMet(conditionWithEntity, this.hass!);
|
|
}
|
|
|
|
const filters = entityConf.state_filter ?? this._config!.state_filter;
|
|
if (filters) {
|
|
return filters.some((filter) => evaluateStateFilter(stateObj, filter));
|
|
}
|
|
|
|
return false;
|
|
});
|
|
|
|
if (entitiesList.length === 0 && this._config.show_empty === false) {
|
|
this.style.display = "none";
|
|
this.toggleAttribute("hidden", true);
|
|
return;
|
|
}
|
|
|
|
if (!this.lastChild) {
|
|
this._element.setConfig({
|
|
...this._baseCardConfig!,
|
|
entities: entitiesList,
|
|
});
|
|
this._oldEntities = entitiesList;
|
|
} else if (this._element.tagName !== "HUI-ERROR-CARD") {
|
|
const isSame =
|
|
this._oldEntities &&
|
|
entitiesList.length === this._oldEntities.length &&
|
|
entitiesList.every((entity, idx) => entity === this._oldEntities![idx]);
|
|
|
|
if (!isSame) {
|
|
this._oldEntities = entitiesList;
|
|
this._element.setConfig({
|
|
...this._baseCardConfig!,
|
|
entities: entitiesList,
|
|
});
|
|
}
|
|
}
|
|
|
|
// Attach element if it has never been attached.
|
|
if (!this.lastChild) {
|
|
this.appendChild(this._element);
|
|
}
|
|
|
|
this.style.display = "block";
|
|
this.toggleAttribute("hidden", false);
|
|
}
|
|
|
|
private _haveEntitiesChanged(oldHass: HomeAssistant | null): boolean {
|
|
if (!this.hass || !oldHass) {
|
|
return true;
|
|
}
|
|
|
|
if (!this._configEntities) {
|
|
return true;
|
|
}
|
|
|
|
if (this.hass.localize !== oldHass.localize) {
|
|
return true;
|
|
}
|
|
|
|
for (const config of this._configEntities) {
|
|
if (this.hass.states[config.entity] !== oldHass.states[config.entity]) {
|
|
return true;
|
|
}
|
|
if (config.conditions) {
|
|
const entityIds = extractConditionEntityIds(config.conditions);
|
|
for (const entityId of entityIds) {
|
|
if (this.hass.states[entityId] !== oldHass.states[entityId]) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this._config?.conditions) {
|
|
const entityIds = extractConditionEntityIds(this._config?.conditions);
|
|
for (const entityId of entityIds) {
|
|
if (this.hass.states[entityId] !== oldHass.states[entityId]) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private _createCardElement(cardConfig: LovelaceCardConfig) {
|
|
const element = createCardElement(cardConfig) as LovelaceCard;
|
|
if (this.hass) {
|
|
element.hass = this.hass;
|
|
}
|
|
element.isPanel = this.isPanel;
|
|
element.editMode = this.editMode;
|
|
element.addEventListener(
|
|
"ll-rebuild",
|
|
(ev) => {
|
|
ev.stopPropagation();
|
|
this._rebuildCard(element, cardConfig);
|
|
},
|
|
{ once: true }
|
|
);
|
|
return element;
|
|
}
|
|
|
|
private _rebuildCard(
|
|
cardElToReplace: LovelaceCard,
|
|
config: LovelaceCardConfig
|
|
): void {
|
|
const newCardEl = this._createCardElement(config);
|
|
if (cardElToReplace.parentElement) {
|
|
cardElToReplace.parentElement!.replaceChild(newCardEl, cardElToReplace);
|
|
}
|
|
this._element = newCardEl;
|
|
}
|
|
}
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
"hui-entity-filter-card": HuiEntityFilterCard;
|
|
}
|
|
}
|