WIP minus unfinished split grid layouts
This commit is contained in:
parent
9437a00997
commit
d3fba7fd5f
6 changed files with 156 additions and 3 deletions
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
.inRoom {
|
.inRoom {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
|
|
|
@ -64,6 +64,7 @@ import { ParticipantInfo } from "./useGroupCall";
|
||||||
import { TileDescriptor } from "../video-grid/TileDescriptor";
|
import { TileDescriptor } from "../video-grid/TileDescriptor";
|
||||||
import { AudioSink } from "../video-grid/AudioSink";
|
import { AudioSink } from "../video-grid/AudioSink";
|
||||||
import { useCallViewKeyboardShortcuts } from "../useCallViewKeyboardShortcuts";
|
import { useCallViewKeyboardShortcuts } from "../useCallViewKeyboardShortcuts";
|
||||||
|
import { NewVideoGrid } from "../video-grid/NewVideoGrid";
|
||||||
|
|
||||||
const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
|
const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
|
||||||
// There is currently a bug in Safari our our code with cloning and sending MediaStreams
|
// There is currently a bug in Safari our our code with cloning and sending MediaStreams
|
||||||
|
@ -305,7 +306,7 @@ export function InCallView({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VideoGrid
|
<NewVideoGrid
|
||||||
items={items}
|
items={items}
|
||||||
layout={layout}
|
layout={layout}
|
||||||
disableAnimations={prefersReducedMotion || isSafari}
|
disableAnimations={prefersReducedMotion || isSafari}
|
||||||
|
@ -330,7 +331,7 @@ export function InCallView({
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</VideoGrid>
|
</NewVideoGrid>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
18
src/video-grid/NewVideoGrid.module.css
Normal file
18
src/video-grid/NewVideoGrid.module.css
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
.grid {
|
||||||
|
position: relative;
|
||||||
|
flex-grow: 1;
|
||||||
|
padding: 0 22px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slotGrid {
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
grid-auto-rows: 183px;
|
||||||
|
column-gap: 18px;
|
||||||
|
row-gap: 21px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slot {
|
||||||
|
background-color: red;
|
||||||
|
}
|
132
src/video-grid/NewVideoGrid.tsx
Normal file
132
src/video-grid/NewVideoGrid.tsx
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
import { useTransition } from "@react-spring/web";
|
||||||
|
import React, { FC, memo, ReactNode, useMemo, useRef } from "react";
|
||||||
|
import useMeasure from "react-use-measure";
|
||||||
|
import styles from "./NewVideoGrid.module.css";
|
||||||
|
import { TileDescriptor } from "./TileDescriptor";
|
||||||
|
import { VideoGridProps as Props } from "./VideoGrid";
|
||||||
|
|
||||||
|
interface Cell {
|
||||||
|
/**
|
||||||
|
* The item held by the slot containing this cell.
|
||||||
|
*/
|
||||||
|
item: TileDescriptor
|
||||||
|
/**
|
||||||
|
* Whether this cell is the first cell of the containing slot.
|
||||||
|
*/
|
||||||
|
slot: boolean
|
||||||
|
/**
|
||||||
|
* The width, in columns, of the containing slot.
|
||||||
|
*/
|
||||||
|
columns: number
|
||||||
|
/**
|
||||||
|
* The height, in rows, of the containing slot.
|
||||||
|
*/
|
||||||
|
rows: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Rect {
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Tile extends Rect {
|
||||||
|
item: TileDescriptor
|
||||||
|
dragging: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SlotsProps {
|
||||||
|
count: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a number of empty slot divs.
|
||||||
|
*/
|
||||||
|
const Slots: FC<SlotsProps> = memo(({ count }) => {
|
||||||
|
const slots = new Array<ReactNode>(count)
|
||||||
|
for (let i = 0; i < count; i++) slots[i] = <div className={styles.slot} key={i} />
|
||||||
|
return <>{slots}</>
|
||||||
|
})
|
||||||
|
|
||||||
|
export const NewVideoGrid: FC<Props> = ({ items, children }) => {
|
||||||
|
const slotGridRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [gridRef, gridBounds] = useMeasure();
|
||||||
|
|
||||||
|
const slotRects = useMemo(() => {
|
||||||
|
if (slotGridRef.current === null) return [];
|
||||||
|
|
||||||
|
const slots = slotGridRef.current.getElementsByClassName(styles.slot)
|
||||||
|
const rects = new Array<Rect>(slots.length)
|
||||||
|
for (let i = 0; i < slots.length; i++) {
|
||||||
|
const slot = slots[i] as HTMLElement
|
||||||
|
rects[i] = {
|
||||||
|
x: slot.offsetLeft,
|
||||||
|
y: slot.offsetTop,
|
||||||
|
width: slot.offsetWidth,
|
||||||
|
height: slot.offsetHeight,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rects;
|
||||||
|
}, [items, gridBounds]);
|
||||||
|
|
||||||
|
const cells: Cell[] = useMemo(() => items.map(item => ({
|
||||||
|
item,
|
||||||
|
slot: true,
|
||||||
|
columns: 1,
|
||||||
|
rows: 1,
|
||||||
|
})), [items])
|
||||||
|
|
||||||
|
const slotCells = useMemo(() => cells.filter(cell => cell.slot), [cells])
|
||||||
|
|
||||||
|
const tiles: Tile[] = useMemo(() => slotRects.map((slot, i) => {
|
||||||
|
const cell = slotCells[i]
|
||||||
|
return {
|
||||||
|
item: cell.item,
|
||||||
|
x: slot.x,
|
||||||
|
y: slot.y,
|
||||||
|
width: slot.width,
|
||||||
|
height: slot.height,
|
||||||
|
dragging: false,
|
||||||
|
}
|
||||||
|
}), [slotRects, cells])
|
||||||
|
|
||||||
|
const [tileTransitions] = useTransition(tiles, () => ({
|
||||||
|
key: ({ item }: Tile) => item.id,
|
||||||
|
from: { opacity: 0 },
|
||||||
|
enter: ({ x, y, width, height }: Tile) => ({ opacity: 1, x, y, width, height }),
|
||||||
|
update: ({ x, y, width, height }: Tile) => ({ x, y, width, height }),
|
||||||
|
leave: { opacity: 0 },
|
||||||
|
}), [tiles])
|
||||||
|
|
||||||
|
const slotGridStyle = useMemo(() => {
|
||||||
|
const columnCount = gridBounds.width >= 800 ? 6 : 3;
|
||||||
|
return {
|
||||||
|
gridTemplateColumns: `repeat(${columnCount}, 1fr)`,
|
||||||
|
};
|
||||||
|
}, [gridBounds]);
|
||||||
|
|
||||||
|
// Render nothing if the bounds are not yet known
|
||||||
|
if (gridBounds.width === 0) {
|
||||||
|
return <div ref={gridRef} className={styles.grid} />
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={gridRef}
|
||||||
|
className={styles.grid}
|
||||||
|
>
|
||||||
|
<div style={slotGridStyle} ref={slotGridRef} className={styles.slotGrid}>
|
||||||
|
<Slots count={items.length} />
|
||||||
|
</div>
|
||||||
|
{tileTransitions((style, tile) => children({
|
||||||
|
key: tile.item.id,
|
||||||
|
style: style as any,
|
||||||
|
width: tile.width,
|
||||||
|
height: tile.height,
|
||||||
|
item: tile.item,
|
||||||
|
}))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -705,7 +705,7 @@ interface ChildrenProperties extends ReactDOMAttributes {
|
||||||
[index: string]: unknown;
|
[index: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface VideoGridProps {
|
export interface VideoGridProps {
|
||||||
items: TileDescriptor[];
|
items: TileDescriptor[];
|
||||||
layout: Layout;
|
layout: Layout;
|
||||||
disableAnimations?: boolean;
|
disableAnimations?: boolean;
|
||||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
.videoTile {
|
.videoTile {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
will-change: transform, width, height, opacity, box-shadow;
|
will-change: transform, width, height, opacity, box-shadow;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
Loading…
Add table
Reference in a new issue