Pārlūkot izejas kodu

feat(thumbnail) Video thumbnails redesign and refactor (#10351)

Update video thumbnail design
Update design of indicators
In filmstrip view move Screen Sharing indicator to the top
Removed dominant speaker indicator
Use ContextMenu component for the connection stats popover
Combine Remove video menu and Meeting participant context menu into one component
Moved some styles from SCSS to JSS
Fix mobile avatars too big
Fix mobile horizontal scroll
Created button for Send to breakout room action
master
Robert Pintilii 3 gadus atpakaļ
vecāks
revīzija
91437c50e3
Revīzijas autora e-pasta adrese nav piesaistīta nevienam kontam
66 mainītis faili ar 1864 papildinājumiem un 2560 dzēšanām
  1. 0
    8
      css/_atlaskit_overrides.scss
  2. 0
    9
      css/_drawer.scss
  3. 0
    5
      css/_popover.scss
  4. 10
    114
      css/_popup_menu.scss
  5. 0
    12
      css/_variables.scss
  6. 1
    380
      css/_videolayout_default.scss
  7. 1
    28
      css/filmstrip/_small_video.scss
  8. 0
    11
      css/filmstrip/_tile_view.scss
  9. 0
    12
      css/filmstrip/_tile_view_overrides.scss
  10. 0
    66
      css/filmstrip/_vertical_filmstrip_overrides.scss
  11. 0
    4
      css/themes/_light.scss
  12. 1
    1
      interface_config.js
  13. 2
    2
      lang/main.json
  14. 18
    4
      react/features/base/components/context-menu/ContextMenu.js
  15. 129
    0
      react/features/base/components/context-menu/ContextMenuItem.js
  16. 5
    89
      react/features/base/components/context-menu/ContextMenuItemGroup.js
  17. 2
    2
      react/features/base/icons/svg/crown.svg
  18. 3
    10
      react/features/base/icons/svg/mute-everyone-else.svg
  19. 2
    2
      react/features/base/icons/svg/share-desktop.svg
  20. 3
    13
      react/features/base/popover/components/Popover.web.js
  21. 51
    58
      react/features/base/react/components/web/BaseIndicator.js
  22. 74
    51
      react/features/connection-indicator/components/web/ConnectionIndicator.js
  23. 37
    11
      react/features/connection-stats/components/ConnectionStatsTable.js
  24. 66
    9
      react/features/display-name/components/web/DisplayName.js
  25. 12
    22
      react/features/filmstrip/components/web/AudioMutedIndicator.js
  26. 0
    51
      react/features/filmstrip/components/web/DominantSpeakerIndicator.js
  27. 10
    23
      react/features/filmstrip/components/web/ModeratorIndicator.js
  28. 45
    23
      react/features/filmstrip/components/web/RaisedHandIndicator.js
  29. 1
    2
      react/features/filmstrip/components/web/ScreenShareIndicator.js
  30. 13
    35
      react/features/filmstrip/components/web/StatusIndicators.js
  31. 186
    443
      react/features/filmstrip/components/web/Thumbnail.js
  32. 47
    0
      react/features/filmstrip/components/web/ThumbnailAudioIndicator.js
  33. 84
    0
      react/features/filmstrip/components/web/ThumbnailBottomIndicators.js
  34. 132
    0
      react/features/filmstrip/components/web/ThumbnailTopIndicators.js
  35. 12
    2
      react/features/filmstrip/components/web/ThumbnailWrapper.js
  36. 69
    0
      react/features/filmstrip/components/web/VideoMenuTriggerButton.js
  37. 0
    43
      react/features/filmstrip/components/web/VideoMutedIndicator.js
  38. 0
    2
      react/features/filmstrip/components/web/index.js
  39. 33
    44
      react/features/filmstrip/constants.js
  40. 57
    13
      react/features/filmstrip/functions.web.js
  41. 15
    447
      react/features/participants-pane/components/web/MeetingParticipantContextMenu.js
  42. 1
    1
      react/features/participants-pane/components/web/RaisedHandIndicator.js
  43. 2
    0
      react/features/participants-pane/constants.js
  44. 1
    1
      react/features/toolbox/components/MuteEveryonesVideoButton.js
  45. 3
    1
      react/features/video-menu/components/AbstractMuteButton.js
  46. 1
    1
      react/features/video-menu/components/AbstractMuteEveryoneElsesVideoButton.js
  47. 49
    0
      react/features/video-menu/components/web/AskToUnmuteButton.js
  48. 7
    8
      react/features/video-menu/components/web/ConnectionStatusButton.js
  49. 13
    6
      react/features/video-menu/components/web/FlipLocalVideoButton.js
  50. 7
    8
      react/features/video-menu/components/web/GrantModeratorButton.js
  51. 14
    7
      react/features/video-menu/components/web/HideSelfViewVideoButton.js
  52. 8
    8
      react/features/video-menu/components/web/KickButton.js
  53. 65
    25
      react/features/video-menu/components/web/LocalVideoMenuTriggerButton.js
  54. 13
    16
      react/features/video-menu/components/web/MuteButton.js
  55. 6
    8
      react/features/video-menu/components/web/MuteEveryoneElseButton.js
  56. 6
    8
      react/features/video-menu/components/web/MuteEveryoneElsesVideoButton.js
  57. 13
    17
      react/features/video-menu/components/web/MuteVideoButton.js
  58. 332
    0
      react/features/video-menu/components/web/ParticipantContextMenu.js
  59. 6
    7
      react/features/video-menu/components/web/PrivateMessageMenuButton.js
  60. 9
    10
      react/features/video-menu/components/web/RemoteControlButton.js
  61. 58
    193
      react/features/video-menu/components/web/RemoteVideoMenuTriggerButton.js
  62. 50
    0
      react/features/video-menu/components/web/SendToRoomButton.js
  63. 0
    51
      react/features/video-menu/components/web/VideoMenu.js
  64. 0
    111
      react/features/video-menu/components/web/VideoMenuButton.js
  65. 78
    21
      react/features/video-menu/components/web/VolumeSlider.js
  66. 1
    1
      react/features/video-menu/components/web/index.js

+ 0
- 8
css/_atlaskit_overrides.scss Parādīt failu

24
     bottom: calc(#{$newToolbarSizeWithPadding}) !important;
24
     bottom: calc(#{$newToolbarSizeWithPadding}) !important;
25
 }
25
 }
26
 
26
 
27
-/**
28
- * Override @atlaskit/theme styling for the top toolbar so it displays over
29
- * the video thumbnail while obscuring as little as possible.
30
- */
31
-.videocontainer__toptoolbar > div > div {
32
-    background: none;
33
-}
34
-
35
 
27
 
36
 /**
28
 /**
37
  * Keep overflow menu within screen vertical bounds and make it scrollable.
29
  * Keep overflow menu within screen vertical bounds and make it scrollable.

+ 0
- 9
css/_drawer.scss Parādīt failu

42
         }
42
         }
43
     }
43
     }
44
 
44
 
45
-    .popupmenu {
46
-        margin: auto;
47
-        width: 100%;
48
-    }
49
-
50
-    .popupmenu__item {
51
-        height: 48px;
52
-    }
53
-
54
     &#{&} .overflow-menu {
45
     &#{&} .overflow-menu {
55
         margin: auto;
46
         margin: auto;
56
         font-size: 1.2em;
47
         font-size: 1.2em;

+ 0
- 5
css/_popover.scss Parādīt failu

43
 
43
 
44
 .popover {
44
 .popover {
45
     margin: -16px -24px;
45
     margin: -16px -24px;
46
-    padding: 16px 24px;
47
     z-index: $popoverZ;
46
     z-index: $popoverZ;
48
 }
47
 }
49
-
50
-.padded-content {
51
-    padding: 4px 8px;
52
-}

+ 10
- 114
css/_popup_menu.scss Parādīt failu

2
 * Initialize
2
 * Initialize
3
 **/
3
 **/
4
 
4
 
5
-.popupmenu {
6
-    background-color: $menuBG;
7
-    border-radius: 3px;
8
-    list-style-type: none;
9
-    min-width: 150px;
10
-    text-align: left;
11
-    padding: 0px;
12
-    white-space: nowrap;
13
-
14
-    &__item {
15
-        list-style-type: none;
16
-        height: 35px;
17
-    }
18
-
19
-    // Link Appearance
20
-    &__link,
21
-    &__contents {
22
-        display: block;
23
-        box-sizing: border-box;
24
-        text-decoration: none;
25
-        height: 100%;
26
-        font-size: 9pt;
27
-        width: 100%;
28
-        cursor: pointer;
29
-        padding: 0 5px;
30
-        color: $popupMenuColor;
31
-
32
-        &:hover {
33
-            background-color: $popupMenuHoverBackground;
34
-            color: $popupMenuHoverColor;
35
-        }
36
-
37
-        &.disabled {
38
-            pointer-events: none;
39
-        }
40
-    }
41
-
42
-    &__list {
43
-        margin: 0;
44
-        padding: 0;
45
-    }
46
-
47
-    &__text {
48
-        display: inline-block;
49
-        margin-left: 8px;
50
-        vertical-align: middle;
51
-    }
52
-
53
-    &__link {
54
-        i {
55
-            cursor: pointer;
56
-        }
57
-    }
58
-
59
-    &__contents {
60
-        display: flex;
61
-
62
-        /**
63
-         * Positioning styles on the slider and its container are used to make
64
-         * the container fit the popup width, by removing the slider from the
65
-         * page flow, and then making the slider fit the container.
66
-         */
67
-        .popupmenu__slider_container {
68
-            position: relative;
69
-            width: 100%;
70
-
71
-            .popupmenu__slider {
72
-                position: absolute;
73
-                top: 50%;
74
-                transform: translate(0, -50%);
75
-                width: 100%;
76
-
77
-                &::-webkit-slider-runnable-track {
78
-                    background-color: $popupSliderColor;
79
-                }
80
-
81
-                &::-moz-range-track {
82
-                    background-color: $popupSliderColor;
83
-                }
84
-
85
-                &::-ms-fill-lower {
86
-                    background-color: $popupSliderColor;
87
-                }
5
+.popupmenu__contents {
6
+    .popupmenu__volume-slider {
7
+            &::-webkit-slider-runnable-track {
8
+                background-color: $popupSliderColor;
88
             }
9
             }
89
-        }
90
-    }
91
 
10
 
92
-    &__icon {
93
-        vertical-align: middle;
94
-        position: relative;
95
-        display: inline-block;
96
-        min-width: 20px;
97
-        height: 100%;
98
-        padding-right: 10px;
11
+            &::-moz-range-track {
12
+                background-color: $popupSliderColor;
13
+            }
99
 
14
 
100
-        > * {
101
-            @include absoluteAligning();
15
+            &::-ms-fill-lower {
16
+                background-color: $popupSliderColor;
17
+            }
102
         }
18
         }
103
-    }
104
-
105
-    .icon-kick,
106
-    .icon-play,
107
-    .icon-stop {
108
-        font-size: 8pt;
109
-    }
110
-}
111
-
112
-/**
113
- * Override reset css styling modifying all lists and set negative margin to
114
- * reduce the visibility of padding on AtlasKit
115
- * InlineDialogs.
116
- */
117
-ul.popupmenu {
118
-    margin: -16px -24px;
119
-}
120
-
121
-span.localvideomenu:hover ul.popupmenu, span.remotevideomenu:hover ul.popupmenu, ul.popupmenu:hover {
122
-    display:block !important;
123
 }
19
 }

+ 0
- 12
css/_variables.scss Parādīt failu

13
  */
13
  */
14
 
14
 
15
 // Video layout.
15
 // Video layout.
16
-$thumbnailToolbarHeight: 22px;
17
-$thumbnailIndicatorBorder: 2px;
18
-$thumbnailIndicatorSize: $thumbnailToolbarHeight;
19
 $thumbnailVideoMargin: 2px;
16
 $thumbnailVideoMargin: 2px;
20
 $thumbnailsBorder: 2px;
17
 $thumbnailsBorder: 2px;
21
 $thumbnailVideoBorder: 2px;
18
 $thumbnailVideoBorder: 2px;
56
 /**
53
 /**
57
  * Video layout
54
  * Video layout
58
  */
55
  */
59
-$videoThumbnailHovered: rgba(22, 94, 204, .4);
60
-$videoThumbnailSelected: #165ECC;
61
 $participantNameColor: #fff;
56
 $participantNameColor: #fff;
62
-$thumbnailPictogramColor: #fff;
63
-$dominantSpeakerBg: #165ecc;
64
-$raiseHandBg: #F8AE1A;
65
 $audioLevelBg: #44A5FF;
57
 $audioLevelBg: #44A5FF;
66
-$connectionIndicatorBg: #165ecc;
67
 $audioLevelShadow: rgba(9, 36, 77, 0.9);
58
 $audioLevelShadow: rgba(9, 36, 77, 0.9);
68
 $videoStateIndicatorColor: $defaultColor;
59
 $videoStateIndicatorColor: $defaultColor;
69
 $videoStateIndicatorBackground: $toolbarBackground;
60
 $videoStateIndicatorBackground: $toolbarBackground;
70
 $videoStateIndicatorSize: 40px;
61
 $videoStateIndicatorSize: 40px;
71
-$remoteVideoMenuIconMargin: initial;
72
 
62
 
73
 /**
63
 /**
74
  * Feedback Modal
64
  * Feedback Modal
102
  * Misc.
92
  * Misc.
103
  */
93
  */
104
 $borderRadius: 4px;
94
 $borderRadius: 4px;
105
-$popoverMenuPadding: 13px;
106
 $happySoftwareBackground: transparent;
95
 $happySoftwareBackground: transparent;
107
 $desktopAppDragBarHeight: 25px;
96
 $desktopAppDragBarHeight: 25px;
108
 $scrollHeight: 7px;
97
 $scrollHeight: 7px;
118
 $labelsZ: 5;
107
 $labelsZ: 5;
119
 $subtitlesZ: 7;
108
 $subtitlesZ: 7;
120
 $popoverZ: 8;
109
 $popoverZ: 8;
121
-$zindex10: 10;
122
 $reloadZ: 20;
110
 $reloadZ: 20;
123
 $poweredByZ: 100;
111
 $poweredByZ: 100;
124
 $ringingZ: 300;
112
 $ringingZ: 300;

+ 1
- 380
css/_videolayout_default.scss Parādīt failu

43
 .videocontainer {
43
 .videocontainer {
44
     position: relative;
44
     position: relative;
45
     text-align: center;
45
     text-align: center;
46
-
47
-    &__background {
48
-        @include topLeft();
49
-        background-color: black;
50
-        border-radius: $borderRadius;
51
-        width: 100%;
52
-        height: 100%;
53
-    }
54
-
55
-    /**
56
-     * The toolbar of the video thumbnail.
57
-     */
58
-    &__toolbar,
59
-    &__toptoolbar {
60
-        position: absolute;
61
-        left: 0;
62
-        pointer-events: none;
63
-        z-index: $zindex10;
64
-        width: 100%;
65
-        box-sizing: border-box; // Includes the padding in the 100% width.
66
-
67
-        /**
68
-         * FIXME (lenny): Disabling pointer-events is a pretty big sin that
69
-         * sidesteps the problems. There are z-index wars occurring within
70
-         * videocontainer and AtlasKit Tooltips rely on their parent z-indexe
71
-         * being higher than whatever they need to appear over. So set a higher
72
-         * z-index for the tooltip containers but make any empty space not block
73
-         * mouse overs for various mouseover triggers.
74
-         */
75
-        pointer-events: none;
76
-
77
-        * {
78
-            pointer-events: auto;
79
-        }
80
-
81
-        .indicator-container {
82
-            display: inline-block;
83
-            float: left;
84
-            pointer-events: all;
85
-        }
86
-    }
87
-
88
-    &__toolbar {
89
-        bottom: 0;
90
-        padding: 0 5px 0 5px;
91
-    }
92
-
93
-    &__toptoolbar {
94
-        $toolbarIconMargin: 5px;
95
-        top: 0;
96
-        padding-bottom: 0;
97
-        /**
98
-         * Override text-align center as icons need to be left justified.
99
-         */
100
-        text-align: left;
101
-
102
-        /**
103
-         * Intentionally use margin on the icon itself as AtlasKit InlineDialog
104
-         * positioning depends on the trigger (indicator icon).
105
-         */
106
-        .indicator {
107
-            margin-left: 5px;
108
-            margin-top: $toolbarIconMargin;
109
-        }
110
-
111
-        .indicator-container:nth-child(1) .indicator {
112
-            margin-left: $toolbarIconMargin;
113
-        }
114
-
115
-        .indicator-container {
116
-            display: inline-block;
117
-            vertical-align: top;
118
-
119
-            .popover-trigger {
120
-                display: inline-block;
121
-            }
122
-        }
123
-
124
-        .connection-indicator,
125
-        .indicator {
126
-            position: relative;
127
-            font-size: 8px;
128
-            text-align: center;
129
-            line-height: $thumbnailIndicatorSize;
130
-            padding: 0;
131
-            @include circle($thumbnailIndicatorSize);
132
-            box-sizing: border-box;
133
-            z-index: $zindex3;
134
-            background: $dominantSpeakerBg;
135
-            color: $thumbnailPictogramColor;
136
-            border: $thumbnailIndicatorBorder solid $thumbnailPictogramColor;
137
-
138
-            .indicatoricon {
139
-                @include absoluteAligning();
140
-            }
141
-
142
-            .connection {
143
-                position: relative;
144
-                display: inline-block;
145
-                margin: 0 auto;
146
-                left: 0;
147
-                @include transform(translate(0, -50%));
148
-
149
-                &_empty,
150
-                &_lost
151
-                {
152
-                    color: #8B8B8B;/*#FFFFFF*/
153
-                    overflow: hidden;
154
-                }
155
-
156
-                &_full
157
-                {
158
-                    @include topLeft();
159
-                    color: #FFFFFF;/*#15A1ED*/
160
-                    overflow: hidden;
161
-                }
162
-
163
-                &_ninja
164
-                {
165
-                    font-size: 1.5em;
166
-                }
167
-            }
168
-
169
-            .icon-gsm-bars {
170
-                cursor: pointer;
171
-                font-size: 1em;
172
-            }
173
-        }
174
-
175
-        .hide-connection-indicator {
176
-            display: none;
177
-        }
178
-    }
179
-
180
-    &__hoverOverlay {
181
-        background: rgba(0,0,0,.6);
182
-        border-radius: $borderRadius;
183
-        position: absolute;
184
-        top: 0px;
185
-        left: 0px;
186
-        width: 100%;
187
-        height: 100%;
188
-        visibility: hidden;
189
-        z-index: $zindex2;
190
-    }
191
-
192
-    &__participant-name {
193
-        color: #fff;
194
-        background-color: rgba(0,0,0,.4);
195
-        padding: 3px 7px;
196
-        border-radius: 3px;
197
-        max-width: calc(100% - 32px);
198
-        text-overflow: ellipsis;
199
-        overflow: hidden;
200
-        white-space: nowrap;
201
-        height: 16px;
202
-        display: inline-block;
203
-        text-align: right;
204
-    }
46
+    overflow: 'hidden';
205
 
47
 
206
     @media (min-width: 581px) {
48
     @media (min-width: 581px) {
207
         &.shift-right {
49
         &.shift-right {
288
     z-index: $zindex0;
130
     z-index: $zindex0;
289
 }
131
 }
290
 
132
 
291
-/**
292
- * Positions video thumbnail display name and editor.
293
- */
294
-#alwaysOnTop .displayname,
295
-.videocontainer .displayname,
296
-.videocontainer .editdisplayname {
297
-    font-weight: 100;
298
-    color: $participantNameColor;
299
-}
300
-
301
 #alwaysOnTop .displayname {
133
 #alwaysOnTop .displayname {
302
     font-size: 15px;
134
     font-size: 15px;
303
     position: inherit;
135
     position: inherit;
307
     margin-top: 10px;
139
     margin-top: 10px;
308
 }
140
 }
309
 
141
 
310
-/**
311
- * Positions video thumbnail display name editor.
312
- */
313
-.videocontainer .editdisplayname {
314
-    outline: none;
315
-    border: none;
316
-    background: none;
317
-    box-shadow: none;
318
-    padding: 0;
319
-}
320
-
321
-#localVideoContainer .displayname:hover {
322
-    cursor: text;
323
-}
324
-
325
-.videocontainer .displayname {
326
-    pointer-events: none;
327
-    padding: 0 3px 0 3px;
328
-}
329
-
330
-.videocontainer .editdisplayname {
331
-    height: auto;
332
-}
333
-
334
-#localDisplayName {
335
-    pointer-events: auto !important;
336
-}
337
-
338
-.videocontainer>a.displayname {
339
-    display: inline-block;
340
-    position: absolute;
341
-    color: #FFFFFF;
342
-    bottom: 0;
343
-    right: 0;
344
-    padding: 3px 5px;
345
-    font-size: 9pt;
346
-    cursor: pointer;
347
-    z-index: $zindex2;
348
-}
349
-
350
-/**
351
- * Video thumbnail toolbar icon.
352
- */
353
-.videocontainer .toolbar-icon {
354
-    font-size: 8pt;
355
-    text-align: center;
356
-    text-shadow: 0px 1px 0px rgba(255,255,255,.3), 0px -1px 0px rgba(0,0,0,.7);
357
-    color: #FFFFFF;
358
-    width: 12px;
359
-    line-height: $thumbnailToolbarHeight;
360
-    height: $thumbnailToolbarHeight;
361
-    padding: 0;
362
-    border: 0;
363
-    margin: 0px 5px 0px 0px;
364
-}
365
-
366
-/**
367
- * Toolbar icon internal i elements (font icons).
368
- */
369
-.toolbar-icon>div {
370
-    height: $thumbnailToolbarHeight;
371
-    display: flex;
372
-    flex-direction: column;
373
-    justify-content: center;
374
-}
375
-
376
-/**
377
- * Toolbar icons positioned on the right.
378
- */
379
-.moderator-icon {
380
-    display: inline-block;
381
-
382
-    &.right {
383
-        float: right;
384
-        margin: 0px 0px 0px 5px;
385
-    }
386
-
387
-    .toolbar-icon {
388
-        margin: 0;
389
-    }
390
-}
391
-
392
-.raisehandindicator {
393
-  background: $raiseHandBg !important;
394
-}
395
-
396
-.connection-indicator {
397
-    background: $connectionIndicatorBg;
398
-
399
-    &.status-high {
400
-        background: green;
401
-    }
402
-
403
-    &.status-med {
404
-        background: #FFD740;
405
-    }
406
-
407
-    &.status-lost {
408
-        background: gray;
409
-    }
410
-
411
-    &.status-low {
412
-        background: #BF2117;
413
-    }
414
-
415
-    &.status-other {
416
-        background: $connectionIndicatorBg;
417
-    }
418
-
419
-    &.status-disabled {
420
-        background: transparent;
421
-        border: none
422
-    }
423
-}
424
-
425
-.local-video-menu-trigger,
426
-.remote-video-menu-trigger,
427
-.localvideomenu,
428
-.remotevideomenu
429
-{
430
-    display: inline-block;
431
-    position: absolute;
432
-    top: 0px;
433
-    right: 0;
434
-    z-index: $zindex2;
435
-    width: 18px;
436
-    height: 18px;
437
-    color: #FFF;
438
-    font-size: 10pt;
439
-    margin-right: $remoteVideoMenuIconMargin;
440
-
441
-    >i{
442
-        cursor: hand;
443
-    }
444
-}
445
-.local-video-menu-trigger,
446
-.remote-video-menu-trigger {
447
-    margin-top: 7px;
448
-}
449
-
450
 /**
142
 /**
451
  * Audio indicator on video thumbnails.
143
  * Audio indicator on video thumbnails.
452
  */
144
  */
623
     display: none;
315
     display: none;
624
 }
316
 }
625
 
317
 
626
-.display-avatar-with-name {
627
-    .avatar-container {
628
-        visibility: visible;
629
-    }
630
-
631
-    .displayNameContainer {
632
-        visibility: visible;
633
-    }
634
-
635
-    .videocontainer__hoverOverlay {
636
-        visibility: visible;
637
-    }
638
-
639
-    video {
640
-        visibility: hidden;
641
-    }
642
-}
643
-
644
-.display-name-on-black {
645
-    .avatar-container {
646
-        visibility: hidden;
647
-    }
648
-
649
-    .displayNameContainer {
650
-        visibility: visible;
651
-    }
652
-
653
-    .videocontainer__hoverOverlay {
654
-        visibility: hidden;
655
-    }
656
-
657
-    video {
658
-        opacity: 0.2;
659
-        visibility: visible;
660
-    }
661
-}
662
-
663
 .display-video {
318
 .display-video {
664
     .avatar-container {
319
     .avatar-container {
665
         visibility: hidden;
320
         visibility: hidden;
666
     }
321
     }
667
 
322
 
668
-    .displayNameContainer {
669
-        visibility: hidden;
670
-    }
671
-
672
-    .videocontainer__hoverOverlay {
673
-        visibility: hidden;
674
-    }
675
-
676
-    video {
677
-        visibility: visible;
678
-    }
679
-}
680
-
681
-.display-name-on-video {
682
-    .avatar-container {
683
-        visibility: hidden;
684
-    }
685
-
686
-    .displayNameContainer {
687
-        visibility: visible;
688
-    }
689
-
690
-    .videocontainer__hoverOverlay {
691
-        visibility: visible;
692
-    }
693
-
694
     video {
323
     video {
695
         visibility: visible;
324
         visibility: visible;
696
     }
325
     }
701
         visibility: visible;
330
         visibility: visible;
702
     }
331
     }
703
 
332
 
704
-    .displayNameContainer {
705
-        visibility: hidden;
706
-    }
707
-
708
-    .videocontainer__hoverOverlay {
709
-        visibility: hidden;
710
-    }
711
-
712
     video {
333
     video {
713
         visibility: hidden;
334
         visibility: hidden;
714
     }
335
     }

+ 1
- 28
css/filmstrip/_small_video.scss Parādīt failu

6
     border-radius: $borderRadius;
6
     border-radius: $borderRadius;
7
     margin: 0 $thumbnailVideoMargin;
7
     margin: 0 $thumbnailVideoMargin;
8
 
8
 
9
-    &.videoContainerFocused, &:hover {
9
+    &:hover {
10
         cursor: hand;
10
         cursor: hand;
11
     }
11
     }
12
 
12
 
13
-    /**
14
-     * Focused video thumbnail.
15
-     */
16
-    &.videoContainerFocused {
17
-        border: $thumbnailVideoBorder solid $videoThumbnailSelected;
18
-        box-shadow: inset 0 0 3px $videoThumbnailSelected,
19
-        0 0 3px $videoThumbnailSelected;
20
-    }
21
-
22
-    .remotevideomenu > .icon-menu, .localvideomenu > .icon-menu {
23
-        display: none;
24
-    }
25
-
26
-    /**
27
-     * Hovered video thumbnail.
28
-     */
29
-    &:hover:not(.videoContainerFocused):not(.active-speaker) {
30
-        cursor: hand;
31
-        border: $thumbnailVideoBorder solid $videoThumbnailHovered;
32
-        box-shadow: inset 0 0 3px $videoThumbnailHovered,
33
-        0 0 3px $videoThumbnailHovered;
34
-
35
-        .remotevideomenu > .icon-menu, .localvideomenu > .icon-menu {
36
-            display: inline-block;
37
-        }
38
-    }
39
-
40
     & > video {
13
     & > video {
41
         cursor: hand;
14
         cursor: hand;
42
         border-radius: $borderRadius;
15
         border-radius: $borderRadius;

+ 0
- 11
css/filmstrip/_tile_view.scss Parādīt failu

2
  * CSS styles that are specific to the filmstrip that shows the thumbnail tiles.
2
  * CSS styles that are specific to the filmstrip that shows the thumbnail tiles.
3
  */
3
  */
4
 .tile-view {
4
 .tile-view {
5
-    /**
6
-     * Add a border around the active speaker to make the thumbnail easier to
7
-     * see.
8
-     */
9
-    .active-speaker {
10
-        box-shadow: 0px 0px 1px 1.5px black, 0px 0px 1.3px 4px $videoThumbnailSelected;
11
-    }
12
 
5
 
13
     .remote-videos {
6
     .remote-videos {
14
         align-items: center;
7
         align-items: center;
134
         }
127
         }
135
     }
128
     }
136
 }
129
 }
137
-
138
-.indicator-icon-container {
139
-    display: inline-block;
140
-}

+ 0
- 12
css/filmstrip/_tile_view_overrides.scss Parādīt failu

35
     #remotePresenceMessage {
35
     #remotePresenceMessage {
36
         display: none !important;
36
         display: none !important;
37
     }
37
     }
38
-
39
-    /**
40
-     * Thumbnail popover menus can overlap other thumbnails. Setting an auto
41
-     * z-index will allow AtlasKit InlineDialog's large z-index to be
42
-     * respected and thereby display over elements in other thumbnails,
43
-     * specifically the various status icons.
44
-     */
45
-    .remotevideomenu,
46
-    .localvideomenu,
47
-    .videocontainer__toptoolbar {
48
-        z-index: auto;
49
-    }
50
 }
38
 }

+ 0
- 66
css/filmstrip/_vertical_filmstrip_overrides.scss Parādīt failu

19
  * Overrides for small videos in vertical filmstrip mode.
19
  * Overrides for small videos in vertical filmstrip mode.
20
  */
20
  */
21
 .vertical-filmstrip .filmstrip__videos .videocontainer {
21
 .vertical-filmstrip .filmstrip__videos .videocontainer {
22
-    /**
23
-     * Move status icons to the bottom right of the thumbnail.
24
-     */
25
-    .videocontainer__toolbar {
26
-        /**
27
-         * FIXME: disable pointer to allow any elements moved below to still
28
-         * be clickable. The real fix would to make sure those moved elements
29
-         * are actually part of the toolbar instead of positioning being faked.
30
-         */
31
-        pointer-events: none;
32
-        text-align: right;
33
-
34
-        > div {
35
-            pointer-events: none;
36
-        }
37
-
38
-        .right {
39
-            float: none;
40
-            margin: auto;
41
-        }
42
-
43
-        .toolbar-icon {
44
-            pointer-events: all;
45
-        }
46
-    }
47
-
48
-    /**
49
-     * Apply hardware acceleration to prevent flickering on scroll. The
50
-     * selectors are specific to icon wrappers to prevent fixed position dialogs
51
-     * and tooltips from getting a new location context due to translate3d.
52
-     */
53
-    .connection-indicator,
54
-    .local-video-menu-trigger,
55
-    .remote-video-menu-trigger,
56
-    .indicator-icon-container {
57
-        transform: translate3d(0, 0, 0);
58
-    }
59
-
60
-    .indicator-icon-container {
61
-        display: inline-block;
62
-    }
63
-
64
-    .indicator-container {
65
-        float: none;
66
-    }
67
-
68
-    /**
69
-     * Move the remote video menu trigger to the bottom left of the video
70
-     * thumbnail.
71
-     */
72
-    .localvideomenu,
73
-    .remotevideomenu,
74
-    .local-video-menu-trigger,
75
-    .remote-video-menu-trigger {
76
-        bottom: 0;
77
-        left: 0;
78
-        top: auto;
79
-        right: auto;
80
-    }
81
-
82
-    .local-video-menu-trigger,
83
-    .remote-video-menu-trigger {
84
-        margin-bottom: 3px;
85
-        margin-left: $remoteVideoMenuIconMargin;
86
-    }
87
-
88
     .self-view-mobile-portrait video {
22
     .self-view-mobile-portrait video {
89
         object-fit: contain;
23
         object-fit: contain;
90
     }
24
     }

+ 0
- 4
css/themes/_light.scss Parādīt failu

75
 $feedbackCancelFontColor: #333;
75
 $feedbackCancelFontColor: #333;
76
 
76
 
77
 // Popover colors
77
 // Popover colors
78
-$popoverBg: initial;
79
 $popoverFontColor: #ffffff !important;
78
 $popoverFontColor: #ffffff !important;
80
-$popupMenuColor: #ffffff !important;
81
-$popupMenuHoverColor: #ffffff !important;
82
-$popupMenuHoverBackground: rgba(255, 255, 255, 0.1);
83
 $popupSliderColor: #0376da;
79
 $popupSliderColor: #0376da;
84
 
80
 
85
 // Toolbar
81
 // Toolbar

+ 1
- 1
interface_config.js Parādīt failu

26
 
26
 
27
     CLOSE_PAGE_GUEST_HINT: false, // A html text to be shown to guests on the close page, false disables it
27
     CLOSE_PAGE_GUEST_HINT: false, // A html text to be shown to guests on the close page, false disables it
28
 
28
 
29
-    DEFAULT_BACKGROUND: '#474747',
29
+    DEFAULT_BACKGROUND: '#040404',
30
     DEFAULT_LOGO_URL: 'images/watermark.svg',
30
     DEFAULT_LOGO_URL: 'images/watermark.svg',
31
     DEFAULT_WELCOME_PAGE_LOGO_URL: 'images/watermark.svg',
31
     DEFAULT_WELCOME_PAGE_LOGO_URL: 'images/watermark.svg',
32
 
32
 

+ 2
- 2
lang/main.json Parādīt failu

949
             "mute": "Mute / Unmute",
949
             "mute": "Mute / Unmute",
950
             "muteEveryone": "Mute everyone",
950
             "muteEveryone": "Mute everyone",
951
             "muteEveryoneElse": "Mute everyone else",
951
             "muteEveryoneElse": "Mute everyone else",
952
-            "muteEveryonesVideo": "Disable everyone's video",
953
-            "muteEveryoneElsesVideo": "Disable everyone else's video",
952
+            "muteEveryonesVideoStream": "Stop everyone's video",
953
+            "muteEveryoneElsesVideoStream": "Stop everyone else's video",
954
             "participants": "Participants",
954
             "participants": "Participants",
955
             "pip": "Toggle Picture-in-Picture mode",
955
             "pip": "Toggle Picture-in-Picture mode",
956
             "privateMessage": "Send private message",
956
             "privateMessage": "Send private message",

+ 18
- 4
react/features/base/components/context-menu/ContextMenu.js Parādīt failu

19
     /**
19
     /**
20
      * Class name for context menu. Used to overwrite default styles.
20
      * Class name for context menu. Used to overwrite default styles.
21
      */
21
      */
22
-    className?: string,
22
+    className?: ?string,
23
 
23
 
24
     /**
24
     /**
25
      * The entity for which the context menu is displayed.
25
      * The entity for which the context menu is displayed.
31
      */
31
      */
32
     hidden?: boolean,
32
     hidden?: boolean,
33
 
33
 
34
+    /**
35
+     * Whether or not the menu is already in a drawer.
36
+     */
37
+    inDrawer?: ?boolean,
38
+
34
     /**
39
     /**
35
      * Whether or not drawer should be open.
40
      * Whether or not drawer should be open.
36
      */
41
      */
37
-    isDrawerOpen: boolean,
42
+    isDrawerOpen?: boolean,
38
 
43
 
39
     /**
44
     /**
40
      * Target elements against which positioning calculations are made.
45
      * Target elements against which positioning calculations are made.
49
     /**
54
     /**
50
      * Callback for drawer close.
55
      * Callback for drawer close.
51
      */
56
      */
52
-    onDrawerClose: Function,
57
+    onDrawerClose?: Function,
53
 
58
 
54
     /**
59
     /**
55
      * Callback for the mouse entering the component.
60
      * Callback for the mouse entering the component.
59
     /**
64
     /**
60
      * Callback for the mouse leaving the component.
65
      * Callback for the mouse leaving the component.
61
      */
66
      */
62
-    onMouseLeave: Function
67
+    onMouseLeave?: Function
63
 };
68
 };
