UNPKG

communication-react-19

Version:

React library for building modern communication user experiences utilizing Azure Communication Services (React 19 compatible fork)

97 lines 8.08 kB
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. import React, { useMemo, useState, memo, useEffect } from 'react'; import { moveAnimationStyles, spriteAnimationStyles } from './styles/ReactionOverlay.style'; import { REACTION_NUMBER_OF_ANIMATION_FRAMES } from './VideoGallery/utils/reactionUtils'; import { Icon, mergeStyles, Stack, Text } from '@fluentui/react'; import { getEmojiResource } from './VideoGallery/utils/videoGalleryLayoutUtils'; import { useLocale } from '../localization'; import { calculateScaledSize, getTogetherModeParticipantOverlayStyle, participantStatusTransitionStyle, REACTION_MAX_TRAVEL_HEIGHT, REACTION_TRAVEL_HEIGHT, setTogetherModeSeatPositionStyle, togetherModeIconStyle, togetherModeParticipantDisplayName, togetherModeParticipantEmojiSpriteStyle, togetherModeParticipantStatusContainer } from './styles/TogetherMode.styles'; import { useTheme } from '../theming'; import { RaisedHandIcon } from './assets/RaisedHandIcon'; /** * TogetherModeOverlay component renders an empty JSX element. * * @returns {JSX.Element} An empty JSX element. */ export const TogetherModeOverlay = memo((props) => { const locale = useLocale(); const theme = useTheme(); const callingPalette = theme.callingPalette; const { emojiSize, reactionResources, remoteParticipants, localParticipant, togetherModeSeatPositions } = props; const [togetherModeParticipantStatus, setTogetherModeParticipantStatus] = useState({}); const [hoveredParticipantID, setHoveredParticipantID] = useState(''); /* * The useMemo hook is used to calculate the participant status for the Together Mode overlay. * It updates the togetherModeParticipantStatus state when there's a change in the remoteParticipants, localParticipant, * raisedHand, spotlight, isMuted, displayName, or hoveredParticipantID. */ const updatedParticipantStatus = useMemo(() => { const allParticipants = [...remoteParticipants, localParticipant]; const participantsWithVideoAvailable = allParticipants.filter((p) => { var _a; return ((_a = p.videoStream) === null || _a === void 0 ? void 0 : _a.isAvailable) && togetherModeSeatPositions[p.userId]; }); const updatedSignals = {}; for (const p of participantsWithVideoAvailable) { const { userId, reaction, raisedHand, spotlight, isMuted, displayName } = p; const seatingPosition = togetherModeSeatPositions[userId]; if (seatingPosition) { updatedSignals[userId] = { id: userId, reaction: reactionResources && reaction, isHandRaised: !!raisedHand, isSpotlighted: !!spotlight, isMuted, displayName: displayName || locale.strings.videoGallery.displayNamePlaceholder, showDisplayName: !!(spotlight || raisedHand || hoveredParticipantID === userId), scaledSize: calculateScaledSize(seatingPosition.width, seatingPosition.height), seatPositionStyle: setTogetherModeSeatPositionStyle(seatingPosition) }; } } // This is used to remove the participants bounding box from the DOM when they are no longer in the stream const participantsNotInTogetherModeStream = Object.keys(togetherModeParticipantStatus).filter((id) => !updatedSignals[id]); const newSignals = Object.assign(Object.assign({}, togetherModeParticipantStatus), updatedSignals); participantsNotInTogetherModeStream.forEach((id) => { delete newSignals[id]; }); const hasSignalingChange = Object.keys(newSignals).some((key) => JSON.stringify(newSignals[key]) !== JSON.stringify(togetherModeParticipantStatus[key])); const updateTogetherModeParticipantStatusState = hasSignalingChange || Object.keys(newSignals).length !== Object.keys(togetherModeParticipantStatus).length; return updateTogetherModeParticipantStatusState ? newSignals : togetherModeParticipantStatus; }, [ remoteParticipants, localParticipant, togetherModeParticipantStatus, togetherModeSeatPositions, reactionResources, locale.strings.videoGallery.displayNamePlaceholder, hoveredParticipantID ]); useEffect(() => { if (hoveredParticipantID && !updatedParticipantStatus[hoveredParticipantID]) { setHoveredParticipantID(''); } setTogetherModeParticipantStatus(updatedParticipantStatus); }, [hoveredParticipantID, updatedParticipantStatus]); return (React.createElement("div", { style: { position: 'absolute', width: '100%', height: '100%', overflow: 'hidden' } }, Object.values(togetherModeParticipantStatus).map((participantStatus) => { var _a, _b; return participantStatus.id && (React.createElement("div", { key: participantStatus.id, "data-ui-id": `together-mode-participant-${participantStatus.id}`, style: Object.assign({}, getTogetherModeParticipantOverlayStyle(participantStatus.seatPositionStyle)), onMouseEnter: () => setHoveredParticipantID(participantStatus.id), onMouseLeave: () => setHoveredParticipantID('') }, React.createElement("div", null, participantStatus.showDisplayName && (React.createElement("div", { style: Object.assign({}, participantStatusTransitionStyle) }, React.createElement("div", { style: Object.assign({}, togetherModeParticipantStatusContainer(callingPalette.videoTileLabelBackgroundLight, theme.effects.roundedCorner4)) }, participantStatus.isHandRaised && React.createElement(RaisedHandIcon, null), participantStatus.showDisplayName && (React.createElement(Text, { style: Object.assign({}, togetherModeParticipantDisplayName(hoveredParticipantID === participantStatus.id, parseFloat(participantStatus.seatPositionStyle.seatPosition.width), participantStatus.displayName ? theme.palette.neutralSecondary : 'inherit')) }, participantStatus.displayName)), participantStatus.isMuted && (React.createElement(Stack, { className: mergeStyles(togetherModeIconStyle) }, React.createElement(Icon, { iconName: "VideoTileMicOff" }))), participantStatus.isSpotlighted && (React.createElement(Stack, { className: mergeStyles(togetherModeIconStyle) }, React.createElement(Icon, { iconName: "VideoTileSpotlighted" })))))), ((_a = participantStatus.reaction) === null || _a === void 0 ? void 0 : _a.reactionType) && ( // First div - Section that fixes the travel height and applies the movement animation // Second div - Responsible for ensuring the sprite emoji is always centered in the participant seat position // Third div - Play Animation as the other animation applies on the base play animation for the sprite React.createElement("div", { style: moveAnimationStyles(parseFloat(participantStatus.seatPositionStyle.seatPosition.height) * REACTION_MAX_TRAVEL_HEIGHT, parseFloat(participantStatus.seatPositionStyle.seatPosition.height) * REACTION_TRAVEL_HEIGHT) }, React.createElement("div", { "data-ui-id": `together-mode-participant-reaction-${participantStatus.id}`, style: Object.assign({}, togetherModeParticipantEmojiSpriteStyle(emojiSize, participantStatus.scaledSize || 1, participantStatus.seatPositionStyle.seatPosition.width)) }, React.createElement("div", { style: spriteAnimationStyles(REACTION_NUMBER_OF_ANIMATION_FRAMES, participantStatus.scaledSize || 1, (_b = (participantStatus.reaction && getEmojiResource(participantStatus === null || participantStatus === void 0 ? void 0 : participantStatus.reaction.reactionType, reactionResources))) !== null && _b !== void 0 ? _b : '') }))))))); }))); }); //# sourceMappingURL=TogetherModeOverlay.js.map