1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00
lens/dashboard/client/components/table/table.tsx
Jari Kolehmainen 1d0815abd2
Lens app source code (#119)
Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
2020-03-15 09:52:02 +02:00

185 lines
6.4 KiB
TypeScript

import "./table.scss";
import * as React from "react";
import { observer } from "mobx-react";
import { computed, observable } from "mobx";
import { autobind, cssNames, noop } from "../../utils";
import { TableRow, TableRowElem, TableRowProps } from "./table-row";
import { TableHead, TableHeadElem, TableHeadProps } from "./table-head";
import { TableCellElem } from "./table-cell";
import { VirtualList } from "../virtual-list";
import { navigation, setQueryParams } from "../../navigation";
import orderBy from "lodash/orderBy"
import { ItemObject } from "../../item.store";
// todo: refactor + decouple search from location
export type SortBy = string;
export type OrderBy = "asc" | "desc" | string;
export type SortParams = { sortBy: SortBy; orderBy: OrderBy }
export type SortingCallback<D = any> = (data: D) => string | number | (string | number)[];
export interface TableProps extends React.DOMAttributes<HTMLDivElement> {
items?: ItemObject[]; // Raw items data
className?: string;
autoSize?: boolean; // Setup auto-sizing for all columns (flex: 1 0)
selectable?: boolean; // Highlight rows on hover
scrollable?: boolean; // Use scrollbar if content is bigger than parent's height
storageKey?: string; // Keep some data in localStorage & restore on page reload, e.g sorting params
sortable?: {
// Define sortable callbacks for every column in <TableHead><TableCell sortBy="someCol"><TableHead>
// @sortItem argument in the callback is an object, provided in <TableRow sortItem={someColDataItem}/>
[sortBy: string]: SortingCallback;
};
sortSyncWithUrl?: boolean; // sorting state is managed globally from url params
sortByDefault?: Partial<SortParams>; // default sorting params
onSort?: (params: SortParams) => void; // callback on sort change, default: global sync with url
noItems?: React.ReactNode; // Show no items state table list is empty
selectedItemId?: string; // Allows to scroll list to selected item
virtual?: boolean; // Use virtual list component to render only visible rows
rowPadding?: string;
rowLineHeight?: string;
customRowHeights?: (item: object, lineHeight: number, paddings: number) => number;
getTableRow?: (uid: string) => React.ReactElement<TableRowProps>;
}
@observer
export class Table extends React.Component<TableProps> {
static defaultProps: TableProps = {
scrollable: true,
autoSize: true,
rowPadding: "8px",
rowLineHeight: "17px",
sortSyncWithUrl: true,
}
@observable sortParamsLocal = this.props.sortByDefault;
@computed get sortParams(): Partial<SortParams> {
if (this.props.sortSyncWithUrl) {
const sortBy = navigation.searchParams.get("sortBy")
const orderBy = navigation.searchParams.get("orderBy")
return { sortBy, orderBy };
}
return this.sortParamsLocal || {};
}
renderHead() {
const { sortable, children } = this.props;
const content = React.Children.toArray(children) as (TableRowElem | TableHeadElem)[];
const headElem: React.ReactElement<TableHeadProps> = content.find(elem => elem.type === TableHead);
if (headElem) {
if (sortable) {
const columns = React.Children.toArray(headElem.props.children) as TableCellElem[];
return React.cloneElement(headElem, {
children: columns.map(elem => {
if (elem.props.checkbox) {
return elem;
}
const title = elem.props.title || (
// copy cell content to title if it's a string
// usable if part of TableCell's content is hidden when there is not enough space
typeof elem.props.children === "string" ? elem.props.children : undefined
);
return React.cloneElement(elem, {
title: title,
_sort: this.sort,
_sorting: this.sortParams,
_nowrap: headElem.props.nowrap,
})
})
});
}
return headElem;
}
}
getSorted(items: any[]) {
const { sortParams } = this;
const sortingCallback = this.props.sortable[sortParams.sortBy] || noop;
return orderBy(
items,
sortingCallback,
sortParams.orderBy as any
);
}
@autobind()
protected onSort(params: SortParams) {
const { sortSyncWithUrl, onSort } = this.props;
if (sortSyncWithUrl) {
setQueryParams(params)
}
else {
this.sortParamsLocal = params;
}
if (onSort) {
onSort(params)
}
}
@autobind()
sort(colName: SortBy) {
const { sortBy, orderBy } = this.sortParams;
const sameColumn = sortBy == colName;
const newSortBy: SortBy = colName;
const newOrderBy: OrderBy = (!orderBy || !sameColumn || orderBy === "desc") ? "asc" : "desc";
this.onSort({
sortBy: String(newSortBy),
orderBy: newOrderBy,
});
}
renderRows() {
const { sortable, noItems, children, virtual, customRowHeights, rowLineHeight, rowPadding, items, getTableRow, selectedItemId, className } = this.props;
const content = React.Children.toArray(children) as (TableRowElem | TableHeadElem)[];
let rows: React.ReactElement<TableRowProps>[] = content.filter(elem => elem.type === TableRow);
let sortedItems = rows.length ? rows.map(row => row.props.sortItem) : [...items];
if (sortable) {
sortedItems = this.getSorted(sortedItems);
if (rows.length) {
rows = sortedItems.map(item => rows.find(row => {
return item == row.props.sortItem
}))
}
}
if (!rows.length && !items.length && noItems) {
return noItems;
}
if (virtual) {
const lineHeight = parseFloat(rowLineHeight);
const padding = parseFloat(rowPadding);
let rowHeights: number[] = Array(items.length).fill(lineHeight + padding * 2);
if (customRowHeights) {
rowHeights = sortedItems.map(item => {
return customRowHeights(item, lineHeight, padding * 2);
});
}
return (
<VirtualList
items={sortedItems}
rowHeights={rowHeights}
getTableRow={getTableRow}
selectedItemId={selectedItemId}
className={className}
/>
);
}
return rows;
}
render() {
const { selectable, scrollable, sortable, autoSize, virtual } = this.props;
let { className } = this.props;
className = cssNames("Table flex column", className, {
selectable, scrollable, sortable, autoSize, virtual,
});
return (
<div className={className}>
{this.renderHead()}
{this.renderRows()}
</div>
)
}
}