64
 
69
 
65
 const useStyles = makeStyles(theme => {
70
 const useStyles = makeStyles(theme => {
106
     className,
111
     className,
107
     entity,
112
     entity,
108
     hidden,
113
     hidden,
114
+    inDrawer,
109
     isDrawerOpen,
115
     isDrawerOpen,
110
     offsetTarget,
116
     offsetTarget,
111
     onClick,
117
     onClick,
147
         }
153
         }
148
     }, [ hidden ]);
154
     }, [ hidden ]);
149
 
155
 
156
+    if (_overflowDrawer && inDrawer) {
157
+        return (<div
158
+            className = { styles.drawer }
159
+            onClick = { onDrawerClose }>
160
+            {children}
161
+        </div>);
162
+    }
163
+
150
     return _overflowDrawer
164
     return _overflowDrawer
151
         ? <JitsiPortal>
165
         ? <JitsiPortal>
152
             <Drawer
166
             <Drawer

+ 129
- 0
react/features/base/components/context-menu/ContextMenuItem.js Parādīt failu

1
+// @flow
2
+
3
+import { makeStyles } from '@material-ui/styles';
4
+import clsx from 'clsx';
5
+import React from 'react';
6
+import { useSelector } from 'react-redux';
7
+
8
+import { showOverflowDrawer } from '../../../toolbox/functions.web';
9
+import { Icon } from '../../icons';
10
+
11
+export type Props = {
12
+
13
+    /**
14
+     * Label used for accessibility.
15
+     */
16
+    accessibilityLabel: string,
17
+
18
+    /**
19
+     * CSS class name used for custom styles.
20
+     */
21
+    className?: string,
22
+
23
+    /**
24
+     * Custom icon. If used, the icon prop is ignored.
25
+     * Used to allow custom children instead of just the default icons.
26
+     */
27
+    customIcon?: React$Node,
28
+
29
+    /**
30
+     * Whether or not the action is disabled.
31
+     */
32
+    disabled?: boolean,
33
+
34
+    /**
35
+     * Id of the action container.
36
+     */
37
+    id?: string,
38
+
39
+    /**
40
+     * Default icon for action.
41
+     */
42
+    icon?: Function,
43
+
44
+    /**
45
+     * Click handler.
46
+     */
47
+    onClick?: Function,
48
+
49
+    /**
50
+     * Action text.
51
+     */
52
+    text: string,
53
+
54
+    /**
55
+     * Class name for the text.
56
+     */
57
+    textClassName?: string
58
+}
59
+
60
+const useStyles = makeStyles(theme => {
61
+    return {
62
+        contextMenuItem: {
63
+            alignItems: 'center',
64
+            cursor: 'pointer',
65
+            display: 'flex',
66
+            minHeight: '40px',
67
+            padding: '10px 16px',
68
+            boxSizing: 'border-box',
69
+
70
+            '& > *:not(:last-child)': {
71
+                marginRight: `${theme.spacing(3)}px`
72
+            },
73
+
74
+            '&:hover': {
75
+                backgroundColor: theme.palette.ui04
76
+            }
77
+        },
78
+
79
+        contextMenuItemDisabled: {
80
+            pointerEvents: 'none'
81
+        },
82
+
83
+        contextMenuItemDrawer: {
84
+            padding: '12px 16px'
85
+        },
86
+
87
+        contextMenuItemIcon: {
88
+            '& svg': {
89
+                fill: theme.palette.icon01
90
+            }
91
+        }
92
+    };
93
+});
94
+
95
+const ContextMenuItem = ({
96
+    accessibilityLabel,
97
+    className,
98
+    customIcon,
99
+    disabled,
100
+    id,
101
+    icon,
102
+    onClick,
103
+    text,
104
+    textClassName }: Props) => {
105
+    const styles = useStyles();
106
+    const _overflowDrawer = useSelector(showOverflowDrawer);
107
+
108
+    return (
109
+        <div
110
+            aria-label = { accessibilityLabel }
111
+            className = { clsx(styles.contextMenuItem,
112
+                    _overflowDrawer && styles.contextMenuItemDrawer,
113
+                    disabled && styles.contextMenuItemDisabled,
114
+                    className
115
+            ) }
116
+            id = { id }
117
+            key = { text }
118
+            onClick = { onClick }>
119
+            {customIcon ? customIcon
120
+                : icon && <Icon
121
+                    className = { styles.contextMenuItemIcon }
122
+                    size = { 20 }
123
+                    src = { icon } />}
124
+            <span className = { textClassName ?? '' }>{text}</span>
125
+        </div>
126
+    );
127
+};
128
+
129
+export default ContextMenuItem;

+ 5
- 89
react/features/base/components/context-menu/ContextMenuItemGroup.js Parādīt failu

1
 // @flow
1
 // @flow
2
 import { makeStyles } from '@material-ui/core';
2
 import { makeStyles } from '@material-ui/core';
3
-import clsx from 'clsx';
4
 import React from 'react';
3
 import React from 'react';
5
-import { useSelector } from 'react-redux';
6
 
4
 
7
-import { showOverflowDrawer } from '../../../toolbox/functions.web';
8
-import { Icon } from '../../icons';
9
-
10
-export type Action = {
11
-
12
-    /**
13
-     * Label used for accessibility.
14
-     */
15
-    accessibilityLabel: string,
16
-
17
-    /**
18
-     * CSS class name used for custom styles.
19
-     */
20
-    className?: string,
21
-
22
-    /**
23
-     * Custom icon. If used, the icon prop is ignored.
24
-     * Used to allow custom children instead of just the default icons.
25
-     */
26
-    customIcon?: React$Node,
27
-
28
-    /**
29
-     * Id of the action container.
30
-     */
31
-    id?: string,
32
-
33
-    /**
34
-     * Default icon for action.
35
-     */
36
-    icon?: Function,
37
-
38
-    /**
39
-     * Click handler.
40
-     */
41
-    onClick?: Function,
42
-
43
-    /**
44
-     * Action text.
45
-     */
46
-    text: string
47
-}
5
+import ContextMenuItem, { type Props as Action } from './ContextMenuItem';
48
 
6
 
49
 type Props = {
7
 type Props = {
50
 
8
 
59
     children?: React$Node,
17
     children?: React$Node,
60
 };
18
 };
61
 
19
 
62
-
63
 const useStyles = makeStyles(theme => {
20
 const useStyles = makeStyles(theme => {
64
     return {
21
     return {
65
         contextMenuItemGroup: {
22
         contextMenuItemGroup: {
70
             '& + &:not(:empty)': {
27
             '& + &:not(:empty)': {
71
                 borderTop: `1px solid ${theme.palette.ui04}`
28
                 borderTop: `1px solid ${theme.palette.ui04}`
72
             }
29
             }
73
-        },
74
-
75
-        contextMenuItem: {
76
-            alignItems: 'center',
77
-            cursor: 'pointer',
78
-            display: 'flex',
79
-            minHeight: '40px',
80
-            padding: '10px 16px',
81
-            boxSizing: 'border-box',
82
-
83
-            '& > *:not(:last-child)': {
84
-                marginRight: `${theme.spacing(3)}px`
85
-            },
86
-
87
-            '&:hover': {
88
-                backgroundColor: theme.palette.ui04
89
-            }
90
-        },
91
-
92
-        contextMenuItemDrawer: {
93
-            padding: '12px 16px'
94
-        },
95
-
96
-        contextMenuItemIcon: {
97
-            '& svg': {
98
-                fill: theme.palette.icon01
99
-            }
100
         }
30
         }
101
     };
31
     };
102
 });
32
 });
106
     children
36
     children
107
 }: Props) => {
37
 }: Props) => {
108
     const styles = useStyles();
38
     const styles = useStyles();
109
-    const _overflowDrawer = useSelector(showOverflowDrawer);
110
 
39
 
111
     return (
40
     return (
112
         <div className = { styles.contextMenuItemGroup }>
41
         <div className = { styles.contextMenuItemGroup }>
113
             {children}
42
             {children}
114
-            {actions && actions.map(({ accessibilityLabel, className, customIcon, id, icon, onClick, text }) => (
115
-                <div
116
-                    aria-label = { accessibilityLabel }
117
-                    className = { clsx(styles.contextMenuItem,
118
-                        _overflowDrawer && styles.contextMenuItemDrawer,
119
-                        className
120
-                    ) }
121
-                    id = { id }
122
-                    key = { text }
123
-                    onClick = { onClick }>
124
-                    {customIcon ? customIcon
125
-                        : icon && <Icon
126
-                            className = { styles.contextMenuItemIcon }
127
-                            size = { 20 }
128
-                            src = { icon } />}
129
-                    <span>{text}</span>
130
-                </div>
43
+            {actions && actions.map(actionProps => (
44
+                <ContextMenuItem
45
+                    key = { actionProps.text }
46
+                    { ...actionProps } />
131
             ))}
47
             ))}
132
         </div>
48
         </div>
133
     );
49
     );

+ 2
- 2
react/features/base/icons/svg/crown.svg Parādīt failu

1
-<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
2
-<path fill-rule="evenodd" clip-rule="evenodd" d="M14 4C14 4.85739 13.4605 5.58876 12.7024 5.87317L14.2286 9.94296L14.9455 11.8546C15.0074 11.9292 15.0708 11.9292 15.1098 11.8902L16.5535 10.4465L18.5858 8.41421C18.2239 8.05228 18 7.55228 18 7C18 5.89543 18.8954 5 20 5C21.1046 5 22 5.89543 22 7C22 8.10457 21.1046 9 20 9C19.9441 9 19.8887 8.9977 19.8339 8.9932L19 19C19 20.1046 18.1046 21 17 21H7C5.89543 21 5 20.1046 5 19L4.1661 8.9932C4.11133 8.9977 4.05593 9 4 9C2.89543 9 2 8.10457 2 7C2 5.89543 2.89543 5 4 5C5.10457 5 6 5.89543 6 7C6 7.55228 5.77614 8.05228 5.41421 8.41421L7.44654 10.4465L8.89019 11.8902C8.9775 11.9325 9.03514 11.9063 9.05453 11.8546L9.77139 9.94296L11.2976 5.87317C10.5395 5.58876 10 4.85739 10 4C10 2.89543 10.8954 2 12 2C13.1046 2 14 2.89543 14 4ZM6.84027 17L6.44651 12.2749L7.47597 13.3044C7.68795 13.5164 7.94285 13.6805 8.22354 13.7858C9.30949 14.193 10.52 13.6428 10.9272 12.5568L12 9.696L13.0728 12.5568C13.1781 12.8375 13.3422 13.0924 13.5542 13.3044C14.3743 14.1245 15.7039 14.1245 16.524 13.3044L17.5535 12.2749L17.1597 17H6.84027Z"/>
1
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+<path fill-rule="evenodd" clip-rule="evenodd" d="M8.75 2.5C8.75 3.03587 8.41281 3.49298 7.93902 3.67073L8.89288 6.21435L9.34092 7.40912C9.37965 7.45577 9.41923 7.45577 9.44363 7.43137L10.3459 6.52909L11.6161 5.25888C11.3899 5.03268 11.25 4.72018 11.25 4.375C11.25 3.68464 11.8096 3.125 12.5 3.125C13.1904 3.125 13.75 3.68464 13.75 4.375C13.75 5.06536 13.1904 5.625 12.5 5.625C12.465 5.625 12.4304 5.62356 12.3962 5.62075L11.875 11.875C11.875 12.5654 11.3154 13.125 10.625 13.125H4.375C3.68464 13.125 3.125 12.5654 3.125 11.875L2.60381 5.62075C2.56958 5.62356 2.53496 5.625 2.5 5.625C1.80964 5.625 1.25 5.06536 1.25 4.375C1.25 3.68464 1.80964 3.125 2.5 3.125C3.19036 3.125 3.75 3.68464 3.75 4.375C3.75 4.72018 3.61009 5.03268 3.38388 5.25888L4.65409 6.52909L5.55637 7.43137C5.61094 7.45781 5.64696 7.44144 5.65908 7.40912L6.10712 6.21435L7.06098 3.67073C6.58719 3.49298 6.25 3.03587 6.25 2.5C6.25 1.80964 6.80964 1.25 7.5 1.25C8.19036 1.25 8.75 1.80964 8.75 2.5ZM4.27517 10.625L4.02907 7.67184L4.67248 8.31525C4.80497 8.44773 4.96428 8.55032 5.13971 8.6161C5.81843 8.87063 6.57497 8.52674 6.82949 7.84802L7.5 6.06L8.17051 7.84802C8.23629 8.02345 8.33888 8.18277 8.47136 8.31525C8.98392 8.82781 9.81495 8.82781 10.3275 8.31525L10.9709 7.67184L10.7248 10.625H4.27517Z" fill="white"/>
3
 </svg>
3
 </svg>

+ 3
- 10
react/features/base/icons/svg/mute-everyone-else.svg Parādīt failu

1
-<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
2
-<g clip-path="url(#clip0)">
3
-<path fill-rule="evenodd" clip-rule="evenodd" d="M6 13.078V15C6 16.3999 6.9589 17.5759 8.25572 17.907C8.25195 17.9374 8.25 17.9685 8.25 18V19.4378C6.12171 19.0807 4.5 17.2297 4.5 15C4.5 14.5858 4.16421 14.25 3.75 14.25C3.33579 14.25 3 14.5858 3 15C3 18.0597 5.29027 20.5845 8.25 20.9536V21.75C8.25 22.1642 8.58579 22.5 9 22.5C9.41421 22.5 9.75 22.1642 9.75 21.75V20.9536C10.8412 20.8175 11.8415 20.3884 12.6694 19.7475L15.1986 22.2766C15.4964 22.5744 15.9791 22.5745 16.2768 22.2768C16.5745 21.9791 16.5744 21.4964 16.2766 21.1986L13.7475 18.6694C13.7502 18.6659 13.753 18.6623 13.7557 18.6588L12.6831 17.5861C12.6805 17.5898 12.6779 17.5935 12.6753 17.5972L11.5911 16.513C11.5934 16.5091 11.5957 16.5051 11.598 16.5011L10.4566 15.3596C10.4554 15.3647 10.4541 15.3697 10.4528 15.3748L7.5 12.422V12.403L6 10.903V10.922L2.80143 7.72339C2.50364 7.4256 2.02091 7.42553 1.72322 7.72322C1.42553 8.02091 1.4256 8.50364 1.72339 8.80143L6 13.078ZM7.5 14.578V15C7.5 15.8284 8.17157 16.5 9 16.5C9.1294 16.5 9.25498 16.4836 9.37476 16.4528L7.5 14.578ZM10.513 17.5911C10.2756 17.73 10.0175 17.8372 9.74428 17.907C9.74805 17.9374 9.75 17.9685 9.75 18V19.4378C10.4295 19.3238 11.0573 19.0575 11.5972 18.6753L10.513 17.5911ZM12 14.747L10.5 13.247V10.5C10.5 9.67157 9.82843 9 9 9C8.25144 9 7.63095 9.54832 7.51827 10.2652L6.34845 9.09541C6.85223 8.14635 7.85064 7.5 9 7.5C10.6569 7.5 12 8.84315 12 10.5V14.747ZM13.3623 16.1092L14.5462 17.2932C14.8386 16.5867 15 15.8122 15 15C15 14.5858 14.6642 14.25 14.25 14.25C13.8358 14.25 13.5 14.5858 13.5 15C13.5 15.3828 13.4522 15.7544 13.3623 16.1092Z" />
4
-<path fill-rule="evenodd" clip-rule="evenodd" d="M16 4.71869V6C16 6.93329 16.6393 7.71727 17.5038 7.93797C17.5013 7.95829 17.5 7.97899 17.5 8V8.95852C16.0811 8.72048 15 7.4865 15 6C15 5.72386 14.7761 5.5 14.5 5.5C14.2239 5.5 14 5.72386 14 6C14 8.03981 15.5268 9.723 17.5 9.96905V10.5C17.5 10.7761 17.7239 11 18 11C18.2761 11 18.5 10.7761 18.5 10.5V9.96905C19.2275 9.87834 19.8943 9.59227 20.4463 9.16499L22.1324 10.8511C22.3309 11.0496 22.6527 11.0496 22.8512 10.8512C23.0496 10.6527 23.0496 10.3309 22.8511 10.1324L21.165 8.4463C21.1668 8.44393 21.1687 8.44155 21.1705 8.43918L20.4554 7.7241C20.4537 7.72656 20.4519 7.72903 20.4502 7.73149L19.7274 7.00869C19.7289 7.00603 19.7305 7.00338 19.732 7.00072L18.9711 6.23977C18.9702 6.24313 18.9694 6.24649 18.9685 6.24984L17 4.28131V4.26869L16 3.26869V3.28131L13.8676 1.14893C13.6691 0.950402 13.3473 0.950351 13.1488 1.14881C12.9504 1.34727 12.9504 1.6691 13.1489 1.86762L16 4.71869ZM17 5.71869V6C17 6.55228 17.4477 7 18 7C18.0863 7 18.17 6.98908 18.2498 6.96854L17 5.71869ZM19.0087 7.72738C18.8504 7.81999 18.6783 7.89148 18.4962 7.93797C18.4987 7.95829 18.5 7.97899 18.5 8V8.95852C18.953 8.88252 19.3715 8.70502 19.7315 8.45019L19.0087 7.72738ZM20 5.83131L19 4.83131V3C19 2.44772 18.5523 2 18 2C17.501 2 17.0873 2.36555 17.0122 2.84348L16.2323 2.06361C16.5682 1.4309 17.2338 1 18 1C19.1046 1 20 1.89543 20 3V5.83131ZM20.9082 6.73948L21.6975 7.52877C21.8924 7.05778 22 6.54145 22 6C22 5.72386 21.7761 5.5 21.5 5.5C21.2239 5.5 21 5.72386 21 6C21 6.25519 20.9681 6.50294 20.9082 6.73948Z" />
5
-</g>
6
-<defs>
7
-<clipPath id="clip0">
8
-<rect width="24" height="24"/>
9
-</clipPath>
10
-</defs>
1
+<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+<path fill-rule="evenodd" clip-rule="evenodd" d="M5 10.8984V12.5C5 13.6666 5.79908 14.6466 6.87977 14.9225C6.87662 14.9479 6.875 14.9737 6.875 15V16.1982C5.10143 15.9006 3.75 14.3581 3.75 12.5C3.75 12.1548 3.47018 11.875 3.125 11.875C2.77982 11.875 2.5 12.1548 2.5 12.5C2.5 15.0498 4.40856 17.1538 6.875 17.4613V18.125C6.875 18.4702 7.15482 18.75 7.5 18.75C7.84518 18.75 8.125 18.4702 8.125 18.125V17.4613C9.03436 17.3479 9.86788 16.9903 10.5579 16.4562L12.6655 18.5638C12.9136 18.812 13.3159 18.8121 13.564 18.564C13.8121 18.3159 13.812 17.9136 13.5638 17.6655L11.4562 15.5579C11.4585 15.5549 11.4608 15.5519 11.4631 15.549L10.5693 14.6551C10.5671 14.6582 10.5649 14.6613 10.5627 14.6644L9.65923 13.7609C9.66117 13.7575 9.6631 13.7542 9.66503 13.7509L8.71384 12.7997C8.7128 12.8039 8.71175 12.8081 8.71067 12.8123L6.25 10.3516V10.3359L5 9.08587V9.10163L2.33453 6.43616C2.08637 6.188 1.68409 6.18794 1.43602 6.43602C1.18794 6.68409 1.188 7.08637 1.43616 7.33453L5 10.8984ZM6.25 12.1484V12.5C6.25 13.1904 6.80964 13.75 7.5 13.75C7.60783 13.75 7.71248 13.7363 7.8123 13.7107L6.25 12.1484ZM8.76086 14.6592C8.56304 14.775 8.34788 14.8643 8.12023 14.9225C8.12338 14.9479 8.125 14.9737 8.125 15V16.1982C8.69123 16.1032 9.21443 15.8813 9.66436 15.5627L8.76086 14.6592ZM10 12.2891L8.75 11.0391V8.75C8.75 8.05964 8.19036 7.5 7.5 7.5C6.8762 7.5 6.35913 7.95693 6.26522 8.55435L5.29038 7.57951C5.71019 6.78863 6.5422 6.25 7.5 6.25C8.88071 6.25 10 7.36929 10 8.75V12.2891ZM11.1352 13.4243L12.1218 14.411C12.3655 13.8222 12.5 13.1768 12.5 12.5C12.5 12.1548 12.2202 11.875 11.875 11.875C11.5298 11.875 11.25 12.1548 11.25 12.5C11.25 12.819 11.2102 13.1287 11.1352 13.4243Z" fill="white"/>
3
+<path fill-rule="evenodd" clip-rule="evenodd" d="M13.3333 3.93224V5C13.3333 5.77774 13.8661 6.43106 14.5865 6.61497C14.5844 6.63191 14.5833 6.64916 14.5833 6.66666V7.46543C13.401 7.26706 12.5 6.23874 12.5 4.99999C12.5 4.76988 12.3135 4.58333 12.0833 4.58333C11.8532 4.58333 11.6667 4.76988 11.6667 4.99999C11.6667 6.69984 12.939 8.1025 14.5833 8.30754V8.74999C14.5833 8.98011 14.7699 9.16666 15 9.16666C15.2301 9.16666 15.4167 8.98011 15.4167 8.74999V8.30754C16.0229 8.23194 16.5786 7.99355 17.0386 7.63749L18.4437 9.04256C18.6091 9.20799 18.8773 9.20804 19.0427 9.04265C19.2081 8.87727 19.208 8.60908 19.0426 8.44364L17.6375 7.03857C17.639 7.0366 17.6406 7.03462 17.6421 7.03264L17.0462 6.43674C17.0447 6.4388 17.0433 6.44085 17.0418 6.4429L16.4395 5.84057C16.4408 5.83836 16.4421 5.83614 16.4434 5.83392L15.8092 5.1998C15.8085 5.2026 15.8078 5.2054 15.8071 5.2082L14.1667 3.56775V3.55724L13.3333 2.72391V2.73442L11.5564 0.957435C11.3909 0.791997 11.1227 0.791954 10.9574 0.957339C10.792 1.12272 10.792 1.39091 10.9574 1.55635L13.3333 3.93224ZM14.1667 4.76557V4.99999C14.1667 5.46023 14.5398 5.83333 15 5.83333C15.0719 5.83333 15.1417 5.82422 15.2082 5.80711L14.1667 4.76557ZM15.8406 6.43948C15.7087 6.51666 15.5653 6.57623 15.4135 6.61497C15.4156 6.63191 15.4167 6.64916 15.4167 6.66666V7.46543C15.7942 7.4021 16.143 7.25417 16.4429 7.04182L15.8406 6.43948ZM16.6667 4.85942L15.8333 4.02608V2.49999C15.8333 2.03976 15.4602 1.66666 15 1.66666C14.5841 1.66666 14.2394 1.97128 14.1768 2.36956L13.5269 1.71967C13.8068 1.19241 14.3615 0.833328 15 0.833328C15.9205 0.833328 16.6667 1.57952 16.6667 2.49999V4.85942ZM17.4235 5.61623L18.0812 6.27397C18.2437 5.88148 18.3333 5.45121 18.3333 4.99999C18.3333 4.76988 18.1468 4.58333 17.9167 4.58333C17.6866 4.58333 17.5 4.76988 17.5 4.99999C17.5 5.21265 17.4735 5.41911 17.4235 5.61623Z" fill="white"/>
11
 </svg>
4
 </svg>

+ 2
- 2
react/features/base/icons/svg/share-desktop.svg Parādīt failu

1
-<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
2
-<path fill-rule="evenodd" clip-rule="evenodd" d="M3.66671 2.75H18.3334C19.3459 2.75 20.1667 3.57081 20.1667 4.58333V15.5833C20.1667 16.5959 19.3459 17.4167 18.3334 17.4167H15.5834C16.0896 17.4167 16.5 17.8271 16.5 18.3333C16.5 18.8396 16.0896 19.25 15.5834 19.25H6.41671C5.91045 19.25 5.50004 18.8396 5.50004 18.3333C5.50004 17.8271 5.91045 17.4167 6.41671 17.4167H3.66671C2.65419 17.4167 1.83337 16.5959 1.83337 15.5833V4.58333C1.83337 3.57081 2.65419 2.75 3.66671 2.75ZM3.66671 4.58333V15.5833H18.3334V4.58333H3.66671ZM11.9167 8.25C8.16671 8.25 6.41671 9.85417 6.41671 14.6667C8.41671 10.7708 11.9167 11 11.9167 11V12.274C11.9167 12.6941 12.4034 12.9269 12.7305 12.6633L16.017 10.0143C16.2654 9.81413 16.2654 9.43582 16.017 9.23568L12.7305 6.5867C12.4034 6.32307 11.9167 6.55589 11.9167 6.97599V8.25Z" />
1
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+<path fill-rule="evenodd" clip-rule="evenodd" d="M2.5 1.875H12.5C13.1904 1.875 13.75 2.43464 13.75 3.125V10.625C13.75 11.3154 13.1904 11.875 12.5 11.875H10.625C10.9702 11.875 11.25 12.1548 11.25 12.5C11.25 12.8452 10.9702 13.125 10.625 13.125H4.375C4.02982 13.125 3.75 12.8452 3.75 12.5C3.75 12.1548 4.02982 11.875 4.375 11.875H2.5C1.80964 11.875 1.25 11.3154 1.25 10.625V3.125C1.25 2.43464 1.80964 1.875 2.5 1.875ZM2.5 3.125V10.625H12.5V3.125H2.5ZM8.125 5.625C5.56818 5.625 4.375 6.71875 4.375 10C5.73864 7.34375 8.125 7.5 8.125 7.5V8.03605C8.125 8.45615 8.61169 8.68897 8.93877 8.42534L10.767 6.95178C11.0153 6.75164 11.0153 6.37333 10.767 6.17319L8.93877 4.69963C8.61169 4.436 8.125 4.66882 8.125 5.08892V5.625Z" fill="white"/>
3
 </svg>
3
 </svg>

+ 3
- 13
react/features/base/popover/components/Popover.web.js Parādīt failu

1
 /* @flow */
1
 /* @flow */
2
-import clsx from 'clsx';
3
 import React, { Component } from 'react';
2
 import React, { Component } from 'react';
4
 
3
 
5
 import { Drawer, JitsiPortal, DialogPortal } from '../../../toolbox/components/web';
4
 import { Drawer, JitsiPortal, DialogPortal } from '../../../toolbox/components/web';
60
      */
59
      */
61
     position: string,
60
     position: string,
62
 
61
 
63
-    /**
64
-     * Whether the content show have some padding.
65
-     */
66
-    paddedContent: ?boolean,
67
-
68
     /**
62
     /**
69
      * Whether the popover is visible or not.
63
      * Whether the popover is visible or not.
70
      */
64
      */
79
     /**
73
     /**
80
      * The style to apply to the context menu in order to position it correctly.
74
      * The style to apply to the context menu in order to position it correctly.
81
      */
75
      */
82
-     contextMenuStyle: Object
76
+    contextMenuStyle: Object
83
 };
77
 };
84
 
78
 
85
 /**
79
 /**
364
      * @returns {ReactElement}
358
      * @returns {ReactElement}
365
      */
359
      */
