import React from "react";
import {
CompactSelection,
type EditableGridCell,
type GridCell,
GridCellKind,
type GridColumn,
type Rectangle,
type Item,
type CellArray,
} from "../../internal/data-grid/data-grid-types.js";
import { SimpleThemeWrapper } from "../../stories/story-utils.js";
import { DataEditorAll as DataEditor, type DataEditorAllProps as DataEditorProps } from "../../data-editor-all.js";
import range from "lodash/range.js";
import chunk from "lodash/chunk.js";
import type { DataEditorRef } from "../../data-editor/data-editor.js";
import { BeautifulWrapper } from "../../data-editor/stories/utils.js";
import { Description } from "../doc-wrapper.js";
export default {
title: "Glide-Data-Grid/DataEditor Demos",
decorators: [
(Story: React.ComponentType) => (
),
],
};
type RowCallback = (range: Item) => Promise;
type RowToCell = (row: T, col: number) => GridCell;
type RowEditedCallback = (cell: Item, newVal: EditableGridCell, rowData: T) => T | undefined;
function useAsyncData(
pageSize: number,
maxConcurrency: number,
getRowData: RowCallback,
toCell: RowToCell,
onEdited: RowEditedCallback,
gridRef: React.MutableRefObject
): Pick {
pageSize = Math.max(pageSize, 1);
const loadingRef = React.useRef(CompactSelection.empty());
const dataRef = React.useRef([]);
const [visiblePages, setVisiblePages] = React.useState({ x: 0, y: 0, width: 0, height: 0 });
const visiblePagesRef = React.useRef(visiblePages);
visiblePagesRef.current = visiblePages;
const onVisibleRegionChanged: NonNullable = React.useCallback(r => {
setVisiblePages(cv => {
if (r.x === cv.x && r.y === cv.y && r.width === cv.width && r.height === cv.height) return cv;
return r;
});
}, []);
const getCellContent = React.useCallback(
cell => {
const [col, row] = cell;
const rowData: TRowType | undefined = dataRef.current[row];
if (rowData !== undefined) {
return toCell(rowData, col);
}
return {
kind: GridCellKind.Loading,
allowOverlay: false,
};
},
[toCell]
);
const loadPage = React.useCallback(
async (page: number) => {
loadingRef.current = loadingRef.current.add(page);
const startIndex = page * pageSize;
const d = await getRowData([startIndex, (page + 1) * pageSize]);
const vr = visiblePagesRef.current;
const damageList: { cell: [number, number] }[] = [];
const data = dataRef.current;
for (const [i, element] of d.entries()) {
data[i + startIndex] = element;
for (let col = vr.x; col <= vr.x + vr.width; col++) {
damageList.push({
cell: [col, i + startIndex],
});
}
}
gridRef.current?.updateCells(damageList);
},
[getRowData, gridRef, pageSize]
);
const getCellsForSelection = React.useCallback(
(r: Rectangle): (() => Promise) => {
return async () => {
const firstPage = Math.max(0, Math.floor(r.y / pageSize));
const lastPage = Math.floor((r.y + r.height) / pageSize);
for (const pageChunk of chunk(
range(firstPage, lastPage + 1).filter(i => !loadingRef.current.hasIndex(i)),
maxConcurrency
)) {
await Promise.allSettled(pageChunk.map(loadPage));
}
const result: GridCell[][] = [];
for (let y = r.y; y < r.y + r.height; y++) {
const row: GridCell[] = [];
for (let x = r.x; x < r.x + r.width; x++) {
row.push(getCellContent([x, y]));
}
result.push(row);
}
return result;
};
},
[getCellContent, loadPage, maxConcurrency, pageSize]
);
React.useEffect(() => {
const r = visiblePages;
const firstPage = Math.max(0, Math.floor((r.y - pageSize / 2) / pageSize));
const lastPage = Math.floor((r.y + r.height + pageSize / 2) / pageSize);
for (const page of range(firstPage, lastPage + 1)) {
if (loadingRef.current.hasIndex(page)) continue;
void loadPage(page);
}
}, [loadPage, pageSize, visiblePages]);
const onCellEdited = React.useCallback(
(cell: Item, newVal: EditableGridCell) => {
const [, row] = cell;
const current = dataRef.current[row];
if (current === undefined) return;
const result = onEdited(cell, newVal, current);
if (result !== undefined) {
dataRef.current[row] = result;
}
},
[onEdited]
);
return {
getCellContent,
onVisibleRegionChanged,
onCellEdited,
getCellsForSelection,
};
}
export const ServerSideData: React.VFC = () => {
const ref = React.useRef(null);
const getRowData = React.useCallback(async (r: Item) => {
await new Promise(res => setTimeout(res, 300));
return range(r[0], r[1]).map(rowIndex => [`1, ${rowIndex}`, `2, ${rowIndex}`]);
}, []);
const columns = React.useMemo(() => {
return [
{
title: "A",
width: 150,
},
{
title: "B",
width: 200,
},
];
}, []);
const args = useAsyncData(
50,
5,
getRowData,
React.useCallback(
(rowData, col) => ({
kind: GridCellKind.Text,
data: rowData[col],
allowOverlay: true,
displayData: rowData[col],
}),
[]
),
React.useCallback((cell, newVal, rowData) => {
const [col] = cell;
if (newVal.kind !== GridCellKind.Text) return undefined;
const newRow: string[] = [...rowData];
newRow[col] = newVal.data;
return newRow;
}, []),
ref
);
return (
Glide data grid is fully ready to handle your server side data needs. This example condenses the
implementation into a single custom hook and loads in pages of 50. We are using 300ms sleeps, but
network transactions should work the same.
}>
);
};
(ServerSideData as any).parameters = {
options: {
showPanel: false,
},
};