fix(av-moderation): buttons for participants pane (#12977)

* fix(av): buttons for participants pane

* fix tests

* fix lint

* rename cliked from participant pane
This commit is contained in:
Gabriel Borlea 2023-03-06 19:05:26 +02:00 committed by GitHub
parent f727b9295f
commit 02c232440e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 200 additions and 119 deletions

View File

@ -15,11 +15,15 @@ export type MediaType = 'audio' | 'video' | 'screenshare';
* *
* @enum {string} * @enum {string}
*/ */
export const MEDIA_TYPE: { [key: string]: MediaType; } = { export const MEDIA_TYPE: {
AUDIO: 'audio', AUDIO: MediaType;
SCREENSHARE: 'screenshare', SCREENSHARE: MediaType;
VIDEO: 'video' VIDEO: MediaType;
}; } = {
AUDIO: 'audio',
SCREENSHARE: 'screenshare',
VIDEO: 'video'
};
/* eslint-disable no-bitwise */ /* eslint-disable no-bitwise */

View File

@ -2,7 +2,6 @@
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { translate } from '../../../base/i18n';
import { JitsiTrackEvents } from '../../../base/lib-jitsi-meet'; import { JitsiTrackEvents } from '../../../base/lib-jitsi-meet';
import { MEDIA_TYPE } from '../../../base/media'; import { MEDIA_TYPE } from '../../../base/media';
import { import {
@ -163,9 +162,9 @@ type Props = {
participantID: ?string, participantID: ?string,
/** /**
* The translate function. * Callback used to stop a participant's video.
*/ */
t: Function, stopVideo: Function,
/** /**
* The translated "you" text. * The translated "you" text.
@ -192,17 +191,15 @@ function MeetingParticipantItem({
_quickActionButtonType, _quickActionButtonType,
_raisedHand, _raisedHand,
_videoMediaState, _videoMediaState,
askUnmuteText,
isHighlighted, isHighlighted,
isInBreakoutRoom, isInBreakoutRoom,
muteAudio, muteAudio,
muteParticipantButtonText,
onContextMenu, onContextMenu,
onLeave, onLeave,
openDrawerForParticipant, openDrawerForParticipant,
overflowDrawer, overflowDrawer,
participantActionEllipsisLabel, participantActionEllipsisLabel,
t, stopVideo,
youText youText
}: Props) { }: Props) {
@ -242,12 +239,6 @@ function MeetingParticipantItem({
const audioMediaState = _audioMediaState === MEDIA_STATE.UNMUTED && hasAudioLevels const audioMediaState = _audioMediaState === MEDIA_STATE.UNMUTED && hasAudioLevels
? MEDIA_STATE.DOMINANT_SPEAKER : _audioMediaState; ? MEDIA_STATE.DOMINANT_SPEAKER : _audioMediaState;
let askToUnmuteText = askUnmuteText;
if (_audioMediaState !== MEDIA_STATE.FORCE_MUTED && _videoMediaState === MEDIA_STATE.FORCE_MUTED) {
askToUnmuteText = t('participantsPane.actions.allowVideo');
}
return ( return (
<ParticipantItem <ParticipantItem
actionsTrigger = { ACTION_TRIGGER.HOVER } actionsTrigger = { ACTION_TRIGGER.HOVER }
@ -273,16 +264,16 @@ function MeetingParticipantItem({
&& <> && <>
{!isInBreakoutRoom && ( {!isInBreakoutRoom && (
<ParticipantQuickAction <ParticipantQuickAction
askUnmuteText = { askToUnmuteText }
buttonType = { _quickActionButtonType } buttonType = { _quickActionButtonType }
muteAudio = { muteAudio } muteAudio = { muteAudio }
muteParticipantButtonText = { muteParticipantButtonText }
participantID = { _participantID } participantID = { _participantID }
participantName = { _displayName } /> participantName = { _displayName }
stopVideo = { stopVideo } />
)} )}
<ParticipantActionEllipsis <ParticipantActionEllipsis
accessibilityLabel = { participantActionEllipsisLabel } accessibilityLabel = { participantActionEllipsisLabel }
onClick = { onContextMenu } /> onClick = { onContextMenu }
participantID = { _participantID } />
</> </>
} }
@ -318,7 +309,7 @@ function _mapStateToProps(state, ownProps): Object {
const _isVideoMuted = isParticipantVideoMuted(participant, state); const _isVideoMuted = isParticipantVideoMuted(participant, state);
const _audioMediaState = getParticipantAudioMediaState(participant, _isAudioMuted, state); const _audioMediaState = getParticipantAudioMediaState(participant, _isAudioMuted, state);
const _videoMediaState = getParticipantVideoMediaState(participant, _isVideoMuted, state); const _videoMediaState = getParticipantVideoMediaState(participant, _isVideoMuted, state);
const _quickActionButtonType = getQuickActionButtonType(participant, _isAudioMuted, state); const _quickActionButtonType = getQuickActionButtonType(participant, _isAudioMuted, _isVideoMuted, state);
const tracks = state['features/base/tracks']; const tracks = state['features/base/tracks'];
const _audioTrack = participantID === localParticipantId const _audioTrack = participantID === localParticipantId
@ -342,4 +333,4 @@ function _mapStateToProps(state, ownProps): Object {
}; };
} }
export default translate(connect(_mapStateToProps)(MeetingParticipantItem)); export default connect(_mapStateToProps)(MeetingParticipantItem);

View File

@ -66,6 +66,11 @@ type Props = {
*/ */
searchString?: string, searchString?: string,
/**
* Callback used to stop a participant's video.
*/
stopVideo: Function,
/** /**
* The translated "you" text. * The translated "you" text.
*/ */
@ -78,28 +83,25 @@ type Props = {
* @returns {ReactNode} * @returns {ReactNode}
*/ */
function MeetingParticipantItems({ function MeetingParticipantItems({
askUnmuteText,
isInBreakoutRoom, isInBreakoutRoom,
lowerMenu, lowerMenu,
toggleMenu, toggleMenu,
muteAudio, muteAudio,
muteParticipantButtonText,
participantIds, participantIds,
openDrawerForParticipant, openDrawerForParticipant,
overflowDrawer, overflowDrawer,
raiseContextId, raiseContextId,
participantActionEllipsisLabel, participantActionEllipsisLabel,
searchString, searchString,
stopVideo,
youText youText
}: Props) { }: Props) {
const renderParticipant = id => ( const renderParticipant = id => (
<MeetingParticipantItem <MeetingParticipantItem
askUnmuteText = { askUnmuteText }
isHighlighted = { raiseContextId === id } isHighlighted = { raiseContextId === id }
isInBreakoutRoom = { isInBreakoutRoom } isInBreakoutRoom = { isInBreakoutRoom }
key = { id } key = { id }
muteAudio = { muteAudio } muteAudio = { muteAudio }
muteParticipantButtonText = { muteParticipantButtonText }
onContextMenu = { toggleMenu(id) } onContextMenu = { toggleMenu(id) }
onLeave = { lowerMenu } onLeave = { lowerMenu }
openDrawerForParticipant = { openDrawerForParticipant } openDrawerForParticipant = { openDrawerForParticipant }
@ -107,6 +109,7 @@ function MeetingParticipantItems({
participantActionEllipsisLabel = { participantActionEllipsisLabel } participantActionEllipsisLabel = { participantActionEllipsisLabel }
participantID = { id } participantID = { id }
searchString = { searchString } searchString = { searchString }
stopVideo = { stopVideo }
youText = { youText } /> youText = { youText } />
); );

View File

@ -5,7 +5,7 @@ import { useDispatch, useSelector } from 'react-redux';
import { makeStyles } from 'tss-react/mui'; import { makeStyles } from 'tss-react/mui';
import { IReduxState } from '../../../app/types'; import { IReduxState } from '../../../app/types';
import { rejectParticipantAudio } from '../../../av-moderation/actions'; import { rejectParticipantAudio, rejectParticipantVideo } from '../../../av-moderation/actions';
import participantsPaneTheme from '../../../base/components/themes/participantsPaneTheme.json'; import participantsPaneTheme from '../../../base/components/themes/participantsPaneTheme.json';
import { isToolbarButtonEnabled } from '../../../base/config/functions.web'; import { isToolbarButtonEnabled } from '../../../base/config/functions.web';
import { MEDIA_TYPE } from '../../../base/media/constants'; import { MEDIA_TYPE } from '../../../base/media/constants';
@ -88,11 +88,14 @@ function MeetingParticipants({
const { t } = useTranslation(); const { t } = useTranslation();
const [ lowerMenu, , toggleMenu, menuEnter, menuLeave, raiseContext ] = useContextMenu(); const [ lowerMenu, , toggleMenu, menuEnter, menuLeave, raiseContext ] = useContextMenu();
const muteAudio = useCallback(id => () => { const muteAudio = useCallback(id => () => {
dispatch(muteRemote(id, MEDIA_TYPE.AUDIO)); dispatch(muteRemote(id, MEDIA_TYPE.AUDIO));
dispatch(rejectParticipantAudio(id)); dispatch(rejectParticipantAudio(id));
}, [ dispatch ]); }, [ dispatch ]);
const stopVideo = useCallback(id => () => {
dispatch(muteRemote(id, MEDIA_TYPE.VIDEO));
dispatch(rejectParticipantVideo(id));
}, [ dispatch ]);
const [ drawerParticipant, closeDrawer, openDrawerForParticipant ] = useParticipantDrawer(); const [ drawerParticipant, closeDrawer, openDrawerForParticipant ] = useParticipantDrawer();
// FIXME: // FIXME:
@ -103,8 +106,6 @@ function MeetingParticipants({
// mounted. // mounted.
const participantActionEllipsisLabel = t('participantsPane.actions.moreParticipantOptions'); const participantActionEllipsisLabel = t('participantsPane.actions.moreParticipantOptions');
const youText = t('chat.you'); const youText = t('chat.you');
const askUnmuteText = t('participantsPane.actions.askUnmute');
const muteParticipantButtonText = t('dialog.muteParticipantButton');
const isBreakoutRoom = useSelector(isInBreakoutRoom); const isBreakoutRoom = useSelector(isInBreakoutRoom);
const visitorsCount = useSelector((state: IReduxState) => state['features/visitors'].count); const visitorsCount = useSelector((state: IReduxState) => state['features/visitors'].count);
@ -136,11 +137,9 @@ function MeetingParticipants({
value = { searchString } /> value = { searchString } />
<div> <div>
<MeetingParticipantItems <MeetingParticipantItems
askUnmuteText = { askUnmuteText }
isInBreakoutRoom = { isBreakoutRoom } isInBreakoutRoom = { isBreakoutRoom }
lowerMenu = { lowerMenu } lowerMenu = { lowerMenu }
muteAudio = { muteAudio } muteAudio = { muteAudio }
muteParticipantButtonText = { muteParticipantButtonText }
openDrawerForParticipant = { openDrawerForParticipant } openDrawerForParticipant = { openDrawerForParticipant }
overflowDrawer = { overflowDrawer } overflowDrawer = { overflowDrawer }
participantActionEllipsisLabel = { participantActionEllipsisLabel } participantActionEllipsisLabel = { participantActionEllipsisLabel }
@ -148,6 +147,7 @@ function MeetingParticipants({
participantsCount = { participantsCount } participantsCount = { participantsCount }
raiseContextId = { raiseContext.entity } raiseContextId = { raiseContext.entity }
searchString = { normalizeAccents(searchString) } searchString = { normalizeAccents(searchString) }
stopVideo = { stopVideo }
toggleMenu = { toggleMenu } toggleMenu = { toggleMenu }
youText = { youText } /> youText = { youText } />
</div> </div>

View File

@ -14,14 +14,17 @@ interface IProps {
* Click handler function. * Click handler function.
*/ */
onClick: () => void; onClick: () => void;
participantID?: string;
} }
const ParticipantActionEllipsis = ({ accessibilityLabel, onClick }: IProps) => ( const ParticipantActionEllipsis = ({ accessibilityLabel, onClick, participantID }: IProps) => (
<Button <Button
accessibilityLabel = { accessibilityLabel } accessibilityLabel = { accessibilityLabel }
icon = { IconDotsHorizontal } icon = { IconDotsHorizontal }
onClick = { onClick } onClick = { onClick }
size = 'small' /> size = 'small'
testId = { participantID ? `participant-more-options-${participantID}` : undefined } />
); );
export default ParticipantActionEllipsis; export default ParticipantActionEllipsis;

View File

@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { makeStyles } from 'tss-react/mui'; import { makeStyles } from 'tss-react/mui';
import { approveParticipant } from '../../../av-moderation/actions'; import { approveParticipantAudio, approveParticipantVideo } from '../../../av-moderation/actions';
import Button from '../../../base/ui/components/web/Button'; import Button from '../../../base/ui/components/web/Button';
import { QUICK_ACTION_BUTTON } from '../../constants'; import { QUICK_ACTION_BUTTON } from '../../constants';
@ -43,6 +43,12 @@ interface IProps {
* The name of the participant. * The name of the participant.
*/ */
participantName: string; participantName: string;
/**
* Callback used to stop a participant's video.
*/
stopVideo: Function;
} }
const useStyles = makeStyles()(theme => { const useStyles = makeStyles()(theme => {
@ -54,19 +60,22 @@ const useStyles = makeStyles()(theme => {
}); });
const ParticipantQuickAction = ({ const ParticipantQuickAction = ({
askUnmuteText,
buttonType, buttonType,
muteAudio, muteAudio,
muteParticipantButtonText,
participantID, participantID,
participantName participantName,
stopVideo
}: IProps) => { }: IProps) => {
const { classes: styles } = useStyles(); const { classes: styles } = useStyles();
const dispatch = useDispatch(); const dispatch = useDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const askToUnmute = useCallback(() => { const askToUnmute = useCallback(() => {
dispatch(approveParticipant(participantID)); dispatch(approveParticipantAudio(participantID));
}, [ dispatch, participantID ]);
const allowVideo = useCallback(() => {
dispatch(approveParticipantVideo(participantID));
}, [ dispatch, participantID ]); }, [ dispatch, participantID ]);
switch (buttonType) { switch (buttonType) {
@ -75,10 +84,10 @@ const ParticipantQuickAction = ({
<Button <Button
accessibilityLabel = { `${t('participantsPane.actions.mute')} ${participantName}` } accessibilityLabel = { `${t('participantsPane.actions.mute')} ${participantName}` }
className = { styles.button } className = { styles.button }
label = { muteParticipantButtonText } label = { t('participantsPane.actions.mute') }
onClick = { muteAudio(participantID) } onClick = { muteAudio(participantID) }
size = 'small' size = 'small'
testId = { `mute-${participantID}` } /> testId = { `mute-audio-${participantID}` } />
); );
} }
case QUICK_ACTION_BUTTON.ASK_TO_UNMUTE: { case QUICK_ACTION_BUTTON.ASK_TO_UNMUTE: {
@ -86,10 +95,32 @@ const ParticipantQuickAction = ({
<Button <Button
accessibilityLabel = { `${t('participantsPane.actions.askUnmute')} ${participantName}` } accessibilityLabel = { `${t('participantsPane.actions.askUnmute')} ${participantName}` }
className = { styles.button } className = { styles.button }
label = { askUnmuteText } label = { t('participantsPane.actions.askUnmute') }
onClick = { askToUnmute } onClick = { askToUnmute }
size = 'small' size = 'small'
testId = { `unmute-${participantID}` } /> testId = { `unmute-audio-${participantID}` } />
);
}
case QUICK_ACTION_BUTTON.ALLOW_VIDEO: {
return (
<Button
accessibilityLabel = { `${t('participantsPane.actions.askUnmute')} ${participantName}` }
className = { styles.button }
label = { t('participantsPane.actions.allowVideo') }
onClick = { allowVideo }
size = 'small'
testId = { `unmute-video-${participantID}` } />
);
}
case QUICK_ACTION_BUTTON.STOP_VIDEO: {
return (
<Button
accessibilityLabel = { `${t('participantsPane.actions.mute')} ${participantName}` }
className = { styles.button }
label = { t('participantsPane.actions.stopVideo') }
onClick = { stopVideo(participantID) }
size = 'small'
testId = { `mute-video-${participantID}` } />
); );
} }
default: { default: {

View File

@ -36,19 +36,23 @@ export const MEDIA_STATE: { [key: string]: MediaState; } = {
NONE: 'None' NONE: 'None'
}; };
export type QuickActionButtonType = 'Mute' | 'AskToUnmute' | 'None'; export type QuickActionButtonType = 'Mute' | 'AskToUnmute' | 'AllowVideo' | 'StopVideo' | 'None';
/** /**
* Enum of possible participant mute button states. * Enum of possible participant mute button states.
*/ */
export const QUICK_ACTION_BUTTON: { export const QUICK_ACTION_BUTTON: {
ALLOW_VIDEO: QuickActionButtonType;
ASK_TO_UNMUTE: QuickActionButtonType; ASK_TO_UNMUTE: QuickActionButtonType;
MUTE: QuickActionButtonType; MUTE: QuickActionButtonType;
NONE: QuickActionButtonType; NONE: QuickActionButtonType;
STOP_VIDEO: QuickActionButtonType;
} = { } = {
ALLOW_VIDEO: 'AllowVideo',
MUTE: 'Mute', MUTE: 'Mute',
ASK_TO_UNMUTE: 'AskToUnmute', ASK_TO_UNMUTE: 'AskToUnmute',
NONE: 'None' NONE: 'None',
STOP_VIDEO: 'StopVideo'
}; };
/** /**

View File

@ -131,15 +131,28 @@ export const getParticipantsPaneOpen = (state: IReduxState) => Boolean(getState(
* *
* @param {IParticipant} participant - The participant. * @param {IParticipant} participant - The participant.
* @param {boolean} isAudioMuted - If audio is muted for the participant. * @param {boolean} isAudioMuted - If audio is muted for the participant.
* @param {boolean} isVideoMuted - If audio is muted for the participant.
* @param {IReduxState} state - The redux state. * @param {IReduxState} state - The redux state.
* @returns {string} - The type of the quick action button. * @returns {string} - The type of the quick action button.
*/ */
export function getQuickActionButtonType(participant: IParticipant, isAudioMuted: Boolean, state: IReduxState) { export function getQuickActionButtonType(
participant: IParticipant,
isAudioMuted: Boolean,
isVideoMuted: Boolean,
state: IReduxState) {
// handled only by moderators // handled only by moderators
const isVideoForceMuted = isForceMuted(participant, MEDIA_TYPE.VIDEO, state);
if (isLocalParticipantModerator(state)) { if (isLocalParticipantModerator(state)) {
if (!isAudioMuted) { if (!isAudioMuted) {
return QUICK_ACTION_BUTTON.MUTE; return QUICK_ACTION_BUTTON.MUTE;
} }
if (!isVideoMuted) {
return QUICK_ACTION_BUTTON.STOP_VIDEO;
}
if (isVideoForceMuted) {
return QUICK_ACTION_BUTTON.ALLOW_VIDEO;
}
if (isSupported()(state)) { if (isSupported()(state)) {
return QUICK_ACTION_BUTTON.ASK_TO_UNMUTE; return QUICK_ACTION_BUTTON.ASK_TO_UNMUTE;
} }

View File

@ -1,49 +0,0 @@
// @flow
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { approveParticipant } from '../../../av-moderation/actions';
import { IconMic } from '../../../base/icons';
import ContextMenuItem from '../../../base/ui/components/web/ContextMenuItem';
type Props = {
/**
* Whether or not the participant is audio force muted.
*/
isAudioForceMuted: boolean,
/**
* Whether or not the participant is video force muted.
*/
isVideoForceMuted: boolean,
/**
* The ID for the participant on which the button will act.
*/
participantID: string
}
const AskToUnmuteButton = ({ isAudioForceMuted, isVideoForceMuted, participantID }: Props) => {
const dispatch = useDispatch();
const { t } = useTranslation();
const _onClick = useCallback(() => {
dispatch(approveParticipant(participantID));
}, [ participantID ]);
const text = isAudioForceMuted || !isVideoForceMuted
? t('participantsPane.actions.askUnmute')
: t('participantsPane.actions.allowVideo');
return (
<ContextMenuItem
accessibilityLabel = { text }
icon = { IconMic }
onClick = { _onClick }
text = { text } />
);
};
export default AskToUnmuteButton;

View File

@ -0,0 +1,61 @@
// @flow
import React, { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { approveParticipantAudio, approveParticipantVideo } from '../../../av-moderation/actions';
import { IconMic, IconVideo } from '../../../base/icons/svg';
import { MEDIA_TYPE, MediaType } from '../../../base/media/constants';
import ContextMenuItem from '../../../base/ui/components/web/ContextMenuItem';
type Props = {
buttonType: MediaType;
/**
* The ID for the participant on which the button will act.
*/
participantID: string;
};
const AskToUnmuteButton = ({ buttonType, participantID }: Props) => {
const dispatch = useDispatch();
const { t } = useTranslation();
const _onClick = useCallback(() => {
if (buttonType === MEDIA_TYPE.AUDIO) {
dispatch(approveParticipantAudio(participantID));
} else if (buttonType === MEDIA_TYPE.VIDEO) {
dispatch(approveParticipantVideo(participantID));
}
}, [ participantID, buttonType ]);
const text = useMemo(() => {
if (buttonType === MEDIA_TYPE.AUDIO) {
return t('participantsPane.actions.askUnmute');
} else if (buttonType === MEDIA_TYPE.VIDEO) {
return t('participantsPane.actions.allowVideo');
}
return '';
}, [ buttonType ]);
const icon = useMemo(() => {
if (buttonType === MEDIA_TYPE.AUDIO) {
return IconMic;
} else if (buttonType === MEDIA_TYPE.VIDEO) {
return IconVideo;
}
}, [ buttonType ]);
return (
<ContextMenuItem
accessibilityLabel = { text }
icon = { icon }
onClick = { _onClick }
testId = { `unmute-${buttonType}-${participantID}` }
text = { text } />
);
};
export default AskToUnmuteButton;

View File

@ -1,6 +1,6 @@
/* eslint-disable lines-around-comment */ /* eslint-disable lines-around-comment */
import React, { useCallback } from 'react'; import React, { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { makeStyles } from 'tss-react/mui'; import { makeStyles } from 'tss-react/mui';
@ -14,14 +14,15 @@ import { MEDIA_TYPE } from '../../../base/media/constants';
import { PARTICIPANT_ROLE } from '../../../base/participants/constants'; import { PARTICIPANT_ROLE } from '../../../base/participants/constants';
import { getLocalParticipant } from '../../../base/participants/functions'; import { getLocalParticipant } from '../../../base/participants/functions';
import { IParticipant } from '../../../base/participants/types'; import { IParticipant } from '../../../base/participants/types';
import { isParticipantAudioMuted } from '../../../base/tracks/functions'; import { isParticipantAudioMuted, isParticipantVideoMuted } from '../../../base/tracks/functions.any';
import ContextMenu from '../../../base/ui/components/web/ContextMenu'; import ContextMenu from '../../../base/ui/components/web/ContextMenu';
import ContextMenuItemGroup from '../../../base/ui/components/web/ContextMenuItemGroup'; import ContextMenuItemGroup from '../../../base/ui/components/web/ContextMenuItemGroup';
import { getBreakoutRooms, getCurrentRoomId, isInBreakoutRoom } from '../../../breakout-rooms/functions'; import { getBreakoutRooms, getCurrentRoomId, isInBreakoutRoom } from '../../../breakout-rooms/functions';
import { displayVerification } from '../../../e2ee/functions'; import { displayVerification } from '../../../e2ee/functions';
import { setVolume } from '../../../filmstrip/actions.web'; import { setVolume } from '../../../filmstrip/actions.web';
import { isStageFilmstripAvailable } from '../../../filmstrip/functions.web'; import { isStageFilmstripAvailable } from '../../../filmstrip/functions.web';
import { isForceMuted } from '../../../participants-pane/functions'; import { QUICK_ACTION_BUTTON } from '../../../participants-pane/constants';
import { getQuickActionButtonType, isForceMuted } from '../../../participants-pane/functions';
// @ts-ignore // @ts-ignore
import { requestRemoteControl, stopController } from '../../../remote-control'; import { requestRemoteControl, stopController } from '../../../remote-control';
import { showOverflowDrawer } from '../../../toolbox/functions.web'; import { showOverflowDrawer } from '../../../toolbox/functions.web';
@ -139,11 +140,10 @@ const ParticipantContextMenu = ({
const localParticipant = useSelector(getLocalParticipant); const localParticipant = useSelector(getLocalParticipant);
const _isModerator = Boolean(localParticipant?.role === PARTICIPANT_ROLE.MODERATOR); const _isModerator = Boolean(localParticipant?.role === PARTICIPANT_ROLE.MODERATOR);
const _isAudioForceMuted = useSelector<IReduxState>(state =>
isForceMuted(participant, MEDIA_TYPE.AUDIO, state));
const _isVideoForceMuted = useSelector<IReduxState>(state => const _isVideoForceMuted = useSelector<IReduxState>(state =>
isForceMuted(participant, MEDIA_TYPE.VIDEO, state)); isForceMuted(participant, MEDIA_TYPE.VIDEO, state));
const _isAudioMuted = useSelector((state: IReduxState) => isParticipantAudioMuted(participant, state)); const _isAudioMuted = useSelector((state: IReduxState) => isParticipantAudioMuted(participant, state));
const _isVideoMuted = useSelector((state: IReduxState) => isParticipantVideoMuted(participant, state));
const _overflowDrawer: boolean = useSelector(showOverflowDrawer); const _overflowDrawer: boolean = useSelector(showOverflowDrawer);
const { remoteVideoMenu = {}, disableRemoteMute, startSilent, customParticipantMenuButtons } const { remoteVideoMenu = {}, disableRemoteMute, startSilent, customParticipantMenuButtons }
= useSelector((state: IReduxState) => state['features/base/config']); = useSelector((state: IReduxState) => state['features/base/config']);
@ -172,6 +172,12 @@ const ParticipantContextMenu = ({
} }
, [ thumbnailMenu, _overflowDrawer, drawerParticipant, participant ]); , [ thumbnailMenu, _overflowDrawer, drawerParticipant, participant ]);
const isClickedFromParticipantPane = useMemo(
() => !_overflowDrawer && !thumbnailMenu,
[ _overflowDrawer, thumbnailMenu ]);
const quickActionButtonType = useSelector((state: IReduxState) =>
getQuickActionButtonType(participant, _isAudioMuted, _isVideoMuted, state));
const buttons: JSX.Element[] = []; const buttons: JSX.Element[] = [];
const buttons2: JSX.Element[] = []; const buttons2: JSX.Element[] = [];
@ -182,30 +188,44 @@ const ParticipantContextMenu = ({
&& !isNaN(_volume); && !isNaN(_volume);
if (_isModerator) { if (_isModerator) {
if ((thumbnailMenu || _overflowDrawer) && isModerationSupported && _isAudioMuted) { if (isModerationSupported) {
buttons.push(<AskToUnmuteButton if (_isAudioMuted
isAudioForceMuted = { _isAudioForceMuted } && !(isClickedFromParticipantPane && quickActionButtonType === QUICK_ACTION_BUTTON.ASK_TO_UNMUTE)) {
isVideoForceMuted = { _isVideoForceMuted } buttons.push(<AskToUnmuteButton
key = 'ask-unmute' buttonType = { MEDIA_TYPE.AUDIO }
participantID = { _getCurrentParticipantId() } /> key = 'ask-unmute'
); participantID = { _getCurrentParticipantId() } />
);
}
if (_isVideoForceMuted
&& !(isClickedFromParticipantPane && quickActionButtonType === QUICK_ACTION_BUTTON.ALLOW_VIDEO)) {
buttons.push(<AskToUnmuteButton
buttonType = { MEDIA_TYPE.VIDEO }
key = 'allow-video'
participantID = { _getCurrentParticipantId() } />
);
}
} }
if (!disableRemoteMute) { if (!disableRemoteMute) {
buttons.push( if (!(isClickedFromParticipantPane && quickActionButtonType === QUICK_ACTION_BUTTON.MUTE)) {
<MuteButton buttons.push(
key = 'mute' <MuteButton
participantID = { _getCurrentParticipantId() } /> key = 'mute'
); participantID = { _getCurrentParticipantId() } />
);
}
buttons.push( buttons.push(
<MuteEveryoneElseButton <MuteEveryoneElseButton
key = 'mute-others' key = 'mute-others'
participantID = { _getCurrentParticipantId() } /> participantID = { _getCurrentParticipantId() } />
); );
buttons.push( if (!(isClickedFromParticipantPane && quickActionButtonType === QUICK_ACTION_BUTTON.STOP_VIDEO)) {
<MuteVideoButton buttons.push(
key = 'mute-video' <MuteVideoButton
participantID = { _getCurrentParticipantId() } /> key = 'mute-video'
); participantID = { _getCurrentParticipantId() } />
);
}
buttons.push( buttons.push(
<MuteEveryoneElsesVideoButton <MuteEveryoneElsesVideoButton
key = 'mute-others-video' key = 'mute-others-video'