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 33KB

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