浏览代码

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 年前
父节点
当前提交
1ad9046a38
没有帐户链接到提交者的电子邮件
共有 56 个文件被更改,包括 858 次插入800 次删除
  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 查看文件

206
     bottom: 0;
206
     bottom: 0;
207
     width: 35%;
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 查看文件

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 查看文件

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

+ 6
- 0
css/_variables.scss 查看文件

264
 */
264
 */
265
 $smallScreen: 700px;
265
 $smallScreen: 700px;
266
 $verySmallScreen: 500px;
266
 $verySmallScreen: 500px;
267
+
268
+/**
269
+* Prejoin / premeeting screen
270
+*/
271
+
272
+$prejoinDefaultContentWidth: 336px;

+ 1
- 5
css/main.scss 查看文件

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

css/_connection-status.scss → css/premeeting/_connection-status.scss 查看文件

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

+ 35
- 0
css/premeeting/_device-status.scss 查看文件

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 查看文件

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
 
71
 
69
     button {
72
     button {
70
         align-self: stretch;
73
         align-self: stretch;
71
-        margin: 8px 0;
74
+        margin-bottom: 8px 0;
72
         padding: 12px;
75
         padding: 12px;
73
         transition: .2s transform ease;
76
         transition: .2s transform ease;
74
 
77
 

+ 7
- 0
css/premeeting/_main.scss 查看文件

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 查看文件


+ 39
- 0
css/premeeting/_prejoin-third-party.scss 查看文件

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 查看文件

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 查看文件

1
-/**
2
- * Shared style for full screen local track based dialogs/modals.
3
- */
4
  .premeeting-screen {
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
     display: flex;
4
     display: flex;
16
-    flex-direction: column;
17
     font-size: 1.3em;
5
     font-size: 1.3em;
6
+    left: 0;
7
+    position: absolute;
8
+    right: 0;
9
+    top: 0;
18
     z-index: $toolbarZ + 1;
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
     .action-btn {
12
     .action-btn {
32
-        border-radius: 3px;
13
+        border-radius: 6px;
33
         box-sizing: border-box;
14
         box-sizing: border-box;
34
         color: #fff;
15
         color: #fff;
35
         cursor: pointer;
16
         cursor: pointer;
36
         display: inline-block;
17
         display: inline-block;
37
-        font-size: 15px;
18
+        font-size: 14px;
38
         line-height: 24px;
19
         line-height: 24px;
20
+        margin-bottom: 16px;
39
         padding: 7px 16px;
21
         padding: 7px 16px;
40
         position: relative;
22
         position: relative;
41
         text-align: center;
23
         text-align: center;
42
-        width: 320px;
43
-
44
-        @include adjust-for-max-width(320px, 8px);
24
+        width: 100%;
45
 
25
 
46
         &.primary {
26
         &.primary {
47
             background: #0376DA;
27
             background: #0376DA;
49
         }
29
         }
50
 
30
 
51
         &.secondary {
31
         &.secondary {
52
-            background: transparent;
53
-            border: 1px solid #5E6D7A;
32
+            background: #3D3D3D;
33
+            border: 1px solid transparent;
54
         }
34
         }
55
 
35
 
56
         &.text {
36
         &.text {
96
 
76
 
97
     .content {
77
     .content {
98
         align-items: center;
78
         align-items: center;
79
+        box-sizing: border-box;
99
         display: flex;
80
         display: flex;
100
-        flex: 1;
101
         flex-direction: column;
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
         z-index: $toolbarZ + 2;
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
             align-items: center;
91
             align-items: center;
115
-            cursor: pointer;
116
-            color: #fff;
117
             display: flex;
92
             display: flex;
118
             flex-direction: column;
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
 #preview {
202
 #preview {
203
+    background: #040404;
204
+    display: flex;
205
+    align-items: center;
206
+    justify-content: center;
205
     height: 100%;
207
     height: 100%;
206
-    position: absolute;
207
     width: 100%;
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
     .avatar {
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
     video {
220
     video {
220
         height: 100%;
221
         height: 100%;
221
         object-fit: cover;
222
         object-fit: cover;
222
-        position: absolute;
223
         width: 100%;
223
         width: 100%;
224
     }
224
     }
225
 }
225
 }
241
 }
241
 }
242
 
242
 
243
 .toggle-button {
243
 .toggle-button {
244
-    border-radius: 3px;
244
+    border-radius: 6px;
245
     cursor: pointer;
245
     cursor: pointer;
246
     color: #fff;
246
     color: #fff;
247
     font-size: 13px;
247
     font-size: 13px;
248
     height: 40px;
248
     height: 40px;
249
     margin: 0 auto;
249
     margin: 0 auto;
250
     transition: background 0.16s ease-out;
250
     transition: background 0.16s ease-out;
251
-    width: 320px;
252
 
251
 
253
-    @include adjust-for-max-width(320px, 8px);
254
     @include flex-centered();
252
     @include flex-centered();
255
 
253
 
256
     svg {
254
     svg {

+ 0
- 1
lang/main-de.json 查看文件

209
         "e2eeLabel": "Ende-zu-Ende-Verschlüsselung aktivieren",
209
         "e2eeLabel": "Ende-zu-Ende-Verschlüsselung aktivieren",
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.",
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
         "enterDisplayName": "Bitte geben Sie hier Ihren Namen ein",
211
         "enterDisplayName": "Bitte geben Sie hier Ihren Namen ein",
212
-        "enterDisplayNameToJoin" : "Benutzername für Konferenz eingeben" ,
213
         "embedMeeting": "Besprechung einbetten",
212
         "embedMeeting": "Besprechung einbetten",
214
         "error": "Fehler",
213
         "error": "Fehler",
215
         "gracefulShutdown": "Der Dienst steht momentan wegen Wartungsarbeiten nicht zur Verfügung. Bitte versuchen Sie es später noch einmal.",
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 查看文件

179
         "e2eeLabel": "Ŝlosilo",
179
         "e2eeLabel": "Ŝlosilo",
180
         "e2eeTitle": "Tutvoja ĉifrado",
180
         "e2eeTitle": "Tutvoja ĉifrado",
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>",
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
         "error": "Eraro",
183
         "error": "Eraro",
184
         "externalInstallationMsg": "Vi devas instali nian ekranvidadan kromprogramon.",
184
         "externalInstallationMsg": "Vi devas instali nian ekranvidadan kromprogramon.",
185
         "externalInstallationTitle": "Kromprogramo bezonata",
185
         "externalInstallationTitle": "Kromprogramo bezonata",

+ 0
- 1
lang/main-eu.json 查看文件

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

+ 0
- 1
lang/main-fr.json 查看文件

213
     "e2eeLabel": "Activer le chiffrement de Bout-en-Bout",
213
     "e2eeLabel": "Activer le chiffrement de Bout-en-Bout",
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.",
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
     "enterDisplayName": "Merci de saisir votre nom ici",
215
     "enterDisplayName": "Merci de saisir votre nom ici",
216
-    "enterDisplayNameToJoin": "Merci de saisir votre nom pour rejoindre",
217
     "embedMeeting": "Intégrer la réunion",
216
     "embedMeeting": "Intégrer la réunion",
218
     "error": "Erreur",
217
     "error": "Erreur",
219
     "gracefulShutdown": "Notre service est actuellement en maintenance. Veuillez réessayer plus tard.",
218
     "gracefulShutdown": "Notre service est actuellement en maintenance. Veuillez réessayer plus tard.",

+ 1
- 1
lang/main-id.json 查看文件

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

+ 0
- 1
lang/main-pt.json 查看文件

197
         "e2eeLabel": "Habilitar encriptação de ponta a ponta",
197
         "e2eeLabel": "Habilitar encriptação de ponta a ponta",
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.",
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
         "enterDisplayName": "Digite o seu nome aqui",
199
         "enterDisplayName": "Digite o seu nome aqui",
200
-        "enterDisplayNameToJoin": "Por favor, digite o seu nome para participar",
201
         "embedMeeting": "Embutir reunião",
200
         "embedMeeting": "Embutir reunião",
202
         "error": "Erro",
201
         "error": "Erro",
203
         "gracefulShutdown": "O nosso serviço está atualmente em manutenção. Por favor, tente novamente mais tarde.",
202
         "gracefulShutdown": "O nosso serviço está atualmente em manutenção. Por favor, tente novamente mais tarde.",

+ 0
- 1
lang/main-ptBR.json 查看文件

209
         "e2eeLabel": "Enable End-to-End Encryption",
209
         "e2eeLabel": "Enable End-to-End Encryption",
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.",
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
         "enterDisplayName": "Digite seu nome aqui",
211
         "enterDisplayName": "Digite seu nome aqui",
212
-        "enterDisplayNameToJoin": "Digite seu nome para participar",
213
         "embedMeeting": "Reunião em formato compacto",
212
         "embedMeeting": "Reunião em formato compacto",
214
         "error": "Erro",
213
         "error": "Erro",
215
         "gracefulShutdown": "Nosso serviço está em manutenção. Tente novamente mais tarde.",
214
         "gracefulShutdown": "Nosso serviço está em manutenção. Tente novamente mais tarde.",

+ 0
- 1
lang/main-sq.json 查看文件

209
         "e2eeLabel": "Aktivizo Fshehtëzim Skaj-më-Skaj",
209
         "e2eeLabel": "Aktivizo Fshehtëzim Skaj-më-Skaj",
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ë.",
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
         "enterDisplayName": "Ju lutemi, jepni këtu emrin tuaj",
211
         "enterDisplayName": "Ju lutemi, jepni këtu emrin tuaj",
212
-        "enterDisplayNameToJoin": "Që të merrni pjesë, ju lutemi, jepni emrin tuaj",
213
         "embedMeeting": "Trupëzoni takim",
212
         "embedMeeting": "Trupëzoni takim",
214
         "error": "Gabim",
213
         "error": "Gabim",
215
         "gracefulShutdown": "Shërbimi ynë është aktualisht i ndërprerë, për punë mirëmbajtjeje. Ju lutemi, riprovoni më vonë.",
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 查看文件

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

+ 4
- 4
lang/main.json 查看文件

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.",
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
         "e2eeLabel": "Enable End-to-End Encryption",
213
         "e2eeLabel": "Enable End-to-End Encryption",
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.",
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
         "embedMeeting": "Embed meeting",
216
         "embedMeeting": "Embed meeting",
218
         "error": "Error",
217
         "error": "Error",
219
         "gracefulShutdown": "Our service is currently down for maintenance. Please try again later.",
218
         "gracefulShutdown": "Our service is currently down for maintenance. Please try again later.",
694
         "joinWithoutAudio": "Join without audio",
693
         "joinWithoutAudio": "Join without audio",
695
         "initiated": "Call initiated",
694
         "initiated": "Call initiated",
696
         "linkCopied": "Link copied to clipboard",
695
         "linkCopied": "Link copied to clipboard",
697
-        "lookGood": "It sounds like your microphone is working properly",
696
+        "lookGood": "Your microphone is working properly",
698
         "or": "or",
697
         "or": "or",
699
         "premeeting": "Pre meeting",
698
         "premeeting": "Pre meeting",
700
         "showScreen": "Enable pre meeting screen",
699
         "showScreen": "Enable pre meeting screen",
1121
         "admitAll": "Admit all",
1120
         "admitAll": "Admit all",
1122
         "knockingParticipantList": "Knocking participant list",
1121
         "knockingParticipantList": "Knocking participant list",
1123
         "allow": "Allow",
1122
         "allow": "Allow",
1124
-        "backToKnockModeButton": "No password, ask to join instead",
1123
+        "backToKnockModeButton": "Ask to join",
1125
         "dialogTitle": "Lobby mode",
1124
         "dialogTitle": "Lobby mode",
1126
         "disableDialogContent": "Lobby mode is currently enabled. This feature ensures that unwanted participants can't join your meeting. Do you want to disable it?",
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
         "disableDialogSubmit": "Disable",
1126
         "disableDialogSubmit": "Disable",
1131
         "enableDialogText": "Lobby mode lets you protect your meeting by only allowing people to enter after a formal approval by a moderator.",
1130
         "enableDialogText": "Lobby mode lets you protect your meeting by only allowing people to enter after a formal approval by a moderator.",
1132
         "enterPasswordButton": "Enter meeting password",
1131
         "enterPasswordButton": "Enter meeting password",
1133
         "enterPasswordTitle": "Enter password to join meeting",
1132
         "enterPasswordTitle": "Enter password to join meeting",
1133
+        "errorMissingPassword": "Please enter the meeting password",
1134
         "invalidPassword": "Invalid password",
1134
         "invalidPassword": "Invalid password",
1135
         "joiningMessage": "You'll join the meeting as soon as someone accepts your request",
1135
         "joiningMessage": "You'll join the meeting as soon as someone accepts your request",
1136
         "joinWithPasswordMessage": "Trying to join with password, please wait...",
1136
         "joinWithPasswordMessage": "Trying to join with password, please wait...",

+ 5
- 3
react/features/app/components/App.web.js 查看文件

43
      */
43
      */
44
     _renderDialogContainer() {
44
     _renderDialogContainer() {
45
         return (
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 查看文件

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

+ 3
- 0
react/features/base/icons/svg/check-solid.svg 查看文件

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 查看文件

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

+ 26
- 28
react/features/base/premeeting/components/web/ConnectionStatus.js 查看文件

79
 
79
 
80
     return (
80
     return (
81
         <div className = 'con-status'>
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
                     <Icon
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
                 </div>
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
             </div>
105
             </div>
106
+            <div
107
+                aria-level = '2'
108
+                className = { `con-status-details ${detailsClassName}` }
109
+                role = 'heading'>
110
+                {detailsText}</div>
113
         </div>
111
         </div>
114
     );
112
     );
115
 }
113
 }

+ 0
- 67
react/features/base/premeeting/components/web/CopyMeetingUrl.js 查看文件

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 查看文件

2
 
2
 
3
 import React, { PureComponent } from 'react';
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
 import ConnectionStatus from './ConnectionStatus';
8
 import ConnectionStatus from './ConnectionStatus';
12
-import CopyMeetingUrl from './CopyMeetingUrl';
13
 import Preview from './Preview';
9
 import Preview from './Preview';
14
 
10
 
15
 type Props = {
11
 type Props = {
17
     /**
13
     /**
18
      * Children component(s) to be rendered on the screen.
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
      * The name of the participant.
24
      * The name of the participant.
35
     showCopyUrlButton: boolean,
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
      * Title of the screen.
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
      * True if the preview overlay should be muted, false otherwise.
54
      * True if the preview overlay should be muted, false otherwise.
62
     /**
58
     /**
63
      * The video track to render as preview (if omitted, the default local track will be rendered).
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
  * Implements a pre-meeting screen that can be used at various pre-meeting phases, for example
67
  * Implements a pre-meeting screen that can be used at various pre-meeting phases, for example
75
  * on the prejoin screen (pre-connection) or lobby (post-connection).
68
  * on the prejoin screen (pre-connection) or lobby (post-connection).
81
      * @static
74
      * @static
82
      */
75
      */
83
     static defaultProps = {
76
     static defaultProps = {
84
-        showAvatar: true,
85
         showCopyUrlButton: true,
77
         showCopyUrlButton: true,
86
-        showConferenceInfo: true
78
+        showToolbox: true
87
     };
79
     };
88
 
80
 
89
     /**
81
     /**
93
      */
85
      */
94
     render() {
86
     render() {
95
         const {
87
         const {
96
-            name,
97
-            showAvatar,
98
-            showConferenceInfo,
99
-            showCopyUrlButton,
88
+            children,
89
+            className,
90
+            showDeviceStatus,
91
+            skipPrejoinButton,
100
             title,
92
             title,
93
+            toolbarButtons,
101
             videoMuted,
94
             videoMuted,
102
-            videoTrack,
103
-            visibleButtons
95
+            videoTrack
104
         } = this.props;
96
         } = this.props;
105
-        const showSharingButton = allowUrlSharing() && showCopyUrlButton;
97
+
98
+        const containerClassName = `premeeting-screen ${className ? className : ''}`;
106
 
99
 
107
         return (
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
                 <div className = 'content'>
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
                     </div>
113
                     </div>
144
-                    { this.props.skipPrejoinButton }
145
-                    { this.props.footer }
146
                 </div>
114
                 </div>
115
+
116
+                <Preview
117
+                    videoMuted = { videoMuted }
118
+                    videoTrack = { videoTrack } />
147
             </div>
119
             </div>
148
         );
120
         );
149
     }
121
     }

+ 37
- 12
react/features/base/premeeting/components/web/Preview.js 查看文件

2
 
2
 
3
 import React from 'react';
3
 import React from 'react';
4
 
4
 
5
+import { getDisplayName } from '../../../../base/settings';
6
+import { Avatar } from '../../../avatar';
5
 import { Video } from '../../../media';
7
 import { Video } from '../../../media';
8
+import { getLocalParticipant } from '../../../participants';
6
 import { connect } from '../../../redux';
9
 import { connect } from '../../../redux';
7
 import { getLocalVideoTrack } from '../../../tracks';
10
 import { getLocalVideoTrack } from '../../../tracks';
8
 
11
 
9
 export type Props = {
12
 export type Props = {
10
 
13
 
14
+    /**
15
+     * Local participant id
16
+     */
17
+    _participantId: string,
18
+
11
     /**
19
     /**
12
      * Flag controlling whether the video should be flipped or not.
20
      * Flag controlling whether the video should be flipped or not.
13
      */
21
      */
14
     flipVideo: boolean,
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
      * Flag signaling the visibility of camera preview.
30
      * Flag signaling the visibility of camera preview.
18
      */
31
      */
31
  * @returns {ReactElement}
44
  * @returns {ReactElement}
32
  */
45
  */
33
 function Preview(props: Props) {
46
 function Preview(props: Props) {
34
-    const { videoMuted, videoTrack, flipVideo } = props;
47
+    const { _participantId, flipVideo, name, videoMuted, videoTrack } = props;
35
     const className = flipVideo ? 'flipVideoX' : '';
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
  * @returns {Props}
75
  * @returns {Props}
56
  */
76
  */
57
 function _mapStateToProps(state, ownProps) {
77
 function _mapStateToProps(state, ownProps) {
78
+    const name = getDisplayName(state);
79
+    const { id: _participantId } = getLocalParticipant(state);
80
+
58
     return {
81
     return {
82
+        _participantId,
59
         flipVideo: state['features/base/settings'].localFlipX,
83
         flipVideo: state['features/base/settings'].localFlipX,
84
+        name,
60
         videoMuted: ownProps.videoTrack ? ownProps.videoMuted : state['features/base/media'].video.muted,
85
         videoMuted: ownProps.videoTrack ? ownProps.videoMuted : state['features/base/media'].video.muted,
61
         videoTrack: ownProps.videoTrack || (getLocalVideoTrack(state['features/base/tracks']) || {}).jitsiTrack
86
         videoTrack: ownProps.videoTrack || (getLocalVideoTrack(state['features/base/tracks']) || {}).jitsiTrack
62
     };
87
     };

+ 0
- 11
react/features/base/premeeting/functions.js 查看文件

213
         connectionDetails: []
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 查看文件

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

+ 12
- 11
react/features/conference/components/web/Conference.js 查看文件

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

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 查看文件

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

+ 5
- 0
react/features/lobby/actionTypes.js 查看文件

20
  */
20
  */
21
 export const SET_KNOCKING_STATE = 'SET_KNOCKING_STATE';
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
  * Action type to set the password join failed status.
29
  * Action type to set the password join failed status.
25
  */
30
  */

+ 26
- 0
react/features/lobby/actions.any.js 查看文件

6
     getCurrentConference
6
     getCurrentConference
7
 } from '../base/conference';
7
 } from '../base/conference';
8
 
8
 
9
+import { SET_LOBBY_VISIBILITY } from './actionTypes';
10
+
9
 /**
11
 /**
10
  * Action to toggle lobby mode on or off.
12
  * Action to toggle lobby mode on or off.
11
  *
13
  *
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 查看文件

9
     sendLocalParticipant,
9
     sendLocalParticipant,
10
     setPassword
10
     setPassword
11
 } from '../base/conference';
11
 } from '../base/conference';
12
-import { hideDialog, openDialog } from '../base/dialog';
13
 import { getLocalParticipant } from '../base/participants';
12
 import { getLocalParticipant } from '../base/participants';
14
 export * from './actions.any';
13
 export * from './actions.any';
15
 
14
 
20
     SET_LOBBY_MODE_ENABLED,
19
     SET_LOBBY_MODE_ENABLED,
21
     SET_PASSWORD_JOIN_FAILED
20
     SET_PASSWORD_JOIN_FAILED
22
 } from './actionTypes';
21
 } from './actionTypes';
23
-import { LobbyScreen } from './components';
24
 
22
 
25
 declare var APP: Object;
23
 declare var APP: Object;
26
 
24
 
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
  * Tries to join with a preset password.
46
  * Tries to join with a preset password.
58
  *
47
  *
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
  * Action to be executed when a participant starts knocking or an already knocking participant gets updated.
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 查看文件

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

+ 10
- 9
react/features/lobby/components/native/LobbyScreen.js 查看文件

27
 
27
 
28
         return (
28
         return (
29
             <CustomDialog
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
             </CustomDialog>
40
             </CustomDialog>
40
         );
41
         );
41
     }
42
     }

+ 18
- 11
react/features/lobby/components/web/LobbyScreen.js 查看文件

20
      * @inheritdoc
20
      * @inheritdoc
21
      */
21
      */
22
     render() {
22
     render() {
23
-        const { showCopyUrlButton, t } = this.props;
23
+        const { _deviceStatusVisible, showCopyUrlButton, t } = this.props;
24
 
24
 
25
         return (
25
         return (
26
             <PreMeetingScreen
26
             <PreMeetingScreen
27
+                className = 'lobby-screen'
27
                 showCopyUrlButton = { showCopyUrlButton }
28
                 showCopyUrlButton = { showCopyUrlButton }
29
+                showDeviceStatus = { _deviceStatusVisible }
28
                 title = { t(this._getScreenTitleKey()) }>
30
                 title = { t(this._getScreenTitleKey()) }>
29
                 { this._renderContent() }
31
                 { this._renderContent() }
30
             </PreMeetingScreen>
32
             </PreMeetingScreen>
62
      */
64
      */
63
     _renderJoining() {
65
     _renderJoining() {
64
         return (
66
         return (
65
-            <div className = 'container'>
67
+            <div className = 'lobby-screen-content'>
66
                 <div className = 'spinner'>
68
                 <div className = 'spinner'>
67
                     <LoadingIndicator size = 'large' />
69
                     <LoadingIndicator size = 'large' />
68
                 </div>
70
                 </div>
113
         const { _passwordJoinFailed, t } = this.props;
115
         const { _passwordJoinFailed, t } = this.props;
114
 
116
 
115
         return (
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
         return (
142
         return (
135
             <>
143
             <>
136
                 <ActionButton
144
                 <ActionButton
137
-                    disabled = { !this.state.password }
138
                     onClick = { this._onJoinWithPassword }
145
                     onClick = { this._onJoinWithPassword }
139
                     testId = 'lobby.passwordJoinButton'
146
                     testId = 'lobby.passwordJoinButton'
140
                     type = 'primary'>
147
                     type = 'primary'>
141
-                    { t('lobby.passwordJoinButton') }
148
+                    { t('prejoin.joinMeeting') }
142
                 </ActionButton>
149
                 </ActionButton>
143
                 <ActionButton
150
                 <ActionButton
144
                     onClick = { this._onSwitchToKnockMode }
151
                     onClick = { this._onSwitchToKnockMode }

+ 10
- 0
react/features/lobby/functions.js 查看文件

20
     return state['features/lobby'].knockingParticipants;
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
  * Selector to return array with knocking participant ids.
34
  * Selector to return array with knocking participant ids.
25
  *
35
  *

+ 7
- 0
react/features/lobby/reducer.js 查看文件

8
     KNOCKING_PARTICIPANT_LEFT,
8
     KNOCKING_PARTICIPANT_LEFT,
9
     SET_KNOCKING_STATE,
9
     SET_KNOCKING_STATE,
10
     SET_LOBBY_MODE_ENABLED,
10
     SET_LOBBY_MODE_ENABLED,
11
+    SET_LOBBY_VISIBILITY,
11
     SET_PASSWORD_JOIN_FAILED
12
     SET_PASSWORD_JOIN_FAILED
12
 } from './actionTypes';
13
 } from './actionTypes';
13
 
14
 
15
     knocking: false,
16
     knocking: false,
16
     knockingParticipants: [],
17
     knockingParticipants: [],
17
     lobbyEnabled: false,
18
     lobbyEnabled: false,
19
+    lobbyVisible: false,
18
     passwordJoinFailed: false
20
     passwordJoinFailed: false
19
 };
21
 };
20
 
22
 
53
             ...state,
55
             ...state,
54
             lobbyEnabled: action.enabled
56
             lobbyEnabled: action.enabled
55
         };
57
         };
58
+    case SET_LOBBY_VISIBILITY:
59
+        return {
60
+            ...state,
61
+            lobbyVisible: action.visible
62
+        };
56
     case SET_PASSWORD:
63
     case SET_PASSWORD:
57
         return {
64
         return {
58
             ...state,
65
             ...state,

+ 3
- 2
react/features/prejoin/actions.js 查看文件

33
     SET_PREJOIN_DEVICE_ERRORS,
33
     SET_PREJOIN_DEVICE_ERRORS,
34
     SET_PREJOIN_PAGE_VISIBILITY
34
     SET_PREJOIN_PAGE_VISIBILITY
35
 } from './actionTypes';
35
 } from './actionTypes';
36
+import { type PREJOIN_SCREEN_STATE } from './constants';
36
 import {
37
 import {
37
     getFullDialOutNumber,
38
     getFullDialOutNumber,
38
     getDialOutConferenceUrl,
39
     getDialOutConferenceUrl,
480
 /**
481
 /**
481
  * Action used to set the visibility of the prejoin page.
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
  * @returns {Object}
485
  * @returns {Object}
485
  */
486
  */
486
-export function setPrejoinPageVisibility(value: boolean) {
487
+export function setPrejoinPageVisibility(value: PREJOIN_SCREEN_STATE) {
487
     return {
488
     return {
488
         type: SET_PREJOIN_PAGE_VISIBILITY,
489
         type: SET_PREJOIN_PAGE_VISIBILITY,
489
         value
490
         value

+ 74
- 155
react/features/prejoin/components/Prejoin.js 查看文件

4
 import React, { Component } from 'react';
4
 import React, { Component } from 'react';
5
 
5
 
6
 import { getRoomName } from '../../base/conference';
6
 import { getRoomName } from '../../base/conference';
7
-import { getToolbarButtons } from '../../base/config';
8
 import { translate } from '../../base/i18n';
7
 import { translate } from '../../base/i18n';
9
 import { Icon, IconArrowDown, IconArrowUp, IconPhone, IconVolumeOff } from '../../base/icons';
8
 import { Icon, IconArrowDown, IconArrowUp, IconPhone, IconVolumeOff } from '../../base/icons';
10
 import { isVideoMutedByUser } from '../../base/media';
9
 import { isVideoMutedByUser } from '../../base/media';
12
 import { connect } from '../../base/redux';
11
 import { connect } from '../../base/redux';
13
 import { getDisplayName, updateSettings } from '../../base/settings';
12
 import { getDisplayName, updateSettings } from '../../base/settings';
14
 import { getLocalJitsiVideoTrack } from '../../base/tracks';
13
 import { getLocalJitsiVideoTrack } from '../../base/tracks';
15
-import { isButtonEnabled } from '../../toolbox/functions.web';
16
 import {
14
 import {
17
     joinConference as joinConferenceAction,
15
     joinConference as joinConferenceAction,
18
     joinConferenceWithoutAudio as joinConferenceWithoutAudioAction,
16
     joinConferenceWithoutAudio as joinConferenceWithoutAudioAction,
28
 } from '../functions';
26
 } from '../functions';
29
 
27
 
30
 import JoinByPhoneDialog from './dialogs/JoinByPhoneDialog';
28
 import JoinByPhoneDialog from './dialogs/JoinByPhoneDialog';
31
-import DeviceStatus from './preview/DeviceStatus';
32
-
33
-declare var interfaceConfig: Object;
34
 
29
 
35
 type Props = {
30
 type Props = {
36
 
31
 
84
      */
79
      */
85
     setJoinByPhoneDialogVisiblity: Function,
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
      * Flag signaling the visibility of camera preview.
83
      * Flag signaling the visibility of camera preview.
94
      */
84
      */
99
      */
89
      */
100
     showErrorOnJoin: boolean,
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
      * If 'JoinByPhoneDialog' is visible or not.
93
      * If 'JoinByPhoneDialog' is visible or not.
114
      */
94
      */
115
     showDialog: boolean,
95
     showDialog: boolean,
116
 
96
 
117
-    /**
118
-     * Flag signaling the visibility of the skip prejoin toggle
119
-     */
120
-    showSkipPrejoin: boolean,
121
-
122
     /**
97
     /**
123
      * Used for translation.
98
      * Used for translation.
124
      */
99
      */
127
     /**
102
     /**
128
      * The JitsiLocalTrack to display.
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
 type State = {
108
 type State = {
152
  * This component is displayed before joining a meeting.
122
  * This component is displayed before joining a meeting.
153
  */
123
  */
154
 class Prejoin extends Component<Props, State> {
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
      * Initializes a new {@code Prejoin} instance.
126
      * Initializes a new {@code Prejoin} instance.
168
      *
127
      *
344
      */
303
      */
345
     render() {
304
     render() {
346
         const {
305
         const {
306
+            deviceStatusVisible,
347
             hasJoinByPhoneButton,
307
             hasJoinByPhoneButton,
348
             joinConference,
308
             joinConference,
349
             joinConferenceWithoutAudio,
309
             joinConferenceWithoutAudio,
350
             name,
310
             name,
351
-            showAvatar,
352
             showCameraPreview,
311
             showCameraPreview,
353
             showDialog,
312
             showDialog,
354
-            showConferenceInfo,
355
-            showJoinActions,
356
             t,
313
             t,
357
-            videoTrack,
358
-            visibleButtons
314
+            videoTrack
359
         } = this.props;
315
         } = this.props;
360
 
316
 
361
         const { _closeDialog, _onDropdownClose, _onJoinButtonClick, _onJoinKeyPress, _showDialogKeyPress,
317
         const { _closeDialog, _onDropdownClose, _onJoinButtonClick, _onJoinKeyPress, _showDialogKeyPress,
364
 
320
 
365
         return (
321
         return (
366
             <PreMeetingScreen
322
             <PreMeetingScreen
367
-                footer = { this._renderFooter() }
368
-                name = { name }
369
-                showAvatar = { showAvatar }
370
-                showConferenceInfo = { showConferenceInfo }
323
+                showDeviceStatus = { deviceStatusVisible }
371
                 skipPrejoinButton = { this._renderSkipPrejoinButton() }
324
                 skipPrejoinButton = { this._renderSkipPrejoinButton() }
372
                 title = { t('prejoin.joinMeeting') }
325
                 title = { t('prejoin.joinMeeting') }
373
                 videoMuted = { !showCameraPreview }
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
                     </div>
393
                     </div>
449
-                )}
394
+                </div>
450
                 { showDialog && (
395
                 { showDialog && (
451
                     <JoinByPhoneDialog
396
                     <JoinByPhoneDialog
452
                         joinConferenceWithoutAudio = { joinConferenceWithoutAudio }
397
                         joinConferenceWithoutAudio = { joinConferenceWithoutAudio }
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
      * Renders the 'skip prejoin' button.
405
      * Renders the 'skip prejoin' button.
470
      *
406
      *
471
      * @returns {React$Element}
407
      * @returns {React$Element}
472
      */
408
      */
473
     _renderSkipPrejoinButton() {
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
         return (
412
         return (
481
             <div className = 'prejoin-checkbox-container'>
413
             <div className = 'prejoin-checkbox-container'>
493
  * Maps (parts of) the redux state to the React {@code Component} props.
425
  * Maps (parts of) the redux state to the React {@code Component} props.
494
  *
426
  *
495
  * @param {Object} state - The redux state.
427
  * @param {Object} state - The redux state.
496
- * @param {Object} ownProps - The props passed to the component.
497
  * @returns {Object}
428
  * @returns {Object}
498
  */
429
  */
499
-function mapStateToProps(state, ownProps): Object {
430
+function mapStateToProps(state): Object {
500
     const name = getDisplayName(state);
431
     const name = getDisplayName(state);
501
     const showErrorOnJoin = isDisplayNameRequired(state) && !name;
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
     return {
434
     return {
514
         buttonIsToggled: isPrejoinSkipped(state),
435
         buttonIsToggled: isPrejoinSkipped(state),
519
         showErrorOnJoin,
440
         showErrorOnJoin,
520
         hasJoinByPhoneButton: isJoinByPhoneButtonVisible(state),
441
         hasJoinByPhoneButton: isJoinByPhoneButtonVisible(state),
521
         showCameraPreview: !isVideoMutedByUser(state),
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 查看文件

9
 import { setConfig } from '../../base/config';
9
 import { setConfig } from '../../base/config';
10
 import { DialogContainer } from '../../base/dialog';
10
 import { DialogContainer } from '../../base/dialog';
11
 import { createPrejoinTracks } from '../../base/tracks';
11
 import { createPrejoinTracks } from '../../base/tracks';
12
+import JitsiThemeProvider from '../../base/ui/components/JitsiThemeProvider';
12
 import { initPrejoin, makePrecallTest } from '../actions';
13
 import { initPrejoin, makePrecallTest } from '../actions';
13
 
14
 
14
-import Prejoin from './Prejoin';
15
+import PrejoinThirdParty from './PrejoinThirdParty';
15
 
16
 
16
 type Props = {
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
  * Wrapper application for prejoin.
26
  * Wrapper application for prejoin.
50
         this._init.then(async () => {
41
         this._init.then(async () => {
51
             const { store } = this.state;
42
             const { store } = this.state;
52
             const { dispatch } = store;
43
             const { dispatch } = store;
53
-            const { showAvatar, showJoinActions, showSkipPrejoin } = this.props;
44
+            const { styleType } = this.props;
54
 
45
 
55
             super._navigate({
46
             super._navigate({
56
-                component: Prejoin,
47
+                component: PrejoinThirdParty,
57
                 props: {
48
                 props: {
58
-                    showAvatar,
59
-                    showJoinActions,
60
-                    showSkipPrejoin
49
+                    className: styleType
61
                 }
50
                 }
62
             });
51
             });
63
 
52
 
88
      */
77
      */
89
     _createMainElement(component, props) {
78
     _createMainElement(component, props) {
90
         return (
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
      */
92
      */
102
     _renderDialogContainer() {
93
     _renderDialogContainer() {
103
         return (
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 查看文件

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 查看文件

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

+ 19
- 0
react/features/prejoin/constants.js 查看文件

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 查看文件

4
 import { getDialOutStatusUrl, getDialOutUrl } from '../base/config/functions';
4
 import { getDialOutStatusUrl, getDialOutUrl } from '../base/config/functions';
5
 import { isAudioMuted, isVideoMutedByUser } from '../base/media';
5
 import { isAudioMuted, isVideoMutedByUser } from '../base/media';
6
 
6
 
7
+import { PREJOIN_SCREEN_STATES } from './constants';
8
+
7
 /**
9
 /**
8
  * Selector for the visibility of the 'join by phone' button.
10
  * Selector for the visibility of the 'join by phone' button.
9
  *
11
  *
160
  * @returns {boolean}
162
  * @returns {boolean}
161
  */
163
  */
162
 export function isPrejoinPageVisible(state: Object): boolean {
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 查看文件

1
 // @flow
1
 // @flow
2
 
2
 
3
+import { CONFERENCE_JOINED } from '../base/conference';
3
 import { updateConfig } from '../base/config';
4
 import { updateConfig } from '../base/config';
4
 import { SET_AUDIO_MUTED, SET_VIDEO_MUTED } from '../base/media';
5
 import { SET_AUDIO_MUTED, SET_VIDEO_MUTED } from '../base/media';
5
 import { MiddlewareRegistry } from '../base/redux';
6
 import { MiddlewareRegistry } from '../base/redux';
17
     setDeviceStatusWarning,
18
     setDeviceStatusWarning,
18
     setPrejoinPageVisibility
19
     setPrejoinPageVisibility
19
 } from './actions';
20
 } from './actions';
21
+import { PREJOIN_SCREEN_STATES } from './constants';
20
 import { isPrejoinPageVisible } from './functions';
22
 import { isPrejoinPageVisible } from './functions';
21
 
23
 
22
 declare var APP: Object;
24
 declare var APP: Object;
56
 
58
 
57
         const jitsiTracks = localTracks.map(t => t.jitsiTrack);
59
         const jitsiTracks = localTracks.map(t => t.jitsiTrack);
58
 
60
 
59
-        dispatch(setPrejoinPageVisibility(false));
61
+        dispatch(setPrejoinPageVisibility(PREJOIN_SCREEN_STATES.LOADING));
62
+
60
         APP.conference.prejoinStart(jitsiTracks);
63
         APP.conference.prejoinStart(jitsiTracks);
61
 
64
 
62
         break;
65
         break;
103
         }
106
         }
104
         break;
107
         break;
105
     }
108
     }
106
-
109
+    case CONFERENCE_JOINED:
110
+        return _conferenceJoined(store, next, action);
107
     }
111
     }
108
 
112
 
109
     return next(action);
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 查看文件

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

+ 29
- 14
react/features/toolbox/components/web/Toolbox.js 查看文件

28
 import { EmbedMeetingButton } from '../../../embed-meeting';
28
 import { EmbedMeetingButton } from '../../../embed-meeting';
29
 import { SharedDocumentButton } from '../../../etherpad';
29
 import { SharedDocumentButton } from '../../../etherpad';
30
 import { FeedbackButton } from '../../../feedback';
30
 import { FeedbackButton } from '../../../feedback';
31
+import { InviteButton } from '../../../invite/components/add-people-dialog';
31
 import { isVpaasMeeting } from '../../../jaas/functions';
32
 import { isVpaasMeeting } from '../../../jaas/functions';
32
 import { KeyboardShortcutsButton } from '../../../keyboard-shortcuts';
33
 import { KeyboardShortcutsButton } from '../../../keyboard-shortcuts';
33
 import { LocalRecordingButton } from '../../../local-recording';
34
 import { LocalRecordingButton } from '../../../local-recording';
66
 import { VideoBackgroundButton } from '../../../virtual-background';
67
 import { VideoBackgroundButton } from '../../../virtual-background';
67
 import { toggleBackgroundEffect } from '../../../virtual-background/actions';
68
 import { toggleBackgroundEffect } from '../../../virtual-background/actions';
68
 import { VIRTUAL_BACKGROUND_TYPE } from '../../../virtual-background/constants';
69
 import { VIRTUAL_BACKGROUND_TYPE } from '../../../virtual-background/constants';
69
-import { checkBlurSupport } from '../../../virtual-background/functions';
70
 import {
70
 import {
71
     setFullScreen,
71
     setFullScreen,
72
     setOverflowMenuVisible,
72
     setOverflowMenuVisible,
207
      */
207
      */
208
     _visible: boolean,
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
      * Returns the selected virtual source object.
211
      * Returns the selected virtual source object.
217
      */
212
      */
230
     /**
225
     /**
231
      * Invoked to obtain translated strings.
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
 declare var APP: Object;
236
 declare var APP: Object;
399
      * @returns {ReactElement}
399
      * @returns {ReactElement}
400
      */
400
      */
401
     render() {
401
     render() {
402
-        const { _chatOpen, _visible, _visibleButtons } = this.props;
402
+        const { _chatOpen, _visible, _toolbarButtons } = this.props;
403
         const rootClassNames = `new-toolbox ${_visible ? 'visible' : ''} ${
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
         return (
406
         return (
407
             <div
407
             <div
598
 
598
 
599
         const participants = {
599
         const participants = {
600
             key: 'participants-pane',
600
             key: 'participants-pane',
601
-            alias: 'invite',
602
             Content: ParticipantsPaneButton,
601
             Content: ParticipantsPaneButton,
603
             handleClick: this._onToolbarToggleParticipantsPane,
602
             handleClick: this._onToolbarToggleParticipantsPane,
604
             group: 2
603
             group: 2
605
         };
604
         };
606
 
605
 
606
+        const invite = {
607
+            key: 'invite',
608
+            Content: InviteButton,
609
+            group: 2
610
+        };
611
+
607
         const tileview = {
612
         const tileview = {
608
             key: 'tileview',
613
             key: 'tileview',
609
             Content: TileViewButton,
614
             Content: TileViewButton,
691
             group: 3
696
             group: 3
692
         };
697
         };
693
 
698
 
694
-        const virtualBackground = !_screenSharing && checkBlurSupport() && {
699
+        const virtualBackground = !_screenSharing && {
695
             key: 'select-background',
700
             key: 'select-background',
696
             Content: VideoBackgroundButton,
701
             Content: VideoBackgroundButton,
697
             group: 3
702
             group: 3
747
             chat,
752
             chat,
748
             raisehand,
753
             raisehand,
749
             participants,
754
             participants,
755
+            invite,
750
             tileview,
756
             tileview,
751
             toggleCamera,
757
             toggleCamera,
752
             videoQuality,
758
             videoQuality,
1238
  * props.
1244
  * props.
1239
  *
1245
  *
1240
  * @param {Object} state - The redux store/state.
1246
  * @param {Object} state - The redux store/state.
1247
+ * @param {Object} ownProps - The props explicitly passed.
1241
  * @private
1248
  * @private
1242
  * @returns {{}}
1249
  * @returns {{}}
1243
  */
1250
  */
1244
-function _mapStateToProps(state) {
1251
+function _mapStateToProps(state, ownProps) {
1245
     const { conference } = state['features/base/conference'];
1252
     const { conference } = state['features/base/conference'];
1246
     let desktopSharingEnabled = JitsiMeetJS.isDesktopSharingEnabled();
1253
     let desktopSharingEnabled = JitsiMeetJS.isDesktopSharingEnabled();
1247
     const {
1254
     const {
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
     return {
1287
     return {
1272
         _chatOpen: state['features/chat'].isOpen,
1288
         _chatOpen: state['features/chat'].isOpen,
1273
         _clientWidth: clientWidth,
1289
         _clientWidth: clientWidth,
1289
         _participantsPaneOpen: getParticipantsPaneOpen(state),
1305
         _participantsPaneOpen: getParticipantsPaneOpen(state),
1290
         _raisedHand: localParticipant?.raisedHand,
1306
         _raisedHand: localParticipant?.raisedHand,
1291
         _screenSharing: isScreenVideoShared(state),
1307
         _screenSharing: isScreenVideoShared(state),
1292
-        _toolbarButtons: getToolbarButtons(state),
1308
+        _toolbarButtons: toolbarButtons,
1293
         _visible: isToolboxVisible(state),
1309
         _visible: isToolboxVisible(state),
1294
-        _visibleButtons: getToolbarButtons(state),
1295
         _reactionsEnabled: enableReactions
1310
         _reactionsEnabled: enableReactions
1296
     };
1311
     };
1297
 }
1312
 }

+ 3
- 1
react/features/virtual-background/components/VideoBackgroundButton.js 查看文件

6
 import { connect } from '../../base/redux';
6
 import { connect } from '../../base/redux';
7
 import { AbstractButton } from '../../base/toolbox/components';
7
 import { AbstractButton } from '../../base/toolbox/components';
8
 import type { AbstractButtonProps } from '../../base/toolbox/components';
8
 import type { AbstractButtonProps } from '../../base/toolbox/components';
9
+import { checkBlurSupport } from '../functions';
9
 
10
 
10
 import { VirtualBackgroundDialog } from './index';
11
 import { VirtualBackgroundDialog } from './index';
11
 
12
 
72
 function _mapStateToProps(state): Object {
73
 function _mapStateToProps(state): Object {
73
 
74
 
74
     return {
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 查看文件

13
 
13
 
14
           const url = new URL(window.location.href);
14
           const url = new URL(window.location.href);
15
           const params = new URLSearchParams(url.search);
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
           JitsiMeetJS.app.renderEntryPoint({
18
           JitsiMeetJS.app.renderEntryPoint({
21
               Component: JitsiMeetJS.app.entryPoints.PREJOIN,
19
               Component: JitsiMeetJS.app.entryPoints.PREJOIN,
22
               props: {
20
               props: {
23
-                showAvatar,
24
-                showJoinActions,
25
-                showSkipPrejoin
21
+                styleType
26
               }
22
               }
27
           })
23
           })
28
       })
24
       })

正在加载...
取消
保存