Allow the grid to resize with the window width

This commit is contained in:
Robin Townsend 2023-06-17 21:33:54 -04:00
parent 4f582c6ad7
commit afbcea7b66
3 changed files with 109 additions and 13 deletions

View File

@ -45,6 +45,7 @@ import {
cycleTileSize,
appendItems,
tryMoveTile,
resize,
} from "./model";
import { TileWrapper } from "./TileWrapper";
@ -82,8 +83,11 @@ const useGridState = (
}),
};
// Step 2: Backfill gaps left behind by removed tiles
const grid2 = fillGaps(grid1);
// Step 2: Resize the grid if necessary and backfill gaps left behind by
// removed tiles
// Resizing already takes care of backfilling gaps
const grid2 =
columns !== grid1.columns ? resize(grid1, columns!) : fillGaps(grid1);
// Step 3: Add new tiles to the end of the grid
const existingItemIds = new Set(
@ -205,14 +209,10 @@ export const NewVideoGrid: FC<Props> = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [slotGrid, slotGridGeneration, gridBounds]);
const [columns] = useReactiveState<number | null>(
// Since grid resizing isn't implemented yet, pick a column count on mount
// and stick to it
(prevColumns) =>
prevColumns !== undefined && prevColumns !== null
? prevColumns
: // The grid bounds might not be known yet
gridBounds.width === 0
const columns = useMemo(
() =>
// The grid bounds might not be known yet
gridBounds.width === 0
? null
: Math.max(2, Math.floor(gridBounds.width * 0.0045)),
[gridBounds]

View File

@ -418,6 +418,11 @@ export function appendItems(items: TileDescriptor[], g: Grid): Grid {
};
}
const largeTileDimensions = (g: Grid): [number, number] => [
Math.min(3, Math.max(2, g.columns - 1)),
2,
];
/**
* Changes the size of a tile, rearranging the grid to make space.
* @param tileId The ID of the tile to modify.
@ -433,9 +438,7 @@ export function cycleTileSize(tileId: string, g: Grid): Grid {
// The target dimensions, which toggle between 1×1 and larger than 1×1
const [toWidth, toHeight] =
fromWidth === 1 && fromHeight === 1
? [Math.min(3, Math.max(2, g.columns - 1)), 2]
: [1, 1];
fromWidth === 1 && fromHeight === 1 ? largeTileDimensions(g) : [1, 1];
// If we're expanding the tile, we want to create enough new rows at the
// tile's target position such that every new unit of grid area created during
@ -547,3 +550,46 @@ export function cycleTileSize(tileId: string, g: Grid): Grid {
// Fill any gaps that remain
return fillGaps(gappyGrid);
}
/**
* Resizes the grid to a new column width.
*/
export function resize(g: Grid, columns: number): Grid {
const result: Grid = { columns, cells: [] };
const [largeColumns, largeRows] = largeTileDimensions(result);
// Copy each tile from the old grid to the resized one in the same order
// The next index in the result grid to copy a tile to
let next = 0;
for (const cell of g.cells) {
if (cell?.origin) {
const [nextColumns, nextRows] =
cell.columns > 1 || cell.rows > 1 ? [largeColumns, largeRows] : [1, 1];
// If there isn't enough space left on this row, jump to the next row
if (columns - column(next, result) < nextColumns)
next = columns * (Math.floor(next / columns) + 1);
const nextEnd = areaEnd(next, nextColumns, nextRows, result);
// Expand the cells array as necessary
if (result.cells.length <= nextEnd)
result.cells.push(...new Array(nextEnd + 1 - result.cells.length));
// Copy the tile into place
forEachCellInArea(next, nextEnd, result, (_c, i) => {
result.cells[i] = {
item: cell.item,
origin: i === next,
columns: nextColumns,
rows: nextRows,
};
});
next = nextEnd + 1;
}
}
return fillGaps(result);
}

View File

@ -21,6 +21,7 @@ import {
fillGaps,
forEachCellInArea,
Grid,
resize,
row,
tryMoveTile,
} from "../../src/video-grid/model";
@ -386,3 +387,52 @@ abb
ccd
cce`
);
function testResize(
title: string,
columns: number,
input: string,
output: string
): void {
test(`resize ${title}`, () => {
expect(showGrid(resize(mkGrid(input), columns))).toBe(output);
});
}
testResize(
"contracts the grid",
2,
`
abbb
cbbb
ddde
dddf
gh`,
`
af
bb
bb
ch
dd
dd
eg`
);
testResize(
"expands the grid",
4,
`
af
bb
bb
ch
dd
dd
eg`,
`
bbbc
bbbf
addd
hddd
ge`
);