소스 검색

ref: merge prejoin with lobby

master
Bettenbuk Zoltan 5 년 전
부모
커밋
29dc63fbcb
68개의 변경된 파일1415개의 추가작업 그리고 1211개의 파일을 삭제
  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 파일 보기

@@ -1,115 +1,48 @@
1 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 34
     display: flex;
94 35
     flex-direction: column;
95
-    margin: 32px 0;
96
-
97
-    .description {
98
-        margin-bottom: 18px;
99
-    }
100 36
 
101
-    .field {
37
+    .control-row {
102 38
         display: flex;
103 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,11 +95,7 @@
162 95
             }
163 96
         }
164 97
     }
165
-}
166 98
 
167
-// Common styles
168
-
169
-#lobby-dialog, #lobby-screen, #knocking-participant-list {
170 99
     input {
171 100
         align-self: stretch;
172 101
         background-color: transparent;
@@ -208,4 +137,4 @@
208 137
             border-width: 0;
209 138
         }
210 139
     }
211
-}
140
+}

+ 3
- 157
css/_prejoin.scss 파일 보기

@@ -1,18 +1,4 @@
1 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 3
     &-input-area {
18 4
         margin: 0 auto;
@@ -27,65 +13,6 @@
27 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 16
     &-text-btns {
90 17
         display: flex;
91 18
         justify-content: space-between;
@@ -179,25 +106,6 @@
179 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 109
     &-overlay {
202 110
         height: 100%;
203 111
         position: absolute;
@@ -217,22 +125,20 @@
217 125
 
218 126
     &-status {
219 127
         align-items: center;
220
-        bottom: 0;
128
+        align-self: stretch;
221 129
         color: #fff;
222 130
         display: flex;
223 131
         font-size: 13px;
224 132
         min-height: 24px;
225 133
         justify-content: center;
226
-        position: absolute;
227 134
         text-align: center;
228
-        width: 100%;
229 135
         z-index: 1;
230 136
 
231 137
         &--warning {
232
-            background: rgba(241, 173, 51, 0.5)
138
+            background: rgba(241, 173, 51, 0.7)
233 139
         }
234 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,63 +197,3 @@
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 파일 보기

@@ -0,0 +1,194 @@
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 파일 보기

@@ -97,5 +97,6 @@ $flagsImagePath: "../images/";
97 97
 @import 'country-picker';
98 98
 @import 'modals/invite/invite_more';
99 99
 @import 'modals/security/security';
100
+@import 'premeeting-screens';
100 101
 
101 102
 /* Modules END */

+ 37
- 15
css/modals/security/_security.scss 파일 보기

@@ -3,25 +3,47 @@
3 3
         color: #fff;
4 4
         font-size: 15px;
5 5
         line-height: 24px;
6
-        
7
-        &.password {
6
+
7
+        &.password-section {
8 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,4 +56,4 @@
34 56
         background: rgba(241, 173, 51, 0.7);
35 57
         border: 1px solid rgba(255, 255, 255, 0.4);
36 58
     }
37
-}
59
+}

+ 1
- 1
interface_config.js 파일 보기

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

+ 9
- 6
lang/main.json 파일 보기

@@ -867,26 +867,29 @@
867 867
     },
868 868
     "lobby": {
869 869
         "allow": "Allow",
870
-        "backToKnockModeButton": "No password, knock instead",
870
+        "backToKnockModeButton": "No password, ask to join instead",
871 871
         "dialogTitle": "Lobby mode",
872 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 873
         "disableDialogSubmit": "Disable",
874 874
         "emailField": "Enter your email address",
875 875
         "enableDialogPasswordField": "Set password (optional)",
876 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 878
         "enterPasswordButton": "Enter meeting password",
879
+        "enterPasswordTitle": "Enter password to join meeting",
880
+        "invalidPassword": "Invalid password",
879 881
         "joiningMessage": "You'll join the meeting as soon as someone accepts your request",
880 882
         "joinWithPasswordMessage": "Trying to join with password, please wait...",
881 883
         "joinRejectedMessage": "Your join request was rejected by a moderator.",
882 884
         "joinTitle": "Join Meeting",
883
-        "joiningTitle": "Asking to join",
884
-        "joiningWithPasswordTitle": "Joining",
885
+        "joiningTitle": "Asking to join meeting...",
886
+        "joiningWithPasswordTitle": "Joining with password...",
885 887
         "knockButton": "Ask to Join",
886 888
         "knockTitle": "Someone wants to join the meeting",
887 889
         "nameField": "Enter your name",
888
-        "passwordField": "Enter password",
890
+        "passwordField": "Enter meeting password",
889 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 파일 보기

@@ -249,6 +249,7 @@ export function authStatusChanged(authEnabled: boolean, authLogin: string) {
249 249
  * @param {JitsiConference} conference - The JitsiConference that has failed.
250 250
  * @param {string} error - The error describing/detailing the cause of the
251 251
  * failure.
252
+ * @param {any} params - Rest of the params that we receive together with the event.
252 253
  * @returns {{
253 254
  *     type: CONFERENCE_FAILED,
254 255
  *     conference: JitsiConference,
@@ -651,28 +652,23 @@ export function setPassword(
651 652
         case conference.join: {
652 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 673
             break;
678 674
         }

+ 4
- 5
react/features/base/conference/middleware.js 파일 보기

@@ -145,10 +145,6 @@ function _conferenceFailed({ dispatch, getState }, next, action) {
145 145
     const result = next(action);
146 146
     const { conference, error } = action;
147 147
 
148
-    if (error.name === JitsiConferenceErrors.OFFER_ANSWER_FAILED) {
149
-        sendAnalytics(createOfferAnswerFailedEvent());
150
-    }
151
-
152 148
     // Handle specific failure reasons.
153 149
     switch (error.name) {
154 150
     case JitsiConferenceErrors.CONFERENCE_DESTROYED: {
@@ -167,7 +163,7 @@ function _conferenceFailed({ dispatch, getState }, next, action) {
167 163
     case JitsiConferenceErrors.CONNECTION_ERROR: {
168 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 167
         dispatch(showErrorNotification({
172 168
             descriptionArguments: { msg },
173 169
             descriptionKey: msg ? 'dialog.connectErrorWithMsg' : 'dialog.connectError',
@@ -176,6 +172,9 @@ function _conferenceFailed({ dispatch, getState }, next, action) {
176 172
 
177 173
         break;
178 174
     }
175
+    case JitsiConferenceErrors.OFFER_ANSWER_FAILED:
176
+        sendAnalytics(createOfferAnswerFailedEvent());
177
+        break;
179 178
     }
180 179
 
181 180
     // FIXME: Workaround for the web version. Currently, the creation of the

+ 24
- 28
react/features/base/conference/reducer.js 파일 보기

@@ -387,34 +387,30 @@ function _setDesktopSharingEnabled(state, action) {
387 387
 function _setPassword(state, { conference, method, password }) {
388 388
     switch (method) {
389 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 415
     case conference.lock:
420 416
         return assign(state, {

+ 1
- 2
react/features/base/connection/actionTypes.js 파일 보기

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

+ 5
- 9
react/features/base/connection/actions.native.js 파일 보기

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

+ 4
- 1
react/features/base/dialog/actions.js 파일 보기

@@ -30,14 +30,17 @@ export function hideDialog(component: ?Object) {
30 30
  * @param {Object} component - The component to display as dialog.
31 31
  * @param {Object} [componentProps] - The React {@code Component} props of the
32 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 35
  * @returns {{
34 36
  *     type: OPEN_DIALOG,
35 37
  *     component: React.Component,
36 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 42
     return {
43
+        rawDialog,
41 44
         type: OPEN_DIALOG,
42 45
         component,
43 46
         componentProps

+ 8
- 6
react/features/base/dialog/components/AbstractDialogContainer.js 파일 보기

@@ -17,6 +17,11 @@ type Props = {
17 17
      */
18 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 26
      * True if the UI is in a compact state where we don't show dialogs.
22 27
      */
@@ -52,19 +57,16 @@ export default class AbstractDialogContainer extends Component<Props> {
52 57
  *
53 58
  * @param {Object} state - The redux state.
54 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 63
     const stateFeaturesBaseDialog = state['features/base/dialog'];
63 64
     const { reducedUI } = state['features/base/responsive-ui'];
64 65
 
65 66
     return {
66 67
         _component: stateFeaturesBaseDialog.component,
67 68
         _componentProps: stateFeaturesBaseDialog.componentProps,
69
+        _rawDialog: stateFeaturesBaseDialog.rawDialog,
68 70
         _reducedUI: reducedUI
69 71
     };
70 72
 }

+ 4
- 0
react/features/base/dialog/components/web/DialogContainer.js 파일 보기

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

+ 4
- 2
react/features/base/dialog/reducer.js 파일 보기

@@ -21,7 +21,8 @@ ReducerRegistry.register('features/base/dialog', (state = {}, action) => {
21 21
         if (typeof component === 'undefined' || state.component === component) {
22 22
             return assign(state, {
23 23
                 component: undefined,
24
-                componentProps: undefined
24
+                componentProps: undefined,
25
+                rawDialog: false
25 26
             });
26 27
         }
27 28
         break;
@@ -30,7 +31,8 @@ ReducerRegistry.register('features/base/dialog', (state = {}, action) => {
30 31
     case OPEN_DIALOG:
31 32
         return assign(state, {
32 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 파일 보기

@@ -1,3 +1,3 @@
1
-<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
-<path fill-rule="evenodd" clip-rule="evenodd" d="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 3
 </svg>

+ 1
- 1
react/features/base/icons/svg/index.js 파일 보기

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

+ 0
- 0
react/features/base/premeeting/components/index.native.js 파일 보기


+ 3
- 0
react/features/base/premeeting/components/index.web.js 파일 보기

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

+ 77
- 0
react/features/base/premeeting/components/web/ActionButton.js 파일 보기

@@ -0,0 +1,77 @@
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 파일 보기

@@ -2,10 +2,10 @@
2 2
 
3 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 9
 import logger from '../../logger';
10 10
 
11 11
 type Props = {
@@ -108,7 +108,8 @@ class CopyMeetingUrl extends Component<Props, State> {
108 108
      */
109 109
     _hideCopyLink() {
110 110
         this.setState({
111
-            showCopyLink: false
111
+            showCopyLink: false,
112
+            showLinkCopied: false
112 113
         });
113 114
     }
114 115
 
@@ -122,7 +123,8 @@ class CopyMeetingUrl extends Component<Props, State> {
122 123
      */
123 124
     _showCopyLink() {
124 125
         this.setState({
125
-            showCopyLink: true
126
+            showCopyLink: true,
127
+            showLinkCopied: false
126 128
         });
127 129
     }
128 130
 
@@ -152,35 +154,30 @@ class CopyMeetingUrl extends Component<Props, State> {
152 154
         const { url, t } = this.props;
153 155
         const { _copyUrl, _showCopyLink, _hideCopyLink } = this;
154 156
         const src = showLinkCopied ? IconCheck : IconCopy;
155
-        const iconCls = showCopyLink || showCopyLink ? 'prejoin-copy-icon--white' : 'prejoin-copy-icon--light';
156 157
 
157 158
         return (
158 159
             <div
159
-                className = 'prejoin-copy-meeting'
160
+                className = 'copy-meeting'
160 161
                 onMouseEnter = { _showCopyLink }
161 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 174
                 <textarea
178
-                    className = 'prejoin-copy-textarea'
179 175
                     readOnly = { true }
180 176
                     ref = { this.textarea }
181 177
                     tabIndex = '-1'
182 178
                     value = { url } />
183
-            </div>);
179
+            </div>
180
+        );
184 181
     }
185 182
 }
186 183
 

+ 175
- 0
react/features/base/premeeting/components/web/InputField.js 파일 보기

@@ -0,0 +1,175 @@
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 파일 보기

@@ -0,0 +1,73 @@
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 파일 보기

@@ -0,0 +1,73 @@
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 파일 보기

@@ -0,0 +1,5 @@
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 파일 보기

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

+ 5
- 0
react/features/base/premeeting/logger.js 파일 보기

@@ -0,0 +1,5 @@
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 파일 보기

@@ -209,9 +209,9 @@ class Conference extends AbstractConference<Props, *> {
209 209
 
210 210
                 { this.renderNotificationsContainer() }
211 211
 
212
-                { !filmstripOnly && _showPrejoin && <Prejoin />}
213
-
214 212
                 <CalleeInfoContainer />
213
+
214
+                { !filmstripOnly && _showPrejoin && <Prejoin />}
215 215
             </div>
216 216
         );
217 217
     }

+ 5
- 0
react/features/lobby/actionTypes.js 파일 보기

@@ -19,3 +19,8 @@ export const SET_LOBBY_MODE_ENABLED = 'SET_LOBBY_MODE_ENABLED';
19 19
  * Action type to set the knocking state of the participant.
20 20
  */
21 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 파일 보기

@@ -0,0 +1,25 @@
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 파일 보기

@@ -3,17 +3,18 @@
3 3
 import { type Dispatch } from 'redux';
4 4
 
5 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 8
 import { getLocalParticipant } from '../base/participants';
9 9
 
10 10
 import {
11 11
     KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED,
12 12
     KNOCKING_PARTICIPANT_LEFT,
13 13
     SET_KNOCKING_STATE,
14
-    SET_LOBBY_MODE_ENABLED
14
+    SET_LOBBY_MODE_ENABLED,
15
+    SET_PASSWORD_JOIN_FAILED
15 16
 } from './actionTypes';
16
-import { DisableLobbyModeDialog, EnableLobbyModeDialog, LobbyScreen } from './components';
17
+import { LobbyScreen } from './components';
17 18
 
18 19
 declare var APP: Object;
19 20
 
@@ -23,7 +24,7 @@ declare var APP: Object;
23 24
  * @returns {Function}
24 25
  */
25 26
 export function cancelKnocking() {
26
-    return async (dispatch: Dispatch<any>, getState: Function) => {
27
+    return async (dispatch: Dispatch<any>) => {
27 28
         if (typeof APP !== 'undefined') {
28 29
             // when we are redirecting the library should handle any
29 30
             // unload and clean of the connection.
@@ -33,59 +34,46 @@ export function cancelKnocking() {
33 34
             return;
34 35
         }
35 36
 
36
-        dispatch(conferenceLeft(getCurrentConference(getState)));
37 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,7 +83,7 @@ export function startKnocking(password?: string) {
95 83
  * @returns {openDialog}
96 84
  */
97 85
 export function openLobbyScreen() {
98
-    return openDialog(LobbyScreen);
86
+    return openDialog(LobbyScreen, {}, true);
99 87
 }
100 88
 
101 89
 /**
@@ -123,7 +111,7 @@ export function participantIsKnockingOrUpdated(participant: Object) {
123 111
  */
124 112
 export function setKnockingParticipantApproval(id: string, approved: boolean) {
125 113
     return async (dispatch: Dispatch<any>, getState: Function) => {
126
-        const { conference } = getState()['features/base/conference'];
114
+        const conference = getCurrentConference(getState);
127 115
 
128 116
         if (conference) {
129 117
             if (approved) {
@@ -135,6 +123,22 @@ export function setKnockingParticipantApproval(id: string, approved: boolean) {
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 143
  * Action to set the new state of the lobby mode.
140 144
  *
@@ -152,36 +156,50 @@ export function setLobbyModeEnabled(enabled: boolean) {
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 192
  * Action to toggle lobby mode on or off.
174 193
  *
175 194
  * @param {boolean} enabled - The desired (new) state of the lobby mode.
176
- * @param {string} password - Optional password to be set.
177 195
  * @returns {Function}
178 196
  */
179
-export function toggleLobbyMode(enabled: boolean, password?: string) {
197
+export function toggleLobbyMode(enabled: boolean) {
180 198
     return async (dispatch: Dispatch<any>, getState: Function) => {
181
-        const { conference } = getState()['features/base/conference'];
199
+        const conference = getCurrentConference(getState);
182 200
 
183 201
         if (enabled) {
184
-            conference.enableLobby(password);
202
+            conference.enableLobby();
185 203
         } else {
186 204
             conference.disableLobby();
187 205
         }

+ 0
- 47
react/features/lobby/components/AbstractDisableLobbyModeDialog.js 파일 보기

@@ -1,47 +0,0 @@
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 파일 보기

@@ -1,75 +0,0 @@
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 파일 보기

@@ -3,21 +3,15 @@
3 3
 import { PureComponent } from 'react';
4 4
 
5 5
 import { isLocalParticipantModerator } from '../../base/participants';
6
-import { isToolboxVisible } from '../../toolbox';
7 6
 import { setKnockingParticipantApproval } from '../actions';
8 7
 
9
-type Props = {
8
+export type Props = {
10 9
 
11 10
     /**
12 11
      * The list of participants.
13 12
      */
14 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 16
      * True if the list should be rendered.
23 17
      */
@@ -37,13 +31,13 @@ type Props = {
37 31
 /**
38 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 36
      * Instantiates a new component.
43 37
      *
44 38
      * @inheritdoc
45 39
      */
46
-    constructor(props: Props) {
40
+    constructor(props: P) {
47 41
         super(props);
48 42
 
49 43
         this._onRespondToParticipant = this._onRespondToParticipant.bind(this);
@@ -72,11 +66,10 @@ export default class AbstractKnockingParticipantList extends PureComponent<Props
72 66
  * @returns {Props}
73 67
  */
74 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 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 파일 보기

@@ -6,7 +6,7 @@ import { getConferenceName } from '../../base/conference';
6 6
 import { getLocalParticipant } from '../../base/participants';
7 7
 import { getFieldValue } from '../../base/react';
8 8
 import { updateSettings } from '../../base/settings';
9
-import { cancelKnocking, startKnocking } from '../actions';
9
+import { cancelKnocking, joinWithPassword, setPasswordJoinFailed, startKnocking } from '../actions';
10 10
 
11 11
 export const SCREEN_STATES = {
12 12
     EDIT: 1,
@@ -41,6 +41,11 @@ export type Props = {
41 41
      */
42 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 50
      * The Redux dispatch function.
46 51
      */
@@ -69,6 +74,11 @@ type State = {
69 74
      */
70 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 83
      * The state of the screen. One of {@code SCREEN_STATES[*]}
74 84
      */
@@ -78,19 +88,20 @@ type State = {
78 88
 /**
79 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 93
      * Instantiates a new component.
84 94
      *
85 95
      * @inheritdoc
86 96
      */
87
-    constructor(props: Props) {
97
+    constructor(props: P) {
88 98
         super(props);
89 99
 
90 100
         this.state = {
91 101
             displayName: props._participantName || '',
92 102
             email: props._participantEmail || '',
93 103
             password: '',
104
+            passwordJoinFailed: false,
94 105
             screenState: props._participantName ? SCREEN_STATES.VIEW : SCREEN_STATES.EDIT
95 106
         };
96 107
 
@@ -100,21 +111,37 @@ export default class AbstractLobbyScreen extends PureComponent<Props, State> {
100 111
         this._onChangeEmail = this._onChangeEmail.bind(this);
101 112
         this._onChangePassword = this._onChangePassword.bind(this);
102 113
         this._onEnableEdit = this._onEnableEdit.bind(this);
114
+        this._onJoinWithPassword = this._onJoinWithPassword.bind(this);
103 115
         this._onSwitchToKnockMode = this._onSwitchToKnockMode.bind(this);
104 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 134
      * Returns the screen title.
109 135
      *
110 136
      * @returns {string}
111 137
      */
112 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 147
     _onAskToJoin: () => void;
@@ -125,7 +152,11 @@ export default class AbstractLobbyScreen extends PureComponent<Props, State> {
125 152
      * @returns {void}
126 153
      */
127 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 161
         return false;
131 162
     }
@@ -211,6 +242,20 @@ export default class AbstractLobbyScreen extends PureComponent<Props, State> {
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 259
     _onSwitchToKnockMode: () => void;
215 260
 
216 261
     /**
@@ -220,8 +265,10 @@ export default class AbstractLobbyScreen extends PureComponent<Props, State> {
220 265
      */
221 266
     _onSwitchToKnockMode() {
222 267
         this.setState({
268
+            password: '',
223 269
             screenState: this.state.displayName ? SCREEN_STATES.VIEW : SCREEN_STATES.EDIT
224 270
         });
271
+        this.props.dispatch(setPasswordJoinFailed(false));
225 272
     }
226 273
 
227 274
     _onSwitchToPasswordMode: () => void;
@@ -244,11 +291,10 @@ export default class AbstractLobbyScreen extends PureComponent<Props, State> {
244 291
      */
245 292
     _renderContent() {
246 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 300
         return (
@@ -267,10 +313,9 @@ export default class AbstractLobbyScreen extends PureComponent<Props, State> {
267 313
     /**
268 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 316
      * @returns {React$Element}
272 317
      */
273
-    _renderJoining: boolean => React$Element<*>;
318
+    _renderJoining: () => React$Element<*>;
274 319
 
275 320
     /**
276 321
      * Renders the participant form to let the knocking participant enter its details.
@@ -301,7 +346,7 @@ export default class AbstractLobbyScreen extends PureComponent<Props, State> {
301 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 351
      * @returns {React$Element}
307 352
      */
@@ -317,12 +362,14 @@ export default class AbstractLobbyScreen extends PureComponent<Props, State> {
317 362
 export function _mapStateToProps(state: Object): $Shape<Props> {
318 363
     const localParticipant = getLocalParticipant(state);
319 364
     const participantId = localParticipant?.id;
365
+    const { knocking, passwordJoinFailed } = state['features/lobby'];
320 366
 
321 367
     return {
322
-        _knocking: state['features/lobby'].knocking,
368
+        _knocking: knocking,
323 369
         _meetingName: getConferenceName(state),
324
-        _participantEmail: localParticipant.email,
370
+        _participantEmail: localParticipant?.email,
325 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 파일 보기

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

+ 0
- 2
react/features/lobby/components/index.web.js 파일 보기

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

+ 33
- 3
react/features/lobby/components/native/DisableLobbyModeDialog.js 파일 보기

@@ -1,16 +1,35 @@
1 1
 // @flow
2 2
 
3
-import React from 'react';
3
+import React, { PureComponent } from 'react';
4 4
 
5 5
 import { ConfirmDialog } from '../../../base/dialog';
6 6
 import { translate } from '../../../base/i18n';
7 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 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 34
      * Implements {@code PureComponent#render}.
16 35
      *
@@ -25,6 +44,17 @@ class DisableLobbyModeDialog extends AbstractDisableLobbyModeDialog {
25 44
     }
26 45
 
27 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 60
 export default translate(connect()(DisableLobbyModeDialog));

+ 35
- 24
react/features/lobby/components/native/EnableLobbyModeDialog.js 파일 보기

@@ -1,37 +1,50 @@
1 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 6
 import { ColorSchemeRegistry } from '../../../base/color-scheme';
7 7
 import { CustomSubmitDialog } from '../../../base/dialog';
8 8
 import { translate } from '../../../base/i18n';
9 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 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 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 43
      * Implements {@code PureComponent#render}.
29 44
      *
30 45
      * @inheritdoc
31 46
      */
32 47
     render() {
33
-        const { _dialogStyles, t } = this.props;
34
-
35 48
         return (
36 49
             <CustomSubmitDialog
37 50
                 okKey = 'lobby.enableDialogSubmit'
@@ -39,27 +52,25 @@ class EnableLobbyModeDialog extends AbstractEnableLobbyModeDialog<Props> {
39 52
                 titleKey = 'lobby.dialogTitle'>
40 53
                 <View style = { styles.formWrapper }>
41 54
                     <Text>
42
-                        { t('lobby.enableDialogText') }
55
+                        { this.props.t('lobby.enableDialogText') }
43 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 57
                 </View>
56 58
             </CustomSubmitDialog>
57 59
         );
58 60
     }
59 61
 
60
-    _onChangePassword: Object => void;
61
-
62 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 파일 보기

@@ -6,7 +6,10 @@ import { ScrollView, Text, View, TouchableOpacity } from 'react-native';
6 6
 import { Avatar } from '../../../base/avatar';
7 7
 import { translate } from '../../../base/i18n';
8 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 14
 import styles from './styles';
12 15
 
@@ -20,15 +23,16 @@ class KnockingParticipantList extends AbstractKnockingParticipantList {
20 23
      * @inheritdoc
21 24
      */
22 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 32
         return (
29 33
             <ScrollView
30 34
                 style = { styles.knockingParticipantList }>
31
-                { participants.map(p => (
35
+                { _participants.map(p => (
32 36
                     <View
33 37
                         key = { p.id }
34 38
                         style = { styles.knockingParticipantListEntry }>
@@ -75,4 +79,21 @@ class KnockingParticipantList extends AbstractKnockingParticipantList {
75 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 파일 보기

@@ -1,11 +1,12 @@
1 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 11
 type Props = AbstractProps & {
11 12
 
@@ -63,7 +64,7 @@ class LobbyModeButton extends AbstractButton<Props, any> {
63 64
  * @returns {Props}
64 65
  */
65 66
 export function _mapStateToProps(state: Object): $Shape<Props> {
66
-    const { conference } = state['features/base/conference'];
67
+    const conference = getCurrentConference(state);
67 68
     const { lobbyEnabled } = state['features/lobby'];
68 69
     const lobbySupported = conference && conference.isLobbySupported();
69 70
 

+ 13
- 6
react/features/lobby/components/native/LobbyScreen.js 파일 보기

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

+ 1
- 0
react/features/lobby/components/native/index.js 파일 보기

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

+ 7
- 4
react/features/lobby/components/native/styles.js 파일 보기

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

+ 0
- 36
react/features/lobby/components/web/DisableLobbyModeDialog.js 파일 보기

@@ -1,36 +0,0 @@
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 파일 보기

@@ -1,51 +0,0 @@
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 파일 보기

@@ -5,12 +5,24 @@ import React from 'react';
5 5
 import { Avatar } from '../../../base/avatar';
6 6
 import { translate } from '../../../base/i18n';
7 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 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 27
      * Implements {@code PureComponent#render}.
16 28
      *
@@ -69,4 +81,17 @@ class KnockingParticipantList extends AbstractKnockingParticipantList {
69 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 파일 보기

@@ -2,13 +2,13 @@
2 2
 
3 3
 import React from 'react';
4 4
 
5
-import { Avatar } from '../../../base/avatar';
6
-import { Dialog } from '../../../base/dialog';
7 5
 import { translate } from '../../../base/i18n';
8
-import { Icon, IconEdit } from '../../../base/icons';
6
+import { ActionButton, InputField, PreMeetingScreen } from '../../../base/premeeting';
9 7
 import { LoadingIndicator } from '../../../base/react';
10 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 14
  * Implements a waiting screen that represents the participant being in the lobby.
@@ -20,27 +20,10 @@ class LobbyScreen extends AbstractLobbyScreen {
20 20
      * @inheritdoc
21 21
      */
22 22
     render() {
23
-        const { _meetingName, t } = this.props;
24
-
25 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,6 +41,8 @@ class LobbyScreen extends AbstractLobbyScreen {
58 41
 
59 42
     _onEnableEdit: () => void;
60 43
 
44
+    _onJoinWithPassword: () => void;
45
+
61 46
     _onSubmit: () => boolean;
62 47
 
63 48
     _onSwitchToKnockMode: () => void;
@@ -71,42 +56,16 @@ class LobbyScreen extends AbstractLobbyScreen {
71 56
      *
72 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 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 67
                 </span>
106
-                <input
107
-                    onChange = { this._onChangeEmail }
108
-                    type = 'email'
109
-                    value = { email } />
68
+                { this._renderStandardButtons() }
110 69
             </div>
111 70
         );
112 71
     }
@@ -118,26 +77,21 @@ class LobbyScreen extends AbstractLobbyScreen {
118 77
      */
119 78
     _renderParticipantInfo() {
120 79
         const { displayName, email } = this.state;
121
-        const { _participantId } = this.props;
80
+        const { t } = this.props;
122 81
 
123 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 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 95
             </div>
142 96
         );
143 97
     }
@@ -148,13 +102,14 @@ class LobbyScreen extends AbstractLobbyScreen {
148 102
      * @inheritdoc
149 103
      */
150 104
     _renderPasswordForm() {
105
+        const { _passwordJoinFailed, t } = this.props;
106
+
151 107
         return (
152 108
             <div className = 'form'>
153
-                <span>
154
-                    { this.props.t('lobby.passwordField') }
155
-                </span>
156
-                <input
109
+                <InputField
110
+                    className = { _passwordJoinFailed ? 'error' : '' }
157 111
                     onChange = { this._onChangePassword }
112
+                    placeHolder = { _passwordJoinFailed ? t('lobby.invalidPassword') : t('lobby.passwordField') }
158 113
                     type = 'password'
159 114
                     value = { this.state.password } />
160 115
             </div>
@@ -171,19 +126,17 @@ class LobbyScreen extends AbstractLobbyScreen {
171 126
 
172 127
         return (
173 128
             <>
174
-                <button
175
-                    className = 'primary'
129
+                <ActionButton
176 130
                     disabled = { !this.state.password }
177
-                    onClick = { this._onAskToJoin }
178
-                    type = 'submit'>
131
+                    onClick = { this._onJoinWithPassword }
132
+                    type = 'primary'>
179 133
                     { t('lobby.passwordJoinButton') }
180
-                </button>
181
-                <button
182
-                    className = 'borderLess'
134
+                </ActionButton>
135
+                <ActionButton
183 136
                     onClick = { this._onSwitchToKnockMode }
184
-                    type = 'button'>
137
+                    type = 'secondary'>
185 138
                     { t('lobby.backToKnockModeButton') }
186
-                </button>
139
+                </ActionButton>
187 140
             </>
188 141
         );
189 142
     }
@@ -194,23 +147,21 @@ class LobbyScreen extends AbstractLobbyScreen {
194 147
      * @inheritdoc
195 148
      */
196 149
     _renderStandardButtons() {
197
-        const { t } = this.props;
150
+        const { _knocking, t } = this.props;
198 151
 
199 152
         return (
200 153
             <>
201
-                <button
202
-                    className = 'primary'
154
+                { _knocking || <ActionButton
203 155
                     disabled = { !this.state.displayName }
204 156
                     onClick = { this._onAskToJoin }
205
-                    type = 'submit'>
157
+                    type = 'primary'>
206 158
                     { t('lobby.knockButton') }
207
-                </button>
208
-                <button
209
-                    className = 'borderLess'
159
+                </ActionButton> }
160
+                <ActionButton
210 161
                     onClick = { this._onSwitchToPasswordMode }
211
-                    type = 'button'>
162
+                    type = 'secondary'>
212 163
                     { t('lobby.enterPasswordButton') }
213
-                </button>
164
+                </ActionButton>
214 165
             </>
215 166
         );
216 167
     }

+ 136
- 0
react/features/lobby/components/web/LobbySection.js 파일 보기

@@ -0,0 +1,136 @@
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 파일 보기

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

+ 2
- 18
react/features/lobby/functions.js 파일 보기

@@ -1,22 +1,6 @@
1 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 6
  * Approves (lets in) or rejects a knocking participant.
@@ -27,7 +11,7 @@ export function getKnockingParticipantDisplayName(name: string) {
27 11
  * @returns {Function}
28 12
  */
29 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 16
     if (conference) {
33 17
         if (approved) {

+ 31
- 15
react/features/lobby/middleware.js 파일 보기

@@ -1,20 +1,22 @@
1 1
 // @flow
2 2
 
3 3
 import { CONFERENCE_FAILED, CONFERENCE_JOINED } from '../base/conference';
4
-import { hideDialog } from '../base/dialog';
5 4
 import { JitsiConferenceErrors, JitsiConferenceEvents } from '../base/lib-jitsi-meet';
6 5
 import { getFirstLoadableAvatarUrl } from '../base/participants';
7 6
 import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
8 7
 import { NOTIFICATION_TYPE, showNotification } from '../notifications';
8
+import { isPrejoinPageEnabled } from '../prejoin/functions';
9 9
 
10 10
 import { KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED } from './actionTypes';
11 11
 import {
12
+    hideLobbyScreen,
12 13
     knockingParticipantLeft,
13 14
     openLobbyScreen,
14 15
     participantIsKnockingOrUpdated,
15
-    setLobbyModeEnabled
16
+    setLobbyModeEnabled,
17
+    startKnocking,
18
+    setPasswordJoinFailed
16 19
 } from './actions';
17
-import { LobbyScreen } from './components';
18 20
 
19 21
 MiddlewareRegistry.register(store => next => action => {
20 22
     switch (action.type) {
@@ -76,25 +78,38 @@ StateListenerRegistry.register(
76 78
  * @param {Object} action - The Redux action.
77 79
  * @returns {Object}
78 80
  */
79
-function _conferenceFailed({ dispatch }, next, action) {
81
+function _conferenceFailed({ dispatch, getState }, next, action) {
80 82
     const { error } = action;
83
+    const state = getState();
84
+    const nonFirstFailure = Boolean(state['features/base/conference'].membersOnly);
81 85
 
82 86
     if (error.name === JitsiConferenceErrors.MEMBERS_ONLY_ERROR) {
83 87
         if (typeof error.recoverable === 'undefined') {
84 88
             error.recoverable = true;
85 89
         }
86 90
 
91
+        const result = next(action);
92
+
87 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 115
     return next(action);
@@ -109,7 +124,7 @@ function _conferenceFailed({ dispatch }, next, action) {
109 124
  * @returns {Object}
110 125
  */
111 126
 function _conferenceJoined({ dispatch }, next, action) {
112
-    dispatch(hideDialog(LobbyScreen));
127
+    dispatch(hideLobbyScreen());
113 128
 
114 129
     return next(action);
115 130
 }
@@ -123,8 +138,9 @@ function _conferenceJoined({ dispatch }, next, action) {
123 138
  */
124 139
 function _findLoadableAvatarForKnockingParticipant({ dispatch, getState }, { id }) {
125 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 144
         getFirstLoadableAvatarUrl(updatedParticipant).then(loadableAvatarUrl => {
129 145
             if (loadableAvatarUrl) {
130 146
                 dispatch(participantIsKnockingOrUpdated({

+ 19
- 6
react/features/lobby/reducer.js 파일 보기

@@ -1,19 +1,21 @@
1 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 4
 import { ReducerRegistry } from '../base/redux';
5 5
 
6 6
 import {
7 7
     KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED,
8 8
     KNOCKING_PARTICIPANT_LEFT,
9 9
     SET_KNOCKING_STATE,
10
-    SET_LOBBY_MODE_ENABLED
10
+    SET_LOBBY_MODE_ENABLED,
11
+    SET_PASSWORD_JOIN_FAILED
11 12
 } from './actionTypes';
12 13
 
13 14
 const DEFAULT_STATE = {
14 15
     knocking: false,
15 16
     knockingParticipants: [],
16
-    lobbyEnabled: false
17
+    lobbyEnabled: false,
18
+    passwordJoinFailed: false
17 19
 };
18 20
 
19 21
 /**
@@ -26,12 +28,12 @@ const DEFAULT_STATE = {
26 28
  */
27 29
 ReducerRegistry.register('features/lobby', (state = DEFAULT_STATE, action) => {
28 30
     switch (action.type) {
29
-    case CONFERENCE_FAILED:
30 31
     case CONFERENCE_JOINED:
31 32
     case CONFERENCE_LEFT:
32 33
         return {
33 34
             ...state,
34
-            knocking: false
35
+            knocking: false,
36
+            passwordJoinFailed: false
35 37
         };
36 38
     case KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED:
37 39
         return _knockingParticipantArrivedOrUpdated(action.participant, state);
@@ -43,13 +45,24 @@ ReducerRegistry.register('features/lobby', (state = DEFAULT_STATE, action) => {
43 45
     case SET_KNOCKING_STATE:
44 46
         return {
45 47
             ...state,
46
-            knocking: action.knocking
48
+            knocking: action.knocking,
49
+            passwordJoinFailed: false
47 50
         };
48 51
     case SET_LOBBY_MODE_ENABLED:
49 52
         return {
50 53
             ...state,
51 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 68
     return state;

+ 2
- 2
react/features/overlay/middleware.js 파일 보기

@@ -10,7 +10,7 @@ declare var APP: Object;
10 10
 /**
11 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 14
     JitsiConferenceErrors.CONFERENCE_ACCESS_DENIED,
15 15
     JitsiConferenceErrors.CONFERENCE_DESTROYED,
16 16
     JitsiConferenceErrors.CONNECTION_ERROR
@@ -31,7 +31,7 @@ StateListenerRegistry.register(
31 31
     },
32 32
     /* listener */ (error, { dispatch }) => {
33 33
         error
34
-            && NON_FATAR_ERRORS.indexOf(error.name) === -1
34
+            && NON_OVERLAY_ERRORS.indexOf(error.name) === -1
35 35
             && typeof error.recoverable === 'undefined'
36 36
             && dispatch(setFatalError(error));
37 37
     }

+ 39
- 38
react/features/prejoin/components/Prejoin.js 파일 보기

@@ -6,10 +6,9 @@ import React, { Component } from 'react';
6 6
 import { getRoomName } from '../../base/conference';
7 7
 import { translate } from '../../base/i18n';
8 8
 import { Icon, IconPhone, IconVolumeOff } from '../../base/icons';
9
+import { ActionButton, InputField, PreMeetingScreen } from '../../base/premeeting';
9 10
 import { connect } from '../../base/redux';
10 11
 import { getDisplayName, updateSettings } from '../../base/settings';
11
-import { isGuest } from '../../invite';
12
-import { VideoSettingsButton, AudioSettingsButton } from '../../toolbox';
13 12
 import {
14 13
     joinConference as joinConferenceAction,
15 14
     joinConferenceWithoutAudio as joinConferenceWithoutAudioAction,
@@ -17,18 +16,15 @@ import {
17 16
     setJoinByPhoneDialogVisiblity as setJoinByPhoneDialogVisiblityAction
18 17
 } from '../actions';
19 18
 import {
19
+    getActiveVideoTrack,
20 20
     isJoinByPhoneButtonVisible,
21 21
     isDeviceStatusVisible,
22
-    isJoinByPhoneDialogVisible
22
+    isJoinByPhoneDialogVisible,
23
+    isPrejoinVideoMuted
23 24
 } from '../functions';
24 25
 
25
-import ActionButton from './buttons/ActionButton';
26 26
 import JoinByPhoneDialog from './dialogs/JoinByPhoneDialog';
27
-import CopyMeetingUrl from './preview/CopyMeetingUrl';
28 27
 import DeviceStatus from './preview/DeviceStatus';
29
-import ParticipantName from './preview/ParticipantName';
30
-import Preview from './preview/Preview';
31
-
32 28
 
33 29
 type Props = {
34 30
 
@@ -42,11 +38,6 @@ type Props = {
42 38
      */
43 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 42
      * Joins the current meeting.
52 43
      */
@@ -82,6 +73,11 @@ type Props = {
82 73
      */
83 74
     setJoinByPhoneDialogVisiblity: Function,
84 75
 
76
+    /**
77
+     * Flag signaling the visibility of camera preview.
78
+     */
79
+    showCameraPreview: boolean,
80
+
85 81
     /**
86 82
      * If 'JoinByPhoneDialog' is visible or not.
87 83
      */
@@ -91,6 +87,11 @@ type Props = {
91 87
      * Used for translation.
92 88
      */
93 89
     t: Function,
90
+
91
+    /**
92
+     * The JitsiLocalTrack to display.
93
+     */
94
+    videoTrack: ?Object,
94 95
 };
95 96
 
96 97
 type State = {
@@ -211,34 +212,31 @@ class Prejoin extends Component<Props, State> {
211 212
      */
212 213
     render() {
213 214
         const {
214
-            deviceStatusVisible,
215 215
             hasJoinByPhoneButton,
216
-            isAnonymousUser,
217 216
             joinConference,
218 217
             joinConferenceWithoutAudio,
219 218
             name,
219
+            showCameraPreview,
220 220
             showDialog,
221
-            t
221
+            t,
222
+            videoTrack
222 223
         } = this.props;
223 224
 
224 225
         const { _closeDialog, _onCheckboxChange, _onDropdownClose, _onOptionsClick, _setName, _showDialog } = this;
225 226
         const { showJoinByPhoneButtons } = this.state;
226 227
 
227 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 234
                 <div className = 'prejoin-input-area-container'>
231 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 240
                             value = { name } />
243 241
 
244 242
                         <div className = 'prejoin-preview-dropdown-container'>
@@ -275,11 +273,6 @@ class Prejoin extends Component<Props, State> {
275 273
                                 </ActionButton>
276 274
                             </InlineDialog>
277 275
                         </div>
278
-
279
-                        <div className = 'prejoin-preview-btn-container'>
280
-                            <AudioSettingsButton visible = { true } />
281
-                            <VideoSettingsButton visible = { true } />
282
-                        </div>
283 276
                     </div>
284 277
 
285 278
                     <div className = 'prejoin-checkbox-container'>
@@ -290,16 +283,23 @@ class Prejoin extends Component<Props, State> {
290 283
                         <span>{t('prejoin.doNotShow')}</span>
291 284
                     </div>
292 285
                 </div>
293
-
294
-                { deviceStatusVisible && <DeviceStatus /> }
295 286
                 { showDialog && (
296 287
                     <JoinByPhoneDialog
297 288
                         joinConferenceWithoutAudio = { joinConferenceWithoutAudio }
298 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,12 +310,13 @@ class Prejoin extends Component<Props, State> {
310 310
  */
311 311
 function mapStateToProps(state): Object {
312 312
     return {
313
-        isAnonymousUser: isGuest(state),
314 313
         deviceStatusVisible: isDeviceStatusVisible(state),
315 314
         name: getDisplayName(state),
316 315
         roomName: getRoomName(state),
317 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 파일 보기

@@ -1,88 +0,0 @@
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 파일 보기

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

+ 1
- 1
react/features/prejoin/components/dialogs/DialOutDialog.js 파일 보기

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

+ 3
- 1
react/features/prejoin/components/preview/DeviceStatus.js 파일 보기

@@ -62,7 +62,9 @@ function DeviceStatus({ deviceStatusType, deviceStatusText, rawError, t }: Props
62 62
                 size = { 16 }
63 63
                 src = { src } />
64 64
             <span className = 'prejoin-preview-error-desc'>{t(deviceStatusText)}</span>
65
-            <span>{rawError}</span>
65
+            { rawError && <span>
66
+                { rawError }
67
+            </span> }
66 68
         </div>
67 69
     );
68 70
 }

+ 0
- 110
react/features/prejoin/components/preview/ParticipantName.js 파일 보기

@@ -1,110 +0,0 @@
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 파일 보기

@@ -1,76 +0,0 @@
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 파일 보기

@@ -259,7 +259,8 @@ export function isJoinByPhoneDialogVisible(state: Object): boolean {
259 259
  * @returns {boolean}
260 260
  */
261 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 264
         && !state['features/base/settings'].userSelectedSkipPrejoin;
264 265
 }
265 266
 

+ 16
- 13
react/features/security/components/security-dialog/PasswordSection.js 파일 보기

@@ -169,19 +169,22 @@ function PasswordSection({
169 169
     }
170 170
 
171 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 188
             </div>
186 189
         </div>
187 190
     );

+ 4
- 4
react/features/security/components/security-dialog/SecurityDialog.js 파일 보기

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

+ 2
- 1
react/features/security/components/security-dialog/SecurityDialogButton.js 파일 보기

@@ -63,9 +63,10 @@ class SecurityDialogButton extends AbstractButton<Props, *> {
63 63
  */
64 64
 function mapStateToProps(state: Object) {
65 65
     const { locked } = state['features/base/conference'];
66
+    const { lobbyEnabled } = state['features/lobby'];
66 67
 
67 68
     return {
68
-        _locked: locked
69
+        _locked: locked || lobbyEnabled
69 70
     };
70 71
 }
71 72
 

+ 1
- 1
react/features/toolbox/components/native/OverflowMenu.js 파일 보기

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

+ 0
- 6
react/features/toolbox/components/web/Toolbox.js 파일 보기

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

Loading…
취소
저장