Browse Source

Improve premeeting screens ux (#9726)

* feat(prejoin) move invite to toolbar section

* feat(premeeting) redesign prejoin and lobby screens

* code review changes

* fix prejoin flicker and avatar id

* fix password error message and native lobby dialog close position
master
Avram Tudor 3 years ago
parent
commit
1ad9046a38
No account linked to committer's email address
56 changed files with 858 additions and 800 deletions
  1. 0
    10
      css/_mixins.scss
  2. 0
    153
      css/_prejoin.scss
  3. 1
    1
      css/_toolbars.scss
  4. 6
    0
      css/_variables.scss
  5. 1
    5
      css/main.scss
  6. 10
    25
      css/premeeting/_connection-status.scss
  7. 35
    0
      css/premeeting/_device-status.scss
  8. 16
    13
      css/premeeting/_lobby.scss
  9. 7
    0
      css/premeeting/_main.scss
  10. 0
    0
      css/premeeting/_prejoin-dialog.scss
  11. 39
    0
      css/premeeting/_prejoin-third-party.scss
  12. 73
    0
      css/premeeting/_prejoin.scss
  13. 121
    123
      css/premeeting/_premeeting-screens.scss
  14. 0
    1
      lang/main-de.json
  15. 1
    1
      lang/main-eo.json
  16. 0
    1
      lang/main-eu.json
  17. 0
    1
      lang/main-fr.json
  18. 1
    1
      lang/main-id.json
  19. 0
    1
      lang/main-pt.json
  20. 0
    1
      lang/main-ptBR.json
  21. 0
    1
      lang/main-sq.json
  22. 0
    1
      lang/main-zhTW.json
  23. 4
    4
      lang/main.json
  24. 5
    3
      react/features/app/components/App.web.js
  25. 2
    3
      react/features/base/conference/middleware.web.js
  26. 3
    0
      react/features/base/icons/svg/check-solid.svg
  27. 1
    0
      react/features/base/icons/svg/index.js
  28. 26
    28
      react/features/base/premeeting/components/web/ConnectionStatus.js
  29. 0
    67
      react/features/base/premeeting/components/web/CopyMeetingUrl.js
  30. 39
    67
      react/features/base/premeeting/components/web/PreMeetingScreen.js
  31. 37
    12
      react/features/base/premeeting/components/web/Preview.js
  32. 0
    11
      react/features/base/premeeting/functions.js
  33. 13
    1
      react/features/conference/components/native/Conference.js
  34. 12
    11
      react/features/conference/components/web/Conference.js
  35. 44
    0
      react/features/invite/components/add-people-dialog/web/InviteButton.js
  36. 1
    0
      react/features/invite/components/add-people-dialog/web/index.js
  37. 5
    0
      react/features/lobby/actionTypes.js
  38. 26
    0
      react/features/lobby/actions.any.js
  39. 0
    20
      react/features/lobby/actions.web.js
  40. 8
    0
      react/features/lobby/components/AbstractLobbyScreen.js
  41. 10
    9
      react/features/lobby/components/native/LobbyScreen.js
  42. 18
    11
      react/features/lobby/components/web/LobbyScreen.js
  43. 10
    0
      react/features/lobby/functions.js
  44. 7
    0
      react/features/lobby/reducer.js
  45. 3
    2
      react/features/prejoin/actions.js
  46. 74
    155
      react/features/prejoin/components/Prejoin.js
  47. 18
    25
      react/features/prejoin/components/PrejoinApp.js
  48. 87
    0
      react/features/prejoin/components/PrejoinThirdParty.js
  49. 6
    7
      react/features/prejoin/components/preview/DeviceStatus.js
  50. 19
    0
      react/features/prejoin/constants.js
  51. 13
    1
      react/features/prejoin/functions.js
  52. 20
    2
      react/features/prejoin/middleware.js
  53. 2
    1
      react/features/settings/actions.js
  54. 29
    14
      react/features/toolbox/components/web/Toolbox.js
  55. 3
    1
      react/features/virtual-background/components/VideoBackgroundButton.js
  56. 2
    6
      static/prejoin.html

+ 0
- 10
css/_mixins.scss View File

@@ -206,13 +206,3 @@
206 206
     bottom: 0;
207 207
     width: 35%;
208 208
 }
209
-
210
-/**
211
- * Resizes elements width to fill the whole screen width with some margin
212
- */
213
-@mixin adjust-for-max-width($width, $margin) {
214
-    @media (max-width: $width) {
215
-        margin: 0 $margin;
216
-        width: $width - 2 * $margin;
217
-    }
218
-}

+ 0
- 153
css/_prejoin.scss View File

@@ -1,153 +0,0 @@
1
-.prejoin {
2
-
3
-    &-input-area {
4
-        margin: 0 auto;
5
-        text-align: center;
6
-
7
-       &-label {
8
-           display: block;
9
-           margin-bottom: 5px;
10
-           color: #ffffff;
11
-           font-weight: 300;
12
-           font-size: 15px;
13
-           line-height: 24px;
14
-       }
15
-    }
16
-
17
-    &-title {
18
-        color: #fff;
19
-        font-size: 24px;
20
-        line-height: 32px;
21
-        margin-bottom: 16px;
22
-    }
23
-
24
-    &-text-btns {
25
-        display: flex;
26
-        justify-content: space-between;
27
-    }
28
-
29
-    &-input-label {
30
-        color: #A4B8D1;
31
-        font-size: 13px;
32
-        line-height: 20px;
33
-        margin-top: 32px 0 8px 0;
34
-        text-align: center;
35
-        width: 100%;
36
-    }
37
-
38
-    &-checkbox {
39
-        border: 0;
40
-        height: 16px;
41
-        margin-right: 8px;
42
-        padding: 0;
43
-        width: 16px;
44
-    }
45
-
46
-    &-checkbox-container {
47
-        margin-bottom: 14px;
48
-        width: 100%;
49
-    }
50
-
51
-    &-error {
52
-        color: white;
53
-        background-color: rgba(225, 45, 45, 0.6);
54
-        border-radius: 3px;
55
-        width: 100%;
56
-        padding: 2px;
57
-        box-sizing: border-box;
58
-        margin-top: 4px;
59
-        font-size: 13px;
60
-        text-align: center;
61
-    }
62
-}
63
-
64
-@mixin name-placeholder {
65
-    color: #fff;
66
-    font-weight: 300;
67
-    opacity: 0.6;
68
-}
69
-
70
-.prejoin-preview {
71
-    &-status {
72
-        align-items: center;
73
-        align-self: stretch;
74
-        bottom: 0;
75
-        color: #fff;
76
-        display: flex;
77
-        font-size: 13px;
78
-        min-height: 24px;
79
-        justify-content: center;
80
-        position: absolute;
81
-        text-align: center;
82
-        width: 100%;
83
-        z-index: 1;
84
-
85
-        &--warning {
86
-            background: rgba(241, 173, 51, 1);
87
-        }
88
-        &--ok {
89
-            background: rgba(49, 183, 106, 1);
90
-        }
91
-    }
92
-
93
-    &-icon {
94
-        background-position: center;
95
-        background-repeat: no-repeat;
96
-        display: inline-block;
97
-        height: 16px;
98
-        margin-right: 8px;
99
-        width: 16px;
100
-    }
101
-
102
-    &-error-desc {
103
-        margin-right: 4px;
104
-        color: #fff;
105
-        font-weight: bold;
106
-    }
107
-
108
-    .settings-button-container {
109
-        width: 49px;
110
-        margin: 0 8px;
111
-    }
112
-
113
-    &-dropdown-btns {
114
-        width: 320px;
115
-        padding: 8px 0;
116
-
117
-        @include adjust-for-max-width(320px, 8px);
118
-    }
119
-
120
-    &-dropdown-btn {
121
-        align-items: center;
122
-        color: #1C2025;
123
-        cursor: pointer;
124
-        display: flex;
125
-        height: 40px;
126
-        font-size: 15px;
127
-        line-height: 24px;
128
-        padding: 0 16px;
129
-
130
-        &:hover {
131
-            background-color: #DAEBFA;
132
-        }
133
-    }
134
-
135
-    &-dropdown-icon {
136
-        display: inline-block;
137
-        margin-right: 16px;
138
-
139
-        & > svg {
140
-            fill:  #1C2025;
141
-        }
142
-    }
143
-
144
-    &-dropdown-container {
145
-        margin-top: 16px;
146
-
147
-        & > div:nth-child(2) {
148
-            background: #fff;
149
-            padding: 0;
150
-        }
151
-    }
152
-
153
-}

+ 1
- 1
css/_toolbars.scss View File

@@ -334,7 +334,7 @@
334 334
             border-radius: 0;
335 335
             display: flex;
336 336
             justify-content: space-evenly;
337
-            padding: 6px 0;
337
+            padding: 8px 0;
338 338
             width: 100%;
339 339
         }
340 340
 

+ 6
- 0
css/_variables.scss View File

@@ -264,3 +264,9 @@ $chromeExtensionBannerRightInMeeeting: 10px;
264 264
 */
265 265
 $smallScreen: 700px;
266 266
 $verySmallScreen: 500px;
267
+
268
+/**
269
+* Prejoin / premeeting screen
270
+*/
271
+
272
+$prejoinDefaultContentWidth: 336px;

+ 1
- 5
css/main.scss View File

@@ -79,7 +79,6 @@ $flagsImagePath: "../images/";
79 79
 @import 'filmstrip/vertical_filmstrip';
80 80
 @import 'filmstrip/vertical_filmstrip_overrides';
81 81
 @import 'labels';
82
-@import 'lobby';
83 82
 @import 'unsupported-browser/main';
84 83
 @import 'modals/invite/add-people';
85 84
 @import 'deep-linking/main';
@@ -95,15 +94,12 @@ $flagsImagePath: "../images/";
95 94
 @import 'meter';
96 95
 @import 'audio-preview';
97 96
 @import 'video-preview';
98
-@import 'prejoin';
99
-@import 'prejoin-dialog';
97
+@import 'premeeting/main';
100 98
 @import 'country-picker';
101 99
 @import 'modals/invite/invite_more';
102 100
 @import 'modals/security/security';
103
-@import 'premeeting-screens';
104 101
 @import 'e2ee';
105 102
 @import 'responsive';
106
-@import 'connection-status';
107 103
 @import 'drawer';
108 104
 @import 'participants-pane';
109 105
 @import 'reactions-menu';

css/_connection-status.scss → css/premeeting/_connection-status.scss View File

