// @flow import { makeStyles } from '@material-ui/styles'; import React, { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { useDispatch, useSelector } from 'react-redux'; import { isSupported as isAvModerationSupported } from '../../../av-moderation/functions'; import { Avatar } from '../../../base/avatar'; import ContextMenu from '../../../base/components/context-menu/ContextMenu'; import ContextMenuItemGroup from '../../../base/components/context-menu/ContextMenuItemGroup'; import { isIosMobileBrowser, isMobileBrowser } from '../../../base/environment/utils'; import { IconShareVideo } from '../../../base/icons'; import { MEDIA_TYPE } from '../../../base/media'; import { getLocalParticipant, PARTICIPANT_ROLE } from '../../../base/participants'; import { isParticipantAudioMuted } from '../../../base/tracks'; import { getBreakoutRooms, getCurrentRoomId, isInBreakoutRoom } from '../../../breakout-rooms/functions'; import { setVolume } from '../../../filmstrip/actions.web'; import { isStageFilmstripAvailable } from '../../../filmstrip/functions.web'; import { isForceMuted } from '../../../participants-pane/functions'; import { requestRemoteControl, stopController } from '../../../remote-control'; import { stopSharedVideo } from '../../../shared-video/actions.any'; import { showOverflowDrawer } from '../../../toolbox/functions.web'; import { REMOTE_CONTROL_MENU_STATES } from './RemoteControlButton'; import SendToRoomButton from './SendToRoomButton'; import { AskToUnmuteButton, ConnectionStatusButton, GrantModeratorButton, MuteButton, MuteEveryoneElseButton, MuteEveryoneElsesVideoButton, MuteVideoButton, KickButton, PrivateMessageMenuButton, RemoteControlButton, TogglePinToStageButton, VolumeSlider } from './'; type Props = { /** * Class name for the context menu. */ className?: string, /** * Closes a drawer if open. */ closeDrawer?: Function, /** * The participant for which the drawer is open. * It contains the displayName & participantID. */ drawerParticipant?: Object, /** * Shared video local participant owner. */ localVideoOwner?: boolean, /** * Target elements against which positioning calculations are made. */ offsetTarget?: HTMLElement, /** * Callback for the mouse entering the component. */ onEnter?: Function, /** * Callback for the mouse leaving the component. */ onLeave?: Function, /** * Callback for making a selection in the menu. */ onSelect: Function, /** * Participant reference. */ participant: Object, /** * The current state of the participant's remote control session. */ remoteControlState?: number, /** * Whether or not the menu is displayed in the thumbnail remote video menu. */ thumbnailMenu: ?boolean } const useStyles = makeStyles(theme => { return { text: { color: theme.palette.text02, padding: '10px 16px', height: '40px', overflow: 'hidden', display: 'flex', alignItems: 'center', boxSizing: 'border-box' } }; }); const ParticipantContextMenu = ({ className, closeDrawer, drawerParticipant, localVideoOwner, offsetTarget, onEnter, onLeave, onSelect, participant, remoteControlState, thumbnailMenu }: Props) => { const dispatch = useDispatch(); const { t } = useTranslation(); const styles = useStyles(); const localParticipant = useSelector(getLocalParticipant); const _isModerator = Boolean(localParticipant?.role === PARTICIPANT_ROLE.MODERATOR); const _isAudioForceMuted = useSelector(state => isForceMuted(participant, MEDIA_TYPE.AUDIO, state)); const _isVideoForceMuted = useSelector(state => isForceMuted(participant, MEDIA_TYPE.VIDEO, state)); const _isAudioMuted = useSelector(state => isParticipantAudioMuted(participant, state)); const _overflowDrawer = useSelector(showOverflowDrawer); const { remoteVideoMenu = {}, disableRemoteMute, startSilent } = useSelector(state => state['features/base/config']); const { disableKick, disableGrantModerator, disablePrivateChat } = remoteVideoMenu; const { participantsVolume } = useSelector(state => state['features/filmstrip']); const _volume = (participant?.local ?? true ? undefined : participant?.id ? participantsVolume[participant?.id] : undefined) ?? 1; const isBreakoutRoom = useSelector(isInBreakoutRoom); const isModerationSupported = useSelector(isAvModerationSupported); const stageFilmstrip = useSelector(isStageFilmstripAvailable); const _currentRoomId = useSelector(getCurrentRoomId); const _rooms = Object.values(useSelector(getBreakoutRooms)); const _onVolumeChange = useCallback(value => { dispatch(setVolume(participant.id, value)); }, [ setVolume, dispatch ]); const clickHandler = useCallback(() => onSelect(true), [ onSelect ]); const _onStopSharedVideo = useCallback(() => { clickHandler(); dispatch(stopSharedVideo()); }, [ stopSharedVideo ]); const _getCurrentParticipantId = useCallback(() => { const drawer = _overflowDrawer && !thumbnailMenu; return (drawer ? drawerParticipant?.participantID : participant?.id) ?? ''; } , [ thumbnailMenu, _overflowDrawer, drawerParticipant, participant ]); const buttons = []; const buttons2 = []; const showVolumeSlider = !startSilent && !isIosMobileBrowser() && (_overflowDrawer || thumbnailMenu) && typeof _volume === 'number' && !isNaN(_volume); const fakeParticipantActions = [ { accessibilityLabel: t('toolbar.stopSharedVideo'), icon: IconShareVideo, onClick: _onStopSharedVideo, text: t('toolbar.stopSharedVideo') } ]; if (_isModerator) { if ((thumbnailMenu || _overflowDrawer) && isModerationSupported && _isAudioMuted) { buttons.push( ); } if (!disableRemoteMute) { buttons.push( ); buttons.push( ); buttons.push( ); buttons.push( ); } if (!disableGrantModerator && !isBreakoutRoom) { buttons2.push( ); } if (!disableKick) { buttons2.push( ); } } if (stageFilmstrip) { buttons2.push(); } if (!disablePrivateChat) { buttons2.push( ); } if (thumbnailMenu && isMobileBrowser()) { buttons2.push( ); } if (thumbnailMenu && remoteControlState) { let onRemoteControlToggle = null; if (remoteControlState === REMOTE_CONTROL_MENU_STATES.STARTED) { onRemoteControlToggle = () => dispatch(stopController(true)); } else if (remoteControlState === REMOTE_CONTROL_MENU_STATES.NOT_STARTED) { onRemoteControlToggle = () => dispatch(requestRemoteControl(_getCurrentParticipantId())); } buttons2.push( ); } const breakoutRoomsButtons = []; if (!thumbnailMenu && _isModerator) { _rooms.forEach((room: Object) => { if (room.id !== _currentRoomId) { breakoutRoomsButtons.push( ); } }); } return ( {!thumbnailMenu && _overflowDrawer && drawerParticipant && , text: drawerParticipant.displayName } ] } />} {participant?.isFakeParticipant ? localVideoOwner && ( ) : ( <> {buttons.length > 0 && ( {buttons} )} {buttons2} {showVolumeSlider && ( )} {breakoutRoomsButtons.length > 0 && ( {t('breakoutRooms.actions.sendToBreakoutRoom')} {breakoutRoomsButtons} )} > )} ); }; export default ParticipantContextMenu;