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

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