366
     _renderContent() {
360
     _renderContent() {
367
-        const { content, paddedContent } = this.props;
368
-        const className = clsx(
369
-            'popover popupmenu',
370
-            paddedContent && 'padded-content'
371
-        );
361
+        const { content } = this.props;
372
 
362
 
373
         return (
363
         return (
374
             <div
364
             <div
375
-                className = { className }
365
+                className = 'popover'
376
                 onKeyDown = { this._onEscKey }>
366
                 onKeyDown = { this._onEscKey }>
377
                 { content }
367
                 { content }
378
                 {!isMobileBrowser() && (
368
                 {!isMobileBrowser() && (

+ 51
- 58
react/features/base/react/components/web/BaseIndicator.js Parādīt failu

1
 /* @flow */
1
 /* @flow */
2
 
2
 
3
-import React, { Component } from 'react';
3
+import { makeStyles } from '@material-ui/core';
4
+import React from 'react';
4
 
5
 
5
 import { translate } from '../../../i18n';
6
 import { translate } from '../../../i18n';
6
 import { Icon } from '../../../icons';
7
 import { Icon } from '../../../icons';
12
 type Props = {
13
 type Props = {
13
 
14
 
14
     /**
15
     /**
15
-     * Additional CSS class names to set on the icon container.
16
+     * Additional CSS class name.
16
      */
17
      */
17
     className: string,
18
     className: string,
18
 
19
 
59
     tooltipPosition: string
60
     tooltipPosition: string
60
 };
61
 };
61
 
62
 
63
+const useStyles = makeStyles(() => {
64
+    return {
65
+        indicator: {
66
+            width: '20px',
67
+            height: '20px',
68
+            display: 'flex',
69
+            alignItems: 'center',
70
+            justifyContent: 'center'
71
+        }
72
+    };
73
+});
74
+
62
 /**
75
 /**
63
  * React {@code Component} for showing an icon with a tooltip.
76
  * React {@code Component} for showing an icon with a tooltip.
64
  *
77
  *
65
- * @augments Component
78
+ * @returns {ReactElement}
66
  */
79
  */
67
-class BaseIndicator extends Component<Props> {
68
-    /**
69
-     * Default values for {@code BaseIndicator} component's properties.
70
-     *
71
-     * @static
72
-     */
73
-    static defaultProps = {
74
-        className: '',
75
-        id: '',
76
-        tooltipPosition: 'top'
77
-    };
78
-
79
-    /**
80
-     * Implements React's {@link Component#render()}.
81
-     *
82
-     * @inheritdoc
83
-     * @returns {ReactElement}
84
-     */
85
-    render() {
86
-        const {
87
-            className,
88
-            icon,
89
-            iconClassName,
90
-            iconId,
91
-            iconSize,
92
-            id,
93
-            t,
94
-            tooltipKey,
95
-            tooltipPosition
96
-        } = this.props;
97
-        const iconContainerClassName = `indicator-icon-container ${className}`;
98
-        const style = {};
99
-
100
-        if (iconSize) {
101
-            style.fontSize = iconSize;
102
-        }
103
-
104
-        return (
105
-            <div className = 'indicator-container'>
106
-                <Tooltip
107
-                    content = { t(tooltipKey) }
108
-                    position = { tooltipPosition }>
109
-                    <span
110
-                        className = { iconContainerClassName }
111
-                        id = { id }>
112
-                        <Icon
113
-                            className = { iconClassName }
114
-                            id = { iconId }
115
-                            src = { icon }
116
-                            style = { style } />
117
-                    </span>
118
-                </Tooltip>
119
-            </div>
120
-        );
80
+const BaseIndicator = ({
81
+    className = '',
82
+    icon,
83
+    iconClassName,
84
+    iconId,
85
+    iconSize,
86
+    id = '',
87
+    t,
88
+    tooltipKey,
89
+    tooltipPosition = 'top'
90
+}: Props) => {
91
+    const styles = useStyles();
92
+    const style = {};
93
+
94
+    if (iconSize) {
95
+        style.fontSize = iconSize;
121
     }
96
     }
122
-}
97
+
98
+    return (
99
+        <div className = { styles.indicator }>
100
+            <Tooltip
101
+                content = { t(tooltipKey) }
102
+                position = { tooltipPosition }>
103
+                <span
104
+                    className = { className }
105
+                    id = { id }>
106
+                    <Icon
107
+                        className = { iconClassName }
108
+                        id = { iconId }
109
+                        src = { icon }
110
+                        style = { style } />
111
+                </span>
112
+            </Tooltip>
113
+        </div>
114
+    );
115
+};
123
 
116
 
124
 export default translate(BaseIndicator);
117
 export default translate(BaseIndicator);

+ 74
- 51
react/features/connection-indicator/components/web/ConnectionIndicator.js Parādīt failu

1
 // @flow
1
 // @flow
2
 
2
 
3
+import { withStyles } from '@material-ui/styles';
4
+import clsx from 'clsx';
3
 import React from 'react';
5
 import React from 'react';
4
 import type { Dispatch } from 'redux';
6
 import type { Dispatch } from 'redux';
5
 
7
 
30
     {
32
     {
31
         colorClass: 'status-high',
33
         colorClass: 'status-high',
32
         percent: INDICATOR_DISPLAY_THRESHOLD,
34
         percent: INDICATOR_DISPLAY_THRESHOLD,
33
-        tip: 'connectionindicator.quality.good',
34
-        width: '100%'
35
+        tip: 'connectionindicator.quality.good'
35
     },
36
     },
36
 
37
 
37
     // 2 bars
38
     // 2 bars
38
     {
39
     {
39
         colorClass: 'status-med',
40
         colorClass: 'status-med',
40
         percent: 10,
41
         percent: 10,
41
-        tip: 'connectionindicator.quality.nonoptimal',
42
-        width: '66%'
42
+        tip: 'connectionindicator.quality.nonoptimal'
43
     },
43
     },
44
 
44
 
45
     // 1 bar
45
     // 1 bar
46
     {
46
     {
47
         colorClass: 'status-low',
47
         colorClass: 'status-low',
48
         percent: 0,
48
         percent: 0,
49
-        tip: 'connectionindicator.quality.poor',
50
-        width: '33%'
49
+        tip: 'connectionindicator.quality.poor'
51
     }
50
     }
52
 
51
 
53
     // Note: we never show 0 bars as long as there is a connection.
52
     // Note: we never show 0 bars as long as there is a connection.
85
      */
84
      */
86
     audioSsrc: number,
85
     audioSsrc: number,
87
 
86
 
87
+    /**
88
+     * An object containing the CSS classes.
89
+     */
90
+    classes: Object,
91
+
88
     /**
92
     /**
89
      * The Redux dispatch function.
93
      * The Redux dispatch function.
90
      */
94
      */
122
     popoverVisible: boolean
126
     popoverVisible: boolean
123
 }
127
 }
124
 
128
 
129
+const styles = theme => {
130
+    return {
131
+        container: {
132
+            display: 'inline-block'
133
+        },
134
+
135
+        hidden: {
136
+            display: 'none'
137
+        },
138
+
139
+        icon: {
140
+            padding: '6px',
141
+            borderRadius: '4px',
142
+
143
+            '&.status-high': {
144
+                backgroundColor: theme.palette.success01
145
+            },
146
+
147
+            '&.status-med': {
148
+                backgroundColor: theme.palette.warning01
149
+            },
150
+
151
+            '&.status-low': {
152
+                backgroundColor: theme.palette.iconError
153
+            },
154
+
155
+            '&.status-disabled': {
156
+                background: 'transparent'
157
+            },
158
+
159
+            '&.status-lost': {
160
+                backgroundColor: theme.palette.ui05
161
+            },
162
+
163
+            '&.status-other': {
164
+                backgroundColor: theme.palette.action01
165
+            }
166
+        },
167
+
168
+        inactiveIcon: {
169
+            padding: 0,
170
+            borderRadius: '50%'
171
+        }
172
+    };
173
+};
174
+
125
 /**
175
 /**
126
  * Implements a React {@link Component} which displays the current connection
176
  * Implements a React {@link Component} which displays the current connection
127
  * quality percentage and has a popover to show more detailed connection stats.
177
  * quality percentage and has a popover to show more detailed connection stats.
154
      * @returns {ReactElement}
204
      * @returns {ReactElement}
155
      */
205
      */
156
     render() {
206
     render() {
157
-        const { enableStatsDisplay, participantId, statsPopoverPosition } = this.props;
207
+        const { enableStatsDisplay, participantId, statsPopoverPosition, classes } = this.props;
158
         const visibilityClass = this._getVisibilityClass();
208
         const visibilityClass = this._getVisibilityClass();
159
-        const rootClassNames = `indicator-container ${visibilityClass}`;
160
 
209
 
161
         if (this.props._popoverDisabled) {
210
         if (this.props._popoverDisabled) {
162
             return this._renderIndicator();
211
             return this._renderIndicator();
164
 
213
 
165
         return (
214
         return (
166
             <Popover
215
             <Popover
167
-                className = { rootClassNames }
216
+                className = { clsx(classes.container, visibilityClass) }
168
                 content = { <ConnectionIndicatorContent
217
                 content = { <ConnectionIndicatorContent
169
                     inheritedStats = { this.state.stats }
218
                     inheritedStats = { this.state.stats }
170
                     participantId = { participantId } /> }
219
                     participantId = { participantId } /> }
173
                 noPaddingContent = { true }
222
                 noPaddingContent = { true }
174
                 onPopoverClose = { this._onHidePopover }
223
                 onPopoverClose = { this._onHidePopover }
175
                 onPopoverOpen = { this._onShowPopover }
224
                 onPopoverOpen = { this._onShowPopover }
176
-                paddedContent = { true }
177
                 position = { statsPopoverPosition }
225
                 position = { statsPopoverPosition }
178
                 visible = { this.state.popoverVisible }>
226
                 visible = { this.state.popoverVisible }>
179
                 { this._renderIndicator() }
227
                 { this._renderIndicator() }
231
      * @returns {string}
279
      * @returns {string}
232
      */
280
      */
233
     _getVisibilityClass() {
281
     _getVisibilityClass() {
234
-        const { _connectionStatus } = this.props;
282
+        const { _connectionStatus, classes } = this.props;
235
 
283
 
236
         return this.state.showIndicator
284
         return this.state.showIndicator
237
             || this.props.alwaysVisible
285
             || this.props.alwaysVisible
238
             || _connectionStatus === JitsiParticipantConnectionStatus.INTERRUPTED
286
             || _connectionStatus === JitsiParticipantConnectionStatus.INTERRUPTED
239
             || _connectionStatus === JitsiParticipantConnectionStatus.INACTIVE
287
             || _connectionStatus === JitsiParticipantConnectionStatus.INACTIVE
240
-            ? 'show-connection-indicator' : 'hide-connection-indicator';
288
+            ? '' : classes.hidden;
241
     }
289
     }
242
 
290
 
243
     _onHidePopover: () => void;
291
     _onHidePopover: () => void;
259
      * @returns {ReactElement}
307
      * @returns {ReactElement}
260
      */
308
      */
261
     _renderIcon() {
309
     _renderIcon() {
310
+        const colorClass = this._getConnectionColorClass();
311
+
262
         if (this.props._connectionStatus === JitsiParticipantConnectionStatus.INACTIVE) {
312
         if (this.props._connectionStatus === JitsiParticipantConnectionStatus.INACTIVE) {
263
             if (this.props._connectionIndicatorInactiveDisabled) {
313
             if (this.props._connectionIndicatorInactiveDisabled) {
264
                 return null;
314
                 return null;
267
             return (
317
             return (
268
                 <span className = 'connection_ninja'>
318
                 <span className = 'connection_ninja'>
269
                     <Icon
319
                     <Icon
270
-                        className = 'icon-ninja'
271
-                        size = '1.5em'
320
+                        className = { clsx(this.props.classes.icon, this.props.classes.inactiveIcon, colorClass) }
321
+                        size = { 24 }
272
                         src = { IconConnectionInactive } />
322
                         src = { IconConnectionInactive } />
273
                 </span>
323
                 </span>
274
             );
324
             );
275
         }
325
         }
276
 
326
 
277
-        let iconWidth;
278
         let emptyIconWrapperClassName = 'connection_empty';
327
         let emptyIconWrapperClassName = 'connection_empty';
279
 
328
 
280
         if (this.props._connectionStatus
329
         if (this.props._connectionStatus
283
             // emptyIconWrapperClassName is used by the torture tests to
332
             // emptyIconWrapperClassName is used by the torture tests to
284
             // identify lost connection status handling.
333
             // identify lost connection status handling.
285
             emptyIconWrapperClassName = 'connection_lost';
334
             emptyIconWrapperClassName = 'connection_lost';
286
-            iconWidth = '0%';
287
-        } else if (typeof this.state.stats.percent === 'undefined') {
288
-            iconWidth = '100%';
289
-        } else {
290
-            const { percent } = this.state.stats;
291
-
292
-            iconWidth = this._getDisplayConfiguration(percent).width;
293
         }
335
         }
294
 
336
 
295
-        return [
296
-            <span
297
-                className = { emptyIconWrapperClassName }
298
-                key = 'icon-empty'>
299
-                <Icon
300
-                    className = 'icon-gsm-bars'
301
-                    size = '1em'
302
-                    src = { IconConnectionActive } />
303
-            </span>,
304
-            <span
305
-                className = 'connection_full'
306
-                key = 'icon-full'
307
-                style = {{ width: iconWidth }}>
337
+        return (
338
+            <span className = { emptyIconWrapperClassName }>
308
                 <Icon
339
                 <Icon
309
-                    className = 'icon-gsm-bars'
310
-                    size = '1em'
340
+                    className = { clsx(this.props.classes.icon, colorClass) }
341
+                    size = { 12 }
311
                     src = { IconConnectionActive } />
342
                     src = { IconConnectionActive } />
312
             </span>
343
             </span>
313
-        ];
344
+        );
314
     }
345
     }
315
 
346
 
316
     _onShowPopover: () => void;
347
     _onShowPopover: () => void;
332
      * @returns {ReactElement}
363
      * @returns {ReactElement}
333
      */
364
      */
334
     _renderIndicator() {
365
     _renderIndicator() {
335
-        const colorClass = this._getConnectionColorClass();
336
-        const indicatorContainerClassNames
337
-              = `connection-indicator indicator ${colorClass}`;
338
-
339
         return (
366
         return (
340
-            <div className = 'popover-trigger'>
341
-                <div
342
-                    className = { indicatorContainerClassNames }
343
-                    style = {{ fontSize: this.props.iconSize }}>
344
-                    <div className = 'connection indicatoricon'>
345
-                        { this._renderIcon() }
346
-                    </div>
347
-                </div>
367
+            <div
368
+                style = {{ fontSize: this.props.iconSize }}>
369
+                {this._renderIcon()}
348
             </div>
370
             </div>
349
         );
371
         );
350
     }
372
     }
369
         _connectionStatus: participant?.connectionStatus
391
         _connectionStatus: participant?.connectionStatus
370
     };
392
     };
371
 }
393
 }
372
-export default translate(connect(_mapStateToProps)(ConnectionIndicator));
394
+export default translate(connect(_mapStateToProps)(
395
+    withStyles(styles)(ConnectionIndicator)));

+ 37
- 11
react/features/connection-stats/components/ConnectionStatsTable.js Parādīt failu

1
 /* @flow */
1
 /* @flow */
2
 
2
 
3
+import { withStyles } from '@material-ui/styles';
3
 import React, { Component } from 'react';
4
 import React, { Component } from 'react';
4
 
5
 
5
 import { isMobileBrowser } from '../../../features/base/environment/utils';
6
 import { isMobileBrowser } from '../../../features/base/environment/utils';
7
+import ContextMenu from '../../base/components/context-menu/ContextMenu';
6
 import { translate } from '../../base/i18n';
8
 import { translate } from '../../base/i18n';
7
 
9
 
8
 /**
10
 /**
40
      */
42
      */
41
     bridgeCount: number,
43
     bridgeCount: number,
42
 
44
 
45
+    /**
46
+     * An object containing the CSS classes.
47
+     */
48
+    classes: Object,
49
+
43
     /**
50
     /**
44
      * Audio/video codecs in use for the connection.
51
      * Audio/video codecs in use for the connection.
45
      */
52
      */
163
     event.stopPropagation();
170
     event.stopPropagation();
164
 }
171
 }
165
 
172
 
173
+const styles = theme => {
174
+    return {
175
+        contextMenu: {
176
+            position: 'relative',
177
+            marginTop: 0,
178
+            right: 'auto',
179
+            padding: `${theme.spacing(2)}px ${theme.spacing(1)}px`,
180
+            marginLeft: '4px',
181
+            marginRight: '4px',
182
+            marginBottom: '4px'
183
+        }
184
+    };
185
+};
186
+
166
 /**
187
 /**
167
  * React {@code Component} for displaying connection statistics.
188
  * React {@code Component} for displaying connection statistics.
168
  *
189
  *
176
      * @returns {ReactElement}
197
      * @returns {ReactElement}
177
      */
198
      */
178
     render() {
199
     render() {
179
-        const { isLocalVideo, enableSaveLogs, disableShowMoreStats } = this.props;
200
+        const { isLocalVideo, enableSaveLogs, disableShowMoreStats, classes } = this.props;
180
         const className = isMobileBrowser() ? 'connection-info connection-info__mobile' : 'connection-info';
201
         const className = isMobileBrowser() ? 'connection-info connection-info__mobile' : 'connection-info';
181
 
202
 
182
         return (
203
         return (
183
-            <div
184
-                className = { className }
185
-                onClick = { onClick }>
186
-                { this._renderStatistics() }
187
-                <div className = 'connection-actions'>
188
-                    { isLocalVideo && enableSaveLogs ? this._renderSaveLogs() : null}
189
-                    { !disableShowMoreStats && this._renderShowMoreLink() }
204
+            <ContextMenu
205
+                className = { classes.contextMenu }
206
+                hidden = { false }
207
+                inDrawer = { true }>
208
+                <div
209
+                    className = { className }
210
+                    onClick = { onClick }>
211
+                    { this._renderStatistics() }
212
+                    <div className = 'connection-actions'>
213
+                        { isLocalVideo && enableSaveLogs ? this._renderSaveLogs() : null}
214
+                        { !disableShowMoreStats && this._renderShowMoreLink() }
215
+                    </div>
216
+                    { this.props.shouldShowMore ? this._renderAdditionalStats() : null }
190
                 </div>
217
                 </div>
191
-                { this.props.shouldShowMore ? this._renderAdditionalStats() : null }
192
-            </div>
218
+            </ContextMenu>
193
         );
219
         );
194
     }
220
     }
195
 
221
 
839
     return res;
865
     return res;
840
 }
866
 }
841
 
867
 
842
-export default translate(ConnectionStatsTable);
868
+export default translate(withStyles(styles)(ConnectionStatsTable));

+ 66
- 9
react/features/display-name/components/web/DisplayName.js Parādīt failu

1
 /* @flow */
1
 /* @flow */
2
 
2
 
3
+import { withStyles } from '@material-ui/styles';
3
 import React, { Component } from 'react';
4
 import React, { Component } from 'react';
4
 import type { Dispatch } from 'redux';
5
 import type { Dispatch } from 'redux';
5
 
6
 
10
 } from '../../../base/participants';
11
 } from '../../../base/participants';
11
 import { connect } from '../../../base/redux';
12
 import { connect } from '../../../base/redux';
12
 import { updateSettings } from '../../../base/settings';
13
 import { updateSettings } from '../../../base/settings';
14
+import { Tooltip } from '../../../base/tooltip';
15
+import { getIndicatorsTooltipPosition } from '../../../filmstrip/functions.web';
13
 import { appendSuffix } from '../../functions';
16
 import { appendSuffix } from '../../functions';
14
 
17
 
15
 /**
18
 /**
33
      */
36
      */
34
     allowEditing: boolean,
37
     allowEditing: boolean,
35
 
38
 
39
+    /**
40
+     * The current layout of the filmstrip.
41
+     */
42
+    currentLayout: string,
43
+
36
     /**
44
     /**
37
      * Invoked to update the participant's display name.
45
      * Invoked to update the participant's display name.
38
      */
46
      */
43
      */
51
      */
44
     displayNameSuffix: string,
52
     displayNameSuffix: string,
45
 
53
 
54
+    /**
55
+     * An object containing the CSS classes.
56
+     */
57
+    classes: Object,
58
+
46
     /**
59
     /**
47
      * The ID attribute to add to the component. Useful for global querying for
60
      * The ID attribute to add to the component. Useful for global querying for
48
      * the component by legacy components and torture tests.
61
      * the component by legacy components and torture tests.
76
     isEditing: boolean
89
     isEditing: boolean
77
 };
90
 };
78
 
91
 
92
+const styles = theme => {
93
+    return {
94
+        displayName: {
95
+            ...theme.typography.labelBold,
96
+            lineHeight: `${theme.typography.labelBold.lineHeight}px`,
97
+            color: theme.palette.text01,
98
+            overflow: 'hidden',
99
+            textOverflow: 'ellipsis',
100
+            whiteSpace: 'nowrap'
101
+        },
102
+
103
+        editDisplayName: {
104
+            outline: 'none',
105
+            border: 'none',
106
+            background: 'none',
107
+            boxShadow: 'none',
108
+            padding: 0,
109
+            ...theme.typography.labelBold,
110
+            lineHeight: `${theme.typography.labelBold.lineHeight}px`,
111
+            color: theme.palette.text01
112
+        }
113
+    };
114
+};
115
+
79
 /**
116
 /**
80
  * React {@code Component} for displaying and editing a participant's name.
117
  * React {@code Component} for displaying and editing a participant's name.
81
  *
118
  *
146
         const {
183
         const {
147
             _nameToDisplay,
184
             _nameToDisplay,
148
             allowEditing,
185
             allowEditing,
186
+            currentLayout,
149
             displayNameSuffix,
187
             displayNameSuffix,
188
+            classes,
150
             elementID,
189
             elementID,
151
             t
190
             t
152
         } = this.props;
191
         } = this.props;
155
             return (
194
             return (
156
                 <input
195
                 <input
157
                     autoFocus = { true }
196
                     autoFocus = { true }
158
-                    className = 'editdisplayname'
197
+                    className = { classes.editDisplayName }
159
                     id = 'editDisplayName'
198
                     id = 'editDisplayName'
160
                     onBlur = { this._onSubmit }
199
                     onBlur = { this._onSubmit }
161
                     onChange = { this._onChange }
200
                     onChange = { this._onChange }
201
+                    onClick = { this._onClick }
162
                     onKeyDown = { this._onKeyDown }
202
                     onKeyDown = { this._onKeyDown }
163
                     placeholder = { t('defaultNickname') }
203
                     placeholder = { t('defaultNickname') }
164
                     ref = { this._setNameInputRef }
204
                     ref = { this._setNameInputRef }
169
         }
209
         }
170
 
210
 
171
         return (
211
         return (
172
-            <span
173
-                className = 'displayname'
174
-                id = { elementID }
175
-                onClick = { this._onStartEditing }>
176
-                { appendSuffix(_nameToDisplay, displayNameSuffix) }
177
-            </span>
212
+            <Tooltip
213
+                content = { appendSuffix(_nameToDisplay, displayNameSuffix) }
214
+                position = { getIndicatorsTooltipPosition(currentLayout) }>
215
+                <span
216
+                    className = { `displayname ${classes.displayName}` }
217
+                    id = { elementID }
218
+                    onClick = { this._onStartEditing }>
219
+                    { appendSuffix(_nameToDisplay, displayNameSuffix) }
220
+                </span>
221
+            </Tooltip>
178
         );
222
         );
179
     }
223
     }
180
 
224
 
225
+    /**
226
+     * Stop click event propagation.
227
+     *
228
+     * @param {MouseEvent} e - The click event.
229
+     * @private
230
+     * @returns {void}
231
+     */
232
+    _onClick(e) {
233
+        e.stopPropagation();
234
+    }
235
+
181
     _onChange: () => void;
236
     _onChange: () => void;
182
 
237
 
183
     /**
238
     /**
215
      * Updates the component to display an editable input field and sets the
270
      * Updates the component to display an editable input field and sets the
216
      * initial value to the current display name.
271
      * initial value to the current display name.
217
      *
272
      *
273
+     * @param {MouseEvent} e - The click event.
218
      * @private
274
      * @private
219
      * @returns {void}
275
      * @returns {void}
220
      */
276
      */
221
-    _onStartEditing() {
277
+    _onStartEditing(e) {
222
         if (this.props.allowEditing) {
278
         if (this.props.allowEditing) {
279
+            e.stopPropagation();
223
             this.setState({
280
             this.setState({
224
                 isEditing: true,
281
                 isEditing: true,
225
                 editDisplayNameValue: this.props._configuredDisplayName
282
                 editDisplayNameValue: this.props._configuredDisplayName
292
     };
349
     };
293
 }
350
 }
294
 
351
 
295
-export default translate(connect(_mapStateToProps)(DisplayName));
352
+export default translate(connect(_mapStateToProps)(withStyles(styles)(DisplayName)));

+ 12
- 22
react/features/filmstrip/components/web/AudioMutedIndicator.js Parādīt failu

1
 /* @flow */
1
 /* @flow */
2
 
2
 
3
-import React, { Component } from 'react';
3
+import React from 'react';
4
 
4
 
5
-import { IconMicDisabled } from '../../../base/icons';
5
+import { IconMicrophoneEmptySlash } from '../../../base/icons';
6
 import { BaseIndicator } from '../../../base/react';
6
 import { BaseIndicator } from '../../../base/react';
7
 
7
 
8
 /**
8
 /**
19
 /**
19
 /**
20
  * React {@code Component} for showing an audio muted icon with a tooltip.
20
  * React {@code Component} for showing an audio muted icon with a tooltip.
21
  *
21
  *
22
- * @augments Component
22
+ * @returns {Component}
23
  */
23
  */
24
-class AudioMutedIndicator extends Component<Props> {
25
-    /**
26
-     * Implements React's {@link Component#render()}.
27
-     *
28
-     * @inheritdoc
29
-     * @returns {ReactElement}
30
-     */
31
-    render() {
32
-        return (
33
-            <BaseIndicator
34
-                className = 'audioMuted toolbar-icon'
35
-                icon = { IconMicDisabled }
36
-                iconId = 'mic-disabled'
37
-                iconSize = { 13 }
38
-                tooltipKey = 'videothumbnail.mute'
39
-                tooltipPosition = { this.props.tooltipPosition } />
40
-        );
41
-    }
42
-}
24
+const AudioMutedIndicator = ({ tooltipPosition }: Props) => (
25
+    <BaseIndicator
26
+        icon = { IconMicrophoneEmptySlash }
27
+        iconId = 'mic-disabled'
28
+        iconSize = { 15 }
29
+        id = 'audioMuted'
30
+        tooltipKey = 'videothumbnail.mute'
31
+        tooltipPosition = { tooltipPosition } />
32
+);
43
 
33
 
44
 export default AudioMutedIndicator;
34
 export default AudioMutedIndicator;

+ 0
- 51
react/features/filmstrip/components/web/DominantSpeakerIndicator.js Parādīt failu

1
-/* @flow */
2
-
3
-import React, { Component } from 'react';
4
-
5
-import { IconDominantSpeaker } from '../../../base/icons';
6
-import { BaseIndicator } from '../../../base/react';
7
-
8
-/**
9
- * The type of the React {@code Component} props of
10
- * {@link DominantSpeakerIndicator}.
11
- */
12
-type Props = {
13
-
14
-    /**
15
-     * The font-size for the icon.
16
-     */
17
-    iconSize: number,
18
-
19
-    /**
20
-     * From which side of the indicator the tooltip should appear from.
21
-     */
22
-    tooltipPosition: string
23
-};
24
-
25
-/**
26
- * Thumbnail badge showing that the participant is the dominant speaker in
27
- * the conference.
28
- *
29
- * @augments Component
30
- */
31
-class DominantSpeakerIndicator extends Component<Props> {
32
-    /**
33
-     * Implements React's {@link Component#render()}.
34
-     *
35
-     * @inheritdoc
36
-     */
37
-    render() {
38
-        return (
39
-            <BaseIndicator
40
-                className = 'indicator show-inline'
41
-                icon = { IconDominantSpeaker }
42
-                iconClassName = 'indicatoricon'
43
-                iconSize = { `${this.props.iconSize}px` }
44
-                id = 'dominantspeakerindicator'
45
-                tooltipKey = 'speaker'
46
-                tooltipPosition = { this.props.tooltipPosition } />
47
-        );
48
-    }
49
-}
50
-
51
-export default DominantSpeakerIndicator;

+ 10
- 23
react/features/filmstrip/components/web/ModeratorIndicator.js Parādīt failu

1
 /* @flow */
1
 /* @flow */
2
 
2
 
3
-import React, { Component } from 'react';
3
+import React from 'react';
4
 
4
 
5
-import { IconModerator } from '../../../base/icons';
5
+import { IconCrown } from '../../../base/icons';
6
 import { BaseIndicator } from '../../../base/react';
6
 import { BaseIndicator } from '../../../base/react';
7
 
7
 
8
 /**
8
 /**
19
 /**
19
 /**
20
  * React {@code Component} for showing a moderator icon with a tooltip.
20
  * React {@code Component} for showing a moderator icon with a tooltip.
21
  *
21
  *
22
- * @augments Component
22
+ * @returns {Component}
23
  */
23
  */
24
-class ModeratorIndicator extends Component<Props> {
25
-    /**
26
-     * Implements React's {@link Component#render()}.
27
-     *
28
-     * @inheritdoc
29
-     * @returns {ReactElement}
30
-     */
31
-    render() {
32
-        return (
33
-            <div className = 'moderator-icon right'>
34
-                <BaseIndicator
35
-                    className = 'focusindicator toolbar-icon'
36
-                    icon = { IconModerator }
37
-                    iconSize = { 13 }
38
-                    tooltipKey = 'videothumbnail.moderator'
39
-                    tooltipPosition = { this.props.tooltipPosition } />
40
-            </div>
41
-        );
42
-    }
43
-}
24
+const ModeratorIndicator = ({ tooltipPosition }: Props) => (
25
+    <BaseIndicator
26
+        icon = { IconCrown }
27
+        iconSize = { 15 }
28
+        tooltipKey = 'videothumbnail.moderator'
29
+        tooltipPosition = { tooltipPosition } />
30
+);
44
 
31
 
45
 export default ModeratorIndicator;
32
 export default ModeratorIndicator;

+ 45
- 23
react/features/filmstrip/components/web/RaisedHandIndicator.js Parādīt failu

1
 /* @flow */
1
 /* @flow */
2
 
2
 
3
+import { makeStyles } from '@material-ui/styles';
3
 import React from 'react';
4
 import React from 'react';
5
+import { useSelector } from 'react-redux';
4
 
6
 
5
 import { IconRaisedHand } from '../../../base/icons';
7
 import { IconRaisedHand } from '../../../base/icons';
8
+import { getParticipantById, hasRaisedHand } from '../../../base/participants';
6
 import { BaseIndicator } from '../../../base/react';
9
 import { BaseIndicator } from '../../../base/react';
7
-import { connect } from '../../../base/redux';
8
-import AbstractRaisedHandIndicator, {
9
-    type Props as AbstractProps,
10
-    _mapStateToProps
11
-} from '../AbstractRaisedHandIndicator';
12
 
10
 
13
 /**
11
 /**
14
  * The type of the React {@code Component} props of {@link RaisedHandIndicator}.
12
  * The type of the React {@code Component} props of {@link RaisedHandIndicator}.
15
  */
13
  */
16
-type Props = AbstractProps & {
14
+type Props = {
17
 
15
 
18
     /**
16
     /**
19
      * The font-size for the icon.
17
      * The font-size for the icon.
20
      */
18
      */
21
     iconSize: number,
19
     iconSize: number,
22
 
20
 
21
+    /**
22
+     * The participant id who we want to render the raised hand indicator
23
+     * for.
24
+     */
25
+    participantId: string,
26
+
23
     /**
27
     /**
24
      * From which side of the indicator the tooltip should appear from.
28
      * From which side of the indicator the tooltip should appear from.
25
      */
29
      */
26
     tooltipPosition: string
30
     tooltipPosition: string
27
 };
31
 };
28
 
32
 
33
+const useStyles = makeStyles(theme => {
34
+    return {
35
+        raisedHandIndicator: {
36
+            backgroundColor: theme.palette.warning01,
37
+            padding: '2px',
38
+            zIndex: 3,
39
+            display: 'inline-block',
40
+            borderRadius: '4px',
41
+            boxSizing: 'border-box'
42
+        }
43
+    };
44
+});
45
+
29
 /**
46
 /**
30
  * Thumbnail badge showing that the participant would like to speak.
47
  * Thumbnail badge showing that the participant would like to speak.
31
  *
48
  *
32
- * @augments Component
49
+ * @returns {ReactElement}
33
  */
50
  */
34
-class RaisedHandIndicator extends AbstractRaisedHandIndicator<Props> {
35
-    /**
36
-     * Renders the platform specific indicator element.
37
-     *
38
-     * @returns {React$Element<*>}
39
-     */
40
-    _renderIndicator() {
41
-        return (
51
+const RaisedHandIndicator = ({
52
+    iconSize,
53
+    participantId,
54
+    tooltipPosition
55
+}: Props) => {
56
+    const _raisedHand = hasRaisedHand(useSelector(state =>
57
+        getParticipantById(state, participantId)));
58
+    const styles = useStyles();
59
+
60
+    if (!_raisedHand) {
61
+        return null;
62
+    }
63
+
64
+    return (
65
+        <div className = { styles.raisedHandIndicator }>
42
             <BaseIndicator
66
             <BaseIndicator
43
-                className = 'raisehandindicator indicator show-inline'
44
                 icon = { IconRaisedHand }
67
                 icon = { IconRaisedHand }
45
-                iconClassName = 'indicatoricon'
46
-                iconSize = { `${this.props.iconSize}px` }
68
+                iconSize = { `${iconSize}px` }
47
                 tooltipKey = 'raisedHand'
69
                 tooltipKey = 'raisedHand'
48
-                tooltipPosition = { this.props.tooltipPosition } />
49
-        );
50
-    }
51
-}
70
+                tooltipPosition = { tooltipPosition } />
71
+        </div>
72
+    );
73
+};
52
 
74
 
53
-export default connect(_mapStateToProps)(RaisedHandIndicator);
75
+export default RaisedHandIndicator;

+ 1
- 2
react/features/filmstrip/components/web/ScreenShareIndicator.js Parādīt failu

23
 export default function ScreenShareIndicator(props: Props) {
23
 export default function ScreenShareIndicator(props: Props) {
24
     return (
24
     return (
25
         <BaseIndicator
25
         <BaseIndicator
26
-            className = 'screenShare toolbar-icon'
27
             icon = { IconShareDesktop }
26
             icon = { IconShareDesktop }
28
             iconId = 'share-desktop'
27
             iconId = 'share-desktop'
29
-            iconSize = { 13 }
28
+            iconSize = { 15 }
30
             tooltipKey = 'videothumbnail.videomute'
29
             tooltipKey = 'videothumbnail.videomute'
31
             tooltipPosition = { props.tooltipPosition } />
30
             tooltipPosition = { props.tooltipPosition } />
32
     );
31
     );

+ 13
- 35
react/features/filmstrip/components/web/StatusIndicators.js Parādīt failu

6
 import { getParticipantByIdOrUndefined, PARTICIPANT_ROLE } from '../../../base/participants';
6
 import { getParticipantByIdOrUndefined, PARTICIPANT_ROLE } from '../../../base/participants';
7
 import { connect } from '../../../base/redux';
7
 import { connect } from '../../../base/redux';
8
 import { getTrackByMediaTypeAndParticipant, isLocalTrackMuted, isRemoteTrackMuted } from '../../../base/tracks';
8
 import { getTrackByMediaTypeAndParticipant, isLocalTrackMuted, isRemoteTrackMuted } from '../../../base/tracks';
9
-import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
9
+import { getCurrentLayout } from '../../../video-layout';
10
+import { getIndicatorsTooltipPosition } from '../../functions.web';
10
 
11
 
11
 import AudioMutedIndicator from './AudioMutedIndicator';
12
 import AudioMutedIndicator from './AudioMutedIndicator';
12
 import ModeratorIndicator from './ModeratorIndicator';
13
 import ModeratorIndicator from './ModeratorIndicator';
13
 import ScreenShareIndicator from './ScreenShareIndicator';
14
 import ScreenShareIndicator from './ScreenShareIndicator';
14
-import VideoMutedIndicator from './VideoMutedIndicator';
15
 
15
 
16
 declare var interfaceConfig: Object;
16
 declare var interfaceConfig: Object;
17
 
17
 
40
      */
40
      */
41
     _showScreenShareIndicator: Boolean,
41
     _showScreenShareIndicator: Boolean,
42
 
42
 
43
-    /**
44
-     * Indicates if the video muted indicator should be visible or not.
45
-     */
46
-    _showVideoMutedIndicator: Boolean,
47
-
48
     /**
43
     /**
49
      * The ID of the participant for which the status bar is rendered.
44
      * The ID of the participant for which the status bar is rendered.
50
      */
45
      */
68
             _currentLayout,
63
             _currentLayout,
69
             _showAudioMutedIndicator,
64
             _showAudioMutedIndicator,
70
             _showModeratorIndicator,
65
             _showModeratorIndicator,
71
-            _showScreenShareIndicator,
72
-            _showVideoMutedIndicator
66
+            _showScreenShareIndicator
73
         } = this.props;
67
         } = this.props;
74
-        let tooltipPosition;
75
-
76
-        switch (_currentLayout) {
77
-        case LAYOUTS.TILE_VIEW:
78
-            tooltipPosition = 'right';
79
-            break;
80
-        case LAYOUTS.VERTICAL_FILMSTRIP_VIEW:
81
-            tooltipPosition = 'left';
82
-            break;
83
-        default:
84
-            tooltipPosition = 'top';
85
-        }
68
+        const tooltipPosition = getIndicatorsTooltipPosition(_currentLayout);
86
 
69
 
87
         return (
70
         return (
88
-            <div>
89
-                { _showAudioMutedIndicator ? <AudioMutedIndicator tooltipPosition = { tooltipPosition } /> : null }
90
-                { _showScreenShareIndicator ? <ScreenShareIndicator tooltipPosition = { tooltipPosition } /> : null }
91
-                { _showVideoMutedIndicator ? <VideoMutedIndicator tooltipPosition = { tooltipPosition } /> : null }
92
-                { _showModeratorIndicator ? <ModeratorIndicator tooltipPosition = { tooltipPosition } /> : null }
93
-            </div>
71
+            <>
72
+                { _showAudioMutedIndicator && <AudioMutedIndicator tooltipPosition = { tooltipPosition } /> }
73
+                { _showModeratorIndicator && <ModeratorIndicator tooltipPosition = { tooltipPosition } />}
74
+                { _showScreenShareIndicator && <ScreenShareIndicator tooltipPosition = { tooltipPosition } /> }
75
+            </>
94
         );
76
         );
95
     }
77
     }
96
 }
78
 }
108
  * }}
90
  * }}
109
 */
91
 */
110
 function _mapStateToProps(state, ownProps) {
92
 function _mapStateToProps(state, ownProps) {
111
-    const { participantID } = ownProps;
93
+    const { participantID, audio, moderator, screenshare } = ownProps;
112
 
94
 
113
     // Only the local participant won't have id for the time when the conference is not yet joined.
95
     // Only the local participant won't have id for the time when the conference is not yet joined.
114
     const participant = getParticipantByIdOrUndefined(state, participantID);
96
     const participant = getParticipantByIdOrUndefined(state, participantID);
115
 
97
 
116
     const tracks = state['features/base/tracks'];
98
     const tracks = state['features/base/tracks'];
117
-    let isVideoMuted = true;
118
     let isAudioMuted = true;
99
     let isAudioMuted = true;
119
     let isScreenSharing = false;
100
     let isScreenSharing = false;
120
 
101
 
121
     if (participant?.local) {
102
     if (participant?.local) {
122
-        isVideoMuted = isLocalTrackMuted(tracks, MEDIA_TYPE.VIDEO);
123
         isAudioMuted = isLocalTrackMuted(tracks, MEDIA_TYPE.AUDIO);
103
         isAudioMuted = isLocalTrackMuted(tracks, MEDIA_TYPE.AUDIO);
124
     } else if (!participant?.isFakeParticipant) { // remote participants excluding shared video
104
     } else if (!participant?.isFakeParticipant) { // remote participants excluding shared video
125
         const track = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, participantID);
105
         const track = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, participantID);
126
 
106
 
127
         isScreenSharing = track?.videoType === 'desktop';
107
         isScreenSharing = track?.videoType === 'desktop';
128
-        isVideoMuted = isRemoteTrackMuted(tracks, MEDIA_TYPE.VIDEO, participantID);
129
         isAudioMuted = isRemoteTrackMuted(tracks, MEDIA_TYPE.AUDIO, participantID);
108
         isAudioMuted = isRemoteTrackMuted(tracks, MEDIA_TYPE.AUDIO, participantID);
130
     }
109
     }
