Document the component
This commit is contained in:
parent
8d46687a54
commit
d852e33413
1 changed files with 60 additions and 42 deletions
|
@ -148,18 +148,27 @@ interface DragState {
|
|||
cursorY: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* An interactive, animated grid of video tiles.
|
||||
*/
|
||||
export const NewVideoGrid: FC<Props> = ({
|
||||
items,
|
||||
disableAnimations,
|
||||
children,
|
||||
}) => {
|
||||
// Overview: This component lays out tiles by rendering an invisible template
|
||||
// grid of "slots" for tiles to go in. Once rendered, it uses the DOM API to
|
||||
// get the dimensions of each slot, feeding these numbers back into
|
||||
// react-spring to let the actual tiles move freely atop the template.
|
||||
|
||||
// To know when the rendered grid becomes consistent with the layout we've
|
||||
// requested, we give it a data-generation attribute which holds the ID of the
|
||||
// most recently rendered generation of the grid, and watch it with a
|
||||
// MutationObserver.
|
||||
|
||||
const [slotGrid, setSlotGrid] = useState<HTMLDivElement | null>(null);
|
||||
const [slotGridGeneration, setSlotGridGeneration] = useState(0);
|
||||
|
||||
const [gridRef1, gridBounds] = useMeasure();
|
||||
const gridRef2 = useRef<HTMLDivElement | null>(null);
|
||||
const gridRef = useMergedRefs(gridRef1, gridRef2);
|
||||
|
||||
useEffect(() => {
|
||||
if (slotGrid !== null) {
|
||||
setSlotGridGeneration(
|
||||
|
@ -179,6 +188,10 @@ export const NewVideoGrid: FC<Props> = ({
|
|||
}
|
||||
}, [slotGrid, setSlotGridGeneration]);
|
||||
|
||||
const [gridRef1, gridBounds] = useMeasure();
|
||||
const gridRef2 = useRef<HTMLDivElement | null>(null);
|
||||
const gridRef = useMergedRefs(gridRef1, gridRef2);
|
||||
|
||||
const slotRects = useMemo(() => {
|
||||
if (slotGrid === null) return [];
|
||||
|
||||
|
@ -214,7 +227,7 @@ export const NewVideoGrid: FC<Props> = ({
|
|||
|
||||
const [tiles] = useReactiveState<Tile[]>(
|
||||
(prevTiles) => {
|
||||
// If React hasn't yet rendered the current generation of the layout, skip
|
||||
// If React hasn't yet rendered the current generation of the grid, skip
|
||||
// the update, because grid and slotRects will be out of sync
|
||||
if (slotGridGeneration !== grid?.generation) return prevTiles ?? [];
|
||||
|
||||
|
@ -264,43 +277,6 @@ export const NewVideoGrid: FC<Props> = ({
|
|||
// react-spring's types are bugged and can't infer the spring type
|
||||
) as unknown as [TransitionFn<Tile, TileSpring>, SpringRef<TileSpring>];
|
||||
|
||||
const slotGridStyle = useMemo(() => {
|
||||
if (grid === null) return {};
|
||||
|
||||
const areas = new Array<(number | null)[]>(
|
||||
Math.ceil(grid.cells.length / grid.columns)
|
||||
);
|
||||
for (let i = 0; i < areas.length; i++)
|
||||
areas[i] = new Array<number | null>(grid.columns).fill(null);
|
||||
|
||||
let slotId = 0;
|
||||
for (let i = 0; i < grid.cells.length; i++) {
|
||||
const cell = grid.cells[i];
|
||||
if (cell?.origin) {
|
||||
const slotEnd = i + cell.columns - 1 + grid.columns * (cell.rows - 1);
|
||||
forEachCellInArea(
|
||||
i,
|
||||
slotEnd,
|
||||
grid,
|
||||
(_c, j) => (areas[row(j, grid)][column(j, grid)] = slotId)
|
||||
);
|
||||
slotId++;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
gridTemplateAreas: areas
|
||||
.map(
|
||||
(row) =>
|
||||
`'${row
|
||||
.map((slotId) => (slotId === null ? "." : `s${slotId}`))
|
||||
.join(" ")}'`
|
||||
)
|
||||
.join(" "),
|
||||
gridTemplateColumns: `repeat(${columns}, 1fr)`,
|
||||
};
|
||||
}, [grid, columns]);
|
||||
|
||||
const animateDraggedTile = (endOfGesture: boolean) => {
|
||||
const { tileId, tileX, tileY, cursorX, cursorY } = dragState.current!;
|
||||
const tile = tiles.find((t) => t.item.id === tileId)!;
|
||||
|
@ -357,6 +333,11 @@ export const NewVideoGrid: FC<Props> = ({
|
|||
}
|
||||
};
|
||||
|
||||
// Callback for useDrag. We could call useDrag here, but the default
|
||||
// pattern of spreading {...bind()} across the children to bind the gesture
|
||||
// ends up breaking memoization and ruining this component's performance.
|
||||
// Instead, we pass this callback to each tile via a ref, to let them bind the
|
||||
// gesture using the much more sensible ref-based method.
|
||||
const onTileDrag = (
|
||||
tileId: string,
|
||||
{
|
||||
|
@ -411,6 +392,43 @@ export const NewVideoGrid: FC<Props> = ({
|
|||
{ target: gridRef2 }
|
||||
);
|
||||
|
||||
const slotGridStyle = useMemo(() => {
|
||||
if (grid === null) return {};
|
||||
|
||||
const areas = new Array<(number | null)[]>(
|
||||
Math.ceil(grid.cells.length / grid.columns)
|
||||
);
|
||||
for (let i = 0; i < areas.length; i++)
|
||||
areas[i] = new Array<number | null>(grid.columns).fill(null);
|
||||
|
||||
let slotId = 0;
|
||||
for (let i = 0; i < grid.cells.length; i++) {
|
||||
const cell = grid.cells[i];
|
||||
if (cell?.origin) {
|
||||
const slotEnd = i + cell.columns - 1 + grid.columns * (cell.rows - 1);
|
||||
forEachCellInArea(
|
||||
i,
|
||||
slotEnd,
|
||||
grid,
|
||||
(_c, j) => (areas[row(j, grid)][column(j, grid)] = slotId)
|
||||
);
|
||||
slotId++;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
gridTemplateAreas: areas
|
||||
.map(
|
||||
(row) =>
|
||||
`'${row
|
||||
.map((slotId) => (slotId === null ? "." : `s${slotId}`))
|
||||
.join(" ")}'`
|
||||
)
|
||||
.join(" "),
|
||||
gridTemplateColumns: `repeat(${columns}, 1fr)`,
|
||||
};
|
||||
}, [grid, columns]);
|
||||
|
||||
const slots = useMemo(() => {
|
||||
const slots = new Array<ReactNode>(items.length);
|
||||
for (let i = 0; i < items.length; i++)
|
||||
|
|
Loading…
Reference in a new issue