@@ -1,32 +1,24 @@
1 1
 .con-status {
2
+    border-radius: 6px;
3
+    color: #fff;
4
+    font-size: 12px;
5
+    letter-spacing: 0.16px;
6
+    line-height: 16px;
2 7
     position: absolute;
3
-    top: 24px;
4 8
     width: 100%;
5
-    z-index: $toolbarZ + 3;
6
-
7
-    &-container {
8
-        border-radius: 3px;
9
-        color: #fff;
10
-        font-size: 13px;
11
-        line-height: 13px;
12
-        margin: 0 auto;
13
-        width: 320px;
14
-
15
-        @include adjust-for-max-width(320px, 8px);
16
-    }
17 9
 
18 10
     &-header {
19
-        background: rgba(28, 32, 37, .5);
11
+        background-color: rgba(0, 0, 0, 0.7);
20 12
         align-items: center;
21 13
         display: flex;
22
-        justify-content: space-between;
14
+        padding: 8px 12px;
23 15
     }
24 16
 
25 17
     &-circle {
26 18
         border-radius: 50%;
27 19
         display: inline-block;
28 20
         padding: 4px;
29
-        margin: 8px;
21
+        margin-right: 16px;
30 22
     }
31 23
 
32 24
     &--good {
@@ -42,14 +34,7 @@
42 34
     }
43 35
 
44 36
     &-arrow {
45
-        height: 36px;
46
-        width: 36px;
47
-        border-radius: 3px;
48
-        margin-left: 8px;
49
-        margin-right: 2px;
50
-        display: flex;
51
-        align-items: center;
52
-        justify-content: center;
37
+        margin-left: auto;
53 38
         transition: background-color 0.16s ease-out;
54 39
 
55 40
         &--up {
@@ -70,7 +55,7 @@
70 55
     }
71 56
 
72 57
     &-details {
73
-        background: rgba(28, 32, 37, .5);
58
+        background-color: rgba(0, 0, 0, 0.7);
74 59
         border-top: 1px solid #5E6D7A;
75 60
         padding: 16px;
76 61
         transition: opacity 0.16s ease-out;

+ 35
- 0
css/premeeting/_device-status.scss View File

@@ -0,0 +1,35 @@
1
+.device {
2
+    &-status {
3
+        align-items: center;
4
+        align-self: stretch;
5
+        color: #fff;
6
+        display: flex;
7
+        font-size: 14px;
8
+        font-weight: 400;
9
+        justify-content: center;
10
+        line-height: 20px;
11
+        margin-top: 8px;
12
+        padding: 6px;
13
+        text-align: center;
14
+    }
15
+
16
+    &-icon {
17
+        background-position: center;
18
+        background-repeat: no-repeat;
19
+        display: inline-block;
20
+        height: 16px;
21
+        margin-right: 10px;
22
+        width: 16px;
23
+
24
+        &--warning {
25
+            svg path {
26
+                fill: rgba(241, 173, 51, 1);
27
+            }
28
+        }
29
+        &--ok {
30
+            svg path {
31
+                fill: #189b55;
32
+            }
33
+        }
34
+    }
35
+}

css/_lobby.scss → css/premeeting/_lobby.scss View File

@@ -1,18 +1,21 @@
1
-#lobby-screen {
2
-    .content {
1
+.lobby-screen {
2
+    font-size: 16px;
3
+    font-weight: 400;
4
+    line-height: 26px;
3 5
 
4
-        .container {
5
-            align-items: center;
6
-            display: flex;
7
-            flex-direction: column;
6
+    &-content {
7
+        align-items: center;
8
+        display: flex;
9
+        flex-direction: column;
8 10
 
9
-            .spinner {
10
-                margin: 30px;
11
-            }
11
+        .spinner {
12
+            margin: 8px;
13
+        }
12 14
 
13
-            .joining-message {
14
-                margin: 10px;
15
-            }
15
+        .joining-message {
16
+            color: white;
17
+            margin: 24px auto;
18
+            text-align: center;
16 19
         }
17 20
     }
18 21
 }
@@ -68,7 +71,7 @@
68 71
 
69 72
     button {
70 73
         align-self: stretch;
71
-        margin: 8px 0;
74
+        margin-bottom: 8px 0;
72 75
         padding: 12px;
73 76
         transition: .2s transform ease;
74 77
 

+ 7
- 0
css/premeeting/_main.scss View File

@@ -0,0 +1,7 @@
1
+@import 'connection-status';
2
+@import 'device-status';
3
+@import 'lobby';
4
+@import 'premeeting-screens';
5
+@import 'prejoin';
6
+@import 'prejoin-dialog';
7
+@import 'prejoin-third-party';

css/_prejoin-dialog.scss → css/premeeting/_prejoin-dialog.scss View File


+ 39
- 0
css/premeeting/_prejoin-third-party.scss View File

@@ -0,0 +1,39 @@
1
+$sidePanelWidth: 300px;
2
+
3
+.prejoin-third-party {
4
+  flex-direction: column-reverse;
5
+  
6
+  .content {
7
+      height: auto;
8
+      margin: 0 auto;
9
+
10
+      .new-toolbox {
11
+          width: auto;
12
+      }
13
+  }
14
+
15
+  #preview {
16
+      background-color: transparent;
17
+      bottom: 0;
18
+      left: 0;
19
+      position: absolute;
20
+      right: 0;
21
+      top: 0;
22
+
23
+      .avatar {
24
+          display: none;
25
+      }
26
+  }
27
+
28
+  &.splash {
29
+      .content {
30
+          margin-left: calc((100% - #{$prejoinDefaultContentWidth} + #{$sidePanelWidth}) / 2)
31
+      }
32
+  }
33
+
34
+  &.guest {
35
+      .content {
36
+          margin-bottom: auto;
37
+      }
38
+  }
39
+}

+ 73
- 0
css/premeeting/_prejoin.scss View File

@@ -0,0 +1,73 @@
1
+.prejoin {
2
+    &-input-area {
3
+        width: 100%;
4
+    }
5
+
6
+    &-checkbox-container {
7
+        margin-bottom: 16px;
8
+        width: 100%;
9
+        text-align: center;
10
+    }
11
+
12
+    &-error {
13
+        color: white;
14
+        background-color: #E04757;
15
+        border-radius: 6px;
16
+        padding: 4px;
17
+        box-sizing: border-box;
18
+        margin-bottom: 16px;
19
+        margin-top: -8px;
20
+        font-size: 12px;
21
+        text-align: center;
22
+        width: 100%;
23
+    }
24
+}
25
+
26
+.prejoin-preview {
27
+    &-dropdown-btns {
28
+        padding: 8px 0;
29
+        width: calc(100% - 48px);
30
+    }
31
+  
32
+    &-dropdown-btn {
33
+        align-items: center;
34
+        color: #1C2025;
35
+        cursor: pointer;
36
+        display: flex;
37
+        height: 40px;
38
+        font-size: 15px;
39
+        line-height: 24px;
40
+        padding: 0 16px;
41
+  
42
+        &:hover {
43
+            background-color: #DAEBFA;
44
+        }
45
+    }
46
+  
47
+    &-dropdown-icon {
48
+        display: inline-block;
49
+        margin-right: 16px;
50
+  
51
+        & > svg {
52
+            fill:  #1C2025;
53
+        }
54
+    }
55
+  
56
+    &-dropdown-container {
57
+        position: relative;
58
+        width: 100%;
59
+  
60
+        /**
61
+        * Override default InlineDialog behaviour, since it does not play nicely with relative widths
62
+        */
63
+        & > div:nth-child(2) {
64
+            background: #fff;
65
+            padding: 0;
66
+            position: absolute !important;
67
+            top: 48px !important;
68
+            transform: none !important;
69
+            width: 100%;
70
+        }
71
+    }
72
+  }
73
+  

css/_premeeting-screens.scss → css/premeeting/_premeeting-screens.scss View File

@@ -1,47 +1,27 @@
1
-/**
2
- * Shared style for full screen local track based dialogs/modals.
3
- */
4 1
  .premeeting-screen {
5
-     position: absolute;
6
-     left: 0;
7
-     right: 0;
8
-     top: 0;
9
-     bottom: 0;
10
-  }
11
-
12
- .premeeting-screen {
13
-    align-items: stretch;
14
-    background: radial-gradient(50% 50% at 50% 50%, #2A3A4B 20.83%, #1E2A36 100%);
2
+    background: #292929;
3
+    bottom: 0;
15 4
     display: flex;
16
-    flex-direction: column;
17 5
     font-size: 1.3em;
6
+    left: 0;
7
+    position: absolute;
8
+    right: 0;
9
+    top: 0;
18 10
     z-index: $toolbarZ + 1;
19 11
 
20
-    &-avatar {
21
-        background-color: #A4B8D1;
22
-        margin-bottom: 24px;
23
-
24
-        text {
25
-            fill: black;
26
-            font-size: 26px;
27
-            font-weight: 400;
28
-        }
29
-    }
30
-
31 12
     .action-btn {
32
-        border-radius: 3px;
13
+        border-radius: 6px;
33 14
         box-sizing: border-box;
34 15
         color: #fff;
35 16
         cursor: pointer;
36 17
         display: inline-block;
37
-        font-size: 15px;
18
+        font-size: 14px;
38 19
         line-height: 24px;
20
+        margin-bottom: 16px;
39 21
         padding: 7px 16px;
40 22
         position: relative;
41 23
         text-align: center;
42
-        width: 320px;
43
-
44
-        @include adjust-for-max-width(320px, 8px);
24
+        width: 100%;
45 25
 
46 26
         &.primary {
47 27
             background: #0376DA;
@@ -49,8 +29,8 @@
49 29
         }
50 30
 
51 31
         &.secondary {
52
-            background: transparent;
53
-            border: 1px solid #5E6D7A;
32
+            background: #3D3D3D;
33
+            border: 1px solid transparent;
54 34
         }
55 35
 
56 36
         &.text {
@@ -96,130 +76,150 @@
96 76
 
97 77
     .content {
98 78
         align-items: center;
79
+        box-sizing: border-box;
99 80
         display: flex;
100
-        flex: 1;
101 81
         flex-direction: column;
102
-        justify-content: flex-end;
103
-        padding-bottom: 24px;
82
+        flex-shrink: 0;
83
+        height: 100%;
84
+        margin: 0 110px;
85
+        padding: 24px 0 16px;
86
+        position: relative;
87
+        width: $prejoinDefaultContentWidth;
104 88
         z-index: $toolbarZ + 2;
105 89
 
106
-        .title {
107
-            color: #fff;
108
-            font-size: 24px;
109
-            line-height: 32px;
110
-            margin-bottom: 16px;
111
-        }
112
-
113
-        .copy-meeting {
90
+        &-controls {
114 91
             align-items: center;
115
-            cursor: pointer;
116
-            color: #fff;
117 92
             display: flex;
118 93
             flex-direction: column;
119
-            font-size: 15px;
120
-            font-weight: 300;
121
-            justify-content: center;
122
-            line-height: 24px;
123
-            margin-bottom: 16px;
124
-
125
-            .url {
126
-                background: rgba(28, 32, 37, 0.5);
127
-                border-radius: 4px;
128
-                display: flex;
129
-                padding: 8px 10px;
130
-                transition: background 0.16s ease-out;
131
-
132
-                &:hover {
133
-                    background: #1C2025;
134
-                }
135
-
136
-                &.done {
137
-                    background: #31B76A;
94
+            margin: auto;
95
+            width: 100%;
96
+
97
+            .title {
98
+                color: #fff;
99
+                font-size: 28px;
100
+                font-weight: 600;
101
+                letter-spacing: -0.015;
102
+                line-height: 36px;
103
+                margin-bottom: 32px;
104
+                text-align: center;
105
+            }
106
+    
107
+            input.field {
108
+                background-color: white;
109
+                border: none;
110
+                outline: none;
111
+                border-radius: 6px;
112
+                font-size: 14px;
113
+                line-height: 20px;
114
+                margin-bottom: 16px;
115
+                color: #1C2025;
116
+                padding: 10px 16px;
117
+                text-align: center;
118
+                width: 100%;
119
+    
120
+                &.error {
121
+                    border: 1px solid #E04757;
138 122
                 }
139
-
140
-                .jitsi-icon {
141
-                    margin-left: 10px;
123
+    
124
+                &.focused {
125
+                    box-shadow: 0px 0px 1px 1.5px black, 0px 0px 1.3px 4px white;
142 126
                 }
143 127
             }
144
-            .copy-button{
145
-                width: 298px;
146
-             }
147
-
148
-            .copy-meeting-text {
149
-                width: 266px;
150
-                white-space: nowrap;
151
-                overflow: hidden;
152
-                text-overflow: ellipsis;
153
-            }
154 128
 
155
-            &:hover {
156
-                align-self: stretch;
129
+            #new-toolbox {
130
+                bottom: 0;
131
+                margin-bottom: 16px;
132
+                position: relative;
133
+                transition: none;
134
+
135
+                .toolbox-content,
136
+                .toolbox-content-wrapper,
137
+                .toolbox-content-items {
138
+                    box-sizing: border-box;
139
+                    width: 100%;
140
+                }
157 141
             }
142
+        }
143
+    }
158 144
 
159
-            textarea {
160
-                border-width: 0;
161
-                height: 0;
162
-                opacity: 0;
163
-                padding: 0;
164
-                width: 0;
165
-            }
145
+    @media (max-width: 1000px) {
146
+        flex-direction: column-reverse;
147
+        
148
+        .content {
149
+            height: auto;
150
+            margin: 0 auto;
166 151
         }
167 152
 
168
-        input.field {
169
-            background-color: white;
170
-            border: none;
171
-            outline: none;
172
-            border-radius: 3px;
173
-            font-size: 15px;
174
-            line-height: 24px;
175
-            color: #1C2025;
176
-            padding: 8px 0;
177
-            text-align: center;
178
-            width: 320px;
153
+        .con-status {
154
+            margin: 24px auto;
155
+            position: fixed;
156
+            top: 0;
157
+            width: $prejoinDefaultContentWidth;
158
+        }
159
+    }
179 160
 
180
-            @include adjust-for-max-width(320px, 8px);
161
+    @media (max-width: 400px) {
162
+        .content {
163
+            padding: 16px;
164
+            width: 100%;
181 165
 
182
-            &.error {
183
-                box-shadow: 0px 0px 4px 3px rgba(225, 45, 45, 0.4);
166
+            .title {
167
+                font-size: 20px;
168
+                line-height: 28px;
169
+                letter-spacing: -0.012;
170
+                margin-bottom: 24px;
184 171
             }
172
+        }
185 173
 
186
-            &.focused {
187
-                box-shadow: 0px 0px 1px 1.5px black, 0px 0px 1.3px 4px white;
188
-            }
174
+        .con-status {
175
+            margin: 16px;
176
+            width: calc(100% - 32px);
189 177
         }
190
-    }
191 178
 
192
-    .media-btn-container {
193
-        display: flex;
194
-        justify-content: center;
195
-        margin: 24px 0 16px 0;
196
-        width: 100%;
179
+        input.field {
180
+            font-size: 16px;
181
+            padding: 14px 16px;
182
+        }
197 183
 
198
-        &> div {
199
-            margin: 0 12px;
184
+        .action-btn {
185
+            font-size: 16px;
186
+            padding: 11px 16px;
187
+        }
188
+
189
+        .toolbox-content-items {
190
+            border-radius: 0;
191
+            display: flex;
192
+            justify-content: space-evenly;
193
+            padding: 8px 0;
200 194
         }
201 195
     }
196
+
197
+    input::placeholder {
198
+        color: #040404;
199
+    }
202 200
 }
203 201
 
204 202
 #preview {
203
+    background: #040404;
204
+    display: flex;
205
+    align-items: center;
206
+    justify-content: center;
205 207
     height: 100%;
206
-    position: absolute;
207 208
     width: 100%;
208 209
 
209
-    &.no-video {
210
-        background: radial-gradient(50% 50% at 50% 50%, #5B6F80 0%, #365067 100%), #FFFFFF;
211
-        text-align: center;
212
-    }
213
-
214 210
     .avatar {
215
-        background: #A4B8D1;
216
-        margin: 0 auto;
211
+        background: #0045B3;
212
+
213
+        text {
214
+            fill: white;
215
+            font-size: 26px;
216
+            font-weight: 400;
217
+        }
217 218
     }
218 219
 
219 220
     video {
220 221
         height: 100%;
221 222
         object-fit: cover;
222
-        position: absolute;
223 223
         width: 100%;
224 224
     }
225 225
 }
@@ -241,16 +241,14 @@
241 241
 }
242 242
 
243 243
 .toggle-button {
244
-    border-radius: 3px;
244
+    border-radius: 6px;
245 245
     cursor: pointer;
246 246
     color: #fff;
247 247
     font-size: 13px;
248 248
     height: 40px;
249 249
     margin: 0 auto;
250 250
     transition: background 0.16s ease-out;
251
-    width: 320px;
252 251
 
253
-    @include adjust-for-max-width(320px, 8px);
254 252
     @include flex-centered();
255 253
 
256 254
     svg {

+ 0
- 1
lang/main-de.json View File

@@ -209,7 +209,6 @@
209 209
         "e2eeLabel": "Ende-zu-Ende-Verschlüsselung aktivieren",
210 210
         "e2eeWarning": "WARNUNG: Nicht alle Personen dieser Konferenz scheinen Ende-zu-Ende-Verschlüsselung zu unterstützen. Wenn Sie diese aktivieren, können die entsprechenden Personen nichts mehr sehen oder hören.",
211 211
         "enterDisplayName": "Bitte geben Sie hier Ihren Namen ein",
212
-        "enterDisplayNameToJoin" : "Benutzername für Konferenz eingeben" ,
213 212
         "embedMeeting": "Besprechung einbetten",
214 213
         "error": "Fehler",
215 214
         "gracefulShutdown": "Der Dienst steht momentan wegen Wartungsarbeiten nicht zur Verfügung. Bitte versuchen Sie es später noch einmal.",

+ 1
- 1
lang/main-eo.json View File

@@ -179,7 +179,7 @@
179 179
         "e2eeLabel": "Ŝlosilo",
180 180
         "e2eeTitle": "Tutvoja ĉifrado",
181 181
         "e2eeWarning": "<br /><p><strong>ATENTIGO:</strong> Ne ĉiuj partoprenantoj en ĉi tiu kunveno ŝajnas havi subtenon de tutvoja ĉifrado. Se vi ŝaltos ĝin, ili ne povos vidi aŭ aŭdi vin.</p>",
182
-        "enterDisplayName": "Please enter your name here",
182
+        "enterDisplayName": "Enter your name here",
183 183
         "error": "Eraro",
184 184
         "externalInstallationMsg": "Vi devas instali nian ekranvidadan kromprogramon.",
185 185
         "externalInstallationTitle": "Kromprogramo bezonata",

+ 0
- 1
lang/main-eu.json View File

@@ -203,7 +203,6 @@
203 203
         "e2eeLabel": "Aktibatu puntutik punturako zifratzea",
204 204
         "e2eeWarning": "OHARRA: bileraren partaide guztiek ezin dute puntutik punturako zifratzea erabili. Aukera hau aktibatzen baduzu, batzuk ezingo zaituzte ikusi eta entzun.",
205 205
         "enterDisplayName": "Sartu zure izena hemen",
206
-        "enterDisplayNameToJoin": "Mesedez idatzi zure izena bileran sartzeko",
207 206
         "embedMeeting": "Kapsulatu bilera",
208 207
         "error": "Errorea",
209 208
         "gracefulShutdown": "Zerbitzua ez dago erabilgarri mantentze-lanak direla eta. Saiatu berriro beranduago.",

+ 0
- 1
lang/main-fr.json View File

@@ -213,7 +213,6 @@
213 213
     "e2eeLabel": "Activer le chiffrement de Bout-en-Bout",
214 214
     "e2eeWarning": "ATTENTION : Tous les participants de cette réunion ne semblent pas prendre en charge le chiffrement de Bout-en-Bout. Si vous activez le chiffrement, ils ne pourront ni vous voir, ni vous entendre.",
215 215
     "enterDisplayName": "Merci de saisir votre nom ici",
216
-    "enterDisplayNameToJoin": "Merci de saisir votre nom pour rejoindre",
217 216
     "embedMeeting": "Intégrer la réunion",
218 217
     "error": "Erreur",
219 218
     "gracefulShutdown": "Notre service est actuellement en maintenance. Veuillez réessayer plus tard.",

+ 1
- 1
lang/main-id.json View File

@@ -175,7 +175,7 @@
175 175
         "dismiss": "Dismiss",
176 176
         "displayNameRequired": "Hi! What’s your name?",
177 177
         "done": "Done",
178
-        "enterDisplayName": "Please enter your name here",
178
+        "enterDisplayName": "Enter your name here",
179 179
         "error": "Error",
180 180
         "externalInstallationMsg": "You need to install our desktop sharing extension.",
181 181
         "externalInstallationTitle": "Extension required",

+ 0
- 1
lang/main-pt.json View File

@@ -197,7 +197,6 @@
197 197
         "e2eeLabel": "Habilitar encriptação de ponta a ponta",
198 198
         "e2eeWarning": "AVISO: Nem todos os participantes neste encontro parecem ter apoio para a encriptação de ponta a ponta. Se o permitir, eles não o poderão ver nem ouvir.",
199 199
         "enterDisplayName": "Digite o seu nome aqui",
200
-        "enterDisplayNameToJoin": "Por favor, digite o seu nome para participar",
201 200
         "embedMeeting": "Embutir reunião",
202 201
         "error": "Erro",
203 202
         "gracefulShutdown": "O nosso serviço está atualmente em manutenção. Por favor, tente novamente mais tarde.",

+ 0
- 1
lang/main-ptBR.json View File

@@ -209,7 +209,6 @@
209 209
         "e2eeLabel": "Enable End-to-End Encryption",
210 210
         "e2eeWarning": "WARNING: Not all participants in this meeting seem to have support for End-to-End encryption. If you enable it they won't be able to see nor hear you.",
211 211
         "enterDisplayName": "Digite seu nome aqui",
212
-        "enterDisplayNameToJoin": "Digite seu nome para participar",
213 212
         "embedMeeting": "Reunião em formato compacto",
214 213
         "error": "Erro",
215 214
         "gracefulShutdown": "Nosso serviço está em manutenção. Tente novamente mais tarde.",

+ 0
- 1
lang/main-sq.json View File

@@ -209,7 +209,6 @@
209 209
         "e2eeLabel": "Aktivizo Fshehtëzim Skaj-më-Skaj",
210 210
         "e2eeWarning": "KUJDES: Jo të gjithë pjesëmarrësit në këtë takim duket të kenë mbulim për fshehtëzim Skaj-më-Skaj. Në e aktivizofshi, ata s’do të jenë në gjendje t’ju shohin apo dëgjojnë.",
211 211
         "enterDisplayName": "Ju lutemi, jepni këtu emrin tuaj",
212
-        "enterDisplayNameToJoin": "Që të merrni pjesë, ju lutemi, jepni emrin tuaj",
213 212
         "embedMeeting": "Trupëzoni takim",
214 213
         "error": "Gabim",
215 214
         "gracefulShutdown": "Shërbimi ynë është aktualisht i ndërprerë, për punë mirëmbajtjeje. Ju lutemi, riprovoni më vonë.",

+ 0
- 1
lang/main-zhTW.json View File

@@ -209,7 +209,6 @@
209 209
         "e2eeLabel": "啟用端對端加密",
210 210
         "e2eeWarning": "警告:看來不是每位此會議的參與者都有啟用端對端加密,如果您啟用了,他們可能無法看/聽到您。",
211 211
         "enterDisplayName": "請在此輸入您自己的名字",
212
-        "enterDisplayNameToJoin": "請輸入您的名字以加入",
213 212
         "embedMeeting": "嵌入會議",
214 213
         "error": "錯誤",
215 214
         "gracefulShutdown": "我們的服務目前關閉維護中,請稍後再試。",

+ 4
- 4
lang/main.json View File

@@ -212,8 +212,7 @@
212 212
         "e2eeDescription": "End-to-End Encryption is currently EXPERIMENTAL. Please keep in mind that turning on end-to-end encryption will effectively disable server-side provided services such as: phone participation. Also keep in mind that the meeting will only work for people joining from browsers with support for insertable streams.",
213 213
         "e2eeLabel": "Enable End-to-End Encryption",
214 214
         "e2eeWarning": "WARNING: Not all participants in this meeting seem to have support for End-to-End encryption. If you enable it they won't be able to see nor hear you.",
215
-        "enterDisplayName": "Please enter your name here",
216
-        "enterDisplayNameToJoin": "Please enter your name to join",
215
+        "enterDisplayName": "Enter your name here",
217 216
         "embedMeeting": "Embed meeting",
218 217
         "error": "Error",
219 218
         "gracefulShutdown": "Our service is currently down for maintenance. Please try again later.",
@@ -694,7 +693,7 @@
694 693
         "joinWithoutAudio": "Join without audio",
695 694
         "initiated": "Call initiated",
696 695
         "linkCopied": "Link copied to clipboard",
697
-        "lookGood": "It sounds like your microphone is working properly",
696
+        "lookGood": "Your microphone is working properly",
698 697
         "or": "or",
699 698
         "premeeting": "Pre meeting",
700 699
         "showScreen": "Enable pre meeting screen",
@@ -1121,7 +1120,7 @@
1121 1120
         "admitAll": "Admit all",
1122 1121
         "knockingParticipantList": "Knocking participant list",
1123 1122
         "allow": "Allow",
1124
-        "backToKnockModeButton": "No password, ask to join instead",
1123
+        "backToKnockModeButton": "Ask to join",
1125 1124
         "dialogTitle": "Lobby mode",
1126 1125
         "disableDialogContent": "Lobby mode is currently enabled. This feature ensures that unwanted participants can't join your meeting. Do you want to disable it?",
1127 1126
         "disableDialogSubmit": "Disable",
@@ -1131,6 +1130,7 @@
1131 1130
         "enableDialogText": "Lobby mode lets you protect your meeting by only allowing people to enter after a formal approval by a moderator.",
1132 1131
         "enterPasswordButton": "Enter meeting password",
1133 1132
         "enterPasswordTitle": "Enter password to join meeting",
1133
+        "errorMissingPassword": "Please enter the meeting password",
1134 1134
         "invalidPassword": "Invalid password",
1135 1135
         "joiningMessage": "You'll join the meeting as soon as someone accepts your request",
1136 1136
         "joinWithPasswordMessage": "Trying to join with password, please wait...",

+ 5
- 3
react/features/app/components/App.web.js View File

@@ -43,9 +43,11 @@ export class App extends AbstractApp {
43 43
      */
44 44
     _renderDialogContainer() {
45 45
         return (
46
-            <AtlasKitThemeProvider mode = 'dark'>
47
-                <DialogContainer />
48
-            </AtlasKitThemeProvider>
46
+            <JitsiThemeProvider>
47
+                <AtlasKitThemeProvider mode = 'dark'>
48
+                    <DialogContainer />
49
+                </AtlasKitThemeProvider>
50
+            </JitsiThemeProvider>
49 51
         );
50 52
     }
51 53
 }

+ 2
- 3
react/features/base/conference/middleware.web.js View File

@@ -1,21 +1,20 @@
1 1
 // @flow
2 2
 
3 3
 import { setPrejoinPageVisibility, setSkipPrejoinOnReload } from '../../prejoin';
4
+import { PREJOIN_SCREEN_STATES } from '../../prejoin/constants';
4 5
 import { JitsiConferenceErrors } from '../lib-jitsi-meet';
5 6
 import { MiddlewareRegistry } from '../redux';
6 7
 
7 8
 import { CONFERENCE_FAILED, CONFERENCE_JOINED } from './actionTypes';
8 9
 import './middleware.any';
9 10
 
10
-declare var APP: Object;
11
-
12 11
 MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
13 12
     const { enableForcedReload } = getState()['features/base/config'];
14 13
 
15 14
     switch (action.type) {
16 15
     case CONFERENCE_JOINED: {
17 16
         if (enableForcedReload) {
18
-            dispatch(setPrejoinPageVisibility(false));
17
+            dispatch(setPrejoinPageVisibility(PREJOIN_SCREEN_STATES.HIDDEN));
19 18
             dispatch(setSkipPrejoinOnReload(false));
20 19
         }
21 20
 

+ 3
- 0
react/features/base/icons/svg/check-solid.svg View File

@@ -0,0 +1,3 @@
1
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+<path fill-rule="evenodd" clip-rule="evenodd" d="M0.666687 9.00002C0.666687 13.6024 4.39765 17.3334 9.00002 17.3334C13.6024 17.3334 17.3334 13.6024 17.3334 9.00002C17.3334 4.39765 13.6024 0.666687 9.00002 0.666687C4.39765 0.666687 0.666687 4.39765 0.666687 9.00002ZM13.7119 5.86983C13.3639 5.56869 12.8376 5.60672 12.5365 5.95477L7.55616 11.711L5.42261 9.57743C5.09717 9.25199 4.56954 9.25199 4.2441 9.57743C3.91866 9.90287 3.91866 10.4305 4.2441 10.7559L7.01102 13.5229C7.35319 13.865 7.91386 13.8448 8.23047 13.4789L13.7969 7.04527C14.098 6.69722 14.06 6.17096 13.7119 5.86983Z" fill="white"/>
3
+</svg>

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

@@ -26,6 +26,7 @@ export { default as IconChat } from './chat.svg';
26 26
 export { default as IconChatSend } from './send.svg';
27 27
 export { default as IconChatUnread } from './chat-unread.svg';
28 28
 export { default as IconCheck } from './check.svg';
29
+export { default as IconCheckSolid } from './check-solid.svg';
29 30
 export { default as IconClose } from './close.svg';
30 31
 export { default as IconCloseCircle } from './close-circle.svg';
31 32
 export { default as IconCloseX } from './close-x.svg';

+ 26
- 28
react/features/base/premeeting/components/web/ConnectionStatus.js View File

@@ -79,37 +79,35 @@ function ConnectionStatus({ connectionDetails, t, connectionType }: Props) {
79 79
 
80 80
     return (
81 81
         <div className = 'con-status'>
82
-            <div className = 'con-status-container'>
83
-                <div
84
-                    aria-level = { 1 }
85
-                    className = 'con-status-header'
86
-                    role = 'heading'>
87
-                    <div className = { `con-status-circle ${connectionClass}` }>
88
-                        <Icon
89
-                            size = { 16 }
90
-                            src = { icon } />
91
-                    </div>
92
-                    <span
93
-                        aria-hidden = { !showDetails }
94
-                        className = 'con-status-text'
95
-                        id = 'connection-status-description'>{t(connectionText)}</span>
82
+            <div
83
+                aria-level = { 1 }
84
+                className = 'con-status-header'
85
+                role = 'heading'>
86
+                <div className = { `con-status-circle ${connectionClass}` }>
96 87
                     <Icon
97
-                        ariaDescribedBy = 'connection-status-description'
98
-                        ariaPressed = { showDetails }
99
-                        className = { arrowClassName }
100
-                        onClick = { onToggleDetails }
101
-                        onKeyPress = { onKeyPressToggleDetails }
102
-                        role = 'button'
103
-                        size = { 24 }
104
-                        src = { IconArrowDownSmall }
105
-                        tabIndex = { 0 } />
88
+                        size = { 16 }
89
+                        src = { icon } />
106 90
                 </div>
107
-                <div
108
-                    aria-level = '2'
109
-                    className = { `con-status-details ${detailsClassName}` }
110
-                    role = 'heading'>
111
-                    {detailsText}</div>
91
+                <span
92
+                    aria-hidden = { !showDetails }
93
+                    className = 'con-status-text'
94
+                    id = 'connection-status-description'>{t(connectionText)}</span>
95
+                <Icon
96
+                    ariaDescribedBy = 'connection-status-description'
97
+                    ariaPressed = { showDetails }
98
+                    className = { arrowClassName }
99
+                    onClick = { onToggleDetails }
100
+                    onKeyPress = { onKeyPressToggleDetails }
101
+                    role = 'button'
102
+                    size = { 24 }
103
+                    src = { IconArrowDownSmall }
104
+                    tabIndex = { 0 } />
112 105
             </div>
106
+            <div
107
+                aria-level = '2'
108
+                className = { `con-status-details ${detailsClassName}` }
109
+                role = 'heading'>
110
+                {detailsText}</div>
113 111
         </div>
114 112
     );
115 113
 }

+ 0
- 67
react/features/base/premeeting/components/web/CopyMeetingUrl.js View File

@@ -1,67 +0,0 @@
1
-// @flow
2
-
3
-import React, { Component } from 'react';
4
-
5
-import CopyMeetingLinkSection
6
-    from '../../../../invite/components/add-people-dialog/web/CopyMeetingLinkSection';
7
-import { getCurrentConferenceUrl } from '../../../connection';
8
-import { translate } from '../../../i18n';
9
-import { connect } from '../../../redux';
10
-
11
-type Props = {
12
-
13
-    /**
14
-     * The meeting url.
15
-     */
16
-    url: string,
17
-
18
-    /**
19
-     * Used for translation.
20
-     */
21
-    t: Function,
22
-
23
-    /**
24
-     * Used to determine if invitation link should be automatically copied
25
-     * after creating a meeting.
26
-     */
27
-    _enableAutomaticUrlCopy: boolean,
28
-};
29
-
30
-
31
-/**
32
- * Component used to copy meeting url on prejoin page.
33
- */
34
-class CopyMeetingUrl extends Component<Props> {
35
-
36
-    /**
37
-     * Implements React's {@link Component#render()}.
38
-     *
39
-     * @inheritdoc
40
-     * @returns {ReactElement}
41
-     */
42
-    render() {
43
-        return (
44
-            <div className = 'copy-meeting'>
45
-                <CopyMeetingLinkSection url = { this.props.url } />
46
-            </div>
47
-        );
48
-    }
49
-}
50
-
51
-/**
52
- * Maps (parts of) the redux state to the React {@code Component} props.
53
- *
54
- * @param {Object} state - The redux state.
55
- * @returns {Object}
56
- */
57
-function mapStateToProps(state) {
58
-    const { enableAutomaticUrlCopy } = state['features/base/config'];
59
-    const { customizationReady } = state['features/dynamic-branding'];
60
-
61
-    return {
62
-        url: customizationReady ? getCurrentConferenceUrl(state) : '',
63
-        _enableAutomaticUrlCopy: enableAutomaticUrlCopy || false
64
-    };
65
-}
66
-
67
-export default connect(mapStateToProps)(translate(CopyMeetingUrl));

+ 39
- 67
react/features/base/premeeting/components/web/PreMeetingScreen.js View File

@@ -2,14 +2,10 @@
2 2
 
3 3
 import React, { PureComponent } from 'react';
4 4
 
5
-import { AudioSettingsButton, VideoSettingsButton } from '../../../../toolbox/components/web';
6
-import { VideoBackgroundButton } from '../../../../virtual-background';
7
-import { checkBlurSupport } from '../../../../virtual-background/functions';
8
-import { Avatar } from '../../../avatar';
9
-import { allowUrlSharing } from '../../functions';
5
+import DeviceStatus from '../../../../prejoin/components/preview/DeviceStatus';
6
+import { Toolbox } from '../../../../toolbox/components/web';
10 7
 
11 8
 import ConnectionStatus from './ConnectionStatus';
12
-import CopyMeetingUrl from './CopyMeetingUrl';
13 9
 import Preview from './Preview';
14 10
 
15 11
 type Props = {
@@ -17,12 +13,12 @@ type Props = {
17 13
     /**
18 14
      * Children component(s) to be rendered on the screen.
19 15
      */
20
-    children: React$Node,
16
+    children?: React$Node,
21 17
 
22 18
     /**
23
-     * Footer to be rendered for the page (if any).
19
+     * Additional CSS class names to set on the icon container.
24 20
      */
25
-    footer?: React$Node,
21
+    className?: string,
26 22
 
27 23
     /**
28 24
      * The name of the participant.
@@ -35,24 +31,24 @@ type Props = {
35 31
     showCopyUrlButton: boolean,
36 32
 
37 33
     /**
38
-     * Indicates whether the avatar should be shown when video is off
34
+     * Indicates whether the device status should be shown
39 35
      */
40
-    showAvatar: boolean,
36
+    showDeviceStatus: boolean,
41 37
 
42 38
     /**
43
-     * Indicates whether the label and copy url action should be shown
39
+     * The 'Skip prejoin' button to be rendered (if any).
44 40
      */
45
-    showConferenceInfo: boolean,
41
+     skipPrejoinButton?: React$Node,
46 42
 
47 43
     /**
48 44
      * Title of the screen.
49 45
      */
50
-    title: string,
46
+    title?: string,
51 47
 
52 48
     /**
53
-     * The 'Skip prejoin' button to be rendered (if any).
49
+     * Override for default toolbar buttons
54 50
      */
55
-     skipPrejoinButton?: React$Node,
51
+     toolbarButtons?: Array<string>,
56 52
 
57 53
     /**
58 54
      * True if the preview overlay should be muted, false otherwise.
@@ -62,14 +58,11 @@ type Props = {
62 58
     /**
63 59
      * The video track to render as preview (if omitted, the default local track will be rendered).
64 60
      */
65
-    videoTrack?: Object,
66
-
67
-    /**
68
-     * Array with the buttons which this Toolbox should display.
69
-     */
70
-    visibleButtons?: Array<string>
61
+    videoTrack?: Object
71 62
 }
72 63
 
64
+const buttons = [ 'microphone', 'camera', 'select-background', 'invite', 'settings' ];
65
+
73 66
 /**
74 67
  * Implements a pre-meeting screen that can be used at various pre-meeting phases, for example
75 68
  * on the prejoin screen (pre-connection) or lobby (post-connection).
@@ -81,9 +74,8 @@ export default class PreMeetingScreen extends PureComponent<Props> {
81 74
      * @static
82 75
      */
83 76
     static defaultProps = {
84
-        showAvatar: true,
85 77
         showCopyUrlButton: true,
86
-        showConferenceInfo: true
78
+        showToolbox: true
87 79
     };
88 80
 
89 81
     /**
@@ -93,57 +85,37 @@ export default class PreMeetingScreen extends PureComponent<Props> {
93 85
      */
94 86
     render() {
95 87
         const {
96
-            name,
97
-            showAvatar,
98
-            showConferenceInfo,
99
-            showCopyUrlButton,
88
+            children,
89
+            className,
90
+            showDeviceStatus,
91
+            skipPrejoinButton,
100 92
             title,
93
+            toolbarButtons,
101 94
             videoMuted,
102
-            videoTrack,
103
-            visibleButtons
95
+            videoTrack
104 96
         } = this.props;
105
-        const showSharingButton = allowUrlSharing() && showCopyUrlButton;
97
+
98
+        const containerClassName = `premeeting-screen ${className ? className : ''}`;
106 99
 
107 100
         return (
108
-            <div
109
-                className = 'premeeting-screen'
110
-                id = 'lobby-screen'>
111
-                <ConnectionStatus />
112
-                <Preview
113
-                    videoMuted = { videoMuted }
114
-                    videoTrack = { videoTrack } />
101
+            <div className = { containerClassName }>
115 102
                 <div className = 'content'>
116
-                    {showAvatar && videoMuted && (
117
-                        <Avatar
118
-                            className = 'premeeting-screen-avatar'
119
-                            displayName = { name }
120
-                            dynamicColor = { false }
121
-                            participantId = 'local'
122
-                            size = { 80 } />
123
-                    )}
124
-                    {showConferenceInfo && (
125
-                        <>
126
-                            <h1 className = 'title'>
127
-                                { title }
128
-                            </h1>
129
-                            {showSharingButton ? <CopyMeetingUrl /> : null}
130
-                        </>
131
-                    )}
132
-                    { this.props.children }
133
-                    <div className = 'media-btn-container'>
134
-                        <div className = 'toolbox-content'>
135
-                            <div className = 'toolbox-content-items'>
136
-                                <AudioSettingsButton visible = { true } />
137
-                                <VideoSettingsButton visible = { true } />
138
-                                { ((visibleButtons && visibleButtons.includes('select-background'))
139
-                                   || (visibleButtons && visibleButtons.includes('videobackgroundblur')))
140
-                                   && <VideoBackgroundButton visible = { checkBlurSupport() } /> }
141
-                            </div>
142
-                        </div>
103
+                    <ConnectionStatus />
104
+
105
+                    <div className = 'content-controls'>
106
+                        <h1 className = 'title'>
107
+                            { title }
108
+                        </h1>
109
+                        { children }
110
+                        <Toolbox toolbarButtons = { toolbarButtons || buttons } />
111
+                        { skipPrejoinButton }
112
+                        { showDeviceStatus && <DeviceStatus /> }
143 113
                     </div>
144
-                    { this.props.skipPrejoinButton }
145
-                    { this.props.footer }
146 114
                 </div>
115
+
116
+                <Preview
117
+                    videoMuted = { videoMuted }
118
+                    videoTrack = { videoTrack } />
147 119
             </div>
148 120
         );
149 121
     }

+ 37
- 12
react/features/base/premeeting/components/web/Preview.js View File

@@ -2,17 +2,30 @@
2 2
 
3 3
 import React from 'react';
4 4
 
5
+import { getDisplayName } from '../../../../base/settings';
6
+import { Avatar } from '../../../avatar';
5 7
 import { Video } from '../../../media';
8
+import { getLocalParticipant } from '../../../participants';
6 9
 import { connect } from '../../../redux';
7 10
 import { getLocalVideoTrack } from '../../../tracks';
8 11
 
9 12
 export type Props = {
10 13
 
14
+    /**
15
+     * Local participant id
16
+     */
17
+    _participantId: string,
18
+
11 19
     /**
12 20
      * Flag controlling whether the video should be flipped or not.
13 21
      */
14 22
     flipVideo: boolean,
15 23
 
24
+    /**
25
+     * The name of the user that is about to join.
26
+     */
27
+     name: string,
28
+
16 29
     /**
17 30
      * Flag signaling the visibility of camera preview.
18 31
      */
@@ -31,20 +44,27 @@ export type Props = {
31 44
  * @returns {ReactElement}
32 45
  */
33 46
 function Preview(props: Props) {
34
-    const { videoMuted, videoTrack, flipVideo } = props;
47
+    const { _participantId, flipVideo, name, videoMuted, videoTrack } = props;
35 48
     const className = flipVideo ? 'flipVideoX' : '';
36 49
 
37
-    if (!videoMuted && videoTrack) {
38
-        return (
39
-            <div id = 'preview'>
40
-                <Video
41
-                    className = { className }
42
-                    videoTrack = {{ jitsiTrack: videoTrack }} />
43
-            </div>
44
-        );
45
-    }
46
-
47
-    return null;
50
+    return (
51
+        <div id = 'preview'>
52
+            {!videoMuted && videoTrack
53
+                ? (
54
+                    <Video
55
+                        className = { className }
56
+                        videoTrack = {{ jitsiTrack: videoTrack }} />
57
+                )
58
+                : (
59
+                    <Avatar
60
+                        className = 'premeeting-screen-avatar'
61
+                        displayName = { name }
62
+                        dynamicColor = { false }
63
+                        participantId = { _participantId }
64
+                        size = { 180 } />
65
+                )}
66
+        </div>
67
+    );
48 68
 }
49 69
 
50 70
 /**
@@ -55,8 +75,13 @@ function Preview(props: Props) {
55 75
  * @returns {Props}
56 76
  */
57 77
 function _mapStateToProps(state, ownProps) {
78
+    const name = getDisplayName(state);
79
+    const { id: _participantId } = getLocalParticipant(state);
80
+
58 81
     return {
82
+        _participantId,
59 83
         flipVideo: state['features/base/settings'].localFlipX,
84
+        name,
60 85
         videoMuted: ownProps.videoTrack ? ownProps.videoMuted : state['features/base/media'].video.muted,
61 86
         videoTrack: ownProps.videoTrack || (getLocalVideoTrack(state['features/base/tracks']) || {}).jitsiTrack
62 87
     };

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

@@ -213,14 +213,3 @@ export function getConnectionData(state: Object) {
213 213
         connectionDetails: []
214 214
     };
215 215
 }
216
-
217
-/**
218
- * Returns if url sharing is enabled in interface configuration.
219
- *
220
- * @returns {boolean}
221
- */
222
-export function allowUrlSharing() {
223
-    return typeof interfaceConfig === 'undefined'
224
-        || typeof interfaceConfig.SHARING_FEATURES === 'undefined'
225
-        || (interfaceConfig.SHARING_FEATURES.length && interfaceConfig.SHARING_FEATURES.indexOf('url') > -1);
226
-}

+ 13
- 1
react/features/conference/components/native/Conference.js View File

@@ -22,6 +22,8 @@ import {
22 22
 import { AddPeopleDialog, CalleeInfoContainer } from '../../../invite';
23 23
 import { LargeVideo } from '../../../large-video';
24 24
 import { KnockingParticipantList } from '../../../lobby';
25
+import { LobbyScreen } from '../../../lobby/components/native';
26
+import { getIsLobbyVisible } from '../../../lobby/functions';
25 27
 import { BackButtonRegistry } from '../../../mobile/back-button';
26 28
 import { ParticipantsPane } from '../../../participants-pane/components/native';
27 29
 import { Captions } from '../../../subtitles';
@@ -98,6 +100,11 @@ type Props = AbstractProps & {
98 100
      */
99 101
     _toolboxVisible: boolean,
100 102
 
103
+    /**
104
+     * Indicates whether the lobby screen should be visible.
105
+     */
106
+    _showLobby: boolean,
107
+
101 108
     /**
102 109
      * The redux {@code dispatch} function.
103 110
      */
@@ -154,7 +161,11 @@ class Conference extends AbstractConference<Props, *> {
154 161
      * @returns {ReactElement}
155 162
      */
156 163
     render() {
157
-        const { _fullscreenEnabled } = this.props;
164
+        const { _fullscreenEnabled, _showLobby } = this.props;
165
+
166
+        if (_showLobby) {
167
+            return <LobbyScreen />;
168
+        }
158 169
 
159 170
         return (
160 171
             <Container style = { styles.conference }>
@@ -427,6 +438,7 @@ function _mapStateToProps(state) {
427 438
         _largeVideoParticipantId: state['features/large-video'].participantId,
428 439
         _pictureInPictureEnabled: getFeatureFlag(state, PIP_ENABLED),
429 440
         _reducedUI: reducedUI,
441
+        _showLobby: getIsLobbyVisible(state),
430 442
         _toolboxVisible: isToolboxVisible(state)
431 443
     };
432 444
 }

+ 12
- 11
react/features/conference/components/web/Conference.js View File

@@ -15,9 +15,10 @@ import { Filmstrip } from '../../../filmstrip';
15 15
 import { CalleeInfoContainer } from '../../../invite';
16 16
 import { LargeVideo } from '../../../large-video';
17 17
 import { KnockingParticipantList, LobbyScreen } from '../../../lobby';
18
+import { getIsLobbyVisible } from '../../../lobby/functions';
18 19
 import { ParticipantsPane } from '../../../participants-pane/components/web';
19 20
 import { getParticipantsPaneOpen } from '../../../participants-pane/functions';
20
-import { Prejoin, isPrejoinPageVisible } from '../../../prejoin';
21
+import { Prejoin, isPrejoinPageVisible, isPrejoinPageLoading } from '../../../prejoin';
21 22
 import { fullScreenChanged, showToolbox } from '../../../toolbox/actions.web';
22 23
 import { Toolbox } from '../../../toolbox/components/web';
23 24
 import { LAYOUTS, getCurrentLayout } from '../../../video-layout';
@@ -70,11 +71,6 @@ type Props = AbstractProps & {
70 71
      */
71 72
     _backgroundAlpha: number,
72 73
 
73
-    /**
74
-     * Returns true if the 'lobby screen' is visible.
75
-     */
76
-    _isLobbyScreenVisible: boolean,
77
-
78 74
     /**
79 75
      * If participants pane is visible or not.
80 76
      */
@@ -96,6 +92,11 @@ type Props = AbstractProps & {
96 92
      */
97 93
     _roomName: string,
98 94
 
95
+    /**
96
+     * If lobby page is visible or not.
97
+     */
98
+     _showLobby: boolean,
99
+
99 100
     /**
100 101
      * If prejoin page is visible or not.
101 102
      */
@@ -207,9 +208,9 @@ class Conference extends AbstractConference<Props, *> {
207 208
      */
208 209
     render() {
209 210
         const {
210
-            _isLobbyScreenVisible,
211 211
             _isParticipantsPaneVisible,
212 212
             _layoutClassName,
213
+            _showLobby,
213 214
             _showPrejoin
214 215
         } = this.props;
215 216
 
@@ -237,7 +238,7 @@ class Conference extends AbstractConference<Props, *> {
237 238
                         <Filmstrip />
238 239
                     </div>
239 240
 
240
-                    { _showPrejoin || _isLobbyScreenVisible || <Toolbox /> }
241
+                    { _showPrejoin || _showLobby || <Toolbox /> }
241 242
                     <Chat />
242 243
 
243 244
                     { this.renderNotificationsContainer() }
@@ -245,7 +246,7 @@ class Conference extends AbstractConference<Props, *> {
245 246
                     <CalleeInfoContainer />
246 247
 
247 248
                     { _showPrejoin && <Prejoin />}
248
-
249
+                    { _showLobby && <LobbyScreen />}
249 250
                 </div>
250 251
                 <ParticipantsPane />
251 252
             </div>
@@ -373,12 +374,12 @@ function _mapStateToProps(state) {
373 374
     return {
374 375
         ...abstractMapStateToProps(state),
375 376
         _backgroundAlpha: backgroundAlpha,
376
-        _isLobbyScreenVisible: state['features/base/dialog']?.component === LobbyScreen,
377 377
         _isParticipantsPaneVisible: getParticipantsPaneOpen(state),
378 378
         _layoutClassName: LAYOUT_CLASSNAMES[getCurrentLayout(state)],
379 379
         _mouseMoveCallbackInterval: mouseMoveCallbackInterval,
380 380
         _roomName: getConferenceNameForTitle(state),
381
-        _showPrejoin: isPrejoinPageVisible(state)
381
+        _showLobby: getIsLobbyVisible(state),
382
+        _showPrejoin: isPrejoinPageVisible(state) || isPrejoinPageLoading(state)
382 383
     };
383 384
 }
384 385
 

+ 44
- 0
react/features/invite/components/add-people-dialog/web/InviteButton.js View File

@@ -0,0 +1,44 @@
1
+// @flow
2
+
3
+import { createToolbarEvent, sendAnalytics } from '../../../../analytics';
4
+import { translate } from '../../../../base/i18n';
5
+import { IconAddPeople } from '../../../../base/icons';
6
+import { connect } from '../../../../base/redux';
7
+import { AbstractButton, type AbstractButtonProps } from '../../../../base/toolbox/components';
8
+import { beginAddPeople } from '../../../actions.any';
9
+
10
+/**
11
+ * The type of the React {@code Component} props of {@link EmbedMeetingButton}.
12
+ */
13
+type Props = AbstractButtonProps & {
14
+
15
+    /**
16
+     * The redux {@code dispatch} function.
17
+     */
18
+    dispatch: Function
19
+};
20
+
21
+/**
22
+ * Implementation of a button for opening invite people dialog.
23
+ */
24
+class InviteButton extends AbstractButton<Props, *> {
25
+    accessibilityLabel = 'toolbar.accessibilityLabel.invite';
26
+    icon = IconAddPeople;
27
+    label = 'toolbar.invite';
28
+    tooltip = 'toolbar.invite';
29
+
30
+    /**
31
+     * Handles clicking / pressing the button, and opens the appropriate dialog.
32
+     *
33
+     * @protected
34
+     * @returns {void}
35
+     */
36
+    _handleClick() {
37
+        const { dispatch } = this.props;
38
+
39
+        sendAnalytics(createToolbarEvent('invite'));
40
+        dispatch(beginAddPeople());
41
+    }
42
+}
43
+
44
+export default translate(connect()(InviteButton));

+ 1
- 0
react/features/invite/components/add-people-dialog/web/index.js View File

@@ -1,3 +1,4 @@
1 1
 // @flow
2 2
 
3 3
 export { default as AddPeopleDialog } from './AddPeopleDialog';
4
+export { default as InviteButton } from './InviteButton';

+ 5
- 0
react/features/lobby/actionTypes.js View File

@@ -20,6 +20,11 @@ export const SET_LOBBY_MODE_ENABLED = 'SET_LOBBY_MODE_ENABLED';
20 20
  */
21 21
 export const SET_KNOCKING_STATE = 'SET_KNOCKING_STATE';
22 22
 
23
+/**
24
+ * Action type to set the lobby visibility.
25
+ */
26
+export const SET_LOBBY_VISIBILITY = 'TOGGLE_LOBBY_VISIBILITY';
27
+
23 28
 /**
24 29
  * Action type to set the password join failed status.
25 30
  */

+ 26
- 0
react/features/lobby/actions.any.js View File

@@ -6,6 +6,8 @@ import {
6 6
     getCurrentConference
7 7
 } from '../base/conference';
8 8
 
9
+import { SET_LOBBY_VISIBILITY } from './actionTypes';
10
+
9 11
 /**
10 12
  * Action to toggle lobby mode on or off.
11 13
  *
@@ -23,3 +25,27 @@ export function toggleLobbyMode(enabled: boolean) {
23 25
         }
24 26
     };
25 27
 }
28
+
29
+/**
30
+ * Action to open the lobby screen.
31
+ *
32
+ * @returns {openDialog}
33
+ */
34
+export function openLobbyScreen() {
35
+    return {
36
+        type: SET_LOBBY_VISIBILITY,
37
+        visible: true
38
+    };
39
+}
40
+
41
+/**
42
+ * Action to hide the lobby screen.
43
+ *
44
+ * @returns {hideDialog}
45
+ */
46
+export function hideLobbyScreen() {
47
+    return {
48
+        type: SET_LOBBY_VISIBILITY,
49
+        visible: false
50
+    };
51
+}

+ 0
- 20
react/features/lobby/actions.web.js View File

@@ -9,7 +9,6 @@ import {
9 9
     sendLocalParticipant,
10 10
     setPassword
11 11
 } from '../base/conference';
12
-import { hideDialog, openDialog } from '../base/dialog';
13 12
 import { getLocalParticipant } from '../base/participants';
14 13
 export * from './actions.any';
15 14
 
@@ -20,7 +19,6 @@ import {
20 19
     SET_LOBBY_MODE_ENABLED,
21 20
     SET_PASSWORD_JOIN_FAILED
22 21
 } from './actionTypes';
23
-import { LobbyScreen } from './components';
24 22
 
25 23
 declare var APP: Object;
26 24
 
@@ -44,15 +42,6 @@ export function cancelKnocking() {
44 42
     };
45 43
 }
46 44
 
47
-/**
48
- * Action to hide the lobby screen.
49
- *
50
- * @returns {hideDialog}
51
- */
52
-export function hideLobbyScreen() {
53
-    return hideDialog(LobbyScreen);
54
-}
55
-
56 45
 /**
57 46
  * Tries to join with a preset password.
58 47
  *
@@ -83,15 +72,6 @@ export function knockingParticipantLeft(id: string) {
83 72
     };
84 73
 }
85 74
 
86
-/**
87
- * Action to open the lobby screen.
88
- *
89
- * @returns {openDialog}
90
- */
91
-export function openLobbyScreen() {
92
-    return openDialog(LobbyScreen, {}, true);
93
-}
94
-
95 75
 /**
96 76
  * Action to be executed when a participant starts knocking or an already knocking participant gets updated.
97 77
  *

+ 8
- 0
react/features/lobby/components/AbstractLobbyScreen.js View File

@@ -7,6 +7,7 @@ import { getFeatureFlag, INVITE_ENABLED } from '../../base/flags';
7 7
 import { getLocalParticipant } from '../../base/participants';
8 8
 import { getFieldValue } from '../../base/react';
9 9
 import { updateSettings } from '../../base/settings';
10
+import { isDeviceStatusVisible } from '../../prejoin/functions';
10 11
 import { cancelKnocking, joinWithPassword, setPasswordJoinFailed, startKnocking } from '../actions';
11 12
 
12 13
 export const SCREEN_STATES = {
@@ -17,6 +18,11 @@ export const SCREEN_STATES = {
17 18
 
18 19
 export type Props = {
19 20
 
21
+    /**
22
+     * Indicates whether the device status should be visible.
23
+     */
24
+    _deviceStatusVisible: boolean,
25
+
20 26
     /**
21 27
      * True if knocking is already happening, so we're waiting for a response.
22 28
      */
@@ -380,8 +386,10 @@ export function _mapStateToProps(state: Object): $Shape<Props> {
380 386
     const { knocking, passwordJoinFailed } = state['features/lobby'];
381 387
     const { iAmSipGateway } = state['features/base/config'];
382 388
     const showCopyUrlButton = inviteEnabledFlag || !disableInviteFunctions;
389
+    const deviceStatusVisible = isDeviceStatusVisible(state);
383 390
 
384 391
     return {
392
+        _deviceStatusVisible: deviceStatusVisible,
385 393
         _knocking: knocking,
386 394
         _meetingName: getConferenceName(state),
387 395
         _participantEmail: localParticipant?.email,

+ 10
- 9
react/features/lobby/components/native/LobbyScreen.js View File

@@ -27,15 +27,16 @@ class LobbyScreen extends AbstractLobbyScreen {
27 27
 
28 28
         return (
29 29
             <CustomDialog
30
-                onCancel = { this._onCancel }
31
-                style = { styles.contentWrapper }>
32
-                <Text style = { styles.dialogTitle }>
33
-                    { t(this._getScreenTitleKey()) }
34
-                </Text>
35
-                <Text style = { styles.secondaryText }>
36
-                    { _meetingName }
37
-                </Text>
38
-                { this._renderContent() }
30
+                onCancel = { this._onCancel }>
31
+                <View style = { styles.contentWrapper }>
32
+                    <Text style = { styles.dialogTitle }>
33
+                        { t(this._getScreenTitleKey()) }
34
+                    </Text>
35
+                    <Text style = { styles.secondaryText }>
36
+                        { _meetingName }
37
+                    </Text>
38
+                    { this._renderContent() }
39
+                </View>
39 40
             </CustomDialog>
40 41
         );
41 42
     }

+ 18
- 11
react/features/lobby/components/web/LobbyScreen.js View File

@@ -20,11 +20,13 @@ class LobbyScreen extends AbstractLobbyScreen {
20 20
      * @inheritdoc
21 21
      */
22 22
     render() {
23
-        const { showCopyUrlButton, t } = this.props;
23
+        const { _deviceStatusVisible, showCopyUrlButton, t } = this.props;
24 24
 
25 25
         return (
26 26
             <PreMeetingScreen
27
+                className = 'lobby-screen'
27 28
                 showCopyUrlButton = { showCopyUrlButton }
29
+                showDeviceStatus = { _deviceStatusVisible }
28 30
                 title = { t(this._getScreenTitleKey()) }>
29 31
                 { this._renderContent() }
30 32
             </PreMeetingScreen>
@@ -62,7 +64,7 @@ class LobbyScreen extends AbstractLobbyScreen {
62 64
      */
63 65
     _renderJoining() {
64 66
         return (
65
-            <div className = 'container'>
67
+            <div className = 'lobby-screen-content'>
66 68
                 <div className = 'spinner'>
67 69
                     <LoadingIndicator size = 'large' />
68 70
                 </div>
@@ -113,13 +115,19 @@ class LobbyScreen extends AbstractLobbyScreen {
113 115
         const { _passwordJoinFailed, t } = this.props;
114 116
 
115 117
         return (
116
-            <InputField
117
-                className = { _passwordJoinFailed ? 'error' : '' }
118
-                onChange = { this._onChangePassword }
119
-                placeHolder = { _passwordJoinFailed ? t('lobby.invalidPassword') : t('lobby.passwordField') }
120
-                testId = 'lobby.password'
121
-                type = 'password'
122
-                value = { this.state.password } />
118
+            <>
119
+                <InputField
120
+                    className = { _passwordJoinFailed ? 'error' : '' }
121
+                    onChange = { this._onChangePassword }
122
+                    placeHolder = { t('lobby.passwordField') }
123
+                    testId = 'lobby.password'
124
+                    type = 'password'
125
+                    value = { this.state.password } />
126
+
127
+                {_passwordJoinFailed && <div
128
+                    className = 'prejoin-error'
129
+                    data-testid = 'lobby.errorMessage'>{t('lobby.invalidPassword')}</div>}
130
+            </>
123 131
         );
124 132
     }
125 133
 
@@ -134,11 +142,10 @@ class LobbyScreen extends AbstractLobbyScreen {
134 142
         return (
135 143
             <>
136 144
                 <ActionButton
137
-                    disabled = { !this.state.password }
138 145
                     onClick = { this._onJoinWithPassword }
139 146
                     testId = 'lobby.passwordJoinButton'
140 147
                     type = 'primary'>
141
-                    { t('lobby.passwordJoinButton') }
148
+                    { t('prejoin.joinMeeting') }
142 149
                 </ActionButton>
143 150
                 <ActionButton
144 151
                     onClick = { this._onSwitchToKnockMode }

+ 10
- 0
react/features/lobby/functions.js View File

@@ -20,6 +20,16 @@ export function getKnockingParticipants(state: any) {
20 20
     return state['features/lobby'].knockingParticipants;
21 21
 }
22 22
 
23
+/**
24
+ * Selector to return lobby visibility.
25
+ *
26
+ * @param {any} state - State object.
27
+ * @returns {any}
28
+ */
29
+export function getIsLobbyVisible(state: any) {
30
+    return state['features/lobby'].lobbyVisible;
31
+}
32
+
23 33
 /**
24 34
  * Selector to return array with knocking participant ids.
25 35
  *

+ 7
- 0
react/features/lobby/reducer.js View File

@@ -8,6 +8,7 @@ import {
8 8
     KNOCKING_PARTICIPANT_LEFT,
9 9
     SET_KNOCKING_STATE,
10 10
     SET_LOBBY_MODE_ENABLED,
11
+    SET_LOBBY_VISIBILITY,
11 12
     SET_PASSWORD_JOIN_FAILED
12 13
 } from './actionTypes';
13 14
 
@@ -15,6 +16,7 @@ const DEFAULT_STATE = {
15 16
     knocking: false,
16 17
     knockingParticipants: [],
17 18
     lobbyEnabled: false,
19
+    lobbyVisible: false,
18 20
     passwordJoinFailed: false
19 21
 };
20 22
 
@@ -53,6 +55,11 @@ ReducerRegistry.register('features/lobby', (state = DEFAULT_STATE, action) => {
53 55
             ...state,
54 56
             lobbyEnabled: action.enabled
55 57
         };
58
+    case SET_LOBBY_VISIBILITY:
59
+        return {
60
+            ...state,
61
+            lobbyVisible: action.visible
62
+        };
56 63
     case SET_PASSWORD:
57 64
         return {
58 65
             ...state,

+ 3
- 2
react/features/prejoin/actions.js View File

@@ -33,6 +33,7 @@ import {
33 33
     SET_PREJOIN_DEVICE_ERRORS,
34 34
     SET_PREJOIN_PAGE_VISIBILITY
35 35
 } from './actionTypes';
36
+import { type PREJOIN_SCREEN_STATE } from './constants';
36 37
 import {
37 38
     getFullDialOutNumber,
38 39
     getDialOutConferenceUrl,
@@ -480,10 +481,10 @@ export function setPrejoinDeviceErrors(value: Object) {
480 481
 /**
481 482
  * Action used to set the visibility of the prejoin page.
482 483
  *
483
- * @param {boolean} value - The value.
484
+ * @param {string} value - The value.
484 485
  * @returns {Object}
485 486
  */
486
-export function setPrejoinPageVisibility(value: boolean) {
487
+export function setPrejoinPageVisibility(value: PREJOIN_SCREEN_STATE) {
487 488
     return {
488 489
         type: SET_PREJOIN_PAGE_VISIBILITY,
489 490
         value

+ 74
- 155
react/features/prejoin/components/Prejoin.js View File

@@ -4,7 +4,6 @@ import InlineDialog from '@atlaskit/inline-dialog';
4 4
 import React, { Component } from 'react';
5 5
 
6 6
 import { getRoomName } from '../../base/conference';
7
-import { getToolbarButtons } from '../../base/config';
8 7
 import { translate } from '../../base/i18n';
9 8
 import { Icon, IconArrowDown, IconArrowUp, IconPhone, IconVolumeOff } from '../../base/icons';
10 9
 import { isVideoMutedByUser } from '../../base/media';
@@ -12,7 +11,6 @@ import { ActionButton, InputField, PreMeetingScreen, ToggleButton } from '../../
12 11
 import { connect } from '../../base/redux';
13 12
 import { getDisplayName, updateSettings } from '../../base/settings';
14 13
 import { getLocalJitsiVideoTrack } from '../../base/tracks';
15
-import { isButtonEnabled } from '../../toolbox/functions.web';
16 14
 import {
17 15
     joinConference as joinConferenceAction,
18 16
     joinConferenceWithoutAudio as joinConferenceWithoutAudioAction,
@@ -28,9 +26,6 @@ import {
28 26
 } from '../functions';
29 27
 
30 28
 import JoinByPhoneDialog from './dialogs/JoinByPhoneDialog';
31
-import DeviceStatus from './preview/DeviceStatus';
32
-
33
-declare var interfaceConfig: Object;
34 29
 
35 30
 type Props = {
36 31
 
@@ -84,11 +79,6 @@ type Props = {
84 79
      */
85 80
     setJoinByPhoneDialogVisiblity: Function,
86 81
 
87
-    /**
88
-     * Indicates whether the avatar should be shown when video is off
89
-     */
90
-    showAvatar: boolean,
91
-
92 82
     /**
93 83
      * Flag signaling the visibility of camera preview.
94 84
      */
@@ -99,26 +89,11 @@ type Props = {
99 89
      */
100 90
     showErrorOnJoin: boolean,
101 91
 
102
-    /**
103
-     * Flag signaling the visibility of join label, input and buttons
104
-     */
105
-    showJoinActions: boolean,
106
-
107
-    /**
108
-     * Flag signaling the visibility of the conference URL section.
109
-     */
110
-    showConferenceInfo: boolean,
111
-
112 92
     /**
113 93
      * If 'JoinByPhoneDialog' is visible or not.
114 94
      */
115 95
     showDialog: boolean,
116 96
 
117
-    /**
118
-     * Flag signaling the visibility of the skip prejoin toggle
119
-     */
120
-    showSkipPrejoin: boolean,
121
-
122 97
     /**
123 98
      * Used for translation.
124 99
      */
@@ -127,12 +102,7 @@ type Props = {
127 102
     /**
128 103
      * The JitsiLocalTrack to display.
129 104
      */
130
-    videoTrack: ?Object,
131
-
132
-    /**
133
-     * Array with the buttons which this Toolbox should display.
134
-     */
135
-    visibleButtons: Array<string>
105
+    videoTrack: ?Object
136 106
 };
137 107
 
138 108
 type State = {
@@ -152,17 +122,6 @@ type State = {
152 122
  * This component is displayed before joining a meeting.
153 123
  */
154 124
 class Prejoin extends Component<Props, State> {
155
-    /**
156
-     * Default values for {@code Prejoin} component's properties.
157
-     *
158
-     * @static
159
-     */
160
-    static defaultProps = {
161
-        showConferenceInfo: true,
162
-        showJoinActions: true,
163
-        showSkipPrejoin: true
164
-    };
165
-
166 125
     /**
167 126
      * Initializes a new {@code Prejoin} instance.
168 127
      *
@@ -344,18 +303,15 @@ class Prejoin extends Component<Props, State> {
344 303
      */
345 304
     render() {
346 305
         const {
306
+            deviceStatusVisible,
347 307
             hasJoinByPhoneButton,
348 308
             joinConference,
349 309
             joinConferenceWithoutAudio,
350 310
             name,
351
-            showAvatar,
352 311
             showCameraPreview,
353 312
             showDialog,
354
-            showConferenceInfo,
355
-            showJoinActions,
356 313
             t,
357
-            videoTrack,
358
-            visibleButtons
314
+            videoTrack
359 315
         } = this.props;
360 316
 
361 317
         const { _closeDialog, _onDropdownClose, _onJoinButtonClick, _onJoinKeyPress, _showDialogKeyPress,
@@ -364,89 +320,78 @@ class Prejoin extends Component<Props, State> {
364 320
 
365 321
         return (
366 322
             <PreMeetingScreen
367
-                footer = { this._renderFooter() }
368
-                name = { name }
369
-                showAvatar = { showAvatar }
370
-                showConferenceInfo = { showConferenceInfo }
323
+                showDeviceStatus = { deviceStatusVisible }
371 324
                 skipPrejoinButton = { this._renderSkipPrejoinButton() }
372 325
                 title = { t('prejoin.joinMeeting') }
373 326
                 videoMuted = { !showCameraPreview }
374
-                videoTrack = { videoTrack }
375
-                visibleButtons = { visibleButtons }>
376
-                {showJoinActions && (
377
-                    <div className = 'prejoin-input-area-container'>
378
-                        <div className = 'prejoin-input-area'>
379
-                            <label
380
-                                className = 'prejoin-input-area-label'
381
-                                htmlFor = { 'Prejoin-input-field-id' } >
382
-                                { t('dialog.enterDisplayNameToJoin') }</label>
383
-                            <InputField
384
-                                autoComplete = { 'name' }
385
-                                autoFocus = { true }
386
-                                className = { showError ? 'error' : '' }
387
-                                hasError = { showError }
388
-                                id = { 'Prejoin-input-field-id' }
389
-                                onChange = { _setName }
390
-                                onSubmit = { joinConference }
391
-                                placeHolder = { t('dialog.enterDisplayName') }
392
-                                value = { name } />
393
-
394
-                            {showError && <div
395
-                                className = 'prejoin-error'
396
-                                data-testid = 'prejoin.errorMessage'>{t('prejoin.errorMissingName')}</div>}
397
-
398
-                            <div className = 'prejoin-preview-dropdown-container'>
399
-                                <InlineDialog
400
-                                    content = { <div className = 'prejoin-preview-dropdown-btns'>
401
-                                        <div
402
-                                            className = 'prejoin-preview-dropdown-btn'
403
-                                            data-testid = 'prejoin.joinWithoutAudio'
404
-                                            onClick = { joinConferenceWithoutAudio }
405
-                                            onKeyPress = { _onJoinConferenceWithoutAudioKeyPress }
406
-                                            role = 'button'
407
-                                            tabIndex = { 0 }>
408
-                                            <Icon
409
-                                                className = 'prejoin-preview-dropdown-icon'
410
-                                                size = { 24 }
411
-                                                src = { IconVolumeOff } />
412
-                                            { t('prejoin.joinWithoutAudio') }
413
-                                        </div>
414
-                                        {hasJoinByPhoneButton && <div
415
-                                            className = 'prejoin-preview-dropdown-btn'
416
-                                            onClick = { _showDialog }
417
-                                            onKeyPress = { _showDialogKeyPress }
418
-                                            role = 'button'
419
-                                            tabIndex = { 0 }>
420
-                                            <Icon
421
-                                                className = 'prejoin-preview-dropdown-icon'
422
-                                                data-testid = 'prejoin.joinByPhone'
423
-                                                size = { 24 }
424
-                                                src = { IconPhone } />
425
-                                            { t('prejoin.joinAudioByPhone') }
426
-                                        </div>}
427
-                                    </div> }
428
-                                    isOpen = { showJoinByPhoneButtons }
429
-                                    onClose = { _onDropdownClose }>
430
-                                    <ActionButton
431
-                                        OptionsIcon = { showJoinByPhoneButtons ? IconArrowUp : IconArrowDown }
432
-                                        ariaDropDownLabel = { t('prejoin.joinWithoutAudio') }
433
-                                        ariaLabel = { t('prejoin.joinMeeting') }
434
-                                        ariaPressed = { showJoinByPhoneButtons }
435
-                                        hasOptions = { true }
436
-                                        onClick = { _onJoinButtonClick }
437
-                                        onKeyPress = { _onJoinKeyPress }
438
-                                        onOptionsClick = { _onOptionsClick }
439
-                                        role = 'button'
440
-                                        tabIndex = { 0 }
441
-                                        testId = 'prejoin.joinMeeting'
442
-                                        type = 'primary'>
443
-                                        { t('prejoin.joinMeeting') }
444
-                                    </ActionButton>
445
-                                </InlineDialog>
446
-                            </div>
447
-                        </div>
327
+                videoTrack = { videoTrack }>
328
+                <div
329
+                    className = 'prejoin-input-area'
330
+                    data-testid = 'prejoin.screen'>
331
+                    <InputField
332
+                        autoComplete = { 'name' }
333
+                        autoFocus = { true }
334
+                        className = { showError ? 'error' : '' }
335
+                        hasError = { showError }
336
+                        onChange = { _setName }
337
+                        onSubmit = { joinConference }
338
+                        placeHolder = { t('dialog.enterDisplayName') }
339
+                        value = { name } />
340
+
341
+                    {showError && <div
342
+                        className = 'prejoin-error'
343
+                        data-testid = 'prejoin.errorMessage'>{t('prejoin.errorMissingName')}</div>}
344
+
345
+                    <div className = 'prejoin-preview-dropdown-container'>
346
+                        <InlineDialog
347
+                            content = { <div className = 'prejoin-preview-dropdown-btns'>
348
+                                <div
349
+                                    className = 'prejoin-preview-dropdown-btn'
350
+                                    data-testid = 'prejoin.joinWithoutAudio'
351
+                                    onClick = { joinConferenceWithoutAudio }
352
+                                    onKeyPress = { _onJoinConferenceWithoutAudioKeyPress }
353
+                                    role = 'button'
354
+                                    tabIndex = { 0 }>
355
+                                    <Icon
356
+                                        className = 'prejoin-preview-dropdown-icon'
357
+                                        size = { 24 }
358
+                                        src = { IconVolumeOff } />
359
+                                    { t('prejoin.joinWithoutAudio') }
360
+                                </div>
361
+                                {hasJoinByPhoneButton && <div
362
+                                    className = 'prejoin-preview-dropdown-btn'
363
+                                    onClick = { _showDialog }
364
+                                    onKeyPress = { _showDialogKeyPress }
365
+                                    role = 'button'
366
+                                    tabIndex = { 0 }>
367
+                                    <Icon
368
+                                        className = 'prejoin-preview-dropdown-icon'
369
+                                        data-testid = 'prejoin.joinByPhone'
370
+                                        size = { 24 }
371
+                                        src = { IconPhone } />
372
+                                    { t('prejoin.joinAudioByPhone') }
373
+                                </div>}
374
+                            </div> }
375
+                            isOpen = { showJoinByPhoneButtons }
376
+                            onClose = { _onDropdownClose }>
377
+                            <ActionButton
378
+                                OptionsIcon = { showJoinByPhoneButtons ? IconArrowUp : IconArrowDown }
379
+                                ariaDropDownLabel = { t('prejoin.joinWithoutAudio') }
380
+                                ariaLabel = { t('prejoin.joinMeeting') }
381
+                                ariaPressed = { showJoinByPhoneButtons }
382
+                                hasOptions = { true }
383
+                                onClick = { _onJoinButtonClick }
384
+                                onKeyPress = { _onJoinKeyPress }
385
+                                onOptionsClick = { _onOptionsClick }
386
+                                role = 'button'
387
+                                tabIndex = { 0 }
388
+                                testId = 'prejoin.joinMeeting'
389
+                                type = 'primary'>
390
+                                { t('prejoin.joinMeeting') }
391
+                            </ActionButton>
392
+                        </InlineDialog>
448 393
                     </div>
449
-                )}
394
+                </div>
450 395
                 { showDialog && (
451 396
                     <JoinByPhoneDialog
452 397
                         joinConferenceWithoutAudio = { joinConferenceWithoutAudio }
@@ -456,26 +401,13 @@ class Prejoin extends Component<Props, State> {
456 401
         );
457 402
     }
458 403
 
459
-    /**
460
-     * Renders the screen footer if any.
461
-     *
462
-     * @returns {React$Element}
463
-     */
464
-    _renderFooter() {
465
-        return this.props.deviceStatusVisible && <DeviceStatus />;
466
-    }
467
-
468 404
     /**
469 405
      * Renders the 'skip prejoin' button.
470 406
      *
471 407
      * @returns {React$Element}
472 408
      */
473 409
     _renderSkipPrejoinButton() {
474
-        const { buttonIsToggled, t, showSkipPrejoin } = this.props;
475
-
476
-        if (!showSkipPrejoin) {
477
-            return null;
478
-        }
410
+        const { buttonIsToggled, t } = this.props;
479 411
 
480 412
         return (
481 413
             <div className = 'prejoin-checkbox-container'>
@@ -493,22 +425,11 @@ class Prejoin extends Component<Props, State> {
493 425
  * Maps (parts of) the redux state to the React {@code Component} props.
494 426
  *
495 427
  * @param {Object} state - The redux state.
496
- * @param {Object} ownProps - The props passed to the component.
497 428
  * @returns {Object}
498 429
  */
499
-function mapStateToProps(state, ownProps): Object {
430
+function mapStateToProps(state): Object {
500 431
     const name = getDisplayName(state);
501 432
     const showErrorOnJoin = isDisplayNameRequired(state) && !name;
502
-    const { showJoinActions } = ownProps;
503
-    const isInviteButtonEnabled = isButtonEnabled('invite', state);
504
-
505
-    // Hide conference info when interfaceConfig is available and the invite button is disabled.
506
-    // In all other cases we want to preserve the behaviour and control the the conference info
507
-    // visibility through showJoinActions.
508
-    const showConferenceInfo
509
-        = typeof isInviteButtonEnabled === 'undefined' || isInviteButtonEnabled === true
510
-            ? showJoinActions
511
-            : false;
512 433
 
513 434
     return {
514 435
         buttonIsToggled: isPrejoinSkipped(state),
@@ -519,9 +440,7 @@ function mapStateToProps(state, ownProps): Object {
519 440
         showErrorOnJoin,
520 441
         hasJoinByPhoneButton: isJoinByPhoneButtonVisible(state),
521 442
         showCameraPreview: !isVideoMutedByUser(state),
522
-        showConferenceInfo,
523
-        videoTrack: getLocalJitsiVideoTrack(state),
524
-        visibleButtons: getToolbarButtons(state)
443
+        videoTrack: getLocalJitsiVideoTrack(state)
525 444
     };
526 445
 }
527 446
 

+ 18
- 25
react/features/prejoin/components/PrejoinApp.js View File

@@ -9,27 +9,18 @@ import { getConferenceOptions } from '../../base/conference/functions';
9 9
 import { setConfig } from '../../base/config';
10 10
 import { DialogContainer } from '../../base/dialog';
11 11
 import { createPrejoinTracks } from '../../base/tracks';
12
+import JitsiThemeProvider from '../../base/ui/components/JitsiThemeProvider';
12 13
 import { initPrejoin, makePrecallTest } from '../actions';
13 14
 
14
-import Prejoin from './Prejoin';
15
+import PrejoinThirdParty from './PrejoinThirdParty';
15 16
 
16 17
 type Props = {
17 18
 
18 19
     /**
19
-     * Indicates whether the avatar should be shown when video is off
20
+     * Indicates the style type that needs to be applied.
20 21
      */
21
-    showAvatar: boolean,
22
-
23
-    /**
24
-     * Flag signaling the visibility of join label, input and buttons
25
-     */
26
-    showJoinActions: boolean,
27
-
28
-    /**
29
-     * Flag signaling the visibility of the skip prejoin toggle
30
-     */
31
-    showSkipPrejoin: boolean,
32
-};
22
+    styleType: string
23
+}
33 24
 
34 25
 /**
35 26
  * Wrapper application for prejoin.
@@ -50,14 +41,12 @@ export default class PrejoinApp extends BaseApp<Props> {
50 41
         this._init.then(async () => {
51 42
             const { store } = this.state;
52 43
             const { dispatch } = store;
53
-            const { showAvatar, showJoinActions, showSkipPrejoin } = this.props;
44
+            const { styleType } = this.props;
54 45
 
55 46
             super._navigate({
56
-                component: Prejoin,
47
+                component: PrejoinThirdParty,
57 48
                 props: {
58
-                    showAvatar,
59
-                    showJoinActions,
60
-                    showSkipPrejoin
49
+                    className: styleType
61 50
                 }
62 51
             });
63 52
 
@@ -88,9 +77,11 @@ export default class PrejoinApp extends BaseApp<Props> {
88 77
      */
89 78
     _createMainElement(component, props) {
90 79
         return (
91
-            <AtlasKitThemeProvider mode = 'dark'>
92
-                { super._createMainElement(component, props) }
93
-            </AtlasKitThemeProvider>
80
+            <JitsiThemeProvider>
81
+                <AtlasKitThemeProvider mode = 'dark'>
82
+                    { super._createMainElement(component, props) }
83
+                </AtlasKitThemeProvider>
84
+            </JitsiThemeProvider>
94 85
         );
95 86
     }
96 87
 
@@ -101,9 +92,11 @@ export default class PrejoinApp extends BaseApp<Props> {
101 92
      */
102 93
     _renderDialogContainer() {
103 94
         return (
104
-            <AtlasKitThemeProvider mode = 'dark'>
105
-                <DialogContainer />
106
-            </AtlasKitThemeProvider>
95
+            <JitsiThemeProvider>
96
+                <AtlasKitThemeProvider mode = 'dark'>
97
+                    <DialogContainer />
98
+                </AtlasKitThemeProvider>
99
+            </JitsiThemeProvider>
107 100
         );
108 101
     }
109 102
 }

+ 87
- 0
react/features/prejoin/components/PrejoinThirdParty.js View File

@@ -0,0 +1,87 @@
1
+// @flow
2
+
3
+import React, { Component } from 'react';
4
+
5
+import { translate } from '../../base/i18n';
6
+import { isVideoMutedByUser } from '../../base/media';
7
+import { PreMeetingScreen } from '../../base/premeeting';
8
+import { connect } from '../../base/redux';
9
+import { getLocalJitsiVideoTrack } from '../../base/tracks';
10
+import { isDeviceStatusVisible } from '../functions';
11
+
12
+type Props = {
13
+
14
+    /**
15
+     * Indicates the className that needs to be applied.
16
+    */
17
+     className: string,
18
+
19
+    /**
20
+     * Flag signaling if the device status is visible or not.
21
+     */
22
+    deviceStatusVisible: boolean,
23
+
24
+    /**
25
+     * Flag signaling the visibility of camera preview.
26
+     */
27
+    showCameraPreview: boolean,
28
+
29
+    /**
30
+     * Used for translation.
31
+     */
32
+    t: Function,
33
+
34
+    /**
35
+     * The JitsiLocalTrack to display.
36
+     */
37
+    videoTrack: ?Object
38
+};
39
+
40
+const buttons = [ 'microphone', 'camera', 'select-background' ];
41
+
42
+/**
43
+ * This component is displayed before joining a meeting.
44
+ */
45
+class PrejoinThirdParty extends Component<Props> {
46
+    /**
47
+     * Implements React's {@link Component#render()}.
48
+     *
49
+     * @inheritdoc
50
+     * @returns {ReactElement}
51
+     */
52
+    render() {
53
+        const {
54
+            className,
55
+            deviceStatusVisible,
56
+            showCameraPreview,
57
+            videoTrack
58
+        } = this.props;
59
+
60
+        return (
61
+            <PreMeetingScreen
62
+                className = { `prejoin-third-party ${className}` }
63
+                showDeviceStatus = { deviceStatusVisible }
64
+                skipPrejoinButton = { false }
65
+                toolbarButtons = { buttons }
66
+                videoMuted = { !showCameraPreview }
67
+                videoTrack = { videoTrack } />
68
+        );
69
+    }
70
+}
71
+
72
+/**
73
+ * Maps (parts of) the redux state to the React {@code Component} props.
74
+ *
75
+ * @param {Object} state - The redux state.
76
+ * @param {Object} ownProps - The props passed to the component.
77
+ * @returns {Object}
78
+ */
79
+function mapStateToProps(state): Object {
80
+    return {
81
+        deviceStatusVisible: isDeviceStatusVisible(state),
82
+        showCameraPreview: !isVideoMutedByUser(state),
83
+        videoTrack: getLocalJitsiVideoTrack(state)
84
+    };
85
+}
86
+
87
+export default connect(mapStateToProps)(translate(PrejoinThirdParty));

+ 6
- 7
react/features/prejoin/components/preview/DeviceStatus.js View File

@@ -3,7 +3,7 @@
3 3
 import React from 'react';
4 4
 
5 5
 import { translate } from '../../../base/i18n';
6
-import { Icon, IconCheck, IconExclamation } from '../../../base/icons';
6
+import { Icon, IconCheckSolid, IconExclamation } from '../../../base/icons';
7 7
 import { connect } from '../../../base/redux';
8 8
 import {
9 9
     getDeviceStatusType,
@@ -38,11 +38,11 @@ export type Props = {
38 38
 const iconMap = {
39 39
     warning: {
40 40
         src: IconExclamation,
41
-        className: 'prejoin-preview-status--warning'
41
+        className: 'device-icon--warning'
42 42
     },
43 43
     ok: {
44
-        src: IconCheck,
45
-        className: 'prejoin-preview-status--ok'
44
+        src: IconCheckSolid,
45
+        className: 'device-icon--ok'
46 46
     }
47 47
 };
48 48
 
@@ -57,15 +57,14 @@ function DeviceStatus({ deviceStatusType, deviceStatusText, rawError, t }: Props
57 57
 
58 58
     return (
59 59
         <div
60
-            className = { `prejoin-preview-status ${className}` }
60
+            className = 'device-status'
61 61
             role = 'alert'
62 62
             tabIndex = { -1 }>
63 63
             <Icon
64
-                className = 'prejoin-preview-icon'
64
+                className = { `device-icon ${className}` }
65 65
                 size = { 16 }
66 66
                 src = { src } />
67 67
             <span
68
-                className = 'prejoin-preview-error-desc'
69 68
                 role = 'heading'>
70 69
                 {t(deviceStatusText)}
71 70
             </span>

+ 19
- 0
react/features/prejoin/constants.js View File

@@ -0,0 +1,19 @@
1
+// @flow
2
+
3
+export type PREJOIN_SCREEN_STATE = "hidden" | "loading" | true;
4
+
5
+
6
+type PREJOIN_SCREEN_STATE_TYPE = {
7
+    HIDDEN: PREJOIN_SCREEN_STATE,
8
+    LOADING: PREJOIN_SCREEN_STATE,
9
+    VISIBLE: PREJOIN_SCREEN_STATE
10
+  }
11
+
12
+/**
13
+ * Enum of possible prejoin screen states.
14
+ */
15
+export const PREJOIN_SCREEN_STATES: PREJOIN_SCREEN_STATE_TYPE = {
16
+    HIDDEN: 'hidden',
17
+    LOADING: 'loading',
18
+    VISIBLE: true // backwards compatibility with old boolean implementation
19
+};

+ 13
- 1
react/features/prejoin/functions.js View File

@@ -4,6 +4,8 @@ import { getRoomName } from '../base/conference';
4 4
 import { getDialOutStatusUrl, getDialOutUrl } from '../base/config/functions';
5 5
 import { isAudioMuted, isVideoMutedByUser } from '../base/media';
6 6
 
7
+import { PREJOIN_SCREEN_STATES } from './constants';
8
+
7 9
 /**
8 10
  * Selector for the visibility of the 'join by phone' button.
9 11
  *
@@ -160,7 +162,17 @@ export function isPrejoinPageEnabled(state: Object): boolean {
160 162
  * @returns {boolean}
161 163
  */
162 164
 export function isPrejoinPageVisible(state: Object): boolean {
163
-    return isPrejoinPageEnabled(state) && state['features/prejoin']?.showPrejoin;
165
+    return isPrejoinPageEnabled(state) && state['features/prejoin']?.showPrejoin === PREJOIN_SCREEN_STATES.VISIBLE;
166
+}
167
+
168
+/**
169
+ * Returns true if the prejoin page is loading.
170
+ *
171
+ * @param {Object} state - The state of the app.
172
+ * @returns {boolean}
173
+ */
174
+export function isPrejoinPageLoading(state: Object): boolean {
175
+    return isPrejoinPageEnabled(state) && state['features/prejoin']?.showPrejoin === PREJOIN_SCREEN_STATES.LOADING;
164 176
 }
165 177
 
166 178
 /**

+ 20
- 2
react/features/prejoin/middleware.js View File

@@ -1,5 +1,6 @@
1 1
 // @flow
2 2
 
3
+import { CONFERENCE_JOINED } from '../base/conference';
3 4
 import { updateConfig } from '../base/config';
4 5
 import { SET_AUDIO_MUTED, SET_VIDEO_MUTED } from '../base/media';
5 6
 import { MiddlewareRegistry } from '../base/redux';
@@ -17,6 +18,7 @@ import {
17 18
     setDeviceStatusWarning,
18 19
     setPrejoinPageVisibility
19 20
 } from './actions';
21
+import { PREJOIN_SCREEN_STATES } from './constants';
20 22
 import { isPrejoinPageVisible } from './functions';
21 23
 
22 24
 declare var APP: Object;
@@ -56,7 +58,8 @@ MiddlewareRegistry.register(store => next => async action => {
56 58
 
57 59
         const jitsiTracks = localTracks.map(t => t.jitsiTrack);
58 60
 
59
-        dispatch(setPrejoinPageVisibility(false));
61
+        dispatch(setPrejoinPageVisibility(PREJOIN_SCREEN_STATES.LOADING));
62
+
60 63
         APP.conference.prejoinStart(jitsiTracks);
61 64
 
62 65
         break;
@@ -103,8 +106,23 @@ MiddlewareRegistry.register(store => next => async action => {
103 106
         }
104 107
         break;
105 108
     }
106
-
109
+    case CONFERENCE_JOINED:
110
+        return _conferenceJoined(store, next, action);
107 111
     }
108 112
 
109 113
     return next(action);
110 114
 });
115
+
116
+/**
117
+ * Handles cleanup of prejoin state when a conference is joined.
118
+ *
119
+ * @param {Object} store - The Redux store.
120
+ * @param {Function} next - The Redux next function.
121
+ * @param {Object} action - The Redux action.
122
+ * @returns {Object}
123
+ */
124
+function _conferenceJoined({ dispatch }, next, action) {
125
+    dispatch(setPrejoinPageVisibility(PREJOIN_SCREEN_STATES.HIDDEN));
126
+
127
+    return next(action);
128
+}

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

@@ -5,6 +5,7 @@ import { openDialog } from '../base/dialog';
5 5
 import { i18next } from '../base/i18n';
6 6
 import { updateSettings } from '../base/settings';
7 7
 import { setPrejoinPageVisibility } from '../prejoin/actions';
8
+import { PREJOIN_SCREEN_STATES } from '../prejoin/constants';
8 9
 import { setScreenshareFramerate } from '../screen-share/actions';
9 10
 
10 11
 import {
@@ -84,7 +85,7 @@ export function submitMoreTab(newState: Object): Function {
84 85
             // The 'showPrejoin' flag starts as 'true' on every new session.
85 86
             // This prevents displaying the prejoin page when the user re-enables it.
86 87
             if (showPrejoinPage && getState()['features/prejoin']?.showPrejoin) {
87
-                dispatch(setPrejoinPageVisibility(false));
88
+                dispatch(setPrejoinPageVisibility(PREJOIN_SCREEN_STATES.HIDDEN));
88 89
             }
89 90
             dispatch(updateSettings({
90 91
                 userSelectedSkipPrejoin: !showPrejoinPage

+ 29
- 14
react/features/toolbox/components/web/Toolbox.js View File

@@ -28,6 +28,7 @@ import { DominantSpeakerName } from '../../../display-name';
28 28
 import { EmbedMeetingButton } from '../../../embed-meeting';
29 29
 import { SharedDocumentButton } from '../../../etherpad';
30 30
 import { FeedbackButton } from '../../../feedback';
31
+import { InviteButton } from '../../../invite/components/add-people-dialog';
31 32
 import { isVpaasMeeting } from '../../../jaas/functions';
32 33
 import { KeyboardShortcutsButton } from '../../../keyboard-shortcuts';
33 34
 import { LocalRecordingButton } from '../../../local-recording';
@@ -66,7 +67,6 @@ import { VideoQualityDialog, VideoQualityButton } from '../../../video-quality/c
66 67
 import { VideoBackgroundButton } from '../../../virtual-background';
67 68
 import { toggleBackgroundEffect } from '../../../virtual-background/actions';
68 69
 import { VIRTUAL_BACKGROUND_TYPE } from '../../../virtual-background/constants';
69
-import { checkBlurSupport } from '../../../virtual-background/functions';
70 70
 import {
71 71
     setFullScreen,
72 72
     setOverflowMenuVisible,
@@ -207,11 +207,6 @@ type Props = {
207 207
      */
208 208
     _visible: boolean,
209 209
 
210
-    /**
211
-     * Array with the buttons which this Toolbox should display.
212
-     */
213
-    _visibleButtons: Array<string>,
214
-
215 210
     /**
216 211
      * Returns the selected virtual source object.
217 212
      */
@@ -230,7 +225,12 @@ type Props = {
230 225
     /**
231 226
      * Invoked to obtain translated strings.
232 227
      */
233
-    t: Function
228
+    t: Function,
229
+
230
+    /**
231
+     * Explicitly passed array with the buttons which this Toolbox should display.
232
+     */
233
+    toolbarButtons: Array<string>,
234 234
 };
235 235
 
236 236
 declare var APP: Object;
@@ -399,9 +399,9 @@ class Toolbox extends Component<Props> {
399 399
      * @returns {ReactElement}
400 400
      */
401 401
     render() {
402
-        const { _chatOpen, _visible, _visibleButtons } = this.props;
402
+        const { _chatOpen, _visible, _toolbarButtons } = this.props;
403 403
         const rootClassNames = `new-toolbox ${_visible ? 'visible' : ''} ${
404
-            _visibleButtons.length ? '' : 'no-buttons'} ${_chatOpen ? 'shift-right' : ''}`;
404
+            _toolbarButtons.length ? '' : 'no-buttons'} ${_chatOpen ? 'shift-right' : ''}`;
405 405
 
406 406
         return (
407 407
             <div
@@ -598,12 +598,17 @@ class Toolbox extends Component<Props> {
598 598
 
599 599
         const participants = {
600 600
             key: 'participants-pane',
601
-            alias: 'invite',
602 601
             Content: ParticipantsPaneButton,
603 602
             handleClick: this._onToolbarToggleParticipantsPane,
604 603
             group: 2
605 604
         };
606 605
 
606
+        const invite = {
607
+            key: 'invite',
608
+            Content: InviteButton,
609
+            group: 2
610
+        };
611
+
607 612
         const tileview = {
608 613
             key: 'tileview',
609 614
             Content: TileViewButton,
@@ -691,7 +696,7 @@ class Toolbox extends Component<Props> {
691 696
             group: 3
692 697
         };
693 698
 
694
-        const virtualBackground = !_screenSharing && checkBlurSupport() && {
699
+        const virtualBackground = !_screenSharing && {
695 700
             key: 'select-background',
696 701
             Content: VideoBackgroundButton,
697 702
             group: 3
@@ -747,6 +752,7 @@ class Toolbox extends Component<Props> {
747 752
             chat,
748 753
             raisehand,
749 754
             participants,
755
+            invite,
750 756
             tileview,
751 757
             toggleCamera,
752 758
             videoQuality,
@@ -1238,10 +1244,11 @@ class Toolbox extends Component<Props> {
1238 1244
  * props.
1239 1245
  *
1240 1246
  * @param {Object} state - The redux store/state.
1247
+ * @param {Object} ownProps - The props explicitly passed.
1241 1248
  * @private
1242 1249
  * @returns {{}}
1243 1250
  */
1244
-function _mapStateToProps(state) {
1251
+function _mapStateToProps(state, ownProps) {
1245 1252
     const { conference } = state['features/base/conference'];
1246 1253
     let desktopSharingEnabled = JitsiMeetJS.isDesktopSharingEnabled();
1247 1254
     const {
@@ -1268,6 +1275,15 @@ function _mapStateToProps(state) {
1268 1275
         }
1269 1276
     }
1270 1277
 
1278
+    let { toolbarButtons } = ownProps;
1279
+    const stateToolbarButtons = getToolbarButtons(state);
1280
+
1281
+    if (toolbarButtons) {
1282
+        toolbarButtons = toolbarButtons.filter(name => isToolbarButtonEnabled(name, stateToolbarButtons));
1283
+    } else {
1284
+        toolbarButtons = stateToolbarButtons;
1285
+    }
1286
+
1271 1287
     return {
1272 1288
         _chatOpen: state['features/chat'].isOpen,
1273 1289
         _clientWidth: clientWidth,
@@ -1289,9 +1305,8 @@ function _mapStateToProps(state) {
1289 1305
         _participantsPaneOpen: getParticipantsPaneOpen(state),
1290 1306
         _raisedHand: localParticipant?.raisedHand,
1291 1307
         _screenSharing: isScreenVideoShared(state),
1292
-        _toolbarButtons: getToolbarButtons(state),
1308
+        _toolbarButtons: toolbarButtons,
1293 1309
         _visible: isToolboxVisible(state),
1294
-        _visibleButtons: getToolbarButtons(state),
1295 1310
         _reactionsEnabled: enableReactions
1296 1311
     };
1297 1312
 }

+ 3
- 1
react/features/virtual-background/components/VideoBackgroundButton.js View File

@@ -6,6 +6,7 @@ import { IconVirtualBackground } from '../../base/icons';
6 6
 import { connect } from '../../base/redux';
7 7
 import { AbstractButton } from '../../base/toolbox/components';
8 8
 import type { AbstractButtonProps } from '../../base/toolbox/components';
9
+import { checkBlurSupport } from '../functions';
9 10
 
10 11
 import { VirtualBackgroundDialog } from './index';
11 12
 
@@ -72,7 +73,8 @@ class VideoBackgroundButton extends AbstractButton<Props, *> {
72 73
 function _mapStateToProps(state): Object {
73 74
 
74 75
     return {
75
-        _isBackgroundEnabled: Boolean(state['features/virtual-background'].backgroundEffectEnabled)
76
+        _isBackgroundEnabled: Boolean(state['features/virtual-background'].backgroundEffectEnabled),
77
+        visible: checkBlurSupport()
76 78
     };
77 79
 }
78 80
 

+ 2
- 6
static/prejoin.html View File

@@ -13,16 +13,12 @@
13 13
 
14 14
           const url = new URL(window.location.href);
15 15
           const params = new URLSearchParams(url.search);
16
-          const showAvatar = params.get('showAvatar') === 'true';
17
-          const showJoinActions = params.get('showJoinActions') === 'true';
18
-          const showSkipPrejoin = params.get('showSkipPrejoin') === 'true';
16
+          const styleType = params.get('styleType');
19 17
 
20 18
           JitsiMeetJS.app.renderEntryPoint({
21 19
               Component: JitsiMeetJS.app.entryPoints.PREJOIN,
22 20
               props: {
23
-                showAvatar,
24
-                showJoinActions,
25
-                showSkipPrejoin
21
+                styleType
26 22
               }
27 23
           })
28 24
       })

Loading…
Cancel
Save