Save data table filters in session storage (#20894)

* Save data table filters in session storage

* typing fix

* remove url logic, rename storage key
This commit is contained in:
Bram Kragten 2024-05-29 12:20:30 +02:00 committed by GitHub
parent 91e5fcacd5
commit 2921161336
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 324 additions and 95 deletions

View File

@ -108,6 +108,8 @@ export const storage =
subscribe?: boolean; subscribe?: boolean;
state?: boolean; state?: boolean;
stateOptions?: InternalPropertyDeclaration; stateOptions?: InternalPropertyDeclaration;
serializer?: (value: any) => any;
deserializer?: (value: any) => any;
}): any => }): any =>
(clsElement: ClassElement) => { (clsElement: ClassElement) => {
const storageName = options.storage || "localStorage"; const storageName = options.storage || "localStorage";
@ -141,7 +143,9 @@ export const storage =
const getValue = (): any => const getValue = (): any =>
storageInstance.hasKey(storageKey!) storageInstance.hasKey(storageKey!)
? storageInstance.getValue(storageKey!) ? options.deserializer
? options.deserializer(storageInstance.getValue(storageKey!))
: storageInstance.getValue(storageKey!)
: initVal; : initVal;
const setValue = (el: ReactiveElement, value: any) => { const setValue = (el: ReactiveElement, value: any) => {
@ -149,7 +153,10 @@ export const storage =
if (options.state) { if (options.state) {
oldValue = getValue(); oldValue = getValue();
} }
storageInstance.setValue(storageKey!, value); storageInstance.setValue(
storageKey!,
options.serializer ? options.serializer(value) : value
);
if (options.state) { if (options.state) {
el.requestUpdate(clsElement.key, oldValue); el.requestUpdate(clsElement.key, oldValue);
} }

View File

@ -11,6 +11,7 @@ declare global {
export interface NavigateOptions { export interface NavigateOptions {
replace?: boolean; replace?: boolean;
data?: any;
} }
export const navigate = (path: string, options?: NavigateOptions) => { export const navigate = (path: string, options?: NavigateOptions) => {
@ -24,7 +25,7 @@ export const navigate = (path: string, options?: NavigateOptions) => {
if (__DEMO__) { if (__DEMO__) {
if (replace) { if (replace) {
mainWindow.history.replaceState( mainWindow.history.replaceState(
mainWindow.history.state?.root ? { root: true } : null, mainWindow.history.state?.root ? { root: true } : options?.data ?? null,
"", "",
`${mainWindow.location.pathname}#${path}` `${mainWindow.location.pathname}#${path}`
); );
@ -33,12 +34,12 @@ export const navigate = (path: string, options?: NavigateOptions) => {
} }
} else if (replace) { } else if (replace) {
mainWindow.history.replaceState( mainWindow.history.replaceState(
mainWindow.history.state?.root ? { root: true } : null, mainWindow.history.state?.root ? { root: true } : options?.data ?? null,
"", "",
path path
); );
} else { } else {
mainWindow.history.pushState(null, "", path); mainWindow.history.pushState(options?.data ?? null, "", path);
} }
fireEvent(mainWindow, "location-changed", { fireEvent(mainWindow, "location-changed", {
replace, replace,

View File

@ -1,7 +1,14 @@
import { SelectedDetail } from "@material/mwc-list"; import { SelectedDetail } from "@material/mwc-list";
import "@material/mwc-menu/mwc-menu-surface"; import "@material/mwc-menu/mwc-menu-surface";
import { mdiFilterVariantRemove } from "@mdi/js"; import { mdiFilterVariantRemove } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import {
css,
CSSResultGroup,
html,
LitElement,
nothing,
PropertyValues,
} from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event"; import { fireEvent } from "../common/dom/fire_event";
import { Blueprints, fetchBlueprints } from "../data/blueprint"; import { Blueprints, fetchBlueprints } from "../data/blueprint";
@ -25,6 +32,16 @@ export class HaFilterBlueprints extends LitElement {
@state() private _blueprints?: Blueprints; @state() private _blueprints?: Blueprints;
public willUpdate(properties: PropertyValues) {
super.willUpdate(properties);
if (!this.hasUpdated) {
if (this.value?.length) {
this._findRelated();
}
}
}
protected render() { protected render() {
return html` return html`
<ha-expansion-panel <ha-expansion-panel
@ -96,7 +113,6 @@ export class HaFilterBlueprints extends LitElement {
ev: CustomEvent<SelectedDetail<Set<number>>> ev: CustomEvent<SelectedDetail<Set<number>>>
) { ) {
const blueprints = this._blueprints!; const blueprints = this._blueprints!;
const relatedPromises: Promise<RelatedResult>[] = [];
if (!ev.detail.index.size) { if (!ev.detail.index.size) {
fireEvent(this, "data-table-filter-changed", { fireEvent(this, "data-table-filter-changed", {
@ -112,13 +128,33 @@ export class HaFilterBlueprints extends LitElement {
for (const index of ev.detail.index) { for (const index of ev.detail.index) {
const blueprintId = Object.keys(blueprints)[index]; const blueprintId = Object.keys(blueprints)[index];
value.push(blueprintId); value.push(blueprintId);
}
this.value = value;
this._findRelated();
}
private async _findRelated() {
if (!this.value?.length) {
fireEvent(this, "data-table-filter-changed", {
value: [],
items: undefined,
});
this.value = [];
return;
}
const relatedPromises: Promise<RelatedResult>[] = [];
for (const blueprintId of this.value) {
if (this.type) { if (this.type) {
relatedPromises.push( relatedPromises.push(
findRelated(this.hass, `${this.type}_blueprint`, blueprintId) findRelated(this.hass, `${this.type}_blueprint`, blueprintId)
); );
} }
} }
this.value = value;
const results = await Promise.all(relatedPromises); const results = await Promise.all(relatedPromises);
const items: Set<string> = new Set(); const items: Set<string> = new Set();
for (const result of results) { for (const result of results) {
@ -128,7 +164,7 @@ export class HaFilterBlueprints extends LitElement {
} }
fireEvent(this, "data-table-filter-changed", { fireEvent(this, "data-table-filter-changed", {
value, value: this.value,
items: this.type ? items : undefined, items: this.type ? items : undefined,
}); });
} }

View File

@ -41,6 +41,9 @@ export class HaFilterDevices extends LitElement {
if (!this.hasUpdated) { if (!this.hasUpdated) {
loadVirtualizer(); loadVirtualizer();
if (this.value?.length) {
this._findRelated();
}
} }
} }

View File

@ -42,6 +42,9 @@ export class HaFilterEntities extends LitElement {
if (!this.hasUpdated) { if (!this.hasUpdated) {
loadVirtualizer(); loadVirtualizer();
if (this.value?.length) {
this._findRelated();
}
} }
} }
@ -186,15 +189,12 @@ export class HaFilterEntities extends LitElement {
return; return;
} }
const value: string[] = [];
for (const entityId of this.value) { for (const entityId of this.value) {
value.push(entityId);
if (this.type) { if (this.type) {
relatedPromises.push(findRelated(this.hass, "entity", entityId)); relatedPromises.push(findRelated(this.hass, "entity", entityId));
} }
} }
this.value = value;
const results = await Promise.all(relatedPromises); const results = await Promise.all(relatedPromises);
const items: Set<string> = new Set(); const items: Set<string> = new Set();
for (const result of results) { for (const result of results) {
@ -204,7 +204,7 @@ export class HaFilterEntities extends LitElement {
} }
fireEvent(this, "data-table-filter-changed", { fireEvent(this, "data-table-filter-changed", {
value, value: this.value,
items: this.type ? items : undefined, items: this.type ? items : undefined,
}); });
} }

View File

@ -1,7 +1,14 @@
import "@material/mwc-menu/mwc-menu-surface"; import "@material/mwc-menu/mwc-menu-surface";
import { mdiFilterVariantRemove, mdiTextureBox } from "@mdi/js"; import { mdiFilterVariantRemove, mdiTextureBox } from "@mdi/js";
import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; import {
CSSResultGroup,
LitElement,
PropertyValues,
css,
html,
nothing,
} from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
import { repeat } from "lit/directives/repeat"; import { repeat } from "lit/directives/repeat";
@ -42,6 +49,16 @@ export class HaFilterFloorAreas extends SubscribeMixin(LitElement) {
@state() private _floors?: FloorRegistryEntry[]; @state() private _floors?: FloorRegistryEntry[];
public willUpdate(properties: PropertyValues) {
super.willUpdate(properties);
if (!this.hasUpdated) {
if (this.value?.floors?.length || this.value?.areas?.length) {
this._findRelated();
}
}
}
protected render() { protected render() {
const areas = this._areas(this.hass.areas, this._floors); const areas = this._areas(this.hass.areas, this._floors);
@ -190,6 +207,10 @@ export class HaFilterFloorAreas extends SubscribeMixin(LitElement) {
} }
} }
protected firstUpdated() {
this._findRelated();
}
private _expandedWillChange(ev) { private _expandedWillChange(ev) {
this._shouldRender = ev.detail.expanded; this._shouldRender = ev.detail.expanded;
} }

View File

@ -0,0 +1,28 @@
export interface DataTableFilters {
[key: string]: {
value: string[] | { key: string[] } | undefined;
items: Set<string> | undefined;
};
}
export const serializeFilters = (value: DataTableFilters) => {
const serializedValue = {};
Object.entries(value).forEach(([key, val]) => {
serializedValue[key] = {
value: val.value,
items: val.items instanceof Set ? Array.from(val.items) : val.items,
};
});
return serializedValue;
};
export const deserializeFilters = (value: DataTableFilters) => {
const deserializedValue = {};
Object.entries(value).forEach(([key, val]) => {
deserializedValue[key] = {
value: val.value,
items: Array.isArray(val.items) ? new Set(val.items) : val.items,
};
});
return deserializedValue;
};

View File

@ -114,6 +114,11 @@ import { showCategoryRegistryDetailDialog } from "../category/show-dialog-catego
import { configSections } from "../ha-panel-config"; import { configSections } from "../ha-panel-config";
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail"; import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
import { showNewAutomationDialog } from "./show-dialog-new-automation"; import { showNewAutomationDialog } from "./show-dialog-new-automation";
import {
DataTableFilters,
deserializeFilters,
serializeFilters,
} from "../../../data/data_table_filters";
type AutomationItem = AutomationEntity & { type AutomationItem = AutomationEntity & {
name: string; name: string;
@ -140,10 +145,15 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
@state() private _filteredAutomations?: string[] | null; @state() private _filteredAutomations?: string[] | null;
@state() private _filters: Record< @storage({
string, storage: "sessionStorage",
{ value: string[] | undefined; items: Set<string> | undefined } key: "automation-table-filters-full",
> = {}; state: true,
subscribe: false,
serializer: serializeFilters,
deserializer: deserializeFilters,
})
private _filters: DataTableFilters = {};
@state() private _expandedFilter?: string; @state() private _expandedFilter?: string;
@ -916,7 +926,7 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
private _filterChanged(ev) { private _filterChanged(ev) {
const type = ev.target.localName; const type = ev.target.localName;
this._filters[type] = ev.detail; this._filters = { ...this._filters, [type]: ev.detail };
this._applyFilters(); this._applyFilters();
} }
@ -935,7 +945,11 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
items.intersection(filter.items) items.intersection(filter.items)
: new Set([...items].filter((x) => filter.items!.has(x))); : new Set([...items].filter((x) => filter.items!.has(x)));
} }
if (key === "ha-filter-categories" && filter.value?.length) { if (
key === "ha-filter-categories" &&
Array.isArray(filter.value) &&
filter.value.length
) {
const categoryItems: Set<string> = new Set(); const categoryItems: Set<string> = new Set();
this.automations this.automations
.filter( .filter(
@ -956,13 +970,17 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
items.intersection(categoryItems) items.intersection(categoryItems)
: new Set([...items].filter((x) => categoryItems!.has(x))); : new Set([...items].filter((x) => categoryItems!.has(x)));
} }
if (key === "ha-filter-labels" && filter.value?.length) { if (
key === "ha-filter-labels" &&
Array.isArray(filter.value) &&
filter.value.length
) {
const labelItems: Set<string> = new Set(); const labelItems: Set<string> = new Set();
this.automations this.automations
.filter((automation) => .filter((automation) =>
this._entityReg this._entityReg
.find((reg) => reg.entity_id === automation.entity_id) .find((reg) => reg.entity_id === automation.entity_id)
?.labels.some((lbl) => filter.value!.includes(lbl)) ?.labels.some((lbl) => (filter.value as string[]).includes(lbl))
) )
.forEach((automation) => labelItems.add(automation.entity_id)); .forEach((automation) => labelItems.add(automation.entity_id));
if (!items) { if (!items) {

View File

@ -10,6 +10,7 @@ import {
import { import {
CSSResultGroup, CSSResultGroup,
LitElement, LitElement,
PropertyValues,
TemplateResult, TemplateResult,
css, css,
html, html,
@ -85,6 +86,11 @@ import "../integrations/ha-integration-overflow-menu";
import { showAddIntegrationDialog } from "../integrations/show-add-integration-dialog"; import { showAddIntegrationDialog } from "../integrations/show-add-integration-dialog";
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail"; import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
import {
serializeFilters,
deserializeFilters,
DataTableFilters,
} from "../../../data/data_table_filters";
interface DeviceRowData extends DeviceRegistryEntry { interface DeviceRowData extends DeviceRegistryEntry {
device?: DeviceRowData; device?: DeviceRowData;
@ -118,10 +124,15 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
@state() private _filter: string = history.state?.filter || ""; @state() private _filter: string = history.state?.filter || "";
@state() private _filters: Record< @storage({
string, storage: "sessionStorage",
{ value: string[] | undefined; items: Set<string> | undefined } key: "devices-table-filters-full",
> = {}; state: true,
subscribe: false,
serializer: serializeFilters,
deserializer: deserializeFilters,
})
private _filters: DataTableFilters = {};
@state() private _expandedFilter?: string; @state() private _expandedFilter?: string;
@ -180,15 +191,12 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
}, },
]); ]);
firstUpdated() { willUpdate(changedProps: PropertyValues) {
this._filters = { super.willUpdate(changedProps);
"ha-filter-states": { if (!this.hasUpdated) {
value: [],
items: undefined,
},
};
this._setFiltersFromUrl(); this._setFiltersFromUrl();
} }
}
private _setFiltersFromUrl() { private _setFiltersFromUrl() {
if (this._searchParms.has("domain")) { if (this._searchParms.has("domain")) {
@ -196,7 +204,7 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
...this._filters, ...this._filters,
"ha-filter-states": { "ha-filter-states": {
value: [ value: [
...(this._filters["ha-filter-states"]?.value || []), ...((this._filters["ha-filter-states"]?.value as string[]) || []),
"disabled", "disabled",
], ],
items: undefined, items: undefined,
@ -212,7 +220,7 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
...this._filters, ...this._filters,
"ha-filter-states": { "ha-filter-states": {
value: [ value: [
...(this._filters["ha-filter-states"]?.value || []), ...((this._filters["ha-filter-states"]?.value as string[]) || []),
"disabled", "disabled",
], ],
items: undefined, items: undefined,
@ -253,10 +261,7 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
entities: EntityRegistryEntry[], entities: EntityRegistryEntry[],
areas: HomeAssistant["areas"], areas: HomeAssistant["areas"],
manifests: IntegrationManifest[], manifests: IntegrationManifest[],
filters: Record< filters: DataTableFilters,
string,
{ value: string[] | undefined; items: Set<string> | undefined }
>,
localize: LocalizeFunc, localize: LocalizeFunc,
labelReg?: LabelRegistryEntry[] labelReg?: LabelRegistryEntry[]
) => { ) => {
@ -295,15 +300,21 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
const filteredDomains = new Set<string>(); const filteredDomains = new Set<string>();
Object.entries(filters).forEach(([key, filter]) => { Object.entries(filters).forEach(([key, filter]) => {
if (key === "config_entry" && filter.value?.length) { if (
key === "config_entry" &&
Array.isArray(filter.value) &&
filter.value.length
) {
outputDevices = outputDevices.filter((device) => outputDevices = outputDevices.filter((device) =>
device.config_entries.some((entryId) => device.config_entries.some((entryId) =>
filter.value?.includes(entryId) (filter.value as string[]).includes(entryId)
) )
); );
const configEntries = entries.filter( const configEntries = entries.filter(
(entry) => entry.entry_id && filter.value?.includes(entry.entry_id) (entry) =>
entry.entry_id &&
(filter.value as string[]).includes(entry.entry_id)
); );
configEntries.forEach((configEntry) => { configEntries.forEach((configEntry) => {
@ -312,17 +323,31 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
if (configEntries.length === 1) { if (configEntries.length === 1) {
filteredConfigEntry = configEntries[0]; filteredConfigEntry = configEntries[0];
} }
} else if (key === "ha-filter-integrations" && filter.value?.length) { } else if (
key === "ha-filter-integrations" &&
Array.isArray(filter.value) &&
filter.value.length
) {
const entryIds = entries const entryIds = entries
.filter((entry) => filter.value!.includes(entry.domain)) .filter((entry) =>
(filter.value as string[]).includes(entry.domain)
)
.map((entry) => entry.entry_id); .map((entry) => entry.entry_id);
outputDevices = outputDevices.filter((device) => outputDevices = outputDevices.filter((device) =>
device.config_entries.some((entryId) => entryIds.includes(entryId)) device.config_entries.some((entryId) => entryIds.includes(entryId))
); );
filter.value!.forEach((domain) => filteredDomains.add(domain)); (filter.value as string[]).forEach((domain) =>
} else if (key === "ha-filter-labels" && filter.value?.length) { filteredDomains.add(domain)
);
} else if (
key === "ha-filter-labels" &&
Array.isArray(filter.value) &&
filter.value.length
) {
outputDevices = outputDevices.filter((device) => outputDevices = outputDevices.filter((device) =>
device.labels.some((lbl) => filter.value!.includes(lbl)) device.labels.some((lbl) =>
(filter.value as string[]).includes(lbl)
)
); );
} else if (filter.items) { } else if (filter.items) {
outputDevices = outputDevices.filter((device) => outputDevices = outputDevices.filter((device) =>
@ -331,7 +356,9 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
} }
}); });
const stateFilters = filters["ha-filter-states"]?.value; const stateFilters = filters["ha-filter-states"]?.value as
| string[]
| undefined;
const showDisabled = const showDisabled =
stateFilters?.length && stateFilters.includes("disabled"); stateFilters?.length && stateFilters.includes("disabled");
@ -698,7 +725,8 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
> >
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon> <ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
</ha-fab> </ha-fab>
${this._filters.config_entry?.value?.length ${Array.isArray(this._filters.config_entry?.value) &&
this._filters.config_entry?.value.length
? html`<ha-alert slot="filter-pane"> ? html`<ha-alert slot="filter-pane">
Filtering by config entry Filtering by config entry
${this.entries?.find( ${this.entries?.find(

View File

@ -98,6 +98,11 @@ import { configSections } from "../ha-panel-config";
import "../integrations/ha-integration-overflow-menu"; import "../integrations/ha-integration-overflow-menu";
import { showAddIntegrationDialog } from "../integrations/show-add-integration-dialog"; import { showAddIntegrationDialog } from "../integrations/show-add-integration-dialog";
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail"; import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
import {
serializeFilters,
deserializeFilters,
DataTableFilters,
} from "../../../data/data_table_filters";
export interface StateEntity export interface StateEntity
extends Omit<EntityRegistryEntry, "id" | "unique_id"> { extends Omit<EntityRegistryEntry, "id" | "unique_id"> {
@ -143,10 +148,15 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
@state() private _searchParms = new URLSearchParams(window.location.search); @state() private _searchParms = new URLSearchParams(window.location.search);
@state() private _filters: Record< @storage({
string, storage: "sessionStorage",
{ value: string[] | undefined; items: Set<string> | undefined } key: "entities-table-filters-full",
> = {}; state: true,
subscribe: false,
serializer: serializeFilters,
deserializer: deserializeFilters,
})
private _filters: DataTableFilters = {};
@state() private _selected: string[] = []; @state() private _selected: string[] = [];
@ -414,16 +424,13 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
devices: HomeAssistant["devices"], devices: HomeAssistant["devices"],
areas: HomeAssistant["areas"], areas: HomeAssistant["areas"],
stateEntities: StateEntity[], stateEntities: StateEntity[],
filters: Record< filters: DataTableFilters,
string,
{ value: string[] | undefined; items: Set<string> | undefined }
>,
entries?: ConfigEntry[], entries?: ConfigEntry[],
labelReg?: LabelRegistryEntry[] labelReg?: LabelRegistryEntry[]
) => { ) => {
const result: EntityRow[] = []; const result: EntityRow[] = [];
const stateFilters = filters["ha-filter-states"]?.value; const stateFilters = filters["ha-filter-states"]?.value as string[];
const showEnabled = const showEnabled =
!stateFilters?.length || stateFilters.includes("enabled"); !stateFilters?.length || stateFilters.includes("enabled");
@ -448,11 +455,15 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
const filteredDomains = new Set<string>(); const filteredDomains = new Set<string>();
Object.entries(filters).forEach(([key, filter]) => { Object.entries(filters).forEach(([key, filter]) => {
if (key === "config_entry" && filter.value?.length) { if (
key === "config_entry" &&
Array.isArray(filter.value) &&
filter.value.length
) {
filteredEntities = filteredEntities.filter( filteredEntities = filteredEntities.filter(
(entity) => (entity) =>
entity.config_entry_id && entity.config_entry_id &&
filter.value?.includes(entity.config_entry_id) (filter.value as string[]).includes(entity.config_entry_id)
); );
if (!entries) { if (!entries) {
@ -461,7 +472,9 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
} }
const configEntries = entries.filter( const configEntries = entries.filter(
(entry) => entry.entry_id && filter.value?.includes(entry.entry_id) (entry) =>
entry.entry_id &&
(filter.value as string[]).includes(entry.entry_id)
); );
configEntries.forEach((configEntry) => { configEntries.forEach((configEntry) => {
@ -470,29 +483,45 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
if (configEntries.length === 1) { if (configEntries.length === 1) {
filteredConfigEntry = configEntries[0]; filteredConfigEntry = configEntries[0];
} }
} else if (key === "ha-filter-integrations" && filter.value?.length) { } else if (
key === "ha-filter-integrations" &&
Array.isArray(filter.value) &&
filter.value.length
) {
if (!entries) { if (!entries) {
this._loadConfigEntries(); this._loadConfigEntries();
return; return;
} }
const entryIds = entries const entryIds = entries
.filter((entry) => filter.value!.includes(entry.domain)) .filter((entry) =>
(filter.value as string[]).includes(entry.domain)
)
.map((entry) => entry.entry_id); .map((entry) => entry.entry_id);
filteredEntities = filteredEntities.filter( filteredEntities = filteredEntities.filter(
(entity) => (entity) =>
filter.value?.includes(entity.platform) || (filter.value as string[]).includes(entity.platform) ||
(entity.config_entry_id && (entity.config_entry_id &&
entryIds.includes(entity.config_entry_id)) entryIds.includes(entity.config_entry_id))
); );
filter.value!.forEach((domain) => filteredDomains.add(domain)); filter.value!.forEach((domain) => filteredDomains.add(domain));
} else if (key === "ha-filter-domains" && filter.value?.length) { } else if (
key === "ha-filter-domains" &&
Array.isArray(filter.value) &&
filter.value.length
) {
filteredEntities = filteredEntities.filter((entity) => filteredEntities = filteredEntities.filter((entity) =>
filter.value?.includes(computeDomain(entity.entity_id)) (filter.value as string[]).includes(computeDomain(entity.entity_id))
); );
} else if (key === "ha-filter-labels" && filter.value?.length) { } else if (
key === "ha-filter-labels" &&
Array.isArray(filter.value) &&
filter.value.length
) {
filteredEntities = filteredEntities.filter((entity) => filteredEntities = filteredEntities.filter((entity) =>
entity.labels.some((lbl) => filter.value!.includes(lbl)) entity.labels.some((lbl) =>
(filter.value as string[]).includes(lbl)
)
); );
} else if (filter.items) { } else if (filter.items) {
filteredEntities = filteredEntities.filter((entity) => filteredEntities = filteredEntities.filter((entity) =>
@ -811,7 +840,8 @@ ${
</ha-button-menu-new> </ha-button-menu-new>
${ ${
this._filters.config_entry?.value?.length Array.isArray(this._filters.config_entry?.value) &&
this._filters.config_entry?.value.length
? html`<ha-alert slot="filter-pane"> ? html`<ha-alert slot="filter-pane">
Filtering by config entry Filtering by config entry
${this._entries?.find( ${this._entries?.find(
@ -913,6 +943,9 @@ ${
} }
protected firstUpdated() { protected firstUpdated() {
if (Object.keys(this._filters).length) {
return;
}
this._filters = { this._filters = {
"ha-filter-states": { "ha-filter-states": {
value: ["enabled"], value: ["enabled"],

View File

@ -96,6 +96,11 @@ import "../integrations/ha-integration-overflow-menu";
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail"; import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
import { isHelperDomain } from "./const"; import { isHelperDomain } from "./const";
import { showHelperDetailDialog } from "./show-dialog-helper-detail"; import { showHelperDetailDialog } from "./show-dialog-helper-detail";
import {
serializeFilters,
deserializeFilters,
DataTableFilters,
} from "../../../data/data_table_filters";
type HelperItem = { type HelperItem = {
id: string; id: string;
@ -164,10 +169,15 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
@state() private _activeFilters?: string[]; @state() private _activeFilters?: string[];
@state() private _filters: Record< @storage({
string, storage: "sessionStorage",
{ value: string[] | undefined; items: Set<string> | undefined } key: "helpers-table-filters-full",
> = {}; state: true,
subscribe: false,
serializer: serializeFilters,
deserializer: deserializeFilters,
})
private _filters: DataTableFilters = {};
@state() private _expandedFilter?: string; @state() private _expandedFilter?: string;
@ -722,7 +732,7 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
private _filterChanged(ev) { private _filterChanged(ev) {
const type = ev.target.localName; const type = ev.target.localName;
this._filters[type] = ev.detail; this._filters = { ...this._filters, [type]: ev.detail };
this._applyFilters(); this._applyFilters();
} }
@ -741,13 +751,17 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
items.intersection(filter.items) items.intersection(filter.items)
: new Set([...items].filter((x) => filter.items!.has(x))); : new Set([...items].filter((x) => filter.items!.has(x)));
} }
if (key === "ha-filter-labels" && filter.value?.length) { if (
key === "ha-filter-labels" &&
Array.isArray(filter.value) &&
filter.value.length
) {
const labelItems: Set<string> = new Set(); const labelItems: Set<string> = new Set();
this._stateItems this._stateItems
.filter((stateItem) => .filter((stateItem) =>
this._entityReg this._entityReg
.find((reg) => reg.entity_id === stateItem.entity_id) .find((reg) => reg.entity_id === stateItem.entity_id)
?.labels.some((lbl) => filter.value!.includes(lbl)) ?.labels.some((lbl) => (filter.value as string[]).includes(lbl))
) )
.forEach((stateItem) => labelItems.add(stateItem.entity_id)); .forEach((stateItem) => labelItems.add(stateItem.entity_id));
if (!items) { if (!items) {
@ -760,7 +774,11 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
items.intersection(labelItems) items.intersection(labelItems)
: new Set([...items].filter((x) => labelItems!.has(x))); : new Set([...items].filter((x) => labelItems!.has(x)));
} }
if (key === "ha-filter-categories" && filter.value?.length) { if (
key === "ha-filter-categories" &&
Array.isArray(filter.value) &&
filter.value.length
) {
const categoryItems: Set<string> = new Set(); const categoryItems: Set<string> = new Set();
this._stateItems this._stateItems
.filter( .filter(

View File

@ -104,6 +104,11 @@ import { showAssignCategoryDialog } from "../category/show-dialog-assign-categor
import { showCategoryRegistryDetailDialog } from "../category/show-dialog-category-registry-detail"; import { showCategoryRegistryDetailDialog } from "../category/show-dialog-category-registry-detail";
import { configSections } from "../ha-panel-config"; import { configSections } from "../ha-panel-config";
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail"; import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
import {
serializeFilters,
deserializeFilters,
DataTableFilters,
} from "../../../data/data_table_filters";
type SceneItem = SceneEntity & { type SceneItem = SceneEntity & {
name: string; name: string;
@ -132,10 +137,15 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
@state() private _filteredScenes?: string[] | null; @state() private _filteredScenes?: string[] | null;
@state() private _filters: Record< @storage({
string, storage: "sessionStorage",
{ value: string[] | undefined; items: Set<string> | undefined } key: "scene-table-filters-full",
> = {}; state: true,
subscribe: false,
serializer: serializeFilters,
deserializer: deserializeFilters,
})
private _filters: DataTableFilters = {};
@state() private _expandedFilter?: string; @state() private _expandedFilter?: string;
@ -783,7 +793,7 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
private _filterChanged(ev) { private _filterChanged(ev) {
const type = ev.target.localName; const type = ev.target.localName;
this._filters[type] = ev.detail; this._filters = { ...this._filters, [type]: ev.detail };
this._applyFilters(); this._applyFilters();
} }
@ -802,7 +812,11 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
items.intersection(filter.items) items.intersection(filter.items)
: new Set([...items].filter((x) => filter.items!.has(x))); : new Set([...items].filter((x) => filter.items!.has(x)));
} }
if (key === "ha-filter-categories" && filter.value?.length) { if (
key === "ha-filter-categories" &&
Array.isArray(filter.value) &&
filter.value.length
) {
const categoryItems: Set<string> = new Set(); const categoryItems: Set<string> = new Set();
this.scenes this.scenes
.filter( .filter(
@ -822,13 +836,17 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
items.intersection(categoryItems) items.intersection(categoryItems)
: new Set([...items].filter((x) => categoryItems!.has(x))); : new Set([...items].filter((x) => categoryItems!.has(x)));
} }
if (key === "ha-filter-labels" && filter.value?.length) { if (
key === "ha-filter-labels" &&
Array.isArray(filter.value) &&
filter.value.length
) {
const labelItems: Set<string> = new Set(); const labelItems: Set<string> = new Set();
this.scenes this.scenes
.filter((scene) => .filter((scene) =>
this._entityReg this._entityReg
.find((reg) => reg.entity_id === scene.entity_id) .find((reg) => reg.entity_id === scene.entity_id)
?.labels.some((lbl) => filter.value!.includes(lbl)) ?.labels.some((lbl) => (filter.value as string[]).includes(lbl))
) )
.forEach((scene) => labelItems.add(scene.entity_id)); .forEach((scene) => labelItems.add(scene.entity_id));
if (!items) { if (!items) {

View File

@ -106,6 +106,11 @@ import { showAssignCategoryDialog } from "../category/show-dialog-assign-categor
import { showCategoryRegistryDetailDialog } from "../category/show-dialog-category-registry-detail"; import { showCategoryRegistryDetailDialog } from "../category/show-dialog-category-registry-detail";
import { configSections } from "../ha-panel-config"; import { configSections } from "../ha-panel-config";
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail"; import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
import {
serializeFilters,
deserializeFilters,
DataTableFilters,
} from "../../../data/data_table_filters";
type ScriptItem = ScriptEntity & { type ScriptItem = ScriptEntity & {
name: string; name: string;
@ -136,10 +141,15 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
@state() private _filteredScripts?: string[] | null; @state() private _filteredScripts?: string[] | null;
@state() private _filters: Record< @storage({
string, storage: "sessionStorage",
{ value: string[] | undefined; items: Set<string> | undefined } key: "script-table-filters-full",
> = {}; state: true,
subscribe: false,
serializer: serializeFilters,
deserializer: deserializeFilters,
})
private _filters: DataTableFilters = {};
@state() private _expandedFilter?: string; @state() private _expandedFilter?: string;
@ -812,7 +822,7 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
private _filterChanged(ev) { private _filterChanged(ev) {
const type = ev.target.localName; const type = ev.target.localName;
this._filters[type] = ev.detail; this._filters = { ...this._filters, [type]: ev.detail };
this._applyFilters(); this._applyFilters();
} }
@ -836,7 +846,11 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
items.intersection(filter.items) items.intersection(filter.items)
: new Set([...items].filter((x) => filter.items!.has(x))); : new Set([...items].filter((x) => filter.items!.has(x)));
} }
if (key === "ha-filter-categories" && filter.value?.length) { if (
key === "ha-filter-categories" &&
Array.isArray(filter.value) &&
filter.value.length
) {
const categoryItems: Set<string> = new Set(); const categoryItems: Set<string> = new Set();
this.scripts this.scripts
.filter( .filter(
@ -856,13 +870,17 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
items.intersection(categoryItems) items.intersection(categoryItems)
: new Set([...items].filter((x) => categoryItems!.has(x))); : new Set([...items].filter((x) => categoryItems!.has(x)));
} }
if (key === "ha-filter-labels" && filter.value?.length) { if (
key === "ha-filter-labels" &&
Array.isArray(filter.value) &&
filter.value.length
) {
const labelItems: Set<string> = new Set(); const labelItems: Set<string> = new Set();
this.scripts this.scripts
.filter((script) => .filter((script) =>
this._entityReg this._entityReg
.find((reg) => reg.entity_id === script.entity_id) .find((reg) => reg.entity_id === script.entity_id)
?.labels.some((lbl) => filter.value!.includes(lbl)) ?.labels.some((lbl) => (filter.value as string[]).includes(lbl))
) )
.forEach((script) => labelItems.add(script.entity_id)); .forEach((script) => labelItems.add(script.entity_id));
if (!items) { if (!items) {