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.

ParticipantsPane.tsx 7.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. import React, { useCallback, useEffect, useState } from 'react';
  2. import { useTranslation } from 'react-i18next';
  3. import { useDispatch, useSelector } from 'react-redux';
  4. import { makeStyles } from 'tss-react/mui';
  5. import { IReduxState } from '../../../app/types';
  6. import participantsPaneTheme from '../../../base/components/themes/participantsPaneTheme.json';
  7. import { openDialog } from '../../../base/dialog/actions';
  8. import { IconCloseLarge, IconDotsHorizontal } from '../../../base/icons/svg';
  9. import { isLocalParticipantModerator } from '../../../base/participants/functions';
  10. import Button from '../../../base/ui/components/web/Button';
  11. import ClickableIcon from '../../../base/ui/components/web/ClickableIcon';
  12. import { BUTTON_TYPES } from '../../../base/ui/constants.web';
  13. import { findAncestorByClass } from '../../../base/ui/functions.web';
  14. import { isAddBreakoutRoomButtonVisible } from '../../../breakout-rooms/functions';
  15. import MuteEveryoneDialog from '../../../video-menu/components/web/MuteEveryoneDialog';
  16. import { close } from '../../actions.web';
  17. import {
  18. getParticipantsPaneOpen,
  19. isMoreActionsVisible,
  20. isMuteAllVisible
  21. } from '../../functions';
  22. import { AddBreakoutRoomButton } from '../breakout-rooms/components/web/AddBreakoutRoomButton';
  23. import { RoomList } from '../breakout-rooms/components/web/RoomList';
  24. import { FooterContextMenu } from './FooterContextMenu';
  25. import LobbyParticipants from './LobbyParticipants';
  26. import MeetingParticipants from './MeetingParticipants';
  27. import VisitorsList from './VisitorsList';
  28. const useStyles = makeStyles()(theme => {
  29. return {
  30. participantsPane: {
  31. backgroundColor: theme.palette.ui01,
  32. flexShrink: 0,
  33. overflow: 'hidden',
  34. position: 'relative',
  35. transition: 'width .16s ease-in-out',
  36. width: '315px',
  37. zIndex: 0,
  38. display: 'flex',
  39. flexDirection: 'column',
  40. fontWeight: 600,
  41. height: '100%',
  42. [[ '& > *:first-child', '& > *:last-child' ] as any]: {
  43. flexShrink: 0
  44. },
  45. '@media (max-width: 580px)': {
  46. height: '100dvh',
  47. position: 'fixed',
  48. left: 0,
  49. right: 0,
  50. top: 0,
  51. width: '100%'
  52. }
  53. },
  54. container: {
  55. boxSizing: 'border-box',
  56. flex: 1,
  57. overflowY: 'auto',
  58. position: 'relative',
  59. padding: `0 ${participantsPaneTheme.panePadding}px`,
  60. '&::-webkit-scrollbar': {
  61. display: 'none'
  62. }
  63. },
  64. closeButton: {
  65. alignItems: 'center',
  66. cursor: 'pointer',
  67. display: 'flex',
  68. justifyContent: 'center'
  69. },
  70. header: {
  71. alignItems: 'center',
  72. boxSizing: 'border-box',
  73. display: 'flex',
  74. height: '60px',
  75. padding: `0 ${participantsPaneTheme.panePadding}px`,
  76. justifyContent: 'flex-end'
  77. },
  78. antiCollapse: {
  79. fontSize: 0,
  80. '&:first-child': {
  81. display: 'none'
  82. },
  83. '&:first-child + *': {
  84. marginTop: 0
  85. }
  86. },
  87. footer: {
  88. display: 'flex',
  89. justifyContent: 'flex-end',
  90. padding: `${theme.spacing(4)} ${participantsPaneTheme.panePadding}px`,
  91. '& > *:not(:last-child)': {
  92. marginRight: theme.spacing(3)
  93. }
  94. },
  95. footerMoreContainer: {
  96. position: 'relative'
  97. }
  98. };
  99. });
  100. const ParticipantsPane = () => {
  101. const { classes, cx } = useStyles();
  102. const paneOpen = useSelector(getParticipantsPaneOpen);
  103. const isBreakoutRoomsSupported = useSelector((state: IReduxState) => state['features/base/conference'])
  104. .conference?.getBreakoutRooms()?.isSupported();
  105. const showAddRoomButton = useSelector(isAddBreakoutRoomButtonVisible);
  106. const showFooter = useSelector(isLocalParticipantModerator);
  107. const showMuteAllButton = useSelector(isMuteAllVisible);
  108. const showMoreActionsButton = useSelector(isMoreActionsVisible);
  109. const dispatch = useDispatch();
  110. const { t } = useTranslation();
  111. const [ contextOpen, setContextOpen ] = useState(false);
  112. const [ searchString, setSearchString ] = useState('');
  113. const onWindowClickListener = useCallback((e: any) => {
  114. if (contextOpen && !findAncestorByClass(e.target, classes.footerMoreContainer)) {
  115. setContextOpen(false);
  116. }
  117. }, [ contextOpen ]);
  118. useEffect(() => {
  119. window.addEventListener('click', onWindowClickListener);
  120. return () => {
  121. window.removeEventListener('click', onWindowClickListener);
  122. };
  123. }, []);
  124. const onClosePane = useCallback(() => {
  125. dispatch(close());
  126. }, []);
  127. const onDrawerClose = useCallback(() => {
  128. setContextOpen(false);
  129. }, []);
  130. const onMuteAll = useCallback(() => {
  131. dispatch(openDialog(MuteEveryoneDialog));
  132. }, []);
  133. const onToggleContext = useCallback(() => {
  134. setContextOpen(open => !open);
  135. }, []);
  136. if (!paneOpen) {
  137. return null;
  138. }
  139. return (
  140. <div className = { cx('participants_pane', classes.participantsPane) }>
  141. <div className = { classes.header }>
  142. <ClickableIcon
  143. accessibilityLabel = { t('participantsPane.close', 'Close') }
  144. icon = { IconCloseLarge }
  145. onClick = { onClosePane } />
  146. </div>
  147. <div className = { classes.container }>
  148. <VisitorsList />
  149. <br className = { classes.antiCollapse } />
  150. <LobbyParticipants />
  151. <br className = { classes.antiCollapse } />
  152. <MeetingParticipants
  153. searchString = { searchString }
  154. setSearchString = { setSearchString } />
  155. {isBreakoutRoomsSupported && <RoomList searchString = { searchString } />}
  156. {showAddRoomButton && <AddBreakoutRoomButton />}
  157. </div>
  158. {showFooter && (
  159. <div className = { classes.footer }>
  160. {showMuteAllButton && (
  161. <Button
  162. accessibilityLabel = { t('participantsPane.actions.muteAll') }
  163. labelKey = { 'participantsPane.actions.muteAll' }
  164. onClick = { onMuteAll }
  165. type = { BUTTON_TYPES.SECONDARY } />
  166. )}
  167. {showMoreActionsButton && (
  168. <div className = { classes.footerMoreContainer }>
  169. <Button
  170. accessibilityLabel = { t('participantsPane.actions.moreModerationActions') }
  171. icon = { IconDotsHorizontal }
  172. id = 'participants-pane-context-menu'
  173. onClick = { onToggleContext }
  174. type = { BUTTON_TYPES.SECONDARY } />
  175. <FooterContextMenu
  176. isOpen = { contextOpen }
  177. onDrawerClose = { onDrawerClose }
  178. onMouseLeave = { onToggleContext } />
  179. </div>
  180. )}
  181. </div>
  182. )}
  183. </div>
  184. );
  185. };
  186. export default ParticipantsPane;