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

ParticipantsPane.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. // @flow
  2. import { withStyles } from '@material-ui/core';
  3. import React, { Component } from 'react';
  4. import participantsPaneTheme from '../../../base/components/themes/participantsPaneTheme.json';
  5. import { openDialog } from '../../../base/dialog';
  6. import { translate } from '../../../base/i18n';
  7. import { Icon, IconClose, IconHorizontalPoints } from '../../../base/icons';
  8. import { isLocalParticipantModerator } from '../../../base/participants';
  9. import { connect } from '../../../base/redux';
  10. import { getBreakoutRoomsConfig } from '../../../breakout-rooms/functions';
  11. import { MuteEveryoneDialog } from '../../../video-menu/components/';
  12. import { close } from '../../actions';
  13. import { findAncestorByClass, getParticipantsPaneOpen } from '../../functions';
  14. import { AddBreakoutRoomButton } from '../breakout-rooms/components/web/AddBreakoutRoomButton';
  15. import { RoomList } from '../breakout-rooms/components/web/RoomList';
  16. import FooterButton from './FooterButton';
  17. import { FooterContextMenu } from './FooterContextMenu';
  18. import LobbyParticipants from './LobbyParticipants';
  19. import MeetingParticipants from './MeetingParticipants';
  20. /**
  21. * The type of the React {@code Component} props of {@link ParticipantsPane}.
  22. */
  23. type Props = {
  24. /**
  25. * Whether there is backend support for Breakout Rooms.
  26. */
  27. _isBreakoutRoomsSupported: Boolean,
  28. /**
  29. * Whether to display the context menu as a drawer.
  30. */
  31. _overflowDrawer: boolean,
  32. /**
  33. * Should the add breakout room button be displayed?
  34. */
  35. _showAddRoomButton: boolean,
  36. /**
  37. * Is the participants pane open.
  38. */
  39. _paneOpen: boolean,
  40. /**
  41. * Whether to show the footer menu.
  42. */
  43. _showFooter: boolean,
  44. /**
  45. * The Redux dispatch function.
  46. */
  47. dispatch: Function,
  48. /**
  49. * An object containing the CSS classes.
  50. */
  51. classes: Object,
  52. /**
  53. * The i18n translate function.
  54. */
  55. t: Function
  56. };
  57. /**
  58. * The type of the React {@code Component} state of {@link ParticipantsPane}.
  59. */
  60. type State = {
  61. /**
  62. * Indicates if the footer context menu is open.
  63. */
  64. contextOpen: boolean,
  65. /**
  66. * Participants search string.
  67. */
  68. searchString: string
  69. };
  70. const styles = theme => {
  71. return {
  72. container: {
  73. boxSizing: 'border-box',
  74. flex: 1,
  75. overflowY: 'auto',
  76. position: 'relative',
  77. padding: `0 ${participantsPaneTheme.panePadding}px`,
  78. [`& > * + *:not(.${participantsPaneTheme.ignoredChildClassName})`]: {
  79. marginTop: theme.spacing(3)
  80. },
  81. '&::-webkit-scrollbar': {
  82. display: 'none'
  83. }
  84. },
  85. closeButton: {
  86. alignItems: 'center',
  87. cursor: 'pointer',
  88. display: 'flex',
  89. justifyContent: 'center'
  90. },
  91. header: {
  92. alignItems: 'center',
  93. boxSizing: 'border-box',
  94. display: 'flex',
  95. height: `${participantsPaneTheme.headerSize}px`,
  96. padding: '0 20px',
  97. justifyContent: 'flex-end'
  98. },
  99. antiCollapse: {
  100. fontSize: 0,
  101. '&:first-child': {
  102. display: 'none'
  103. },
  104. '&:first-child + *': {
  105. marginTop: 0
  106. }
  107. },
  108. footer: {
  109. display: 'flex',
  110. justifyContent: 'flex-end',
  111. padding: `${theme.spacing(4)}px ${participantsPaneTheme.panePadding}px`,
  112. '& > *:not(:last-child)': {
  113. marginRight: `${theme.spacing(3)}px`
  114. }
  115. },
  116. footerMoreContainer: {
  117. position: 'relative'
  118. }
  119. };
  120. };
  121. /**
  122. * Implements the participants list.
  123. */
  124. class ParticipantsPane extends Component<Props, State> {
  125. /**
  126. * Initializes a new {@code ParticipantsPane} instance.
  127. *
  128. * @inheritdoc
  129. */
  130. constructor(props) {
  131. super(props);
  132. this.state = {
  133. contextOpen: false,
  134. searchString: ''
  135. };
  136. // Bind event handlers so they are only bound once per instance.
  137. this._onClosePane = this._onClosePane.bind(this);
  138. this._onDrawerClose = this._onDrawerClose.bind(this);
  139. this._onKeyPress = this._onKeyPress.bind(this);
  140. this._onMuteAll = this._onMuteAll.bind(this);
  141. this._onToggleContext = this._onToggleContext.bind(this);
  142. this._onWindowClickListener = this._onWindowClickListener.bind(this);
  143. this.setSearchString = this.setSearchString.bind(this);
  144. }
  145. /**
  146. * Implements React's {@link Component#componentDidMount()}.
  147. *
  148. * @inheritdoc
  149. */
  150. componentDidMount() {
  151. window.addEventListener('click', this._onWindowClickListener);
  152. }
  153. /**
  154. * Implements React's {@link Component#componentWillUnmount()}.
  155. *
  156. * @inheritdoc
  157. */
  158. componentWillUnmount() {
  159. window.removeEventListener('click', this._onWindowClickListener);
  160. }
  161. /**
  162. * Implements React's {@link Component#render}.
  163. *
  164. * @inheritdoc
  165. */
  166. render() {
  167. const {
  168. _isBreakoutRoomsSupported,
  169. _paneOpen,
  170. _showAddRoomButton,
  171. _showFooter,
  172. classes,
  173. t
  174. } = this.props;
  175. const { contextOpen, searchString } = this.state;
  176. // when the pane is not open optimize to not
  177. // execute the MeetingParticipantList render for large list of participants
  178. if (!_paneOpen) {
  179. return null;
  180. }
  181. return (
  182. <div className = 'participants_pane'>
  183. <div className = 'participants_pane-content'>
  184. <div className = { classes.header }>
  185. <div
  186. aria-label = { t('participantsPane.close', 'Close') }
  187. className = { classes.closeButton }
  188. onClick = { this._onClosePane }
  189. onKeyPress = { this._onKeyPress }
  190. role = 'button'
  191. tabIndex = { 0 }>
  192. <Icon
  193. size = { 24 }
  194. src = { IconClose } />
  195. </div>
  196. </div>
  197. <div className = { classes.container }>
  198. <LobbyParticipants />
  199. <br className = { classes.antiCollapse } />
  200. <MeetingParticipants
  201. searchString = { searchString }
  202. setSearchString = { this.setSearchString } />
  203. {_isBreakoutRoomsSupported && <RoomList searchString = { searchString } />}
  204. {_showAddRoomButton && <AddBreakoutRoomButton />}
  205. </div>
  206. {_showFooter && (
  207. <div className = { classes.footer }>
  208. <FooterButton
  209. accessibilityLabel = { t('participantsPane.actions.muteAll') }
  210. onClick = { this._onMuteAll }>
  211. {t('participantsPane.actions.muteAll')}
  212. </FooterButton>
  213. <div className = { classes.footerMoreContainer }>
  214. <FooterButton
  215. accessibilityLabel = { t('participantsPane.actions.moreModerationActions') }
  216. id = 'participants-pane-context-menu'
  217. isIconButton = { true }
  218. onClick = { this._onToggleContext }>
  219. <Icon src = { IconHorizontalPoints } />
  220. </FooterButton>
  221. <FooterContextMenu
  222. isOpen = { contextOpen }
  223. onDrawerClose = { this._onDrawerClose }
  224. onMouseLeave = { this._onToggleContext } />
  225. </div>
  226. </div>
  227. )}
  228. </div>
  229. </div>
  230. );
  231. }
  232. setSearchString: (string) => void;
  233. /**
  234. * Sets the search string.
  235. *
  236. * @param {string} newSearchString - The new search string.
  237. * @returns {void}
  238. */
  239. setSearchString(newSearchString) {
  240. this.setState({
  241. searchString: newSearchString
  242. });
  243. }
  244. _onClosePane: () => void;
  245. /**
  246. * Callback for closing the participant pane.
  247. *
  248. * @private
  249. * @returns {void}
  250. */
  251. _onClosePane() {
  252. this.props.dispatch(close());
  253. }
  254. _onDrawerClose: () => void;
  255. /**
  256. * Callback for closing the drawer.
  257. *
  258. * @private
  259. * @returns {void}
  260. */
  261. _onDrawerClose() {
  262. this.setState({
  263. contextOpen: false
  264. });
  265. }
  266. _onKeyPress: (Object) => void;
  267. /**
  268. * KeyPress handler for accessibility for closing the participants pane.
  269. *
  270. * @param {Object} e - The key event to handle.
  271. *
  272. * @returns {void}
  273. */
  274. _onKeyPress(e) {
  275. if (e.key === ' ' || e.key === 'Enter') {
  276. e.preventDefault();
  277. this._onClosePane();
  278. }
  279. }
  280. _onMuteAll: () => void;
  281. /**
  282. * The handler for clicking mute all button.
  283. *
  284. * @returns {void}
  285. */
  286. _onMuteAll() {
  287. this.props.dispatch(openDialog(MuteEveryoneDialog));
  288. }
  289. _onToggleContext: () => void;
  290. /**
  291. * Handler for toggling open/close of the footer context menu.
  292. *
  293. * @returns {void}
  294. */
  295. _onToggleContext() {
  296. this.setState({
  297. contextOpen: !this.state.contextOpen
  298. });
  299. }
  300. _onWindowClickListener: (event: Object) => void;
  301. /**
  302. * Window click event listener.
  303. *
  304. * @param {Event} e - The click event.
  305. * @returns {void}
  306. */
  307. _onWindowClickListener(e) {
  308. if (this.state.contextOpen && !findAncestorByClass(e.target, this.props.classes.footerMoreContainer)) {
  309. this.setState({
  310. contextOpen: false
  311. });
  312. }
  313. }
  314. }
  315. /**
  316. * Maps (parts of) the redux state to the React {@code Component} props of
  317. * {@code ParticipantsPane}.
  318. *
  319. * @param {Object} state - The redux state.
  320. * @protected
  321. * @returns {Props}
  322. */
  323. function _mapStateToProps(state: Object) {
  324. const isPaneOpen = getParticipantsPaneOpen(state);
  325. const { hideAddRoomButton } = getBreakoutRoomsConfig(state);
  326. const { conference } = state['features/base/conference'];
  327. // $FlowExpectedError
  328. const _isBreakoutRoomsSupported = conference?.getBreakoutRooms()?.isSupported();
  329. const _isLocalParticipantModerator = isLocalParticipantModerator(state);
  330. return {
  331. _isBreakoutRoomsSupported,
  332. _paneOpen: isPaneOpen,
  333. _showAddRoomButton: _isBreakoutRoomsSupported && !hideAddRoomButton && _isLocalParticipantModerator,
  334. _showFooter: isPaneOpen && isLocalParticipantModerator(state)
  335. };
  336. }
  337. export default translate(connect(_mapStateToProps)(withStyles(styles)(ParticipantsPane)));