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.

MeetingParticipants.tsx 7.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. /* eslint-disable lines-around-comment */
  2. import React, { useCallback } from 'react';
  3. import { useTranslation } from 'react-i18next';
  4. import { useDispatch, useSelector } from 'react-redux';
  5. import { makeStyles } from 'tss-react/mui';
  6. import { IReduxState } from '../../../app/types';
  7. import { rejectParticipantAudio } from '../../../av-moderation/actions';
  8. import participantsPaneTheme from '../../../base/components/themes/participantsPaneTheme.json';
  9. import { isToolbarButtonEnabled } from '../../../base/config/functions.web';
  10. import { MEDIA_TYPE } from '../../../base/media/constants';
  11. import { getParticipantById, isScreenShareParticipant } from '../../../base/participants/functions';
  12. import { connect } from '../../../base/redux/functions';
  13. import { withPixelLineHeight } from '../../../base/styles/functions.web';
  14. import Input from '../../../base/ui/components/web/Input';
  15. import useContextMenu from '../../../base/ui/hooks/useContextMenu.web';
  16. import { normalizeAccents } from '../../../base/util/strings.web';
  17. import { getBreakoutRooms, getCurrentRoomId, isInBreakoutRoom } from '../../../breakout-rooms/functions';
  18. import { showOverflowDrawer } from '../../../toolbox/functions.web';
  19. import { muteRemote } from '../../../video-menu/actions.web';
  20. import { getSortedParticipantIds, shouldRenderInviteButton } from '../../functions';
  21. import { useParticipantDrawer } from '../../hooks';
  22. import { InviteButton } from './InviteButton';
  23. // @ts-ignore
  24. import MeetingParticipantContextMenu from './MeetingParticipantContextMenu';
  25. // @ts-ignore
  26. import MeetingParticipantItems from './MeetingParticipantItems';
  27. /* eslint-enable lines-around-comment */
  28. const useStyles = makeStyles()(theme => {
  29. return {
  30. heading: {
  31. color: theme.palette.text02,
  32. // @ts-ignore
  33. ...withPixelLineHeight(theme.typography.labelButton),
  34. margin: `8px 0 ${participantsPaneTheme.panePadding}px`,
  35. [`@media(max-width: ${participantsPaneTheme.MD_BREAKPOINT})`]: {
  36. // @ts-ignore
  37. ...withPixelLineHeight(theme.typography.labelButtonLarge)
  38. }
  39. },
  40. search: {
  41. '& input': {
  42. textAlign: 'center',
  43. paddingRight: '16px'
  44. }
  45. }
  46. };
  47. });
  48. interface IProps {
  49. currentRoom?: { name: string; };
  50. overflowDrawer?: boolean;
  51. participantsCount?: number;
  52. searchString: string;
  53. setSearchString: (newValue: string) => void;
  54. showInviteButton?: boolean;
  55. sortedParticipantIds?: Array<string>;
  56. }
  57. /**
  58. * Renders the MeetingParticipantList component.
  59. * NOTE: This component is not using useSelector on purpose. The child components MeetingParticipantItem
  60. * and MeetingParticipantContextMenu are using connect. Having those mixed leads to problems.
  61. * When this one was using useSelector and the other two were not -the other two were re-rendered before this one was
  62. * re-rendered, so when participant is leaving, we first re-render the item and menu components,
  63. * throwing errors (closing the page) before removing those components for the participant that left.
  64. *
  65. * @returns {ReactNode} - The component.
  66. */
  67. function MeetingParticipants({
  68. currentRoom,
  69. overflowDrawer,
  70. participantsCount,
  71. searchString,
  72. setSearchString,
  73. showInviteButton,
  74. sortedParticipantIds = []
  75. }: IProps) {
  76. const dispatch = useDispatch();
  77. const { t } = useTranslation();
  78. const [ lowerMenu, , toggleMenu, menuEnter, menuLeave, raiseContext ] = useContextMenu();
  79. const muteAudio = useCallback(id => () => {
  80. dispatch(muteRemote(id, MEDIA_TYPE.AUDIO));
  81. dispatch(rejectParticipantAudio(id));
  82. }, [ dispatch ]);
  83. const [ drawerParticipant, closeDrawer, openDrawerForParticipant ] = useParticipantDrawer();
  84. // FIXME:
  85. // It seems that useTranslation is not very scalable. Unmount 500 components that have the useTranslation hook is
  86. // taking more than 10s. To workaround the issue we need to pass the texts as props. This is temporary and dirty
  87. // solution!!!
  88. // One potential proper fix would be to use react-window component in order to lower the number of components
  89. // mounted.
  90. const participantActionEllipsisLabel = t('participantsPane.actions.moreParticipantOptions');
  91. const youText = t('chat.you');
  92. const askUnmuteText = t('participantsPane.actions.askUnmute');
  93. const muteParticipantButtonText = t('dialog.muteParticipantButton');
  94. const isBreakoutRoom = useSelector(isInBreakoutRoom);
  95. const { classes: styles } = useStyles();
  96. return (
  97. <>
  98. <div className = { styles.heading }>
  99. {currentRoom?.name
  100. ? `${currentRoom.name} (${participantsCount})`
  101. : t('participantsPane.headings.participantsList', { count: participantsCount })}
  102. </div>
  103. {showInviteButton && <InviteButton />}
  104. <Input
  105. className = { styles.search }
  106. clearable = { true }
  107. onChange = { setSearchString }
  108. placeholder = { t('participantsPane.search') }
  109. value = { searchString } />
  110. <div>
  111. <MeetingParticipantItems
  112. askUnmuteText = { askUnmuteText }
  113. isInBreakoutRoom = { isBreakoutRoom }
  114. lowerMenu = { lowerMenu }
  115. muteAudio = { muteAudio }
  116. muteParticipantButtonText = { muteParticipantButtonText }
  117. openDrawerForParticipant = { openDrawerForParticipant }
  118. overflowDrawer = { overflowDrawer }
  119. participantActionEllipsisLabel = { participantActionEllipsisLabel }
  120. participantIds = { sortedParticipantIds }
  121. participantsCount = { participantsCount }
  122. raiseContextId = { raiseContext.entity }
  123. searchString = { normalizeAccents(searchString) }
  124. toggleMenu = { toggleMenu }
  125. youText = { youText } />
  126. </div>
  127. <MeetingParticipantContextMenu
  128. closeDrawer = { closeDrawer }
  129. drawerParticipant = { drawerParticipant }
  130. muteAudio = { muteAudio }
  131. offsetTarget = { raiseContext?.offsetTarget }
  132. onEnter = { menuEnter }
  133. onLeave = { menuLeave }
  134. onSelect = { lowerMenu }
  135. overflowDrawer = { overflowDrawer }
  136. participantID = { raiseContext?.entity } />
  137. </>
  138. );
  139. }
  140. /**
  141. * Maps (parts of) the redux state to the associated props for this component.
  142. *
  143. * @param {Object} state - The Redux state.
  144. * @param {Object} ownProps - The own props of the component.
  145. * @private
  146. * @returns {IProps}
  147. */
  148. function _mapStateToProps(state: IReduxState): Object {
  149. let sortedParticipantIds: any = getSortedParticipantIds(state);
  150. // Filter out the virtual screenshare participants since we do not want them to be displayed as separate
  151. // participants in the participants pane.
  152. sortedParticipantIds = sortedParticipantIds.filter((id: any) => {
  153. const participant = getParticipantById(state, id);
  154. return !isScreenShareParticipant(participant);
  155. });
  156. const participantsCount = sortedParticipantIds.length;
  157. const showInviteButton = shouldRenderInviteButton(state) && isToolbarButtonEnabled('invite', state);
  158. const overflowDrawer = showOverflowDrawer(state);
  159. const currentRoomId = getCurrentRoomId(state);
  160. const currentRoom = getBreakoutRooms(state)[currentRoomId];
  161. return {
  162. currentRoom,
  163. overflowDrawer,
  164. participantsCount,
  165. showInviteButton,
  166. sortedParticipantIds
  167. };
  168. }
  169. export default connect(_mapStateToProps)(MeetingParticipants);