Browse Source

feat(prejoin_page): Rework prejoin page

* Add a checkbox for skipping the prejoin page on next use. (This is hidden for
now, until we also have a settings entry for it).
* Rework 'Join by Phone' buttons and add new overlay.
* Update the device status accordingly if there were errors while adding
devices.
* The input is filled with the display name if there was one previously used.
* Join the meeting on 'Enter' press.
master
Vlad Piersec 5 years ago
parent
commit
908712b96f

+ 83
- 7
css/_prejoin.scss View File

9
 
9
 
10
     &-input-area-container {
10
     &-input-area-container {
11
         position: absolute;
11
         position: absolute;
12
-        bottom: 128px;
12
+        bottom: 48px;
13
         width: 100%;
13
         width: 100%;
14
-        z-index: 1;
14
+        z-index: 2;
15
     }
15
     }
16
 
16
 
17
     &-input-area {
17
     &-input-area {
34
         display: inline-block;
34
         display: inline-block;
35
         font-size: 15px;
35
         font-size: 15px;
36
         line-height: 24px;
36
         line-height: 24px;
37
-        margin-bottom: 16px;
38
         padding: 7px 16px;
37
         padding: 7px 16px;
38
+        position: relative;
39
         text-align: center;
39
         text-align: center;
40
         width: 286px;
40
         width: 286px;
41
 
41
 
51
 
51
 
52
         &--text {
52
         &--text {
53
             width: auto;
53
             width: auto;
54
+            font-size: 13px;
54
             margin: 0;
55
             margin: 0;
55
             padding: 0;
56
             padding: 0;
56
         }
57
         }
57
     }
58
     }
58
 
59
 
60
+    &-btn-options {
61
+        align-items: center;
62
+        border-left: 1px solid #fff;
63
+        display: flex;
64
+        height: 100%;
65
+        justify-content: center;
66
+        position: absolute;
67
+        right: 0;
68
+        top: 0;
69
+        width: 40px;
70
+    }
71
+
59
     &-text-btns {
72
     &-text-btns {
60
         display: flex;
73
         display: flex;
61
         justify-content: space-between;
74
         justify-content: space-between;
69
         text-align: center;
82
         text-align: center;
70
         width: 100%;
83
         width: 100%;
71
     }
84
     }
85
+
86
+    &-checkbox {
87
+        border: 0;
88
+        height: 16px;
89
+        margin-right: 8px;
90
+        padding: 0;
91
+        width: 16px;
92
+    }
93
+
94
+    &-checkbox-container {
95
+        align-items: center;
96
+        color: #fff;
97
+        display: none;
98
+        font-size: 13px;
99
+        justify-content: center;
100
+        line-height: 20px;
101
+        margin-top: 16px;
102
+        width: 100%;
103
+    }
72
 }
104
 }
73
 
105
 
74
 @mixin name-placeholder {
106
 @mixin name-placeholder {
128
     &-btn-container {
160
     &-btn-container {
129
         display: flex;
161
         display: flex;
130
         justify-content: center;
162
         justify-content: center;
131
-        position: absolute;
132
-        bottom: 50px;
163
+        margin-top: 32px;
133
         width: 100%;
164
         width: 100%;
134
-        z-index: 1;
135
 
165
 
136
         &> div {
166
         &> div {
137
             margin: 0 12px;
167
             margin: 0 12px;
151
         position: absolute;
181
         position: absolute;
152
         width: 100%;
182
         width: 100%;
153
         z-index: 1;
183
         z-index: 1;
154
-        background: linear-gradient(0deg, rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.3)), linear-gradient(360deg, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0) 54.25%);
184
+        background: linear-gradient(0deg, rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.3));
185
+    }
186
+
187
+    &-bottom-overlay {
188
+        background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.9) 100%);
189
+        bottom: 0;
190
+        height: 50%;
191
+        position: absolute;
192
+        width: 100%;
193
+        z-index: 1;
155
     }
194
     }
156
 
195
 
157
     &-status {
196
     &-status {
192
         width: 49px;
231
         width: 49px;
193
         margin: 0 8px;
232
         margin: 0 8px;
194
     }
233
     }
234
+
235
+    &-dropdown-btns {
236
+        width: 320px;
237
+        padding: 8px 0;
238
+    }
239
+
240
+    &-dropdown-btn {
241
+        align-items: center;
242
+        color: #1C2025;
243
+        cursor: pointer;
244
+        display: flex;
245
+        height: 40px;
246
+        font-size: 15px;
247
+        line-height: 24px;
248
+        padding: 0 16px;
249
+
250
+        &:hover {
251
+            background-color: #DAEBFA;
252
+        }
253
+    }
254
+
255
+    &-dropdown-icon {
256
+        display: inline-block;
257
+        margin-right: 16px;
258
+
259
+        & > svg {
260
+            fill:  #1C2025;
261
+        }
262
+    }
263
+
264
+    &-dropdown-container {
265
+        & > div > div:nth-child(2) > div > div {
266
+            background: #fff;
267
+            padding: 0;
268
+        }
269
+    }
270
+
195
 }
271
 }
196
 
272
 
