jiti-meet/react/features/prejoin/components/Prejoin.js

456 lines
14 KiB
JavaScript
Raw Normal View History

2020-04-16 10:47:10 +00:00
// @flow
import InlineDialog from '@atlaskit/inline-dialog';
2020-05-20 10:57:03 +00:00
import React, { Component } from 'react';
import { getRoomName } from '../../base/conference';
import { translate } from '../../base/i18n';
2020-09-29 11:00:30 +00:00
import { Icon, IconArrowDown, IconArrowUp, IconPhone, IconVolumeOff } from '../../base/icons';
import { isVideoMutedByUser } from '../../base/media';
import { ActionButton, InputField, PreMeetingScreen, ToggleButton } from '../../base/premeeting';
2020-05-20 10:57:03 +00:00
import { connect } from '../../base/redux';
import { getDisplayName, updateSettings } from '../../base/settings';
import { getLocalJitsiVideoTrack } from '../../base/tracks';
2020-08-04 22:25:16 +00:00
import { isButtonEnabled } from '../../toolbox/functions.web';
2020-04-16 10:47:10 +00:00
import {
joinConference as joinConferenceAction,
joinConferenceWithoutAudio as joinConferenceWithoutAudioAction,
setSkipPrejoin as setSkipPrejoinAction,
setJoinByPhoneDialogVisiblity as setJoinByPhoneDialogVisiblityAction
2020-04-16 10:47:10 +00:00
} from '../actions';
import {
isDeviceStatusVisible,
isDisplayNameRequired,
isJoinByPhoneButtonVisible,
isJoinByPhoneDialogVisible,
isPrejoinSkipped
2020-04-16 10:47:10 +00:00
} from '../functions';
2020-05-20 10:57:03 +00:00
import JoinByPhoneDialog from './dialogs/JoinByPhoneDialog';
2020-04-16 10:47:10 +00:00
import DeviceStatus from './preview/DeviceStatus';
2020-08-04 22:25:16 +00:00
declare var interfaceConfig: Object;
2020-04-16 10:47:10 +00:00
type Props = {
/**
* Flag signaling if the 'skip prejoin' button is toggled or not.
*/
buttonIsToggled: boolean,
2020-04-16 10:47:10 +00:00
/**
* Flag signaling if the device status is visible or not.
*/
deviceStatusVisible: boolean,
/**
* If join by phone button should be visible.
*/
hasJoinByPhoneButton: boolean,
2020-04-16 10:47:10 +00:00
/**
* Joins the current meeting.
*/
joinConference: Function,
/**
* Joins the current meeting without audio.
*/
joinConferenceWithoutAudio: Function,
/**
* The name of the user that is about to join.
*/
name: string,
/**
* Updates settings.
2020-04-16 10:47:10 +00:00
*/
updateSettings: Function,
2020-04-16 10:47:10 +00:00
/**
* The name of the meeting that is about to be joined.
*/
roomName: string,
/**
* Sets visibility of the prejoin page for the next sessions.
*/
setSkipPrejoin: Function,
/**
* Sets visibility of the 'JoinByPhoneDialog'.
2020-04-16 10:47:10 +00:00
*/
setJoinByPhoneDialogVisiblity: Function,
2020-06-29 07:45:58 +00:00
/**
* Indicates whether the avatar should be shown when video is off
*/
showAvatar: boolean,
2020-05-20 08:25:31 +00:00
/**
* Flag signaling the visibility of camera preview.
*/
showCameraPreview: boolean,
/**
* If should show an error when joining without a name.
*/
showErrorOnJoin: boolean,
2020-06-29 07:45:58 +00:00
/**
* Flag signaling the visibility of join label, input and buttons
*/
showJoinActions: boolean,
2020-08-04 22:25:16 +00:00
/**
* Flag signaling the visibility of the conference URL section.
*/
showConferenceInfo: boolean,
2020-04-16 10:47:10 +00:00
/**
* If 'JoinByPhoneDialog' is visible or not.
*/
showDialog: boolean,
/**
* Flag signaling the visibility of the skip prejoin toggle
*/
showSkipPrejoin: boolean,
2020-04-16 10:47:10 +00:00
/**
* Used for translation.
*/
t: Function,
2020-05-20 08:25:31 +00:00
/**
* The JitsiLocalTrack to display.
*/
videoTrack: ?Object,
2020-04-16 10:47:10 +00:00
};
type State = {
/**
* Flag controlling the visibility of the error label.
*/
showError: boolean,
/**
* Flag controlling the visibility of the 'join by phone' buttons.
*/
showJoinByPhoneButtons: boolean
}
2020-04-16 10:47:10 +00:00
/**
* This component is displayed before joining a meeting.
*/
class Prejoin extends Component<Props, State> {
2020-06-29 07:45:58 +00:00
/**
* Default values for {@code Prejoin} component's properties.
*
* @static
*/
static defaultProps = {
2020-08-04 22:25:16 +00:00
showConferenceInfo: true,
showJoinActions: true,
showSkipPrejoin: true
2020-06-29 07:45:58 +00:00
};
2020-04-16 10:47:10 +00:00
/**
* Initializes a new {@code Prejoin} instance.
*
* @inheritdoc
*/
constructor(props) {
super(props);
this.state = {
showError: false,
showJoinByPhoneButtons: false
};
this._closeDialog = this._closeDialog.bind(this);
2020-04-16 10:47:10 +00:00
this._showDialog = this._showDialog.bind(this);
this._onJoinButtonClick = this._onJoinButtonClick.bind(this);
this._onToggleButtonClick = this._onToggleButtonClick.bind(this);
this._onDropdownClose = this._onDropdownClose.bind(this);
this._onOptionsClick = this._onOptionsClick.bind(this);
this._setName = this._setName.bind(this);
}
_onJoinButtonClick: () => void;
/**
* Handler for the join button.
*
* @param {Object} e - The synthetic event.
* @returns {void}
*/
_onJoinButtonClick() {
if (this.props.showErrorOnJoin) {
this.setState({
showError: true
});
return;
}
this.setState({ showError: false });
this.props.joinConference();
}
_onToggleButtonClick: () => void;
/**
* Handler for the toggle button.
*
* @param {Object} e - The synthetic event.
* @returns {void}
*/
_onToggleButtonClick() {
this.props.setSkipPrejoin(!this.props.buttonIsToggled);
}
_onDropdownClose: () => void;
/**
* Closes the dropdown.
*
* @returns {void}
*/
_onDropdownClose() {
this.setState({
showJoinByPhoneButtons: false
});
}
_onOptionsClick: () => void;
/**
* Displays the join by phone buttons dropdown.
*
* @param {Object} e - The synthetic event.
* @returns {void}
*/
_onOptionsClick(e) {
e.stopPropagation();
this.setState({
showJoinByPhoneButtons: !this.state.showJoinByPhoneButtons
});
}
_setName: () => void;
/**
* Sets the guest participant name.
*
* @param {string} displayName - Participant name.
* @returns {void}
*/
_setName(displayName) {
this.props.updateSettings({
displayName
});
2020-04-16 10:47:10 +00:00
}
_closeDialog: () => void;
/**
* Closes the join by phone dialog.
*
* @returns {undefined}
*/
_closeDialog() {
this.props.setJoinByPhoneDialogVisiblity(false);
}
2020-04-16 10:47:10 +00:00
_showDialog: () => void;
/**
* Displays the dialog for joining a meeting by phone.
*
* @returns {undefined}
*/
_showDialog() {
this.props.setJoinByPhoneDialogVisiblity(true);
this._onDropdownClose();
2020-04-16 10:47:10 +00:00
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const {
hasJoinByPhoneButton,
2020-04-16 10:47:10 +00:00
joinConference,
joinConferenceWithoutAudio,
name,
2020-06-29 07:45:58 +00:00
showAvatar,
2020-05-20 08:25:31 +00:00
showCameraPreview,
showDialog,
2020-08-04 22:25:16 +00:00
showConferenceInfo,
2020-06-29 07:45:58 +00:00
showJoinActions,
2020-05-20 08:25:31 +00:00
t,
videoTrack
2020-04-16 10:47:10 +00:00
} = this.props;
const { _closeDialog, _onDropdownClose, _onJoinButtonClick, _onOptionsClick, _setName, _showDialog } = this;
const { showJoinByPhoneButtons, showError } = this.state;
2020-04-16 10:47:10 +00:00
return (
2020-05-20 08:25:31 +00:00
<PreMeetingScreen
footer = { this._renderFooter() }
name = { name }
2020-06-29 07:45:58 +00:00
showAvatar = { showAvatar }
2020-08-04 22:25:16 +00:00
showConferenceInfo = { showConferenceInfo }
skipPrejoinButton = { this._renderSkipPrejoinButton() }
2020-05-20 08:25:31 +00:00
title = { t('prejoin.joinMeeting') }
videoMuted = { !showCameraPreview }
videoTrack = { videoTrack }>
2020-06-29 07:45:58 +00:00
{showJoinActions && (
<div className = 'prejoin-input-area-container'>
<div className = 'prejoin-input-area'>
<InputField
autoFocus = { true }
2020-09-29 11:00:30 +00:00
className = { showError ? 'error' : '' }
hasError = { showError }
2020-06-29 07:45:58 +00:00
onChange = { _setName }
onSubmit = { joinConference }
placeHolder = { t('dialog.enterDisplayName') }
value = { name } />
{showError && <div
className = 'prejoin-error'
data-testid = 'prejoin.errorMessage'>{t('prejoin.errorMissingName')}</div>}
2020-06-29 07:45:58 +00:00
<div className = 'prejoin-preview-dropdown-container'>
<InlineDialog
content = { <div className = 'prejoin-preview-dropdown-btns'>
<div
className = 'prejoin-preview-dropdown-btn'
2020-08-18 11:36:32 +00:00
data-testid = 'prejoin.joinWithoutAudio'
2020-06-29 07:45:58 +00:00
onClick = { joinConferenceWithoutAudio }>
<Icon
className = 'prejoin-preview-dropdown-icon'
size = { 24 }
src = { IconVolumeOff } />
{ t('prejoin.joinWithoutAudio') }
</div>
{hasJoinByPhoneButton && <div
className = 'prejoin-preview-dropdown-btn'
onClick = { _showDialog }>
<Icon
className = 'prejoin-preview-dropdown-icon'
2020-08-18 11:36:32 +00:00
data-testid = 'prejoin.joinByPhone'
2020-06-29 07:45:58 +00:00
size = { 24 }
src = { IconPhone } />
{ t('prejoin.joinAudioByPhone') }
</div>}
</div> }
isOpen = { showJoinByPhoneButtons }
onClose = { _onDropdownClose }>
<ActionButton
2020-09-29 11:00:30 +00:00
OptionsIcon = { showJoinByPhoneButtons ? IconArrowUp : IconArrowDown }
2020-06-29 07:45:58 +00:00
hasOptions = { true }
onClick = { _onJoinButtonClick }
2020-06-29 07:45:58 +00:00
onOptionsClick = { _onOptionsClick }
testId = 'prejoin.joinMeeting'
2020-06-29 07:45:58 +00:00
type = 'primary'>
{ t('prejoin.joinMeeting') }
</ActionButton>
</InlineDialog>
</div>
</div>
2020-04-16 10:47:10 +00:00
</div>
2020-06-29 07:45:58 +00:00
)}
{ showDialog && (
<JoinByPhoneDialog
joinConferenceWithoutAudio = { joinConferenceWithoutAudio }
onClose = { _closeDialog } />
)}
2020-05-20 08:25:31 +00:00
</PreMeetingScreen>
2020-04-16 10:47:10 +00:00
);
}
2020-05-20 08:25:31 +00:00
/**
* Renders the screen footer if any.
*
* @returns {React$Element}
*/
_renderFooter() {
return this.props.deviceStatusVisible && <DeviceStatus />;
}
/**
* Renders the 'skip prejoin' button.
*
* @returns {React$Element}
*/
_renderSkipPrejoinButton() {
const { buttonIsToggled, t, showSkipPrejoin } = this.props;
if (!showSkipPrejoin) {
return null;
}
return (
<div className = 'prejoin-checkbox-container'>
<ToggleButton
isToggled = { buttonIsToggled }
onClick = { this._onToggleButtonClick }>
{t('prejoin.doNotShow')}
</ToggleButton>
</div>
);
}
2020-04-16 10:47:10 +00:00
}
/**
* Maps (parts of) the redux state to the React {@code Component} props.
*
* @param {Object} state - The redux state.
2020-08-04 22:25:16 +00:00
* @param {Object} ownProps - The props passed to the component.
2020-04-16 10:47:10 +00:00
* @returns {Object}
*/
2020-08-04 22:25:16 +00:00
function mapStateToProps(state, ownProps): Object {
const name = getDisplayName(state);
const showErrorOnJoin = isDisplayNameRequired(state) && !name;
2020-08-04 22:25:16 +00:00
const { showJoinActions } = ownProps;
const isInviteButtonEnabled = isButtonEnabled('invite');
// Hide conference info when interfaceConfig is available and the invite button is disabled.
// In all other cases we want to preserve the behaviour and control the the conference info
// visibility trough showJoinActions.
const showConferenceInfo
= typeof isInviteButtonEnabled === 'undefined' || isInviteButtonEnabled === true
? showJoinActions
: false;
2020-04-16 10:47:10 +00:00
return {
buttonIsToggled: isPrejoinSkipped(state),
name,
2020-04-16 10:47:10 +00:00
deviceStatusVisible: isDeviceStatusVisible(state),
roomName: getRoomName(state),
showDialog: isJoinByPhoneDialogVisible(state),
showErrorOnJoin,
2020-05-20 08:25:31 +00:00
hasJoinByPhoneButton: isJoinByPhoneButtonVisible(state),
showCameraPreview: !isVideoMutedByUser(state),
2020-08-04 22:25:16 +00:00
showConferenceInfo,
videoTrack: getLocalJitsiVideoTrack(state)
2020-04-16 10:47:10 +00:00
};
}
const mapDispatchToProps = {
joinConferenceWithoutAudio: joinConferenceWithoutAudioAction,
joinConference: joinConferenceAction,
setJoinByPhoneDialogVisiblity: setJoinByPhoneDialogVisiblityAction,
setSkipPrejoin: setSkipPrejoinAction,
updateSettings
2020-04-16 10:47:10 +00:00
};
export default connect(mapStateToProps, mapDispatchToProps)(translate(Prejoin));