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

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