197
 .prejoin-copy {
273
 .prejoin-copy {

+ 2
- 1
lang/main.json View File

488
         "dialInMeeting": "Dial into the meeting",
488
         "dialInMeeting": "Dial into the meeting",
489
         "dialInPin": "Dial into the meeting and enter PIN code:",
489
         "dialInPin": "Dial into the meeting and enter PIN code:",
490
         "dialing": "Dialing",
490
         "dialing": "Dialing",
491
+        "doNotShow": "Don't show this again",
491
         "iWantToDialIn": "I want to dial in",
492
         "iWantToDialIn": "I want to dial in",
492
         "joinAudioByPhone": "Join with phone audio",
493
         "joinAudioByPhone": "Join with phone audio",
493
         "joinMeeting": "Join meeting",
494
         "joinMeeting": "Join meeting",
494
         "joinWithoutAudio": "Join without audio",
495
         "joinWithoutAudio": "Join without audio",
495
         "initiated": "Call initiated",
496
         "initiated": "Call initiated",
496
         "linkCopied": "Link copied to clipboard",
497
         "linkCopied": "Link copied to clipboard",
497
-        "lookGood": "Speaker and microphone look good",
498
+        "lookGood": "It sounds like your microphone is working properly",
498
         "or": "or",
499
         "or": "or",
499
         "calling": "Calling",
500
         "calling": "Calling",
500
         "startWithPhone": "Start with phone audio",
501
         "startWithPhone": "Start with phone audio",

+ 16
- 6
react/features/base/devices/middleware.js View File

18
     SET_AUDIO_INPUT_DEVICE,
18
     SET_AUDIO_INPUT_DEVICE,
19
     SET_VIDEO_INPUT_DEVICE
19
     SET_VIDEO_INPUT_DEVICE
20
 } from './actionTypes';
20
 } from './actionTypes';
21
-import { replaceAudioTrackById, replaceVideoTrackById } from '../../prejoin/actions';
21
+import { replaceAudioTrackById, replaceVideoTrackById, setDeviceStatusWarning } from '../../prejoin/actions';
22
 import { isPrejoinPageVisible } from '../../prejoin/functions';
22
 import { isPrejoinPageVisible } from '../../prejoin/functions';
23
 import { showNotification, showWarningNotification } from '../../notifications';
23
 import { showNotification, showWarningNotification } from '../../notifications';
24
 import { updateSettings } from '../settings';
24
 import { updateSettings } from '../settings';
65
             || JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
65
             || JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
66
                 .camera[JitsiTrackErrors.GENERAL];
66
                 .camera[JitsiTrackErrors.GENERAL];
67
         const additionalCameraErrorMsg = cameraJitsiTrackErrorMsg ? null : message;
67
         const additionalCameraErrorMsg = cameraJitsiTrackErrorMsg ? null : message;
68
+        const titleKey = name === JitsiTrackErrors.PERMISSION_DENIED
69
+            ? 'deviceError.cameraPermission' : 'deviceError.cameraError';
68
 
70
 
69
         store.dispatch(showWarningNotification({
71
         store.dispatch(showWarningNotification({
70
             description: additionalCameraErrorMsg,
72
             description: additionalCameraErrorMsg,
71
             descriptionKey: cameraErrorMsg,
73
             descriptionKey: cameraErrorMsg,
72
-            titleKey: name === JitsiTrackErrors.PERMISSION_DENIED
73
-                ? 'deviceError.cameraPermission' : 'deviceError.cameraError'
74
+            titleKey
74
         }));
75
         }));
75
 
76
 
77
+        if (isPrejoinPageVisible(store.getState())) {
78
+            store.dispatch(setDeviceStatusWarning(titleKey));
79
+        }
80
+
76
         break;
81
         break;
77
     }
82
     }
78
     case NOTIFY_MIC_ERROR: {
83
     case NOTIFY_MIC_ERROR: {
88
             || JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
93
             || JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
89
                 .microphone[JitsiTrackErrors.GENERAL];
94
                 .microphone[JitsiTrackErrors.GENERAL];
90
         const additionalMicErrorMsg = micJitsiTrackErrorMsg ? null : message;
95
         const additionalMicErrorMsg = micJitsiTrackErrorMsg ? null : message;
96
+        const titleKey = name === JitsiTrackErrors.PERMISSION_DENIED
97
+            ? 'deviceError.microphonePermission'
98
+            : 'deviceError.microphoneError';
91
 
99
 
92
         store.dispatch(showWarningNotification({
100
         store.dispatch(showWarningNotification({
93
             description: additionalMicErrorMsg,
101
             description: additionalMicErrorMsg,
94
             descriptionKey: micErrorMsg,
102
             descriptionKey: micErrorMsg,
95
-            titleKey: name === JitsiTrackErrors.PERMISSION_DENIED
96
-                ? 'deviceError.microphonePermission'
97
-                : 'deviceError.microphoneError'
103
+            titleKey
98
         }));
104
         }));
99
 
105
 
106
+        if (isPrejoinPageVisible(store.getState())) {
107
+            store.dispatch(setDeviceStatusWarning(titleKey));
108
+        }
109
+
100
         break;
110
         break;
101
     }
111
     }
102
     case SET_AUDIO_INPUT_DEVICE:
112
     case SET_AUDIO_INPUT_DEVICE:

+ 1
- 0
react/features/base/icons/svg/index.js View File

85
 export { default as IconVideoQualitySD } from './SD.svg';
85
 export { default as IconVideoQualitySD } from './SD.svg';
86
 export { default as IconVolume } from './volume.svg';
86
 export { default as IconVolume } from './volume.svg';
87
 export { default as IconVolumeEmpty } from './volume-empty.svg';
87
 export { default as IconVolumeEmpty } from './volume-empty.svg';
88
+export { default as IconVolumeOff } from './volume-off.svg';

+ 3
- 0
react/features/base/icons/svg/volume-off.svg View File

