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.

ToolboxV2.web.js 33KB

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