Implement different column counts and mobile layout
This commit is contained in:
parent
206730ffc0
commit
6adcf95aaa
4 changed files with 90 additions and 31 deletions
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue