Selaa lähdekoodia

ref: merge prejoin with lobby

master
Bettenbuk Zoltan 5 vuotta sitten
vanhempi
commit
29dc63fbcb
68 muutettua tiedostoa jossa 1415 lisäystä ja 1211 poistoa
  1. 27
    98
      css/_lobby.scss
  2. 3
    157
      css/_prejoin.scss
  3. 194
    0
      css/_premeeting-screens.scss
  4. 1
    0
      css/main.scss
  5. 37
    15
      css/modals/security/_security.scss
  6. 1
    1
      interface_config.js
  7. 9
    6
      lang/main.json
  8. 15
    19
      react/features/base/conference/actions.js
  9. 4
    5
      react/features/base/conference/middleware.js
  10. 24
    28
      react/features/base/conference/reducer.js
  11. 1
    2
      react/features/base/connection/actionTypes.js
  12. 5
    9
      react/features/base/connection/actions.native.js
  13. 4
    1
      react/features/base/dialog/actions.js
  14. 8
    6
      react/features/base/dialog/components/AbstractDialogContainer.js
  15. 4
    0
      react/features/base/dialog/components/web/DialogContainer.js
  16. 4
    2
      react/features/base/dialog/reducer.js
  17. 2
    2
      react/features/base/icons/svg/copy.svg
  18. 1
    1
      react/features/base/icons/svg/index.js
  19. 0
    0
      react/features/base/premeeting/components/index.native.js
  20. 3
    0
      react/features/base/premeeting/components/index.web.js
  21. 77
    0
      react/features/base/premeeting/components/web/ActionButton.js
  22. 22
    25
      react/features/base/premeeting/components/web/CopyMeetingUrl.js
  23. 175
    0
      react/features/base/premeeting/components/web/InputField.js
  24. 73
    0
      react/features/base/premeeting/components/web/PreMeetingScreen.js
  25. 73
    0
      react/features/base/premeeting/components/web/Preview.js
  26. 5
    0
      react/features/base/premeeting/components/web/index.js
  27. 3
    0
      react/features/base/premeeting/index.js
  28. 5
    0
      react/features/base/premeeting/logger.js
  29. 2
    2
      react/features/conference/components/web/Conference.js
  30. 5
    0
      react/features/lobby/actionTypes.js
  31. 25
    0
      react/features/lobby/actions.native.js
  32. 72
    54
      react/features/lobby/actions.web.js
  33. 0
    47
      react/features/lobby/components/AbstractDisableLobbyModeDialog.js
  34. 0
    75
      react/features/lobby/components/AbstractEnableLobbyModeDialog.js
  35. 6
    13
      react/features/lobby/components/AbstractKnockingParticipantList.js
  36. 65
    18
      react/features/lobby/components/AbstractLobbyScreen.js
  37. 0
    2
      react/features/lobby/components/index.native.js
  38. 0
    2
      react/features/lobby/components/index.web.js
  39. 33
    3
      react/features/lobby/components/native/DisableLobbyModeDialog.js
  40. 35
    24
      react/features/lobby/components/native/EnableLobbyModeDialog.js
  41. 27
    6
      react/features/lobby/components/native/KnockingParticipantList.js
  42. 8
    7
      react/features/lobby/components/native/LobbyModeButton.js
  43. 13
    6
      react/features/lobby/components/native/LobbyScreen.js
  44. 1
    0
      react/features/lobby/components/native/index.js
  45. 7
    4
      react/features/lobby/components/native/styles.js
  46. 0
    36
      react/features/lobby/components/web/DisableLobbyModeDialog.js
  47. 0
    51
      react/features/lobby/components/web/EnableLobbyModeDialog.js
  48. 28
    3
      react/features/lobby/components/web/KnockingParticipantList.js
  49. 48
    97
      react/features/lobby/components/web/LobbyScreen.js
  50. 136
    0
      react/features/lobby/components/web/LobbySection.js
  51. 1
    2
      react/features/lobby/components/web/index.js
  52. 2
    18
      react/features/lobby/functions.js
  53. 31
    15
      react/features/lobby/middleware.js
  54. 19
    6
      react/features/lobby/reducer.js
  55. 2
    2
      react/features/overlay/middleware.js
  56. 39
    38
      react/features/prejoin/components/Prejoin.js
  57. 0
    88
      react/features/prejoin/components/buttons/ActionButton.js
  58. 1
    1
      react/features/prejoin/components/dialogs/DialInDialog.js
  59. 1
    1
      react/features/prejoin/components/dialogs/DialOutDialog.js
  60. 3
    1
      react/features/prejoin/components/preview/DeviceStatus.js
  61. 0
    110
      react/features/prejoin/components/preview/ParticipantName.js
  62. 0
    76
      react/features/prejoin/components/preview/Preview.js
  63. 2
    1
      react/features/prejoin/functions.js
  64. 16
    13
      react/features/security/components/security-dialog/PasswordSection.js
  65. 4
    4
      react/features/security/components/security-dialog/SecurityDialog.js
  66. 2
    1
      react/features/security/components/security-dialog/SecurityDialogButton.js
  67. 1
    1
      react/features/toolbox/components/native/OverflowMenu.js
  68. 0
    6
      react/features/toolbox/components/web/Toolbox.js

+ 27
- 98
css/_lobby.scss Näytä tiedosto

1
 #lobby-screen {
1
 #lobby-screen {
2
-    align-items: center;
3
-    color: $overflowMenuItemColor;
4
-    display: flex;
5
-    flex-direction: column;
6
-    font-size: 1.2em;
7
-    margin: 48px 36px;
8
-
9
-    span {
10
-        padding: 8px 0;
11
-    }
12
-
13
-    .title {
14
-        color: $defaultColor;
15
-        font-size: 2em;
16
-    }
17
-
18
-    .roomName {
19
-        font-size: 1em;
20
-    }
2
+    .content {
21
 
3
 
22
-    .participantInfo {
23
-        align-items: center;
24
-        align-self: stretch;
25
-        border: 1px solid #B8C7E0;
26
-        border-radius: 4px;
27
-        display: flex;
28
-        flex-direction: column;
29
-        margin: 24px 0;
30
-        padding: 34px 0;
31
-
32
-        &:hover {
33
-            padding-top: 0px;
4
+        .container {
5
+            align-items: center;
6
+            display: flex;
7
+            flex-direction: column;
34
 
8
 
35
-            .editButton {
36
-                display: flex;
9
+            .spinner {
10
+                margin: 30px;
37
             }
11
             }
38
-        }
39
-
40
-        .editButton {
41
-            align-self: stretch;
42
-            display: none;
43
-            justify-content: flex-end;
44
-            padding: 5px;
45
-            position: relative;
46
-
47
-            button {
48
-                background-color: transparent;
49
-                border-width: 0;
50
-                margin: 0;
51
-                padding: 0;
12
+    
13
+            .joining-message {
14
+                margin: 10px;
52
             }
15
             }
53
         }
16
         }
54
 
17
 
55
-        .displayName {
56
-            color: $defaultColor;
57
-            font-size: 1.3em;
58
-        }
59
-    }
60
-
61
-    .form {
62
-        align-self: stretch;
63
-        display: flex;
64
-        flex-direction: column;
65
-        margin: 32px 0;
66
-
67
-        input {
68
-            margin: 5px 0 15px 0;
69
-        }
70
-
71
-        span {
72
-            color: white;
73
-            font-size: 1.3em;
74
-            text-align: center;
18
+        .form {
19
+            align-items: stretch;
20
+            display: flex;
21
+            flex-direction: column;
22
+            min-width: 400px;
75
         }
23
         }
76
-    }
77
-
78
-    .joiningContainer {
79
-        align-items: center;
80
-        display: flex;
81
-        flex-direction: column;
82
-        margin: 36px 0;
83
 
24
 
84
-        span {
85
-            margin-top: 36px;
86
-            text-align: center;
25
+        .participant-info {
26
+            align-items: center;
27
+            display: flex;
28
+            flex-direction: column;
87
         }
29
         }
88
     }
30
     }
89
 }
31
 }
90
 
32
 
91
-#lobby-dialog {
92
-    align-self: stretch;
33
+#lobby-section {
93
     display: flex;
34
     display: flex;
94
     flex-direction: column;
35
     flex-direction: column;
95
-    margin: 32px 0;
96
-
97
-    .description {
98
-        margin-bottom: 18px;
99
-    }
100
 
36
 
101
-    .field {
37
+    .control-row {
102
         display: flex;
38
         display: flex;
103
         flex-direction: row;
39
         flex-direction: row;
40
+        justify-content: space-between;
41
+        margin-top: 15px;
104
 
42
 
105
-        :first-child {
106
-            align-items: center;
107
-            display: flex;
108
-            padding-right: 15px;
109
-        }
110
-
111
-        :last-child {
112
-            flex: 1;
43
+        label {
44
+            font-size: 14px;
45
+            font-weight: bold;
113
         }
46
         }
114
     }
47
     }
115
 }
48
 }
162
             }
95
             }
163
         }
96
         }
164
     }
97
     }
165
-}
166
 
98
 
167
-// Common styles
168
-
169
-#lobby-dialog, #lobby-screen, #knocking-participant-list {
170
     input {
99
     input {
171
         align-self: stretch;
100
         align-self: stretch;
172
         background-color: transparent;
101
         background-color: transparent;
208
             border-width: 0;
137
             border-width: 0;
209
         }
138
         }
210
     }
139
     }
211
-}
140
+}

+ 3
- 157
css/_prejoin.scss Näytä tiedosto

1
 .prejoin {
1
 .prejoin {
2
-    &-full-page {
3
-        background: #1C2025;
4
-        position: absolute;
5
-        width: 100%;
6
-        height: 100%;
7
-        z-index: $toolbarZ + 1;
8
-    }
9
-
10
-    &-input-area-container {
11
-        position: absolute;
12
-        bottom: 48px;
13
-        width: 100%;
14
-        z-index: 2;
15
-    }
16
 
2
 
17
     &-input-area {
3
     &-input-area {
18
         margin: 0 auto;
4
         margin: 0 auto;
27
         margin-bottom: 16px;
13
         margin-bottom: 16px;
28
     }
14
     }
29
 
15
 
30
-    &-btn {
31
-        border-radius: 3px;
32
-        color: #fff;
33
-        cursor: pointer;
34
-        display: inline-block;
35
-        font-size: 15px;
36
-        line-height: 24px;
37
-        padding: 7px 16px;
38
-        position: relative;
39
-        text-align: center;
40
-        width: 286px;
41
-
42
-        &--primary {
43
-            background: #0376DA;
44
-            border: 1px solid #0376DA;
45
-        }
46
-
47
-        &--secondary {
48
-            background: #2A3A4B;
49
-            border: 1px solid #5E6D7A;
50
-        }
51
-
52
-        &--text {
53
-            width: auto;
54
-            font-size: 13px;
55
-            margin: 0;
56
-            padding: 0;
57
-        }
58
-
59
-        &--disabled {
60
-            background: #5E6D7A;
61
-            border: 1px solid #5E6D7A;
62
-            color: #AFB6BC;
63
-            cursor: initial;
64
-
65
-            .prejoin-btn-icon {
66
-                & > svg {
67
-                    fill: #AFB6BC;
68
-                }
69
-            }
70
-
71
-            .prejoin-btn-options {
72
-                border-left: 1px solid #AFB6BC;
73
-            }
74
-        }
75
-    }
76
-
77
-    &-btn-options {
78
-        align-items: center;
79
-        border-left: 1px solid #fff;
80
-        display: flex;
81
-        height: 100%;
82
-        justify-content: center;
83
-        position: absolute;
84
-        right: 0;
85
-        top: 0;
86
-        width: 40px;
87
-    }
88
-
89
     &-text-btns {
16
     &-text-btns {
90
         display: flex;
17
         display: flex;
91
         justify-content: space-between;
18
         justify-content: space-between;
179
         margin: 200px auto 0 auto;
106
         margin: 200px auto 0 auto;
180
     }
107
     }
181
 
108
 
182
-    &-btn-container {
183
-        display: flex;
184
-        justify-content: center;
185
-        margin-top: 32px;
186
-        width: 100%;
187
-
188
-        &> div {
189
-            margin: 0 12px;
190
-        }
191
-
192
-        .settings-button-small-icon {
193
-            right: -8px;
194
-
195
-            &--hovered {
196
-                right: -10px;
197
-            }
198
-        }
199
-    }
200
-
201
     &-overlay {
109
     &-overlay {
202
         height: 100%;
110
         height: 100%;
203
         position: absolute;
111
         position: absolute;
217
 
125
 
218
     &-status {
126
     &-status {
219
         align-items: center;
127
         align-items: center;
220
-        bottom: 0;
128
+        align-self: stretch;
221
         color: #fff;
129
         color: #fff;
222
         display: flex;
130
         display: flex;
223
         font-size: 13px;
131
         font-size: 13px;
224
         min-height: 24px;
132
         min-height: 24px;
225
         justify-content: center;
133
         justify-content: center;
226
-        position: absolute;
227
         text-align: center;
134
         text-align: center;
228
-        width: 100%;
229
         z-index: 1;
135
         z-index: 1;
230
 
136
 
231
         &--warning {
137
         &--warning {
232
-            background: rgba(241, 173, 51, 0.5)
138
+            background: rgba(241, 173, 51, 0.7)
233
         }
139
         }
234
         &--ok {
140
         &--ok {
235
-            background: rgba(49, 183, 106, 0.5);
141
+            background: rgba(49, 183, 106, 0.7);
236
         }
142
         }
237
     }
143
     }
238
 
144
 
291
     }
197
     }
292
 
198
 
293
 }
199
 }
294
-
295
-.prejoin-copy {
296
-    &-meeting {
297
-        cursor: pointer;
298
-        color: #fff;
299
-        font-size: 15px;
300
-        font-weight: 300;
301
-        line-height: 24px;
302
-        position: relative;
303
-    }
304
-
305
-    &-url {
306
-        max-width: 278px;
307
-        padding: 8px 10px;
308
-        overflow: hidden;
309
-        text-overflow: ellipsis;
310
-    }
311
-
312
-    &-badge {
313
-        border-radius: 4px;
314
-        height: 100%;
315
-        line-height: 38px;
316
-        position: absolute;
317
-        padding-left: 10px;
318
-        text-align: left;
319
-        top: 0;
320
-        width: 100%;
321
-
322
-        &--hover {
323
-            background: #1C2025;
324
-        }
325
-
326
-        &--done {
327
-            background: #31B76A;
328
-        }
329
-    }
330
-
331
-    &-icon {
332
-        position: absolute;
333
-        right: 8px;
334
-        top: 8px;
335
-
336
-       &--white {
337
-           &> svg > path {
338
-               fill: #fff
339
-           }
340
-       }
341
-
342
-       &--light {
343
-           &> svg > path {
344
-               fill: #D1DBE8;
345
-           }
346
-       }
347
-    }
348
-
349
-    &-textarea {
350
-        position: absolute;
351
-        left: -9999px;
352
-    }
353
-}

+ 194
- 0
css/_premeeting-screens.scss Näytä tiedosto

1
+/**
2
+ * Shared style for full screen local track based dialogs/modals.
3
+ */
4
+ .premeeting-screen {
5
+    align-items: stretch;
6
+    background: #1C2025;
7
+    bottom: 0;
8
+    display: flex;
9
+    flex-direction: column;
10
+    font-size: 1.3em;
11
+    left: 0;
12
+    position: absolute;
13
+    right: 0;
14
+    top: 0;
15
+    z-index: $toolbarZ + 1;
16
+
17
+    .content {
18
+        align-items: center;
19
+        background-image: linear-gradient(transparent, black);
20
+        display: flex;
21
+        flex: 1;
22
+        flex-direction: column;
23
+        justify-content: flex-end;
24
+        z-index: $toolbarZ + 2;
25
+
26
+        .title {
27
+            color: #fff;
28
+            font-size: 24px;
29
+            line-height: 32px;
30
+            margin-bottom: 16px;
31
+        }
32
+
33
+        .copy-meeting {
34
+            align-items: center;
35
+            cursor: pointer;
36
+            color: #fff;
37
+            display: flex;
38
+            flex-direction: row;
39
+            font-size: 15px;
40
+            font-weight: 300;
41
+            justify-content: center;
42
+            line-height: 24px;
43
+
44
+            .url {
45
+                display: flex;
46
+                padding: 8px 10px;
47
+
48
+                &:hover {
49
+                    background: #1C2025;
50
+                    border-radius: 4px;
51
+                }
52
+
53
+                &.done {
54
+                    background: #31B76A;
55
+                }
56
+
57
+                .jitsi-icon {
58
+                    margin-left: 10px;
59
+                }
60
+            }
61
+
62
+            &:hover {
63
+                align-self: stretch;
64
+            }
65
+
66
+            textarea {
67
+                border-width: 0;
68
+                height: 0;
69
+                opacity: 0;
70
+                padding: 0;
71
+                width: 0;
72
+            }
73
+        }
74
+
75
+        input.field {
76
+            background-color: transparent;
77
+            border: 1px solid transparent;
78
+            color: white;
79
+            outline-width: 0;
80
+            padding: 20px;
81
+            text-align: center;
82
+
83
+            &.focused {
84
+                border-bottom: 1px solid white;
85
+            }
86
+
87
+            &.error::placeholder {
88
+                color: $defaultWarningColor;
89
+            }
90
+        }
91
+
92
+        .action-btn {
93
+            border-radius: 3px;
94
+            color: #fff;
95
+            cursor: pointer;
96
+            display: inline-block;
97
+            font-size: 15px;
98
+            line-height: 24px;
99
+            margin: 10px;
100
+            padding: 7px 16px;
101
+            position: relative;
102
+            text-align: center;
103
+            width: 286px;
104
+    
105
+            &.primary {
106
+                background: #0376DA;
107
+                border: 1px solid #0376DA;
108
+            }
109
+    
110
+            &.secondary {
111
+                background: transparent;
112
+                border: 1px solid #5E6D7A;
113
+            }
114
+    
115
+            &.text {
116
+                width: auto;
117
+                font-size: 13px;
118
+                margin: 0;
119
+                padding: 0;
120
+            }
121
+    
122
+            &.disabled {
123
+                background: #5E6D7A;
124
+                border: 1px solid #5E6D7A;
125
+                color: #AFB6BC;
126
+                cursor: initial;
127
+    
128
+                .icon {
129
+                    & > svg {
130
+                        fill: #AFB6BC;
131
+                    }
132
+                }
133
+    
134
+                .options {
135
+                    border-left: 1px solid #AFB6BC;
136
+                }
137
+            }
138
+
139
+            .options {
140
+                align-items: center;
141
+                border-left: 1px solid #fff;
142
+                display: flex;
143
+                height: 100%;
144
+                justify-content: center;
145
+                position: absolute;
146
+                right: 0;
147
+                top: 0;
148
+                width: 40px;
149
+            }
150
+        }
151
+    }
152
+
153
+    .media-btn-container {
154
+        display: flex;
155
+        justify-content: center;
156
+        margin: 32px 0;
157
+        width: 100%;
158
+
159
+        &> div {
160
+            margin: 0 12px;
161
+        }
162
+
163
+        .settings-button-small-icon {
164
+            right: -8px;
165
+
166
+            &--hovered {
167
+                right: -10px;
168
+            }
169
+        }
170
+    }
171
+}
172
+
173
+#preview {
174
+    height: 100%;
175
+    position: absolute;
176
+    width: 100%;
177
+
178
+    &.no-video {
179
+        background: radial-gradient(50% 50% at 50% 50%, #5B6F80 0%, #365067 100%), #FFFFFF;
180
+        text-align: center;
181
+    }
182
+
183
+    .avatar {
184
+        background: #A4B8D1;
185
+        margin: 200px auto 0 auto;
186
+    }
187
+
188
+    video {
189
+        height: 100%;
190
+        object-fit: cover;
191
+        position: absolute;
192
+        width: 100%;
193
+    }
194
+}

