您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

MeetingParticipants.tsx 7.8KB

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