Move exclude entities config to area card (#25909)

This commit is contained in:
Paul Bottein 2025-06-25 16:18:47 +02:00 committed by GitHub
parent 3ab6a02994
commit af149dcfab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 77 additions and 38 deletions

View File

@ -18,6 +18,7 @@ import type { AreaRegistryEntry } from "../../../data/area_registry";
import { forwardHaptic } from "../../../data/haptics"; import { forwardHaptic } from "../../../data/haptics";
import { computeCssVariable } from "../../../resources/css-variables"; import { computeCssVariable } from "../../../resources/css-variables";
import type { HomeAssistant } from "../../../types"; import type { HomeAssistant } from "../../../types";
import type { AreaCardFeatureContext } from "../cards/hui-area-card";
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles"; import { cardFeatureStyles } from "./common/card-feature-styles";
import type { import type {
@ -86,8 +87,8 @@ export const supportsAreaControlsCardFeature = (
export const getAreaControlEntities = ( export const getAreaControlEntities = (
controls: AreaControl[], controls: AreaControl[],
areaId: string, areaId: string,
hass: HomeAssistant, excludeEntities: string[] | undefined,
excludeEntities: string[] = [] hass: HomeAssistant
): Record<AreaControl, string[]> => ): Record<AreaControl, string[]> =>
controls.reduce( controls.reduce(
(acc, control) => { (acc, control) => {
@ -99,7 +100,7 @@ export const getAreaControlEntities = (
}); });
acc[control] = Object.keys(hass.entities).filter( acc[control] = Object.keys(hass.entities).filter(
(entityId) => filter(entityId) && !excludeEntities.includes(entityId) (entityId) => filter(entityId) && !excludeEntities?.includes(entityId)
); );
return acc; return acc;
}, },
@ -115,7 +116,7 @@ class HuiAreaControlsCardFeature
{ {
@property({ attribute: false }) public hass?: HomeAssistant; @property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext; @property({ attribute: false }) public context?: AreaCardFeatureContext;
@property({ attribute: false }) @property({ attribute: false })
public position?: LovelaceCardFeaturePosition; public position?: LovelaceCardFeaturePosition;
@ -168,7 +169,7 @@ class HuiAreaControlsCardFeature
const controlEntities = this._controlEntities( const controlEntities = this._controlEntities(
this._controls, this._controls,
this.context.area_id, this.context.area_id,
this._config.exclude_entities, this.context.exclude_entities,
this.hass!.entities, this.hass!.entities,
this.hass!.devices, this.hass!.devices,
this.hass!.areas this.hass!.areas
@ -192,7 +193,7 @@ class HuiAreaControlsCardFeature
_entities: HomeAssistant["entities"], _entities: HomeAssistant["entities"],
_devices: HomeAssistant["devices"], _devices: HomeAssistant["devices"],
_areas: HomeAssistant["areas"] _areas: HomeAssistant["areas"]
) => getAreaControlEntities(controls, areaId, this.hass!, excludeEntities) ) => getAreaControlEntities(controls, areaId, excludeEntities, this.hass!)
); );
protected render() { protected render() {
@ -209,7 +210,7 @@ class HuiAreaControlsCardFeature
const controlEntities = this._controlEntities( const controlEntities = this._controlEntities(
this._controls, this._controls,
this.context.area_id!, this.context.area_id!,
this._config.exclude_entities, this.context.exclude_entities,
this.hass!.entities, this.hass!.entities,
this.hass!.devices, this.hass!.devices,
this.hass!.areas this.hass!.areas

View File

@ -179,7 +179,6 @@ export type AreaControl = (typeof AREA_CONTROLS)[number];
export interface AreaControlsCardFeatureConfig { export interface AreaControlsCardFeatureConfig {
type: "area-controls"; type: "area-controls";
controls?: AreaControl[]; controls?: AreaControl[];
exclude_entities?: string[];
} }
export type LovelaceCardFeaturePosition = "bottom" | "inline"; export type LovelaceCardFeaturePosition = "bottom" | "inline";

View File

@ -53,6 +53,10 @@ export const DEVICE_CLASSES = {
binary_sensor: ["motion", "moisture"], binary_sensor: ["motion", "moisture"],
}; };
export interface AreaCardFeatureContext extends LovelaceCardFeatureContext {
exclude_entities?: string[];
}
@customElement("hui-area-card") @customElement("hui-area-card")
export class HuiAreaCard extends LitElement implements LovelaceCard { export class HuiAreaCard extends LitElement implements LovelaceCard {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@ -61,7 +65,7 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
@state() private _config?: AreaCardConfig; @state() private _config?: AreaCardConfig;
@state() private _featureContext: LovelaceCardFeatureContext = {}; @state() private _featureContext: AreaCardFeatureContext = {};
private _ratio: { private _ratio: {
w: number; w: number;
@ -87,6 +91,7 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
this._featureContext = { this._featureContext = {
area_id: config.area, area_id: config.area,
exclude_entities: config.exclude_entities,
}; };
} }
@ -166,7 +171,8 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
( (
entities: HomeAssistant["entities"], entities: HomeAssistant["entities"],
areaId: string, areaId: string,
sensorClasses: string[] sensorClasses: string[],
excludeEntities?: string[]
): Map<string, string[]> => { ): Map<string, string[]> => {
const sensorFilter = generateEntityFilter(this.hass, { const sensorFilter = generateEntityFilter(this.hass, {
area: areaId, area: areaId,
@ -174,7 +180,10 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
domain: "sensor", domain: "sensor",
device_class: sensorClasses, device_class: sensorClasses,
}); });
const entityIds = Object.keys(entities).filter(sensorFilter); const entityIds = Object.keys(entities).filter(
(id) => sensorFilter(id) && !excludeEntities?.includes(id)
);
return this._groupEntitiesByDeviceClass(entityIds); return this._groupEntitiesByDeviceClass(entityIds);
} }
); );
@ -183,7 +192,8 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
( (
entities: HomeAssistant["entities"], entities: HomeAssistant["entities"],
areaId: string, areaId: string,
binarySensorClasses: string[] binarySensorClasses: string[],
excludeEntities?: string[]
): Map<string, string[]> => { ): Map<string, string[]> => {
const binarySensorFilter = generateEntityFilter(this.hass, { const binarySensorFilter = generateEntityFilter(this.hass, {
area: areaId, area: areaId,
@ -191,7 +201,11 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
domain: "binary_sensor", domain: "binary_sensor",
device_class: binarySensorClasses, device_class: binarySensorClasses,
}); });
const entityIds = Object.keys(entities).filter(binarySensorFilter);
const entityIds = Object.keys(entities).filter(
(id) => binarySensorFilter(id) && !excludeEntities?.includes(id)
);
return this._groupEntitiesByDeviceClass(entityIds); return this._groupEntitiesByDeviceClass(entityIds);
} }
); );
@ -215,13 +229,15 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
const areaId = this._config?.area; const areaId = this._config?.area;
const area = areaId ? this.hass.areas[areaId] : undefined; const area = areaId ? this.hass.areas[areaId] : undefined;
const alertClasses = this._config?.alert_classes; const alertClasses = this._config?.alert_classes;
const excludeEntities = this._config?.exclude_entities;
if (!area || !alertClasses) { if (!area || !alertClasses) {
return []; return [];
} }
const groupedEntities = this._groupedBinarySensorEntityIds( const groupedEntities = this._groupedBinarySensorEntityIds(
this.hass.entities, this.hass.entities,
area.area_id, area.area_id,
alertClasses alertClasses,
excludeEntities
); );
return ( return (
@ -286,6 +302,7 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
const areaId = this._config?.area; const areaId = this._config?.area;
const area = areaId ? this.hass.areas[areaId] : undefined; const area = areaId ? this.hass.areas[areaId] : undefined;
const sensorClasses = this._config?.sensor_classes; const sensorClasses = this._config?.sensor_classes;
const excludeEntities = this._config?.exclude_entities;
if (!area || !sensorClasses) { if (!area || !sensorClasses) {
return undefined; return undefined;
} }
@ -293,7 +310,8 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
const groupedEntities = this._groupedSensorEntityIds( const groupedEntities = this._groupedSensorEntityIds(
this.hass.entities, this.hass.entities,
area.area_id, area.area_id,
sensorClasses sensorClasses,
excludeEntities
); );
const sensorStates = sensorClasses const sensorStates = sensorClasses

View File

@ -117,6 +117,7 @@ export interface AreaCardConfig extends LovelaceCardConfig {
alert_classes?: string[]; alert_classes?: string[];
features?: LovelaceCardFeatureConfig[]; features?: LovelaceCardFeatureConfig[];
features_position?: LovelaceCardFeaturePosition; features_position?: LovelaceCardFeaturePosition;
exclude_entities?: string[];
} }
export interface ButtonCardConfig extends LovelaceCardConfig { export interface ButtonCardConfig extends LovelaceCardConfig {

View File

@ -32,7 +32,10 @@ import type {
LovelaceCardFeatureConfig, LovelaceCardFeatureConfig,
LovelaceCardFeatureContext, LovelaceCardFeatureContext,
} from "../../card-features/types"; } from "../../card-features/types";
import { DEVICE_CLASSES } from "../../cards/hui-area-card"; import {
DEVICE_CLASSES,
type AreaCardFeatureContext,
} from "../../cards/hui-area-card";
import type { AreaCardConfig } from "../../cards/types"; import type { AreaCardConfig } from "../../cards/types";
import type { LovelaceCardEditor } from "../../types"; import type { LovelaceCardEditor } from "../../types";
import { baseLovelaceCardConfig } from "../structs/base-card-struct"; import { baseLovelaceCardConfig } from "../structs/base-card-struct";
@ -55,6 +58,7 @@ const cardConfigStruct = assign(
features: optional(array(any())), features: optional(array(any())),
features_position: optional(enums(["bottom", "inline"])), features_position: optional(enums(["bottom", "inline"])),
aspect_ratio: optional(string()), aspect_ratio: optional(string()),
exclude_entities: optional(array(string())),
}) })
); );
@ -69,11 +73,7 @@ export class HuiAreaCardEditor
@state() private _numericDeviceClasses?: string[]; @state() private _numericDeviceClasses?: string[];
private _featureContext = memoizeOne( @state() private _featureContext: AreaCardFeatureContext = {};
(areaId?: string): LovelaceCardFeatureContext => ({
area_id: areaId,
})
);
private _schema = memoizeOne( private _schema = memoizeOne(
( (
@ -174,7 +174,10 @@ export class HuiAreaCardEditor
); );
private _binaryClassesForArea = memoizeOne( private _binaryClassesForArea = memoizeOne(
(area: string | undefined): string[] => { (
area: string | undefined,
excludeEntities: string[] | undefined
): string[] => {
if (!area) { if (!area) {
return []; return [];
} }
@ -186,7 +189,9 @@ export class HuiAreaCardEditor
}); });
const classes = Object.keys(this.hass!.entities) const classes = Object.keys(this.hass!.entities)
.filter(binarySensorFilter) .filter(
(id) => binarySensorFilter(id) && !excludeEntities?.includes(id)
)
.map((id) => this.hass!.states[id]?.attributes.device_class) .map((id) => this.hass!.states[id]?.attributes.device_class)
.filter((c): c is string => Boolean(c)); .filter((c): c is string => Boolean(c));
@ -195,7 +200,11 @@ export class HuiAreaCardEditor
); );
private _sensorClassesForArea = memoizeOne( private _sensorClassesForArea = memoizeOne(
(area: string | undefined, numericDeviceClasses?: string[]): string[] => { (
area: string | undefined,
excludeEntities: string[] | undefined,
numericDeviceClasses: string[] | undefined
): string[] => {
if (!area) { if (!area) {
return []; return [];
} }
@ -208,7 +217,7 @@ export class HuiAreaCardEditor
}); });
const classes = Object.keys(this.hass!.entities) const classes = Object.keys(this.hass!.entities)
.filter(sensorFilter) .filter((id) => sensorFilter(id) && !excludeEntities?.includes(id))
.map((id) => this.hass!.states[id]?.attributes.device_class) .map((id) => this.hass!.states[id]?.attributes.device_class)
.filter((c): c is string => Boolean(c)); .filter((c): c is string => Boolean(c));
@ -257,6 +266,11 @@ export class HuiAreaCardEditor
display_type: displayType, display_type: displayType,
}; };
delete this._config.show_camera; delete this._config.show_camera;
this._featureContext = {
area_id: config.area,
exclude_entities: config.exclude_entities,
};
} }
protected async updated() { protected async updated() {
@ -306,11 +320,13 @@ export class HuiAreaCardEditor
return nothing; return nothing;
} }
const areaId = this._config!.area; const possibleBinaryClasses = this._binaryClassesForArea(
this._config.area,
const possibleBinaryClasses = this._binaryClassesForArea(this._config.area); this._config.exclude_entities
);
const possibleSensorClasses = this._sensorClassesForArea( const possibleSensorClasses = this._sensorClassesForArea(
this._config.area, this._config.area,
this._config.exclude_entities,
this._numericDeviceClasses this._numericDeviceClasses
); );
const binarySelectOptions = this._buildBinaryOptions( const binarySelectOptions = this._buildBinaryOptions(
@ -347,8 +363,9 @@ export class HuiAreaCardEditor
...this._config, ...this._config,
}; };
const featureContext = this._featureContext(areaId); const hasCompatibleFeatures = this._hasCompatibleFeatures(
const hasCompatibleFeatures = this._hasCompatibleFeatures(featureContext); this._featureContext
);
return html` return html`
<ha-form <ha-form
@ -381,7 +398,7 @@ export class HuiAreaCardEditor
: nothing} : nothing}
<hui-card-features-editor <hui-card-features-editor
.hass=${this.hass} .hass=${this.hass}
.context=${featureContext} .context=${this._featureContext}
.features=${this._config!.features ?? []} .features=${this._config!.features ?? []}
@features-changed=${this._featuresChanged} @features-changed=${this._featuresChanged}
@edit-detail-element=${this._editDetailElement} @edit-detail-element=${this._editDetailElement}
@ -428,12 +445,11 @@ export class HuiAreaCardEditor
private _editDetailElement(ev: HASSDomEvent<EditDetailElementEvent>): void { private _editDetailElement(ev: HASSDomEvent<EditDetailElementEvent>): void {
const index = ev.detail.subElementConfig.index; const index = ev.detail.subElementConfig.index;
const config = this._config!.features![index!]; const config = this._config!.features![index!];
const featureContext = this._featureContext(this._config!.area);
fireEvent(this, "edit-sub-element", { fireEvent(this, "edit-sub-element", {
config: config, config: config,
saveConfig: (newConfig) => this._updateFeature(index!, newConfig), saveConfig: (newConfig) => this._updateFeature(index!, newConfig),
context: featureContext, context: this._featureContext,
type: "feature", type: "feature",
} as EditSubElementEvent< } as EditSubElementEvent<
LovelaceCardFeatureConfig, LovelaceCardFeatureConfig,

View File

@ -17,8 +17,8 @@ import {
AREA_CONTROLS, AREA_CONTROLS,
type AreaControl, type AreaControl,
type AreaControlsCardFeatureConfig, type AreaControlsCardFeatureConfig,
type LovelaceCardFeatureContext,
} from "../../card-features/types"; } from "../../card-features/types";
import type { AreaCardFeatureContext } from "../../cards/hui-area-card";
import type { LovelaceCardFeatureEditor } from "../../types"; import type { LovelaceCardFeatureEditor } from "../../types";
type AreaControlsCardFeatureData = AreaControlsCardFeatureConfig & { type AreaControlsCardFeatureData = AreaControlsCardFeatureConfig & {
@ -32,7 +32,7 @@ export class HuiAreaControlsCardFeatureEditor
{ {
@property({ attribute: false }) public hass?: HomeAssistant; @property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext; @property({ attribute: false }) public context?: AreaCardFeatureContext;
@state() private _config?: AreaControlsCardFeatureConfig; @state() private _config?: AreaControlsCardFeatureConfig;
@ -78,6 +78,7 @@ export class HuiAreaControlsCardFeatureEditor
private _supportedControls = memoizeOne( private _supportedControls = memoizeOne(
( (
areaId: string, areaId: string,
excludeEntities: string[] | undefined,
// needed to update memoized function when entities, devices or areas change // needed to update memoized function when entities, devices or areas change
_entities: HomeAssistant["entities"], _entities: HomeAssistant["entities"],
_devices: HomeAssistant["devices"], _devices: HomeAssistant["devices"],
@ -89,6 +90,7 @@ export class HuiAreaControlsCardFeatureEditor
const controlEntities = getAreaControlEntities( const controlEntities = getAreaControlEntities(
AREA_CONTROLS as unknown as AreaControl[], AREA_CONTROLS as unknown as AreaControl[],
areaId, areaId,
excludeEntities,
this.hass! this.hass!
); );
return ( return (
@ -104,6 +106,7 @@ export class HuiAreaControlsCardFeatureEditor
const supportedControls = this._supportedControls( const supportedControls = this._supportedControls(
this.context.area_id, this.context.area_id,
this.context.exclude_entities,
this.hass.entities, this.hass.entities,
this.hass.devices, this.hass.devices,
this.hass.areas this.hass.areas
@ -148,6 +151,7 @@ export class HuiAreaControlsCardFeatureEditor
if (customize_controls && !config.controls) { if (customize_controls && !config.controls) {
config.controls = this._supportedControls( config.controls = this._supportedControls(
this.context!.area_id!, this.context!.area_id!,
this.context!.exclude_entities,
this.hass!.entities, this.hass!.entities,
this.hass!.devices, this.hass!.devices,
this.hass!.areas this.hass!.areas

View File

@ -83,8 +83,8 @@ export class AreasOverviewViewStrategy extends ReactiveElement {
const controlEntities = getAreaControlEntities( const controlEntities = getAreaControlEntities(
controls, controls,
area.area_id, area.area_id,
hass, hiddenEntities,
hiddenEntities hass
); );
const filteredControls = controls.filter( const filteredControls = controls.filter(
@ -105,12 +105,12 @@ export class AreasOverviewViewStrategy extends ReactiveElement {
"occupancy", "occupancy",
"presence", "presence",
], ],
exclude_entities: hiddenEntities,
features: filteredControls.length features: filteredControls.length
? [ ? [
{ {
type: "area-controls", type: "area-controls",
controls: filteredControls, controls: filteredControls,
exclude_entities: hiddenEntities,
}, },
] ]
: [], : [],