ref: merge prejoin with lobby
This commit is contained in:
parent
475a2ae596
commit
29dc63fbcb
127
css/_lobby.scss
127
css/_lobby.scss
|
@ -1,115 +1,48 @@
|
|||
#lobby-screen {
|
||||
align-items: center;
|
||||
color: $overflowMenuItemColor;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 1.2em;
|
||||
margin: 48px 36px;
|
||||
.content {
|
||||
|
||||
span {
|
||||
padding: 8px 0;
|
||||
}
|
||||
.container {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.title {
|
||||
color: $defaultColor;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.roomName {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.participantInfo {
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
border: 1px solid #B8C7E0;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 24px 0;
|
||||
padding: 34px 0;
|
||||
|
||||
&:hover {
|
||||
padding-top: 0px;
|
||||
|
||||
.editButton {
|
||||
display: flex;
|
||||
.spinner {
|
||||
margin: 30px;
|
||||
}
|
||||
|
||||
.joining-message {
|
||||
margin: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.editButton {
|
||||
align-self: stretch;
|
||||
display: none;
|
||||
justify-content: flex-end;
|
||||
padding: 5px;
|
||||
position: relative;
|
||||
|
||||
button {
|
||||
background-color: transparent;
|
||||
border-width: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.form {
|
||||
align-items: stretch;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 400px;
|
||||
}
|
||||
|
||||
.displayName {
|
||||
color: $defaultColor;
|
||||
font-size: 1.3em;
|
||||
}
|
||||
}
|
||||
|
||||
.form {
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 32px 0;
|
||||
|
||||
input {
|
||||
margin: 5px 0 15px 0;
|
||||
}
|
||||
|
||||
span {
|
||||
color: white;
|
||||
font-size: 1.3em;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.joiningContainer {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 36px 0;
|
||||
|
||||
span {
|
||||
margin-top: 36px;
|
||||
text-align: center;
|
||||
.participant-info {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#lobby-dialog {
|
||||
align-self: stretch;
|
||||
#lobby-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 32px 0;
|
||||
|
||||
.description {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.field {
|
||||
.control-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
margin-top: 15px;
|
||||
|
||||
:first-child {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
:last-child {
|
||||
flex: 1;
|
||||
label {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -162,11 +95,7 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Common styles
|
||||
|
||||
#lobby-dialog, #lobby-screen, #knocking-participant-list {
|
||||
input {
|
||||
align-self: stretch;
|
||||
background-color: transparent;
|
||||
|
@ -208,4 +137,4 @@
|
|||
border-width: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,4 @@
|
|||
.prejoin {
|
||||
&-full-page {
|
||||
background: #1C2025;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: $toolbarZ + 1;
|
||||
}
|
||||
|
||||
&-input-area-container {
|
||||
position: absolute;
|
||||
bottom: 48px;
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&-input-area {
|
||||
margin: 0 auto;
|
||||
|
@ -27,65 +13,6 @@
|
|||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
&-btn {
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 15px;
|
||||
line-height: 24px;
|
||||
padding: 7px 16px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
width: 286px;
|
||||
|
||||
&--primary {
|
||||
background: #0376DA;
|
||||
border: 1px solid #0376DA;
|
||||
}
|
||||
|
||||
&--secondary {
|
||||
background: #2A3A4B;
|
||||
border: 1px solid #5E6D7A;
|
||||
}
|
||||
|
||||
&--text {
|
||||
width: auto;
|
||||
font-size: 13px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
background: #5E6D7A;
|
||||
border: 1px solid #5E6D7A;
|
||||
color: #AFB6BC;
|
||||
cursor: initial;
|
||||
|
||||
.prejoin-btn-icon {
|
||||
& > svg {
|
||||
fill: #AFB6BC;
|
||||
}
|
||||
}
|
||||
|
||||
.prejoin-btn-options {
|
||||
border-left: 1px solid #AFB6BC;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-btn-options {
|
||||
align-items: center;
|
||||
border-left: 1px solid #fff;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
&-text-btns {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
@ -179,25 +106,6 @@
|
|||
margin: 200px auto 0 auto;
|
||||
}
|
||||
|
||||
&-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 32px;
|
||||
width: 100%;
|
||||
|
||||
&> div {
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
.settings-button-small-icon {
|
||||
right: -8px;
|
||||
|
||||
&--hovered {
|
||||
right: -10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-overlay {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
|
@ -217,22 +125,20 @@
|
|||
|
||||
&-status {
|
||||
align-items: center;
|
||||
bottom: 0;
|
||||
align-self: stretch;
|
||||
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)
|
||||
background: rgba(241, 173, 51, 0.7)
|
||||
}
|
||||
&--ok {
|
||||
background: rgba(49, 183, 106, 0.5);
|
||||
background: rgba(49, 183, 106, 0.7);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -291,63 +197,3 @@
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,194 @@
|
|||
/**
|
||||
* Shared style for full screen local track based dialogs/modals.
|
||||
*/
|
||||
.premeeting-screen {
|
||||
align-items: stretch;
|
||||
background: #1C2025;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 1.3em;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: $toolbarZ + 1;
|
||||
|
||||
.content {
|
||||
align-items: center;
|
||||
background-image: linear-gradient(transparent, black);
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
z-index: $toolbarZ + 2;
|
||||
|
||||
.title {
|
||||
color: #fff;
|
||||
font-size: 24px;
|
||||
line-height: 32px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.copy-meeting {
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: 15px;
|
||||
font-weight: 300;
|
||||
justify-content: center;
|
||||
line-height: 24px;
|
||||
|
||||
.url {
|
||||
display: flex;
|
||||
padding: 8px 10px;
|
||||
|
||||
&:hover {
|
||||
background: #1C2025;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&.done {
|
||||
background: #31B76A;
|
||||
}
|
||||
|
||||
.jitsi-icon {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
textarea {
|
||||
border-width: 0;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
padding: 0;
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
input.field {
|
||||
background-color: transparent;
|
||||
border: 1px solid transparent;
|
||||
color: white;
|
||||
outline-width: 0;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
|
||||
&.focused {
|
||||
border-bottom: 1px solid white;
|
||||
}
|
||||
|
||||
&.error::placeholder {
|
||||
color: $defaultWarningColor;
|
||||
}
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 15px;
|
||||
line-height: 24px;
|
||||
margin: 10px;
|
||||
padding: 7px 16px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
width: 286px;
|
||||
|
||||
&.primary {
|
||||
background: #0376DA;
|
||||
border: 1px solid #0376DA;
|
||||
}
|
||||
|
||||
&.secondary {
|
||||
background: transparent;
|
||||
border: 1px solid #5E6D7A;
|
||||
}
|
||||
|
||||
&.text {
|
||||
width: auto;
|
||||
font-size: 13px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
background: #5E6D7A;
|
||||
border: 1px solid #5E6D7A;
|
||||
color: #AFB6BC;
|
||||
cursor: initial;
|
||||
|
||||
.icon {
|
||||
& > svg {
|
||||
fill: #AFB6BC;
|
||||
}
|
||||
}
|
||||
|
||||
.options {
|
||||
border-left: 1px solid #AFB6BC;
|
||||
}
|
||||
}
|
||||
|
||||
.options {
|
||||
align-items: center;
|
||||
border-left: 1px solid #fff;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 32px 0;
|
||||
width: 100%;
|
||||
|
||||
&> div {
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
.settings-button-small-icon {
|
||||
right: -8px;
|
||||
|
||||
&--hovered {
|
||||
right: -10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
background: #A4B8D1;
|
||||
margin: 200px auto 0 auto;
|
||||
}
|
||||
|
||||
video {
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
|
@ -97,5 +97,6 @@ $flagsImagePath: "../images/";
|
|||
@import 'country-picker';
|
||||
@import 'modals/invite/invite_more';
|
||||
@import 'modals/security/security';
|
||||
@import 'premeeting-screens';
|
||||
|
||||
/* Modules END */
|
||||
|
|
|
@ -3,25 +3,47 @@
|
|||
color: #fff;
|
||||
font-size: 15px;
|
||||
line-height: 24px;
|
||||
|
||||
&.password {
|
||||
|
||||
&.password-section {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
||||
&-actions {
|
||||
a {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
color: #6FB1EA;
|
||||
}
|
||||
.password {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 15px;
|
||||
|
||||
& > :first-child:not(:last-child) {
|
||||
margin-right: 24px;
|
||||
&-actions {
|
||||
a {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
color: #6FB1EA;
|
||||
}
|
||||
|
||||
&>a+a {
|
||||
margin-left: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&> :first-child:not(:last-child) {
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
.separator-line {
|
||||
margin: 24px 0 24px -20px;
|
||||
padding: 0 20px;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: #5E6D7A;
|
||||
|
||||
&:last-child {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,4 +56,4 @@
|
|||
background: rgba(241, 173, 51, 0.7);
|
||||
border: 1px solid rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -48,7 +48,7 @@ var interfaceConfig = {
|
|||
*/
|
||||
TOOLBAR_BUTTONS: [
|
||||
'microphone', 'camera', 'closedcaptions', 'desktop', 'fullscreen',
|
||||
'fodeviceselection', 'hangup', 'lobby', 'profile', 'chat', 'recording',
|
||||
'fodeviceselection', 'hangup', 'profile', 'chat', 'recording',
|
||||
'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
|
||||
'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
|
||||
'tileview', 'videobackgroundblur', 'download', 'help', 'mute-everyone',
|
||||
|
|
|
@ -867,26 +867,29 @@
|
|||
},
|
||||
"lobby": {
|
||||
"allow": "Allow",
|
||||
"backToKnockModeButton": "No password, knock instead",
|
||||
"backToKnockModeButton": "No password, ask to join instead",
|
||||
"dialogTitle": "Lobby mode",
|
||||
"disableDialogContent": "Lobby mode is currently enabled. This feature ensures that unwanted participants can't join your meeting. Do you want to disable it?",
|
||||
"disableDialogSubmit": "Disable",
|
||||
"emailField": "Enter your email address",
|
||||
"enableDialogPasswordField": "Set password (optional)",
|
||||
"enableDialogSubmit": "Enable",
|
||||
"enableDialogText": "Lobby mode lets you protect your meeting by only allowing people to enter after a formal approve of a moderator or by entering an optional predefined password.",
|
||||
"enableDialogText": "Lobby mode lets you protect your meeting by only allowing people to enter after a formal approval by a moderator.",
|
||||
"enterPasswordButton": "Enter meeting password",
|
||||
"enterPasswordTitle": "Enter password to join meeting",
|
||||
"invalidPassword": "Invalid password",
|
||||
"joiningMessage": "You'll join the meeting as soon as someone accepts your request",
|
||||
"joinWithPasswordMessage": "Trying to join with password, please wait...",
|
||||
"joinRejectedMessage": "Your join request was rejected by a moderator.",
|
||||
"joinTitle": "Join Meeting",
|
||||
"joiningTitle": "Asking to join",
|
||||
"joiningWithPasswordTitle": "Joining",
|
||||
"joiningTitle": "Asking to join meeting...",
|
||||
"joiningWithPasswordTitle": "Joining with password...",
|
||||
"knockButton": "Ask to Join",
|
||||
"knockTitle": "Someone wants to join the meeting",
|
||||
"nameField": "Enter your name",
|
||||
"passwordField": "Enter password",
|
||||
"passwordField": "Enter meeting password",
|
||||
"passwordJoinButton": "Join",
|
||||
"reject": "Reject"
|
||||
"reject": "Reject",
|
||||
"toggleLabel": "Enable lobby"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -249,6 +249,7 @@ export function authStatusChanged(authEnabled: boolean, authLogin: string) {
|
|||
* @param {JitsiConference} conference - The JitsiConference that has failed.
|
||||
* @param {string} error - The error describing/detailing the cause of the
|
||||
* failure.
|
||||
* @param {any} params - Rest of the params that we receive together with the event.
|
||||
* @returns {{
|
||||
* type: CONFERENCE_FAILED,
|
||||
* conference: JitsiConference,
|
||||
|
@ -651,28 +652,23 @@ export function setPassword(
|
|||
case conference.join: {
|
||||
let state = getState()['features/base/conference'];
|
||||
|
||||
// Make sure that the action will set a password for a conference
|
||||
// that the application wants joined.
|
||||
if (state.passwordRequired === conference) {
|
||||
dispatch({
|
||||
type: SET_PASSWORD,
|
||||
conference,
|
||||
method,
|
||||
password
|
||||
});
|
||||
dispatch({
|
||||
type: SET_PASSWORD,
|
||||
conference,
|
||||
method,
|
||||
password
|
||||
});
|
||||
|
||||
// Join the conference with the newly-set password.
|
||||
// Join the conference with the newly-set password.
|
||||
|
||||
// Make sure that the action did set the password.
|
||||
state = getState()['features/base/conference'];
|
||||
if (state.password === password
|
||||
&& !state.passwordRequired
|
||||
// Make sure that the action did set the password.
|
||||
state = getState()['features/base/conference'];
|
||||
if (state.password === password
|
||||
|
||||
// Make sure that the application still wants the
|
||||
// conference joined.
|
||||
&& !state.conference) {
|
||||
method.call(conference, password);
|
||||
}
|
||||
// Make sure that the application still wants the
|
||||
// conference joined.
|
||||
&& !state.conference) {
|
||||
method.call(conference, password);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -145,10 +145,6 @@ function _conferenceFailed({ dispatch, getState }, next, action) {
|
|||
const result = next(action);
|
||||
const { conference, error } = action;
|
||||
|
||||
if (error.name === JitsiConferenceErrors.OFFER_ANSWER_FAILED) {
|
||||
sendAnalytics(createOfferAnswerFailedEvent());
|
||||
}
|
||||
|
||||
// Handle specific failure reasons.
|
||||
switch (error.name) {
|
||||
case JitsiConferenceErrors.CONFERENCE_DESTROYED: {
|
||||
|
@ -167,7 +163,7 @@ function _conferenceFailed({ dispatch, getState }, next, action) {
|
|||
case JitsiConferenceErrors.CONNECTION_ERROR: {
|
||||
const [ msg ] = error.params;
|
||||
|
||||
dispatch(connectionDisconnected(getState()['features/base/connection'].connection, 'Disconnected'));
|
||||
dispatch(connectionDisconnected(getState()['features/base/connection'].connection));
|
||||
dispatch(showErrorNotification({
|
||||
descriptionArguments: { msg },
|
||||
descriptionKey: msg ? 'dialog.connectErrorWithMsg' : 'dialog.connectError',
|
||||
|
@ -176,6 +172,9 @@ function _conferenceFailed({ dispatch, getState }, next, action) {
|
|||
|
||||
break;
|
||||
}
|
||||
case JitsiConferenceErrors.OFFER_ANSWER_FAILED:
|
||||
sendAnalytics(createOfferAnswerFailedEvent());
|
||||
break;
|
||||
}
|
||||
|
||||
// FIXME: Workaround for the web version. Currently, the creation of the
|
||||
|
|
|
@ -387,34 +387,30 @@ function _setDesktopSharingEnabled(state, action) {
|
|||
function _setPassword(state, { conference, method, password }) {
|
||||
switch (method) {
|
||||
case conference.join:
|
||||
if (state.passwordRequired === conference) {
|
||||
return assign(state, {
|
||||
// XXX 1. The JitsiConference which transitions away from
|
||||
// passwordRequired MUST remain in the redux state
|
||||
// features/base/conference until it transitions into
|
||||
// conference; otherwise, there is a span of time during which
|
||||
// the redux state does not even know that there is a
|
||||
// JitsiConference whatsoever.
|
||||
//
|
||||
// 2. The redux action setPassword will attempt to join the
|
||||
// JitsiConference so joining is an appropriate transitional
|
||||
// redux state.
|
||||
//
|
||||
// 3. The redux action setPassword will perform the same check
|
||||
// before it proceeds with the re-join.
|
||||
joining: state.conference ? state.joining : conference,
|
||||
locked: LOCKED_REMOTELY,
|
||||
return assign(state, {
|
||||
// 1. The JitsiConference which transitions away from
|
||||
// passwordRequired MUST remain in the redux state
|
||||
// features/base/conference until it transitions into
|
||||
// conference; otherwise, there is a span of time during which
|
||||
// the redux state does not even know that there is a
|
||||
// JitsiConference whatsoever.
|
||||
//
|
||||
// 2. The redux action setPassword will attempt to join the
|
||||
// JitsiConference so joining is an appropriate transitional
|
||||
// redux state.
|
||||
//
|
||||
// 3. The redux action setPassword will perform the same check
|
||||
// before it proceeds with the re-join.
|
||||
joining: state.conference ? state.joining : conference,
|
||||
locked: LOCKED_REMOTELY,
|
||||
|
||||
/**
|
||||
* The password with which the conference is to be joined.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
password,
|
||||
passwordRequired: undefined
|
||||
});
|
||||
}
|
||||
break;
|
||||
/**
|
||||
* The password with which the conference is to be joined.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
password
|
||||
});
|
||||
|
||||
case conference.lock:
|
||||
return assign(state, {
|
||||
|
|
|
@ -5,8 +5,7 @@
|
|||
*
|
||||
* {
|
||||
* type: CONNECTION_DISCONNECTED,
|
||||
* connection: JitsiConnection,
|
||||
* message: string
|
||||
* connection: JitsiConnection
|
||||
* }
|
||||
*/
|
||||
export const CONNECTION_DISCONNECTED = 'CONNECTION_DISCONNECTED';
|
||||
|
|
|
@ -113,13 +113,12 @@ export function connect(id: ?string, password: ?string) {
|
|||
* Dispatches {@code CONNECTION_DISCONNECTED} action when connection is
|
||||
* disconnected.
|
||||
*
|
||||
* @param {string} message - Disconnect reason.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _onConnectionDisconnected(message: string) {
|
||||
function _onConnectionDisconnected() {
|
||||
unsubscribe();
|
||||
dispatch(connectionDisconnected(connection, message));
|
||||
dispatch(connectionDisconnected(connection));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -187,19 +186,16 @@ export function connect(id: ?string, password: ?string) {
|
|||
*
|
||||
* @param {JitsiConnection} connection - The {@code JitsiConnection} which
|
||||
* disconnected.
|
||||
* @param {string} message - Error message.
|
||||
* @private
|
||||
* @returns {{
|
||||
* type: CONNECTION_DISCONNECTED,
|
||||
* connection: JitsiConnection,
|
||||
* message: string
|
||||
* connection: JitsiConnection
|
||||
* }}
|
||||
*/
|
||||
export function connectionDisconnected(connection: Object, message: string) {
|
||||
export function connectionDisconnected(connection: Object) {
|
||||
return {
|
||||
type: CONNECTION_DISCONNECTED,
|
||||
connection,
|
||||
message
|
||||
connection
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -30,14 +30,17 @@ export function hideDialog(component: ?Object) {
|
|||
* @param {Object} component - The component to display as dialog.
|
||||
* @param {Object} [componentProps] - The React {@code Component} props of the
|
||||
* specified {@code component}.
|
||||
* @param {boolean} rawDialog - True if the dialog is a raw dialog.
|
||||
* (Doesn't inherit behavior from other common frameworks).
|
||||
* @returns {{
|
||||
* type: OPEN_DIALOG,
|
||||
* component: React.Component,
|
||||
* componentProps: (Object | undefined)
|
||||
* }}
|
||||
*/
|
||||
export function openDialog(component: Object, componentProps: ?Object) {
|
||||
export function openDialog(component: Object, componentProps: ?Object, rawDialog?: boolean) {
|
||||
return {
|
||||
rawDialog,
|
||||
type: OPEN_DIALOG,
|
||||
component,
|
||||
componentProps
|
||||
|
|
|
@ -17,6 +17,11 @@ type Props = {
|
|||
*/
|
||||
_componentProps: Object,
|
||||
|
||||
/**
|
||||
* True if the dialog is a raw dialog (doesn't inherit behavior from other common frameworks, such as atlaskit).
|
||||
*/
|
||||
_rawDialog: boolean,
|
||||
|
||||
/**
|
||||
* True if the UI is in a compact state where we don't show dialogs.
|
||||
*/
|
||||
|
@ -52,19 +57,16 @@ export default class AbstractDialogContainer extends Component<Props> {
|
|||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _component: React.Component,
|
||||
* _componentProps: Object,
|
||||
* _reducedUI: boolean
|
||||
* }}
|
||||
* @returns {Props}
|
||||
*/
|
||||
export function abstractMapStateToProps(state: Object) {
|
||||
export function abstractMapStateToProps(state: Object): $Shape<Props> {
|
||||
const stateFeaturesBaseDialog = state['features/base/dialog'];
|
||||
const { reducedUI } = state['features/base/responsive-ui'];
|
||||
|
||||
return {
|
||||
_component: stateFeaturesBaseDialog.component,
|
||||
_componentProps: stateFeaturesBaseDialog.componentProps,
|
||||
_rawDialog: stateFeaturesBaseDialog.rawDialog,
|
||||
_reducedUI: reducedUI
|
||||
};
|
||||
}
|
||||
|
|
|
@ -20,6 +20,10 @@ class DialogContainer extends AbstractDialogContainer {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
if (this.props._rawDialog) {
|
||||
return this._renderDialogContent();
|
||||
}
|
||||
|
||||
return (
|
||||
<ModalTransition>
|
||||
{ this._renderDialogContent() }
|
||||
|
|
|
@ -21,7 +21,8 @@ ReducerRegistry.register('features/base/dialog', (state = {}, action) => {
|
|||
if (typeof component === 'undefined' || state.component === component) {
|
||||
return assign(state, {
|
||||
component: undefined,
|
||||
componentProps: undefined
|
||||
componentProps: undefined,
|
||||
rawDialog: false
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
@ -30,7 +31,8 @@ ReducerRegistry.register('features/base/dialog', (state = {}, action) => {
|
|||
case OPEN_DIALOG:
|
||||
return assign(state, {
|
||||
component: action.component,
|
||||
componentProps: action.componentProps
|
||||
componentProps: action.componentProps,
|
||||
rawDialog: action.rawDialog
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 4C4 2.89543 4.89543 2 6 2H14C15.1046 2 16 2.89543 16 4H6V18C4.89543 18 4 17.1046 4 16V4ZM10 8V20H18V8H10ZM10 6H18C19.1046 6 20 6.89543 20 8V20C20 21.1046 19.1046 22 18 22H10C8.89543 22 8 21.1046 8 20V8C8 6.89543 8.89543 6 10 6Z" fill="#5E6D7A"/>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 4C4 2.89543 4.89543 2 6 2H14C15.1046 2 16 2.89543 16 4H6V18C4.89543 18 4 17.1046 4 16V4ZM10 8V20H18V8H10ZM10 6H18C19.1046 6 20 6.89543 20 8V20C20 21.1046 19.1046 22 18 22H10C8.89543 22 8 21.1046 8 20V8C8 6.89543 8.89543 6 10 6Z"/>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 401 B After Width: | Height: | Size: 374 B |
|
@ -31,8 +31,8 @@ export { default as IconDominantSpeaker } from './dominant-speaker.svg';
|
|||
export { default as IconDownload } from './download.svg';
|
||||
export { default as IconDragHandle } from './drag-handle.svg';
|
||||
export { default as IconE2EE } from './e2ee.svg';
|
||||
export { default as IconEmail } from './envelope.svg';
|
||||
export { default as IconEdit } from './edit.svg';
|
||||
export { default as IconEmail } from './envelope.svg';
|
||||
export { default as IconEventNote } from './event_note.svg';
|
||||
export { default as IconExclamation } from './exclamation.svg';
|
||||
export { default as IconExclamationSolid } from './exclamation-solid.svg';
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
// @flow
|
||||
|
||||
export * from './web';
|
|
@ -0,0 +1,77 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Icon, IconArrowDown } from '../../../icons';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Text of the button.
|
||||
*/
|
||||
children: React$Node,
|
||||
|
||||
/**
|
||||
* Text css class of the button.
|
||||
*/
|
||||
className?: string,
|
||||
|
||||
/**
|
||||
* If the button is disabled or not.
|
||||
*/
|
||||
disabled?: boolean,
|
||||
|
||||
/**
|
||||
* If the button has options.
|
||||
*/
|
||||
hasOptions?: boolean,
|
||||
|
||||
/**
|
||||
* The type of th button: primary, secondary, text.
|
||||
*/
|
||||
type: string,
|
||||
|
||||
/**
|
||||
* OnClick button handler.
|
||||
*/
|
||||
onClick: Function,
|
||||
|
||||
/**
|
||||
* Click handler for options.
|
||||
*/
|
||||
onOptionsClick?: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* Button used for pre meeting actions.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
function ActionButton({
|
||||
children,
|
||||
className = '',
|
||||
disabled,
|
||||
hasOptions,
|
||||
type = 'primary',
|
||||
onClick,
|
||||
onOptionsClick
|
||||
}: Props) {
|
||||
return (
|
||||
<div
|
||||
className = { `action-btn ${className} ${type} ${disabled ? 'disabled' : ''}` }
|
||||
onClick = { disabled ? undefined : onClick }>
|
||||
{children}
|
||||
{hasOptions && <div
|
||||
className = 'options'
|
||||
onClick = { disabled ? undefined : onOptionsClick }>
|
||||
<Icon
|
||||
className = 'icon'
|
||||
size = { 14 }
|
||||
src = { IconArrowDown } />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ActionButton;
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { getCurrentConferenceUrl } from '../../../base/connection';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Icon, IconCopy, IconCheck } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { getCurrentConferenceUrl } from '../../../connection';
|
||||
import { translate } from '../../../i18n';
|
||||
import { Icon, IconCopy, IconCheck } from '../../../icons';
|
||||
import { connect } from '../../../redux';
|
||||
import logger from '../../logger';
|
||||
|
||||
type Props = {
|
||||
|
@ -108,7 +108,8 @@ class CopyMeetingUrl extends Component<Props, State> {
|
|||
*/
|
||||
_hideCopyLink() {
|
||||
this.setState({
|
||||
showCopyLink: false
|
||||
showCopyLink: false,
|
||||
showLinkCopied: false
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -122,7 +123,8 @@ class CopyMeetingUrl extends Component<Props, State> {
|
|||
*/
|
||||
_showCopyLink() {
|
||||
this.setState({
|
||||
showCopyLink: true
|
||||
showCopyLink: true,
|
||||
showLinkCopied: false
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -152,35 +154,30 @@ class CopyMeetingUrl extends Component<Props, 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'
|
||||
className = '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}` }
|
||||
onClick = { _copyUrl }
|
||||
size = { 24 }
|
||||
src = { src } />
|
||||
<div
|
||||
className = { `url ${showLinkCopied ? 'done' : ''}` }
|
||||
onClick = { _copyUrl } >
|
||||
{ !showCopyLink && !showLinkCopied && url }
|
||||
{ showCopyLink && t('prejoin.copyAndShare') }
|
||||
{ showLinkCopied && t('prejoin.linkCopied') }
|
||||
<Icon
|
||||
onClick = { _copyUrl }
|
||||
size = { 24 }
|
||||
src = { src } />
|
||||
</div>
|
||||
<textarea
|
||||
className = 'prejoin-copy-textarea'
|
||||
readOnly = { true }
|
||||
ref = { this.textarea }
|
||||
tabIndex = '-1'
|
||||
value = { url } />
|
||||
</div>);
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { getFieldValue } from '../../../react';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Class name to be appended to the default class list.
|
||||
*/
|
||||
className?: string,
|
||||
|
||||
/**
|
||||
* Callback for the onChange event of the field.
|
||||
*/
|
||||
onChange: Function,
|
||||
|
||||
/**
|
||||
* Callback to be used when the user hits Enter in the field.
|
||||
*/
|
||||
onSubmit?: Function,
|
||||
|
||||
/**
|
||||
* Placeholder text for the field.
|
||||
*/
|
||||
placeHolder: string,
|
||||
|
||||
/**
|
||||
* The field type (e.g. text, password...etc).
|
||||
*/
|
||||
type: string,
|
||||
|
||||
/**
|
||||
* Externally provided value.
|
||||
*/
|
||||
value?: string
|
||||
};
|
||||
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* True if the field is focused, false otherwise.
|
||||
*/
|
||||
focused: boolean,
|
||||
|
||||
/**
|
||||
* The current value of the field.
|
||||
*/
|
||||
value: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a pre-styled input field to be used on pre-meeting screens.
|
||||
*/
|
||||
export default class InputField extends PureComponent<Props, State> {
|
||||
static defaultProps: {
|
||||
className: '',
|
||||
type: 'text'
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiates a new component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
focused: false,
|
||||
value: props.value || ''
|
||||
};
|
||||
|
||||
this._onBlur = this._onBlur.bind(this);
|
||||
this._onChange = this._onChange.bind(this);
|
||||
this._onFocus = this._onFocus.bind(this);
|
||||
this._onKeyDown = this._onKeyDown.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@code PureComponent.getDerivedStateFromProps}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
static getDerivedStateFromProps(props: Props, state: State) {
|
||||
const { value } = props;
|
||||
|
||||
if (state.value !== value) {
|
||||
return {
|
||||
...state,
|
||||
value
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@code PureComponent#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<input
|
||||
className = { `field ${this.state.focused ? 'focused' : ''} ${this.props.className || ''}` }
|
||||
onBlur = { this._onBlur }
|
||||
onChange = { this._onChange }
|
||||
onFocus = { this._onFocus }
|
||||
onKeyDown = { this._onKeyDown }
|
||||
placeholder = { this.props.placeHolder }
|
||||
type = { this.props.type }
|
||||
value = { this.state.value } />
|
||||
);
|
||||
}
|
||||
|
||||
_onBlur: () => void;
|
||||
|
||||
/**
|
||||
* Callback for the onBlur event of the field.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onBlur() {
|
||||
this.setState({
|
||||
focused: false
|
||||
});
|
||||
}
|
||||
|
||||
_onChange: Object => void;
|
||||
|
||||
/**
|
||||
* Callback for the onChange event of the field.
|
||||
*
|
||||
* @param {Object} evt - The static event.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onChange(evt) {
|
||||
const value = getFieldValue(evt);
|
||||
|
||||
this.setState({
|
||||
value
|
||||
});
|
||||
|
||||
const { onChange } = this.props;
|
||||
|
||||
onChange && onChange(value);
|
||||
}
|
||||
|
||||
_onFocus: () => void;
|
||||
|
||||
/**
|
||||
* Callback for the onFocus event of the field.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onFocus() {
|
||||
this.setState({
|
||||
focused: true
|
||||
});
|
||||
}
|
||||
|
||||
_onKeyDown: Object => void;
|
||||
|
||||
/**
|
||||
* Joins the conference on 'Enter'.
|
||||
*
|
||||
* @param {Event} event - Key down event object.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onKeyDown(event) {
|
||||
const { onSubmit } = this.props;
|
||||
|
||||
onSubmit && event.key === 'Enter' && onSubmit();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { AudioSettingsButton, VideoSettingsButton } from '../../../../toolbox';
|
||||
|
||||
import CopyMeetingUrl from './CopyMeetingUrl';
|
||||
import Preview from './Preview';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Children component(s) to be rendered on the screen.
|
||||
*/
|
||||
children: React$Node,
|
||||
|
||||
/**
|
||||
* Footer to be rendered for the page (if any).
|
||||
*/
|
||||
footer?: React$Node,
|
||||
|
||||
/**
|
||||
* Title of the screen.
|
||||
*/
|
||||
title: string,
|
||||
|
||||
/**
|
||||
* True if the preview overlay should be muted, false otherwise.
|
||||
*/
|
||||
videoMuted?: boolean,
|
||||
|
||||
/**
|
||||
* The video track to render as preview (if omitted, the default local track will be rendered).
|
||||
*/
|
||||
videoTrack?: Object
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a pre-meeting screen that can be used at various pre-meeting phases, for example
|
||||
* on the prejoin screen (pre-connection) or lobby (post-connection).
|
||||
*/
|
||||
export default class PreMeetingScreen extends PureComponent<Props> {
|
||||
/**
|
||||
* Implements {@code PureComponent#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { title, videoMuted, videoTrack } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className = 'premeeting-screen'
|
||||
id = 'lobby-screen'>
|
||||
<Preview
|
||||
videoMuted = { videoMuted }
|
||||
videoTrack = { videoTrack } />
|
||||
<div className = 'content'>
|
||||
<div className = 'title'>
|
||||
{ title }
|
||||
</div>
|
||||
<CopyMeetingUrl />
|
||||
{ this.props.children }
|
||||
<div className = 'media-btn-container'>
|
||||
<AudioSettingsButton visible = { true } />
|
||||
<VideoSettingsButton visible = { true } />
|
||||
</div>
|
||||
{ this.props.footer }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Avatar } from '../../../avatar';
|
||||
import { Video } from '../../../media';
|
||||
import { connect } from '../../../redux';
|
||||
import { getLocalVideoTrack } from '../../../tracks';
|
||||
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
* The name of the user that is about to join.
|
||||
*/
|
||||
name: string,
|
||||
|
||||
/**
|
||||
* Flag signaling the visibility of camera preview.
|
||||
*/
|
||||
videoMuted: 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, videoMuted, videoTrack } = props;
|
||||
|
||||
if (!videoMuted && videoTrack) {
|
||||
return (
|
||||
<div id = 'preview'>
|
||||
<Video
|
||||
className = 'flipVideoX'
|
||||
videoTrack = {{ jitsiTrack: videoTrack }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className = 'no-video'
|
||||
id = 'preview'>
|
||||
<Avatar
|
||||
className = 'preview-avatar'
|
||||
displayName = { name }
|
||||
size = { 200 } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {Props} ownProps - The own props of the component.
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
videoMuted: ownProps.videoTrack ? ownProps.videoMuted : state['features/base/media'].video.muted,
|
||||
videoTrack: ownProps.videoTrack || (getLocalVideoTrack(state['features/base/tracks']) || {}).jitsiTrack
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(Preview);
|
|
@ -0,0 +1,5 @@
|
|||
// @flow
|
||||
|
||||
export { default as ActionButton } from './ActionButton';
|
||||
export { default as InputField } from './InputField';
|
||||
export { default as PreMeetingScreen } from './PreMeetingScreen';
|
|
@ -0,0 +1,3 @@
|
|||
// @flow
|
||||
|
||||
export * from './components';
|
|
@ -0,0 +1,5 @@
|
|||
// @flow
|
||||
|
||||
import { getLogger } from '../logging/functions';
|
||||
|
||||
export default getLogger('features/base/premeeting');
|
|
@ -209,9 +209,9 @@ class Conference extends AbstractConference<Props, *> {
|
|||
|
||||
{ this.renderNotificationsContainer() }
|
||||
|
||||
{ !filmstripOnly && _showPrejoin && <Prejoin />}
|
||||
|
||||
<CalleeInfoContainer />
|
||||
|
||||
{ !filmstripOnly && _showPrejoin && <Prejoin />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -19,3 +19,8 @@ export const SET_LOBBY_MODE_ENABLED = 'SET_LOBBY_MODE_ENABLED';
|
|||
* Action type to set the knocking state of the participant.
|
||||
*/
|
||||
export const SET_KNOCKING_STATE = 'SET_KNOCKING_STATE';
|
||||
|
||||
/**
|
||||
* Action type to set the password join failed status.
|
||||
*/
|
||||
export const SET_PASSWORD_JOIN_FAILED = 'SET_PASSWORD_JOIN_FAILED';
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
// @flow
|
||||
|
||||
import { openDialog } from '../base/dialog';
|
||||
|
||||
import { DisableLobbyModeDialog, EnableLobbyModeDialog } from './components/native';
|
||||
|
||||
export * from './actions.web';
|
||||
|
||||
/**
|
||||
* Action to show the dialog to disable lobby mode.
|
||||
*
|
||||
* @returns {showNotification}
|
||||
*/
|
||||
export function showDisableLobbyModeDialog() {
|
||||
return openDialog(DisableLobbyModeDialog);
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to show the dialog to enable lobby mode.
|
||||
*
|
||||
* @returns {showNotification}
|
||||
*/
|
||||
export function showEnableLobbyModeDialog() {
|
||||
return openDialog(EnableLobbyModeDialog);
|
||||
}
|
|
@ -3,17 +3,18 @@
|
|||
import { type Dispatch } from 'redux';
|
||||
|
||||
import { appNavigate, maybeRedirectToWelcomePage } from '../app';
|
||||
import { conferenceLeft, conferenceWillJoin, getCurrentConference } from '../base/conference';
|
||||
import { openDialog } from '../base/dialog';
|
||||
import { conferenceWillJoin, getCurrentConference, setPassword } from '../base/conference';
|
||||
import { hideDialog, openDialog } from '../base/dialog';
|
||||
import { getLocalParticipant } from '../base/participants';
|
||||
|
||||
import {
|
||||
KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED,
|
||||
KNOCKING_PARTICIPANT_LEFT,
|
||||
SET_KNOCKING_STATE,
|
||||
SET_LOBBY_MODE_ENABLED
|
||||
SET_LOBBY_MODE_ENABLED,
|
||||
SET_PASSWORD_JOIN_FAILED
|
||||
} from './actionTypes';
|
||||
import { DisableLobbyModeDialog, EnableLobbyModeDialog, LobbyScreen } from './components';
|
||||
import { LobbyScreen } from './components';
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
|
@ -23,7 +24,7 @@ declare var APP: Object;
|
|||
* @returns {Function}
|
||||
*/
|
||||
export function cancelKnocking() {
|
||||
return async (dispatch: Dispatch<any>, getState: Function) => {
|
||||
return async (dispatch: Dispatch<any>) => {
|
||||
if (typeof APP !== 'undefined') {
|
||||
// when we are redirecting the library should handle any
|
||||
// unload and clean of the connection.
|
||||
|
@ -33,11 +34,33 @@ export function cancelKnocking() {
|
|||
return;
|
||||
}
|
||||
|
||||
dispatch(conferenceLeft(getCurrentConference(getState)));
|
||||
dispatch(appNavigate(undefined));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to hide the lobby screen.
|
||||
*
|
||||
* @returns {hideDialog}
|
||||
*/
|
||||
export function hideLobbyScreen() {
|
||||
return hideDialog(LobbyScreen);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to join with a preset password.
|
||||
*
|
||||
* @param {string} password - The password to join with.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function joinWithPassword(password: string) {
|
||||
return async (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const conference = getCurrentConference(getState);
|
||||
|
||||
dispatch(setPassword(conference, conference.join, password));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to be dispatched when a knocking poarticipant leaves before any response.
|
||||
*
|
||||
|
@ -54,48 +77,13 @@ export function knockingParticipantLeft(id: string) {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to set the knocking state of the participant.
|
||||
*
|
||||
* @param {boolean} knocking - The new state.
|
||||
* @returns {{
|
||||
* state: boolean,
|
||||
* type: SET_KNOCKING_STATE
|
||||
* }}
|
||||
*/
|
||||
export function setKnockingState(knocking: boolean) {
|
||||
return {
|
||||
knocking,
|
||||
type: SET_KNOCKING_STATE
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts knocking and waiting for approval.
|
||||
*
|
||||
* @param {string} password - The password to bypass knocking, if any.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function startKnocking(password?: string) {
|
||||
return async (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const state = getState();
|
||||
const { membersOnly } = state['features/base/conference'];
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
|
||||
dispatch(setKnockingState(true));
|
||||
dispatch(conferenceWillJoin(membersOnly));
|
||||
membersOnly
|
||||
&& membersOnly.joinLobby(localParticipant.name, localParticipant.email, password ? password : undefined);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to open the lobby screen.
|
||||
*
|
||||
* @returns {openDialog}
|
||||
*/
|
||||
export function openLobbyScreen() {
|
||||
return openDialog(LobbyScreen);
|
||||
return openDialog(LobbyScreen, {}, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -123,7 +111,7 @@ export function participantIsKnockingOrUpdated(participant: Object) {
|
|||
*/
|
||||
export function setKnockingParticipantApproval(id: string, approved: boolean) {
|
||||
return async (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const { conference } = getState()['features/base/conference'];
|
||||
const conference = getCurrentConference(getState);
|
||||
|
||||
if (conference) {
|
||||
if (approved) {
|
||||
|
@ -135,6 +123,22 @@ export function setKnockingParticipantApproval(id: string, approved: boolean) {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to set the knocking state of the participant.
|
||||
*
|
||||
* @param {boolean} knocking - The new state.
|
||||
* @returns {{
|
||||
* state: boolean,
|
||||
* type: SET_KNOCKING_STATE
|
||||
* }}
|
||||
*/
|
||||
export function setKnockingState(knocking: boolean) {
|
||||
return {
|
||||
knocking,
|
||||
type: SET_KNOCKING_STATE
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to set the new state of the lobby mode.
|
||||
*
|
||||
|
@ -152,36 +156,50 @@ export function setLobbyModeEnabled(enabled: boolean) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Action to show the dialog to disable lobby mode.
|
||||
* Action to be dispatched when we failed to join with a password.
|
||||
*
|
||||
* @returns {showNotification}
|
||||
* @param {boolean} failed - True of recent password join failed.
|
||||
* @returns {{
|
||||
* failed: boolean,
|
||||
* type: SET_PASSWORD_JOIN_FAILED
|
||||
* }}
|
||||
*/
|
||||
export function showDisableLobbyModeDialog() {
|
||||
return openDialog(DisableLobbyModeDialog);
|
||||
export function setPasswordJoinFailed(failed: boolean) {
|
||||
return {
|
||||
failed,
|
||||
type: SET_PASSWORD_JOIN_FAILED
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to show the dialog to enable lobby mode.
|
||||
* Starts knocking and waiting for approval.
|
||||
*
|
||||
* @returns {showNotification}
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function showEnableLobbyModeDialog() {
|
||||
return openDialog(EnableLobbyModeDialog);
|
||||
export function startKnocking() {
|
||||
return async (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const state = getState();
|
||||
const { membersOnly } = state['features/base/conference'];
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
|
||||
dispatch(conferenceWillJoin(membersOnly));
|
||||
membersOnly.joinLobby(localParticipant.name, localParticipant.email);
|
||||
dispatch(setKnockingState(true));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to toggle lobby mode on or off.
|
||||
*
|
||||
* @param {boolean} enabled - The desired (new) state of the lobby mode.
|
||||
* @param {string} password - Optional password to be set.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function toggleLobbyMode(enabled: boolean, password?: string) {
|
||||
export function toggleLobbyMode(enabled: boolean) {
|
||||
return async (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const { conference } = getState()['features/base/conference'];
|
||||
const conference = getCurrentConference(getState);
|
||||
|
||||
if (enabled) {
|
||||
conference.enableLobby(password);
|
||||
conference.enableLobby();
|
||||
} else {
|
||||
conference.disableLobby();
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { toggleLobbyMode } from '../actions';
|
||||
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
* The Redux Dispatch function.
|
||||
*/
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* Function to be used to translate i18n labels.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract class to encapsulate the platform common code of the {@code DisableLobbyModeDialog}.
|
||||
*/
|
||||
export default class AbstractDisableLobbyModeDialog<P: Props = Props> extends PureComponent<P> {
|
||||
/**
|
||||
* Instantiates a new component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: P) {
|
||||
super(props);
|
||||
|
||||
this._onDisableLobbyMode = this._onDisableLobbyMode.bind(this);
|
||||
}
|
||||
|
||||
_onDisableLobbyMode: () => void;
|
||||
|
||||
/**
|
||||
* Callback to be invoked when the user initiates the lobby mode disable flow.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onDisableLobbyMode() {
|
||||
this.props.dispatch(toggleLobbyMode(false));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { getFieldValue } from '../../base/react';
|
||||
import { toggleLobbyMode } from '../actions';
|
||||
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
* The Redux Dispatch function.
|
||||
*/
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* Function to be used to translate i18n labels.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* The password value entered into the field.
|
||||
*/
|
||||
password: string
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract class to encapsulate the platform common code of the {@code EnableLobbyModeDialog}.
|
||||
*/
|
||||
export default class AbstractEnableLobbyModeDialog<P: Props = Props> extends PureComponent<P, State> {
|
||||
/**
|
||||
* Instantiates a new component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: P) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
password: ''
|
||||
};
|
||||
|
||||
this._onEnableLobbyMode = this._onEnableLobbyMode.bind(this);
|
||||
this._onChangePassword = this._onChangePassword.bind(this);
|
||||
}
|
||||
|
||||
_onChangePassword: Object => void;
|
||||
|
||||
/**
|
||||
* Callback to be invoked when the user changes the password.
|
||||
*
|
||||
* @param {SyntheticEvent} event - The SyntheticEvent instance of the change.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onChangePassword(event) {
|
||||
this.setState({
|
||||
password: getFieldValue(event)
|
||||
});
|
||||
}
|
||||
|
||||
_onEnableLobbyMode: () => void;
|
||||
|
||||
/**
|
||||
* Callback to be invoked when the user initiates the lobby mode enable flow.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onEnableLobbyMode() {
|
||||
this.props.dispatch(toggleLobbyMode(true, this.state.password));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -3,21 +3,15 @@
|
|||
import { PureComponent } from 'react';
|
||||
|
||||
import { isLocalParticipantModerator } from '../../base/participants';
|
||||
import { isToolboxVisible } from '../../toolbox';
|
||||
import { setKnockingParticipantApproval } from '../actions';
|
||||
|
||||
type Props = {
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
* The list of participants.
|
||||
*/
|
||||
_participants: Array<Object>,
|
||||
|
||||
/**
|
||||
* True if the toolbox is visible, so we need to adjust the position.
|
||||
*/
|
||||
_toolboxVisible: boolean,
|
||||
|
||||
/**
|
||||
* True if the list should be rendered.
|
||||
*/
|
||||
|
@ -37,13 +31,13 @@ type Props = {
|
|||
/**
|
||||
* Abstract class to encapsulate the platform common code of the {@code KnockingParticipantList}.
|
||||
*/
|
||||
export default class AbstractKnockingParticipantList extends PureComponent<Props> {
|
||||
export default class AbstractKnockingParticipantList<P: Props = Props> extends PureComponent<P> {
|
||||
/**
|
||||
* Instantiates a new component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
constructor(props: P) {
|
||||
super(props);
|
||||
|
||||
this._onRespondToParticipant = this._onRespondToParticipant.bind(this);
|
||||
|
@ -72,11 +66,10 @@ export default class AbstractKnockingParticipantList extends PureComponent<Props
|
|||
* @returns {Props}
|
||||
*/
|
||||
export function mapStateToProps(state: Object): $Shape<Props> {
|
||||
const _participants = state['features/lobby'].knockingParticipants;
|
||||
const { knockingParticipants, lobbyEnabled } = state['features/lobby'];
|
||||
|
||||
return {
|
||||
_participants,
|
||||
_toolboxVisible: isToolboxVisible(state),
|
||||
_visible: isLocalParticipantModerator(state) && Boolean(_participants?.length)
|
||||
_participants: knockingParticipants,
|
||||
_visible: lobbyEnabled && isLocalParticipantModerator(state) && Boolean(knockingParticipants.length)
|
||||
};
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import { getConferenceName } from '../../base/conference';
|
|||
import { getLocalParticipant } from '../../base/participants';
|
||||
import { getFieldValue } from '../../base/react';
|
||||
import { updateSettings } from '../../base/settings';
|
||||
import { cancelKnocking, startKnocking } from '../actions';
|
||||
import { cancelKnocking, joinWithPassword, setPasswordJoinFailed, startKnocking } from '../actions';
|
||||
|
||||
export const SCREEN_STATES = {
|
||||
EDIT: 1,
|
||||
|
@ -41,6 +41,11 @@ export type Props = {
|
|||
*/
|
||||
_participantName: string;
|
||||
|
||||
/**
|
||||
* True if a recent attempt to join with password failed.
|
||||
*/
|
||||
_passwordJoinFailed: boolean,
|
||||
|
||||
/**
|
||||
* The Redux dispatch function.
|
||||
*/
|
||||
|
@ -69,6 +74,11 @@ type State = {
|
|||
*/
|
||||
password: string,
|
||||
|
||||
/**
|
||||
* True if a recent attempt to join with password failed.
|
||||
*/
|
||||
passwordJoinFailed: boolean,
|
||||
|
||||
/**
|
||||
* The state of the screen. One of {@code SCREEN_STATES[*]}
|
||||
*/
|
||||
|
@ -78,19 +88,20 @@ type State = {
|
|||
/**
|
||||
* Abstract class to encapsulate the platform common code of the {@code LobbyScreen}.
|
||||
*/
|
||||
export default class AbstractLobbyScreen extends PureComponent<Props, State> {
|
||||
export default class AbstractLobbyScreen<P: Props = Props> extends PureComponent<P, State> {
|
||||
/**
|
||||
* Instantiates a new component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
constructor(props: P) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
displayName: props._participantName || '',
|
||||
email: props._participantEmail || '',
|
||||
password: '',
|
||||
passwordJoinFailed: false,
|
||||
screenState: props._participantName ? SCREEN_STATES.VIEW : SCREEN_STATES.EDIT
|
||||
};
|
||||
|
||||
|
@ -100,21 +111,37 @@ export default class AbstractLobbyScreen extends PureComponent<Props, State> {
|
|||
this._onChangeEmail = this._onChangeEmail.bind(this);
|
||||
this._onChangePassword = this._onChangePassword.bind(this);
|
||||
this._onEnableEdit = this._onEnableEdit.bind(this);
|
||||
this._onJoinWithPassword = this._onJoinWithPassword.bind(this);
|
||||
this._onSwitchToKnockMode = this._onSwitchToKnockMode.bind(this);
|
||||
this._onSwitchToPasswordMode = this._onSwitchToPasswordMode.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@code PureComponent.getDerivedStateFromProps}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
static getDerivedStateFromProps(props: Props, state: State) {
|
||||
if (props._passwordJoinFailed && !state.passwordJoinFailed) {
|
||||
return {
|
||||
password: '',
|
||||
passwordJoinFailed: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the screen title.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
_getScreenTitleKey() {
|
||||
const withPassword = Boolean(this.state.password);
|
||||
const { screenState } = this.state;
|
||||
const passwordPrompt = screenState === SCREEN_STATES.PASSWORD;
|
||||
|
||||
return this.props._knocking
|
||||
? withPassword ? 'lobby.joiningWithPasswordTitle' : 'lobby.joiningTitle'
|
||||
: 'lobby.joinTitle';
|
||||
return !passwordPrompt && this.props._knocking
|
||||
? 'lobby.joiningTitle'
|
||||
: passwordPrompt ? 'lobby.enterPasswordTitle' : 'lobby.joinTitle';
|
||||
}
|
||||
|
||||
_onAskToJoin: () => void;
|
||||
|
@ -125,7 +152,11 @@ export default class AbstractLobbyScreen extends PureComponent<Props, State> {
|
|||
* @returns {void}
|
||||
*/
|
||||
_onAskToJoin() {
|
||||
this.props.dispatch(startKnocking(this.state.password));
|
||||
this.setState({
|
||||
password: ''
|
||||
});
|
||||
|
||||
this.props.dispatch(startKnocking());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -211,6 +242,20 @@ export default class AbstractLobbyScreen extends PureComponent<Props, State> {
|
|||
});
|
||||
}
|
||||
|
||||
_onJoinWithPassword: () => void;
|
||||
|
||||
/**
|
||||
* Callback to be invoked when the user tries to join using a preset password.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onJoinWithPassword() {
|
||||
this.setState({
|
||||
passwordJoinFailed: false
|
||||
});
|
||||
this.props.dispatch(joinWithPassword(this.state.password));
|
||||
}
|
||||
|
||||
_onSwitchToKnockMode: () => void;
|
||||
|
||||
/**
|
||||
|
@ -220,8 +265,10 @@ export default class AbstractLobbyScreen extends PureComponent<Props, State> {
|
|||
*/
|
||||
_onSwitchToKnockMode() {
|
||||
this.setState({
|
||||
password: '',
|
||||
screenState: this.state.displayName ? SCREEN_STATES.VIEW : SCREEN_STATES.EDIT
|
||||
});
|
||||
this.props.dispatch(setPasswordJoinFailed(false));
|
||||
}
|
||||
|
||||
_onSwitchToPasswordMode: () => void;
|
||||
|
@ -244,11 +291,10 @@ export default class AbstractLobbyScreen extends PureComponent<Props, State> {
|
|||
*/
|
||||
_renderContent() {
|
||||
const { _knocking } = this.props;
|
||||
const { password, screenState } = this.state;
|
||||
const withPassword = Boolean(password);
|
||||
const { screenState } = this.state;
|
||||
|
||||
if (_knocking) {
|
||||
return this._renderJoining(withPassword);
|
||||
if (screenState !== SCREEN_STATES.PASSWORD && _knocking) {
|
||||
return this._renderJoining();
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -267,10 +313,9 @@ export default class AbstractLobbyScreen extends PureComponent<Props, State> {
|
|||
/**
|
||||
* Renders the joining (waiting) fragment of the screen.
|
||||
*
|
||||
* @param {boolean} withPassword - True if we're joining with a password. False otherwise.
|
||||
* @returns {React$Element}
|
||||
*/
|
||||
_renderJoining: boolean => React$Element<*>;
|
||||
_renderJoining: () => React$Element<*>;
|
||||
|
||||
/**
|
||||
* Renders the participant form to let the knocking participant enter its details.
|
||||
|
@ -301,7 +346,7 @@ export default class AbstractLobbyScreen extends PureComponent<Props, State> {
|
|||
_renderPasswordJoinButtons: () => React$Element<*>;
|
||||
|
||||
/**
|
||||
* Renders the standard button set.
|
||||
* Renders the standard (pre-knocking) button set.
|
||||
*
|
||||
* @returns {React$Element}
|
||||
*/
|
||||
|
@ -317,12 +362,14 @@ export default class AbstractLobbyScreen extends PureComponent<Props, State> {
|
|||
export function _mapStateToProps(state: Object): $Shape<Props> {
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
const participantId = localParticipant?.id;
|
||||
const { knocking, passwordJoinFailed } = state['features/lobby'];
|
||||
|
||||
return {
|
||||
_knocking: state['features/lobby'].knocking,
|
||||
_knocking: knocking,
|
||||
_meetingName: getConferenceName(state),
|
||||
_participantEmail: localParticipant.email,
|
||||
_participantEmail: localParticipant?.email,
|
||||
_participantId: participantId,
|
||||
_participantName: localParticipant.name
|
||||
_participantName: localParticipant?.name,
|
||||
_passwordJoinFailed: passwordJoinFailed
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// @flow
|
||||
|
||||
export * from './native';
|
||||
|
||||
export { default as LobbyModeButton } from './LobbyModeButton';
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// @flow
|
||||
|
||||
export * from './web';
|
||||
|
||||
export { default as LobbyModeButton } from './LobbyModeButton';
|
||||
|
|
|
@ -1,16 +1,35 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { ConfirmDialog } from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
import AbstractDisableLobbyModeDialog from '../AbstractDisableLobbyModeDialog';
|
||||
import { toggleLobbyMode } from '../../actions';
|
||||
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
* The Redux Dispatch function.
|
||||
*/
|
||||
dispatch: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a dialog that lets the user disable the lobby mode.
|
||||
*/
|
||||
class DisableLobbyModeDialog extends AbstractDisableLobbyModeDialog {
|
||||
class DisableLobbyModeDialog extends PureComponent<Props> {
|
||||
/**
|
||||
* Instantiates a new component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._onDisableLobbyMode = this._onDisableLobbyMode.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@code PureComponent#render}.
|
||||
*
|
||||
|
@ -25,6 +44,17 @@ class DisableLobbyModeDialog extends AbstractDisableLobbyModeDialog {
|
|||
}
|
||||
|
||||
_onDisableLobbyMode: () => void;
|
||||
|
||||
/**
|
||||
* Callback to be invoked when the user initiates the lobby mode disable flow.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onDisableLobbyMode() {
|
||||
this.props.dispatch(toggleLobbyMode(false));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect()(DisableLobbyModeDialog));
|
||||
|
|
|
@ -1,37 +1,50 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { Text, TextInput, View } from 'react-native';
|
||||
import React, { PureComponent } from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
import { CustomSubmitDialog } from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { StyleType } from '../../../base/styles';
|
||||
import AbstractEnableLobbyModeDialog, { type Props as AbstractProps } from '../AbstractEnableLobbyModeDialog';
|
||||
import { toggleLobbyMode } from '../../actions';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
type Props = AbstractProps & {
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Color schemed common style of the dialog feature.
|
||||
* The Redux Dispatch function.
|
||||
*/
|
||||
_dialogStyles: StyleType
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* Function to be used to translate i18n labels.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a dialog that lets the user enable the lobby mode.
|
||||
*/
|
||||
class EnableLobbyModeDialog extends AbstractEnableLobbyModeDialog<Props> {
|
||||
class EnableLobbyModeDialog extends PureComponent<Props> {
|
||||
/**
|
||||
* Instantiates a new component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._onEnableLobbyMode = this._onEnableLobbyMode.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@code PureComponent#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { _dialogStyles, t } = this.props;
|
||||
|
||||
return (
|
||||
<CustomSubmitDialog
|
||||
okKey = 'lobby.enableDialogSubmit'
|
||||
|
@ -39,27 +52,25 @@ class EnableLobbyModeDialog extends AbstractEnableLobbyModeDialog<Props> {
|
|||
titleKey = 'lobby.dialogTitle'>
|
||||
<View style = { styles.formWrapper }>
|
||||
<Text>
|
||||
{ t('lobby.enableDialogText') }
|
||||
{ this.props.t('lobby.enableDialogText') }
|
||||
</Text>
|
||||
<View style = { styles.fieldRow }>
|
||||
<Text>
|
||||
{ t('lobby.enableDialogPasswordField') }
|
||||
</Text>
|
||||
<TextInput
|
||||
autoCapitalize = 'none'
|
||||
autoCompleteType = 'off'
|
||||
onChangeText = { this._onChangePassword }
|
||||
secureTextEntry = { true }
|
||||
style = { _dialogStyles.field } />
|
||||
</View>
|
||||
</View>
|
||||
</CustomSubmitDialog>
|
||||
);
|
||||
}
|
||||
|
||||
_onChangePassword: Object => void;
|
||||
|
||||
_onEnableLobbyMode: () => void;
|
||||
|
||||
/**
|
||||
* Callback to be invoked when the user initiates the lobby mode enable flow.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onEnableLobbyMode() {
|
||||
this.props.dispatch(toggleLobbyMode(true));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,7 +6,10 @@ import { ScrollView, Text, View, TouchableOpacity } from 'react-native';
|
|||
import { Avatar } from '../../../base/avatar';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
import AbstractKnockingParticipantList, { mapStateToProps } from '../AbstractKnockingParticipantList';
|
||||
import AbstractKnockingParticipantList, {
|
||||
mapStateToProps as abstractMapStateToProps,
|
||||
type Props
|
||||
} from '../AbstractKnockingParticipantList';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
|
@ -20,15 +23,16 @@ class KnockingParticipantList extends AbstractKnockingParticipantList {
|
|||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { _participants, t } = this.props;
|
||||
const { _participants, _visible, t } = this.props;
|
||||
|
||||
// On mobile we only show a portion of the list for screen real estate reasons
|
||||
const participants = _participants.slice(0, 2);
|
||||
if (!_visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
style = { styles.knockingParticipantList }>
|
||||
{ participants.map(p => (
|
||||
{ _participants.map(p => (
|
||||
<View
|
||||
key = { p.id }
|
||||
style = { styles.knockingParticipantListEntry }>
|
||||
|
@ -75,4 +79,21 @@ class KnockingParticipantList extends AbstractKnockingParticipantList {
|
|||
_onRespondToParticipant: (string, boolean) => Function;
|
||||
}
|
||||
|
||||
export default translate(connect(mapStateToProps)(KnockingParticipantList));
|
||||
/**
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state: Object): $Shape<Props> {
|
||||
const abstractProps = abstractMapStateToProps(state);
|
||||
|
||||
return {
|
||||
...abstractProps,
|
||||
|
||||
// On mobile we only show a portion of the list for screen real estate reasons
|
||||
_participants: abstractProps._participants.slice(0, 2)
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(KnockingParticipantList));
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
// @flow
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
import { IconMeetingUnlocked, IconMeetingLocked } from '../../base/icons';
|
||||
import { isLocalParticipantModerator } from '../../base/participants';
|
||||
import { connect } from '../../base/redux';
|
||||
import AbstractButton, { type Props as AbstractProps } from '../../base/toolbox/components/AbstractButton';
|
||||
import { showDisableLobbyModeDialog, showEnableLobbyModeDialog } from '../actions';
|
||||
import { getCurrentConference } from '../../../base/conference';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { IconMeetingUnlocked, IconMeetingLocked } from '../../../base/icons';
|
||||
import { isLocalParticipantModerator } from '../../../base/participants';
|
||||
import { connect } from '../../../base/redux';
|
||||
import AbstractButton, { type Props as AbstractProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
import { showDisableLobbyModeDialog, showEnableLobbyModeDialog } from '../../actions.native';
|
||||
|
||||
type Props = AbstractProps & {
|
||||
|
||||
|
@ -63,7 +64,7 @@ class LobbyModeButton extends AbstractButton<Props, any> {
|
|||
* @returns {Props}
|
||||
*/
|
||||
export function _mapStateToProps(state: Object): $Shape<Props> {
|
||||
const { conference } = state['features/base/conference'];
|
||||
const conference = getCurrentConference(state);
|
||||
const { lobbyEnabled } = state['features/lobby'];
|
||||
const lobbySupported = conference && conference.isLobbySupported();
|
||||
|
|
@ -54,6 +54,8 @@ class LobbyScreen extends AbstractLobbyScreen {
|
|||
|
||||
_onEnableEdit: () => void;
|
||||
|
||||
_onJoinWithPassword: () => void;
|
||||
|
||||
_onSwitchToKnockMode: () => void;
|
||||
|
||||
_onSwitchToPasswordMode: () => void;
|
||||
|
@ -74,6 +76,7 @@ class LobbyScreen extends AbstractLobbyScreen {
|
|||
<Text style = { styles.joiningMessage }>
|
||||
{ this.props.t('lobby.joiningMessage') }
|
||||
</Text>
|
||||
{ this._renderStandardButtons() }
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -126,8 +129,7 @@ class LobbyScreen extends AbstractLobbyScreen {
|
|||
</TouchableOpacity>
|
||||
<Avatar
|
||||
participantId = { this.props._participantId }
|
||||
size = { 64 }
|
||||
style = { styles.avatar } />
|
||||
size = { 64 } />
|
||||
<Text style = { styles.displayNameText }>
|
||||
{ displayName }
|
||||
</Text>
|
||||
|
@ -144,6 +146,8 @@ class LobbyScreen extends AbstractLobbyScreen {
|
|||
* @inheritdoc
|
||||
*/
|
||||
_renderPasswordForm() {
|
||||
const { _passwordJoinFailed, t } = this.props;
|
||||
|
||||
return (
|
||||
<View style = { styles.formWrapper }>
|
||||
<Text style = { styles.fieldLabel }>
|
||||
|
@ -156,6 +160,9 @@ class LobbyScreen extends AbstractLobbyScreen {
|
|||
secureTextEntry = { true }
|
||||
style = { styles.field }
|
||||
value = { this.state.password } />
|
||||
{ _passwordJoinFailed && <Text style = { styles.fieldError }>
|
||||
{ t('lobby.invalidPassword') }
|
||||
</Text> }
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
@ -172,7 +179,7 @@ class LobbyScreen extends AbstractLobbyScreen {
|
|||
<>
|
||||
<TouchableOpacity
|
||||
disabled = { !this.state.password }
|
||||
onPress = { this._onAskToJoin }
|
||||
onPress = { this._onJoinWithPassword }
|
||||
style = { [
|
||||
styles.button,
|
||||
styles.primaryButton
|
||||
|
@ -201,11 +208,11 @@ class LobbyScreen extends AbstractLobbyScreen {
|
|||
* @inheritdoc
|
||||
*/
|
||||
_renderStandardButtons() {
|
||||
const { t } = this.props;
|
||||
const { _knocking, t } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<TouchableOpacity
|
||||
{ _knocking || <TouchableOpacity
|
||||
disabled = { !this.state.displayName }
|
||||
onPress = { this._onAskToJoin }
|
||||
style = { [
|
||||
|
@ -215,7 +222,7 @@ class LobbyScreen extends AbstractLobbyScreen {
|
|||
<Text style = { styles.primaryButtonText }>
|
||||
{ t('lobby.knockButton') }
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</TouchableOpacity> }
|
||||
<TouchableOpacity
|
||||
onPress = { this._onSwitchToPasswordMode }
|
||||
style = { [
|
||||
|
|
|
@ -3,4 +3,5 @@
|
|||
export { default as DisableLobbyModeDialog } from './DisableLobbyModeDialog';
|
||||
export { default as EnableLobbyModeDialog } from './EnableLobbyModeDialog';
|
||||
export { default as KnockingParticipantList } from './KnockingParticipantList';
|
||||
export { default as LobbyModeButton } from './LobbyModeButton';
|
||||
export { default as LobbyScreen } from './LobbyScreen';
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
// @flow
|
||||
|
||||
import { ColorPalette } from '../../../base/styles';
|
||||
|
||||
const SECONDARY_COLOR = '#B8C7E0';
|
||||
|
||||
export default {
|
||||
avatar: {
|
||||
borderColor: 'red'
|
||||
},
|
||||
|
||||
button: {
|
||||
alignItems: 'center',
|
||||
borderRadius: 4,
|
||||
|
@ -49,6 +47,11 @@ export default {
|
|||
padding: 8
|
||||
},
|
||||
|
||||
fieldError: {
|
||||
color: ColorPalette.warning,
|
||||
fontSize: 10
|
||||
},
|
||||
|
||||
fieldRow: {
|
||||
paddingTop: 16
|
||||
},
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Dialog } from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
import AbstractDisableLobbyModeDialog from '../AbstractDisableLobbyModeDialog';
|
||||
|
||||
/**
|
||||
* Implements a dialog that lets the user disable the lobby mode.
|
||||
*/
|
||||
class DisableLobbyModeDialog extends AbstractDisableLobbyModeDialog {
|
||||
/**
|
||||
* Implements {@code PureComponent#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
className = 'lobby-screen'
|
||||
okKey = 'lobby.disableDialogSubmit'
|
||||
onSubmit = { this._onDisableLobbyMode }
|
||||
titleKey = 'lobby.dialogTitle'>
|
||||
{ t('lobby.disableDialogContent') }
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
_onDisableLobbyMode: () => void;
|
||||
}
|
||||
|
||||
export default translate(connect()(DisableLobbyModeDialog));
|
|
@ -1,51 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Dialog } from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
import AbstractEnableLobbyModeDialog from '../AbstractEnableLobbyModeDialog';
|
||||
|
||||
/**
|
||||
* Implements a dialog that lets the user enable the lobby mode.
|
||||
*/
|
||||
class EnableLobbyModeDialog extends AbstractEnableLobbyModeDialog {
|
||||
/**
|
||||
* Implements {@code PureComponent#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
className = 'lobby-screen'
|
||||
okKey = 'lobby.enableDialogSubmit'
|
||||
onSubmit = { this._onEnableLobbyMode }
|
||||
titleKey = 'lobby.dialogTitle'>
|
||||
<div id = 'lobby-dialog'>
|
||||
<span className = 'description'>
|
||||
{ t('lobby.enableDialogText') }
|
||||
</span>
|
||||
<div className = 'field'>
|
||||
<label htmlFor = 'password'>
|
||||
{ t('lobby.enableDialogPasswordField') }
|
||||
</label>
|
||||
<input
|
||||
onChange = { this._onChangePassword }
|
||||
type = 'password'
|
||||
value = { this.state.password } />
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
_onChangePassword: Object => void;
|
||||
|
||||
_onEnableLobbyMode: () => void;
|
||||
}
|
||||
|
||||
export default translate(connect()(EnableLobbyModeDialog));
|
|
@ -5,12 +5,24 @@ import React from 'react';
|
|||
import { Avatar } from '../../../base/avatar';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
import AbstractKnockingParticipantList, { mapStateToProps } from '../AbstractKnockingParticipantList';
|
||||
import { isToolboxVisible } from '../../../toolbox';
|
||||
import AbstractKnockingParticipantList, {
|
||||
mapStateToProps as abstractMapStateToProps,
|
||||
type Props as AbstractProps
|
||||
} from '../AbstractKnockingParticipantList';
|
||||
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* True if the toolbox is visible, so we need to adjust the position.
|
||||
*/
|
||||
_toolboxVisible: boolean,
|
||||
};
|
||||
|
||||
/**
|
||||
* Component to render a list for the actively knocking participants.
|
||||
*/
|
||||
class KnockingParticipantList extends AbstractKnockingParticipantList {
|
||||
class KnockingParticipantList extends AbstractKnockingParticipantList<Props> {
|
||||
/**
|
||||
* Implements {@code PureComponent#render}.
|
||||
*
|
||||
|
@ -69,4 +81,17 @@ class KnockingParticipantList extends AbstractKnockingParticipantList {
|
|||
_onRespondToParticipant: (string, boolean) => Function;
|
||||
}
|
||||
|
||||
export default translate(connect(mapStateToProps)(KnockingParticipantList));
|
||||
/**
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state: Object): $Shape<Props> {
|
||||
return {
|
||||
...abstractMapStateToProps(state),
|
||||
_toolboxVisible: isToolboxVisible(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(KnockingParticipantList));
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { Avatar } from '../../../base/avatar';
|
||||
import { Dialog } from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Icon, IconEdit } from '../../../base/icons';
|
||||
import { ActionButton, InputField, PreMeetingScreen } from '../../../base/premeeting';
|
||||
import { LoadingIndicator } from '../../../base/react';
|
||||
import { connect } from '../../../base/redux';
|
||||
import AbstractLobbyScreen, { _mapStateToProps } from '../AbstractLobbyScreen';
|
||||
import AbstractLobbyScreen, {
|
||||
_mapStateToProps
|
||||
} from '../AbstractLobbyScreen';
|
||||
|
||||
/**
|
||||
* Implements a waiting screen that represents the participant being in the lobby.
|
||||
|
@ -20,27 +20,10 @@ class LobbyScreen extends AbstractLobbyScreen {
|
|||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { _meetingName, t } = this.props;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
disableBlanketClickDismiss = { false }
|
||||
disableEnter = { true }
|
||||
hideCancelButton = { true }
|
||||
isModal = { false }
|
||||
onCancel = { this._onCancel }
|
||||
submitDisabled = { true }
|
||||
width = 'small'>
|
||||
<div id = 'lobby-screen'>
|
||||
<span className = 'title'>
|
||||
{ t(this._getScreenTitleKey()) }
|
||||
</span>
|
||||
<span className = 'roomName'>
|
||||
{ _meetingName }
|
||||
</span>
|
||||
{ this._renderContent() }
|
||||
</div>
|
||||
</Dialog>
|
||||
<PreMeetingScreen title = { this.props.t(this._getScreenTitleKey()) }>
|
||||
{ this._renderContent() }
|
||||
</PreMeetingScreen>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -58,6 +41,8 @@ class LobbyScreen extends AbstractLobbyScreen {
|
|||
|
||||
_onEnableEdit: () => void;
|
||||
|
||||
_onJoinWithPassword: () => void;
|
||||
|
||||
_onSubmit: () => boolean;
|
||||
|
||||
_onSwitchToKnockMode: () => void;
|
||||
|
@ -71,42 +56,16 @@ class LobbyScreen extends AbstractLobbyScreen {
|
|||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
_renderJoining(withPassword) {
|
||||
_renderJoining() {
|
||||
return (
|
||||
<div className = 'joiningContainer'>
|
||||
<LoadingIndicator />
|
||||
<span>
|
||||
{ this.props.t(`lobby.${withPassword ? 'joinWithPasswordMessage' : 'joiningMessage'}`) }
|
||||
<div className = 'container'>
|
||||
<div className = 'spinner'>
|
||||
<LoadingIndicator size = 'large' />
|
||||
</div>
|
||||
<span className = 'joining-message'>
|
||||
{ this.props.t('lobby.joiningMessage') }
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the participant form to let the knocking participant enter its details.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
_renderParticipantForm() {
|
||||
const { t } = this.props;
|
||||
const { displayName, email } = this.state;
|
||||
|
||||
return (
|
||||
<div className = 'form'>
|
||||
<span>
|
||||
{ t('lobby.nameField') }
|
||||
</span>
|
||||
<input
|
||||
onChange = { this._onChangeDisplayName }
|
||||
type = 'text'
|
||||
value = { displayName } />
|
||||
<span>
|
||||
{ t('lobby.emailField') }
|
||||
</span>
|
||||
<input
|
||||
onChange = { this._onChangeEmail }
|
||||
type = 'email'
|
||||
value = { email } />
|
||||
{ this._renderStandardButtons() }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -118,26 +77,21 @@ class LobbyScreen extends AbstractLobbyScreen {
|
|||
*/
|
||||
_renderParticipantInfo() {
|
||||
const { displayName, email } = this.state;
|
||||
const { _participantId } = this.props;
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<div className = 'participantInfo'>
|
||||
<div className = 'editButton'>
|
||||
<button
|
||||
onClick = { this._onEnableEdit }
|
||||
type = 'button'>
|
||||
<Icon src = { IconEdit } />
|
||||
</button>
|
||||
<div className = 'participant-info'>
|
||||
<div className = 'form'>
|
||||
<InputField
|
||||
onChange = { this._onChangeDisplayName }
|
||||
placeHolder = { t('lobby.nameField') }
|
||||
value = { displayName } />
|
||||
|
||||
<InputField
|
||||
onChange = { this._onChangeEmail }
|
||||
placeHolder = { t('lobby.emailField') }
|
||||
value = { email } />
|
||||
</div>
|
||||
<Avatar
|
||||
participantId = { _participantId }
|
||||
size = { 64 } />
|
||||
<span className = 'displayName'>
|
||||
{ displayName }
|
||||
</span>
|
||||
<span className = 'email'>
|
||||
{ email }
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -148,13 +102,14 @@ class LobbyScreen extends AbstractLobbyScreen {
|
|||
* @inheritdoc
|
||||
*/
|
||||
_renderPasswordForm() {
|
||||
const { _passwordJoinFailed, t } = this.props;
|
||||
|
||||
return (
|
||||
<div className = 'form'>
|
||||
<span>
|
||||
{ this.props.t('lobby.passwordField') }
|
||||
</span>
|
||||
<input
|
||||
<InputField
|
||||
className = { _passwordJoinFailed ? 'error' : '' }
|
||||
onChange = { this._onChangePassword }
|
||||
placeHolder = { _passwordJoinFailed ? t('lobby.invalidPassword') : t('lobby.passwordField') }
|
||||
type = 'password'
|
||||
value = { this.state.password } />
|
||||
</div>
|
||||
|
@ -171,19 +126,17 @@ class LobbyScreen extends AbstractLobbyScreen {
|
|||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
className = 'primary'
|
||||
<ActionButton
|
||||
disabled = { !this.state.password }
|
||||
onClick = { this._onAskToJoin }
|
||||
type = 'submit'>
|
||||
onClick = { this._onJoinWithPassword }
|
||||
type = 'primary'>
|
||||
{ t('lobby.passwordJoinButton') }
|
||||
</button>
|
||||
<button
|
||||
className = 'borderLess'
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
onClick = { this._onSwitchToKnockMode }
|
||||
type = 'button'>
|
||||
type = 'secondary'>
|
||||
{ t('lobby.backToKnockModeButton') }
|
||||
</button>
|
||||
</ActionButton>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -194,23 +147,21 @@ class LobbyScreen extends AbstractLobbyScreen {
|
|||
* @inheritdoc
|
||||
*/
|
||||
_renderStandardButtons() {
|
||||
const { t } = this.props;
|
||||
const { _knocking, t } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
className = 'primary'
|
||||
{ _knocking || <ActionButton
|
||||
disabled = { !this.state.displayName }
|
||||
onClick = { this._onAskToJoin }
|
||||
type = 'submit'>
|
||||
type = 'primary'>
|
||||
{ t('lobby.knockButton') }
|
||||
</button>
|
||||
<button
|
||||
className = 'borderLess'
|
||||
</ActionButton> }
|
||||
<ActionButton
|
||||
onClick = { this._onSwitchToPasswordMode }
|
||||
type = 'button'>
|
||||
type = 'secondary'>
|
||||
{ t('lobby.enterPasswordButton') }
|
||||
</button>
|
||||
</ActionButton>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { isLocalParticipantModerator } from '../../../base/participants';
|
||||
import { Switch } from '../../../base/react';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { toggleLobbyMode } from '../../actions';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* True if lobby is currently enabled in the conference.
|
||||
*/
|
||||
_lobbyEnabled: boolean,
|
||||
|
||||
/**
|
||||
* True if the section should be visible.
|
||||
*/
|
||||
_visible: boolean,
|
||||
|
||||
/**
|
||||
* The Redux Dispatch function.
|
||||
*/
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* Function to be used to translate i18n labels.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* True if the lobby switch is toggled on.
|
||||
*/
|
||||
lobbyEnabled: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a security feature section to control lobby mode.
|
||||
*/
|
||||
class LobbySection extends PureComponent<Props, State> {
|
||||
/**
|
||||
* Instantiates a new component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
lobbyEnabled: props._lobbyEnabled
|
||||
};
|
||||
|
||||
this._onToggleLobby = this._onToggleLobby.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@code PureComponent#componentDidUpdate}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (this.props._lobbyEnabled !== prevProps._lobbyEnabled
|
||||
&& this.state.lobbyEnabled !== prevState.lobbyEnabled) {
|
||||
// eslint-disable-next-line react/no-did-update-set-state
|
||||
this.setState({
|
||||
lobbyEnabled: this.props._lobbyEnabled
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@code PureComponent#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { _visible, t } = this.props;
|
||||
|
||||
if (!_visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div id = 'lobby-section'>
|
||||
{ t('lobby.enableDialogText') }
|
||||
<div className = 'control-row'>
|
||||
<label>
|
||||
{ t('lobby.toggleLabel') }
|
||||
</label>
|
||||
<Switch
|
||||
onValueChange = { this._onToggleLobby }
|
||||
value = { this.state.lobbyEnabled } />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_onToggleLobby: () => void;
|
||||
|
||||
/**
|
||||
* Callback to be invoked when the user toggles the lobby feature on or off.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onToggleLobby() {
|
||||
const newValue = !this.state.lobbyEnabled;
|
||||
|
||||
this.setState({
|
||||
lobbyEnabled: newValue
|
||||
});
|
||||
|
||||
this.props.dispatch(toggleLobbyMode(newValue));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @returns {Props}
|
||||
*/
|
||||
function mapStateToProps(state: Object): $Shape<Props> {
|
||||
const { conference } = state['features/base/conference'];
|
||||
|
||||
return {
|
||||
_lobbyEnabled: state['features/lobby'].lobbyEnabled,
|
||||
_visible: conference && conference.isLobbySupported() && isLocalParticipantModerator(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(mapStateToProps)(LobbySection));
|
|
@ -1,6 +1,5 @@
|
|||
// @flow
|
||||
|
||||
export { default as DisableLobbyModeDialog } from './DisableLobbyModeDialog';
|
||||
export { default as EnableLobbyModeDialog } from './EnableLobbyModeDialog';
|
||||
export { default as KnockingParticipantList } from './KnockingParticipantList';
|
||||
export { default as LobbySection } from './LobbySection';
|
||||
export { default as LobbyScreen } from './LobbyScreen';
|
||||
|
|
|
@ -1,22 +1,6 @@
|
|||
// @flow
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
/**
|
||||
* Returns a displayable name for the knocking participant.
|
||||
*
|
||||
* @param {string} name - The received name.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getKnockingParticipantDisplayName(name: string) {
|
||||
if (name) {
|
||||
return name;
|
||||
}
|
||||
|
||||
return typeof interfaceConfig === 'object'
|
||||
? interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME
|
||||
: 'Fellow Jitster';
|
||||
}
|
||||
import { getCurrentConference } from '../base/conference';
|
||||
|
||||
/**
|
||||
* Approves (lets in) or rejects a knocking participant.
|
||||
|
@ -27,7 +11,7 @@ export function getKnockingParticipantDisplayName(name: string) {
|
|||
* @returns {Function}
|
||||
*/
|
||||
export function setKnockingParticipantApproval(getState: Function, id: string, approved: boolean) {
|
||||
const { conference } = getState()['features/base/conference'];
|
||||
const conference = getCurrentConference(getState());
|
||||
|
||||
if (conference) {
|
||||
if (approved) {
|
||||
|
|
|
@ -1,20 +1,22 @@
|
|||
// @flow
|
||||
|
||||
import { CONFERENCE_FAILED, CONFERENCE_JOINED } from '../base/conference';
|
||||
import { hideDialog } from '../base/dialog';
|
||||
import { JitsiConferenceErrors, JitsiConferenceEvents } from '../base/lib-jitsi-meet';
|
||||
import { getFirstLoadableAvatarUrl } from '../base/participants';
|
||||
import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
|
||||
import { NOTIFICATION_TYPE, showNotification } from '../notifications';
|
||||
import { isPrejoinPageEnabled } from '../prejoin/functions';
|
||||
|
||||
import { KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED } from './actionTypes';
|
||||
import {
|
||||
hideLobbyScreen,
|
||||
knockingParticipantLeft,
|
||||
openLobbyScreen,
|
||||
participantIsKnockingOrUpdated,
|
||||
setLobbyModeEnabled
|
||||
setLobbyModeEnabled,
|
||||
startKnocking,
|
||||
setPasswordJoinFailed
|
||||
} from './actions';
|
||||
import { LobbyScreen } from './components';
|
||||
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
|
@ -76,25 +78,38 @@ StateListenerRegistry.register(
|
|||
* @param {Object} action - The Redux action.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function _conferenceFailed({ dispatch }, next, action) {
|
||||
function _conferenceFailed({ dispatch, getState }, next, action) {
|
||||
const { error } = action;
|
||||
const state = getState();
|
||||
const nonFirstFailure = Boolean(state['features/base/conference'].membersOnly);
|
||||
|
||||
if (error.name === JitsiConferenceErrors.MEMBERS_ONLY_ERROR) {
|
||||
if (typeof error.recoverable === 'undefined') {
|
||||
error.recoverable = true;
|
||||
}
|
||||
|
||||
dispatch(openLobbyScreen());
|
||||
} else {
|
||||
dispatch(hideDialog(LobbyScreen));
|
||||
const result = next(action);
|
||||
|
||||
if (error.name === JitsiConferenceErrors.CONFERENCE_ACCESS_DENIED) {
|
||||
dispatch(showNotification({
|
||||
appearance: NOTIFICATION_TYPE.ERROR,
|
||||
hideErrorSupportLink: true,
|
||||
titleKey: 'lobby.joinRejectedMessage'
|
||||
}));
|
||||
dispatch(openLobbyScreen());
|
||||
|
||||
if (isPrejoinPageEnabled(state) && !state['features/lobby'].knocking) {
|
||||
// prejoin is enabled, so we knock automatically
|
||||
dispatch(startKnocking());
|
||||
}
|
||||
|
||||
dispatch(setPasswordJoinFailed(nonFirstFailure));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
dispatch(hideLobbyScreen());
|
||||
|
||||
if (error.name === JitsiConferenceErrors.CONFERENCE_ACCESS_DENIED) {
|
||||
dispatch(showNotification({
|
||||
appearance: NOTIFICATION_TYPE.ERROR,
|
||||
hideErrorSupportLink: true,
|
||||
titleKey: 'lobby.joinRejectedMessage'
|
||||
}));
|
||||
}
|
||||
|
||||
return next(action);
|
||||
|
@ -109,7 +124,7 @@ function _conferenceFailed({ dispatch }, next, action) {
|
|||
* @returns {Object}
|
||||
*/
|
||||
function _conferenceJoined({ dispatch }, next, action) {
|
||||
dispatch(hideDialog(LobbyScreen));
|
||||
dispatch(hideLobbyScreen());
|
||||
|
||||
return next(action);
|
||||
}
|
||||
|
@ -123,8 +138,9 @@ function _conferenceJoined({ dispatch }, next, action) {
|
|||
*/
|
||||
function _findLoadableAvatarForKnockingParticipant({ dispatch, getState }, { id }) {
|
||||
const updatedParticipant = getState()['features/lobby'].knockingParticipants.find(p => p.id === id);
|
||||
const { disableThirdPartyRequests } = getState()['features/base/config'];
|
||||
|
||||
if (updatedParticipant && !updatedParticipant.loadableAvatarUrl) {
|
||||
if (!disableThirdPartyRequests && updatedParticipant && !updatedParticipant.loadableAvatarUrl) {
|
||||
getFirstLoadableAvatarUrl(updatedParticipant).then(loadableAvatarUrl => {
|
||||
if (loadableAvatarUrl) {
|
||||
dispatch(participantIsKnockingOrUpdated({
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
// @flow
|
||||
|
||||
import { CONFERENCE_FAILED, CONFERENCE_JOINED, CONFERENCE_LEFT } from '../base/conference';
|
||||
import { CONFERENCE_JOINED, CONFERENCE_LEFT, SET_PASSWORD } from '../base/conference';
|
||||
import { ReducerRegistry } from '../base/redux';
|
||||
|
||||
import {
|
||||
KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED,
|
||||
KNOCKING_PARTICIPANT_LEFT,
|
||||
SET_KNOCKING_STATE,
|
||||
SET_LOBBY_MODE_ENABLED
|
||||
SET_LOBBY_MODE_ENABLED,
|
||||
SET_PASSWORD_JOIN_FAILED
|
||||
} from './actionTypes';
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
knocking: false,
|
||||
knockingParticipants: [],
|
||||
lobbyEnabled: false
|
||||
lobbyEnabled: false,
|
||||
passwordJoinFailed: false
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -26,12 +28,12 @@ const DEFAULT_STATE = {
|
|||
*/
|
||||
ReducerRegistry.register('features/lobby', (state = DEFAULT_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case CONFERENCE_FAILED:
|
||||
case CONFERENCE_JOINED:
|
||||
case CONFERENCE_LEFT:
|
||||
return {
|
||||
...state,
|
||||
knocking: false
|
||||
knocking: false,
|
||||
passwordJoinFailed: false
|
||||
};
|
||||
case KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED:
|
||||
return _knockingParticipantArrivedOrUpdated(action.participant, state);
|
||||
|
@ -43,13 +45,24 @@ ReducerRegistry.register('features/lobby', (state = DEFAULT_STATE, action) => {
|
|||
case SET_KNOCKING_STATE:
|
||||
return {
|
||||
...state,
|
||||
knocking: action.knocking
|
||||
knocking: action.knocking,
|
||||
passwordJoinFailed: false
|
||||
};
|
||||
case SET_LOBBY_MODE_ENABLED:
|
||||
return {
|
||||
...state,
|
||||
lobbyEnabled: action.enabled
|
||||
};
|
||||
case SET_PASSWORD:
|
||||
return {
|
||||
...state,
|
||||
passwordJoinFailed: false
|
||||
};
|
||||
case SET_PASSWORD_JOIN_FAILED:
|
||||
return {
|
||||
...state,
|
||||
passwordJoinFailed: action.failed
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
|
|
|
@ -10,7 +10,7 @@ declare var APP: Object;
|
|||
/**
|
||||
* List of errors that are not fatal (or handled differently) so then the overlays won't kick in.
|
||||
*/
|
||||
const NON_FATAR_ERRORS = [
|
||||
const NON_OVERLAY_ERRORS = [
|
||||
JitsiConferenceErrors.CONFERENCE_ACCESS_DENIED,
|
||||
JitsiConferenceErrors.CONFERENCE_DESTROYED,
|
||||
JitsiConferenceErrors.CONNECTION_ERROR
|
||||
|
@ -31,7 +31,7 @@ StateListenerRegistry.register(
|
|||
},
|
||||
/* listener */ (error, { dispatch }) => {
|
||||
error
|
||||
&& NON_FATAR_ERRORS.indexOf(error.name) === -1
|
||||
&& NON_OVERLAY_ERRORS.indexOf(error.name) === -1
|
||||
&& typeof error.recoverable === 'undefined'
|
||||
&& dispatch(setFatalError(error));
|
||||
}
|
||||
|
|
|
@ -6,10 +6,9 @@ import React, { Component } from 'react';
|
|||
import { getRoomName } from '../../base/conference';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { Icon, IconPhone, IconVolumeOff } from '../../base/icons';
|
||||
import { ActionButton, InputField, PreMeetingScreen } from '../../base/premeeting';
|
||||
import { connect } from '../../base/redux';
|
||||
import { getDisplayName, updateSettings } from '../../base/settings';
|
||||
import { isGuest } from '../../invite';
|
||||
import { VideoSettingsButton, AudioSettingsButton } from '../../toolbox';
|
||||
import {
|
||||
joinConference as joinConferenceAction,
|
||||
joinConferenceWithoutAudio as joinConferenceWithoutAudioAction,
|
||||
|
@ -17,18 +16,15 @@ import {
|
|||
setJoinByPhoneDialogVisiblity as setJoinByPhoneDialogVisiblityAction
|
||||
} from '../actions';
|
||||
import {
|
||||
getActiveVideoTrack,
|
||||
isJoinByPhoneButtonVisible,
|
||||
isDeviceStatusVisible,
|
||||
isJoinByPhoneDialogVisible
|
||||
isJoinByPhoneDialogVisible,
|
||||
isPrejoinVideoMuted
|
||||
} from '../functions';
|
||||
|
||||
import ActionButton from './buttons/ActionButton';
|
||||
import JoinByPhoneDialog from './dialogs/JoinByPhoneDialog';
|
||||
import CopyMeetingUrl from './preview/CopyMeetingUrl';
|
||||
import DeviceStatus from './preview/DeviceStatus';
|
||||
import ParticipantName from './preview/ParticipantName';
|
||||
import Preview from './preview/Preview';
|
||||
|
||||
|
||||
type Props = {
|
||||
|
||||
|
@ -42,11 +38,6 @@ type Props = {
|
|||
*/
|
||||
hasJoinByPhoneButton: boolean,
|
||||
|
||||
/**
|
||||
* Flag signaling if a user is logged in or not.
|
||||
*/
|
||||
isAnonymousUser: boolean,
|
||||
|
||||
/**
|
||||
* Joins the current meeting.
|
||||
*/
|
||||
|
@ -82,6 +73,11 @@ type Props = {
|
|||
*/
|
||||
setJoinByPhoneDialogVisiblity: Function,
|
||||
|
||||
/**
|
||||
* Flag signaling the visibility of camera preview.
|
||||
*/
|
||||
showCameraPreview: boolean,
|
||||
|
||||
/**
|
||||
* If 'JoinByPhoneDialog' is visible or not.
|
||||
*/
|
||||
|
@ -91,6 +87,11 @@ type Props = {
|
|||
* Used for translation.
|
||||
*/
|
||||
t: Function,
|
||||
|
||||
/**
|
||||
* The JitsiLocalTrack to display.
|
||||
*/
|
||||
videoTrack: ?Object,
|
||||
};
|
||||
|
||||
type State = {
|
||||
|
@ -211,34 +212,31 @@ class Prejoin extends Component<Props, State> {
|
|||
*/
|
||||
render() {
|
||||
const {
|
||||
deviceStatusVisible,
|
||||
hasJoinByPhoneButton,
|
||||
isAnonymousUser,
|
||||
joinConference,
|
||||
joinConferenceWithoutAudio,
|
||||
name,
|
||||
showCameraPreview,
|
||||
showDialog,
|
||||
t
|
||||
t,
|
||||
videoTrack
|
||||
} = this.props;
|
||||
|
||||
const { _closeDialog, _onCheckboxChange, _onDropdownClose, _onOptionsClick, _setName, _showDialog } = this;
|
||||
const { showJoinByPhoneButtons } = this.state;
|
||||
|
||||
return (
|
||||
<div className = 'prejoin-full-page'>
|
||||
<Preview name = { name } />
|
||||
<PreMeetingScreen
|
||||
footer = { this._renderFooter() }
|
||||
title = { t('prejoin.joinMeeting') }
|
||||
videoMuted = { !showCameraPreview }
|
||||
videoTrack = { videoTrack }>
|
||||
<div className = 'prejoin-input-area-container'>
|
||||
<div className = 'prejoin-input-area'>
|
||||
<div className = 'prejoin-title'>
|
||||
{t('prejoin.joinMeeting')}
|
||||
</div>
|
||||
|
||||
<CopyMeetingUrl />
|
||||
|
||||
<ParticipantName
|
||||
isEditable = { isAnonymousUser }
|
||||
joinConference = { joinConference }
|
||||
setName = { _setName }
|
||||
<InputField
|
||||
onChange = { _setName }
|
||||
onSubmit = { joinConference }
|
||||
placeHolder = { t('dialog.enterDisplayName') }
|
||||
value = { name } />
|
||||
|
||||
<div className = 'prejoin-preview-dropdown-container'>
|
||||
|
@ -275,11 +273,6 @@ class Prejoin extends Component<Props, State> {
|
|||
</ActionButton>
|
||||
</InlineDialog>
|
||||
</div>
|
||||
|
||||
<div className = 'prejoin-preview-btn-container'>
|
||||
<AudioSettingsButton visible = { true } />
|
||||
<VideoSettingsButton visible = { true } />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className = 'prejoin-checkbox-container'>
|
||||
|
@ -290,16 +283,23 @@ class Prejoin extends Component<Props, State> {
|
|||
<span>{t('prejoin.doNotShow')}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{ deviceStatusVisible && <DeviceStatus /> }
|
||||
{ showDialog && (
|
||||
<JoinByPhoneDialog
|
||||
joinConferenceWithoutAudio = { joinConferenceWithoutAudio }
|
||||
onClose = { _closeDialog } />
|
||||
)}
|
||||
</div>
|
||||
</PreMeetingScreen>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the screen footer if any.
|
||||
*
|
||||
* @returns {React$Element}
|
||||
*/
|
||||
_renderFooter() {
|
||||
return this.props.deviceStatusVisible && <DeviceStatus />;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -310,12 +310,13 @@ class Prejoin extends Component<Props, State> {
|
|||
*/
|
||||
function mapStateToProps(state): Object {
|
||||
return {
|
||||
isAnonymousUser: isGuest(state),
|
||||
deviceStatusVisible: isDeviceStatusVisible(state),
|
||||
name: getDisplayName(state),
|
||||
roomName: getRoomName(state),
|
||||
showDialog: isJoinByPhoneDialogVisible(state),
|
||||
hasJoinByPhoneButton: isJoinByPhoneButtonVisible(state)
|
||||
hasJoinByPhoneButton: isJoinByPhoneButtonVisible(state),
|
||||
showCameraPreview: !isPrejoinVideoMuted(state),
|
||||
videoTrack: getActiveVideoTrack(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Icon, IconArrowDown } from '../../../base/icons';
|
||||
|
||||
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,
|
||||
|
||||
/**
|
||||
* If the button is disabled or not.
|
||||
*/
|
||||
disabled?: boolean,
|
||||
|
||||
/**
|
||||
* If the button has options.
|
||||
*/
|
||||
hasOptions?: boolean,
|
||||
|
||||
/**
|
||||
* The type of th button: primary, secondary, text.
|
||||
*/
|
||||
type: string,
|
||||
|
||||
/**
|
||||
* OnClick button handler.
|
||||
*/
|
||||
onClick: Function,
|
||||
|
||||
/**
|
||||
* Click handler for options.
|
||||
*/
|
||||
onOptionsClick?: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* Button used for prejoin actions: Join/Join without audio/Join by phone.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
function ActionButton({ children, className, disabled, hasOptions, type, onClick, onOptionsClick }: Props) {
|
||||
let ownClassName = 'prejoin-btn';
|
||||
let clickHandler = onClick;
|
||||
let optionsClickHandler = onOptionsClick;
|
||||
|
||||
if (disabled) {
|
||||
clickHandler = null;
|
||||
optionsClickHandler = null;
|
||||
ownClassName = `${ownClassName} prejoin-btn--disabled`;
|
||||
} else {
|
||||
ownClassName = `${ownClassName} ${classNameByType[type]}`;
|
||||
}
|
||||
const cls = className ? `${className} ${ownClassName}` : ownClassName;
|
||||
|
||||
return (
|
||||
<div
|
||||
className = { cls }
|
||||
onClick = { clickHandler }>
|
||||
{children}
|
||||
{hasOptions && <div
|
||||
className = 'prejoin-btn-options'
|
||||
onClick = { optionsClickHandler }>
|
||||
<Icon
|
||||
className = 'prejoin-btn-icon'
|
||||
size = { 14 }
|
||||
src = { IconArrowDown } />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ActionButton;
|
|
@ -4,9 +4,9 @@ import React from 'react';
|
|||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Icon, IconArrowLeft } from '../../../base/icons';
|
||||
import { ActionButton } from '../../../base/premeeting';
|
||||
import { getCountryCodeFromPhone } from '../../utils';
|
||||
import Label from '../Label';
|
||||
import ActionButton from '../buttons/ActionButton';
|
||||
|
||||
type Props = {
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@ import React from 'react';
|
|||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Icon, IconClose } from '../../../base/icons';
|
||||
import { ActionButton } from '../../../base/premeeting';
|
||||
import Label from '../Label';
|
||||
import ActionButton from '../buttons/ActionButton';
|
||||
import CountryPicker from '../country-picker/CountryPicker';
|
||||
|
||||
type Props = {
|
||||
|
|
|
@ -62,7 +62,9 @@ function DeviceStatus({ deviceStatusType, deviceStatusText, rawError, t }: Props
|
|||
size = { 16 }
|
||||
src = { src } />
|
||||
<span className = 'prejoin-preview-error-desc'>{t(deviceStatusText)}</span>
|
||||
<span>{rawError}</span>
|
||||
{ rawError && <span>
|
||||
{ rawError }
|
||||
</span> }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,110 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Flag signaling if the name is ediable or not.
|
||||
*/
|
||||
isEditable: boolean,
|
||||
|
||||
/**
|
||||
* Joins the current meeting.
|
||||
*/
|
||||
joinConference: Function,
|
||||
|
||||
/**
|
||||
* 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._onKeyDown = this._onKeyDown.bind(this);
|
||||
this._onNameChange = this._onNameChange.bind(this);
|
||||
}
|
||||
|
||||
_onKeyDown: () => void;
|
||||
|
||||
/**
|
||||
* Joins the conference on 'Enter'.
|
||||
*
|
||||
* @param {Event} event - Key down event object.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onKeyDown(event) {
|
||||
if (event.key === 'Enter') {
|
||||
this.props.joinConference();
|
||||
}
|
||||
}
|
||||
|
||||
_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;
|
||||
const { _onKeyDown, _onNameChange } = this;
|
||||
|
||||
return isEditable ? (
|
||||
<input
|
||||
autoFocus = { true }
|
||||
className = 'prejoin-preview-name prejoin-preview-name--editable'
|
||||
onChange = { _onNameChange }
|
||||
onKeyDown = { _onKeyDown }
|
||||
placeholder = { t('dialog.enterDisplayName') }
|
||||
value = { value } />
|
||||
)
|
||||
: <div
|
||||
className = 'prejoin-preview-name prejoin-preview-name--text'
|
||||
onKeyDown = { _onKeyDown }
|
||||
tabIndex = '0' >
|
||||
{value}
|
||||
</div>
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default translate(ParticipantName);
|
|
@ -1,76 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Avatar } from '../../../base/avatar';
|
||||
import { Video } from '../../../base/media';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { getActiveVideoTrack, 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' />
|
||||
<div className = 'prejoin-preview-bottom-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 {
|
||||
videoTrack: getActiveVideoTrack(state),
|
||||
showCameraPreview: !isPrejoinVideoMuted(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(Preview);
|
|
@ -259,7 +259,8 @@ export function isJoinByPhoneDialogVisible(state: Object): boolean {
|
|||
* @returns {boolean}
|
||||
*/
|
||||
export function isPrejoinPageEnabled(state: Object): boolean {
|
||||
return state['features/base/config'].prejoinPageEnabled
|
||||
return navigator.product !== 'ReactNative'
|
||||
&& state['features/base/config'].prejoinPageEnabled
|
||||
&& !state['features/base/settings'].userSelectedSkipPrejoin;
|
||||
}
|
||||
|
||||
|
|
|
@ -169,19 +169,22 @@ function PasswordSection({
|
|||
}
|
||||
|
||||
return (
|
||||
<div className = 'security-dialog password'>
|
||||
<div
|
||||
className = 'info-dialog info-dialog-column info-dialog-password'
|
||||
ref = { formRef }>
|
||||
<PasswordForm
|
||||
editEnabled = { passwordEditEnabled }
|
||||
locked = { locked }
|
||||
onSubmit = { onPasswordSubmit }
|
||||
password = { password }
|
||||
passwordNumberOfDigits = { passwordNumberOfDigits } />
|
||||
</div>
|
||||
<div className = 'security-dialog password-actions'>
|
||||
{ renderPasswordActions() }
|
||||
<div className = 'security-dialog password-section'>
|
||||
{ t('security.about') }
|
||||
<div className = 'security-dialog password'>
|
||||
<div
|
||||
className = 'info-dialog info-dialog-column info-dialog-password'
|
||||
ref = { formRef }>
|
||||
<PasswordForm
|
||||
editEnabled = { passwordEditEnabled }
|
||||
locked = { locked }
|
||||
onSubmit = { onPasswordSubmit }
|
||||
password = { password }
|
||||
passwordNumberOfDigits = { passwordNumberOfDigits } />
|
||||
</div>
|
||||
<div className = 'security-dialog password-actions'>
|
||||
{ renderPasswordActions() }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -7,6 +7,7 @@ import { Dialog } from '../../../base/dialog';
|
|||
import { translate } from '../../../base/i18n';
|
||||
import { isLocalParticipantModerator } from '../../../base/participants';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { LobbySection } from '../../../lobby';
|
||||
|
||||
import Header from './Header';
|
||||
import PasswordSection from './PasswordSection';
|
||||
|
@ -62,8 +63,7 @@ function SecurityDialog({
|
|||
_locked,
|
||||
_password,
|
||||
_passwordNumberOfDigits,
|
||||
setPassword,
|
||||
t
|
||||
setPassword
|
||||
}: Props) {
|
||||
const [ passwordEditEnabled, setPasswordEditEnabled ] = useState(false);
|
||||
|
||||
|
@ -81,8 +81,8 @@ function SecurityDialog({
|
|||
titleKey = 'security.securityOptions'
|
||||
width = { 'small' }>
|
||||
<div className = 'security-dialog'>
|
||||
{ t('security.about') }
|
||||
<div className = 'invite-more-dialog separator' />
|
||||
<LobbySection />
|
||||
<div className = 'separator-line' />
|
||||
<PasswordSection
|
||||
canEditPassword = { _canEditPassword }
|
||||
conference = { _conference }
|
||||
|
|
|
@ -63,9 +63,10 @@ class SecurityDialogButton extends AbstractButton<Props, *> {
|
|||
*/
|
||||
function mapStateToProps(state: Object) {
|
||||
const { locked } = state['features/base/conference'];
|
||||
const { lobbyEnabled } = state['features/lobby'];
|
||||
|
||||
return {
|
||||
_locked: locked
|
||||
_locked: locked || lobbyEnabled
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import { connect } from '../../../base/redux';
|
|||
import { StyleType } from '../../../base/styles';
|
||||
import { SharedDocumentButton } from '../../../etherpad';
|
||||
import { InviteButton } from '../../../invite';
|
||||
import { LobbyModeButton } from '../../../lobby';
|
||||
import { LobbyModeButton } from '../../../lobby/components/native';
|
||||
import { AudioRouteButton } from '../../../mobile/audio-mode';
|
||||
import { LiveStreamButton, RecordButton } from '../../../recording';
|
||||
import { RoomLockButton } from '../../../room-lock';
|
||||
|
|
|
@ -38,7 +38,6 @@ import { SharedDocumentButton } from '../../../etherpad';
|
|||
import { openFeedbackDialog } from '../../../feedback';
|
||||
import { beginAddPeople } from '../../../invite';
|
||||
import { openKeyboardShortcutsDialog } from '../../../keyboard-shortcuts';
|
||||
import { LobbyModeButton } from '../../../lobby';
|
||||
import {
|
||||
LocalRecordingButton,
|
||||
LocalRecordingInfoDialog
|
||||
|
@ -1189,9 +1188,6 @@ class Toolbox extends Component<Props, State> {
|
|||
if (this._shouldShowButton('closedcaptions')) {
|
||||
buttonsLeft.push('closedcaptions');
|
||||
}
|
||||
if (this._shouldShowButton('lobby')) {
|
||||
buttonsRight.push('lobby');
|
||||
}
|
||||
if (overflowHasItems) {
|
||||
buttonsRight.push('overflowmenu');
|
||||
}
|
||||
|
@ -1275,8 +1271,6 @@ class Toolbox extends Component<Props, State> {
|
|||
{ this._renderVideoButton() }
|
||||
</div>
|
||||
<div className = 'button-group-right'>
|
||||
{ (buttonsRight.indexOf('lobby') !== -1)
|
||||
&& <LobbyModeButton /> }
|
||||
{ buttonsRight.indexOf('localrecording') !== -1
|
||||
&& <LocalRecordingButton
|
||||
onClick = {
|
||||
|
|
Loading…
Reference in New Issue