1
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+<path fill-rule="evenodd" clip-rule="evenodd" d="M7.8887 6.42609L7.87494 6.43755L3.73524 2.29786C3.33819 1.9008 2.69455 1.9007 2.29763 2.29763C1.9007 2.69455 1.9008 3.33819 2.29786 3.73524L6.30688 7.74427L6 8H3C2.44772 8 2 8.44772 2 9V15C2 15.5523 2.44772 16 3 16H6L11.1799 20.3166C11.2698 20.3915 11.383 20.4325 11.5 20.4325C11.7761 20.4325 12 20.2086 12 19.9325V13.4374L14 15.4374V16C14.179 16 14.3553 15.9882 14.5281 15.9655L16.1615 17.5989C15.4909 17.8579 14.762 18 14 18V20C15.3237 20 16.5723 19.6785 17.672 19.1094L20.2648 21.7021C20.6618 22.0992 21.3055 22.0993 21.7024 21.7024C22.0993 21.3055 22.0992 20.6618 21.7021 20.2648L19.3686 17.9312C19.373 17.9272 19.3774 17.9232 19.3818 17.9192L17.9655 16.5029C17.961 16.5068 17.9565 16.5107 17.9521 16.5147L16.5332 15.0958C16.5378 15.092 16.5424 15.0882 16.547 15.0844L15.1203 13.6577C15.1153 13.6611 15.1103 13.6645 15.1052 13.6678L12 10.5626V10.5374L10 8.53739V8.56261L9.29498 7.8576L9.30874 7.84613L7.8887 6.42609ZM14 9.66261L17.7452 13.4078C17.9099 12.9699 18 12.4955 18 12C18 9.79086 16.2091 8 14 8V9.66261ZM19.248 14.9106L20.7042 16.3668C21.5237 15.1112 22 13.6112 22 12C22 7.58172 18.4183 4 14 4V6C17.3137 6 20 8.68629 20 12C20 13.0562 19.7271 14.0486 19.248 14.9106ZM12 7.66261L9.45676 5.11937L11.1799 3.68341C11.392 3.50663 11.7073 3.53529 11.8841 3.74743C11.959 3.83729 12 3.95055 12 4.06752V7.66261ZM6.7241 10L7.72692 9.16431L10 11.4374V16.7299L6.7241 14H4V10H6.7241Z" />
3
+</svg>

+ 11
- 0
react/features/base/settings/functions.web.js View File

32
     return state['features/base/settings'].audioOutputDeviceId;
32
     return state['features/base/settings'].audioOutputDeviceId;
33
 }
33
 }
34
 
34
 
35
+/**
36
+ * Returns the saved display name.
37
+ *
38
+ * @param {Object} state - The state of the application.
39
+ * @returns {string}
40
+ */
41
+export function getDisplayName(state: Object): string {
42
+    return state['features/base/settings'].displayName || '';
43
+}
44
+
45
+
35
 /**
46
 /**
36
  * Handles changes to the `disableCallIntegration` setting.
47
  * Handles changes to the `disableCallIntegration` setting.
37
  * Noop on web.
48
  * Noop on web.

+ 2
- 1
react/features/base/settings/reducer.js View File

39
     userSelectedMicDeviceId: undefined,
39
     userSelectedMicDeviceId: undefined,
40
     userSelectedAudioOutputDeviceLabel: undefined,
40
     userSelectedAudioOutputDeviceLabel: undefined,
41
     userSelectedCameraDeviceLabel: undefined,
41
     userSelectedCameraDeviceLabel: undefined,
42
-    userSelectedMicDeviceLabel: undefined
42
+    userSelectedMicDeviceLabel: undefined,
43
+    userSelectedSkipPrejoin: undefined
43
 };
44
 };
44
 
45
 
45
 const STORE_NAME = 'features/base/settings';
46
 const STORE_NAME = 'features/base/settings';

+ 5
- 5
react/features/prejoin/actionTypes.js View File

24
  */
24
  */
25
 export const SET_DEVICE_STATUS = 'SET_DEVICE_STATUS';
25
 export const SET_DEVICE_STATUS = 'SET_DEVICE_STATUS';
26
 
26
 
27
+/**
28
+ * Action type to set the visiblity of the prejoin page for the future.
29
+ */
30
+export const SET_SKIP_PREJOIN = 'SET_SKIP_PREJOIN';
31
+
27
 /**
32
 /**
28
  * Action type to set the visiblity of the 'JoinByPhone' dialog.
33
  * Action type to set the visiblity of the 'JoinByPhone' dialog.
29
  */
34
  */
44
  */
49
  */
45
 export const SET_PREJOIN_DEVICE_ERRORS = 'SET_PREJOIN_DEVICE_ERRORS';
50
 export const SET_PREJOIN_DEVICE_ERRORS = 'SET_PREJOIN_DEVICE_ERRORS';
46
 
51
 
47
-/**
48
- * Action type to set the name of the user.
49
- */
50
-export const SET_PREJOIN_NAME = 'SET_PREJOIN_NAME';
51
-
52
 /**
52
 /**
53
  * Action type to set the visibility of the prejoin page.
53
  * Action type to set the visibility of the prejoin page.
54
  */
54
  */

+ 13
- 14
react/features/prejoin/actions.js View File

6
     ADD_PREJOIN_VIDEO_TRACK,
6
     ADD_PREJOIN_VIDEO_TRACK,
7
     PREJOIN_START_CONFERENCE,
7
     PREJOIN_START_CONFERENCE,
8
     SET_DEVICE_STATUS,
8
     SET_DEVICE_STATUS,
9
+    SET_SKIP_PREJOIN,
9
     SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
10
     SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
10
     SET_PREJOIN_AUDIO_DISABLED,
11
     SET_PREJOIN_AUDIO_DISABLED,
11
     SET_PREJOIN_AUDIO_MUTED,
12
     SET_PREJOIN_AUDIO_MUTED,
12
     SET_PREJOIN_DEVICE_ERRORS,
13
     SET_PREJOIN_DEVICE_ERRORS,
13
-    SET_PREJOIN_NAME,
14
     SET_PREJOIN_PAGE_VISIBILITY,
14
     SET_PREJOIN_PAGE_VISIBILITY,
15
     SET_PREJOIN_VIDEO_DISABLED,
15
     SET_PREJOIN_VIDEO_DISABLED,
16
     SET_PREJOIN_VIDEO_MUTED
16
     SET_PREJOIN_VIDEO_MUTED
