You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

MeetingParticipantContextMenu.js 6.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. // @flow
  2. import React, { useCallback, useLayoutEffect, useRef, useState } from 'react';
  3. import { useTranslation } from 'react-i18next';
  4. import { useDispatch, useSelector } from 'react-redux';
  5. import { isToolbarButtonEnabled } from '../../base/config/functions.web';
  6. import { openDialog } from '../../base/dialog';
  7. import {
  8. IconCloseCircle,
  9. IconCrown,
  10. IconMessage,
  11. IconMicDisabled,
  12. IconMuteEveryoneElse,
  13. IconVideoOff
  14. } from '../../base/icons';
  15. import { isLocalParticipantModerator, isParticipantModerator } from '../../base/participants';
  16. import { getIsParticipantAudioMuted, getIsParticipantVideoMuted } from '../../base/tracks';
  17. import { openChat } from '../../chat/actions';
  18. import { GrantModeratorDialog, KickRemoteParticipantDialog, MuteEveryoneDialog } from '../../video-menu';
  19. import MuteRemoteParticipantsVideoDialog from '../../video-menu/components/web/MuteRemoteParticipantsVideoDialog';
  20. import { getComputedOuterHeight } from '../functions';
  21. import {
  22. ContextMenu,
  23. ContextMenuIcon,
  24. ContextMenuItem,
  25. ContextMenuItemGroup,
  26. ignoredChildClassName
  27. } from './styled';
  28. type Props = {
  29. /**
  30. * Callback used to open a confirmation dialog for audio muting.
  31. */
  32. muteAudio: Function,
  33. /**
  34. * Target elements against which positioning calculations are made
  35. */
  36. offsetTarget: HTMLElement,
  37. /**
  38. * Callback for the mouse entering the component
  39. */
  40. onEnter: Function,
  41. /**
  42. * Callback for the mouse leaving the component
  43. */
  44. onLeave: Function,
  45. /**
  46. * Callback for making a selection in the menu
  47. */
  48. onSelect: Function,
  49. /**
  50. * Participant reference
  51. */
  52. participant: Object
  53. };
  54. export const MeetingParticipantContextMenu = ({
  55. offsetTarget,
  56. onEnter,
  57. onLeave,
  58. onSelect,
  59. muteAudio,
  60. participant
  61. }: Props) => {
  62. const dispatch = useDispatch();
  63. const containerRef = useRef(null);
  64. const isLocalModerator = useSelector(isLocalParticipantModerator);
  65. const isChatButtonEnabled = useSelector(isToolbarButtonEnabled('chat'));
  66. const isParticipantVideoMuted = useSelector(getIsParticipantVideoMuted(participant));
  67. const isParticipantAudioMuted = useSelector(getIsParticipantAudioMuted(participant));
  68. const [ isHidden, setIsHidden ] = useState(true);
  69. const { t } = useTranslation();
  70. useLayoutEffect(() => {
  71. if (participant
  72. && containerRef.current
  73. && offsetTarget?.offsetParent
  74. && offsetTarget.offsetParent instanceof HTMLElement
  75. ) {
  76. const { current: container } = containerRef;
  77. const { offsetTop, offsetParent: { offsetHeight, scrollTop } } = offsetTarget;
  78. const outerHeight = getComputedOuterHeight(container);
  79. container.style.top = offsetTop + outerHeight > offsetHeight + scrollTop
  80. ? offsetTop - outerHeight
  81. : offsetTop;
  82. setIsHidden(false);
  83. } else {
  84. setIsHidden(true);
  85. }
  86. }, [ participant, offsetTarget ]);
  87. const grantModerator = useCallback(() => {
  88. dispatch(openDialog(GrantModeratorDialog, {
  89. participantID: participant.id
  90. }));
  91. }, [ dispatch, participant ]);
  92. const kick = useCallback(() => {
  93. dispatch(openDialog(KickRemoteParticipantDialog, {
  94. participantID: participant.id
  95. }));
  96. }, [ dispatch, participant ]);
  97. const muteEveryoneElse = useCallback(() => {
  98. dispatch(openDialog(MuteEveryoneDialog, {
  99. exclude: [ participant.id ]
  100. }));
  101. }, [ dispatch, participant ]);
  102. const muteVideo = useCallback(() => {
  103. dispatch(openDialog(MuteRemoteParticipantsVideoDialog, {
  104. participantID: participant.id
  105. }));
  106. }, [ dispatch, participant ]);
  107. const sendPrivateMessage = useCallback(() => {
  108. dispatch(openChat(participant));
  109. }, [ dispatch, participant ]);
  110. if (!participant) {
  111. return null;
  112. }
  113. return (
  114. <ContextMenu
  115. className = { ignoredChildClassName }
  116. innerRef = { containerRef }
  117. isHidden = { isHidden }
  118. onClick = { onSelect }
  119. onMouseEnter = { onEnter }
  120. onMouseLeave = { onLeave }>
  121. <ContextMenuItemGroup>
  122. {isLocalModerator && (
  123. <>
  124. {!isParticipantAudioMuted
  125. && <ContextMenuItem onClick = { muteAudio(participant) }>
  126. <ContextMenuIcon src = { IconMicDisabled } />
  127. <span>{t('dialog.muteParticipantButton')}</span>
  128. </ContextMenuItem>}
  129. <ContextMenuItem onClick = { muteEveryoneElse }>
  130. <ContextMenuIcon src = { IconMuteEveryoneElse } />
  131. <span>{t('toolbar.accessibilityLabel.muteEveryoneElse')}</span>
  132. </ContextMenuItem>
  133. </>
  134. )}
  135. {isLocalModerator && (isParticipantVideoMuted || (
  136. <ContextMenuItem onClick = { muteVideo }>
  137. <ContextMenuIcon src = { IconVideoOff } />
  138. <span>{t('participantsPane.actions.stopVideo')}</span>
  139. </ContextMenuItem>
  140. ))}
  141. </ContextMenuItemGroup>
  142. <ContextMenuItemGroup>
  143. {isLocalModerator && (
  144. <>
  145. {!isParticipantModerator(participant)
  146. && <ContextMenuItem onClick = { grantModerator }>
  147. <ContextMenuIcon src = { IconCrown } />
  148. <span>{t('toolbar.accessibilityLabel.grantModerator')}</span>
  149. </ContextMenuItem>}
  150. <ContextMenuItem onClick = { kick }>
  151. <ContextMenuIcon src = { IconCloseCircle } />
  152. <span>{t('videothumbnail.kick')}</span>
  153. </ContextMenuItem>
  154. </>
  155. )}
  156. {isChatButtonEnabled && (
  157. <ContextMenuItem onClick = { sendPrivateMessage }>
  158. <ContextMenuIcon src = { IconMessage } />
  159. <span>{t('toolbar.accessibilityLabel.privateMessage')}</span>
  160. </ContextMenuItem>
  161. )}
  162. </ContextMenuItemGroup>
  163. </ContextMenu>
  164. );
  165. };