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 5.6KB

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