273
     };
273
     };
274
 }
274
 }
275
 
275
 
276
-
277
 /**
276
 /**
278
- * Action used to set the visiblitiy of the 'JoinByPhoneDialog'.
277
+ * Sets the visibility of the prejoin page for future uses.
279
  *
278
  *
280
- * @param {boolean} value - The value.
279
+ * @param {boolean} value - The visibility value.
281
  * @returns {Object}
280
  * @returns {Object}
282
  */
281
  */
283
-export function setJoinByPhoneDialogVisiblity(value: boolean) {
282
+export function setSkipPrejoin(value: boolean) {
284
     return {
283
     return {
285
-        type: SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
284
+        type: SET_SKIP_PREJOIN,
286
         value
285
         value
287
     };
286
     };
288
 }
287
 }
289
 
288
 
290
 /**
289
 /**
291
- * Action used to set the initial errors after creating the tracks.
290
+ * Action used to set the visiblitiy of the 'JoinByPhoneDialog'.
292
  *
291
  *
293
- * @param {Object} value - The track errors.
292
+ * @param {boolean} value - The value.
294
  * @returns {Object}
293
  * @returns {Object}
295
  */
294
  */
296
-export function setPrejoinDeviceErrors(value: Object) {
295
+export function setJoinByPhoneDialogVisiblity(value: boolean) {
297
     return {
296
     return {
298
-        type: SET_PREJOIN_DEVICE_ERRORS,
297
+        type: SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
299
         value
298
         value
300
     };
299
     };
301
 }
300
 }
302
 
301
 
303
 /**
302
 /**
304
- * Action used to set the name of the guest user.
303
+ * Action used to set the initial errors after creating the tracks.
305
  *
304
  *
306
- * @param {string} value - The name.
305
+ * @param {Object} value - The track errors.
307
  * @returns {Object}
306
  * @returns {Object}
308
  */
307
  */
309
-export function setPrejoinName(value: string) {
308
+export function setPrejoinDeviceErrors(value: Object) {
310
     return {
309
     return {
311
-        type: SET_PREJOIN_NAME,
310
+        type: SET_PREJOIN_DEVICE_ERRORS,
312
         value
311
         value
313
     };
312
     };
314
 }
313
 }

+ 143
- 36
react/features/prejoin/components/Prejoin.js View File

1
 // @flow
1
 // @flow
2
 
2
 
3
 import React, { Component } from 'react';
3
 import React, { Component } from 'react';
4
+import InlineDialog from '@atlaskit/inline-dialog';
4
 import {
5
 import {
5
     joinConference as joinConferenceAction,
6
     joinConference as joinConferenceAction,
6
     joinConferenceWithoutAudio as joinConferenceWithoutAudioAction,
7
     joinConferenceWithoutAudio as joinConferenceWithoutAudioAction,
7
-    setJoinByPhoneDialogVisiblity as setJoinByPhoneDialogVisiblityAction,
8
-    setPrejoinName
8
+    setSkipPrejoin as setSkipPrejoinAction,
9
+    setJoinByPhoneDialogVisiblity as setJoinByPhoneDialogVisiblityAction
9
 } from '../actions';
10
 } from '../actions';
10
 import { getRoomName } from '../../base/conference';
11
 import { getRoomName } from '../../base/conference';
12
+import { Icon, IconPhone, IconVolumeOff } from '../../base/icons';
11
 import { translate } from '../../base/i18n';
13
 import { translate } from '../../base/i18n';
12
 import { connect } from '../../base/redux';
14
 import { connect } from '../../base/redux';
15
+import { getDisplayName, updateSettings } from '../../base/settings';
13
 import ActionButton from './buttons/ActionButton';
16
 import ActionButton from './buttons/ActionButton';
14
 import {
17
 import {
15
     areJoinByPhoneButtonsVisible,
18
     areJoinByPhoneButtonsVisible,
16
-    getPrejoinName,
17
     isDeviceStatusVisible,
19
     isDeviceStatusVisible,
18
     isJoinByPhoneDialogVisible
20
     isJoinByPhoneDialogVisible
19
 } from '../functions';
21
 } from '../functions';
52
     name: string,
54
     name: string,
53
 
55
 
54
     /**
56
     /**
55
-     * Sets the name for the joining user.
57
+     * Updates settings.
56
      */
58
      */
57
-    setName: Function,
59
+    updateSettings: Function,
58
 
60
 
59
     /**
61
     /**
60
      * The name of the meeting that is about to be joined.
62
      * The name of the meeting that is about to be joined.
62
     roomName: string,
64
     roomName: string,
63
 
65
 
64
     /**
66
     /**
65
-     * Sets visibilit of the 'JoinByPhoneDialog'.
67
+     * Sets visibility of the prejoin page for the next sessions.
68
+     */
69
+    setSkipPrejoin: Function,
70
+
71
+    /**
72
+     * Sets visibility of the 'JoinByPhoneDialog'.
66
      */
73
      */
67
     setJoinByPhoneDialogVisiblity: Function,
74
     setJoinByPhoneDialogVisiblity: Function,
68
 
75
 
74
     /**
81
     /**
75
      * If join by phone buttons should be visible.
82
      * If join by phone buttons should be visible.
76
      */
83
      */
77
-    showJoinByPhoneButtons: boolean,
84
+    hasJoinByPhoneButtons: boolean,
78
 
85
 
79
     /**
86
     /**
80
      * Used for translation.
87
      * Used for translation.
82
     t: Function,
89
     t: Function,
83
 };
90
 };
84
 
91
 
92
+type State = {
93
+
94
+    /**
95
+     * Flag controlling the visibility of the 'join by phone' buttons.
96
+     */
97
+    showJoinByPhoneButtons: boolean
98
+}
99
+
85
 /**
100
 /**
86
  * This component is displayed before joining a meeting.
101
  * This component is displayed before joining a meeting.
87
  */
