diff --git a/src/room/InCallView.module.css b/src/room/InCallView.module.css index 310cf47..04228aa 100644 --- a/src/room/InCallView.module.css +++ b/src/room/InCallView.module.css @@ -20,9 +20,10 @@ limitations under the License. flex-direction: column; overflow: hidden; min-height: 100%; - position: fixed; height: 100%; width: 100%; + --footerPadding: 8px; + --footerHeight: calc(50px + 2 * var(--footerPadding)); } .centerMessage { @@ -39,12 +40,27 @@ limitations under the License. } .footer { - position: relative; - flex-shrink: 0; + position: absolute; + left: 0; + bottom: 0; + width: 100%; display: flex; justify-content: 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 > * { @@ -75,13 +91,13 @@ limitations under the License. } @media (min-height: 300px) { - .footer { - height: calc(50px + 2 * 24px); + .inRoom { + --footerPadding: 24px; } } @media (min-width: 800px) { - .footer { - height: calc(50px + 2 * 32px); + .inRoom { + --footerPadding: 32px; } } diff --git a/src/video-grid/NewVideoGrid.module.css b/src/video-grid/NewVideoGrid.module.css index 7d0a896..e6f64ba 100644 --- a/src/video-grid/NewVideoGrid.module.css +++ b/src/video-grid/NewVideoGrid.module.css @@ -2,20 +2,31 @@ contain: strict; position: relative; flex-grow: 1; - padding: 0 22px; + padding: 0 20px; overflow-y: auto; overflow-x: hidden; } .slotGrid { - contain: strict; position: relative; display: grid; - grid-auto-rows: 183px; - column-gap: 18px; - row-gap: 21px; + grid-auto-rows: 163px; + gap: 8px; + padding-bottom: var(--footerHeight); } .slot { contain: strict; } + +@media (min-width: 800px) { + .grid { + padding: 0 22px; + } + + .slotGrid { + grid-auto-rows: 183px; + column-gap: 18px; + row-gap: 21px; + } +} diff --git a/src/video-grid/NewVideoGrid.tsx b/src/video-grid/NewVideoGrid.tsx index f4b5811..b4d13fc 100644 --- a/src/video-grid/NewVideoGrid.tsx +++ b/src/video-grid/NewVideoGrid.tsx @@ -290,7 +290,9 @@ const cycleTileSize = (tileId: string, g: Grid): Grid => { const fromEnd = areaEnd(from, fromWidth, fromHeight, g); 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( 0, 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 scanColumnOffset = Math.floor((toWidth - 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; 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 end = areaEnd(start, candidateWidth, candidateHeight, g); const startColumn = column(start, g); - const startRow = row(start, g) + const startRow = row(start, g); const endColumn = column(end, g); if ( @@ -424,8 +426,30 @@ export const NewVideoGrid: FC<Props> = ({ return rects; }, [items, slotGridGeneration, slotGrid, gridBounds]); - const [grid, setGrid] = useReactiveState<Grid>( - (prevGrid = { generation: 0, columns: 6, cells: [] }) => { + 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 + ? 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 // the grid const itemsById = new Map(items.map((i) => [i.id, i])); @@ -462,18 +486,20 @@ export const NewVideoGrid: FC<Props> = ({ return grid3; }, - [items] + [items, columns] ); const [tiles] = useReactiveState<Tile[]>( (prevTiles) => { // If React hasn't yet rendered the current generation of the layout, skip // 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 tileRects = new Map<TileDescriptor, Rect>(zipWith(slotCells, slotRects, (cell, rect) => [cell.item, rect])) - return items.map(item => ({ ...tileRects.get(item)!, item })) + const tileRects = new Map<TileDescriptor, Rect>( + zipWith(slotCells, slotRects, (cell, rect) => [cell.item, rect]) + ); + return items.map((item) => ({ ...tileRects.get(item)!, item })); }, [slotRects, grid, slotGridGeneration] ); @@ -516,7 +542,7 @@ export const NewVideoGrid: FC<Props> = ({ ) as unknown as [TransitionFn<Tile, TileSpring>, SpringRef<TileSpring>]; const slotGridStyle = useMemo(() => { - const columnCount = 6; + if (grid === null) return {}; const areas = new Array<(number | null)[]>( Math.ceil(grid.cells.length / grid.columns) @@ -548,9 +574,9 @@ export const NewVideoGrid: FC<Props> = ({ .join(" ")}'` ) .join(" "), - gridTemplateColumns: `repeat(${columnCount}, 1fr)`, + gridTemplateColumns: `repeat(${columns}, 1fr)`, }; - }, [grid]); + }, [grid, columns]); const animateDraggedTile = (endOfGesture: boolean) => { const { tileId, tileX, tileY, cursorX, cursorY } = dragState.current!; @@ -598,8 +624,8 @@ export const NewVideoGrid: FC<Props> = ({ ); if (overTile !== undefined && overTile.item.id !== tileId) { setGrid((g) => ({ - ...g, - cells: g.cells.map((c) => { + ...g!, + cells: g!.cells.map((c) => { if (c?.item === overTile.item) return { ...c, item: tile.item }; if (c?.item === tile.item) return { ...c, item: overTile.item }; return c; @@ -618,7 +644,7 @@ export const NewVideoGrid: FC<Props> = ({ }: Parameters<Handler<"drag", EventTypes["drag"]>>[0] ) => { if (tap) { - setGrid((g) => cycleTileSize(tileId, g)); + setGrid((g) => cycleTileSize(tileId, g!)); } else { const tileSpring = springRef.current .find((c) => (c.item as Tile).item.id === tileId)! @@ -671,8 +697,8 @@ export const NewVideoGrid: FC<Props> = ({ return slots; }, [items.length]); - // Render nothing if the bounds are not yet known - if (gridBounds.width === 0) { + // Render nothing if the grid has yet to be generated + if (grid === null) { return <div ref={gridRef} className={styles.grid} />; } diff --git a/src/video-grid/VideoTile.module.css b/src/video-grid/VideoTile.module.css index 543e4fa..ce44d22 100644 --- a/src/video-grid/VideoTile.module.css +++ b/src/video-grid/VideoTile.module.css @@ -20,7 +20,7 @@ limitations under the License. top: 0; width: var(--tileWidth); height: var(--tileHeight); - border-radius: 20px; + border-radius: 8px; overflow: hidden; cursor: pointer; @@ -174,3 +174,9 @@ limitations under the License. max-width: 360px; border-radius: 20px; } + +@media (min-width: 800px) { + .videoTile { + border-radius: 20px; + } +}