mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-27 03:06:41 +00:00
Add filter options to entity and device selectors (#15302)
This commit is contained in:
parent
cf377558ae
commit
c4160e8368
@ -2,6 +2,7 @@ type NonUndefined<T> = T extends undefined ? never : T;
|
|||||||
|
|
||||||
export function ensureArray(value: undefined): undefined;
|
export function ensureArray(value: undefined): undefined;
|
||||||
export function ensureArray<T>(value: T | T[]): NonUndefined<T>[];
|
export function ensureArray<T>(value: T | T[]): NonUndefined<T>[];
|
||||||
|
export function ensureArray<T>(value: T | readonly T[]): NonUndefined<T>[];
|
||||||
export function ensureArray(value) {
|
export function ensureArray(value) {
|
||||||
if (value === undefined || Array.isArray(value)) {
|
if (value === undefined || Array.isArray(value)) {
|
||||||
return value;
|
return value;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import "@material/mwc-list/mwc-list-item";
|
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 { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
@ -37,6 +37,8 @@ export type HaDevicePickerDeviceFilterFunc = (
|
|||||||
device: DeviceRegistryEntry
|
device: DeviceRegistryEntry
|
||||||
) => boolean;
|
) => boolean;
|
||||||
|
|
||||||
|
export type HaDevicePickerEntityFilterFunc = (entity: HassEntity) => boolean;
|
||||||
|
|
||||||
const rowRenderer: ComboBoxLitRenderer<Device> = (item) => html`<mwc-list-item
|
const rowRenderer: ComboBoxLitRenderer<Device> = (item) => html`<mwc-list-item
|
||||||
.twoline=${!!item.area}
|
.twoline=${!!item.area}
|
||||||
>
|
>
|
||||||
@ -94,6 +96,8 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@property() public deviceFilter?: HaDevicePickerDeviceFilterFunc;
|
@property() public deviceFilter?: HaDevicePickerDeviceFilterFunc;
|
||||||
|
|
||||||
|
@property() public entityFilter?: HaDevicePickerEntityFilterFunc;
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled?: boolean;
|
@property({ type: Boolean }) public disabled?: boolean;
|
||||||
|
|
||||||
@property({ type: Boolean }) public required?: boolean;
|
@property({ type: Boolean }) public required?: boolean;
|
||||||
@ -113,6 +117,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
|||||||
excludeDomains: this["excludeDomains"],
|
excludeDomains: this["excludeDomains"],
|
||||||
includeDeviceClasses: this["includeDeviceClasses"],
|
includeDeviceClasses: this["includeDeviceClasses"],
|
||||||
deviceFilter: this["deviceFilter"],
|
deviceFilter: this["deviceFilter"],
|
||||||
|
entityFilter: this["entityFilter"],
|
||||||
excludeDevices: this["excludeDevices"]
|
excludeDevices: this["excludeDevices"]
|
||||||
): Device[] => {
|
): Device[] => {
|
||||||
if (!devices.length) {
|
if (!devices.length) {
|
||||||
@ -127,7 +132,12 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
const deviceEntityLookup: DeviceEntityLookup = {};
|
const deviceEntityLookup: DeviceEntityLookup = {};
|
||||||
|
|
||||||
if (includeDomains || excludeDomains || includeDeviceClasses) {
|
if (
|
||||||
|
includeDomains ||
|
||||||
|
excludeDomains ||
|
||||||
|
includeDeviceClasses ||
|
||||||
|
entityFilter
|
||||||
|
) {
|
||||||
for (const entity of entities) {
|
for (const entity of entities) {
|
||||||
if (!entity.device_id) {
|
if (!entity.device_id) {
|
||||||
continue;
|
continue;
|
||||||
@ -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) {
|
if (deviceFilter) {
|
||||||
inputDevices = inputDevices.filter(
|
inputDevices = inputDevices.filter(
|
||||||
(device) =>
|
(device) =>
|
||||||
@ -274,6 +300,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
|||||||
this.excludeDomains,
|
this.excludeDomains,
|
||||||
this.includeDeviceClasses,
|
this.includeDeviceClasses,
|
||||||
this.deviceFilter,
|
this.deviceFilter,
|
||||||
|
this.entityFilter,
|
||||||
this.excludeDevices
|
this.excludeDevices
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||||
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
@ -83,7 +85,7 @@ export class HaAreaPicker extends LitElement {
|
|||||||
|
|
||||||
@property() public deviceFilter?: HaDevicePickerDeviceFilterFunc;
|
@property() public deviceFilter?: HaDevicePickerDeviceFilterFunc;
|
||||||
|
|
||||||
@property() public entityFilter?: (entity: EntityRegistryEntry) => boolean;
|
@property() public entityFilter?: (entity: HassEntity) => boolean;
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled?: boolean;
|
@property({ type: Boolean }) public disabled?: boolean;
|
||||||
|
|
||||||
@ -135,7 +137,12 @@ export class HaAreaPicker extends LitElement {
|
|||||||
let inputDevices: DeviceRegistryEntry[] | undefined;
|
let inputDevices: DeviceRegistryEntry[] | undefined;
|
||||||
let inputEntities: EntityRegistryEntry[] | undefined;
|
let inputEntities: EntityRegistryEntry[] | undefined;
|
||||||
|
|
||||||
if (includeDomains || excludeDomains || includeDeviceClasses) {
|
if (
|
||||||
|
includeDomains ||
|
||||||
|
excludeDomains ||
|
||||||
|
includeDeviceClasses ||
|
||||||
|
entityFilter
|
||||||
|
) {
|
||||||
for (const entity of entities) {
|
for (const entity of entities) {
|
||||||
if (!entity.device_id) {
|
if (!entity.device_id) {
|
||||||
continue;
|
continue;
|
||||||
@ -145,16 +152,9 @@ export class HaAreaPicker extends LitElement {
|
|||||||
}
|
}
|
||||||
deviceEntityLookup[entity.device_id].push(entity);
|
deviceEntityLookup[entity.device_id].push(entity);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
inputDevices = devices;
|
inputDevices = devices;
|
||||||
inputEntities = entities.filter((entity) => entity.area_id);
|
inputEntities = entities.filter((entity) => entity.area_id);
|
||||||
} else {
|
|
||||||
if (deviceFilter) {
|
|
||||||
inputDevices = devices;
|
|
||||||
}
|
|
||||||
if (entityFilter) {
|
|
||||||
inputEntities = entities.filter((entity) => entity.area_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (includeDomains) {
|
if (includeDomains) {
|
||||||
inputDevices = inputDevices!.filter((device) => {
|
inputDevices = inputDevices!.filter((device) => {
|
||||||
@ -218,9 +218,23 @@ export class HaAreaPicker extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (entityFilter) {
|
if (entityFilter) {
|
||||||
inputEntities = inputEntities!.filter((entity) =>
|
inputDevices = inputDevices!.filter((device) => {
|
||||||
entityFilter!(entity)
|
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;
|
let outputAreas = areas;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { css, html, LitElement, TemplateResult } from "lit";
|
import { css, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import type { EntityRegistryEntry } from "../data/entity_registry";
|
|
||||||
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
||||||
@ -48,7 +48,7 @@ export class HaAreasPicker extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@property() public deviceFilter?: HaDevicePickerDeviceFilterFunc;
|
@property() public deviceFilter?: HaDevicePickerDeviceFilterFunc;
|
||||||
|
|
||||||
@property() public entityFilter?: (entity: EntityRegistryEntry) => boolean;
|
@property() public entityFilter?: (entity: HassEntity) => boolean;
|
||||||
|
|
||||||
@property({ attribute: "picked-area-label" })
|
@property({ attribute: "picked-area-label" })
|
||||||
public pickedAreaLabel?: string;
|
public pickedAreaLabel?: string;
|
||||||
|
@ -2,6 +2,7 @@ import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
|
|||||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
|
import { ensureArray } from "../../common/array/ensure-array";
|
||||||
import type { DeviceRegistryEntry } from "../../data/device_registry";
|
import type { DeviceRegistryEntry } from "../../data/device_registry";
|
||||||
import { getDeviceIntegrationLookup } from "../../data/device_registry";
|
import { getDeviceIntegrationLookup } from "../../data/device_registry";
|
||||||
import {
|
import {
|
||||||
@ -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 {
|
protected updated(changedProperties: PropertyValues): void {
|
||||||
if (
|
if (
|
||||||
changedProperties.has("selector") &&
|
changedProperties.has("selector") &&
|
||||||
(this.selector.area?.device?.integration ||
|
this._hasIntegration(this.selector) &&
|
||||||
this.selector.area?.entity?.integration) &&
|
|
||||||
!this._entitySources
|
!this._entitySources
|
||||||
) {
|
) {
|
||||||
fetchEntitySourcesWithCache(this.hass).then((sources) => {
|
fetchEntitySourcesWithCache(this.hass).then((sources) => {
|
||||||
@ -66,11 +77,7 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (
|
if (this._hasIntegration(this.selector) && !this._entitySources) {
|
||||||
(this.selector.area?.device?.integration ||
|
|
||||||
this.selector.area?.entity?.integration) &&
|
|
||||||
!this._entitySources
|
|
||||||
) {
|
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,10 +117,8 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return filterSelectorEntities(
|
return ensureArray(this.selector.area.entity).some((filter) =>
|
||||||
this.selector.area.entity,
|
filterSelectorEntities(filter, entity, this._entitySources)
|
||||||
entity,
|
|
||||||
this._entitySources
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -127,10 +132,8 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
|
|||||||
? this._deviceIntegrationLookup(this._entitySources, this._entities)
|
? this._deviceIntegrationLookup(this._entitySources, this._entities)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
return filterSelectorDevices(
|
return ensureArray(this.selector.area.device).some((filter) =>
|
||||||
this.selector.area.device,
|
filterSelectorDevices(filter, device, deviceIntegrations)
|
||||||
device,
|
|
||||||
deviceIntegrations
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -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 { html, LitElement } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
|
import { ensureArray } from "../../common/array/ensure-array";
|
||||||
import type { DeviceRegistryEntry } from "../../data/device_registry";
|
import type { DeviceRegistryEntry } from "../../data/device_registry";
|
||||||
import { getDeviceIntegrationLookup } from "../../data/device_registry";
|
import { getDeviceIntegrationLookup } from "../../data/device_registry";
|
||||||
import {
|
import {
|
||||||
@ -13,7 +14,10 @@ import {
|
|||||||
fetchEntitySourcesWithCache,
|
fetchEntitySourcesWithCache,
|
||||||
} from "../../data/entity_sources";
|
} from "../../data/entity_sources";
|
||||||
import type { DeviceSelector } from "../../data/selector";
|
import type { DeviceSelector } from "../../data/selector";
|
||||||
import { filterSelectorDevices } from "../../data/selector";
|
import {
|
||||||
|
filterSelectorDevices,
|
||||||
|
filterSelectorEntities,
|
||||||
|
} from "../../data/selector";
|
||||||
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import "../device/ha-device-picker";
|
import "../device/ha-device-picker";
|
||||||
@ -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 {
|
protected updated(changedProperties): void {
|
||||||
super.updated(changedProperties);
|
super.updated(changedProperties);
|
||||||
if (
|
if (
|
||||||
changedProperties.has("selector") &&
|
changedProperties.has("selector") &&
|
||||||
this.selector.device?.integration &&
|
this._hasIntegration(this.selector) &&
|
||||||
!this._entitySources
|
!this._entitySources
|
||||||
) {
|
) {
|
||||||
fetchEntitySourcesWithCache(this.hass).then((sources) => {
|
fetchEntitySourcesWithCache(this.hass).then((sources) => {
|
||||||
@ -63,7 +80,7 @@ export class HaDeviceSelector extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (this.selector.device?.integration && !this._entitySources) {
|
if (this._hasIntegration(this.selector) && !this._entitySources) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,12 +92,7 @@ export class HaDeviceSelector extends SubscribeMixin(LitElement) {
|
|||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.helper=${this.helper}
|
.helper=${this.helper}
|
||||||
.deviceFilter=${this._filterDevices}
|
.deviceFilter=${this._filterDevices}
|
||||||
.includeDeviceClasses=${this.selector.device?.entity?.device_class
|
.entityFilter=${this._filterEntities}
|
||||||
? [this.selector.device.entity.device_class]
|
|
||||||
: undefined}
|
|
||||||
.includeDomains=${this.selector.device?.entity?.domain
|
|
||||||
? [this.selector.device.entity.domain]
|
|
||||||
: undefined}
|
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
allow-custom-entity
|
allow-custom-entity
|
||||||
@ -95,12 +107,7 @@ export class HaDeviceSelector extends SubscribeMixin(LitElement) {
|
|||||||
.value=${this.value}
|
.value=${this.value}
|
||||||
.helper=${this.helper}
|
.helper=${this.helper}
|
||||||
.deviceFilter=${this._filterDevices}
|
.deviceFilter=${this._filterDevices}
|
||||||
.includeDeviceClasses=${this.selector.device.entity?.device_class
|
.entityFilter=${this._filterEntities}
|
||||||
? [this.selector.device.entity.device_class]
|
|
||||||
: undefined}
|
|
||||||
.includeDomains=${this.selector.device.entity?.domain
|
|
||||||
? [this.selector.device.entity.domain]
|
|
||||||
: undefined}
|
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
></ha-devices-picker>
|
></ha-devices-picker>
|
||||||
@ -108,18 +115,25 @@ export class HaDeviceSelector extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _filterDevices = (device: DeviceRegistryEntry): boolean => {
|
private _filterDevices = (device: DeviceRegistryEntry): boolean => {
|
||||||
|
if (!this.selector.device?.filter) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
const deviceIntegrations =
|
const deviceIntegrations =
|
||||||
this._entitySources && this._entities
|
this._entitySources && this._entities
|
||||||
? this._deviceIntegrationLookup(this._entitySources, this._entities)
|
? this._deviceIntegrationLookup(this._entitySources, this._entities)
|
||||||
: undefined;
|
: 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 true;
|
||||||
}
|
}
|
||||||
return filterSelectorDevices(
|
return ensureArray(this.selector.device.entity).some((filter) =>
|
||||||
this.selector.device,
|
filterSelectorEntities(filter, entity, this._entitySources)
|
||||||
device,
|
|
||||||
deviceIntegrations
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { html, LitElement, PropertyValues } from "lit";
|
import { html, LitElement, PropertyValues } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { ensureArray } from "../../common/array/ensure-array";
|
||||||
import {
|
import {
|
||||||
EntitySources,
|
EntitySources,
|
||||||
fetchEntitySourcesWithCache,
|
fetchEntitySourcesWithCache,
|
||||||
@ -29,7 +30,18 @@ export class HaEntitySelector extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public required = true;
|
@property({ type: Boolean }) public required = true;
|
||||||
|
|
||||||
|
private _hasIntegration(selector: EntitySelector) {
|
||||||
|
return (
|
||||||
|
selector.entity?.filter &&
|
||||||
|
ensureArray(selector.entity.filter).some((filter) => filter.integration)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
|
if (this._hasIntegration(this.selector) && !this._entitySources) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.selector.entity?.multiple) {
|
if (!this.selector.entity?.multiple) {
|
||||||
return html`<ha-entity-picker
|
return html`<ha-entity-picker
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@ -64,7 +76,7 @@ export class HaEntitySelector extends LitElement {
|
|||||||
super.updated(changedProps);
|
super.updated(changedProps);
|
||||||
if (
|
if (
|
||||||
changedProps.has("selector") &&
|
changedProps.has("selector") &&
|
||||||
this.selector.entity?.integration &&
|
this._hasIntegration(this.selector) &&
|
||||||
!this._entitySources
|
!this._entitySources
|
||||||
) {
|
) {
|
||||||
fetchEntitySourcesWithCache(this.hass).then((sources) => {
|
fetchEntitySourcesWithCache(this.hass).then((sources) => {
|
||||||
@ -74,13 +86,11 @@ export class HaEntitySelector extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _filterEntities = (entity: HassEntity): boolean => {
|
private _filterEntities = (entity: HassEntity): boolean => {
|
||||||
if (!this.selector?.entity) {
|
if (!this.selector?.entity?.filter) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return filterSelectorEntities(
|
return ensureArray(this.selector.entity.filter).some((filter) =>
|
||||||
this.selector.entity,
|
filterSelectorEntities(filter, entity, this._entitySources)
|
||||||
entity,
|
|
||||||
this._entitySources
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,6 @@ import {
|
|||||||
DeviceRegistryEntry,
|
DeviceRegistryEntry,
|
||||||
getDeviceIntegrationLookup,
|
getDeviceIntegrationLookup,
|
||||||
} from "../../data/device_registry";
|
} from "../../data/device_registry";
|
||||||
import { EntityRegistryEntry } from "../../data/entity_registry";
|
|
||||||
import {
|
import {
|
||||||
EntitySources,
|
EntitySources,
|
||||||
fetchEntitySourcesWithCache,
|
fetchEntitySourcesWithCache,
|
||||||
@ -45,12 +44,24 @@ export class HaTargetSelector extends LitElement {
|
|||||||
|
|
||||||
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
|
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
|
||||||
|
|
||||||
|
private _hasIntegration(selector: TargetSelector) {
|
||||||
|
return (
|
||||||
|
(selector.target?.entity &&
|
||||||
|
ensureArray(selector.target.entity).some(
|
||||||
|
(filter) => filter.integration
|
||||||
|
)) ||
|
||||||
|
(selector.target?.device &&
|
||||||
|
ensureArray(selector.target.device).some(
|
||||||
|
(device) => device.integration
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
protected updated(changedProperties: PropertyValues): void {
|
protected updated(changedProperties: PropertyValues): void {
|
||||||
super.updated(changedProperties);
|
super.updated(changedProperties);
|
||||||
if (
|
if (
|
||||||
changedProperties.has("selector") &&
|
changedProperties.has("selector") &&
|
||||||
(this.selector.target?.device?.integration ||
|
this._hasIntegration(this.selector) &&
|
||||||
this.selector.target?.entity?.integration) &&
|
|
||||||
!this._entitySources
|
!this._entitySources
|
||||||
) {
|
) {
|
||||||
fetchEntitySourcesWithCache(this.hass).then((sources) => {
|
fetchEntitySourcesWithCache(this.hass).then((sources) => {
|
||||||
@ -60,11 +71,7 @@ export class HaTargetSelector extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (
|
if (this._hasIntegration(this.selector) && !this._entitySources) {
|
||||||
(this.selector.target?.device?.integration ||
|
|
||||||
this.selector.target?.entity?.integration) &&
|
|
||||||
!this._entitySources
|
|
||||||
) {
|
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,39 +80,21 @@ export class HaTargetSelector extends LitElement {
|
|||||||
.value=${this.value}
|
.value=${this.value}
|
||||||
.helper=${this.helper}
|
.helper=${this.helper}
|
||||||
.deviceFilter=${this._filterDevices}
|
.deviceFilter=${this._filterDevices}
|
||||||
.entityFilter=${this._filterStates}
|
.entityFilter=${this._filterEntities}
|
||||||
.entityRegFilter=${this._filterRegEntities}
|
|
||||||
.includeDeviceClasses=${this.selector.target?.entity?.device_class
|
|
||||||
? [this.selector.target?.entity.device_class]
|
|
||||||
: undefined}
|
|
||||||
.includeDomains=${this.selector.target?.entity?.domain
|
|
||||||
? ensureArray(this.selector.target.entity.domain as string | string[])
|
|
||||||
: undefined}
|
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
></ha-target-picker>`;
|
></ha-target-picker>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _filterStates = (entity: HassEntity): boolean => {
|
private _filterEntities = (entity: HassEntity): boolean => {
|
||||||
if (!this.selector.target?.entity) {
|
if (!this.selector.target?.entity) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return filterSelectorEntities(
|
return ensureArray(this.selector.target.entity).some((filter) =>
|
||||||
this.selector.target.entity,
|
filterSelectorEntities(filter, entity, this._entitySources)
|
||||||
entity,
|
|
||||||
this._entitySources
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
private _filterRegEntities = (entity: EntityRegistryEntry): boolean => {
|
|
||||||
if (this.selector.target?.entity?.integration) {
|
|
||||||
if (entity.platform !== this.selector.target.entity.integration) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
private _filterDevices = (device: DeviceRegistryEntry): boolean => {
|
private _filterDevices = (device: DeviceRegistryEntry): boolean => {
|
||||||
if (!this.selector.target?.device) {
|
if (!this.selector.target?.device) {
|
||||||
return true;
|
return true;
|
||||||
@ -118,10 +107,8 @@ export class HaTargetSelector extends LitElement {
|
|||||||
)
|
)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
return filterSelectorDevices(
|
return ensureArray(this.selector.target.device).some((filter) =>
|
||||||
this.selector.target.device,
|
filterSelectorDevices(filter, device, deviceIntegrations)
|
||||||
device,
|
|
||||||
deviceIntegrations
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
import { html, LitElement, PropertyValues } from "lit";
|
import { html, LitElement, PropertyValues } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
|
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
|
||||||
import type { Selector } from "../../data/selector";
|
import {
|
||||||
|
Selector,
|
||||||
|
handleLegacyEntitySelector,
|
||||||
|
handleLegacyDeviceSelector,
|
||||||
|
} from "../../data/selector";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
const LOAD_ELEMENTS = {
|
const LOAD_ELEMENTS = {
|
||||||
@ -75,12 +80,22 @@ export class HaSelector extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _handleLegacySelector = memoizeOne((selector: Selector) => {
|
||||||
|
if ("entity" in selector) {
|
||||||
|
return handleLegacyEntitySelector(selector);
|
||||||
|
}
|
||||||
|
if ("device" in selector) {
|
||||||
|
return handleLegacyDeviceSelector(selector);
|
||||||
|
}
|
||||||
|
return selector;
|
||||||
|
});
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`
|
return html`
|
||||||
${dynamicElement(`ha-selector-${this._type}`, {
|
${dynamicElement(`ha-selector-${this._type}`, {
|
||||||
hass: this.hass,
|
hass: this.hass,
|
||||||
name: this.name,
|
name: this.name,
|
||||||
selector: this.selector,
|
selector: this._handleLegacySelector(this.selector),
|
||||||
value: this.value,
|
value: this.value,
|
||||||
label: this.label,
|
label: this.label,
|
||||||
placeholder: this.placeholder,
|
placeholder: this.placeholder,
|
||||||
|
@ -9,32 +9,19 @@ import {
|
|||||||
mdiUnfoldMoreVertical,
|
mdiUnfoldMoreVertical,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import "@polymer/paper-tooltip/paper-tooltip";
|
import "@polymer/paper-tooltip/paper-tooltip";
|
||||||
import {
|
import { HassEntity, HassServiceTarget } from "home-assistant-js-websocket";
|
||||||
HassEntity,
|
|
||||||
HassServiceTarget,
|
|
||||||
UnsubscribeFunc,
|
|
||||||
} from "home-assistant-js-websocket";
|
|
||||||
import { css, CSSResultGroup, html, LitElement, unsafeCSS } from "lit";
|
import { css, CSSResultGroup, html, LitElement, unsafeCSS } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
|
||||||
import { ensureArray } from "../common/array/ensure-array";
|
import { ensureArray } from "../common/array/ensure-array";
|
||||||
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { computeDomain } from "../common/entity/compute_domain";
|
import { computeDomain } from "../common/entity/compute_domain";
|
||||||
import { computeStateName } from "../common/entity/compute_state_name";
|
import { computeStateName } from "../common/entity/compute_state_name";
|
||||||
import {
|
|
||||||
AreaRegistryEntry,
|
|
||||||
subscribeAreaRegistry,
|
|
||||||
} from "../data/area_registry";
|
|
||||||
import {
|
import {
|
||||||
computeDeviceName,
|
computeDeviceName,
|
||||||
DeviceRegistryEntry,
|
DeviceRegistryEntry,
|
||||||
subscribeDeviceRegistry,
|
|
||||||
} from "../data/device_registry";
|
} from "../data/device_registry";
|
||||||
import {
|
import { EntityRegistryEntry } from "../data/entity_registry";
|
||||||
EntityRegistryEntry,
|
|
||||||
subscribeEntityRegistry,
|
|
||||||
} from "../data/entity_registry";
|
|
||||||
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import "./device/ha-device-picker";
|
import "./device/ha-device-picker";
|
||||||
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
||||||
@ -46,7 +33,7 @@ import "./ha-input-helper-text";
|
|||||||
import "./ha-svg-icon";
|
import "./ha-svg-icon";
|
||||||
|
|
||||||
@customElement("ha-target-picker")
|
@customElement("ha-target-picker")
|
||||||
export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
export class HaTargetPicker extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ attribute: false }) public value?: HassServiceTarget;
|
@property({ attribute: false }) public value?: HassServiceTarget;
|
||||||
@ -73,52 +60,17 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@property() public deviceFilter?: HaDevicePickerDeviceFilterFunc;
|
@property() public deviceFilter?: HaDevicePickerDeviceFilterFunc;
|
||||||
|
|
||||||
@property() public entityRegFilter?: (entity: EntityRegistryEntry) => boolean;
|
|
||||||
|
|
||||||
@property() public entityFilter?: HaEntityPickerEntityFilterFunc;
|
@property() public entityFilter?: HaEntityPickerEntityFilterFunc;
|
||||||
|
|
||||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public horizontal = false;
|
@property({ type: Boolean }) public horizontal = false;
|
||||||
|
|
||||||
@state() private _areas?: { [areaId: string]: AreaRegistryEntry };
|
|
||||||
|
|
||||||
@state() private _devices?: {
|
|
||||||
[deviceId: string]: DeviceRegistryEntry;
|
|
||||||
};
|
|
||||||
|
|
||||||
@state() private _entities?: EntityRegistryEntry[];
|
|
||||||
|
|
||||||
@state() private _addMode?: "area_id" | "entity_id" | "device_id";
|
@state() private _addMode?: "area_id" | "entity_id" | "device_id";
|
||||||
|
|
||||||
@query("#input") private _inputElement?;
|
@query("#input") private _inputElement?;
|
||||||
|
|
||||||
public hassSubscribe(): UnsubscribeFunc[] {
|
|
||||||
return [
|
|
||||||
subscribeAreaRegistry(this.hass.connection!, (areas) => {
|
|
||||||
const areaLookup: { [areaId: string]: AreaRegistryEntry } = {};
|
|
||||||
for (const area of areas) {
|
|
||||||
areaLookup[area.area_id] = area;
|
|
||||||
}
|
|
||||||
this._areas = areaLookup;
|
|
||||||
}),
|
|
||||||
subscribeDeviceRegistry(this.hass.connection!, (devices) => {
|
|
||||||
const deviceLookup: { [deviceId: string]: DeviceRegistryEntry } = {};
|
|
||||||
for (const device of devices) {
|
|
||||||
deviceLookup[device.id] = device;
|
|
||||||
}
|
|
||||||
this._devices = deviceLookup;
|
|
||||||
}),
|
|
||||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
|
||||||
this._entities = entities;
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (!this._areas || !this._devices || !this._entities) {
|
|
||||||
return html``;
|
|
||||||
}
|
|
||||||
return html`
|
return html`
|
||||||
${this.horizontal
|
${this.horizontal
|
||||||
? html`
|
? html`
|
||||||
@ -141,7 +93,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||||||
<div class="mdc-chip-set items">
|
<div class="mdc-chip-set items">
|
||||||
${this.value?.area_id
|
${this.value?.area_id
|
||||||
? ensureArray(this.value.area_id).map((area_id) => {
|
? ensureArray(this.value.area_id).map((area_id) => {
|
||||||
const area = this._areas![area_id];
|
const area = this.hass.devices![area_id];
|
||||||
return this._renderChip(
|
return this._renderChip(
|
||||||
"area_id",
|
"area_id",
|
||||||
area_id,
|
area_id,
|
||||||
@ -153,7 +105,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||||||
: ""}
|
: ""}
|
||||||
${this.value?.device_id
|
${this.value?.device_id
|
||||||
? ensureArray(this.value.device_id).map((device_id) => {
|
? ensureArray(this.value.device_id).map((device_id) => {
|
||||||
const device = this._devices![device_id];
|
const device = this.hass.devices![device_id];
|
||||||
return this._renderChip(
|
return this._renderChip(
|
||||||
"device_id",
|
"device_id",
|
||||||
device_id,
|
device_id,
|
||||||
@ -342,7 +294,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||||||
)}
|
)}
|
||||||
no-add
|
no-add
|
||||||
.deviceFilter=${this.deviceFilter}
|
.deviceFilter=${this.deviceFilter}
|
||||||
.entityFilter=${this.entityRegFilter}
|
.entityFilter=${this.entityFilter}
|
||||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||||
.includeDomains=${this.includeDomains}
|
.includeDomains=${this.includeDomains}
|
||||||
.excludeAreas=${ensureArray(this.value?.area_id)}
|
.excludeAreas=${ensureArray(this.value?.area_id)}
|
||||||
@ -359,6 +311,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||||||
"ui.components.target-picker.add_device_id"
|
"ui.components.target-picker.add_device_id"
|
||||||
)}
|
)}
|
||||||
.deviceFilter=${this.deviceFilter}
|
.deviceFilter=${this.deviceFilter}
|
||||||
|
.entityFilter=${this.entityFilter}
|
||||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||||
.includeDomains=${this.includeDomains}
|
.includeDomains=${this.includeDomains}
|
||||||
.excludeDevices=${ensureArray(this.value?.device_id)}
|
.excludeDevices=${ensureArray(this.value?.device_id)}
|
||||||
@ -419,7 +372,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||||||
const newDevices: string[] = [];
|
const newDevices: string[] = [];
|
||||||
const newEntities: string[] = [];
|
const newEntities: string[] = [];
|
||||||
if (target.type === "area_id") {
|
if (target.type === "area_id") {
|
||||||
Object.values(this._devices!).forEach((device) => {
|
Object.values(this.hass.devices).forEach((device) => {
|
||||||
if (
|
if (
|
||||||
device.area_id === target.id &&
|
device.area_id === target.id &&
|
||||||
!this.value!.device_id?.includes(device.id) &&
|
!this.value!.device_id?.includes(device.id) &&
|
||||||
@ -428,7 +381,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||||||
newDevices.push(device.id);
|
newDevices.push(device.id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this._entities!.forEach((entity) => {
|
Object.values(this.hass.entities).forEach((entity) => {
|
||||||
if (
|
if (
|
||||||
entity.area_id === target.id &&
|
entity.area_id === target.id &&
|
||||||
!this.value!.entity_id?.includes(entity.entity_id) &&
|
!this.value!.entity_id?.includes(entity.entity_id) &&
|
||||||
@ -438,7 +391,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (target.type === "device_id") {
|
} else if (target.type === "device_id") {
|
||||||
this._entities!.forEach((entity) => {
|
Object.values(this.hass.entities).forEach((entity) => {
|
||||||
if (
|
if (
|
||||||
entity.device_id === target.id &&
|
entity.device_id === target.id &&
|
||||||
!this.value!.entity_id?.includes(entity.entity_id) &&
|
!this.value!.entity_id?.includes(entity.entity_id) &&
|
||||||
@ -502,9 +455,10 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _deviceMeetsFilter(device: DeviceRegistryEntry): boolean {
|
private _deviceMeetsFilter(device: DeviceRegistryEntry): boolean {
|
||||||
const devEntities = this._entities?.filter(
|
const devEntities = Object.values(this.hass.entities).filter(
|
||||||
(entity) => entity.device_id === device.id
|
(entity) => entity.device_id === device.id
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.includeDomains) {
|
if (this.includeDomains) {
|
||||||
if (!devEntities || !devEntities.length) {
|
if (!devEntities || !devEntities.length) {
|
||||||
return false;
|
return false;
|
||||||
@ -541,7 +495,23 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.deviceFilter) {
|
if (this.deviceFilter) {
|
||||||
return this.deviceFilter(device);
|
if (!this.deviceFilter(device)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.entityFilter) {
|
||||||
|
if (
|
||||||
|
!devEntities.some((entity) => {
|
||||||
|
const stateObj = this.hass.states[entity.entity_id];
|
||||||
|
if (!stateObj) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this.entityFilter!(stateObj);
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -550,6 +520,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||||||
if (entity.entity_category) {
|
if (entity.entity_category) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.includeDomains &&
|
this.includeDomains &&
|
||||||
!this.includeDomains.includes(computeDomain(entity.entity_id))
|
!this.includeDomains.includes(computeDomain(entity.entity_id))
|
||||||
@ -568,8 +539,15 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.entityRegFilter) {
|
|
||||||
return this.entityRegFilter(entity);
|
if (this.entityFilter) {
|
||||||
|
const stateObj = this.hass.states[entity.entity_id];
|
||||||
|
if (!stateObj) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!this.entityFilter!(stateObj)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,10 @@ export type Selector =
|
|||||||
| DateSelector
|
| DateSelector
|
||||||
| DateTimeSelector
|
| DateTimeSelector
|
||||||
| DeviceSelector
|
| DeviceSelector
|
||||||
|
| LegacyDeviceSelector
|
||||||
| DurationSelector
|
| DurationSelector
|
||||||
| EntitySelector
|
| EntitySelector
|
||||||
|
| LegacyEntitySelector
|
||||||
| FileSelector
|
| FileSelector
|
||||||
| IconSelector
|
| IconSelector
|
||||||
| LocationSelector
|
| LocationSelector
|
||||||
@ -48,22 +50,10 @@ export interface AddonSelector {
|
|||||||
} | null;
|
} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SelectorDevice {
|
|
||||||
integration?: NonNullable<DeviceSelector["device"]>["integration"];
|
|
||||||
manufacturer?: NonNullable<DeviceSelector["device"]>["manufacturer"];
|
|
||||||
model?: NonNullable<DeviceSelector["device"]>["model"];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SelectorEntity {
|
|
||||||
integration?: NonNullable<EntitySelector["entity"]>["integration"];
|
|
||||||
domain?: NonNullable<EntitySelector["entity"]>["domain"];
|
|
||||||
device_class?: NonNullable<EntitySelector["entity"]>["device_class"];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AreaSelector {
|
export interface AreaSelector {
|
||||||
area: {
|
area: {
|
||||||
entity?: SelectorEntity;
|
entity?: EntitySelectorFilter | readonly EntitySelectorFilter[];
|
||||||
device?: SelectorDevice;
|
device?: DeviceSelectorFilter | readonly DeviceSelectorFilter[];
|
||||||
multiple?: boolean;
|
multiple?: boolean;
|
||||||
} | null;
|
} | null;
|
||||||
}
|
}
|
||||||
@ -108,33 +98,77 @@ export interface DateTimeSelector {
|
|||||||
datetime: {} | null;
|
datetime: {} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DeviceSelector {
|
interface DeviceSelectorFilter {
|
||||||
device: {
|
|
||||||
integration?: string;
|
integration?: string;
|
||||||
manufacturer?: string;
|
manufacturer?: string;
|
||||||
model?: string;
|
model?: string;
|
||||||
entity?: SelectorEntity;
|
}
|
||||||
|
|
||||||
|
export interface DeviceSelector {
|
||||||
|
device: {
|
||||||
|
filter?: DeviceSelectorFilter | readonly DeviceSelectorFilter[];
|
||||||
|
entity?: EntitySelectorFilter | readonly EntitySelectorFilter[];
|
||||||
multiple?: boolean;
|
multiple?: boolean;
|
||||||
} | null;
|
} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LegacyDeviceSelector {
|
||||||
|
device:
|
||||||
|
| DeviceSelector["device"] & {
|
||||||
|
/**
|
||||||
|
* @deprecated Use filter instead
|
||||||
|
*/
|
||||||
|
integration?: DeviceSelectorFilter["integration"];
|
||||||
|
/**
|
||||||
|
* @deprecated Use filter instead
|
||||||
|
*/
|
||||||
|
manufacturer?: DeviceSelectorFilter["manufacturer"];
|
||||||
|
/**
|
||||||
|
* @deprecated Use filter instead
|
||||||
|
*/
|
||||||
|
model?: DeviceSelectorFilter["model"];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export interface DurationSelector {
|
export interface DurationSelector {
|
||||||
duration: {
|
duration: {
|
||||||
enable_day?: boolean;
|
enable_day?: boolean;
|
||||||
} | null;
|
} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EntitySelector {
|
interface EntitySelectorFilter {
|
||||||
entity: {
|
|
||||||
integration?: string;
|
integration?: string;
|
||||||
domain?: string | readonly string[];
|
domain?: string | readonly string[];
|
||||||
device_class?: string;
|
device_class?: string | readonly string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EntitySelector {
|
||||||
|
entity: {
|
||||||
multiple?: boolean;
|
multiple?: boolean;
|
||||||
include_entities?: string[];
|
include_entities?: string[];
|
||||||
exclude_entities?: string[];
|
exclude_entities?: string[];
|
||||||
|
filter?: EntitySelectorFilter | readonly EntitySelectorFilter[];
|
||||||
} | null;
|
} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LegacyEntitySelector {
|
||||||
|
entity:
|
||||||
|
| EntitySelector["entity"] & {
|
||||||
|
/**
|
||||||
|
* @deprecated Use filter instead
|
||||||
|
*/
|
||||||
|
integration?: EntitySelectorFilter["integration"];
|
||||||
|
/**
|
||||||
|
* @deprecated Use filter instead
|
||||||
|
*/
|
||||||
|
domain?: EntitySelectorFilter["domain"];
|
||||||
|
/**
|
||||||
|
* @deprecated Use filter instead
|
||||||
|
*/
|
||||||
|
device_class?: EntitySelectorFilter["device_class"];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export interface StatisticSelector {
|
export interface StatisticSelector {
|
||||||
statistic: {
|
statistic: {
|
||||||
device_class?: string;
|
device_class?: string;
|
||||||
@ -250,8 +284,8 @@ export interface StringSelector {
|
|||||||
|
|
||||||
export interface TargetSelector {
|
export interface TargetSelector {
|
||||||
target: {
|
target: {
|
||||||
entity?: SelectorEntity;
|
entity?: EntitySelectorFilter | readonly EntitySelectorFilter[];
|
||||||
device?: SelectorDevice;
|
device?: DeviceSelectorFilter | readonly DeviceSelectorFilter[];
|
||||||
} | null;
|
} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -281,7 +315,7 @@ export interface UiColorSelector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const filterSelectorDevices = (
|
export const filterSelectorDevices = (
|
||||||
filterDevice: SelectorDevice,
|
filterDevice: DeviceSelectorFilter,
|
||||||
device: DeviceRegistryEntry,
|
device: DeviceRegistryEntry,
|
||||||
deviceIntegrationLookup: Record<string, string[]> | undefined
|
deviceIntegrationLookup: Record<string, string[]> | undefined
|
||||||
): boolean => {
|
): boolean => {
|
||||||
@ -308,7 +342,7 @@ export const filterSelectorDevices = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const filterSelectorEntities = (
|
export const filterSelectorEntities = (
|
||||||
filterEntity: SelectorEntity,
|
filterEntity: EntitySelectorFilter,
|
||||||
entity: HassEntity,
|
entity: HassEntity,
|
||||||
entitySources?: EntitySources
|
entitySources?: EntitySources
|
||||||
): boolean => {
|
): boolean => {
|
||||||
@ -329,12 +363,16 @@ export const filterSelectorEntities = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (filterDeviceClass) {
|
||||||
|
const entityDeviceClass = entity.attributes.device_class;
|
||||||
if (
|
if (
|
||||||
filterDeviceClass &&
|
entityDeviceClass && Array.isArray(filterDeviceClass)
|
||||||
entity.attributes.device_class !== filterDeviceClass
|
? !filterDeviceClass.includes(entityDeviceClass)
|
||||||
|
: entityDeviceClass !== filterDeviceClass
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
filterIntegration &&
|
filterIntegration &&
|
||||||
@ -345,3 +383,59 @@ export const filterSelectorEntities = (
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const handleLegacyEntitySelector = (
|
||||||
|
selector: LegacyEntitySelector | EntitySelector
|
||||||
|
): EntitySelector => {
|
||||||
|
if (!selector.entity) return { entity: null };
|
||||||
|
|
||||||
|
if ("filter" in selector.entity) return selector;
|
||||||
|
|
||||||
|
const { domain, integration, device_class, ...rest } = (
|
||||||
|
selector as LegacyEntitySelector
|
||||||
|
).entity!;
|
||||||
|
|
||||||
|
if (domain || integration || device_class) {
|
||||||
|
return {
|
||||||
|
entity: {
|
||||||
|
...rest,
|
||||||
|
filter: {
|
||||||
|
domain,
|
||||||
|
integration,
|
||||||
|
device_class,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
entity: rest,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const handleLegacyDeviceSelector = (
|
||||||
|
selector: LegacyDeviceSelector | DeviceSelector
|
||||||
|
): DeviceSelector => {
|
||||||
|
if (!selector.device) return { device: null };
|
||||||
|
|
||||||
|
if ("filter" in selector.device) return selector;
|
||||||
|
|
||||||
|
const { integration, manufacturer, model, ...rest } = (
|
||||||
|
selector as LegacyDeviceSelector
|
||||||
|
).device!;
|
||||||
|
|
||||||
|
if (integration || manufacturer || model) {
|
||||||
|
return {
|
||||||
|
device: {
|
||||||
|
...rest,
|
||||||
|
filter: {
|
||||||
|
integration,
|
||||||
|
manufacturer,
|
||||||
|
model,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
device: rest,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user