feat(toolbox): axe the old toolbox (#2731)

This PR takes The Bulldozer Approach (R): removes the old toolbox and lots of
associated code, though not all of it. Subsequent cleanups will follow.
This commit is contained in:
Saúl Ibarra Corretgé 2018-04-09 07:03:26 +02:00 committed by virtuacoplenny
parent 0cd32c8155
commit b73b51f1f4
24 changed files with 1151 additions and 3193 deletions

View File

@ -106,10 +106,7 @@ import {
suspendDetected suspendDetected
} from './react/features/overlay'; } from './react/features/overlay';
import { setSharedVideoStatus } from './react/features/shared-video'; import { setSharedVideoStatus } from './react/features/shared-video';
import { import { isButtonEnabled } from './react/features/toolbox';
isButtonEnabled,
showDesktopSharingButton
} from './react/features/toolbox';
const logger = require('jitsi-meet-logger').getLogger(__filename); const logger = require('jitsi-meet-logger').getLogger(__filename);
@ -768,7 +765,6 @@ export default {
APP.store.dispatch( APP.store.dispatch(
setDesktopSharingEnabled(this.isDesktopSharingEnabled)); setDesktopSharingEnabled(this.isDesktopSharingEnabled));
APP.store.dispatch(showDesktopSharingButton());
this._createRoom(tracks); this._createRoom(tracks);
APP.remoteControl.init(); APP.remoteControl.init();
@ -1359,7 +1355,6 @@ export default {
this.isSharingScreen = newStream && newStream.videoType === 'desktop'; this.isSharingScreen = newStream && newStream.videoType === 'desktop';
if (wasSharingScreen !== this.isSharingScreen) { if (wasSharingScreen !== this.isSharingScreen) {
APP.UI.updateDesktopSharingButtons();
APP.API.notifyScreenSharingStatusChanged(this.isSharingScreen); APP.API.notifyScreenSharingStatusChanged(this.isSharingScreen);
} }
}, },

View File

