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() {
|
@computed get items() {
|
||||||
const {filters, filterCallbacks } = this;
|
const { filters, filterCallbacks } = this;
|
||||||
const filterGroups = groupBy<Filter>(filters, ({ type }) => type);
|
const filterGroups = groupBy<Filter>(filters, ({ type }) => type);
|
||||||
|
|
||||||
const filterItems: ItemsFilter[] = [];
|
const filterItems: ItemsFilter[] = [];
|
||||||
@ -212,7 +212,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
|||||||
getRow(uid: string) {
|
getRow(uid: string) {
|
||||||
const {
|
const {
|
||||||
isSelectable, renderTableHeader, renderTableContents, renderItemMenu,
|
isSelectable, renderTableHeader, renderTableContents, renderItemMenu,
|
||||||
store, hasDetailsView, onDetails,
|
store, hasDetailsView, onDetails, tableId,
|
||||||
copyClassNameFromHeadCells, customizeTableRowProps, detailsItem,
|
copyClassNameFromHeadCells, customizeTableRowProps, detailsItem,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { isSelected } = store;
|
const { isSelected } = store;
|
||||||
@ -241,14 +241,21 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
|||||||
{
|
{
|
||||||
renderTableContents(item).map((content, index) => {
|
renderTableContents(item).map((content, index) => {
|
||||||
const cellProps: TableCellProps = isReactNode(content) ? { children: content } : content;
|
const cellProps: TableCellProps = isReactNode(content) ? { children: content } : content;
|
||||||
const headCell = renderTableHeader?.[index];
|
const headCell: TableCellProps = renderTableHeader?.[index];
|
||||||
|
|
||||||
if (copyClassNameFromHeadCells && headCell) {
|
if (copyClassNameFromHeadCells && headCell) {
|
||||||
cellProps.className = cssNames(cellProps.className, headCell.className);
|
cellProps.className = cssNames(cellProps.className, headCell.className);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!headCell || !this.isHiddenColumn(headCell)) {
|
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() {
|
renderTableHeader() {
|
||||||
const { customizeTableRowProps, renderTableHeader, isSelectable, isConfigurable, store } = this.props;
|
const { customizeTableRowProps, renderTableHeader, isSelectable, isConfigurable, store, tableId } = this.props;
|
||||||
|
|
||||||
if (!renderTableHeader) {
|
if (!renderTableHeader) {
|
||||||
return;
|
return;
|
||||||
@ -400,7 +407,13 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
|||||||
)}
|
)}
|
||||||
{renderTableHeader.map((cellProps, index) => {
|
{renderTableHeader.map((cellProps, index) => {
|
||||||
if (!this.isHiddenColumn(cellProps)) {
|
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">
|
<TableCell className="menu">
|
||||||
@ -413,7 +426,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
|||||||
renderList() {
|
renderList() {
|
||||||
const {
|
const {
|
||||||
store, hasDetailsView, addRemoveButtons = {}, virtual, sortingCallbacks, detailsItem,
|
store, hasDetailsView, addRemoveButtons = {}, virtual, sortingCallbacks, detailsItem,
|
||||||
tableProps = {},
|
tableId, tableProps = {},
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { isReady, removeItemsDialog, items } = this;
|
const { isReady, removeItemsDialog, items } = this;
|
||||||
const { selectedItems } = store;
|
const { selectedItems } = store;
|
||||||
@ -426,6 +439,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
|||||||
)}
|
)}
|
||||||
{isReady && (
|
{isReady && (
|
||||||
<Table
|
<Table
|
||||||
|
storageKey={tableId}
|
||||||
virtual={virtual}
|
virtual={virtual}
|
||||||
selectable={hasDetailsView}
|
selectable={hasDetailsView}
|
||||||
sortable={sortingCallbacks}
|
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
|
// fix: don't hide "sortIcon" when used with <TableHead nowrap> and there is not enough space to show all the content
|
||||||
&.sorting.nowrap {
|
&.sorting.nowrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@ -2,16 +2,34 @@ import "./table-cell.scss";
|
|||||||
import type { TableSortBy, TableSortParams } from "./table";
|
import type { TableSortBy, TableSortParams } from "./table";
|
||||||
|
|
||||||
import React, { ReactNode } from "react";
|
import React, { ReactNode } from "react";
|
||||||
|
import { computed, observable } from "mobx";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
import { autobind, cssNames, displayBooleans } from "../../utils";
|
import { autobind, cssNames, displayBooleans } from "../../utils";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { Checkbox } from "../checkbox";
|
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 type TableCellElem = React.ReactElement<TableCellProps>;
|
||||||
|
|
||||||
export interface TableCellProps extends React.DOMAttributes<HTMLDivElement> {
|
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;
|
className?: string;
|
||||||
title?: ReactNode;
|
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
|
checkbox?: boolean; // render cell with a checkbox
|
||||||
isChecked?: boolean; // mark checkbox as checked or not
|
isChecked?: boolean; // mark checkbox as checked or not
|
||||||
renderBoolean?: boolean; // show "true" or "false" for all of the children elements are "typeof boolean"
|
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 (!)
|
_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 (!)
|
_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 (!)
|
_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> {
|
export class TableCell extends React.Component<TableCellProps> {
|
||||||
|
private elem: HTMLElement;
|
||||||
|
|
||||||
|
static defaultProps: TableCellProps = {
|
||||||
|
isResizable: true,
|
||||||
|
};
|
||||||
|
|
||||||
@autobind()
|
@autobind()
|
||||||
onClick(evt: React.MouseEvent<HTMLDivElement>) {
|
onClick(evt: React.MouseEvent<HTMLDivElement>) {
|
||||||
if (this.props.onClick) {
|
if (this.props.onClick) {
|
||||||
@ -60,24 +86,87 @@ export class TableCell extends React.Component<TableCellProps> {
|
|||||||
const showCheckbox = isChecked !== undefined;
|
const showCheckbox = isChecked !== undefined;
|
||||||
|
|
||||||
if (checkbox && showCheckbox) {
|
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() {
|
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, {
|
const classNames = cssNames("TableCell", className, {
|
||||||
checkbox,
|
checkbox,
|
||||||
nowrap: _nowrap,
|
nowrap: _nowrap,
|
||||||
sorting: this.isSortable,
|
sorting: this.isSortable,
|
||||||
|
resizing: this.isResizing,
|
||||||
|
resizable: isResizable,
|
||||||
});
|
});
|
||||||
const content = displayBooleans(displayBoolean, title || children);
|
const content = displayBooleans(displayBoolean, title || children);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div {...cellProps} className={classNames} onClick={this.onClick}>
|
<div {...cellProps} className={classNames} style={this.style} onClick={this.onClick} ref={this.bindRef}>
|
||||||
{this.renderCheckbox()}
|
{this.renderCheckbox()}
|
||||||
{_nowrap ? <div className="content">{content}</div> : content}
|
{_nowrap ? <div className="content">{content}</div> : content}
|
||||||
{this.renderSortIcon()}
|
{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>
|
</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)
|
autoSize?: boolean; // Setup auto-sizing for all columns (flex: 1 0)
|
||||||
selectable?: boolean; // Highlight rows on hover
|
selectable?: boolean; // Highlight rows on hover
|
||||||
scrollable?: boolean; // Use scrollbar if content is bigger than parent's height
|
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>
|
* 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}/>
|
* @sortItem argument in the callback is an object, provided in <TableRow sortItem={someColDataItem}/>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user