+ 1
- 0
css/main.scss Näytä tiedosto

97
 @import 'country-picker';
97
 @import 'country-picker';
98
 @import 'modals/invite/invite_more';
98
 @import 'modals/invite/invite_more';
99
 @import 'modals/security/security';
99
 @import 'modals/security/security';
100
+@import 'premeeting-screens';
100
 
101
 
101
 /* Modules END */
102
 /* Modules END */

+ 37
- 15
css/modals/security/_security.scss Näytä tiedosto

3
         color: #fff;
3
         color: #fff;
4
         font-size: 15px;
4
         font-size: 15px;
5
         line-height: 24px;
5
         line-height: 24px;
6
-        
7
-        &.password {
6
+
7
+        &.password-section {
8
             display: flex;
8
             display: flex;
9
-            justify-content: space-between;
10
-            align-items: center;
11
-
12
-            &-actions {
13
-                a {
14
-                    cursor: pointer;
15
-                    text-decoration: none;
16
-                    font-size: 14px;
17
-                    color: #6FB1EA;
18
-                }
9
+            flex-direction: column;
10
+
11
+            .password {
12
+                align-items: center;
13
+                display: flex;
14
+                justify-content: space-between;
15
+                margin-top: 15px;
19
 
16
 
20
-                & > :first-child:not(:last-child) {
21
-                    margin-right: 24px;
17
+                &-actions {
18
+                    a {
19
+                        cursor: pointer;
20
+                        text-decoration: none;
21
+                        font-size: 14px;
22
+                        color: #6FB1EA;
23
+                    }
24
+
25
+                    &>a+a {
26
+                        margin-left: 24px;
27
+                    }
22
                 }
28
                 }
23
             }
29
             }
24
         }
30
         }
31
+
32
+        &> :first-child:not(:last-child) {
33
+            margin-right: 24px;
34
+        }
35
+
36
+        .separator-line {
37
+            margin: 24px 0 24px -20px;
38
+            padding: 0 20px;
39
+            width: 100%;
40
+            height: 1px;
41
+            background: #5E6D7A;
42
+
43
+            &:last-child {
44
+                display: none;
45
+            }
46
+        }
25
     }
47
     }
26
 }
48
 }
27
 
49
 
34
         background: rgba(241, 173, 51, 0.7);
56
         background: rgba(241, 173, 51, 0.7);
35
         border: 1px solid rgba(255, 255, 255, 0.4);
57
         border: 1px solid rgba(255, 255, 255, 0.4);
36
     }
58
     }
37
-}
59
+}

+ 1
- 1
interface_config.js Näytä tiedosto

48
      */
48
      */
49
     TOOLBAR_BUTTONS: [
49
     TOOLBAR_BUTTONS: [
50
         'microphone', 'camera', 'closedcaptions', 'desktop', 'fullscreen',
50
         'microphone', 'camera', 'closedcaptions', 'desktop', 'fullscreen',
51
-        'fodeviceselection', 'hangup', 'lobby', 'profile', 'chat', 'recording',
51
+        'fodeviceselection', 'hangup', 'profile', 'chat', 'recording',
52
         'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
52
         'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
53
         'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
53
         'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
54
         'tileview', 'videobackgroundblur', 'download', 'help', 'mute-everyone',
54
         'tileview', 'videobackgroundblur', 'download', 'help', 'mute-everyone',

+ 9
- 6
lang/main.json Näytä tiedosto

867
     },
867
     },
868
     "lobby": {
868
     "lobby": {
869
         "allow": "Allow",
869
         "allow": "Allow",
870
-        "backToKnockModeButton": "No password, knock instead",
870
+        "backToKnockModeButton": "No password, ask to join instead",
871
         "dialogTitle": "Lobby mode",
871
         "dialogTitle": "Lobby mode",
872
         "disableDialogContent": "Lobby mode is currently enabled. This feature ensures that unwanted participants can't join your meeting. Do you want to disable it?",
872
         "disableDialogContent": "Lobby mode is currently enabled. This feature ensures that unwanted participants can't join your meeting. Do you want to disable it?",
873
         "disableDialogSubmit": "Disable",
873
         "disableDialogSubmit": "Disable",
874
         "emailField": "Enter your email address",
874
         "emailField": "Enter your email address",
875
         "enableDialogPasswordField": "Set password (optional)",
875
         "enableDialogPasswordField": "Set password (optional)",
876
         "enableDialogSubmit": "Enable",
876
         "enableDialogSubmit": "Enable",
877
-        "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.",
877
+        "enableDialogText": "Lobby mode lets you protect your meeting by only allowing people to enter after a formal approval by a moderator.",
878
         "enterPasswordButton": "Enter meeting password",
878
         "enterPasswordButton": "Enter meeting password",
879
+        "enterPasswordTitle": "Enter password to join meeting",
880
+        "invalidPassword": "Invalid password",
879
         "joiningMessage": "You'll join the meeting as soon as someone accepts your request",
881
         "joiningMessage": "You'll join the meeting as soon as someone accepts your request",
880
         "joinWithPasswordMessage": "Trying to join with password, please wait...",
882
         "joinWithPasswordMessage": "Trying to join with password, please wait...",
881
         "joinRejectedMessage": "Your join request was rejected by a moderator.",
883
         "joinRejectedMessage": "Your join request was rejected by a moderator.",
882
         "joinTitle": "Join Meeting",
884
         "joinTitle": "Join Meeting",
883
-        "joiningTitle": "Asking to join",
884
-        "joiningWithPasswordTitle": "Joining",
885
+        "joiningTitle": "Asking to join meeting...",
886
+        "joiningWithPasswordTitle": "Joining with password...",
885
         "knockButton": "Ask to Join",
887
         "knockButton": "Ask to Join",
886
         "knockTitle": "Someone wants to join the meeting",
888
         "knockTitle": "Someone wants to join the meeting",
887
         "nameField": "Enter your name",
889
         "nameField": "Enter your name",
888
-        "passwordField": "Enter password",
890
+        "passwordField": "Enter meeting password",
889
         "passwordJoinButton": "Join",
891
         "passwordJoinButton": "Join",
890
-        "reject": "Reject"
892
+        "reject": "Reject",
893
+        "toggleLabel": "Enable lobby"
891
     }
894
     }
892
 }
895
 }

+ 15
- 19
react/features/base/conference/actions.js Näytä tiedosto

249
  * @param {JitsiConference} conference - The JitsiConference that has failed.
249
  * @param {JitsiConference} conference - The JitsiConference that has failed.
250
  * @param {string} error - The error describing/detailing the cause of the
250
  * @param {string} error - The error describing/detailing the cause of the
251
  * failure.
251
  * failure.