131
 
110
 
133
 
112
 
134
     return {
113
     return {
135
         _currentLayout: getCurrentLayout(state),
114
         _currentLayout: getCurrentLayout(state),
136
-        _showAudioMutedIndicator: isAudioMuted,
115
+        _showAudioMutedIndicator: isAudioMuted && audio,
137
         _showModeratorIndicator:
116
         _showModeratorIndicator:
138
-            !disableModeratorIndicator && participant && participant.role === PARTICIPANT_ROLE.MODERATOR,
139
-        _showScreenShareIndicator: isScreenSharing,
140
-        _showVideoMutedIndicator: isVideoMuted
117
+            !disableModeratorIndicator && participant && participant.role === PARTICIPANT_ROLE.MODERATOR && moderator,
118
+        _showScreenShareIndicator: isScreenSharing && screenshare
141
     };
119
     };
142
 }
120
 }
143
 
121
 

+ 186
- 443
react/features/filmstrip/components/web/Thumbnail.js Parādīt failu

1
 // @flow
1
 // @flow
2
 
2
 
3
+import { withStyles } from '@material-ui/styles';
4
+import clsx from 'clsx';
3
 import React, { Component } from 'react';
5
 import React, { Component } from 'react';
4
 
6
 
5
 import { createScreenSharingIssueEvent, sendAnalytics } from '../../../analytics';
7
 import { createScreenSharingIssueEvent, sendAnalytics } from '../../../analytics';
6
-import { AudioLevelIndicator } from '../../../audio-level-indicator';
7
 import { Avatar } from '../../../base/avatar';
8
 import { Avatar } from '../../../base/avatar';
8
-import { isNameReadOnly } from '../../../base/config';
9
 import { isMobileBrowser } from '../../../base/environment/utils';
9
 import { isMobileBrowser } from '../../../base/environment/utils';
10
-import JitsiMeetJS from '../../../base/lib-jitsi-meet/_';
11
 import { MEDIA_TYPE, VideoTrack } from '../../../base/media';
10
 import { MEDIA_TYPE, VideoTrack } from '../../../base/media';
12
 import {
11
 import {
13
     getParticipantByIdOrUndefined,
12
     getParticipantByIdOrUndefined,
14
-    getParticipantCount,
15
     pinParticipant
13
     pinParticipant
16
 } from '../../../base/participants';
14
 } from '../../../base/participants';
17
 import { connect } from '../../../base/redux';
15
 import { connect } from '../../../base/redux';
23
     getTrackByMediaTypeAndParticipant,
21
     getTrackByMediaTypeAndParticipant,
24
     updateLastTrackVideoMediaEvent
22
     updateLastTrackVideoMediaEvent
25
 } from '../../../base/tracks';
23
 } from '../../../base/tracks';
26
-import { ConnectionIndicator } from '../../../connection-indicator';
27
-import { DisplayName } from '../../../display-name';
28
-import { StatusIndicators, RaisedHandIndicator, DominantSpeakerIndicator } from '../../../filmstrip';
29
 import { PresenceLabel } from '../../../presence-status';
24
 import { PresenceLabel } from '../../../presence-status';
30
 import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
25
 import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
31
-import { LocalVideoMenuTriggerButton, RemoteVideoMenuTriggerButton } from '../../../video-menu';
32
-import { setVolume } from '../../actions.web';
33
 import {
26
 import {
34
     DISPLAY_MODE_TO_CLASS_NAME,
27
     DISPLAY_MODE_TO_CLASS_NAME,
35
     DISPLAY_VIDEO,
28
     DISPLAY_VIDEO,
36
-    DISPLAY_VIDEO_WITH_NAME,
37
     VIDEO_TEST_EVENTS,
29
     VIDEO_TEST_EVENTS,
38
     SHOW_TOOLBAR_CONTEXT_MENU_AFTER
30
     SHOW_TOOLBAR_CONTEXT_MENU_AFTER
39
 } from '../../constants';
31
 } from '../../constants';
40
-import { isVideoPlayable, computeDisplayMode } from '../../functions';
32
+import { isVideoPlayable, computeDisplayModeFromInput, getDisplayModeInput } from '../../functions';
41
 
33
 
42
-const JitsiTrackEvents = JitsiMeetJS.events.track;
34
+import ThumbnailAudioIndicator from './ThumbnailAudioIndicator';
35
+import ThumbnailBottomIndicators from './ThumbnailBottomIndicators';
36
+import ThumbnailTopIndicators from './ThumbnailTopIndicators';
43
 
37
 
44
 declare var interfaceConfig: Object;
38
 declare var interfaceConfig: Object;
45
 
39
 
48
  */
42
  */
49
 export type State = {|
43
 export type State = {|
50
 
44
 
51
-    /**
52
-     * The current audio level value for the Thumbnail.
53
-     */
54
-    audioLevel: number,
55
-
56
     /**
45
     /**
57
      * Indicates that the canplay event has been received.
46
      * Indicates that the canplay event has been received.
58
      */
47
      */
64
     displayMode: number,
53
     displayMode: number,
65
 
54
 
66
     /**
55
     /**
67
-     * Indicates whether the thumbnail is hovered or not.
56
+     * Whether popover is visible or not.
68
      */
57
      */
69
-    isHovered: boolean,
58
+    popoverVisible: boolean,
70
 
59
 
71
     /**
60
     /**
72
-     * Whether popover is visible or not.
61
+     * Indicates whether the thumbnail is hovered or not.
73
      */
62
      */
74
-    popoverVisible: boolean
63
+    isHovered: boolean
75
 |};
64
 |};
76
 
65
 
77
 /**
66
 /**
79
  */
68
  */
80
 export type Props = {|
69
 export type Props = {|
81
 
70
 
82
-    /**
83
-     * If the display name is editable or not.
84
-     */
85
-    _allowEditing: boolean,
86
-
87
     /**
71
     /**
88
      * The audio track related to the participant.
72
      * The audio track related to the participant.
89
      */
73
      */
90
     _audioTrack: ?Object,
74
     _audioTrack: ?Object,
91
 
75
 
92
-    /**
93
-     * Disable/enable the auto hide functionality for the connection indicator.
94
-     */
95
-    _connectionIndicatorAutoHideEnabled: boolean,
96
-
97
-    /**
98
-     * Disable/enable the connection indicator.
99
-     */
100
-    _connectionIndicatorDisabled: boolean,
101
-
102
     /**
76
     /**
103
      * The current layout of the filmstrip.
77
      * The current layout of the filmstrip.
104
      */
78
      */
105
     _currentLayout: string,
79
     _currentLayout: string,
106
 
80
 
107
-    /**
108
-     * The default display name for the local participant.
109
-     */
110
-    _defaultLocalDisplayName: string,
111
-
112
     /**
81
     /**
113
      * Indicates whether the local video flip feature is disabled or not.
82
      * Indicates whether the local video flip feature is disabled or not.
114
      */
83
      */
119
      */
88
      */
120
     _disableTileEnlargement: boolean,
89
     _disableTileEnlargement: boolean,
121
 
90
 
122
-    /**
123
-     * The display mode of the thumbnail.
124
-     */
125
-    _displayMode: number,
126
-
127
     /**
91
     /**
128
      * The height of the Thumbnail.
92
      * The height of the Thumbnail.
129
      */
93
      */
130
     _height: number,
94
     _height: number,
131
 
95
 
132
     /**
96
     /**
133
-     * The aspect ratio of the Thumbnail in percents.
97
+     * Indicates whether the thumbnail should be hidden or not.
134
      */
98
      */
135
-    _heightToWidthPercent: number,
99
+    _isHidden: boolean,
136
 
100
 
137
     /**
101
     /**
138
-     * Indicates whether the thumbnail should be hidden or not.
102
+     * Whether or not there is a pinned participant.
139
      */
103
      */
140
-    _isHidden: boolean,
104
+    _isAnyParticipantPinned: boolean,
141
 
105
 
142
     /**
106
     /**
143
      * Indicates whether audio only mode is enabled.
107
      * Indicates whether audio only mode is enabled.
179
      */
143
      */
180
     _isTestModeEnabled: boolean,
144
     _isTestModeEnabled: boolean,
181
 
145
 
182
-    /**
183
-     * The size of the icon of indicators.
184
-     */
185
-    _indicatorIconSize: number,
186
-
187
     /**
146
     /**
188
      * The current local video flip setting.
147
      * The current local video flip setting.
189
      */
148
      */
195
     _participant: Object,
154
     _participant: Object,
196
 
155
 
197
     /**
156
     /**
198
-     * True if there are more than 2 participants in the call.
199
-     */
200
-     _participantCountMoreThan2: boolean,
201
-
202
-    /**
203
-     * Indicates whether the "start silent" mode is enabled.
204
-     */
205
-    _startSilent: Boolean,
206
-
207
-     /**
208
      * The video track that will be displayed in the thumbnail.
157
      * The video track that will be displayed in the thumbnail.
209
      */
158
      */
210
     _videoTrack: ?Object,
159
     _videoTrack: ?Object,
211
 
160
 
212
-    /**
213
-     * The volume level for the thumbnail.
214
-     */
215
-    _volume?: ?number,
216
-
217
     /**
161
     /**
218
      * The width of the thumbnail.
162
      * The width of the thumbnail.
219
      */
163
      */
224
      */
168
      */
225
     dispatch: Function,
169
     dispatch: Function,
226
 
170
 
171
+    /**
172
+     * An object containing the CSS classes.
173
+     */
174
+    classes: Object,
175
+
227
     /**
176
     /**
228
      * The horizontal offset in px for the thumbnail. Used to center the thumbnails from the last row in tile view.
177
      * The horizontal offset in px for the thumbnail. Used to center the thumbnails from the last row in tile view.
229
      */
178
      */
240
     style?: ?Object
189
     style?: ?Object
241
 |};
190
 |};
242
 
191
 
243
-/**
244
- * Click handler for the display name container.
245
- *
246
- * @param {SyntheticEvent} event - The click event.
247
- * @returns {void}
248
- */
249
-function onClick(event) {
250
-    // If the event is propagated to the thumbnail container the participant will be pinned. That's why the propagation
251
-    // needs to be stopped.
252
-    event.stopPropagation();
253
-}
192
+const defaultStyles = theme => {
193
+    return {
194
+        indicatorsContainer: {
195
+            position: 'absolute',
196
+            padding: `${theme.spacing(1)}px`,
197
+            zIndex: 10,
198
+            width: '100%',
199
+            boxSizing: 'border-box',
200
+            display: 'flex',
201
+            left: 0,
202
+
203
+            '&.tile-view-mode': {
204
+                padding: `${theme.spacing(2)}px`
205
+            }
206
+        },
207
+
208
+        indicatorsTopContainer: {
209
+            top: 0,
210
+            justifyContent: 'space-between'
211
+        },
212
+
213
+        indicatorsBottomContainer: {
214
+            bottom: 0
215
+        },
216
+
217
+        indicatorsBackground: {
218
+            backgroundColor: 'rgba(0, 0, 0, 0.7)',
219
+            borderRadius: '4px',
220
+            display: 'flex',
221
+            alignItems: 'center',
222
+            maxWidth: '100%',
223
+            overflow: 'hidden',
224
+
225
+            '&:not(:empty)': {
226
+                padding: '2px'
227
+            },
228
+
229
+            '& > *:not(:last-child)': {
230
+                marginRight: '4px'
231
+            },
232
+
233
+            '&:not(.top-indicators) > *:last-child': {
234
+                marginRight: '6px'
235
+            }
236
+        },
237
+
238
+        containerBackground: {
239
+            position: 'absolute',
240
+            top: 0,
241
+            left: 0,
242
+            height: '100%',
243
+            width: '100%',
244
+            borderRadius: '4px',
245
+            backgroundColor: theme.palette.ui02
246
+        },
247
+
248
+        activeSpeaker: {
249
+            '& .active-speaker-indicator': {
250
+                boxShadow: `inset 0px 0px 0px 4px ${theme.palette.link01Active} !important`,
251
+                position: 'absolute',
252
+                width: '100%',
253
+                height: '100%',
254
+                zIndex: '9',
255
+                borderRadius: '4px'
256
+            }
257
+        }
258
+    };
259
+};
254
 
260
 
255
 /**
261
 /**
256
  * Implements a thumbnail.
262
  * Implements a thumbnail.
263
      */
269
      */
264
     timeoutHandle: Object;
270
     timeoutHandle: Object;
265
 
271
 
266
-    /**
267
-     * Reference to local or remote Video Menu trigger button instance.
268
-     */
269
-    videoMenuTriggerRef: Object;
270
-
271
     /**
272
     /**
272
      * Timeout used to detect double tapping.
273
      * Timeout used to detect double tapping.
273
      * It is active while user has tapped once.
274
      * It is active while user has tapped once.
284
         super(props);
285
         super(props);
285
 
286
 
286
         const state = {
287
         const state = {
287
-            audioLevel: 0,
288
             canPlayEventReceived: false,
288
             canPlayEventReceived: false,
289
-            isHovered: false,
290
             displayMode: DISPLAY_VIDEO,
289
             displayMode: DISPLAY_VIDEO,
291
-            popoverVisible: false
290
+            popoverVisible: false,
291
+            isHovered: false
292
         };
292
         };
293
 
293
 
294
         this.state = {
294
         this.state = {
295
             ...state,
295
             ...state,
296
-            displayMode: computeDisplayMode(Thumbnail.getDisplayModeInput(props, state)),
297
-            popoverVisible: false
296
+            displayMode: computeDisplayModeFromInput(getDisplayModeInput(props, state))
298
         };
297
         };
299
         this.timeoutHandle = null;
298
         this.timeoutHandle = null;
300
-        this.videoMenuTriggerRef = null;
301
 
299
 
302
         this._clearDoubleClickTimeout = this._clearDoubleClickTimeout.bind(this);
300
         this._clearDoubleClickTimeout = this._clearDoubleClickTimeout.bind(this);
303
-        this._updateAudioLevel = this._updateAudioLevel.bind(this);
304
         this._onCanPlay = this._onCanPlay.bind(this);
301
         this._onCanPlay = this._onCanPlay.bind(this);
305
         this._onClick = this._onClick.bind(this);
302
         this._onClick = this._onClick.bind(this);
306
-        this._onVolumeChange = this._onVolumeChange.bind(this);
307
         this._onMouseEnter = this._onMouseEnter.bind(this);
303
         this._onMouseEnter = this._onMouseEnter.bind(this);
308
         this._onMouseLeave = this._onMouseLeave.bind(this);
304
         this._onMouseLeave = this._onMouseLeave.bind(this);
309
         this._onTestingEvent = this._onTestingEvent.bind(this);
305
         this._onTestingEvent = this._onTestingEvent.bind(this);
321
      * @returns {void}
317
      * @returns {void}
322
      */
318
      */
323
     componentDidMount() {
319
     componentDidMount() {
324
-        this._listenForAudioUpdates();
325
         this._onDisplayModeChanged();
320
         this._onDisplayModeChanged();
326
     }
321
     }
327
 
322
 
333
      * @returns {void}
328
      * @returns {void}
334
      */
329
      */
335
     componentDidUpdate(prevProps: Props, prevState: State) {
330
     componentDidUpdate(prevProps: Props, prevState: State) {
336
-        if (prevProps._audioTrack !== this.props._audioTrack) {
337
-            this._stopListeningForAudioUpdates(prevProps._audioTrack);
338
-            this._listenForAudioUpdates();
339
-            this._updateAudioLevel(0);
340
-        }
341
-
342
         if (prevState.displayMode !== this.state.displayMode) {
331
         if (prevState.displayMode !== this.state.displayMode) {
343
             this._onDisplayModeChanged();
332
             this._onDisplayModeChanged();
344
         }
333
         }
350
      * @returns {void}
339
      * @returns {void}
351
      */
340
      */
352
     _onDisplayModeChanged() {
341
     _onDisplayModeChanged() {
353
-        const input = Thumbnail.getDisplayModeInput(this.props, this.state);
342
+        const input = getDisplayModeInput(this.props, this.state);
354
 
343
 
355
         this._maybeSendScreenSharingIssueEvents(input);
344
         this._maybeSendScreenSharingIssueEvents(input);
356
     }
345
     }
370
         const { displayMode } = this.state;
359
         const { displayMode } = this.state;
371
         const tileViewActive = _currentLayout === LAYOUTS.TILE_VIEW;
360
         const tileViewActive = _currentLayout === LAYOUTS.TILE_VIEW;
372
 
361
 
373
-        if (![ DISPLAY_VIDEO, DISPLAY_VIDEO_WITH_NAME ].includes(displayMode)
362
+        if (!(DISPLAY_VIDEO === displayMode)
374
             && tileViewActive
363
             && tileViewActive
375
             && _isScreenSharing
364
             && _isScreenSharing
376
             && !_isAudioOnly) {
365
             && !_isAudioOnly) {
395
 
384
 
396
             return {
385
             return {
397
                 ...newState,
386
                 ...newState,
398
-                displayMode: computeDisplayMode(Thumbnail.getDisplayModeInput(props, newState))
387
+                displayMode: computeDisplayModeFromInput(getDisplayModeInput(props, newState))
399
             };
388
             };
400
         }
389
         }
401
 
390
 
402
-        const newDisplayMode = computeDisplayMode(Thumbnail.getDisplayModeInput(props, prevState));
391
+        const newDisplayMode = computeDisplayModeFromInput(getDisplayModeInput(props, prevState));
403
 
392
 
404
         if (newDisplayMode !== prevState.displayMode) {
393
         if (newDisplayMode !== prevState.displayMode) {
405
             return {
394
             return {
411
         return null;
400
         return null;
412
     }
401
     }
413
 
402
 
414
-    /**
415
-     * Extracts information for props and state needed to compute the display mode.
416
-     *
417
-     * @param {Props} props - The component's props.
418
-     * @param {State} state - The component's state.
419
-     * @returns {Object}
420
-     */
421
-    static getDisplayModeInput(props: Props, state: State) {
422
-        const {
423
-            _currentLayout,
424
-            _isAudioOnly,
425
-            _isCurrentlyOnLargeVideo,
426
-            _isScreenSharing,
427
-            _isVideoPlayable,
428
-            _participant,
429
-            _videoTrack
430
-        } = props;
431
-        const tileViewActive = _currentLayout === LAYOUTS.TILE_VIEW;
432
-        const { canPlayEventReceived, isHovered } = state;
433
-
434
-        return {
435
-            isCurrentlyOnLargeVideo: _isCurrentlyOnLargeVideo,
436
-            isHovered,
437
-            isAudioOnly: _isAudioOnly,
438
-            tileViewActive,
439
-            isVideoPlayable: _isVideoPlayable,
440
-            connectionStatus: _participant?.connectionStatus,
441
-            canPlayEventReceived,
442
-            videoStream: Boolean(_videoTrack),
443
-            isRemoteParticipant: !_participant?.isFakeParticipant && !_participant?.local,
444
-            isScreenSharing: _isScreenSharing,
445
-            videoStreamMuted: _videoTrack ? _videoTrack.muted : 'no stream'
446
-        };
447
-    }
448
-
449
-    /**
450
-     * Unsubscribe from audio level updates.
451
-     *
452
-     * @inheritdoc
453
-     * @returns {void}
454
-     */
455
-    componentWillUnmount() {
456
-        this._stopListeningForAudioUpdates(this.props._audioTrack);
457
-    }
458
-
459
     _clearDoubleClickTimeout: () => void;
403
     _clearDoubleClickTimeout: () => void;
460
 
404
 
461
     /**
405
     /**
468
         this._firstTap = undefined;
412
         this._firstTap = undefined;
469
     }
413
     }
470
 
414
 
471
-    /**
472
-     * Starts listening for audio level updates from the library.
473
-     *
474
-     * @private
475
-     * @returns {void}
476
-     */
477
-    _listenForAudioUpdates() {
478
-        const { _audioTrack } = this.props;
479
-
480
-        if (_audioTrack) {
481
-            const { jitsiTrack } = _audioTrack;
482
-
483
-            jitsiTrack && jitsiTrack.on(JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED, this._updateAudioLevel);
484
-        }
485
-    }
486
-
487
-    /**
488
-     * Stops listening to further updates from the passed track.
489
-     *
490
-     * @param {Object} audioTrack - The track.
491
-     * @private
492
-     * @returns {void}
493
-     */
494
-    _stopListeningForAudioUpdates(audioTrack) {
495
-        if (audioTrack) {
496
-            const { jitsiTrack } = audioTrack;
497
-
498
-            jitsiTrack && jitsiTrack.off(JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED, this._updateAudioLevel);
499
-        }
500
-    }
501
-
502
-    _updateAudioLevel: (number) => void;
503
-
504
-    /**
505
-     * Updates the internal state of the last know audio level. The level should
506
-     * be between 0 and 1, as the level will be used as a percentage out of 1.
507
-     *
508
-     * @param {number} audioLevel - The new audio level for the track.
509
-     * @private
510
-     * @returns {void}
511
-     */
512
-    _updateAudioLevel(audioLevel) {
513
-        this.setState({
514
-            audioLevel
515
-        });
516
-    }
517
-
518
     _showPopover: () => void;
415
     _showPopover: () => void;
519
 
416
 
520
     /**
417
     /**
549
      * @returns {Object} - The styles for the thumbnail.
446
      * @returns {Object} - The styles for the thumbnail.
550
      */
447
      */
551
     _getStyles(): Object {
448
     _getStyles(): Object {
552
-
553
         const { canPlayEventReceived } = this.state;
449
         const { canPlayEventReceived } = this.state;
554
         const {
450
         const {
555
             _currentLayout,
451
             _currentLayout,
575
             video: {}
471
             video: {}
576
         };
472
         };
577
 
473
 
578
-        const avatarSize = _height / 2;
474
+        const avatarSize = Math.min(_height / 2, _width - 30);
579
         let { left } = style || {};
475
         let { left } = style || {};
580
 
476
 
581
         if (typeof left === 'number' && horizontalOffset) {
477
         if (typeof left === 'number' && horizontalOffset) {
730
         );
626
         );
731
     }
627
     }
732
 
628
 
733
-    /**
734
-     * Renders the top indicators of the thumbnail.
735
-     *
736
-     * @returns {Component}
737
-     */
738
-    _renderTopIndicators() {
739
-        const {
740
-            _connectionIndicatorAutoHideEnabled,
741
-            _connectionIndicatorDisabled,
742
-            _currentLayout,
743
-            _isDominantSpeakerDisabled,
744
-            _indicatorIconSize: iconSize,
745
-            _participant,
746
-            _participantCountMoreThan2
747
-        } = this.props;
748
-        const { isHovered } = this.state;
749
-        const showConnectionIndicator = isHovered || !_connectionIndicatorAutoHideEnabled;
750
-        const { id, dominantSpeaker = false } = _participant;
751
-        const showDominantSpeaker = !_isDominantSpeakerDisabled && dominantSpeaker;
752
-        let statsPopoverPosition, tooltipPosition;
753
-
754
-        switch (_currentLayout) {
755
-        case LAYOUTS.TILE_VIEW:
756
-            statsPopoverPosition = 'right-start';
757
-            tooltipPosition = 'right';
758
-            break;
759
-        case LAYOUTS.VERTICAL_FILMSTRIP_VIEW:
760
-            statsPopoverPosition = 'left-start';
761
-            tooltipPosition = 'left';
762
-            break;
763
-        case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW:
764
-            statsPopoverPosition = 'top';
765
-            tooltipPosition = 'top';
766
-            break;
767
-        default:
768
-            statsPopoverPosition = 'auto';
769
-            tooltipPosition = 'top';
770
-        }
771
-
772
-        return (
773
-            <div>
774
-                { !_connectionIndicatorDisabled
775
-                    && <ConnectionIndicator
776
-                        alwaysVisible = { showConnectionIndicator }
777
-                        enableStatsDisplay = { true }
778
-                        iconSize = { iconSize }
779
-                        participantId = { id }
780
-                        statsPopoverPosition = { statsPopoverPosition } />
781
-                }
782
-                <RaisedHandIndicator
783
-                    iconSize = { iconSize }
784
-                    participantId = { id }
785
-                    tooltipPosition = { tooltipPosition } />
786
-                { showDominantSpeaker && _participantCountMoreThan2
787
-                    && <DominantSpeakerIndicator
788
-                        iconSize = { iconSize }
789
-                        tooltipPosition = { tooltipPosition } />
790
-                }
791
-            </div>);
792
-    }
793
-
794
     /**
629
     /**
795
      * Renders the avatar.
630
      * Renders the avatar.
796
      *
631
      *
820
     _getContainerClassName() {
655
     _getContainerClassName() {
821
         let className = 'videocontainer';
656
         let className = 'videocontainer';
822
         const { displayMode } = this.state;
657
         const { displayMode } = this.state;
823
-        const { _isAudioOnly, _isDominantSpeakerDisabled, _isHidden, _participant } = this.props;
824
-        const isRemoteParticipant = !_participant?.local && !_participant?.isFakeParticipant;
825
-
826
-        className += ` ${DISPLAY_MODE_TO_CLASS_NAME[displayMode]}`;
827
-
828
-        if (_participant?.pinned) {
829
-            className += ' videoContainerFocused';
830
-        }
831
-
832
-        if (!_isDominantSpeakerDisabled && _participant?.dominantSpeaker) {
833
-            className += ' active-speaker';
834
-        }
835
-
836
-        if (_isHidden) {
837
-            className += ' hidden';
838
-        }
839
-
840
-        if (isRemoteParticipant && _isAudioOnly) {
841
-            className += ' audio-only';
842
-        }
843
-
844
-        return className;
845
-    }
846
-
847
-    /**
848
-     * Renders the local participant's thumbnail.
849
-     *
850
-     * @returns {ReactElement}
851
-     */
852
-    _renderLocalParticipant() {
853
         const {
658
         const {
854
-            _allowEditing,
855
-            _defaultLocalDisplayName,
856
-            _disableLocalVideoFlip,
857
-            _isMobile,
858
-            _isMobilePortrait,
859
-            _isScreenSharing,
860
-            _localFlipX,
659
+            _isDominantSpeakerDisabled,
861
             _participant,
660
             _participant,
862
-            _videoTrack
661
+            _currentLayout,
662
+            _isAnyParticipantPinned,
663
+            classes
863
         } = this.props;
664
         } = this.props;
864
-        const { id } = _participant || {};
865
-        const { audioLevel } = this.state;
866
-        const styles = this._getStyles();
867
-        let containerClassName = this._getContainerClassName();
868
-        const videoTrackClassName
869
-            = !_disableLocalVideoFlip && _videoTrack && !_isScreenSharing && _localFlipX ? 'flipVideoX' : '';
870
 
665
 
871
-        if (_isMobilePortrait) {
872
-            styles.thumbnail.height = styles.thumbnail.width;
873
-            containerClassName = `${containerClassName} self-view-mobile-portrait`;
874
-        }
666
+        className += ` ${DISPLAY_MODE_TO_CLASS_NAME[displayMode]}`;
875
 
667
 
876
-        return (
877
-            <span
878
-                className = { containerClassName }
879
-                id = 'localVideoContainer'
880
-                { ...(_isMobile
881
-                    ? {
882
-                        onTouchEnd: this._onTouchEnd,
883
-                        onTouchMove: this._onTouchMove,
884
-                        onTouchStart: this._onTouchStart
885
-                    }
886
-                    : {
887
-                        onClick: this._onClick,
888
-                        onMouseEnter: this._onMouseEnter,
889
-                        onMouseLeave: this._onMouseLeave
890
-                    }
891
-                ) }
892
-                style = { styles.thumbnail }>
893
-                <div className = 'videocontainer__background' />
894
-                <span id = 'localVideoWrapper'>
895
-                    <VideoTrack
896
-                        className = { videoTrackClassName }
897
-                        id = 'localVideo_container'
898
-                        style = { styles.video }
899
-                        videoTrack = { _videoTrack } />
900
-                </span>
901
-                <div className = 'videocontainer__toolbar'>
902
-                    <StatusIndicators participantID = { id } />
903
-                    <div
904
-                        className = 'videocontainer__participant-name'
905
-                        onClick = { onClick }>
906
-                        <DisplayName
907
-                            allowEditing = { _allowEditing }
908
-                            displayNameSuffix = { _defaultLocalDisplayName }
909
-                            elementID = 'localDisplayName'
910
-                            participantID = { id } />
911
-                    </div>
912
-                </div>
913
-                <div className = 'videocontainer__toptoolbar'>
914
-                    { this._renderTopIndicators() }
915
-                </div>
916
-                <div className = 'videocontainer__hoverOverlay' />
917
-                { this._renderAvatar(styles.avatar) }
918
-                <span className = 'audioindicator-container'>
919
-                    <AudioLevelIndicator audioLevel = { audioLevel } />
920
-                </span>
921
-                <span className = 'localvideomenu'>
922
-                    <LocalVideoMenuTriggerButton
923
-                        hidePopover = { this._hidePopover }
924
-                        popoverVisible = { this.state.popoverVisible }
925
-                        showPopover = { this._showPopover } />
926
-                </span>
668
+        if (_currentLayout === LAYOUTS.TILE_VIEW) {
669
+            if (!_isDominantSpeakerDisabled && _participant?.dominantSpeaker) {
670
+                className += ` ${classes.activeSpeaker} dominant-speaker`;
671
+            }
672
+        } else if (_isAnyParticipantPinned) {
673
+            if (_participant?.pinned) {
674
+                className += ` videoContainerFocused ${classes.activeSpeaker}`;
675
+            }
676
+        } else if (!_isDominantSpeakerDisabled && _participant?.dominantSpeaker) {
677
+            className += ` ${classes.activeSpeaker} dominant-speaker`;
678
+        }
927
 
679
 
928
-            </span>
929
-        );
680
+        return className;
930
     }
681
     }
931
 
682
 
932
     _onCanPlay: Object => void;
683
     _onCanPlay: Object => void;
971
     /**
722
     /**
972
      * Renders a remote participant's 'thumbnail.
723
      * Renders a remote participant's 'thumbnail.
973
      *
724
      *
725
+     * @param {boolean} local - Whether or not it's the local participant.
974
      * @returns {ReactElement}
726
      * @returns {ReactElement}
975
      */
727
      */
976
-    _renderRemoteParticipant() {
728
+    _renderParticipant(local = false) {
977
         const {
729
         const {
730
+            _audioTrack,
731
+            _currentLayout,
732
+            _disableLocalVideoFlip,
978
             _isMobile,
733
             _isMobile,
734
+            _isMobilePortrait,
735
+            _isScreenSharing,
979
             _isTestModeEnabled,
736
             _isTestModeEnabled,
737
+            _localFlipX,
980
             _participant,
738
             _participant,
981
-            _startSilent,
982
             _videoTrack,
739
             _videoTrack,
983
-            _volume = 1
740
+            classes
984
         } = this.props;
741
         } = this.props;
985
-        const { id } = _participant;
986
-        const { audioLevel } = this.state;
742
+        const { id } = _participant || {};
743
+        const { isHovered, popoverVisible } = this.state;
987
         const styles = this._getStyles();
744
         const styles = this._getStyles();
988
-        const containerClassName = this._getContainerClassName();
989
-
990
-        // hide volume when in silent mode
991
-        const onVolumeChange = _startSilent ? undefined : this._onVolumeChange;
745
+        let containerClassName = this._getContainerClassName();
746
+        const videoTrackClassName
747
+            = !_disableLocalVideoFlip && _videoTrack && !_isScreenSharing && _localFlipX ? 'flipVideoX' : '';
992
         const jitsiVideoTrack = _videoTrack?.jitsiTrack;
748
         const jitsiVideoTrack = _videoTrack?.jitsiTrack;
993
         const videoTrackId = jitsiVideoTrack && jitsiVideoTrack.getId();
749
         const videoTrackId = jitsiVideoTrack && jitsiVideoTrack.getId();
994
         const videoEventListeners = {};
750
         const videoEventListeners = {};
995
 
751
 
996
-        if (_videoTrack && _isTestModeEnabled) {
997
-            VIDEO_TEST_EVENTS.forEach(attribute => {
998
-                videoEventListeners[attribute] = this._onTestingEvent;
999
-            });
752
+        if (local) {
753
+            if (_isMobilePortrait) {
754
+                styles.thumbnail.height = styles.thumbnail.width;
755
+                containerClassName = `${containerClassName} self-view-mobile-portrait`;
756
+            }
757
+        } else {
758
+            if (_videoTrack && _isTestModeEnabled) {
759
+                VIDEO_TEST_EVENTS.forEach(attribute => {
760
+                    videoEventListeners[attribute] = this._onTestingEvent;
761
+                });
762
+            }
763
+            videoEventListeners.onCanPlay = this._onCanPlay;
1000
         }
764
         }
1001
 
765
 
1002
-        videoEventListeners.onCanPlay = this._onCanPlay;
766
+        const video = _videoTrack && <VideoTrack
767
+            className = { local ? videoTrackClassName : '' }
768
+            eventHandlers = { videoEventListeners }
769
+            id = { local ? 'localVideo_container' : `remoteVideo_${videoTrackId || ''}` }
770
+            muted = { local ? undefined : true }
771
+            style = { styles.video }
772
+            videoTrack = { _videoTrack } />;
1003
 
773
 
1004
         return (
774
         return (
1005
             <span
775
             <span
1006
                 className = { containerClassName }
776
                 className = { containerClassName }
1007
-                id = { `participant_${id}` }
777
+                id = { local ? 'localVideoContainer' : `participant_${id}` }
1008
                 { ...(_isMobile
778
                 { ...(_isMobile
1009
                     ? {
779
                     ? {
1010
                         onTouchEnd: this._onTouchEnd,
780
                         onTouchEnd: this._onTouchEnd,
1018
                     }
788
                     }
1019
                 ) }
789
                 ) }
1020
                 style = { styles.thumbnail }>
790
                 style = { styles.thumbnail }>
1021
-                {
1022
-                    _videoTrack && <VideoTrack
1023
-                        eventHandlers = { videoEventListeners }
1024
-                        id = { `remoteVideo_${videoTrackId || ''}` }
1025
-                        muted = { true }
1026
-                        style = { styles.video }
1027
-                        videoTrack = { _videoTrack } />
1028
-                }
1029
-                <div className = 'videocontainer__background' />
1030
-                <div className = 'videocontainer__toptoolbar'>
1031
-                    { this._renderTopIndicators() }
791
+                {local
792
+                    ? <span id = 'localVideoWrapper'>{video}</span>
793
+                    : video}
794
+                <div className = { classes.containerBackground } />
795
+                <div
796
+                    className = { clsx(classes.indicatorsContainer,
797
+                        classes.indicatorsTopContainer,
798
+                        _currentLayout === LAYOUTS.TILE_VIEW && 'tile-view-mode'
799
+                    ) }>
800
+                    <ThumbnailTopIndicators
801
+                        currentLayout = { _currentLayout }
802
+                        hidePopover = { this._hidePopover }
803
+                        indicatorsClassName = { classes.indicatorsBackground }
804
+                        isHovered = { isHovered }
805
+                        local = { local }
806
+                        participantId = { id }
807
+                        popoverVisible = { popoverVisible }
808
+                        showPopover = { this._showPopover } />
1032
                 </div>
809
                 </div>
1033
-                <div className = 'videocontainer__toolbar'>
1034
-                    <StatusIndicators participantID = { id } />
1035
-                    <div className = 'videocontainer__participant-name'>
1036
-                        <DisplayName
1037
-                            elementID = { `participant_${id}_name` }
1038
-                            participantID = { id } />
1039
-                    </div>
810
+                <div
811
+                    className = { clsx(classes.indicatorsContainer,
812
+                        classes.indicatorsBottomContainer,
813
+                        _currentLayout === LAYOUTS.TILE_VIEW && 'tile-view-mode'
814
+                    ) }>
815
+                    <ThumbnailBottomIndicators
816
+                        className = { classes.indicatorsBackground }
817
+                        currentLayout = { _currentLayout }
818
+                        local = { local }
819
+                        participantId = { id } />
1040
                 </div>
820
                 </div>
1041
-                <div className = 'videocontainer__hoverOverlay' />
1042
                 { this._renderAvatar(styles.avatar) }
821
                 { this._renderAvatar(styles.avatar) }
1043
-                <div className = 'presence-label-container'>
1044
-                    <PresenceLabel
1045
-                        className = 'presence-label'
1046
-                        participantID = { id } />
1047
-                </div>
1048
-                <span className = 'audioindicator-container'>
1049
-                    <AudioLevelIndicator audioLevel = { audioLevel } />
1050
-                </span>
1051
-                <span className = 'remotevideomenu'>
1052
-                    <RemoteVideoMenuTriggerButton
1053
-                        hidePopover = { this._hidePopover }
1054
-                        initialVolumeValue = { _volume }
1055
-                        onVolumeChange = { onVolumeChange }
1056
-                        participantID = { id }
1057
-                        popoverVisible = { this.state.popoverVisible }
1058
-                        showPopover = { this._showPopover } />
1059
-                </span>
822
+                { !local && (
823
+                    <div className = 'presence-label-container'>
824
+                        <PresenceLabel
825
+                            className = 'presence-label'
826
+                            participantID = { id } />
827
+                    </div>
828
+                )}
829
+                <ThumbnailAudioIndicator _audioTrack = { _audioTrack } />
830
+                <div className = 'active-speaker-indicator' />
1060
             </span>
831
             </span>
1061
         );
832
         );
1062
     }
833
     }
1063
 
834
 
1064
-    _onVolumeChange: number => void;
1065
-
1066
-    /**
1067
-     * Handles volume changes.
1068
-     *
1069
-     * @param {number} value - The new value for the volume.
1070
-     * @returns {void}
1071
-     */
1072
-    _onVolumeChange(value) {
1073
-        const { _participant, dispatch } = this.props;
1074
-        const { id } = _participant;
1075
-
1076
-        dispatch(setVolume(id, value));
1077
-    }
1078
-
1079
     /**
835
     /**
1080
      * Implements React's {@link Component#render()}.
836
      * Implements React's {@link Component#render()}.
1081
      *
837
      *
1092
         const { isFakeParticipant, local } = _participant;
848
         const { isFakeParticipant, local } = _participant;
1093
 
849
 
1094
         if (local) {
850
         if (local) {
1095
-            return this._renderLocalParticipant();
851
+            return this._renderParticipant(true);
1096
         }
852
         }
1097
 
853
 
1098
         if (isFakeParticipant) {
854
         if (isFakeParticipant) {
1099
             return this._renderFakeParticipant();
855
             return this._renderFakeParticipant();
1100
         }
856
         }
1101
 
857
 
1102
-        return this._renderRemoteParticipant();
858
+        return this._renderParticipant();
1103
     }
859
     }
1104
 }
860
 }
1105
 
861
 
1118
     const id = participant?.id;
874
     const id = participant?.id;
1119
     const isLocal = participant?.local ?? true;
875
     const isLocal = participant?.local ?? true;
1120
     const tracks = state['features/base/tracks'];
876
     const tracks = state['features/base/tracks'];
1121
-    const { participantsVolume } = state['features/filmstrip'];
1122
     const _videoTrack = isLocal
877
     const _videoTrack = isLocal
1123
         ? getLocalVideoTrack(tracks) : getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, participantID);
878
         ? getLocalVideoTrack(tracks) : getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, participantID);
1124
     const _audioTrack = isLocal
879
     const _audioTrack = isLocal
1127
     let size = {};
882
     let size = {};
1128
     let _isMobilePortrait = false;
883
     let _isMobilePortrait = false;
1129
     const {
884
     const {
1130
-        startSilent,
1131
         defaultLocalDisplayName,
885
         defaultLocalDisplayName,
1132
         disableLocalVideoFlip,
886
         disableLocalVideoFlip,
1133
         disableTileEnlargement,
887
         disableTileEnlargement,
1134
         iAmRecorder,
888
         iAmRecorder,
1135
         iAmSipGateway
889
         iAmSipGateway
1136
     } = state['features/base/config'];
890
     } = state['features/base/config'];
1137
-    const { NORMAL = 8 } = interfaceConfig.INDICATOR_FONT_SIZES || {};
1138
     const { localFlipX } = state['features/base/settings'];
891
     const { localFlipX } = state['features/base/settings'];
1139
     const _isMobile = isMobileBrowser();
892
     const _isMobile = isMobileBrowser();
1140
 
893
 
1167
         break;
920
         break;
1168
     }
921
     }
1169
     case LAYOUTS.TILE_VIEW: {
922
     case LAYOUTS.TILE_VIEW: {
1170
-
1171
         const { width, height } = state['features/filmstrip'].tileViewDimensions.thumbnailSize;
923
         const { width, height } = state['features/filmstrip'].tileViewDimensions.thumbnailSize;
1172
 
924
 
1173
         size = {
925
         size = {
1179
     }
931
     }
1180
 
932
 
1181
     return {
933
     return {
1182
-        _allowEditing: !isNameReadOnly(state),
1183
         _audioTrack,
934
         _audioTrack,
1184
-        _connectionIndicatorAutoHideEnabled:
1185
-        Boolean(state['features/base/config'].connectionIndicators?.autoHide ?? true),
1186
-        _connectionIndicatorDisabled: _isMobile
1187
-            || Boolean(state['features/base/config'].connectionIndicators?.disabled),
1188
         _currentLayout,
935
         _currentLayout,
1189
         _defaultLocalDisplayName: defaultLocalDisplayName,
936
         _defaultLocalDisplayName: defaultLocalDisplayName,
1190
         _disableLocalVideoFlip: Boolean(disableLocalVideoFlip),
937
         _disableLocalVideoFlip: Boolean(disableLocalVideoFlip),
1198
         _isScreenSharing: _videoTrack?.videoType === 'desktop',
945
         _isScreenSharing: _videoTrack?.videoType === 'desktop',
1199
         _isTestModeEnabled: isTestModeEnabled(state),
946
         _isTestModeEnabled: isTestModeEnabled(state),
1200
         _isVideoPlayable: id && isVideoPlayable(state, id),
947
         _isVideoPlayable: id && isVideoPlayable(state, id),
1201
-        _indicatorIconSize: NORMAL,
1202
         _localFlipX: Boolean(localFlipX),
948
         _localFlipX: Boolean(localFlipX),
1203
         _participant: participant,
949
         _participant: participant,
1204
-        _participantCountMoreThan2: getParticipantCount(state) > 2,
1205
-        _startSilent: Boolean(startSilent),
1206
         _videoTrack,
950
         _videoTrack,
1207
-        _volume: isLocal ? undefined : id ? participantsVolume[id] : undefined,
1208
         ...size
951
         ...size
1209
     };
952
     };
1210
 }
953
 }
1211
 
954
 
1212
-export default connect(_mapStateToProps)(Thumbnail);
955
+export default connect(_mapStateToProps)(withStyles(defaultStyles)(Thumbnail));

+ 47
- 0
react/features/filmstrip/components/web/ThumbnailAudioIndicator.js Parādīt failu

1
+// @flow
2
+
3
+import React, { useEffect, useState } from 'react';
4
+
5
+import { AudioLevelIndicator } from '../../../audio-level-indicator';
6
+import JitsiMeetJS from '../../../base/lib-jitsi-meet/_';
7
+
8
+const JitsiTrackEvents = JitsiMeetJS.events.track;
9
+
10
+type Props = {
11
+
12
+    /**
13
+     * The audio track related to the participant.
14
+     */
15
+    _audioTrack: ?Object
16
+}
17
+
18
+const ThumbnailAudioIndicator = ({
19
+    _audioTrack
20
+}: Props) => {
21
+    const [ audioLevel, setAudioLevel ] = useState(0);
22
+
23
+    useEffect(() => {
24
+        setAudioLevel(0);
25
+        if (_audioTrack) {
26
+            const { jitsiTrack } = _audioTrack;
27
+
28
+            jitsiTrack && jitsiTrack.on(JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED, setAudioLevel);
29
+        }
30
+
31
+        return () => {
32
+            if (_audioTrack) {
33
+                const { jitsiTrack } = _audioTrack;
34
+
35
+                jitsiTrack && jitsiTrack.off(JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED, setAudioLevel);
36
+            }
37
+        };
38
+    }, [ _audioTrack ]);
39
+
40
+    return (
41
+        <span className = 'audioindicator-container'>
42
+            <AudioLevelIndicator audioLevel = { audioLevel } />
43
+        </span>
44
+    );
45
+};
46
+
47
+export default ThumbnailAudioIndicator;

+ 84
- 0
react/features/filmstrip/components/web/ThumbnailBottomIndicators.js Parādīt failu

1
+// @flow
2
+
3
+import { makeStyles } from '@material-ui/styles';
4
+import React from 'react';
5
+import { useSelector } from 'react-redux';
6
+
7
+import { isNameReadOnly } from '../../../base/config/functions.any';
8
+import DisplayName from '../../../display-name/components/web/DisplayName';
9
+import { LAYOUTS } from '../../../video-layout';
10
+
11
+import StatusIndicators from './StatusIndicators';
12
+
13
+declare var interfaceConfig: Object;
14
+
15
+type Props = {
16
+
17
+    /**
18
+     * The current layout of the filmstrip.
19
+     */
20
+    currentLayout: string,
21
+
22
+    /**
23
+     * Class name for indicators container.
24
+     */
25
+    className: string,
26
+
27
+    /**
28
+     * Whether or not the indicators are for the local participant.
29
+     */
30
+    local: boolean,
31
+
32
+    /**
33
+     * Id of the participant for which the component is displayed.
34
+     */
35
+    participantId: string
36
+}
37
+
38
+const useStyles = makeStyles(() => {
39
+    return {
40
+        nameContainer: {
41
+            display: 'flex',
42
+            overflow: 'hidden',
43
+            padding: '2px 0',
44
+
45
+            '&>div': {
46
+                display: 'flex',
47
+                overflow: 'hidden'
48
+            },
49
+
50
+            '&:first-child': {
51
+                marginLeft: '6px'
52
+            }
53
+        }
54
+    };
55
+});
56
+
57
+const ThumbnailBottomIndicators = ({
58
+    className,
59
+    currentLayout,
60
+    local,
61
+    participantId
62
+}: Props) => {
63
+    const styles = useStyles();
64
+    const _allowEditing = !useSelector(isNameReadOnly);
65
+    const _defaultLocalDisplayName = interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME;
66
+
67
+    return (<div className = { className }>
68
+        <StatusIndicators
69
+            audio = { true }
70
+            moderator = { true }
71
+            participantID = { participantId }
72
+            screenshare = { currentLayout === LAYOUTS.TILE_VIEW } />
73
+        <span className = { styles.nameContainer }>
74
+            <DisplayName
75
+                allowEditing = { local ? _allowEditing : false }
76
+                currentLayout = { currentLayout }
77
+                displayNameSuffix = { local ? _defaultLocalDisplayName : '' }
78
+                elementID = { local ? 'localDisplayName' : `participant_${participantId}_name` }
79
+                participantID = { participantId } />
80
+        </span>
81
+    </div>);
82
+};
83
+
84
+export default ThumbnailBottomIndicators;

+ 132
- 0
react/features/filmstrip/components/web/ThumbnailTopIndicators.js Parādīt failu

1
+// @flow
2
+
3
+import { makeStyles } from '@material-ui/styles';
4
+import clsx from 'clsx';
5
+import React from 'react';
6
+import { useSelector } from 'react-redux';
7
+
8
+import { isMobileBrowser } from '../../../base/environment/utils';
9
+import ConnectionIndicator from '../../../connection-indicator/components/web/ConnectionIndicator';
10
+import { LAYOUTS } from '../../../video-layout';
11
+import { STATS_POPOVER_POSITION } from '../../constants';
12
+import { getIndicatorsTooltipPosition } from '../../functions.web';
13
+
14
+import RaisedHandIndicator from './RaisedHandIndicator';
15
+import StatusIndicators from './StatusIndicators';
16
+import VideoMenuTriggerButton from './VideoMenuTriggerButton';
17
+
18
+declare var interfaceConfig: Object;
19
+
20
+type Props = {
21
+
22
+    /**
23
+     * The current layout of the filmstrip.
24
+     */
25
+    currentLayout: string,
26
+
27
+    /**
28
+     * Hide popover callback.
29
+     */
30
+    hidePopover: Function,
31
+
32
+    /**
33
+     * Class name for the status indicators container.
34
+     */
35
+    indicatorsClassName: string,
36
+
37
+    /**
38
+     * Whether or not the thumbnail is hovered.
39
+     */
40
+    isHovered: boolean,
41
+
42
+    /**
43
+     * Whether or not the indicators are for the local participant.
44
+     */
45
+    local: boolean,
46
+
47
+    /**
48
+     * Id of the participant for which the component is displayed.
49
+     */
50
+    participantId: string,
51
+
52
+    /**
53
+     * Whether popover is visible or not.
54
+     */
55
+    popoverVisible: boolean,
56
+
57
+    /**
58
+     * Show popover callback.
59
+     */
60
+    showPopover: Function
61
+}
62
+
63
+const useStyles = makeStyles(() => {
64
+    return {
65
+        container: {
66
+            display: 'flex',
67
+
68
+            '& > *:not(:last-child)': {
69
+                marginRight: '4px'
70
+            }
71
+        }
72
+    };
73
+});
74
+
75
+const ThumbnailTopIndicators = ({
76
+    currentLayout,
77
+    hidePopover,
78
+    indicatorsClassName,
79
+    isHovered,
80
+    local,
81
+    participantId,
82
+    popoverVisible,
83
+    showPopover
84
+}: Props) => {
85
+    const styles = useStyles();
86
+
87
+    const _isMobile = isMobileBrowser();
88
+    const { NORMAL = 16 } = interfaceConfig.INDICATOR_FONT_SIZES || {};
89
+    const _indicatorIconSize = NORMAL;
90
+    const _connectionIndicatorAutoHideEnabled = Boolean(
91
+        useSelector(state => state['features/base/config'].connectionIndicators?.autoHide) ?? true);
92
+    const _connectionIndicatorDisabled = _isMobile
93
+        || Boolean(useSelector(state => state['features/base/config'].connectionIndicators?.disabled));
94
+
95
+    const showConnectionIndicator = isHovered || !_connectionIndicatorAutoHideEnabled;
96
+
97
+    return (
98
+        <>
99
+            <div className = { styles.container }>
100
+                {!_connectionIndicatorDisabled
101
+                    && <ConnectionIndicator
102
+                        alwaysVisible = { showConnectionIndicator }
103
+                        enableStatsDisplay = { true }
104
+                        iconSize = { _indicatorIconSize }
105
+                        participantId = { participantId }
106
+                        statsPopoverPosition = { STATS_POPOVER_POSITION[currentLayout] } />
107
+                }
108
+                <RaisedHandIndicator
109
+                    iconSize = { _indicatorIconSize }
110
+                    participantId = { participantId }
111
+                    tooltipPosition = { getIndicatorsTooltipPosition(currentLayout) } />
112
+                {currentLayout === LAYOUTS.TILE_VIEW && (
113
+                    <div className = { clsx(indicatorsClassName, 'top-indicators') }>
114
+                        <StatusIndicators
115
+                            participantID = { participantId }
116
+                            screenshare = { true } />
117
+                    </div>
118
+                )}
119
+            </div>
120
+            <div className = { styles.container }>
121
+                <VideoMenuTriggerButton
122
+                    hidePopover = { hidePopover }
123
+                    local = { local }
124
+                    participantId = { participantId }
125
+                    popoverVisible = { popoverVisible }
126
+                    showPopover = { showPopover }
127
+                    visible = { isHovered } />
128
+            </div>
129
+        </>);
130
+};
131
+
132
+export default ThumbnailTopIndicators;

+ 12
- 2
react/features/filmstrip/components/web/ThumbnailWrapper.js Parādīt failu

23
      */
23
      */
24
     _horizontalOffset: number,
24
     _horizontalOffset: number,
25
 
25
 
26
+    /**
27
+     * Whether or not there is a pinned participant.
28
+     */
29
+    _isAnyParticipantPinned: boolean,
30
+
26
     /**
31
     /**
27
      * The ID of the participant associated with the Thumbnail.
32
      * The ID of the participant associated with the Thumbnail.
28
      */
33
      */
75
      * @returns {ReactElement}
80
      * @returns {ReactElement}
76
      */
81
      */
77
     render() {
82
     render() {
78
-        const { _participantID, style, _horizontalOffset = 0, _disableSelfView } = this.props;
83
+        const { _participantID, style, _horizontalOffset = 0, _isAnyParticipantPinned, _disableSelfView } = this.props;
79
 
84
 
80
         if (typeof _participantID !== 'string') {
85
         if (typeof _participantID !== 'string') {
81
             return null;
86
             return null;
91
 
96
 
92
         return (
97
         return (
93
             <Thumbnail
98
             <Thumbnail
99
+                _isAnyParticipantPinned = { _isAnyParticipantPinned }
94
                 horizontalOffset = { _horizontalOffset }
100
                 horizontalOffset = { _horizontalOffset }
95
                 key = { `remote_${_participantID}` }
101
                 key = { `remote_${_participantID}` }
96
                 participantID = { _participantID }
102
                 participantID = { _participantID }
109
 function _mapStateToProps(state, ownProps) {
115
 function _mapStateToProps(state, ownProps) {
110
     const _currentLayout = getCurrentLayout(state);
116
     const _currentLayout = getCurrentLayout(state);
111
     const { remoteParticipants } = state['features/filmstrip'];
117
     const { remoteParticipants } = state['features/filmstrip'];
118
+    const { remote, local } = state['features/base/participants'];
112
     const remoteParticipantsLength = remoteParticipants.length;
119
     const remoteParticipantsLength = remoteParticipants.length;
113
     const { testing = {} } = state['features/base/config'];
120
     const { testing = {} } = state['features/base/config'];
114
     const disableSelfView = getDisableSelfView(state);
121
     const disableSelfView = getDisableSelfView(state);
160
         return {};
167
         return {};
161
     }
168
     }
162
 
169
 
170
+    const _isAnyParticipantPinned = Boolean([ ...remote ].find(([ , value ]) => value?.pinned) || local?.pinned);
171
+
163
     return {
172
     return {
164
-        _participantID: remoteParticipants[index]
173
+        _participantID: remoteParticipants[index],
174
+        _isAnyParticipantPinned
165
     };
175
     };
166
 }
176
 }
167
 
177
 

+ 69
- 0
react/features/filmstrip/components/web/VideoMenuTriggerButton.js Parādīt failu

1
+// @flow
2
+
3
+import React from 'react';
4
+
5
+import { LocalVideoMenuTriggerButton, RemoteVideoMenuTriggerButton } from '../../../video-menu';
6
+
7
+type Props = {
8
+
9
+    /**
10
+     * Hide popover callback.
11
+     */
12
+    hidePopover: Function,
13
+
14
+    /**
15
+     * Whether or not the button is for the local participant.
16
+     */
17
+    local: boolean,
18
+
19
+    /**
20
+     * The id of the participant for which the button is.
21
+     */
22
+    participantId?: string,
23
+
24
+    /**
25
+     * Whether popover is visible or not.
26
+     */
27
+    popoverVisible: boolean,
28
+
29
+    /**
30
+     * Show popover callback.
31
+     */
32
+    showPopover: Function,
33
+
34
+    /**
35
+     * Whether or not the component is visible.
36
+     */
37
+    visible: boolean
38
+}
39
+
40
+// eslint-disable-next-line no-confusing-arrow
41
+const VideoMenuTriggerButton = ({
42
+    hidePopover,
43
+    local,
44
+    participantId,
45
+    popoverVisible,
46
+    showPopover,
47
+    visible
48
+}: Props) => local
49
+    ? (
50
+        <span id = 'localvideomenu'>
51
+            <LocalVideoMenuTriggerButton
52
+                buttonVisible = { visible }
53
+                hidePopover = { hidePopover }
54
+                popoverVisible = { popoverVisible }
55
+                showPopover = { showPopover } />
56
+        </span>
57
+    )
58
+    : (
59
+        <span id = 'remotevideomenu'>
60
+            <RemoteVideoMenuTriggerButton
61
+                buttonVisible = { visible }
62
+                hidePopover = { hidePopover }
63
+                participantID = { participantId }
64
+                popoverVisible = { popoverVisible }
65
+                showPopover = { showPopover } />
66
+        </span>
67
+    );
68
+
69
+export default VideoMenuTriggerButton;

+ 0
- 43
react/features/filmstrip/components/web/VideoMutedIndicator.js Parādīt failu

1
-/* @flow */
2
-
3
-import React, { Component } from 'react';
4
-
5
-import { IconCameraDisabled } from '../../../base/icons';
6
-import { BaseIndicator } from '../../../base/react';
7
-
8
-/**
9
- * The type of the React {@code Component} props of {@link VideoMutedIndicator}.
10
- */
11
-type Props = {
12
-
13
-    /**
14
-     * From which side of the indicator the tooltip should appear from.
15
-     */
16
-    tooltipPosition: string
17
-};
18
-
19
-/**
20
- * React {@code Component} for showing a video muted icon with a tooltip.
21
- *
22
- * @augments Component
23
- */
24
-class VideoMutedIndicator extends Component<Props> {
25
-    /**
26
-     * Implements React's {@link Component#render()}.
27
-     *
28
-     * @inheritdoc
29
-     */
30
-    render() {
31
-        return (
32
-            <BaseIndicator
33
-                className = 'videoMuted toolbar-icon'
34
-                icon = { IconCameraDisabled }
35
-                iconId = 'camera-disabled'
36
-                iconSize = { 13 }
37
-                tooltipKey = 'videothumbnail.videomute'
38
-                tooltipPosition = { this.props.tooltipPosition } />
39
-        );
40
-    }
41
-}
42
-
43
-export default VideoMutedIndicator;

+ 0
- 2
react/features/filmstrip/components/web/index.js Parādīt failu

1
 // @flow
1
 // @flow
2
 
2
 
3
 export { default as AudioMutedIndicator } from './AudioMutedIndicator';
3
 export { default as AudioMutedIndicator } from './AudioMutedIndicator';
4
-export { default as DominantSpeakerIndicator } from './DominantSpeakerIndicator';
5
 export { default as Filmstrip } from './Filmstrip';
4
 export { default as Filmstrip } from './Filmstrip';
6
 export { default as ModeratorIndicator } from './ModeratorIndicator';
5
 export { default as ModeratorIndicator } from './ModeratorIndicator';
7
 export { default as RaisedHandIndicator } from './RaisedHandIndicator';
6
 export { default as RaisedHandIndicator } from './RaisedHandIndicator';
8
 export { default as StatusIndicators } from './StatusIndicators';
7
 export { default as StatusIndicators } from './StatusIndicators';
9
-export { default as VideoMutedIndicator } from './VideoMutedIndicator';
10
 export { default as Thumbnail } from './Thumbnail';
8
 export { default as Thumbnail } from './Thumbnail';

+ 33
- 44
react/features/filmstrip/constants.js Parādīt failu

1
 // @flow
1
 // @flow
2
 
2
 
3
 import { BoxModel } from '../base/styles';
3
 import { BoxModel } from '../base/styles';
4
+import { LAYOUTS } from '../video-layout/constants';
4
 
5
 
5
 /**
6
 /**
6
  * The size (height and width) of the small (not tile view) thumbnails.
7
  * The size (height and width) of the small (not tile view) thumbnails.
97
 export const DISPLAY_AVATAR = 1;
98
 export const DISPLAY_AVATAR = 1;
98
 
99
 
99
 /**
100
 /**
100
- * Display mode constant used when neither video nor avatar is being displayed
101
- * on the small video. And we just show the display name.
101
+ * Maps the display modes to class name that will be applied on the thumbnail container.
102
  *
102
  *
103
- * @type {number}
103
+ * @type {Array<string>}
104
  * @constant
104
  * @constant
105
  */
105
  */
106
-export const DISPLAY_BLACKNESS_WITH_NAME = 2;
106
+export const DISPLAY_MODE_TO_CLASS_NAME = [
107
+    'display-video',
108
+    'display-avatar-only'
109
+];
107
 
110
 
108
 /**
111
 /**
109
- * Display mode constant used when video is displayed and display name
110
- * at the same time.
112
+ * The vertical margin of a tile.
111
  *
113
  *
112
  * @type {number}
114
  * @type {number}
113
- * @constant
114
  */
115
  */
115
-export const DISPLAY_VIDEO_WITH_NAME = 3;
116
+export const TILE_VERTICAL_MARGIN = 4;
116
 
117
 
117
 /**
118
 /**
118
- * Display mode constant used when neither video nor avatar is being displayed
119
- * on the small video. And we just show the display name.
119
+ * The horizontal margin of a tile.
120
  *
120
  *
121
  * @type {number}
121
  * @type {number}
122
- * @constant
123
- */
124
-export const DISPLAY_AVATAR_WITH_NAME = 4;
125
-
126
-/**
127
- * Maps the display modes to class name that will be applied on the thumbnail container.
128
- *
129
- * @type {Array<string>}
130
- * @constant
131
  */
122
  */
132
-export const DISPLAY_MODE_TO_CLASS_NAME = [
133
-    'display-video',
134
-    'display-avatar-only',
135
-    'display-name-on-black',
136
-    'display-name-on-video',
137
-    'display-avatar-with-name'
138
-];
139
-
140
-/**
141
- * Maps the display modes to string.
142
- *
143
- * @type {Array<string>}
144
- * @constant
145
- */
146
-export const DISPLAY_MODE_TO_STRING = [
147
-    'video',
148
-    'avatar',
149
-    'blackness-with-name',
150
-    'video-with-name',
151
-    'avatar-with-name'
152
-];
123
+export const TILE_HORIZONTAL_MARGIN = 4;
153
 
124
 
154
 /**
125
 /**
155
- * The vertical margin of a tile.
126
+ * The vertical margin of the tile grid container.
156
  *
127
  *
157
  * @type {number}
128
  * @type {number}
158
  */
129
  */
159
-export const TILE_VERTICAL_MARGIN = 4;
130
+export const TILE_VIEW_GRID_VERTICAL_MARGIN = 12;
160
 
131
 
161
 /**
132
 /**
162
- * The horizontal margin of a tile.
133
+ * The horizontal margin of the tile grid container.
163
  *
134
  *
164
  * @type {number}
135
  * @type {number}
165
  */
136
  */
166
-export const TILE_HORIZONTAL_MARGIN = 4;
137
+export const TILE_VIEW_GRID_HORIZONTAL_MARGIN = 12;
167
 
138
 
168
 /**
139
 /**
169
  * The height of the whole toolbar.
140
  * The height of the whole toolbar.
238
  * @type {number}
209
  * @type {number}
239
  */
210
  */
240
 export const TILE_MARGIN = 10;
211
 export const TILE_MARGIN = 10;
212
+
213
+/**
214
+ * The popover position for the connection stats table.
215
+ */
216
+export const STATS_POPOVER_POSITION = {
217
+    [LAYOUTS.TILE_VIEW]: 'right-start',
218
+    [LAYOUTS.VERTICAL_FILMSTRIP_VIEW]: 'left-start',
219
+    [LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW]: 'top-end'
220
+};
221
+
222
+/**
223
+ * The tooltip position for the indicators on the thumbnail.
224
+ */
225
+export const INDICATORS_TOOLTIP_POSITION = {
226
+    [LAYOUTS.TILE_VIEW]: 'right',
227
+    [LAYOUTS.VERTICAL_FILMSTRIP_VIEW]: 'left',
228
+    [LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW]: 'top'
229
+};

+ 57
- 13
react/features/filmstrip/functions.web.js Parādīt failu

15
     isLocalTrackMuted,
15
     isLocalTrackMuted,
16
     isRemoteTrackMuted
16
     isRemoteTrackMuted
17
 } from '../base/tracks/functions';
17
 } from '../base/tracks/functions';
18
+import { LAYOUTS } from '../video-layout';
18
 
19
 
19
 import {
20
 import {
20
     ASPECT_RATIO_BREAKPOINT,
21
     ASPECT_RATIO_BREAKPOINT,
21
     DISPLAY_AVATAR,
22
     DISPLAY_AVATAR,
22
-    DISPLAY_AVATAR_WITH_NAME,
23
-    DISPLAY_BLACKNESS_WITH_NAME,
24
     DISPLAY_VIDEO,
23
     DISPLAY_VIDEO,
25
-    DISPLAY_VIDEO_WITH_NAME,
24
+    INDICATORS_TOOLTIP_POSITION,
26
     SCROLL_SIZE,
25
     SCROLL_SIZE,
27
     SQUARE_TILE_ASPECT_RATIO,
26
     SQUARE_TILE_ASPECT_RATIO,
28
     STAGE_VIEW_THUMBNAIL_HORIZONTAL_BORDER,
27
     STAGE_VIEW_THUMBNAIL_HORIZONTAL_BORDER,
29
     TILE_ASPECT_RATIO,
28
     TILE_ASPECT_RATIO,
30
     TILE_HORIZONTAL_MARGIN,
29
     TILE_HORIZONTAL_MARGIN,
31
     TILE_VERTICAL_MARGIN,
30
     TILE_VERTICAL_MARGIN,
31
+    TILE_VIEW_GRID_HORIZONTAL_MARGIN,
32
+    TILE_VIEW_GRID_VERTICAL_MARGIN,
32
     VERTICAL_FILMSTRIP_MIN_HORIZONTAL_MARGIN
33
     VERTICAL_FILMSTRIP_MIN_HORIZONTAL_MARGIN
33
 } from './constants';
34
 } from './constants';
34
 
35
 
190
         aspectRatio = SQUARE_TILE_ASPECT_RATIO;
191
         aspectRatio = SQUARE_TILE_ASPECT_RATIO;
191
     }
192
     }
192
 
193
 
193
-    const viewWidth = clientWidth - (columns * TILE_HORIZONTAL_MARGIN);
194
-    const viewHeight = clientHeight - (minVisibleRows * TILE_VERTICAL_MARGIN);
194
+    const viewWidth = clientWidth - (columns * TILE_HORIZONTAL_MARGIN) - TILE_VIEW_GRID_HORIZONTAL_MARGIN;
195
+    const viewHeight = clientHeight - (minVisibleRows * TILE_VERTICAL_MARGIN) - TILE_VIEW_GRID_VERTICAL_MARGIN;
195
     const initialWidth = viewWidth / columns;
196
     const initialWidth = viewWidth / columns;
196
     const initialHeight = viewHeight / minVisibleRows;
197
     const initialHeight = viewHeight / minVisibleRows;
197
     const aspectRatioHeight = initialWidth / aspectRatio;
198
     const aspectRatioHeight = initialWidth / aspectRatio;
240
 /**
241
 /**
241
  * Computes information that determine the display mode.
242
  * Computes information that determine the display mode.
242
  *
243
  *
243
- * @param {Object} input - Obejct containing all necessary information for determining the display mode for
244
+ * @param {Object} input - Object containing all necessary information for determining the display mode for
244
  * the thumbnail.
245
  * the thumbnail.
245
- * @returns {number} - One of <tt>DISPLAY_VIDEO</tt>, <tt>DISPLAY_AVATAR</tt> or <tt>DISPLAY_BLACKNESS_WITH_NAME</tt>.
246
+ * @returns {number} - One of <tt>DISPLAY_VIDEO</tt> or <tt>DISPLAY_AVATAR</tt>.
246
 */
247
 */
247
-export function computeDisplayMode(input: Object) {
248
+export function computeDisplayModeFromInput(input: Object) {
248
     const {
249
     const {
249
         isAudioOnly,
250
         isAudioOnly,
250
         isCurrentlyOnLargeVideo,
251
         isCurrentlyOnLargeVideo,
251
         isScreenSharing,
252
         isScreenSharing,
252
         canPlayEventReceived,
253
         canPlayEventReceived,
253
-        isHovered,
254
         isRemoteParticipant,
254
         isRemoteParticipant,
255
         tileViewActive
255
         tileViewActive
256
     } = input;
256
     } = input;
257
     const adjustedIsVideoPlayable = input.isVideoPlayable && (!isRemoteParticipant || canPlayEventReceived);
257
     const adjustedIsVideoPlayable = input.isVideoPlayable && (!isRemoteParticipant || canPlayEventReceived);
258
 
258
 
259
     if (!tileViewActive && isScreenSharing && isRemoteParticipant) {
259
     if (!tileViewActive && isScreenSharing && isRemoteParticipant) {
260
-        return isHovered ? DISPLAY_AVATAR_WITH_NAME : DISPLAY_AVATAR;
260
+        return DISPLAY_AVATAR;
261
     } else if (isCurrentlyOnLargeVideo && !tileViewActive) {
261
     } else if (isCurrentlyOnLargeVideo && !tileViewActive) {
262
         // Display name is always and only displayed when user is on the stage
262
         // Display name is always and only displayed when user is on the stage
263
-        return adjustedIsVideoPlayable && !isAudioOnly ? DISPLAY_BLACKNESS_WITH_NAME : DISPLAY_AVATAR_WITH_NAME;
263
+        return adjustedIsVideoPlayable && !isAudioOnly ? DISPLAY_VIDEO : DISPLAY_AVATAR;
264
     } else if (adjustedIsVideoPlayable && !isAudioOnly) {
264
     } else if (adjustedIsVideoPlayable && !isAudioOnly) {
265
         // check hovering and change state to video with name
265
         // check hovering and change state to video with name
266
-        return isHovered ? DISPLAY_VIDEO_WITH_NAME : DISPLAY_VIDEO;
266
+        return DISPLAY_VIDEO;
267
     }
267
     }
268
 
268
 
269
     // check hovering and change state to avatar with name
269
     // check hovering and change state to avatar with name
270
-    return isHovered ? DISPLAY_AVATAR_WITH_NAME : DISPLAY_AVATAR;
270
+    return DISPLAY_AVATAR;
271
+}
272
+
273
+/**
274
+ * Extracts information for props and state needed to compute the display mode.
275
+ *
276
+ * @param {Object} props - The Thumbnail component's props.
277
+ * @param {Object} state - The Thumbnail component's state.
278
+ * @returns {Object}
279
+*/
280
+export function getDisplayModeInput(props: Object, state: Object) {
281
+    const {
282
+        _currentLayout,
283
+        _isAudioOnly,
284
+        _isCurrentlyOnLargeVideo,
285
+        _isScreenSharing,
286
+        _isVideoPlayable,
287
+        _participant,
288
+        _videoTrack
289
+    } = props;
290
+    const tileViewActive = _currentLayout === LAYOUTS.TILE_VIEW;
291
+    const { canPlayEventReceived } = state;
292
+
293
+    return {
294
+        isCurrentlyOnLargeVideo: _isCurrentlyOnLargeVideo,
295
+        isAudioOnly: _isAudioOnly,
296
+        tileViewActive,
297
+        isVideoPlayable: _isVideoPlayable,
298
+        connectionStatus: _participant?.connectionStatus,
299
+        canPlayEventReceived,
300
+        videoStream: Boolean(_videoTrack),
301
+        isRemoteParticipant: !_participant?.isFakeParticipant && !_participant?.local,
302
+        isScreenSharing: _isScreenSharing,
303
+        videoStreamMuted: _videoTrack ? _videoTrack.muted : 'no stream'
304
+    };
305
+}
306
+
307
+/**
308
+ * Gets the tooltip position for the thumbnail indicators.
309
+ *
310
+ * @param {string} currentLayout - The current layout of the app.
311
+ * @returns {string}
312
+ */
313
+export function getIndicatorsTooltipPosition(currentLayout: string) {
314
+    return INDICATORS_TOOLTIP_POSITION[currentLayout] || 'top';
271
 }
315
 }

+ 15
- 447
react/features/participants-pane/components/web/MeetingParticipantContextMenu.js Parādīt failu

1
 // @flow
1
 // @flow
2
-import { withStyles } from '@material-ui/styles';
3
 import React, { Component } from 'react';
2
 import React, { Component } from 'react';
4
 
3
 
5
-import { createBreakoutRoomsEvent, sendAnalytics } from '../../../analytics';
6
-import { approveParticipant } from '../../../av-moderation/actions';
7
-import { Avatar } from '../../../base/avatar';
8
-import { ContextMenu, ContextMenuItemGroup } from '../../../base/components';
9
-import { isToolbarButtonEnabled } from '../../../base/config/functions.web';
10
-import { openDialog } from '../../../base/dialog';
11
-import { isIosMobileBrowser } from '../../../base/environment/utils';
12
 import { translate } from '../../../base/i18n';
4
 import { translate } from '../../../base/i18n';
13
-import {
14
-    IconCloseCircle,
15
-    IconCrown,
16
-    IconMessage,
17
-    IconMicDisabled,
18
-    IconMicrophone,
19
-    IconMuteEveryoneElse,
20
-    IconRingGroup,
21
-    IconShareVideo,
22
-    IconVideoOff
23
-} from '../../../base/icons';
24
-import { MEDIA_TYPE } from '../../../base/media';
25
 import {
5
 import {
26
     getLocalParticipant,
6
     getLocalParticipant,
27
-    getParticipantByIdOrUndefined,
28
-    isLocalParticipantModerator,
29
-    isParticipantModerator
7
+    getParticipantByIdOrUndefined
30
 } from '../../../base/participants';
8
 } from '../../../base/participants';
31
 import { connect } from '../../../base/redux';
9
 import { connect } from '../../../base/redux';
32
-import { isParticipantAudioMuted, isParticipantVideoMuted } from '../../../base/tracks';
33
-import { sendParticipantToRoom } from '../../../breakout-rooms/actions';
34
-import { getBreakoutRooms, getCurrentRoomId } from '../../../breakout-rooms/functions';
35
-import { openChatById } from '../../../chat/actions';
36
-import { setVolume } from '../../../filmstrip/actions.web';
37
-import { stopSharedVideo } from '../../../shared-video/actions.any';
38
-import { GrantModeratorDialog, KickRemoteParticipantDialog, MuteEveryoneDialog } from '../../../video-menu';
39
-import { VolumeSlider } from '../../../video-menu/components/web';
40
-import MuteRemoteParticipantsVideoDialog from '../../../video-menu/components/web/MuteRemoteParticipantsVideoDialog';
41
-import { isForceMuted } from '../../functions';
10
+import ParticipantContextMenu from '../../../video-menu/components/web/ParticipantContextMenu';
42
 
11
 
43
 type Props = {
12
 type Props = {
44
 
13
 
45
-    /**
46
-     * Whether or not the participant is audio force muted.
47
-     */
48
-    _isAudioForceMuted: boolean,
49
-
50
-    /**
51
-     * The id of the current room.
52
-     */
53
-    _currentRoomId: String,
54
-
55
-    /**
56
-     * True if the local participant is moderator and false otherwise.
57
-     */
58
-    _isLocalModerator: boolean,
59
-
60
-    /**
61
-     * True if the chat button is enabled and false otherwise.
62
-     */
63
-    _isChatButtonEnabled: boolean,
64
-
65
-    /**
66
-     * True if the participant is moderator and false otherwise.
67
-     */
68
-    _isParticipantModerator: boolean,
69
-
70
-    /**
71
-     * True if the participant is video muted and false otherwise.
72
-     */
73
-    _isParticipantVideoMuted: boolean,
74
-
75
-    /**
76
-     * True if the participant is audio muted and false otherwise.
77
-     */
78
-    _isParticipantAudioMuted: boolean,
79
-
80
-    /**
81
-     * Whether or not the participant is video force muted.
82
-     */
83
-    _isVideoForceMuted: boolean,
84
-
85
     /**
14
     /**
86
      * Shared video local participant owner.
15
      * Shared video local participant owner.
87
      */
16
      */
92
      */
21
      */
93
     _participant: Object,
22
     _participant: Object,
94
 
23
 
95
-    /**
96
-     * Rooms reference.
97
-     */
98
-    _rooms: Array<Object>,
99
-
100
-    /**
101
-     * A value between 0 and 1 indicating the volume of the participant's
102
-     * audio element.
103
-     */
104
-    _volume: ?number,
105
-
106
     /**
24
     /**
107
      * Closes a drawer if open.
25
      * Closes a drawer if open.
108
      */
26
      */
109
     closeDrawer: Function,
27
     closeDrawer: Function,
110
 
28
 
111
-    /**
112
-     * An object containing the CSS classes.
113
-     */
114
-    classes?: {[ key: string]: string},
115
-
116
     /**
29
     /**
117
      * The dispatch function from redux.
30
      * The dispatch function from redux.
118
      */
31
      */
124
      */
37
      */
125
     drawerParticipant: Object,
38
     drawerParticipant: Object,
126
 
39
 
127
-    /**
128
-     * Callback used to open a confirmation dialog for audio muting.
129
-     */
130
-    muteAudio: Function,
131
-
132
     /**
40
     /**
133
      * Target elements against which positioning calculations are made.
41
      * Target elements against which positioning calculations are made.
134
      */
42
      */
152
     /**
60
     /**
153
      * The ID of the participant.
61
      * The ID of the participant.
154
      */
62
      */
155
-    participantID?: string,
156
-
157
-    /**
158
-     * True if an overflow drawer should be displayed.
159
-     */
160
-    overflowDrawer: boolean,
161
-
162
-    /**
163
-     * The translate function.
164
-     */
165
-    t: Function
166
-};
167
-
168
-const styles = theme => {
169
-    return {
170
-        text: {
171
-            color: theme.palette.text02,
172
-            padding: '10px 16px',
173
-            height: '40px',
174
-            overflow: 'hidden',
175
-            display: 'flex',
176
-            alignItems: 'center',
177
-            boxSizing: 'border-box'
178
-        }
179
-    };
63
+    participantID: string
180
 };
64
 };
181
 
65
 
182
 /**
66
 /**
184
  */
68
  */
185
 class MeetingParticipantContextMenu extends Component<Props> {
69
 class MeetingParticipantContextMenu extends Component<Props> {
186
 
70
 
187
-    /**
188
-     * Creates new instance of MeetingParticipantContextMenu.
189
-     *
190
-     * @param {Props} props - The props.
191
-     */
192
-    constructor(props: Props) {
193
-        super(props);
194
-
195
-        this._getCurrentParticipantId = this._getCurrentParticipantId.bind(this);
196
-        this._onGrantModerator = this._onGrantModerator.bind(this);
197
-        this._onKick = this._onKick.bind(this);
198
-        this._onMuteEveryoneElse = this._onMuteEveryoneElse.bind(this);
199
-        this._onMuteVideo = this._onMuteVideo.bind(this);
200
-        this._onSendPrivateMessage = this._onSendPrivateMessage.bind(this);
201
-        this._onStopSharedVideo = this._onStopSharedVideo.bind(this);
202
-        this._onSendToRoom = this._onSendToRoom.bind(this);
203
-        this._onVolumeChange = this._onVolumeChange.bind(this);
204
-        this._onAskToUnmute = this._onAskToUnmute.bind(this);
205
-    }
206
-
207
-    _getCurrentParticipantId: () => string;
208
-
209
-    /**
210
-     * Returns the participant id for the item we want to operate.
211
-     *
212
-     * @returns {void}
213
-     */
214
-    _getCurrentParticipantId() {
215
-        const { _participant, drawerParticipant, overflowDrawer } = this.props;
216
-
217
-        return overflowDrawer ? drawerParticipant?.participantID : _participant?.id;
218
-    }
219
-
220
-    _onGrantModerator: () => void;
221
-
222
-    /**
223
-     * Grant moderator permissions.
224
-     *
225
-     * @returns {void}
226
-     */
227
-    _onGrantModerator() {
228
-        this.props.dispatch(openDialog(GrantModeratorDialog, {
229
-            participantID: this._getCurrentParticipantId()
230
-        }));
231
-    }
232
-
233
-    _onKick: () => void;
234
-
235
-    /**
236
-     * Kicks the participant.
237
-     *
238
-     * @returns {void}
239
-     */
240
-    _onKick() {
241
-        this.props.dispatch(openDialog(KickRemoteParticipantDialog, {
242
-            participantID: this._getCurrentParticipantId()
243
-        }));
244
-    }
245
-
246
-    _onStopSharedVideo: () => void;
247
-
248
-    /**
249
-     * Stops shared video.
250
-     *
251
-     * @returns {void}
252
-     */
253
-    _onStopSharedVideo() {
254
-        const { dispatch, onSelect } = this.props;
255
-
256
-        onSelect(true);
257
-        dispatch(stopSharedVideo());
258
-    }
259
-
260
-    _onMuteEveryoneElse: () => void;
261
-
262
-    /**
263
-     * Mutes everyone else.
264
-     *
265
-     * @returns {void}
266
-     */
267
-    _onMuteEveryoneElse() {
268
-        this.props.dispatch(openDialog(MuteEveryoneDialog, {
269
-            exclude: [ this._getCurrentParticipantId() ]
270
-        }));
271
-    }
272
-
273
-    _onMuteVideo: () => void;
274
-
275
-    /**
276
-     * Mutes the video of the selected participant.
277
-     *
278
-     * @returns {void}
279
-     */
280
-    _onMuteVideo() {
281
-        this.props.dispatch(openDialog(MuteRemoteParticipantsVideoDialog, {
282
-            participantID: this._getCurrentParticipantId()
283
-        }));
284
-    }
285
-
286
-    _onSendPrivateMessage: () => void;
287
-
288
-    /**
289
-     * Sends private message.
290
-     *
291
-     * @returns {void}
292
-     */
293
-    _onSendPrivateMessage() {
294
-        const { dispatch } = this.props;
295
-
296
-        dispatch(openChatById(this._getCurrentParticipantId()));
297
-    }
298
-
299
-    _onSendToRoom: (room: Object) => void;
300
-
301
-    /**
302
-     * Sends a participant to a room.
303
-     *
304
-     * @param {Object} room - The room that the participant should be moved to.
305
-     * @returns {void}
306
-     */
307
-    _onSendToRoom(room: Object) {
308
-        return () => {
309
-            const { _participant, dispatch, onSelect } = this.props;
310
-
311
-            onSelect(true);
312
-            sendAnalytics(createBreakoutRoomsEvent('send.participant.to.room'));
313
-            dispatch(sendParticipantToRoom(_participant.id, room.id));
314
-        };
315
-    }
316
-
317
-    _onVolumeChange: (number) => void;
318
-
319
-    /**
320
-     * Handles volume changes.
321
-     *
322
-     * @param {number} value - The new value for the volume.
323
-     * @returns {void}
324
-     */
325
-    _onVolumeChange(value) {
326
-        const { _participant, dispatch } = this.props;
327
-        const { id } = _participant;
328
-
329
-        dispatch(setVolume(id, value));
330
-    }
331
-
332
-    _onAskToUnmute: () => void;
333
-
334
-    /**
335
-     * Handles click on ask to unmute.
336
-     *
337
-     * @returns {void}
338
-     */
339
-    _onAskToUnmute() {
340
-        const { _participant, dispatch } = this.props;
341
-        const { id } = _participant;
342
-
343
-        dispatch(approveParticipant(id));
344
-    }
345
-
346
-
347
     /**
71
     /**
348
      * Implements React's {@link Component#render()}.
72
      * Implements React's {@link Component#render()}.
349
      *
73
      *
352
      */
76
      */
353
     render() {
77
     render() {
354
         const {
78
         const {
355
-            _isAudioForceMuted,
356
-            _currentRoomId,
357
-            _isLocalModerator,
358
-            _isChatButtonEnabled,
359
-            _isParticipantModerator,
360
-            _isParticipantVideoMuted,
361
-            _isParticipantAudioMuted,
362
-            _isVideoForceMuted,
363
             _localVideoOwner,
79
             _localVideoOwner,
364
             _participant,
80
             _participant,
365
-            _rooms,
366
-            _volume = 1,
367
-            classes,
368
             closeDrawer,
81
             closeDrawer,
369
             drawerParticipant,
82
             drawerParticipant,
370
             offsetTarget,
83
             offsetTarget,
371
             onEnter,
84
             onEnter,
372
             onLeave,
85
             onLeave,
373
-            onSelect,
374
-            overflowDrawer,
375
-            muteAudio,
376
-            t
86
+            onSelect
377
         } = this.props;
87
         } = this.props;
378
 
88
 
379
         if (!_participant) {
89
         if (!_participant) {
380
             return null;
90
             return null;
381
         }
91
         }
382
 
92
 
383
-        const showVolumeSlider = !isIosMobileBrowser()
384
-            && overflowDrawer
385
-            && typeof _volume === 'number'
386
-            && !isNaN(_volume);
387
-
388
-        const fakeParticipantActions = [ {
389
-            accessibilityLabel: t('toolbar.stopSharedVideo'),
390
-            icon: IconShareVideo,
391
-            onClick: this._onStopSharedVideo,
392
-            text: t('toolbar.stopSharedVideo')
393
-        } ];
394
-
395
-        const moderatorActions1 = [
396
-            overflowDrawer && (_isAudioForceMuted || _isVideoForceMuted) ? {
397
-                accessibilityLabel: t(_isAudioForceMuted
398
-                    ? 'participantsPane.actions.askUnmute'
399
-                    : 'participantsPane.actions.allowVideo'),
400
-                icon: IconMicrophone,
401
-                onClick: this._onAskToUnmute,
402
-                text: t(_isAudioForceMuted
403
-                    ? 'participantsPane.actions.askUnmute'
404
-                    : 'participantsPane.actions.allowVideo')
405
-            } : null,
406
-            !_isParticipantAudioMuted && overflowDrawer ? {
407
-                accessibilityLabel: t('dialog.muteParticipantButton'),
408
-                icon: IconMicDisabled,
409
-                onClick: muteAudio(_participant),
410
-                text: t('dialog.muteParticipantButton')
411
-            } : null, {
412
-                accessibilityLabel: t('toolbar.accessibilityLabel.muteEveryoneElse'),
413
-                icon: IconMuteEveryoneElse,
414
-                onClick: this._onMuteEveryoneElse,
415
-                text: t('toolbar.accessibilityLabel.muteEveryoneElse')
416
-            },
417
-            _isParticipantVideoMuted ? null : {
418
-                accessibilityLabel: t('participantsPane.actions.stopVideo'),
419
-                icon: IconVideoOff,
420
-                onClick: this._onMuteVideo,
421
-                text: t('participantsPane.actions.stopVideo')
422
-            }
423
-        ].filter(Boolean);
424
-
425
-        const moderatorActions2 = [
426
-            _isLocalModerator && !_isParticipantModerator ? {
427
-                accessibilityLabel: t('toolbar.accessibilityLabel.grantModerator'),
428
-                icon: IconCrown,
429
-                onClick: this._onGrantModerator,
430
-                text: t('toolbar.accessibilityLabel.grantModerator')
431
-            } : null,
432
-            _isLocalModerator ? {
433
-                accessibilityLabel: t('videothumbnail.kick'),
434
-                icon: IconCloseCircle,
435
-                onClick: this._onKick,
436
-                text: t('videothumbnail.kick')
437
-            } : null,
438
-            _isChatButtonEnabled ? {
439
-                accessibilityLabel: t('toolbar.accessibilityLabel.privateMessage'),
440
-                icon: IconMessage,
441
-                onClick: this._onSendPrivateMessage,
442
-                text: t('toolbar.accessibilityLabel.privateMessage')
443
-            } : null
444
-        ].filter(Boolean);
445
-
446
-        const breakoutRoomActions = _rooms.map(room => {
447
-            if (room.id !== _currentRoomId) {
448
-                return {
449
-                    accessibilityLabel: room.name || t('breakoutRooms.mainRoom'),
450
-                    icon: IconRingGroup,
451
-                    onClick: this._onSendToRoom(room),
452
-                    text: room.name || t('breakoutRooms.mainRoom')
453
-                };
454
-            }
455
-
456
-            return null;
457
-        }
458
-        ).filter(Boolean);
459
-
460
-        const actions
461
-            = _participant?.isFakeParticipant ? (
462
-                <>
463
-                    {_localVideoOwner && (
464
-                        <ContextMenuItemGroup
465
-                            actions = { fakeParticipantActions } />
466
-                    )}
467
-                </>
468
-            ) : (
469
-                <>
470
-                    {_isLocalModerator
471
-                        && <ContextMenuItemGroup actions = { moderatorActions1 } />
472
-                    }
473
-
474
-                    <ContextMenuItemGroup actions = { moderatorActions2 } />
475
-
476
-                    {
477
-                        _isLocalModerator && _rooms.length > 1
478
-                            && <ContextMenuItemGroup actions = { breakoutRoomActions } >
479
-                                <div className = { classes && classes.text }>
480
-                                    {t('breakoutRooms.actions.sendToBreakoutRoom')}
481
-                                </div>
482
-                            </ContextMenuItemGroup>
483
-                    }
484
-                    { showVolumeSlider
485
-                        && <ContextMenuItemGroup>
486
-                            <VolumeSlider
487
-                                initialValue = { _volume }
488
-                                key = 'volume-slider'
489
-                                onChange = { this._onVolumeChange } />
490
-                        </ContextMenuItemGroup>
491
-                    }
492
-                </>
493
-            );
494
-
495
         return (
93
         return (
496
-            <ContextMenu
497
-                entity = { _participant }
498
-                isDrawerOpen = { drawerParticipant }
94
+            <ParticipantContextMenu
95
+                closeDrawer = { closeDrawer }
96
+                drawerParticipant = { drawerParticipant }
97
+                localVideoOwner = { _localVideoOwner }
499
                 offsetTarget = { offsetTarget }
98
                 offsetTarget = { offsetTarget }
500
-                onClick = { onSelect }
501
-                onDrawerClose = { closeDrawer }
502
-                onMouseEnter = { onEnter }
503
-                onMouseLeave = { onLeave }>
504
-                {overflowDrawer && <ContextMenuItemGroup
505
-                    actions = { [ {
506
-                        accessibilityLabel: drawerParticipant && drawerParticipant.displayName,
507
-                        customIcon: <Avatar
508
-                            participantId = { drawerParticipant && drawerParticipant.participantID }
509
-                            size = { 20 } />,
510
-                        text: drawerParticipant && drawerParticipant.displayName
511
-                    } ] } />}
512
-                {actions}
513
-            </ContextMenu>
99
+                onEnter = { onEnter }
100
+                onLeave = { onLeave }
101
+                onSelect = { onSelect }
102
+                participant = { _participant }
103
+                thumbnailMenu = { false } />
514
         );
104
         );
515
     }
105
     }
516
 }
106
 }
531
     const participant = getParticipantByIdOrUndefined(state,
121
     const participant = getParticipantByIdOrUndefined(state,
532
         overflowDrawer ? drawerParticipant?.participantID : participantID);
122
         overflowDrawer ? drawerParticipant?.participantID : participantID);
533
 
123
 
534
-    const _currentRoomId = getCurrentRoomId(state);
535
-    const _isLocalModerator = isLocalParticipantModerator(state);
536
-    const _isChatButtonEnabled = isToolbarButtonEnabled('chat', state);
537
-    const _isParticipantVideoMuted = isParticipantVideoMuted(participant, state);
538
-    const _isParticipantAudioMuted = isParticipantAudioMuted(participant, state);
539
-    const _isParticipantModerator = isParticipantModerator(participant);
540
-    const _rooms = Object.values(getBreakoutRooms(state));
541
-
542
-    const { participantsVolume } = state['features/filmstrip'];
543
-    const id = participant?.id;
544
-    const isLocal = participant?.local ?? true;
545
-
546
     return {
124
     return {
547
-        _isAudioForceMuted: isForceMuted(participant, MEDIA_TYPE.AUDIO, state),
548
-        _currentRoomId,
549
-        _isLocalModerator,
550
-        _isChatButtonEnabled,
551
-        _isParticipantModerator,
552
-        _isParticipantVideoMuted,
553
-        _isParticipantAudioMuted,
554
-        _isVideoForceMuted: isForceMuted(participant, MEDIA_TYPE.VIDEO, state),
555
         _localVideoOwner: Boolean(ownerId === localParticipantId),
125
         _localVideoOwner: Boolean(ownerId === localParticipantId),
556
-        _participant: participant,
557
-        _rooms,
558
-        _volume: isLocal ? undefined : id ? participantsVolume[id] : undefined
126
+        _participant: participant
559
     };
127
     };
560
 }
128
 }
