Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/common/array/ensure-array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ type NonUndefined<T> = T extends undefined ? never : T;

export function ensureArray(value: undefined): undefined;
export function ensureArray<T>(value: T | T[]): NonUndefined<T>[];
export function ensureArray<T>(value: T | readonly T[]): NonUndefined<T>[];
export function ensureArray(value) {
if (value === undefined || Array.isArray(value)) {
return value;
Expand Down
31 changes: 29 additions & 2 deletions src/components/device/ha-device-picker.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import "@material/mwc-list/mwc-list-item";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
import { customElement, property, query, state } from "lit/decorators";
Expand Down Expand Up @@ -37,6 +37,8 @@ export type HaDevicePickerDeviceFilterFunc = (
device: DeviceRegistryEntry
) => boolean;

export type HaDevicePickerEntityFilterFunc = (entity: HassEntity) => boolean;

const rowRenderer: ComboBoxLitRenderer<Device> = (item) => html`<mwc-list-item
.twoline=${!!item.area}
>
Expand Down Expand Up @@ -94,6 +96,8 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {

@property() public deviceFilter?: HaDevicePickerDeviceFilterFunc;

@property() public entityFilter?: HaDevicePickerEntityFilterFunc;

@property({ type: Boolean }) public disabled?: boolean;

@property({ type: Boolean }) public required?: boolean;
Expand All @@ -113,6 +117,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
excludeDomains: this["excludeDomains"],
includeDeviceClasses: this["includeDeviceClasses"],
deviceFilter: this["deviceFilter"],
entityFilter: this["entityFilter"],
excludeDevices: this["excludeDevices"]
): Device[] => {
if (!devices.length) {
Expand All @@ -127,7 +132,12 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {

const deviceEntityLookup: DeviceEntityLookup = {};

if (includeDomains || excludeDomains || includeDeviceClasses) {
if (
includeDomains ||
excludeDomains ||
includeDeviceClasses ||
entityFilter
) {
for (const entity of entities) {
if (!entity.device_id) {
continue;
Expand Down Expand Up @@ -198,6 +208,22 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
});
}

if (entityFilter) {
inputDevices = inputDevices.filter((device) => {
const devEntities = deviceEntityLookup[device.id];
if (!devEntities || !devEntities.length) {
return false;
}
return deviceEntityLookup[device.id].some((entity) => {
const stateObj = this.hass.states[entity.entity_id];
if (!stateObj) {
return false;
}
return entityFilter(stateObj);
});
});
}

if (deviceFilter) {
inputDevices = inputDevices.filter(
(device) =>
Expand Down Expand Up @@ -274,6 +300,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
this.excludeDomains,
this.includeDeviceClasses,
this.deviceFilter,
this.entityFilter,
this.excludeDevices
);
}
Expand Down
42 changes: 28 additions & 14 deletions src/components/ha-area-picker.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import "@material/mwc-list/mwc-list-item";
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
import { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
Expand Down Expand Up @@ -83,7 +85,7 @@ export class HaAreaPicker extends LitElement {

@property() public deviceFilter?: HaDevicePickerDeviceFilterFunc;

@property() public entityFilter?: (entity: EntityRegistryEntry) => boolean;
@property() public entityFilter?: (entity: HassEntity) => boolean;

@property({ type: Boolean }) public disabled?: boolean;

Expand Down Expand Up @@ -135,7 +137,12 @@ export class HaAreaPicker extends LitElement {
let inputDevices: DeviceRegistryEntry[] | undefined;
let inputEntities: EntityRegistryEntry[] | undefined;

if (includeDomains || excludeDomains || includeDeviceClasses) {
if (
includeDomains ||
excludeDomains ||
includeDeviceClasses ||
entityFilter
) {
for (const entity of entities) {
if (!entity.device_id) {
continue;
Expand All @@ -145,16 +152,9 @@ export class HaAreaPicker extends LitElement {
}
deviceEntityLookup[entity.device_id].push(entity);
}
inputDevices = devices;
inputEntities = entities.filter((entity) => entity.area_id);
} else {
if (deviceFilter) {
inputDevices = devices;
}
if (entityFilter) {
inputEntities = entities.filter((entity) => entity.area_id);
}
}
inputDevices = devices;
inputEntities = entities.filter((entity) => entity.area_id);

if (includeDomains) {
inputDevices = inputDevices!.filter((device) => {
Expand Down Expand Up @@ -218,9 +218,23 @@ export class HaAreaPicker extends LitElement {
}

if (entityFilter) {
inputEntities = inputEntities!.filter((entity) =>
entityFilter!(entity)
);
inputDevices = inputDevices!.filter((device) => {
const devEntities = deviceEntityLookup[device.id];
if (!devEntities || !devEntities.length) {
return false;
}
return deviceEntityLookup[device.id].some((entity) => {
const stateObj = this.hass.states[entity.entity_id];
if (!stateObj) {
return false;
}
return entityFilter(stateObj);
});
});
inputEntities = inputEntities!.filter((entity) => {
const stateObj = this.hass.states[entity.entity_id];
return entityFilter!(stateObj);
});
}

let outputAreas = areas;
Expand Down
4 changes: 2 additions & 2 deletions src/components/ha-areas-picker.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { HassEntity } from "home-assistant-js-websocket";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import type { EntityRegistryEntry } from "../data/entity_registry";
import { SubscribeMixin } from "../mixins/subscribe-mixin";
import type { HomeAssistant } from "../types";
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
Expand Down Expand Up @@ -48,7 +48,7 @@ export class HaAreasPicker extends SubscribeMixin(LitElement) {

@property() public deviceFilter?: HaDevicePickerDeviceFilterFunc;

@property() public entityFilter?: (entity: EntityRegistryEntry) => boolean;
@property() public entityFilter?: (entity: HassEntity) => boolean;

@property({ attribute: "picked-area-label" })
public pickedAreaLabel?: string;
Expand Down
33 changes: 18 additions & 15 deletions src/components/ha-selector/ha-selector-area.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { ensureArray } from "../../common/array/ensure-array";
import type { DeviceRegistryEntry } from "../../data/device_registry";
import { getDeviceIntegrationLookup } from "../../data/device_registry";
import {
Expand Down Expand Up @@ -52,11 +53,21 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
];
}

private _hasIntegration(selector: AreaSelector) {
return (
(selector.area?.entity &&
ensureArray(selector.area.entity).some(
(filter) => filter.integration
)) ||
(selector.area?.device &&
ensureArray(selector.area.device).some((device) => device.integration))
);
}

protected updated(changedProperties: PropertyValues): void {
if (
changedProperties.has("selector") &&
(this.selector.area?.device?.integration ||
this.selector.area?.entity?.integration) &&
this._hasIntegration(this.selector) &&
!this._entitySources
) {
fetchEntitySourcesWithCache(this.hass).then((sources) => {
Expand All @@ -66,11 +77,7 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
}

protected render(): TemplateResult {
if (
(this.selector.area?.device?.integration ||
this.selector.area?.entity?.integration) &&
!this._entitySources
) {
if (this._hasIntegration(this.selector) && !this._entitySources) {
return html``;
}

Expand Down Expand Up @@ -110,10 +117,8 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
return true;
}

return filterSelectorEntities(
this.selector.area.entity,
entity,
this._entitySources
return ensureArray(this.selector.area.entity).some((filter) =>
filterSelectorEntities(filter, entity, this._entitySources)
);
};

Expand All @@ -127,10 +132,8 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
? this._deviceIntegrationLookup(this._entitySources, this._entities)
: undefined;

return filterSelectorDevices(
this.selector.area.device,
device,
deviceIntegrations
return ensureArray(this.selector.area.device).some((filter) =>
filterSelectorDevices(filter, device, deviceIntegrations)
);
};
}
Expand Down
56 changes: 35 additions & 21 deletions src/components/ha-selector/ha-selector-device.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { ensureArray } from "../../common/array/ensure-array";
import type { DeviceRegistryEntry } from "../../data/device_registry";
import { getDeviceIntegrationLookup } from "../../data/device_registry";
import {
Expand All @@ -13,7 +14,10 @@ import {
fetchEntitySourcesWithCache,
} from "../../data/entity_sources";
import type { DeviceSelector } from "../../data/selector";
import { filterSelectorDevices } from "../../data/selector";
import {
filterSelectorDevices,
filterSelectorEntities,
} from "../../data/selector";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import type { HomeAssistant } from "../../types";
import "../device/ha-device-picker";
Expand Down Expand Up @@ -49,11 +53,24 @@ export class HaDeviceSelector extends SubscribeMixin(LitElement) {
];
}

private _hasIntegration(selector: DeviceSelector) {
return (
(selector.device?.filter &&
ensureArray(selector.device.filter).some(
(filter) => filter.integration
)) ||
(selector.device?.entity &&
ensureArray(selector.device.entity).some(
(device) => device.integration
))
);
}

protected updated(changedProperties): void {
super.updated(changedProperties);
if (
changedProperties.has("selector") &&
this.selector.device?.integration &&
this._hasIntegration(this.selector) &&
!this._entitySources
) {
fetchEntitySourcesWithCache(this.hass).then((sources) => {
Expand All @@ -63,7 +80,7 @@ export class HaDeviceSelector extends SubscribeMixin(LitElement) {
}

protected render() {
if (this.selector.device?.integration && !this._entitySources) {
if (this._hasIntegration(this.selector) && !this._entitySources) {
return html``;
}

Expand All @@ -75,12 +92,7 @@ export class HaDeviceSelector extends SubscribeMixin(LitElement) {
.label=${this.label}
.helper=${this.helper}
.deviceFilter=${this._filterDevices}
.includeDeviceClasses=${this.selector.device?.entity?.device_class
? [this.selector.device.entity.device_class]
: undefined}
.includeDomains=${this.selector.device?.entity?.domain
? [this.selector.device.entity.domain]
: undefined}
.entityFilter=${this._filterEntities}
.disabled=${this.disabled}
.required=${this.required}
allow-custom-entity
Expand All @@ -95,31 +107,33 @@ export class HaDeviceSelector extends SubscribeMixin(LitElement) {
.value=${this.value}
.helper=${this.helper}
.deviceFilter=${this._filterDevices}
.includeDeviceClasses=${this.selector.device.entity?.device_class
? [this.selector.device.entity.device_class]
: undefined}
.includeDomains=${this.selector.device.entity?.domain
? [this.selector.device.entity.domain]
: undefined}
.entityFilter=${this._filterEntities}
.disabled=${this.disabled}
.required=${this.required}
></ha-devices-picker>
`;
}

private _filterDevices = (device: DeviceRegistryEntry): boolean => {
if (!this.selector.device?.filter) {
return true;
}
const deviceIntegrations =
this._entitySources && this._entities
? this._deviceIntegrationLookup(this._entitySources, this._entities)
: undefined;

if (!this.selector.device) {
return ensureArray(this.selector.device.filter).some((filter) =>
filterSelectorDevices(filter, device, deviceIntegrations)
);
};

private _filterEntities = (entity: HassEntity): boolean => {
if (!this.selector.device?.entity) {
return true;
}
return filterSelectorDevices(
this.selector.device,
device,
deviceIntegrations
return ensureArray(this.selector.device.entity).some((filter) =>
filterSelectorEntities(filter, entity, this._entitySources)
);
};
}
Expand Down
Loading