Implement different column counts and mobile layout

This commit is contained in:
Robin Townsend 2023-02-04 00:43:53 -05:00
parent 206730ffc0
commit 6adcf95aaa
4 changed files with 90 additions and 31 deletions

View file

@ -20,9 +20,10 @@ limitations under the License.
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
min-height: 100%; min-height: 100%;
position: fixed;
height: 100%; height: 100%;
width: 100%; width: 100%;
--footerPadding: 8px;
--footerHeight: calc(50px + 2 * var(--footerPadding));
} }
.centerMessage { .centerMessage {
@ -39,12 +40,27 @@ limitations under the License.
} }
.footer { .footer {
position: relative; position: absolute;
flex-shrink: 0; left: 0;
bottom: 0;
width: 100%;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
height: calc(50px + 2 * 8px); padding: var(--footerPadding) 0;
/* TODO: Un-hardcode these colors */
background: linear-gradient(
360deg,
#15191e 0%,
rgba(21, 25, 30, 0.9) 37%,
rgba(21, 25, 30, 0.8) 49.68%,
rgba(21, 25, 30, 0.7) 56.68%,
rgba(21, 25, 30, 0.427397) 72.92%,
rgba(21, 25, 30, 0.257534) 81.06%,
rgba(21, 25, 30, 0.136986) 87.29%,
rgba(21, 25, 30, 0.0658079) 92.4%,
rgba(21, 25, 30, 0) 100%
);
} }
.footer > * { .footer > * {
@ -75,13 +91,13 @@ limitations under the License.
} }
@media (min-height: 300px) { @media (min-height: 300px) {
.footer { .inRoom {
height: calc(50px + 2 * 24px); --footerPadding: 24px;
} }
} }
@media (min-width: 800px) { @media (min-width: 800px) {
.footer { .inRoom {
height: calc(50px + 2 * 32px); --footerPadding: 32px;
} }
} }

View file

@ -2,20 +2,31 @@
contain: strict; contain: strict;
position: relative; position: relative;
flex-grow: 1; flex-grow: 1;
padding: 0 22px; padding: 0 20px;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
} }
.slotGrid { .slotGrid {
contain: strict;
position: relative; position: relative;
display: grid; display: grid;
grid-auto-rows: 183px; grid-auto-rows: 163px;
column-gap: 18px; gap: 8px;
row-gap: 21px; padding-bottom: var(--footerHeight);
} }
.slot { .slot {
contain: strict; contain: strict;
} }
@media (min-width: 800px) {
.grid {
padding: 0 22px;
}
.slotGrid {
grid-auto-rows: 183px;
column-gap: 18px;
row-gap: 21px;
}
}

View file

