Files
jitsi-meet/react/features/video-menu/components/web/ParticipantContextMenu.js
Robert Pintilii 91437c50e3 feat(thumbnail) Video thumbnails redesign and refactor (#10351)
Update video thumbnail design
Update design of indicators
In filmstrip view move Screen Sharing indicator to the top
Removed dominant speaker indicator
Use ContextMenu component for the connection stats popover
Combine Remove video menu and Meeting participant context menu into one component
Moved some styles from SCSS to JSS
Fix mobile avatars too big
Fix mobile horizontal scroll
Created button for Send to breakout room action
2021-12-15 15:18:41 +02:00

333 lines
11 KiB
JavaScript

// @flow
import { makeStyles } from '@material-ui/styles';
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
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 { getBreakoutRooms, getCurrentRoomId } from '../../../breakout-rooms/functions';
import { setVolume } from '../../../filmstrip/actions.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,
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 _overflowDrawer = useSelector(showOverflowDrawer);
const { remoteVideoMenu = {}, disableRemoteMute, startSilent }
= useSelector(state => state['features/base/config']);
const { disableKick, disableGrantModerator } = remoteVideoMenu;
const { participantsVolume } = useSelector(state => state['features/filmstrip']);
const _volume = (participant?.local ?? true ? undefined
: participant?.id ? participantsVolume[participant?.id] : undefined) || 1;
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) {
buttons.push(<AskToUnmuteButton
isAudioForceMuted = { _isAudioForceMuted }
isVideoForceMuted = { _isVideoForceMuted }
key = 'ask-unmute'
participantID = { _getCurrentParticipantId() } />
);
}
if (!disableRemoteMute) {
buttons.push(
<MuteButton
key = 'mute'
participantID = { _getCurrentParticipantId() } />
);
buttons.push(
<MuteEveryoneElseButton
key = 'mute-others'
participantID = { _getCurrentParticipantId() } />
);
buttons.push(
<MuteVideoButton
key = 'mute-video'
participantID = { _getCurrentParticipantId() } />
);
buttons.push(
<MuteEveryoneElsesVideoButton
key = 'mute-others-video'
participantID = { _getCurrentParticipantId() } />
);
}
if (!disableGrantModerator) {
buttons2.push(
<GrantModeratorButton
key = 'grant-moderator'
participantID = { _getCurrentParticipantId() } />
);
}
if (!disableKick) {
buttons2.push(
<KickButton
key = 'kick'
participantID = { _getCurrentParticipantId() } />
);
}
}
buttons2.push(
<PrivateMessageMenuButton
key = 'privateMessage'
participantID = { _getCurrentParticipantId() } />
);
if (thumbnailMenu && isMobileBrowser()) {
buttons2.push(
<ConnectionStatusButton
key = 'conn-status'
participantId = { _getCurrentParticipantId() } />
);
}
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(
<RemoteControlButton
key = 'remote-control'
onClick = { onRemoteControlToggle }
participantID = { _getCurrentParticipantId() }
remoteControlState = { remoteControlState } />
);
}
const breakoutRoomsButtons = [];
if (!thumbnailMenu && _isModerator) {
_rooms.forEach((room: Object) => {
if (room.id !== _currentRoomId) {
breakoutRoomsButtons.push(
<SendToRoomButton
key = { room.id }
onClick = { clickHandler }
participantID = { _getCurrentParticipantId() }
room = { room } />
);
}
});
}
return (
<ContextMenu
className = { className }
entity = { participant }
hidden = { thumbnailMenu ? false : undefined }
inDrawer = { thumbnailMenu && _overflowDrawer }
isDrawerOpen = { drawerParticipant }
offsetTarget = { offsetTarget }
onClick = { onSelect }
onDrawerClose = { thumbnailMenu ? onSelect : closeDrawer }
onMouseEnter = { onEnter }
onMouseLeave = { onLeave }>
{!thumbnailMenu && _overflowDrawer && drawerParticipant && <ContextMenuItemGroup
actions = { [ {
accessibilityLabel: drawerParticipant.displayName,
customIcon: <Avatar
participantId = { drawerParticipant.participantID }
size = { 20 } />,
text: drawerParticipant.displayName
} ] } />}
{participant?.isFakeParticipant ? localVideoOwner && (
<ContextMenuItemGroup
actions = { fakeParticipantActions } />
) : (
<>
{buttons.length > 0 && (
<ContextMenuItemGroup>
{buttons}
</ContextMenuItemGroup>
)}
<ContextMenuItemGroup>
{buttons2}
</ContextMenuItemGroup>
{showVolumeSlider && (
<ContextMenuItemGroup>
<VolumeSlider
initialValue = { _volume }
key = 'volume-slider'
onChange = { _onVolumeChange } />
</ContextMenuItemGroup>
)}
{breakoutRoomsButtons.length > 0 && (
<ContextMenuItemGroup>
<div className = { styles.text }>
{t('breakoutRooms.actions.sendToBreakoutRoom')}
</div>
{breakoutRoomsButtons}
</ContextMenuItemGroup>
)}
</>
)}
</ContextMenu>
);
};
export default ParticipantContextMenu;