WiP(invite-ui): Initial move of invite UI to invite button (#1950)

* WiP(invite-ui): Initial move of invite UI to invite button

* Adjusts styling to fit both horizontal and vertical filmstrip

* Removes comment and functions not needed

* [squash] Addressing various review comments

* [squash] Move invite options to a separate config

* [squash] Adjust invite button styles until we fix the whole UI theme

* [squash] Fix the remote videos scroll

* [squash]:Do not show popup menu when 1 option is available

* [squash]: Disable the invite button in filmstrip mode

* feat(connection-indicator): implement automatic hiding on good connection (#2009)

* ref(connection-stats): use PropTypes package

* feat(connection-stats): display a summary of the connection quality

* feat(connection-indicator): show empty bars for interrupted connection

* feat(connection-indicator): change background color based on status

* feat(connection-indicator): implement automatic hiding on good connection

* fix(connection-indicator): explicitly set font size

Currently non-react code will set an icon size on ConnectionIndicator.
This doesn't work on initial call join in vertical filmstrip after
some changes to support hiding the indicator. The chosen fix is
passing in the icon size to mirror what would happe with full
filmstrip reactification.

* ref(connection-stats): rename statuses

* feat(connection-indicator): make hiding behavior configurable

The original implementation made the auto hiding of the indicator
configured in interfaceConfig.

* fix(connection-indicator): readd class expected by torture tests

* fix(connection-indicator): change connection quality display styling

Bold the connection summary in the stats popover so it stands out.
Change the summaries so there are only three--strong, nonoptimal,
poor.

* fix(connection-indicator): gray background on lost connection

* feat(icons): add new gsm bars icon

* feat(connection-indicator): use new 3-bar icon

* ref(icons): remove icon-connection and icon-connection-lost

Both have been replaced by icon-gsm-bars so they are not
being referenced anymore. Mobile looks to have connect-lost
as a separate icon in font-icons/jitsi.json.

* fix(defaultToolbarButtons): Fixes unresolved InfoDialogButton component problem

* [squash]: Makes invite button fit the container

* [squash]:Addressing invite truncate, remote menu position and comment

* [squash]:Fix z-index in horizontal mode, z-index in lonely call

* [squash]: Fix filmstripOnly property, remove important from css
This commit is contained in:
yanas 2017-10-03 11:30:42 -05:00 committed by GitHub
parent dfebd692f3
commit 86fcfcc535
26 changed files with 766 additions and 533 deletions

View File

@ -895,13 +895,6 @@ export default {
let user = room.getParticipantById(id); let user = room.getParticipantById(id);
return user && user.isModerator(); return user && user.isModerator();
}, },
/**
* Check if SIP is supported.
* @returns {boolean}
*/
sipGatewayEnabled() {
return room.isSIPCallingSupported();
},
get membersCount() { get membersCount() {
return room.getParticipants().length + 1; return room.getParticipants().length + 1;
}, },

View File

@ -48,21 +48,66 @@
&__videos { &__videos {
@extend %align-right; @extend %align-right;
position:relative; position:relative;
height:196px;
padding: 0; padding: 0;
/* The filmstrip should not be covered by the left toolbar. */ /* The filmstrip should not be covered by the left toolbar. */
bottom: 0; bottom: 0;
width:auto; width:auto;
transition: bottom 2s; transition: bottom 2s;
overflow: visible !important; overflow: visible !important;
/*!!! Removes the gap between the local video container and the remote
videos. */
font-size: 0pt;
&#remoteVideos { &#remoteVideos {
border: $thumbnailsBorder solid transparent; border: $thumbnailsBorder solid transparent;
padding-left: $defaultToolbarSize + 5; padding-left: $defaultToolbarSize + 5;
} }
/**
* The local video identifier.
*/
&#filmstripLocalVideo {
bottom: 32px;
flex-direction: column;
/**
* The invite button style.
*/
.filmstrip__invite {
padding-bottom: 5px;
margin-left: 2px;
}
/**
* The invite button group style.
* TOFIX: use AtlasKit.ButtonGroup if it starts supporting different
* flex grow options for the buttons.
*/
.invite-button-group {
display: inline-flex;
justify-content: space-between;
width: 100%;
& button {
background: $toolbarBackground;
flex-grow: 1;
flex-shrink: 1;
overflow: hidden;
}
& > * {
color: $toolbarButtonColor;
flex-grow: 0;
flex-shrink: 0;
margin-left: 2px;
}
/**
* Making sure any svg-s in an invite button group will be
* colored the way we want.
*/
& path {
fill: $toolbarButtonColor;
}
}
}
&.hidden { &.hidden {
bottom: -196px; bottom: -196px;

View File

@ -24,10 +24,6 @@
*/ */
z-index: #{$tooltipsZ + 1}; z-index: #{$tooltipsZ + 1};
&.hide-videos {
z-index: #{$tooltipsZ - 1};
}
/** /**
* Hide videos by making them slight to the right. * Hide videos by making them slight to the right.
*/ */
@ -50,9 +46,21 @@
} }
} }
/**
* Re-styles the local Video and invite button to better fit the
* vertical filmstrip layout.
*/
#filmstripLocalVideo { #filmstripLocalVideo {
bottom: 5px;
flex-direction: column-reverse;
height: auto; height: auto;
justify-content: flex-end; justify-content: flex-start;
.filmstrip__invite {
padding-bottom: 0px;
padding-top: 5px;
z-index: $dropdownZ;
}
} }
/** /**
@ -69,10 +77,11 @@
flex: 1; flex: 1;
flex-direction: column; flex-direction: column;
height: auto; height: auto;
overflow-x: hidden !important; justify-content: flex-end;
.remote-videos-container { #filmstripRemoteVideosContainer {
flex-direction: column; flex-direction: column-reverse;
overflow-x: hidden;
} }
} }
@ -101,7 +110,7 @@
} }
#remoteVideos { #remoteVideos {
flex-direction: column-reverse; flex-direction: column;
flex-grow: 1; flex-grow: 1;
} }
@ -153,15 +162,11 @@
* be hidden. * be hidden.
* The class opening is for when the filmstrip is transitioning from hidden * The class opening is for when the filmstrip is transitioning from hidden
* to visible. * to visible.
* The class with-remote-videos is for when the filmstrip has remote videos
* displayed, as opposed to 1-on-1 mode where they might be hidden.
* The class without-remote-videos is for when the filmstrip is visible
* but it has no videos to display.
*/ */
.video-state-indicator.moveToCorner { .video-state-indicator.moveToCorner {
transition: right 0.5s; transition: right 0.5s;
&.with-filmstrip.with-remote-videos { &.with-filmstrip {
&#recordingLabel { &#recordingLabel {
right: 200px; right: 200px;
} }
@ -171,11 +176,7 @@
} }
} }
&.with-filmstrip.without-remote-videos { &.with-filmstrip.opening {
transition-delay: 0.5s;
}
&.with-filmstrip.with-remote-videos.opening {
transition: 0.9s; transition: 0.9s;
transition-timing-function: ease-in-out; transition-timing-function: ease-in-out;
} }

View File

@ -35,13 +35,14 @@ var interfaceConfig = { // eslint-disable-line no-unused-vars
//main toolbar //main toolbar
'microphone', 'camera', 'desktop', 'invite', 'fullscreen', 'fodeviceselection', 'hangup', // jshint ignore:line 'microphone', 'camera', 'desktop', 'invite', 'fullscreen', 'fodeviceselection', 'hangup', // jshint ignore:line
//extended toolbar //extended toolbar
'profile', 'addtocall', 'contacts', 'info', 'chat', 'recording', 'etherpad', 'sharedvideo', 'dialout', 'settings', 'raisehand', 'videoquality', 'filmstrip'], // jshint ignore:line 'profile', 'contacts', 'info', 'chat', 'recording', 'etherpad', 'sharedvideo', 'settings', 'raisehand', 'videoquality', 'filmstrip'], // jshint ignore:line
/** /**
* Main Toolbar Buttons * Main Toolbar Buttons
* All of them should be in TOOLBAR_BUTTONS * All of them should be in TOOLBAR_BUTTONS
*/ */
MAIN_TOOLBAR_BUTTONS: ['microphone', 'camera', 'desktop', 'invite', 'fullscreen', 'fodeviceselection', 'hangup'], // jshint ignore:line MAIN_TOOLBAR_BUTTONS: ['microphone', 'camera', 'desktop', 'invite', 'fullscreen', 'fodeviceselection', 'hangup'], // jshint ignore:line
SETTINGS_SECTIONS: ['language', 'devices', 'moderator'], SETTINGS_SECTIONS: ['language', 'devices', 'moderator'],
INVITE_OPTIONS: ['invite', 'dialout', 'addtocall'],
// Determines how the video would fit the screen. 'both' would fit the whole // Determines how the video would fit the screen. 'both' would fit the whole
// screen, 'height' would fit the original video height to the height of the // screen, 'height' would fit the original video height to the height of the
// screen, 'width' would fit the original video width to the width of the // screen, 'width' would fit the original video width to the width of the
@ -124,4 +125,9 @@ var interfaceConfig = { // eslint-disable-line no-unused-vars
* @type {number} * @type {number}
*/ */
CONNECTION_INDICATOR_AUTO_HIDE_TIMEOUT: 5000 CONNECTION_INDICATOR_AUTO_HIDE_TIMEOUT: 5000
/**
* The name of the application connected to the "Add people" search service.
*/
// ADD_PEOPLE_APP_NAME: ""
}; };

View File

@ -446,6 +446,7 @@
"hidePassword": "Hide password", "hidePassword": "Hide password",
"inviteTo": "Invite people to __conferenceName__", "inviteTo": "Invite people to __conferenceName__",
"invitedYouTo": "__userName__ has invited you to the __inviteURL__ conference", "invitedYouTo": "__userName__ has invited you to the __inviteURL__ conference",
"invitePeople": "Invite people",
"locked": "This call is locked. New callers must have the link and enter the password to join.", "locked": "This call is locked. New callers must have the link and enter the password to join.",
"showPassword": "Show password", "showPassword": "Show password",
"unlocked": "This call is unlocked. Any new caller with the link may join the call." "unlocked": "This call is unlocked. Any new caller with the link may join the call."
@ -467,7 +468,7 @@
}, },
"dialOut": { "dialOut": {
"dial": "Dial", "dial": "Dial",
"dialOut": "Call a phone number", "dialOut": "Call a #",
"statusMessage": "is now __status__", "statusMessage": "is now __status__",
"enterPhone": "Enter phone number", "enterPhone": "Enter phone number",
"phoneNotAllowed": "Oh, we don't support that destination yet! Sorry!" "phoneNotAllowed": "Oh, we don't support that destination yet! Sorry!"

View File

@ -37,7 +37,6 @@ import {
showDialPadButton, showDialPadButton,
showEtherpadButton, showEtherpadButton,
showSharedVideoButton, showSharedVideoButton,
showDialOutButton,
showToolbox showToolbox
} from '../../react/features/toolbox'; } from '../../react/features/toolbox';
import { import {
@ -474,7 +473,6 @@ UI.onPeerVideoTypeChanged
UI.updateLocalRole = isModerator => { UI.updateLocalRole = isModerator => {
VideoLayout.showModeratorIndicator(); VideoLayout.showModeratorIndicator();
APP.store.dispatch(showDialOutButton(isModerator));
APP.store.dispatch(showSharedVideoButton()); APP.store.dispatch(showSharedVideoButton());
Recording.showRecordingButton(isModerator); Recording.showRecordingButton(isModerator);

View File

@ -193,16 +193,6 @@ const Filmstrip = {
} }
}, },
/**
* Returns the width of filmstip
* @returns {number} width
*/
getFilmstripWidth() {
return this.filmstrip.innerWidth()
- parseInt(this.filmstrip.css('paddingLeft'), 10)
- parseInt(this.filmstrip.css('paddingRight'), 10);
},
/** /**
* Calculates the size for thumbnails: local and remote one * Calculates the size for thumbnails: local and remote one
* @returns {*|{localVideo, remoteVideo}} * @returns {*|{localVideo, remoteVideo}}
@ -433,11 +423,14 @@ const Filmstrip = {
promises.push(new Promise((resolve) => { promises.push(new Promise((resolve) => {
// Let CSS take care of height in vertical filmstrip mode. // Let CSS take care of height in vertical filmstrip mode.
if (interfaceConfig.VERTICAL_FILMSTRIP) { if (interfaceConfig.VERTICAL_FILMSTRIP) {
resolve(); $('#filmstripLocalVideo').animate({
// adds 4 px because of small video 2px border
width: local.thumbWidth + 4
}, this._getAnimateOptions(animate, resolve));
} else { } else {
this.filmstrip.animate({ this.filmstrip.animate({
// adds 2 px because of small video 1px border // adds 4 px because of small video 2px border
height: remote.thumbHeight + 2 height: remote.thumbHeight + 4
}, this._getAnimateOptions(animate, resolve)); }, this._getAnimateOptions(animate, resolve));
} }
})); }));

View File

@ -29,7 +29,7 @@ function LocalVideo(VideoLayout, emitter) {
this.isLocal = true; this.isLocal = true;
this.emitter = emitter; this.emitter = emitter;
this.statsPopoverLocation = interfaceConfig.VERTICAL_FILMSTRIP this.statsPopoverLocation = interfaceConfig.VERTICAL_FILMSTRIP
? 'left bottom' : 'top center'; ? 'left top' : 'top center';
Object.defineProperty(this, 'id', { Object.defineProperty(this, 'id', {
get: function () { get: function () {

View File

@ -43,7 +43,7 @@ function RemoteVideo(user, VideoLayout, emitter) {
this.hasRemoteVideoMenu = false; this.hasRemoteVideoMenu = false;
this._supportsRemoteControl = false; this._supportsRemoteControl = false;
this.statsPopoverLocation = interfaceConfig.VERTICAL_FILMSTRIP this.statsPopoverLocation = interfaceConfig.VERTICAL_FILMSTRIP
? 'left top' : 'top center'; ? 'left bottom' : 'top center';
this.addRemoteVideoContainer(); this.addRemoteVideoContainer();
this.updateIndicators(); this.updateIndicators();
this.setDisplayName(); this.setDisplayName();

View File

@ -151,3 +151,14 @@ export const SET_RECEIVE_VIDEO_QUALITY = Symbol('SET_RECEIVE_VIDEO_QUALITY');
* } * }
*/ */
export const SET_ROOM = Symbol('SET_ROOM'); export const SET_ROOM = Symbol('SET_ROOM');
/**
* The type of (redux) action, which indicates if a SIP gateway is enabled on
* the server.
*
* {
* type: SET_SIP_GATEWAY_ENABLED
* isSIPGatewayEnabled: boolean
* }
*/
export const SET_SIP_GATEWAY_ENABLED = Symbol('SET_SIP_GATEWAY_ENABLED');

View File

@ -14,7 +14,8 @@ import {
SET_AUDIO_ONLY, SET_AUDIO_ONLY,
SET_PASSWORD, SET_PASSWORD,
SET_RECEIVE_VIDEO_QUALITY, SET_RECEIVE_VIDEO_QUALITY,
SET_ROOM SET_ROOM,
SET_SIP_GATEWAY_ENABLED
} from './actionTypes'; } from './actionTypes';
import { VIDEO_QUALITY_LEVELS } from './constants'; import { VIDEO_QUALITY_LEVELS } from './constants';
import { isRoomValid } from './functions'; import { isRoomValid } from './functions';
@ -60,6 +61,9 @@ ReducerRegistry.register('features/base/conference', (state = {}, action) => {
case SET_ROOM: case SET_ROOM:
return _setRoom(state, action); return _setRoom(state, action);
case SET_SIP_GATEWAY_ENABLED:
return _setSIPGatewayEnabled(state, action);
} }
return state; return state;
@ -363,3 +367,17 @@ function _setRoom(state, action) {
*/ */
return set(state, 'room', room); return set(state, 'room', room);
} }
/**
* Reduces a specific Redux action SET_SIP_GATEWAY_ENABLED of the feature
* base/conference.
*
* @param {Object} state - The Redux state of the feature base/conference.
* @param {Action} action - The Redux action SET_SIP_GATEWAY_ENABLED to reduce.
* @private
* @returns {Object} The new state of the feature base/conference after the
* reduction of the specified action.
*/
function _setSIPGatewayEnabled(state, action) {
return set(state, 'isSIPGatewayEnabled', action.isSIPGatewayEnabled);
}

View File

@ -74,7 +74,7 @@ class Conference extends Component {
<div id = 'videoconference_page'> <div id = 'videoconference_page'>
<div id = 'videospace'> <div id = 'videospace'>
<LargeVideo /> <LargeVideo />
<Filmstrip displayToolbox = { filmStripOnly } /> <Filmstrip filmstripOnly = { filmStripOnly } />
</div> </div>
{ filmStripOnly ? null : <Toolbox /> } { filmStripOnly ? null : <Toolbox /> }

View File

@ -1,5 +1,3 @@
import { openDialog } from '../../features/base/dialog';
import { import {
DIAL_OUT_CANCELED, DIAL_OUT_CANCELED,
DIAL_OUT_CODES_UPDATED, DIAL_OUT_CODES_UPDATED,
@ -7,8 +5,6 @@ import {
PHONE_NUMBER_CHECKED PHONE_NUMBER_CHECKED
} from './actionTypes'; } from './actionTypes';
import { DialOutDialog } from './components';
declare var $: Function; declare var $: Function;
declare var config: Object; declare var config: Object;
@ -76,16 +72,6 @@ export function checkDialNumber(dialNumber) {
}; };
} }
/**
* Opens the dial-out dialog.
*
* @returns {Function}
*/
export function openDialOutDialog() {
return openDialog(DialOutDialog);
}
/** /**
* Sends an ajax request for dial-out country codes. * Sends an ajax request for dial-out country codes.
* *

View File

@ -5,6 +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 { InviteButton } from '../../invite';
import { Toolbox } from '../../toolbox'; import { Toolbox } from '../../toolbox';
import { setFilmstripHovered } from '../actions'; import { setFilmstripHovered } from '../actions';
@ -48,9 +49,9 @@ class Filmstrip extends Component {
dispatch: PropTypes.func, dispatch: PropTypes.func,
/** /**
* Whether or not the toolbox should be displayed within the filmstrip. * Whether or not the conference is in filmstripOnly mode.
*/ */
displayToolbox: PropTypes.bool filmstripOnly: PropTypes.bool
}; };
/** /**
@ -100,7 +101,7 @@ class Filmstrip extends Component {
return ( return (
<div className = { filmstripClassNames }> <div className = { filmstripClassNames }>
{ this.props.displayToolbox ? <Toolbox /> : null } { this.props.filmstripOnly ? <Toolbox /> : null }
<div <div
className = 'filmstrip__videos' className = 'filmstrip__videos'
id = 'remoteVideos'> id = 'remoteVideos'>
@ -108,7 +109,9 @@ class Filmstrip extends Component {
className = 'filmstrip__videos' className = 'filmstrip__videos'
id = 'filmstripLocalVideo' id = 'filmstripLocalVideo'
onMouseOut = { this._onMouseOut } onMouseOut = { this._onMouseOut }
onMouseOver = { this._onMouseOver } /> onMouseOver = { this._onMouseOver }>
{ this.props.filmstripOnly ? null : <InviteButton /> }
</div>
<div <div
className = 'filmstrip__videos' className = 'filmstrip__videos'
id = 'filmstripRemoteVideos'> id = 'filmstripRemoteVideos'>

View File

@ -5,7 +5,7 @@ import {
UPDATE_DIAL_IN_NUMBERS_FAILED, UPDATE_DIAL_IN_NUMBERS_FAILED,
UPDATE_DIAL_IN_NUMBERS_SUCCESS UPDATE_DIAL_IN_NUMBERS_SUCCESS
} from './actionTypes'; } from './actionTypes';
import { AddPeopleDialog, InviteDialog } from './components'; import { InviteDialog } from './components';
declare var $: Function; declare var $: Function;
@ -18,15 +18,6 @@ export function openInviteDialog() {
return openDialog(InviteDialog); return openDialog(InviteDialog);
} }
/**
* Opens the Add People Dialog.
*
* @returns {Function}
*/
export function openAddPeopleDialog() {
return openDialog(AddPeopleDialog);
}
/** /**
* Opens the inline conference info dialog. * Opens the inline conference info dialog.
* *

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,231 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Button from '@atlaskit/button';
import DropdownMenu from '@atlaskit/dropdown-menu';
import { translate } from '../../base/i18n';
import { getLocalParticipant, PARTICIPANT_ROLE } from '../../base/participants';
import { openDialog } from '../../base/dialog';
import { AddPeopleDialog, InviteDialog } from '.';
import { DialOutDialog } from '../../dial-out';
import { isInviteOptionEnabled, getInviteOptionPosition } from '../functions';
declare var interfaceConfig: Object;
const SHARE_LINK_OPTION = 'invite';
const DIAL_OUT_OPTION = 'dialout';
const ADD_TO_CALL_OPTION = 'addtocall';
/**
* The button that provides different invite options.
*/
class InviteButton extends Component {
/**
* {@code InviteButton}'s property types.
*
* @static
*/
static propTypes = {
/**
* Indicates if the "Add to call" feature is available.
*/
_isAddToCallAvailable: PropTypes.bool,
/**
* Indicates if the "Dial out" feature is available.
*/
_isDialOutAvailable: PropTypes.bool,
/**
* The function opening the dialog.
*/
openDialog: PropTypes.func,
/**
* Invoked to obtain translated strings.
*/
t: PropTypes.func
};
/**
* Initializes a new {@code InviteButton} instance.
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
*/
constructor(props) {
super(props);
this._onInviteClick = this._onInviteClick.bind(this);
this._onInviteOptionSelected = this._onInviteOptionSelected.bind(this);
this._updateInviteItems = this._updateInviteItems.bind(this);
this._updateInviteItems(this.props);
}
/**
* Implements React's {@link Component#componentWillReceiveProps()}.
*
* @inheritdoc
* @param {Object} nextProps - The read-only props which this Component will
* receive.
* @returns {void}
*/
componentWillReceiveProps(nextProps) {
if (this.props._isDialOutAvailable !== nextProps._isDialOutAvailable
|| this.props._isAddToCallAvailable
!== nextProps._isAddToCallAvailable) {
this._updateInviteItems(nextProps);
}
}
/**
* Renders the content of this component.
*
* @returns {ReactElement}
*/
render() {
const { t } = this.props;
const { VERTICAL_FILMSTRIP } = interfaceConfig;
return (
<div className = 'filmstrip__invite'>
<div className = 'invite-button-group'>
<Button
onClick = { this._onInviteClick }
shouldFitContainer = { true }>
{ t('invite.invitePeople') }
</Button>
{ this.props._isDialOutAvailable
|| this.props._isAddToCallAvailable
? <DropdownMenu
items = { this.state.inviteOptions }
onItemActivated = { this._onInviteOptionSelected }
position = { VERTICAL_FILMSTRIP
? 'bottom right'
: 'top right' }
shouldFlip = { true }
triggerType = 'button' />
: null }
</div>
</div>
);
}
/**
* Handles the click of the invite button.
*
* @private
* @returns {void}
*/
_onInviteClick() {
this.props.openDialog(InviteDialog);
}
/**
* Handles selection of the invite options.
*
* @param { Object } option - The invite option that has been selected from
* the dropdown menu.
* @private
* @returns {void}
*/
_onInviteOptionSelected(option) {
this.state.inviteOptions[0].items.forEach(item => {
if (item.content === option.item.content) {
item.action();
}
});
}
/**
* Updates the invite items list depending on the availability of the
* features.
*
* @param {Object} props - The read-only properties of the component.
* @private
* @returns {void}
*/
_updateInviteItems(props) {
const { t } = this.props;
const inviteItems = [];
inviteItems.splice(
getInviteOptionPosition(SHARE_LINK_OPTION),
0,
{
content: t('toolbar.invite'),
action: () => this.props.openDialog(InviteDialog)
}
);
if (props._isDialOutAvailable) {
inviteItems.splice(
getInviteOptionPosition(DIAL_OUT_OPTION),
0,
{
content: t('dialOut.dialOut'),
action: () => this.props.openDialog(DialOutDialog)
}
);
}
if (props._isAddToCallAvailable) {
inviteItems.splice(
getInviteOptionPosition(ADD_TO_CALL_OPTION),
0,
{
content: interfaceConfig.ADD_PEOPLE_APP_NAME,
action: () => this.props.openDialog(AddPeopleDialog)
}
);
}
this.state = {
/**
* The list of invite options.
*/
inviteOptions: [
{
items: inviteItems
}
]
};
}
}
/**
* Maps (parts of) the Redux state to the associated {@code InviteButton}'s
* props.
*
* @param {Object} state - The Redux state.
* @private
* @returns {{
* _isAddToCallAvailable: boolean,
* _isDialOutAvailable: boolean
* }}
*/
function _mapStateToProps(state) {
const { enableUserRolesBasedOnToken } = state['features/base/config'];
const { conference } = state['features/base/conference'];
const { isGuest } = state['features/jwt'];
return {
_isAddToCallAvailable: !isGuest
&& isInviteOptionEnabled(ADD_TO_CALL_OPTION),
_isDialOutAvailable:
getLocalParticipant(state).role === PARTICIPANT_ROLE.MODERATOR
&& conference && conference.isSIPCallingSupported()
&& isInviteOptionEnabled(DIAL_OUT_OPTION)
&& (!enableUserRolesBasedOnToken || !isGuest)
};
}
export default translate(connect(
_mapStateToProps, { openDialog })(InviteButton));

View File

@ -1,3 +1,4 @@
export { default as AddPeopleDialog } from './AddPeopleDialog'; export { default as AddPeopleDialog } from './AddPeopleDialog';
export { default as InfoDialogButton } from './InfoDialogButton'; export { default as InfoDialogButton } from './InfoDialogButton';
export { default as InviteButton } from './InviteButton';
export { default as InviteDialog } from './InviteDialog'; export { default as InviteDialog } from './InviteDialog';

View File

@ -1,4 +1,5 @@
declare var $: Function; declare var $: Function;
declare var interfaceConfig: Object;
/** /**
* Sends an ajax request to a directory service. * Sends an ajax request to a directory service.
@ -76,3 +77,27 @@ export function inviteRooms(conference, rooms) {
} }
} }
} }
/**
* Indicates if an invite option is enabled in the configuration.
*
* @param {string} name - The name of the option defined in
* interfaceConfig.INVITE_OPTIONS.
* @returns {boolean} - True to indicate that the given invite option is
* enabled, false - otherwise.
*/
export function isInviteOptionEnabled(name) {
return interfaceConfig.INVITE_OPTIONS.indexOf(name) !== -1;
}
/**
* Get the position of the invite option in the interfaceConfig.INVITE_OPTIONS
* list.
*
* @param {string} optionName - The invite option name.
* @private
* @returns {number} - The position of the option in the list.
*/
export function getInviteOptionPosition(optionName) {
return interfaceConfig.INVITE_OPTIONS.indexOf(optionName);
}