561
 
129
 
562
-export default translate(connect(_mapStateToProps)(withStyles(styles)(MeetingParticipantContextMenu)));
130
+export default translate(connect(_mapStateToProps)(MeetingParticipantContextMenu));

+ 1
- 1
react/features/participants-pane/components/web/RaisedHandIndicator.js Parādīt failu

8
 const useStyles = makeStyles(theme => {
8
 const useStyles = makeStyles(theme => {
9
     return {
9
     return {
10
         indicator: {
10
         indicator: {
11
-            backgroundColor: theme.palette.warning02,
11
+            backgroundColor: theme.palette.warning01,
12
             borderRadius: `${theme.shape.borderRadius / 2}px`,
12
             borderRadius: `${theme.shape.borderRadius / 2}px`,
13
             height: '24px',
13
             height: '24px',
14
             width: '24px'
14
             width: '24px'

+ 2
- 0
react/features/participants-pane/constants.js Parādīt failu

95
     [MEDIA_STATE.FORCE_MUTED]: (
95
     [MEDIA_STATE.FORCE_MUTED]: (
96
         <Icon
96
         <Icon
97
             color = '#E04757'
97
             color = '#E04757'
98
+            id = 'videoMuted'
98
             size = { 16 }
99
             size = { 16 }
99
             src = { IconCameraEmptyDisabled } />
100
             src = { IconCameraEmptyDisabled } />
100
     ),
101
     ),
101
     [MEDIA_STATE.MUTED]: (
102
     [MEDIA_STATE.MUTED]: (
102
         <Icon
103
         <Icon
104
+            id = 'videoMuted'
103
             size = { 16 }
105
             size = { 16 }
104
             src = { IconCameraEmptyDisabled } />
106
             src = { IconCameraEmptyDisabled } />
105
     ),
107
     ),

+ 1
- 1
react/features/toolbox/components/MuteEveryonesVideoButton.js Parādīt failu

27
  * every participant (except the local one).
27
  * every participant (except the local one).
28
  */
28
  */
29
 class MuteEveryonesVideoButton extends AbstractButton<Props, *> {
29
 class MuteEveryonesVideoButton extends AbstractButton<Props, *> {
30
-    accessibilityLabel = 'toolbar.accessibilityLabel.muteEveryonesVideo';
30
+    accessibilityLabel = 'toolbar.accessibilityLabel.muteEveryonesVideoStream';
31
     icon = IconMuteVideoEveryone;
31
     icon = IconMuteVideoEveryone;
32
     label = 'toolbar.muteEveryonesVideo';
32
     label = 'toolbar.muteEveryonesVideo';
33
     tooltip = 'toolbar.muteEveryonesVideo';
33
     tooltip = 'toolbar.muteEveryonesVideo';

+ 3
- 1
react/features/video-menu/components/AbstractMuteButton.js Parādīt failu

4
     createRemoteVideoMenuButtonEvent,
4
     createRemoteVideoMenuButtonEvent,
5
     sendAnalytics
5
     sendAnalytics
6
 } from '../../analytics';
6
 } from '../../analytics';
7
+import { rejectParticipantAudio } from '../../av-moderation/actions';
7
 import { IconMicDisabled } from '../../base/icons';
8
 import { IconMicDisabled } from '../../base/icons';
8
 import { MEDIA_TYPE } from '../../base/media';
9
 import { MEDIA_TYPE } from '../../base/media';
9
 import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox/components';
10
 import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox/components';
54
         const { dispatch, participantID } = this.props;
55
         const { dispatch, participantID } = this.props;
55
 
56
 
56
         sendAnalytics(createRemoteVideoMenuButtonEvent(
57
         sendAnalytics(createRemoteVideoMenuButtonEvent(
57
-            'mute.button',
58
+            'mute',
58
             {
59
             {
59
                 'participant_id': participantID
60
                 'participant_id': participantID
60
             }));
61
             }));
61
 
62
 
62
         dispatch(muteRemote(participantID, MEDIA_TYPE.AUDIO));
63
         dispatch(muteRemote(participantID, MEDIA_TYPE.AUDIO));
64
+        dispatch(rejectParticipantAudio(participantID));
63
     }
65
     }
64
 
66
 
65
     /**
67
     /**

+ 1
- 1
react/features/video-menu/components/AbstractMuteEveryoneElsesVideoButton.js Parādīt failu

29
  * An abstract remote video menu button which disables the camera of all the other participants.
29
  * An abstract remote video menu button which disables the camera of all the other participants.
30
  */
30
  */
31
 export default class AbstractMuteEveryoneElsesVideoButton extends AbstractButton<Props, *> {
31
 export default class AbstractMuteEveryoneElsesVideoButton extends AbstractButton<Props, *> {
32
-    accessibilityLabel = 'toolbar.accessibilityLabel.muteEveryoneElsesVideo';
32
+    accessibilityLabel = 'toolbar.accessibilityLabel.muteEveryoneElsesVideoStream';
33
     icon = IconMuteVideoEveryone;
33
     icon = IconMuteVideoEveryone;
34
     label = 'videothumbnail.domuteVideoOfOthers';
34
     label = 'videothumbnail.domuteVideoOfOthers';
35
 
35
 

+ 49
- 0
react/features/video-menu/components/web/AskToUnmuteButton.js Parādīt failu

1
+// @flow
2
+
3
+import React, { useCallback } from 'react';
4
+import { useTranslation } from 'react-i18next';
5
+import { useDispatch } from 'react-redux';
6
+
7
+import { approveParticipant } from '../../../av-moderation/actions';
8
+import ContextMenuItem from '../../../base/components/context-menu/ContextMenuItem';
9
+import { IconMicrophoneEmpty } from '../../../base/icons';
10
+
11
+type Props = {
12
+
13
+    /**
14
+     * Whether or not the participant is audio force muted.
15
+     */
16
+    isAudioForceMuted: boolean,
17
+
18
+    /**
19
+     * Whether or not the participant is video force muted.
20
+     */
21
+    isVideoForceMuted: boolean,
22
+
23
+    /**
24
+     * The ID for the participant on which the button will act.
25
+     */
26
+    participantID: string
27
+}
28
+
29
+const AskToUnmuteButton = ({ isAudioForceMuted, isVideoForceMuted, participantID }: Props) => {
30
+    const dispatch = useDispatch();
31
+    const { t } = useTranslation();
32
+    const _onClick = useCallback(() => {
33
+        dispatch(approveParticipant(participantID));
34
+    }, [ participantID ]);
35
+
36
+    const text = isAudioForceMuted || !isVideoForceMuted
37
+        ? t('participantsPane.actions.askUnmute')
38
+        : t('participantsPane.actions.allowVideo');
39
+
40
+    return (
41
+        <ContextMenuItem
42
+            accessibilityLabel = { text }
43
+            icon = { IconMicrophoneEmpty }
44
+            onClick = { _onClick }
45
+            text = { text } />
46
+    );
47
+};
48
+
49
+export default AskToUnmuteButton;

+ 7
- 8
react/features/video-menu/components/web/ConnectionStatusButton.js Parādīt failu

1
 // @flow
1
 // @flow
2
 import React, { useCallback } from 'react';
2
 import React, { useCallback } from 'react';
3
 
3
 
4
+import ContextMenuItem from '../../../base/components/context-menu/ContextMenuItem';
4
 import { translate } from '../../../base/i18n';
5
 import { translate } from '../../../base/i18n';
5
 import { IconInfo } from '../../../base/icons';
6
 import { IconInfo } from '../../../base/icons';
6
 import { connect } from '../../../base/redux';
7
 import { connect } from '../../../base/redux';
7
 import { renderConnectionStatus } from '../../actions.web';
8
 import { renderConnectionStatus } from '../../actions.web';
8
 
9
 
9
-import VideoMenuButton from './VideoMenuButton';
10
-
11
 type Props = {
10
 type Props = {
12
 
11
 
13
     /**
12
     /**
29
 
28
 
30
 const ConnectionStatusButton = ({
29
 const ConnectionStatusButton = ({
31
     dispatch,
30
     dispatch,
32
-    participantId,
33
     t
31
     t
34
 }: Props) => {
32
 }: Props) => {
35
-    const onClick = useCallback(() => {
33
+    const onClick = useCallback(e => {
34
+        e.stopPropagation();
36
         dispatch(renderConnectionStatus(true));
35
         dispatch(renderConnectionStatus(true));
37
     }, [ dispatch ]);
36
     }, [ dispatch ]);
38
 
37
 
39
     return (
38
     return (
40
-        <VideoMenuButton
41
-            buttonText = { t('videothumbnail.connectionInfo') }
39
+        <ContextMenuItem
40
+            accessibilityLabel = { t('videothumbnail.connectionInfo') }
42
             icon = { IconInfo }
41
             icon = { IconInfo }
43
-            id = { `connstatus_${participantId}` }
44
-            onClick = { onClick } />
42
+            onClick = { onClick }
43
+            text = { t('videothumbnail.connectionInfo') } />
45
     );
44
     );
46
 };
45
 };
47
 
46
 

+ 13
- 6
react/features/video-menu/components/web/FlipLocalVideoButton.js Parādīt failu

2
 
2
 
3
 import React, { PureComponent } from 'react';
3
 import React, { PureComponent } from 'react';
4
 
4
 
5
+import ContextMenuItem from '../../../base/components/context-menu/ContextMenuItem';
5
 import { translate } from '../../../base/i18n';
6
 import { translate } from '../../../base/i18n';
6
 import { connect } from '../../../base/redux';
7
 import { connect } from '../../../base/redux';
7
 import { updateSettings } from '../../../base/settings';
8
 import { updateSettings } from '../../../base/settings';
8
 
9
 
9
-import VideoMenuButton from './VideoMenuButton';
10
-
11
 /**
10
 /**
12
  * The type of the React {@code Component} props of {@link FlipLocalVideoButton}.
11
  * The type of the React {@code Component} props of {@link FlipLocalVideoButton}.
13
  */
12
  */
18
      */
17
      */
19
     _localFlipX: boolean,
18
     _localFlipX: boolean,
20
 
19
 
20
+    /**
21
+     * Button text class name.
22
+     */
23
+    className: string,
24
+
21
     /**
25
     /**
22
      * The redux dispatch function.
26
      * The redux dispatch function.
23
      */
27
      */
61
      */
65
      */
62
     render() {
66
     render() {
63
         const {
67
         const {
68
+            className,
64
             t
69
             t
65
         } = this.props;
70
         } = this.props;
66
 
71
 
67
         return (
72
         return (
68
-            <VideoMenuButton
69
-                buttonText = { t('videothumbnail.flip') }
70
-                displayClass = 'fliplink'
73
+            <ContextMenuItem
74
+                accessibilityLabel = { t('videothumbnail.flip') }
75
+                className = 'fliplink'
71
                 id = 'flipLocalVideoButton'
76
                 id = 'flipLocalVideoButton'
72
-                onClick = { this._onClick } />
77
+                onClick = { this._onClick }
78
+                text = { t('videothumbnail.flip') }
79
+                textClassName = { className } />
73
         );
80
         );
74
     }
81
     }
75
 
82
 

+ 7
- 8
react/features/video-menu/components/web/GrantModeratorButton.js Parādīt failu

2
 
2
 
3
 import React from 'react';
3
 import React from 'react';
4
 
4
 
5
+import ContextMenuItem from '../../../base/components/context-menu/ContextMenuItem';
5
 import { translate } from '../../../base/i18n';
6
 import { translate } from '../../../base/i18n';
6
 import { IconCrown } from '../../../base/icons';
7
 import { IconCrown } from '../../../base/icons';
7
 import { connect } from '../../../base/redux';
8
 import { connect } from '../../../base/redux';
10
     type Props
11
     type Props
11
 } from '../AbstractGrantModeratorButton';
12
 } from '../AbstractGrantModeratorButton';
12
 
13
 
13
-import VideoMenuButton from './VideoMenuButton';
14
-
15
 declare var interfaceConfig: Object;
14
 declare var interfaceConfig: Object;
16
 
15
 
17
 /**
16
 /**
37
      * @returns {ReactElement}
36
      * @returns {ReactElement}
38
      */
37
      */
39
     render() {
38
     render() {
40
-        const { participantID, t, visible } = this.props;
39
+        const { t, visible } = this.props;
41
 
40
 
42
         if (!visible) {
41
         if (!visible) {
43
             return null;
42
             return null;
44
         }
43
         }
45
 
44
 
46
         return (
45
         return (
47
-            <VideoMenuButton
48
-                buttonText = { t('videothumbnail.grantModerator') }
49
-                displayClass = 'grantmoderatorlink'
46
+            <ContextMenuItem
47
+                accessibilityLabel = { t('toolbar.accessibilityLabel.grantModerator') }
48
+                className = 'grantmoderatorlink'
50
                 icon = { IconCrown }
49
                 icon = { IconCrown }
51
-                id = { `grantmoderatorlink_${participantID}` }
52
                 // eslint-disable-next-line react/jsx-handler-names
50
                 // eslint-disable-next-line react/jsx-handler-names
53
-                onClick = { this._handleClick } />
51
+                onClick = { this._handleClick }
52
+                text = { t('videothumbnail.grantModerator') } />
54
         );
53
         );
55
     }
54
     }
56
 
55
 

+ 14
- 7
react/features/video-menu/components/web/HideSelfViewVideoButton.js Parādīt failu

2
 
2
 
3
 import React, { PureComponent } from 'react';
3
 import React, { PureComponent } from 'react';
4
 
4
 
5
+import ContextMenuItem from '../../../base/components/context-menu/ContextMenuItem';
5
 import { translate } from '../../../base/i18n';
6
 import { translate } from '../../../base/i18n';
6
 import { connect } from '../../../base/redux';
7
 import { connect } from '../../../base/redux';
7
 import { updateSettings } from '../../../base/settings';
8
 import { updateSettings } from '../../../base/settings';
8
 import { NOTIFICATION_TIMEOUT_TYPE, showNotification } from '../../../notifications';
9
 import { NOTIFICATION_TIMEOUT_TYPE, showNotification } from '../../../notifications';
9
 import { openSettingsDialog, SETTINGS_TABS } from '../../../settings';
10
 import { openSettingsDialog, SETTINGS_TABS } from '../../../settings';
10
 
11
 
11
-import VideoMenuButton from './VideoMenuButton';
12
-
13
 /**
12
 /**
14
  * The type of the React {@code Component} props of {@link HideSelfViewVideoButton}.
13
  * The type of the React {@code Component} props of {@link HideSelfViewVideoButton}.
15
  */
14
  */
25
      */
24
      */
26
     dispatch: Function,
25
     dispatch: Function,
27
 
26
 
27
+    /**
28
+     * Button text class name.
29
+     */
30
+    className: string,
31
+
28
     /**
32
     /**
29
      * Click handler executed aside from the main action.
33
      * Click handler executed aside from the main action.
30
      */
34
      */
63
      */
67
      */
64
     render() {
68
     render() {
65
         const {
69
         const {
70
+            className,
66
             t
71
             t
67
         } = this.props;
72
         } = this.props;
68
 
73
 
69
         return (
74
         return (
70
-            <VideoMenuButton
71
-                buttonText = { t('videothumbnail.hideSelfView') }
72
-                displayClass = 'hideselflink'
73
-                id = 'hideselfviewbutton'
74
-                onClick = { this._onClick } />
75
+            <ContextMenuItem
76
+                accessibilityLabel = { t('videothumbnail.hideSelfView') }
77
+                className = 'hideselflink'
78
+                id = 'hideselfviewButton'
79
+                onClick = { this._onClick }
80
+                text = { t('videothumbnail.hideSelfView') }
81
+                textClassName = { className } />
75
         );
82
         );
76
     }
83
     }
77
 
84
 

+ 8
- 8
react/features/video-menu/components/web/KickButton.js Parādīt failu

2
 
2
 
3
 import React from 'react';
3
 import React from 'react';
4
 
4
 
5
+import ContextMenuItem from '../../../base/components/context-menu/ContextMenuItem';
5
 import { translate } from '../../../base/i18n';
6
 import { translate } from '../../../base/i18n';
6
-import { IconKick } from '../../../base/icons';
7
+import { IconCloseCircle } from '../../../base/icons';
7
 import { connect } from '../../../base/redux';
8
 import { connect } from '../../../base/redux';
8
 import AbstractKickButton, {
9
 import AbstractKickButton, {
9
     type Props
10
     type Props
10
 } from '../AbstractKickButton';
11
 } from '../AbstractKickButton';
11
 
12
 
12
-import VideoMenuButton from './VideoMenuButton';
13
-
14
 /**
13
 /**
15
  * Implements a React {@link Component} which displays a button for kicking out
14
  * Implements a React {@link Component} which displays a button for kicking out
16
  * a participant from the conference.
15
  * a participant from the conference.
43
         const { participantID, t } = this.props;
42
         const { participantID, t } = this.props;
44
 
43
 
45
         return (
44
         return (
46
-            <VideoMenuButton
47
-                buttonText = { t('videothumbnail.kick') }
48
-                displayClass = 'kicklink'
49
-                icon = { IconKick }
45
+            <ContextMenuItem
46
+                accessibilityLabel = { t('videothumbnail.kick') }
47
+                className = 'kicklink'
48
+                icon = { IconCloseCircle }
50
                 id = { `ejectlink_${participantID}` }
49
                 id = { `ejectlink_${participantID}` }
51
                 // eslint-disable-next-line react/jsx-handler-names
50
                 // eslint-disable-next-line react/jsx-handler-names
52
-                onClick = { this._handleClick } />
51
+                onClick = { this._handleClick }
52
+                text = { t('videothumbnail.kick') } />
53
         );
53
         );
54
     }
54
     }
55
 
55
 

+ 65
- 25
react/features/video-menu/components/web/LocalVideoMenuTriggerButton.js Parādīt failu

1
 // @flow
1
 // @flow
2
 
2
 
3
+import { withStyles } from '@material-ui/styles';
3
 import React, { Component } from 'react';
4
 import React, { Component } from 'react';
4
 import { batch } from 'react-redux';
5
 import { batch } from 'react-redux';
5
 
6
 
7
+import ContextMenu from '../../../base/components/context-menu/ContextMenu';
8
+import ContextMenuItemGroup from '../../../base/components/context-menu/ContextMenuItemGroup';
6
 import { isMobileBrowser } from '../../../base/environment/utils';
9
 import { isMobileBrowser } from '../../../base/environment/utils';
7
 import { translate } from '../../../base/i18n';
10
 import { translate } from '../../../base/i18n';
8
-import { Icon, IconMenuThumb } from '../../../base/icons';
11
+import { Icon, IconHorizontalPoints } from '../../../base/icons';
9
 import {
12
 import {
10
     getLocalParticipant
13
     getLocalParticipant
11
 } from '../../../base/participants';
14
 } from '../../../base/participants';
20
 import ConnectionStatusButton from './ConnectionStatusButton';
23
 import ConnectionStatusButton from './ConnectionStatusButton';
21
 import FlipLocalVideoButton from './FlipLocalVideoButton';
24
 import FlipLocalVideoButton from './FlipLocalVideoButton';
22
 import HideSelfViewVideoButton from './HideSelfViewVideoButton';
25
 import HideSelfViewVideoButton from './HideSelfViewVideoButton';
23
-import VideoMenu from './VideoMenu';
24
-
25
 
26
 
26
 /**
27
 /**
27
  * The type of the React {@code Component} props of
28
  * The type of the React {@code Component} props of
30
 type Props = {
31
 type Props = {
31
 
32
 
32
     /**
33
     /**
33
-     * The redux dispatch function.
34
+     * Whether or not the button should be visible.
35
+     */
36
+    buttonVisible: boolean,
37
+
38
+    /**
39
+     * An object containing the CSS classes.
34
      */
40
      */
35
-     dispatch: Function,
41
+    classes: Object,
36
 
42
 
37
     /**
43
     /**
38
-     * Gets a ref to the current component instance.
44
+     * The redux dispatch function.
39
      */
45
      */
40
-     getRef: Function,
46
+    dispatch: Function,
41
 
47
 
42
     /**
48
     /**
43
      * Hides popover.
49
      * Hides popover.
44
      */
50
      */
45
-     hidePopover: Function,
51
+    hidePopover: Function,
46
 
52
 
47
     /**
53
     /**
48
      * Whether the popover is visible or not.
54
      * Whether the popover is visible or not.
49
      */
55
      */
50
-     popoverVisible: boolean,
56
+    popoverVisible: boolean,
51
 
57
 
52
     /**
58
     /**
53
      * Shows popover.
59
      * Shows popover.
54
      */
60
      */
55
-     showPopover: Function,
61
+    showPopover: Function,
56
 
62
 
57
     /**
63
     /**
58
      * The id of the local participant.
64
      * The id of the local participant.
87
     t: Function
93
     t: Function
88
 };
94
 };
89
 
95
 
96
+const styles = theme => {
97
+    return {
98
+        triggerButton: {
99
+            backgroundColor: theme.palette.action01,
100
+            padding: '3px',
101
+            display: 'inline-block',
102
+            borderRadius: '4px'
103
+        },
104
+
105
+        contextMenu: {
106
+            position: 'relative',
107
+            marginTop: 0,
108
+            right: 'auto',
109
+            padding: '0',
110
+            minWidth: '200px'
111
+        },
112
+
113
+        flipText: {
114
+            marginLeft: '36px'
115
+        }
116
+    };
117
+};
118
+
90
 /**
119
 /**
91
  * React Component for displaying an icon associated with opening the
120
  * React Component for displaying an icon associated with opening the
92
  * the video menu for the local participant.
121
  * the video menu for the local participant.
122
             _showConnectionInfo,
151
             _showConnectionInfo,
123
             _overflowDrawer,
152
             _overflowDrawer,
124
             _showLocalVideoFlipButton,
153
             _showLocalVideoFlipButton,
154
+            buttonVisible,
155
+            classes,
125
             hidePopover,
156
             hidePopover,
126
             popoverVisible,
157
             popoverVisible,
127
             t
158
             t
130
         const content = _showConnectionInfo
161
         const content = _showConnectionInfo
131
             ? <ConnectionIndicatorContent participantId = { _localParticipantId } />
162
             ? <ConnectionIndicatorContent participantId = { _localParticipantId } />
132
             : (
163
             : (
133
-                <VideoMenu id = 'localVideoMenu'>
134
-                    <FlipLocalVideoButton onClick = { hidePopover } />
135
-                    <HideSelfViewVideoButton onClick = { hidePopover } />
136
-                    { isMobileBrowser()
137
-                            && <ConnectionStatusButton participantId = { _localParticipantId } />
138
-                    }
139
-                </VideoMenu>
164
+                <ContextMenu
165
+                    className = { classes.contextMenu }
166
+                    hidden = { false }
167
+                    inDrawer = { _overflowDrawer }>
168
+                    <ContextMenuItemGroup>
169
+                        <FlipLocalVideoButton
170
+                            className = { _overflowDrawer ? classes.flipText : '' }
171
+                            onClick = { hidePopover } />
172
+                        <HideSelfViewVideoButton
173
+                            className = { _overflowDrawer ? classes.flipText : '' }
174
+                            onClick = { hidePopover } />
175
+                        { isMobileBrowser()
176
+                                    && <ConnectionStatusButton participantId = { _localParticipantId } />
177
+                        }
178
+                    </ContextMenuItemGroup>
179
+                </ContextMenu>
140
             );
180
             );
141
 
181
 
142
         return (
182
         return (
149
                     overflowDrawer = { _overflowDrawer }
189
                     overflowDrawer = { _overflowDrawer }
150
                     position = { _menuPosition }
190
                     position = { _menuPosition }
151
                     visible = { popoverVisible }>
191
                     visible = { popoverVisible }>
152
-                    {!_overflowDrawer && (
192
+                    {!_overflowDrawer && buttonVisible && (
153
                         <span
193
                         <span
154
-                            className = 'popover-trigger local-video-menu-trigger'>
194
+                            className = { classes.triggerButton }
195
+                            role = 'button'>
155
                             {!isMobileBrowser() && <Icon
196
                             {!isMobileBrowser() && <Icon
156
                                 ariaLabel = { t('dialog.localUserControls') }
197
                                 ariaLabel = { t('dialog.localUserControls') }
157
-                                role = 'button'
158
-                                size = '1.4em'
159
-                                src = { IconMenuThumb }
198
+                                size = { 18 }
199
+                                src = { IconHorizontalPoints }
160
                                 tabIndex = { 0 }
200
                                 tabIndex = { 0 }
161
                                 title = { t('dialog.localUserControls') } />
201
                                 title = { t('dialog.localUserControls') } />
162
                             }
202
                             }
221
         _menuPosition = 'left-start';
261
         _menuPosition = 'left-start';
222
         break;
262
         break;
223
     case LAYOUTS.VERTICAL_FILMSTRIP_VIEW:
263
     case LAYOUTS.VERTICAL_FILMSTRIP_VIEW:
224
-        _menuPosition = 'left-end';
264
+        _menuPosition = 'left-start';
225
         break;
265
         break;
226
     case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW:
266
     case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW:
227
-        _menuPosition = 'top';
267
+        _menuPosition = 'top-start';
228
         break;
268
         break;
229
     default:
269
     default:
230
         _menuPosition = 'auto';
270
         _menuPosition = 'auto';
239
     };
279
     };
240
 }
280
 }
241
 
281
 
242
-export default translate(connect(_mapStateToProps)(LocalVideoMenuTriggerButton));
282
+export default translate(connect(_mapStateToProps)(withStyles(styles)(LocalVideoMenuTriggerButton)));

+ 13
- 16
react/features/video-menu/components/web/MuteButton.js Parādīt failu

2
 
2
 
3
 import React from 'react';
3
 import React from 'react';
4
 
4
 
5
+import ContextMenuItem from '../../../base/components/context-menu/ContextMenuItem';
5
 import { translate } from '../../../base/i18n';
6
 import { translate } from '../../../base/i18n';
6
-import { IconMicDisabled } from '../../../base/icons';
7
+import { IconMicrophoneEmptySlash } from '../../../base/icons';
7
 import { connect } from '../../../base/redux';
8
 import { connect } from '../../../base/redux';
8
 import AbstractMuteButton, {
9
 import AbstractMuteButton, {
9
     _mapStateToProps,
10
     _mapStateToProps,
10
     type Props
11
     type Props
11
 } from '../AbstractMuteButton';
12
 } from '../AbstractMuteButton';
12
 
13
 
13
-import VideoMenuButton from './VideoMenuButton';
14
 
14
 
15
 /**
15
 /**
16
  * Implements a React {@link Component} which displays a button for audio muting
16
  * Implements a React {@link Component} which displays a button for audio muting
41
      * @returns {ReactElement}
41
      * @returns {ReactElement}
42
      */
42
      */
43
     render() {
43
     render() {
44
-        const { _audioTrackMuted, participantID, t } = this.props;
45
-        const muteConfig = _audioTrackMuted ? {
46
-            translationKey: 'videothumbnail.muted',
47
-            muteClassName: 'mutelink disabled'
48
-        } : {
49
-            translationKey: 'videothumbnail.domute',
50
-            muteClassName: 'mutelink'
51
-        };
44
+        const { _audioTrackMuted, t } = this.props;
45
+
46
+        if (_audioTrackMuted) {
47
+            return null;
48
+        }
52
 
49
 
53
         return (
50
         return (
54
-            <VideoMenuButton
55
-                buttonText = { t(muteConfig.translationKey) }
56
-                displayClass = { muteConfig.muteClassName }
57
-                icon = { IconMicDisabled }
58
-                id = { `mutelink_${participantID}` }
51
+            <ContextMenuItem
52
+                accessibilityLabel = { t('dialog.muteParticipantButton') }
53
+                className = 'mutelink'
54
+                icon = { IconMicrophoneEmptySlash }
59
                 // eslint-disable-next-line react/jsx-handler-names
55
                 // eslint-disable-next-line react/jsx-handler-names
60
-                onClick = { this._handleClick } />
56
+                onClick = { this._handleClick }
57
+                text = { t('dialog.muteParticipantButton') } />
61
         );
58
         );
62
     }
59
     }
63
 
60
 

+ 6
- 8
react/features/video-menu/components/web/MuteEveryoneElseButton.js Parādīt failu

2
 
2
 
3
 import React from 'react';
3
 import React from 'react';
4
 
4
 
5
+import ContextMenuItem from '../../../base/components/context-menu/ContextMenuItem';
5
 import { translate } from '../../../base/i18n';
6
 import { translate } from '../../../base/i18n';
6
 import { IconMuteEveryoneElse } from '../../../base/icons';
7
 import { IconMuteEveryoneElse } from '../../../base/icons';
7
 import { connect } from '../../../base/redux';
8
 import { connect } from '../../../base/redux';
9
     type Props
10
     type Props
10
 } from '../AbstractMuteEveryoneElseButton';
11
 } from '../AbstractMuteEveryoneElseButton';
11
 
12
 
12
-import VideoMenuButton from './VideoMenuButton';
13
-
14
 /**
13
 /**
15
  * Implements a React {@link Component} which displays a button for audio muting
14
  * Implements a React {@link Component} which displays a button for audio muting
16
  * every participant in the conference except the one with the given
15
  * every participant in the conference except the one with the given
35
      * @returns {ReactElement}
34
      * @returns {ReactElement}
36
      */
35
      */
37
     render() {
36
     render() {
38
-        const { participantID, t } = this.props;
37
+        const { t } = this.props;
39
 
38
 
40
         return (
39
         return (
41
-            <VideoMenuButton
42
-                buttonText = { t('videothumbnail.domuteOthers') }
43
-                displayClass = { 'mutelink' }
40
+            <ContextMenuItem
41
+                accessibilityLabel = { t('toolbar.accessibilityLabel.muteEveryoneElse') }
44
                 icon = { IconMuteEveryoneElse }
42
                 icon = { IconMuteEveryoneElse }
45
-                id = { `mutelink_${participantID}` }
46
                 // eslint-disable-next-line react/jsx-handler-names
43
                 // eslint-disable-next-line react/jsx-handler-names
47
-                onClick = { this._handleClick } />
44
+                onClick = { this._handleClick }
45
+                text = { t('videothumbnail.domuteOthers') } />
48
         );
46
         );
49
     }
47
     }
50
 
48
 

+ 6
- 8
react/features/video-menu/components/web/MuteEveryoneElsesVideoButton.js Parādīt failu

2
 
2
 
3
 import React from 'react';
3
 import React from 'react';
4
 
4
 
5
+import ContextMenuItem from '../../../base/components/context-menu/ContextMenuItem';
5
 import { translate } from '../../../base/i18n';
6
 import { translate } from '../../../base/i18n';
6
 import { IconMuteVideoEveryoneElse } from '../../../base/icons';
7
 import { IconMuteVideoEveryoneElse } from '../../../base/icons';
7
 import { connect } from '../../../base/redux';
8
 import { connect } from '../../../base/redux';
9
     type Props
10
     type Props
10
 } from '../AbstractMuteEveryoneElsesVideoButton';
11
 } from '../AbstractMuteEveryoneElsesVideoButton';
11
 
12
 
12
-import VideoMenuButton from './VideoMenuButton';
13
-
14
 /**
13
 /**
15
  * Implements a React {@link Component} which displays a button for audio muting
14
  * Implements a React {@link Component} which displays a button for audio muting
16
  * every participant in the conference except the one with the given
15
  * every participant in the conference except the one with the given
35
      * @returns {ReactElement}
34
      * @returns {ReactElement}
36
      */
35
      */
37
     render() {
36
     render() {
38
-        const { participantID, t } = this.props;
37
+        const { t } = this.props;
39
 
38
 
40
         return (
39
         return (
41
-            <VideoMenuButton
42
-                buttonText = { t('videothumbnail.domuteVideoOfOthers') }
43
-                displayClass = { 'mutelink' }
40
+            <ContextMenuItem
41
+                accessibilityLabel = { t('toolbar.accessibilityLabel.muteEveryoneElsesVideoStream') }
44
                 icon = { IconMuteVideoEveryoneElse }
42
                 icon = { IconMuteVideoEveryoneElse }
45
-                id = { `mutelink_${participantID}` }
46
                 // eslint-disable-next-line react/jsx-handler-names
43
                 // eslint-disable-next-line react/jsx-handler-names
47
-                onClick = { this._handleClick } />
44
+                onClick = { this._handleClick }
45
+                text = { t('videothumbnail.domuteVideoOfOthers') } />
48
         );
46
         );
49
     }
47
     }
50
 
48
 

+ 13
- 17
react/features/video-menu/components/web/MuteVideoButton.js Parādīt failu

2
 
2
 
3
 import React from 'react';
3
 import React from 'react';
4
 
4
 
5
+import ContextMenuItem from '../../../base/components/context-menu/ContextMenuItem';
5
 import { translate } from '../../../base/i18n';
6
 import { translate } from '../../../base/i18n';
6
-import { IconCameraDisabled } from '../../../base/icons';
7
+import { IconVideoOff } from '../../../base/icons';
7
 import { connect } from '../../../base/redux';
8
 import { connect } from '../../../base/redux';
8
 import AbstractMuteVideoButton, {
9
 import AbstractMuteVideoButton, {
9
     _mapStateToProps,
10
     _mapStateToProps,
10
     type Props
11
     type Props
11
 } from '../AbstractMuteVideoButton';
12
 } from '../AbstractMuteVideoButton';
12
 
13
 
13
-import VideoMenuButton from './VideoMenuButton';
14
-
15
 /**
14
 /**
16
  * Implements a React {@link Component} which displays a button for disabling
15
  * Implements a React {@link Component} which displays a button for disabling
17
  * the camera of a participant in the conference.
16
  * the camera of a participant in the conference.
41
      * @returns {ReactElement}
40
      * @returns {ReactElement}
42
      */
41
      */
43
     render() {
42
     render() {
44
-        const { _videoTrackMuted, participantID, t } = this.props;
45
-        const muteConfig = _videoTrackMuted ? {
46
-            translationKey: 'videothumbnail.videoMuted',
47
-            muteClassName: 'mutevideolink disabled'
48
-        } : {
49
-            translationKey: 'videothumbnail.domuteVideo',
50
-            muteClassName: 'mutevideolink'
51
-        };
43
+        const { _videoTrackMuted, t } = this.props;
44
+
45
+        if (_videoTrackMuted) {
46
+            return null;
47
+        }
52
 
48
 
53
         return (
49
         return (
54
-            <VideoMenuButton
55
-                buttonText = { t(muteConfig.translationKey) }
56
-                displayClass = { muteConfig.muteClassName }
57
-                icon = { IconCameraDisabled }
58
-                id = { `mutelink_${participantID}` }
50
+            <ContextMenuItem
51
+                accessibilityLabel = { t('participantsPane.actions.stopVideo') }
52
+                className = 'mutevideolink'
53
+                icon = { IconVideoOff }
59
                 // eslint-disable-next-line react/jsx-handler-names
54
                 // eslint-disable-next-line react/jsx-handler-names
60
-                onClick = { this._handleClick } />
55
+                onClick = { this._handleClick }
56
+                text = { t('participantsPane.actions.stopVideo') } />
61
         );
57
         );
62
     }
58
     }
63
 
59
 

+ 332
- 0
react/features/video-menu/components/web/ParticipantContextMenu.js Parādīt failu

1
+// @flow
2
+
3
+import { makeStyles } from '@material-ui/styles';
4
+import React, { useCallback } from 'react';
5
+import { useTranslation } from 'react-i18next';
6
+import { useDispatch, useSelector } from 'react-redux';
7
+
8
+import { Avatar } from '../../../base/avatar';
9
+import ContextMenu from '../../../base/components/context-menu/ContextMenu';
10
+import ContextMenuItemGroup from '../../../base/components/context-menu/ContextMenuItemGroup';
11
+import { isIosMobileBrowser, isMobileBrowser } from '../../../base/environment/utils';
12
+import { IconShareVideo } from '../../../base/icons';
13
+import { MEDIA_TYPE } from '../../../base/media';
14
+import { getLocalParticipant, PARTICIPANT_ROLE } from '../../../base/participants';
15
+import { getBreakoutRooms, getCurrentRoomId } from '../../../breakout-rooms/functions';
16
+import { setVolume } from '../../../filmstrip/actions.web';
17
+import { isForceMuted } from '../../../participants-pane/functions';
18
+import { requestRemoteControl, stopController } from '../../../remote-control';
19
+import { stopSharedVideo } from '../../../shared-video/actions.any';
20
+import { showOverflowDrawer } from '../../../toolbox/functions.web';
21
+
22
+import { REMOTE_CONTROL_MENU_STATES } from './RemoteControlButton';
23
+import SendToRoomButton from './SendToRoomButton';
24
+
25
+import {
26
+    AskToUnmuteButton,
27
+    ConnectionStatusButton,
28
+    GrantModeratorButton,
29
+    MuteButton,
30
+    MuteEveryoneElseButton,
31
+    MuteEveryoneElsesVideoButton,
32
+    MuteVideoButton,
33
+    KickButton,
34
+    PrivateMessageMenuButton,
35
+    RemoteControlButton,
36
+    VolumeSlider
37
+} from './';
38
+
39
+type Props = {
40
+
41
+    /**
42
+     * Class name for the context menu.
43
+     */
44
+    className?: string,
45
+
46
+    /**
47
+     * Closes a drawer if open.
48
+     */
49
+    closeDrawer?: Function,
50
+
51
+    /**
52
+     * The participant for which the drawer is open.
53
+     * It contains the displayName & participantID.
54
+     */
55
+    drawerParticipant?: Object,
56
+
57
+    /**
58
+     * Shared video local participant owner.
59
+     */
60
+    localVideoOwner?: boolean,
61
+
62
+    /**
63
+     * Target elements against which positioning calculations are made.
64
+     */
65
+    offsetTarget?: HTMLElement,
66
+
67
+    /**
68
+     * Callback for the mouse entering the component.
69
+     */
70
+    onEnter?: Function,
71
+
72
+    /**
73
+     * Callback for the mouse leaving the component.
74
+     */
75
+    onLeave?: Function,
76
+
77
+    /**
78
+     * Callback for making a selection in the menu.
79
+     */
80
+    onSelect: Function,
81
+
82
+    /**
83
+     * Participant reference.
84
+     */
85
+    participant: Object,
86
+
87
+    /**
88
+     * The current state of the participant's remote control session.
89
+     */
90
+    remoteControlState?: number,
91
+
92
+    /**
93
+     * Whether or not the menu is displayed in the thumbnail remote video menu.
94
+     */
95
+    thumbnailMenu: ?boolean
96
+}
97
+
98
+const useStyles = makeStyles(theme => {
99
+    return {
100
+        text: {
101
+            color: theme.palette.text02,
102
+            padding: '10px 16px',
103
+            height: '40px',
104
+            overflow: 'hidden',
105
+            display: 'flex',
106
+            alignItems: 'center',
107
+            boxSizing: 'border-box'
108
+        }
109
+    };
110
+});
111
+
112
+const ParticipantContextMenu = ({
113
+    className,
114
+    closeDrawer,
115
+    drawerParticipant,
116
+    localVideoOwner,
117
+    offsetTarget,
118
+    onEnter,
119
+    onLeave,
120
+    onSelect,
121
+    participant,
122
+    remoteControlState,
123
+    thumbnailMenu
124
+}: Props) => {
125
+    const dispatch = useDispatch();
126
+    const { t } = useTranslation();
127
+    const styles = useStyles();
128
+
129
+    const localParticipant = useSelector(getLocalParticipant);
130
+    const _isModerator = Boolean(localParticipant?.role === PARTICIPANT_ROLE.MODERATOR);
131
+    const _isAudioForceMuted = useSelector(state =>
132
+        isForceMuted(participant, MEDIA_TYPE.AUDIO, state));
133
+    const _isVideoForceMuted = useSelector(state =>
134
+        isForceMuted(participant, MEDIA_TYPE.VIDEO, state));
135
+    const _overflowDrawer = useSelector(showOverflowDrawer);
136
+    const { remoteVideoMenu = {}, disableRemoteMute, startSilent }
137
+        = useSelector(state => state['features/base/config']);
138
+    const { disableKick, disableGrantModerator } = remoteVideoMenu;
139
+    const { participantsVolume } = useSelector(state => state['features/filmstrip']);
140
+    const _volume = (participant?.local ?? true ? undefined
141
+        : participant?.id ? participantsVolume[participant?.id] : undefined) || 1;
142
+
143
+    const _currentRoomId = useSelector(getCurrentRoomId);
144
+    const _rooms = Object.values(useSelector(getBreakoutRooms));
145
+
146
+    const _onVolumeChange = useCallback(value => {
147
+        dispatch(setVolume(participant.id, value));
148
+    }, [ setVolume, dispatch ]);
149
+
150
+    const clickHandler = useCallback(() => onSelect(true), [ onSelect ]);
151
+
152
+    const _onStopSharedVideo = useCallback(() => {
153
+        clickHandler();
154
+        dispatch(stopSharedVideo());
155
+    }, [ stopSharedVideo ]);
156
+
157
+    const _getCurrentParticipantId = useCallback(() => {
158
+        const drawer = _overflowDrawer && !thumbnailMenu;
159
+
160
+        return (drawer ? drawerParticipant?.participantID : participant?.id) ?? '';
161
+    }
162
+    , [ thumbnailMenu, _overflowDrawer, drawerParticipant, participant ]);
163
+
164
+    const buttons = [];
165
+    const buttons2 = [];
166
+
167
+    const showVolumeSlider = !startSilent
168
+        && !isIosMobileBrowser()
169
+        && (_overflowDrawer || thumbnailMenu)
170
+        && typeof _volume === 'number'
171
+        && !isNaN(_volume);
172
+
173
+    const fakeParticipantActions = [ {
174
+        accessibilityLabel: t('toolbar.stopSharedVideo'),
175
+        icon: IconShareVideo,
176
+        onClick: _onStopSharedVideo,
177
+        text: t('toolbar.stopSharedVideo')
178
+    } ];
179
+
180
+    if (_isModerator) {
181
+        if (thumbnailMenu || _overflowDrawer) {
182
+            buttons.push(<AskToUnmuteButton
183
+                isAudioForceMuted = { _isAudioForceMuted }
184
+                isVideoForceMuted = { _isVideoForceMuted }
185
+                key = 'ask-unmute'
186
+                participantID = { _getCurrentParticipantId() } />
187
+            );
188
+        }
189
+        if (!disableRemoteMute) {
190
+            buttons.push(
191
+                <MuteButton
192
+                    key = 'mute'
193
+                    participantID = { _getCurrentParticipantId() } />
194
+            );
195
+            buttons.push(
196
+                <MuteEveryoneElseButton
197
+                    key = 'mute-others'
198
+                    participantID = { _getCurrentParticipantId() } />
199
+            );
200
+            buttons.push(
201
+                <MuteVideoButton
202
+                    key = 'mute-video'
203
+                    participantID = { _getCurrentParticipantId() } />
204
+            );
205
+            buttons.push(
206
+                <MuteEveryoneElsesVideoButton
207
+                    key = 'mute-others-video'
208
+                    participantID = { _getCurrentParticipantId() } />
209
+            );
210
+        }
211
+
212
+        if (!disableGrantModerator) {
213
+            buttons2.push(
214
+                <GrantModeratorButton
215
+                    key = 'grant-moderator'
216
+                    participantID = { _getCurrentParticipantId() } />
217
+            );
218
+        }
219
+
220
+        if (!disableKick) {
221
+            buttons2.push(
222
+                <KickButton
223
+                    key = 'kick'
224
+                    participantID = { _getCurrentParticipantId() } />
225
+            );
226
+        }
227
+    }
228
+
229
+    buttons2.push(
230
+        <PrivateMessageMenuButton
231
+            key = 'privateMessage'
232
+            participantID = { _getCurrentParticipantId() } />
233
+    );
234
+
235
+    if (thumbnailMenu && isMobileBrowser()) {
236
+        buttons2.push(
237
+            <ConnectionStatusButton
238
+                key = 'conn-status'
239
+                participantId = { _getCurrentParticipantId() } />
240
+        );
241
+    }
242
+
243
+    if (thumbnailMenu && remoteControlState) {
244
+        let onRemoteControlToggle = null;
245
+
246
+        if (remoteControlState === REMOTE_CONTROL_MENU_STATES.STARTED) {
247
+            onRemoteControlToggle = () => dispatch(stopController(true));
248
+        } else if (remoteControlState === REMOTE_CONTROL_MENU_STATES.NOT_STARTED) {
249
+            onRemoteControlToggle = () => dispatch(requestRemoteControl(_getCurrentParticipantId()));
250
+        }
251
+
252
+        buttons2.push(
253
+            <RemoteControlButton
254
+                key = 'remote-control'
255
+                onClick = { onRemoteControlToggle }
256
+                participantID = { _getCurrentParticipantId() }
257
+                remoteControlState = { remoteControlState } />
258
+        );
259
+    }
260
+
261
+    const breakoutRoomsButtons = [];
262
+
263
+    if (!thumbnailMenu && _isModerator) {
264
+        _rooms.forEach((room: Object) => {
265
+            if (room.id !== _currentRoomId) {
266
+                breakoutRoomsButtons.push(
267
+                    <SendToRoomButton
268
+                        key = { room.id }
269
+                        onClick = { clickHandler }
270
+                        participantID = { _getCurrentParticipantId() }
271
+                        room = { room } />
272
+                );
273
+            }
274
+        });
275
+    }
276
+
277
+    return (
278
+        <ContextMenu
279
+            className = { className }
280
+            entity = { participant }
281
+            hidden = { thumbnailMenu ? false : undefined }
282
+            inDrawer = { thumbnailMenu && _overflowDrawer }
283
+            isDrawerOpen = { drawerParticipant }
284
+            offsetTarget = { offsetTarget }
285
+            onClick = { onSelect }
286
+            onDrawerClose = { thumbnailMenu ? onSelect : closeDrawer }
287
+            onMouseEnter = { onEnter }
288
+            onMouseLeave = { onLeave }>
289
+            {!thumbnailMenu && _overflowDrawer && drawerParticipant && <ContextMenuItemGroup
290
+                actions = { [ {
291
+                    accessibilityLabel: drawerParticipant.displayName,
292
+                    customIcon: <Avatar
293
+                        participantId = { drawerParticipant.participantID }
294
+                        size = { 20 } />,
295
+                    text: drawerParticipant.displayName
296
+                } ] } />}
297
+            {participant?.isFakeParticipant ? localVideoOwner && (
298
+                <ContextMenuItemGroup
299
+                    actions = { fakeParticipantActions } />
300
+            ) : (
301
+                <>
302
+                    {buttons.length > 0 && (
303
+                        <ContextMenuItemGroup>
304
+                            {buttons}
305
+                        </ContextMenuItemGroup>
306
+                    )}
307
+                    <ContextMenuItemGroup>
308
+                        {buttons2}
309
+                    </ContextMenuItemGroup>
310
+                    {showVolumeSlider && (
311
+                        <ContextMenuItemGroup>
312
+                            <VolumeSlider
313
+                                initialValue = { _volume }
314
+                                key = 'volume-slider'
315
+                                onChange = { _onVolumeChange } />
316
+                        </ContextMenuItemGroup>
317
+                    )}
318
+                    {breakoutRoomsButtons.length > 0 && (
319
+                        <ContextMenuItemGroup>
320
+                            <div className = { styles.text }>
321
+                                {t('breakoutRooms.actions.sendToBreakoutRoom')}
322
+                            </div>
323
+                            {breakoutRoomsButtons}
324
+                        </ContextMenuItemGroup>
325
+                    )}
326
+                </>
327
+            )}
328
+        </ContextMenu>
329
+    );
330
+};
331
+
332
+export default ParticipantContextMenu;

+ 6
- 7
react/features/video-menu/components/web/PrivateMessageMenuButton.js Parādīt failu

2
 
2
 
3
 import React, { Component } from 'react';
3
 import React, { Component } from 'react';
4
 
4
 
5
+import ContextMenuItem from '../../../base/components/context-menu/ContextMenuItem';
5
 import { translate } from '../../../base/i18n';
6
 import { translate } from '../../../base/i18n';
6
 import { IconMessage } from '../../../base/icons';
7
 import { IconMessage } from '../../../base/icons';
7
 import { connect } from '../../../base/redux';
8
 import { connect } from '../../../base/redux';
12
 } from '../../../chat/components/web/PrivateMessageButton';
13
 } from '../../../chat/components/web/PrivateMessageButton';
13
 import { isButtonEnabled } from '../../../toolbox/functions.web';
14
 import { isButtonEnabled } from '../../../toolbox/functions.web';
14
 
15
 
15
-import VideoMenuButton from './VideoMenuButton';
16
-
17
 declare var interfaceConfig: Object;
16
 declare var interfaceConfig: Object;
18
 
17
 
19
 type Props = AbstractProps & {
18
 type Props = AbstractProps & {
49
      * @returns {ReactElement}
48
      * @returns {ReactElement}
50
      */
49
      */
51
     render() {
50
     render() {
52
-        const { participantID, t, _hidden } = this.props;
51
+        const { t, _hidden } = this.props;
53
 
52
 
54
         if (_hidden) {
53
         if (_hidden) {
55
             return null;
54
             return null;
56
         }
55
         }
57
 
56
 
58
         return (
57
         return (
59
-            <VideoMenuButton
60
-                buttonText = { t('toolbar.privateMessage') }
58
+            <ContextMenuItem
59
+                accessibilityLabel = { t('toolbar.accessibilityLabel.privateMessage') }
61
                 icon = { IconMessage }
60
                 icon = { IconMessage }
62
-                id = { `privmsglink_${participantID}` }
63
-                onClick = { this._onClick } />
61
+                onClick = { this._onClick }
62
+                text = { t('toolbar.privateMessage') } />
64
         );
63
         );
65
     }
64
     }
66
 
65
 

+ 9
- 10
react/features/video-menu/components/web/RemoteControlButton.js Parādīt failu

6
     createRemoteVideoMenuButtonEvent,
6
     createRemoteVideoMenuButtonEvent,
7
     sendAnalytics
7
     sendAnalytics
8
 } from '../../../analytics';
8
 } from '../../../analytics';
9
+import ContextMenuItem from '../../../base/components/context-menu/ContextMenuItem';
9
 import { translate } from '../../../base/i18n';
10
 import { translate } from '../../../base/i18n';
10
 import { IconRemoteControlStart, IconRemoteControlStop } from '../../../base/icons';
11
 import { IconRemoteControlStart, IconRemoteControlStop } from '../../../base/icons';
11
 
12
 
12
-import VideoMenuButton from './VideoMenuButton';
13
-
14
 // TODO: Move these enums into the store after further reactification of the
13
 // TODO: Move these enums into the store after further reactification of the
15
 // non-react RemoteVideo component.
14
 // non-react RemoteVideo component.
16
 export const REMOTE_CONTROL_MENU_STATES = {
15
 export const REMOTE_CONTROL_MENU_STATES = {
76
      */
75
      */
77
     render() {
76
     render() {
78
         const {
77
         const {
79
-            participantID,
80
             remoteControlState,
78
             remoteControlState,
81
             t
79
             t
82
         } = this.props;
80
         } = this.props;
83
 
81
 
84
-        let className, icon;
82
+        let disabled = false, icon;
85
 
83
 
86
         switch (remoteControlState) {
84
         switch (remoteControlState) {
87
         case REMOTE_CONTROL_MENU_STATES.NOT_STARTED:
85
         case REMOTE_CONTROL_MENU_STATES.NOT_STARTED:
88
             icon = IconRemoteControlStart;
86
             icon = IconRemoteControlStart;
89
             break;
87
             break;
90
         case REMOTE_CONTROL_MENU_STATES.REQUESTING:
88
         case REMOTE_CONTROL_MENU_STATES.REQUESTING:
91
-            className = ' disabled';
89
+            disabled = true;
92
             icon = IconRemoteControlStart;
90
             icon = IconRemoteControlStart;
93
             break;
91
             break;
94
         case REMOTE_CONTROL_MENU_STATES.STARTED:
92
         case REMOTE_CONTROL_MENU_STATES.STARTED:
102
         }
100
         }
103
 
101
 
104
         return (
102
         return (
105
-            <VideoMenuButton
106
-                buttonText = { t('videothumbnail.remoteControl') }
107
-                displayClass = { className }
103
+            <ContextMenuItem
104
+                accessibilityLabel = { t('videothumbnail.remoteControl') }
105
+                className = 'kicklink'
106
+                disabled = { disabled }
108
                 icon = { icon }
107
                 icon = { icon }
109
-                id = { `remoteControl_${participantID}` }
110
-                onClick = { this._onClick } />
108
+                onClick = { this._onClick }
109
+                text = { t('videothumbnail.remoteControl') } />
111
         );
110
         );
112
     }
111
     }
113
 
112
 

+ 58
- 193
react/features/video-menu/components/web/RemoteVideoMenuTriggerButton.js Parādīt failu

1
 // @flow
1
 // @flow
2
 
2
 
3
 /* eslint-disable react/jsx-handler-names */
3
 /* eslint-disable react/jsx-handler-names */
4
+import { withStyles } from '@material-ui/styles';
4
 import React, { Component } from 'react';
5
 import React, { Component } from 'react';
5
 import { batch } from 'react-redux';
6
 import { batch } from 'react-redux';
6
 
7
 
7
 import ConnectionIndicatorContent from
8
 import ConnectionIndicatorContent from
8
     '../../../../features/connection-indicator/components/web/ConnectionIndicatorContent';
9
     '../../../../features/connection-indicator/components/web/ConnectionIndicatorContent';
9
-import { isIosMobileBrowser, isMobileBrowser } from '../../../base/environment/utils';
10
+import { isMobileBrowser } from '../../../base/environment/utils';
10
 import { translate } from '../../../base/i18n';
11
 import { translate } from '../../../base/i18n';
11
-import { Icon, IconMenuThumb } from '../../../base/icons';
12
-import { getLocalParticipant, getParticipantById, PARTICIPANT_ROLE } from '../../../base/participants';
12
+import { Icon, IconHorizontalPoints } from '../../../base/icons';
13
+import { getParticipantById } from '../../../base/participants';
13
 import { Popover } from '../../../base/popover';
14
 import { Popover } from '../../../base/popover';
14
 import { connect } from '../../../base/redux';
15
 import { connect } from '../../../base/redux';
15
 import { setParticipantContextMenuOpen } from '../../../base/responsive-ui/actions';
16
 import { setParticipantContextMenuOpen } from '../../../base/responsive-ui/actions';
16
-import { requestRemoteControl, stopController } from '../../../remote-control';
17
 import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
17
 import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
18
 import { renderConnectionStatus } from '../../actions.web';
18
 import { renderConnectionStatus } from '../../actions.web';
19
 
19
 
20
-import ConnectionStatusButton from './ConnectionStatusButton';
21
-import MuteEveryoneElseButton from './MuteEveryoneElseButton';
22
-import MuteEveryoneElsesVideoButton from './MuteEveryoneElsesVideoButton';
20
+import ParticipantContextMenu from './ParticipantContextMenu';
23
 import { REMOTE_CONTROL_MENU_STATES } from './RemoteControlButton';
21
 import { REMOTE_CONTROL_MENU_STATES } from './RemoteControlButton';
24
 
22
 
25
 
23
 
26
-import {
27
-    GrantModeratorButton,
28
-    MuteButton,
29
-    MuteVideoButton,
30
-    KickButton,
31
-    PrivateMessageMenuButton,
32
-    RemoteControlButton,
33
-    VideoMenu,
34
-    VolumeSlider
35
-} from './';
36
-
37
 declare var $: Object;
24
 declare var $: Object;
38
 
25
 
39
 /**
26
 /**
45
     /**
32
     /**
46
      * Hides popover.
33
      * Hides popover.
47
      */
34
      */
48
-     hidePopover: Function,
35
+    hidePopover: Function,
49
 
36
 
50
     /**
37
     /**
51
      * Whether the popover is visible or not.
38
      * Whether the popover is visible or not.
52
      */
39
      */
53
-     popoverVisible: boolean,
40
+    popoverVisible: boolean,
54
 
41
 
55
     /**
42
     /**
56
      * Shows popover.
43
      * Shows popover.
57
      */
44
      */
58
-     showPopover: Function,
59
-
60
-    /**
61
-     * Whether or not to display the kick button.
62
-     */
63
-    _disableKick: boolean,
64
-
65
-    /**
66
-     * Whether or not to display the remote mute buttons.
67
-     */
68
-    _disableRemoteMute: Boolean,
69
-
70
-    /**
71
-     * Whether or not to display the grant moderator button.
72
-     */
73
-    _disableGrantModerator: Boolean,
74
-
75
-    /**
76
-     * Whether or not the participant is a conference moderator.
77
-     */
78
-    _isModerator: boolean,
45
+    showPopover: Function,
79
 
46
 
80
     /**
47
     /**
81
      * The position relative to the trigger the remote menu should display
48
      * The position relative to the trigger the remote menu should display
90
     _overflowDrawer: boolean,
57
     _overflowDrawer: boolean,
91
 
58
 
92
     /**
59
     /**
93
-     * The current state of the participant's remote control session.
60
+     * Participant reference.
94
      */
61
      */
95
-    _remoteControlState: number,
62
+    _participant: Object,
96
 
63
 
97
     /**
64
     /**
98
-     * The redux dispatch function.
65
+     * The current state of the participant's remote control session.
99
      */
66
      */
100
-    dispatch: Function,
67
+    _remoteControlState: number,
101
 
68
 
102
     /**
69
     /**
103
-     * Gets a ref to the current component instance.
70
+     * Whether or not the button should be visible.
104
      */
71
      */
105
-    getRef: Function,
72
+    buttonVisible: boolean,
106
 
73
 
107
     /**
74
     /**
108
-     * A value between 0 and 1 indicating the volume of the participant's
109
-     * audio element.
75
+     * An object containing the CSS classes.
110
      */
76
      */
111
-    initialVolumeValue: ?number,
77
+    classes: Object,
112
 
78
 
113
     /**
79
     /**
114
-     * Callback to invoke when changing the level of the participant's
115
-     * audio element.
80
+     * The redux dispatch function.
116
      */
81
      */
117
-    onVolumeChange: Function,
82
+    dispatch: Function,
118
 
83
 
119
     /**
84
     /**
120
      * The ID for the participant on which the remote video menu will act.
85
      * The ID for the participant on which the remote video menu will act.
137
     t: Function
102
     t: Function
138
 };
103
 };
139
 
104
 
105
+const styles = theme => {
106
+    return {
107
+        triggerButton: {
108
+            backgroundColor: theme.palette.action01,
109
+            padding: '3px',
110
+            display: 'inline-block',
111
+            borderRadius: '4px'
112
+        },
113
+
114
+        contextMenu: {
115
+            position: 'relative',
116
+            marginTop: 0,
117
+            right: 'auto',
118
+            padding: '0',
119
+            marginRight: '4px',
120
+            marginBottom: '4px'
121
+        }
122
+    };
123
+};
124
+
140
 /**
125
 /**
141
  * React {@code Component} for displaying an icon associated with opening the
126
  * React {@code Component} for displaying an icon associated with opening the
142
  * the {@code VideoMenu}.
127
  * the {@code VideoMenu}.
169
             _overflowDrawer,
154
             _overflowDrawer,
170
             _showConnectionInfo,
155
             _showConnectionInfo,
171
             _participantDisplayName,
156
             _participantDisplayName,
157
+            buttonVisible,
158
+            classes,
172
             participantID,
159
             participantID,
173
             popoverVisible
160
             popoverVisible
174
         } = this.props;
161
         } = this.props;
190
                 onPopoverOpen = { this._onPopoverOpen }
177
                 onPopoverOpen = { this._onPopoverOpen }
191
                 position = { this.props._menuPosition }
178
                 position = { this.props._menuPosition }
192
                 visible = { popoverVisible }>
179
                 visible = { popoverVisible }>
193
-                {!_overflowDrawer && (
194
-                    <span className = 'popover-trigger remote-video-menu-trigger'>
180
+                {!_overflowDrawer && buttonVisible && (
181
+                    <span
182
+                        className = { classes.triggerButton }
183
+                        role = 'button'>
195
                         {!isMobileBrowser() && <Icon
184
                         {!isMobileBrowser() && <Icon
196
                             ariaLabel = { this.props.t('dialog.remoteUserControls', { username }) }
185
                             ariaLabel = { this.props.t('dialog.remoteUserControls', { username }) }
197
-                            role = 'button'
198
-                            size = '1.4em'
199
-                            src = { IconMenuThumb }
186
+                            size = { 18 }
187
+                            src = { IconHorizontalPoints }
200
                             tabIndex = { 0 }
188
                             tabIndex = { 0 }
201
                             title = { this.props.t('dialog.remoteUserControls', { username }) } />
189
                             title = { this.props.t('dialog.remoteUserControls', { username }) } />
202
                         }
190
                         }
245
      * @returns {ReactElement}
233
      * @returns {ReactElement}
246
      */
234
      */
247
     _renderRemoteVideoMenu() {
235
     _renderRemoteVideoMenu() {
248
-        const {
249
-            _disableKick,
250
-            _disableRemoteMute,
251
-            _disableGrantModerator,
252
-            _isModerator,
253
-            dispatch,
254
-            initialVolumeValue,
255
-            onVolumeChange,
256
-            _remoteControlState,
257
-            participantID
258
-        } = this.props;
236
+        const { _participant, _remoteControlState, classes } = this.props;
259
 
237
 
260
-        const actions = [];
261
-        const buttons = [];
262
-        const showVolumeSlider = !isIosMobileBrowser()
263
-              && onVolumeChange
264
-              && typeof initialVolumeValue === 'number'
265
-              && !isNaN(initialVolumeValue);
266
-
267
-        if (_isModerator) {
268
-            if (!_disableRemoteMute) {
269
-                buttons.push(
270
-                    <MuteButton
271
-                        key = 'mute'
272
-                        participantID = { participantID } />
273
-                );
274
-                buttons.push(
275
-                    <MuteEveryoneElseButton
276
-                        key = 'mute-others'
277
-                        participantID = { participantID } />
278
-                );
279
-                buttons.push(
280
-                    <MuteVideoButton
281
-                        key = 'mute-video'
282
-                        participantID = { participantID } />
283
-                );
284
-                buttons.push(
285
-                    <MuteEveryoneElsesVideoButton
286
-                        key = 'mute-others-video'
287
-                        participantID = { participantID } />
288
-                );
289
-            }
290
-
291
-            if (!_disableGrantModerator) {
292
-                buttons.push(
293
-                    <GrantModeratorButton
294
-                        key = 'grant-moderator'
295
-                        participantID = { participantID } />
296
-                );
297
-            }
298
-
299
-            if (!_disableKick) {
300
-                buttons.push(
301
-                    <KickButton
302
-                        key = 'kick'
303
-                        participantID = { participantID } />
304
-                );
305
-            }
306
-        }
307
-
308
-        if (_remoteControlState) {
309
-            let onRemoteControlToggle = null;
310
-
311
-            if (_remoteControlState === REMOTE_CONTROL_MENU_STATES.STARTED) {
312
-                onRemoteControlToggle = () => dispatch(stopController(true));
313
-            } else if (_remoteControlState === REMOTE_CONTROL_MENU_STATES.NOT_STARTED) {
314
-                onRemoteControlToggle = () => dispatch(requestRemoteControl(participantID));
315
-            }
316
-
317
-            buttons.push(
318
-                <RemoteControlButton
319
-                    key = 'remote-control'
320
-                    onClick = { onRemoteControlToggle }
321
-                    participantID = { participantID }
322
-                    remoteControlState = { _remoteControlState } />
323
-            );
324
-        }
325
-
326
-        buttons.push(
327
-            <PrivateMessageMenuButton
328
-                key = 'privateMessage'
329
-                participantID = { participantID } />
238
+        return (
239
+            <ParticipantContextMenu
240
+                className = { classes.contextMenu }
241
+                onSelect = { this._onPopoverClose }
242
+                participant = { _participant }
243
+                remoteControlState = { _remoteControlState }
244
+                thumbnailMenu = { true } />
330
         );
245
         );
331
-
332
-        if (isMobileBrowser()) {
333
-            actions.push(
334
-                <ConnectionStatusButton
335
-                    key = 'conn-status'
336
-                    participantId = { participantID } />
337
-            );
338
-        }
339
-
340
-        if (showVolumeSlider) {
341
-            actions.push(
342
-                <VolumeSlider
343
-                    initialValue = { initialVolumeValue }
344
-                    key = 'volume-slider'
345
-                    onChange = { onVolumeChange } />
346
-            );
347
-        }
348
-
349
-        if (buttons.length > 0 || actions.length > 0) {
350
-            return (
351
-                <VideoMenu id = { participantID }>
352
-                    <>
353
-                        { buttons.length > 0
354
-                          && <li onClick = { this.props.hidePopover }>
355
-                              <ul className = 'popupmenu__list'>
356
-                                  { buttons }
357
-                              </ul>
358
-                          </li>
359
-                        }
360
-                    </>
361
-                    <>
362
-                        { actions.length > 0
363
-                         && <li>
364
-                             <ul className = 'popupmenu__list'>
365
-                                 {actions}
366
-                             </ul>
367
-                         </li>
368
-                        }
369
-                    </>
370
-                </VideoMenu>
371
-            );
372
-        }
373
-
374
-        return null;
375
     }
246
     }
376
 }
247
 }
377
 
248
 
385
  */
256
  */
386
 function _mapStateToProps(state, ownProps) {
257
 function _mapStateToProps(state, ownProps) {
387
     const { participantID } = ownProps;
258
     const { participantID } = ownProps;
388
-    const localParticipant = getLocalParticipant(state);
389
-    const { remoteVideoMenu = {}, disableRemoteMute } = state['features/base/config'];
390
-    const { disableKick, disableGrantModerator } = remoteVideoMenu;
391
     let _remoteControlState = null;
259
     let _remoteControlState = null;
392
     const participant = getParticipantById(state, participantID);
260
     const participant = getParticipantById(state, participantID);
393
     const _participantDisplayName = participant?.name;
261
     const _participantDisplayName = participant?.name;
428
     }
296
     }
429
 
297
 
430
     return {
298
     return {
431
-        _isModerator: Boolean(localParticipant?.role === PARTICIPANT_ROLE.MODERATOR),
432
-        _disableKick: Boolean(disableKick),
433
-        _disableRemoteMute: Boolean(disableRemoteMute),
434
-        _remoteControlState,
435
         _menuPosition,
299
         _menuPosition,
436
         _overflowDrawer: overflowDrawer,
300
         _overflowDrawer: overflowDrawer,
301
+        _participant: participant,
437
         _participantDisplayName,
302
         _participantDisplayName,
438
-        _disableGrantModerator: Boolean(disableGrantModerator),
303
+        _remoteControlState,
439
         _showConnectionInfo: showConnectionInfo
304
         _showConnectionInfo: showConnectionInfo
440
     };
305
     };
441
 }
306
 }
442
 
307
 
443
-export default translate(connect(_mapStateToProps)(RemoteVideoMenuTriggerButton));
444
-/* eslint-enable react/jsx-handler-names */
308
+export default translate(connect(_mapStateToProps)(
309
+    withStyles(styles)(RemoteVideoMenuTriggerButton)));

+ 50
- 0
react/features/video-menu/components/web/SendToRoomButton.js Parādīt failu

1
+// @flow
2
+
3
+import React, { useCallback } from 'react';
4
+import { useTranslation } from 'react-i18next';
5
+import { useDispatch } from 'react-redux';
6
+
7
+import { createBreakoutRoomsEvent, sendAnalytics } from '../../../analytics';
8
+import ContextMenuItem from '../../../base/components/context-menu/ContextMenuItem';
9
+import { IconRingGroup } from '../../../base/icons';
10
+import { sendParticipantToRoom } from '../../../breakout-rooms/actions';
11
+
12
+type Props = {
13
+
14
+    /**
15
+     * Click handler.
16
+     */
17
+    onClick: ?Function,
18
+
19
+    /**
20
+     * The ID for the participant on which the button will act.
21
+     */
22
+    participantID: string,
23
+
24
+    /**
25
+     * The room to send the participant to.
26
+     */
27
+    room: Object
28
+}
29
+
30
+const SendToRoomButton = ({ onClick, participantID, room }: Props) => {
31
+    const dispatch = useDispatch();
32
+    const { t } = useTranslation();
33
+    const _onClick = useCallback(() => {
34
+        onClick && onClick();
35
+        sendAnalytics(createBreakoutRoomsEvent('send.participant.to.room'));
36
+        dispatch(sendParticipantToRoom(participantID, room.id));
37
+    }, [ participantID, room ]);
38
+
39
+    const roomName = room.name || t('breakoutRooms.mainRoom');
40
+
41
+    return (
42
+        <ContextMenuItem
43
+            accessibilityLabel = { roomName }
44
+            icon = { IconRingGroup }
45
+            onClick = { _onClick }
46
+            text = { roomName } />
47
+    );
48
+};
49
+
50
+export default SendToRoomButton;

+ 0
- 51
react/features/video-menu/components/web/VideoMenu.js Parādīt failu

1
-// @flow
2
-
3
-import React from 'react';
4
-
5
-/**
6
- * The type of the React {@code Component} props of {@link VideoMenu}.
7
- */
8
-type Props = {
9
-
10
-    /**
11
-     * The components to place as the body of the {@code VideoMenu}.
12
-     */
13
-    children: React$Node,
14
-
15
-    /**
16
-     * The id attribute to be added to the component's DOM for retrieval when
17
-     * querying the DOM. Not used directly by the component.
18
-     */
19
-    id: string
20
-};
21
-
22
-/**
23
- * Click handler.
24
- *
25
- * @param {SyntheticEvent} event - The click event.
26
- * @returns {void}
27
- */
28
-function onClick(event) {
29
-    // If the event is propagated to the thumbnail container the participant will be pinned. That's why the propagation
30
-    // needs to be stopped.
31
-    event.stopPropagation();
32
-}
33
-
34
-/**
35
- * React {@code Component} responsible for displaying other components as a menu
36
- * for manipulating participant state.
37
- *
38
- * @param {Props} props - The component's props.
39
- * @returns {Component}
40
- */
41
-export default function VideoMenu(props: Props) {
42
-    return (
43
-        <ul
44
-            className = 'popupmenu'
45
-            id = { props.id }
46
-            onClick = { onClick }>
47
-            { props.children }
48
-        </ul>
49
-    );
50
-}
51
-

+ 0
- 111
react/features/video-menu/components/web/VideoMenuButton.js Parādīt failu

1
-/* @flow */
2
-
3
-import React, { Component } from 'react';
4
-
5
-import { Icon } from '../../../base/icons';
6
-
7
-/**
8
- * The type of the React {@code Component} props of
9
- * {@link VideoMenuButton}.
10
- */
11
-type Props = {
12
-
13
-    /**
14
-     * Text to display within the component that describes the onClick action.
15
-     */
16
-    buttonText: string,
17
-
18
-    /**
19
-     * Additional CSS classes to add to the component.
20
-     */
21
-    displayClass?: string,
22
-
23
-    /**
24
-     * The icon that will display within the component.
25
-     */
26
-    icon?: Object,
27
-
28
-    /**
29
-     * The id attribute to be added to the component's DOM for retrieval when
30
-     * querying the DOM. Not used directly by the component.
31
-     */
32
-    id: string,
33
-
34
-    /**
35
-     * Callback to invoke when the component is clicked.
36
-     */
37
-    onClick: Function,
38
-};
39
-
40
-/**
41
- * React {@code Component} for displaying an action in {@code VideoMenuButton}.
42
- *
43
- * @augments {Component}
44
- */
45
-export default class VideoMenuButton extends Component<Props> {
46
-    /**
47
-     * Initializes a new {@code RemoteVideoMenuButton} instance.
48
-     *
49
-     * @param {*} props - The read-only properties with which the new instance
50
-     * is to be initialized.
51
-     */
52
-    constructor(props: Props) {
53
-        super(props);
54
-
55
-        // Bind event handler so it is only bound once for every instance.
56
-        this._onKeyPress = this._onKeyPress.bind(this);
57
-    }
58
-
59
-    _onKeyPress: (Object) => void;
60
-
61
-    /**
62
-     * KeyPress handler for accessibility.
63
-     *
64
-     * @param {Object} e - The synthetic event.
65
-     * @returns {void}
66
-     */
67
-    _onKeyPress(e) {
68
-        if (this.props.onClick && (e.key === ' ' || e.key === 'Enter')) {
69
-            e.preventDefault();
70
-            this.props.onClick();
71
-        }
72
-    }
73
-
74
-    /**
75
-     * Implements React's {@link Component#render()}.
76
-     *
77
-     * @inheritdoc
78
-     * @returns {ReactElement}
79
-     */
80
-    render() {
81
-        const {
82
-            buttonText,
83
-            displayClass,
84
-            icon,
85
-            id,
86
-            onClick
87
-        } = this.props;
88
-
89
-        const linkClassName = `popupmenu__link ${displayClass || ''}`;
90
-
91
-        return (
92
-            <li className = 'popupmenu__item'>
93
-                <a
94
-                    aria-label = { buttonText ? buttonText : 'some thing' }
95
-                    className = { linkClassName }
96
-                    id = { id }
97
-                    onClick = { onClick }
98
-                    onKeyPress = { this._onKeyPress }
99
-                    role = 'button'
100
-                    tabIndex = { 0 }>
101
-                    <span className = 'popupmenu__icon'>
102
-                        { icon && <Icon src = { icon } /> }
103
-                    </span>
104
-                    <span className = 'popupmenu__text'>
105
-                        { buttonText }
106
-                    </span>
107
-                </a>
108
-            </li>
109
-        );
110
-    }
111
-}

+ 78
- 21
react/features/video-menu/components/web/VolumeSlider.js Parādīt failu

1
 /* @flow */
1
 /* @flow */
2
 
2
 
3
+import { withStyles } from '@material-ui/styles';
4
+import clsx from 'clsx';
3
 import React, { Component } from 'react';
5
 import React, { Component } from 'react';
4
 
6
 
5
 import { translate } from '../../../base/i18n';
7
 import { translate } from '../../../base/i18n';
11
  */
13
  */
12
 type Props = {
14
 type Props = {
13
 
15
 
16
+    /**
17
+     * An object containing the CSS classes.
18
+     */
19
+    classes: Object,
20
+
14
     /**
21
     /**
15
      * The value of the audio slider should display at when the component first
22
      * The value of the audio slider should display at when the component first
16
      * mounts. Changes will be stored in state. The value should be a number
23
      * mounts. Changes will be stored in state. The value should be a number
41
     volumeLevel: number
48
     volumeLevel: number
42
 };
49
 };
43
 
50
 
51
+const styles = theme => {
52
+    return {
53
+        container: {
54
+            minHeight: '40px',
55
+            width: '100%',
56
+            boxSizing: 'border-box',
57
+            cursor: 'pointer',
58
+            display: 'flex',
59
+            alignItems: 'center',
60
+            padding: '0 5px',
61
+
62
+            '&:hover': {
63
+                backgroundColor: theme.palette.ui04
64
+            }
65
+        },
66
+
67
+        icon: {
68
+            minWidth: '20px',
69
+            padding: '5px',
70
+            position: 'relative'
71
+        },
72
+
73
+        sliderContainer: {
74
+            position: 'relative',
75
+            width: '100%',
76
+            paddingRight: '5px'
77
+        },
78
+
79
+        slider: {
80
+            position: 'absolute',
81
+            width: '100%',
82
+            top: '50%',
83
+            transform: 'translate(0, -50%)'
84
+        }
85
+    };
86
+};
87
+
44
 /**
88
 /**
45
  * Implements a React {@link Component} which displays an input slider for
89
  * Implements a React {@link Component} which displays an input slider for
46
  * adjusting the local volume of a remote participant.
90
  * adjusting the local volume of a remote participant.
65
         this._onVolumeChange = this._onVolumeChange.bind(this);
109
         this._onVolumeChange = this._onVolumeChange.bind(this);
66
     }
110
     }
67
 
111
 
112
+    /**
113
+     * Click handler.
114
+     *
115
+     * @param {MouseEvent} e - Click event.
116
+     * @returns {void}
117
+     */
118
+    _onClick(e) {
119
+        e.stopPropagation();
120
+    }
121
+
68
     /**
122
     /**
69
      * Implements React's {@link Component#render()}.
123
      * Implements React's {@link Component#render()}.
70
      *
124
      *
72
      * @returns {ReactElement}
126
      * @returns {ReactElement}
73
      */
127
      */
74
     render() {
128
     render() {
129
+        const { classes } = this.props;
130
+
75
         return (
131
         return (
76
-            <li
132
+            <div
77
                 aria-label = { this.props.t('volumeSlider') }
133
                 aria-label = { this.props.t('volumeSlider') }
78
-                className = 'popupmenu__item'>
79
-                <div className = 'popupmenu__contents'>
80
-                    <span className = 'popupmenu__icon'>
81
-                        <Icon src = { IconVolume } />
82
-                    </span>
83
-                    <div className = 'popupmenu__slider_container'>
84
-                        <input
85
-                            aria-valuemax = { VOLUME_SLIDER_SCALE }
86
-                            aria-valuemin = { 0 }
87
-                            aria-valuenow = { this.state.volumeLevel }
88
-                            className = 'popupmenu__slider'
89
-                            max = { VOLUME_SLIDER_SCALE }
90
-                            min = { 0 }
91
-                            onChange = { this._onVolumeChange }
92
-                            tabIndex = { 0 }
93
-                            type = 'range'
94
-                            value = { this.state.volumeLevel } />
95
-                    </div>
134
+                className = { clsx('popupmenu__contents', classes.container) }
135
+                onClick = { this._onClick }>
136
+                <span className = { classes.icon }>
137
+                    <Icon
138
+                        size = { 22 }
139
+                        src = { IconVolume } />
140
+                </span>
141
+                <div className = { classes.sliderContainer }>
142
+                    <input
143
+                        aria-valuemax = { VOLUME_SLIDER_SCALE }
144
+                        aria-valuemin = { 0 }
145
+                        aria-valuenow = { this.state.volumeLevel }
146
+                        className = { clsx('popupmenu__volume-slider', classes.slider) }
147
+                        max = { VOLUME_SLIDER_SCALE }
148
+                        min = { 0 }
149
+                        onChange = { this._onVolumeChange }
150
+                        tabIndex = { 0 }
151
+                        type = 'range'
152
+                        value = { this.state.volumeLevel } />
96
                 </div>
153
                 </div>
97
-            </li>
154
+            </div>
98
         );
155
         );
99
     }
156
     }
100
 
157
 
116
     }
173
     }
117
 }
174
 }
118
 
175
 
119
-export default translate(VolumeSlider);
176
+export default translate(withStyles(styles)(VolumeSlider));

+ 1
- 1
react/features/video-menu/components/web/index.js Parādīt failu

1
 // @flow
1
 // @flow
2
 
2
 
3
+export { default as AskToUnmuteButton } from './AskToUnmuteButton';
3
 export { default as ConnectionStatusButton } from './ConnectionStatusButton';
4
 export { default as ConnectionStatusButton } from './ConnectionStatusButton';
4
 export { default as GrantModeratorButton } from './GrantModeratorButton';
5
 export { default as GrantModeratorButton } from './GrantModeratorButton';
5
 export { default as GrantModeratorDialog } from './GrantModeratorDialog';
6
 export { default as GrantModeratorDialog } from './GrantModeratorDialog';
14
 export { default as MuteRemoteParticipantsVideoDialog } from './MuteRemoteParticipantsVideoDialog';
15
 export { default as MuteRemoteParticipantsVideoDialog } from './MuteRemoteParticipantsVideoDialog';
15
 export { default as PrivateMessageMenuButton } from './PrivateMessageMenuButton';
16
 export { default as PrivateMessageMenuButton } from './PrivateMessageMenuButton';
16
 export { REMOTE_CONTROL_MENU_STATES, default as RemoteControlButton } from './RemoteControlButton';
17
 export { REMOTE_CONTROL_MENU_STATES, default as RemoteControlButton } from './RemoteControlButton';
17
-export { default as VideoMenu } from './VideoMenu';
18
 export { default as RemoteVideoMenuTriggerButton } from './RemoteVideoMenuTriggerButton';
18
 export { default as RemoteVideoMenuTriggerButton } from './RemoteVideoMenuTriggerButton';
19
 export { default as LocalVideoMenuTriggerButton } from './LocalVideoMenuTriggerButton';
19
 export { default as LocalVideoMenuTriggerButton } from './LocalVideoMenuTriggerButton';
20
 export { default as VolumeSlider } from './VolumeSlider';
20
 export { default as VolumeSlider } from './VolumeSlider';

Notiek ielāde…
Atcelt
Saglabāt