102
  */
88
-class Prejoin extends Component<Props> {
103
+class Prejoin extends Component<Props, State> {
89
     /**
104
     /**
90
      * Initializes a new {@code Prejoin} instance.
105
      * Initializes a new {@code Prejoin} instance.
91
      *
106
      *
94
     constructor(props) {
109
     constructor(props) {
95
         super(props);
110
         super(props);
96
 
111
 
112
+        this.state = {
113
+            showJoinByPhoneButtons: false
114
+        };
97
         this._showDialog = this._showDialog.bind(this);
115
         this._showDialog = this._showDialog.bind(this);
116
+        this._onCheckboxChange = this._onCheckboxChange.bind(this);
117
+        this._onDropdownClose = this._onDropdownClose.bind(this);
118
+        this._onOptionsClick = this._onOptionsClick.bind(this);
119
+        this._setName = this._setName.bind(this);
120
+    }
121
+
122
+    _onCheckboxChange: () => void;
123
+
124
+    /**
125
+     * Handler for the checkbox.
126
+     *
127
+     * @param {Object} e - The synthetic event.
128
+     * @returns {void}
129
+     */
130
+    _onCheckboxChange(e) {
131
+        this.props.setSkipPrejoin(e.target.checked);
132
+    }
133
+
134
+    _onDropdownClose: () => void;
135
+
136
+    /**
137
+     * Closes the dropdown.
138
+     *
139
+     * @returns {void}
140
+     */
141
+    _onDropdownClose() {
142
+        this.setState({
143
+            showJoinByPhoneButtons: false
144
+        });
145
+    }
146
+
147
+    _onOptionsClick: () => void;
148
+
149
+    /**
150
+     * Displays the join by phone buttons dropdown.
151
+     *
152
+     * @param {Object} e - The synthetic event.
153
+     * @returns {void}
154
+     */
155
+    _onOptionsClick(e) {
156
+        e.stopPropagation();
157
+
158
+        this.setState({
159
+            showJoinByPhoneButtons: !this.state.showJoinByPhoneButtons
160
+        });
161
+    }
162
+
163
+    _setName: () => void;
164
+
165
+    /**
166
+     * Sets the guest participant name.
167
+     *
168
+     * @param {string} displayName - Participant name.
169
+     * @returns {void}
170
+     */
171
+    _setName(displayName) {
172
+        this.props.updateSettings({
173
+            displayName
174
+        });
98
     }
175
     }
99
 
176
 
100
     _showDialog: () => void;
177
     _showDialog: () => void;
121
             joinConference,
198
             joinConference,
122
             joinConferenceWithoutAudio,
199
             joinConferenceWithoutAudio,
123
             name,
200
             name,
124
-            setName,
125
-            showJoinByPhoneButtons,
201
+            hasJoinByPhoneButtons,
126
             t
202
             t
127
         } = this.props;
203
         } = this.props;
128
-        const { _showDialog } = this;
204
+        const { _onCheckboxChange, _onDropdownClose, _onOptionsClick, _setName, _showDialog } = this;
205
+        const { showJoinByPhoneButtons } = this.state;
129
 
206
 
130
         return (
207
         return (
131
             <div className = 'prejoin-full-page'>
208
             <div className = 'prejoin-full-page'>
132
-                <Preview />
209
+                <Preview name = { name } />
133
                 <div className = 'prejoin-input-area-container'>
210
                 <div className = 'prejoin-input-area-container'>
134
                     <div className = 'prejoin-input-area'>
211
                     <div className = 'prejoin-input-area'>
135
                         <div className = 'prejoin-title'>
212
                         <div className = 'prejoin-title'>
136
                             {t('prejoin.joinMeeting')}
213
                             {t('prejoin.joinMeeting')}
137
                         </div>
214
                         </div>
215
+
138
                         <CopyMeetingUrl />
216
                         <CopyMeetingUrl />
217
+
139
                         <ParticipantName
218
                         <ParticipantName
140
                             isEditable = { isAnonymousUser }
219
                             isEditable = { isAnonymousUser }
141
-                            setName = { setName }
220
+                            joinConference = { joinConference }
221
+                            setName = { _setName }
142
                             value = { name } />
222
                             value = { name } />
143
-                        <ActionButton
144
-                            onClick = { joinConference }
145
-                            type = 'primary'>
146
-                            { t('calendarSync.join') }
147
-                        </ActionButton>
148
-                        {showJoinByPhoneButtons
149
-                            && <div className = 'prejoin-text-btns'>
150
-                                <ActionButton
151
-                                    onClick = { joinConferenceWithoutAudio }
152
-                                    type = 'text'>
153
-                                    { t('prejoin.joinWithoutAudio') }
154
-                                </ActionButton>
223
+
224
+                        <div className = 'prejoin-preview-dropdown-container'>
225
+                            <InlineDialog
226
+                                content = { <div className = 'prejoin-preview-dropdown-btns'>
227
+                                    <div
228
+                                        className = 'prejoin-preview-dropdown-btn'
229
+                                        onClick = { joinConferenceWithoutAudio }>
230
+                                        <Icon
231
+                                            className = 'prejoin-preview-dropdown-icon'
232
+                                            size = { 24 }
233
+                                            src = { IconVolumeOff } />
234
+                                        { t('prejoin.joinWithoutAudio') }
235
+                                    </div>
236
+                                    <div
237
+                                        className = 'prejoin-preview-dropdown-btn'
238
+                                        onClick = { _showDialog }>
239
+                                        <Icon
240
+                                            className = 'prejoin-preview-dropdown-icon'
241
+                                            size = { 24 }
242
+                                            src = { IconPhone } />
243
+                                        { t('prejoin.joinAudioByPhone') }
244
+                                    </div>
245
+                                </div> }
246
+                                isOpen = { showJoinByPhoneButtons }
247
+                                onClose = { _onDropdownClose }>
155
                                 <ActionButton
248
                                 <ActionButton
156
-                                    onClick = { _showDialog }
157
-                                    type = 'text'>
158
-                                    { t('prejoin.joinAudioByPhone') }
249
+                                    hasOptions = { hasJoinByPhoneButtons }
250
+                                    onClick = { joinConference }
251
+                                    onOptionsClick = { _onOptionsClick }
252
+                                    type = 'primary'>
253
+                                    { t('prejoin.joinMeeting') }
159
                                 </ActionButton>
254
                                 </ActionButton>
160
-                            </div>}
255
+                            </InlineDialog>
256
+                        </div>
257
+
258
+                        <div className = 'prejoin-preview-btn-container'>
259
+                            <AudioSettingsButton visible = { true } />
260
+                            <VideoSettingsButton visible = { true } />
261
+                        </div>
262
+                    </div>
263
+
264
+                    <div className = 'prejoin-checkbox-container'>
265
+                        <input
266
+                            className = 'prejoin-checkbox'
267
+                            onChange = { _onCheckboxChange }
268
+                            type = 'checkbox' />
269
+                        <span>{t('prejoin.doNotShow')}</span>
161
                     </div>
270
                     </div>
162
                 </div>
271
                 </div>
163
-                <div className = 'prejoin-preview-btn-container'>
164
-                    <AudioSettingsButton visible = { true } />
165
-                    <VideoSettingsButton visible = { true } />
166
-                </div>
272
+
167
                 { deviceStatusVisible && <DeviceStatus /> }
273
                 { deviceStatusVisible && <DeviceStatus /> }
168
             </div>
274
             </div>
169
         );
275
         );
