Skip to content

Commit

Permalink
Merge branch 'dev' into fix/remove-some-deprecated-methods
Browse files Browse the repository at this point in the history
  • Loading branch information
TheoPascoli authored Jan 30, 2025
2 parents d3c01ea + be08fb8 commit 2b33b6f
Show file tree
Hide file tree
Showing 9 changed files with 446 additions and 101 deletions.
2 changes: 1 addition & 1 deletion webapp/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export default [
{
// Includes hooks from 'react-use'
additionalHooks:
"(useSafeMemo|useUpdateEffectOnce|useDeepCompareEffect|useShallowCompareEffect|useCustomCompareEffect)",
"(useSafeMemo|useUpdateEffect|useUpdateEffectOnce|useDeepCompareEffect|useShallowCompareEffect|useCustomCompareEffect)",
},
],
"require-await": "warn", // TODO: switch to "error" when the quantity of warning will be low
Expand Down
121 changes: 65 additions & 56 deletions webapp/src/components/common/DataGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,83 +19,97 @@ import {
type EditListItem,
type GridSelection,
type DataEditorProps,
type GridCell,
} from "@glideapps/glide-data-grid";
import "@glideapps/glide-data-grid/dist/index.css";
import { useCallback, useEffect, useState } from "react";
import { useCallback, useState } from "react";
import { voidFn } from "@/utils/fnUtils";
import { darkTheme } from "./Matrix/styles";
import { useUpdateEffect } from "react-use";

interface StringRowMarkerOptions {
kind: "string" | "clickable-string";
getTitle?: (rowIndex: number) => string;
width?: number;
}

type RowMarkers =
export type RowMarkers =
| NonNullable<DataEditorProps["rowMarkers"]>
| StringRowMarkerOptions["kind"]
| StringRowMarkerOptions;

type RowMarkersOptions = Exclude<RowMarkers, string>;

export interface DataGridProps
extends Omit<DataEditorProps, "rowMarkers" | "onGridSelectionChange" | "gridSelection"> {
export interface DataGridProps extends Omit<DataEditorProps, "rowMarkers" | "gridSelection"> {
rowMarkers?: RowMarkers;
enableColumnResize?: boolean;
}

const ROW_HEIGHT = 30;
const OVERSCROLL = 2;

function isStringRowMarkerOptions(
rowMarkerOptions: RowMarkersOptions,
): rowMarkerOptions is StringRowMarkerOptions {
return rowMarkerOptions.kind === "string" || rowMarkerOptions.kind === "clickable-string";
}

function DataGrid(props: DataGridProps) {
const {
rowMarkers = { kind: "none" },
getCellContent,
columns: columnsFromProps,
onCellEdited,
onCellsEdited,
onColumnResize,
onColumnResizeStart,
onColumnResizeEnd,
enableColumnResize = true,
freezeColumns,
...rest
} = props;

function DataGrid({
rowMarkers = { kind: "none" },
getCellContent,
columns: columnsFromProps,
onCellEdited,
onCellsEdited,
onColumnResize,
onColumnResizeStart,
onColumnResizeEnd,
onGridSelectionChange,
enableColumnResize = true,
freezeColumns,
rows,
...rest
}: DataGridProps) {
const rowMarkersOptions: RowMarkersOptions =
typeof rowMarkers === "string" ? { kind: rowMarkers } : rowMarkers;

const isStringRowMarkers = isStringRowMarkerOptions(rowMarkersOptions);
const adjustedFreezeColumns = isStringRowMarkers ? (freezeColumns || 0) + 1 : freezeColumns;

const [columns, setColumns] = useState(columnsFromProps);
const [selection, setSelection] = useState<GridSelection>({
columns: CompactSelection.empty(),
// Manually calculate the height allows to remove the blank space at the bottom of the grid
// when there is no scrollbar. Header is included in the height calculation.
//! Group header is not included, fix it when needed.
const height = ROW_HEIGHT * (rows + 1) + OVERSCROLL;

const [columns, setColumns] = useState(initColumns);
const [gridSelection, setGridSelection] = useState<GridSelection>({
rows: CompactSelection.empty(),
columns: CompactSelection.empty(),
});

// Add a column for the "string" row markers if needed
useEffect(() => {
setColumns(
isStringRowMarkers ? [{ id: "", title: "" }, ...columnsFromProps] : columnsFromProps,
);
useUpdateEffect(() => {
setColumns(initColumns());
}, [columnsFromProps, isStringRowMarkers]);

////////////////////////////////////////////////////////////////
// Utils
////////////////////////////////////////////////////////////////

function initColumns() {
return isStringRowMarkers
? [{ id: "", title: "", width: rowMarkersOptions.width }, ...columnsFromProps]
: columnsFromProps;
}

const ifElseStringRowMarkers = <R1, R2>(
colIndex: number,
onTrue: () => R1,
onTrue: (options: StringRowMarkerOptions) => R1,
onFalse: (colIndex: number) => R2,
) => {
let adjustedColIndex = colIndex;

if (isStringRowMarkers) {
if (colIndex === 0) {
return onTrue();
return onTrue(rowMarkersOptions);
}

adjustedColIndex = colIndex - 1;
Expand All @@ -118,11 +132,8 @@ function DataGrid(props: DataGridProps) {

return ifElseStringRowMarkers(
colIndex,
() => {
const title =
isStringRowMarkers && rowMarkersOptions.getTitle
? rowMarkersOptions.getTitle(rowIndex)
: `Row ${rowIndex + 1}`;
({ getTitle }) => {
const title = getTitle ? getTitle(rowIndex) : `Row ${rowIndex + 1}`;

return {
kind: GridCellKind.Text,
Expand All @@ -133,7 +144,7 @@ function DataGrid(props: DataGridProps) {
themeOverride: {
bgCell: darkTheme.bgHeader,
},
};
} satisfies GridCell;
},
(adjustedColIndex) => {
return getCellContent([adjustedColIndex, rowIndex]);
Expand Down Expand Up @@ -223,7 +234,7 @@ function DataGrid(props: DataGridProps) {
if (newSelection.current) {
// Select the whole row when clicking on a row marker cell
if (rowMarkersOptions.kind === "clickable-string" && newSelection.current.cell[0] === 0) {
setSelection({
setGridSelection({
...newSelection,
current: undefined,
rows: CompactSelection.fromSingleSelection(newSelection.current.cell[1]),
Expand All @@ -238,28 +249,24 @@ function DataGrid(props: DataGridProps) {
}
}

// Prevent selecting the row marker column
// Select/Deselect all the rows like others row markers when selecting the column
if (newSelection.columns.hasIndex(0)) {
// TODO find a way to have the rows length to select all the rows like other row markers
// setSelection({
// ...newSelection,
// columns: CompactSelection.empty(),
// rows: CompactSelection.fromSingleSelection([
// 0,
// // rowsLength
// ]),
// });

setSelection({
const isSelectedAll = gridSelection.rows.length === rows;

setGridSelection({
...newSelection,
columns: newSelection.columns.remove(0),
columns: CompactSelection.empty(),
rows: isSelectedAll
? CompactSelection.empty()
: CompactSelection.fromSingleSelection([0, rows]),
});

return;
}
}

setSelection(newSelection);
setGridSelection(newSelection);
onGridSelectionChange?.(newSelection);
}
};

Expand All @@ -269,16 +276,18 @@ function DataGrid(props: DataGridProps) {

return (
<DataEditor
groupHeaderHeight={30}
headerHeight={30}
rowHeight={30}
groupHeaderHeight={ROW_HEIGHT}
headerHeight={ROW_HEIGHT}
rowHeight={ROW_HEIGHT}
smoothScrollX
smoothScrollY
overscrollX={2}
overscrollY={2}
overscrollX={OVERSCROLL}
overscrollY={OVERSCROLL}
width="100%"
height={height}
theme={darkTheme}
{...rest}
rows={rows}
columns={columns}
rowMarkers={isStringRowMarkers ? "none" : rowMarkersOptions}
getCellContent={getCellContentWrapper}
Expand All @@ -287,7 +296,7 @@ function DataGrid(props: DataGridProps) {
onColumnResize={handleColumnResize}
onColumnResizeStart={handleColumnResizeStart}
onColumnResizeEnd={handleColumnResizeEnd}
gridSelection={selection}
gridSelection={gridSelection}
onGridSelectionChange={handleGridSelectionChange}
freezeColumns={adjustedFreezeColumns}
/>
Expand Down
59 changes: 40 additions & 19 deletions webapp/src/components/common/DataGridForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
} from "@glideapps/glide-data-grid";
import type { DeepPartial } from "react-hook-form";
import { useCallback, useEffect, useMemo, useState } from "react";
import DataGrid, { type DataGridProps } from "./DataGrid";
import DataGrid, { type DataGridProps, type RowMarkers } from "./DataGrid";
import { Box, Divider, IconButton, setRef, Tooltip, type SxProps, type Theme } from "@mui/material";
import useUndo, { type Actions } from "use-undo";
import UndoIcon from "@mui/icons-material/Undo";
Expand All @@ -36,6 +36,8 @@ import useEnqueueErrorSnackbar from "@/hooks/useEnqueueErrorSnackbar";
import useFormCloseProtection from "@/hooks/useCloseFormSecurity";
import { useUpdateEffect } from "react-use";
import { toError } from "@/utils/fnUtils";
import useSafeMemo from "@/hooks/useSafeMemo";
import { getColumnWidth } from "@/utils/dataGridUtils";

type Data = Record<string, Record<string, string | boolean | number>>;

Expand Down Expand Up @@ -72,7 +74,7 @@ function DataGridForm<TData extends Data>({
columns,
allowedFillDirections = "vertical",
enableColumnResize,
rowMarkers,
rowMarkers: rowMarkersFromProps,
onSubmit,
onSubmitSuccessful,
onStateChange,
Expand All @@ -91,8 +93,6 @@ function DataGridForm<TData extends Data>({
// Deep comparison fix the issue but with big data it can be slow.
const isDirty = savedData !== data;

const rowNames = Object.keys(data);

const formState = useMemo<DataGridFormState>(
() => ({
isDirty,
Expand All @@ -107,16 +107,43 @@ function DataGridForm<TData extends Data>({

useEffect(() => setRef(apiRef, { data, setData, formState }), [apiRef, data, setData, formState]);

// Rows cannot be added or removed, so no dependencies are needed
const rowNames = useSafeMemo(() => Object.keys(defaultData), []);

const columnsWithAdjustedSize = useMemo(
() =>
columns.map((column) => ({
...column,
width: getColumnWidth(column, () =>
rowNames.map((rowName) => defaultData[rowName][column.id].toString()),
),
})),
[columns, defaultData, rowNames],
);

const columnIds = useMemo(() => columns.map((column) => column.id), [columns]);

const rowMarkers = useMemo<RowMarkers>(
() =>
rowMarkersFromProps || {
kind: "clickable-string",
getTitle: (index) => rowNames[index],
width: getColumnWidth({ title: "", id: "" }, () => rowNames),
},
[rowMarkersFromProps, rowNames],
);

////////////////////////////////////////////////////////////////
// Utils
////////////////////////////////////////////////////////////////

const getRowAndColumnNames = (location: Item) => {
const [colIndex, rowIndex] = location;
const columnIds = columns.map((column) => column.id);

return [rowNames[rowIndex], columnIds[colIndex]];
};
const getRowAndColumnNames = useCallback(
(location: Item) => {
const [colIndex, rowIndex] = location;
return [rowNames[rowIndex], columnIds[colIndex]];
},
[rowNames, columnIds],
);

const getDirtyValues = () => {
return rowNames.reduce((acc, rowName) => {
Expand Down Expand Up @@ -190,7 +217,7 @@ function DataGridForm<TData extends Data>({
readonly: true,
};
},
[data, columns],
[data, getRowAndColumnNames],
);

////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -261,15 +288,10 @@ function DataGridForm<TData extends Data>({
>
<DataGrid
getCellContent={getCellContent}
columns={columns}
columns={columnsWithAdjustedSize}
rows={rowNames.length}
onCellsEdited={handleCellsEdited}
rowMarkers={
rowMarkers || {
kind: "clickable-string",
getTitle: (index) => rowNames[index],
}
}
rowMarkers={rowMarkers}
fillHandle
allowedFillDirections={allowedFillDirections}
enableColumnResize={enableColumnResize}
Expand All @@ -283,7 +305,6 @@ function DataGridForm<TData extends Data>({
mt: 1.5,
}}
>
<Divider flexItem />
<Box sx={{ display: "flex" }}>
<LoadingButton
type="submit"
Expand Down
Loading

0 comments on commit 2b33b6f

Please sign in to comment.