communication-react-19
Version:
React library for building modern communication user experiences utilizing Azure Communication Services (React 19 compatible fork)
117 lines • 8.02 kB
JavaScript
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { LayerHost, mergeStyles, Stack } from '@fluentui/react';
import { useId } from '@fluentui/react-hooks';
import React, { useMemo, useRef, useState } from 'react';
import { useTheme } from '../../theming';
import { GridLayout } from '../GridLayout';
import { isNarrowWidth } from '../utils/responsive';
import { isShortHeight } from '../utils/responsive';
import { FloatingLocalVideo } from './FloatingLocalVideo';
import { LARGE_FLOATING_MODAL_SIZE_REM, localVideoTileContainerStyle, localVideoTileWithControlsContainerStyle, LOCAL_VIDEO_TILE_ZINDEX, SMALL_FLOATING_MODAL_SIZE_REM } from './styles/FloatingLocalVideo.styles';
import { SHORT_VERTICAL_GALLERY_FLOATING_MODAL_SIZE_REM, VERTICAL_GALLERY_FLOATING_MODAL_SIZE_REM } from './styles/FloatingLocalVideo.styles';
import { innerLayoutStyle, layerHostStyle, rootLayoutStyle } from './styles/FloatingLocalVideoLayout.styles';
import { videoGalleryLayoutGap } from './styles/Layout.styles';
import { MAX_GRID_PARTICIPANTS_NOT_LARGE_GALLERY, renderTiles, useOrganizedParticipants } from './utils/videoGalleryLayoutUtils';
import { OverflowGallery } from './OverflowGallery';
/**
* FloatingLocalVideoLayout displays remote participants and a screen sharing component in
* a grid and overflow gallery while floating the local video
*
* @private
*/
export const FloatingLocalVideoLayout = (props) => {
const { remoteParticipants = [], dominantSpeakers, localVideoComponent, screenShareComponent, onRenderRemoteParticipant, styles, maxRemoteVideoStreams, showCameraSwitcherInLocalPreview, parentWidth, parentHeight, overflowGalleryPosition = 'horizontalBottom', pinnedParticipantUserIds = [], localVideoTileSize, spotlightedParticipantUserIds } = props;
const theme = useTheme();
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.
const childrenPerPage = useRef(4);
const remoteVideosOn = remoteParticipants.filter((p) => { var _a; return (_a = p.videoStream) === null || _a === void 0 ? void 0 : _a.isAvailable; }).length > 0;
const { gridParticipants, overflowGalleryParticipants } = useOrganizedParticipants({
remoteParticipants,
dominantSpeakers,
maxGridParticipants: remoteVideosOn ? maxRemoteVideoStreams : MAX_GRID_PARTICIPANTS_NOT_LARGE_GALLERY,
isScreenShareActive: !!screenShareComponent,
maxOverflowGalleryDominantSpeakers: screenShareComponent
? childrenPerPage.current - (pinnedParticipantUserIds.length % childrenPerPage.current)
: childrenPerPage.current,
pinnedParticipantUserIds,
layout: 'floatingLocalVideo',
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([]);
const { gridTiles, overflowGalleryTiles } = renderTiles(gridParticipants, onRenderRemoteParticipant, maxRemoteVideoStreams, indexesToRender, overflowGalleryParticipants, dominantSpeakers);
const shouldFloatLocalVideo = remoteParticipants.length > 0;
if (!shouldFloatLocalVideo && localVideoComponent) {
gridTiles.push(localVideoComponent);
}
const layerHostId = useId('layerhost');
const localVideoSizeRem = useMemo(() => {
if ((isNarrow && localVideoTileSize !== '16:9') || localVideoTileSize === '9:16') {
return SMALL_FLOATING_MODAL_SIZE_REM;
}
if ((overflowGalleryTiles.length > 0 || screenShareComponent) && overflowGalleryPosition === 'verticalRight') {
return isNarrow
? SMALL_FLOATING_MODAL_SIZE_REM
: isShort
? SHORT_VERTICAL_GALLERY_FLOATING_MODAL_SIZE_REM
: VERTICAL_GALLERY_FLOATING_MODAL_SIZE_REM;
}
if ((overflowGalleryTiles.length > 0 || screenShareComponent) && overflowGalleryPosition === 'horizontalBottom') {
return localVideoTileSize === '16:9' || !isNarrow ? LARGE_FLOATING_MODAL_SIZE_REM : SMALL_FLOATING_MODAL_SIZE_REM;
}
return LARGE_FLOATING_MODAL_SIZE_REM;
}, [
overflowGalleryTiles.length,
isNarrow,
screenShareComponent,
isShort,
overflowGalleryPosition,
localVideoTileSize
]);
const wrappedLocalVideoComponent = (localVideoComponent && shouldFloatLocalVideo) || (screenShareComponent && localVideoComponent) ? (
// When we use showCameraSwitcherInLocalPreview it disables dragging to allow keyboard navigation.
showCameraSwitcherInLocalPreview ? (React.createElement(Stack, { className: mergeStyles(localVideoTileWithControlsContainerStyle(theme, localVideoSizeRem), {
boxShadow: theme.effects.elevation8,
zIndex: LOCAL_VIDEO_TILE_ZINDEX
}) }, localVideoComponent)) : overflowGalleryTiles.length > 0 || screenShareComponent ? (React.createElement(Stack, { className: mergeStyles(localVideoTileContainerStyle(theme, localVideoSizeRem, !!screenShareComponent, overflowGalleryPosition)) }, localVideoComponent)) : (React.createElement(FloatingLocalVideo, { localVideoComponent: localVideoComponent, layerHostId: layerHostId, localVideoSizeRem: localVideoSizeRem, parentWidth: parentWidth, parentHeight: parentHeight }))) : undefined;
const overflowGallery = useMemo(() => {
if (overflowGalleryTiles.length === 0 && !screenShareComponent) {
return null;
}
return (React.createElement(OverflowGallery, { isShort: isShort, onFetchTilesToRender: setIndexesToRender, isNarrow: isNarrow, shouldFloatLocalVideo: !!localVideoComponent, overflowGalleryElements: overflowGalleryTiles, horizontalGalleryStyles: styles === null || styles === void 0 ? void 0 : styles.horizontalGallery, verticalGalleryStyles: styles === null || styles === void 0 ? void 0 : styles.verticalGallery, overflowGalleryPosition: overflowGalleryPosition, onChildrenPerPageChange: (n) => {
childrenPerPage.current = n;
}, parentWidth: parentWidth }));
}, [
isNarrow,
isShort,
screenShareComponent,
overflowGalleryTiles,
styles === null || styles === void 0 ? void 0 : styles.horizontalGallery,
overflowGalleryPosition,
setIndexesToRender,
styles === null || styles === void 0 ? void 0 : styles.verticalGallery,
parentWidth,
localVideoComponent
]);
return (React.createElement(Stack, { styles: rootLayoutStyle },
React.createElement(Stack, { horizontal: overflowGalleryPosition === 'verticalRight', styles: innerLayoutStyle, tokens: videoGalleryLayoutGap },
props.overflowGalleryPosition === 'horizontalTop' ? overflowGallery : React.createElement(React.Fragment, null),
screenShareComponent ? (screenShareComponent) : (React.createElement(GridLayout, { key: "grid-layout", styles: styles === null || styles === void 0 ? void 0 : styles.gridLayout }, gridTiles)),
overflowGalleryTrampoline(overflowGallery, props.overflowGalleryPosition)),
React.createElement(LayerHost, { id: layerHostId, className: mergeStyles(layerHostStyle) }),
wrappedLocalVideoComponent));
};
const overflowGalleryTrampoline = (gallery, galleryPosition) => {
return galleryPosition !== 'horizontalTop' ? gallery : React.createElement(React.Fragment, null);
};
//# sourceMappingURL=FloatingLocalVideoLayout.js.map