252
+ * @param {any} params - Rest of the params that we receive together with the event.
252
  * @returns {{
253
  * @returns {{
253
  *     type: CONFERENCE_FAILED,
254
  *     type: CONFERENCE_FAILED,
254
  *     conference: JitsiConference,
255
  *     conference: JitsiConference,
651
         case conference.join: {
652
         case conference.join: {
652
             let state = getState()['features/base/conference'];
653
             let state = getState()['features/base/conference'];
653
 
654
 
654
-            // Make sure that the action will set a password for a conference
655
-            // that the application wants joined.
656
-            if (state.passwordRequired === conference) {
657
-                dispatch({
658
-                    type: SET_PASSWORD,
659
-                    conference,
660
-                    method,
661
-                    password
662
-                });
655
+            dispatch({
656
+                type: SET_PASSWORD,
657
+                conference,
658
+                method,
659
+                password
660
+            });
663
 
661
 
664
-                // Join the conference with the newly-set password.
662
+            // Join the conference with the newly-set password.
665
 
663
 
666
-                // Make sure that the action did set the password.
667
-                state = getState()['features/base/conference'];
668
-                if (state.password === password
669
-                        && !state.passwordRequired
664
+            // Make sure that the action did set the password.
665
+            state = getState()['features/base/conference'];
666
+            if (state.password === password
670
 
667
 
671
-                        // Make sure that the application still wants the
672
-                        // conference joined.
673
-                        && !state.conference) {
674
-                    method.call(conference, password);
675
-                }
668
+                    // Make sure that the application still wants the
669
+                    // conference joined.
670
+                    && !state.conference) {
671
+                method.call(conference, password);
676
             }
672
             }
677
             break;
673
             break;
678
         }
674
         }

+ 4
- 5
react/features/base/conference/middleware.js Näytä tiedosto

145
     const result = next(action);
145
     const result = next(action);
146
     const { conference, error } = action;
146
     const { conference, error } = action;
147
 
147
 
148
-    if (error.name === JitsiConferenceErrors.OFFER_ANSWER_FAILED) {
149
-        sendAnalytics(createOfferAnswerFailedEvent());
150
-    }
151
-
152
     // Handle specific failure reasons.
148
     // Handle specific failure reasons.
153
     switch (error.name) {
149
     switch (error.name) {
154
     case JitsiConferenceErrors.CONFERENCE_DESTROYED: {
150
     case JitsiConferenceErrors.CONFERENCE_DESTROYED: {
167
     case JitsiConferenceErrors.CONNECTION_ERROR: {
163
     case JitsiConferenceErrors.CONNECTION_ERROR: {
168
         const [ msg ] = error.params;
164
         const [ msg ] = error.params;
169
 
165
 
170
-        dispatch(connectionDisconnected(getState()['features/base/connection'].connection, 'Disconnected'));
166
+        dispatch(connectionDisconnected(getState()['features/base/connection'].connection));
171
         dispatch(showErrorNotification({
167
         dispatch(showErrorNotification({
172
             descriptionArguments: { msg },
168
             descriptionArguments: { msg },
173
             descriptionKey: msg ? 'dialog.connectErrorWithMsg' : 'dialog.connectError',
169
             descriptionKey: msg ? 'dialog.connectErrorWithMsg' : 'dialog.connectError',
176
 
172
 
177
         break;
173
         break;
178
     }
174
     }
175
+    case JitsiConferenceErrors.OFFER_ANSWER_FAILED:
176
+        sendAnalytics(createOfferAnswerFailedEvent());
177
+        break;
179
     }
178
     }
180
 
179
 
181
     // FIXME: Workaround for the web version. Currently, the creation of the
180
     // FIXME: Workaround for the web version. Currently, the creation of the

+ 24
- 28
react/features/base/conference/reducer.js Näytä tiedosto

387
 function _setPassword(state, { conference, method, password }) {
387
 function _setPassword(state, { conference, method, password }) {
388
     switch (method) {
388
     switch (method) {
389
     case conference.join:
389
     case conference.join:
390
-        if (state.passwordRequired === conference) {
391
-            return assign(state, {
392
-                // XXX 1. The JitsiConference which transitions away from
393
-                // passwordRequired MUST remain in the redux state
394
-                // features/base/conference until it transitions into
395
-                // conference; otherwise, there is a span of time during which
396
-                // the redux state does not even know that there is a
397
-                // JitsiConference whatsoever.
398
-                //
399
-                // 2. The redux action setPassword will attempt to join the
400
-                // JitsiConference so joining is an appropriate transitional
401
-                // redux state.
402
-                //
403
-                // 3. The redux action setPassword will perform the same check
404
-                // before it proceeds with the re-join.
405
-                joining: state.conference ? state.joining : conference,
406
-                locked: LOCKED_REMOTELY,
407
-
408
-                /**
409
-                 * The password with which the conference is to be joined.
410
-                 *
411
-                 * @type {string}
412
-                 */
413
-                password,
414
-                passwordRequired: undefined
415
-            });
416
-        }
417
-        break;
390
+        return assign(state, {
391
+            // 1. The JitsiConference which transitions away from
392
+            // passwordRequired MUST remain in the redux state
393
+            // features/base/conference until it transitions into
394
+            // conference; otherwise, there is a span of time during which
395
+            // the redux state does not even know that there is a
396
+            // JitsiConference whatsoever.
397
+            //
398
+            // 2. The redux action setPassword will attempt to join the
399
+            // JitsiConference so joining is an appropriate transitional
400
+            // redux state.
401
+            //
402
+            // 3. The redux action setPassword will perform the same check
403
+            // before it proceeds with the re-join.
404
+            joining: state.conference ? state.joining : conference,
405
+            locked: LOCKED_REMOTELY,
406
+
407
+            /**
408
+             * The password with which the conference is to be joined.
409
+             *
410
+             * @type {string}
411
+             */
412
+            password
413
+        });
418
 
414
 
419
     case conference.lock:
415
     case conference.lock:
420
         return assign(state, {
416
         return assign(state, {

+ 1
- 2
react/features/base/connection/actionTypes.js Näytä tiedosto

5
  *
5
  *
6
  * {
6
  * {
7
  *     type: CONNECTION_DISCONNECTED,
7
  *     type: CONNECTION_DISCONNECTED,
8
- *     connection: JitsiConnection,
9
- *     message: string
8
+ *     connection: JitsiConnection
10
  * }
9
  * }
11
  */
10
  */
12
 export const CONNECTION_DISCONNECTED = 'CONNECTION_DISCONNECTED';
11
 export const CONNECTION_DISCONNECTED = 'CONNECTION_DISCONNECTED';

+ 5
- 9
react/features/base/connection/actions.native.js Näytä tiedosto

113
          * Dispatches {@code CONNECTION_DISCONNECTED} action when connection is
113
          * Dispatches {@code CONNECTION_DISCONNECTED} action when connection is
114
          * disconnected.
114
          * disconnected.
115
          *
115
          *
116
-         * @param {string} message - Disconnect reason.
117
          * @private
116
          * @private
118
          * @returns {void}
117
          * @returns {void}
119
          */
118
          */
120
-        function _onConnectionDisconnected(message: string) {
119
+        function _onConnectionDisconnected() {
121
             unsubscribe();
120
             unsubscribe();
122
-            dispatch(connectionDisconnected(connection, message));
121
+            dispatch(connectionDisconnected(connection));
123
         }
122
         }
124
 
123
 
125
         /**
124
         /**
187
  *
186
  *
188
  * @param {JitsiConnection} connection - The {@code JitsiConnection} which
187
  * @param {JitsiConnection} connection - The {@code JitsiConnection} which
189
  * disconnected.
188
  * disconnected.
190
- * @param {string} message - Error message.
191
  * @private
189
  * @private
192
  * @returns {{
190
  * @returns {{
193
  *     type: CONNECTION_DISCONNECTED,
191
  *     type: CONNECTION_DISCONNECTED,
194
- *     connection: JitsiConnection,
195
- *     message: string
192
+ *     connection: JitsiConnection
196
  * }}
193
  * }}
197
  */
194
  */
198
-export function connectionDisconnected(connection: Object, message: string) {
195
+export function connectionDisconnected(connection: Object) {
199
     return {
196
     return {
200
         type: CONNECTION_DISCONNECTED,
197
         type: CONNECTION_DISCONNECTED,
201
-        connection,
202
-        message
198
+        connection
203
     };
199
     };
204
 }
200
 }
205
 
201
 

+ 4
- 1
react/features/base/dialog/actions.js Näytä tiedosto

30
  * @param {Object} component - The component to display as dialog.
30
  * @param {Object} component - The component to display as dialog.
31
  * @param {Object} [componentProps] - The React {@code Component} props of the
31
  * @param {Object} [componentProps] - The React {@code Component} props of the
32
  * specified {@code component}.
32
  * specified {@code component}.
33
+ * @param {boolean} rawDialog - True if the dialog is a raw dialog.
34
+ * (Doesn't inherit behavior from other common frameworks).
33
  * @returns {{
35
  * @returns {{
34
  *     type: OPEN_DIALOG,
36
  *     type: OPEN_DIALOG,
35
  *     component: React.Component,
37
  *     component: React.Component,
36
  *     componentProps: (Object | undefined)
38
  *     componentProps: (Object | undefined)
37
  * }}
39
  * }}
38
  */
40
  */
39
-export function openDialog(component: Object, componentProps: ?Object) {
41
+export function openDialog(component: Object, componentProps: ?Object, rawDialog?: boolean) {
40
     return {
42
     return {
43
+        rawDialog,
41
         type: OPEN_DIALOG,
44
         type: OPEN_DIALOG,
42
         component,
45
         component,
43
         componentProps
46
         componentProps

+ 8
- 6
react/features/base/dialog/components/AbstractDialogContainer.js Näytä tiedosto

17
      */
17
      */
18
     _componentProps: Object,
18
     _componentProps: Object,
19
 
19
 
20
+    /**
21
+     * True if the dialog is a raw dialog (doesn't inherit behavior from other common frameworks, such as atlaskit).
22
+     */
23
+    _rawDialog: boolean,
24
+
20
     /**
25
     /**
21
      * True if the UI is in a compact state where we don't show dialogs.
26
      * True if the UI is in a compact state where we don't show dialogs.
22
      */
27
      */
52
  *
57
  *
53
  * @param {Object} state - The redux state.
58
  * @param {Object} state - The redux state.
54
  * @private
59
  * @private
55
- * @returns {{
56
- *     _component: React.Component,
57
- *     _componentProps: Object,
58
- *     _reducedUI: boolean
59
- * }}
60
+ * @returns {Props}
60
  */
61
  */
61
-export function abstractMapStateToProps(state: Object) {
62
+export function abstractMapStateToProps(state: Object): $Shape<Props> {
62
     const stateFeaturesBaseDialog = state['features/base/dialog'];
63
     const stateFeaturesBaseDialog = state['features/base/dialog'];
63
     const { reducedUI } = state['features/base/responsive-ui'];
64
     const { reducedUI } = state['features/base/responsive-ui'];
64
 
65
 
65
     return {
66
     return {
66
         _component: stateFeaturesBaseDialog.component,
67
         _component: stateFeaturesBaseDialog.component,
67
         _componentProps: stateFeaturesBaseDialog.componentProps,
68
         _componentProps: stateFeaturesBaseDialog.componentProps,
69
+        _rawDialog: stateFeaturesBaseDialog.rawDialog,
68
         _reducedUI: reducedUI
70
         _reducedUI: reducedUI
69
     };
71
     };
70
 }
72
 }

+ 4
- 0
react/features/base/dialog/components/web/DialogContainer.js Näytä tiedosto

20
      * @returns {ReactElement}
20
      * @returns {ReactElement}
21
      */
21
      */
22
     render() {
22
     render() {
23
+        if (this.props._rawDialog) {
24
+            return this._renderDialogContent();
25
+        }
26
+
23
         return (
27
         return (
24
             <ModalTransition>
28
             <ModalTransition>
25
                 { this._renderDialogContent() }
29
                 { this._renderDialogContent() }

+ 4
- 2
react/features/base/dialog/reducer.js Näytä tiedosto

21
         if (typeof component === 'undefined' || state.component === component) {
21
         if (typeof component === 'undefined' || state.component === component) {
22
             return assign(state, {
22
             return assign(state, {
23
                 component: undefined,
23
                 component: undefined,
24
-                componentProps: undefined
24
+                componentProps: undefined,
25
+                rawDialog: false
25
             });
26
             });
26
         }
27
         }
27
         break;
28
         break;
30
     case OPEN_DIALOG:
31
     case OPEN_DIALOG:
31
         return assign(state, {
32
         return assign(state, {
32
             component: action.component,
33
             component: action.component,
33
-            componentProps: action.componentProps
34
+            componentProps: action.componentProps,
35
+            rawDialog: action.rawDialog
34
         });
36
         });
35
     }
37
     }
36
 
38
 

+ 2
- 2
react/features/base/icons/svg/copy.svg Näytä tiedosto

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="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"/>
1
+<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
2
+<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"/>
3
 </svg>
3
 </svg>

+ 1
- 1
react/features/base/icons/svg/index.js Näytä tiedosto

31
 export { default as IconDownload } from './download.svg';
31
 export { default as IconDownload } from './download.svg';
32
 export { default as IconDragHandle } from './drag-handle.svg';
32
 export { default as IconDragHandle } from './drag-handle.svg';
33
 export { default as IconE2EE } from './e2ee.svg';
33
 export { default as IconE2EE } from './e2ee.svg';
34
-export { default as IconEmail } from './envelope.svg';
35
 export { default as IconEdit } from './edit.svg';
34
 export { default as IconEdit } from './edit.svg';
35
+export { default as IconEmail } from './envelope.svg';
36
 export { default as IconEventNote } from './event_note.svg';
36
 export { default as IconEventNote } from './event_note.svg';
37
 export { default as IconExclamation } from './exclamation.svg';
37
 export { default as IconExclamation } from './exclamation.svg';
38
 export { default as IconExclamationSolid } from './exclamation-solid.svg';
38
 export { default as IconExclamationSolid } from './exclamation-solid.svg';

+ 0
- 0
react/features/base/premeeting/components/index.native.js Näytä tiedosto


+ 3
- 0
react/features/base/premeeting/components/index.web.js Näytä tiedosto

1
+// @flow
2
+
3
+export * from './web';

+ 77
- 0
react/features/base/premeeting/components/web/ActionButton.js Näytä tiedosto

1
+// @flow
2
+
3
+import React from 'react';
4
+
5
+import { Icon, IconArrowDown } from '../../../icons';
6
+
7
+type Props = {
8
+
9
+    /**
10
+     * Text of the button.
11
+     */
12
+    children: React$Node,
13
+
14
+    /**
15
+     * Text css class of the button.
16
+     */
17
+    className?: string,
18
+
19
+    /**
20
+     * If the button is disabled or not.
21
+     */
22
+    disabled?: boolean,
23
+
24
+    /**
25
+     * If the button has options.
26
+     */
27
+    hasOptions?: boolean,
28
+
29
+    /**
30
+     * The type of th button: primary, secondary, text.
31
+     */
32
+    type: string,
33
+
34
+    /**
35
+     * OnClick button handler.
36
+     */
37
+    onClick: Function,
38
+
39
+    /**
40
+     * Click handler for options.
41
+     */
42
+    onOptionsClick?: Function
43
+};
44
+
45
+/**
46
+ * Button used for pre meeting actions.
47
+ *
48
+ * @returns {ReactElement}
49
+ */
50
+function ActionButton({
51
+    children,
52
+    className = '',
53
+    disabled,
54
+    hasOptions,
55
+    type = 'primary',
56
+    onClick,
57
+    onOptionsClick
58
+}: Props) {
59
+    return (
60
+        <div
61
+            className = { `action-btn ${className} ${type} ${disabled ? 'disabled' : ''}` }
62
+            onClick = { disabled ? undefined : onClick }>
63
+            {children}
64
+            {hasOptions && <div
65
+                className = 'options'
66
+                onClick = { disabled ? undefined : onOptionsClick }>
67
+                <Icon
68
+                    className = 'icon'
69
+                    size = { 14 }
70
+                    src = { IconArrowDown } />
71
+            </div>
72
+            }
73
+        </div>
74
+    );
75
+}
76
+
77
+export default ActionButton;

react/features/prejoin/components/preview/CopyMeetingUrl.js → react/features/base/premeeting/components/web/CopyMeetingUrl.js Näytä tiedosto

2
 
2
 
3
 import React, { Component } from 'react';
3
 import React, { Component } from 'react';
4
 
4
 
5
-import { getCurrentConferenceUrl } from '../../../base/connection';
6
-import { translate } from '../../../base/i18n';
7
-import { Icon, IconCopy, IconCheck } from '../../../base/icons';
8
-import { connect } from '../../../base/redux';
5
+import { getCurrentConferenceUrl } from '../../../connection';
6
+import { translate } from '../../../i18n';
7
+import { Icon, IconCopy, IconCheck } from '../../../icons';
8
+import { connect } from '../../../redux';
9
 import logger from '../../logger';
9
 import logger from '../../logger';
10
 
10
 
11
 type Props = {
11
 type Props = {
108
      */
108
      */
109
     _hideCopyLink() {
109
     _hideCopyLink() {
110
         this.setState({
110
         this.setState({
111
-            showCopyLink: false
111
+            showCopyLink: false,
112
+            showLinkCopied: false
112
         });
113
         });
113
     }
114
     }
114
 
115
 
122
      */
123
      */
123
     _showCopyLink() {
124
     _showCopyLink() {
124
         this.setState({
125
         this.setState({
125
-            showCopyLink: true
126
+            showCopyLink: true,
127
+            showLinkCopied: false
126
         });
128
         });
127
     }
129
     }
128
 
130
 
152
         const { url, t } = this.props;
154
         const { url, t } = this.props;
153
         const { _copyUrl, _showCopyLink, _hideCopyLink } = this;
155
         const { _copyUrl, _showCopyLink, _hideCopyLink } = this;
154
         const src = showLinkCopied ? IconCheck : IconCopy;
156
         const src = showLinkCopied ? IconCheck : IconCopy;
155
-        const iconCls = showCopyLink || showCopyLink ? 'prejoin-copy-icon--white' : 'prejoin-copy-icon--light';
156
 
157
 
157
         return (
158
         return (
158
             <div
159
             <div
159
-                className = 'prejoin-copy-meeting'
160
+                className = 'copy-meeting'
160
                 onMouseEnter = { _showCopyLink }
161
                 onMouseEnter = { _showCopyLink }
161
                 onMouseLeave = { _hideCopyLink }>
162
                 onMouseLeave = { _hideCopyLink }>
162
-                <div className = 'prejoin-copy-url'>{url}</div>
163
-                {showCopyLink && <div
164
-                    className = 'prejoin-copy-badge prejoin-copy-badge--hover'
165
-                    onClick = { _copyUrl }>
166
-                    {t('prejoin.copyAndShare')}
167
-                </div>}
168
-                {showLinkCopied && <div
169
-                    className = 'prejoin-copy-badge prejoin-copy-badge--done'>
170
-                    {t('prejoin.linkCopied')}
171
-                </div>}
172
-                <Icon
173
-                    className = { `prejoin-copy-icon ${iconCls}` }
174
-                    onClick = { _copyUrl }
175
-                    size = { 24 }
176
-                    src = { src } />
163
+                <div
164
+                    className = { `url ${showLinkCopied ? 'done' : ''}` }
165
+                    onClick = { _copyUrl } >
166
+                    { !showCopyLink && !showLinkCopied && url }
167
+                    { showCopyLink && t('prejoin.copyAndShare') }
168
+                    { showLinkCopied && t('prejoin.linkCopied') }
169
+                    <Icon
170
+                        onClick = { _copyUrl }
171
+                        size = { 24 }
172
+                        src = { src } />
173
+                </div>
177
                 <textarea
174
                 <textarea
178
-                    className = 'prejoin-copy-textarea'
179
                     readOnly = { true }
175
                     readOnly = { true }
180
                     ref = { this.textarea }
176
                     ref = { this.textarea }
181
                     tabIndex = '-1'
177
                     tabIndex = '-1'
182
                     value = { url } />
178
                     value = { url } />
183
-            </div>);
179
+            </div>
180
+        );
184
     }
181
     }
185
 }
182
 }
186
 
183
 

+ 175
- 0
react/features/base/premeeting/components/web/InputField.js Näytä tiedosto

1
+// @flow
2
+
3
+import React, { PureComponent } from 'react';
4
+
5
+import { getFieldValue } from '../../../react';
6
+
7
+type Props = {
8
+
9
+    /**
10
+     * Class name to be appended to the default class list.
11
+     */
12
+    className?: string,
13
+
14
+    /**
15
+     * Callback for the onChange event of the field.
16
+     */
17
+    onChange: Function,
18
+
19
+    /**
20
+     * Callback to be used when the user hits Enter in the field.
21
+     */
22
+    onSubmit?: Function,
23
+
24
+    /**
25
+     * Placeholder text for the field.
26
+     */
27
+    placeHolder: string,
28
+
29
+    /**
30
+     * The field type (e.g. text, password...etc).
31
+     */
32
+    type: string,
33
+
34
+    /**
35
+     * Externally provided value.
36
+     */
37
+    value?: string
38
+};
39
+
40
+type State = {
41
+
42
+    /**
43
+     * True if the field is focused, false otherwise.
44
+     */
45
+    focused: boolean,
46
+
47
+    /**
48
+     * The current value of the field.
49
+     */
50
+    value: string
51
+}
52
+
53
+/**
54
+ * Implements a pre-styled input field to be used on pre-meeting screens.
55
+ */
56
+export default class InputField extends PureComponent<Props, State> {
57
+    static defaultProps: {
58
+        className: '',
59
+        type: 'text'
60
+    };
61
+
62
+    /**
63
+     * Instantiates a new component.
64
+     *
65
+     * @inheritdoc
66
+     */
67
+    constructor(props: Props) {
68
+        super(props);
69
+
70
+        this.state = {
71
+            focused: false,
72
+            value: props.value || ''
73
+        };
74
+
75
+        this._onBlur = this._onBlur.bind(this);
76
+        this._onChange = this._onChange.bind(this);
77
+        this._onFocus = this._onFocus.bind(this);
78
+        this._onKeyDown = this._onKeyDown.bind(this);
79
+    }
80
+
81
+    /**
82
+     * Implements {@code PureComponent.getDerivedStateFromProps}.
83
+     *
84
+     * @inheritdoc
85
+     */
86
+    static getDerivedStateFromProps(props: Props, state: State) {
87
+        const { value } = props;
88
+
89
+        if (state.value !== value) {
90
+            return {
91
+                ...state,
92
+                value
93
+            };
94
+        }
95
+    }
96
+
97
+    /**
98
+     * Implements {@code PureComponent#render}.
99
+     *
100
+     * @inheritdoc
101
+     */
102
+    render() {
103
+        return (
104
+            <input
105
+                className = { `field ${this.state.focused ? 'focused' : ''} ${this.props.className || ''}` }
106
+                onBlur = { this._onBlur }
107
+                onChange = { this._onChange }
108
+                onFocus = { this._onFocus }
109
+                onKeyDown = { this._onKeyDown }
110
+                placeholder = { this.props.placeHolder }
111
+                type = { this.props.type }
112
+                value = { this.state.value } />
113
+        );
114
+    }
115
+
116
+    _onBlur: () => void;
117
+
118
+    /**
119
+     * Callback for the onBlur event of the field.
120
+     *
121
+     * @returns {void}
122
+     */
123
+    _onBlur() {
124
+        this.setState({
125
+            focused: false
126
+        });
127
+    }
128
+
129
+    _onChange: Object => void;
130
+
131
+    /**
132
+     * Callback for the onChange event of the field.
133
+     *
134
+     * @param {Object} evt - The static event.
135
+     * @returns {void}
136
+     */
137
+    _onChange(evt) {
138
+        const value = getFieldValue(evt);
139
+
140
+        this.setState({
141
+            value
142
+        });
143
+
144
+        const { onChange } = this.props;
145
+
146
+        onChange && onChange(value);
147
+    }
148
+
149
+    _onFocus: () => void;
150
+
151
+    /**
152
+     * Callback for the onFocus event of the field.
153
+     *
154
+     * @returns {void}
155
+     */
156
+    _onFocus() {
157
+        this.setState({
158
+            focused: true
159
+        });
160
+    }
161
+
162
+    _onKeyDown: Object => void;
163
+
164
+    /**
165
+     * Joins the conference on 'Enter'.
166
+     *
167
+     * @param {Event} event - Key down event object.
168
+     * @returns {void}
169
+     */
170
+    _onKeyDown(event) {
171
+        const { onSubmit } = this.props;
172
+
173
+        onSubmit && event.key === 'Enter' && onSubmit();
174
+    }
175
+}

+ 73
- 0
react/features/base/premeeting/components/web/PreMeetingScreen.js Näytä tiedosto

1
+// @flow
2
+
3
+import React, { PureComponent } from 'react';
4
+
5
+import { AudioSettingsButton, VideoSettingsButton } from '../../../../toolbox';
6
+
7
+import CopyMeetingUrl from './CopyMeetingUrl';
8
+import Preview from './Preview';
9
+
10
+type Props = {
11
+
12
+    /**
13
+     * Children component(s) to be rendered on the screen.
14
+     */
15
+    children: React$Node,
16
+
17
+    /**
18
+     * Footer to be rendered for the page (if any).
19
+     */
20
+    footer?: React$Node,
21
+
22
+    /**
23
+     * Title of the screen.
24
+     */
25
+    title: string,
26
+
27
+    /**
28
+     * True if the preview overlay should be muted, false otherwise.
29
+     */
30
+    videoMuted?: boolean,
31
+
32
+    /**
33
+     * The video track to render as preview (if omitted, the default local track will be rendered).
34
+     */
35
+    videoTrack?: Object
36
+}
37
+
38
+/**
39
+ * Implements a pre-meeting screen that can be used at various pre-meeting phases, for example
40
+ * on the prejoin screen (pre-connection) or lobby (post-connection).
41
+ */
42
+export default class PreMeetingScreen extends PureComponent<Props> {
43
+    /**
44
+     * Implements {@code PureComponent#render}.
45
+     *
46
+     * @inheritdoc
47
+     */
48
+    render() {
49
+        const { title, videoMuted, videoTrack } = this.props;
50
+
51
+        return (
52
+            <div
53
+                className = 'premeeting-screen'
54
+                id = 'lobby-screen'>
55
+                <Preview
56
+                    videoMuted = { videoMuted }
57
+                    videoTrack = { videoTrack } />
58
+                <div className = 'content'>
59
+                    <div className = 'title'>
60
+                        { title }
61
+                    </div>
62
+                    <CopyMeetingUrl />
63
+                    { this.props.children }
64
+                    <div className = 'media-btn-container'>
65
+                        <AudioSettingsButton visible = { true } />
66
+                        <VideoSettingsButton visible = { true } />
67
+                    </div>
68
+                    { this.props.footer }
69
+                </div>
70
+            </div>
71
+        );
72
+    }
73
+}

+ 73
- 0
react/features/base/premeeting/components/web/Preview.js Näytä tiedosto

1
+// @flow
2
+
3
+import React from 'react';
4
+
5
+import { Avatar } from '../../../avatar';
6
+import { Video } from '../../../media';
7
+import { connect } from '../../../redux';
8
+import { getLocalVideoTrack } from '../../../tracks';
9
+
10
+export type Props = {
11
+
12
+    /**
13
+     * The name of the user that is about to join.
14
+     */
15
+    name: string,
16
+
17
+    /**
18
+     * Flag signaling the visibility of camera preview.
19
+     */
20
+    videoMuted: boolean,
21
+
22
+    /**
23
+     * The JitsiLocalTrack to display.
24
+     */
25
+    videoTrack: ?Object,
26
+};
27
+
28
+/**
29
+ * Component showing the video preview and device status.
30
+ *
31
+ * @param {Props} props - The props of the component.
32
+ * @returns {ReactElement}
33
+ */
34
+function Preview(props: Props) {
35
+    const { name, videoMuted, videoTrack } = props;
36
+
37
+    if (!videoMuted && videoTrack) {
38
+        return (
39
+            <div id = 'preview'>
40
+                <Video
41
+                    className = 'flipVideoX'
42
+                    videoTrack = {{ jitsiTrack: videoTrack }} />
43
+            </div>
44
+        );
45
+    }
46
+
47
+    return (
48
+        <div
49
+            className = 'no-video'
50
+            id = 'preview'>
51
+            <Avatar
52
+                className = 'preview-avatar'
53
+                displayName = { name }
54
+                size = { 200 } />
55
+        </div>
56
+    );
57
+}
58
+
59
+/**
60
+ * Maps part of the Redux state to the props of this component.
61
+ *
62
+ * @param {Object} state - The Redux state.
63
+ * @param {Props} ownProps - The own props of the component.
64
+ * @returns {Props}
65
+ */
66
+function _mapStateToProps(state, ownProps) {
67
+    return {
68
+        videoMuted: ownProps.videoTrack ? ownProps.videoMuted : state['features/base/media'].video.muted,
69
+        videoTrack: ownProps.videoTrack || (getLocalVideoTrack(state['features/base/tracks']) || {}).jitsiTrack
70
+    };
71
+}
72
+
73
+export default connect(_mapStateToProps)(Preview);

+ 5
- 0
react/features/base/premeeting/components/web/index.js Näytä tiedosto

1
+// @flow
2
+
3
+export { default as ActionButton } from './ActionButton';
4
+export { default as InputField } from './InputField';
5
+export { default as PreMeetingScreen } from './PreMeetingScreen';

+ 3
- 0
react/features/base/premeeting/index.js Näytä tiedosto

1
+// @flow
2
+
3
+export * from './components';

+ 5
- 0
react/features/base/premeeting/logger.js Näytä tiedosto

1
+// @flow
2
+
3
+import { getLogger } from '../logging/functions';
4
+
5
+export default getLogger('features/base/premeeting');

+ 2
- 2
react/features/conference/components/web/Conference.js Näytä tiedosto

209
 
209
 
210
                 { this.renderNotificationsContainer() }
210
                 { this.renderNotificationsContainer() }
211
 
211
 
212
-                { !filmstripOnly && _showPrejoin && <Prejoin />}
213
-
214
                 <CalleeInfoContainer />
212
                 <CalleeInfoContainer />
213
+
214
+                { !filmstripOnly && _showPrejoin && <Prejoin />}
215
             </div>
215
             </div>
216
         );
216
         );
217
     }
217
     }

+ 5
- 0
react/features/lobby/actionTypes.js Näytä tiedosto

19
  * Action type to set the knocking state of the participant.
19
  * Action type to set the knocking state of the participant.
20
  */
20
  */
21
 export const SET_KNOCKING_STATE = 'SET_KNOCKING_STATE';
21
 export const SET_KNOCKING_STATE = 'SET_KNOCKING_STATE';
22
+
23
+/**
24
+ * Action type to set the password join failed status.
25
+ */
26
+export const SET_PASSWORD_JOIN_FAILED = 'SET_PASSWORD_JOIN_FAILED';

+ 25
- 0
react/features/lobby/actions.native.js Näytä tiedosto

1
+// @flow
2
+
3
+import { openDialog } from '../base/dialog';
4
+
5
+import { DisableLobbyModeDialog, EnableLobbyModeDialog } from './components/native';
6
+
7
+export * from './actions.web';
8
+
9
+/**
10
+ * Action to show the dialog to disable lobby mode.
11
+ *
12
+ * @returns {showNotification}
13
+ */
14
+export function showDisableLobbyModeDialog() {
15
+    return openDialog(DisableLobbyModeDialog);
16
+}
17
+
18
+/**
19
+ * Action to show the dialog to enable lobby mode.
20
+ *
21
+ * @returns {showNotification}
22
+ */
23
+export function showEnableLobbyModeDialog() {
24
+    return openDialog(EnableLobbyModeDialog);
25
+}

react/features/lobby/actions.js → react/features/lobby/actions.web.js Näytä tiedosto

3
 import { type Dispatch } from 'redux';
3
 import { type Dispatch } from 'redux';
4
 
4
 
5
 import { appNavigate, maybeRedirectToWelcomePage } from '../app';
5
 import { appNavigate, maybeRedirectToWelcomePage } from '../app';
6
-import { conferenceLeft, conferenceWillJoin, getCurrentConference } from '../base/conference';
7
-import { openDialog } from '../base/dialog';
6
+import { conferenceWillJoin, getCurrentConference, setPassword } from '../base/conference';
7
+import { hideDialog, openDialog } from '../base/dialog';
8
 import { getLocalParticipant } from '../base/participants';
8
 import { getLocalParticipant } from '../base/participants';
9
 
9
 
10
 import {
10
 import {
11
     KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED,
11
     KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED,
12
     KNOCKING_PARTICIPANT_LEFT,
12
     KNOCKING_PARTICIPANT_LEFT,
13
     SET_KNOCKING_STATE,
13
     SET_KNOCKING_STATE,
14
-    SET_LOBBY_MODE_ENABLED
14
+    SET_LOBBY_MODE_ENABLED,
15
+    SET_PASSWORD_JOIN_FAILED
15
 } from './actionTypes';
16
 } from './actionTypes';
16
-import { DisableLobbyModeDialog, EnableLobbyModeDialog, LobbyScreen } from './components';
17
+import { LobbyScreen } from './components';
17
 
18
 
18
 declare var APP: Object;
19
 declare var APP: Object;
19
 
20
 
23
  * @returns {Function}
24
  * @returns {Function}
24
  */
25
  */
25
 export function cancelKnocking() {
26
 export function cancelKnocking() {
26
-    return async (dispatch: Dispatch<any>, getState: Function) => {
27
+    return async (dispatch: Dispatch<any>) => {
27
         if (typeof APP !== 'undefined') {
28
         if (typeof APP !== 'undefined') {
28
             // when we are redirecting the library should handle any
29
             // when we are redirecting the library should handle any
29
             // unload and clean of the connection.
30
             // unload and clean of the connection.
33
             return;
34
             return;
34
         }
35
         }
35
 
36
 
36
-        dispatch(conferenceLeft(getCurrentConference(getState)));
37
         dispatch(appNavigate(undefined));
37
         dispatch(appNavigate(undefined));
38
     };
38
     };
39
 }
39
 }
40
 
40
 
41
 /**
41
 /**
42
- * Action to be dispatched when a knocking poarticipant leaves before any response.
42
+ * Action to hide the lobby screen.
43
  *
43
  *
44
- * @param {string} id - The ID of the participant.
45
- * @returns {{
46
- *     id: string,
47
- *     type: KNOCKING_PARTICIPANT_LEFT
48
- * }}
44
+ * @returns {hideDialog}
49
  */
45
  */
50
-export function knockingParticipantLeft(id: string) {
51
-    return {
52
-        id,
53
-        type: KNOCKING_PARTICIPANT_LEFT
54
-    };
46
+export function hideLobbyScreen() {
47
+    return hideDialog(LobbyScreen);
55
 }
48
 }
56
 
49
 
57
 /**
50
 /**
58
- * Action to set the knocking state of the participant.
51
+ * Tries to join with a preset password.
59
  *
52
  *
60
- * @param {boolean} knocking - The new state.
61
- * @returns {{
62
- *     state: boolean,
63
- *     type: SET_KNOCKING_STATE
64
- * }}
53
+ * @param {string} password - The password to join with.
54
+ * @returns {Function}
65
  */
55
  */
66
-export function setKnockingState(knocking: boolean) {
67
-    return {
68
-        knocking,
69
-        type: SET_KNOCKING_STATE
56
+export function joinWithPassword(password: string) {
57
+    return async (dispatch: Dispatch<any>, getState: Function) => {
58
+        const conference = getCurrentConference(getState);
59
+
60
+        dispatch(setPassword(conference, conference.join, password));
70
     };
61
     };
71
 }
62
 }
72
 
63
 
73
 /**
64
 /**
74
- * Starts knocking and waiting for approval.
65
+ * Action to be dispatched when a knocking poarticipant leaves before any response.
75
  *
66
  *
76
- * @param {string} password - The password to bypass knocking, if any.
77
- * @returns {Function}
67
+ * @param {string} id - The ID of the participant.
68
+ * @returns {{
69
+ *     id: string,
70
+ *     type: KNOCKING_PARTICIPANT_LEFT
71
+ * }}
78
  */
72
  */
79
-export function startKnocking(password?: string) {
80
-    return async (dispatch: Dispatch<any>, getState: Function) => {
81
-        const state = getState();
82
-        const { membersOnly } = state['features/base/conference'];
83
-        const localParticipant = getLocalParticipant(state);
84
-
85
-        dispatch(setKnockingState(true));
86
-        dispatch(conferenceWillJoin(membersOnly));
87
-        membersOnly
88
-            && membersOnly.joinLobby(localParticipant.name, localParticipant.email, password ? password : undefined);
73
+export function knockingParticipantLeft(id: string) {
74
+    return {
75
+        id,
76
+        type: KNOCKING_PARTICIPANT_LEFT
89
     };
77
     };
90
 }
78
 }
91
 
79
 
95
  * @returns {openDialog}
83
  * @returns {openDialog}
96
  */
84
  */
97
 export function openLobbyScreen() {
85
 export function openLobbyScreen() {
98
-    return openDialog(LobbyScreen);
86
+    return openDialog(LobbyScreen, {}, true);
99
 }
87
 }
100
 
88
 
101
 /**
89
 /**
123
  */
111
  */
124
 export function setKnockingParticipantApproval(id: string, approved: boolean) {
112
 export function setKnockingParticipantApproval(id: string, approved: boolean) {
125
     return async (dispatch: Dispatch<any>, getState: Function) => {
113
     return async (dispatch: Dispatch<any>, getState: Function) => {
126
-        const { conference } = getState()['features/base/conference'];
114
+        const conference = getCurrentConference(getState);
127
 
115
 
128
         if (conference) {
116
         if (conference) {
129
             if (approved) {
117
             if (approved) {
135
     };
123
     };
136
 }
124
 }
137
 
125
 
126
+/**
127
+ * Action to set the knocking state of the participant.
128
+ *
129
+ * @param {boolean} knocking - The new state.
130
+ * @returns {{
131
+ *     state: boolean,
132
+ *     type: SET_KNOCKING_STATE
133
+ * }}
134
+ */
135
+export function setKnockingState(knocking: boolean) {
136
+    return {
137
+        knocking,
138
+        type: SET_KNOCKING_STATE
139
+    };
140
+}
141
+
138
 /**
142
 /**
139
  * Action to set the new state of the lobby mode.
143
  * Action to set the new state of the lobby mode.
140
  *
144
  *
152
 }
156
 }
153
 
157
 
154
 /**
158
 /**
155
- * Action to show the dialog to disable lobby mode.
159
+ * Action to be dispatched when we failed to join with a password.
156
  *
160
  *
157
- * @returns {showNotification}
161
+ * @param {boolean} failed - True of recent password join failed.
162
+ * @returns {{
163
+ *     failed: boolean,
164
+ *     type: SET_PASSWORD_JOIN_FAILED
165
+ * }}
158
  */
166
  */
159
-export function showDisableLobbyModeDialog() {
160
-    return openDialog(DisableLobbyModeDialog);
167
+export function setPasswordJoinFailed(failed: boolean) {
168
+    return {
169
+        failed,
170
+        type: SET_PASSWORD_JOIN_FAILED
171
+    };
161
 }
172
 }
162
 
173
 
163
 /**
174
 /**
164
- * Action to show the dialog to enable lobby mode.
175
+ * Starts knocking and waiting for approval.
165
  *
176
  *
166
- * @returns {showNotification}
177
+ * @returns {Function}
167
  */
178
  */
168
-export function showEnableLobbyModeDialog() {
169
-    return openDialog(EnableLobbyModeDialog);
179
+export function startKnocking() {
180
+    return async (dispatch: Dispatch<any>, getState: Function) => {
181
+        const state = getState();
182
+        const { membersOnly } = state['features/base/conference'];
183
+        const localParticipant = getLocalParticipant(state);
184
+
185
+        dispatch(conferenceWillJoin(membersOnly));
186
+        membersOnly.joinLobby(localParticipant.name, localParticipant.email);
187
+        dispatch(setKnockingState(true));
188
+    };
170
 }
189
 }
171
 
190
 
172
 /**
191
 /**
173
  * Action to toggle lobby mode on or off.
192
  * Action to toggle lobby mode on or off.
174
  *
193
  *
175
  * @param {boolean} enabled - The desired (new) state of the lobby mode.
194
  * @param {boolean} enabled - The desired (new) state of the lobby mode.
176
- * @param {string} password - Optional password to be set.
177
  * @returns {Function}
195
  * @returns {Function}
178
  */
196
  */
179
-export function toggleLobbyMode(enabled: boolean, password?: string) {
197
+export function toggleLobbyMode(enabled: boolean) {
180
     return async (dispatch: Dispatch<any>, getState: Function) => {
198
     return async (dispatch: Dispatch<any>, getState: Function) => {
181
-        const { conference } = getState()['features/base/conference'];
199
+        const conference = getCurrentConference(getState);
182
 
200
 
183
         if (enabled) {
201
         if (enabled) {
184
-            conference.enableLobby(password);
202
+            conference.enableLobby();
185
         } else {
203
         } else {
186
             conference.disableLobby();
204
             conference.disableLobby();
187
         }
205
         }

+ 0
- 47
react/features/lobby/components/AbstractDisableLobbyModeDialog.js Näytä tiedosto

1
-// @flow
2
-
3
-import { PureComponent } from 'react';
4
-
5
-import { toggleLobbyMode } from '../actions';
6
-
7
-export type Props = {
8
-
9
-    /**
10
-     * The Redux Dispatch function.
11
-     */
12
-    dispatch: Function,
13
-
14
-    /**
15
-     * Function to be used to translate i18n labels.
16
-     */
17
-    t: Function
18
-};
19
-
20
-/**
21
- * Abstract class to encapsulate the platform common code of the {@code DisableLobbyModeDialog}.
22
- */
23
-export default class AbstractDisableLobbyModeDialog<P: Props = Props> extends PureComponent<P> {
24
-    /**
25
-     * Instantiates a new component.
26
-     *
27
-     * @inheritdoc
28
-     */
29
-    constructor(props: P) {
30
-        super(props);
31
-
32
-        this._onDisableLobbyMode = this._onDisableLobbyMode.bind(this);
33
-    }
34
-
35
-    _onDisableLobbyMode: () => void;
36
-
37
-    /**
38
-     * Callback to be invoked when the user initiates the lobby mode disable flow.
39
-     *
40
-     * @returns {void}
41
-     */
42
-    _onDisableLobbyMode() {
43
-        this.props.dispatch(toggleLobbyMode(false));
44
-
45
-        return true;
46
-    }
47
-}

+ 0
- 75
react/features/lobby/components/AbstractEnableLobbyModeDialog.js Näytä tiedosto

1
-// @flow
2
-
3
-import { PureComponent } from 'react';
4
-
5
-import { getFieldValue } from '../../base/react';
6
-import { toggleLobbyMode } from '../actions';
7
-
8
-export type Props = {
9
-
10
-    /**
11
-     * The Redux Dispatch function.
12
-     */
13
-    dispatch: Function,
14
-
15
-    /**
16
-     * Function to be used to translate i18n labels.
17
-     */
18
-    t: Function
19
-};
20
-
21
-type State = {
22
-
23
-    /**
24
-     * The password value entered into the field.
25
-     */
26
-    password: string
27
-};
28
-
29
-/**
30
- * Abstract class to encapsulate the platform common code of the {@code EnableLobbyModeDialog}.
31
- */
32
-export default class AbstractEnableLobbyModeDialog<P: Props = Props> extends PureComponent<P, State> {
33
-    /**
34
-     * Instantiates a new component.
35
-     *
36
-     * @inheritdoc
37
-     */
38
-    constructor(props: P) {
39
-        super(props);
40
-
41
-        this.state = {
42
-            password: ''
43
-        };
44
-
45
-        this._onEnableLobbyMode = this._onEnableLobbyMode.bind(this);
46
-        this._onChangePassword = this._onChangePassword.bind(this);
47
-    }
48
-
49
-    _onChangePassword: Object => void;
50
-
51
-    /**
52
-     * Callback to be invoked when the user changes the password.
53
-     *
54
-     * @param {SyntheticEvent} event - The SyntheticEvent instance of the change.
55
-     * @returns {void}
56
-     */
57
-    _onChangePassword(event) {
58
-        this.setState({
59
-            password: getFieldValue(event)
60
-        });
61
-    }
62
-
63
-    _onEnableLobbyMode: () => void;
64
-
65
-    /**
66
-     * Callback to be invoked when the user initiates the lobby mode enable flow.
67
-     *
68
-     * @returns {void}
69
-     */
70
-    _onEnableLobbyMode() {
71
-        this.props.dispatch(toggleLobbyMode(true, this.state.password));
72
-
73
-        return true;
74
-    }
75
-}

+ 6
- 13
react/features/lobby/components/AbstractKnockingParticipantList.js Näytä tiedosto

3
 import { PureComponent } from 'react';
3
 import { PureComponent } from 'react';
4
 
4
 
5
 import { isLocalParticipantModerator } from '../../base/participants';
5
 import { isLocalParticipantModerator } from '../../base/participants';
6
-import { isToolboxVisible } from '../../toolbox';
7
 import { setKnockingParticipantApproval } from '../actions';
6
 import { setKnockingParticipantApproval } from '../actions';
8
 
7
 
9
-type Props = {
8
+export type Props = {
10
 
9
 
11
     /**
10
     /**
12
      * The list of participants.
11
      * The list of participants.
13
      */
12
      */
14
     _participants: Array<Object>,
13
     _participants: Array<Object>,
15
 
14
 
16
-    /**
17
-     * True if the toolbox is visible, so we need to adjust the position.
18
-     */
19
-    _toolboxVisible: boolean,
20
-
21
     /**
15
     /**
22
      * True if the list should be rendered.
16
      * True if the list should be rendered.
23
      */
17
      */
37
 /**
31
 /**
38
  * Abstract class to encapsulate the platform common code of the {@code KnockingParticipantList}.
32
  * Abstract class to encapsulate the platform common code of the {@code KnockingParticipantList}.
39
  */
33
  */
40
-export default class AbstractKnockingParticipantList extends PureComponent<Props> {
34
+export default class AbstractKnockingParticipantList<P: Props = Props> extends PureComponent<P> {
41
     /**
35
     /**
42
      * Instantiates a new component.
36
      * Instantiates a new component.
43
      *
37
      *
44
      * @inheritdoc
38
      * @inheritdoc
45
      */
39
      */
46
-    constructor(props: Props) {
40
+    constructor(props: P) {
47
         super(props);
41
         super(props);
48
 
42
 
49
         this._onRespondToParticipant = this._onRespondToParticipant.bind(this);
43
         this._onRespondToParticipant = this._onRespondToParticipant.bind(this);
72
  * @returns {Props}
66
  * @returns {Props}
73
  */
67
  */
74
 export function mapStateToProps(state: Object): $Shape<Props> {
68
 export function mapStateToProps(state: Object): $Shape<Props> {
75
-    const _participants = state['features/lobby'].knockingParticipants;
69
+    const { knockingParticipants, lobbyEnabled } = state['features/lobby'];
76
 
70
 
77
     return {
71
     return {
78
-        _participants,
79
-        _toolboxVisible: isToolboxVisible(state),
80
-        _visible: isLocalParticipantModerator(state) && Boolean(_participants?.length)
72
+        _participants: knockingParticipants,
73
+        _visible: lobbyEnabled && isLocalParticipantModerator(state) && Boolean(knockingParticipants.length)
81
     };
74
     };
82
 }
75
 }

+ 65
- 18
react/features/lobby/components/AbstractLobbyScreen.js Näytä tiedosto

6
 import { getLocalParticipant } from '../../base/participants';
6
 import { getLocalParticipant } from '../../base/participants';
7
 import { getFieldValue } from '../../base/react';
7
 import { getFieldValue } from '../../base/react';
8
 import { updateSettings } from '../../base/settings';
8
 import { updateSettings } from '../../base/settings';
9
-import { cancelKnocking, startKnocking } from '../actions';
9
+import { cancelKnocking, joinWithPassword, setPasswordJoinFailed, startKnocking } from '../actions';
10
 
10
 
11
 export const SCREEN_STATES = {
11
 export const SCREEN_STATES = {
12
     EDIT: 1,
12
     EDIT: 1,
41
      */
41
      */
42
     _participantName: string;
42
     _participantName: string;
43
 
43
 
44
+    /**
45
+     * True if a recent attempt to join with password failed.
46
+     */
47
+    _passwordJoinFailed: boolean,
48
+
44
     /**
49
     /**
45
      * The Redux dispatch function.
50
      * The Redux dispatch function.
46
      */
51
      */
69
      */
74
      */
70
     password: string,
75
     password: string,
71
 
76
 
77
+    /**
78
+     * True if a recent attempt to join with password failed.
79
+     */
80
+    passwordJoinFailed: boolean,
81
+
72
     /**
82
     /**
73
      * The state of the screen. One of {@code SCREEN_STATES[*]}
83
      * The state of the screen. One of {@code SCREEN_STATES[*]}
74
      */
84
      */
78
 /**
88
 /**
79
  * Abstract class to encapsulate the platform common code of the {@code LobbyScreen}.
89
  * Abstract class to encapsulate the platform common code of the {@code LobbyScreen}.
80
  */
90
  */
81
-export default class AbstractLobbyScreen extends PureComponent<Props, State> {
91
+export default class AbstractLobbyScreen<P: Props = Props> extends PureComponent<P, State> {
82
     /**
92
     /**
83
      * Instantiates a new component.
93
      * Instantiates a new component.
84
      *
94
      *
85
      * @inheritdoc
95
      * @inheritdoc
86
      */
96
      */
87
-    constructor(props: Props) {
97
+    constructor(props: P) {
88
         super(props);
98
         super(props);
89
 
99
 
90
         this.state = {
100
         this.state = {
91
             displayName: props._participantName || '',
101
             displayName: props._participantName || '',
92
             email: props._participantEmail || '',
102
             email: props._participantEmail || '',
93
             password: '',
103
             password: '',
104
+            passwordJoinFailed: false,
94
             screenState: props._participantName ? SCREEN_STATES.VIEW : SCREEN_STATES.EDIT
105
             screenState: props._participantName ? SCREEN_STATES.VIEW : SCREEN_STATES.EDIT
95
         };
106
         };
96
 
107
 
100
         this._onChangeEmail = this._onChangeEmail.bind(this);
111
         this._onChangeEmail = this._onChangeEmail.bind(this);
101
         this._onChangePassword = this._onChangePassword.bind(this);
112
         this._onChangePassword = this._onChangePassword.bind(this);
102
         this._onEnableEdit = this._onEnableEdit.bind(this);
113
         this._onEnableEdit = this._onEnableEdit.bind(this);
114
+        this._onJoinWithPassword = this._onJoinWithPassword.bind(this);
103
         this._onSwitchToKnockMode = this._onSwitchToKnockMode.bind(this);
115
         this._onSwitchToKnockMode = this._onSwitchToKnockMode.bind(this);
104
         this._onSwitchToPasswordMode = this._onSwitchToPasswordMode.bind(this);
116
         this._onSwitchToPasswordMode = this._onSwitchToPasswordMode.bind(this);
105
     }
117
     }
106
 
118
 
119
+    /**
120
+     * Implements {@code PureComponent.getDerivedStateFromProps}.
121
+     *
122
+     * @inheritdoc
123
+     */
124
+    static getDerivedStateFromProps(props: Props, state: State) {
125
+        if (props._passwordJoinFailed && !state.passwordJoinFailed) {
126
+            return {
127
+                password: '',
128
+                passwordJoinFailed: true
129
+            };
130
+        }
131
+    }
132
+
107
     /**
133
     /**
108
      * Returns the screen title.
134
      * Returns the screen title.
109
      *
135
      *
110
      * @returns {string}
136
      * @returns {string}
111
      */
137
      */
112
     _getScreenTitleKey() {
138
     _getScreenTitleKey() {
113
-        const withPassword = Boolean(this.state.password);
139
+        const { screenState } = this.state;
140
+        const passwordPrompt = screenState === SCREEN_STATES.PASSWORD;
114
 
141
 
115
-        return this.props._knocking
116
-            ? withPassword ? 'lobby.joiningWithPasswordTitle' : 'lobby.joiningTitle'
117
-            : 'lobby.joinTitle';
142
+        return !passwordPrompt && this.props._knocking
143
+            ? 'lobby.joiningTitle'
144
+            : passwordPrompt ? 'lobby.enterPasswordTitle' : 'lobby.joinTitle';
118
     }
145
     }
119
 
146
 
120
     _onAskToJoin: () => void;
147
     _onAskToJoin: () => void;
125
      * @returns {void}
152
      * @returns {void}
126
      */
153
      */
127
     _onAskToJoin() {
154
     _onAskToJoin() {
128
-        this.props.dispatch(startKnocking(this.state.password));
155
+        this.setState({
156
+            password: ''
157
+        });
158
+
159
+        this.props.dispatch(startKnocking());
129
 
160
 
130
         return false;
161
         return false;
131
     }
162
     }
211
         });
242
         });
212
     }
243
     }
213
 
244
 
245
+    _onJoinWithPassword: () => void;
246
+
247
+    /**
248
+     * Callback to be invoked when the user tries to join using a preset password.
249
+     *
250
+     * @returns {void}
251
+     */
252
+    _onJoinWithPassword() {
253
+        this.setState({
254
+            passwordJoinFailed: false
255
+        });
256
+        this.props.dispatch(joinWithPassword(this.state.password));
257
+    }
258
+
214
     _onSwitchToKnockMode: () => void;
259
     _onSwitchToKnockMode: () => void;
215
 
260
 
216
     /**
261
     /**
220
      */
265
      */
221
     _onSwitchToKnockMode() {
266
     _onSwitchToKnockMode() {
222
         this.setState({
267
         this.setState({
268
+            password: '',
223
             screenState: this.state.displayName ? SCREEN_STATES.VIEW : SCREEN_STATES.EDIT
269
             screenState: this.state.displayName ? SCREEN_STATES.VIEW : SCREEN_STATES.EDIT
224
         });
270
         });
271
+        this.props.dispatch(setPasswordJoinFailed(false));
225
     }
272
     }
226
 
273
 
227
     _onSwitchToPasswordMode: () => void;
274
     _onSwitchToPasswordMode: () => void;
244
      */
291
      */
245
     _renderContent() {
292
     _renderContent() {
246
         const { _knocking } = this.props;
293
         const { _knocking } = this.props;
247
-        const { password, screenState } = this.state;
248
-        const withPassword = Boolean(password);
294
+        const { screenState } = this.state;
249
 
295
 
250
-        if (_knocking) {
251
-            return this._renderJoining(withPassword);
296
+        if (screenState !== SCREEN_STATES.PASSWORD && _knocking) {
297
+            return this._renderJoining();
252
         }
298
         }
253
 
299
 
254
         return (
300
         return (
267
     /**
313
     /**
268
      * Renders the joining (waiting) fragment of the screen.
314
      * Renders the joining (waiting) fragment of the screen.
269
      *
315
      *
270
-     * @param {boolean} withPassword - True if we're joining with a password. False otherwise.
271
      * @returns {React$Element}
316
      * @returns {React$Element}
272
      */
317
      */
273
-    _renderJoining: boolean => React$Element<*>;
318
+    _renderJoining: () => React$Element<*>;
274
 
319
 
275
     /**
320
     /**
276
      * Renders the participant form to let the knocking participant enter its details.
321
      * Renders the participant form to let the knocking participant enter its details.
301
     _renderPasswordJoinButtons: () => React$Element<*>;
346
     _renderPasswordJoinButtons: () => React$Element<*>;
302
 
347
 
303
     /**
348
     /**
304
-     * Renders the standard button set.
349
+     * Renders the standard (pre-knocking) button set.
305
      *
350
      *
306
      * @returns {React$Element}
351
      * @returns {React$Element}
307
      */
352
      */
317
 export function _mapStateToProps(state: Object): $Shape<Props> {
362
 export function _mapStateToProps(state: Object): $Shape<Props> {
318
     const localParticipant = getLocalParticipant(state);
363
     const localParticipant = getLocalParticipant(state);
319
     const participantId = localParticipant?.id;
364
     const participantId = localParticipant?.id;
365
+    const { knocking, passwordJoinFailed } = state['features/lobby'];
320
 
366
 
321
     return {
367
     return {
322
-        _knocking: state['features/lobby'].knocking,
368
+        _knocking: knocking,
323
         _meetingName: getConferenceName(state),
369
         _meetingName: getConferenceName(state),
324
-        _participantEmail: localParticipant.email,
370
+        _participantEmail: localParticipant?.email,
325
         _participantId: participantId,
371
         _participantId: participantId,
326
-        _participantName: localParticipant.name
372
+        _participantName: localParticipant?.name,
373
+        _passwordJoinFailed: passwordJoinFailed
327
     };
374
     };
328
 }
375
 }

+ 0
- 2
react/features/lobby/components/index.native.js Näytä tiedosto

1
 // @flow
1
 // @flow
2
 
2
 
3
 export * from './native';
3
 export * from './native';
4
-
5
-export { default as LobbyModeButton } from './LobbyModeButton';

+ 0
- 2
react/features/lobby/components/index.web.js Näytä tiedosto

1
 // @flow
1
 // @flow
2
 
2
 
3
 export * from './web';
3
 export * from './web';
4
-
5
-export { default as LobbyModeButton } from './LobbyModeButton';

+ 33
- 3
react/features/lobby/components/native/DisableLobbyModeDialog.js Näytä tiedosto

1
 // @flow
1
 // @flow
2
 
2
 
3
-import React from 'react';
3
+import React, { PureComponent } from 'react';
4
 
4
 
5
 import { ConfirmDialog } from '../../../base/dialog';
5
 import { ConfirmDialog } from '../../../base/dialog';
6
 import { translate } from '../../../base/i18n';
6
 import { translate } from '../../../base/i18n';
7
 import { connect } from '../../../base/redux';
7
 import { connect } from '../../../base/redux';
8
-import AbstractDisableLobbyModeDialog from '../AbstractDisableLobbyModeDialog';
8
+import { toggleLobbyMode } from '../../actions';
9
+
10
+export type Props = {
11
+
12
+    /**
13
+     * The Redux Dispatch function.
14
+     */
15
+    dispatch: Function
16
+};
9
 
17
 
10
 /**
18
 /**
11
  * Implements a dialog that lets the user disable the lobby mode.
19
  * Implements a dialog that lets the user disable the lobby mode.
12
  */
20
  */
13
-class DisableLobbyModeDialog extends AbstractDisableLobbyModeDialog {
21
+class DisableLobbyModeDialog extends PureComponent<Props> {
22
+    /**
23
+     * Instantiates a new component.
24
+     *
25
+     * @inheritdoc
26
+     */
27
+    constructor(props) {
28
+        super(props);
29
+
30
+        this._onDisableLobbyMode = this._onDisableLobbyMode.bind(this);
31
+    }
32
+
14
     /**
33
     /**
15
      * Implements {@code PureComponent#render}.
34
      * Implements {@code PureComponent#render}.
16
      *
35
      *
25
     }
44
     }
26
 
45
 
27
     _onDisableLobbyMode: () => void;
46
     _onDisableLobbyMode: () => void;
47
+
48
+    /**
49
+     * Callback to be invoked when the user initiates the lobby mode disable flow.
50
+     *
51
+     * @returns {void}
52
+     */
53
+    _onDisableLobbyMode() {
54
+        this.props.dispatch(toggleLobbyMode(false));
55
+
56
+        return true;
57
+    }
28
 }
58
 }
29
 
59
 
30
 export default translate(connect()(DisableLobbyModeDialog));
60
 export default translate(connect()(DisableLobbyModeDialog));

+ 35
- 24
react/features/lobby/components/native/EnableLobbyModeDialog.js Näytä tiedosto

1
 // @flow
1
 // @flow
2
 
2
 
3
-import React from 'react';
4
-import { Text, TextInput, View } from 'react-native';
3
+import React, { PureComponent } from 'react';
4
+import { Text, View } from 'react-native';
5
 
5
 
6
 import { ColorSchemeRegistry } from '../../../base/color-scheme';
6
 import { ColorSchemeRegistry } from '../../../base/color-scheme';
7
 import { CustomSubmitDialog } from '../../../base/dialog';
7
 import { CustomSubmitDialog } from '../../../base/dialog';
8
 import { translate } from '../../../base/i18n';
8
 import { translate } from '../../../base/i18n';
9
 import { connect } from '../../../base/redux';
9
 import { connect } from '../../../base/redux';
10
-import { StyleType } from '../../../base/styles';
11
-import AbstractEnableLobbyModeDialog, { type Props as AbstractProps } from '../AbstractEnableLobbyModeDialog';
10
+import { toggleLobbyMode } from '../../actions';
12
 
11
 
13
 import styles from './styles';
12
 import styles from './styles';
14
 
13
 
15
-type Props = AbstractProps & {
14
+type Props = {
16
 
15
 
17
     /**
16
     /**
18
-     * Color schemed common style of the dialog feature.
17
+     * The Redux Dispatch function.
19
      */
18
      */
20
-    _dialogStyles: StyleType
19
+    dispatch: Function,
20
+
21
+    /**
22
+     * Function to be used to translate i18n labels.
23
+     */
24
+    t: Function
21
 };
25
 };
22
 
26
 
23
 /**
27
 /**
24
  * Implements a dialog that lets the user enable the lobby mode.
28
  * Implements a dialog that lets the user enable the lobby mode.
25
  */
29
  */
26
-class EnableLobbyModeDialog extends AbstractEnableLobbyModeDialog<Props> {
30
+class EnableLobbyModeDialog extends PureComponent<Props> {
31
+    /**
32
+     * Instantiates a new component.
33
+     *
34
+     * @inheritdoc
35
+     */
36
+    constructor(props: Props) {
37
+        super(props);
38
+
39
+        this._onEnableLobbyMode = this._onEnableLobbyMode.bind(this);
40
+    }
41
+
27
     /**
42
     /**
28
      * Implements {@code PureComponent#render}.
43
      * Implements {@code PureComponent#render}.
29
      *
44
      *
30
      * @inheritdoc
45
      * @inheritdoc
31
      */
46
      */
32
     render() {
47
     render() {
33
-        const { _dialogStyles, t } = this.props;
34
-
35
         return (
48
         return (
36
             <CustomSubmitDialog
49
             <CustomSubmitDialog
37
                 okKey = 'lobby.enableDialogSubmit'
50
                 okKey = 'lobby.enableDialogSubmit'
39
                 titleKey = 'lobby.dialogTitle'>
52
                 titleKey = 'lobby.dialogTitle'>
40
                 <View style = { styles.formWrapper }>
53
                 <View style = { styles.formWrapper }>
41
                     <Text>
54
                     <Text>
42
-                        { t('lobby.enableDialogText') }
55
+                        { this.props.t('lobby.enableDialogText') }
43
                     </Text>
56
                     </Text>
44
-                    <View style = { styles.fieldRow }>
45
-                        <Text>
46
-                            { t('lobby.enableDialogPasswordField') }
47
-                        </Text>
48
-                        <TextInput
49
-                            autoCapitalize = 'none'
50
-                            autoCompleteType = 'off'
51
-                            onChangeText = { this._onChangePassword }
52
-                            secureTextEntry = { true }
53
-                            style = { _dialogStyles.field } />
54
-                    </View>
55
                 </View>
57
                 </View>
56
             </CustomSubmitDialog>
58
             </CustomSubmitDialog>
57
         );
59
         );
58
     }
60
     }
59
 
61
 
60
-    _onChangePassword: Object => void;
61
-
62
     _onEnableLobbyMode: () => void;
62
     _onEnableLobbyMode: () => void;
63
+
64
+    /**
65
+     * Callback to be invoked when the user initiates the lobby mode enable flow.
66
+     *
67
+     * @returns {void}
68
+     */
69
+    _onEnableLobbyMode() {
70
+        this.props.dispatch(toggleLobbyMode(true));
71
+
72
+        return true;
73
+    }
63
 }
74
 }
64
 
75
 
65
 /**
76
 /**

+ 27
- 6
react/features/lobby/components/native/KnockingParticipantList.js Näytä tiedosto

6
 import { Avatar } from '../../../base/avatar';
6
 import { Avatar } from '../../../base/avatar';
7
 import { translate } from '../../../base/i18n';
7
 import { translate } from '../../../base/i18n';
8
 import { connect } from '../../../base/redux';
8
 import { connect } from '../../../base/redux';
9
-import AbstractKnockingParticipantList, { mapStateToProps } from '../AbstractKnockingParticipantList';
9
+import AbstractKnockingParticipantList, {
10
+    mapStateToProps as abstractMapStateToProps,
11
+    type Props
12
+} from '../AbstractKnockingParticipantList';
10
 
13
 
11
 import styles from './styles';
14
 import styles from './styles';
12
 
15
 
20
      * @inheritdoc
23
      * @inheritdoc
21
      */
24
      */
22
     render() {
25
     render() {
23
-        const { _participants, t } = this.props;
26
+        const { _participants, _visible, t } = this.props;
24
 
27
 
25
-        // On mobile we only show a portion of the list for screen real estate reasons
26
-        const participants = _participants.slice(0, 2);
28
+        if (!_visible) {
29
+            return null;
30
+        }
27
 
31
 
28
         return (
32
         return (
29
             <ScrollView
33
             <ScrollView
30
                 style = { styles.knockingParticipantList }>
34
                 style = { styles.knockingParticipantList }>
31
-                { participants.map(p => (
35
+                { _participants.map(p => (
32
                     <View
36
                     <View
33
                         key = { p.id }
37
                         key = { p.id }
34
                         style = { styles.knockingParticipantListEntry }>
38
                         style = { styles.knockingParticipantListEntry }>
75
     _onRespondToParticipant: (string, boolean) => Function;
79
     _onRespondToParticipant: (string, boolean) => Function;
76
 }
80
 }
77
 
81
 
78
-export default translate(connect(mapStateToProps)(KnockingParticipantList));
82
+/**
83
+ * Maps part of the Redux state to the props of this component.
84
+ *
85
+ * @param {Object} state - The Redux state.
86
+ * @returns {Props}
87
+ */
88
+function _mapStateToProps(state: Object): $Shape<Props> {
89
+    const abstractProps = abstractMapStateToProps(state);
90
+
91
+    return {
92
+        ...abstractProps,
93
+
94
+        // On mobile we only show a portion of the list for screen real estate reasons
95
+        _participants: abstractProps._participants.slice(0, 2)
96
+    };
97
+}
98
+
99
+export default translate(connect(_mapStateToProps)(KnockingParticipantList));

react/features/lobby/components/LobbyModeButton.js → react/features/lobby/components/native/LobbyModeButton.js Näytä tiedosto

1
 // @flow
1
 // @flow
2
 
2
 
3
-import { translate } from '../../base/i18n';
4
-import { IconMeetingUnlocked, IconMeetingLocked } from '../../base/icons';
5
-import { isLocalParticipantModerator } from '../../base/participants';
6
-import { connect } from '../../base/redux';
7
-import AbstractButton, { type Props as AbstractProps } from '../../base/toolbox/components/AbstractButton';
8
-import { showDisableLobbyModeDialog, showEnableLobbyModeDialog } from '../actions';
3
+import { getCurrentConference } from '../../../base/conference';
4
+import { translate } from '../../../base/i18n';
5
+import { IconMeetingUnlocked, IconMeetingLocked } from '../../../base/icons';
6
+import { isLocalParticipantModerator } from '../../../base/participants';
7
+import { connect } from '../../../base/redux';
8
+import AbstractButton, { type Props as AbstractProps } from '../../../base/toolbox/components/AbstractButton';
9
+import { showDisableLobbyModeDialog, showEnableLobbyModeDialog } from '../../actions.native';
9
 
10
 
10
 type Props = AbstractProps & {
11
 type Props = AbstractProps & {
11
 
12
 
63
  * @returns {Props}
64
  * @returns {Props}
64
  */
65
  */
65
 export function _mapStateToProps(state: Object): $Shape<Props> {
66
 export function _mapStateToProps(state: Object): $Shape<Props> {
66
-    const { conference } = state['features/base/conference'];
67
+    const conference = getCurrentConference(state);
67
     const { lobbyEnabled } = state['features/lobby'];
68
     const { lobbyEnabled } = state['features/lobby'];
68
     const lobbySupported = conference && conference.isLobbySupported();
69
     const lobbySupported = conference && conference.isLobbySupported();
69
 
70
 

+ 13
- 6
react/features/lobby/components/native/LobbyScreen.js Näytä tiedosto

54
 
54
 
55
     _onEnableEdit: () => void;
55
     _onEnableEdit: () => void;
56
 
56
 
57
+    _onJoinWithPassword: () => void;
58
+
57
     _onSwitchToKnockMode: () => void;
59
     _onSwitchToKnockMode: () => void;
58
 
60
 
59
     _onSwitchToPasswordMode: () => void;
61
     _onSwitchToPasswordMode: () => void;
74
                 <Text style = { styles.joiningMessage }>
76
                 <Text style = { styles.joiningMessage }>
75
                     { this.props.t('lobby.joiningMessage') }
77
                     { this.props.t('lobby.joiningMessage') }
76
                 </Text>
78
                 </Text>
79
+                { this._renderStandardButtons() }
77
             </>
80
             </>
78
         );
81
         );
79
     }
82
     }
126
                 </TouchableOpacity>
129
                 </TouchableOpacity>
127
                 <Avatar
130
                 <Avatar
128
                     participantId = { this.props._participantId }
131
                     participantId = { this.props._participantId }
129
-                    size = { 64 }
130
-                    style = { styles.avatar } />
132
+                    size = { 64 } />
131
                 <Text style = { styles.displayNameText }>
133
                 <Text style = { styles.displayNameText }>
132
                     { displayName }
134
                     { displayName }
133
                 </Text>
135
                 </Text>
144
      * @inheritdoc
146
      * @inheritdoc
145
      */
147
      */
146
     _renderPasswordForm() {
148
     _renderPasswordForm() {
149
+        const { _passwordJoinFailed, t } = this.props;
150
+
147
         return (
151
         return (
148
             <View style = { styles.formWrapper }>
152
             <View style = { styles.formWrapper }>
149
                 <Text style = { styles.fieldLabel }>
153
                 <Text style = { styles.fieldLabel }>
156
                     secureTextEntry = { true }
160
                     secureTextEntry = { true }
157
                     style = { styles.field }
161
                     style = { styles.field }
158
                     value = { this.state.password } />
162
                     value = { this.state.password } />
163
+                { _passwordJoinFailed && <Text style = { styles.fieldError }>
164
+                    { t('lobby.invalidPassword') }
165
+                </Text> }
159
             </View>
166
             </View>
160
         );
167
         );
161
     }
168
     }
172
             <>
179
             <>
173
                 <TouchableOpacity
180
                 <TouchableOpacity
174
                     disabled = { !this.state.password }
181
                     disabled = { !this.state.password }
175
-                    onPress = { this._onAskToJoin }
182
+                    onPress = { this._onJoinWithPassword }
176
                     style = { [
183
                     style = { [
177
                         styles.button,
184
                         styles.button,
178
                         styles.primaryButton
185
                         styles.primaryButton
201
      * @inheritdoc
208
      * @inheritdoc
202
      */
209
      */
203
     _renderStandardButtons() {
210
     _renderStandardButtons() {
204
-        const { t } = this.props;
211
+        const { _knocking, t } = this.props;
205
 
212
 
206
         return (
213
         return (
207
             <>
214
             <>
208
-                <TouchableOpacity
215
+                { _knocking || <TouchableOpacity
209
                     disabled = { !this.state.displayName }
216
                     disabled = { !this.state.displayName }
210
                     onPress = { this._onAskToJoin }
217
                     onPress = { this._onAskToJoin }
211
                     style = { [
218
                     style = { [
215
                     <Text style = { styles.primaryButtonText }>
222
                     <Text style = { styles.primaryButtonText }>
216
                         { t('lobby.knockButton') }
223
                         { t('lobby.knockButton') }
217
                     </Text>
224
                     </Text>
218
-                </TouchableOpacity>
225
+                </TouchableOpacity> }
219
                 <TouchableOpacity
226
                 <TouchableOpacity
220
                     onPress = { this._onSwitchToPasswordMode }
227
                     onPress = { this._onSwitchToPasswordMode }
221
                     style = { [
228
                     style = { [

+ 1
- 0
react/features/lobby/components/native/index.js Näytä tiedosto

3
 export { default as DisableLobbyModeDialog } from './DisableLobbyModeDialog';
3
 export { default as DisableLobbyModeDialog } from './DisableLobbyModeDialog';
4
 export { default as EnableLobbyModeDialog } from './EnableLobbyModeDialog';
4
 export { default as EnableLobbyModeDialog } from './EnableLobbyModeDialog';
5
 export { default as KnockingParticipantList } from './KnockingParticipantList';
5
 export { default as KnockingParticipantList } from './KnockingParticipantList';
6
+export { default as LobbyModeButton } from './LobbyModeButton';
6
 export { default as LobbyScreen } from './LobbyScreen';
7
 export { default as LobbyScreen } from './LobbyScreen';

+ 7
- 4
react/features/lobby/components/native/styles.js Näytä tiedosto

1
 // @flow
1
 // @flow
2
 
2
 
3
+import { ColorPalette } from '../../../base/styles';
4
+
3
 const SECONDARY_COLOR = '#B8C7E0';
5
 const SECONDARY_COLOR = '#B8C7E0';
4
 
6
 
5
 export default {
7
 export default {
6
-    avatar: {
7
-        borderColor: 'red'
8
-    },
9
-
10
     button: {
8
     button: {
11
         alignItems: 'center',
9
         alignItems: 'center',
12
         borderRadius: 4,
10
         borderRadius: 4,
49
         padding: 8
47
         padding: 8
50
     },
48
     },
51
 
49
 
50
+    fieldError: {
51
+        color: ColorPalette.warning,
52
+        fontSize: 10
53
+    },
54
+
52
     fieldRow: {
55
     fieldRow: {
53
         paddingTop: 16
56
         paddingTop: 16
54
     },
57
     },

+ 0
- 36
react/features/lobby/components/web/DisableLobbyModeDialog.js Näytä tiedosto

1
-// @flow
2
-
3
-import React from 'react';
4
-
5
-import { Dialog } from '../../../base/dialog';
6
-import { translate } from '../../../base/i18n';
7
-import { connect } from '../../../base/redux';
8
-import AbstractDisableLobbyModeDialog from '../AbstractDisableLobbyModeDialog';
9
-
10
-/**
11
- * Implements a dialog that lets the user disable the lobby mode.
12
- */
13
-class DisableLobbyModeDialog extends AbstractDisableLobbyModeDialog {
14
-    /**
15
-     * Implements {@code PureComponent#render}.
16
-     *
17
-     * @inheritdoc
18
-     */
19
-    render() {
20
-        const { t } = this.props;
21
-
22
-        return (
23
-            <Dialog
24
-                className = 'lobby-screen'
25
-                okKey = 'lobby.disableDialogSubmit'
26
-                onSubmit = { this._onDisableLobbyMode }
27
-                titleKey = 'lobby.dialogTitle'>
28
-                { t('lobby.disableDialogContent') }
29
-            </Dialog>
30
-        );
31
-    }
32
-
33
-    _onDisableLobbyMode: () => void;
34
-}
35
-
36
-export default translate(connect()(DisableLobbyModeDialog));

+ 0
- 51
react/features/lobby/components/web/EnableLobbyModeDialog.js Näytä tiedosto

1
-// @flow
2
-
3
-import React from 'react';
4
-
5
-import { Dialog } from '../../../base/dialog';
6
-import { translate } from '../../../base/i18n';
7
-import { connect } from '../../../base/redux';
8
-import AbstractEnableLobbyModeDialog from '../AbstractEnableLobbyModeDialog';
9
-
10
-/**
11
- * Implements a dialog that lets the user enable the lobby mode.
12
- */
13
-class EnableLobbyModeDialog extends AbstractEnableLobbyModeDialog {
14
-    /**
15
-     * Implements {@code PureComponent#render}.
16
-     *
17
-     * @inheritdoc
18
-     */
19
-    render() {
20
-        const { t } = this.props;
21
-
22
-        return (
23
-            <Dialog
24
-                className = 'lobby-screen'
25
-                okKey = 'lobby.enableDialogSubmit'
26
-                onSubmit = { this._onEnableLobbyMode }
27
-                titleKey = 'lobby.dialogTitle'>
28
-                <div id = 'lobby-dialog'>
29
-                    <span className = 'description'>
30
-                        { t('lobby.enableDialogText') }
31
-                    </span>
32
-                    <div className = 'field'>
33
-                        <label htmlFor = 'password'>
34
-                            { t('lobby.enableDialogPasswordField') }
35
-                        </label>
36
-                        <input
37
-                            onChange = { this._onChangePassword }
38
-                            type = 'password'
39
-                            value = { this.state.password } />
40
-                    </div>
41
-                </div>
42
-            </Dialog>
43
-        );
44
-    }
45
-
46
-    _onChangePassword: Object => void;
47
-
48
-    _onEnableLobbyMode: () => void;
49
-}
50
-
51
-export default translate(connect()(EnableLobbyModeDialog));

+ 28
- 3
react/features/lobby/components/web/KnockingParticipantList.js Näytä tiedosto

5
 import { Avatar } from '../../../base/avatar';
5
 import { Avatar } from '../../../base/avatar';
6
 import { translate } from '../../../base/i18n';
6
 import { translate } from '../../../base/i18n';
7
 import { connect } from '../../../base/redux';
7
 import { connect } from '../../../base/redux';
8
-import AbstractKnockingParticipantList, { mapStateToProps } from '../AbstractKnockingParticipantList';
8
+import { isToolboxVisible } from '../../../toolbox';
9
+import AbstractKnockingParticipantList, {
10
+    mapStateToProps as abstractMapStateToProps,
11
+    type Props as AbstractProps
12
+} from '../AbstractKnockingParticipantList';
13
+
14
+type Props = AbstractProps & {
15
+
16
+    /**
17
+     * True if the toolbox is visible, so we need to adjust the position.
18
+     */
19
+    _toolboxVisible: boolean,
20
+};
9
 
21
 
10
 /**
22
 /**
11
  * Component to render a list for the actively knocking participants.
23
  * Component to render a list for the actively knocking participants.
12
  */
24
  */
13
-class KnockingParticipantList extends AbstractKnockingParticipantList {
25
+class KnockingParticipantList extends AbstractKnockingParticipantList<Props> {
14
     /**
26
     /**
15
      * Implements {@code PureComponent#render}.
27
      * Implements {@code PureComponent#render}.
16
      *
28
      *
69
     _onRespondToParticipant: (string, boolean) => Function;
81
     _onRespondToParticipant: (string, boolean) => Function;
70
 }
82
 }
71
 
83
 
72
-export default translate(connect(mapStateToProps)(KnockingParticipantList));
84
+/**
85
+ * Maps part of the Redux state to the props of this component.
86
+ *
87
+ * @param {Object} state - The Redux state.
88
+ * @returns {Props}
89
+ */
90
+function _mapStateToProps(state: Object): $Shape<Props> {
91
+    return {
92
+        ...abstractMapStateToProps(state),
93
+        _toolboxVisible: isToolboxVisible(state)
94
+    };
95
+}
96
+
97
+export default translate(connect(_mapStateToProps)(KnockingParticipantList));

+ 48
- 97
react/features/lobby/components/web/LobbyScreen.js Näytä tiedosto

2
 
2
 
3
 import React from 'react';
3
 import React from 'react';
4
 
4
 
5
-import { Avatar } from '../../../base/avatar';
6
-import { Dialog } from '../../../base/dialog';
7
 import { translate } from '../../../base/i18n';
5
 import { translate } from '../../../base/i18n';
8
-import { Icon, IconEdit } from '../../../base/icons';
6
+import { ActionButton, InputField, PreMeetingScreen } from '../../../base/premeeting';
9
 import { LoadingIndicator } from '../../../base/react';
7
 import { LoadingIndicator } from '../../../base/react';
10
 import { connect } from '../../../base/redux';
8
 import { connect } from '../../../base/redux';
11
-import AbstractLobbyScreen, { _mapStateToProps } from '../AbstractLobbyScreen';
9
+import AbstractLobbyScreen, {
10
+    _mapStateToProps
11
+} from '../AbstractLobbyScreen';
12
 
12
 
13
 /**
13
 /**
14
  * Implements a waiting screen that represents the participant being in the lobby.
14
  * Implements a waiting screen that represents the participant being in the lobby.
20
      * @inheritdoc
20
      * @inheritdoc
21
      */
21
      */
22
     render() {
22
     render() {
23
-        const { _meetingName, t } = this.props;
24
-
25
         return (
23
         return (
26
-            <Dialog
27
-                disableBlanketClickDismiss = { false }
28
-                disableEnter = { true }
29
-                hideCancelButton = { true }
30
-                isModal = { false }
31
-                onCancel = { this._onCancel }
32
-                submitDisabled = { true }
33
-                width = 'small'>
34
-                <div id = 'lobby-screen'>
35
-                    <span className = 'title'>
36
-                        { t(this._getScreenTitleKey()) }
37
-                    </span>
38
-                    <span className = 'roomName'>
39
-                        { _meetingName }
40
-                    </span>
41
-                    { this._renderContent() }
42
-                </div>
43
-            </Dialog>
24
+            <PreMeetingScreen title = { this.props.t(this._getScreenTitleKey()) }>
25
+                { this._renderContent() }
26
+            </PreMeetingScreen>
44
         );
27
         );
45
     }
28
     }
46
 
29
 
58
 
41
 
59
     _onEnableEdit: () => void;
42
     _onEnableEdit: () => void;
60
 
43
 
44
+    _onJoinWithPassword: () => void;
45
+
61
     _onSubmit: () => boolean;
46
     _onSubmit: () => boolean;
62
 
47
 
63
     _onSwitchToKnockMode: () => void;
48
     _onSwitchToKnockMode: () => void;
71
      *
56
      *
72
      * @inheritdoc
57
      * @inheritdoc
73
      */
58
      */
74
-    _renderJoining(withPassword) {
75
-        return (
76
-            <div className = 'joiningContainer'>
77
-                <LoadingIndicator />
78
-                <span>
79
-                    { this.props.t(`lobby.${withPassword ? 'joinWithPasswordMessage' : 'joiningMessage'}`) }
80
-                </span>
81
-            </div>
82
-        );
83
-    }
84
-
85
-    /**
86
-     * Renders the participant form to let the knocking participant enter its details.
87
-     *
88
-     * @inheritdoc
89
-     */
90
-    _renderParticipantForm() {
91
-        const { t } = this.props;
92
-        const { displayName, email } = this.state;
93
-
59
+    _renderJoining() {
94
         return (
60
         return (
95
-            <div className = 'form'>
96
-                <span>
97
-                    { t('lobby.nameField') }
98
-                </span>
99
-                <input
100
-                    onChange = { this._onChangeDisplayName }
101
-                    type = 'text'
102
-                    value = { displayName } />
103
-                <span>
104
-                    { t('lobby.emailField') }
61
+            <div className = 'container'>
62
+                <div className = 'spinner'>
63
+                    <LoadingIndicator size = 'large' />
64
+                </div>
65
+                <span className = 'joining-message'>
66
+                    { this.props.t('lobby.joiningMessage') }
105
                 </span>
67
                 </span>
106
-                <input
107
-                    onChange = { this._onChangeEmail }
108
-                    type = 'email'
109
-                    value = { email } />
68
+                { this._renderStandardButtons() }
110
             </div>
69
             </div>
111
         );
70
         );
112
     }
71
     }
118
      */
77
      */
119
     _renderParticipantInfo() {
78
     _renderParticipantInfo() {
120
         const { displayName, email } = this.state;
79
         const { displayName, email } = this.state;
121
-        const { _participantId } = this.props;
80
+        const { t } = this.props;
122
 
81
 
123
         return (
82
         return (
124
-            <div className = 'participantInfo'>
125
-                <div className = 'editButton'>
126
-                    <button
127
-                        onClick = { this._onEnableEdit }
128
-                        type = 'button'>
129
-                        <Icon src = { IconEdit } />
130
-                    </button>
83
+            <div className = 'participant-info'>
84
+                <div className = 'form'>
85
+                    <InputField
86
+                        onChange = { this._onChangeDisplayName }
87
+                        placeHolder = { t('lobby.nameField') }
88
+                        value = { displayName } />
89
+
90
+                    <InputField
91
+                        onChange = { this._onChangeEmail }
92
+                        placeHolder = { t('lobby.emailField') }
93
+                        value = { email } />
131
                 </div>
94
                 </div>
132
-                <Avatar
133
-                    participantId = { _participantId }
134
-                    size = { 64 } />
135
-                <span className = 'displayName'>
136
-                    { displayName }
137
-                </span>
138
-                <span className = 'email'>
139
-                    { email }
140
-                </span>
141
             </div>
95
             </div>
142
         );
96
         );
143
     }
97
     }
148
      * @inheritdoc
102
      * @inheritdoc
149
      */
103
      */
150
     _renderPasswordForm() {
104
     _renderPasswordForm() {
105
+        const { _passwordJoinFailed, t } = this.props;
106
+
151
         return (
107
         return (
152
             <div className = 'form'>
108
             <div className = 'form'>
153
-                <span>
154
-                    { this.props.t('lobby.passwordField') }
155
-                </span>
156
-                <input
109
+                <InputField
110
+                    className = { _passwordJoinFailed ? 'error' : '' }
157
                     onChange = { this._onChangePassword }
111
                     onChange = { this._onChangePassword }
112
+                    placeHolder = { _passwordJoinFailed ? t('lobby.invalidPassword') : t('lobby.passwordField') }
158
                     type = 'password'
113
                     type = 'password'
159
                     value = { this.state.password } />
114
                     value = { this.state.password } />
160
             </div>
115
             </div>
171
 
126
 
172
         return (
127
         return (
173
             <>
128
             <>
174
-                <button
175
-                    className = 'primary'
129
+                <ActionButton
176
                     disabled = { !this.state.password }
130
                     disabled = { !this.state.password }
177
-                    onClick = { this._onAskToJoin }
178
-                    type = 'submit'>
131
+                    onClick = { this._onJoinWithPassword }
132
+                    type = 'primary'>
179
                     { t('lobby.passwordJoinButton') }
133
                     { t('lobby.passwordJoinButton') }
180
-                </button>
181
-                <button
182
-                    className = 'borderLess'
134
+                </ActionButton>
135
+                <ActionButton
183
                     onClick = { this._onSwitchToKnockMode }
136
                     onClick = { this._onSwitchToKnockMode }
184
-                    type = 'button'>
137
+                    type = 'secondary'>
185
                     { t('lobby.backToKnockModeButton') }
138
                     { t('lobby.backToKnockModeButton') }
186
-                </button>
139
+                </ActionButton>
187
             </>
140
             </>
188
         );
141
         );
189
     }
142
     }
194
      * @inheritdoc
147
      * @inheritdoc
195
      */
148
      */
196
     _renderStandardButtons() {
149
     _renderStandardButtons() {
197
-        const { t } = this.props;
150
+        const { _knocking, t } = this.props;
198
 
151
 
199
         return (
152
         return (
200
             <>
153
             <>
201
-                <button
202
-                    className = 'primary'
154
+                { _knocking || <ActionButton
203
                     disabled = { !this.state.displayName }
155
                     disabled = { !this.state.displayName }
204
                     onClick = { this._onAskToJoin }
156
                     onClick = { this._onAskToJoin }
205
-                    type = 'submit'>
157
+                    type = 'primary'>
206
                     { t('lobby.knockButton') }
158
                     { t('lobby.knockButton') }
207
-                </button>
208
-                <button
209
-                    className = 'borderLess'
159
+                </ActionButton> }
160
+                <ActionButton
210
                     onClick = { this._onSwitchToPasswordMode }
161
                     onClick = { this._onSwitchToPasswordMode }
211
-                    type = 'button'>
162
+                    type = 'secondary'>
212
                     { t('lobby.enterPasswordButton') }
163
                     { t('lobby.enterPasswordButton') }
213
-                </button>
164
+                </ActionButton>
214
             </>
165
             </>
215
         );
166
         );
216
     }
167
     }

+ 136
- 0
react/features/lobby/components/web/LobbySection.js Näytä tiedosto

1
+// @flow
2
+
3
+import React, { PureComponent } from 'react';
4
+
5
+import { translate } from '../../../base/i18n';
6
+import { isLocalParticipantModerator } from '../../../base/participants';
7
+import { Switch } from '../../../base/react';
8
+import { connect } from '../../../base/redux';
9
+import { toggleLobbyMode } from '../../actions';
10
+
11
+type Props = {
12
+
13
+    /**
14
+     * True if lobby is currently enabled in the conference.
15
+     */
16
+    _lobbyEnabled: boolean,
17
+
18
+    /**
19
+     * True if the section should be visible.
20
+     */
21
+    _visible: boolean,
22
+
23
+    /**
24
+     * The Redux Dispatch function.
25
+     */
26
+    dispatch: Function,
27
+
28
+    /**
29
+     * Function to be used to translate i18n labels.
30
+     */
31
+    t: Function
32
+};
33
+
34
+type State = {
35
+
36
+    /**
37
+     * True if the lobby switch is toggled on.
38
+     */
39
+    lobbyEnabled: boolean
40
+}
41
+
42
+/**
43
+ * Implements a security feature section to control lobby mode.
44
+ */
45
+class LobbySection extends PureComponent<Props, State> {
46
+    /**
47
+     * Instantiates a new component.
48
+     *
49
+     * @inheritdoc
50
+     */
51
+    constructor(props: Props) {
52
+        super(props);
53
+
54
+        this.state = {
55
+            lobbyEnabled: props._lobbyEnabled
56
+        };
57
+
58
+        this._onToggleLobby = this._onToggleLobby.bind(this);
59
+    }
60
+
61
+    /**
62
+     * Implements {@code PureComponent#componentDidUpdate}.
63
+     *
64
+     * @inheritdoc
65
+     */
66
+    componentDidUpdate(prevProps, prevState) {
67
+        if (this.props._lobbyEnabled !== prevProps._lobbyEnabled
68
+                && this.state.lobbyEnabled !== prevState.lobbyEnabled) {
69
+            // eslint-disable-next-line react/no-did-update-set-state
70
+            this.setState({
71
+                lobbyEnabled: this.props._lobbyEnabled
72
+            });
73
+        }
74
+    }
75
+
76
+    /**
77
+     * Implements {@code PureComponent#render}.
78
+     *
79
+     * @inheritdoc
80
+     */
81
+    render() {
82
+        const { _visible, t } = this.props;
83
+
84
+        if (!_visible) {
85
+            return null;
86
+        }
87
+
88
+        return (
89
+            <div id = 'lobby-section'>
90
+                { t('lobby.enableDialogText') }
91
+                <div className = 'control-row'>
92
+                    <label>
93
+                        { t('lobby.toggleLabel') }
94
+                    </label>
95
+                    <Switch
96
+                        onValueChange = { this._onToggleLobby }
97
+                        value = { this.state.lobbyEnabled } />
98
+                </div>
99
+            </div>
100
+        );
101
+    }
102
+
103
+    _onToggleLobby: () => void;
104
+
105
+    /**
106
+     * Callback to be invoked when the user toggles the lobby feature on or off.
107
+     *
108
+     * @returns {void}
109
+     */
110
+    _onToggleLobby() {
111
+        const newValue = !this.state.lobbyEnabled;
112
+
113
+        this.setState({
114
+            lobbyEnabled: newValue
115
+        });
116
+
117
+        this.props.dispatch(toggleLobbyMode(newValue));
118
+    }
119
+}
120
+
121
+/**
122
+ * Maps part of the Redux state to the props of this component.
123
+ *
124
+ * @param {Object} state - The Redux state.
125
+ * @returns {Props}
126
+ */
127
+function mapStateToProps(state: Object): $Shape<Props> {
128
+    const { conference } = state['features/base/conference'];
129
+
130
+    return {
131
+        _lobbyEnabled: state['features/lobby'].lobbyEnabled,
132
+        _visible: conference && conference.isLobbySupported() && isLocalParticipantModerator(state)
133
+    };
134
+}
135
+
136
+export default translate(connect(mapStateToProps)(LobbySection));

+ 1
- 2
react/features/lobby/components/web/index.js Näytä tiedosto

1
 // @flow
1
 // @flow
2
 
2
 
3
-export { default as DisableLobbyModeDialog } from './DisableLobbyModeDialog';
4
-export { default as EnableLobbyModeDialog } from './EnableLobbyModeDialog';
5
 export { default as KnockingParticipantList } from './KnockingParticipantList';
3
 export { default as KnockingParticipantList } from './KnockingParticipantList';
4
+export { default as LobbySection } from './LobbySection';
6
 export { default as LobbyScreen } from './LobbyScreen';
5
 export { default as LobbyScreen } from './LobbyScreen';

+ 2
- 18
react/features/lobby/functions.js Näytä tiedosto

1
 // @flow
1
 // @flow
2
 
2
 
3
-declare var interfaceConfig: Object;
4
-
5
-/**
6
- * Returns a displayable name for the knocking participant.
7
- *
8
- * @param {string} name - The received name.
9
- * @returns {string}
10
- */
11
-export function getKnockingParticipantDisplayName(name: string) {
12
-    if (name) {
13
-        return name;
14
-    }
15
-
16
-    return typeof interfaceConfig === 'object'
17
-        ? interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME
18
-        : 'Fellow Jitster';
19
-}
3
+import { getCurrentConference } from '../base/conference';
20
 
4
 
21
 /**
5
 /**
22
  * Approves (lets in) or rejects a knocking participant.
6
  * Approves (lets in) or rejects a knocking participant.
27
  * @returns {Function}
11
  * @returns {Function}
28
  */
12
  */
29
 export function setKnockingParticipantApproval(getState: Function, id: string, approved: boolean) {
13
 export function setKnockingParticipantApproval(getState: Function, id: string, approved: boolean) {
30
-    const { conference } = getState()['features/base/conference'];
14
+    const conference = getCurrentConference(getState());
31
 
15
 
32
     if (conference) {
16
     if (conference) {
33
         if (approved) {
17
         if (approved) {

+ 31
- 15
react/features/lobby/middleware.js Näytä tiedosto

1
 // @flow
1
 // @flow
2
 
2
 
3
 import { CONFERENCE_FAILED, CONFERENCE_JOINED } from '../base/conference';
3
 import { CONFERENCE_FAILED, CONFERENCE_JOINED } from '../base/conference';
4
-import { hideDialog } from '../base/dialog';
5
 import { JitsiConferenceErrors, JitsiConferenceEvents } from '../base/lib-jitsi-meet';
4
 import { JitsiConferenceErrors, JitsiConferenceEvents } from '../base/lib-jitsi-meet';
6
 import { getFirstLoadableAvatarUrl } from '../base/participants';
5
 import { getFirstLoadableAvatarUrl } from '../base/participants';
7
 import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
6
 import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
8
 import { NOTIFICATION_TYPE, showNotification } from '../notifications';
7
 import { NOTIFICATION_TYPE, showNotification } from '../notifications';
8
+import { isPrejoinPageEnabled } from '../prejoin/functions';
9
 
9
 
10
 import { KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED } from './actionTypes';
10
 import { KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED } from './actionTypes';
11
 import {
11
 import {
12
+    hideLobbyScreen,
12
     knockingParticipantLeft,
13
     knockingParticipantLeft,
13
     openLobbyScreen,
14
     openLobbyScreen,
14
     participantIsKnockingOrUpdated,
15
     participantIsKnockingOrUpdated,
15
-    setLobbyModeEnabled
16
+    setLobbyModeEnabled,
17
+    startKnocking,
18
+    setPasswordJoinFailed
16
 } from './actions';
19
 } from './actions';
17
-import { LobbyScreen } from './components';
18
 
20
 
19
 MiddlewareRegistry.register(store => next => action => {
21
 MiddlewareRegistry.register(store => next => action => {
20
     switch (action.type) {
22
     switch (action.type) {
76
  * @param {Object} action - The Redux action.
78
  * @param {Object} action - The Redux action.
77
  * @returns {Object}
79
  * @returns {Object}
78
  */
80
  */
79
-function _conferenceFailed({ dispatch }, next, action) {
81
+function _conferenceFailed({ dispatch, getState }, next, action) {
80
     const { error } = action;
82
     const { error } = action;
83
+    const state = getState();
84
+    const nonFirstFailure = Boolean(state['features/base/conference'].membersOnly);
81
 
85
 
82
     if (error.name === JitsiConferenceErrors.MEMBERS_ONLY_ERROR) {
86
     if (error.name === JitsiConferenceErrors.MEMBERS_ONLY_ERROR) {
83
         if (typeof error.recoverable === 'undefined') {
87
         if (typeof error.recoverable === 'undefined') {
84
             error.recoverable = true;
88
             error.recoverable = true;
85
         }
89
         }
86
 
90
 
91
+        const result = next(action);
92
+
87
         dispatch(openLobbyScreen());
93
         dispatch(openLobbyScreen());
88
-    } else {
89
-        dispatch(hideDialog(LobbyScreen));
90
-
91
-        if (error.name === JitsiConferenceErrors.CONFERENCE_ACCESS_DENIED) {
92
-            dispatch(showNotification({
93
-                appearance: NOTIFICATION_TYPE.ERROR,
94
-                hideErrorSupportLink: true,
95
-                titleKey: 'lobby.joinRejectedMessage'
96
-            }));
94
+
95
+        if (isPrejoinPageEnabled(state) && !state['features/lobby'].knocking) {
96
+            // prejoin is enabled, so we knock automatically
97
+            dispatch(startKnocking());
97
         }
98
         }
99
+
100
+        dispatch(setPasswordJoinFailed(nonFirstFailure));
101
+
102
+        return result;
103
+    }
104
+
105
+    dispatch(hideLobbyScreen());
106
+
107
+    if (error.name === JitsiConferenceErrors.CONFERENCE_ACCESS_DENIED) {
108
+        dispatch(showNotification({
109
+            appearance: NOTIFICATION_TYPE.ERROR,
110
+            hideErrorSupportLink: true,
111
+            titleKey: 'lobby.joinRejectedMessage'
112
+        }));
98
     }
113
     }
99
 
114
 
100
     return next(action);
115
     return next(action);
109
  * @returns {Object}
124
  * @returns {Object}
110
  */
125
  */
111
 function _conferenceJoined({ dispatch }, next, action) {
126
 function _conferenceJoined({ dispatch }, next, action) {
112
-    dispatch(hideDialog(LobbyScreen));
127
+    dispatch(hideLobbyScreen());
113
 
128
 
114
     return next(action);
129
     return next(action);
115
 }
130
 }
123
  */
138
  */
124
 function _findLoadableAvatarForKnockingParticipant({ dispatch, getState }, { id }) {
139
 function _findLoadableAvatarForKnockingParticipant({ dispatch, getState }, { id }) {
125
     const updatedParticipant = getState()['features/lobby'].knockingParticipants.find(p => p.id === id);
140
     const updatedParticipant = getState()['features/lobby'].knockingParticipants.find(p => p.id === id);
141
+    const { disableThirdPartyRequests } = getState()['features/base/config'];
126
 
142
 
127
-    if (updatedParticipant && !updatedParticipant.loadableAvatarUrl) {
143
+    if (!disableThirdPartyRequests && updatedParticipant && !updatedParticipant.loadableAvatarUrl) {
128
         getFirstLoadableAvatarUrl(updatedParticipant).then(loadableAvatarUrl => {
144
         getFirstLoadableAvatarUrl(updatedParticipant).then(loadableAvatarUrl => {
129
             if (loadableAvatarUrl) {
145
             if (loadableAvatarUrl) {
130
                 dispatch(participantIsKnockingOrUpdated({
146
                 dispatch(participantIsKnockingOrUpdated({

+ 19
- 6
react/features/lobby/reducer.js Näytä tiedosto

1
 // @flow
1
 // @flow
2
 
2
 
3
-import { CONFERENCE_FAILED, CONFERENCE_JOINED, CONFERENCE_LEFT } from '../base/conference';
3
+import { CONFERENCE_JOINED, CONFERENCE_LEFT, SET_PASSWORD } from '../base/conference';
4
 import { ReducerRegistry } from '../base/redux';
4
 import { ReducerRegistry } from '../base/redux';
5
 
5
 
6
 import {
6
 import {
7
     KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED,
7
     KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED,
8
     KNOCKING_PARTICIPANT_LEFT,
8
     KNOCKING_PARTICIPANT_LEFT,
9
     SET_KNOCKING_STATE,
9
     SET_KNOCKING_STATE,
10
-    SET_LOBBY_MODE_ENABLED
10
+    SET_LOBBY_MODE_ENABLED,
11
+    SET_PASSWORD_JOIN_FAILED
11
 } from './actionTypes';
12
 } from './actionTypes';
12
 
13
 
13
 const DEFAULT_STATE = {
14
 const DEFAULT_STATE = {
14
     knocking: false,
15
     knocking: false,
15
     knockingParticipants: [],
16
     knockingParticipants: [],
16
-    lobbyEnabled: false
17
+    lobbyEnabled: false,
18
+    passwordJoinFailed: false
17
 };
19
 };
18
 
20
 
19
 /**
21
 /**
26
  */
28
  */
27
 ReducerRegistry.register('features/lobby', (state = DEFAULT_STATE, action) => {
29
 ReducerRegistry.register('features/lobby', (state = DEFAULT_STATE, action) => {
28
     switch (action.type) {
30
     switch (action.type) {
29
-    case CONFERENCE_FAILED:
30
     case CONFERENCE_JOINED:
31
     case CONFERENCE_JOINED:
31
     case CONFERENCE_LEFT:
32
     case CONFERENCE_LEFT:
32
         return {
33
         return {
33
             ...state,
34
             ...state,
34
-            knocking: false
35
+            knocking: false,
36
+            passwordJoinFailed: false
35
         };
37
         };
36
     case KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED:
38
     case KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED:
37
         return _knockingParticipantArrivedOrUpdated(action.participant, state);
39
         return _knockingParticipantArrivedOrUpdated(action.participant, state);
43
     case SET_KNOCKING_STATE:
45
     case SET_KNOCKING_STATE:
44
         return {
46
         return {
45
             ...state,
47
             ...state,
46
-            knocking: action.knocking
48
+            knocking: action.knocking,
49
+            passwordJoinFailed: false
47
         };
50
         };
48
     case SET_LOBBY_MODE_ENABLED:
51
     case SET_LOBBY_MODE_ENABLED:
49
         return {
52
         return {
50
             ...state,
53
             ...state,
51
             lobbyEnabled: action.enabled
54
             lobbyEnabled: action.enabled
52
         };
55
         };
56
+    case SET_PASSWORD:
57
+        return {
58
+            ...state,
59
+            passwordJoinFailed: false
60
+        };
61
+    case SET_PASSWORD_JOIN_FAILED:
62
+        return {
63
+            ...state,
64
+            passwordJoinFailed: action.failed
65
+        };
53
     }
66
     }
54
 
67
 
55
     return state;
68
     return state;

+ 2
- 2
react/features/overlay/middleware.js Näytä tiedosto

10
 /**
10
 /**
11
  * List of errors that are not fatal (or handled differently) so then the overlays won't kick in.
11
  * List of errors that are not fatal (or handled differently) so then the overlays won't kick in.
12
  */
12
  */
13
-const NON_FATAR_ERRORS = [
13
+const NON_OVERLAY_ERRORS = [
14
     JitsiConferenceErrors.CONFERENCE_ACCESS_DENIED,
14
     JitsiConferenceErrors.CONFERENCE_ACCESS_DENIED,
15
     JitsiConferenceErrors.CONFERENCE_DESTROYED,
15
     JitsiConferenceErrors.CONFERENCE_DESTROYED,
16
     JitsiConferenceErrors.CONNECTION_ERROR
16
     JitsiConferenceErrors.CONNECTION_ERROR
31
     },
31
     },
32
     /* listener */ (error, { dispatch }) => {
32
     /* listener */ (error, { dispatch }) => {
33
         error
33
         error
34
-            && NON_FATAR_ERRORS.indexOf(error.name) === -1
34
+            && NON_OVERLAY_ERRORS.indexOf(error.name) === -1
35
             && typeof error.recoverable === 'undefined'
35
             && typeof error.recoverable === 'undefined'
36
             && dispatch(setFatalError(error));
36
             && dispatch(setFatalError(error));
37
     }
37
     }

+ 39
- 38
react/features/prejoin/components/Prejoin.js Näytä tiedosto

6
 import { getRoomName } from '../../base/conference';
6
 import { getRoomName } from '../../base/conference';
7
 import { translate } from '../../base/i18n';
7
 import { translate } from '../../base/i18n';
8
 import { Icon, IconPhone, IconVolumeOff } from '../../base/icons';
8
 import { Icon, IconPhone, IconVolumeOff } from '../../base/icons';
9
+import { ActionButton, InputField, PreMeetingScreen } from '../../base/premeeting';
9
 import { connect } from '../../base/redux';
10
 import { connect } from '../../base/redux';
10
 import { getDisplayName, updateSettings } from '../../base/settings';
11
 import { getDisplayName, updateSettings } from '../../base/settings';
11
-import { isGuest } from '../../invite';
12
-import { VideoSettingsButton, AudioSettingsButton } from '../../toolbox';
13
 import {
12
 import {
14
     joinConference as joinConferenceAction,
13
     joinConference as joinConferenceAction,
15
     joinConferenceWithoutAudio as joinConferenceWithoutAudioAction,
14
     joinConferenceWithoutAudio as joinConferenceWithoutAudioAction,
17
     setJoinByPhoneDialogVisiblity as setJoinByPhoneDialogVisiblityAction
16
     setJoinByPhoneDialogVisiblity as setJoinByPhoneDialogVisiblityAction
18
 } from '../actions';
17
 } from '../actions';
19
 import {
18
 import {
19
+    getActiveVideoTrack,
20
     isJoinByPhoneButtonVisible,
20
     isJoinByPhoneButtonVisible,
21
     isDeviceStatusVisible,
21
     isDeviceStatusVisible,
22
-    isJoinByPhoneDialogVisible
22
+    isJoinByPhoneDialogVisible,
23
+    isPrejoinVideoMuted
23
 } from '../functions';
24
 } from '../functions';
24
 
25
 
25
-import ActionButton from './buttons/ActionButton';
26
 import JoinByPhoneDialog from './dialogs/JoinByPhoneDialog';
26
 import JoinByPhoneDialog from './dialogs/JoinByPhoneDialog';
27
-import CopyMeetingUrl from './preview/CopyMeetingUrl';
28
 import DeviceStatus from './preview/DeviceStatus';
27
 import DeviceStatus from './preview/DeviceStatus';
29
-import ParticipantName from './preview/ParticipantName';
30
-import Preview from './preview/Preview';
31
-
32
 
28
 
33
 type Props = {
29
 type Props = {
34
 
30
 
42
      */
38
      */
43
     hasJoinByPhoneButton: boolean,
39
     hasJoinByPhoneButton: boolean,
44
 
40
 
45
-    /**
46
-     * Flag signaling if a user is logged in or not.
47
-     */
48
-    isAnonymousUser: boolean,
49
-
50
     /**
41
     /**
51
      * Joins the current meeting.
42
      * Joins the current meeting.
52
      */
43
      */
82
      */
73
      */
83
     setJoinByPhoneDialogVisiblity: Function,
74
     setJoinByPhoneDialogVisiblity: Function,
84
 
75
 
76
+    /**
77
+     * Flag signaling the visibility of camera preview.
78
+     */
79
+    showCameraPreview: boolean,
80
+
85
     /**
81
     /**
86
      * If 'JoinByPhoneDialog' is visible or not.
82
      * If 'JoinByPhoneDialog' is visible or not.
87
      */
83
      */
91
      * Used for translation.
87
      * Used for translation.
92
      */
88
      */
93
     t: Function,
89
     t: Function,
90
+
91
+    /**
92
+     * The JitsiLocalTrack to display.
93
+     */
94
+    videoTrack: ?Object,
94
 };
95
 };
95
 
96
 
96
 type State = {
97
 type State = {
211
      */
212
      */
212
     render() {
213
     render() {
213
         const {
214
         const {
214
-            deviceStatusVisible,
215
             hasJoinByPhoneButton,
215
             hasJoinByPhoneButton,
216
-            isAnonymousUser,
217
             joinConference,
216
             joinConference,
218
             joinConferenceWithoutAudio,
217
             joinConferenceWithoutAudio,
219
             name,
218
             name,
219
+            showCameraPreview,
220
             showDialog,
220
             showDialog,
221
-            t
221
+            t,
222
+            videoTrack
222
         } = this.props;
223
         } = this.props;
223
 
224
 
224
         const { _closeDialog, _onCheckboxChange, _onDropdownClose, _onOptionsClick, _setName, _showDialog } = this;
225
         const { _closeDialog, _onCheckboxChange, _onDropdownClose, _onOptionsClick, _setName, _showDialog } = this;
225
         const { showJoinByPhoneButtons } = this.state;
226
         const { showJoinByPhoneButtons } = this.state;
226
 
227
 
227
         return (
228
         return (
228
-            <div className = 'prejoin-full-page'>
229
-                <Preview name = { name } />
229
+            <PreMeetingScreen
230
+                footer = { this._renderFooter() }
231
+                title = { t('prejoin.joinMeeting') }
232
+                videoMuted = { !showCameraPreview }
233
+                videoTrack = { videoTrack }>
230
                 <div className = 'prejoin-input-area-container'>
234
                 <div className = 'prejoin-input-area-container'>
231
                     <div className = 'prejoin-input-area'>
235
                     <div className = 'prejoin-input-area'>
232
-                        <div className = 'prejoin-title'>
233
-                            {t('prejoin.joinMeeting')}
234
-                        </div>
235
-
236
-                        <CopyMeetingUrl />
237
-
238
-                        <ParticipantName
239
-                            isEditable = { isAnonymousUser }
240
-                            joinConference = { joinConference }
241
-                            setName = { _setName }
236
+                        <InputField
237
+                            onChange = { _setName }
238
+                            onSubmit = { joinConference }
239
+                            placeHolder = { t('dialog.enterDisplayName') }
242
                             value = { name } />
240
                             value = { name } />
243
 
241
 
244
                         <div className = 'prejoin-preview-dropdown-container'>
242
                         <div className = 'prejoin-preview-dropdown-container'>
275
                                 </ActionButton>
273
                                 </ActionButton>
276
                             </InlineDialog>
274
                             </InlineDialog>
277
                         </div>
275
                         </div>
278
-
279
-                        <div className = 'prejoin-preview-btn-container'>
280
-                            <AudioSettingsButton visible = { true } />
281
-                            <VideoSettingsButton visible = { true } />
282
-                        </div>
283
                     </div>
276
                     </div>
284
 
277
 
285
                     <div className = 'prejoin-checkbox-container'>
278
                     <div className = 'prejoin-checkbox-container'>
290
                         <span>{t('prejoin.doNotShow')}</span>
283
                         <span>{t('prejoin.doNotShow')}</span>
291
                     </div>
284
                     </div>
292
                 </div>
285
                 </div>
293
-
294
-                { deviceStatusVisible && <DeviceStatus /> }
295
                 { showDialog && (
286
                 { showDialog && (
296
                     <JoinByPhoneDialog
287
                     <JoinByPhoneDialog
297
                         joinConferenceWithoutAudio = { joinConferenceWithoutAudio }
288
                         joinConferenceWithoutAudio = { joinConferenceWithoutAudio }
298
                         onClose = { _closeDialog } />
289
                         onClose = { _closeDialog } />
299
                 )}
290
                 )}
300
-            </div>
291
+            </PreMeetingScreen>
301
         );
292
         );
302
     }
293
     }
294
+
295
+    /**
296
+     * Renders the screen footer if any.
297
+     *
298
+     * @returns {React$Element}
299
+     */
300
+    _renderFooter() {
301
+        return this.props.deviceStatusVisible && <DeviceStatus />;
302
+    }
303
 }
303
 }
304
 
304
 
305
 /**
305
 /**
310
  */
310
  */
311
 function mapStateToProps(state): Object {
311
 function mapStateToProps(state): Object {
312
     return {
312
     return {
313
-        isAnonymousUser: isGuest(state),
314
         deviceStatusVisible: isDeviceStatusVisible(state),
313
         deviceStatusVisible: isDeviceStatusVisible(state),
315
         name: getDisplayName(state),
314
         name: getDisplayName(state),
316
         roomName: getRoomName(state),
315
         roomName: getRoomName(state),
317
         showDialog: isJoinByPhoneDialogVisible(state),
316
         showDialog: isJoinByPhoneDialogVisible(state),
318
-        hasJoinByPhoneButton: isJoinByPhoneButtonVisible(state)
317
+        hasJoinByPhoneButton: isJoinByPhoneButtonVisible(state),
318
+        showCameraPreview: !isPrejoinVideoMuted(state),
319
+        videoTrack: getActiveVideoTrack(state)
319
     };
320
     };
320
 }
321
 }
321
 
322
 

+ 0
- 88
react/features/prejoin/components/buttons/ActionButton.js Näytä tiedosto

1
-// @flow
2
-
3
-import React from 'react';
4
-
5
-import { Icon, IconArrowDown } from '../../../base/icons';
6
-
7
-const classNameByType = {
8
-    primary: 'prejoin-btn--primary',
9
-    secondary: 'prejoin-btn--secondary',
10
-    text: 'prejoin-btn--text'
11
-};
12
-
13
-type Props = {
14
-
15
-    /**
16
-     * Text of the button.
17
-     */
18
-    children: React$Node,
19
-
20
-    /**
21
-     * Text css class of the button.
22
-     */
23
-    className?: string,
24
-
25
-    /**
26
-     * If the button is disabled or not.
27
-     */
28
-    disabled?: boolean,
29
-
30
-    /**
31
-     * If the button has options.
32
-     */
33
-    hasOptions?: boolean,
34
-
35
-    /**
36
-     * The type of th button: primary, secondary, text.
37
-     */
38
-    type: string,
39
-
40
-    /**
41
-     * OnClick button handler.
42
-     */
43
-    onClick: Function,
44
-
45
-    /**
46
-     * Click handler for options.
47
-     */
48
-    onOptionsClick?: Function
49
-};
50
-
51
-/**
52
- * Button used for prejoin actions: Join/Join without audio/Join by phone.
53
- *
54
- * @returns {ReactElement}
55
- */
56
-function ActionButton({ children, className, disabled, hasOptions, type, onClick, onOptionsClick }: Props) {
57
-    let ownClassName = 'prejoin-btn';
58
-    let clickHandler = onClick;
59
-    let optionsClickHandler = onOptionsClick;
60
-
61
-    if (disabled) {
62
-        clickHandler = null;
63
-        optionsClickHandler = null;
64
-        ownClassName = `${ownClassName} prejoin-btn--disabled`;
65
-    } else {
66
-        ownClassName = `${ownClassName} ${classNameByType[type]}`;
67
-    }
68
-    const cls = className ? `${className} ${ownClassName}` : ownClassName;
69
-
70
-    return (
71
-        <div
72
-            className = { cls }
73
-            onClick = { clickHandler }>
74
-            {children}
75
-            {hasOptions && <div
76
-                className = 'prejoin-btn-options'
77
-                onClick = { optionsClickHandler }>
78
-                <Icon
79
-                    className = 'prejoin-btn-icon'
80
-                    size = { 14 }
81
-                    src = { IconArrowDown } />
82
-            </div>
83
-            }
84
-        </div>
85
-    );
86
-}
87
-
88
-export default ActionButton;

+ 1
- 1
react/features/prejoin/components/dialogs/DialInDialog.js Näytä tiedosto

4
 
4
 
5
 import { translate } from '../../../base/i18n';
5
 import { translate } from '../../../base/i18n';
6
 import { Icon, IconArrowLeft } from '../../../base/icons';
6
 import { Icon, IconArrowLeft } from '../../../base/icons';
7
+import { ActionButton } from '../../../base/premeeting';
7
 import { getCountryCodeFromPhone } from '../../utils';
8
 import { getCountryCodeFromPhone } from '../../utils';
8
 import Label from '../Label';
9
 import Label from '../Label';
9
-import ActionButton from '../buttons/ActionButton';
10
 
10
 
11
 type Props = {
11
 type Props = {
12
 
12
 

+ 1
- 1
react/features/prejoin/components/dialogs/DialOutDialog.js Näytä tiedosto

4
 
4
 
5
 import { translate } from '../../../base/i18n';
5
 import { translate } from '../../../base/i18n';
6
 import { Icon, IconClose } from '../../../base/icons';
6
 import { Icon, IconClose } from '../../../base/icons';
7
+import { ActionButton } from '../../../base/premeeting';
7
 import Label from '../Label';
8
 import Label from '../Label';
8
-import ActionButton from '../buttons/ActionButton';
9
 import CountryPicker from '../country-picker/CountryPicker';
9
 import CountryPicker from '../country-picker/CountryPicker';
10
 
10
 
11
 type Props = {
11
 type Props = {

+ 3
- 1
react/features/prejoin/components/preview/DeviceStatus.js Näytä tiedosto

62
                 size = { 16 }
62
                 size = { 16 }
63
                 src = { src } />
63
                 src = { src } />
64
             <span className = 'prejoin-preview-error-desc'>{t(deviceStatusText)}</span>
64
             <span className = 'prejoin-preview-error-desc'>{t(deviceStatusText)}</span>
65
-            <span>{rawError}</span>
65
+            { rawError && <span>
66
+                { rawError }
67
+            </span> }
66
         </div>
68
         </div>
67
     );
69
     );
68
 }
70
 }

+ 0
- 110
react/features/prejoin/components/preview/ParticipantName.js Näytä tiedosto

1
-// @flow
2
-
3
-import React, { Component } from 'react';
4
-
5
-import { translate } from '../../../base/i18n';
6
-
7
-type Props = {
8
-
9
-    /**
10
-     * Flag signaling if the name is ediable or not.
11
-     */
12
-    isEditable: boolean,
13
-
14
-    /**
15
-     * Joins the current meeting.
16
-     */
17
-    joinConference: Function,
18
-
19
-    /**
20
-     * Sets the name for the joining user.
21
-     */
22
-    setName: Function,
23
-
24
-    /**
25
-     * Used to obtain translations.
26
-     */
27
-    t: Function,
28
-
29
-    /**
30
-     * The text to be displayed.
31
-     */
32
-    value: string,
33
-};
34
-
35
-/**
36
- * Participant name - can be an editable input or just the text name.
37
- *
38
- * @returns {ReactElement}
39
- */
40
-class ParticipantName extends Component<Props> {
41
-
42
-    /**
43
-     * Initializes a new {@code ParticipantName} instance.
44
-     *
45
-     * @param {Props} props - The props of the component.
46
-     * @inheritdoc
47
-     */
48
-    constructor(props) {
49
-        super(props);
50
-
51
-        this._onKeyDown = this._onKeyDown.bind(this);
52
-        this._onNameChange = this._onNameChange.bind(this);
53
-    }
54
-
55
-    _onKeyDown: () => void;
56
-
57
-    /**
58
-     * Joins the conference on 'Enter'.
59
-     *
60
-     * @param {Event} event - Key down event object.
61
-     * @returns {void}
62
-     */
63
-    _onKeyDown(event) {
64
-        if (event.key === 'Enter') {
65
-            this.props.joinConference();
66
-        }
67
-    }
68
-
69
-    _onNameChange: () => void;
70
-
71
-    /**
72
-     * Handler used for changing the guest user name.
73
-     *
74
-     * @returns {undefined}
75
-     */
76
-    _onNameChange({ target: { value } }) {
77
-        this.props.setName(value);
78
-    }
79
-
80
-    /**
81
-     * Implements React's {@link Component#render()}.
82
-     *
83
-     * @inheritdoc
84
-     * @returns {ReactElement}
85
-     */
86
-    render() {
87
-        const { value, isEditable, t } = this.props;
88
-        const { _onKeyDown, _onNameChange } = this;
89
-
90
-        return isEditable ? (
91
-            <input
92
-                autoFocus = { true }
93
-                className = 'prejoin-preview-name prejoin-preview-name--editable'
94
-                onChange = { _onNameChange }
95
-                onKeyDown = { _onKeyDown }
96
-                placeholder = { t('dialog.enterDisplayName') }
97
-                value = { value } />
98
-        )
99
-            : <div
100
-                className = 'prejoin-preview-name prejoin-preview-name--text'
101
-                onKeyDown = { _onKeyDown }
102
-                tabIndex = '0' >
103
-                {value}
104
-            </div>
105
-        ;
106
-    }
107
-}
108
-
109
-
110
-export default translate(ParticipantName);

+ 0
- 76
react/features/prejoin/components/preview/Preview.js Näytä tiedosto

1
-// @flow
2
-
3
-import React from 'react';
4
-
5
-import { Avatar } from '../../../base/avatar';
6
-import { Video } from '../../../base/media';
7
-import { connect } from '../../../base/redux';
8
-import { getActiveVideoTrack, isPrejoinVideoMuted } from '../../functions';
9
-
10
-export type Props = {
11
-
12
-    /**
13
-     * The name of the user that is about to join.
14
-     */
15
-    name: string,
16
-
17
-    /**
18
-     * Flag signaling the visibility of camera preview.
19
-     */
20
-    showCameraPreview: boolean,
21
-
22
-    /**
23
-     * The JitsiLocalTrack to display.
24
-     */
25
-    videoTrack: ?Object,
26
-};
27
-
28
-/**
29
- * Component showing the video preview and device status.
30
- *
31
- * @param {Props} props - The props of the component.
32
- * @returns {ReactElement}
33
- */
34
-function Preview(props: Props) {
35
-    const {
36
-        name,
37
-        showCameraPreview,
38
-        videoTrack
39
-    } = props;
40
-
41
-    if (showCameraPreview && videoTrack) {
42
-        return (
43
-            <div className = 'prejoin-preview'>
44
-                <div className = 'prejoin-preview-overlay' />
45
-                <div className = 'prejoin-preview-bottom-overlay' />
46
-                <Video
47
-                    className = 'flipVideoX prejoin-preview-video'
48
-                    videoTrack = {{ jitsiTrack: videoTrack }} />
49
-            </div>
50
-        );
51
-    }
52
-
53
-    return (
54
-        <div className = 'prejoin-preview prejoin-preview--no-video'>
55
-            <Avatar
56
-                className = 'prejoin-preview-avatar'
57
-                displayName = { name }
58
-                size = { 200 } />
59
-        </div>
60
-    );
61
-}
62
-
63
-/**
64
- * Maps the redux state to the React {@code Component} props.
65
- *
66
- * @param {Object} state - The redux state.
67
- * @returns {Object}
68
- */
69
-function mapStateToProps(state) {
70
-    return {
71
-        videoTrack: getActiveVideoTrack(state),
72
-        showCameraPreview: !isPrejoinVideoMuted(state)
73
-    };
74
-}
75
-
76
-export default connect(mapStateToProps)(Preview);

+ 2
- 1
react/features/prejoin/functions.js Näytä tiedosto

259
  * @returns {boolean}
259
  * @returns {boolean}
260
  */
260
  */
261
 export function isPrejoinPageEnabled(state: Object): boolean {
261
 export function isPrejoinPageEnabled(state: Object): boolean {
262
-    return state['features/base/config'].prejoinPageEnabled
262
+    return navigator.product !== 'ReactNative'
263
+        && state['features/base/config'].prejoinPageEnabled
263
         && !state['features/base/settings'].userSelectedSkipPrejoin;
264
         && !state['features/base/settings'].userSelectedSkipPrejoin;
264
 }
265
 }
265
 
266
 

+ 16
- 13
react/features/security/components/security-dialog/PasswordSection.js Näytä tiedosto

169
     }
169
     }
170
 
170
 
171
     return (
171
     return (
172
-        <div className = 'security-dialog password'>
173
-            <div
174
-                className = 'info-dialog info-dialog-column info-dialog-password'
175
-                ref = { formRef }>
176
-                <PasswordForm
177
-                    editEnabled = { passwordEditEnabled }
178
-                    locked = { locked }
179
-                    onSubmit = { onPasswordSubmit }
180
-                    password = { password }
181
-                    passwordNumberOfDigits = { passwordNumberOfDigits } />
182
-            </div>
183
-            <div className = 'security-dialog password-actions'>
184
-                { renderPasswordActions() }
172
+        <div className = 'security-dialog password-section'>
173
+            { t('security.about') }
174
+            <div className = 'security-dialog password'>
175
+                <div
176
+                    className = 'info-dialog info-dialog-column info-dialog-password'
177
+                    ref = { formRef }>
178
+                    <PasswordForm
179
+                        editEnabled = { passwordEditEnabled }
180
+                        locked = { locked }
181
+                        onSubmit = { onPasswordSubmit }
182
+                        password = { password }
183
+                        passwordNumberOfDigits = { passwordNumberOfDigits } />
184
+                </div>
185
+                <div className = 'security-dialog password-actions'>
186
+                    { renderPasswordActions() }
187
+                </div>
185
             </div>
188
             </div>
186
         </div>
189
         </div>
187
     );
190
     );

+ 4
- 4
react/features/security/components/security-dialog/SecurityDialog.js Näytä tiedosto

7
 import { translate } from '../../../base/i18n';
7
 import { translate } from '../../../base/i18n';
8
 import { isLocalParticipantModerator } from '../../../base/participants';
8
 import { isLocalParticipantModerator } from '../../../base/participants';
9
 import { connect } from '../../../base/redux';
9
 import { connect } from '../../../base/redux';
10
+import { LobbySection } from '../../../lobby';
10
 
11
 
11
 import Header from './Header';
12
 import Header from './Header';
12
 import PasswordSection from './PasswordSection';
13
 import PasswordSection from './PasswordSection';
62
     _locked,
63
     _locked,
63
     _password,
64
     _password,
64
     _passwordNumberOfDigits,
65
     _passwordNumberOfDigits,
65
-    setPassword,
66
-    t
66
+    setPassword
67
 }: Props) {
67
 }: Props) {
68
     const [ passwordEditEnabled, setPasswordEditEnabled ] = useState(false);
68
     const [ passwordEditEnabled, setPasswordEditEnabled ] = useState(false);
69
 
69
 
81
             titleKey = 'security.securityOptions'
81
             titleKey = 'security.securityOptions'
82
             width = { 'small' }>
82
             width = { 'small' }>
83
             <div className = 'security-dialog'>
83
             <div className = 'security-dialog'>
84
-                { t('security.about') }
85
-                <div className = 'invite-more-dialog separator' />
84
+                <LobbySection />
85
+                <div className = 'separator-line' />
86
                 <PasswordSection
86
                 <PasswordSection
87
                     canEditPassword = { _canEditPassword }
87
                     canEditPassword = { _canEditPassword }
88
                     conference = { _conference }
88
                     conference = { _conference }

+ 2
- 1
react/features/security/components/security-dialog/SecurityDialogButton.js Näytä tiedosto

63
  */
63
  */
64
 function mapStateToProps(state: Object) {
64
 function mapStateToProps(state: Object) {
65
     const { locked } = state['features/base/conference'];
65
     const { locked } = state['features/base/conference'];
66
+    const { lobbyEnabled } = state['features/lobby'];
66
 
67
 
67
     return {
68
     return {
68
-        _locked: locked
69
+        _locked: locked || lobbyEnabled
69
     };
70
     };
70
 }
71
 }
71
 
72
 

+ 1
- 1
react/features/toolbox/components/native/OverflowMenu.js Näytä tiedosto

11
 import { StyleType } from '../../../base/styles';
11
 import { StyleType } from '../../../base/styles';
12
 import { SharedDocumentButton } from '../../../etherpad';
12
 import { SharedDocumentButton } from '../../../etherpad';
13
 import { InviteButton } from '../../../invite';
13
 import { InviteButton } from '../../../invite';
14
-import { LobbyModeButton } from '../../../lobby';
14
+import { LobbyModeButton } from '../../../lobby/components/native';
15
 import { AudioRouteButton } from '../../../mobile/audio-mode';
15
 import { AudioRouteButton } from '../../../mobile/audio-mode';
16
 import { LiveStreamButton, RecordButton } from '../../../recording';
16
 import { LiveStreamButton, RecordButton } from '../../../recording';
17
 import { RoomLockButton } from '../../../room-lock';
17
 import { RoomLockButton } from '../../../room-lock';

+ 0
- 6
react/features/toolbox/components/web/Toolbox.js Näytä tiedosto

38
 import { openFeedbackDialog } from '../../../feedback';
38
 import { openFeedbackDialog } from '../../../feedback';
39
 import { beginAddPeople } from '../../../invite';
39
 import { beginAddPeople } from '../../../invite';
40
 import { openKeyboardShortcutsDialog } from '../../../keyboard-shortcuts';
40
 import { openKeyboardShortcutsDialog } from '../../../keyboard-shortcuts';
41
-import { LobbyModeButton } from '../../../lobby';
42
 import {
41
 import {
43
     LocalRecordingButton,
42
     LocalRecordingButton,
44
     LocalRecordingInfoDialog
43
     LocalRecordingInfoDialog
1189
         if (this._shouldShowButton('closedcaptions')) {
1188
         if (this._shouldShowButton('closedcaptions')) {
1190
             buttonsLeft.push('closedcaptions');
1189
             buttonsLeft.push('closedcaptions');
1191
         }
1190
         }
1192
-        if (this._shouldShowButton('lobby')) {
1193
-            buttonsRight.push('lobby');
1194
-        }
1195
         if (overflowHasItems) {
1191
         if (overflowHasItems) {
1196
             buttonsRight.push('overflowmenu');
1192
             buttonsRight.push('overflowmenu');
1197
         }
1193
         }
1275
                     { this._renderVideoButton() }
1271
                     { this._renderVideoButton() }
1276
                 </div>
1272
                 </div>
1277
                 <div className = 'button-group-right'>
1273
                 <div className = 'button-group-right'>
1278
-                    { (buttonsRight.indexOf('lobby') !== -1)
1279
-                        && <LobbyModeButton /> }
1280
                     { buttonsRight.indexOf('localrecording') !== -1
1274
                     { buttonsRight.indexOf('localrecording') !== -1
1281
                         && <LocalRecordingButton
1275
                         && <LocalRecordingButton
1282
                             onClick = {
1276
                             onClick = {

Loading…
Peruuta
Tallenna