180
     return {
286
     return {
181
         isAnonymousUser: isGuest(state),
287
         isAnonymousUser: isGuest(state),
182
         deviceStatusVisible: isDeviceStatusVisible(state),
288
         deviceStatusVisible: isDeviceStatusVisible(state),
183
-        name: getPrejoinName(state),
289
+        name: getDisplayName(state),
184
         roomName: getRoomName(state),
290
         roomName: getRoomName(state),
185
         showDialog: isJoinByPhoneDialogVisible(state),
291
         showDialog: isJoinByPhoneDialogVisible(state),
186
-        showJoinByPhoneButtons: areJoinByPhoneButtonsVisible(state)
292
+        hasJoinByPhoneButtons: areJoinByPhoneButtonsVisible(state)
187
     };
293
     };
188
 }
294
 }
189
 
295
 
191
     joinConferenceWithoutAudio: joinConferenceWithoutAudioAction,
297
     joinConferenceWithoutAudio: joinConferenceWithoutAudioAction,
192
     joinConference: joinConferenceAction,
298
     joinConference: joinConferenceAction,
193
     setJoinByPhoneDialogVisiblity: setJoinByPhoneDialogVisiblityAction,
299
     setJoinByPhoneDialogVisiblity: setJoinByPhoneDialogVisiblityAction,
194
-    setName: setPrejoinName
300
+    setSkipPrejoin: setSkipPrejoinAction,
301
+    updateSettings
195
 };
302
 };
196
 
303
 
197
 export default connect(mapStateToProps, mapDispatchToProps)(translate(Prejoin));
304
 export default connect(mapStateToProps, mapDispatchToProps)(translate(Prejoin));

+ 22
- 1
react/features/prejoin/components/buttons/ActionButton.js View File

1
 // @flow
1
 // @flow
2
 
2
 
3
 import React from 'react';
3
 import React from 'react';
4
+import { Icon, IconArrowDown } from '../../../base/icons';
5
+
4
 const classNameByType = {
6
 const classNameByType = {
5
     primary: 'prejoin-btn--primary',
7
     primary: 'prejoin-btn--primary',
6
     secondary: 'prejoin-btn--secondary',
8
     secondary: 'prejoin-btn--secondary',
19
      */
21
      */
20
     className?: string,
22
     className?: string,
21
 
23
 
24
+    /**
25
+     * If the button has options.
26
+     */
27
+    hasOptions?: boolean,
28
+
22
     /**
29
     /**
23
      * The type of th button: primary, secondary, text.
30
      * The type of th button: primary, secondary, text.
24
      */
31
      */
28
      * OnClick button handler.
35
      * OnClick button handler.
29
      */
36
      */
30
     onClick: Function,
37
     onClick: Function,
38
+
39
+    /**
40
+     * Click handler for options.
41
+     */
42
+    onOptionsClick?: Function
31
 };
43
 };
32
 
44
 
33
 /**
45
 /**
35
  *
47
  *
36
  * @returns {ReactElement}
48
  * @returns {ReactElement}
37
  */
49
  */
38
-function ActionButton({ children, className, type, onClick }: Props) {
50
+function ActionButton({ children, className, hasOptions, type, onClick, onOptionsClick }: Props) {
39
     const ownClassName = `prejoin-btn ${classNameByType[type]}`;
51
     const ownClassName = `prejoin-btn ${classNameByType[type]}`;
40
     const cls = className ? `${className} ${ownClassName}` : ownClassName;
52
     const cls = className ? `${className} ${ownClassName}` : ownClassName;
41
 
53
 
44
             className = { cls }
56
             className = { cls }
45
             onClick = { onClick }>
57
             onClick = { onClick }>
46
             {children}
58
             {children}
59
+            {hasOptions && <div
60
+                className = 'prejoin-btn-options'
61
+                onClick = { onOptionsClick }>
62
+                <Icon
63
+                    className = 'prejoin-btn-icon'
64
+                    size = { 14 }
65
+                    src = { IconArrowDown } />
66
+            </div>
67
+            }
47
         </div>
68
         </div>
48
     );
69
     );
49
 }
70
 }

