mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-05-14 14:07:50 +00:00
feat(prejoin_page): Add prejoin page
This commit is contained in:
committed by
Saúl Ibarra Corretgé
parent
5b53232964
commit
a45cbf41ef
65
react/features/prejoin/actionTypes.js
Normal file
65
react/features/prejoin/actionTypes.js
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Action type to add a video track to the store.
|
||||
*/
|
||||
export const ADD_PREJOIN_VIDEO_TRACK = 'ADD_PREJOIN_VIDEO_TRACK';
|
||||
|
||||
/**
|
||||
* Action type to add an audio track to the store.
|
||||
*/
|
||||
export const ADD_PREJOIN_AUDIO_TRACK = 'ADD_PREJOIN_AUDIO_TRACK';
|
||||
|
||||
/**
|
||||
* Action type to add a content sharing track to the store.
|
||||
*/
|
||||
export const ADD_PREJOIN_CONTENT_SHARING_TRACK
|
||||
= 'ADD_PREJOIN_CONTENT_SHARING_TRACK';
|
||||
|
||||
/**
|
||||
* Action type to signal the start of the conference.
|
||||
*/
|
||||
export const PREJOIN_START_CONFERENCE = 'PREJOIN_START_CONFERENCE';
|
||||
|
||||
/**
|
||||
* Action type to set the status of the device.
|
||||
*/
|
||||
export const SET_DEVICE_STATUS = 'SET_DEVICE_STATUS';
|
||||
|
||||
/**
|
||||
* Action type to set the visiblity of the 'JoinByPhone' dialog.
|
||||
*/
|
||||
export const SET_JOIN_BY_PHONE_DIALOG_VISIBLITY = 'SET_JOIN_BY_PHONE_DIALOG_VISIBLITY';
|
||||
|
||||
/**
|
||||
* Action type to disable the audio while on prejoin page.
|
||||
*/
|
||||
export const SET_PREJOIN_AUDIO_DISABLED = 'SET_PREJOIN_AUDIO_DISABLED';
|
||||
|
||||
/**
|
||||
* Action type to mute/unmute the audio while on prejoin page.
|
||||
*/
|
||||
export const SET_PREJOIN_AUDIO_MUTED = 'SET_PREJOIN_AUDIO_MUTED';
|
||||
|
||||
/**
|
||||
* Action type to set the errors while creating the prejoin streams.
|
||||
*/
|
||||
export const SET_PREJOIN_DEVICE_ERRORS = 'SET_PREJOIN_DEVICE_ERRORS';
|
||||
|
||||
/**
|
||||
* Action type to set the name of the user.
|
||||
*/
|
||||
export const SET_PREJOIN_NAME = 'SET_PREJOIN_NAME';
|
||||
|
||||
/**
|
||||
* Action type to set the visibility of the prejoin page.
|
||||
*/
|
||||
export const SET_PREJOIN_PAGE_VISIBILITY = 'SET_PREJOIN_PAGE_VISIBILITY';
|
||||
|
||||
/**
|
||||
* Action type to mute/unmute the video while on prejoin page.
|
||||
*/
|
||||
export const SET_PREJOIN_VIDEO_DISABLED = 'SET_PREJOIN_VIDEO_DISABLED';
|
||||
|
||||
/**
|
||||
* Action type to mute/unmute the video while on prejoin page.
|
||||
*/
|
||||
export const SET_PREJOIN_VIDEO_MUTED = 'SET_PREJOIN_VIDEO_MUTED';
|
||||
338
react/features/prejoin/actions.js
Normal file
338
react/features/prejoin/actions.js
Normal file
@@ -0,0 +1,338 @@
|
||||
// @flow
|
||||
|
||||
import {
|
||||
ADD_PREJOIN_AUDIO_TRACK,
|
||||
ADD_PREJOIN_CONTENT_SHARING_TRACK,
|
||||
ADD_PREJOIN_VIDEO_TRACK,
|
||||
PREJOIN_START_CONFERENCE,
|
||||
SET_DEVICE_STATUS,
|
||||
SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
|
||||
SET_PREJOIN_AUDIO_DISABLED,
|
||||
SET_PREJOIN_AUDIO_MUTED,
|
||||
SET_PREJOIN_DEVICE_ERRORS,
|
||||
SET_PREJOIN_NAME,
|
||||
SET_PREJOIN_PAGE_VISIBILITY,
|
||||
SET_PREJOIN_VIDEO_DISABLED,
|
||||
SET_PREJOIN_VIDEO_MUTED
|
||||
} from './actionTypes';
|
||||
import { createLocalTrack } from '../base/lib-jitsi-meet';
|
||||
import { getAudioTrack, getVideoTrack } from './functions';
|
||||
import logger from './logger';
|
||||
|
||||
/**
|
||||
* Action used to add an audio track to the store.
|
||||
*
|
||||
* @param {Object} value - The track to be added.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function addPrejoinAudioTrack(value: Object) {
|
||||
return {
|
||||
type: ADD_PREJOIN_AUDIO_TRACK,
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action used to add a video track to the store.
|
||||
*
|
||||
* @param {Object} value - The track to be added.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function addPrejoinVideoTrack(value: Object) {
|
||||
return {
|
||||
type: ADD_PREJOIN_VIDEO_TRACK,
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action used to add a content sharing track to the store.
|
||||
*
|
||||
* @param {Object} value - The track to be added.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function addPrejoinContentSharingTrack(value: Object) {
|
||||
return {
|
||||
type: ADD_PREJOIN_CONTENT_SHARING_TRACK,
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all the newly created tracks to store on init.
|
||||
*
|
||||
* @param {Object[]} tracks - The newly created tracks.
|
||||
* @param {Object} errors - The errors from creating the tracks.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function initPrejoin(tracks: Object[], errors: Object) {
|
||||
return async function(dispatch: Function) {
|
||||
const audioTrack = tracks.find(t => t.isAudioTrack());
|
||||
const videoTrack = tracks.find(t => t.isVideoTrack());
|
||||
|
||||
dispatch(setPrejoinDeviceErrors(errors));
|
||||
|
||||
if (audioTrack) {
|
||||
dispatch(addPrejoinAudioTrack(audioTrack));
|
||||
} else {
|
||||
dispatch(setAudioDisabled());
|
||||
}
|
||||
|
||||
if (videoTrack) {
|
||||
if (videoTrack.videoType === 'desktop') {
|
||||
dispatch(addPrejoinContentSharingTrack(videoTrack));
|
||||
dispatch(setPrejoinVideoDisabled(true));
|
||||
} else {
|
||||
dispatch(addPrejoinVideoTrack(videoTrack));
|
||||
}
|
||||
} else {
|
||||
dispatch(setPrejoinVideoDisabled(true));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins the conference.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function joinConference() {
|
||||
return function(dispatch: Function) {
|
||||
dispatch(setPrejoinPageVisibility(false));
|
||||
dispatch(startConference());
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins the conference without audio.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function joinConferenceWithoutAudio() {
|
||||
return async function(dispatch: Function, getState: Function) {
|
||||
const audioTrack = getAudioTrack(getState());
|
||||
|
||||
if (audioTrack) {
|
||||
await dispatch(replacePrejoinAudioTrack(null));
|
||||
}
|
||||
dispatch(setAudioDisabled());
|
||||
dispatch(joinConference());
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the existing audio track with a new one.
|
||||
*
|
||||
* @param {Object} track - The new track.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function replacePrejoinAudioTrack(track: Object) {
|
||||
return async (dispatch: Function, getState: Function) => {
|
||||
const oldTrack = getAudioTrack(getState());
|
||||
|
||||
oldTrack && await oldTrack.dispose();
|
||||
dispatch(addPrejoinAudioTrack(track));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new audio track based on a device id and replaces the current one.
|
||||
*
|
||||
* @param {string} deviceId - The deviceId of the microphone.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function replaceAudioTrackById(deviceId: string) {
|
||||
return async (dispatch: Function) => {
|
||||
try {
|
||||
const track = await createLocalTrack('audio', deviceId);
|
||||
|
||||
dispatch(replacePrejoinAudioTrack(track));
|
||||
} catch (err) {
|
||||
dispatch(setDeviceStatusWarning('prejoin.audioTrackError'));
|
||||
logger.log('Error replacing audio track', err);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the existing video track with a new one.
|
||||
*
|
||||
* @param {Object} track - The new track.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function replacePrejoinVideoTrack(track: Object) {
|
||||
return async (dispatch: Function, getState: Function) => {
|
||||
const oldTrack = getVideoTrack(getState());
|
||||
|
||||
oldTrack && await oldTrack.dispose();
|
||||
dispatch(addPrejoinVideoTrack(track));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new video track based on a device id and replaces the current one.
|
||||
*
|
||||
* @param {string} deviceId - The deviceId of the camera.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function replaceVideoTrackById(deviceId: Object) {
|
||||
return async (dispatch: Function) => {
|
||||
try {
|
||||
const track = await createLocalTrack('video', deviceId);
|
||||
|
||||
dispatch(replacePrejoinVideoTrack(track));
|
||||
} catch (err) {
|
||||
dispatch(setDeviceStatusWarning('prejoin.videoTrackError'));
|
||||
logger.log('Error replacing video track', err);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Action used to mark audio muted.
|
||||
*
|
||||
* @param {boolean} value - True for muted.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function setPrejoinAudioMuted(value: boolean) {
|
||||
return {
|
||||
type: SET_PREJOIN_AUDIO_MUTED,
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action used to mark video disabled.
|
||||
*
|
||||
* @param {boolean} value - True for muted.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function setPrejoinVideoDisabled(value: boolean) {
|
||||
return {
|
||||
type: SET_PREJOIN_VIDEO_DISABLED,
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Action used to mark video muted.
|
||||
*
|
||||
* @param {boolean} value - True for muted.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function setPrejoinVideoMuted(value: boolean) {
|
||||
return {
|
||||
type: SET_PREJOIN_VIDEO_MUTED,
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action used to mark audio as disabled.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function setAudioDisabled() {
|
||||
return {
|
||||
type: SET_PREJOIN_AUDIO_DISABLED
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the device status as OK with the corresponding text.
|
||||
*
|
||||
* @param {string} deviceStatusText - The text to be set.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function setDeviceStatusOk(deviceStatusText: string) {
|
||||
return {
|
||||
type: SET_DEVICE_STATUS,
|
||||
value: {
|
||||
deviceStatusText,
|
||||
deviceStatusType: 'ok'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the device status as 'warning' with the corresponding text.
|
||||
*
|
||||
* @param {string} deviceStatusText - The text to be set.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function setDeviceStatusWarning(deviceStatusText: string) {
|
||||
return {
|
||||
type: SET_DEVICE_STATUS,
|
||||
value: {
|
||||
deviceStatusText,
|
||||
deviceStatusType: 'warning'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Action used to set the visiblitiy of the 'JoinByPhoneDialog'.
|
||||
*
|
||||
* @param {boolean} value - The value.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function setJoinByPhoneDialogVisiblity(value: boolean) {
|
||||
return {
|
||||
type: SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action used to set the initial errors after creating the tracks.
|
||||
*
|
||||
* @param {Object} value - The track errors.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function setPrejoinDeviceErrors(value: Object) {
|
||||
return {
|
||||
type: SET_PREJOIN_DEVICE_ERRORS,
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action used to set the name of the guest user.
|
||||
*
|
||||
* @param {string} value - The name.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function setPrejoinName(value: string) {
|
||||
return {
|
||||
type: SET_PREJOIN_NAME,
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action used to set the visiblity of the prejoin page.
|
||||
*
|
||||
* @param {boolean} value - The value.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function setPrejoinPageVisibility(value: boolean) {
|
||||
return {
|
||||
type: SET_PREJOIN_PAGE_VISIBILITY,
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action used to mark the start of the conference.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
function startConference() {
|
||||
return {
|
||||
type: PREJOIN_START_CONFERENCE
|
||||
};
|
||||
}
|
||||
197
react/features/prejoin/components/Prejoin.js
Normal file
197
react/features/prejoin/components/Prejoin.js
Normal file
@@ -0,0 +1,197 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
joinConference as joinConferenceAction,
|
||||
joinConferenceWithoutAudio as joinConferenceWithoutAudioAction,
|
||||
setJoinByPhoneDialogVisiblity as setJoinByPhoneDialogVisiblityAction,
|
||||
setPrejoinName
|
||||
} from '../actions';
|
||||
import { getRoomName } from '../../base/conference';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { connect } from '../../base/redux';
|
||||
import ActionButton from './buttons/ActionButton';
|
||||
import {
|
||||
areJoinByPhoneButtonsVisible,
|
||||
getPrejoinName,
|
||||
isDeviceStatusVisible,
|
||||
isJoinByPhoneDialogVisible
|
||||
} from '../functions';
|
||||
import { isGuest } from '../../invite';
|
||||
import CopyMeetingUrl from './preview/CopyMeetingUrl';
|
||||
import DeviceStatus from './preview/DeviceStatus';
|
||||
import ParticipantName from './preview/ParticipantName';
|
||||
import Preview from './preview/Preview';
|
||||
import { VideoSettingsButton, AudioSettingsButton } from '../../toolbox';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Flag signaling if the device status is visible or not.
|
||||
*/
|
||||
deviceStatusVisible: boolean,
|
||||
|
||||
/**
|
||||
* Flag signaling if a user is logged in or not.
|
||||
*/
|
||||
isAnonymousUser: 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,
|
||||
|
||||
/**
|
||||
* Sets the name for the joining user.
|
||||
*/
|
||||
setName: Function,
|
||||
|
||||
/**
|
||||
* The name of the meeting that is about to be joined.
|
||||
*/
|
||||
roomName: string,
|
||||
|
||||
/**
|
||||
* Sets visibilit of the 'JoinByPhoneDialog'.
|
||||
*/
|
||||
setJoinByPhoneDialogVisiblity: Function,
|
||||
|
||||
/**
|
||||
* If 'JoinByPhoneDialog' is visible or not.
|
||||
*/
|
||||
showDialog: boolean,
|
||||
|
||||
/**
|
||||
* If join by phone buttons should be visible.
|
||||
*/
|
||||
showJoinByPhoneButtons: boolean,
|
||||
|
||||
/**
|
||||
* Used for translation.
|
||||
*/
|
||||
t: Function,
|
||||
};
|
||||
|
||||
/**
|
||||
* This component is displayed before joining a meeting.
|
||||
*/
|
||||
class Prejoin extends Component<Props> {
|
||||
/**
|
||||
* Initializes a new {@code Prejoin} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._showDialog = this._showDialog.bind(this);
|
||||
}
|
||||
|
||||
_showDialog: () => void;
|
||||
|
||||
/**
|
||||
* Displays the dialog for joining a meeting by phone.
|
||||
*
|
||||
* @returns {undefined}
|
||||
*/
|
||||
_showDialog() {
|
||||
this.props.setJoinByPhoneDialogVisiblity(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
deviceStatusVisible,
|
||||
isAnonymousUser,
|
||||
joinConference,
|
||||
joinConferenceWithoutAudio,
|
||||
name,
|
||||
setName,
|
||||
showJoinByPhoneButtons,
|
||||
t
|
||||
} = this.props;
|
||||
const { _showDialog } = this;
|
||||
|
||||
return (
|
||||
<div className = 'prejoin-full-page'>
|
||||
<Preview />
|
||||
<div className = 'prejoin-input-area-container'>
|
||||
<div className = 'prejoin-input-area'>
|
||||
<div className = 'prejoin-title'>
|
||||
{t('prejoin.joinMeeting')}
|
||||
</div>
|
||||
<CopyMeetingUrl />
|
||||
<ParticipantName
|
||||
isEditable = { isAnonymousUser }
|
||||
setName = { setName }
|
||||
value = { name } />
|
||||
<ActionButton
|
||||
onClick = { joinConference }
|
||||
type = 'primary'>
|
||||
{ t('calendarSync.join') }
|
||||
</ActionButton>
|
||||
{showJoinByPhoneButtons
|
||||
&& <div className = 'prejoin-text-btns'>
|
||||
<ActionButton
|
||||
onClick = { joinConferenceWithoutAudio }
|
||||
type = 'text'>
|
||||
{ t('prejoin.joinWithoutAudio') }
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
onClick = { _showDialog }
|
||||
type = 'text'>
|
||||
{ t('prejoin.joinAudioByPhone') }
|
||||
</ActionButton>
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
||||
<div className = 'prejoin-preview-btn-container'>
|
||||
<AudioSettingsButton visible = { true } />
|
||||
<VideoSettingsButton visible = { true } />
|
||||
</div>
|
||||
{ deviceStatusVisible && <DeviceStatus /> }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the React {@code Component} props.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function mapStateToProps(state): Object {
|
||||
return {
|
||||
isAnonymousUser: isGuest(state),
|
||||
deviceStatusVisible: isDeviceStatusVisible(state),
|
||||
name: getPrejoinName(state),
|
||||
roomName: getRoomName(state),
|
||||
showDialog: isJoinByPhoneDialogVisible(state),
|
||||
showJoinByPhoneButtons: areJoinByPhoneButtonsVisible(state)
|
||||
};
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
joinConferenceWithoutAudio: joinConferenceWithoutAudioAction,
|
||||
joinConference: joinConferenceAction,
|
||||
setJoinByPhoneDialogVisiblity: setJoinByPhoneDialogVisiblityAction,
|
||||
setName: setPrejoinName
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(translate(Prejoin));
|
||||
51
react/features/prejoin/components/buttons/ActionButton.js
Normal file
51
react/features/prejoin/components/buttons/ActionButton.js
Normal file
@@ -0,0 +1,51 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
const classNameByType = {
|
||||
primary: 'prejoin-btn--primary',
|
||||
secondary: 'prejoin-btn--secondary',
|
||||
text: 'prejoin-btn--text'
|
||||
};
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Text of the button.
|
||||
*/
|
||||
children: React$Node,
|
||||
|
||||
/**
|
||||
* Text css class of the button.
|
||||
*/
|
||||
className?: string,
|
||||
|
||||
/**
|
||||
* The type of th button: primary, secondary, text.
|
||||
*/
|
||||
type: string,
|
||||
|
||||
/**
|
||||
* OnClick button handler.
|
||||
*/
|
||||
onClick: Function,
|
||||
};
|
||||
|
||||
/**
|
||||
* Button used for prejoin actions: Join/Join without audio/Join by phone.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
function ActionButton({ children, className, type, onClick }: Props) {
|
||||
const ownClassName = `prejoin-btn ${classNameByType[type]}`;
|
||||
const cls = className ? `${className} ${ownClassName}` : ownClassName;
|
||||
|
||||
return (
|
||||
<div
|
||||
className = { cls }
|
||||
onClick = { onClick }>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ActionButton;
|
||||
197
react/features/prejoin/components/preview/CopyMeetingUrl.js
Normal file
197
react/features/prejoin/components/preview/CopyMeetingUrl.js
Normal file
@@ -0,0 +1,197 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { getCurrentConferenceUrl } from '../../../base/connection';
|
||||
import { Icon, IconCopy, IconCheck } from '../../../base/icons';
|
||||
import logger from '../../logger';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The meeting url.
|
||||
*/
|
||||
url: string,
|
||||
|
||||
/**
|
||||
* Used for translation.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* If true it shows the 'copy link' message.
|
||||
*/
|
||||
showCopyLink: boolean,
|
||||
|
||||
/**
|
||||
* If true it shows the 'link copied' message.
|
||||
*/
|
||||
showLinkCopied: boolean,
|
||||
};
|
||||
|
||||
const COPY_TIMEOUT = 2000;
|
||||
|
||||
/**
|
||||
* Component used to copy meeting url on prejoin page.
|
||||
*/
|
||||
class CopyMeetingUrl extends Component<Props, State> {
|
||||
|
||||
textarea: Object;
|
||||
|
||||
/**
|
||||
* Initializes a new {@code Prejoin} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.textarea = React.createRef();
|
||||
this.state = {
|
||||
showCopyLink: false,
|
||||
showLinkCopied: false
|
||||
};
|
||||
this._copyUrl = this._copyUrl.bind(this);
|
||||
this._hideCopyLink = this._hideCopyLink.bind(this);
|
||||
this._hideLinkCopied = this._hideLinkCopied.bind(this);
|
||||
this._showCopyLink = this._showCopyLink.bind(this);
|
||||
this._showLinkCopied = this._showLinkCopied.bind(this);
|
||||
}
|
||||
|
||||
_copyUrl: () => void;
|
||||
|
||||
/**
|
||||
* Callback invoked to copy the url to clipboard.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_copyUrl() {
|
||||
const textarea = this.textarea.current;
|
||||
|
||||
try {
|
||||
textarea.select();
|
||||
document.execCommand('copy');
|
||||
textarea.blur();
|
||||
this._showLinkCopied();
|
||||
window.setTimeout(this._hideLinkCopied, COPY_TIMEOUT);
|
||||
} catch (err) {
|
||||
logger.error('error when copying the meeting url');
|
||||
}
|
||||
}
|
||||
|
||||
_hideLinkCopied: () => void;
|
||||
|
||||
/**
|
||||
* Hides the 'Link copied' message.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_hideLinkCopied() {
|
||||
this.setState({
|
||||
showLinkCopied: false
|
||||
});
|
||||
}
|
||||
|
||||
_hideCopyLink: () => void;
|
||||
|
||||
/**
|
||||
* Hides the 'Copy link' text.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_hideCopyLink() {
|
||||
this.setState({
|
||||
showCopyLink: false
|
||||
});
|
||||
}
|
||||
|
||||
_showCopyLink: () => void;
|
||||
|
||||
/**
|
||||
* Shows the dark 'Copy link' text on hover.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_showCopyLink() {
|
||||
this.setState({
|
||||
showCopyLink: true
|
||||
});
|
||||
}
|
||||
|
||||
_showLinkCopied: () => void;
|
||||
|
||||
/**
|
||||
* Shows the green 'Link copied' message.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_showLinkCopied() {
|
||||
this.setState({
|
||||
showLinkCopied: true,
|
||||
showCopyLink: false
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { showCopyLink, showLinkCopied } = this.state;
|
||||
const { url, t } = this.props;
|
||||
const { _copyUrl, _showCopyLink, _hideCopyLink } = this;
|
||||
const src = showLinkCopied ? IconCheck : IconCopy;
|
||||
const iconCls = showCopyLink || showCopyLink ? 'prejoin-copy-icon--white' : 'prejoin-copy-icon--light';
|
||||
|
||||
return (
|
||||
<div
|
||||
className = 'prejoin-copy-meeting'
|
||||
onMouseEnter = { _showCopyLink }
|
||||
onMouseLeave = { _hideCopyLink }>
|
||||
<div className = 'prejoin-copy-url'>{url}</div>
|
||||
{showCopyLink && <div
|
||||
className = 'prejoin-copy-badge prejoin-copy-badge--hover'
|
||||
onClick = { _copyUrl }>
|
||||
{t('prejoin.copyAndShare')}
|
||||
</div>}
|
||||
{showLinkCopied && <div
|
||||
className = 'prejoin-copy-badge prejoin-copy-badge--done'>
|
||||
{t('prejoin.linkCopied')}
|
||||
</div>}
|
||||
<Icon
|
||||
className = { `prejoin-copy-icon ${iconCls}` }
|
||||
size = { 24 }
|
||||
src = { src } />
|
||||
<textarea
|
||||
className = 'prejoin-copy-textarea'
|
||||
readOnly = { true }
|
||||
ref = { this.textarea }
|
||||
tabIndex = '-1'
|
||||
value = { url } />
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the React {@code Component} props.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
url: getCurrentConferenceUrl(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(translate(CopyMeetingUrl));
|
||||
83
react/features/prejoin/components/preview/DeviceStatus.js
Normal file
83
react/features/prejoin/components/preview/DeviceStatus.js
Normal file
@@ -0,0 +1,83 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Icon, IconCheck, IconExclamation } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
import {
|
||||
getDeviceStatusType,
|
||||
getDeviceStatusText,
|
||||
getRawError
|
||||
} from '../../functions';
|
||||
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
* The text to be displayed in relation to the status of the audio/video devices.
|
||||
*/
|
||||
deviceStatusText: string,
|
||||
|
||||
/**
|
||||
* The type of status for current devices, controlling the background color of the text.
|
||||
* Can be `ok` or `warning`.
|
||||
*/
|
||||
deviceStatusType: string,
|
||||
|
||||
/**
|
||||
* The error coming from device configuration.
|
||||
*/
|
||||
rawError: string,
|
||||
|
||||
/**
|
||||
* Used for translation.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
const iconMap = {
|
||||
warning: {
|
||||
src: IconExclamation,
|
||||
className: 'prejoin-preview-status--warning'
|
||||
},
|
||||
ok: {
|
||||
src: IconCheck,
|
||||
className: 'prejoin-preview-status--ok'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Strip showing the current status of the devices.
|
||||
* User is informed if there are missing or malfunctioning devices.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
function DeviceStatus({ deviceStatusType, deviceStatusText, rawError, t }: Props) {
|
||||
const { src, className } = iconMap[deviceStatusType];
|
||||
|
||||
return (
|
||||
<div className = { `prejoin-preview-status ${className}` }>
|
||||
<Icon
|
||||
className = 'prejoin-preview-icon'
|
||||
size = { 16 }
|
||||
src = { src } />
|
||||
<span className = 'prejoin-preview-error-desc'>{t(deviceStatusText)}</span>
|
||||
<span>{rawError}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the React {@code Component} props.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {{ deviceStatusText: string, deviceStatusText: string }}
|
||||
*/
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
deviceStatusText: getDeviceStatusText(state),
|
||||
deviceStatusType: getDeviceStatusType(state),
|
||||
rawError: getRawError(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(mapStateToProps)(DeviceStatus));
|
||||
80
react/features/prejoin/components/preview/ParticipantName.js
Normal file
80
react/features/prejoin/components/preview/ParticipantName.js
Normal file
@@ -0,0 +1,80 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { translate } from '../../../base/i18n';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Flag signaling if the name is ediable or not.
|
||||
*/
|
||||
isEditable: boolean,
|
||||
|
||||
/**
|
||||
* Sets the name for the joining user.
|
||||
*/
|
||||
setName: Function,
|
||||
|
||||
/**
|
||||
* Used to obtain translations.
|
||||
*/
|
||||
t: Function,
|
||||
|
||||
/**
|
||||
* The text to be displayed.
|
||||
*/
|
||||
value: string,
|
||||
};
|
||||
|
||||
/**
|
||||
* Participant name - can be an editable input or just the text name.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
class ParticipantName extends Component<Props> {
|
||||
/**
|
||||
* Initializes a new {@code ParticipantName} instance.
|
||||
*
|
||||
* @param {Props} props - The props of the component.
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._onNameChange = this._onNameChange.bind(this);
|
||||
}
|
||||
|
||||
_onNameChange: () => void;
|
||||
|
||||
/**
|
||||
* Handler used for changing the guest user name.
|
||||
*
|
||||
* @returns {undefined}
|
||||
*/
|
||||
_onNameChange({ target: { value } }) {
|
||||
this.props.setName(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { value, isEditable, t } = this.props;
|
||||
|
||||
return isEditable ? (
|
||||
<input
|
||||
className = 'prejoin-preview-name prejoin-preview-name--editable'
|
||||
onChange = { this._onNameChange }
|
||||
placeholder = { t('dialog.enterDisplayName') }
|
||||
value = { value } />
|
||||
)
|
||||
: <div className = 'prejoin-preview-name'>{value}</div>
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default translate(ParticipantName);
|
||||
75
react/features/prejoin/components/preview/Preview.js
Normal file
75
react/features/prejoin/components/preview/Preview.js
Normal file
@@ -0,0 +1,75 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { Avatar } from '../../../base/avatar';
|
||||
import { Video } from '../../../base/media';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { getActiveVideoTrack, getPrejoinName, isPrejoinVideoMuted } from '../../functions';
|
||||
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
* The name of the user that is about to join.
|
||||
*/
|
||||
name: string,
|
||||
|
||||
/**
|
||||
* Flag signaling the visibility of camera preview.
|
||||
*/
|
||||
showCameraPreview: boolean,
|
||||
|
||||
/**
|
||||
* The JitsiLocalTrack to display.
|
||||
*/
|
||||
videoTrack: ?Object,
|
||||
};
|
||||
|
||||
/**
|
||||
* Component showing the video preview and device status.
|
||||
*
|
||||
* @param {Props} props - The props of the component.
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
function Preview(props: Props) {
|
||||
const {
|
||||
name,
|
||||
showCameraPreview,
|
||||
videoTrack
|
||||
} = props;
|
||||
|
||||
if (showCameraPreview && videoTrack) {
|
||||
return (
|
||||
<div className = 'prejoin-preview'>
|
||||
<div className = 'prejoin-preview-overlay' />
|
||||
<Video
|
||||
className = 'flipVideoX prejoin-preview-video'
|
||||
videoTrack = {{ jitsiTrack: videoTrack }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className = 'prejoin-preview prejoin-preview--no-video'>
|
||||
<Avatar
|
||||
className = 'prejoin-preview-avatar'
|
||||
displayName = { name }
|
||||
size = { 200 } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the redux state to the React {@code Component} props.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
name: getPrejoinName(state),
|
||||
videoTrack: getActiveVideoTrack(state),
|
||||
showCameraPreview: !isPrejoinVideoMuted(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(Preview);
|
||||
228
react/features/prejoin/functions.js
Normal file
228
react/features/prejoin/functions.js
Normal file
@@ -0,0 +1,228 @@
|
||||
// @flow
|
||||
|
||||
|
||||
/**
|
||||
* Mutes or unmutes a track.
|
||||
*
|
||||
* @param {Object} track - The track to be configured.
|
||||
* @param {boolean} shouldMute - If it should mute or not.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function applyMuteOptionsToTrack(track, shouldMute) {
|
||||
if (track.isMuted() === shouldMute) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldMute) {
|
||||
return track.mute();
|
||||
}
|
||||
|
||||
return track.unmute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for the visibility of the 'join by phone' buttons.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function areJoinByPhoneButtonsVisible(state: Object): boolean {
|
||||
return state['features/prejoin'].buttonsVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for determining if the device status strip is visible or not.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isDeviceStatusVisible(state: Object): boolean {
|
||||
return !((isAudioDisabled(state) && isPrejoinVideoDisabled(state))
|
||||
|| (isPrejoinAudioMuted(state) && isPrejoinVideoMuted(state)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for getting the active video/content sharing track.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function getActiveVideoTrack(state: Object): Object {
|
||||
const track = getVideoTrack(state) || getContentSharingTrack(state);
|
||||
|
||||
if (track && track.isActive()) {
|
||||
return track;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list with all the prejoin tracks configured according to
|
||||
* user's preferences.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {Promise<Object[]>}
|
||||
*/
|
||||
export async function getAllPrejoinConfiguredTracks(state: Object): Promise<Object[]> {
|
||||
const tracks = [];
|
||||
const audioTrack = getAudioTrack(state);
|
||||
const videoTrack = getVideoTrack(state);
|
||||
const csTrack = getContentSharingTrack(state);
|
||||
|
||||
if (csTrack) {
|
||||
tracks.push(csTrack);
|
||||
} else if (videoTrack) {
|
||||
await applyMuteOptionsToTrack(videoTrack, isPrejoinVideoMuted(state));
|
||||
tracks.push(videoTrack);
|
||||
}
|
||||
|
||||
if (audioTrack) {
|
||||
await applyMuteOptionsToTrack(audioTrack, isPrejoinAudioMuted(state));
|
||||
isPrejoinAudioMuted(state) && audioTrack.mute();
|
||||
tracks.push(audioTrack);
|
||||
}
|
||||
|
||||
return tracks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for getting the prejoin audio track.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function getAudioTrack(state: Object): Object {
|
||||
return state['features/prejoin'].audioTrack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for getting the prejoin content sharing track.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function getContentSharingTrack(state: Object): Object {
|
||||
return state['features/prejoin'].contentSharingTrack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text for the prejoin status bar.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getDeviceStatusText(state: Object): string {
|
||||
return state['features/prejoin'].deviceStatusText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the prejoin status bar: 'ok'|'warning'.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getDeviceStatusType(state: Object): string {
|
||||
return state['features/prejoin'].deviceStatusType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for getting the prejoin video track.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function getVideoTrack(state: Object): Object {
|
||||
return state['features/prejoin'].videoTrack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for getting the mute status of the prejoin audio.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isPrejoinAudioMuted(state: Object): boolean {
|
||||
return state['features/prejoin'].audioMuted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for getting the name that the user filled while configuring.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function getPrejoinName(state: Object): string {
|
||||
return state['features/prejoin'].name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for getting the mute status of the prejoin video.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isPrejoinVideoMuted(state: Object): boolean {
|
||||
return state['features/prejoin'].videoMuted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for getting the error if any while creating streams.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getRawError(state: Object): string {
|
||||
return state['features/prejoin'].rawError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for getting state of the prejoin audio.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isAudioDisabled(state: Object): Object {
|
||||
return state['features/prejoin'].audioDisabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for getting state of the prejoin video.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isPrejoinVideoDisabled(state: Object): Object {
|
||||
return state['features/prejoin'].videoDisabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for getting the visiblity state for the 'JoinByPhoneDialog'.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isJoinByPhoneDialogVisible(state: Object): boolean {
|
||||
return state['features/prejoin'].showJoinByPhoneDialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the prejoin page is enabled and no flag
|
||||
* to bypass showing the page is present.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isPrejoinPageEnabled(state: Object): boolean {
|
||||
return state['features/base/config'].prejoinPageEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the prejoin page is visible & active.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isPrejoinPageVisible(state: Object): boolean {
|
||||
return isPrejoinPageEnabled(state) && state['features/prejoin'].showPrejoin;
|
||||
}
|
||||
7
react/features/prejoin/index.js
Normal file
7
react/features/prejoin/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
export * from './actions';
|
||||
export * from './functions';
|
||||
|
||||
export { default as Prejoin } from './components/Prejoin';
|
||||
|
||||
import './middleware';
|
||||
import './reducer';
|
||||
5
react/features/prejoin/logger.js
Normal file
5
react/features/prejoin/logger.js
Normal file
@@ -0,0 +1,5 @@
|
||||
// @flow
|
||||
|
||||
import { getLogger } from '../base/logging/functions';
|
||||
|
||||
export default getLogger('features/prejoin');
|
||||
95
react/features/prejoin/middleware.js
Normal file
95
react/features/prejoin/middleware.js
Normal file
@@ -0,0 +1,95 @@
|
||||
// @flow
|
||||
|
||||
import {
|
||||
ADD_PREJOIN_AUDIO_TRACK,
|
||||
ADD_PREJOIN_VIDEO_TRACK,
|
||||
PREJOIN_START_CONFERENCE
|
||||
} from './actionTypes';
|
||||
import { setPrejoinAudioMuted, setPrejoinVideoMuted } from './actions';
|
||||
import { SET_AUDIO_MUTED, SET_VIDEO_MUTED } from '../base/media';
|
||||
import { participantUpdated, getLocalParticipant } from '../base/participants';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
import { updateSettings } from '../base/settings';
|
||||
import { getAllPrejoinConfiguredTracks, getPrejoinName } from './functions';
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
/**
|
||||
* The redux middleware for {@link PrejoinPage}.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => async action => {
|
||||
switch (action.type) {
|
||||
case ADD_PREJOIN_AUDIO_TRACK: {
|
||||
const { value: audioTrack } = action;
|
||||
|
||||
if (audioTrack) {
|
||||
store.dispatch(
|
||||
updateSettings({
|
||||
micDeviceId: audioTrack.getDeviceId()
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ADD_PREJOIN_VIDEO_TRACK: {
|
||||
const { value: videoTrack } = action;
|
||||
|
||||
if (videoTrack) {
|
||||
store.dispatch(
|
||||
updateSettings({
|
||||
cameraDeviceId: videoTrack.getDeviceId()
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case PREJOIN_START_CONFERENCE: {
|
||||
const { dispatch, getState } = store;
|
||||
|
||||
_syncParticipantName(dispatch, getState);
|
||||
const tracks = await getAllPrejoinConfiguredTracks(getState());
|
||||
|
||||
APP.conference.prejoinStart(tracks);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SET_AUDIO_MUTED: {
|
||||
store.dispatch(setPrejoinAudioMuted(Boolean(action.muted)));
|
||||
break;
|
||||
}
|
||||
|
||||
case SET_VIDEO_MUTED: {
|
||||
store.dispatch(setPrejoinVideoMuted(Boolean(action.muted)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
|
||||
/**
|
||||
* Sets the local participant name if one is present.
|
||||
*
|
||||
* @param {Function} dispatch - The redux dispatch function.
|
||||
* @param {Function} getState - Gets the current state.
|
||||
* @returns {undefined}
|
||||
*/
|
||||
function _syncParticipantName(dispatch, getState) {
|
||||
const state = getState();
|
||||
const name = getPrejoinName(state);
|
||||
|
||||
name && dispatch(
|
||||
participantUpdated({
|
||||
...getLocalParticipant(state),
|
||||
name
|
||||
}),
|
||||
);
|
||||
}
|
||||
168
react/features/prejoin/reducer.js
Normal file
168
react/features/prejoin/reducer.js
Normal file
@@ -0,0 +1,168 @@
|
||||
import { ReducerRegistry } from '../base/redux';
|
||||
|
||||
import {
|
||||
ADD_PREJOIN_AUDIO_TRACK,
|
||||
ADD_PREJOIN_CONTENT_SHARING_TRACK,
|
||||
ADD_PREJOIN_VIDEO_TRACK,
|
||||
SET_DEVICE_STATUS,
|
||||
SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
|
||||
SET_PREJOIN_AUDIO_DISABLED,
|
||||
SET_PREJOIN_AUDIO_MUTED,
|
||||
SET_PREJOIN_DEVICE_ERRORS,
|
||||
SET_PREJOIN_NAME,
|
||||
SET_PREJOIN_PAGE_VISIBILITY,
|
||||
SET_PREJOIN_VIDEO_DISABLED,
|
||||
SET_PREJOIN_VIDEO_MUTED
|
||||
} from './actionTypes';
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
audioDisabled: false,
|
||||
audioMuted: false,
|
||||
videoMuted: false,
|
||||
videoDisabled: false,
|
||||
deviceStatusText: 'prejoin.configuringDevices',
|
||||
deviceStatusType: 'ok',
|
||||
showPrejoin: true,
|
||||
showJoinByPhoneDialog: false,
|
||||
videoTrack: null,
|
||||
audioTrack: null,
|
||||
contentSharingTrack: null,
|
||||
rawError: '',
|
||||
name: ''
|
||||
};
|
||||
|
||||
/**
|
||||
* Listen for actions that mutate the prejoin state
|
||||
*/
|
||||
ReducerRegistry.register(
|
||||
'features/prejoin', (state = DEFAULT_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case ADD_PREJOIN_AUDIO_TRACK: {
|
||||
return {
|
||||
...state,
|
||||
audioTrack: action.value
|
||||
};
|
||||
}
|
||||
|
||||
case ADD_PREJOIN_CONTENT_SHARING_TRACK: {
|
||||
return {
|
||||
...state,
|
||||
contentSharingTrack: action.value
|
||||
};
|
||||
}
|
||||
|
||||
case ADD_PREJOIN_VIDEO_TRACK: {
|
||||
return {
|
||||
...state,
|
||||
videoTrack: action.value
|
||||
};
|
||||
}
|
||||
|
||||
case SET_PREJOIN_NAME: {
|
||||
return {
|
||||
...state,
|
||||
name: action.value
|
||||
};
|
||||
}
|
||||
|
||||
case SET_PREJOIN_PAGE_VISIBILITY:
|
||||
return {
|
||||
...state,
|
||||
showPrejoin: action.value
|
||||
};
|
||||
|
||||
case SET_PREJOIN_VIDEO_DISABLED: {
|
||||
return {
|
||||
...state,
|
||||
videoDisabled: action.value
|
||||
};
|
||||
}
|
||||
|
||||
case SET_PREJOIN_VIDEO_MUTED:
|
||||
return {
|
||||
...state,
|
||||
videoMuted: action.value
|
||||
};
|
||||
|
||||
case SET_PREJOIN_AUDIO_MUTED:
|
||||
return {
|
||||
...state,
|
||||
audioMuted: action.value
|
||||
};
|
||||
|
||||
case SET_PREJOIN_DEVICE_ERRORS: {
|
||||
const status = getStatusFromErrors(action.value);
|
||||
|
||||
return {
|
||||
...state,
|
||||
...status
|
||||
};
|
||||
}
|
||||
|
||||
case SET_DEVICE_STATUS: {
|
||||
return {
|
||||
...state,
|
||||
deviceStatusText: action.text,
|
||||
deviceStatusType: action.type
|
||||
};
|
||||
}
|
||||
|
||||
case SET_PREJOIN_AUDIO_DISABLED: {
|
||||
return {
|
||||
...state,
|
||||
audioDisabled: true
|
||||
};
|
||||
}
|
||||
|
||||
case SET_JOIN_BY_PHONE_DIALOG_VISIBLITY: {
|
||||
return {
|
||||
...state,
|
||||
showJoinByPhoneDialog: action.value
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns a suitable error object based on the track errors.
|
||||
*
|
||||
* @param {Object} errors - The errors got while creating local tracks.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function getStatusFromErrors(errors) {
|
||||
const { audioOnlyError, videoOnlyError, audioAndVideoError } = errors;
|
||||
|
||||
if (audioAndVideoError) {
|
||||
if (audioOnlyError) {
|
||||
if (videoOnlyError) {
|
||||
return {
|
||||
deviceStatusType: 'warning',
|
||||
deviceStatusText: 'prejoin.audioAndVideoError',
|
||||
rawError: audioAndVideoError.message
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
deviceStatusType: 'warning',
|
||||
deviceStatusText: 'prejoin.audioOnlyError',
|
||||
rawError: audioOnlyError.message
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
deviceStatusType: 'warning',
|
||||
deviceStatusText: 'prejoin.videoOnlyError',
|
||||
rawError: audioAndVideoError.message
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
deviceStatusType: 'ok',
|
||||
deviceStatusText: 'prejoin.lookGood',
|
||||
rawError: ''
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user