From 9b248c0040a6fd627c2adbc9c8b67e1dea2ab667 Mon Sep 17 00:00:00 2001 From: Steffen Slavetinsky Date: Mon, 18 Sep 2023 15:26:07 +0200 Subject: [PATCH] :bug: Add resizing context in order to account for different column orders --- src/widgets/DataGrid/Cell/HeaderCell.tsx | 20 ++- src/widgets/DataGrid/DataGrid.tsx | 123 +++++------------- src/widgets/DataGrid/HeaderGrid.tsx | 13 +- src/widgets/DataGrid/TableGrid.tsx | 15 ++- .../DataGrid/context/resizeContext.tsx | 117 +++++++++++++++++ 5 files changed, 170 insertions(+), 118 deletions(-) create mode 100644 src/widgets/DataGrid/context/resizeContext.tsx diff --git a/src/widgets/DataGrid/Cell/HeaderCell.tsx b/src/widgets/DataGrid/Cell/HeaderCell.tsx index 28720f3d..ac4a57a6 100644 --- a/src/widgets/DataGrid/Cell/HeaderCell.tsx +++ b/src/widgets/DataGrid/Cell/HeaderCell.tsx @@ -4,7 +4,7 @@ import Tooltip from '../../../components/ui/Tooltip'; import dataformat from '../../../dataformat'; import { useColorTransferFunction } from '../../../hooks/useColorTransferFunction'; import * as React from 'react'; -import { FunctionComponent, useCallback, useMemo } from 'react'; +import { FunctionComponent, useCallback, useContext, useMemo } from 'react'; import { HiChevronDown, HiChevronUp } from 'react-icons/hi'; import type { GridChildComponentProps as CellProps } from 'react-window'; import { Dataset, Sorting, useDataset } from '../../../stores/dataset'; @@ -12,6 +12,7 @@ import tw from 'twin.macro'; import { useColumn } from '../context/columnContext'; import { useSortByColumn } from '../context/sortingContext'; import RelevanceIndicator from '../RelevanceIndicator'; +import { ResizingContext } from '../context/resizeContext'; interface SortingIndicatorProps { sorting?: Sorting; @@ -39,17 +40,14 @@ const stopPropagation: React.MouseEventHandler = (event) => { event.stopPropagation(); }; -type ItemData = { - onStartResize: (columnIndex: number) => void; - resizedIndex?: number; -}; - -type Props = CellProps; +type Props = CellProps; -const HeaderCell: FunctionComponent = ({ data, style, columnIndex }) => { +const HeaderCell: FunctionComponent = ({ style, columnIndex }) => { const column = useColumn(columnIndex); const [columnSorting, sortBy, resetSorting] = useSortByColumn(column.key); + const { startResizing, resizedIndex } = useContext(ResizingContext); + const tags = useDataset(tagsSelector); const tagColorTransferFunction = useColorTransferFunction(tags, { kind: 'str', @@ -130,9 +128,9 @@ const HeaderCell: FunctionComponent = ({ data, style, columnIndex }) => { const onStartResize: React.MouseEventHandler = useCallback( (event) => { stopPropagation(event); - data.onStartResize(columnIndex); + startResizing(columnIndex); }, - [columnIndex, data] + [columnIndex, startResizing] ); return ( @@ -163,7 +161,7 @@ const HeaderCell: FunctionComponent = ({ data, style, columnIndex }) => { onMouseDown={onStartResize} css={[ tw`h-full w-[3px] border-r bg-none hover:border-r-0 hover:bg-gray-400 transition transform cursor-col-resize`, - data.resizedIndex === columnIndex && tw`bg-gray-400 border-r-0`, + resizedIndex.current === columnIndex && tw`bg-gray-400 border-r-0`, ]} /> diff --git a/src/widgets/DataGrid/DataGrid.tsx b/src/widgets/DataGrid/DataGrid.tsx index 6011054f..7ae06aff 100644 --- a/src/widgets/DataGrid/DataGrid.tsx +++ b/src/widgets/DataGrid/DataGrid.tsx @@ -1,7 +1,7 @@ import 'twin.macro'; import TableIcon from '../../icons/Table'; import { Widget } from '../types'; -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useCallback, useMemo, useRef } from 'react'; import AutoSizer from 'react-virtualized-auto-sizer'; import { VariableSizeGrid as Grid } from 'react-window'; import { Dataset, Sorting, useDataset } from '../../stores/dataset'; @@ -16,10 +16,8 @@ import HeaderGrid from './HeaderGrid'; import MenuBar from './MenuBar'; import TableGrid, { Ref as TableGridRef } from './TableGrid'; import GridContextMenu from './GridContextMenu'; -import columnWidthByType from './columnWidthByType'; import { WidgetContainer, WidgetContent } from '../../lib'; - -const MIN_COLUMN_WIDTH = 50; +import { ColumnResizeProvider } from './context/resizeContext'; const headerHeight = 24; @@ -50,73 +48,12 @@ const DataGrid: Widget = () => { 'visibleColumns', defaultVisibleColumnKeys ); - const [columnWidths, persistColumnWidths] = useWidgetConfig>( - 'columnWidths', - allColumns.reduce((acc: Record, column: DataColumn) => { - acc[column.key] = columnWidthByType[column.type.kind]; - return acc; - }, {} as Record) - ); - - const columnWidthsRef = useRef(columnWidths); - - useEffect(() => { - columnWidthsRef.current = columnWidths; - }, [columnWidths]); const resetGridsAfterIndex = useCallback((index: number) => { tableGrid.current?.resetAfterColumnIndex(index); headerGrid.current?.resetAfterColumnIndex(index); }, []); - const [resizedIndex, setResizedIndex] = useState(); - const lastResizePosition = useRef(null); - - const onStartResize = useCallback( - (columnIndex: number) => { - const columnKey = visibleColumns[columnIndex]; - const onMouseMoveWhileResize = (event: MouseEvent) => { - if (lastResizePosition.current !== null) { - const delta = event.clientX - lastResizePosition.current; - if (delta > 0 || delta < 0) { - const oldWidths = columnWidthsRef.current; - const newColumnWidth = Math.max( - MIN_COLUMN_WIDTH, - oldWidths[columnKey] + delta - ); - const newWidths = { - ...oldWidths, - [columnKey]: newColumnWidth, - }; - columnWidthsRef.current = newWidths; - resetGridsAfterIndex(columnIndex); - } - } else { - setResizedIndex(columnIndex); - } - lastResizePosition.current = event.clientX; - }; - - window.addEventListener('mousemove', onMouseMoveWhileResize); - - const onMouseUpWhileResize = () => { - window.removeEventListener('mousemove', onMouseMoveWhileResize); - window.removeEventListener('mouseup', onMouseUpWhileResize); - lastResizePosition.current = null; - setResizedIndex(undefined); - persistColumnWidths(columnWidthsRef.current); - }; - - window.addEventListener('mouseup', onMouseUpWhileResize); - }, - [visibleColumns, resetGridsAfterIndex, persistColumnWidths] - ); - - const columnWidth = useCallback( - (index: number) => columnWidthsRef.current[visibleColumns[index]], - [visibleColumns] - ); - const resetVisibleColumns = useCallback( () => setVisibleColumns(defaultVisibleColumnKeys), [defaultVisibleColumnKeys, setVisibleColumns] @@ -145,36 +82,34 @@ const DataGrid: Widget = () => { setAreOrderedByRelevance={setAreOrderedByRelevance} resetColumns={resetVisibleColumns} > - - - - - {({ width, height }) => ( -
- -
- + + + + + {({ width, height }) => ( +
+ +
+ +
+ -
- - -
- )} - - - +
+
+ )} +
+
+
+ diff --git a/src/widgets/DataGrid/HeaderGrid.tsx b/src/widgets/DataGrid/HeaderGrid.tsx index ccb38225..81f1f1e4 100644 --- a/src/widgets/DataGrid/HeaderGrid.tsx +++ b/src/widgets/DataGrid/HeaderGrid.tsx @@ -1,26 +1,26 @@ import usePrevious from '../../hooks/usePrevious'; import * as React from 'react'; -import { useCallback, useEffect } from 'react'; +import { useCallback, useContext, useEffect } from 'react'; import { VariableSizeGrid as Grid } from 'react-window'; import 'twin.macro'; import HeaderCell from './Cell/HeaderCell'; import { useColumnCount, useVisibleColumns } from './context/columnContext'; +import { ResizingContext } from './context/resizeContext'; interface Props { height: number; width: number; - columnWidth: (index: number) => number; - onStartResize: (columnIndex: number) => void; - resizedIndex?: number; } const HeaderGrid: React.ForwardRefRenderFunction = ( - { height, width, columnWidth, onStartResize, resizedIndex }, + { height, width }, ref ) => { const rowHeight = useCallback(() => height, [height]); const columnCount = useColumnCount(); + const { getColumnWidth } = useContext(ResizingContext); + const [displayedColumns] = useVisibleColumns(); const previousColumnKeys = usePrevious(displayedColumns.map(({ key }) => key)); @@ -39,12 +39,11 @@ const HeaderGrid: React.ForwardRefRenderFunction = ( return ( number; } export type Ref = { @@ -31,7 +32,7 @@ export type Ref = { }; const TableGrid: ForwardRefRenderFunction = ( - { width, height, onScroll, columnWidth }, + { width, height, onScroll }, fwdRef ) => { const ref = useRef(null); @@ -46,6 +47,8 @@ const TableGrid: ForwardRefRenderFunction = ( const [displayedColumns] = useVisibleColumns(); + const { getColumnWidth } = useContext(ResizingContext); + const { getOriginalIndex } = useSort(); const rowCount = useRowCount(); @@ -96,7 +99,7 @@ const TableGrid: ForwardRefRenderFunction = ( height={height} columnCount={columnCount} rowCount={Math.max(1, rowCount)} - columnWidth={columnWidth} + columnWidth={getColumnWidth} estimatedColumnWidth={100} rowHeight={getRowHeight} itemKey={rowCount ? itemKey : undefined} diff --git a/src/widgets/DataGrid/context/resizeContext.tsx b/src/widgets/DataGrid/context/resizeContext.tsx new file mode 100644 index 00000000..cea7dd5d --- /dev/null +++ b/src/widgets/DataGrid/context/resizeContext.tsx @@ -0,0 +1,117 @@ +import * as React from 'react'; +import { MutableRefObject, useCallback, useMemo, useRef } from 'react'; +import type { Dataset } from '../../../types'; +import { useColumns } from './columnContext'; +import columnWidthByType from '../columnWidthByType'; +import { useWidgetConfig } from '../../../hooks'; + +const MIN_COLUMN_WIDTH = 50; +const editCellsSelector = (d: Dataset) => d.editCells; + +type EditingContextState = { + getColumnWidth: (index: number) => number; + startResizing: (columnIndex: number) => void; + resizedIndex: MutableRefObject; +}; + +export const ResizingContext = React.createContext({ + getColumnWidth: () => MIN_COLUMN_WIDTH, + startResizing: () => undefined, + resizedIndex: { current: undefined }, +}); + +interface Props { + children: React.ReactNode; + onResize: (columnIndex: number) => void; +} + +export const ColumnResizeProvider = ({ children, onResize }: Props) => { + const columns = useColumns(); + + const defaultColumnWidths = useMemo( + () => + columns.reduce((acc, column) => { + acc[column.key] = columnWidthByType[column.type.kind]; + return acc; + }, {} as Record), + [columns] + ); + + const resizedIndex = useRef(); + const lastResizePosition = useRef(null); + + const [columnWidths, _persistColumnWidths] = useWidgetConfig< + Record + >('dataGridColumnWidths', {}); + + const columnWidthsRef = useRef>({}); + + const persistColumnWidths = useCallback(() => { + _persistColumnWidths((oldWidths) => ({ + ...oldWidths, + ...columnWidthsRef.current, + })); + }, [_persistColumnWidths]); + + const onStartResize = useCallback( + (columnIndex: number) => { + const columnKey = columns[columnIndex].key; + const onMouseMoveWhileResize = (event: MouseEvent) => { + if (lastResizePosition.current !== null) { + const delta = event.clientX - lastResizePosition.current; + if (delta > 0 || delta < 0) { + const currentWidth = + columnWidthsRef.current[columnKey] || + defaultColumnWidths[columnKey] || + MIN_COLUMN_WIDTH; + columnWidthsRef.current[columnKey] = Math.max( + MIN_COLUMN_WIDTH, + currentWidth + delta + ); + onResize(columnIndex); + } + } else { + resizedIndex.current = columnIndex; + } + lastResizePosition.current = event.clientX; + }; + + window.addEventListener('mousemove', onMouseMoveWhileResize); + + const onMouseUpWhileResize = () => { + window.removeEventListener('mousemove', onMouseMoveWhileResize); + window.removeEventListener('mouseup', onMouseUpWhileResize); + lastResizePosition.current = null; + resizedIndex.current = undefined; + persistColumnWidths(); + }; + + window.addEventListener('mouseup', onMouseUpWhileResize); + }, + [columns, defaultColumnWidths, onResize, persistColumnWidths] + ); + + const getColumnWidth = useCallback( + (index: number) => { + const columnKey = columns[index]?.key; + if (columnKey) { + return ( + columnWidthsRef.current[columnKey] || + columnWidths[columnKey] || + defaultColumnWidths[columnKey] || + MIN_COLUMN_WIDTH + ); + } + return 0; + }, + [columns, defaultColumnWidths] + ); + + return ( + + {children} + + ); +};