@ -153,19 +153,7 @@ var interfaceConfig = {
* *
* @type {boolean} * @type {boolean}
*/ */
VIDEO_QUALITY_LABEL_DISABLED: false, 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
/** /**
* Specify custom URL for downloading android mobile app. * Specify custom URL for downloading android mobile app.

View File

@ -36,14 +36,7 @@ import {
showWarningNotification showWarningNotification
} from '../../react/features/notifications'; } from '../../react/features/notifications';
import { import {
checkAutoEnableDesktopSharing,
clearButtonPopup,
dockToolbox, dockToolbox,
setButtonPopupTimeout,
setToolbarButton,
showDialPadButton,
showEtherpadButton,
showSharedVideoButton,
showToolbox showToolbox
} from '../../react/features/toolbox'; } from '../../react/features/toolbox';
@ -278,7 +271,7 @@ UI.setLocalRaisedHandStatus
* Initialize conference UI. * Initialize conference UI.
*/ */
UI.initConference = function() { UI.initConference = function() {
const { dispatch, getState } = APP.store; const { getState } = APP.store;
const { email, id, name } = getLocalParticipant(getState); const { email, id, name } = getLocalParticipant(getState);
// Update default button states before showing the toolbar // Update default button states before showing the toolbar
@ -298,8 +291,6 @@ UI.initConference = function() {
UI.setUserEmail(id, email); UI.setUserEmail(id, email);
} }
dispatch(checkAutoEnableDesktopSharing());
// FollowMe attempts to copy certain aspects of the moderator's UI into the // FollowMe attempts to copy certain aspects of the moderator's UI into the
// other participants' UI. Consequently, it needs (1) read and write access // other participants' UI. Consequently, it needs (1) read and write access
// to the UI (depending on the moderator role of the local participant) and // to the UI (depending on the moderator role of the local participant) and
@ -372,11 +363,10 @@ UI.start = function() {
$('body').addClass('vertical-filmstrip'); $('body').addClass('vertical-filmstrip');
} }
// TODO: remove this class once the old toolbar has been removed. This class // 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 // 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. // 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'); $('body').addClass('use-new-toolbox');
} }
@ -476,7 +466,6 @@ UI.initEtherpad = name => {
= new EtherpadManager(config.etherpad_base, name, eventEmitter); = new EtherpadManager(config.etherpad_base, name, eventEmitter);
APP.store.dispatch(setEtherpadHasInitialzied()); APP.store.dispatch(setEtherpadHasInitialzied());
APP.store.dispatch(showEtherpadButton());
}; };
/** /**
@ -544,8 +533,6 @@ UI.onPeerVideoTypeChanged
UI.updateLocalRole = isModerator => { UI.updateLocalRole = isModerator => {
VideoLayout.showModeratorIndicator(); VideoLayout.showModeratorIndicator();
APP.store.dispatch(showSharedVideoButton());
Recording.showRecordingButton(isModerator); Recording.showRecordingButton(isModerator);
if (isModerator) { if (isModerator) {
@ -672,14 +659,9 @@ UI.inputDisplayNameHandler = function(newDisplayName) {
* @param {number} timeout - The time to show the popup * @param {number} timeout - The time to show the popup
* @returns {void} * @returns {void}
*/ */
// eslint-disable-next-line max-params // eslint-disable-next-line max-params, no-unused-vars
UI.showCustomToolbarPopup = function(buttonName, popupID, show, timeout) { UI.showCustomToolbarPopup = function(buttonName, popupID, show, timeout) {
const action // TODO: this is no longer implemented as of Toolbox v2. Remove?
= show
? setButtonPopupTimeout(buttonName, popupID, timeout)
: clearButtonPopup(buttonName);
APP.store.dispatch(action);
}; };
/** /**
@ -904,17 +886,6 @@ UI.promptDisplayName = () => {
*/ */
UI.setAudioLevel = (id, lvl) => VideoLayout.setAudioLevel(id, lvl); 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. * Hide connection quality statistics from UI.
*/ */
@ -946,8 +917,9 @@ UI.addMessage = function(from, displayName, message, stamp) {
Chat.updateChatConversation(from, displayName, message, stamp); Chat.updateChatConversation(from, displayName, message, stamp);
}; };
UI.updateDTMFSupport // TODO: With Toolbox v2 this got scrapped. Remove?
= isDTMFSupported => APP.store.dispatch(showDialPadButton(isDTMFSupported)); // eslint-disable-next-line no-empty-function
UI.updateDTMFSupport = () => { };
UI.updateRecordingState = function(state) { UI.updateRecordingState = function(state) {
Recording.updateRecordingState(state); Recording.updateRecordingState(state);

View File

@ -129,8 +129,7 @@ class Etherpad extends LargeContainer {
let height, width; let height, width;
if (interfaceConfig.VERTICAL_FILMSTRIP) { if (interfaceConfig.VERTICAL_FILMSTRIP) {
height = interfaceConfig._USE_NEW_TOOLBOX height = containerHeight - getToolboxHeight();
? containerHeight - getToolboxHeight() : containerHeight;
width = containerWidth - Filmstrip.getFilmstripWidth(); width = containerWidth - Filmstrip.getFilmstripWidth();
} else { } else {
height = containerHeight - Filmstrip.getFilmstripHeight(); height = containerHeight - Filmstrip.getFilmstripHeight();

View File

@ -699,8 +699,7 @@ class SharedVideoContainer extends LargeContainer {
let height, width; let height, width;
if (interfaceConfig.VERTICAL_FILMSTRIP) { if (interfaceConfig.VERTICAL_FILMSTRIP) {
height = interfaceConfig._USE_NEW_TOOLBOX height = containerHeight - getToolboxHeight();
? containerHeight - getToolboxHeight() : containerHeight;
width = containerWidth - Filmstrip.getFilmstripWidth(); width = containerWidth - Filmstrip.getFilmstripWidth();
} else { } else {
height = containerHeight - Filmstrip.getFilmstripHeight(); height = containerHeight - Filmstrip.getFilmstripHeight();

View File

@ -1,4 +1,4 @@
/* global APP, $, interfaceConfig */ /* global APP, $ */
import { processReplacements, linkify } from './Replacement'; import { processReplacements, linkify } from './Replacement';
import CommandsProcessor from './Commands'; import CommandsProcessor from './Commands';
@ -181,15 +181,11 @@ function resizeChatConversation() {
$('#smileysContainer').css('bottom', msgareaHeight); $('#smileysContainer').css('bottom', msgareaHeight);
chat.width(width - 10); chat.width(width - 10);
if (interfaceConfig._USE_NEW_TOOLBOX) { const maybeAMagicNumberForPaddingAndMargin = 100;
const maybeAMagicNumberForPaddingAndMargin = 100; const offset = maybeAMagicNumberForPaddingAndMargin
const offset = maybeAMagicNumberForPaddingAndMargin + msgareaHeight + getToolboxHeight();
+ msgareaHeight + getToolboxHeight();
chat.height(window.innerHeight - offset); chat.height(window.innerHeight - offset);
} else {
chat.height(window.innerHeight - 15 - msgareaHeight);
}
} }
/** /**

View File

@ -14,7 +14,6 @@ import { NotificationsContainer } from '../../notifications';
import { SidePanel } from '../../side-panel'; import { SidePanel } from '../../side-panel';
import { import {
Toolbox, Toolbox,
ToolboxV2,
fullScreenChanged, fullScreenChanged,
setToolboxAlwaysVisible, setToolboxAlwaysVisible,
showToolbox showToolbox
@ -139,7 +138,6 @@ class Conference extends Component<Props> {
*/ */
render() { render() {
const { const {
_USE_NEW_TOOLBOX,
VIDEO_QUALITY_LABEL_DISABLED, VIDEO_QUALITY_LABEL_DISABLED,
filmStripOnly filmStripOnly
} = interfaceConfig; } = interfaceConfig;
@ -148,16 +146,6 @@ class Conference extends Component<Props> {
|| VIDEO_QUALITY_LABEL_DISABLED || VIDEO_QUALITY_LABEL_DISABLED
|| this.props._iAmRecorder; || this.props._iAmRecorder;
let ToolboxToUse;
if (filmStripOnly) {
ToolboxToUse = null;
} else if (interfaceConfig._USE_NEW_TOOLBOX) {
ToolboxToUse = ToolboxV2;
} else {
ToolboxToUse = Toolbox;
}
return ( return (
<div <div
id = 'videoconference_page' id = 'videoconference_page'
@ -168,10 +156,8 @@ class Conference extends Component<Props> {
<Filmstrip filmstripOnly = { filmStripOnly } /> <Filmstrip filmstripOnly = { filmStripOnly } />
</div> </div>
{ ToolboxToUse && <ToolboxToUse /> } { !filmStripOnly && <Toolbox /> }
{ !filmStripOnly && <SidePanel /> }
{ _USE_NEW_TOOLBOX && !filmStripOnly
&& <SidePanel /> }
<DialogContainer /> <DialogContainer />
<NotificationsContainer /> <NotificationsContainer />

View File

@ -5,9 +5,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { getLocalParticipant, PARTICIPANT_ROLE } from '../../base/participants'; import { ToolboxFilmstrip, dockToolbox } from '../../toolbox';
import { InviteButton } from '../../invite';
import { Toolbox, ToolboxFilmstrip, dockToolbox } from '../../toolbox';
import { setFilmstripHovered } from '../actions'; import { setFilmstripHovered } from '../actions';
import { shouldRemoteVideosBeVisible } from '../functions'; import { shouldRemoteVideosBeVisible } from '../functions';
@ -35,29 +33,11 @@ class Filmstrip extends Component<*> {
* @static * @static
*/ */
static propTypes = { 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. * Whether or not remote videos are currently being hovered over.
*/ */
_hovered: PropTypes.bool, _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 * Whether or not the remote videos should be visible. Will toggle
* a class for hiding the videos. * a class for hiding the videos.
@ -115,9 +95,6 @@ class Filmstrip extends Component<*> {
*/ */
render() { render() {
const { const {
_hideInviteButton,
_isAddToCallAvailable,
_isDialOutAvailable,
_remoteVideosVisible, _remoteVideosVisible,
_toolboxVisible, _toolboxVisible,
filmstripOnly filmstripOnly
@ -136,12 +113,9 @@ class Filmstrip extends Component<*> {
const filmstripClassNames = `filmstrip ${_remoteVideosVisible const filmstripClassNames = `filmstrip ${_remoteVideosVisible
? '' : 'hide-videos'} ${reduceHeight ? 'reduce-height' : ''}`; ? '' : 'hide-videos'} ${reduceHeight ? 'reduce-height' : ''}`;
const ToolboxToUse = interfaceConfig._USE_NEW_TOOLBOX
? ToolboxFilmstrip : Toolbox;
return ( return (
<div className = { filmstripClassNames }> <div className = { filmstripClassNames }>
{ filmstripOnly ? <ToolboxToUse /> : null } { filmstripOnly && <ToolboxFilmstrip /> }
<div <div
className = 'filmstrip__videos' className = 'filmstrip__videos'
id = 'remoteVideos'> id = 'remoteVideos'>
@ -150,11 +124,6 @@ class Filmstrip extends Component<*> {
id = 'filmstripLocalVideo' id = 'filmstripLocalVideo'
onMouseOut = { this._onMouseOut } onMouseOut = { this._onMouseOut }
onMouseOver = { this._onMouseOver }> onMouseOver = { this._onMouseOver }>
{ filmstripOnly || _hideInviteButton
? null
: <InviteButton
enableAddPeople = { _isAddToCallAvailable }
enableDialOut = { _isDialOutAvailable } /> }
<div id = 'filmstripLocalVideoThumbnail' /> <div id = 'filmstripLocalVideoThumbnail' />
</div> </div>
<div <div
@ -185,9 +154,7 @@ class Filmstrip extends Component<*> {
*/ */
_notifyOfHoveredStateUpdate() { _notifyOfHoveredStateUpdate() {
if (this.props._hovered !== this._isHovered) { 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)); this.props.dispatch(setFilmstripHovered(this._isHovered));
} }
} }
@ -223,36 +190,16 @@ class Filmstrip extends Component<*> {
* @param {Object} state - The Redux state. * @param {Object} state - The Redux state.
* @private * @private
* @returns {{ * @returns {{
* _hideInviteButton: boolean,
* _hovered: boolean, * _hovered: boolean,
* _isAddToCallAvailable: boolean,
* _isDialOutAvailable: boolean,
* _remoteVideosVisible: boolean, * _remoteVideosVisible: boolean,
* _toolboxVisible: boolean * _toolboxVisible: boolean
* }} * }}
*/ */
function _mapStateToProps(state) { 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 { hovered } = state['features/filmstrip'];
const isAddToCallAvailable = !isGuest;
const isDialOutAvailable
= getLocalParticipant(state).role === PARTICIPANT_ROLE.MODERATOR
&& conference && conference.isSIPCallingSupported()
&& (!enableUserRolesBasedOnToken || !isGuest);
return { return {
_hideInviteButton: iAmRecorder
|| (!isAddToCallAvailable && !isDialOutAvailable)
|| interfaceConfig._USE_NEW_TOOLBOX,
_hovered: hovered, _hovered: hovered,
_isAddToCallAvailable: isAddToCallAvailable,
_isDialOutAvailable: isDialOutAvailable,
_remoteVideosVisible: shouldRemoteVideosBeVisible(state), _remoteVideosVisible: shouldRemoteVideosBeVisible(state),
_toolboxVisible: state['features/toolbox'].visible _toolboxVisible: state['features/toolbox'].visible
}; };

View File

@ -1,5 +1,3 @@
/* global interfaceConfig */
import InlineDialog from '@atlaskit/inline-dialog'; import InlineDialog from '@atlaskit/inline-dialog';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
@ -8,30 +6,12 @@ import { connect } from 'react-redux';
import { createToolbarEvent, sendAnalytics } from '../../analytics'; import { createToolbarEvent, sendAnalytics } from '../../analytics';
import { translate } from '../../base/i18n'; import { translate } from '../../base/i18n';
import { getParticipantCount } from '../../base/participants'; import { getParticipantCount } from '../../base/participants';
import { import { ToolbarButtonV2 } from '../../toolbox';
ToolbarButton,
ToolbarButtonV2,
TOOLTIP_TO_POPUP_POSITION
} from '../../toolbox';
import { updateDialInNumbers } from '../actions'; import { updateDialInNumbers } from '../actions';
import { InfoDialog } from './info-dialog'; 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 amount of time, in milliseconds, to wait until automatically showing
* the {@code InfoDialog}. This is essentially a hack as automatic showing * the {@code InfoDialog}. This is essentially a hack as automatic showing
@ -170,9 +150,26 @@ class InfoDialogButton extends Component {
* @returns {ReactElement} * @returns {ReactElement}
*/ */
render() { render() {
return interfaceConfig._USE_NEW_TOOLBOX const { t } = this.props;
? this._renderNewToolbarButton() const { showDialog } = this.state;
: this._renderOldToolbarButton(); const iconClass = `icon-info ${showDialog ? 'toggled' : ''}`;
return (
<div className = 'toolbox-button-wth-dialog'>
<InlineDialog
content = {
<InfoDialog onClose = { this._onDialogClose } /> }
isOpen = { showDialog }
onClose = { this._onDialogClose }
position = { 'top right' }>
<ToolbarButtonV2
accessibilityLabel = 'Info'
iconName = { iconClass }
onClick = { this._onDialogToggle }
tooltip = { t('info.tooltip') } />
</InlineDialog>
</div>
);
} }
/** /**
@ -209,69 +206,6 @@ class InfoDialogButton extends Component {
this.setState({ showDialog: !this.state.showDialog }); 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 (
<InlineDialog
content = { <InfoDialog onClose = { this._onDialogClose } /> }
isOpen = { showDialog }
onClose = { this._onDialogClose }
position = { TOOLTIP_TO_POPUP_POSITION[tooltipPosition] }>
<ToolbarButton
button = { buttonConfiguration }
onClick = { this._onDialogToggle }
tooltipPosition = { tooltipPosition } />
</InlineDialog>
);
}
/**
* 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 (
<div className = 'toolbox-button-wth-dialog'>
<InlineDialog
content = {
<InfoDialog onClose = { this._onDialogClose } /> }
isOpen = { showDialog }
onClose = { this._onDialogClose }
position = { 'top right' }>
<ToolbarButtonV2
accessibilityLabel = 'Info'
iconName = { iconClass }
onClick = { this._onDialogToggle }
tooltip = { t('info.tooltip') } />
</InlineDialog>
</div>
);
}
} }
/** /**

View File

@ -61,17 +61,6 @@ export const SET_SUBJECT = Symbol('SET_SUBJECT');
*/ */
export const SET_SUBJECT_SLIDE_IN = Symbol('SET_SUBJECT_SLIDE_IN'); 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 * The type of the action which sets the indicator which determiens whether a
* fToolbar in the Toolbox is hovered. * fToolbar in the Toolbox is hovered.

View File

@ -1,12 +1,9 @@
/* @flow */ /* @flow */
import type { Dispatch } from 'redux-thunk';
import { import {
CLEAR_TOOLBOX_TIMEOUT, CLEAR_TOOLBOX_TIMEOUT,
SET_SUBJECT, SET_SUBJECT,
SET_SUBJECT_SLIDE_IN, SET_SUBJECT_SLIDE_IN,
SET_TOOLBAR_BUTTON,
SET_TOOLBAR_HOVERED, SET_TOOLBAR_HOVERED,
SET_TOOLBOX_ALWAYS_VISIBLE, SET_TOOLBOX_ALWAYS_VISIBLE,
SET_TOOLBOX_ENABLED, SET_TOOLBOX_ENABLED,
@ -15,28 +12,6 @@ import {
SET_TOOLBOX_VISIBLE SET_TOOLBOX_VISIBLE
} from './actionTypes'; } 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. * 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. * Signals that toolbar is hovered value should be changed.
* *
@ -203,51 +159,3 @@ export function setToolboxVisible(visible: boolean): Object {
visible 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
}));
};
}

View File

@ -1,73 +1,24 @@
/* @flow */ /* @flow */
import Recording from '../../../modules/UI/recording/Recording';
import SideContainerToggler import SideContainerToggler
from '../../../modules/UI/side_pannels/SideContainerToggler'; from '../../../modules/UI/side_pannels/SideContainerToggler';
import UIEvents from '../../../service/UI/UIEvents';
import { import {
changeLocalRaiseHand,
clearToolboxTimeout, clearToolboxTimeout,
setSubjectSlideIn, setSubjectSlideIn,
setToolbarButton,
setToolboxTimeout, setToolboxTimeout,
setToolboxTimeoutMS, setToolboxTimeoutMS,
setToolboxVisible, setToolboxVisible
toggleToolbarButton
} from './actions.native'; } from './actions.native';
import { import {
FULL_SCREEN_CHANGED, FULL_SCREEN_CHANGED,
SET_DEFAULT_TOOLBOX_BUTTONS,
SET_FULL_SCREEN SET_FULL_SCREEN
} from './actionTypes'; } from './actionTypes';
import {
getButton,
getDefaultToolboxButtons,
isButtonEnabled
} from './functions';
declare var $: Function;
declare var APP: Object;
declare var config: Object;
declare var interfaceConfig: Object; declare var interfaceConfig: Object;
export * from './actions.native'; 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. * 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. * 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. * 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. * 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);
}

View File

@ -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 (
<Toolbar
className = { primaryToolbarClassName }
toolbarButtons = { _primaryToolbarButtons }
tooltipPosition = { tooltipPosition } />
);
}
}
/**
* 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);

View File

@ -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 (
<Toolbar
className = { secondaryToolbarClassName }
toolbarButtons = { _secondaryToolbarButtons }
tooltipPosition = 'right'>
{ _callStatsID
? <FeedbackButton tooltipPosition = 'right' /> : null }
</Toolbar>
);
}
}
/**
* 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);

View File

@ -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 (
<StatelessToolbar { ...props }>
{
[ ...this.props.toolbarButtons.entries() ]
.map(this._renderToolbarButton)
}
{
this.props.children
}
</StatelessToolbar>
);
}
_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 (
<button.component
key = { key }
toggled = { button.toggled }
tooltipPosition = { tooltipPosition } />
);
}
const {
childComponent: ChildComponent,
onClick,
onMount,
onUnmount
} = button;
const onClickWithDispatch = (...args) =>
onClick && onClick(this.props.dispatch, ...args);
return (
<ToolbarButton
button = { button }
key = { key }
// TODO The following disables an eslint error alerting about a
// known potential/theoretical performance pernalty:
//
// A bind call or arrow function in a JSX prop will create a
// brand new function on every single render. This is bad for
// performance, as it will result in the garbage collector being
// invoked way more than is necessary. It may also cause
// unnecessary re-renders if a brand new function is passed as a
// prop to a component that uses reference equality check on the
// prop to determine if it should update.
//
// I'm not addressing the potential/theoretical performance
// penalty at the time of this writing because I don't know for
// a fact that it's a practical performance penalty in the case.
//
// eslint-disable-next-line react/jsx-no-bind
onClick = { onClickWithDispatch }
onMount = { onMount }
onUnmount = { onUnmount }
tooltipPosition = { tooltipPosition }>
{ button.html || null }
{ ChildComponent ? <ChildComponent /> : null }
</ToolbarButton>
);
}
}
export default connect()(Toolbar);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,5 @@ export { default as ToolbarButton } from './ToolbarButton';
export { default as ToolbarButtonV2 } from './ToolbarButtonV2'; export { default as ToolbarButtonV2 } from './ToolbarButtonV2';
export { default as ToolbarButtonWithDialog } export { default as ToolbarButtonWithDialog }
from './ToolbarButtonWithDialog'; from './ToolbarButtonWithDialog';
export { default as Toolbox } from './Toolbox';
export { default as ToolboxFilmstrip } from './ToolboxFilmstrip'; export { default as ToolboxFilmstrip } from './ToolboxFilmstrip';
export { default as ToolboxV2 } from './ToolboxV2'; export { default as Toolbox } from './Toolbox';

View File

@ -1 +0,0 @@
export default undefined;

View File

@ -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: <span className = 'badge-round'>
<span id = 'unreadMessages' /></span>,
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;
}

View File

@ -1,10 +1,5 @@
// @flow // @flow
import SideContainerToggler
from '../../../modules/UI/side_pannels/SideContainerToggler';
import getDefaultButtons from './defaultToolbarButtons';
declare var interfaceConfig: Object; declare var interfaceConfig: Object;
export { export {
@ -13,92 +8,6 @@ export {
getButton getButton
} from './functions.native'; } 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. * Helper for getting the height of the toolbox.
* *
@ -122,18 +31,3 @@ export function isButtonEnabled(name: string) {
return interfaceConfig.TOOLBAR_BUTTONS.indexOf(name) !== -1 return interfaceConfig.TOOLBAR_BUTTONS.indexOf(name) !== -1
|| interfaceConfig.MAIN_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');
}

View File

@ -1,17 +1,9 @@
// @flow // @flow
import {
MEDIA_TYPE,
SET_AUDIO_AVAILABLE,
SET_VIDEO_AVAILABLE
} from '../base/media';
import { MiddlewareRegistry } from '../base/redux'; import { MiddlewareRegistry } from '../base/redux';
import { isLocalTrackMuted, TRACK_UPDATED } from '../base/tracks';
import { setToolbarButton, toggleFullScreen } from './actions';
import { import {
CLEAR_TOOLBOX_TIMEOUT, CLEAR_TOOLBOX_TIMEOUT,
FULL_SCREEN_CHANGED,
SET_TOOLBOX_TIMEOUT, SET_TOOLBOX_TIMEOUT,
SET_FULL_SCREEN SET_FULL_SCREEN
} from './actionTypes'; } from './actionTypes';
@ -34,12 +26,6 @@ MiddlewareRegistry.register(store => next => action => {
break; break;
} }
case FULL_SCREEN_CHANGED:
return _fullScreenChanged(store, next, action);
case SET_AUDIO_AVAILABLE:
return _setMediaAvailableOrMuted(store, next, action);
case SET_FULL_SCREEN: case SET_FULL_SCREEN:
return _setFullScreen(next, action); return _setFullScreen(next, action);
@ -48,102 +34,15 @@ MiddlewareRegistry.register(store => next => action => {
const { handler, timeoutMS } = action; const { handler, timeoutMS } = action;
clearTimeout(timeoutID); clearTimeout(timeoutID);
const newTimeoutId = setTimeout(handler, timeoutMS); action.timeoutID = setTimeout(handler, timeoutMS);
action.timeoutID = newTimeoutId;
break; 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); 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. * Makes an external request to enter or exit full screen mode.
* *

View File

@ -8,7 +8,6 @@ import {
SET_DEFAULT_TOOLBOX_BUTTONS, SET_DEFAULT_TOOLBOX_BUTTONS,
SET_SUBJECT, SET_SUBJECT,
SET_SUBJECT_SLIDE_IN, SET_SUBJECT_SLIDE_IN,
SET_TOOLBAR_BUTTON,
SET_TOOLBAR_HOVERED, SET_TOOLBAR_HOVERED,
SET_TOOLBOX_ALWAYS_VISIBLE, SET_TOOLBOX_ALWAYS_VISIBLE,
SET_TOOLBOX_ENABLED, SET_TOOLBOX_ENABLED,
@ -16,7 +15,6 @@ import {
SET_TOOLBOX_TIMEOUT_MS, SET_TOOLBOX_TIMEOUT_MS,
SET_TOOLBOX_VISIBLE SET_TOOLBOX_VISIBLE
} from './actionTypes'; } from './actionTypes';
import getDefaultButtons from './defaultToolbarButtons';
declare var interfaceConfig: Object; declare var interfaceConfig: Object;
@ -161,9 +159,6 @@ ReducerRegistry.register(
subjectSlideIn: action.subjectSlideIn subjectSlideIn: action.subjectSlideIn
}; };
case SET_TOOLBAR_BUTTON:
return _setToolbarButton(state, action);
case SET_TOOLBAR_HOVERED: case SET_TOOLBAR_HOVERED:
return { return {
...state, ...state,
@ -204,46 +199,3 @@ ReducerRegistry.register(
return state; 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)
};
}