Skip to content

Commit

Permalink
Add search to device and entity filters (#20341)
Browse files Browse the repository at this point in the history
  • Loading branch information
bramkragten authored Apr 2, 2024
1 parent 5b86b12 commit 1e2c1d1
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 44 deletions.
85 changes: 58 additions & 27 deletions src/components/ha-filter-devices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ import { stringCompare } from "../common/string/compare";
import { computeDeviceName } from "../data/device_registry";
import { findRelated, RelatedResult } from "../data/search";
import { haStyleScrollbar } from "../resources/styles";
import { loadVirtualizer } from "../resources/virtualizer";
import type { HomeAssistant } from "../types";
import "./ha-expansion-panel";
import "./ha-check-list-item";
import { loadVirtualizer } from "../resources/virtualizer";
import "./ha-expansion-panel";
import "./search-input-outlined";

@customElement("ha-filter-devices")
export class HaFilterDevices extends LitElement {
Expand All @@ -32,6 +33,8 @@ export class HaFilterDevices extends LitElement {

@state() private _shouldRender = false;

@state() private _filter?: string;

public willUpdate(properties: PropertyValues) {
super.willUpdate(properties);

Expand All @@ -55,15 +58,25 @@ export class HaFilterDevices extends LitElement {
: nothing}
</div>
${this._shouldRender
? html`<mwc-list class="ha-scrollbar">
<lit-virtualizer
.items=${this._devices(this.hass.devices, this.value)}
.keyFunction=${this._keyFunction}
.renderItem=${this._renderItem}
@click=${this._handleItemClick}
? html`<search-input-outlined
.hass=${this.hass}
.filter=${this._filter}
@value-changed=${this._handleSearchChange}
>
</lit-virtualizer>
</mwc-list>`
</search-input-outlined>
<mwc-list class="ha-scrollbar">
<lit-virtualizer
.items=${this._devices(
this.hass.devices,
this._filter || "",
this.value
)}
.keyFunction=${this._keyFunction}
.renderItem=${this._renderItem}
@click=${this._handleItemClick}
>
</lit-virtualizer>
</mwc-list>`
: nothing}
</ha-expansion-panel>
`;
Expand All @@ -72,12 +85,14 @@ export class HaFilterDevices extends LitElement {
private _keyFunction = (device) => device?.id;

private _renderItem = (device) =>
html`<ha-check-list-item
.value=${device.id}
.selected=${this.value?.includes(device.id)}
>
${computeDeviceName(device, this.hass)}
</ha-check-list-item>`;
!device
? nothing
: html`<ha-check-list-item
.value=${device.id}
.selected=${this.value?.includes(device.id)}
>
${computeDeviceName(device, this.hass)}
</ha-check-list-item>`;

private _handleItemClick(ev) {
const listItem = ev.target.closest("ha-check-list-item");
Expand All @@ -99,7 +114,7 @@ export class HaFilterDevices extends LitElement {
setTimeout(() => {
if (!this.expanded) return;
this.renderRoot.querySelector("mwc-list")!.style.height =
`${this.clientHeight - 49}px`;
`${this.clientHeight - 49 - 32}px`; // 32px is the height of the search input
}, 300);
}
}
Expand All @@ -112,16 +127,28 @@ export class HaFilterDevices extends LitElement {
this.expanded = ev.detail.expanded;
}

private _devices = memoizeOne((devices: HomeAssistant["devices"], _value) => {
const values = Object.values(devices);
return values.sort((a, b) =>
stringCompare(
a.name_by_user || a.name || "",
b.name_by_user || b.name || "",
this.hass.locale.language
)
);
});
private _handleSearchChange(ev: CustomEvent) {
this._filter = ev.detail.value.toLowerCase();
}

private _devices = memoizeOne(
(devices: HomeAssistant["devices"], filter: string, _value) => {
const values = Object.values(devices);
return values
.filter(
(device) =>
!filter ||
computeDeviceName(device, this.hass).toLowerCase().includes(filter)
)
.sort((a, b) =>
stringCompare(
computeDeviceName(a, this.hass),
computeDeviceName(b, this.hass),
this.hass.locale.language
)
);
}
);

private async _findRelated() {
const relatedPromises: Promise<RelatedResult>[] = [];
Expand Down Expand Up @@ -197,6 +224,10 @@ export class HaFilterDevices extends LitElement {
ha-check-list-item {
width: 100%;
}
search-input-outlined {
display: block;
padding: 0 8px;
}
`,
];
}
Expand Down
65 changes: 48 additions & 17 deletions src/components/ha-filter-entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ import { computeStateName } from "../common/entity/compute_state_name";
import { stringCompare } from "../common/string/compare";
import { findRelated, RelatedResult } from "../data/search";
import { haStyleScrollbar } from "../resources/styles";
import { loadVirtualizer } from "../resources/virtualizer";
import type { HomeAssistant } from "../types";
import "./ha-state-icon";
import "./ha-check-list-item";
import { loadVirtualizer } from "../resources/virtualizer";
import "./ha-state-icon";
import "./search-input-outlined";

@customElement("ha-filter-entities")
export class HaFilterEntities extends LitElement {
Expand All @@ -33,6 +34,8 @@ export class HaFilterEntities extends LitElement {

@state() private _shouldRender = false;

@state() private _filter?: string;

public willUpdate(properties: PropertyValues) {
super.willUpdate(properties);

Expand All @@ -57,11 +60,18 @@ export class HaFilterEntities extends LitElement {
</div>
${this._shouldRender
? html`
<search-input-outlined
.hass=${this.hass}
.filter=${this._filter}
@value-changed=${this._handleSearchChange}
>
</search-input-outlined>
<mwc-list class="ha-scrollbar">
<lit-virtualizer
.items=${this._entities(
this.hass.states,
this.type,
this._filter || "",
this.value
)}
.keyFunction=${this._keyFunction}
Expand All @@ -81,26 +91,28 @@ export class HaFilterEntities extends LitElement {
setTimeout(() => {
if (!this.expanded) return;
this.renderRoot.querySelector("mwc-list")!.style.height =
`${this.clientHeight - 49}px`;
`${this.clientHeight - 49 - 32}px`; // 32px is the height of the search input
}, 300);
}
}

private _keyFunction = (entity) => entity?.entity_id;

private _renderItem = (entity) =>
html`<ha-check-list-item
.value=${entity.entity_id}
.selected=${this.value?.includes(entity.entity_id)}
graphic="icon"
>
<ha-state-icon
slot="graphic"
.hass=${this.hass}
.stateObj=${entity}
></ha-state-icon>
${computeStateName(entity)}
</ha-check-list-item>`;
!entity
? nothing
: html`<ha-check-list-item
.value=${entity.entity_id}
.selected=${this.value?.includes(entity.entity_id)}
graphic="icon"
>
<ha-state-icon
slot="graphic"
.hass=${this.hass}
.stateObj=${entity}
></ha-state-icon>
${computeStateName(entity)}
</ha-check-list-item>`;

private _handleItemClick(ev) {
const listItem = ev.target.closest("ha-check-list-item");
Expand All @@ -125,12 +137,27 @@ export class HaFilterEntities extends LitElement {
this.expanded = ev.detail.expanded;
}

private _handleSearchChange(ev: CustomEvent) {
this._filter = ev.detail.value.toLowerCase();
}

private _entities = memoizeOne(
(states: HomeAssistant["states"], type: this["type"], _value) => {
(
states: HomeAssistant["states"],
type: this["type"],
filter: string,
_value
) => {
const values = Object.values(states);
return values
.filter(
(entityState) => !type || computeStateDomain(entityState) !== type
(entityState) =>
(!type || computeStateDomain(entityState) !== type) &&
(!filter ||
entityState.entity_id.toLowerCase().includes(filter) ||
entityState.attributes.friendly_name
?.toLowerCase()
.includes(filter))
)
.sort((a, b) =>
stringCompare(
Expand Down Expand Up @@ -216,6 +243,10 @@ export class HaFilterEntities extends LitElement {
--mdc-list-item-graphic-margin: 16px;
width: 100%;
}
search-input-outlined {
display: block;
padding: 0 8px;
}
`,
];
}
Expand Down

0 comments on commit 1e2c1d1

Please sign in to comment.