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

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