+ 1
- 0
react/features/prejoin/components/preview/CopyMeetingUrl.js View File

170
                 </div>}
170
                 </div>}
171
                 <Icon
171
                 <Icon
172
                     className = { `prejoin-copy-icon ${iconCls}` }
172
                     className = { `prejoin-copy-icon ${iconCls}` }
173
+                    onClick = { _copyUrl }
173
                     size = { 24 }
174
                     size = { 24 }
174
                     src = { src } />
175
                     src = { src } />
175
                 <textarea
176
                 <textarea

+ 31
- 2
react/features/prejoin/components/preview/ParticipantName.js View File

10
      */
10
      */
11
     isEditable: boolean,
11
     isEditable: boolean,
12
 
12
 
13
+    /**
14
+     * Joins the current meeting.
15
+     */
16
+    joinConference: Function,
17
+
13
     /**
18
     /**
14
      * Sets the name for the joining user.
19
      * Sets the name for the joining user.
15
      */
20
      */
32
  * @returns {ReactElement}
37
  * @returns {ReactElement}
33
  */
38
  */
34
 class ParticipantName extends Component<Props> {
39
 class ParticipantName extends Component<Props> {
40
+
35
     /**
41
     /**
36
      * Initializes a new {@code ParticipantName} instance.
42
      * Initializes a new {@code ParticipantName} instance.
37
      *
43
      *
41
     constructor(props) {
47
     constructor(props) {
42
         super(props);
48
         super(props);
43
 
49
 
50
+        this._onKeyDown = this._onKeyDown.bind(this);
44
         this._onNameChange = this._onNameChange.bind(this);
51
         this._onNameChange = this._onNameChange.bind(this);
45
     }
52
     }
46
 
53
 
54
+    _onKeyDown: () => void;
55
+
56
+    /**
57
+     * Joins the conference on 'Enter'.
58
+     *
59
+     * @param {Event} event - Key down event object.
60
+     * @returns {void}
61
+     */
62
+    _onKeyDown(event) {
63
+        if (event.key === 'Enter') {
64
+            this.props.joinConference();
65
+        }
66
+    }
67
+
47
     _onNameChange: () => void;
68
     _onNameChange: () => void;
48
 
69
 
49
     /**
70
     /**
63
      */
84
      */
64
     render() {
85
     render() {
65
         const { value, isEditable, t } = this.props;
86
         const { value, isEditable, t } = this.props;
87
+        const { _onKeyDown, _onNameChange } = this;
66
 
88
 
67
         return isEditable ? (
89
         return isEditable ? (
68
             <input
90
             <input
91
+                autoFocus = { true }
69
                 className = 'prejoin-preview-name prejoin-preview-name--editable'
92
                 className = 'prejoin-preview-name prejoin-preview-name--editable'
70
-                onChange = { this._onNameChange }
93
+                onChange = { _onNameChange }
94
+                onKeyDown = { _onKeyDown }
71
                 placeholder = { t('dialog.enterDisplayName') }
95
                 placeholder = { t('dialog.enterDisplayName') }
72
                 value = { value } />
96
                 value = { value } />
73
         )
97
         )
74
-            : <div className = 'prejoin-preview-name'>{value}</div>
98
+            : <div
99
+                className = 'prejoin-preview-name'
100
+                onKeyDown = { _onKeyDown }
101
+                tabIndex = '0' >
102
+                {value}
103
+            </div>
75
         ;
104
         ;
76
     }
105
     }
77
 }
106
 }

+ 2
- 2
react/features/prejoin/components/preview/Preview.js View File

4
 import { Avatar } from '../../../base/avatar';
4
 import { Avatar } from '../../../base/avatar';
5
 import { Video } from '../../../base/media';
5
 import { Video } from '../../../base/media';
6
 import { connect } from '../../../base/redux';
6
 import { connect } from '../../../base/redux';
7
-import { getActiveVideoTrack, getPrejoinName, isPrejoinVideoMuted } from '../../functions';
7
+import { getActiveVideoTrack, isPrejoinVideoMuted } from '../../functions';
8
 
8
 
