diff --git a/conference.js b/conference.js index 81b5abfb9f..95d957d83b 100644 --- a/conference.js +++ b/conference.js @@ -106,10 +106,7 @@ import { suspendDetected } from './react/features/overlay'; import { setSharedVideoStatus } from './react/features/shared-video'; -import { - isButtonEnabled, - showDesktopSharingButton -} from './react/features/toolbox'; +import { isButtonEnabled } from './react/features/toolbox'; const logger = require('jitsi-meet-logger').getLogger(__filename); @@ -768,7 +765,6 @@ export default { APP.store.dispatch( setDesktopSharingEnabled(this.isDesktopSharingEnabled)); - APP.store.dispatch(showDesktopSharingButton()); this._createRoom(tracks); APP.remoteControl.init(); @@ -1359,7 +1355,6 @@ export default { this.isSharingScreen = newStream && newStream.videoType === 'desktop'; if (wasSharingScreen !== this.isSharingScreen) { - APP.UI.updateDesktopSharingButtons(); APP.API.notifyScreenSharingStatusChanged(this.isSharingScreen); } }, diff --git a/interface_config.js b/interface_config.js index c9fc4198c9..b3290d352a 100644 --- a/interface_config.js +++ b/interface_config.js @@ -153,19 +153,7 @@ var interfaceConfig = { * * @type {boolean} */ - VIDEO_QUALITY_LABEL_DISABLED: false, - - /** - * This is a temporary feature flag used to gate access to the toolbox so it - * can be developed through smaller changesets and set to false if bad bugs - * are found. This feature flag will be removed at some point, as well as - * the old toolbox. This new toolbox will be horizontal and the previous - * feature of supporting menu button ordering through interfaceConfig will - * be removed. Support for configuring which buttons display will remain. - * - * @type {boolean} - */ - _USE_NEW_TOOLBOX: true + VIDEO_QUALITY_LABEL_DISABLED: false /** * Specify custom URL for downloading android mobile app. diff --git a/modules/UI/UI.js b/modules/UI/UI.js index 767cd282a4..9be1dad3b1 100644 --- a/modules/UI/UI.js +++ b/modules/UI/UI.js @@ -36,14 +36,7 @@ import { showWarningNotification } from '../../react/features/notifications'; import { - checkAutoEnableDesktopSharing, - clearButtonPopup, dockToolbox, - setButtonPopupTimeout, - setToolbarButton, - showDialPadButton, - showEtherpadButton, - showSharedVideoButton, showToolbox } from '../../react/features/toolbox'; @@ -278,7 +271,7 @@ UI.setLocalRaisedHandStatus * Initialize conference UI. */ UI.initConference = function() { - const { dispatch, getState } = APP.store; + const { getState } = APP.store; const { email, id, name } = getLocalParticipant(getState); // Update default button states before showing the toolbar @@ -298,8 +291,6 @@ UI.initConference = function() { UI.setUserEmail(id, email); } - dispatch(checkAutoEnableDesktopSharing()); - // FollowMe attempts to copy certain aspects of the moderator's UI into the // other participants' UI. Consequently, it needs (1) read and write access // to the UI (depending on the moderator role of the local participant) and @@ -372,11 +363,10 @@ UI.start = function() { $('body').addClass('vertical-filmstrip'); } - // TODO: remove this class once the old toolbar has been removed. This class // is set so that any CSS changes needed to adjust elements outside of the // new toolbar can be scoped to just the app with the new toolbar enabled. - if (interfaceConfig._USE_NEW_TOOLBOX && !interfaceConfig.filmStripOnly) { + if (!interfaceConfig.filmStripOnly) { $('body').addClass('use-new-toolbox'); } @@ -476,7 +466,6 @@ UI.initEtherpad = name => { = new EtherpadManager(config.etherpad_base, name, eventEmitter); APP.store.dispatch(setEtherpadHasInitialzied()); - APP.store.dispatch(showEtherpadButton()); }; /** @@ -544,8 +533,6 @@ UI.onPeerVideoTypeChanged UI.updateLocalRole = isModerator => { VideoLayout.showModeratorIndicator(); - APP.store.dispatch(showSharedVideoButton()); - Recording.showRecordingButton(isModerator); if (isModerator) { @@ -672,14 +659,9 @@ UI.inputDisplayNameHandler = function(newDisplayName) { * @param {number} timeout - The time to show the popup * @returns {void} */ -// eslint-disable-next-line max-params +// eslint-disable-next-line max-params, no-unused-vars UI.showCustomToolbarPopup = function(buttonName, popupID, show, timeout) { - const action - = show - ? setButtonPopupTimeout(buttonName, popupID, timeout) - : clearButtonPopup(buttonName); - - APP.store.dispatch(action); + // TODO: this is no longer implemented as of Toolbox v2. Remove? }; /** @@ -904,17 +886,6 @@ UI.promptDisplayName = () => { */ UI.setAudioLevel = (id, lvl) => VideoLayout.setAudioLevel(id, lvl); -/** - * Update state of desktop sharing buttons. - * - * @returns {void} - */ -UI.updateDesktopSharingButtons - = () => - APP.store.dispatch(setToolbarButton('desktop', { - toggled: APP.conference.isSharingScreen - })); - /** * Hide connection quality statistics from UI. */ @@ -946,8 +917,9 @@ UI.addMessage = function(from, displayName, message, stamp) { Chat.updateChatConversation(from, displayName, message, stamp); }; -UI.updateDTMFSupport - = isDTMFSupported => APP.store.dispatch(showDialPadButton(isDTMFSupported)); +// TODO: With Toolbox v2 this got scrapped. Remove? +// eslint-disable-next-line no-empty-function +UI.updateDTMFSupport = () => { }; UI.updateRecordingState = function(state) { Recording.updateRecordingState(state); diff --git a/modules/UI/etherpad/Etherpad.js b/modules/UI/etherpad/Etherpad.js index 0114224328..70dbc584cf 100644 --- a/modules/UI/etherpad/Etherpad.js +++ b/modules/UI/etherpad/Etherpad.js @@ -129,8 +129,7 @@ class Etherpad extends LargeContainer { let height, width; if (interfaceConfig.VERTICAL_FILMSTRIP) { - height = interfaceConfig._USE_NEW_TOOLBOX - ? containerHeight - getToolboxHeight() : containerHeight; + height = containerHeight - getToolboxHeight(); width = containerWidth - Filmstrip.getFilmstripWidth(); } else { height = containerHeight - Filmstrip.getFilmstripHeight(); diff --git a/modules/UI/shared_video/SharedVideo.js b/modules/UI/shared_video/SharedVideo.js index c9ad43b4cd..51a0fb6cd4 100644 --- a/modules/UI/shared_video/SharedVideo.js +++ b/modules/UI/shared_video/SharedVideo.js @@ -699,8 +699,7 @@ class SharedVideoContainer extends LargeContainer { let height, width; if (interfaceConfig.VERTICAL_FILMSTRIP) { - height = interfaceConfig._USE_NEW_TOOLBOX - ? containerHeight - getToolboxHeight() : containerHeight; + height = containerHeight - getToolboxHeight(); width = containerWidth - Filmstrip.getFilmstripWidth(); } else { height = containerHeight - Filmstrip.getFilmstripHeight(); diff --git a/modules/UI/side_pannels/chat/Chat.js b/modules/UI/side_pannels/chat/Chat.js index c9d917d4b4..2481dec093 100644 --- a/modules/UI/side_pannels/chat/Chat.js +++ b/modules/UI/side_pannels/chat/Chat.js @@ -1,4 +1,4 @@ -/* global APP, $, interfaceConfig */ +/* global APP, $ */ import { processReplacements, linkify } from './Replacement'; import CommandsProcessor from './Commands'; @@ -181,15 +181,11 @@ function resizeChatConversation() { $('#smileysContainer').css('bottom', msgareaHeight); chat.width(width - 10); - if (interfaceConfig._USE_NEW_TOOLBOX) { - const maybeAMagicNumberForPaddingAndMargin = 100; - const offset = maybeAMagicNumberForPaddingAndMargin - + msgareaHeight + getToolboxHeight(); + const maybeAMagicNumberForPaddingAndMargin = 100; + const offset = maybeAMagicNumberForPaddingAndMargin + + msgareaHeight + getToolboxHeight(); - chat.height(window.innerHeight - offset); - } else { - chat.height(window.innerHeight - 15 - msgareaHeight); - } + chat.height(window.innerHeight - offset); } /** diff --git a/react/features/conference/components/Conference.web.js b/react/features/conference/components/Conference.web.js index 08e55743be..5805a45390 100644 --- a/react/features/conference/components/Conference.web.js +++ b/react/features/conference/components/Conference.web.js @@ -14,7 +14,6 @@ import { NotificationsContainer } from '../../notifications'; import { SidePanel } from '../../side-panel'; import { Toolbox, - ToolboxV2, fullScreenChanged, setToolboxAlwaysVisible, showToolbox @@ -139,7 +138,6 @@ class Conference extends Component { */ render() { const { - _USE_NEW_TOOLBOX, VIDEO_QUALITY_LABEL_DISABLED, filmStripOnly } = interfaceConfig; @@ -148,16 +146,6 @@ class Conference extends Component { || VIDEO_QUALITY_LABEL_DISABLED || this.props._iAmRecorder; - let ToolboxToUse; - - if (filmStripOnly) { - ToolboxToUse = null; - } else if (interfaceConfig._USE_NEW_TOOLBOX) { - ToolboxToUse = ToolboxV2; - } else { - ToolboxToUse = Toolbox; - } - return (
{
- { ToolboxToUse && } - - { _USE_NEW_TOOLBOX && !filmStripOnly - && } + { !filmStripOnly && } + { !filmStripOnly && } diff --git a/react/features/filmstrip/components/Filmstrip.web.js b/react/features/filmstrip/components/Filmstrip.web.js index 1240981e6f..559882a437 100644 --- a/react/features/filmstrip/components/Filmstrip.web.js +++ b/react/features/filmstrip/components/Filmstrip.web.js @@ -5,9 +5,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { connect } from 'react-redux'; -import { getLocalParticipant, PARTICIPANT_ROLE } from '../../base/participants'; -import { InviteButton } from '../../invite'; -import { Toolbox, ToolboxFilmstrip, dockToolbox } from '../../toolbox'; +import { ToolboxFilmstrip, dockToolbox } from '../../toolbox'; import { setFilmstripHovered } from '../actions'; import { shouldRemoteVideosBeVisible } from '../functions'; @@ -35,29 +33,11 @@ class Filmstrip extends Component<*> { * @static */ static propTypes = { - /** - * Whether invite button rendering should be skipped, - * by default it is. false - */ - _hideInviteButton: PropTypes.bool, - /** * Whether or not remote videos are currently being hovered over. */ _hovered: PropTypes.bool, - /** - * Whether or not the feature to directly invite people into the - * conference is available. - */ - _isAddToCallAvailable: PropTypes.bool, - - /** - * Whether or not the feature to dial out to number to join the - * conference is available. - */ - _isDialOutAvailable: PropTypes.bool, - /** * Whether or not the remote videos should be visible. Will toggle * a class for hiding the videos. @@ -115,9 +95,6 @@ class Filmstrip extends Component<*> { */ render() { const { - _hideInviteButton, - _isAddToCallAvailable, - _isDialOutAvailable, _remoteVideosVisible, _toolboxVisible, filmstripOnly @@ -136,12 +113,9 @@ class Filmstrip extends Component<*> { const filmstripClassNames = `filmstrip ${_remoteVideosVisible ? '' : 'hide-videos'} ${reduceHeight ? 'reduce-height' : ''}`; - const ToolboxToUse = interfaceConfig._USE_NEW_TOOLBOX - ? ToolboxFilmstrip : Toolbox; - return (
- { filmstripOnly ? : null } + { filmstripOnly && }
@@ -150,11 +124,6 @@ class Filmstrip extends Component<*> { id = 'filmstripLocalVideo' onMouseOut = { this._onMouseOut } onMouseOver = { this._onMouseOver }> - { filmstripOnly || _hideInviteButton - ? null - : }
{ */ _notifyOfHoveredStateUpdate() { if (this.props._hovered !== this._isHovered) { - if (interfaceConfig._USE_NEW_TOOLBOX) { - this.props.dispatch(dockToolbox(this._isHovered)); - } + this.props.dispatch(dockToolbox(this._isHovered)); this.props.dispatch(setFilmstripHovered(this._isHovered)); } } @@ -223,36 +190,16 @@ class Filmstrip extends Component<*> { * @param {Object} state - The Redux state. * @private * @returns {{ - * _hideInviteButton: boolean, * _hovered: boolean, - * _isAddToCallAvailable: boolean, - * _isDialOutAvailable: boolean, * _remoteVideosVisible: boolean, * _toolboxVisible: boolean * }} */ function _mapStateToProps(state) { - const { conference } = state['features/base/conference']; - const { - enableUserRolesBasedOnToken, - iAmRecorder - } = state['features/base/config']; - const { isGuest } = state['features/base/jwt']; const { hovered } = state['features/filmstrip']; - const isAddToCallAvailable = !isGuest; - const isDialOutAvailable - = getLocalParticipant(state).role === PARTICIPANT_ROLE.MODERATOR - && conference && conference.isSIPCallingSupported() - && (!enableUserRolesBasedOnToken || !isGuest); - return { - _hideInviteButton: iAmRecorder - || (!isAddToCallAvailable && !isDialOutAvailable) - || interfaceConfig._USE_NEW_TOOLBOX, _hovered: hovered, - _isAddToCallAvailable: isAddToCallAvailable, - _isDialOutAvailable: isDialOutAvailable, _remoteVideosVisible: shouldRemoteVideosBeVisible(state), _toolboxVisible: state['features/toolbox'].visible }; diff --git a/react/features/invite/components/InfoDialogButton.web.js b/react/features/invite/components/InfoDialogButton.web.js index f66a82c83c..95c1b1988f 100644 --- a/react/features/invite/components/InfoDialogButton.web.js +++ b/react/features/invite/components/InfoDialogButton.web.js @@ -1,5 +1,3 @@ -/* global interfaceConfig */ - import InlineDialog from '@atlaskit/inline-dialog'; import PropTypes from 'prop-types'; import React, { Component } from 'react'; @@ -8,30 +6,12 @@ import { connect } from 'react-redux'; import { createToolbarEvent, sendAnalytics } from '../../analytics'; import { translate } from '../../base/i18n'; import { getParticipantCount } from '../../base/participants'; -import { - ToolbarButton, - ToolbarButtonV2, - TOOLTIP_TO_POPUP_POSITION -} from '../../toolbox'; +import { ToolbarButtonV2 } from '../../toolbox'; import { updateDialInNumbers } from '../actions'; import { InfoDialog } from './info-dialog'; -/** - * A configuration object to describe how {@code ToolbarButton} should render - * the button. - * - * @type {object} - */ -const DEFAULT_BUTTON_CONFIGURATION = { - buttonName: 'info', - classNames: [ 'button', 'icon-info' ], - enabled: true, - id: 'toolbar_button_info', - tooltipKey: 'info.tooltip' -}; - /** * The amount of time, in milliseconds, to wait until automatically showing * the {@code InfoDialog}. This is essentially a hack as automatic showing @@ -170,9 +150,26 @@ class InfoDialogButton extends Component { * @returns {ReactElement} */ render() { - return interfaceConfig._USE_NEW_TOOLBOX - ? this._renderNewToolbarButton() - : this._renderOldToolbarButton(); + const { t } = this.props; + const { showDialog } = this.state; + const iconClass = `icon-info ${showDialog ? 'toggled' : ''}`; + + return ( +
+ } + isOpen = { showDialog } + onClose = { this._onDialogClose } + position = { 'top right' }> + + +
+ ); } /** @@ -209,69 +206,6 @@ class InfoDialogButton extends Component { this.setState({ showDialog: !this.state.showDialog }); } - - /** - * Renders a React Element for the {@code InfoDialog} using legacy - * {@code ToolbarButton}. - * - * @private - * @returns {ReactElement} - */ - _renderOldToolbarButton() { - const { tooltipPosition } = this.props; - const { showDialog } = this.state; - - const buttonConfiguration = { - ...DEFAULT_BUTTON_CONFIGURATION, - classNames: [ - ...DEFAULT_BUTTON_CONFIGURATION.classNames, - showDialog ? 'toggled button-active' : '' - ] - }; - - return ( - } - isOpen = { showDialog } - onClose = { this._onDialogClose } - position = { TOOLTIP_TO_POPUP_POSITION[tooltipPosition] }> - - - ); - } - - /** - * Renders a React Element for the {@code InfoDialog} using the newer - * {@code ToolbarButtonV2}. - * - * @private - * @returns {ReactElement} - */ - _renderNewToolbarButton() { - const { t } = this.props; - const { showDialog } = this.state; - const iconClass = `icon-info ${showDialog ? 'toggled' : ''}`; - - return ( -
- } - isOpen = { showDialog } - onClose = { this._onDialogClose } - position = { 'top right' }> - - -
- ); - } } /** diff --git a/react/features/toolbox/actionTypes.js b/react/features/toolbox/actionTypes.js index 521b77d738..542a86796d 100644 --- a/react/features/toolbox/actionTypes.js +++ b/react/features/toolbox/actionTypes.js @@ -61,17 +61,6 @@ export const SET_SUBJECT = Symbol('SET_SUBJECT'); */ export const SET_SUBJECT_SLIDE_IN = Symbol('SET_SUBJECT_SLIDE_IN'); -/** - * The type of the action which sets the descriptor of a toolbar button. - * - * { - * type: SET_TOOLBAR_BUTTON, - * button: Object, - * key: string - * } - */ -export const SET_TOOLBAR_BUTTON = Symbol('SET_TOOLBAR_BUTTON'); - /** * The type of the action which sets the indicator which determiens whether a * fToolbar in the Toolbox is hovered. diff --git a/react/features/toolbox/actions.native.js b/react/features/toolbox/actions.native.js index 77d750a3f3..a7b1b0b07b 100644 --- a/react/features/toolbox/actions.native.js +++ b/react/features/toolbox/actions.native.js @@ -1,12 +1,9 @@ /* @flow */ -import type { Dispatch } from 'redux-thunk'; - import { CLEAR_TOOLBOX_TIMEOUT, SET_SUBJECT, SET_SUBJECT_SLIDE_IN, - SET_TOOLBAR_BUTTON, SET_TOOLBAR_HOVERED, SET_TOOLBOX_ALWAYS_VISIBLE, SET_TOOLBOX_ENABLED, @@ -15,28 +12,6 @@ import { SET_TOOLBOX_VISIBLE } from './actionTypes'; -/** - * FIXME: We should make sure all common functions for native and web are - * merged in a global functions file. - */ -import { getButton } from './functions.native'; - -/** - * Event handler for local raise hand changed event. - * - * @param {boolean} handRaised - Flag showing whether hand is raised. - * @returns {Function} - */ -export function changeLocalRaiseHand(handRaised: boolean): Function { - return (dispatch: Dispatch<*>, getState: Function) => { - const buttonName = 'raisehand'; - const button = getButton(buttonName, getState()); - - button.toggled = handRaised; - - dispatch(setToolbarButton(buttonName, button)); - }; -} /** * Signals that toolbox timeout should be cleared. @@ -81,25 +56,6 @@ export function setSubjectSlideIn(subjectSlideIn: boolean): Object { }; } -/** - * Signals that value of the button specified by key should be changed. - * - * @param {string} buttonName - Button key. - * @param {Object} button - Button object. - * @returns {{ - * type: SET_TOOLBAR_BUTTON, - * button: Object, - * buttonName: string - * }} - */ -export function setToolbarButton(buttonName: string, button: Object): Object { - return { - type: SET_TOOLBAR_BUTTON, - button, - buttonName - }; -} - /** * Signals that toolbar is hovered value should be changed. * @@ -203,51 +159,3 @@ export function setToolboxVisible(visible: boolean): Object { visible }; } - -/** - * Shows etherpad button if it's not shown. - * - * @returns {Function} - */ -export function showEtherpadButton(): Function { - return (dispatch: Dispatch<*>) => { - dispatch(setToolbarButton('etherpad', { - hidden: false - })); - }; -} - -/** - * Event handler for full screen toggled event. - * - * @param {boolean} isFullScreen - Flag showing whether app in full - * screen mode. - * @returns {Function} - */ -export function toggleFullScreen(isFullScreen: boolean): Function { - return (dispatch: Dispatch<*>, getState: Function) => { - const buttonName = 'fullscreen'; - const button = getButton(buttonName, getState()); - - if (button) { - button.toggled = isFullScreen; - dispatch(setToolbarButton(buttonName, button)); - } - }; -} - -/** - * Sets negation of button's toggle property. - * - * @param {string} buttonName - Button key. - * @returns {Function} - */ -export function toggleToolbarButton(buttonName: string): Function { - return (dispatch: Dispatch, getState: Function) => { - const button = getButton(buttonName, getState()); - - dispatch(setToolbarButton(buttonName, { - toggled: !button.toggled - })); - }; -} diff --git a/react/features/toolbox/actions.web.js b/react/features/toolbox/actions.web.js index 1ac139344b..ae6f9ebae7 100644 --- a/react/features/toolbox/actions.web.js +++ b/react/features/toolbox/actions.web.js @@ -1,73 +1,24 @@ /* @flow */ -import Recording from '../../../modules/UI/recording/Recording'; import SideContainerToggler from '../../../modules/UI/side_pannels/SideContainerToggler'; -import UIEvents from '../../../service/UI/UIEvents'; - import { - changeLocalRaiseHand, clearToolboxTimeout, setSubjectSlideIn, - setToolbarButton, setToolboxTimeout, setToolboxTimeoutMS, - setToolboxVisible, - toggleToolbarButton + setToolboxVisible } from './actions.native'; import { FULL_SCREEN_CHANGED, - SET_DEFAULT_TOOLBOX_BUTTONS, SET_FULL_SCREEN } from './actionTypes'; -import { - getButton, - getDefaultToolboxButtons, - isButtonEnabled -} from './functions'; -declare var $: Function; -declare var APP: Object; -declare var config: Object; declare var interfaceConfig: Object; export * from './actions.native'; -/** - * Checks whether desktop sharing is enabled and whether - * we have params to start automatically sharing. - * - * @returns {Function} - */ -export function checkAutoEnableDesktopSharing(): Function { - return () => { - // XXX Should use dispatcher to toggle screensharing but screensharing - // hasn't been React-ified yet. - if (isButtonEnabled('desktop') - && config.autoEnableDesktopSharing) { - APP.UI.eventEmitter.emit(UIEvents.TOGGLE_SCREENSHARING); - } - }; -} - -/** - * Dispatches an action to hide any popups displayed by the associated button. - * - * @param {string} buttonName - The name of the button as specified in the - * button configurations for the toolbar. - * @returns {Function} - */ -export function clearButtonPopup(buttonName) { - return (dispatch, getState) => { - _clearPopupTimeout(buttonName, getState()); - - dispatch(setToolbarButton(buttonName, { - popupDisplay: null - })); - }; -} - /** * Docks/undocks the Toolbox. * @@ -115,56 +66,6 @@ export function fullScreenChanged(fullScreen: boolean) { }; } -/** - * Returns button on mount/unmount handlers with dispatch function stored in - * closure. - * - * @param {Function} dispatch - Redux action dispatcher. - * @returns {Object} Button on mount/unmount handlers. - * @private - */ -function _getButtonHandlers(dispatch) { - const localRaiseHandHandler - = (...args) => dispatch(changeLocalRaiseHand(...args)); - - return { - /** - * Mount handler for desktop button. - * - * @type {Object} - */ - desktop: { - onMount: () => dispatch(showDesktopSharingButton()) - }, - - /** - * Mount/Unmount handlers for raisehand button. - * - * @type {button} - */ - raisehand: { - onMount: () => - APP.UI.addListener( - UIEvents.LOCAL_RAISE_HAND_CHANGED, - localRaiseHandHandler), - onUnmount: () => - APP.UI.removeListener( - UIEvents.LOCAL_RAISE_HAND_CHANGED, - localRaiseHandHandler) - }, - - /** - * Mount handler for recording button. - * - * @type {Object} - */ - recording: { - onMount: () => - config.enableRecording && dispatch(showRecordingButton()) - } - }; -} - /** * Hides the toolbox. * @@ -202,97 +103,6 @@ export function hideToolbox(force: boolean = false): Function { }; } -/** - * Dispatches an action to show the popup associated with a button. Sets a - * timeout to be fired which will dismiss the popup. - * - * @param {string} buttonName - The name of the button as specified in the - * button configurations for the toolbar. - * @param {string} popupName - The id of the popup to show as specified in - * the button configurations for the toolbar. - * @param {number} timeout - The time in milliseconds to show the popup. - * @returns {Function} - */ -export function setButtonPopupTimeout(buttonName, popupName, timeout) { - return (dispatch, getState) => { - _clearPopupTimeout(buttonName, getState()); - - const newTimeoutId = setTimeout(() => { - dispatch(clearButtonPopup(buttonName)); - }, timeout); - - dispatch(setToolbarButton(buttonName, { - popupDisplay: { - popupID: popupName, - timeoutID: newTimeoutId - } - })); - }; -} - -/** - * Sets the default toolbar buttons of the Toolbox. - * - * @returns {Function} - */ -export function setDefaultToolboxButtons(): Function { - return (dispatch: Dispatch) => { - // Save dispatch function in closure. - const buttonHandlers = _getButtonHandlers(dispatch); - const toolboxButtons = getDefaultToolboxButtons(buttonHandlers); - - dispatch({ - type: SET_DEFAULT_TOOLBOX_BUTTONS, - ...toolboxButtons - }); - }; -} - -/** - * Shows desktop sharing button. - * - * @returns {Function} - */ -export function showDesktopSharingButton(): Function { - return (dispatch: Dispatch<*>) => { - const buttonName = 'desktop'; - const disabledTooltipText - = APP.conference.desktopSharingDisabledTooltip; - const showTooltip - = disabledTooltipText - && APP.conference.isDesktopSharingDisabledByConfig; - const visible - = isButtonEnabled(buttonName) - && (APP.conference.isDesktopSharingEnabled || showTooltip); - - const newState = { - enabled: APP.conference.isDesktopSharingEnabled, - hidden: !visible, - tooltipText: showTooltip ? disabledTooltipText : undefined - }; - - dispatch(setToolbarButton(buttonName, newState)); - }; -} - -/** - * Shows or hides the dialpad button. - * - * @param {boolean} show - Flag showing whether to show button or not. - * @returns {Function} - */ -export function showDialPadButton(show: boolean): Function { - return (dispatch: Dispatch<*>) => { - const buttonName = 'dialpad'; - - if (show && isButtonEnabled(buttonName)) { - dispatch(setToolbarButton(buttonName, { - hidden: false - })); - } - }; -} - /** * Signals a request to enter or exit full screen mode. * @@ -309,39 +119,6 @@ export function setFullScreen(fullScreen: boolean) { }; } -/** - * Shows recording button. - * - * @returns {Function} - */ -export function showRecordingButton(): Function { - return (dispatch: Dispatch<*>) => { - dispatch(setToolbarButton('recording', { - hidden: false - })); - - Recording.initRecordingButton(); - }; -} - -/** - * Shows or hides the 'shared video' button. - * - * @returns {Function} - */ -export function showSharedVideoButton(): Function { - return (dispatch: Dispatch<*>) => { - const buttonName = 'sharedvideo'; - - if (isButtonEnabled(buttonName) - && !config.disableThirdPartyRequests) { - dispatch(setToolbarButton(buttonName, { - hidden: false - })); - } - }; -} - /** * Shows the toolbox for specified timeout. * @@ -374,43 +151,3 @@ export function showToolbox(timeout: number = 0): Object { } }; } - -/** - * Event handler for side toolbar container toggled event. - * - * @param {string} containerId - ID of the container. - * @returns {Function} - */ -export function toggleSideToolbarContainer(containerId: string): Function { - return (dispatch: Dispatch, getState: Function) => { - const { secondaryToolbarButtons } = getState()['features/toolbox']; - - for (const key of secondaryToolbarButtons.keys()) { - const button = secondaryToolbarButtons.get(key); - - if (isButtonEnabled(key) - && button.sideContainerId - && button.sideContainerId === containerId) { - dispatch(toggleToolbarButton(key)); - break; - } - } - }; -} - -/** - * Clears the timeout set for hiding a button popup. - * - * @param {string} buttonName - The name of the button as specified in the - * button configurations for the toolbar. - * @param {Object} state - The redux state in which the button is expected to - * be defined. - * @private - * @returns {void} - */ -function _clearPopupTimeout(buttonName, state) { - const { popupDisplay } = getButton(buttonName, state); - const { timeoutID } = popupDisplay || {}; - - clearTimeout(timeoutID); -} diff --git a/react/features/toolbox/components/PrimaryToolbar.web.js b/react/features/toolbox/components/PrimaryToolbar.web.js deleted file mode 100644 index 754cfe5b6e..0000000000 --- a/react/features/toolbox/components/PrimaryToolbar.web.js +++ /dev/null @@ -1,97 +0,0 @@ -/* @flow */ - -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; - -import { getToolbarClassNames } from '../functions'; -import Toolbar from './Toolbar'; - -declare var interfaceConfig: Object; - -/** - * Implementation of PrimaryToolbar React Component. - * - * @class PrimaryToolbar - * @extends Component - */ -class PrimaryToolbar extends Component<*, *> { - static propTypes = { - - /** - * Contains toolbar buttons for primary toolbar. - */ - _primaryToolbarButtons: PropTypes.instanceOf(Map), - - /** - * Shows whether toolbox is visible. - */ - _visible: PropTypes.bool - }; - - state: Object; - - /** - * Renders primary toolbar component. - * - * @returns {ReactElement} - */ - render(): React$Element<*> | null { - const { _primaryToolbarButtons } = this.props; - - // The number of buttons to show in the toolbar isn't fixed, it depends - // on the availability of features and configuration parameters. So - // there may be nothing to render. - if (_primaryToolbarButtons.size === 0) { - return null; - } - - const { primaryToolbarClassName } = getToolbarClassNames(this.props); - const tooltipPosition - = interfaceConfig.filmStripOnly ? 'left' : 'bottom'; - - return ( - - ); - } -} - -/** - * Maps part of Redux store to React component props. - * - * @param {Object} state - Snapshot of Redux store. - * @returns {{ - * _primaryToolbarButtons: Map, - * _visible: boolean - * }} - * @private - */ -function _mapStateToProps(state: Object): Object { - const { - primaryToolbarButtons, - visible - } = state['features/toolbox']; - - return { - /** - * Default toolbar buttons for primary toolbar. - * - * @private - * @type {Map} - */ - _primaryToolbarButtons: primaryToolbarButtons, - - /** - * Shows whether toolbox is visible. - * - * @private - * @type {boolean} - */ - _visible: visible - }; -} - -export default connect(_mapStateToProps)(PrimaryToolbar); diff --git a/react/features/toolbox/components/SecondaryToolbar.web.js b/react/features/toolbox/components/SecondaryToolbar.web.js deleted file mode 100644 index a20b34472c..0000000000 --- a/react/features/toolbox/components/SecondaryToolbar.web.js +++ /dev/null @@ -1,188 +0,0 @@ -/* @flow */ - -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; - -import { FeedbackButton } from '../../feedback'; -import UIEvents from '../../../../service/UI/UIEvents'; - -import { - toggleSideToolbarContainer -} from '../actions'; -import { getToolbarClassNames } from '../functions'; -import Toolbar from './Toolbar'; - -declare var APP: Object; -declare var config: Object; - -/** - * Implementation of secondary toolbar React component. - * - * @class SecondaryToolbar - * @extends Component - */ -class SecondaryToolbar extends Component<*, *> { - state: Object; - - /** - * Secondary toolbar property types. - * - * @static - */ - static propTypes = { - /** - * Application ID for callstats.io API. The {@code FeedbackButton} will - * display if defined. - */ - _callStatsID: PropTypes.string, - - /** - * The indicator which determines whether the local participant is a - * guest in the conference. - */ - _isGuest: PropTypes.bool, - - /** - * Handler dispatching toggle toolbar container. - */ - _onSideToolbarContainerToggled: PropTypes.func, - - /** - * Contains map of secondary toolbar buttons. - */ - _secondaryToolbarButtons: PropTypes.instanceOf(Map), - - /** - * Shows whether toolbox is visible. - */ - _visible: PropTypes.bool - }; - - /** - * Register legacy UI listener. - * - * @returns {void} - */ - componentDidMount(): void { - APP.UI.addListener( - UIEvents.SIDE_TOOLBAR_CONTAINER_TOGGLED, - this.props._onSideToolbarContainerToggled); - } - - /** - * Unregisters legacy UI listener. - * - * @returns {void} - */ - componentWillUnmount(): void { - APP.UI.removeListener( - UIEvents.SIDE_TOOLBAR_CONTAINER_TOGGLED, - this.props._onSideToolbarContainerToggled); - } - - /** - * Renders secondary toolbar component. - * - * @returns {ReactElement} - */ - render(): React$Element<*> | null { - const { _callStatsID, _secondaryToolbarButtons } = this.props; - - // The number of buttons to show in the toolbar isn't fixed, it depends - // on the availability of features and configuration parameters. So - // there may be nothing to render. - if (_secondaryToolbarButtons.size === 0) { - return null; - } - - const { secondaryToolbarClassName } = getToolbarClassNames(this.props); - - return ( - - { _callStatsID - ? : null } - - ); - } -} - -/** - * Maps some of Redux actions to component's props. - * - * @param {Function} dispatch - Redux action dispatcher. - * @returns {{ - * _onSideToolbarContainerToggled - * }} - * @private - */ -function _mapDispatchToProps(dispatch: Function): Object { - return { - - /** - * Dispatches an action signalling that side toolbar container is - * toggled. - * - * @param {string} containerId - Id of side toolbar container. - * @returns {Object} Dispatched action. - */ - _onSideToolbarContainerToggled(containerId: string) { - dispatch(toggleSideToolbarContainer(containerId)); - } - }; -} - -/** - * Maps part of Redux state to component's props. - * - * @param {Object} state - Snapshot of Redux store. - * @returns {{ - * _isGuest: boolean, - * _secondaryToolbarButtons: Map, - * _visible: boolean - * }} - * @private - */ -function _mapStateToProps(state: Object): Object { - const { isGuest } = state['features/base/jwt']; - const { secondaryToolbarButtons, visible } = state['features/toolbox']; - const { callStatsID } = state['features/base/config']; - - return { - /** - * Application ID for callstats.io API. - */ - _callStatsID: callStatsID, - - /** - * The indicator which determines whether the local participant is a - * guest in the conference. - * - * @private - * @type {boolean} - */ - _isGuest: isGuest, - - /** - * Default toolbar buttons for secondary toolbar. - * - * @private - * @type {Map} - */ - _secondaryToolbarButtons: secondaryToolbarButtons, - - /** - * The indicator which determines whether the {@code SecondaryToolbar} - * is visible. - * - * @private - * @type {boolean} - */ - _visible: visible - }; -} - -export default connect(_mapStateToProps, _mapDispatchToProps)(SecondaryToolbar); diff --git a/react/features/toolbox/components/Toolbar.web.js b/react/features/toolbox/components/Toolbar.web.js deleted file mode 100644 index 30e2b48f04..0000000000 --- a/react/features/toolbox/components/Toolbar.web.js +++ /dev/null @@ -1,181 +0,0 @@ -// @flow - -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; - -import { setToolbarHovered } from '../actions'; - -import StatelessToolbar from './StatelessToolbar'; -import ToolbarButton from './ToolbarButton'; - -/** - * Implements a toolbar in React/Web. It is a strip that contains a set of - * toolbar items such as buttons. Toolbar is commonly placed inside of a - * Toolbox. - * - * @class Toolbar - * @extends Component - */ -class Toolbar extends Component<*> { - /** - * Base toolbar component's property types. - * - * @static - */ - static propTypes = { - /** - * Children of current React component. - */ - children: PropTypes.element, - - /** - * Toolbar's class name. - */ - className: PropTypes.string, - - /** - * Used to dispatch an action when a button is clicked or on mouse - * out/in event. - */ - dispatch: PropTypes.func, - - /** - * Map with toolbar buttons. - */ - toolbarButtons: PropTypes.instanceOf(Map), - - /** - * Indicates the position of the tooltip. - */ - tooltipPosition: PropTypes.oneOf([ 'bottom', 'left', 'right', 'top' ]) - }; - - /** - * Constructor of Primary toolbar class. - * - * @param {Object} props - Object containing React component properties. - */ - constructor(props: Object) { - super(props); - - // Bind callbacks to preverse this. - this._onMouseOut = this._onMouseOut.bind(this); - this._onMouseOver = this._onMouseOver.bind(this); - this._renderToolbarButton = this._renderToolbarButton.bind(this); - } - - /** - * Implements React's {@link Component#render()}. - * - * @inheritdoc - * @returns {ReactElement} - */ - render(): React$Element<*> { - const props = { - className: this.props.className, - onMouseOut: this._onMouseOut, - onMouseOver: this._onMouseOver - }; - - return ( - - { - [ ...this.props.toolbarButtons.entries() ] - .map(this._renderToolbarButton) - } - { - this.props.children - } - - ); - } - - _onMouseOut: () => void; - - /** - * Dispatches an action signalling that toolbar is no being hovered. - * - * @protected - * @returns {void} - */ - _onMouseOut() { - this.props.dispatch(setToolbarHovered(false)); - } - - _onMouseOver: () => void; - - /** - * Dispatches an action signalling that toolbar is now being hovered. - * - * @protected - * @returns {void} - */ - _onMouseOver() { - this.props.dispatch(setToolbarHovered(true)); - } - - _renderToolbarButton: (Array<*>) => React$Element<*>; - - /** - * Renders toolbar button. Method is passed to map function. - * - * @param {Array} keyValuePair - Key value pair containing button and its - * key. - * @private - * @returns {ReactElement} A toolbar button. - */ - _renderToolbarButton([ key, button ]): React$Element<*> { - const { tooltipPosition } = this.props; - - if (button.component) { - return ( - - ); - } - - const { - childComponent: ChildComponent, - onClick, - onMount, - onUnmount - } = button; - const onClickWithDispatch = (...args) => - onClick && onClick(this.props.dispatch, ...args); - - return ( - - { button.html || null } - { ChildComponent ? : null } - - ); - } -} - -export default connect()(Toolbar); diff --git a/react/features/toolbox/components/Toolbox.web.js b/react/features/toolbox/components/Toolbox.web.js index 6dfa99e95a..049087026e 100644 --- a/react/features/toolbox/components/Toolbox.web.js +++ b/react/features/toolbox/components/Toolbox.web.js @@ -1,78 +1,305 @@ -/* @flow */ +// @flow -import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { connect } from 'react-redux'; import { - setDefaultToolboxButtons -} from '../actions'; + ACTION_SHORTCUT_TRIGGERED, + createShortcutEvent, + createToolbarEvent, + sendAnalytics +} from '../../analytics'; +import { openDialog } from '../../base/dialog'; +import { translate } from '../../base/i18n'; import { - abstractMapStateToProps -} from '../functions'; -import Notice from './Notice'; -import PrimaryToolbar from './PrimaryToolbar'; -import SecondaryToolbar from './SecondaryToolbar'; + PARTICIPANT_ROLE, + getLocalParticipant, + participantUpdated +} from '../../base/participants'; +import { getLocalVideoTrack, toggleScreensharing } from '../../base/tracks'; +import { ChatCounter } from '../../chat'; +import { openDeviceSelectionDialog } from '../../device-selection'; +import { toggleDocument } from '../../etherpad'; +import { openFeedbackDialog } from '../../feedback'; +import { AddPeopleDialog, InfoDialogButton } from '../../invite'; +import { openKeyboardShortcutsDialog } from '../../keyboard-shortcuts'; +import { RECORDING_TYPES, toggleRecording } from '../../recording'; +import { toggleSharedVideo } from '../../shared-video'; +import { toggleChat, toggleProfile, toggleSettings } from '../../side-panel'; +import { SpeakerStats } from '../../speaker-stats'; +import { VideoQualityDialog } from '../../video-quality'; + +import { setFullScreen, setToolbarHovered } from '../actions'; + +import OverflowMenuButton from './OverflowMenuButton'; +import OverflowMenuItem from './OverflowMenuItem'; +import OverflowMenuProfileItem from './OverflowMenuProfileItem'; +import ToolbarButtonV2 from './ToolbarButtonV2'; +import { AudioMuteButton, HangupButton, VideoMuteButton } from './buttons'; + +type Props = { + + /** + * Whether or not the feature for adding people directly into the call + * is enabled. + */ + _addPeopleAvailable: boolean, + + /** + * Whether or not the chat feature is currently displayed. + */ + _chatOpen: boolean, + + /** + * The {@code JitsiConference} for the current conference. + */ + _conference: Object, + + /** + * Whether or not desktopsharing was explicitly configured to be disabled. + */ + _desktopSharingDisabledByConfig: boolean, + + /** + * Whether or not screensharing is initialized. + */ + _desktopSharingEnabled: boolean, + + /** + * Whether or not the feature for telephony to dial out to a number is + * enabled. + */ + _dialOutAvailable: boolean, + + /** + * Whether or not a dialog is displayed. + */ + _dialog: boolean, + + /** + * Whether or not the local participant is currently editing a document. + */ + _editingDocument: boolean, + + /** + * Whether or not collaborative document editing is enabled. + */ + _etherpadInitialized: boolean, + + /** + * Whether or not call feedback can be sent. + */ + _feedbackConfigured: boolean, + + /** + * Whether or not the app is currently in full screen. + */ + _fullScreen: boolean, + + /** + * Whether or not invite should be hidden, regardless of feature + * availability. + */ + _hideInviteButton: boolean, + + /** + * Whether or not the conference is currently being recorded by the local + * participant. + */ + _isRecording: boolean, + + /** + * The ID of the local participant. + */ + _localParticipantID: String, + + /** + * Whether or not the local participant's hand is raised. + */ + _raisedHand: boolean, + + /** + * Whether or not the recording feature is enabled for use. + */ + _recordingEnabled: boolean, + + /** + * Whether the recording feature is live streaming (jibri) or is file + * recording (jirecon). + */ + _recordingType: String, + + /** + * Whether or not the local participant is screensharing. + */ + _screensharing: boolean, + + /** + * Whether or not the local participant is sharing a YouTube video. + */ + _sharingVideo: boolean, + + /** + * Flag showing whether toolbar is visible. + */ + _visible: boolean, + + /** + * Invoked to active other features of the app. + */ + dispatch: Function, + + /** + * Invoked to obtain translated strings. + */ + t: Function +} + +type State = { + + /** + * Whether or not the overflow menu is visible. + */ + showOverflowMenu: boolean +} declare var APP: Object; -declare var config: Object; declare var interfaceConfig: Object; /** * Implements the conference toolbox on React/Web. + * + * @extends Component */ -class Toolbox extends Component<*> { +class Toolbox extends Component { + _visibleButtons: Object; + + state = { + showOverflowMenu: false + } + /** - * App component's property types. + * Initializes a new {@code Toolbox} instance. * - * @static + * @param {Props} props - The read-only React {@code Component} props with + * which the new instance is to be initialized. */ - static propTypes = { - /** - * Indicates if the toolbox should always be visible. - */ - _alwaysVisible: PropTypes.bool, + constructor(props: Props) { + super(props); - /** - * Handler dispatching setting default buttons action. - */ - _setDefaultToolboxButtons: PropTypes.func, + this._visibleButtons = new Set(interfaceConfig.TOOLBAR_BUTTONS); - /** - * Represents conference subject. - */ - _subject: PropTypes.string, + // Bind event handlers so they are only bound once per instance. + this._onMouseOut = this._onMouseOut.bind(this); + this._onMouseOver = this._onMouseOver.bind(this); + this._onSetOverflowVisible = this._onSetOverflowVisible.bind(this); - /** - * Flag showing whether to set subject slide in animation. - */ - _subjectSlideIn: PropTypes.bool, + this._onShortcutToggleChat = this._onShortcutToggleChat.bind(this); + this._onShortcutToggleFullScreen + = this._onShortcutToggleFullScreen.bind(this); + this._onShortcutToggleRaiseHand + = this._onShortcutToggleRaiseHand.bind(this); + this._onShortcutToggleScreenshare + = this._onShortcutToggleScreenshare.bind(this); - /** - * Property containing toolbox timeout id. - */ - _timeoutID: PropTypes.number - }; + this._onToolbarOpenFeedback + = this._onToolbarOpenFeedback.bind(this); + this._onToolbarOpenInvite = this._onToolbarOpenInvite.bind(this); + this._onToolbarOpenKeyboardShortcuts + = this._onToolbarOpenKeyboardShortcuts.bind(this); + this._onToolbarOpenSpeakerStats + = this._onToolbarOpenSpeakerStats.bind(this); + this._onToolbarOpenVideoQuality + = this._onToolbarOpenVideoQuality.bind(this); + + this._onToolbarToggleChat = this._onToolbarToggleChat.bind(this); + this._onToolbarToggleEtherpad + = this._onToolbarToggleEtherpad.bind(this); + this._onToolbarToggleFullScreen + = this._onToolbarToggleFullScreen.bind(this); + this._onToolbarToggleProfile + = this._onToolbarToggleProfile.bind(this); + this._onToolbarToggleRaiseHand + = this._onToolbarToggleRaiseHand.bind(this); + this._onToolbarToggleRecording + = this._onToolbarToggleRecording.bind(this); + this._onToolbarToggleScreenshare + = this._onToolbarToggleScreenshare.bind(this); + this._onToolbarToggleSettings + = this._onToolbarToggleSettings.bind(this); + this._onToolbarToggleSharedVideo + = this._onToolbarToggleSharedVideo.bind(this); + } /** - * Invokes reset always visible toolbox after mounting the component and - * registers legacy UI listeners. + * Sets keyboard shortcuts for to trigger ToolbarButtons actions. * + * @inheritdoc * @returns {void} */ - componentDidMount(): void { - // FIXME The redux action SET_DEFAULT_TOOLBOX_BUTTONS and related source - // code such as the redux action creator setDefaultToolboxButtons and - // _setDefaultToolboxButtons were introduced to solve the following bug - // in the implementation of features/toolbar at the time of this - // writing: getDefaultToolboxButtons uses interfaceConfig which is not - // in the redux store at the time of this writing yet interfaceConfig is - // modified after getDefaultToolboxButtons is called. - // SET_DEFAULT_TOOLBOX_BUTTONS represents/implements an explicit delay - // of the invocation of getDefaultToolboxButtons until, heuristically, - // all existing changes to interfaceConfig have been applied already in - // our known execution paths. - this.props._setDefaultToolboxButtons(); + componentDidMount() { + const KEYBOARD_SHORTCUTS = [ + this._shouldShowButton('chat') && { + character: 'C', + exec: this._onShortcutToggleChat, + helpDescription: 'keyboardShortcuts.toggleChat' + }, + this._shouldShowButton('desktop') && { + character: 'D', + exec: this._onShortcutToggleScreenshare, + helpDescription: 'keyboardShortcuts.toggleScreensharing' + }, + this._shouldShowButton('raisehand') && { + character: 'R', + exec: this._onShortcutToggleRaiseHand, + helpDescription: 'keyboardShortcuts.raiseHand' + }, + this._shouldShowButton('fullscreen') && { + character: 'S', + exec: this._onShortcutToggleFullScreen, + helpDescription: 'keyboardShortcuts.fullScreen' + } + ]; + + KEYBOARD_SHORTCUTS.forEach(shortcut => { + if (typeof shortcut === 'object') { + APP.keyboardshortcut.registerShortcut( + shortcut.character, + null, + shortcut.exec, + shortcut.helpDescription); + } + }); + } + + /** + * Update the visibility of the {@code OverflowMenuButton}. + * + * @inheritdoc + */ + componentWillReceiveProps(nextProps) { + // Ensure the dialog is closed when the toolbox becomes hidden. + if (this.state.showOverflowMenu && !nextProps._visible) { + this._onSetOverflowVisible(false); + } + + if (this.state.showOverflowMenu + && !this.props._dialog + && nextProps._dialog) { + this._onSetOverflowVisible(false); + this.props.dispatch(setToolbarHovered(false)); + } + } + + /** + * Removes keyboard shortcuts registered by this component. + * + * @inheritdoc + * @returns {void} + */ + componentWillUnmount() { + [ 'C', 'D', 'R', 'S' ].forEach(letter => + APP.keyboardshortcut.unregisterShortcut(letter)); } /** @@ -81,157 +308,862 @@ class Toolbox extends Component<*> { * @inheritdoc * @returns {ReactElement} */ - render(): React$Element<*> { - return ( -
- { - this._renderSubject() - } - { - this._renderToolbars() - } -
-
- ); - } - - /** - * Returns React element representing toolbox subject. - * - * @returns {ReactElement} - * @private - */ - _renderSubject(): React$Element<*> | null { - const { _subjectSlideIn, _subject } = this.props; - const classNames = [ 'subject' ]; - - if (!_subject) { - return null; - } - - if (_subjectSlideIn) { - classNames.push('subject_slide-in'); - } else { - classNames.push('subject_slide-out'); - } - - // XXX: Since chat is now not reactified we have to dangerously set - // inner HTML into the component. This has to be refactored while - // reactification of the Chat.js - const innerHtml = { - __html: _subject - }; + render() { + const { + _chatOpen, + _hideInviteButton, + _raisedHand, + _visible, + t + } = this.props; + const rootClassNames = `new-toolbox ${_visible ? 'visible' : ''} ${ + this._visibleButtons.size ? '' : 'no-buttons'}`; + const overflowMenuContent = this._renderOverflowMenuContent(); + const overflowHasItems = Boolean(overflowMenuContent.filter( + child => child).length); return (
+ className = { rootClassNames } + id = 'new-toolbox' + onMouseOut = { this._onMouseOut } + onMouseOver = { this._onMouseOver }> +
+ { this._shouldShowButton('desktop') + && this._renderDesktopSharingButton() } + { this._shouldShowButton('raisehand') + && } + { this._shouldShowButton('chat') + &&
+ + +
} +
+
+ { this._shouldShowButton('microphone') + && } + { this._shouldShowButton('hangup') + && } + { this._shouldShowButton('camera') + && } +
+
+ { this._shouldShowButton('invite') + && !_hideInviteButton + && } + { this._shouldShowButton('info') && } + { overflowHasItems + && +
    + { overflowMenuContent } +
+
} +
+
); } /** - * Renders primary and secondary toolbars. + * Callback invoked to display {@code FeedbackDialog}. * - * @returns {ReactElement} * @private + * @returns {void} */ - _renderToolbars(): React$Element<*> | null { - // In case we're not in alwaysVisible mode the toolbox should not be - // shown until timeoutID is initialized. - if (!this.props._alwaysVisible && this.props._timeoutID === null) { + _doOpenFeedback() { + const { _conference } = this.props; + + this.props.dispatch(openFeedbackDialog(_conference)); + } + + /** + * Opens the dialog for inviting people directly into the conference. + * + * @private + * @returns {void} + */ + _doOpenInvite() { + const { _addPeopleAvailable, _dialOutAvailable, dispatch } = this.props; + + if (_addPeopleAvailable || _dialOutAvailable) { + dispatch(openDialog(AddPeopleDialog, { + enableAddPeople: _addPeopleAvailable, + enableDialOut: _dialOutAvailable + })); + } + } + + /** + * Dispatches an action to display {@code KeyboardShortcuts}. + * + * @private + * @returns {void} + */ + _doOpenKeyboardShorcuts() { + this.props.dispatch(openKeyboardShortcutsDialog()); + } + + /** + * Callback invoked to display {@code SpeakerStats}. + * + * @private + * @returns {void} + */ + _doOpenSpeakerStats() { + this.props.dispatch(openDialog(SpeakerStats, { + conference: this.props._conference + })); + } + + /** + * Dispatches an action to toggle the video quality dialog. + * + * @private + * @returns {void} + */ + _doOpenVideoQuality() { + this.props.dispatch(openDialog(VideoQualityDialog)); + } + + /** + * Dispatches an action to toggle the display of chat. + * + * @private + * @returns {void} + */ + _doToggleChat() { + this.props.dispatch(toggleChat()); + } + + /** + * Dispatches an action to show or hide document editing. + * + * @private + * @returns {void} + */ + _doToggleEtherpad() { + this.props.dispatch(toggleDocument()); + } + + /** + * Dispatches an action to toggle screensharing. + * + * @private + * @returns {void} + */ + _doToggleFullScreen() { + const fullScreen = !this.props._fullScreen; + + this.props.dispatch(setFullScreen(fullScreen)); + } + + /** + * Dispatches an action to show or hide the profile edit panel. + * + * @private + * @returns {void} + */ + _doToggleProfile() { + this.props.dispatch(toggleProfile()); + } + + /** + * Dispatches an action to toggle the local participant's raised hand state. + * + * @private + * @returns {void} + */ + _doToggleRaiseHand() { + const { _localParticipantID, _raisedHand } = this.props; + + this.props.dispatch(participantUpdated({ + id: _localParticipantID, + local: true, + raisedHand: !_raisedHand + })); + } + + /** + * Dispatches an action to toggle recording. + * + * @private + * @returns {void} + */ + _doToggleRecording() { + this.props.dispatch(toggleRecording()); + } + + /** + * Dispatches an action to toggle screensharing. + * + * @private + * @returns {void} + */ + _doToggleScreenshare() { + if (this.props._desktopSharingEnabled) { + this.props.dispatch(toggleScreensharing()); + } + } + + /** + * Dispatches an action to toggle display of settings, be it the settings + * panel or directly to device selection. + * + * @private + * @returns {void} + */ + _doToggleSettings() { + if (interfaceConfig.SETTINGS_SECTIONS.length === 1 + && interfaceConfig.SETTINGS_SECTIONS.includes('devices')) { + this.props.dispatch(openDeviceSelectionDialog()); + } else { + this.props.dispatch(toggleSettings()); + } + } + + /** + * Dispatches an action to toggle YouTube video sharing. + * + * @private + * @returns {void} + */ + _doToggleSharedVideo() { + this.props.dispatch(toggleSharedVideo()); + } + + _onMouseOut: () => void; + + /** + * Dispatches an action signaling the toolbar is not being hovered. + * + * @private + * @returns {void} + */ + _onMouseOut() { + this.props.dispatch(setToolbarHovered(false)); + } + + _onMouseOver: () => void; + + /** + * Dispatches an action signaling the toolbar is being hovered. + * + * @private + * @returns {void} + */ + _onMouseOver() { + this.props.dispatch(setToolbarHovered(true)); + } + + _onSetOverflowVisible: (boolean) => void; + + /** + * Sets the visibility of the overflow menu. + * + * @param {boolean} visible - Whether or not the overflow menu should be + * displayed. + * @private + * @returns {void} + */ + _onSetOverflowVisible(visible) { + this.setState({ showOverflowMenu: visible }); + } + + _onShortcutToggleChat: () => void; + + /** + * Creates an analytics keyboard shortcut event and dispatches an action for + * toggling the display of chat. + * + * @private + * @returns {void} + */ + _onShortcutToggleChat() { + sendAnalytics(createShortcutEvent( + 'toggle.chat', + { + enable: !this.props._chatOpen + })); + + this._doToggleChat(); + } + + _onShortcutToggleFullScreen: () => void; + + /** + * Creates an analytics keyboard shortcut event and dispatches an action for + * toggling full screen mode. + * + * @private + * @returns {void} + */ + _onShortcutToggleFullScreen() { + sendAnalytics(createShortcutEvent( + 'toggle.fullscreen', + { + enable: !this.props._fullScreen + })); + + this._doToggleFullScreen(); + } + + _onShortcutToggleRaiseHand: () => void; + + /** + * Creates an analytics keyboard shortcut event and dispatches an action for + * toggling raise hand. + * + * @private + * @returns {void} + */ + _onShortcutToggleRaiseHand() { + sendAnalytics(createShortcutEvent( + 'toggle.raise.hand', + ACTION_SHORTCUT_TRIGGERED, + { enable: !this.props._raisedHand })); + + this._doToggleRaiseHand(); + } + + _onShortcutToggleScreenshare: () => void; + + /** + * Creates an analytics keyboard shortcut event and dispatches an action for + * toggling screensharing. + * + * @private + * @returns {void} + */ + _onShortcutToggleScreenshare() { + sendAnalytics(createToolbarEvent( + 'screen.sharing', + { + enable: !this.props._screensharing + })); + + this._doToggleScreenshare(); + } + + _onToolbarOpenFeedback: () => void; + + /** + * Creates an analytics toolbar event and dispatches an action for toggling + * display of feedback. + * + * @private + * @returns {void} + */ + _onToolbarOpenFeedback() { + sendAnalytics(createToolbarEvent('feedback')); + + this._doOpenFeedback(); + } + + _onToolbarOpenInvite: () => void; + + /** + * Creates an analytics toolbar event and dispatches an action for opening + * the modal for inviting people directly into the conference. + * + * @private + * @returns {void} + */ + _onToolbarOpenInvite() { + sendAnalytics(createToolbarEvent('invite')); + + this._doOpenInvite(); + } + + _onToolbarOpenKeyboardShortcuts: () => void; + + /** + * Creates an analytics toolbar event and dispatches an action for opening + * the modal for showing available keyboard shortcuts. + * + * @private + * @returns {void} + */ + _onToolbarOpenKeyboardShortcuts() { + sendAnalytics(createToolbarEvent('shortcuts')); + + this._doOpenKeyboardShorcuts(); + } + + _onToolbarOpenSpeakerStats: () => void; + + /** + * Creates an analytics toolbar event and dispatches an action for opening + * the speaker stats modal. + * + * @private + * @returns {void} + */ + _onToolbarOpenSpeakerStats() { + sendAnalytics(createToolbarEvent('speaker.stats')); + + this._doOpenSpeakerStats(); + } + + _onToolbarOpenVideoQuality: () => void; + + /** + * Creates an analytics toolbar event and dispatches an action for toggling + * open the video quality dialog. + * + * @private + * @returns {void} + */ + _onToolbarOpenVideoQuality() { + sendAnalytics(createToolbarEvent('video.quality')); + + this._doOpenVideoQuality(); + } + + _onToolbarToggleChat: () => void; + + /** + * Creates an analytics toolbar event and dispatches an action for toggling + * the display of chat. + * + * @private + * @returns {void} + */ + _onToolbarToggleChat() { + sendAnalytics(createToolbarEvent( + 'toggle.chat', + { + enable: !this.props._chatOpen + })); + + this._doToggleChat(); + } + + _onToolbarToggleEtherpad: () => void; + + /** + * Creates an analytics toolbar event and dispatches an action for toggling + * the display of document editing. + * + * @private + * @returns {void} + */ + _onToolbarToggleEtherpad() { + sendAnalytics(createToolbarEvent( + 'toggle.etherpad', + { + enable: !this.props._editingDocument + })); + + this._doToggleEtherpad(); + } + + _onToolbarToggleFullScreen: () => void; + + /** + * Creates an analytics toolbar event and dispatches an action for toggling + * full screen mode. + * + * @private + * @returns {void} + */ + _onToolbarToggleFullScreen() { + sendAnalytics(createToolbarEvent( + 'toggle.fullscreen', + { + enable: !this.props._fullScreen + })); + + this._doToggleFullScreen(); + } + + _onToolbarToggleOverflowMenu: () => void; + + /** + * Callback invoked to change whether the {@code OverflowMenu} is displayed + * or not. + * + * @private + * @returns {void} + */ + _onToolbarToggleOverflowMenu() { + sendAnalytics(createToolbarEvent('overflow')); + + this.setState({ showOverflowMenu: !this.state.showOverflowMenu }); + } + + _onToolbarToggleProfile: () => void; + + /** + * Creates an analytics toolbar event and dispatches an action for showing + * or hiding the profile edit panel. + * + * @private + * @returns {void} + */ + _onToolbarToggleProfile() { + sendAnalytics(createToolbarEvent('profile')); + + this._doToggleProfile(); + } + + _onToolbarToggleRaiseHand: () => void; + + /** + * Creates an analytics toolbar event and dispatches an action for toggling + * raise hand. + * + * @private + * @returns {void} + */ + _onToolbarToggleRaiseHand() { + sendAnalytics(createToolbarEvent( + 'raise.hand', + { enable: !this.props._raisedHand })); + + this._doToggleRaiseHand(); + } + + _onToolbarToggleRecording: () => void; + + /** + * Dispatches an action to toggle recording. + * + * @private + * @returns {void} + */ + _onToolbarToggleRecording() { + // No analytics handling is added here for the click as this action will + // exercise the old toolbar UI flow, which includes analytics handling. + + this._doToggleRecording(); + } + + _onToolbarToggleScreenshare: () => void; + + /** + * Creates an analytics toolbar event and dispatches an action for toggling + * screensharing. + * + * @private + * @returns {void} + */ + _onToolbarToggleScreenshare() { + if (!this.props._desktopSharingEnabled) { + return; + } + + sendAnalytics(createShortcutEvent( + 'toggle.screen.sharing', + ACTION_SHORTCUT_TRIGGERED, + { enable: !this.props._screensharing })); + + this._doToggleScreenshare(); + } + + _onToolbarToggleSettings: () => void; + + /** + * Creates an analytics toolbar event and dispatches an action for toggling + * settings display. + * + * @private + * @returns {void} + */ + _onToolbarToggleSettings() { + sendAnalytics(createToolbarEvent('settings')); + + this._doToggleSettings(); + } + + _onToolbarToggleSharedVideo: () => void; + + /** + * Creates an analytics toolbar event and dispatches an action for toggling + * the sharing of a YouTube video. + * + * @private + * @returns {void} + */ + _onToolbarToggleSharedVideo() { + sendAnalytics(createToolbarEvent('shared.video.toggled', + { + enable: !this.props._sharingVideo + })); + + this._doToggleSharedVideo(); + } + + /** + * Renders a button for toggleing screen sharing. + * + * @private + * @returns {ReactElement|null} + */ + _renderDesktopSharingButton() { + const { + _desktopSharingDisabledByConfig, + _desktopSharingEnabled, + _screensharing, + t + } = this.props; + + const disabledTooltipText + = interfaceConfig.DESKTOP_SHARING_BUTTON_DISABLED_TOOLTIP; + const showDisabledTooltip + = disabledTooltipText && _desktopSharingDisabledByConfig; + const visible = _desktopSharingEnabled || showDisabledTooltip; + + if (!visible) { return null; } + const classNames = `icon-share-desktop ${ + _screensharing ? 'toggled' : ''} ${ + _desktopSharingEnabled ? '' : 'disabled'}`; + const tooltip = showDisabledTooltip + ? disabledTooltipText + : t('toolbar.sharescreen'); + return ( -
- - - -
+ ); } + + /** + * Renders the list elements of the overflow menu. + * + * @private + * @returns {Array} + */ + _renderOverflowMenuContent() { + const { + _editingDocument, + _etherpadInitialized, + _feedbackConfigured, + _fullScreen, + _sharingVideo, + t + } = this.props; + + return [ + this._shouldShowButton('profile') + && , + this._shouldShowButton('settings') + && , + this._shouldShowButton('sharedvideo') + && , + this._shouldShowButton('etherpad') + && _etherpadInitialized + && , + this._shouldShowButton('fullscreen') + && , + this._renderRecordingButton(), + this._shouldShowButton('videoquality') + && , + this._shouldShowButton('stats') + && , + this._shouldShowButton('feedback') + && _feedbackConfigured + && , + this._shouldShowButton('shortcuts') + && + ]; + } + + /** + * Renders an {@code OverflowMenuItem} depending on the current recording + * state. + * + * @private + * @returns {ReactElement|null} + */ + _renderRecordingButton() { + const { + _isRecording, + _recordingEnabled, + _recordingType, + t + } = this.props; + + if (!_recordingEnabled || !this._shouldShowButton('recording')) { + return null; + } + + let translationKey; + + if (_recordingType === RECORDING_TYPES.JIBRI) { + translationKey = _isRecording + ? 'dialog.stopLiveStreaming' + : 'dialog.startLiveStreaming'; + } else { + translationKey = _isRecording + ? 'dialog.stopRecording' + : 'dialog.startRecording'; + } + + return ( + + ); + } + + _shouldShowButton: (string) => boolean; + + /** + * Returns if a button name has been explicitly configured to be displayed. + * + * @param {string} buttonName - The name of the button, as expected in + * {@link intefaceConfig}. + * @private + * @returns {boolean} True if the button should be displayed. + */ + _shouldShowButton(buttonName) { + return this._visibleButtons.has(buttonName); + } } /** - * Maps parts of Redux actions to component props. + * Maps (parts of) the redux state to {@link Toolbox}'s React {@code Component} + * props. * - * @param {Function} dispatch - Redux action dispatcher. - * @returns {{ - * _setDefaultToolboxButtons: Function - * }} + * @param {Object} state - The redux store/state. * @private + * @returns {{}} */ -function _mapDispatchToProps(dispatch: Function): Object { - return { - /** - * Dispatches a (redux) action to set the default toolbar buttons. - * - * @returns {Object} Dispatched action. - */ - _setDefaultToolboxButtons() { - dispatch(setDefaultToolboxButtons()); - } - }; -} - -/** - * Maps parts of toolbox state to component props. - * - * @param {Object} state - Redux state. - * @private - * @returns {{ - * _alwaysVisible: boolean, - * _audioMuted: boolean, - * _subjectSlideIn: boolean, - * _videoMuted: boolean - * }} - */ -function _mapStateToProps(state: Object): Object { +function _mapStateToProps(state) { + const { + conference, + desktopSharingEnabled + } = state['features/base/conference']; + const { + callStatsID, + disableDesktopSharing, + enableRecording, + enableUserRolesBasedOnToken, + iAmRecorder + } = state['features/base/config']; + const { isGuest } = state['features/base/jwt']; + const { isRecording, recordingType } = state['features/recording']; + const sharedVideoStatus = state['features/shared-video'].status; + const { current } = state['features/side-panel']; const { alwaysVisible, - subject, - subjectSlideIn, - timeoutID + fullScreen, + timeoutID, + visible } = state['features/toolbox']; + const localParticipant = getLocalParticipant(state); + const localVideo = getLocalVideoTrack(state['features/base/tracks']); + const isModerator = localParticipant.role === PARTICIPANT_ROLE.MODERATOR; + const isAddPeopleAvailable = !isGuest; + const isDialOutAvailable + = isModerator + && conference && conference.isSIPCallingSupported() + && (!enableUserRolesBasedOnToken || !isGuest); return { - ...abstractMapStateToProps(state), - - /** - * Indicates if the toolbox should always be visible. - * - * @private - * @type {boolean} - */ - _alwaysVisible: alwaysVisible, - - /** - * Property containing conference subject. - * - * @private - * @type {string} - */ - _subject: subject, - - /** - * Flag showing whether to set subject slide in animation. - * - * @private - * @type {boolean} - */ - _subjectSlideIn: subjectSlideIn, - - /** - * Property containing toolbox timeout id. - * - * @private - * @type {number} - */ - _timeoutID: timeoutID + _addPeopleAvailable: isAddPeopleAvailable, + _chatOpen: current === 'chat_container', + _conference: conference, + _desktopSharingEnabled: desktopSharingEnabled, + _desktopSharingDisabledByConfig: disableDesktopSharing, + _dialOutAvailable: isDialOutAvailable, + _dialog: Boolean(state['features/base/dialog'].component), + _editingDocument: Boolean(state['features/etherpad'].editing), + _etherpadInitialized: Boolean(state['features/etherpad'].initialized), + _feedbackConfigured: Boolean(callStatsID), + _hideInviteButton: iAmRecorder + || (!isAddPeopleAvailable && !isDialOutAvailable), + _isRecording: isRecording, + _fullScreen: fullScreen, + _localParticipantID: localParticipant.id, + _raisedHand: localParticipant.raisedHand, + _recordingEnabled: isModerator && enableRecording + && (conference && conference.isRecordingSupported()), + _recordingType: recordingType, + _screensharing: localVideo && localVideo.videoType === 'desktop', + _sharingVideo: sharedVideoStatus === 'playing' + || sharedVideoStatus === 'start' + || sharedVideoStatus === 'pause', + _visible: Boolean(timeoutID || visible || alwaysVisible) }; } -export default connect(_mapStateToProps, _mapDispatchToProps)(Toolbox); +export default translate(connect(_mapStateToProps)(Toolbox)); diff --git a/react/features/toolbox/components/ToolboxV2.native.js b/react/features/toolbox/components/ToolboxV2.native.js deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/react/features/toolbox/components/ToolboxV2.web.js b/react/features/toolbox/components/ToolboxV2.web.js deleted file mode 100644 index 33d56bd991..0000000000 --- a/react/features/toolbox/components/ToolboxV2.web.js +++ /dev/null @@ -1,1169 +0,0 @@ -// @flow - -import React, { Component } from 'react'; -import { connect } from 'react-redux'; - -import { - ACTION_SHORTCUT_TRIGGERED, - createShortcutEvent, - createToolbarEvent, - sendAnalytics -} from '../../analytics'; -import { openDialog } from '../../base/dialog'; -import { translate } from '../../base/i18n'; -import { - PARTICIPANT_ROLE, - getLocalParticipant, - participantUpdated -} from '../../base/participants'; -import { getLocalVideoTrack, toggleScreensharing } from '../../base/tracks'; -import { ChatCounter } from '../../chat'; -import { openDeviceSelectionDialog } from '../../device-selection'; -import { toggleDocument } from '../../etherpad'; -import { openFeedbackDialog } from '../../feedback'; -import { AddPeopleDialog, InfoDialogButton } from '../../invite'; -import { openKeyboardShortcutsDialog } from '../../keyboard-shortcuts'; -import { RECORDING_TYPES, toggleRecording } from '../../recording'; -import { toggleSharedVideo } from '../../shared-video'; -import { toggleChat, toggleProfile, toggleSettings } from '../../side-panel'; -import { SpeakerStats } from '../../speaker-stats'; -import { VideoQualityDialog } from '../../video-quality'; - -import { setFullScreen, setToolbarHovered } from '../actions'; - -import OverflowMenuButton from './OverflowMenuButton'; -import OverflowMenuItem from './OverflowMenuItem'; -import OverflowMenuProfileItem from './OverflowMenuProfileItem'; -import ToolbarButtonV2 from './ToolbarButtonV2'; -import { AudioMuteButton, HangupButton, VideoMuteButton } from './buttons'; - -type Props = { - - /** - * Whether or not the feature for adding people directly into the call - * is enabled. - */ - _addPeopleAvailable: boolean, - - /** - * Whether or not the chat feature is currently displayed. - */ - _chatOpen: boolean, - - /** - * The {@code JitsiConference} for the current conference. - */ - _conference: Object, - - /** - * Whether or not desktopsharing was explicitly configured to be disabled. - */ - _desktopSharingDisabledByConfig: boolean, - - /** - * Whether or not screensharing is initialized. - */ - _desktopSharingEnabled: boolean, - - /** - * Whether or not the feature for telephony to dial out to a number is - * enabled. - */ - _dialOutAvailable: boolean, - - /** - * Whether or not a dialog is displayed. - */ - _dialog: boolean, - - /** - * Whether or not the local participant is currently editing a document. - */ - _editingDocument: boolean, - - /** - * Whether or not collaborative document editing is enabled. - */ - _etherpadInitialized: boolean, - - /** - * Whether or not call feedback can be sent. - */ - _feedbackConfigured: boolean, - - /** - * Whether or not the app is currently in full screen. - */ - _fullScreen: boolean, - - /** - * Whether or not invite should be hidden, regardless of feature - * availability. - */ - _hideInviteButton: boolean, - - /** - * Whether or not the conference is currently being recorded by the local - * participant. - */ - _isRecording: boolean, - - /** - * The ID of the local participant. - */ - _localParticipantID: String, - - /** - * Whether or not the local participant's hand is raised. - */ - _raisedHand: boolean, - - /** - * Whether or not the recording feature is enabled for use. - */ - _recordingEnabled: boolean, - - /** - * Whether the recording feature is live streaming (jibri) or is file - * recording (jirecon). - */ - _recordingType: String, - - /** - * Whether or not the local participant is screensharing. - */ - _screensharing: boolean, - - /** - * Whether or not the local participant is sharing a YouTube video. - */ - _sharingVideo: boolean, - - /** - * Flag showing whether toolbar is visible. - */ - _visible: boolean, - - /** - * Invoked to active other features of the app. - */ - dispatch: Function, - - /** - * Invoked to obtain translated strings. - */ - t: Function -} - -type State = { - - /** - * Whether or not the overflow menu is visible. - */ - showOverflowMenu: boolean -} - -declare var APP: Object; -declare var interfaceConfig: Object; - -/** - * Implements the conference toolbox on React/Web. - * - * @extends Component - */ -class ToolboxV2 extends Component { - _visibleButtons: Object; - - state = { - showOverflowMenu: false - } - - /** - * Initializes a new {@code Toolbox} instance. - * - * @param {Props} props - The read-only React {@code Component} props with - * which the new instance is to be initialized. - */ - constructor(props: Props) { - super(props); - - this._visibleButtons = new Set(interfaceConfig.TOOLBAR_BUTTONS); - - // Bind event handlers so they are only bound once per instance. - this._onMouseOut = this._onMouseOut.bind(this); - this._onMouseOver = this._onMouseOver.bind(this); - this._onSetOverflowVisible = this._onSetOverflowVisible.bind(this); - - this._onShortcutToggleChat = this._onShortcutToggleChat.bind(this); - this._onShortcutToggleFullScreen - = this._onShortcutToggleFullScreen.bind(this); - this._onShortcutToggleRaiseHand - = this._onShortcutToggleRaiseHand.bind(this); - this._onShortcutToggleScreenshare - = this._onShortcutToggleScreenshare.bind(this); - - this._onToolbarOpenFeedback - = this._onToolbarOpenFeedback.bind(this); - this._onToolbarOpenInvite = this._onToolbarOpenInvite.bind(this); - this._onToolbarOpenKeyboardShortcuts - = this._onToolbarOpenKeyboardShortcuts.bind(this); - this._onToolbarOpenSpeakerStats - = this._onToolbarOpenSpeakerStats.bind(this); - this._onToolbarOpenVideoQuality - = this._onToolbarOpenVideoQuality.bind(this); - - this._onToolbarToggleChat = this._onToolbarToggleChat.bind(this); - this._onToolbarToggleEtherpad - = this._onToolbarToggleEtherpad.bind(this); - this._onToolbarToggleFullScreen - = this._onToolbarToggleFullScreen.bind(this); - this._onToolbarToggleProfile - = this._onToolbarToggleProfile.bind(this); - this._onToolbarToggleRaiseHand - = this._onToolbarToggleRaiseHand.bind(this); - this._onToolbarToggleRecording - = this._onToolbarToggleRecording.bind(this); - this._onToolbarToggleScreenshare - = this._onToolbarToggleScreenshare.bind(this); - this._onToolbarToggleSettings - = this._onToolbarToggleSettings.bind(this); - this._onToolbarToggleSharedVideo - = this._onToolbarToggleSharedVideo.bind(this); - } - - /** - * Sets keyboard shortcuts for to trigger ToolbarButtons actions. - * - * @inheritdoc - * @returns {void} - */ - componentDidMount() { - const KEYBOARD_SHORTCUTS = [ - this._shouldShowButton('chat') && { - character: 'C', - exec: this._onShortcutToggleChat, - helpDescription: 'keyboardShortcuts.toggleChat' - }, - this._shouldShowButton('desktop') && { - character: 'D', - exec: this._onShortcutToggleScreenshare, - helpDescription: 'keyboardShortcuts.toggleScreensharing' - }, - this._shouldShowButton('raisehand') && { - character: 'R', - exec: this._onShortcutToggleRaiseHand, - helpDescription: 'keyboardShortcuts.raiseHand' - }, - this._shouldShowButton('fullscreen') && { - character: 'S', - exec: this._onShortcutToggleFullScreen, - helpDescription: 'keyboardShortcuts.fullScreen' - } - ]; - - KEYBOARD_SHORTCUTS.forEach(shortcut => { - if (typeof shortcut === 'object') { - APP.keyboardshortcut.registerShortcut( - shortcut.character, - null, - shortcut.exec, - shortcut.helpDescription); - } - }); - } - - /** - * Update the visibility of the {@code OverflowMenuButton}. - * - * @inheritdoc - */ - componentWillReceiveProps(nextProps) { - // Ensure the dialog is closed when the toolbox becomes hidden. - if (this.state.showOverflowMenu && !nextProps._visible) { - this._onSetOverflowVisible(false); - } - - if (this.state.showOverflowMenu - && !this.props._dialog - && nextProps._dialog) { - this._onSetOverflowVisible(false); - this.props.dispatch(setToolbarHovered(false)); - } - } - - /** - * Removes keyboard shortcuts registered by this component. - * - * @inheritdoc - * @returns {void} - */ - componentWillUnmount() { - [ 'C', 'D', 'R', 'S' ].forEach(letter => - APP.keyboardshortcut.unregisterShortcut(letter)); - } - - /** - * Implements React's {@link Component#render()}. - * - * @inheritdoc - * @returns {ReactElement} - */ - render() { - const { - _chatOpen, - _hideInviteButton, - _raisedHand, - _visible, - t - } = this.props; - const rootClassNames = `new-toolbox ${_visible ? 'visible' : ''} ${ - this._visibleButtons.size ? '' : 'no-buttons'}`; - const overflowMenuContent = this._renderOverflowMenuContent(); - const overflowHasItems = Boolean(overflowMenuContent.filter( - child => child).length); - - return ( -
-
- { this._shouldShowButton('desktop') - && this._renderDesktopSharingButton() } - { this._shouldShowButton('raisehand') - && } - { this._shouldShowButton('chat') - &&
- - -
} -
-
- { this._shouldShowButton('microphone') - && } - { this._shouldShowButton('hangup') - && } - { this._shouldShowButton('camera') - && } -
-
- { this._shouldShowButton('invite') - && !_hideInviteButton - && } - { this._shouldShowButton('info') && } - { overflowHasItems - && -
    - { overflowMenuContent } -
-
} -
-
- ); - } - - /** - * Callback invoked to display {@code FeedbackDialog}. - * - * @private - * @returns {void} - */ - _doOpenFeedback() { - const { _conference } = this.props; - - this.props.dispatch(openFeedbackDialog(_conference)); - } - - /** - * Opens the dialog for inviting people directly into the conference. - * - * @private - * @returns {void} - */ - _doOpenInvite() { - const { _addPeopleAvailable, _dialOutAvailable, dispatch } = this.props; - - if (_addPeopleAvailable || _dialOutAvailable) { - dispatch(openDialog(AddPeopleDialog, { - enableAddPeople: _addPeopleAvailable, - enableDialOut: _dialOutAvailable - })); - } - } - - /** - * Dispatches an action to display {@code KeyboardShortcuts}. - * - * @private - * @returns {void} - */ - _doOpenKeyboardShorcuts() { - this.props.dispatch(openKeyboardShortcutsDialog()); - } - - /** - * Callback invoked to display {@code SpeakerStats}. - * - * @private - * @returns {void} - */ - _doOpenSpeakerStats() { - this.props.dispatch(openDialog(SpeakerStats, { - conference: this.props._conference - })); - } - - /** - * Dispatches an action to toggle the video quality dialog. - * - * @private - * @returns {void} - */ - _doOpenVideoQuality() { - this.props.dispatch(openDialog(VideoQualityDialog)); - } - - /** - * Dispatches an action to toggle the display of chat. - * - * @private - * @returns {void} - */ - _doToggleChat() { - this.props.dispatch(toggleChat()); - } - - /** - * Dispatches an action to show or hide document editing. - * - * @private - * @returns {void} - */ - _doToggleEtherpad() { - this.props.dispatch(toggleDocument()); - } - - /** - * Dispatches an action to toggle screensharing. - * - * @private - * @returns {void} - */ - _doToggleFullScreen() { - const fullScreen = !this.props._fullScreen; - - this.props.dispatch(setFullScreen(fullScreen)); - } - - /** - * Dispatches an action to show or hide the profile edit panel. - * - * @private - * @returns {void} - */ - _doToggleProfile() { - this.props.dispatch(toggleProfile()); - } - - /** - * Dispatches an action to toggle the local participant's raised hand state. - * - * @private - * @returns {void} - */ - _doToggleRaiseHand() { - const { _localParticipantID, _raisedHand } = this.props; - - this.props.dispatch(participantUpdated({ - id: _localParticipantID, - local: true, - raisedHand: !_raisedHand - })); - } - - /** - * Dispatches an action to toggle recording. - * - * @private - * @returns {void} - */ - _doToggleRecording() { - this.props.dispatch(toggleRecording()); - } - - /** - * Dispatches an action to toggle screensharing. - * - * @private - * @returns {void} - */ - _doToggleScreenshare() { - if (this.props._desktopSharingEnabled) { - this.props.dispatch(toggleScreensharing()); - } - } - - /** - * Dispatches an action to toggle display of settings, be it the settings - * panel or directly to device selection. - * - * @private - * @returns {void} - */ - _doToggleSettings() { - if (interfaceConfig.SETTINGS_SECTIONS.length === 1 - && interfaceConfig.SETTINGS_SECTIONS.includes('devices')) { - this.props.dispatch(openDeviceSelectionDialog()); - } else { - this.props.dispatch(toggleSettings()); - } - } - - /** - * Dispatches an action to toggle YouTube video sharing. - * - * @private - * @returns {void} - */ - _doToggleSharedVideo() { - this.props.dispatch(toggleSharedVideo()); - } - - _onMouseOut: () => void; - - /** - * Dispatches an action signaling the toolbar is not being hovered. - * - * @private - * @returns {void} - */ - _onMouseOut() { - this.props.dispatch(setToolbarHovered(false)); - } - - _onMouseOver: () => void; - - /** - * Dispatches an action signaling the toolbar is being hovered. - * - * @private - * @returns {void} - */ - _onMouseOver() { - this.props.dispatch(setToolbarHovered(true)); - } - - _onSetOverflowVisible: (boolean) => void; - - /** - * Sets the visibility of the overflow menu. - * - * @param {boolean} visible - Whether or not the overflow menu should be - * displayed. - * @private - * @returns {void} - */ - _onSetOverflowVisible(visible) { - this.setState({ showOverflowMenu: visible }); - } - - _onShortcutToggleChat: () => void; - - /** - * Creates an analytics keyboard shortcut event and dispatches an action for - * toggling the display of chat. - * - * @private - * @returns {void} - */ - _onShortcutToggleChat() { - sendAnalytics(createShortcutEvent( - 'toggle.chat', - { - enable: !this.props._chatOpen - })); - - this._doToggleChat(); - } - - _onShortcutToggleFullScreen: () => void; - - /** - * Creates an analytics keyboard shortcut event and dispatches an action for - * toggling full screen mode. - * - * @private - * @returns {void} - */ - _onShortcutToggleFullScreen() { - sendAnalytics(createShortcutEvent( - 'toggle.fullscreen', - { - enable: !this.props._fullScreen - })); - - this._doToggleFullScreen(); - } - - _onShortcutToggleRaiseHand: () => void; - - /** - * Creates an analytics keyboard shortcut event and dispatches an action for - * toggling raise hand. - * - * @private - * @returns {void} - */ - _onShortcutToggleRaiseHand() { - sendAnalytics(createShortcutEvent( - 'toggle.raise.hand', - ACTION_SHORTCUT_TRIGGERED, - { enable: !this.props._raisedHand })); - - this._doToggleRaiseHand(); - } - - _onShortcutToggleScreenshare: () => void; - - /** - * Creates an analytics keyboard shortcut event and dispatches an action for - * toggling screensharing. - * - * @private - * @returns {void} - */ - _onShortcutToggleScreenshare() { - sendAnalytics(createToolbarEvent( - 'screen.sharing', - { - enable: !this.props._screensharing - })); - - this._doToggleScreenshare(); - } - - _onToolbarOpenFeedback: () => void; - - /** - * Creates an analytics toolbar event and dispatches an action for toggling - * display of feedback. - * - * @private - * @returns {void} - */ - _onToolbarOpenFeedback() { - sendAnalytics(createToolbarEvent('feedback')); - - this._doOpenFeedback(); - } - - _onToolbarOpenInvite: () => void; - - /** - * Creates an analytics toolbar event and dispatches an action for opening - * the modal for inviting people directly into the conference. - * - * @private - * @returns {void} - */ - _onToolbarOpenInvite() { - sendAnalytics(createToolbarEvent('invite')); - - this._doOpenInvite(); - } - - _onToolbarOpenKeyboardShortcuts: () => void; - - /** - * Creates an analytics toolbar event and dispatches an action for opening - * the modal for showing available keyboard shortcuts. - * - * @private - * @returns {void} - */ - _onToolbarOpenKeyboardShortcuts() { - sendAnalytics(createToolbarEvent('shortcuts')); - - this._doOpenKeyboardShorcuts(); - } - - _onToolbarOpenSpeakerStats: () => void; - - /** - * Creates an analytics toolbar event and dispatches an action for opening - * the speaker stats modal. - * - * @private - * @returns {void} - */ - _onToolbarOpenSpeakerStats() { - sendAnalytics(createToolbarEvent('speaker.stats')); - - this._doOpenSpeakerStats(); - } - - _onToolbarOpenVideoQuality: () => void; - - /** - * Creates an analytics toolbar event and dispatches an action for toggling - * open the video quality dialog. - * - * @private - * @returns {void} - */ - _onToolbarOpenVideoQuality() { - sendAnalytics(createToolbarEvent('video.quality')); - - this._doOpenVideoQuality(); - } - - _onToolbarToggleChat: () => void; - - /** - * Creates an analytics toolbar event and dispatches an action for toggling - * the display of chat. - * - * @private - * @returns {void} - */ - _onToolbarToggleChat() { - sendAnalytics(createToolbarEvent( - 'toggle.chat', - { - enable: !this.props._chatOpen - })); - - this._doToggleChat(); - } - - _onToolbarToggleEtherpad: () => void; - - /** - * Creates an analytics toolbar event and dispatches an action for toggling - * the display of document editing. - * - * @private - * @returns {void} - */ - _onToolbarToggleEtherpad() { - sendAnalytics(createToolbarEvent( - 'toggle.etherpad', - { - enable: !this.props._editingDocument - })); - - this._doToggleEtherpad(); - } - - _onToolbarToggleFullScreen: () => void; - - /** - * Creates an analytics toolbar event and dispatches an action for toggling - * full screen mode. - * - * @private - * @returns {void} - */ - _onToolbarToggleFullScreen() { - sendAnalytics(createToolbarEvent( - 'toggle.fullscreen', - { - enable: !this.props._fullScreen - })); - - this._doToggleFullScreen(); - } - - _onToolbarToggleOverflowMenu: () => void; - - /** - * Callback invoked to change whether the {@code OverflowMenu} is displayed - * or not. - * - * @private - * @returns {void} - */ - _onToolbarToggleOverflowMenu() { - sendAnalytics(createToolbarEvent('overflow')); - - this.setState({ showOverflowMenu: !this.state.showOverflowMenu }); - } - - _onToolbarToggleProfile: () => void; - - /** - * Creates an analytics toolbar event and dispatches an action for showing - * or hiding the profile edit panel. - * - * @private - * @returns {void} - */ - _onToolbarToggleProfile() { - sendAnalytics(createToolbarEvent('profile')); - - this._doToggleProfile(); - } - - _onToolbarToggleRaiseHand: () => void; - - /** - * Creates an analytics toolbar event and dispatches an action for toggling - * raise hand. - * - * @private - * @returns {void} - */ - _onToolbarToggleRaiseHand() { - sendAnalytics(createToolbarEvent( - 'raise.hand', - { enable: !this.props._raisedHand })); - - this._doToggleRaiseHand(); - } - - _onToolbarToggleRecording: () => void; - - /** - * Dispatches an action to toggle recording. - * - * @private - * @returns {void} - */ - _onToolbarToggleRecording() { - // No analytics handling is added here for the click as this action will - // exercise the old toolbar UI flow, which includes analytics handling. - - this._doToggleRecording(); - } - - _onToolbarToggleScreenshare: () => void; - - /** - * Creates an analytics toolbar event and dispatches an action for toggling - * screensharing. - * - * @private - * @returns {void} - */ - _onToolbarToggleScreenshare() { - if (!this.props._desktopSharingEnabled) { - return; - } - - sendAnalytics(createShortcutEvent( - 'toggle.screen.sharing', - ACTION_SHORTCUT_TRIGGERED, - { enable: !this.props._screensharing })); - - this._doToggleScreenshare(); - } - - _onToolbarToggleSettings: () => void; - - /** - * Creates an analytics toolbar event and dispatches an action for toggling - * settings display. - * - * @private - * @returns {void} - */ - _onToolbarToggleSettings() { - sendAnalytics(createToolbarEvent('settings')); - - this._doToggleSettings(); - } - - _onToolbarToggleSharedVideo: () => void; - - /** - * Creates an analytics toolbar event and dispatches an action for toggling - * the sharing of a YouTube video. - * - * @private - * @returns {void} - */ - _onToolbarToggleSharedVideo() { - sendAnalytics(createToolbarEvent('shared.video.toggled', - { - enable: !this.props._sharingVideo - })); - - this._doToggleSharedVideo(); - } - - /** - * Renders a button for toggleing screen sharing. - * - * @private - * @returns {ReactElement|null} - */ - _renderDesktopSharingButton() { - const { - _desktopSharingDisabledByConfig, - _desktopSharingEnabled, - _screensharing, - t - } = this.props; - - const disabledTooltipText - = interfaceConfig.DESKTOP_SHARING_BUTTON_DISABLED_TOOLTIP; - const showDisabledTooltip - = disabledTooltipText && _desktopSharingDisabledByConfig; - const visible = _desktopSharingEnabled || showDisabledTooltip; - - if (!visible) { - return null; - } - - const classNames = `icon-share-desktop ${ - _screensharing ? 'toggled' : ''} ${ - _desktopSharingEnabled ? '' : 'disabled'}`; - const tooltip = showDisabledTooltip - ? disabledTooltipText - : t('toolbar.sharescreen'); - - return ( - - ); - } - - /** - * Renders the list elements of the overflow menu. - * - * @private - * @returns {Array} - */ - _renderOverflowMenuContent() { - const { - _editingDocument, - _etherpadInitialized, - _feedbackConfigured, - _fullScreen, - _sharingVideo, - t - } = this.props; - - return [ - this._shouldShowButton('profile') - && , - this._shouldShowButton('settings') - && , - this._shouldShowButton('sharedvideo') - && , - this._shouldShowButton('etherpad') - && _etherpadInitialized - && , - this._shouldShowButton('fullscreen') - && , - this._renderRecordingButton(), - this._shouldShowButton('videoquality') - && , - this._shouldShowButton('stats') - && , - this._shouldShowButton('feedback') - && _feedbackConfigured - && , - this._shouldShowButton('shortcuts') - && - ]; - } - - /** - * Renders an {@code OverflowMenuItem} depending on the current recording - * state. - * - * @private - * @returns {ReactElement|null} - */ - _renderRecordingButton() { - const { - _isRecording, - _recordingEnabled, - _recordingType, - t - } = this.props; - - if (!_recordingEnabled || !this._shouldShowButton('recording')) { - return null; - } - - let translationKey; - - if (_recordingType === RECORDING_TYPES.JIBRI) { - translationKey = _isRecording - ? 'dialog.stopLiveStreaming' - : 'dialog.startLiveStreaming'; - } else { - translationKey = _isRecording - ? 'dialog.stopRecording' - : 'dialog.startRecording'; - } - - return ( - - ); - } - - _shouldShowButton: (string) => boolean; - - /** - * Returns if a button name has been explicitly configured to be displayed. - * - * @param {string} buttonName - The name of the button, as expected in - * {@link intefaceConfig}. - * @private - * @returns {boolean} True if the button should be displayed. - */ - _shouldShowButton(buttonName) { - return this._visibleButtons.has(buttonName); - } -} - -/** - * Maps (parts of) the redux state to {@link Toolbox}'s React {@code Component} - * props. - * - * @param {Object} state - The redux store/state. - * @private - * @returns {{}} - */ -function _mapStateToProps(state) { - const { - conference, - desktopSharingEnabled - } = state['features/base/conference']; - const { - callStatsID, - disableDesktopSharing, - enableRecording, - enableUserRolesBasedOnToken, - iAmRecorder - } = state['features/base/config']; - const { isGuest } = state['features/base/jwt']; - const { isRecording, recordingType } = state['features/recording']; - const sharedVideoStatus = state['features/shared-video'].status; - const { current } = state['features/side-panel']; - const { - alwaysVisible, - fullScreen, - timeoutID, - visible - } = state['features/toolbox']; - const localParticipant = getLocalParticipant(state); - const localVideo = getLocalVideoTrack(state['features/base/tracks']); - const isModerator = localParticipant.role === PARTICIPANT_ROLE.MODERATOR; - const isAddPeopleAvailable = !isGuest; - const isDialOutAvailable - = isModerator - && conference && conference.isSIPCallingSupported() - && (!enableUserRolesBasedOnToken || !isGuest); - - return { - _addPeopleAvailable: isAddPeopleAvailable, - _chatOpen: current === 'chat_container', - _conference: conference, - _desktopSharingEnabled: desktopSharingEnabled, - _desktopSharingDisabledByConfig: disableDesktopSharing, - _dialOutAvailable: isDialOutAvailable, - _dialog: Boolean(state['features/base/dialog'].component), - _editingDocument: Boolean(state['features/etherpad'].editing), - _etherpadInitialized: Boolean(state['features/etherpad'].initialized), - _feedbackConfigured: Boolean(callStatsID), - _hideInviteButton: iAmRecorder - || (!isAddPeopleAvailable && !isDialOutAvailable), - _isRecording: isRecording, - _fullScreen: fullScreen, - _localParticipantID: localParticipant.id, - _raisedHand: localParticipant.raisedHand, - _recordingEnabled: isModerator && enableRecording - && (conference && conference.isRecordingSupported()), - _recordingType: recordingType, - _screensharing: localVideo && localVideo.videoType === 'desktop', - _sharingVideo: sharedVideoStatus === 'playing' - || sharedVideoStatus === 'start' - || sharedVideoStatus === 'pause', - _visible: Boolean(timeoutID || visible || alwaysVisible) - }; -} - -export default translate(connect(_mapStateToProps)(ToolboxV2)); diff --git a/react/features/toolbox/components/index.js b/react/features/toolbox/components/index.js index 25b4f4eb1f..8e8d3f47f7 100644 --- a/react/features/toolbox/components/index.js +++ b/react/features/toolbox/components/index.js @@ -2,6 +2,5 @@ export { default as ToolbarButton } from './ToolbarButton'; export { default as ToolbarButtonV2 } from './ToolbarButtonV2'; export { default as ToolbarButtonWithDialog } from './ToolbarButtonWithDialog'; -export { default as Toolbox } from './Toolbox'; export { default as ToolboxFilmstrip } from './ToolboxFilmstrip'; -export { default as ToolboxV2 } from './ToolboxV2'; +export { default as Toolbox } from './Toolbox'; diff --git a/react/features/toolbox/defaultToolbarButtons.native.js b/react/features/toolbox/defaultToolbarButtons.native.js deleted file mode 100644 index f237ddf58e..0000000000 --- a/react/features/toolbox/defaultToolbarButtons.native.js +++ /dev/null @@ -1 +0,0 @@ -export default undefined; diff --git a/react/features/toolbox/defaultToolbarButtons.web.js b/react/features/toolbox/defaultToolbarButtons.web.js deleted file mode 100644 index 3f7c2df45b..0000000000 --- a/react/features/toolbox/defaultToolbarButtons.web.js +++ /dev/null @@ -1,532 +0,0 @@ -// @flow - -import React from 'react'; - -import { setFullScreen } from '../toolbox'; -import { - ACTION_SHORTCUT_TRIGGERED as TRIGGERED, - AUDIO_MUTE, - VIDEO_MUTE, - createShortcutEvent, - createToolbarEvent, - sendAnalytics -} from '../analytics'; -import { - getLocalParticipant, - participantUpdated -} from '../base/participants'; -import { ParticipantCounter } from '../contact-list'; -import { openDeviceSelectionDialog } from '../device-selection'; -import { InfoDialogButton } from '../invite'; -import UIEvents from '../../../service/UI/UIEvents'; -import { VideoQualityButton } from '../video-quality'; - -import ProfileButton from './components/ProfileButton'; - -declare var APP: Object; -declare var interfaceConfig: Object; - -/** - * The cache of {@link getDefaultButtons()}. - */ -let defaultButtons: Object; - -/** - * Returns a map of all button descriptors and according properties. - * - * @returns {Object} - The maps of default button descriptors. - */ -export default function getDefaultButtons() { - if (defaultButtons) { - return defaultButtons; - } - - defaultButtons = { - /** - * The descriptor of the camera toolbar button. - */ - camera: { - classNames: [ 'button', 'icon-camera' ], - enabled: true, - isDisplayed: () => true, - id: 'toolbar_button_camera', - onClick() { - // TODO: Why is this different from the code which handles - // a keyboard shortcut? - const newVideoMutedState = !APP.conference.isLocalVideoMuted(); - - // The 'enable' attribute in the event is set to true if the - // button click triggered a mute action, and set to false if it - // triggered an unmute action. - sendAnalytics(createToolbarEvent( - VIDEO_MUTE, - { - enable: newVideoMutedState - })); - APP.UI.emitEvent(UIEvents.VIDEO_MUTED, newVideoMutedState); - }, - popups: [ - { - dataAttr: 'audioOnly.featureToggleDisabled', - dataInterpolate: { feature: 'video mute' }, - id: 'unmuteWhileAudioOnly' - } - ], - shortcut: 'V', - shortcutAttr: 'toggleVideoPopover', - shortcutFunc() { - if (APP.conference.isAudioOnly()) { - APP.UI.emitEvent(UIEvents.VIDEO_UNMUTING_WHILE_AUDIO_ONLY); - - return; - } - - // The 'enable' attribute in the event is set to true if the - // shortcut triggered a mute action, and set to false if it - // triggered an unmute action. - sendAnalytics(createShortcutEvent( - VIDEO_MUTE, - TRIGGERED, - { enable: !APP.conference.isLocalVideoMuted() })); - APP.conference.toggleVideoMuted(); - }, - shortcutDescription: 'keyboardShortcuts.videoMute', - tooltipKey: 'toolbar.videomute' - }, - - /** - * The descriptor of the chat toolbar button. - */ - chat: { - classNames: [ 'button', 'icon-chat' ], - enabled: true, - html: - , - id: 'toolbar_button_chat', - onClick() { - // The 'enable' attribute is set to true if the click resulted - // in the chat panel being shown, and to false if it was hidden. - sendAnalytics(createToolbarEvent( - 'toggle.chat', - { - enable: !APP.UI.isChatVisible() - })); - APP.UI.emitEvent(UIEvents.TOGGLE_CHAT); - }, - shortcut: 'C', - shortcutAttr: 'toggleChatPopover', - shortcutFunc() { - // The 'enable' attribute is set to true if the shortcut - // resulted in the chat panel being shown, and to false if it - // was hidden. - sendAnalytics(createShortcutEvent( - 'toggle.chat', - { - enable: !APP.UI.isChatVisible() - })); - APP.UI.toggleChat(); - }, - shortcutDescription: 'keyboardShortcuts.toggleChat', - sideContainerId: 'chat_container', - tooltipKey: 'toolbar.chat' - }, - - /** - * The descriptor of the contact list toolbar button. - */ - contacts: { - childComponent: ParticipantCounter, - classNames: [ 'button', 'icon-contactList' ], - enabled: true, - id: 'toolbar_contact_list', - onClick() { - // TODO: Include an 'enable' attribute which specifies whether - // the contacts panel was shown or hidden. - sendAnalytics(createToolbarEvent('contacts')); - APP.UI.emitEvent(UIEvents.TOGGLE_CONTACT_LIST); - }, - sideContainerId: 'contacts_container', - tooltipKey: 'bottomtoolbar.contactlist' - }, - - /** - * The descriptor of the desktop sharing toolbar button. - */ - desktop: { - classNames: [ 'button', 'icon-share-desktop' ], - enabled: true, - id: 'toolbar_button_desktopsharing', - onClick() { - // TODO: Why is the button clicked handled differently that - // a keyboard shortcut press (firing a TOGGLE_SCREENSHARING - // event vs. directly calling toggleScreenSharing())? - sendAnalytics(createToolbarEvent( - 'screen.sharing', - { - enable: !APP.conference.isSharingScreen - })); - APP.UI.emitEvent(UIEvents.TOGGLE_SCREENSHARING); - }, - popups: [ - { - dataAttr: 'audioOnly.featureToggleDisabled', - dataInterpolate: { feature: 'screen sharing' }, - id: 'screenshareWhileAudioOnly' - } - ], - shortcut: 'D', - shortcutAttr: 'toggleDesktopSharingPopover', - shortcutFunc() { - // The 'enable' attribute is set to true if pressing the - // shortcut resulted in screen sharing being enabled, and false - // if it resulted in screen sharing being disabled. - sendAnalytics(createShortcutEvent( - 'toggle.screen.sharing', - TRIGGERED, - { enable: !APP.conference.isSharingScreen })); - - // eslint-disable-next-line no-empty-function - APP.conference.toggleScreenSharing().catch(() => {}); - }, - shortcutDescription: 'keyboardShortcuts.toggleScreensharing', - tooltipKey: 'toolbar.sharescreen' - }, - - /** - * The descriptor of the device selection toolbar button. - */ - fodeviceselection: { - classNames: [ 'button', 'icon-settings' ], - enabled: true, - isDisplayed() { - return interfaceConfig.filmStripOnly; - }, - id: 'toolbar_button_fodeviceselection', - onClick(dispatch: Function) { - sendAnalytics( - createToolbarEvent('filmstrip.only.device.selection')); - - dispatch(openDeviceSelectionDialog()); - }, - sideContainerId: 'settings_container', - tooltipKey: 'toolbar.Settings' - }, - - /** - * The descriptor of the dialpad toolbar button. - */ - dialpad: { - classNames: [ 'button', 'icon-dialpad' ], - enabled: true, - - // TODO: remove it after UI.updateDTMFSupport fix - hidden: true, - id: 'toolbar_button_dialpad', - onClick() { - sendAnalytics(createToolbarEvent('dialpad')); - }, - tooltipKey: 'toolbar.dialpad' - }, - - /** - * The descriptor of the etherpad toolbar button. - */ - etherpad: { - classNames: [ 'button', 'icon-share-doc' ], - enabled: true, - hidden: true, - id: 'toolbar_button_etherpad', - onClick() { - // The 'enable' attribute is set to true if the click resulted - // in the etherpad panel being shown, or false it it was hidden. - sendAnalytics(createToolbarEvent( - 'toggle.etherpad', - { - enable: !APP.UI.isEtherpadVisible() - })); - APP.UI.emitEvent(UIEvents.ETHERPAD_CLICKED); - }, - tooltipKey: 'toolbar.etherpad' - }, - - /** - * The descriptor of the toolbar button which toggles full-screen mode. - */ - fullscreen: { - classNames: [ 'button', 'icon-full-screen' ], - enabled: true, - id: 'toolbar_button_fullScreen', - onClick() { - const state = APP.store.getState(); - const isFullScreen = Boolean( - state['features/toolbox'].fullScreen); - - // The 'enable' attribute is set to true if the action resulted - // in fullscreen mode being enabled. - sendAnalytics(createToolbarEvent( - 'toggle.fullscreen', - { - enable: !isFullScreen - })); - - APP.store.dispatch(setFullScreen(!isFullScreen)); - }, - shortcut: 'S', - shortcutAttr: 'toggleFullscreenPopover', - shortcutDescription: 'keyboardShortcuts.fullScreen', - shortcutFunc() { - const state = APP.store.getState(); - const isFullScreen = Boolean( - state['features/toolbox'].fullScreen); - - // The 'enable' attribute is set to true if the action resulted - // in fullscreen mode being enabled. - sendAnalytics(createShortcutEvent( - 'toggle.fullscreen', - { - enable: !isFullScreen - })); - - APP.store.dispatch(setFullScreen(!isFullScreen)); - }, - tooltipKey: 'toolbar.fullscreen' - }, - - /** - * The descriptor of the toolbar button which hangs up the - * call/conference. - */ - hangup: { - classNames: [ 'button', 'icon-hangup', 'button_hangup' ], - enabled: true, - isDisplayed: () => true, - id: 'toolbar_button_hangup', - onClick() { - sendAnalytics(createToolbarEvent('hangup')); - APP.UI.emitEvent(UIEvents.HANGUP); - }, - tooltipKey: 'toolbar.hangup' - }, - - /** - * The descriptor of the toolbar button which opens a dialog for the - * conference URL and inviting others. - */ - info: { - component: InfoDialogButton - }, - - /** - * The descriptor of the microphone toolbar button. - */ - microphone: { - classNames: [ 'button', 'icon-microphone' ], - enabled: true, - isDisplayed: () => true, - id: 'toolbar_button_mute', - onClick() { - const sharedVideoManager = APP.UI.getSharedVideoManager(); - - // TODO: Clicking the mute button and pressing the mute shortcut - // could be handled in a uniform manner. The code below checks - // the mute status and fires the appropriate event (MUTED or - // UNMUTED), while the code which handles the keyboard shortcut - // calls toggleAudioMuted(). Also strangely the the user is - // only warned if they click the button (and not if they use - // the shortcut). - if (APP.conference.isLocalAudioMuted()) { - // If there's a shared video with the volume "on" and we - // aren't the video owner, we warn the user - // that currently it's not possible to unmute. - if (sharedVideoManager - && sharedVideoManager.isSharedVideoVolumeOn() - && !sharedVideoManager.isSharedVideoOwner()) { - APP.UI.showCustomToolbarPopup( - 'microphone', 'unableToUnmutePopup', true, 5000); - } else { - sendAnalytics(createToolbarEvent( - AUDIO_MUTE, - { enable: false })); - APP.UI.emitEvent(UIEvents.AUDIO_MUTED, false, true); - } - } else { - sendAnalytics(createToolbarEvent( - AUDIO_MUTE, - { enable: true })); - APP.UI.emitEvent(UIEvents.AUDIO_MUTED, true, true); - } - }, - popups: [ - { - dataAttr: 'toolbar.micMutedPopup', - id: 'micMutedPopup' - }, - { - dataAttr: 'toolbar.unableToUnmutePopup', - id: 'unableToUnmutePopup' - }, - { - dataAttr: 'toolbar.talkWhileMutedPopup', - id: 'talkWhileMutedPopup' - } - ], - shortcut: 'M', - shortcutAttr: 'mutePopover', - shortcutFunc() { - // The 'enable' attribute in the event is set to true if the - // shortcut triggered a mute action, and set to false if it - // triggered an unmute action. - sendAnalytics(createShortcutEvent( - AUDIO_MUTE, - TRIGGERED, - { enable: !APP.conference.isLocalAudioMuted() })); - APP.conference.toggleAudioMuted(); - }, - shortcutDescription: 'keyboardShortcuts.mute', - tooltipKey: 'toolbar.mute' - }, - - /** - * The descriptor of the profile toolbar button. - */ - profile: { - component: ProfileButton, - sideContainerId: 'profile_container' - }, - - /** - * The descriptor of the "Raise hand" toolbar button. - */ - raisehand: { - classNames: [ 'button', 'icon-raised-hand' ], - enabled: true, - id: 'toolbar_button_raisehand', - onClick() { - // TODO: reduce duplication with shortcutFunc below. - const localParticipant - = getLocalParticipant(APP.store.getState()); - const currentRaisedHand = localParticipant.raisedHand; - - // The 'enable' attribute is set to true if the pressing of the - // shortcut resulted in the hand being raised, and to false - // if it resulted in the hand being 'lowered'. - sendAnalytics(createToolbarEvent( - 'raise.hand', - { enable: !currentRaisedHand })); - - APP.store.dispatch(participantUpdated({ - id: localParticipant.id, - local: true, - raisedHand: !currentRaisedHand - })); - }, - shortcut: 'R', - shortcutAttr: 'raiseHandPopover', - shortcutDescription: 'keyboardShortcuts.raiseHand', - shortcutFunc() { - const localParticipant - = getLocalParticipant(APP.store.getState()); - const currentRaisedHand = localParticipant.raisedHand; - - // The 'enable' attribute is set to true if the pressing of the - // shortcut resulted in the hand being raised, and to false - // if it resulted in the hand being 'lowered'. - sendAnalytics(createShortcutEvent( - 'toggle.raise.hand', - TRIGGERED, - { enable: !currentRaisedHand })); - - APP.store.dispatch(participantUpdated({ - id: localParticipant.id, - local: true, - raisedHand: !currentRaisedHand - })); - }, - tooltipKey: 'toolbar.raiseHand' - }, - - /** - * The descriptor of the recording toolbar button. Requires additional - * initialization in the recording module. - */ - recording: { - classNames: [ 'button' ], - enabled: true, - - // will be displayed once the recording functionality is detected - hidden: true, - id: 'toolbar_button_record', - tooltipKey: 'liveStreaming.buttonTooltip' - }, - - /** - * The descriptor of the settings toolbar button. - */ - settings: { - classNames: [ 'button', 'icon-settings' ], - enabled: true, - id: 'toolbar_button_settings', - onClick() { - // TODO: Include an 'enable' attribute which specifies whether - // the settings panel was shown or hidden. - sendAnalytics(createToolbarEvent('settings')); - APP.UI.emitEvent(UIEvents.TOGGLE_SETTINGS); - }, - sideContainerId: 'settings_container', - tooltipKey: 'toolbar.Settings' - }, - - /** - * The descriptor of the "Share YouTube video" toolbar button. - */ - sharedvideo: { - classNames: [ 'button', 'icon-shared-video' ], - enabled: true, - id: 'toolbar_button_sharedvideo', - onClick() { - // The 'enable' attribute is set to true if the click resulted - // in the "start sharing video" dialog being shown, and false - // if it resulted in the "stop sharing video" dialog being - // shown. - sendAnalytics(createToolbarEvent( - 'shared.video.toggled', - { - enable: !APP.UI.isSharedVideoShown() - })); - APP.UI.emitEvent(UIEvents.SHARED_VIDEO_CLICKED); - }, - popups: [ - { - dataAttr: 'toolbar.sharedVideoMutedPopup', - id: 'sharedVideoMutedPopup' - } - ], - tooltipKey: 'toolbar.sharedvideo' - }, - - videoquality: { - component: VideoQualityButton - } - }; - - Object.keys(defaultButtons).forEach(name => { - const button = defaultButtons[name]; - - if (!button.isDisplayed) { - button.isDisplayed = _isDisplayed; - } - }); - - return defaultButtons; -} - -/** - * The default implementation of the {@code isDisplayed} method of the toolbar - * button definition returned by {@link getDefaultButtons()}. - * - * @returns {boolean} If the user intarface is full i.e. not filmstrip-only, - * then {@code true}; otherwise, {@code false}. - */ -function _isDisplayed() { - return !interfaceConfig.filmStripOnly; -} diff --git a/react/features/toolbox/functions.web.js b/react/features/toolbox/functions.web.js index a1b1da0cbf..5c570852c0 100644 --- a/react/features/toolbox/functions.web.js +++ b/react/features/toolbox/functions.web.js @@ -1,10 +1,5 @@ // @flow -import SideContainerToggler - from '../../../modules/UI/side_pannels/SideContainerToggler'; - -import getDefaultButtons from './defaultToolbarButtons'; - declare var interfaceConfig: Object; export { @@ -13,92 +8,6 @@ export { getButton } from './functions.native'; -/** - * Returns an object which contains the default buttons for the primary and - * secondary toolbars. - * - * @param {Object} buttonHandlers - Contains additional toolbox button - * handlers. - * @returns {Object} - */ -export function getDefaultToolboxButtons(buttonHandlers: Object): Object { - let toolbarButtons = { - primaryToolbarButtons: new Map(), - secondaryToolbarButtons: new Map() - }; - - if (typeof interfaceConfig !== 'undefined' - && interfaceConfig.TOOLBAR_BUTTONS) { - - toolbarButtons - = interfaceConfig.TOOLBAR_BUTTONS.reduce( - (acc, buttonName) => { - const buttons = getDefaultButtons(); - let button = buttons ? buttons[buttonName] : null; - const currentButtonHandlers = buttonHandlers[buttonName]; - - if (button) { - const place = _getToolbarButtonPlace(buttonName); - - button.buttonName = buttonName; - - if (currentButtonHandlers) { - button = { - ...button, - ...currentButtonHandlers - }; - } - - // If isDisplayed method is not defined, display the - // button only for non-filmstripOnly mode - if (button.isDisplayed()) { - acc[place].set(buttonName, button); - } - } - - return acc; - }, - toolbarButtons); - } - - return toolbarButtons; -} - -/** - * Returns toolbar class names to add while rendering. - * - * @param {Object} props - Props object pass to React component. - * @returns {Object} - * @private - */ -export function getToolbarClassNames(props: Object) { - const primaryToolbarClassNames = [ - interfaceConfig.filmStripOnly - ? 'toolbar_filmstrip-only' - : 'toolbar_primary' - ]; - const secondaryToolbarClassNames = [ 'toolbar_secondary' ]; - - if (props._visible) { - const slideInAnimation - = SideContainerToggler.isVisible ? 'slideInExtX' : 'slideInX'; - - primaryToolbarClassNames.push('fadeIn'); - secondaryToolbarClassNames.push(slideInAnimation); - } else { - const slideOutAnimation - = SideContainerToggler.isVisible ? 'slideOutExtX' : 'slideOutX'; - - primaryToolbarClassNames.push('fadeOut'); - secondaryToolbarClassNames.push(slideOutAnimation); - } - - return { - primaryToolbarClassName: primaryToolbarClassNames.join(' '), - secondaryToolbarClassName: secondaryToolbarClassNames.join(' ') - }; -} - /** * Helper for getting the height of the toolbox. * @@ -122,18 +31,3 @@ export function isButtonEnabled(name: string) { return interfaceConfig.TOOLBAR_BUTTONS.indexOf(name) !== -1 || interfaceConfig.MAIN_TOOLBAR_BUTTONS.indexOf(name) !== -1; } - -/** - * Get place for toolbar button. Now it can be in the primary Toolbar or in the - * secondary Toolbar. - * - * @param {string} btn - Button name. - * @private - * @returns {string} - */ -function _getToolbarButtonPlace(btn) { - return ( - interfaceConfig.MAIN_TOOLBAR_BUTTONS.includes(btn) - ? 'primaryToolbarButtons' - : 'secondaryToolbarButtons'); -} diff --git a/react/features/toolbox/middleware.js b/react/features/toolbox/middleware.js index 0b9cf8fdf7..160f74ebe3 100644 --- a/react/features/toolbox/middleware.js +++ b/react/features/toolbox/middleware.js @@ -1,17 +1,9 @@ // @flow -import { - MEDIA_TYPE, - SET_AUDIO_AVAILABLE, - SET_VIDEO_AVAILABLE -} from '../base/media'; import { MiddlewareRegistry } from '../base/redux'; -import { isLocalTrackMuted, TRACK_UPDATED } from '../base/tracks'; -import { setToolbarButton, toggleFullScreen } from './actions'; import { CLEAR_TOOLBOX_TIMEOUT, - FULL_SCREEN_CHANGED, SET_TOOLBOX_TIMEOUT, SET_FULL_SCREEN } from './actionTypes'; @@ -34,12 +26,6 @@ MiddlewareRegistry.register(store => next => action => { break; } - case FULL_SCREEN_CHANGED: - return _fullScreenChanged(store, next, action); - - case SET_AUDIO_AVAILABLE: - return _setMediaAvailableOrMuted(store, next, action); - case SET_FULL_SCREEN: return _setFullScreen(next, action); @@ -48,102 +34,15 @@ MiddlewareRegistry.register(store => next => action => { const { handler, timeoutMS } = action; clearTimeout(timeoutID); - const newTimeoutId = setTimeout(handler, timeoutMS); + action.timeoutID = setTimeout(handler, timeoutMS); - action.timeoutID = newTimeoutId; break; } - - case SET_VIDEO_AVAILABLE: - return _setMediaAvailableOrMuted(store, next, action); - - case TRACK_UPDATED: - if (action.track.jitsiTrack.isLocal()) { - return _setMediaAvailableOrMuted(store, next, action); - } - break; } return next(action); }); -/** - * Updates the the redux state with the current known state of full screen. - * - * @param {Store} store - The redux store in which the specified action is being - * dispatched. - * @param {Dispatch} next - The redux dispatch function to dispatch the - * specified action to the specified store. - * @param {Action} action - The redux action FULL_SCREEN_CHANGED which is being - * dispatched in the specified store. - * @private - * @returns {Object} The value returned by {@code next(action)}. - */ -function _fullScreenChanged({ dispatch }, next, action) { - if (typeof APP === 'object') { - dispatch(toggleFullScreen(action.fullScreen)); - } - - return next(action); -} - -/** - * Adjusts the state of toolbar's microphone or camera button. - * - * @param {Store} store - The redux store. - * @param {Function} next - The redux function to continue dispatching the - * specified {@code action} in the specified {@code store}. - * @param {Object} action - {@code SET_AUDIO_AVAILABLE}, - * {@code SET_VIDEO_AVAILABLE}, or {@code TRACK_UPDATED}. - * @returns {*} - */ -function _setMediaAvailableOrMuted({ dispatch, getState }, next, action) { - const result = next(action); - - let mediaType; - - switch (action.type) { - case SET_AUDIO_AVAILABLE: - mediaType = MEDIA_TYPE.AUDIO; - break; - - case SET_VIDEO_AVAILABLE: - mediaType = MEDIA_TYPE.VIDEO; - break; - - case TRACK_UPDATED: - mediaType - = action.track.jitsiTrack.isAudioTrack() - ? MEDIA_TYPE.AUDIO - : MEDIA_TYPE.VIDEO; - break; - - default: - throw new Error(`Unsupported action ${action}`); - } - - const state = getState(); - const { audio, video } = state['features/base/media']; - const { available } = mediaType === MEDIA_TYPE.AUDIO ? audio : video; - const i18nKey - = mediaType === MEDIA_TYPE.AUDIO - ? available ? 'mute' : 'micDisabled' - : available ? 'videomute' : 'cameraDisabled'; - const tracks = state['features/base/tracks']; - const muted = isLocalTrackMuted(tracks, mediaType); - - dispatch( - setToolbarButton( - mediaType === MEDIA_TYPE.AUDIO ? 'microphone' : 'camera', - { - enabled: available, - i18n: `[content]toolbar.${i18nKey}`, - toggled: available ? muted : true - })); - - return result; -} - /** * Makes an external request to enter or exit full screen mode. * diff --git a/react/features/toolbox/reducer.js b/react/features/toolbox/reducer.js index 2327faa7f7..2e60fa8598 100644 --- a/react/features/toolbox/reducer.js +++ b/react/features/toolbox/reducer.js @@ -8,7 +8,6 @@ import { SET_DEFAULT_TOOLBOX_BUTTONS, SET_SUBJECT, SET_SUBJECT_SLIDE_IN, - SET_TOOLBAR_BUTTON, SET_TOOLBAR_HOVERED, SET_TOOLBOX_ALWAYS_VISIBLE, SET_TOOLBOX_ENABLED, @@ -16,7 +15,6 @@ import { SET_TOOLBOX_TIMEOUT_MS, SET_TOOLBOX_VISIBLE } from './actionTypes'; -import getDefaultButtons from './defaultToolbarButtons'; declare var interfaceConfig: Object; @@ -161,9 +159,6 @@ ReducerRegistry.register( subjectSlideIn: action.subjectSlideIn }; - case SET_TOOLBAR_BUTTON: - return _setToolbarButton(state, action); - case SET_TOOLBAR_HOVERED: return { ...state, @@ -204,46 +199,3 @@ ReducerRegistry.register( return state; }); - -/** - * Reduces the redux action {@code SET_TOOLBAR_BUTTON} in the feature toolbox. - * - * @param {Object} state - The redux state. - * @param {Object} action - The redux action of type {@code SET_TOOLBAR_BUTTON}. - * @param {Object} action.button - Object describing toolbar button. - * @param {Object} action.buttonName - The name of the button. - * @private - * @returns {Object} - */ -function _setToolbarButton(state, { button, buttonName }): Object { - // XXX getDefaultButtons, defaultToolbarButtons, SET_TOOLBAR_BUTTON are - // abstractions fully implemented on Web only. - const buttons = getDefaultButtons && getDefaultButtons(); - const buttonDefinition = buttons && buttons[buttonName]; - - // We don't need to update if the button shouldn't be displayed - if (!buttonDefinition || !buttonDefinition.isDisplayed()) { - return state; - } - - const { primaryToolbarButtons, secondaryToolbarButtons } = state; - let selectedButton = primaryToolbarButtons.get(buttonName); - let place = 'primaryToolbarButtons'; - - if (!selectedButton) { - selectedButton = secondaryToolbarButtons.get(buttonName); - place = 'secondaryToolbarButtons'; - } - - selectedButton = { - ...selectedButton, - ...button - }; - - const updatedToolbar = state[place].set(buttonName, selectedButton); - - return { - ...state, - [place]: new Map(updatedToolbar) - }; -}