|
1 | | -import { memo, useMemo, useRef } from 'react' |
2 | | -import type { TrackReferenceOrPlaceholder } from '@livekit/components-core' |
3 | | -import { styled } from '@/styled-system/jsx' |
4 | | -import { ParticipantTile } from '@/features/rooms/livekit/components/ParticipantTile' |
5 | | -import { usePipElementSize } from '../../hooks/usePipElementSize' |
6 | | -import { usePipFlipAnimations } from '../../hooks/usePipFlipAnimations' |
7 | | -import { computePipGridLayout } from '../../utils/pipGrid' |
8 | | -import { getTrackKey } from '../../utils/pipTrackSelection' |
9 | | - |
10 | | -type PipGridLayoutProps = { |
11 | | - tracks: TrackReferenceOrPlaceholder[] |
12 | | -} |
13 | | - |
14 | | -/** |
15 | | - * Adaptive grid used when 3+ tracks are visible in the PiP window. |
16 | | - * |
17 | | - * All grid math (shape choice + partial-row stretching) is delegated to |
18 | | - * `computePipGridLayout`. This component only measures the container, |
19 | | - * applies the returned placements, and plays a FLIP animation when the |
20 | | - * tile set or grid shape changes (participant joins/leaves or shape shift). |
21 | | - * |
22 | | - * Tiles keep a stable key so resizing never remounts <video> elements. |
23 | | - */ |
24 | | -export const PipGridLayout = memo(function PipGridLayout({ |
25 | | - tracks, |
26 | | -}: PipGridLayoutProps) { |
27 | | - const containerRef = useRef<HTMLDivElement>(null) |
28 | | - const { width, height } = usePipElementSize(containerRef) |
29 | | - |
30 | | - const tileKeys = useMemo(() => tracks.map(getTrackKey), [tracks]) |
31 | | - |
32 | | - const { rows, subColumns, placements } = useMemo( |
33 | | - () => computePipGridLayout(tracks.length, width, height), |
34 | | - [tracks.length, width, height] |
35 | | - ) |
36 | | - |
37 | | - const gridStyle = useMemo( |
38 | | - () => ({ |
39 | | - gridTemplateColumns: `repeat(${subColumns}, minmax(0, 1fr))`, |
40 | | - gridTemplateRows: `repeat(${rows}, minmax(0, 1fr))`, |
41 | | - }), |
42 | | - [subColumns, rows] |
43 | | - ) |
44 | | - |
45 | | - usePipFlipAnimations(containerRef, tileKeys) |
46 | | - |
47 | | - return ( |
48 | | - <GridContainer ref={containerRef} style={gridStyle}> |
49 | | - {tracks.map((track, index) => ( |
50 | | - <GridCell key={tileKeys[index]} style={placements[index]}> |
51 | | - <ParticipantTile trackRef={track} disableMetadata /> |
52 | | - </GridCell> |
53 | | - ))} |
54 | | - </GridContainer> |
55 | | - ) |
56 | | -}) |
57 | | - |
58 | | -const GridContainer = styled('div', { |
59 | | - base: { |
60 | | - width: '100%', |
61 | | - height: '100%', |
62 | | - display: 'grid', |
63 | | - gap: '0.25rem', |
64 | | - }, |
65 | | -}) |
66 | | - |
67 | | -const GridCell = styled('div', { |
68 | | - base: { |
69 | | - position: 'relative', |
70 | | - minWidth: 0, |
71 | | - minHeight: 0, |
72 | | - borderRadius: '4px', |
73 | | - overflow: 'hidden', |
74 | | - backgroundColor: 'primaryDark.100', |
75 | | - // Paint on own layer so FLIP transforms don't trigger layout thrash. |
76 | | - willChange: 'transform', |
77 | | - '& .lk-participant-tile': { |
78 | | - width: '100%', |
79 | | - height: '100%', |
80 | | - }, |
81 | | - }, |
82 | | -}) |
| 1 | +import { memo, useMemo, useRef } from 'react' |
| 2 | +import type { TrackReferenceOrPlaceholder } from '@livekit/components-core' |
| 3 | +import { styled } from '@/styled-system/jsx' |
| 4 | +import { ParticipantTile } from '@/features/rooms/livekit/components/ParticipantTile' |
| 5 | +import { usePipElementSize } from '../../hooks/usePipElementSize' |
| 6 | +import { usePipFlipAnimations } from '../../hooks/usePipFlipAnimations' |
| 7 | +import { computePipGridLayout } from '../../utils/pipGrid' |
| 8 | +import { getTrackKey } from '../../utils/pipTrackSelection' |
| 9 | + |
| 10 | +type PipGridLayoutProps = { |
| 11 | + tracks: TrackReferenceOrPlaceholder[] |
| 12 | +} |
| 13 | + |
| 14 | +/** |
| 15 | + * Adaptive grid used when 3+ tracks are visible in the PiP window. |
| 16 | + * |
| 17 | + * All grid math (shape choice + partial-row stretching) is delegated to |
| 18 | + * `computePipGridLayout`. This component only measures the container, |
| 19 | + * applies the returned placements, and plays a FLIP animation when the |
| 20 | + * tile set or grid shape changes (participant joins/leaves or shape shift). |
| 21 | + * |
| 22 | + * Tiles keep a stable key so resizing never remounts <video> elements. |
| 23 | + */ |
| 24 | +export const PipGridLayout = memo(function PipGridLayout({ |
| 25 | + tracks, |
| 26 | +}: PipGridLayoutProps) { |
| 27 | + const containerRef = useRef<HTMLDivElement>(null) |
| 28 | + const { width, height } = usePipElementSize(containerRef) |
| 29 | + |
| 30 | + const tileKeys = useMemo(() => tracks.map(getTrackKey), [tracks]) |
| 31 | + |
| 32 | + const { rows, subColumns, placements } = useMemo( |
| 33 | + () => computePipGridLayout(tracks.length, width, height), |
| 34 | + [tracks.length, width, height] |
| 35 | + ) |
| 36 | + |
| 37 | + const gridStyle = useMemo( |
| 38 | + () => ({ |
| 39 | + gridTemplateColumns: `repeat(${subColumns}, minmax(0, 1fr))`, |
| 40 | + gridTemplateRows: `repeat(${rows}, minmax(0, 1fr))`, |
| 41 | + }), |
| 42 | + [subColumns, rows] |
| 43 | + ) |
| 44 | + |
| 45 | + usePipFlipAnimations(containerRef, tileKeys) |
| 46 | + |
| 47 | + return ( |
| 48 | + <GridContainer ref={containerRef} style={gridStyle}> |
| 49 | + {tracks.map((track, index) => ( |
| 50 | + <GridCell key={tileKeys[index]} style={placements[index]}> |
| 51 | + <ParticipantTile trackRef={track} disableMetadata /> |
| 52 | + </GridCell> |
| 53 | + ))} |
| 54 | + </GridContainer> |
| 55 | + ) |
| 56 | +}) |
| 57 | + |
| 58 | +const GridContainer = styled('div', { |
| 59 | + base: { |
| 60 | + width: '100%', |
| 61 | + height: '100%', |
| 62 | + display: 'grid', |
| 63 | + gap: '0.25rem', |
| 64 | + }, |
| 65 | +}) |
| 66 | + |
| 67 | +const GridCell = styled('div', { |
| 68 | + base: { |
| 69 | + position: 'relative', |
| 70 | + minWidth: 0, |
| 71 | + minHeight: 0, |
| 72 | + borderRadius: '4px', |
| 73 | + overflow: 'hidden', |
| 74 | + backgroundColor: 'primaryDark.100', |
| 75 | + // Paint on own layer so FLIP transforms don't trigger layout thrash. |
| 76 | + willChange: 'transform', |
| 77 | + '& .lk-participant-tile': { |
| 78 | + width: '100%', |
| 79 | + height: '100%', |
| 80 | + }, |
| 81 | + }, |
| 82 | +}) |
0 commit comments