Reorganize columns sorting and filter systems.
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
parent
519facc608
commit
71b10a3c89
9 changed files with 146 additions and 137 deletions
|
@ -1,4 +1,4 @@
|
||||||
import React, { useEffect, useRef, useState} from "react";
|
import React, {useEffect, useRef, useState} from "react";
|
||||||
import {ColumnKey} from "./Column";
|
import {ColumnKey} from "./Column";
|
||||||
import {normalizeRowDefinition, RowData, RowDefinition} from "./Row";
|
import {normalizeRowDefinition, RowData, RowDefinition} from "./Row";
|
||||||
import {CellDefinition} from "./Cell";
|
import {CellDefinition} from "./Cell";
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React, {useCallback, useContext} from "react";
|
import React, {useCallback, useContext} from "react";
|
||||||
import {Smartable, useTable} from "./Smartable";
|
import {Smartable, useTable} from "./Smartable";
|
||||||
import {ColumnFilter} from "./Columns/ColumnFilter";
|
import {ColumnFilter} from "./Columns/ColumnFilter";
|
||||||
|
import {SortState} from "./Sort";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basic column key type.
|
* Basic column key type.
|
||||||
|
@ -61,31 +62,6 @@ export function createColumn<K extends ColumnKey>(key: K, column: Column): [K, C
|
||||||
return [key, column];
|
return [key, column];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Column sort type.
|
|
||||||
*/
|
|
||||||
export enum SortType
|
|
||||||
{
|
|
||||||
ASC = "asc",
|
|
||||||
DESC = "desc",
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Column sort state.
|
|
||||||
*/
|
|
||||||
export interface SortState
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Sort type (ascending or descending).
|
|
||||||
*/
|
|
||||||
type: SortType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort order.
|
|
||||||
*/
|
|
||||||
order: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table column context data.
|
* Table column context data.
|
||||||
*/
|
*/
|
||||||
|
@ -128,58 +104,6 @@ export function useColumn<CK extends ColumnKey>(smartable?: Smartable<CK>): Colu
|
||||||
return useContext(ColumnContext);
|
return useContext(ColumnContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Hook to get current column filter state.
|
|
||||||
*/
|
|
||||||
export function useFilterState<T = any>(initialValue: T): [T, (newFilterState: T) => void]
|
|
||||||
{
|
|
||||||
// Get current column data.
|
|
||||||
const column = useColumn();
|
|
||||||
// Return filter state array from current column data.
|
|
||||||
return [column.filterState ?? initialValue, column.setFilterState];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default column heading component.
|
|
||||||
*/
|
|
||||||
export function ColumnHeading()
|
|
||||||
{
|
|
||||||
// Get current column data.
|
|
||||||
const {key, column, sortState} = useColumn();
|
|
||||||
|
|
||||||
// Get column sort state setter.
|
|
||||||
const {setColumnSortState} = useTable();
|
|
||||||
|
|
||||||
// Initialize handle click function.
|
|
||||||
const handleClick = useCallback((event: React.MouseEvent) => {
|
|
||||||
if (event.button == 0)
|
|
||||||
{ // Normal click (usually left click).
|
|
||||||
// Toggle sort type.
|
|
||||||
setColumnSortState(key, sortState?.type == SortType.ASC ? SortType.DESC : SortType.ASC);
|
|
||||||
}
|
|
||||||
else if (event.button == 2 || event.button == 1)
|
|
||||||
{ // Alt click (usually right or middle click).
|
|
||||||
// Reset sort type.
|
|
||||||
setColumnSortState(key, null);
|
|
||||||
}
|
|
||||||
}, [key, sortState, setColumnSortState]);
|
|
||||||
|
|
||||||
// Disable context menu function.
|
|
||||||
const disableContextMenu = useCallback((event: React.MouseEvent) => {
|
|
||||||
event.preventDefault();
|
|
||||||
return false;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<th className={sortState?.type ?? undefined} data-sort-order={sortState?.order ?? undefined}
|
|
||||||
onMouseDown={handleClick} onContextMenu={disableContextMenu}>
|
|
||||||
{column.title}
|
|
||||||
|
|
||||||
{sortState?.order && <span className={"order"}>{sortState.order}</span>}
|
|
||||||
</th>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Auto column context provider from table context.
|
* Auto column context provider from table context.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import {useColumn} from "../Column";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Column filter definition.
|
* Column filter definition.
|
||||||
|
@ -18,3 +19,14 @@ export interface ColumnFilter<T = any, FilterState = any>
|
||||||
*/
|
*/
|
||||||
filter: (data: T, filterState: FilterState) => boolean;
|
filter: (data: T, filterState: FilterState) => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to get current column filter state.
|
||||||
|
*/
|
||||||
|
export function useFilterState<T = any>(initialValue: T): [T, (newFilterState: T) => void]
|
||||||
|
{
|
||||||
|
// Get current column data.
|
||||||
|
const column = useColumn();
|
||||||
|
// Return filter state array from current column data.
|
||||||
|
return [column.filterState ?? initialValue, column.setFilterState];
|
||||||
|
}
|
||||||
|
|
45
src/Smartable/Columns/ColumnHeading.tsx
Normal file
45
src/Smartable/Columns/ColumnHeading.tsx
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import React, {useCallback} from "react";
|
||||||
|
import {useColumn} from "../Column";
|
||||||
|
import {useTable} from "../Smartable";
|
||||||
|
import {SortType} from "../Sort";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default column heading component.
|
||||||
|
*/
|
||||||
|
export function ColumnHeading()
|
||||||
|
{
|
||||||
|
// Get current column data.
|
||||||
|
const {key, column, sortState} = useColumn();
|
||||||
|
|
||||||
|
// Get column sort state setter.
|
||||||
|
const {setColumnSortState} = useTable();
|
||||||
|
|
||||||
|
// Initialize handle click function.
|
||||||
|
const handleClick = useCallback((event: React.MouseEvent) => {
|
||||||
|
if (event.button == 0)
|
||||||
|
{ // Normal click (usually left click).
|
||||||
|
// Toggle sort type.
|
||||||
|
setColumnSortState(key, sortState?.type == SortType.ASC ? SortType.DESC : SortType.ASC);
|
||||||
|
}
|
||||||
|
else if (event.button == 2 || event.button == 1)
|
||||||
|
{ // Alt click (usually right or middle click).
|
||||||
|
// Reset sort type.
|
||||||
|
setColumnSortState(key, null);
|
||||||
|
}
|
||||||
|
}, [key, sortState, setColumnSortState]);
|
||||||
|
|
||||||
|
// Disable context menu function.
|
||||||
|
const disableContextMenu = useCallback((event: React.MouseEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
return false;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<th className={sortState?.type ?? undefined} data-sort-order={sortState?.order ?? undefined}
|
||||||
|
onMouseDown={handleClick} onContextMenu={disableContextMenu}>
|
||||||
|
{column.title}
|
||||||
|
|
||||||
|
{sortState?.order && <span className={"order"}>{sortState.order}</span>}
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
import React, {useCallback} from "react";
|
import React, {useCallback} from "react";
|
||||||
import {ColumnFilter} from "../Columns/ColumnFilter";
|
import {ColumnFilter, useFilterState} from "../Columns/ColumnFilter";
|
||||||
import {useFilterState} from "../Column";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter value regex.
|
* Filter value regex.
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import React, {useCallback} from "react";
|
import React, {useCallback} from "react";
|
||||||
import {ColumnFilter} from "../Columns/ColumnFilter";
|
import {ColumnFilter, useFilterState} from "../Columns/ColumnFilter";
|
||||||
import {useFilterState} from "../Column";
|
|
||||||
import {normalizeString} from "@kernelui/core";
|
import {normalizeString} from "@kernelui/core";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import React, {useMemo} from "react";
|
import React, {useMemo} from "react";
|
||||||
import {AutoColumnContextProvider, Column, ColumnHeading, ColumnKey, Columns, SortState, SortType} from "./Column";
|
import {AutoColumnContextProvider, Column, ColumnKey, Columns} from "./Column";
|
||||||
import {SmartableProperties, useTable} from "./Smartable";
|
import {SmartableProperties, useTable} from "./Smartable";
|
||||||
import {RowInstance, RowLoader} from "./Row";
|
import {RowInstance, RowLoader} from "./Row";
|
||||||
import {CurrentRowData, useAsyncManager} from "./AsyncManager";
|
import {useAsyncManager} from "./AsyncManager";
|
||||||
|
import {ColumnHeading} from "./Columns/ColumnHeading";
|
||||||
|
import {sortRows} from "./Sort";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Smartable instance component properties.
|
* Smartable instance component properties.
|
||||||
|
@ -98,55 +100,3 @@ export function TableBody<CK extends ColumnKey>()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort the given rows from the columns sort state.
|
|
||||||
* @param rows Rows to sort.
|
|
||||||
* @param columns Columns definition.
|
|
||||||
* @param columnsSortState Columns current sort state.
|
|
||||||
*/
|
|
||||||
export function sortRows<CK extends ColumnKey>(rows: CurrentRowData<CK>[], columns: Columns<CK>, columnsSortState: Partial<Record<CK, SortState>>): CurrentRowData<CK>[]
|
|
||||||
{
|
|
||||||
// Normalize value to undefined when rows are not loaded.
|
|
||||||
if (!Array.isArray(rows)) return undefined;
|
|
||||||
|
|
||||||
// Get the sort column states following their priority order.
|
|
||||||
const orderedSortColumns = (Object.entries(columnsSortState) as [CK, SortState][]).sort((a, b) => (
|
|
||||||
a[1].order - b[1].order
|
|
||||||
));
|
|
||||||
|
|
||||||
// Sort the given rows.
|
|
||||||
return rows.toSorted((a, b) => {
|
|
||||||
for (const [columnKey, columnSort] of orderedSortColumns)
|
|
||||||
{
|
|
||||||
// Get the current comparison result for the given cells' data.
|
|
||||||
const comparison =
|
|
||||||
(columnSort.type == SortType.DESC ? 1 : -1) *
|
|
||||||
(columns[columnKey]?.compare ?? genericCompare)(a.cells[columnKey]?.data, b.cells[columnKey]?.data);
|
|
||||||
|
|
||||||
if (comparison != 0)
|
|
||||||
// If an order can be determined from the current comparison, returning it.
|
|
||||||
return comparison;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The comparisons always returned 0.
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generic comparison function for table data.
|
|
||||||
* @param a First data to compare.
|
|
||||||
* @param b Second data to compare.
|
|
||||||
*/
|
|
||||||
export function genericCompare(a: any, b: any): number
|
|
||||||
{
|
|
||||||
if (typeof a == "number" && typeof a == typeof b)
|
|
||||||
{ // Compare numbers.
|
|
||||||
return b - a;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{ // Compare data as strings.
|
|
||||||
return String(b).localeCompare(String(a));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import React, {useCallback, useContext, useMemo, useState} from "react";
|
import React, {useCallback, useContext, useMemo, useState} from "react";
|
||||||
import {Instance} from "./Instance";
|
import {Instance} from "./Instance";
|
||||||
import {ColumnKey, Columns, SortState, SortType} from "./Column";
|
import {ColumnKey, Columns} from "./Column";
|
||||||
import {RowDefinition} from "./Row";
|
import {RowDefinition} from "./Row";
|
||||||
import {Promisable} from "@kernelui/core";
|
import {Promisable} from "@kernelui/core";
|
||||||
|
import {SortState, SortType} from "./Sort";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Smartable data type.
|
* Smartable data type.
|
||||||
|
|
79
src/Smartable/Sort.tsx
Normal file
79
src/Smartable/Sort.tsx
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
import {ColumnKey, Columns} from "./Column";
|
||||||
|
import {CurrentRowData} from "./AsyncManager";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Column sort type.
|
||||||
|
*/
|
||||||
|
export enum SortType
|
||||||
|
{
|
||||||
|
ASC = "asc",
|
||||||
|
DESC = "desc",
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Column sort state.
|
||||||
|
*/
|
||||||
|
export interface SortState
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Sort type (ascending or descending).
|
||||||
|
*/
|
||||||
|
type: SortType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort order.
|
||||||
|
*/
|
||||||
|
order: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort the given rows from the columns sort state.
|
||||||
|
* @param rows Rows to sort.
|
||||||
|
* @param columns Columns definition.
|
||||||
|
* @param columnsSortState Columns current sort state.
|
||||||
|
*/
|
||||||
|
export function sortRows<CK extends ColumnKey>(rows: CurrentRowData<CK>[], columns: Columns<CK>, columnsSortState: Partial<Record<CK, SortState>>): CurrentRowData<CK>[]
|
||||||
|
{
|
||||||
|
// Normalize value to undefined when rows are not loaded.
|
||||||
|
if (!Array.isArray(rows)) return undefined;
|
||||||
|
|
||||||
|
// Get the sort column states following their priority order.
|
||||||
|
const orderedSortColumns = (Object.entries(columnsSortState) as [CK, SortState][]).sort((a, b) => (
|
||||||
|
a[1].order - b[1].order
|
||||||
|
));
|
||||||
|
|
||||||
|
// Sort the given rows.
|
||||||
|
return rows.toSorted((a, b) => {
|
||||||
|
for (const [columnKey, columnSort] of orderedSortColumns)
|
||||||
|
{
|
||||||
|
// Get the current comparison result for the given cells' data.
|
||||||
|
const comparison =
|
||||||
|
(columnSort.type == SortType.DESC ? 1 : -1) *
|
||||||
|
(columns[columnKey]?.compare ?? genericCompare)(a.cells[columnKey]?.data, b.cells[columnKey]?.data);
|
||||||
|
|
||||||
|
if (comparison != 0)
|
||||||
|
// If an order can be determined from the current comparison, returning it.
|
||||||
|
return comparison;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The comparisons always returned 0.
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic comparison function for table data.
|
||||||
|
* @param a First data to compare.
|
||||||
|
* @param b Second data to compare.
|
||||||
|
*/
|
||||||
|
export function genericCompare(a: any, b: any): number
|
||||||
|
{
|
||||||
|
if (typeof a == "number" && typeof a == typeof b)
|
||||||
|
{ // Compare numbers.
|
||||||
|
return b - a;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // Compare data as strings.
|
||||||
|
return String(b).localeCompare(String(a));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue