Skip to content

Commit

Permalink
fix(ui-tablemode): adapt the size of the column with the initial data
Browse files Browse the repository at this point in the history
ANT-1217
  • Loading branch information
skamril committed Jan 28, 2025
1 parent f5a4d9e commit 8b92e53
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 62 deletions.
86 changes: 43 additions & 43 deletions webapp/src/components/common/DataGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,21 @@ 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;
Expand All @@ -48,54 +51,57 @@ function isStringRowMarkerOptions(
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,
onGridSelectionChange,
...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 [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 +124,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 +136,7 @@ function DataGrid(props: DataGridProps) {
themeOverride: {
bgCell: darkTheme.bgHeader,
},
};
} satisfies GridCell;
},
(adjustedColIndex) => {
return getCellContent([adjustedColIndex, rowIndex]);
Expand Down Expand Up @@ -238,20 +241,16 @@ 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
// ]),
// });
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;
Expand Down Expand Up @@ -279,6 +278,7 @@ function DataGrid(props: DataGridProps) {
width="100%"
theme={darkTheme}
{...rest}
rows={rows}
columns={columns}
rowMarkers={isStringRowMarkers ? "none" : rowMarkersOptions}
getCellContent={getCellContentWrapper}
Expand Down
64 changes: 46 additions & 18 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 { measureTextWidth } from "@/utils/domUtils";
import useSafeMemo from "@/hooks/useSafeMemo";

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,49 @@ 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:
"width" in column
? column.width
: Math.max(
measureTextWidth(column.title),
...rowNames.map((rowName) =>
measureTextWidth(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: Math.max(...rowNames.map((name) => measureTextWidth(name))),
},
[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 +223,7 @@ function DataGridForm<TData extends Data>({
readonly: true,
};
},
[data, columns],
[data, getRowAndColumnNames],
);

////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -261,15 +294,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 Down
1 change: 0 additions & 1 deletion webapp/src/components/common/TableMode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ function TableMode<T extends TableModeType>({
return {
title,
id: col,
width: title.length * 10,
} satisfies GridColumn;
}),
);
Expand Down
65 changes: 65 additions & 0 deletions webapp/src/utils/domUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* Copyright (c) 2025, RTE (https://www.rte-france.com)
*
* See AUTHORS.txt
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* SPDX-License-Identifier: MPL-2.0
*
* This file is part of the Antares project.
*/

const CANVAS_CONTEXT = document.createElement("canvas").getContext("2d");
const BODY_FONT = getCssFont();

/**
* Gets the computed style of the given element.
*
* @see https://stackoverflow.com/a/21015393
*
* @param element - The element to get the style from.
* @param prop - The property to get the value of.
* @returns The computed style of the given element.
*/
export function getCssStyle(element: HTMLElement, prop: string) {
return window.getComputedStyle(element, null).getPropertyValue(prop);
}

/**
* Gets the font of the given element, or the `body` element if none is provided.
* The returned value follows the CSS `font` shorthand property format.
*
* @see https://stackoverflow.com/a/21015393
*
* @param element - The element to get the font from.
* @returns The font of the given element.
*/
export function getCssFont(element = document.body) {
const fontWeight = getCssStyle(element, "font-weight") || "normal";
const fontSize = getCssStyle(element, "font-size") || "16px";
const fontFamily = getCssStyle(element, "font-family") || "Arial";

return `${fontWeight} ${fontSize} ${fontFamily}`;
}

/**
* Uses `canvas.measureText` to compute and return the width of the specified text,
* using the specified canvas font if defined (use font from the "body" otherwise).
*
* @see https://stackoverflow.com/a/21015393
*
* @param text - The text to be rendered.
* @param [font] - The CSS font that text is to be rendered with (e.g. "bold 14px Arial").
* If not provided, the font of the `body` element is used.
* @returns The width of the text in pixels.
*/
export function measureTextWidth(text: string, font?: string) {
if (CANVAS_CONTEXT) {
CANVAS_CONTEXT.font = font || BODY_FONT;
return CANVAS_CONTEXT.measureText(text).width;
}
return 0;
}

0 comments on commit 8b92e53

Please sign in to comment.