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
JavaScript
// 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