Преглед изворни кода

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 година
родитељ
комит
908712b96f

+ 83
- 7
css/_prejoin.scss Прегледај датотеку

@@ -9,9 +9,9 @@
9 9
 
10 10
     &-input-area-container {
11 11
         position: absolute;
12
-        bottom: 128px;
12
+        bottom: 48px;
13 13
         width: 100%;
14
-        z-index: 1;
14
+        z-index: 2;
15 15
     }
16 16
 
17 17
     &-input-area {
@@ -34,8 +34,8 @@
34 34
         display: inline-block;
35 35
         font-size: 15px;
36 36
         line-height: 24px;
37
-        margin-bottom: 16px;
38 37
         padding: 7px 16px;
38
+        position: relative;
39 39
         text-align: center;
40 40
         width: 286px;
41 41
 
@@ -51,11 +51,24 @@
51 51
 
52 52
         &--text {
53 53
             width: auto;
54
+            font-size: 13px;
54 55
             margin: 0;
55 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 72
     &-text-btns {
60 73
         display: flex;
61 74
         justify-content: space-between;
@@ -69,6 +82,25 @@
69 82
         text-align: center;
70 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 106
 @mixin name-placeholder {
@@ -128,10 +160,8 @@
128 160
     &-btn-container {
129 161
         display: flex;
130 162
         justify-content: center;
131
-        position: absolute;
132
-        bottom: 50px;
163
+        margin-top: 32px;
133 164
         width: 100%;
134
-        z-index: 1;
135 165
 
136 166
         &> div {
137 167
             margin: 0 12px;
@@ -151,7 +181,16 @@
151 181
         position: absolute;
152 182
         width: 100%;
153 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 196
     &-status {
@@ -192,6 +231,43 @@
192 231
         width: 49px;
193 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 273
 .prejoin-copy {

+ 2
- 1
lang/main.json Прегледај датотеку

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

+ 16
- 6
react/features/base/devices/middleware.js Прегледај датотеку

@@ -18,7 +18,7 @@ import {
18 18
     SET_AUDIO_INPUT_DEVICE,
19 19
     SET_VIDEO_INPUT_DEVICE
20 20
 } from './actionTypes';
21
-import { replaceAudioTrackById, replaceVideoTrackById } from '../../prejoin/actions';
21
+import { replaceAudioTrackById, replaceVideoTrackById, setDeviceStatusWarning } from '../../prejoin/actions';
22 22
 import { isPrejoinPageVisible } from '../../prejoin/functions';
23 23
 import { showNotification, showWarningNotification } from '../../notifications';
24 24
 import { updateSettings } from '../settings';
@@ -65,14 +65,19 @@ MiddlewareRegistry.register(store => next => action => {
65 65
             || JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
66 66
                 .camera[JitsiTrackErrors.GENERAL];
67 67
         const additionalCameraErrorMsg = cameraJitsiTrackErrorMsg ? null : message;
68
+        const titleKey = name === JitsiTrackErrors.PERMISSION_DENIED
69
+            ? 'deviceError.cameraPermission' : 'deviceError.cameraError';
68 70
 
69 71
         store.dispatch(showWarningNotification({
70 72
             description: additionalCameraErrorMsg,
71 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 81
         break;
77 82
     }
78 83
     case NOTIFY_MIC_ERROR: {
@@ -88,15 +93,20 @@ MiddlewareRegistry.register(store => next => action => {
88 93
             || JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
89 94
                 .microphone[JitsiTrackErrors.GENERAL];
90 95
         const additionalMicErrorMsg = micJitsiTrackErrorMsg ? null : message;
96
+        const titleKey = name === JitsiTrackErrors.PERMISSION_DENIED
97
+            ? 'deviceError.microphonePermission'
98
+            : 'deviceError.microphoneError';
91 99
 
92 100
         store.dispatch(showWarningNotification({
93 101
             description: additionalMicErrorMsg,
94 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 110
         break;
101 111
     }
102 112
     case SET_AUDIO_INPUT_DEVICE:

+ 1
- 0
react/features/base/icons/svg/index.js Прегледај датотеку

@@ -85,3 +85,4 @@ export { default as IconVideoQualityLD } from './LD.svg';
85 85
 export { default as IconVideoQualitySD } from './SD.svg';
86 86
 export { default as IconVolume } from './volume.svg';
87 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 Прегледај датотеку

@@ -0,0 +1,3 @@
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 Прегледај датотеку

@@ -32,6 +32,17 @@ export function getCurrentOutputDeviceId(state: Object) {
32 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 47
  * Handles changes to the `disableCallIntegration` setting.
37 48
  * Noop on web.

+ 2
- 1
react/features/base/settings/reducer.js Прегледај датотеку

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

+ 5
- 5
react/features/prejoin/actionTypes.js Прегледај датотеку

@@ -24,6 +24,11 @@ export const PREJOIN_START_CONFERENCE = 'PREJOIN_START_CONFERENCE';
24 24
  */
25 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 33
  * Action type to set the visiblity of the 'JoinByPhone' dialog.
29 34
  */
@@ -44,11 +49,6 @@ export const SET_PREJOIN_AUDIO_MUTED = 'SET_PREJOIN_AUDIO_MUTED';
44 49
  */
45 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 53
  * Action type to set the visibility of the prejoin page.
54 54
  */

+ 13
- 14
react/features/prejoin/actions.js Прегледај датотеку

@@ -6,11 +6,11 @@ import {
6 6
     ADD_PREJOIN_VIDEO_TRACK,
7 7
     PREJOIN_START_CONFERENCE,
8 8
     SET_DEVICE_STATUS,
9
+    SET_SKIP_PREJOIN,
9 10
     SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
10 11
     SET_PREJOIN_AUDIO_DISABLED,
11 12
     SET_PREJOIN_AUDIO_MUTED,
12 13
     SET_PREJOIN_DEVICE_ERRORS,
13
-    SET_PREJOIN_NAME,
14 14
     SET_PREJOIN_PAGE_VISIBILITY,
15 15
     SET_PREJOIN_VIDEO_DISABLED,
16 16
     SET_PREJOIN_VIDEO_MUTED
@@ -273,42 +273,41 @@ export function setDeviceStatusWarning(deviceStatusText: string) {
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 280
  * @returns {Object}
282 281
  */
283
-export function setJoinByPhoneDialogVisiblity(value: boolean) {
282
+export function setSkipPrejoin(value: boolean) {
284 283
     return {
285
-        type: SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
284
+        type: SET_SKIP_PREJOIN,
286 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 293
  * @returns {Object}
295 294
  */
296
-export function setPrejoinDeviceErrors(value: Object) {
295
+export function setJoinByPhoneDialogVisiblity(value: boolean) {
297 296
     return {
298
-        type: SET_PREJOIN_DEVICE_ERRORS,
297
+        type: SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
299 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 306
  * @returns {Object}
308 307
  */
309
-export function setPrejoinName(value: string) {
308
+export function setPrejoinDeviceErrors(value: Object) {
310 309
     return {
311
-        type: SET_PREJOIN_NAME,
310
+        type: SET_PREJOIN_DEVICE_ERRORS,
312 311
         value
313 312
     };
314 313
 }

+ 143
- 36
react/features/prejoin/components/Prejoin.js Прегледај датотеку

@@ -1,19 +1,21 @@
1 1
 // @flow
2 2
 
3 3
 import React, { Component } from 'react';
4
+import InlineDialog from '@atlaskit/inline-dialog';
4 5
 import {
5 6
     joinConference as joinConferenceAction,
6 7
     joinConferenceWithoutAudio as joinConferenceWithoutAudioAction,
7
-    setJoinByPhoneDialogVisiblity as setJoinByPhoneDialogVisiblityAction,
8
-    setPrejoinName
8
+    setSkipPrejoin as setSkipPrejoinAction,
9
+    setJoinByPhoneDialogVisiblity as setJoinByPhoneDialogVisiblityAction
9 10
 } from '../actions';
10 11
 import { getRoomName } from '../../base/conference';
12
+import { Icon, IconPhone, IconVolumeOff } from '../../base/icons';
11 13
 import { translate } from '../../base/i18n';
12 14
 import { connect } from '../../base/redux';
15
+import { getDisplayName, updateSettings } from '../../base/settings';
13 16
 import ActionButton from './buttons/ActionButton';
14 17
 import {
15 18
     areJoinByPhoneButtonsVisible,
16
-    getPrejoinName,
17 19
     isDeviceStatusVisible,
18 20
     isJoinByPhoneDialogVisible
19 21
 } from '../functions';
@@ -52,9 +54,9 @@ type Props = {
52 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 62
      * The name of the meeting that is about to be joined.
@@ -62,7 +64,12 @@ type Props = {
62 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 74
     setJoinByPhoneDialogVisiblity: Function,
68 75
 
@@ -74,7 +81,7 @@ type Props = {
74 81
     /**
75 82
      * If join by phone buttons should be visible.
76 83
      */
77
-    showJoinByPhoneButtons: boolean,
84
+    hasJoinByPhoneButtons: boolean,
78 85
 
79 86
     /**
80 87
      * Used for translation.
@@ -82,10 +89,18 @@ type Props = {
82 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 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 105
      * Initializes a new {@code Prejoin} instance.
91 106
      *
@@ -94,7 +109,69 @@ class Prejoin extends Component<Props> {
94 109
     constructor(props) {
95 110
         super(props);
96 111
 
112
+        this.state = {
113
+            showJoinByPhoneButtons: false
114
+        };
97 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 177
     _showDialog: () => void;
@@ -121,49 +198,78 @@ class Prejoin extends Component<Props> {
121 198
             joinConference,
122 199
             joinConferenceWithoutAudio,
123 200
             name,
124
-            setName,
125
-            showJoinByPhoneButtons,
201
+            hasJoinByPhoneButtons,
126 202
             t
127 203
         } = this.props;
128
-        const { _showDialog } = this;
204
+        const { _onCheckboxChange, _onDropdownClose, _onOptionsClick, _setName, _showDialog } = this;
205
+        const { showJoinByPhoneButtons } = this.state;
129 206
 
130 207
         return (
131 208
             <div className = 'prejoin-full-page'>
132
-                <Preview />
209
+                <Preview name = { name } />
133 210
                 <div className = 'prejoin-input-area-container'>
134 211
                     <div className = 'prejoin-input-area'>
135 212
                         <div className = 'prejoin-title'>
136 213
                             {t('prejoin.joinMeeting')}
137 214
                         </div>
215
+
138 216
                         <CopyMeetingUrl />
217
+
139 218
                         <ParticipantName
140 219
                             isEditable = { isAnonymousUser }
141
-                            setName = { setName }
220
+                            joinConference = { joinConference }
221
+                            setName = { _setName }
142 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 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 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 270
                     </div>
162 271
                 </div>
163
-                <div className = 'prejoin-preview-btn-container'>
164
-                    <AudioSettingsButton visible = { true } />
165
-                    <VideoSettingsButton visible = { true } />
166
-                </div>
272
+
167 273
                 { deviceStatusVisible && <DeviceStatus /> }
168 274
             </div>
169 275
         );
@@ -180,10 +286,10 @@ function mapStateToProps(state): Object {
180 286
     return {
181 287
         isAnonymousUser: isGuest(state),
182 288
         deviceStatusVisible: isDeviceStatusVisible(state),
183
-        name: getPrejoinName(state),
289
+        name: getDisplayName(state),
184 290
         roomName: getRoomName(state),
185 291
         showDialog: isJoinByPhoneDialogVisible(state),
186
-        showJoinByPhoneButtons: areJoinByPhoneButtonsVisible(state)
292
+        hasJoinByPhoneButtons: areJoinByPhoneButtonsVisible(state)
187 293
     };
188 294
 }
189 295
 
@@ -191,7 +297,8 @@ const mapDispatchToProps = {
191 297
     joinConferenceWithoutAudio: joinConferenceWithoutAudioAction,
192 298
     joinConference: joinConferenceAction,
193 299
     setJoinByPhoneDialogVisiblity: setJoinByPhoneDialogVisiblityAction,
194
-    setName: setPrejoinName
300
+    setSkipPrejoin: setSkipPrejoinAction,
301
+    updateSettings
195 302
 };
196 303
 
197 304
 export default connect(mapStateToProps, mapDispatchToProps)(translate(Prejoin));

+ 22
- 1
react/features/prejoin/components/buttons/ActionButton.js Прегледај датотеку

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

+ 1
- 0
react/features/prejoin/components/preview/CopyMeetingUrl.js Прегледај датотеку

@@ -170,6 +170,7 @@ class CopyMeetingUrl extends Component<Props, State> {
170 170
                 </div>}
171 171
                 <Icon
172 172
                     className = { `prejoin-copy-icon ${iconCls}` }
173
+                    onClick = { _copyUrl }
173 174
                     size = { 24 }
174 175
                     src = { src } />
175 176
                 <textarea

+ 31
- 2
react/features/prejoin/components/preview/ParticipantName.js Прегледај датотеку

@@ -10,6 +10,11 @@ type Props = {
10 10
      */
11 11
     isEditable: boolean,
12 12
 
13
+    /**
14
+     * Joins the current meeting.
15
+     */
16
+    joinConference: Function,
17
+
13 18
     /**
14 19
      * Sets the name for the joining user.
15 20
      */
@@ -32,6 +37,7 @@ type Props = {
32 37
  * @returns {ReactElement}
33 38
  */
34 39
 class ParticipantName extends Component<Props> {
40
+
35 41
     /**
36 42
      * Initializes a new {@code ParticipantName} instance.
37 43
      *
@@ -41,9 +47,24 @@ class ParticipantName extends Component<Props> {
41 47
     constructor(props) {
42 48
         super(props);
43 49
 
50
+        this._onKeyDown = this._onKeyDown.bind(this);
44 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 68
     _onNameChange: () => void;
48 69
 
49 70
     /**
@@ -63,15 +84,23 @@ class ParticipantName extends Component<Props> {
63 84
      */
64 85
     render() {
65 86
         const { value, isEditable, t } = this.props;
87
+        const { _onKeyDown, _onNameChange } = this;
66 88
 
67 89
         return isEditable ? (
68 90
             <input
91
+                autoFocus = { true }
69 92
                 className = 'prejoin-preview-name prejoin-preview-name--editable'
70
-                onChange = { this._onNameChange }
93
+                onChange = { _onNameChange }
94
+                onKeyDown = { _onKeyDown }
71 95
                 placeholder = { t('dialog.enterDisplayName') }
72 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 Прегледај датотеку

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

+ 2
- 11
react/features/prejoin/functions.js Прегледај датотеку

@@ -146,16 +146,6 @@ export function isPrejoinAudioMuted(state: Object): boolean {
146 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 150
  * Selector for getting the mute status of the prejoin video.
161 151
  *
@@ -214,7 +204,8 @@ export function isJoinByPhoneDialogVisible(state: Object): boolean {
214 204
  * @returns {boolean}
215 205
  */
216 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 Прегледај датотеку

@@ -6,11 +6,10 @@ import {
6 6
     PREJOIN_START_CONFERENCE
7 7
 } from './actionTypes';
8 8
 import { setPrejoinAudioMuted, setPrejoinVideoMuted } from './actions';
9
+import { updateSettings } from '../base/settings';
9 10
 import { SET_AUDIO_MUTED, SET_VIDEO_MUTED } from '../base/media';
10
-import { participantUpdated, getLocalParticipant } from '../base/participants';
11 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 14
 declare var APP: Object;
16 15
 
@@ -51,10 +50,16 @@ MiddlewareRegistry.register(store => next => async action => {
51 50
     }
52 51
 
53 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 64
         APP.conference.prejoinStart(tracks);
60 65
 
@@ -70,26 +75,8 @@ MiddlewareRegistry.register(store => next => async action => {
70 75
         store.dispatch(setPrejoinVideoMuted(Boolean(action.muted)));
71 76
         break;
72 77
     }
78
+
73 79
     }
74 80
 
75 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 Прегледај датотеку

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

Loading…
Откажи
Сачувај