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.

Toolbox.js 35KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174
  1. // @flow
  2. import React, { Component } from 'react';
  3. import { connect } from 'react-redux';
  4. import {
  5. ACTION_SHORTCUT_TRIGGERED,
  6. createShortcutEvent,
  7. createToolbarEvent,
  8. sendAnalytics
  9. } from '../../../analytics';
  10. import { openDialog } from '../../../base/dialog';
  11. import { translate } from '../../../base/i18n';
  12. import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
  13. import {
  14. PARTICIPANT_ROLE,
  15. getLocalParticipant,
  16. participantUpdated
  17. } from '../../../base/participants';
  18. import { getLocalVideoTrack, toggleScreensharing } from '../../../base/tracks';
  19. import { ChatCounter } from '../../../chat';
  20. import { toggleDocument } from '../../../etherpad';
  21. import { openFeedbackDialog } from '../../../feedback';
  22. import {
  23. beginAddPeople,
  24. InfoDialogButton,
  25. isAddPeopleEnabled,
  26. isDialOutEnabled
  27. } from '../../../invite';
  28. import { openKeyboardShortcutsDialog } from '../../../keyboard-shortcuts';
  29. import {
  30. StartLiveStreamDialog,
  31. StartRecordingDialog,
  32. StopLiveStreamDialog,
  33. StopRecordingDialog,
  34. getActiveSession
  35. } from '../../../recording';
  36. import { SettingsButton } from '../../../settings';
  37. import { toggleSharedVideo } from '../../../shared-video';
  38. import { toggleChat, toggleProfile } from '../../../side-panel';
  39. import { SpeakerStats } from '../../../speaker-stats';
  40. import {
  41. OverflowMenuVideoQualityItem,
  42. VideoQualityDialog
  43. } from '../../../video-quality';
  44. import {
  45. setFullScreen,
  46. setOverflowMenuVisible,
  47. setToolbarHovered
  48. } from '../../actions';
  49. import AudioMuteButton from '../AudioMuteButton';
  50. import HangupButton from '../HangupButton';
  51. import OverflowMenuButton from './OverflowMenuButton';
  52. import OverflowMenuItem from './OverflowMenuItem';
  53. import OverflowMenuLiveStreamingItem from './OverflowMenuLiveStreamingItem';
  54. import OverflowMenuProfileItem from './OverflowMenuProfileItem';
  55. import ToolbarButton from './ToolbarButton';
  56. import VideoMuteButton from '../VideoMuteButton';
  57. /**
  58. * The type of the React {@code Component} props of {@link Toolbox}.
  59. */
  60. type Props = {
  61. /**
  62. * Whether or not the chat feature is currently displayed.
  63. */
  64. _chatOpen: boolean,
  65. /**
  66. * The {@code JitsiConference} for the current conference.
  67. */
  68. _conference: Object,
  69. /**
  70. * Whether or not desktopsharing was explicitly configured to be disabled.
  71. */
  72. _desktopSharingDisabledByConfig: boolean,
  73. /**
  74. * Whether or not screensharing is initialized.
  75. */
  76. _desktopSharingEnabled: boolean,
  77. /**
  78. * Whether or not a dialog is displayed.
  79. */
  80. _dialog: boolean,
  81. /**
  82. * Whether or not the local participant is currently editing a document.
  83. */
  84. _editingDocument: boolean,
  85. /**
  86. * Whether or not collaborative document editing is enabled.
  87. */
  88. _etherpadInitialized: boolean,
  89. /**
  90. * Whether or not call feedback can be sent.
  91. */
  92. _feedbackConfigured: boolean,
  93. /**
  94. * Whether or not the file recording feature is enabled for use.
  95. */
  96. _fileRecordingsEnabled: boolean,
  97. /**
  98. * The current file recording session, if any.
  99. */
  100. _fileRecordingSession: Object,
  101. /**
  102. * Whether or not the app is currently in full screen.
  103. */
  104. _fullScreen: boolean,
  105. /**
  106. * Whether or not invite should be hidden, regardless of feature
  107. * availability.
  108. */
  109. _hideInviteButton: boolean,
  110. /**
  111. * Whether or not the current user is logged in through a JWT.
  112. */
  113. _isGuest: boolean,
  114. /**
  115. * Whether or not the live streaming feature is enabled for use.
  116. */
  117. _liveStreamingEnabled: boolean,
  118. /**
  119. * The current live streaming session, if any.
  120. */
  121. _liveStreamingSession: ?Object,
  122. /**
  123. * The ID of the local participant.
  124. */
  125. _localParticipantID: String,
  126. /**
  127. * Whether or not the overflow menu is visible.
  128. */
  129. _overflowMenuVisible: boolean,
  130. /**
  131. * Whether or not the local participant's hand is raised.
  132. */
  133. _raisedHand: boolean,
  134. /**
  135. * Whether or not the local participant is screensharing.
  136. */
  137. _screensharing: boolean,
  138. /**
  139. * Whether or not the local participant is sharing a YouTube video.
  140. */
  141. _sharingVideo: boolean,
  142. /**
  143. * Flag showing whether toolbar is visible.
  144. */
  145. _visible: boolean,
  146. /**
  147. * Set with the buttons which this Toolbox should display.
  148. */
  149. _visibleButtons: Set<string>,
  150. /**
  151. * Invoked to active other features of the app.
  152. */
  153. dispatch: Function,
  154. /**
  155. * Invoked to obtain translated strings.
  156. */
  157. t: Function
  158. };
  159. declare var APP: Object;
  160. declare var interfaceConfig: Object;
  161. /**
  162. * Implements the conference toolbox on React/Web.
  163. *
  164. * @extends Component
  165. */
  166. class Toolbox extends Component<Props> {
  167. /**
  168. * Initializes a new {@code Toolbox} instance.
  169. *
  170. * @param {Props} props - The read-only React {@code Component} props with
  171. * which the new instance is to be initialized.
  172. */
  173. constructor(props: Props) {
  174. super(props);
  175. // Bind event handlers so they are only bound once per instance.
  176. this._onMouseOut = this._onMouseOut.bind(this);
  177. this._onMouseOver = this._onMouseOver.bind(this);
  178. this._onSetOverflowVisible = this._onSetOverflowVisible.bind(this);
  179. this._onShortcutToggleChat = this._onShortcutToggleChat.bind(this);
  180. this._onShortcutToggleFullScreen
  181. = this._onShortcutToggleFullScreen.bind(this);
  182. this._onShortcutToggleRaiseHand
  183. = this._onShortcutToggleRaiseHand.bind(this);
  184. this._onShortcutToggleScreenshare
  185. = this._onShortcutToggleScreenshare.bind(this);
  186. this._onToolbarOpenFeedback
  187. = this._onToolbarOpenFeedback.bind(this);
  188. this._onToolbarOpenInvite = this._onToolbarOpenInvite.bind(this);
  189. this._onToolbarOpenKeyboardShortcuts
  190. = this._onToolbarOpenKeyboardShortcuts.bind(this);
  191. this._onToolbarOpenSpeakerStats
  192. = this._onToolbarOpenSpeakerStats.bind(this);
  193. this._onToolbarOpenVideoQuality
  194. = this._onToolbarOpenVideoQuality.bind(this);
  195. this._onToolbarToggleChat = this._onToolbarToggleChat.bind(this);
  196. this._onToolbarToggleEtherpad
  197. = this._onToolbarToggleEtherpad.bind(this);
  198. this._onToolbarToggleFullScreen
  199. = this._onToolbarToggleFullScreen.bind(this);
  200. this._onToolbarToggleLiveStreaming
  201. = this._onToolbarToggleLiveStreaming.bind(this);
  202. this._onToolbarToggleProfile
  203. = this._onToolbarToggleProfile.bind(this);
  204. this._onToolbarToggleRaiseHand
  205. = this._onToolbarToggleRaiseHand.bind(this);
  206. this._onToolbarToggleRecording
  207. = this._onToolbarToggleRecording.bind(this);
  208. this._onToolbarToggleScreenshare
  209. = this._onToolbarToggleScreenshare.bind(this);
  210. this._onToolbarToggleSharedVideo
  211. = this._onToolbarToggleSharedVideo.bind(this);
  212. }
  213. /**
  214. * Sets keyboard shortcuts for to trigger ToolbarButtons actions.
  215. *
  216. * @inheritdoc
  217. * @returns {void}
  218. */
  219. componentDidMount() {
  220. const KEYBOARD_SHORTCUTS = [
  221. this._shouldShowButton('chat') && {
  222. character: 'C',
  223. exec: this._onShortcutToggleChat,
  224. helpDescription: 'keyboardShortcuts.toggleChat'
  225. },
  226. this._shouldShowButton('desktop') && {
  227. character: 'D',
  228. exec: this._onShortcutToggleScreenshare,
  229. helpDescription: 'keyboardShortcuts.toggleScreensharing'
  230. },
  231. this._shouldShowButton('raisehand') && {
  232. character: 'R',
  233. exec: this._onShortcutToggleRaiseHand,
  234. helpDescription: 'keyboardShortcuts.raiseHand'
  235. },
  236. this._shouldShowButton('fullscreen') && {
  237. character: 'S',
  238. exec: this._onShortcutToggleFullScreen,
  239. helpDescription: 'keyboardShortcuts.fullScreen'
  240. }
  241. ];
  242. KEYBOARD_SHORTCUTS.forEach(shortcut => {
  243. if (typeof shortcut === 'object') {
  244. APP.keyboardshortcut.registerShortcut(
  245. shortcut.character,
  246. null,
  247. shortcut.exec,
  248. shortcut.helpDescription);
  249. }
  250. });
  251. }
  252. /**
  253. * Update the visibility of the {@code OverflowMenuButton}.
  254. *
  255. * @inheritdoc
  256. */
  257. componentWillReceiveProps(nextProps) {
  258. // Ensure the dialog is closed when the toolbox becomes hidden.
  259. if (this.props._overflowMenuVisible && !nextProps._visible) {
  260. this._onSetOverflowVisible(false);
  261. }
  262. if (this.props._overflowMenuVisible
  263. && !this.props._dialog
  264. && nextProps._dialog) {
  265. this._onSetOverflowVisible(false);
  266. this.props.dispatch(setToolbarHovered(false));
  267. }
  268. }
  269. /**
  270. * Removes keyboard shortcuts registered by this component.
  271. *
  272. * @inheritdoc
  273. * @returns {void}
  274. */
  275. componentWillUnmount() {
  276. [ 'C', 'D', 'R', 'S' ].forEach(letter =>
  277. APP.keyboardshortcut.unregisterShortcut(letter));
  278. }
  279. /**
  280. * Implements React's {@link Component#render()}.
  281. *
  282. * @inheritdoc
  283. * @returns {ReactElement}
  284. */
  285. render() {
  286. const {
  287. _chatOpen,
  288. _hideInviteButton,
  289. _overflowMenuVisible,
  290. _raisedHand,
  291. _visible,
  292. _visibleButtons,
  293. t
  294. } = this.props;
  295. const rootClassNames = `new-toolbox ${_visible ? 'visible' : ''} ${
  296. _visibleButtons.size ? '' : 'no-buttons'}`;
  297. const overflowMenuContent = this._renderOverflowMenuContent();
  298. const overflowHasItems = Boolean(overflowMenuContent.filter(
  299. child => child).length);
  300. const toolbarAccLabel = 'toolbar.accessibilityLabel.moreActionsMenu';
  301. return (
  302. <div
  303. className = { rootClassNames }
  304. id = 'new-toolbox'
  305. onMouseOut = { this._onMouseOut }
  306. onMouseOver = { this._onMouseOver }>
  307. <div className = 'button-group-left'>
  308. { this._shouldShowButton('desktop')
  309. && this._renderDesktopSharingButton() }
  310. { this._shouldShowButton('raisehand')
  311. && <ToolbarButton
  312. accessibilityLabel =
  313. { t('toolbar.accessibilityLabel.raiseHand') }
  314. iconName = { _raisedHand
  315. ? 'icon-raised-hand toggled'
  316. : 'icon-raised-hand' }
  317. onClick = { this._onToolbarToggleRaiseHand }
  318. tooltip = { t('toolbar.raiseHand') } /> }
  319. { this._shouldShowButton('chat')
  320. && <div className = 'toolbar-button-with-badge'>
  321. <ToolbarButton
  322. accessibilityLabel =
  323. { t('toolbar.accessibilityLabel.chat') }
  324. iconName = { _chatOpen
  325. ? 'icon-chat toggled'
  326. : 'icon-chat' }
  327. onClick = { this._onToolbarToggleChat }
  328. tooltip = { t('toolbar.chat') } />
  329. <ChatCounter />
  330. </div> }
  331. </div>
  332. <div className = 'button-group-center'>
  333. <AudioMuteButton
  334. visible = { this._shouldShowButton('microphone') } />
  335. <HangupButton
  336. visible = { this._shouldShowButton('hangup') } />
  337. <VideoMuteButton
  338. visible = { this._shouldShowButton('camera') } />
  339. </div>
  340. <div className = 'button-group-right'>
  341. { this._shouldShowButton('invite')
  342. && !_hideInviteButton
  343. && <ToolbarButton
  344. accessibilityLabel =
  345. { t('toolbar.accessibilityLabel.invite') }
  346. iconName = 'icon-add'
  347. onClick = { this._onToolbarOpenInvite }
  348. tooltip = { t('toolbar.invite') } /> }
  349. { this._shouldShowButton('info') && <InfoDialogButton /> }
  350. { overflowHasItems
  351. && <OverflowMenuButton
  352. isOpen = { _overflowMenuVisible }
  353. onVisibilityChange = { this._onSetOverflowVisible }>
  354. <ul
  355. aria-label = { t(toolbarAccLabel) }
  356. className = 'overflow-menu'>
  357. { overflowMenuContent }
  358. </ul>
  359. </OverflowMenuButton> }
  360. </div>
  361. </div>
  362. );
  363. }
  364. /**
  365. * Callback invoked to display {@code FeedbackDialog}.
  366. *
  367. * @private
  368. * @returns {void}
  369. */
  370. _doOpenFeedback() {
  371. const { _conference } = this.props;
  372. this.props.dispatch(openFeedbackDialog(_conference));
  373. }
  374. /**
  375. * Dispatches an action to display {@code KeyboardShortcuts}.
  376. *
  377. * @private
  378. * @returns {void}
  379. */
  380. _doOpenKeyboardShorcuts() {
  381. this.props.dispatch(openKeyboardShortcutsDialog());
  382. }
  383. /**
  384. * Callback invoked to display {@code SpeakerStats}.
  385. *
  386. * @private
  387. * @returns {void}
  388. */
  389. _doOpenSpeakerStats() {
  390. this.props.dispatch(openDialog(SpeakerStats, {
  391. conference: this.props._conference
  392. }));
  393. }
  394. /**
  395. * Dispatches an action to toggle the video quality dialog.
  396. *
  397. * @private
  398. * @returns {void}
  399. */
  400. _doOpenVideoQuality() {
  401. this.props.dispatch(openDialog(VideoQualityDialog));
  402. }
  403. /**
  404. * Dispatches an action to toggle the display of chat.
  405. *
  406. * @private
  407. * @returns {void}
  408. */
  409. _doToggleChat() {
  410. this.props.dispatch(toggleChat());
  411. }
  412. /**
  413. * Dispatches an action to show or hide document editing.
  414. *
  415. * @private
  416. * @returns {void}
  417. */
  418. _doToggleEtherpad() {
  419. this.props.dispatch(toggleDocument());
  420. }
  421. /**
  422. * Dispatches an action to toggle screensharing.
  423. *
  424. * @private
  425. * @returns {void}
  426. */
  427. _doToggleFullScreen() {
  428. const fullScreen = !this.props._fullScreen;
  429. this.props.dispatch(setFullScreen(fullScreen));
  430. }
  431. /**
  432. * Dispatches an action to show a dialog for starting or stopping a live
  433. * streaming session.
  434. *
  435. * @private
  436. * @returns {void}
  437. */
  438. _doToggleLiveStreaming() {
  439. const { _liveStreamingSession } = this.props;
  440. const dialogToDisplay = _liveStreamingSession
  441. ? StopLiveStreamDialog : StartLiveStreamDialog;
  442. this.props.dispatch(
  443. openDialog(dialogToDisplay, { session: _liveStreamingSession }));
  444. }
  445. /**
  446. * Dispatches an action to show or hide the profile edit panel.
  447. *
  448. * @private
  449. * @returns {void}
  450. */
  451. _doToggleProfile() {
  452. this.props.dispatch(toggleProfile());
  453. }
  454. /**
  455. * Dispatches an action to toggle the local participant's raised hand state.
  456. *
  457. * @private
  458. * @returns {void}
  459. */
  460. _doToggleRaiseHand() {
  461. const { _localParticipantID, _raisedHand } = this.props;
  462. this.props.dispatch(participantUpdated({
  463. // XXX Only the local participant is allowed to update without
  464. // stating the JitsiConference instance (i.e. participant property
  465. // `conference` for a remote participant) because the local
  466. // participant is uniquely identified by the very fact that there is
  467. // only one local participant.
  468. id: _localParticipantID,
  469. local: true,
  470. raisedHand: !_raisedHand
  471. }));
  472. }
  473. /**
  474. * Dispatches an action to toggle recording.
  475. *
  476. * @private
  477. * @returns {void}
  478. */
  479. _doToggleRecording() {
  480. const { _fileRecordingSession } = this.props;
  481. const dialog = _fileRecordingSession
  482. ? StopRecordingDialog : StartRecordingDialog;
  483. this.props.dispatch(
  484. openDialog(dialog, { session: _fileRecordingSession }));
  485. }
  486. /**
  487. * Dispatches an action to toggle screensharing.
  488. *
  489. * @private
  490. * @returns {void}
  491. */
  492. _doToggleScreenshare() {
  493. if (this.props._desktopSharingEnabled) {
  494. this.props.dispatch(toggleScreensharing());
  495. }
  496. }
  497. /**
  498. * Dispatches an action to toggle YouTube video sharing.
  499. *
  500. * @private
  501. * @returns {void}
  502. */
  503. _doToggleSharedVideo() {
  504. this.props.dispatch(toggleSharedVideo());
  505. }
  506. _onMouseOut: () => void;
  507. /**
  508. * Dispatches an action signaling the toolbar is not being hovered.
  509. *
  510. * @private
  511. * @returns {void}
  512. */
  513. _onMouseOut() {
  514. this.props.dispatch(setToolbarHovered(false));
  515. }
  516. _onMouseOver: () => void;
  517. /**
  518. * Dispatches an action signaling the toolbar is being hovered.
  519. *
  520. * @private
  521. * @returns {void}
  522. */
  523. _onMouseOver() {
  524. this.props.dispatch(setToolbarHovered(true));
  525. }
  526. _onSetOverflowVisible: (boolean) => void;
  527. /**
  528. * Sets the visibility of the overflow menu.
  529. *
  530. * @param {boolean} visible - Whether or not the overflow menu should be
  531. * displayed.
  532. * @private
  533. * @returns {void}
  534. */
  535. _onSetOverflowVisible(visible) {
  536. this.props.dispatch(setOverflowMenuVisible(visible));
  537. }
  538. _onShortcutToggleChat: () => void;
  539. /**
  540. * Creates an analytics keyboard shortcut event and dispatches an action for
  541. * toggling the display of chat.
  542. *
  543. * @private
  544. * @returns {void}
  545. */
  546. _onShortcutToggleChat() {
  547. sendAnalytics(createShortcutEvent(
  548. 'toggle.chat',
  549. {
  550. enable: !this.props._chatOpen
  551. }));
  552. this._doToggleChat();
  553. }
  554. _onShortcutToggleFullScreen: () => void;
  555. /**
  556. * Creates an analytics keyboard shortcut event and dispatches an action for
  557. * toggling full screen mode.
  558. *
  559. * @private
  560. * @returns {void}
  561. */
  562. _onShortcutToggleFullScreen() {
  563. sendAnalytics(createShortcutEvent(
  564. 'toggle.fullscreen',
  565. {
  566. enable: !this.props._fullScreen
  567. }));
  568. this._doToggleFullScreen();
  569. }
  570. _onShortcutToggleRaiseHand: () => void;
  571. /**
  572. * Creates an analytics keyboard shortcut event and dispatches an action for
  573. * toggling raise hand.
  574. *
  575. * @private
  576. * @returns {void}
  577. */
  578. _onShortcutToggleRaiseHand() {
  579. sendAnalytics(createShortcutEvent(
  580. 'toggle.raise.hand',
  581. ACTION_SHORTCUT_TRIGGERED,
  582. { enable: !this.props._raisedHand }));
  583. this._doToggleRaiseHand();
  584. }
  585. _onShortcutToggleScreenshare: () => void;
  586. /**
  587. * Creates an analytics keyboard shortcut event and dispatches an action for
  588. * toggling screensharing.
  589. *
  590. * @private
  591. * @returns {void}
  592. */
  593. _onShortcutToggleScreenshare() {
  594. sendAnalytics(createToolbarEvent(
  595. 'screen.sharing',
  596. {
  597. enable: !this.props._screensharing
  598. }));
  599. this._doToggleScreenshare();
  600. }
  601. _onToolbarOpenFeedback: () => void;
  602. /**
  603. * Creates an analytics toolbar event and dispatches an action for toggling
  604. * display of feedback.
  605. *
  606. * @private
  607. * @returns {void}
  608. */
  609. _onToolbarOpenFeedback() {
  610. sendAnalytics(createToolbarEvent('feedback'));
  611. this._doOpenFeedback();
  612. }
  613. _onToolbarOpenInvite: () => void;
  614. /**
  615. * Creates an analytics toolbar event and dispatches an action for opening
  616. * the modal for inviting people directly into the conference.
  617. *
  618. * @private
  619. * @returns {void}
  620. */
  621. _onToolbarOpenInvite() {
  622. sendAnalytics(createToolbarEvent('invite'));
  623. this.props.dispatch(beginAddPeople());
  624. }
  625. _onToolbarOpenKeyboardShortcuts: () => void;
  626. /**
  627. * Creates an analytics toolbar event and dispatches an action for opening
  628. * the modal for showing available keyboard shortcuts.
  629. *
  630. * @private
  631. * @returns {void}
  632. */
  633. _onToolbarOpenKeyboardShortcuts() {
  634. sendAnalytics(createToolbarEvent('shortcuts'));
  635. this._doOpenKeyboardShorcuts();
  636. }
  637. _onToolbarOpenSpeakerStats: () => void;
  638. /**
  639. * Creates an analytics toolbar event and dispatches an action for opening
  640. * the speaker stats modal.
  641. *
  642. * @private
  643. * @returns {void}
  644. */
  645. _onToolbarOpenSpeakerStats() {
  646. sendAnalytics(createToolbarEvent('speaker.stats'));
  647. this._doOpenSpeakerStats();
  648. }
  649. _onToolbarOpenVideoQuality: () => void;
  650. /**
  651. * Creates an analytics toolbar event and dispatches an action for toggling
  652. * open the video quality dialog.
  653. *
  654. * @private
  655. * @returns {void}
  656. */
  657. _onToolbarOpenVideoQuality() {
  658. sendAnalytics(createToolbarEvent('video.quality'));
  659. this._doOpenVideoQuality();
  660. }
  661. _onToolbarToggleChat: () => void;
  662. /**
  663. * Creates an analytics toolbar event and dispatches an action for toggling
  664. * the display of chat.
  665. *
  666. * @private
  667. * @returns {void}
  668. */
  669. _onToolbarToggleChat() {
  670. sendAnalytics(createToolbarEvent(
  671. 'toggle.chat',
  672. {
  673. enable: !this.props._chatOpen
  674. }));
  675. this._doToggleChat();
  676. }
  677. _onToolbarToggleEtherpad: () => void;
  678. /**
  679. * Creates an analytics toolbar event and dispatches an action for toggling
  680. * the display of document editing.
  681. *
  682. * @private
  683. * @returns {void}
  684. */
  685. _onToolbarToggleEtherpad() {
  686. sendAnalytics(createToolbarEvent(
  687. 'toggle.etherpad',
  688. {
  689. enable: !this.props._editingDocument
  690. }));
  691. this._doToggleEtherpad();
  692. }
  693. _onToolbarToggleFullScreen: () => void;
  694. /**
  695. * Creates an analytics toolbar event and dispatches an action for toggling
  696. * full screen mode.
  697. *
  698. * @private
  699. * @returns {void}
  700. */
  701. _onToolbarToggleFullScreen() {
  702. sendAnalytics(createToolbarEvent(
  703. 'toggle.fullscreen',
  704. {
  705. enable: !this.props._fullScreen
  706. }));
  707. this._doToggleFullScreen();
  708. }
  709. _onToolbarToggleLiveStreaming: () => void;
  710. /**
  711. * Starts the process for enabling or disabling live streaming.
  712. *
  713. * @private
  714. * @returns {void}
  715. */
  716. _onToolbarToggleLiveStreaming() {
  717. sendAnalytics(createToolbarEvent(
  718. 'livestreaming.button',
  719. {
  720. 'is_streaming': Boolean(this.props._liveStreamingSession),
  721. type: JitsiRecordingConstants.mode.STREAM
  722. }));
  723. this._doToggleLiveStreaming();
  724. }
  725. _onToolbarToggleProfile: () => void;
  726. /**
  727. * Creates an analytics toolbar event and dispatches an action for showing
  728. * or hiding the profile edit panel.
  729. *
  730. * @private
  731. * @returns {void}
  732. */
  733. _onToolbarToggleProfile() {
  734. sendAnalytics(createToolbarEvent('profile'));
  735. this._doToggleProfile();
  736. }
  737. _onToolbarToggleRaiseHand: () => void;
  738. /**
  739. * Creates an analytics toolbar event and dispatches an action for toggling
  740. * raise hand.
  741. *
  742. * @private
  743. * @returns {void}
  744. */
  745. _onToolbarToggleRaiseHand() {
  746. sendAnalytics(createToolbarEvent(
  747. 'raise.hand',
  748. { enable: !this.props._raisedHand }));
  749. this._doToggleRaiseHand();
  750. }
  751. _onToolbarToggleRecording: () => void;
  752. /**
  753. * Dispatches an action to toggle recording.
  754. *
  755. * @private
  756. * @returns {void}
  757. */
  758. _onToolbarToggleRecording() {
  759. sendAnalytics(createToolbarEvent(
  760. 'recording.button',
  761. {
  762. 'is_recording': Boolean(this.props._fileRecordingSession),
  763. type: JitsiRecordingConstants.mode.FILE
  764. }));
  765. this._doToggleRecording();
  766. }
  767. _onToolbarToggleScreenshare: () => void;
  768. /**
  769. * Creates an analytics toolbar event and dispatches an action for toggling
  770. * screensharing.
  771. *
  772. * @private
  773. * @returns {void}
  774. */
  775. _onToolbarToggleScreenshare() {
  776. if (!this.props._desktopSharingEnabled) {
  777. return;
  778. }
  779. sendAnalytics(createShortcutEvent(
  780. 'toggle.screen.sharing',
  781. ACTION_SHORTCUT_TRIGGERED,
  782. { enable: !this.props._screensharing }));
  783. this._doToggleScreenshare();
  784. }
  785. _onToolbarToggleSharedVideo: () => void;
  786. /**
  787. * Creates an analytics toolbar event and dispatches an action for toggling
  788. * the sharing of a YouTube video.
  789. *
  790. * @private
  791. * @returns {void}
  792. */
  793. _onToolbarToggleSharedVideo() {
  794. sendAnalytics(createToolbarEvent('shared.video.toggled',
  795. {
  796. enable: !this.props._sharingVideo
  797. }));
  798. this._doToggleSharedVideo();
  799. }
  800. /**
  801. * Renders a button for toggleing screen sharing.
  802. *
  803. * @private
  804. * @returns {ReactElement|null}
  805. */
  806. _renderDesktopSharingButton() {
  807. const {
  808. _desktopSharingDisabledByConfig,
  809. _desktopSharingEnabled,
  810. _screensharing,
  811. t
  812. } = this.props;
  813. const disabledTooltipText
  814. = interfaceConfig.DESKTOP_SHARING_BUTTON_DISABLED_TOOLTIP;
  815. const showDisabledTooltip
  816. = disabledTooltipText && _desktopSharingDisabledByConfig;
  817. const visible = _desktopSharingEnabled || showDisabledTooltip;
  818. if (!visible) {
  819. return null;
  820. }
  821. const classNames = `icon-share-desktop ${
  822. _screensharing ? 'toggled' : ''} ${
  823. _desktopSharingEnabled ? '' : 'disabled'}`;
  824. const tooltip = showDisabledTooltip
  825. ? disabledTooltipText
  826. : t('dialog.shareYourScreen');
  827. return (
  828. <ToolbarButton
  829. accessibilityLabel
  830. = { t('toolbar.accessibilityLabel.shareYourScreen') }
  831. iconName = { classNames }
  832. onClick = { this._onToolbarToggleScreenshare }
  833. tooltip = { tooltip } />
  834. );
  835. }
  836. /**
  837. * Renders the list elements of the overflow menu.
  838. *
  839. * @private
  840. * @returns {Array<ReactElement>}
  841. */
  842. _renderOverflowMenuContent() {
  843. const {
  844. _editingDocument,
  845. _etherpadInitialized,
  846. _feedbackConfigured,
  847. _fileRecordingsEnabled,
  848. _fullScreen,
  849. _isGuest,
  850. _liveStreamingEnabled,
  851. _liveStreamingSession,
  852. _sharingVideo,
  853. t
  854. } = this.props;
  855. return [
  856. _isGuest
  857. && this._shouldShowButton('profile')
  858. && <OverflowMenuProfileItem
  859. key = 'profile'
  860. onClick = { this._onToolbarToggleProfile } />,
  861. this._shouldShowButton('videoquality')
  862. && <OverflowMenuVideoQualityItem
  863. key = 'videoquality'
  864. onClick = { this._onToolbarOpenVideoQuality } />,
  865. this._shouldShowButton('fullscreen')
  866. && <OverflowMenuItem
  867. accessibilityLabel =
  868. { t('toolbar.accessibilityLabel.fullScreen') }
  869. icon = { _fullScreen
  870. ? 'icon-exit-full-screen'
  871. : 'icon-full-screen' }
  872. key = 'fullscreen'
  873. onClick = { this._onToolbarToggleFullScreen }
  874. text = { _fullScreen
  875. ? t('toolbar.exitFullScreen')
  876. : t('toolbar.enterFullScreen') } />,
  877. _liveStreamingEnabled
  878. && this._shouldShowButton('livestreaming')
  879. && <OverflowMenuLiveStreamingItem
  880. key = 'livestreaming'
  881. onClick = { this._onToolbarToggleLiveStreaming }
  882. session = { _liveStreamingSession } />,
  883. _fileRecordingsEnabled
  884. && this._shouldShowButton('recording')
  885. && this._renderRecordingButton(),
  886. this._shouldShowButton('sharedvideo')
  887. && <OverflowMenuItem
  888. accessibilityLabel =
  889. { t('toolbar.accessibilityLabel.sharedvideo') }
  890. icon = 'icon-shared-video'
  891. key = 'sharedvideo'
  892. onClick = { this._onToolbarToggleSharedVideo }
  893. text = { _sharingVideo
  894. ? t('toolbar.stopSharedVideo')
  895. : t('toolbar.sharedvideo') } />,
  896. this._shouldShowButton('etherpad')
  897. && _etherpadInitialized
  898. && <OverflowMenuItem
  899. accessibilityLabel =
  900. { t('toolbar.accessibilityLabel.document') }
  901. icon = 'icon-share-doc'
  902. key = 'etherpad'
  903. onClick = { this._onToolbarToggleEtherpad }
  904. text = { _editingDocument
  905. ? t('toolbar.documentClose')
  906. : t('toolbar.documentOpen') } />,
  907. <SettingsButton
  908. key = 'settings'
  909. showLabel = { true }
  910. visible = { this._shouldShowButton('settings') } />,
  911. this._shouldShowButton('stats')
  912. && <OverflowMenuItem
  913. accessibilityLabel =
  914. { t('toolbar.accessibilityLabel.speakerStats') }
  915. icon = 'icon-presentation'
  916. key = 'stats'
  917. onClick = { this._onToolbarOpenSpeakerStats }
  918. text = { t('toolbar.speakerStats') } />,
  919. this._shouldShowButton('feedback')
  920. && _feedbackConfigured
  921. && <OverflowMenuItem
  922. accessibilityLabel =
  923. { t('toolbar.accessibilityLabel.feedback') }
  924. icon = 'icon-feedback'
  925. key = 'feedback'
  926. onClick = { this._onToolbarOpenFeedback }
  927. text = { t('toolbar.feedback') } />,
  928. this._shouldShowButton('shortcuts')
  929. && <OverflowMenuItem
  930. accessibilityLabel =
  931. { t('toolbar.accessibilityLabel.shortcuts') }
  932. icon = 'icon-open_in_new'
  933. key = 'shortcuts'
  934. onClick = { this._onToolbarOpenKeyboardShortcuts }
  935. text = { t('toolbar.shortcuts') } />
  936. ];
  937. }
  938. /**
  939. * Renders an {@code OverflowMenuItem} to start or stop recording of the
  940. * current conference.
  941. *
  942. * @private
  943. * @returns {ReactElement|null}
  944. */
  945. _renderRecordingButton() {
  946. const { _fileRecordingSession, t } = this.props;
  947. const translationKey = _fileRecordingSession
  948. ? 'dialog.stopRecording'
  949. : 'dialog.startRecording';
  950. return (
  951. <OverflowMenuItem
  952. accessibilityLabel =
  953. { t('toolbar.accessibilityLabel.recording') }
  954. icon = 'icon-camera-take-picture'
  955. key = 'recording'
  956. onClick = { this._onToolbarToggleRecording }
  957. text = { t(translationKey) } />
  958. );
  959. }
  960. _shouldShowButton: (string) => boolean;
  961. /**
  962. * Returns if a button name has been explicitly configured to be displayed.
  963. *
  964. * @param {string} buttonName - The name of the button, as expected in
  965. * {@link intefaceConfig}.
  966. * @private
  967. * @returns {boolean} True if the button should be displayed.
  968. */
  969. _shouldShowButton(buttonName) {
  970. return this.props._visibleButtons.has(buttonName);
  971. }
  972. }
  973. /**
  974. * Maps (parts of) the redux state to {@link Toolbox}'s React {@code Component}
  975. * props.
  976. *
  977. * @param {Object} state - The redux store/state.
  978. * @private
  979. * @returns {{}}
  980. */
  981. function _mapStateToProps(state) {
  982. const {
  983. conference,
  984. desktopSharingEnabled
  985. } = state['features/base/conference'];
  986. const {
  987. callStatsID,
  988. disableDesktopSharing,
  989. fileRecordingsEnabled,
  990. iAmRecorder,
  991. liveStreamingEnabled
  992. } = state['features/base/config'];
  993. const sharedVideoStatus = state['features/shared-video'].status;
  994. const { current } = state['features/side-panel'];
  995. const {
  996. alwaysVisible,
  997. fullScreen,
  998. overflowMenuVisible,
  999. timeoutID,
  1000. visible
  1001. } = state['features/toolbox'];
  1002. const localParticipant = getLocalParticipant(state);
  1003. const localVideo = getLocalVideoTrack(state['features/base/tracks']);
  1004. const isModerator = localParticipant.role === PARTICIPANT_ROLE.MODERATOR;
  1005. const addPeopleEnabled = isAddPeopleEnabled(state);
  1006. const dialOutEnabled = isDialOutEnabled(state);
  1007. return {
  1008. _chatOpen: current === 'chat_container',
  1009. _conference: conference,
  1010. _desktopSharingEnabled: desktopSharingEnabled,
  1011. _desktopSharingDisabledByConfig: disableDesktopSharing,
  1012. _dialog: Boolean(state['features/base/dialog'].component),
  1013. _editingDocument: Boolean(state['features/etherpad'].editing),
  1014. _etherpadInitialized: Boolean(state['features/etherpad'].initialized),
  1015. _feedbackConfigured: Boolean(callStatsID),
  1016. _hideInviteButton:
  1017. iAmRecorder || (!addPeopleEnabled && !dialOutEnabled),
  1018. _isGuest: state['features/base/jwt'].isGuest,
  1019. _fileRecordingsEnabled: isModerator && fileRecordingsEnabled,
  1020. _fileRecordingSession:
  1021. getActiveSession(state, JitsiRecordingConstants.mode.FILE),
  1022. _fullScreen: fullScreen,
  1023. _liveStreamingEnabled: isModerator && liveStreamingEnabled,
  1024. _liveStreamingSession:
  1025. getActiveSession(state, JitsiRecordingConstants.mode.STREAM),
  1026. _localParticipantID: localParticipant.id,
  1027. _overflowMenuVisible: overflowMenuVisible,
  1028. _raisedHand: localParticipant.raisedHand,
  1029. _screensharing: localVideo && localVideo.videoType === 'desktop',
  1030. _sharingVideo: sharedVideoStatus === 'playing'
  1031. || sharedVideoStatus === 'start'
  1032. || sharedVideoStatus === 'pause',
  1033. _visible: Boolean(timeoutID || visible || alwaysVisible),
  1034. // XXX: We are not currently using state here, but in the future, when
  1035. // interfaceConfig is part of redux we will.
  1036. _visibleButtons: new Set(interfaceConfig.TOOLBAR_BUTTONS)
  1037. };
  1038. }
  1039. export default translate(connect(_mapStateToProps)(Toolbox));