// @flow import InlineDialog from '@atlaskit/inline-dialog'; import React, { Component } from 'react'; import { LoginDialog, WaitForOwnerDialog } from '../../authentication/components'; import { Avatar } from '../../base/avatar'; import { isNameReadOnly } from '../../base/config'; import { isDialogOpen } from '../../base/dialog/functions'; import { translate } from '../../base/i18n'; import { IconArrowDown, IconArrowUp, IconPhone, IconVolumeOff } from '../../base/icons'; import { isVideoMutedByUser } from '../../base/media'; import { getLocalParticipant } from '../../base/participants'; import { ActionButton, InputField, PreMeetingScreen } from '../../base/premeeting'; import { connect } from '../../base/redux'; import { getDisplayName, updateSettings } from '../../base/settings'; import { getLocalJitsiVideoTrack } from '../../base/tracks'; import { getIsLobbyVisible } from '../../lobby/functions'; import { PasswordRequiredPrompt } from '../../room-lock/components'; import { joinConference as joinConferenceAction, joinConferenceWithoutAudio as joinConferenceWithoutAudioAction, setJoinByPhoneDialogVisiblity as setJoinByPhoneDialogVisiblityAction } from '../actions'; import { isDeviceStatusVisible, isDisplayNameRequired, isJoinByPhoneButtonVisible, isJoinByPhoneDialogVisible, isPrejoinDisplayNameVisible } from '../functions'; import DropdownButton from './DropdownButton'; import JoinByPhoneDialog from './dialogs/JoinByPhoneDialog'; type Props = { /** * Indicates whether the display name is editable. */ canEditDisplayName: boolean, /** * Flag signaling if the device status is visible or not. */ deviceStatusVisible: boolean, /** * If join by phone button should be visible. */ hasJoinByPhoneButton: boolean, /** * Whether authentication is taking place or not. */ isAuthInProgress: boolean, /** * 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. */ updateSettings: Function, /** * Local participant id. */ participantId: string, /** * The prejoin config. */ prejoinConfig?: Object, /** * Whether the name input should be read only or not. */ readOnlyName: boolean, /** * Sets visibility of the 'JoinByPhoneDialog'. */ setJoinByPhoneDialogVisiblity: Function, /** * Flag signaling the visibility of camera preview. */ showCameraPreview: boolean, /** * If should show an error when joining without a name. */ showErrorOnJoin: boolean, /** * If 'JoinByPhoneDialog' is visible or not. */ showDialog: boolean, /** * Used for translation. */ t: Function, /** * The JitsiLocalTrack to display. */ videoTrack: ?Object }; type State = { /** * Flag controlling the visibility of the error label. */ showError: boolean, /** * Flag controlling the visibility of the 'join by phone' buttons. */ showJoinByPhoneButtons: boolean } /** * This component is displayed before joining a meeting. */ class Prejoin extends Component { /** * Initializes a new {@code Prejoin} instance. * * @inheritdoc */ constructor(props) { super(props); this.state = { showError: false, showJoinByPhoneButtons: false }; this._closeDialog = this._closeDialog.bind(this); this._showDialog = this._showDialog.bind(this); this._onJoinButtonClick = this._onJoinButtonClick.bind(this); this._onDropdownClose = this._onDropdownClose.bind(this); this._onOptionsClick = this._onOptionsClick.bind(this); this._setName = this._setName.bind(this); this._onJoinConferenceWithoutAudioKeyPress = this._onJoinConferenceWithoutAudioKeyPress.bind(this); this._showDialogKeyPress = this._showDialogKeyPress.bind(this); this._onJoinKeyPress = this._onJoinKeyPress.bind(this); this._getExtraJoinButtons = this._getExtraJoinButtons.bind(this); this.showDisplayNameField = props.canEditDisplayName || props.showErrorOnJoin; } _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(); } _onJoinKeyPress: (Object) => void; /** * KeyPress handler for accessibility. * * @param {Object} e - The key event to handle. * * @returns {void} */ _onJoinKeyPress(e) { if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); this._onJoinButtonClick(); } } _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 }); } _closeDialog: () => void; /** * Closes the join by phone dialog. * * @returns {undefined} */ _closeDialog() { this.props.setJoinByPhoneDialogVisiblity(false); } _showDialog: () => void; /** * Displays the dialog for joining a meeting by phone. * * @returns {undefined} */ _showDialog() { this.props.setJoinByPhoneDialogVisiblity(true); this._onDropdownClose(); } _showDialogKeyPress: (Object) => void; /** * KeyPress handler for accessibility. * * @param {Object} e - The key event to handle. * * @returns {void} */ _showDialogKeyPress(e) { if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); this._showDialog(); } } _onJoinConferenceWithoutAudioKeyPress: (Object) => void; /** * KeyPress handler for accessibility. * * @param {Object} e - The key event to handle. * * @returns {void} */ _onJoinConferenceWithoutAudioKeyPress(e) { if (this.props.joinConferenceWithoutAudio && (e.key === ' ' || e.key === 'Enter')) { e.preventDefault(); this.props.joinConferenceWithoutAudio(); } } _getExtraJoinButtons: () => Object; /** * Gets the list of extra join buttons. * * @returns {Object} - The list of extra buttons. */ _getExtraJoinButtons() { const { joinConferenceWithoutAudio, t } = this.props; const noAudio = { key: 'no-audio', dataTestId: 'prejoin.joinWithoutAudio', icon: IconVolumeOff, label: t('prejoin.joinWithoutAudio'), onButtonClick: joinConferenceWithoutAudio, onKeyPressed: this._onJoinConferenceWithoutAudioKeyPress }; const byPhone = { key: 'by-phone', dataTestId: 'prejoin.joinByPhone', icon: IconPhone, label: t('prejoin.joinAudioByPhone'), onButtonClick: this._showDialog, onKeyPressed: this._showDialogKeyPress }; return { noAudio, byPhone }; } /** * Implements React's {@link Component#render()}. * * @inheritdoc * @returns {ReactElement} */ render() { const { deviceStatusVisible, hasJoinByPhoneButton, isAuthInProgress, joinConference, joinConferenceWithoutAudio, name, participantId, prejoinConfig, readOnlyName, showCameraPreview, showDialog, t, videoTrack } = this.props; const { _closeDialog, _onDropdownClose, _onJoinButtonClick, _onJoinKeyPress, _onOptionsClick, _setName } = this; const extraJoinButtons = this._getExtraJoinButtons(); let extraButtonsToRender = Object.values(extraJoinButtons).filter((val: Object) => !(prejoinConfig?.hideExtraJoinButtons || []).includes(val.key) ); if (!hasJoinByPhoneButton) { extraButtonsToRender = extraButtonsToRender.filter((btn: Object) => btn.key !== 'by-phone'); } const hasExtraJoinButtons = Boolean(extraButtonsToRender.length); const { showJoinByPhoneButtons, showError } = this.state; return (
{this.showDisplayNameField ? ( ) : (
{name}
)} {showError &&
{t('prejoin.errorMissingName')}
}
{extraButtonsToRender.map(({ key, ...rest }: Object) => ( ))}
} isOpen = { showJoinByPhoneButtons } onClose = { _onDropdownClose }> { t('prejoin.joinMeeting') }
{ showDialog && ( )}
); } } /** * Maps (parts of) the redux state to the React {@code Component} props. * * @param {Object} state - The redux state. * @returns {Object} */ function mapStateToProps(state): Object { const name = getDisplayName(state); const showErrorOnJoin = isDisplayNameRequired(state) && !name; const { id: participantId } = getLocalParticipant(state); const isLobbyVisible = getIsLobbyVisible(state); const isAuthInProgress = isDialogOpen(state, WaitForOwnerDialog) || isDialogOpen(state, LoginDialog) || isDialogOpen(state, PasswordRequiredPrompt) || isLobbyVisible; return { canEditDisplayName: isPrejoinDisplayNameVisible(state), deviceStatusVisible: isDeviceStatusVisible(state), hasJoinByPhoneButton: isJoinByPhoneButtonVisible(state), isAuthInProgress, name, participantId, prejoinConfig: state['features/base/config'].prejoinConfig, readOnlyName: isNameReadOnly(state), showCameraPreview: !isVideoMutedByUser(state), showDialog: isJoinByPhoneDialogVisible(state), showErrorOnJoin, videoTrack: getLocalJitsiVideoTrack(state) }; } const mapDispatchToProps = { joinConferenceWithoutAudio: joinConferenceWithoutAudioAction, joinConference: joinConferenceAction, setJoinByPhoneDialogVisiblity: setJoinByPhoneDialogVisiblityAction, updateSettings }; export default connect(mapStateToProps, mapDispatchToProps)(translate(Prejoin));