mirror of
https://github.com/docmost/docmost.git
synced 2026-09-02 20:05:41 +08:00
Two BaseTable instances on the same page (e.g. multiple base embeds in one document) shared the same global jotai atoms for activeViewId, editingCell, property menu state, and row selection. Each instance's useEffect that synced activeViewId would clobber the other's value every render, pinning React into a "Maximum update depth exceeded" loop. Convert every UI atom in base-atoms.ts to an atomFamily keyed by pageId so each base owns its own scope, and thread pageId through the grid component tree (GridRow, GridCell, GridHeaderCell, RowNumberCell, RowNumberHeaderCell, PropertyMenuContent) plus useRowSelection so each consumer reaches the per-base atom. use-base-socket already had pageId in scope; its store.get/store.set calls now resolve through selectedRowIdsAtomFamily(pageId) too.
159 lines
5.2 KiB
TypeScript
159 lines
5.2 KiB
TypeScript
import { memo, useCallback } from "react";
|
|
import { Cell } from "@tanstack/react-table";
|
|
import { useAtom } from "jotai";
|
|
import { IBaseRow, IBaseProperty, EditingCell } from "@/features/base/types/base.types";
|
|
import { editingCellAtomFamily } from "@/features/base/atoms/base-atoms";
|
|
import { isSystemPropertyType } from "@/features/base/hooks/use-base-table";
|
|
import { CellText } from "@/features/base/components/cells/cell-text";
|
|
import { CellNumber } from "@/features/base/components/cells/cell-number";
|
|
import { CellSelect } from "@/features/base/components/cells/cell-select";
|
|
import { CellStatus } from "@/features/base/components/cells/cell-status";
|
|
import { CellMultiSelect } from "@/features/base/components/cells/cell-multi-select";
|
|
import { CellDate } from "@/features/base/components/cells/cell-date";
|
|
import { CellCheckbox } from "@/features/base/components/cells/cell-checkbox";
|
|
import { CellUrl } from "@/features/base/components/cells/cell-url";
|
|
import { CellEmail } from "@/features/base/components/cells/cell-email";
|
|
import { CellPerson } from "@/features/base/components/cells/cell-person";
|
|
import { CellFile } from "@/features/base/components/cells/cell-file";
|
|
import { CellPage } from "@/features/base/components/cells/cell-page";
|
|
import { CellCreatedAt } from "@/features/base/components/cells/cell-created-at";
|
|
import { CellLastEditedAt } from "@/features/base/components/cells/cell-last-edited-at";
|
|
import { CellLastEditedBy } from "@/features/base/components/cells/cell-last-edited-by";
|
|
import { CellFormula } from "@/features/base/components/cells/cell-formula";
|
|
import { RowNumberCell } from "./row-number-cell";
|
|
import classes from "@/features/base/styles/grid.module.css";
|
|
|
|
type CellComponentProps = {
|
|
value: unknown;
|
|
property: IBaseProperty;
|
|
rowId: string;
|
|
isEditing: boolean;
|
|
onCommit: (value: unknown) => void;
|
|
onCancel: () => void;
|
|
};
|
|
|
|
const cellComponents: Record<
|
|
string,
|
|
React.ComponentType<CellComponentProps>
|
|
> = {
|
|
text: CellText,
|
|
number: CellNumber,
|
|
select: CellSelect,
|
|
status: CellStatus,
|
|
multiSelect: CellMultiSelect,
|
|
date: CellDate,
|
|
checkbox: CellCheckbox,
|
|
url: CellUrl,
|
|
email: CellEmail,
|
|
person: CellPerson,
|
|
file: CellFile,
|
|
page: CellPage,
|
|
createdAt: CellCreatedAt,
|
|
lastEditedAt: CellLastEditedAt,
|
|
lastEditedBy: CellLastEditedBy,
|
|
formula: CellFormula,
|
|
};
|
|
|
|
type RowDragProps = {
|
|
draggable: boolean;
|
|
onDragStart: (e: React.DragEvent) => void;
|
|
};
|
|
|
|
type GridCellProps = {
|
|
cell: Cell<IBaseRow, unknown>;
|
|
rowIndex: number;
|
|
onCellUpdate: (rowId: string, propertyId: string, value: unknown) => void;
|
|
rowDragProps?: RowDragProps;
|
|
orderedRowIds?: string[];
|
|
pageId: string;
|
|
};
|
|
|
|
export const GridCell = memo(function GridCell({
|
|
cell,
|
|
rowIndex,
|
|
onCellUpdate,
|
|
rowDragProps,
|
|
orderedRowIds,
|
|
pageId,
|
|
}: GridCellProps) {
|
|
const property = cell.column.columnDef.meta?.property;
|
|
const isRowNumber = cell.column.id === "__row_number";
|
|
const isPinned = cell.column.getIsPinned();
|
|
const pinOffset = isPinned ? cell.column.getStart("left") : undefined;
|
|
|
|
const [editingCell, setEditingCell] = useAtom(editingCellAtomFamily(pageId)) as unknown as [EditingCell, (val: EditingCell) => void];
|
|
|
|
const rowId = cell.row.id;
|
|
const isEditing =
|
|
editingCell?.rowId === rowId &&
|
|
editingCell?.propertyId === property?.id;
|
|
|
|
const handleDoubleClick = useCallback(() => {
|
|
if (!property || isRowNumber) return;
|
|
if (property.type === "checkbox") return;
|
|
if (isSystemPropertyType(property.type)) return;
|
|
setEditingCell({ rowId, propertyId: property.id });
|
|
}, [property, isRowNumber, rowId, setEditingCell]);
|
|
|
|
const handleCommit = useCallback(
|
|
(value: unknown) => {
|
|
if (!property) return;
|
|
const currentValue = cell.getValue();
|
|
const hasChanged = value !== currentValue
|
|
&& !(value === "" && (currentValue === null || currentValue === undefined))
|
|
&& !(value === null && (currentValue === null || currentValue === undefined));
|
|
if (hasChanged) {
|
|
onCellUpdate(rowId, property.id, value);
|
|
}
|
|
setEditingCell(null);
|
|
},
|
|
[property, rowId, cell, onCellUpdate, setEditingCell],
|
|
);
|
|
|
|
const handleCancel = useCallback(() => {
|
|
setEditingCell(null);
|
|
}, [setEditingCell]);
|
|
|
|
if (isRowNumber) {
|
|
return (
|
|
<RowNumberCell
|
|
rowId={rowId}
|
|
rowIndex={rowIndex}
|
|
orderedRowIds={orderedRowIds ?? []}
|
|
isPinned={Boolean(isPinned)}
|
|
pinOffset={pinOffset}
|
|
rowDragProps={rowDragProps}
|
|
pageId={pageId}
|
|
/>
|
|
);
|
|
}
|
|
|
|
if (!property) return null;
|
|
|
|
const CellComponent = cellComponents[property.type];
|
|
if (!CellComponent) return null;
|
|
|
|
const value = cell.getValue();
|
|
|
|
return (
|
|
<div
|
|
className={`${classes.cell} ${isPinned ? classes.cellPinned : ""} ${isEditing ? classes.cellEditing : ""} ${property.isPrimary ? classes.primaryCell : ""}`}
|
|
style={
|
|
isPinned
|
|
? ({ "--pin-offset": `${pinOffset}px` } as React.CSSProperties)
|
|
: undefined
|
|
}
|
|
onDoubleClick={handleDoubleClick}
|
|
>
|
|
<CellComponent
|
|
value={value}
|
|
property={property}
|
|
rowId={rowId}
|
|
isEditing={isEditing}
|
|
onCommit={handleCommit}
|
|
onCancel={handleCancel}
|
|
/>
|
|
</div>
|
|
);
|
|
});
|