packages/react-components/src/components/VideoGallery/DefaultLayout.tsx (117 lines of code) (raw):

// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. import { Stack } from '@fluentui/react'; import React, { useMemo, useState, useRef } from 'react'; import { GridLayout } from '../GridLayout'; import { isNarrowWidth } from '../utils/responsive'; import { isShortHeight } from '../utils/responsive'; import { LayoutProps } from './Layout'; import { rootLayoutStyle } from './styles/DefaultLayout.styles'; import { videoGalleryLayoutGap } from './styles/Layout.styles'; import { MAX_GRID_PARTICIPANTS_NOT_LARGE_GALLERY, renderTiles, useOrganizedParticipants } from './utils/videoGalleryLayoutUtils'; import { OverflowGallery } from './OverflowGallery'; /** * Props for {@link DefaultLayout}. * * @private */ export type DefaultLayoutProps = LayoutProps; /** * DefaultLayout displays remote participants, local video component, and screen sharing component in * a grid an overflow gallery. * * @private */ export const DefaultLayout = (props: DefaultLayoutProps): JSX.Element => { const { remoteParticipants = [], localParticipant, dominantSpeakers, localVideoComponent, screenShareComponent, onRenderRemoteParticipant, styles, maxRemoteVideoStreams, parentWidth, parentHeight, pinnedParticipantUserIds = [], overflowGalleryPosition = 'horizontalBottom', spotlightedParticipantUserIds = [] } = props; const isNarrow = parentWidth ? isNarrowWidth(parentWidth) : false; const isShort = parentHeight ? isShortHeight(parentHeight) : false; // This is for tracking the number of children in the first page of overflow gallery. // This number will be used for the maxOverflowGalleryDominantSpeakers when organizing the remote participants. // We need to add the local participant to the pinned participant count so we are placing the speakers correctly. const childrenPerPage = useRef(4); const remoteVideosOn = remoteParticipants.filter((p) => p.videoStream?.isAvailable).length > 0; const { gridParticipants, overflowGalleryParticipants } = useOrganizedParticipants({ remoteParticipants, localParticipant, dominantSpeakers, maxGridParticipants: remoteVideosOn ? maxRemoteVideoStreams : MAX_GRID_PARTICIPANTS_NOT_LARGE_GALLERY, isScreenShareActive: !!screenShareComponent, maxOverflowGalleryDominantSpeakers: screenShareComponent ? childrenPerPage.current - ((pinnedParticipantUserIds.length + 1) % childrenPerPage.current) : childrenPerPage.current, pinnedParticipantUserIds, layout: 'default', spotlightedParticipantUserIds }); /** * instantiate indexes available to render with indexes available that would be on first page * * For some components which do not strictly follow the order of the array, we might * re-render the initial tiles -> dispose them -> create new tiles, we need to take care of * this case when those components are here */ const [indexesToRender, setIndexesToRender] = useState<number[]>([]); let { gridTiles, overflowGalleryTiles } = renderTiles( gridParticipants, onRenderRemoteParticipant, maxRemoteVideoStreams, indexesToRender, overflowGalleryParticipants, dominantSpeakers ); if (localVideoComponent) { if (screenShareComponent || spotlightedParticipantUserIds.length > 0) { overflowGalleryTiles = [localVideoComponent].concat(overflowGalleryTiles); } else { gridTiles = [localVideoComponent].concat(gridTiles); } } const overflowGallery = useMemo(() => { if (overflowGalleryTiles.length === 0) { return null; } return ( <OverflowGallery isNarrow={isNarrow} isShort={isShort} shouldFloatLocalVideo={false} overflowGalleryElements={overflowGalleryTiles} horizontalGalleryStyles={styles?.horizontalGallery} verticalGalleryStyles={styles?.verticalGallery} overflowGalleryPosition={overflowGalleryPosition} onFetchTilesToRender={setIndexesToRender} onChildrenPerPageChange={(n: number) => { childrenPerPage.current = n; }} parentWidth={parentWidth} /> ); }, [ isNarrow, isShort, overflowGalleryTiles, styles?.horizontalGallery, overflowGalleryPosition, setIndexesToRender, styles?.verticalGallery, parentWidth ]); return ( <Stack horizontal={overflowGalleryPosition === 'verticalRight'} styles={rootLayoutStyle} tokens={videoGalleryLayoutGap} > {props.overflowGalleryPosition === 'horizontalTop' ? overflowGallery : <></>} {screenShareComponent ? ( screenShareComponent ) : ( <GridLayout key="grid-layout" styles={styles?.gridLayout}> {gridTiles} </GridLayout> )} {overflowGalleryTrampoline(overflowGallery, props.overflowGalleryPosition)} </Stack> ); }; const overflowGalleryTrampoline = ( gallery: JSX.Element | null, galleryPosition?: 'horizontalBottom' | 'verticalRight' | 'horizontalTop' ): JSX.Element | null => { return galleryPosition !== 'horizontalTop' ? gallery : <></>; };