mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
allow to resize & restore table column sizes after reload
Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
parent
0765bcee9c
commit
85ddcb47b4
@ -190,7 +190,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
||||
}
|
||||
|
||||
@computed get items() {
|
||||
const {filters, filterCallbacks } = this;
|
||||
const { filters, filterCallbacks } = this;
|
||||
const filterGroups = groupBy<Filter>(filters, ({ type }) => type);
|
||||
|
||||
const filterItems: ItemsFilter[] = [];
|
||||
@ -212,7 +212,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
||||
getRow(uid: string) {
|
||||
const {
|
||||
isSelectable, renderTableHeader, renderTableContents, renderItemMenu,
|
||||
store, hasDetailsView, onDetails,
|
||||
store, hasDetailsView, onDetails, tableId,
|
||||
copyClassNameFromHeadCells, customizeTableRowProps, detailsItem,
|
||||
} = this.props;
|
||||
const { isSelected } = store;
|
||||
@ -241,14 +241,21 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
||||
{
|
||||
renderTableContents(item).map((content, index) => {
|
||||
const cellProps: TableCellProps = isReactNode(content) ? { children: content } : content;
|
||||
const headCell = renderTableHeader?.[index];
|
||||
const headCell: TableCellProps = renderTableHeader?.[index];
|
||||
|
||||
if (copyClassNameFromHeadCells && headCell) {
|
||||
cellProps.className = cssNames(cellProps.className, headCell.className);
|
||||
}
|
||||
|
||||
if (!headCell || !this.isHiddenColumn(headCell)) {
|
||||
return <TableCell key={index} {...cellProps} />;
|
||||
return (
|
||||
<TableCell
|
||||
key={index}
|
||||
tableId={tableId}
|
||||
storageId={headCell.id ?? headCell.storageId}
|
||||
{...cellProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -381,7 +388,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
||||
}
|
||||
|
||||
renderTableHeader() {
|
||||
const { customizeTableRowProps, renderTableHeader, isSelectable, isConfigurable, store } = this.props;
|
||||
const { customizeTableRowProps, renderTableHeader, isSelectable, isConfigurable, store, tableId } = this.props;
|
||||
|
||||
if (!renderTableHeader) {
|
||||
return;
|
||||
@ -400,7 +407,13 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
||||
)}
|
||||
{renderTableHeader.map((cellProps, index) => {
|
||||
if (!this.isHiddenColumn(cellProps)) {
|
||||
return <TableCell key={cellProps.id ?? index} {...cellProps} />;
|
||||
return (
|
||||
<TableCell
|
||||
key={cellProps.id ?? cellProps.storageId ?? index}
|
||||
storageId={cellProps.storageId}
|
||||
tableId={tableId} {...cellProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})}
|
||||
<TableCell className="menu">
|
||||
@ -413,7 +426,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
||||
renderList() {
|
||||
const {
|
||||
store, hasDetailsView, addRemoveButtons = {}, virtual, sortingCallbacks, detailsItem,
|
||||
tableProps = {},
|
||||
tableId, tableProps = {},
|
||||
} = this.props;
|
||||
const { isReady, removeItemsDialog, items } = this;
|
||||
const { selectedItems } = store;
|
||||
@ -426,6 +439,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
||||
)}
|
||||
{isReady && (
|
||||
<Table
|
||||
storageKey={tableId}
|
||||
virtual={virtual}
|
||||
selectable={hasDetailsView}
|
||||
sortable={sortingCallbacks}
|
||||
|
||||
@ -19,6 +19,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.resizable {
|
||||
position: relative; // required for resizing-anchor with `position:absolute`
|
||||
|
||||
&.resizing {
|
||||
filter: invert(50%);
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
// fix: don't hide "sortIcon" when used with <TableHead nowrap> and there is not enough space to show all the content
|
||||
&.sorting.nowrap {
|
||||
display: flex;
|
||||
|
||||
@ -2,16 +2,34 @@ import "./table-cell.scss";
|
||||
import type { TableSortBy, TableSortParams } from "./table";
|
||||
|
||||
import React, { ReactNode } from "react";
|
||||
import { computed, observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { autobind, cssNames, displayBooleans } from "../../utils";
|
||||
import { Icon } from "../icon";
|
||||
import { Checkbox } from "../checkbox";
|
||||
import { ResizeDirection, ResizeGrowthDirection, ResizeSide, ResizingAnchor } from "../resizing-anchor";
|
||||
import { getColumnSize, setColumnSize } from "./table.storage";
|
||||
|
||||
export type TableCellElem = React.ReactElement<TableCellProps>;
|
||||
|
||||
export interface TableCellProps extends React.DOMAttributes<HTMLDivElement> {
|
||||
id?: string; // used for configuration visibility of columns
|
||||
id?: string;
|
||||
/**
|
||||
* Used to persist configuration of table: column size, visibility, etc.
|
||||
* Required with props.isResizable={true}
|
||||
*/
|
||||
storageId?: string;
|
||||
/**
|
||||
* Parent table's props.tableId, required with props.storageId
|
||||
*/
|
||||
tableId?: string;
|
||||
className?: string;
|
||||
title?: ReactNode;
|
||||
/**
|
||||
* Allow to resize width and save to local storage, default: true
|
||||
* (applicable only with props.id)
|
||||
*/
|
||||
isResizable?: boolean;
|
||||
checkbox?: boolean; // render cell with a checkbox
|
||||
isChecked?: boolean; // mark checkbox as checked or not
|
||||
renderBoolean?: boolean; // show "true" or "false" for all of the children elements are "typeof boolean"
|
||||
@ -20,9 +38,17 @@ export interface TableCellProps extends React.DOMAttributes<HTMLDivElement> {
|
||||
_sorting?: Partial<TableSortParams>; // <Table> sorting state, don't use this prop outside (!)
|
||||
_sort?(sortBy: TableSortBy): void; // <Table> sort function, don't use this prop outside (!)
|
||||
_nowrap?: boolean; // indicator, might come from parent <TableHead>, don't use this prop outside (!)
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class TableCell extends React.Component<TableCellProps> {
|
||||
private elem: HTMLElement;
|
||||
|
||||
static defaultProps: TableCellProps = {
|
||||
isResizable: true,
|
||||
};
|
||||
|
||||
@autobind()
|
||||
onClick(evt: React.MouseEvent<HTMLDivElement>) {
|
||||
if (this.props.onClick) {
|
||||
@ -60,24 +86,87 @@ export class TableCell extends React.Component<TableCellProps> {
|
||||
const showCheckbox = isChecked !== undefined;
|
||||
|
||||
if (checkbox && showCheckbox) {
|
||||
return <Checkbox value={isChecked} />;
|
||||
return <Checkbox value={isChecked}/>;
|
||||
}
|
||||
}
|
||||
|
||||
@observable isResizing = false;
|
||||
|
||||
@computed get columnId(): string {
|
||||
return this.props.id ?? this.props.storageId;
|
||||
}
|
||||
|
||||
@computed get columnSize(): number {
|
||||
return getColumnSize(this.props.tableId, this.columnId) ?? 0;
|
||||
}
|
||||
|
||||
@computed get isResizable(): boolean {
|
||||
return [
|
||||
this.props.isResizable,
|
||||
this.props.tableId,
|
||||
this.columnId,
|
||||
].every(Boolean);
|
||||
}
|
||||
|
||||
@computed get style(): React.CSSProperties {
|
||||
const styles: React.CSSProperties & Record<string, any> = Object.assign({}, this.props.style);
|
||||
|
||||
if (this.isResizable && this.columnSize) {
|
||||
styles.flexGrow = 0;
|
||||
styles.flexBasis = this.columnSize;
|
||||
}
|
||||
|
||||
return styles;
|
||||
}
|
||||
|
||||
@autobind()
|
||||
onResize(extent: number) {
|
||||
const { tableId } = this.props;
|
||||
const { columnId } = this;
|
||||
const size = extent || this.elem?.offsetWidth;
|
||||
|
||||
// persist state in storage
|
||||
setColumnSize({ tableId, columnId, size });
|
||||
}
|
||||
|
||||
@autobind()
|
||||
bindRef(ref: HTMLElement) {
|
||||
this.elem = ref;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, checkbox, isChecked, sortBy, _sort, _sorting, _nowrap, children, title, renderBoolean: displayBoolean, showWithColumn, ...cellProps } = this.props;
|
||||
const {
|
||||
className, checkbox, isChecked, isResizable, sortBy,
|
||||
_sort, _sorting, _nowrap, children, title, tableId, storageId,
|
||||
renderBoolean: displayBoolean, showWithColumn,
|
||||
...cellProps
|
||||
} = this.props;
|
||||
|
||||
const classNames = cssNames("TableCell", className, {
|
||||
checkbox,
|
||||
nowrap: _nowrap,
|
||||
sorting: this.isSortable,
|
||||
resizing: this.isResizing,
|
||||
resizable: isResizable,
|
||||
});
|
||||
const content = displayBooleans(displayBoolean, title || children);
|
||||
|
||||
return (
|
||||
<div {...cellProps} className={classNames} onClick={this.onClick}>
|
||||
<div {...cellProps} className={classNames} style={this.style} onClick={this.onClick} ref={this.bindRef}>
|
||||
{this.renderCheckbox()}
|
||||
{_nowrap ? <div className="content">{content}</div> : content}
|
||||
{this.renderSortIcon()}
|
||||
{this.isResizable && (
|
||||
<ResizingAnchor
|
||||
direction={ResizeDirection.HORIZONTAL}
|
||||
placement={ResizeSide.TRAILING}
|
||||
growthDirection={ResizeGrowthDirection.LEFT_TO_RIGHT}
|
||||
getCurrentExtent={() => this.columnSize}
|
||||
onStart={() => this.isResizing = true}
|
||||
onEnd={() => this.isResizing = false}
|
||||
onDrag={this.onResize}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
29
src/renderer/components/table/table.storage.ts
Normal file
29
src/renderer/components/table/table.storage.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { createStorage } from "../../utils";
|
||||
|
||||
// TODO: move here `hiddenTableColumns` from user-store.ts
|
||||
export interface TableStorageModel {
|
||||
columnSizes: {
|
||||
[tableId: string]: {
|
||||
[columnId: string]: number;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const tableStorage = createStorage<TableStorageModel>("table_settings", {
|
||||
columnSizes: {},
|
||||
});
|
||||
|
||||
export function getColumnSize(tableId: string, columnId: string): number | undefined {
|
||||
return tableStorage.get().columnSizes[tableId]?.[columnId];
|
||||
}
|
||||
|
||||
export function setColumnSize(params: { tableId: string, columnId: string, size: number }) {
|
||||
const { tableId, columnId, size } = params;
|
||||
|
||||
if (!tableId || !columnId) return;
|
||||
|
||||
tableStorage.merge(draft => {
|
||||
draft.columnSizes[tableId] ??= {};
|
||||
draft.columnSizes[tableId][columnId] = size;
|
||||
});
|
||||
}
|
||||
@ -24,7 +24,10 @@ export interface TableProps extends React.DOMAttributes<HTMLDivElement> {
|
||||
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
|
||||
/**
|
||||
* Unique key for the storage to keep table' user settings and restore on page reload
|
||||
*/
|
||||
storageKey?: string;
|
||||
/**
|
||||
* 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}/>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user