mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-28 19:56:42 +00:00
Use filter worker in data-table (#3808)
* Filter worker Doesn't work * Remove template for worker * Move files * Sort to worker + debounce filter * Improve performance * Update ha-data-table.ts
This commit is contained in:
parent
7e979f0cf1
commit
7d09e29d60
@ -1,3 +1,9 @@
|
|||||||
export const afterNextRender = (cb: () => void): void => {
|
export const afterNextRender = (cb: () => void): void => {
|
||||||
requestAnimationFrame(() => setTimeout(cb, 0));
|
requestAnimationFrame(() => setTimeout(cb, 0));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const nextRender = () => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
afterNextRender(resolve);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { repeat } from "lit-html/directives/repeat";
|
import { repeat } from "lit-html/directives/repeat";
|
||||||
|
import deepClone from "deep-clone-simple";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
MDCDataTableAdapter,
|
MDCDataTableAdapter,
|
||||||
MDCDataTableFoundation,
|
MDCDataTableFoundation,
|
||||||
} from "../../mdc-data-table/index"; // Because mdc-data-table published ts files, temporary load them from own repo, outside src so our linters won't complain
|
} from "../../../mdc-data-table/index"; // Because mdc-data-table published ts files, temporary load them from own repo, outside src so our linters won't complain
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BaseElement,
|
BaseElement,
|
||||||
@ -19,14 +20,19 @@ import {
|
|||||||
PropertyValues,
|
PropertyValues,
|
||||||
} from "@material/mwc-base/base-element";
|
} from "@material/mwc-base/base-element";
|
||||||
|
|
||||||
import memoizeOne from "memoize-one";
|
// eslint-disable-next-line import/no-webpack-loader-syntax
|
||||||
|
// @ts-ignore
|
||||||
|
// tslint:disable-next-line: no-implicit-dependencies
|
||||||
|
import sortFilterWorker from "workerize-loader!./sort_filter_worker";
|
||||||
|
|
||||||
import "./ha-icon";
|
import "../ha-icon";
|
||||||
import "../common/search/search-input";
|
import "../../common/search/search-input";
|
||||||
import "./ha-checkbox";
|
import "../ha-checkbox";
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
import { HaCheckbox } from "./ha-checkbox";
|
import { HaCheckbox } from "../ha-checkbox";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import { nextRender } from "../../common/util/render-status";
|
||||||
|
import { debounce } from "../../common/util/debounce";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// for fire event
|
// for fire event
|
||||||
@ -57,13 +63,16 @@ export interface DataTabelColumnContainer {
|
|||||||
[key: string]: DataTabelColumnData;
|
[key: string]: DataTabelColumnData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DataTabelColumnData {
|
export interface DataTabelSortColumnData {
|
||||||
title: string;
|
|
||||||
type?: "numeric";
|
|
||||||
sortable?: boolean;
|
sortable?: boolean;
|
||||||
filterable?: boolean;
|
filterable?: boolean;
|
||||||
filterKey?: string;
|
filterKey?: string;
|
||||||
direction?: SortingDirection;
|
direction?: SortingDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DataTabelColumnData extends DataTabelSortColumnData {
|
||||||
|
title: string;
|
||||||
|
type?: "numeric";
|
||||||
template?: (data: any) => TemplateResult;
|
template?: (data: any) => TemplateResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,93 +98,25 @@ export class HaDataTable extends BaseElement {
|
|||||||
@property({ type: String }) private _filter = "";
|
@property({ type: String }) private _filter = "";
|
||||||
@property({ type: String }) private _sortColumn?: string;
|
@property({ type: String }) private _sortColumn?: string;
|
||||||
@property({ type: String }) private _sortDirection: SortingDirection = null;
|
@property({ type: String }) private _sortDirection: SortingDirection = null;
|
||||||
|
@property({ type: Array }) private _filteredData: DataTabelRowData[] = [];
|
||||||
|
private _sortColumns: {
|
||||||
|
[key: string]: DataTabelSortColumnData;
|
||||||
|
} = {};
|
||||||
|
private curRequest = 0;
|
||||||
|
private _worker: any | undefined;
|
||||||
|
|
||||||
private _filterSortData = memoizeOne(
|
private _debounceSearch = debounce(
|
||||||
(
|
(ev) => {
|
||||||
data: DataTabelRowData[],
|
this._filter = ev.detail.value;
|
||||||
columns: DataTabelColumnContainer,
|
},
|
||||||
filter: string,
|
200,
|
||||||
direction: SortingDirection,
|
false
|
||||||
sortColumn?: string
|
|
||||||
) =>
|
|
||||||
sortColumn
|
|
||||||
? this._memSortData(
|
|
||||||
this._memFilterData(data, columns, filter),
|
|
||||||
columns,
|
|
||||||
direction,
|
|
||||||
sortColumn
|
|
||||||
)
|
|
||||||
: this._memFilterData(data, columns, filter)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
private _memFilterData = memoizeOne(
|
protected firstUpdated() {
|
||||||
(
|
super.firstUpdated();
|
||||||
data: DataTabelRowData[],
|
this._worker = sortFilterWorker();
|
||||||
columns: DataTabelColumnContainer,
|
}
|
||||||
filter: string
|
|
||||||
) => {
|
|
||||||
if (!filter) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
const ucFilter = filter.toUpperCase();
|
|
||||||
return data.filter((row) => {
|
|
||||||
return Object.entries(columns).some((columnEntry) => {
|
|
||||||
const [key, column] = columnEntry;
|
|
||||||
if (column.filterable) {
|
|
||||||
if (
|
|
||||||
(column.filterKey ? row[key][column.filterKey] : row[key])
|
|
||||||
.toUpperCase()
|
|
||||||
.includes(ucFilter)
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
private _memSortData = memoizeOne(
|
|
||||||
(
|
|
||||||
data: DataTabelRowData[],
|
|
||||||
columns: DataTabelColumnContainer,
|
|
||||||
direction: SortingDirection,
|
|
||||||
sortColumn: string
|
|
||||||
) => {
|
|
||||||
const sorted = [...data];
|
|
||||||
const column = columns[sortColumn];
|
|
||||||
return sorted.sort((a, b) => {
|
|
||||||
let sort = 1;
|
|
||||||
if (direction === "desc") {
|
|
||||||
sort = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let valA = column.filterKey
|
|
||||||
? a[sortColumn][column.filterKey]
|
|
||||||
: a[sortColumn];
|
|
||||||
|
|
||||||
let valB = column.filterKey
|
|
||||||
? b[sortColumn][column.filterKey]
|
|
||||||
: b[sortColumn];
|
|
||||||
|
|
||||||
if (typeof valA === "string") {
|
|
||||||
valA = valA.toUpperCase();
|
|
||||||
}
|
|
||||||
if (typeof valB === "string") {
|
|
||||||
valB = valB.toUpperCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (valA < valB) {
|
|
||||||
return sort * -1;
|
|
||||||
}
|
|
||||||
if (valA > valB) {
|
|
||||||
return sort * 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
protected updated(properties: PropertyValues) {
|
protected updated(properties: PropertyValues) {
|
||||||
super.updated(properties);
|
super.updated(properties);
|
||||||
@ -192,6 +133,25 @@ export class HaDataTable extends BaseElement {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const clonedColumns: DataTabelColumnContainer = deepClone(this.columns);
|
||||||
|
Object.values(clonedColumns).forEach((column: DataTabelColumnData) => {
|
||||||
|
delete column.title;
|
||||||
|
delete column.type;
|
||||||
|
delete column.template;
|
||||||
|
});
|
||||||
|
|
||||||
|
this._sortColumns = clonedColumns;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
properties.has("data") ||
|
||||||
|
properties.has("columns") ||
|
||||||
|
properties.has("_filter") ||
|
||||||
|
properties.has("_sortColumn") ||
|
||||||
|
properties.has("_sortDirection")
|
||||||
|
) {
|
||||||
|
this._filterData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,13 +221,7 @@ export class HaDataTable extends BaseElement {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody class="mdc-data-table__content">
|
<tbody class="mdc-data-table__content">
|
||||||
${repeat(
|
${repeat(
|
||||||
this._filterSortData(
|
this._filteredData!,
|
||||||
this.data,
|
|
||||||
this.columns,
|
|
||||||
this._filter,
|
|
||||||
this._sortDirection,
|
|
||||||
this._sortColumn
|
|
||||||
),
|
|
||||||
(row: DataTabelRowData) => row[this.id],
|
(row: DataTabelRowData) => row[this.id],
|
||||||
(row: DataTabelRowData) => html`
|
(row: DataTabelRowData) => html`
|
||||||
<tr
|
<tr
|
||||||
@ -356,6 +310,33 @@ export class HaDataTable extends BaseElement {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _filterData() {
|
||||||
|
const startTime = new Date().getTime();
|
||||||
|
this.curRequest++;
|
||||||
|
const curRequest = this.curRequest;
|
||||||
|
|
||||||
|
const filterProm = this._worker.filterSortData(
|
||||||
|
this.data,
|
||||||
|
this._sortColumns,
|
||||||
|
this._filter,
|
||||||
|
this._sortDirection,
|
||||||
|
this._sortColumn
|
||||||
|
);
|
||||||
|
|
||||||
|
const [data] = await Promise.all([filterProm, nextRender]);
|
||||||
|
|
||||||
|
const curTime = new Date().getTime();
|
||||||
|
const elapsed = curTime - startTime;
|
||||||
|
|
||||||
|
if (elapsed < 100) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 100 - elapsed));
|
||||||
|
}
|
||||||
|
if (this.curRequest !== curRequest) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._filteredData = data;
|
||||||
|
}
|
||||||
|
|
||||||
private _getRowIdAtIndex(rowIndex: number): string {
|
private _getRowIdAtIndex(rowIndex: number): string {
|
||||||
return this.rowElements[rowIndex].getAttribute("data-row-id")!;
|
return this.rowElements[rowIndex].getAttribute("data-row-id")!;
|
||||||
}
|
}
|
||||||
@ -420,7 +401,7 @@ export class HaDataTable extends BaseElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _handleSearchChange(ev: CustomEvent): void {
|
private _handleSearchChange(ev: CustomEvent): void {
|
||||||
this._filter = ev.detail.value;
|
this._debounceSearch(ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResult {
|
static get styles(): CSSResult {
|
107
src/components/data-table/sort_filter_worker.ts
Normal file
107
src/components/data-table/sort_filter_worker.ts
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import {
|
||||||
|
DataTabelColumnContainer,
|
||||||
|
DataTabelColumnData,
|
||||||
|
DataTabelRowData,
|
||||||
|
SortingDirection,
|
||||||
|
} from "./ha-data-table";
|
||||||
|
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
|
||||||
|
export const filterSortData = memoizeOne(
|
||||||
|
async (
|
||||||
|
data: DataTabelRowData[],
|
||||||
|
columns: DataTabelColumnContainer,
|
||||||
|
filter: string,
|
||||||
|
direction: SortingDirection,
|
||||||
|
sortColumn?: string
|
||||||
|
) =>
|
||||||
|
sortColumn
|
||||||
|
? _memSortData(
|
||||||
|
await _memFilterData(data, columns, filter),
|
||||||
|
columns,
|
||||||
|
direction,
|
||||||
|
sortColumn
|
||||||
|
)
|
||||||
|
: _memFilterData(data, columns, filter)
|
||||||
|
);
|
||||||
|
|
||||||
|
const _memFilterData = memoizeOne(
|
||||||
|
async (
|
||||||
|
data: DataTabelRowData[],
|
||||||
|
columns: DataTabelColumnContainer,
|
||||||
|
filter: string
|
||||||
|
) => {
|
||||||
|
if (!filter) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
return filterData(data, columns, filter.toUpperCase());
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const _memSortData = memoizeOne(
|
||||||
|
(
|
||||||
|
data: DataTabelRowData[],
|
||||||
|
columns: DataTabelColumnContainer,
|
||||||
|
direction: SortingDirection,
|
||||||
|
sortColumn: string
|
||||||
|
) => {
|
||||||
|
return sortData(data, columns[sortColumn], direction, sortColumn);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const filterData = (
|
||||||
|
data: DataTabelRowData[],
|
||||||
|
columns: DataTabelColumnContainer,
|
||||||
|
filter: string
|
||||||
|
) =>
|
||||||
|
data.filter((row) => {
|
||||||
|
return Object.entries(columns).some((columnEntry) => {
|
||||||
|
const [key, column] = columnEntry;
|
||||||
|
if (column.filterable) {
|
||||||
|
if (
|
||||||
|
(column.filterKey ? row[key][column.filterKey] : row[key])
|
||||||
|
.toUpperCase()
|
||||||
|
.includes(filter)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export const sortData = (
|
||||||
|
data: DataTabelRowData[],
|
||||||
|
column: DataTabelColumnData,
|
||||||
|
direction: SortingDirection,
|
||||||
|
sortColumn: string
|
||||||
|
) =>
|
||||||
|
data.sort((a, b) => {
|
||||||
|
let sort = 1;
|
||||||
|
if (direction === "desc") {
|
||||||
|
sort = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let valA = column.filterKey
|
||||||
|
? a[sortColumn][column.filterKey]
|
||||||
|
: a[sortColumn];
|
||||||
|
|
||||||
|
let valB = column.filterKey
|
||||||
|
? b[sortColumn][column.filterKey]
|
||||||
|
: b[sortColumn];
|
||||||
|
|
||||||
|
if (typeof valA === "string") {
|
||||||
|
valA = valA.toUpperCase();
|
||||||
|
}
|
||||||
|
if (typeof valB === "string") {
|
||||||
|
valB = valB.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valA < valB) {
|
||||||
|
return sort * -1;
|
||||||
|
}
|
||||||
|
if (valA > valB) {
|
||||||
|
return sort * 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
@ -5,7 +5,7 @@ import "@polymer/paper-item/paper-item";
|
|||||||
import "@polymer/paper-item/paper-item-body";
|
import "@polymer/paper-item/paper-item-body";
|
||||||
|
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import "../../../components/ha-data-table";
|
import "../../../components/data-table/ha-data-table";
|
||||||
import "../../../components/entity/ha-state-icon";
|
import "../../../components/entity/ha-state-icon";
|
||||||
import "../../../layouts/hass-subpage";
|
import "../../../layouts/hass-subpage";
|
||||||
import "../../../resources/ha-style";
|
import "../../../resources/ha-style";
|
||||||
@ -28,7 +28,7 @@ import {
|
|||||||
DataTabelColumnContainer,
|
DataTabelColumnContainer,
|
||||||
RowClickedEvent,
|
RowClickedEvent,
|
||||||
DataTabelRowData,
|
DataTabelRowData,
|
||||||
} from "../../../components/ha-data-table";
|
} from "../../../components/data-table/ha-data-table";
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
import { DeviceRegistryEntry } from "../../../data/device_registry";
|
import { DeviceRegistryEntry } from "../../../data/device_registry";
|
||||||
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||||
|
@ -18,12 +18,12 @@ import "../../../../components/entity/state-badge";
|
|||||||
import "../../../../components/ha-relative-time";
|
import "../../../../components/ha-relative-time";
|
||||||
import "../../../../components/ha-icon";
|
import "../../../../components/ha-icon";
|
||||||
|
|
||||||
import "../../../../components/ha-data-table";
|
import "../../../../components/data-table/ha-data-table";
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
import {
|
import {
|
||||||
SelectionChangedEvent,
|
SelectionChangedEvent,
|
||||||
DataTabelColumnContainer,
|
DataTabelColumnContainer,
|
||||||
} from "../../../../components/ha-data-table";
|
} from "../../../../components/data-table/ha-data-table";
|
||||||
|
|
||||||
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
||||||
import { computeDomain } from "../../../../common/entity/compute_domain";
|
import { computeDomain } from "../../../../common/entity/compute_domain";
|
||||||
|
Loading…
x
Reference in New Issue
Block a user