9
 export type Props = {
9
 export type Props = {
10
 
10
 
41
         return (
41
         return (
42
             <div className = 'prejoin-preview'>
42
             <div className = 'prejoin-preview'>
43
                 <div className = 'prejoin-preview-overlay' />
43
                 <div className = 'prejoin-preview-overlay' />
44
+                <div className = 'prejoin-preview-bottom-overlay' />
44
                 <Video
45
                 <Video
45
                     className = 'flipVideoX prejoin-preview-video'
46
                     className = 'flipVideoX prejoin-preview-video'
46
                     videoTrack = {{ jitsiTrack: videoTrack }} />
47
                     videoTrack = {{ jitsiTrack: videoTrack }} />
66
  */
67
  */
67
 function mapStateToProps(state) {
68
 function mapStateToProps(state) {
68
     return {
69
     return {
69
-        name: getPrejoinName(state),
70
         videoTrack: getActiveVideoTrack(state),
70
         videoTrack: getActiveVideoTrack(state),
71
         showCameraPreview: !isPrejoinVideoMuted(state)
71
         showCameraPreview: !isPrejoinVideoMuted(state)
72
     };
72
     };

+ 2
- 11
react/features/prejoin/functions.js View File

146
     return state['features/prejoin'].audioMuted;
146
     return state['features/prejoin'].audioMuted;
147
 }
147
 }
148
 
148
 
149
-/**
150
- * Selector for getting the name that the user filled while configuring.
151
- *
152
- * @param {Object} state - The state of the app.
153
- * @returns {boolean}
154
- */
155
-export function getPrejoinName(state: Object): string {
156
-    return state['features/prejoin'].name;
157
-}
158
-
159
 /**
149
 /**
160
  * Selector for getting the mute status of the prejoin video.
150
  * Selector for getting the mute status of the prejoin video.
161
  *
151
  *
214
  * @returns {boolean}
204
  * @returns {boolean}
215
  */
205
  */
216
 export function isPrejoinPageEnabled(state: Object): boolean {
206
 export function isPrejoinPageEnabled(state: Object): boolean {
217
-    return state['features/base/config'].prejoinPageEnabled;
207
+    return state['features/base/config'].prejoinPageEnabled
208
+        && !state['features/base/settings'].userSelectedSkipPrejoin;
218
 }
209
 }
219
 
210
 
220
 /**
211
 /**

+ 12
- 25
react/features/prejoin/middleware.js View File

6
     PREJOIN_START_CONFERENCE
6
     PREJOIN_START_CONFERENCE
7
 } from './actionTypes';
7
 } from './actionTypes';
8
 import { setPrejoinAudioMuted, setPrejoinVideoMuted } from './actions';
8
 import { setPrejoinAudioMuted, setPrejoinVideoMuted } from './actions';
9
+import { updateSettings } from '../base/settings';
9
 import { SET_AUDIO_MUTED, SET_VIDEO_MUTED } from '../base/media';
10
 import { SET_AUDIO_MUTED, SET_VIDEO_MUTED } from '../base/media';
10
-import { participantUpdated, getLocalParticipant } from '../base/participants';
11
 import { MiddlewareRegistry } from '../base/redux';
11
 import { MiddlewareRegistry } from '../base/redux';
12
-import { updateSettings } from '../base/settings';
13
-import { getAllPrejoinConfiguredTracks, getPrejoinName } from './functions';
12
+import { getAllPrejoinConfiguredTracks } from './functions';
14
 
13
 
15
 declare var APP: Object;
14
 declare var APP: Object;
16
 
15
 
51
     }
50
     }
52
 
51
 
53
     case PREJOIN_START_CONFERENCE: {
52
     case PREJOIN_START_CONFERENCE: {
54
-        const { dispatch, getState } = store;
53
+        const { getState, dispatch } = store;
54
+        const state = getState();
55
+        const { userSelectedSkipPrejoin } = state['features/prejoin'];
56
+
57
+        userSelectedSkipPrejoin && dispatch(updateSettings({
58
+            userSelectedSkipPrejoin
59
+        }));
55
 
60
 
56
-        _syncParticipantName(dispatch, getState);
57
-        const tracks = await getAllPrejoinConfiguredTracks(getState());
61
+
62
+        const tracks = await getAllPrejoinConfiguredTracks(state);
58
 
63
 
59
         APP.conference.prejoinStart(tracks);
64
         APP.conference.prejoinStart(tracks);
60
 
65
 
70
         store.dispatch(setPrejoinVideoMuted(Boolean(action.muted)));
75
         store.dispatch(setPrejoinVideoMuted(Boolean(action.muted)));
71
         break;
76
         break;
72
     }
77
     }
78
+
73
     }
79
     }
74
 
80
 
75
     return next(action);
81
     return next(action);
76
 });
82
 });
77
-
78
-/**
79
- * Sets the local participant name if one is present.
80
- *
81
- * @param {Function} dispatch - The redux dispatch function.
82
- * @param {Function} getState - Gets the current state.
83
- * @returns {undefined}
84
- */
85
-function _syncParticipantName(dispatch, getState) {
86
-    const state = getState();
87
-    const name = getPrejoinName(state);
88
-
89
-    name && dispatch(
90
-            participantUpdated({
91
-                ...getLocalParticipant(state),
92
-                name
93
-            }),
94
-    );
95
-}

+ 5
- 5
react/features/prejoin/reducer.js View File

6
     ADD_PREJOIN_VIDEO_TRACK,
6
     ADD_PREJOIN_VIDEO_TRACK,
7
     SET_DEVICE_STATUS,
7
     SET_DEVICE_STATUS,
8
     SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
8
     SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
9
+    SET_SKIP_PREJOIN,
9
     SET_PREJOIN_AUDIO_DISABLED,
10
     SET_PREJOIN_AUDIO_DISABLED,
10
     SET_PREJOIN_AUDIO_MUTED,
11
     SET_PREJOIN_AUDIO_MUTED,
11
     SET_PREJOIN_DEVICE_ERRORS,
12
     SET_PREJOIN_DEVICE_ERRORS,
12
-    SET_PREJOIN_NAME,
13
     SET_PREJOIN_PAGE_VISIBILITY,
13
     SET_PREJOIN_PAGE_VISIBILITY,
14
     SET_PREJOIN_VIDEO_DISABLED,
14
     SET_PREJOIN_VIDEO_DISABLED,
15
     SET_PREJOIN_VIDEO_MUTED
15
     SET_PREJOIN_VIDEO_MUTED
24
     deviceStatusType: 'ok',
24
     deviceStatusType: 'ok',
25
     showPrejoin: true,
25
     showPrejoin: true,
26
     showJoinByPhoneDialog: false,
26
     showJoinByPhoneDialog: false,
27
+    userSelectedSkipPrejoin: false,
27
     videoTrack: null,
28
     videoTrack: null,
28
     audioTrack: null,
29
     audioTrack: null,
29
     contentSharingTrack: null,
30
     contentSharingTrack: null,
30
-    rawError: '',
31
-    name: ''
31
+    rawError: ''
32
 };
32
 };
33
 
33
 
34
 /**
34
 /**
58
             };
58
             };
59
         }
59
         }
60
 
60
 
61
-        case SET_PREJOIN_NAME: {
61
+        case SET_SKIP_PREJOIN: {
62
             return {
62
             return {
63
                 ...state,
63
                 ...state,
64
-                name: action.value
64
+                userSelectedSkipPrejoin: action.value
65
             };
65
             };
66
         }
66
         }
67
 
67
 

Loading…
Cancel
Save