@ -290,7 +290,9 @@ const cycleTileSize = (tileId: string, g: Grid): Grid => {
const fromEnd = areaEnd(from, fromWidth, fromHeight, g); const fromEnd = areaEnd(from, fromWidth, fromHeight, g);
const [toWidth, toHeight] = const [toWidth, toHeight] =
fromWidth === 1 && fromHeight === 1 ? [3, 2] : [1, 1]; fromWidth === 1 && fromHeight === 1
? [Math.min(3, Math.max(2, g.columns - 1)), 2]
: [1, 1];
const newRows = Math.max( const newRows = Math.max(
0, 0,
Math.ceil((toWidth * toHeight - fromWidth * fromHeight) / g.columns) Math.ceil((toWidth * toHeight - fromWidth * fromHeight) / g.columns)
@ -308,7 +310,7 @@ const cycleTileSize = (tileId: string, g: Grid): Grid => {
const nextScanLocations = new Set<number>([from]); const nextScanLocations = new Set<number>([from]);
const scanColumnOffset = Math.floor((toWidth - 1) / 2); const scanColumnOffset = Math.floor((toWidth - 1) / 2);
const scanRowOffset = Math.floor((toHeight - 1) / 2); const scanRowOffset = Math.floor((toHeight - 1) / 2);
const rows = row(g.cells.length - 1, g) + 1 const rows = row(g.cells.length - 1, g) + 1;
let to: number | null = null; let to: number | null = null;
const displaceable = (c: Cell | undefined, i: number): boolean => const displaceable = (c: Cell | undefined, i: number): boolean =>
@ -320,7 +322,7 @@ const cycleTileSize = (tileId: string, g: Grid): Grid => {
const start = scanLocation - scanColumnOffset - g.columns * scanRowOffset; const start = scanLocation - scanColumnOffset - g.columns * scanRowOffset;
const end = areaEnd(start, candidateWidth, candidateHeight, g); const end = areaEnd(start, candidateWidth, candidateHeight, g);
const startColumn = column(start, g); const startColumn = column(start, g);
const startRow = row(start, g) const startRow = row(start, g);
const endColumn = column(end, g); const endColumn = column(end, g);
if ( if (
@ -424,8 +426,30 @@ export const NewVideoGrid: FC<Props> = ({
return rects; return rects;
}, [items, slotGridGeneration, slotGrid, gridBounds]); }, [items, slotGridGeneration, slotGrid, gridBounds]);
const [grid, setGrid] = useReactiveState<Grid>( const [columns] = useReactiveState<number | null>(
(prevGrid = { generation: 0, columns: 6, cells: [] }) => { // 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
? null
: Math.max(2, Math.floor(gridBounds.width * 0.0045)),
[gridBounds]
);
const [grid, setGrid] = useReactiveState<Grid | null>(
(prevGrid = null) => {
if (prevGrid === null) {
// We can't do anything if the column count isn't known yet
if (columns === null) {
return null;
} else {
prevGrid = { generation: slotGridGeneration, columns, cells: [] };
}
}
// Step 1: Update tiles that still exist, and remove tiles that have left // Step 1: Update tiles that still exist, and remove tiles that have left
// the grid // the grid
const itemsById = new Map(items.map((i) => [i.id, i])); const itemsById = new Map(items.map((i) => [i.id, i]));
@ -462,18 +486,20 @@ export const NewVideoGrid: FC<Props> = ({
return grid3; return grid3;
}, },
[items] [items, columns]
); );
const [tiles] = useReactiveState<Tile[]>( const [tiles] = useReactiveState<Tile[]>(
(prevTiles) => { (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 layout, skip
// the update, because grid and slotRects will be out of sync // the update, because grid and slotRects will be out of sync
if (slotGridGeneration !== grid.generation) return prevTiles ?? []; if (slotGridGeneration !== grid?.generation) return prevTiles ?? [];
const slotCells = grid.cells.filter((c) => c?.slot) as Cell[]; const slotCells = grid.cells.filter((c) => c?.slot) as Cell[];
const tileRects = new Map<TileDescriptor, Rect>(zipWith(slotCells, slotRects, (cell, rect) => [cell.item, rect])) const tileRects = new Map<TileDescriptor, Rect>(
return items.map(item => ({ ...tileRects.get(item)!, item })) zipWith(slotCells, slotRects, (cell, rect) => [cell.item, rect])
);
return items.map((item) => ({ ...tileRects.get(item)!, item }));
}, },
[slotRects, grid, slotGridGeneration] [slotRects, grid, slotGridGeneration]
); );
@ -516,7 +542,7 @@ export const NewVideoGrid: FC<Props> = ({
) as unknown as [TransitionFn<Tile, TileSpring>, SpringRef<TileSpring>]; ) as unknown as [TransitionFn<Tile, TileSpring>, SpringRef<TileSpring>];
const slotGridStyle = useMemo(() => { const slotGridStyle = useMemo(() => {
const columnCount = 6; if (grid === null) return {};
const areas = new Array<(number | null)[]>( const areas = new Array<(number | null)[]>(
Math.ceil(grid.cells.length / grid.columns) Math.ceil(grid.cells.length / grid.columns)
@ -548,9 +574,9 @@ export const NewVideoGrid: FC<Props> = ({
.join(" ")}'` .join(" ")}'`
) )
.join(" "), .join(" "),
gridTemplateColumns: `repeat(${columnCount}, 1fr)`, gridTemplateColumns: `repeat(${columns}, 1fr)`,
}; };
}, [grid]); }, [grid, columns]);
const animateDraggedTile = (endOfGesture: boolean) => { const animateDraggedTile = (endOfGesture: boolean) => {
const { tileId, tileX, tileY, cursorX, cursorY } = dragState.current!; const { tileId, tileX, tileY, cursorX, cursorY } = dragState.current!;
@ -598,8 +624,8 @@ export const NewVideoGrid: FC<Props> = ({
); );
if (overTile !== undefined && overTile.item.id !== tileId) { if (overTile !== undefined && overTile.item.id !== tileId) {
setGrid((g) => ({ setGrid((g) => ({
...g, ...g!,
cells: g.cells.map((c) => { cells: g!.cells.map((c) => {
if (c?.item === overTile.item) return { ...c, item: tile.item }; if (c?.item === overTile.item) return { ...c, item: tile.item };
if (c?.item === tile.item) return { ...c, item: overTile.item }; if (c?.item === tile.item) return { ...c, item: overTile.item };
return c; return c;
@ -618,7 +644,7 @@ export const NewVideoGrid: FC<Props> = ({
}: Parameters<Handler<"drag", EventTypes["drag"]>>[0] }: Parameters<Handler<"drag", EventTypes["drag"]>>[0]
) => { ) => {
if (tap) { if (tap) {
setGrid((g) => cycleTileSize(tileId, g)); setGrid((g) => cycleTileSize(tileId, g!));
} else { } else {
const tileSpring = springRef.current const tileSpring = springRef.current
.find((c) => (c.item as Tile).item.id === tileId)! .find((c) => (c.item as Tile).item.id === tileId)!
@ -671,8 +697,8 @@ export const NewVideoGrid: FC<Props> = ({
return slots; return slots;
}, [items.length]); }, [items.length]);
// Render nothing if the bounds are not yet known // Render nothing if the grid has yet to be generated
if (gridBounds.width === 0) { if (grid === null) {
return <div ref={gridRef} className={styles.grid} />; return <div ref={gridRef} className={styles.grid} />;
} }

View file

@ -20,7 +20,7 @@ limitations under the License.
top: 0; top: 0;
width: var(--tileWidth); width: var(--tileWidth);
height: var(--tileHeight); height: var(--tileHeight);
border-radius: 20px; border-radius: 8px;
overflow: hidden; overflow: hidden;
cursor: pointer; cursor: pointer;
@ -174,3 +174,9 @@ limitations under the License.
max-width: 360px; max-width: 360px;
border-radius: 20px; border-radius: 20px;
} }
@media (min-width: 800px) {
.videoTile {
border-radius: 20px;
}
}