View File

@ -3,7 +3,6 @@ import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { translate } from '../../base/i18n'; import { translate } from '../../base/i18n';
import { shouldRemoteVideosBeVisible } from '../../filmstrip';
/** /**
* Implements a React {@link Component} which displays the current state of * Implements a React {@link Component} which displays the current state of
@ -38,14 +37,6 @@ class RecordingLabel extends Component {
*/ */
_labelDisplayConfiguration: PropTypes.object, _labelDisplayConfiguration: PropTypes.object,
/**
* Whether or not remote videos within the filmstrip are currently
* visible. Depending on the visibility state, coupled with filmstrip
* visibility, CSS classes will be set to allow for adjusting of
* {@code RecordingLabel} positioning.
*/
_remoteVideosVisible: PropTypes.bool,
/** /**
* Invoked to obtain translated string. * Invoked to obtain translated string.
*/ */
@ -106,9 +97,7 @@ class RecordingLabel extends Component {
centered ? '' : 'moveToCorner', centered ? '' : 'moveToCorner',
this.state.filmstripBecomingVisible ? 'opening' : '', this.state.filmstripBecomingVisible ? 'opening' : '',
this.props._filmstripVisible this.props._filmstripVisible
? 'with-filmstrip' : 'without-filmstrip', ? 'with-filmstrip' : 'without-filmstrip'
this.props._remoteVideosVisible
? 'with-remote-videos' : 'without-remote-videos'
].join(' '); ].join(' ');
return ( return (
@ -137,8 +126,7 @@ class RecordingLabel extends Component {
* @private * @private
* @returns {{ * @returns {{
* _filmstripVisible: boolean, * _filmstripVisible: boolean,
* _labelDisplayConfiguration: Object, * _labelDisplayConfiguration: Object
* _remoteVideosVisible: boolean,
* }} * }}
*/ */
function _mapStateToProps(state) { function _mapStateToProps(state) {
@ -159,14 +147,7 @@ function _mapStateToProps(state) {
* *
* @type {Object} * @type {Object}
*/ */
_labelDisplayConfiguration: labelDisplayConfiguration, _labelDisplayConfiguration: labelDisplayConfiguration
/**
* Whether or not remote videos are displayed in the filmstrip.
*
* @type {boolean}
*/
_remoteVideosVisible: shouldRemoteVideosBeVisible(state)
}; };
} }

View File

@ -107,7 +107,7 @@ class RemoteVideoMenuTriggerButton extends Component {
content = { content } content = { content }
onPopoverOpen = { this._onShowRemoteMenu } onPopoverOpen = { this._onShowRemoteMenu }
position = { interfaceConfig.VERTICAL_FILMSTRIP position = { interfaceConfig.VERTICAL_FILMSTRIP
? 'left middle' : 'top center' }> ? 'left bottom' : 'top center' }>
<span <span
className = 'popover-trigger remote-video-menu-trigger'> className = 'popover-trigger remote-video-menu-trigger'>
<i <i

View File

@ -324,29 +324,6 @@ export function showSharedVideoButton(): Function {
}; };
} }
/**
* Shows the dial out button if it's required and appropriate
* flag is passed.
*
* @param {boolean} show - Flag showing whether to show button or not.
* @returns {Function}
*/
export function showDialOutButton(show: boolean): Function {
return (dispatch: Dispatch<*>, getState: Function) => {
const buttonName = 'dialout';
if (show
&& APP.conference.sipGatewayEnabled()
&& isButtonEnabled(buttonName)
&& (!config.enableUserRolesBasedOnToken
|| !getState()['features/jwt'].isGuest)) {
dispatch(setToolbarButton(buttonName, {
hidden: false
}));
}
};
}
/** /**
* Shows the toolbox for specified timeout. * Shows the toolbox for specified timeout.
* *

View File

@ -1,16 +1,16 @@
/* @flow */ /* @flow */
import React from 'react'; import React from 'react';
import _ from 'lodash';
import { ParticipantCounter } from '../contact-list'; import { ParticipantCounter } from '../contact-list';
import { openDeviceSelectionDialog } from '../device-selection'; import { openDeviceSelectionDialog } from '../device-selection';
import { openDialOutDialog } from '../dial-out';
import { import {
InfoDialogButton, InfoDialogButton,
openAddPeopleDialog,
openInviteDialog openInviteDialog
} from '../invite'; } from '../invite';
import { VideoQualityButton } from '../video-quality'; import { VideoQualityButton } from '../video-quality';
import UIEvents from '../../../service/UI/UIEvents'; import UIEvents from '../../../service/UI/UIEvents';
@ -21,412 +21,394 @@ declare var APP: Object;
declare var interfaceConfig: Object; declare var interfaceConfig: Object;
declare var JitsiMeetJS: Object; declare var JitsiMeetJS: Object;
let buttons: Object = {};
/** /**
* All toolbar buttons' descriptors. * Returns a map of all button descriptors and according properties.
*
* @returns {*} - The maps of default button descriptors.
*/ */
const buttons: Object = { function getDefaultButtons() {
addtocall: { if (!_.isEmpty(buttons)) {
classNames: [ 'button', 'icon-add' ], return buttons;
enabled: true, }
id: 'toolbar_button_add',
isDisplayed: () => !APP.store.getState()['features/jwt'].isGuest,
onClick(dispatch) {
JitsiMeetJS.analytics.sendEvent('toolbar.add.clicked');
dispatch(openAddPeopleDialog()); buttons = {
}, /**
tooltipKey: 'toolbar.addPeople' * The descriptor of the camera toolbar button.
}, */
camera: {
classNames: [ 'button', 'icon-camera' ],
enabled: true,
isDisplayed: () => true,
id: 'toolbar_button_camera',
onClick() {
const newVideoMutedState = !APP.conference.isLocalVideoMuted();
/** if (newVideoMutedState) {
* The descriptor of the camera toolbar button. JitsiMeetJS.analytics.sendEvent('toolbar.video.enabled');
*/
camera: {
classNames: [ 'button', 'icon-camera' ],
enabled: true,
isDisplayed: () => true,
id: 'toolbar_button_camera',
onClick() {
const newVideoMutedState = !APP.conference.isLocalVideoMuted();
if (newVideoMutedState) {
JitsiMeetJS.analytics.sendEvent('toolbar.video.enabled');
} else {
JitsiMeetJS.analytics.sendEvent('toolbar.video.disabled');
}
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;
}
JitsiMeetJS.analytics.sendEvent('shortcut.videomute.toggled');
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() {
JitsiMeetJS.analytics.sendEvent('toolbar.chat.toggled');
APP.UI.emitEvent(UIEvents.TOGGLE_CHAT);
},
shortcut: 'C',
shortcutAttr: 'toggleChatPopover',
shortcutFunc() {
JitsiMeetJS.analytics.sendEvent('shortcut.chat.toggled');
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() {
JitsiMeetJS.analytics.sendEvent(
'toolbar.contacts.toggled');
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() {
if (APP.conference.isSharingScreen) {
JitsiMeetJS.analytics.sendEvent('toolbar.screen.disabled');
} else {
JitsiMeetJS.analytics.sendEvent('toolbar.screen.enabled');
}
APP.UI.emitEvent(UIEvents.TOGGLE_SCREENSHARING);
},
popups: [
{
dataAttr: 'audioOnly.featureToggleDisabled',
dataInterpolate: { feature: 'screen sharing' },
id: 'screenshareWhileAudioOnly'
}
],
shortcut: 'D',
shortcutAttr: 'toggleDesktopSharingPopover',
shortcutFunc() {
JitsiMeetJS.analytics.sendEvent('shortcut.screen.toggled');
// eslint-disable-next-line no-empty-function
APP.conference.toggleScreenSharing().catch(() => {});
},
shortcutDescription: 'keyboardShortcuts.toggleScreensharing',
tooltipKey: 'toolbar.sharescreen'
},
/**
* The descriptor of the dial out toolbar button.
*/
dialout: {
classNames: [ 'button', 'icon-telephone' ],
enabled: true,
// Will be displayed once the SIP calls functionality is detected.
hidden: true,
id: 'toolbar_button_dial_out',
onClick(dispatch) {
JitsiMeetJS.analytics.sendEvent('toolbar.sip.clicked');
dispatch(openDialOutDialog());
},
tooltipKey: 'dialOut.dialOut'
},
/**
* 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) {
JitsiMeetJS.analytics.sendEvent(
'toolbar.fodeviceselection.toggled');
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() {
JitsiMeetJS.analytics.sendEvent('toolbar.sip.dialpad.clicked');
},
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() {
JitsiMeetJS.analytics.sendEvent('toolbar.etherpad.clicked');
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() {
JitsiMeetJS.analytics.sendEvent('toolbar.fullscreen.enabled');
APP.UI.emitEvent(UIEvents.TOGGLE_FULLSCREEN);
},
shortcut: 'S',
shortcutAttr: 'toggleFullscreenPopover',
shortcutDescription: 'keyboardShortcuts.fullScreen',
shortcutFunc() {
JitsiMeetJS.analytics.sendEvent('shortcut.fullscreen.toggled');
APP.UI.toggleFullScreen();
},
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() {
JitsiMeetJS.analytics.sendEvent('toolbar.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 toolbar button which shows the invite user dialog.
*/
invite: {
classNames: [ 'button', 'icon-link' ],
enabled: true,
id: 'toolbar_button_link',
onClick(dispatch) {
JitsiMeetJS.analytics.sendEvent('toolbar.invite.clicked');
dispatch(openInviteDialog());
},
tooltipKey: 'toolbar.invite'
},
/**
* 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();
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(
'#unableToUnmutePopup', true, 5000);
} else { } else {
JitsiMeetJS.analytics.sendEvent('toolbar.audio.unmuted'); JitsiMeetJS.analytics.sendEvent('toolbar.video.disabled');
APP.UI.emitEvent(UIEvents.AUDIO_MUTED, false, true);
} }
} else { APP.UI.emitEvent(UIEvents.VIDEO_MUTED, newVideoMutedState);
JitsiMeetJS.analytics.sendEvent('toolbar.audio.muted');
APP.UI.emitEvent(UIEvents.AUDIO_MUTED, true, true);
}
},
popups: [
{
dataAttr: 'toolbar.micMutedPopup',
id: 'micMutedPopup'
}, },
{ popups: [
dataAttr: 'toolbar.unableToUnmutePopup', {
id: 'unableToUnmutePopup' 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;
}
JitsiMeetJS.analytics.sendEvent('shortcut.videomute.toggled');
APP.conference.toggleVideoMuted();
}, },
{ shortcutDescription: 'keyboardShortcuts.videoMute',
dataAttr: 'toolbar.talkWhileMutedPopup', tooltipKey: 'toolbar.videomute'
id: 'talkWhileMutedPopup'
}
],
shortcut: 'M',
shortcutAttr: 'mutePopover',
shortcutFunc() {
JitsiMeetJS.analytics.sendEvent('shortcut.audiomute.toggled');
APP.conference.toggleAudioMuted();
}, },
shortcutDescription: 'keyboardShortcuts.mute',
tooltipKey: 'toolbar.mute'
},
/** /**
* The descriptor of the profile toolbar button. * The descriptor of the chat toolbar button.
*/ */
profile: { chat: {
component: ProfileButton, classNames: [ 'button', 'icon-chat' ],
sideContainerId: 'profile_container' enabled: true,
}, html: <span className = 'badge-round'>
<span id = 'unreadMessages' /></span>,
/** id: 'toolbar_button_chat',
* The descriptor of the "Raise hand" toolbar button. onClick() {
*/ JitsiMeetJS.analytics.sendEvent('toolbar.chat.toggled');
raisehand: { APP.UI.emitEvent(UIEvents.TOGGLE_CHAT);
classNames: [ 'button', 'icon-raised-hand' ], },
enabled: true, shortcut: 'C',
id: 'toolbar_button_raisehand', shortcutAttr: 'toggleChatPopover',
onClick() { shortcutFunc() {
JitsiMeetJS.analytics.sendEvent('toolbar.raiseHand.clicked'); JitsiMeetJS.analytics.sendEvent('shortcut.chat.toggled');
APP.conference.maybeToggleRaisedHand(); APP.UI.toggleChat();
},
shortcutDescription: 'keyboardShortcuts.toggleChat',
sideContainerId: 'chat_container',
tooltipKey: 'toolbar.chat'
}, },
shortcut: 'R',
shortcutAttr: 'raiseHandPopover', /**
shortcutDescription: 'keyboardShortcuts.raiseHand', * The descriptor of the contact list toolbar button.
shortcutFunc() { */
JitsiMeetJS.analytics.sendEvent('shortcut.raisehand.clicked'); contacts: {
APP.conference.maybeToggleRaisedHand(); childComponent: ParticipantCounter,
classNames: [ 'button', 'icon-contactList' ],
enabled: true,
id: 'toolbar_contact_list',
onClick() {
JitsiMeetJS.analytics.sendEvent(
'toolbar.contacts.toggled');
APP.UI.emitEvent(UIEvents.TOGGLE_CONTACT_LIST);
},
sideContainerId: 'contacts_container',
tooltipKey: 'bottomtoolbar.contactlist'
}, },
tooltipKey: 'toolbar.raiseHand'
},
/** /**
* The descriptor of the recording toolbar button. Requires additional * The descriptor of the desktop sharing toolbar button.
* initialization in the recording module. */
*/ desktop: {
recording: { classNames: [ 'button', 'icon-share-desktop' ],
classNames: [ 'button' ], enabled: true,
enabled: true, id: 'toolbar_button_desktopsharing',
onClick() {
if (APP.conference.isSharingScreen) {
JitsiMeetJS.analytics.sendEvent('toolbar.screen.disabled');
} else {
JitsiMeetJS.analytics.sendEvent('toolbar.screen.enabled');
}
APP.UI.emitEvent(UIEvents.TOGGLE_SCREENSHARING);
},
popups: [
{
dataAttr: 'audioOnly.featureToggleDisabled',
dataInterpolate: { feature: 'screen sharing' },
id: 'screenshareWhileAudioOnly'
}
],
shortcut: 'D',
shortcutAttr: 'toggleDesktopSharingPopover',
shortcutFunc() {
JitsiMeetJS.analytics.sendEvent('shortcut.screen.toggled');
// will be displayed once the recording functionality is detected // eslint-disable-next-line no-empty-function
hidden: true, APP.conference.toggleScreenSharing().catch(() => {});
id: 'toolbar_button_record', },
tooltipKey: 'liveStreaming.buttonTooltip' shortcutDescription: 'keyboardShortcuts.toggleScreensharing',
}, tooltipKey: 'toolbar.sharescreen'
/**
* The descriptor of the settings toolbar button.
*/
settings: {
classNames: [ 'button', 'icon-settings' ],
enabled: true,
id: 'toolbar_button_settings',
onClick() {
JitsiMeetJS.analytics.sendEvent('toolbar.settings.toggled');
APP.UI.emitEvent(UIEvents.TOGGLE_SETTINGS);
}, },
sideContainerId: 'settings_container',
tooltipKey: 'toolbar.Settings'
},
/** /**
* The descriptor of the "Share YouTube video" toolbar button. * The descriptor of the device selection toolbar button.
*/ */
sharedvideo: { fodeviceselection: {
classNames: [ 'button', 'icon-shared-video' ], classNames: [ 'button', 'icon-settings' ],
enabled: true, enabled: true,
id: 'toolbar_button_sharedvideo', isDisplayed() {
onClick() { return interfaceConfig.filmStripOnly;
JitsiMeetJS.analytics.sendEvent('toolbar.sharedvideo.clicked'); },
APP.UI.emitEvent(UIEvents.SHARED_VIDEO_CLICKED); id: 'toolbar_button_fodeviceselection',
onClick(dispatch: Function) {
JitsiMeetJS.analytics.sendEvent(
'toolbar.fodeviceselection.toggled');
dispatch(openDeviceSelectionDialog());
},
sideContainerId: 'settings_container',
tooltipKey: 'toolbar.Settings'
}, },
popups: [
{
dataAttr: 'toolbar.sharedVideoMutedPopup',
id: 'sharedVideoMutedPopup'
}
],
tooltipKey: 'toolbar.sharedvideo'
},
videoquality: { /**
component: VideoQualityButton * 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() {
JitsiMeetJS.analytics.sendEvent('toolbar.sip.dialpad.clicked');
},
tooltipKey: 'toolbar.dialpad'
},
Object.keys(buttons).forEach(name => { /**
const button = buttons[name]; * The descriptor of the etherpad toolbar button.
*/
etherpad: {
classNames: [ 'button', 'icon-share-doc' ],
enabled: true,
hidden: true,
id: 'toolbar_button_etherpad',
onClick() {
JitsiMeetJS.analytics.sendEvent('toolbar.etherpad.clicked');
APP.UI.emitEvent(UIEvents.ETHERPAD_CLICKED);
},
tooltipKey: 'toolbar.etherpad'
},
if (!button.isDisplayed) { /**
button.isDisplayed = () => !interfaceConfig.filmStripOnly; * The descriptor of the toolbar button which toggles full-screen mode.
} */
}); fullscreen: {
classNames: [ 'button', 'icon-full-screen' ],
enabled: true,
id: 'toolbar_button_fullScreen',
onClick() {
JitsiMeetJS.analytics.sendEvent('toolbar.fullscreen.enabled');
export default buttons; APP.UI.emitEvent(UIEvents.TOGGLE_FULLSCREEN);
},
shortcut: 'S',
shortcutAttr: 'toggleFullscreenPopover',
shortcutDescription: 'keyboardShortcuts.fullScreen',
shortcutFunc() {
JitsiMeetJS.analytics.sendEvent('shortcut.fullscreen.toggled');
APP.UI.toggleFullScreen();
},
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() {
JitsiMeetJS.analytics.sendEvent('toolbar.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 toolbar button which shows the invite user
* dialog.
*/
invite: {
classNames: [ 'button', 'icon-link' ],
enabled: true,
id: 'toolbar_button_link',
onClick(dispatch: Function) {
JitsiMeetJS.analytics.sendEvent('toolbar.invite.clicked');
dispatch(openInviteDialog());
},
tooltipKey: 'toolbar.invite'
},
/**
* 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();
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(
'#unableToUnmutePopup', true, 5000);
} else {
JitsiMeetJS.analytics
.sendEvent('toolbar.audio.unmuted');
APP.UI.emitEvent(UIEvents.AUDIO_MUTED, false, true);
}
} else {
JitsiMeetJS.analytics.sendEvent('toolbar.audio.muted');
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() {
JitsiMeetJS.analytics.sendEvent('shortcut.audiomute.toggled');
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() {
JitsiMeetJS.analytics.sendEvent('toolbar.raiseHand.clicked');
APP.conference.maybeToggleRaisedHand();
},
shortcut: 'R',
shortcutAttr: 'raiseHandPopover',
shortcutDescription: 'keyboardShortcuts.raiseHand',
shortcutFunc() {
JitsiMeetJS.analytics.sendEvent('shortcut.raisehand.clicked');
APP.conference.maybeToggleRaisedHand();
},
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() {
JitsiMeetJS.analytics.sendEvent('toolbar.settings.toggled');
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() {
JitsiMeetJS.analytics.sendEvent('toolbar.sharedvideo.clicked');
APP.UI.emitEvent(UIEvents.SHARED_VIDEO_CLICKED);
},
popups: [
{
dataAttr: 'toolbar.sharedVideoMutedPopup',
id: 'sharedVideoMutedPopup'
}
],
tooltipKey: 'toolbar.sharedvideo'
},
videoquality: {
component: VideoQualityButton
}
};
Object.keys(buttons).forEach(name => {
const button = buttons[name];
if (!button.isDisplayed) {
button.isDisplayed = () => !interfaceConfig.filmStripOnly;
}
});
return buttons;
}
export default getDefaultButtons;

View File

@ -1,7 +1,7 @@
import SideContainerToggler import SideContainerToggler
from '../../../modules/UI/side_pannels/SideContainerToggler'; from '../../../modules/UI/side_pannels/SideContainerToggler';
import defaultToolbarButtons from './defaultToolbarButtons'; import getDefaultButtons from './defaultToolbarButtons';
declare var interfaceConfig: Object; declare var interfaceConfig: Object;
@ -27,7 +27,8 @@ export function getDefaultToolboxButtons(buttonHandlers: Object): Object {
toolbarButtons toolbarButtons
= interfaceConfig.TOOLBAR_BUTTONS.reduce( = interfaceConfig.TOOLBAR_BUTTONS.reduce(
(acc, buttonName) => { (acc, buttonName) => {
let button = defaultToolbarButtons[buttonName]; const buttons = getDefaultButtons();
let button = buttons ? buttons[buttonName] : null;
const currentButtonHandlers = buttonHandlers[buttonName]; const currentButtonHandlers = buttonHandlers[buttonName];
if (button) { if (button) {

View File

@ -15,7 +15,7 @@ import {
SET_TOOLBOX_TIMEOUT_MS, SET_TOOLBOX_TIMEOUT_MS,
SET_TOOLBOX_VISIBLE SET_TOOLBOX_VISIBLE
} from './actionTypes'; } from './actionTypes';
import defaultToolbarButtons from './defaultToolbarButtons'; import getDefaultButtons from './defaultToolbarButtons';
declare var interfaceConfig: Object; declare var interfaceConfig: Object;
@ -209,7 +209,8 @@ ReducerRegistry.register(
* @returns {Object} * @returns {Object}
*/ */
function _setButton(state, { button, buttonName }): Object { function _setButton(state, { button, buttonName }): Object {
const buttonDefinition = defaultToolbarButtons[buttonName]; const buttons = getDefaultButtons();
const buttonDefinition = buttons ? buttons[buttonName] : null;
// We don't need to update if the button shouldn't be displayed // We don't need to update if the button shouldn't be displayed
if (!buttonDefinition || !buttonDefinition.isDisplayed()) { if (!buttonDefinition || !buttonDefinition.isDisplayed()) {

View File

@ -5,7 +5,6 @@ import { connect } from 'react-redux';
import { VIDEO_QUALITY_LEVELS } from '../../base/conference'; import { VIDEO_QUALITY_LEVELS } from '../../base/conference';
import { translate } from '../../base/i18n'; import { translate } from '../../base/i18n';
import { shouldRemoteVideosBeVisible } from '../../filmstrip';
const { HIGH, STANDARD, LOW } = VIDEO_QUALITY_LEVELS; const { HIGH, STANDARD, LOW } = VIDEO_QUALITY_LEVELS;
@ -59,12 +58,6 @@ export class VideoQualityLabel extends Component {
*/ */
_filmstripVisible: PropTypes.bool, _filmstripVisible: PropTypes.bool,
/**
* Whether or note remote videos are visible in the filmstrip,
* regardless of count. Used to determine display classes to set.
*/
_remoteVideosVisible: PropTypes.bool,
/** /**
* The current video resolution (height) to display a label for. * The current video resolution (height) to display a label for.
*/ */
@ -123,7 +116,6 @@ export class VideoQualityLabel extends Component {
_audioOnly, _audioOnly,
_conferenceStarted, _conferenceStarted,
_filmstripVisible, _filmstripVisible,
_remoteVideosVisible,
_resolution, _resolution,
t t
} = this.props; } = this.props;
@ -140,12 +132,9 @@ export class VideoQualityLabel extends Component {
const baseClasses = 'video-state-indicator moveToCorner'; const baseClasses = 'video-state-indicator moveToCorner';
const filmstrip const filmstrip
= _filmstripVisible ? 'with-filmstrip' : 'without-filmstrip'; = _filmstripVisible ? 'with-filmstrip' : 'without-filmstrip';
const remoteVideosVisible = _remoteVideosVisible
? 'with-remote-videos'
: 'without-remote-videos';
const opening = this.state.togglingToVisible ? 'opening' : ''; const opening = this.state.togglingToVisible ? 'opening' : '';
const classNames const classNames
= `${baseClasses} ${filmstrip} ${remoteVideosVisible} ${opening}`; = `${baseClasses} ${filmstrip} ${opening}`;
const tooltipKey const tooltipKey
= `videoStatus.labelTooltip${_audioOnly ? 'AudioOnly' : 'Video'}`; = `videoStatus.labelTooltip${_audioOnly ? 'AudioOnly' : 'Video'}`;
@ -206,7 +195,6 @@ export class VideoQualityLabel extends Component {
* _audioOnly: boolean, * _audioOnly: boolean,
* _conferenceStarted: boolean, * _conferenceStarted: boolean,
* _filmstripVisible: true, * _filmstripVisible: true,
* _remoteVideosVisible: boolean,
* _resolution: number * _resolution: number
* }} * }}
*/ */
@ -219,7 +207,6 @@ function _mapStateToProps(state) {
_audioOnly: audioOnly, _audioOnly: audioOnly,
_conferenceStarted: Boolean(conference), _conferenceStarted: Boolean(conference),
_filmstripVisible: visible, _filmstripVisible: visible,
_remoteVideosVisible: shouldRemoteVideosBeVisible(state),
_resolution: resolution _resolution: resolution
}; };
} }