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 {normalizeRowDefinition, RowData, RowDefinition} from "./Row";
|
||||
import {CellDefinition} from "./Cell";
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React, {useCallback, useContext} from "react";
|
||||
import {Smartable, useTable} from "./Smartable";
|
||||
import {ColumnFilter} from "./Columns/ColumnFilter";
|
||||
import {SortState} from "./Sort";
|
||||
|
||||
/**
|
||||
* Basic column key type.
|
||||
|
@ -61,31 +62,6 @@ export function createColumn<K extends ColumnKey>(key: K, column: Column): [K, C
|
|||
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.
|
||||
*/
|
||||
|
@ -128,58 +104,6 @@ export function useColumn<CK extends ColumnKey>(smartable?: Smartable<CK>): Colu
|
|||
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.
|
||||
*/
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React from "react";
|
||||
import {useColumn} from "../Column";
|
||||
|
||||
/**
|
||||
* Column filter definition.
|
||||
|
@ -18,3 +19,14 @@ export interface ColumnFilter<T = any, FilterState = any>
|
|||
*/
|
||||
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 {ColumnFilter} from "../Columns/ColumnFilter";
|
||||
import {useFilterState} from "../Column";
|
||||
import {ColumnFilter, useFilterState} from "../Columns/ColumnFilter";
|
||||
|
||||
/**
|
||||
* Filter value regex.
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React, {useCallback} from "react";
|
||||
import {ColumnFilter} from "../Columns/ColumnFilter";
|
||||
import {useFilterState} from "../Column";
|
||||
import {ColumnFilter, useFilterState} from "../Columns/ColumnFilter";
|
||||
import {normalizeString} from "@kernelui/core";
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
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 {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.
|
||||
|
@ -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 {Instance} from "./Instance";
|
||||
import {ColumnKey, Columns, SortState, SortType} from "./Column";
|
||||
import {ColumnKey, Columns} from "./Column";
|
||||
import {RowDefinition} from "./Row";
|
||||
import {Promisable} from "@kernelui/core";
|
||||
import {SortState, SortType} from "./Sort";
|
||||
|
||||
/**
|
||||
* 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