mirror of https://github.com/jitsi/jitsi-meet
parent
5b53232964
commit
a45cbf41ef
@ -0,0 +1,255 @@ |
||||
.prejoin { |
||||
&-full-page { |
||||
background: #1C2025; |
||||
position: absolute; |
||||
width: 100%; |
||||
height: 100%; |
||||
z-index: $toolbarZ + 1; |
||||
} |
||||
|
||||
&-input-area-container { |
||||
position: absolute; |
||||
bottom: 128px; |
||||
width: 100%; |
||||
z-index: 1; |
||||
} |
||||
|
||||
&-input-area { |
||||
margin: 0 auto; |
||||
text-align: center; |
||||
width: 320px; |
||||
} |
||||
|
||||
&-title { |
||||
color: #fff; |
||||
font-size: 24px; |
||||
line-height: 32px; |
||||
margin-bottom: 16px; |
||||
} |
||||
|
||||
&-btn { |
||||
border-radius: 3px; |
||||
color: #fff; |
||||
cursor: pointer; |
||||
display: inline-block; |
||||
font-size: 15px; |
||||
line-height: 24px; |
||||
margin-bottom: 16px; |
||||
padding: 7px 16px; |
||||
text-align: center; |
||||
width: 286px; |
||||
|
||||
&--primary { |
||||
background: #0376DA; |
||||
border: 1px solid #0376DA; |
||||
} |
||||
|
||||
&--secondary { |
||||
background: #2A3A4B; |
||||
border: 1px solid #5E6D7A; |
||||
} |
||||
|
||||
&--text { |
||||
width: auto; |
||||
margin: 0; |
||||
padding: 0; |
||||
} |
||||
} |
||||
|
||||
&-text-btns { |
||||
display: flex; |
||||
justify-content: space-between; |
||||
} |
||||
|
||||
&-input-label { |
||||
color: #A4B8D1; |
||||
font-size: 13px; |
||||
line-height: 20px; |
||||
margin-top: 32px 0 8px 0; |
||||
text-align: center; |
||||
width: 100%; |
||||
} |
||||
} |
||||
|
||||
@mixin name-placeholder { |
||||
color: #fff; |
||||
font-weight: 300; |
||||
opacity: 0.6; |
||||
} |
||||
|
||||
.prejoin-preview { |
||||
height: 100%; |
||||
position: absolute; |
||||
width: 100%; |
||||
|
||||
&--no-video { |
||||
background: radial-gradient(50% 50% at 50% 50%, #5B6F80 0%, #365067 100%), #FFFFFF; |
||||
text-align: center; |
||||
} |
||||
|
||||
&-video { |
||||
height: 100%; |
||||
object-fit: cover; |
||||
position: absolute; |
||||
width: 100%; |
||||
} |
||||
|
||||
&-name { |
||||
color: #fff; |
||||
font-size: 19px; |
||||
line-height: 28px; |
||||
|
||||
&--editable { |
||||
background: none; |
||||
border: 0; |
||||
border-bottom: 1px solid #D1DBE8; |
||||
margin: 24px 0 16px 0; |
||||
outline: none; |
||||
text-align: center; |
||||
width: 100%; |
||||
|
||||
&::-webkit-input-placeholder { |
||||
@include name-placeholder; |
||||
} |
||||
&::-moz-placeholder { |
||||
@include name-placeholder; |
||||
} |
||||
&:-ms-input-placeholder { |
||||
@include name-placeholder; |
||||
} |
||||
} |
||||
} |
||||
|
||||
&-avatar.avatar { |
||||
background: #A4B8D1; |
||||
margin: 200px auto 0 auto; |
||||
} |
||||
|
||||
&-btn-container { |
||||
display: flex; |
||||
justify-content: center; |
||||
position: absolute; |
||||
bottom: 50px; |
||||
width: 100%; |
||||
z-index: 1; |
||||
|
||||
&> div { |
||||
margin: 0 12px; |
||||
} |
||||
|
||||
.settings-button-small-icon { |
||||
right: -8px; |
||||
|
||||
&--hovered { |
||||
right: -10px; |
||||
} |
||||
} |
||||
} |
||||
|
||||
&-overlay { |
||||
height: 100%; |
||||
position: absolute; |
||||
width: 100%; |
||||
z-index: 1; |
||||
background: linear-gradient(0deg, rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.3)), linear-gradient(360deg, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0) 54.25%); |
||||
} |
||||
|
||||
&-status { |
||||
align-items: center; |
||||
bottom: 0; |
||||
color: #fff; |
||||
display: flex; |
||||
font-size: 13px; |
||||
min-height: 24px; |
||||
justify-content: center; |
||||
position: absolute; |
||||
text-align: center; |
||||
width: 100%; |
||||
z-index: 1; |
||||
|
||||
&--warning { |
||||
background: rgba(241, 173, 51, 0.5) |
||||
} |
||||
&--ok { |
||||
background: rgba(49, 183, 106, 0.5); |
||||
} |
||||
} |
||||
|
||||
&-icon { |
||||
background-position: center; |
||||
background-repeat: no-repeat; |
||||
display: inline-block; |
||||
height: 16px; |
||||
margin-right: 8px; |
||||
width: 16px; |
||||
} |
||||
|
||||
&-error-desc { |
||||
margin-right: 4px; |
||||
} |
||||
|
||||
.settings-button-container { |
||||
width: 49px; |
||||
margin: 0 8px; |
||||
} |
||||
} |
||||
|
||||
.prejoin-copy { |
||||
&-meeting { |
||||
cursor: pointer; |
||||
color: #fff; |
||||
font-size: 15px; |
||||
font-weight: 300; |
||||
line-height: 24px; |
||||
position: relative; |
||||
} |
||||
|
||||
&-url { |
||||
max-width: 278px; |
||||
padding: 8px 10px; |
||||
overflow: hidden; |
||||
text-overflow: ellipsis; |
||||
} |
||||
|
||||
&-badge { |
||||
border-radius: 4px; |
||||
height: 100%; |
||||
line-height: 38px; |
||||
position: absolute; |
||||
padding-left: 10px; |
||||
text-align: left; |
||||
top: 0; |
||||
width: 100%; |
||||
|
||||
&--hover { |
||||
background: #1C2025; |
||||
} |
||||
|
||||
&--done { |
||||
background: #31B76A; |
||||
} |
||||
} |
||||
|
||||
&-icon { |
||||
position: absolute; |
||||
right: 8px; |
||||
top: 8px; |
||||
|
||||
&--white { |
||||
&> svg > path { |
||||
fill: #fff |
||||
} |
||||
} |
||||
|
||||
&--light { |
||||
&> svg > path { |
||||
fill: #D1DBE8; |
||||
} |
||||
} |
||||
} |
||||
|
||||
&-textarea { |
||||
position: absolute; |
||||
left: -9999px; |
||||
} |
||||
} |
||||
|
After Width: | Height: | Size: 489 B |
|
Before Width: | Height: | Size: 509 B After Width: | Height: | Size: 494 B |
|
After Width: | Height: | Size: 674 B |
@ -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'; |
||||
@ -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 |
||||
}; |
||||
} |
||||
@ -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)); |
||||
@ -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; |
||||
@ -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)); |
||||
@ -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)); |
||||
@ -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); |
||||
@ -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); |
||||
@ -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; |
||||
} |
||||
@ -0,0 +1,7 @@ |
||||
export * from './actions'; |
||||
export * from './functions'; |
||||
|
||||
export { default as Prejoin } from './components/Prejoin'; |
||||
|
||||
import './middleware'; |
||||
import './reducer'; |
||||
@ -0,0 +1,5 @@ |
||||
// @flow
|
||||
|
||||
import { getLogger } from '../base/logging/functions'; |
||||
|
||||
export default getLogger('features/prejoin'); |
||||
@ -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 |
||||
}), |
||||
); |
||||
} |
||||
@ -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: '' |
||||
}; |
||||
} |
||||
@ -1,2 +1,4 @@ |
||||
export { default as AudioSettingsButton } from './AudioSettingsButton'; |
||||
export { default as VideoSettingsButton } from './VideoSettingsButton'; |
||||
export { default as ToolbarButton } from './ToolbarButton'; |
||||
export { default as Toolbox } from './Toolbox'; |
||||
|
||||
Loading…
Reference in new issue