Selaa lähdekoodia

feat(new-toolbars): initial implementation

efficient_tiling
Leonard Kim 7 vuotta sitten
vanhempi
commit
d93782af8a
100 muutettua tiedostoa jossa 4684 lisäystä ja 772 poistoa
  1. 11
    49
      conference.js
  2. 14
    0
      css/_filmstrip.scss
  3. 3
    0
      css/_font.scss
  4. 31
    0
      css/_side_toolbar_container.scss
  5. 211
    0
      css/_toolbars.scss
  6. 2
    0
      css/_variables.scss
  7. 15
    8
      css/_vertical_filmstrip_overrides.scss
  8. 8
    2
      css/modals/video-quality/_video-quality.scss
  9. BIN
      fonts/jitsi.eot
  10. 1
    0
      fonts/jitsi.svg
  11. BIN
      fonts/jitsi.ttf
  12. BIN
      fonts/jitsi.woff
  13. 247
    220
      fonts/selection.json
  14. 16
    2
      interface_config.js
  15. 15
    2
      lang/main.json
  16. 22
    20
      modules/UI/UI.js
  17. 8
    2
      modules/UI/etherpad/Etherpad.js
  18. 9
    0
      modules/UI/recording/Recording.js
  19. 7
    2
      modules/UI/shared_video/SharedVideo.js
  20. 4
    1
      modules/UI/side_pannels/SideContainerToggler.js
  21. 29
    5
      modules/UI/side_pannels/chat/Chat.js
  22. 2
    35
      modules/UI/util/UIUtil.js
  23. 12
    3
      modules/keyboardshortcut/keyboardshortcut.js
  24. 12
    0
      react/features/base/conference/actionTypes.js
  25. 17
    0
      react/features/base/conference/actions.js
  26. 19
    0
      react/features/base/conference/reducer.js
  27. 5
    3
      react/features/base/connection/actions.web.js
  28. 9
    1
      react/features/base/dialog/components/StatelessDialog.web.js
  29. 247
    220
      react/features/base/font-icons/jitsi.json
  30. 59
    6
      react/features/base/participants/middleware.js
  31. 10
    0
      react/features/base/tracks/actionTypes.js
  32. 15
    0
      react/features/base/tracks/actions.js
  33. 14
    2
      react/features/base/tracks/middleware.js
  34. 23
    0
      react/features/chat/actionTypes.js
  35. 63
    0
      react/features/chat/actions.js
  36. 0
    0
      react/features/chat/components/ChatCounter.native.js
  37. 59
    0
      react/features/chat/components/ChatCounter.web.js
  38. 1
    0
      react/features/chat/components/index.js
  39. 20
    0
      react/features/chat/functions.js
  40. 4
    0
      react/features/chat/index.js
  41. 44
    0
      react/features/chat/reducer.js
  42. 84
    5
      react/features/conference/components/Conference.web.js
  43. 29
    0
      react/features/etherpad/actionTypes.js
  44. 50
    0
      react/features/etherpad/actions.js
  45. 5
    0
      react/features/etherpad/index.js
  46. 30
    0
      react/features/etherpad/middleware.js
  47. 30
    0
      react/features/etherpad/reducer.js
  48. 26
    7
      react/features/filmstrip/components/Filmstrip.web.js
  49. 100
    27
      react/features/invite/components/InfoDialogButton.web.js
  50. 17
    1
      react/features/invite/components/info-dialog/InfoDialog.web.js
  51. 10
    0
      react/features/keyboard-shortcuts/actionTypes.js
  52. 14
    0
      react/features/keyboard-shortcuts/actions.js
  53. 3
    0
      react/features/keyboard-shortcuts/index.js
  54. 26
    0
      react/features/keyboard-shortcuts/middleware.js
  55. 22
    0
      react/features/recording/actionTypes.js
  56. 36
    1
      react/features/recording/actions.js
  57. 12
    0
      react/features/recording/constants.js
  58. 2
    0
      react/features/recording/index.js
  59. 27
    0
      react/features/recording/middleware.js
  60. 11
    1
      react/features/recording/reducer.js
  61. 20
    0
      react/features/shared-video/actionTypes.js
  62. 31
    0
      react/features/shared-video/actions.js
  63. 5
    0
      react/features/shared-video/index.js
  64. 31
    0
      react/features/shared-video/middleware.js
  65. 19
    0
      react/features/shared-video/reducer.js
  66. 49
    0
      react/features/side-panel/actionTypes.js
  67. 77
    0
      react/features/side-panel/actions.js
  68. 0
    0
      react/features/side-panel/components/SidePanel.native.js
  69. 62
    0
      react/features/side-panel/components/SidePanel.web.js
  70. 1
    0
      react/features/side-panel/components/index.js
  71. 6
    0
      react/features/side-panel/index.js
  72. 45
    0
      react/features/side-panel/middleware.js
  73. 18
    0
      react/features/side-panel/reducer.js
  74. 22
    0
      react/features/toolbox/actionTypes.js
  75. 4
    3
      react/features/toolbox/actions.native.js
  76. 38
    20
      react/features/toolbox/actions.web.js
  77. 106
    0
      react/features/toolbox/components/OverflowMenuButton.web.js
  78. 53
    0
      react/features/toolbox/components/OverflowMenuItem.web.js
  79. 0
    0
      react/features/toolbox/components/OverflowMenuProfileItem.native.js
  80. 119
    0
      react/features/toolbox/components/OverflowMenuProfileItem.web.js
  81. 0
    0
      react/features/toolbox/components/ToolbarButtonV2.native.js
  82. 78
    0
      react/features/toolbox/components/ToolbarButtonV2.web.js
  83. 6
    101
      react/features/toolbox/components/Toolbox.native.js
  84. 2
    23
      react/features/toolbox/components/Toolbox.web.js
  85. 0
    0
      react/features/toolbox/components/ToolboxFilmstrip.native.js
  86. 95
    0
      react/features/toolbox/components/ToolboxFilmstrip.web.js
  87. 0
    0
      react/features/toolbox/components/ToolboxV2.native.js
  88. 1116
    0
      react/features/toolbox/components/ToolboxV2.web.js
  89. 87
    0
      react/features/toolbox/components/buttons/AbstractAudioMuteButton.js
  90. 51
    0
      react/features/toolbox/components/buttons/AbstractHangupButton.js
  91. 88
    0
      react/features/toolbox/components/buttons/AbstractVideoMuteButton.js
  92. 73
    0
      react/features/toolbox/components/buttons/AudioMuteButton.native.js
  93. 166
    0
      react/features/toolbox/components/buttons/AudioMuteButton.web.js
  94. 63
    0
      react/features/toolbox/components/buttons/HangupButton.native.js
  95. 82
    0
      react/features/toolbox/components/buttons/HangupButton.web.js
  96. 82
    0
      react/features/toolbox/components/buttons/VideoMuteButton.native.js
  97. 161
    0
      react/features/toolbox/components/buttons/VideoMuteButton.web.js
  98. 3
    0
      react/features/toolbox/components/buttons/index.js
  99. 3
    0
      react/features/toolbox/components/index.js
  100. 0
    0
      react/features/toolbox/defaultToolbarButtons.web.js

+ 11
- 49
conference.js Näytä tiedosto

@@ -43,7 +43,8 @@ import {
43 43
     lockStateChanged,
44 44
     onStartMutedPolicyChanged,
45 45
     p2pStatusChanged,
46
-    sendLocalParticipant
46
+    sendLocalParticipant,
47
+    setDesktopSharingEnabled
47 48
 } from './react/features/base/conference';
48 49
 import { updateDeviceList } from './react/features/base/devices';
49 50
 import {
@@ -104,6 +105,7 @@ import {
104 105
     mediaPermissionPromptVisibilityChanged,
105 106
     suspendDetected
106 107
 } from './react/features/overlay';
108
+import { setSharedVideoStatus } from './react/features/shared-video';
107 109
 import {
108 110
     isButtonEnabled,
109 111
     showDesktopSharingButton
@@ -505,16 +507,6 @@ export default {
505 507
      */
506 508
     desktopSharingDisabledTooltip: null,
507 509
 
508
-    /*
509
-     * Whether the local "raisedHand" flag is on.
510
-     */
511
-    isHandRaised: false,
512
-
513
-    /*
514
-     * Whether the local participant is the dominant speaker in the conference.
515
-     */
516
-    isDominantSpeaker: false,
517
-
518 510
     /**
519 511
      * The local audio track (if any).
520 512
      * FIXME tracks from redux store should be the single source of truth
@@ -773,6 +765,8 @@ export default {
773 765
                     JitsiMeetConferenceEvents.DESKTOP_SHARING_ENABLED_CHANGED,
774 766
                     this.isDesktopSharingEnabled);
775 767
 
768
+                APP.store.dispatch(
769
+                    setDesktopSharingEnabled(this.isDesktopSharingEnabled));
776 770
                 APP.store.dispatch(showDesktopSharingButton());
777 771
 
778 772
                 this._createRoom(tracks);
@@ -1896,19 +1890,6 @@ export default {
1896 1890
             });
1897 1891
         room.on(JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED, id => {
1898 1892
             APP.store.dispatch(dominantSpeakerChanged(id));
1899
-
1900
-            if (this.isLocalId(id)) {
1901
-                this.isDominantSpeaker = true;
1902
-                this.setRaisedHand(false);
1903
-            } else {
1904
-                this.isDominantSpeaker = false;
1905
-                const participant = room.getParticipantById(id);
1906
-
1907
-                if (participant) {
1908
-                    APP.UI.setRaisedHandStatus(participant, false);
1909
-                }
1910
-            }
1911
-            APP.UI.markDominantSpeaker(id);
1912 1893
         });
1913 1894
 
1914 1895
         if (!interfaceConfig.filmStripOnly) {
@@ -2022,7 +2003,10 @@ export default {
2022 2003
             (participant, name, oldValue, newValue) => {
2023 2004
                 switch (name) {
2024 2005
                 case 'raisedHand':
2025
-                    APP.UI.setRaisedHandStatus(participant, newValue);
2006
+                    APP.store.dispatch(participantUpdated({
2007
+                        id: participant.getId(),
2008
+                        raisedHand: newValue === 'true'
2009
+                    }));
2026 2010
                     break;
2027 2011
                 case 'remoteControlSessionStatus':
2028 2012
                     APP.UI.setRemoteControlActiveStatus(
@@ -2361,6 +2345,8 @@ export default {
2361 2345
                         }
2362 2346
                     });
2363 2347
                 }
2348
+
2349
+                APP.store.dispatch(setSharedVideoStatus(state));
2364 2350
             });
2365 2351
         room.addCommandListener(
2366 2352
             this.commands.defaults.SHARED_VIDEO,
@@ -2623,30 +2609,6 @@ export default {
2623 2609
         APP.API.notifyVideoAvailabilityChanged(available);
2624 2610
     },
2625 2611
 
2626
-    /**
2627
-     * Toggles the local "raised hand" status.
2628
-     */
2629
-    maybeToggleRaisedHand() {
2630
-        this.setRaisedHand(!this.isHandRaised);
2631
-    },
2632
-
2633
-    /**
2634
-     * Sets the local "raised hand" status to a particular value.
2635
-     */
2636
-    setRaisedHand(raisedHand) {
2637
-        if (raisedHand !== this.isHandRaised) {
2638
-            APP.UI.onLocalRaiseHandChanged(raisedHand);
2639
-
2640
-            this.isHandRaised = raisedHand;
2641
-
2642
-            // Advertise the updated status
2643
-            room.setLocalParticipantProperty('raisedHand', raisedHand);
2644
-
2645
-            // Update the view
2646
-            APP.UI.setLocalRaisedHandStatus(raisedHand);
2647
-        }
2648
-    },
2649
-
2650 2612
     /**
2651 2613
      * Disconnect from the conference and optionally request user feedback.
2652 2614
      * @param {boolean} [requestFeedback=false] if user feedback should be

+ 14
- 0
css/_filmstrip.scss Näytä tiedosto

@@ -5,6 +5,20 @@
5 5
     justify-content: flex-start;
6 6
 }
7 7
 
8
+.use-new-toolbox {
9
+    .filmstrip.reduce-height {
10
+        bottom: $newToolbarSize;
11
+    }
12
+
13
+    .filmstrip {
14
+        transition: bottom .3s;
15
+    }
16
+
17
+    .filmstrip__videos.hidden {
18
+        bottom: calc(-196px - #{$newToolbarSize});
19
+    }
20
+}
21
+
8 22
 .filmstrip {
9 23
     position: absolute;
10 24
     bottom: 0;

+ 3
- 0
css/_font.scss Näytä tiedosto

@@ -180,3 +180,6 @@
180 180
 .icon-gsm-bars:before {
181 181
     content: "\e926";
182 182
 }
183
+.icon-open_in_new:before {
184
+  content: "\e89e";
185
+}

+ 31
- 0
css/_side_toolbar_container.scss Näytä tiedosto

@@ -1,6 +1,37 @@
1 1
 /**
2 2
  * Toolbar side panel main container element.
3 3
  */
4
+.use-new-toolbox #sideToolbarContainer {
5
+    background-color: rgba(40, 52, 71, 0.5);
6
+
7
+    /**
8
+     * Make the sidebar flush with the top of the toolbar. Take the size of
9
+     * the toolbar, plus its padding, and subtract from 100%.
10
+     */
11
+    height: calc(100% - #{$newToolbarSize} - 10px);
12
+    left: 0;
13
+
14
+    .side-toolbar-close {
15
+        background: gray;
16
+        border: 3px solid rgba(255, 255, 255, 0.1);
17
+        border-radius: 100%;
18
+        color: white;
19
+        cursor:pointer;
20
+        height: 10px;
21
+        line-height: 10px;
22
+        padding: 4px;
23
+        position: absolute;
24
+        right: 5px;
25
+        text-align: center;
26
+        top: 5px;
27
+        width: 10px;
28
+        z-index: 1;
29
+    }
30
+
31
+    #chatconversation {
32
+        top: 15px;
33
+    }
34
+}
4 35
 #sideToolbarContainer {
5 36
     background-color: $sideToolbarContainerBg;
6 37
     height: 100%;

+ 211
- 0
css/_toolbars.scss Näytä tiedosto

@@ -261,6 +261,217 @@
261 261
     }
262 262
 }
263 263
 
264
+/**
265
+ * TODO: when the old filmstrip has been removed, remove the "new-" prefix.
266
+ */
267
+.new-toolbox {
268
+    background-color: rgba(40, 52, 71, 0.5);
269
+    bottom: calc((#{$newToolbarSize} * 2) * -1);
270
+    box-sizing: border-box;
271
+    display: flex;
272
+    justify-content: space-between;
273
+    padding: 5px 20px;
274
+    position: absolute;
275
+    transition: bottom .3s ease-in;
276
+    width: 100%;
277
+    z-index: $toolbarZ;
278
+
279
+    &.visible {
280
+        bottom: 0;
281
+    }
282
+
283
+    &.no-buttons {
284
+        display: none;
285
+    }
286
+
287
+    .button-group-center,
288
+    .button-group-left,
289
+    .button-group-right {
290
+        display: flex;
291
+        width: 33%;
292
+    }
293
+
294
+    .button-group-center {
295
+        justify-content: center;
296
+    }
297
+
298
+    .button-group-right {
299
+        justify-content: flex-end;
300
+    }
301
+
302
+    /**
303
+     * Overwrite font-awesome styling to match jitsi-icon styling.
304
+     */
305
+    .fa {
306
+        font-size: 1.22em;
307
+    }
308
+
309
+    i {
310
+        border-radius: 5px;
311
+        cursor: pointer;
312
+        display: block;
313
+        height: 100%;
314
+        line-height: inherit;
315
+        width: 100%;
316
+    }
317
+
318
+    i:hover {
319
+        background-color: rgba(40, 52, 71, 0.7);
320
+    }
321
+
322
+    i.toggled {
323
+        background: rgba(40, 52, 71, 1);
324
+    }
325
+
326
+    i.toggled:hover {
327
+        background-color: rgba(40, 52, 71, 1);
328
+    }
329
+
330
+    i.disabled {
331
+        cursor: initial
332
+    }
333
+
334
+    i.disabled:hover {
335
+        background-color: initial;
336
+    }
337
+
338
+    .icon-hangup {
339
+        color: $hangupColor;
340
+    }
341
+
342
+    .overflow-menu {
343
+        font-size: 1.2em;
344
+        list-style-type: none;
345
+        /**
346
+         * Undo atlaskit padding by reducing margins.
347
+         */
348
+        margin: -15px -24px;
349
+        padding: 0;
350
+
351
+        .overflow-menu-item {
352
+            align-items: center;
353
+            cursor: pointer;
354
+            display: flex;
355
+            padding: 5px 10px;
356
+
357
+            &:hover {
358
+                background: rgba(0255, 255, 255, 0.2);
359
+            }
360
+
361
+            &.unclickable {
362
+                cursor: default;
363
+            }
364
+            &.unclickable:hover {
365
+                background: inherit;
366
+            }
367
+        }
368
+
369
+        .overflow-menu-item-icon {
370
+            margin-right: 10px;
371
+
372
+            i {
373
+                display: inline;
374
+            }
375
+
376
+            i:hover {
377
+                background-color: initial;
378
+            }
379
+
380
+            img {
381
+                max-width: 18px;
382
+                max-height: 18px;
383
+            }
384
+        }
385
+
386
+        .profile-text {
387
+            max-width: 150px;
388
+            text-overflow: ellipsis;
389
+            overflow: hidden;
390
+            white-space: nowrap;
391
+        }
392
+    }
393
+
394
+    .toolbox-button {
395
+        color: $toolbarButtonColor;
396
+        cursor: pointer;
397
+        display: inline-block;
398
+        font-size: $newToolbarFontSize;
399
+        line-height: $newToolbarSize;
400
+        margin: 0 10px;
401
+        text-align: center;
402
+    }
403
+
404
+    .toolbar-button-with-badge {
405
+        position: relative;
406
+
407
+        .badge-round {
408
+            bottom: 9px;
409
+            position: absolute;
410
+            right: 9px;
411
+        }
412
+    }
413
+
414
+    .toolbox-button-wth-dialog {
415
+        display: inline-block;
416
+    }
417
+
418
+    .toolbox-icon {
419
+        height: $newToolbarSize;
420
+        width: $newToolbarSize;
421
+    }
422
+}
423
+
424
+.filmstrip-toolbox {
425
+    background-color: rgba(40, 52, 71, 0.5);
426
+    box-sizing: border-box;
427
+    display: flex;
428
+    flex-direction: column;
429
+    z-index: $toolbarZ;
430
+
431
+    i {
432
+        cursor: pointer;
433
+        display: block;
434
+        font-size: $newToolbarFontSize;
435
+        height: 37px;
436
+        line-height: 37px;
437
+        width: 37px;
438
+    }
439
+
440
+    i:hover {
441
+        background-color: rgba(40, 52, 71, 0.7);
442
+    }
443
+
444
+    i.toggled {
445
+        background: rgba(40, 52, 71, 1);
446
+    }
447
+
448
+    i.toggled:hover {
449
+        background-color: rgba(40, 52, 71, 1);
450
+    }
451
+
452
+    .icon-hangup {
453
+        color: $hangupColor;
454
+    }
455
+
456
+    .toolbox-button {
457
+        color: $toolbarButtonColor;
458
+        cursor: pointer;
459
+        text-align: center;
460
+    }
461
+
462
+    border-radius: 3px;
463
+
464
+    .toolbox-button:first-child i {
465
+        border-top-left-radius: 3px;
466
+        border-top-right-radius: 3px;
467
+    }
468
+
469
+    .toolbox-button:last-child i {
470
+        border-bottom-left-radius: 3px;
471
+        border-bottom-right-radius: 3px;
472
+    }
473
+}
474
+
264 475
 .filmstrip-only {
265 476
     .toolbox,
266 477
     .toolbox-toolbars {

+ 2
- 0
css/_variables.scss Näytä tiedosto

@@ -36,6 +36,8 @@ $alwaysOnTopToolbarFontSize: 1em;
36 36
 $alwaysOnTopToolbarSize: 30px;
37 37
 $defaultToolbarSize: 50px;
38 38
 $defaultFilmStripOnlyToolbarSize: 37px;
39
+$newToolbarSize: 50px;
40
+$newToolbarFontSize: 1.9em;
39 41
 $secToolbarFontSize: 1.9em;
40 42
 $secToolbarLineHeight: 45px;
41 43
 $toolbarAvatarPadding: 10px;

+ 15
- 8
css/_vertical_filmstrip_overrides.scss Näytä tiedosto

@@ -19,6 +19,20 @@
19 19
         text-align: left;
20 20
     }
21 21
 
22
+    &.use-new-toolbox {
23
+        /**
24
+         * Adjust the height of the filmstrip as the toolbar is displayed.
25
+         */
26
+        .filmstrip {
27
+            top: 0;
28
+            transition: height .3s ease-in;
29
+
30
+            &.reduce-height {
31
+                height: calc(100% - #{$newToolbarSize});
32
+            }
33
+        }
34
+    }
35
+
22 36
     .filmstrip {
23 37
         align-items: flex-end;
24 38
         box-sizing: border-box;
@@ -32,14 +46,7 @@
32 46
          * any parent is also fixed.
33 47
          */
34 48
         position: fixed;
35
-
36
-        /**
37
-         * z-index adjusting is needed because the video state indicator has to
38
-         * display over the filmstrip when no videos are displayed but still be
39
-         * clickable but its inline dialogs must display over the video state
40
-         * indicator when videos are displayed.
41
-         */
42
-        z-index: #{$tooltipsZ + 1};
49
+        z-index: $filmstripVideosZ;
43 50
 
44 51
         /**
45 52
          * Hide videos by making them slight to the right.

+ 8
- 2
css/modals/video-quality/_video-quality.scss Näytä tiedosto

@@ -135,6 +135,12 @@
135 135
     }
136 136
 }
137 137
 
138
+.modal-dialog-form {
139
+    .video-quality-dialog-title {
140
+        display: none;
141
+    }
142
+}
143
+
138 144
 .video-state-indicator {
139 145
     background: $videoStateIndicatorBackground;
140 146
     cursor: default;
@@ -162,11 +168,11 @@
162 168
 }
163 169
 
164 170
 .centeredVideoLabel.moveToCorner {
165
-    z-index: $tooltipsZ;
171
+    z-index: $zindex3;
166 172
 }
167 173
 
168 174
 #videoResolutionLabel {
169
-    z-index: #{$tooltipsZ + 1};
175
+    z-index: $zindex3 + 1;
170 176
 }
171 177
 
172 178
 .centeredVideoLabel {

BIN
fonts/jitsi.eot Näytä tiedosto


+ 1
- 0
fonts/jitsi.svg Näytä tiedosto

@@ -23,6 +23,7 @@
23 23
 <glyph unicode="&#xe616;" glyph-name="event_note" d="M598 426v-84h-300v84h300zM810 214v468h-596v-468h596zM810 896c46 0 86-40 86-86v-596c0-46-40-86-86-86h-596c-48 0-86 40-86 86v596c0 46 38 86 86 86h42v86h86v-86h340v86h86v-86h42zM726 598v-86h-428v86h428z" />
24 24
 <glyph unicode="&#xe61d;" glyph-name="phone-talk" d="M640 512c0 70-58 128-128 128v86c118 0 214-96 214-214h-86zM810 512c0 166-132 298-298 298v86c212 0 384-172 384-384h-86zM854 362c24 0 42-18 42-42v-150c0-24-18-42-42-42-400 0-726 326-726 726 0 24 18 42 42 42h150c24 0 42-18 42-42 0-54 8-104 24-152 4-14 2-32-10-44l-94-94c62-122 162-220 282-282l94 94c12 12 30 14 44 10 48-16 98-24 152-24z" />
25 25
 <glyph unicode="&#xe80b;" glyph-name="public" d="M764 282c56 60 90 142 90 230 0 142-88 266-214 316v-18c0-46-40-84-86-84h-84v-86c0-24-20-42-44-42h-84v-86h256c24 0 42-18 42-42v-128h42c38 0 70-26 82-60zM470 174v82c-46 0-86 40-86 86v42l-204 204c-6-24-10-50-10-76 0-174 132-318 300-338zM512 938c236 0 426-190 426-426s-190-426-426-426-426 190-426 426 190 426 426 426z" />
26
+<glyph unicode="&#xe89e;" glyph-name="open_in_new" d="M598 896h298v-298h-86v152l-418-418-60 60 418 418h-152v86zM810 214v298h86v-298c0-46-40-86-86-86h-596c-48 0-86 40-86 86v596c0 46 38 86 86 86h298v-86h-298v-596h596z" />
26 27
 <glyph unicode="&#xe8b3;" glyph-name="restore" d="M512 682h64v-180l150-90-32-52-182 110v212zM554 896c212 0 384-172 384-384s-172-384-384-384c-106 0-200 42-270 112l60 62c54-54 128-88 210-88 166 0 300 132 300 298s-134 298-300 298-298-132-298-298h128l-172-172-4 6-166 166h128c0 212 172 384 384 384z" />
27 28
 <glyph unicode="&#xe901;" glyph-name="avatar" d="M512 204c106 0 200 56 256 138-2 84-172 132-256 132-86 0-254-48-256-132 56-82 150-138 256-138zM512 810c-70 0-128-58-128-128s58-128 128-128 128 58 128 128-58 128-128 128zM512 938c236 0 426-190 426-426s-190-426-426-426-426 190-426 426 190 426 426 426z" />
28 29
 <glyph unicode="&#xe902;" glyph-name="download" d="M726 470h-128v170h-172v-170h-128l214-214zM826 596c110-8 198-100 198-212 0-118-96-214-214-214h-554c-142 0-256 114-256 256 0 132 100 240 228 254 54 102 160 174 284 174 156 0 284-110 314-258z" />

BIN
fonts/jitsi.ttf Näytä tiedosto


BIN
fonts/jitsi.woff Näytä tiedosto


+ 247
- 220
fonts/selection.json
File diff suppressed because it is too large
Näytä tiedosto


+ 16
- 2
interface_config.js Näytä tiedosto

@@ -44,7 +44,10 @@ var interfaceConfig = {
44 44
         'microphone', 'camera', 'desktop', 'fullscreen', 'fodeviceselection', 'hangup',
45 45
 
46 46
         // extended toolbar
47
-        'profile', 'contacts', 'info', 'chat', 'recording', 'etherpad', 'sharedvideo', 'settings', 'raisehand', 'videoquality', 'filmstrip' ],
47
+        'profile', 'contacts', 'info', 'chat', 'recording', 'etherpad',
48
+        'sharedvideo', 'settings', 'raisehand', 'videoquality', 'filmstrip',
49
+        'invite', 'feedback', 'stats', 'shortcuts'
50
+    ],
48 51
 
49 52
     /**
50 53
      * Main Toolbar Buttons
@@ -150,7 +153,18 @@ var interfaceConfig = {
150 153
      *
151 154
      * @type {boolean}
152 155
      */
153
-    VIDEO_QUALITY_LABEL_DISABLED: false
156
+    VIDEO_QUALITY_LABEL_DISABLED: false,
157
+
158
+    /**
159
+     * This is a temporary feature flag used to gate access to the toolbox so it
160
+     * can be developed through smaller changesets. This feature flag will be
161
+     * removed at some point, as well as the old toolbox. This new toolbox will
162
+     * be horizontal and support for horizontal filmstrip will be removed,
163
+     * except in the case of interfaceConfig.filmStripOnly being true.
164
+     *
165
+     * @type {boolean}
166
+     */
167
+    _USE_NEW_TOOLBOX: false
154 168
 
155 169
     /**
156 170
      * Specify custom URL for downloading android mobile app.

+ 15
- 2
lang/main.json Näytä tiedosto

@@ -73,14 +73,23 @@
73 73
     "toolbar": {
74 74
         "addPeople": "Add people to your call",
75 75
         "audioonly": "Enable / Disable audio only mode (saves bandwidth)",
76
+        "callQuality": "Manage call quality",
77
+        "enterFullScreen": "View full screen",
78
+        "exitFullScreen": "Exit full screen",
79
+        "feedback": "Leave feedback",
80
+        "moreActions": "More actions",
76 81
         "mute": "Mute / Unmute",
77 82
         "videomute": "Start / Stop camera",
78 83
         "authenticate": "Authenticate",
79 84
         "lock": "Lock / Unlock room",
80 85
         "chat": "Open / Close chat",
81 86
         "etherpad": "Open / Close shared document",
87
+        "documentOpen": "Open shared document",
88
+        "documentClose": "Close shared document",
82 89
         "sharedvideo": "Share a YouTube video",
83
-        "sharescreen": "Start / Stop screen sharing",
90
+        "sharescreen": "Screen share",
91
+        "sharescreenDisabled": "Screen share disabled",
92
+        "stopSharedVideo": "Stop YouTube video",
84 93
         "fullscreen": "View / Exit full screen",
85 94
         "sip": "Call SIP number",
86 95
         "Settings": "Settings",
@@ -96,7 +105,9 @@
96 105
         "micDisabled": "Microphone is not available",
97 106
         "filmstrip": "Show / Hide videos",
98 107
         "profile": "Edit your profile",
99
-        "raiseHand": "Raise / Lower your hand"
108
+        "raiseHand": "Raise / Lower your hand",
109
+        "shortcuts": "View shortcuts",
110
+        "speakerStats": "Speaker stats"
100 111
     },
101 112
     "unsupportedBrowser": {
102 113
         "appNotInstalled": "Join this meeting with __app__ on your phone.",
@@ -285,6 +296,7 @@
285 296
         "liveStreaming": "Live Streaming",
286 297
         "streamKey": "Live stream key",
287 298
         "startLiveStreaming": "Go live now",
299
+        "startRecording": "Start recording",
288 300
         "stopStreamingWarning": "Are you sure you would like to stop the live streaming?",
289 301
         "stopRecordingWarning": "Are you sure you would like to stop the recording?",
290 302
         "stopLiveStreaming": "Stop live streaming",
@@ -473,6 +485,7 @@
473 485
         "loadingPeople": "Searching for people to invite",
474 486
         "noResults": "No matching search results",
475 487
         "noValidNumbers": "Please enter a phone number",
488
+        "notAvailable": "You can't invite people.",
476 489
         "searchNumbers": "Enter a phone number to invite",
477 490
         "searchPeople": "Enter a name to invite",
478 491
         "searchPeopleAndNumbers": "Enter a name or phone number to invite",

+ 22
- 20
modules/UI/UI.js Näytä tiedosto

@@ -30,6 +30,7 @@ import {
30 30
 } from '../../react/features/base/participants';
31 31
 import { destroyLocalTracks } from '../../react/features/base/tracks';
32 32
 import { openDisplayNamePrompt } from '../../react/features/display-name';
33
+import { setEtherpadHasInitialzied } from '../../react/features/etherpad';
33 34
 import {
34 35
     setNotificationsEnabled,
35 36
     showWarningNotification
@@ -100,9 +101,6 @@ const UIListeners = new Map([
100 101
     ], [
101 102
         UIEvents.SHARED_VIDEO_CLICKED,
102 103
         () => sharedVideoManager && sharedVideoManager.toggleSharedVideo()
103
-    ], [
104
-        UIEvents.TOGGLE_FULLSCREEN,
105
-        () => UI.toggleFullScreen()
106 104
     ], [
107 105
         UIEvents.TOGGLE_CHAT,
108 106
         () => UI.toggleChat()
@@ -135,14 +133,6 @@ const UIListeners = new Map([
135 133
     ]
136 134
 ]);
137 135
 
138
-/**
139
- * Toggles the application in and out of full screen mode
140
- * (a.k.a. presentation mode in Chrome).
141
- */
142
-UI.toggleFullScreen = function() {
143
-    UIUtil.isFullScreen() ? UIUtil.exitFullScreen() : UIUtil.enterFullScreen();
144
-};
145
-
146 136
 /**
147 137
  * Indicates if we're currently in full screen mode.
148 138
  *
@@ -255,12 +245,20 @@ UI.showLocalConnectionInterrupted = function(isInterrupted) {
255 245
 
256 246
 /**
257 247
  * Sets the "raised hand" status for a participant.
248
+ *
249
+ * @param {string} id - The id of the participant whose raised hand UI should
250
+ * be updated.
251
+ * @param {string} name - The name of the participant with the raised hand
252
+ * update.
253
+ * @param {boolean} raisedHandStatus - Whether the participant's hand is raised
254
+ * or not.
255
+ * @returns {void}
258 256
  */
259
-UI.setRaisedHandStatus = (participant, raisedHandStatus) => {
260
-    VideoLayout.setRaisedHandStatus(participant.getId(), raisedHandStatus);
257
+UI.setRaisedHandStatus = (id, name, raisedHandStatus) => {
258
+    VideoLayout.setRaisedHandStatus(id, raisedHandStatus);
261 259
     if (raisedHandStatus) {
262 260
         messageHandler.participantNotification(
263
-            participant.getDisplayName(),
261
+            name,
264 262
             'notify.somebody',
265 263
             'connected',
266 264
             'notify.raisedHand');
@@ -374,6 +372,14 @@ UI.start = function() {
374 372
         $('body').addClass('vertical-filmstrip');
375 373
     }
376 374
 
375
+
376
+    // TODO: remove this class once the old toolbar has been removed. This class
377
+    // is set so that any CSS changes needed to adjust elements outside of the
378
+    // new toolbar can be scoped to just the app with the new toolbar enabled.
379
+    if (interfaceConfig._USE_NEW_TOOLBOX && !interfaceConfig.filmStripOnly) {
380
+        $('body').addClass('use-new-toolbox');
381
+    }
382
+
377 383
     document.title = interfaceConfig.APP_NAME;
378 384
 };
379 385
 
@@ -404,12 +410,7 @@ UI.bindEvents = () => {
404 410
     // Resize and reposition videos in full screen mode.
405 411
     $(document).on(
406 412
             'webkitfullscreenchange mozfullscreenchange fullscreenchange',
407
-            () => {
408
-                eventEmitter.emit(
409
-                        UIEvents.FULLSCREEN_TOGGLED,
410
-                        UIUtil.isFullScreen());
411
-                onResize();
412
-            });
413
+            onResize);
413 414
 
414 415
     $(window).resize(onResize);
415 416
 };
@@ -474,6 +475,7 @@ UI.initEtherpad = name => {
474 475
     etherpadManager
475 476
         = new EtherpadManager(config.etherpad_base, name, eventEmitter);
476 477
 
478
+    APP.store.dispatch(setEtherpadHasInitialzied());
477 479
     APP.store.dispatch(showEtherpadButton());
478 480
 };
479 481
 

+ 8
- 2
modules/UI/etherpad/Etherpad.js Näytä tiedosto

@@ -1,4 +1,7 @@
1
-/* global $, interfaceConfig */
1
+/* global $, APP, interfaceConfig */
2
+
3
+import { setDocumentEditingState } from '../../../react/features/etherpad';
4
+import { getToolboxHeight } from '../../../react/features/toolbox';
2 5
 
3 6
 import VideoLayout from '../videolayout/VideoLayout';
4 7
 import LargeContainer from '../videolayout/LargeContainer';
@@ -126,7 +129,8 @@ class Etherpad extends LargeContainer {
126 129
         let height, width;
127 130
 
128 131
         if (interfaceConfig.VERTICAL_FILMSTRIP) {
129
-            height = containerHeight;
132
+            height = interfaceConfig._USE_NEW_TOOLBOX
133
+                ? containerHeight - getToolboxHeight() : containerHeight;
130 134
             width = containerWidth - Filmstrip.getFilmstripWidth();
131 135
         } else {
132 136
             height = containerHeight - Filmstrip.getFilmstripHeight();
@@ -242,5 +246,7 @@ export default class EtherpadManager {
242 246
 
243 247
         this.eventEmitter
244 248
             .emit(UIEvents.TOGGLED_SHARED_DOCUMENT, !isVisible);
249
+
250
+        APP.store.dispatch(setDocumentEditingState(!isVisible));
245 251
     }
246 252
 }

+ 9
- 0
modules/UI/recording/Recording.js Näytä tiedosto

@@ -35,6 +35,7 @@ import {
35 35
     StartLiveStreamDialog,
36 36
     StopLiveStreamDialog,
37 37
     hideRecordingLabel,
38
+    setRecordingType,
38 39
     updateRecordingState
39 40
 } from '../../../react/features/recording';
40 41
 
@@ -202,6 +203,8 @@ const Recording = {
202 203
         this.eventEmitter = eventEmitter;
203 204
         this.recordingType = recordingType;
204 205
 
206
+        APP.store.dispatch(setRecordingType(recordingType));
207
+
205 208
         this.updateRecordingState(APP.conference.getRecordingState());
206 209
 
207 210
         if (recordingType === 'jibri') {
@@ -219,6 +222,9 @@ const Recording = {
219 222
             '#toolbar_button_record',
220 223
             ev => this._onToolbarButtonClick(ev));
221 224
 
225
+        this.eventEmitter.on(UIEvents.TOGGLE_RECORDING,
226
+            () => this._onToolbarButtonClick());
227
+
222 228
         // If I am a recorder then I publish my recorder custom role to notify
223 229
         // everyone.
224 230
         if (config.iAmRecorder) {
@@ -287,6 +293,7 @@ const Recording = {
287 293
         this.currentState = recordingState;
288 294
 
289 295
         let labelDisplayConfiguration;
296
+        let isRecording = false;
290 297
 
291 298
         switch (recordingState) {
292 299
         case JitsiRecordingStatus.ON:
@@ -298,6 +305,7 @@ const Recording = {
298 305
             };
299 306
 
300 307
             this._setToolbarButtonToggled(true);
308
+            isRecording = true;
301 309
 
302 310
             break;
303 311
         }
@@ -362,6 +370,7 @@ const Recording = {
362 370
         }
363 371
 
364 372
         APP.store.dispatch(updateRecordingState({
373
+            isRecording,
365 374
             labelDisplayConfiguration,
366 375
             recordingState
367 376
         }));

+ 7
- 2
modules/UI/shared_video/SharedVideo.js Näytä tiedosto

@@ -18,7 +18,11 @@ import {
18 18
     participantJoined,
19 19
     participantLeft
20 20
 } from '../../../react/features/base/participants';
21
-import { dockToolbox, showToolbox } from '../../../react/features/toolbox';
21
+import {
22
+    dockToolbox,
23
+    getToolboxHeight,
24
+    showToolbox
25
+} from '../../../react/features/toolbox';
22 26
 
23 27
 import SharedVideoThumb from './SharedVideoThumb';
24 28
 
@@ -695,7 +699,8 @@ class SharedVideoContainer extends LargeContainer {
695 699
         let height, width;
696 700
 
697 701
         if (interfaceConfig.VERTICAL_FILMSTRIP) {
698
-            height = containerHeight;
702
+            height = interfaceConfig._USE_NEW_TOOLBOX
703
+                ? containerHeight - getToolboxHeight() : containerHeight;
699 704
             width = containerWidth - Filmstrip.getFilmstripWidth();
700 705
         } else {
701 706
             height = containerHeight - Filmstrip.getFilmstripHeight();

+ 4
- 1
modules/UI/side_pannels/SideContainerToggler.js Näytä tiedosto

@@ -1,5 +1,6 @@
1
-/* global $ */
1
+/* global $, APP */
2 2
 import UIEvents from '../../../service/UI/UIEvents';
3
+import { setVisiblePanel } from '../../../react/features/side-panel';
3 4
 
4 5
 /**
5 6
  * Handles open and close of the extended toolbar side panel
@@ -57,6 +58,7 @@ const SideContainerToggler = {
57 58
 
58 59
         if (isSelectorVisible) {
59 60
             this.hide();
61
+            APP.store.dispatch(setVisiblePanel(null));
60 62
         } else {
61 63
             if (this.isVisible()) {
62 64
                 $('#sideToolbarContainer').children()
@@ -74,6 +76,7 @@ const SideContainerToggler = {
74 76
             }
75 77
 
76 78
             this.showInnerContainer(elementSelector);
79
+            APP.store.dispatch(setVisiblePanel(elementId));
77 80
         }
78 81
     },
79 82
 

+ 29
- 5
modules/UI/side_pannels/chat/Chat.js Näytä tiedosto

@@ -1,4 +1,4 @@
1
-/* global APP, $ */
1
+/* global APP, $, interfaceConfig */
2 2
 
3 3
 import { processReplacements, linkify } from './Replacement';
4 4
 import CommandsProcessor from './Commands';
@@ -9,7 +9,12 @@ import UIEvents from '../../../../service/UI/UIEvents';
9 9
 
10 10
 import { smileys } from './smileys';
11 11
 
12
-import { dockToolbox, setSubject } from '../../../../react/features/toolbox';
12
+import { addMessage, markAllRead } from '../../../../react/features/chat';
13
+import {
14
+    dockToolbox,
15
+    getToolboxHeight,
16
+    setSubject
17
+} from '../../../../react/features/toolbox';
13 18
 
14 19
 let unreadMessages = 0;
15 20
 const sidePanelsContainerId = 'sideToolbarContainer';
@@ -163,6 +168,8 @@ function addSmileys() {
163 168
  * Resizes the chat conversation.
164 169
  */
165 170
 function resizeChatConversation() {
171
+    // FIXME: this function can all be done with CSS. If Chat is ever rewritten,
172
+    // do not copy over this logic.
166 173
     const msgareaHeight = $('#usermsg').outerHeight();
167 174
     const chatspace = $(`#${CHAT_CONTAINER_ID}`);
168 175
     const width = chatspace.width();
@@ -173,7 +180,16 @@ function resizeChatConversation() {
173 180
     $('#smileys').css('bottom', (msgareaHeight - 26) / 2);
174 181
     $('#smileysContainer').css('bottom', msgareaHeight);
175 182
     chat.width(width - 10);
176
-    chat.height(window.innerHeight - 15 - msgareaHeight);
183
+
184
+    if (interfaceConfig._USE_NEW_TOOLBOX) {
185
+        const maybeAMagicNumberForPaddingAndMargin = 100;
186
+        const offset = maybeAMagicNumberForPaddingAndMargin
187
+            + msgareaHeight + getToolboxHeight();
188
+
189
+        chat.height(window.innerHeight - offset);
190
+    } else {
191
+        chat.height(window.innerHeight - 15 - msgareaHeight);
192
+    }
177 193
 }
178 194
 
179 195
 /**
@@ -249,6 +265,7 @@ const Chat = {
249 265
                 }
250 266
 
251 267
                 unreadMessages = 0;
268
+                APP.store.dispatch(markAllRead());
252 269
                 updateVisualNotification();
253 270
 
254 271
                 // Undock the toolbar when the chat is shown and if we're in a
@@ -274,9 +291,10 @@ const Chat = {
274 291
      */
275 292
     // eslint-disable-next-line max-params
276 293
     updateChatConversation(id, displayName, message, stamp) {
294
+        const isFromLocalParticipant = APP.conference.isLocalId(id);
277 295
         let divClassName = '';
278 296
 
279
-        if (APP.conference.isLocalId(id)) {
297
+        if (isFromLocalParticipant) {
280 298
             divClassName = 'localuser';
281 299
         } else {
282 300
             divClassName = 'remoteuser';
@@ -294,6 +312,7 @@ const Chat = {
294 312
             .replace(/>/g, '&gt;')
295 313
 .replace(/\n/g, '<br/>');
296 314
         const escDisplayName = UIUtil.escapeHtml(displayName);
315
+        const timestamp = getCurrentTime(stamp);
297 316
 
298 317
         // eslint-disable-next-line no-param-reassign
299 318
         message = processReplacements(escMessage);
@@ -302,13 +321,18 @@ const Chat = {
302 321
             = `${'<div class="chatmessage">'
303 322
                 + '<img src="images/chatArrow.svg" class="chatArrow">'
304 323
                 + '<div class="username '}${divClassName}">${escDisplayName
305
-            }</div><div class="timestamp">${getCurrentTime(stamp)
324
+            }</div><div class="timestamp">${timestamp
306 325
             }</div><div class="usermessage">${message}</div>`
307 326
             + '</div>';
308 327
 
309 328
         $('#chatconversation').append(messageContainer);
310 329
         $('#chatconversation').animate(
311 330
                 { scrollTop: $('#chatconversation')[0].scrollHeight }, 1000);
331
+
332
+        const markAsRead = Chat.isVisible() || isFromLocalParticipant;
333
+
334
+        APP.store.dispatch(addMessage(
335
+            escDisplayName, message, timestamp, markAsRead));
312 336
     },
313 337
 
314 338
     /**

+ 2
- 35
modules/UI/util/UIUtil.js Näytä tiedosto

@@ -227,43 +227,10 @@ const UIUtil = {
227 227
      * mode, {false} otherwise
228 228
      */
229 229
     isFullScreen() {
230
-        return document.fullscreenElement
230
+        return Boolean(document.fullscreenElement
231 231
             || document.mozFullScreenElement
232 232
             || document.webkitFullscreenElement
233
-            || document.msFullscreenElement;
234
-    },
235
-
236
-    /**
237
-     * Exits full screen mode.
238
-     * @see https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API
239
-     */
240
-    exitFullScreen() {
241
-        if (document.exitFullscreen) {
242
-            document.exitFullscreen();
243
-        } else if (document.msExitFullscreen) {
244
-            document.msExitFullscreen();
245
-        } else if (document.mozCancelFullScreen) {
246
-            document.mozCancelFullScreen();
247
-        } else if (document.webkitExitFullscreen) {
248
-            document.webkitExitFullscreen();
249
-        }
250
-    },
251
-
252
-    /**
253
-     * Enter full screen mode.
254
-     * @see https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API
255
-     */
256
-    enterFullScreen() {
257
-        if (document.documentElement.requestFullscreen) {
258
-            document.documentElement.requestFullscreen();
259
-        } else if (document.documentElement.msRequestFullscreen) {
260
-            document.documentElement.msRequestFullscreen();
261
-        } else if (document.documentElement.mozRequestFullScreen) {
262
-            document.documentElement.mozRequestFullScreen();
263
-        } else if (document.documentElement.webkitRequestFullscreen) {
264
-            document.documentElement
265
-                .webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
266
-        }
233
+            || document.msFullscreenElement);
267 234
     },
268 235
 
269 236
     /**

+ 12
- 3
modules/keyboardshortcut/keyboardshortcut.js Näytä tiedosto

@@ -90,6 +90,17 @@ const KeyboardShortcut = {
90 90
         enabled = value;
91 91
     },
92 92
 
93
+    /**
94
+     * Opens the {@KeyboardShortcutsDialog} dialog.
95
+     *
96
+     * @returns {void}
97
+     */
98
+    openDialog() {
99
+        APP.store.dispatch(toggleDialog(KeyboardShortcutsDialog, {
100
+            shortcutDescriptions: _shortcutsHelp
101
+        }));
102
+    },
103
+
93 104
     /**
94 105
      * Registers a new shortcut.
95 106
      *
@@ -177,9 +188,7 @@ const KeyboardShortcut = {
177 188
     _initGlobalShortcuts() {
178 189
         this.registerShortcut('?', null, () => {
179 190
             sendAnalytics(createShortcutEvent('help'));
180
-            APP.store.dispatch(toggleDialog(KeyboardShortcutsDialog, {
181
-                shortcutDescriptions: _shortcutsHelp
182
-            }));
191
+            this.openDialog();
183 192
         }, 'keyboardShortcuts.toggleShortcuts');
184 193
 
185 194
         // register SPACE shortcut in two steps to insure visibility of help

+ 12
- 0
react/features/base/conference/actionTypes.js Näytä tiedosto

@@ -96,6 +96,18 @@ export const P2P_STATUS_CHANGED = Symbol('P2P_STATUS_CHANGED');
96 96
  */
97 97
 export const SET_AUDIO_ONLY = Symbol('SET_AUDIO_ONLY');
98 98
 
99
+/**
100
+ * The type of (redux) action which sets the desktop sharing enabled flag for
101
+ * the current conference.
102
+ *
103
+ * {
104
+ *     type: SET_DESKTOP_SHARING_ENABLED,
105
+ *     desktopSharingEnabled: boolean
106
+ * }
107
+ */
108
+export const SET_DESKTOP_SHARING_ENABLED
109
+    = Symbol('SET_DESKTOP_SHARING_ENABLED');
110
+
99 111
 /**
100 112
  * The type of (redux) action which updates the current known status of the
101 113
  * Follow Me feature.

+ 17
- 0
react/features/base/conference/actions.js Näytä tiedosto

@@ -31,6 +31,7 @@ import {
31 31
     LOCK_STATE_CHANGED,
32 32
     P2P_STATUS_CHANGED,
33 33
     SET_AUDIO_ONLY,
34
+    SET_DESKTOP_SHARING_ENABLED,
34 35
     SET_FOLLOW_ME,
35 36
     SET_LASTN,
36 37
     SET_PASSWORD,
@@ -433,6 +434,22 @@ export function setAudioOnly(audioOnly: boolean) {
433 434
     };
434 435
 }
435 436
 
437
+/**
438
+ * Sets the flag for indicating if desktop sharing is enabled.
439
+ *
440
+ * @param {boolean} desktopSharingEnabled - True if desktop sharing is enabled.
441
+ * @returns {{
442
+ *     type: SET_DESKTOP_SHARING_ENABLED,
443
+ *     desktopSharingEnabled: boolean
444
+ * }}
445
+ */
446
+export function setDesktopSharingEnabled(desktopSharingEnabled: boolean) {
447
+    return {
448
+        type: SET_DESKTOP_SHARING_ENABLED,
449
+        desktopSharingEnabled
450
+    };
451
+}
452
+
436 453
 /**
437 454
  * Enables or disables the Follow Me feature.
438 455
  *

+ 19
- 0
react/features/base/conference/reducer.js Näytä tiedosto

@@ -14,6 +14,7 @@ import {
14 14
     LOCK_STATE_CHANGED,
15 15
     P2P_STATUS_CHANGED,
16 16
     SET_AUDIO_ONLY,
17
+    SET_DESKTOP_SHARING_ENABLED,
17 18
     SET_FOLLOW_ME,
18 19
     SET_PASSWORD,
19 20
     SET_RECEIVE_VIDEO_QUALITY,
@@ -57,6 +58,9 @@ ReducerRegistry.register('features/base/conference', (state = {}, action) => {
57 58
     case SET_AUDIO_ONLY:
58 59
         return _setAudioOnly(state, action);
59 60
 
61
+    case SET_DESKTOP_SHARING_ENABLED:
62
+        return _setDesktopSharingEnabled(state, action);
63
+
60 64
     case SET_FOLLOW_ME:
61 65
         return {
62 66
             ...state,
@@ -329,6 +333,21 @@ function _setAudioOnly(state, action) {
329 333
     return set(state, 'audioOnly', action.audioOnly);
330 334
 }
331 335
 
336
+/**
337
+ * Reduces a specific Redux action SET_DESKTOP_SHARING_ENABLED of the feature
338
+ * base/conference.
339
+ *
340
+ * @param {Object} state - The Redux state of the feature base/conference.
341
+ * @param {Action} action - The Redux action SET_DESKTOP_SHARING_ENABLED to
342
+ * reduce.
343
+ * @private
344
+ * @returns {Object} The new state of the feature base/conference after the
345
+ * reduction of the specified action.
346
+ */
347
+function _setDesktopSharingEnabled(state, action) {
348
+    return set(state, 'desktopSharingEnabled', action.desktopSharingEnabled);
349
+}
350
+
332 351
 /**
333 352
  * Reduces a specific Redux action SET_PASSWORD of the feature base/conference.
334 353
  *

+ 5
- 3
react/features/base/connection/actions.web.js Näytä tiedosto

@@ -1,4 +1,4 @@
1
-/* @flow */
1
+// @flow
2 2
 
3 3
 import type { Dispatch } from 'redux';
4 4
 
@@ -54,10 +54,12 @@ export function connect() {
54 54
 /**
55 55
  * Closes connection.
56 56
  *
57
+ * @param {boolean} [requestFeedback] - Whether or not to attempt showing a
58
+ * request for call feedback.
57 59
  * @returns {Function}
58 60
  */
59
-export function disconnect() {
61
+export function disconnect(requestFeedback: boolean = false) {
60 62
     // XXX For web based version we use conference hanging up logic from the old
61 63
     // app.
62
-    return () => APP.conference.hangup();
64
+    return () => APP.conference.hangup(requestFeedback);
63 65
 }

+ 9
- 1
react/features/base/dialog/components/StatelessDialog.web.js Näytä tiedosto

@@ -39,6 +39,12 @@ type Props = {
39 39
      */
40 40
     disableBlanketClickDismiss: boolean,
41 41
 
42
+    /**
43
+     * If true, the cancel button will not display but cancel actions, like
44
+     * clicking the blanket, will cancel.
45
+     */
46
+    hideCancelButton: boolean,
47
+
42 48
     /**
43 49
      * Whether the dialog is modal. This means clicking on the blanket will
44 50
      * leave the dialog open. No cancel button.
@@ -263,7 +269,9 @@ class StatelessDialog extends Component<Props> {
263 269
      * not modal.
264 270
      */
265 271
     _renderCancelButton(options = {}) {
266
-        if (options.cancelDisabled || options.isModal) {
272
+        if (options.cancelDisabled
273
+                || options.isModal
274
+                || options.hideCancelButton) {
267 275
             return null;
268 276
         }
269 277
 

+ 247
- 220
react/features/base/font-icons/jitsi.json
File diff suppressed because it is too large
Näytä tiedosto


+ 59
- 6
react/features/base/participants/middleware.js Näytä tiedosto

@@ -1,4 +1,4 @@
1
-/* @flow */
1
+// @flow
2 2
 
3 3
 import UIEvents from '../../../../service/UI/UIEvents';
4 4
 
@@ -10,8 +10,12 @@ import {
10 10
 import { MiddlewareRegistry } from '../redux';
11 11
 import { playSound, registerSound, unregisterSound } from '../sounds';
12 12
 
13
-import { localParticipantIdChanged } from './actions';
14 13
 import {
14
+    localParticipantIdChanged,
15
+    participantUpdated
16
+} from './actions';
17
+import {
18
+    DOMINANT_SPEAKER_CHANGED,
15 19
     KICK_PARTICIPANT,
16 20
     MUTE_REMOTE_PARTICIPANT,
17 21
     PARTICIPANT_DISPLAY_NAME_CHANGED,
@@ -27,6 +31,7 @@ import {
27 31
 import {
28 32
     getAvatarURLByParticipantId,
29 33
     getLocalParticipant,
34
+    getParticipantById,
30 35
     getParticipantCount
31 36
 } from './functions';
32 37
 import {
@@ -47,7 +52,7 @@ MiddlewareRegistry.register(store => next => action => {
47 52
     const { conference } = store.getState()['features/base/conference'];
48 53
 
49 54
     if (action.type === PARTICIPANT_JOINED
50
-        || action.type === PARTICIPANT_LEFT) {
55
+            || action.type === PARTICIPANT_LEFT) {
51 56
         _maybePlaySounds(store, action);
52 57
     }
53 58
 
@@ -66,6 +71,27 @@ MiddlewareRegistry.register(store => next => action => {
66 71
         store.dispatch(localParticipantIdChanged(LOCAL_PARTICIPANT_DEFAULT_ID));
67 72
         break;
68 73
 
74
+    case DOMINANT_SPEAKER_CHANGED: {
75
+        // Ensure the raised hand state is cleared for the dominant speaker.
76
+        const participant = getLocalParticipant(store.getState());
77
+
78
+        if (participant) {
79
+            const local = participant.id === action.participant.id;
80
+
81
+            store.dispatch(participantUpdated({
82
+                id: action.participant.id,
83
+                local,
84
+                raisedHand: false
85
+            }));
86
+        }
87
+
88
+        if (typeof APP === 'object') {
89
+            APP.UI.markDominantSpeaker(action.participant.id);
90
+        }
91
+
92
+        break;
93
+    }
94
+
69 95
     case KICK_PARTICIPANT:
70 96
         conference.kickParticipant(action.id);
71 97
         break;
@@ -90,10 +116,37 @@ MiddlewareRegistry.register(store => next => action => {
90 116
 
91 117
     case PARTICIPANT_JOINED:
92 118
     case PARTICIPANT_UPDATED: {
93
-        if (typeof APP !== 'undefined') {
94
-            const participant = action.participant;
95
-            const { id, local } = participant;
119
+        const { participant } = action;
120
+        const { id, local, raisedHand } = participant;
121
+
122
+        // Send an external update of the local participant's raised hand state
123
+        // if a new raised hand state is defined in the action.
124
+        if (typeof raisedHand !== 'undefined') {
125
+            if (local) {
126
+                conference.setLocalParticipantProperty(
127
+                    'raisedHand',
128
+                    raisedHand);
129
+            }
130
+
131
+            if (typeof APP === 'object') {
132
+                if (local) {
133
+                    APP.UI.onLocalRaiseHandChanged(raisedHand);
134
+                    APP.UI.setLocalRaisedHandStatus(raisedHand);
135
+                } else {
136
+                    const remoteParticipant
137
+                        = getParticipantById(store.getState(), id);
138
+
139
+                    remoteParticipant
140
+                        && APP.UI.setRaisedHandStatus(
141
+                            remoteParticipant.id,
142
+                            remoteParticipant.name,
143
+                            raisedHand);
144
+                }
145
+            }
146
+        }
96 147
 
148
+        // Notify external listeners of potential avatarURL changes.
149
+        if (typeof APP === 'object') {
97 150
             const preUpdateAvatarURL
98 151
                 = getAvatarURLByParticipantId(store.getState(), id);
99 152
 

+ 10
- 0
react/features/base/tracks/actionTypes.js Näytä tiedosto

@@ -1,3 +1,13 @@
1
+/**
2
+ * The type of redux action dispatched to disable screensharing or to start the
3
+ * flow for enabling screenshare.
4
+ *
5
+ * {
6
+ *     type: TOGGLE_SCREENSHARING
7
+ * }
8
+ */
9
+export const TOGGLE_SCREENSHARING = Symbol('TOGGLE_SCREENSHARING');
10
+
1 11
 /**
2 12
  * The type of redux action dispatched when a track has been (locally or
3 13
  * remotely) added to the conference.

+ 15
- 0
react/features/base/tracks/actions.js Näytä tiedosto

@@ -12,6 +12,7 @@ import {
12 12
 import { getLocalParticipant } from '../participants';
13 13
 
14 14
 import {
15
+    TOGGLE_SCREENSHARING,
15 16
     TRACK_ADDED,
16 17
     TRACK_CREATE_CANCELED,
17 18
     TRACK_CREATE_ERROR,
@@ -172,6 +173,20 @@ export function destroyLocalTracks() {
172 173
     };
173 174
 }
174 175
 
176
+/**
177
+ * Signals that the local participant is ending screensharing or beginning the
178
+ * screensharing flow.
179
+ *
180
+ * @returns {{
181
+ *     type: TOGGLE_SCREENSHARING,
182
+ * }}
183
+ */
184
+export function toggleScreensharing() {
185
+    return {
186
+        type: TOGGLE_SCREENSHARING
187
+    };
188
+}
189
+
175 190
 /**
176 191
  * Replaces one track with another for one renegotiation instead of invoking
177 192
  * two renegotiations with a separate removeTrack and addTrack. Disposes the

+ 14
- 2
react/features/base/tracks/middleware.js Näytä tiedosto

@@ -1,4 +1,4 @@
1
-/* @flow */
1
+// @flow
2 2
 
3 3
 import {
4 4
     CAMERA_FACING_MODE,
@@ -10,9 +10,15 @@ import {
10 10
     toggleCameraFacingMode
11 11
 } from '../media';
12 12
 import { MiddlewareRegistry } from '../redux';
13
+import UIEvents from '../../../../service/UI/UIEvents';
13 14
 
14 15
 import { createLocalTracksA } from './actions';
15
-import { TRACK_ADDED, TRACK_REMOVED, TRACK_UPDATED } from './actionTypes';
16
+import {
17
+    TOGGLE_SCREENSHARING,
18
+    TRACK_ADDED,
19
+    TRACK_REMOVED,
20
+    TRACK_UPDATED
21
+} from './actionTypes';
16 22
 import { getLocalTrack, setTrackMuted } from './functions';
17 23
 
18 24
 declare var APP: Object;
@@ -81,6 +87,12 @@ MiddlewareRegistry.register(store => next => action => {
81 87
         break;
82 88
     }
83 89
 
90
+    case TOGGLE_SCREENSHARING:
91
+        if (typeof APP === 'object') {
92
+            APP.UI.emitEvent(UIEvents.TOGGLE_SCREENSHARING);
93
+        }
94
+        break;
95
+
84 96
     case TRACK_ADDED:
85 97
         // TODO Remove this middleware case once all UI interested in new tracks
86 98
         // being added are converted to react and listening for store changes.

+ 23
- 0
react/features/chat/actionTypes.js Näytä tiedosto

@@ -0,0 +1,23 @@
1
+/**
2
+ * The type of the action which signals to add a new chat message.
3
+ *
4
+ * {
5
+ *     type: ADD_MESSAGE,
6
+ *     hasRead: boolean,
7
+ *     message: string,
8
+ *     timestamp: string,
9
+ *     userName: string
10
+ * }
11
+ */
12
+export const ADD_MESSAGE = Symbol('ADD_MESSAGE');
13
+
14
+/**
15
+ * The type of the action which updates which is the most recent message that
16
+ * has been seen by the local participant.
17
+ *
18
+ * {
19
+ *     type: SET_LAST_READ_MESSAGE,
20
+ *     message: Object
21
+ * }
22
+ */
23
+export const SET_LAST_READ_MESSAGE = Symbol('SET_LAST_READ_MESSAGE');

+ 63
- 0
react/features/chat/actions.js Näytä tiedosto

@@ -0,0 +1,63 @@
1
+import { ADD_MESSAGE, SET_LAST_READ_MESSAGE } from './actionTypes';
2
+
3
+/* eslint-disable max-params */
4
+
5
+/**
6
+ * Adds a chat message to the collection of messages.
7
+ *
8
+ * @param {string} userName - The username to display of the participant that
9
+ * authored the message.
10
+ * @param {string} message - The received message to display.
11
+ * @param {string} timestamp - A timestamp to display for when the message was
12
+ * received.
13
+ * @param {boolean} hasRead - Whether or not to immediately mark the message as
14
+ * read.
15
+ * @returns {{
16
+ *     type: ADD_MESSAGE,
17
+ *     hasRead: boolean,
18
+ *     message: string,
19
+ *     timestamp: string,
20
+ *     userName: string
21
+ * }}
22
+ */
23
+export function addMessage(userName, message, timestamp, hasRead) {
24
+    return {
25
+        type: ADD_MESSAGE,
26
+        hasRead,
27
+        message,
28
+        timestamp,
29
+        userName
30
+    };
31
+}
32
+
33
+/* eslint-enable max-params */
34
+
35
+/**
36
+ * Sets the last read message cursor to the latest message.
37
+ *
38
+ * @returns {Function}
39
+ */
40
+export function markAllRead() {
41
+    return (dispatch, getState) => {
42
+        const { messages } = getState()['features/chat'];
43
+
44
+        dispatch(setLastReadMessage(messages[messages.length - 1]));
45
+    };
46
+}
47
+
48
+/**
49
+ * Updates the last read message cursor to be set at the passed in message. The
50
+ * assumption is that messages will be ordered chronologically.
51
+ *
52
+ * @param {Object} message - The message from the redux state.
53
+ * @returns {{
54
+ *     type: SET_LAST_READ_MESSAGE,
55
+ *     message: Object
56
+ * }}
57
+ */
58
+export function setLastReadMessage(message) {
59
+    return {
60
+        type: SET_LAST_READ_MESSAGE,
61
+        message
62
+    };
63
+}

+ 0
- 0
react/features/chat/components/ChatCounter.native.js Näytä tiedosto


+ 59
- 0
react/features/chat/components/ChatCounter.web.js Näytä tiedosto

@@ -0,0 +1,59 @@
1
+import PropTypes from 'prop-types';
2
+import React, { Component } from 'react';
3
+import { connect } from 'react-redux';
4
+
5
+import { getUnreadCount } from '../functions';
6
+
7
+/**
8
+ * FIXME: Move this UI logic to a generic component that can be used for
9
+ * {@code ParticipantCounter} as well.
10
+ */
11
+
12
+/**
13
+ * Implements a React {@link Component} which displays a count of the number of
14
+ * unread chat messages.
15
+ *
16
+ * @extends Component
17
+ */
18
+class ChatCounter extends Component {
19
+    static propTypes = {
20
+        /**
21
+         * The number of unread chat messages in the conference.
22
+         */
23
+        _count: PropTypes.number
24
+    };
25
+
26
+    /**
27
+     * Implements React's {@link Component#render()}.
28
+     *
29
+     * @inheritdoc
30
+     * @returns {ReactElement}
31
+     */
32
+    render() {
33
+        return (
34
+            <span className = 'badge-round'>
35
+                <span>
36
+                    { this.props._count || null }
37
+                </span>
38
+            </span>
39
+        );
40
+    }
41
+}
42
+
43
+/**
44
+ * Maps (parts of) the Redux state to the associated {@code ChatCounter}'s
45
+ * props.
46
+ *
47
+ * @param {Object} state - The Redux state.
48
+ * @private
49
+ * @returns {{
50
+ *     _count: number
51
+ * }}
52
+ */
53
+function _mapStateToProps(state) {
54
+    return {
55
+        _count: getUnreadCount(state)
56
+    };
57
+}
58
+
59
+export default connect(_mapStateToProps)(ChatCounter);

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

@@ -0,0 +1 @@
1
+export ChatCounter from './ChatCounter';

+ 20
- 0
react/features/chat/functions.js Näytä tiedosto

@@ -0,0 +1,20 @@
1
+// @flow
2
+
3
+/**
4
+ * Selector for calculating the number of unread chat messages.
5
+ *
6
+ * @param {Object} state - The redux state.
7
+ * @returns {number} The number of unread messages.
8
+ */
9
+export function getUnreadCount(state: Object) {
10
+    const { lastReadMessage, messages } = state['features/chat'];
11
+    const messagesCount = messages.length;
12
+
13
+    if (!messagesCount) {
14
+        return 0;
15
+    }
16
+
17
+    const lastReadIndex = messages.lastIndexOf(lastReadMessage);
18
+
19
+    return messagesCount - (lastReadIndex + 1);
20
+}

+ 4
- 0
react/features/chat/index.js Näytä tiedosto

@@ -1,3 +1,7 @@
1
+export * from './actions';
2
+export * from './actionTypes';
3
+export * from './components';
1 4
 export * from './constants';
2 5
 
3 6
 import './middleware';
7
+import './reducer';

+ 44
- 0
react/features/chat/reducer.js Näytä tiedosto

@@ -0,0 +1,44 @@
1
+// @flow
2
+
3
+import { ReducerRegistry } from '../base/redux';
4
+
5
+import {
6
+    ADD_MESSAGE,
7
+    SET_LAST_READ_MESSAGE
8
+} from './actionTypes';
9
+
10
+const DEFAULT_STATE = {
11
+    open: false,
12
+    messages: [],
13
+    lastReadMessage: null
14
+};
15
+
16
+ReducerRegistry.register('features/chat', (state = DEFAULT_STATE, action) => {
17
+    switch (action.type) {
18
+    case ADD_MESSAGE: {
19
+        const newMessage = {
20
+            message: action.message,
21
+            timestamp: action.timestamp,
22
+            userName: action.userName
23
+        };
24
+
25
+        return {
26
+            ...state,
27
+            lastReadMessage:
28
+                action.hasRead ? newMessage : state.lastReadMessage,
29
+            messages: [
30
+                ...state.messages,
31
+                newMessage
32
+            ]
33
+        };
34
+    }
35
+
36
+    case SET_LAST_READ_MESSAGE:
37
+        return {
38
+            ...state,
39
+            lastReadMessage: action.message
40
+        };
41
+    }
42
+
43
+    return state;
44
+});

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

@@ -11,7 +11,14 @@ import { CalleeInfoContainer } from '../../base/jwt';
11 11
 import { Filmstrip } from '../../filmstrip';
12 12
 import { LargeVideo } from '../../large-video';
13 13
 import { NotificationsContainer } from '../../notifications';
14
-import { showToolbox, Toolbox } from '../../toolbox';
14
+import { SidePanel } from '../../side-panel';
15
+import {
16
+    Toolbox,
17
+    ToolboxV2,
18
+    fullScreenChanged,
19
+    setToolboxAlwaysVisible,
20
+    showToolbox
21
+} from '../../toolbox';
15 22
 import { HideNotificationBarStyle } from '../../unsupported-browser';
16 23
 
17 24
 import { maybeShowSuboptimalExperienceNotification } from '../functions';
@@ -19,11 +26,29 @@ import { maybeShowSuboptimalExperienceNotification } from '../functions';
19 26
 declare var APP: Object;
20 27
 declare var interfaceConfig: Object;
21 28
 
29
+/**
30
+ * DOM events for when full screen mode has changed. Different browsers need
31
+ * different vendor prefixes.
32
+ *
33
+ * @private
34
+ * @type {Array<string>}
35
+ */
36
+const FULL_SCREEN_EVENTS = [
37
+    'webkitfullscreenchange',
38
+    'mozfullscreenchange',
39
+    'fullscreenchange'
40
+];
41
+
22 42
 /**
23 43
  * The type of the React {@code Component} props of {@link Conference}.
24 44
  */
25 45
 type Props = {
26 46
 
47
+    /**
48
+     * Whether the toolbar should stay visible or be able to autohide.
49
+     */
50
+    _alwaysVisibleToolbar: boolean,
51
+
27 52
     /**
28 53
      * Whether the local participant is recording the conference.
29 54
      */
@@ -37,6 +62,7 @@ type Props = {
37 62
  * The conference page of the Web application.
38 63
  */
39 64
 class Conference extends Component<Props> {
65
+    _onFullScreenChange: Function;
40 66
     _onShowToolbar: Function;
41 67
     _originalOnShowToolbar: Function;
42 68
 
@@ -59,6 +85,9 @@ class Conference extends Component<Props> {
59 85
                 leading: true,
60 86
                 trailing: false
61 87
             });
88
+
89
+        // Bind event handler so it is only bound once for every instance.
90
+        this._onFullScreenChange = this._onFullScreenChange.bind(this);
62 91
     }
63 92
 
64 93
     /**
@@ -74,10 +103,16 @@ class Conference extends Component<Props> {
74 103
         APP.UI.registerListeners();
75 104
         APP.UI.bindEvents();
76 105
 
77
-        const { dispatch, t } = this.props;
106
+        FULL_SCREEN_EVENTS.forEach(name =>
107
+            document.addEventListener(name, this._onFullScreenChange));
108
+
109
+        const { _alwaysVisibleToolbar, dispatch, t } = this.props;
78 110
 
79 111
         dispatch(connect());
80 112
         maybeShowSuboptimalExperienceNotification(dispatch, t);
113
+
114
+        dispatch(setToolboxAlwaysVisible(
115
+            _alwaysVisibleToolbar || interfaceConfig.filmStripOnly));
81 116
     }
82 117
 
83 118
     /**
@@ -90,6 +125,9 @@ class Conference extends Component<Props> {
90 125
         APP.UI.unregisterListeners();
91 126
         APP.UI.unbindEvents();
92 127
 
128
+        FULL_SCREEN_EVENTS.forEach(name =>
129
+            document.removeEventListener(name, this._onFullScreenChange));
130
+
93 131
         APP.conference.isJoined() && this.props.dispatch(disconnect());
94 132
     }
95 133
 
@@ -100,12 +138,26 @@ class Conference extends Component<Props> {
100 138
      * @returns {ReactElement}
101 139
      */
102 140
     render() {
103
-        const { filmStripOnly, VIDEO_QUALITY_LABEL_DISABLED } = interfaceConfig;
141
+        const {
142
+            _USE_NEW_TOOLBOX,
143
+            VIDEO_QUALITY_LABEL_DISABLED,
144
+            filmStripOnly
145
+        } = interfaceConfig;
104 146
         const hideVideoQualityLabel
105 147
             = filmStripOnly
106 148
                 || VIDEO_QUALITY_LABEL_DISABLED
107 149
                 || this.props._iAmRecorder;
108 150
 
151
+        let ToolboxToUse;
152
+
153
+        if (filmStripOnly) {
154
+            ToolboxToUse = null;
155
+        } else if (interfaceConfig._USE_NEW_TOOLBOX) {
156
+            ToolboxToUse = ToolboxV2;
157
+        } else {
158
+            ToolboxToUse = Toolbox;
159
+        }
160
+
109 161
         return (
110 162
             <div
111 163
                 id = 'videoconference_page'
@@ -116,7 +168,10 @@ class Conference extends Component<Props> {
116 168
                     <Filmstrip filmstripOnly = { filmStripOnly } />
117 169
                 </div>
118 170
 
119
-                { filmStripOnly ? null : <Toolbox /> }
171
+                { ToolboxToUse && <ToolboxToUse /> }
172
+
173
+                { _USE_NEW_TOOLBOX && !filmStripOnly
174
+                    && <SidePanel /> }
120 175
 
121 176
                 <DialogContainer />
122 177
                 <NotificationsContainer />
@@ -135,6 +190,17 @@ class Conference extends Component<Props> {
135 190
         );
136 191
     }
137 192
 
193
+    /**
194
+     * Updates the Redux state when full screen mode has been enabled or
195
+     * disabled.
196
+     *
197
+     * @private
198
+     * @returns {void}
199
+     */
200
+    _onFullScreenChange() {
201
+        this.props.dispatch(fullScreenChanged(APP.UI.isFullScreen()));
202
+    }
203
+
138 204
     /**
139 205
      * Displays the toolbar.
140 206
      *
@@ -153,17 +219,30 @@ class Conference extends Component<Props> {
153 219
  * @param {Object} state - The Redux state.
154 220
  * @private
155 221
  * @returns {{
222
+ *     _alwaysVisibleToolbar: boolean,
156 223
  *     _iAmRecorder: boolean
157 224
  * }}
158 225
  */
159 226
 function _mapStateToProps(state) {
227
+    const {
228
+        alwaysVisibleToolbar,
229
+        iAmRecorder
230
+    } = state['features/base/config'];
231
+
160 232
     return {
233
+        /**
234
+         * Whether the toolbar should stay visible or be able to autohide.
235
+         *
236
+         * @private
237
+         */
238
+        _alwaysVisibleToolbar: alwaysVisibleToolbar,
239
+
161 240
         /**
162 241
          * Whether the local participant is recording the conference.
163 242
          *
164 243
          * @private
165 244
          */
166
-        _iAmRecorder: state['features/base/config'].iAmRecorder
245
+        _iAmRecorder: iAmRecorder
167 246
     };
168 247
 }
169 248
 

+ 29
- 0
react/features/etherpad/actionTypes.js Näytä tiedosto

@@ -0,0 +1,29 @@
1
+/**
2
+ * The type of the action which signals document editing has been enabled.
3
+ *
4
+ * {
5
+ *     type: ETHERPAD_INITIALIZED
6
+ * }
7
+ */
8
+export const ETHERPAD_INITIALIZED = Symbol('ETHERPAD_INITIALIZED');
9
+
10
+
11
+/**
12
+ * The type of the action which signals document editing has stopped or started.
13
+ *
14
+ * {
15
+ *     type: SET_DOCUMENT_EDITING_STATUS
16
+ * }
17
+ */
18
+export const SET_DOCUMENT_EDITING_STATUS
19
+    = Symbol('SET_DOCUMENT_EDITING_STATUS');
20
+
21
+/**
22
+ * The type of the action which signals to start or stop editing a shared
23
+ * document.
24
+ *
25
+ * {
26
+ *     type: TOGGLE_DOCUMENT_EDITING
27
+ * }
28
+ */
29
+export const TOGGLE_DOCUMENT_EDITING = Symbol('TOGGLE_DOCUMENT_EDITING');

+ 50
- 0
react/features/etherpad/actions.js Näytä tiedosto

@@ -0,0 +1,50 @@
1
+// @flow
2
+
3
+import {
4
+    ETHERPAD_INITIALIZED,
5
+    SET_DOCUMENT_EDITING_STATUS,
6
+    TOGGLE_DOCUMENT_EDITING
7
+} from './actionTypes';
8
+
9
+/**
10
+ * Dispatches an action to set whether document editing has started or stopped.
11
+ *
12
+ * @param {boolean} editing - Whether or not a document is currently being
13
+ * edited.
14
+ * @returns {{
15
+ *    type: SET_DOCUMENT_EDITING_STATUS,
16
+ *    editing: boolean
17
+ * }}
18
+ */
19
+export function setDocumentEditingState(editing: boolean) {
20
+    return {
21
+        type: SET_DOCUMENT_EDITING_STATUS,
22
+        editing
23
+    };
24
+}
25
+
26
+/**
27
+ * Dispatches an action to set Etherpad as having been initialized.
28
+ *
29
+ * @returns {{
30
+ *    type: ETHERPAD_INITIALIZED
31
+ * }}
32
+ */
33
+export function setEtherpadHasInitialzied() {
34
+    return {
35
+        type: ETHERPAD_INITIALIZED
36
+    };
37
+}
38
+
39
+/**
40
+ * Dispatches an action to show or hide Etherpad.
41
+ *
42
+ * @returns {{
43
+ *    type: TOGGLE_DOCUMENT_EDITING
44
+ * }}
45
+ */
46
+export function toggleDocument() {
47
+    return {
48
+        type: TOGGLE_DOCUMENT_EDITING
49
+    };
50
+}

+ 5
- 0
react/features/etherpad/index.js Näytä tiedosto

@@ -0,0 +1,5 @@
1
+export * from './actions';
2
+export * from './actionTypes';
3
+
4
+import './middleware';
5
+import './reducer';

+ 30
- 0
react/features/etherpad/middleware.js Näytä tiedosto

@@ -0,0 +1,30 @@
1
+// @flow
2
+
3
+import { MiddlewareRegistry } from '../base/redux';
4
+import UIEvents from '../../../service/UI/UIEvents';
5
+
6
+import { TOGGLE_DOCUMENT_EDITING } from './actionTypes';
7
+
8
+declare var APP: Object;
9
+
10
+/**
11
+ * Middleware that captures actions related to collaborative document editing
12
+ * and notifies components not hooked into redux.
13
+ *
14
+ * @param {Store} store - The redux store.
15
+ * @returns {Function}
16
+ */
17
+// eslint-disable-next-line no-unused-vars
18
+MiddlewareRegistry.register(store => next => action => {
19
+    if (typeof APP === 'undefined') {
20
+        return next(action);
21
+    }
22
+
23
+    switch (action.type) {
24
+    case TOGGLE_DOCUMENT_EDITING:
25
+        APP.UI.emitEvent(UIEvents.ETHERPAD_CLICKED);
26
+        break;
27
+    }
28
+
29
+    return next(action);
30
+});

+ 30
- 0
react/features/etherpad/reducer.js Näytä tiedosto

@@ -0,0 +1,30 @@
1
+// @flow
2
+
3
+import { ReducerRegistry } from '../base/redux';
4
+
5
+import {
6
+    ETHERPAD_INITIALIZED,
7
+    SET_DOCUMENT_EDITING_STATUS
8
+} from './actionTypes';
9
+
10
+/**
11
+ * Reduces the Redux actions of the feature features/etherpad.
12
+ */
13
+ReducerRegistry.register('features/etherpad', (state = {}, action) => {
14
+    switch (action.type) {
15
+    case ETHERPAD_INITIALIZED:
16
+        return {
17
+            ...state,
18
+            initialized: true
19
+        };
20
+
21
+    case SET_DOCUMENT_EDITING_STATUS:
22
+        return {
23
+            ...state,
24
+            editing: action.editing
25
+        };
26
+
27
+    default:
28
+        return state;
29
+    }
30
+});

+ 26
- 7
react/features/filmstrip/components/Filmstrip.web.js Näytä tiedosto

@@ -7,11 +7,13 @@ import { connect } from 'react-redux';
7 7
 
8 8
 import { getLocalParticipant, PARTICIPANT_ROLE } from '../../base/participants';
9 9
 import { InviteButton } from '../../invite';
10
-import { Toolbox } from '../../toolbox';
10
+import { Toolbox, ToolboxFilmstrip, dockToolbox } from '../../toolbox';
11 11
 
12 12
 import { setFilmstripHovered } from '../actions';
13 13
 import { shouldRemoteVideosBeVisible } from '../functions';
14 14
 
15
+declare var interfaceConfig: Object;
16
+
15 17
 /**
16 18
  * Implements a React {@link Component} which represents the filmstrip on
17 19
  * Web/React.
@@ -62,6 +64,12 @@ class Filmstrip extends Component<*> {
62 64
          */
63 65
         _remoteVideosVisible: PropTypes.bool,
64 66
 
67
+        /**
68
+         * Whether or not the toolbox is visible. The height of the vertical
69
+         * filmstrip needs to adjust to accommodate the horizontal toolbox.
70
+         */
71
+        _toolboxVisible: PropTypes.bool,
72
+
65 73
         /**
66 74
          * Updates the redux store with filmstrip hover changes.
67 75
          */
@@ -111,6 +119,7 @@ class Filmstrip extends Component<*> {
111 119
             _isAddToCallAvailable,
112 120
             _isDialOutAvailable,
113 121
             _remoteVideosVisible,
122
+            _toolboxVisible,
114 123
             filmstripOnly
115 124
         } = this.props;
116 125
 
@@ -122,13 +131,17 @@ class Filmstrip extends Component<*> {
122 131
          * will get updated without replacing the DOM. If the known DOM gets
123 132
          * modified, then the views will get blown away.
124 133
          */
134
+        const reduceHeight
135
+            = _toolboxVisible && interfaceConfig.TOOLBAR_BUTTONS.length;
136
+        const filmstripClassNames = `filmstrip ${_remoteVideosVisible
137
+            ? '' : 'hide-videos'} ${reduceHeight ? 'reduce-height' : ''}`;
125 138
 
126
-        const filmstripClassNames = `filmstrip ${_remoteVideosVisible ? ''
127
-            : 'hide-videos'}`;
139
+        const ToolboxToUse = interfaceConfig._USE_NEW_TOOLBOX
140
+            ? ToolboxFilmstrip : Toolbox;
128 141
 
129 142
         return (
130 143
             <div className = { filmstripClassNames }>
131
-                { filmstripOnly ? <Toolbox /> : null }
144
+                { filmstripOnly ? <ToolboxToUse /> : null }
132 145
                 <div
133 146
                     className = 'filmstrip__videos'
134 147
                     id = 'remoteVideos'>
@@ -172,6 +185,9 @@ class Filmstrip extends Component<*> {
172 185
      */
173 186
     _notifyOfHoveredStateUpdate() {
174 187
         if (this.props._hovered !== this._isHovered) {
188
+            if (interfaceConfig._USE_NEW_TOOLBOX) {
189
+                this.props.dispatch(dockToolbox(this._isHovered));
190
+            }
175 191
             this.props.dispatch(setFilmstripHovered(this._isHovered));
176 192
         }
177 193
     }
@@ -211,7 +227,8 @@ class Filmstrip extends Component<*> {
211 227
  *     _hovered: boolean,
212 228
  *     _isAddToCallAvailable: boolean,
213 229
  *     _isDialOutAvailable: boolean,
214
- *     _remoteVideosVisible: boolean
230
+ *     _remoteVideosVisible: boolean,
231
+ *     _toolboxVisible: boolean
215 232
  * }}
216 233
  */
217 234
 function _mapStateToProps(state) {
@@ -231,11 +248,13 @@ function _mapStateToProps(state) {
231 248
 
232 249
     return {
233 250
         _hideInviteButton: iAmRecorder
234
-            || (!isAddToCallAvailable && !isDialOutAvailable),
251
+            || (!isAddToCallAvailable && !isDialOutAvailable)
252
+            || interfaceConfig._USE_NEW_TOOLBOX,
235 253
         _hovered: hovered,
236 254
         _isAddToCallAvailable: isAddToCallAvailable,
237 255
         _isDialOutAvailable: isDialOutAvailable,
238
-        _remoteVideosVisible: shouldRemoteVideosBeVisible(state)
256
+        _remoteVideosVisible: shouldRemoteVideosBeVisible(state),
257
+        _toolboxVisible: state['features/toolbox'].visible
239 258
     };
240 259
 }
241 260
 

+ 100
- 27
react/features/invite/components/InfoDialogButton.web.js Näytä tiedosto

@@ -5,9 +5,15 @@ import PropTypes from 'prop-types';
5 5
 import React, { Component } from 'react';
6 6
 import { connect } from 'react-redux';
7 7
 
8
-import { ToolbarButton, TOOLTIP_TO_POPUP_POSITION } from '../../toolbox';
8
+import { createToolbarEvent, sendAnalytics } from '../../analytics';
9
+import { translate } from '../../base/i18n';
10
+import {
11
+    ToolbarButton,
12
+    ToolbarButtonV2,
13
+    TOOLTIP_TO_POPUP_POSITION
14
+} from '../../toolbox';
9 15
 
10
-import { setInfoDialogVisibility } from '../actions';
16
+import { setInfoDialogVisibility, updateDialInNumbers } from '../actions';
11 17
 import { InfoDialog } from './info-dialog';
12 18
 
13 19
 const { INITIAL_TOOLBAR_TIMEOUT } = interfaceConfig;
@@ -39,6 +45,15 @@ class InfoDialogButton extends Component {
39 45
      * @static
40 46
      */
41 47
     static propTypes = {
48
+
49
+        /**
50
+         * Phone numbers for dialing into the conference.
51
+         */
52
+        _dialInNumbers: PropTypes.oneOfType([
53
+            PropTypes.object,
54
+            PropTypes.array
55
+        ]),
56
+
42 57
         /**
43 58
          * Whether or not the {@code InfoDialog} should close by itself after a
44 59
          * a timeout.
@@ -61,6 +76,11 @@ class InfoDialogButton extends Component {
61 76
          */
62 77
         dispatch: PropTypes.func,
63 78
 
79
+        /**
80
+         * Invoked to obtain translated strings.
81
+         */
82
+        t: PropTypes.func,
83
+
64 84
         /**
65 85
          * From which side tooltips should display. Will be re-used for
66 86
          * displaying the inline dialog for video quality adjustment.
@@ -100,6 +120,10 @@ class InfoDialogButton extends Component {
100 120
         if (this.props._shouldAutoClose) {
101 121
             this._setAutoCloseTimeout();
102 122
         }
123
+
124
+        if (!this.props._dialInNumbers) {
125
+            this.props.dispatch(updateDialInNumbers());
126
+        }
103 127
     }
104 128
 
105 129
     /**
@@ -145,29 +169,9 @@ class InfoDialogButton extends Component {
145 169
      * @returns {ReactElement}
146 170
      */
147 171
     render() {
148
-        const { _showDialog, _toolboxVisible, tooltipPosition } = this.props;
149
-        const buttonConfiguration = {
150
-            ...DEFAULT_BUTTON_CONFIGURATION,
151
-            classNames: [
152
-                ...DEFAULT_BUTTON_CONFIGURATION.classNames,
153
-                _showDialog ? 'toggled button-active' : ''
154
-            ]
155
-        };
156
-
157
-        return (
158
-            <InlineDialog
159
-                content = { <InfoDialog
160
-                    onClose = { this._onDialogClose }
161
-                    onMouseOver = { this._onDialogMouseOver } /> }
162
-                isOpen = { _toolboxVisible && _showDialog }
163
-                onClose = { this._onDialogClose }
164
-                position = { TOOLTIP_TO_POPUP_POSITION[tooltipPosition] }>
165
-                <ToolbarButton
166
-                    button = { buttonConfiguration }
167
-                    onClick = { this._onDialogToggle }
168
-                    tooltipPosition = { tooltipPosition } />
169
-            </InlineDialog>
170
-        );
172
+        return interfaceConfig._USE_NEW_TOOLBOX
173
+            ? this._renderNewToolbarButton()
174
+            : this._renderOldToolbarButton();
171 175
     }
172 176
 
173 177
     /**
@@ -208,9 +212,75 @@ class InfoDialogButton extends Component {
208 212
      * @returns {void}
209 213
      */
210 214
     _onDialogToggle() {
215
+        sendAnalytics(createToolbarEvent('info'));
216
+
211 217
         this.props.dispatch(setInfoDialogVisibility(!this.props._showDialog));
212 218
     }
213 219
 
220
+    /**
221
+     * Renders a React Element for the {@code InfoDialog} using legacy
222
+     * {@code ToolbarButton}.
223
+     *
224
+     * @private
225
+     * @returns {ReactElement}
226
+     */
227
+    _renderOldToolbarButton() {
228
+        const { _showDialog, _toolboxVisible, tooltipPosition } = this.props;
229
+        const buttonConfiguration = {
230
+            ...DEFAULT_BUTTON_CONFIGURATION,
231
+            classNames: [
232
+                ...DEFAULT_BUTTON_CONFIGURATION.classNames,
233
+                _showDialog ? 'toggled button-active' : ''
234
+            ]
235
+        };
236
+
237
+        return (
238
+            <InlineDialog
239
+                content = { <InfoDialog
240
+                    autoUpdateNumbers = { false }
241
+                    onClose = { this._onDialogClose }
242
+                    onMouseOver = { this._onDialogMouseOver } /> }
243
+                isOpen = { _toolboxVisible && _showDialog }
244
+                onClose = { this._onDialogClose }
245
+                position = { TOOLTIP_TO_POPUP_POSITION[tooltipPosition] }>
246
+                <ToolbarButton
247
+                    button = { buttonConfiguration }
248
+                    onClick = { this._onDialogToggle }
249
+                    tooltipPosition = { tooltipPosition } />
250
+            </InlineDialog>
251
+        );
252
+    }
253
+
254
+    /**
255
+     * Renders a React Element for the {@code InfoDialog} using the newer
256
+     * {@code ToolbarButtonV2}.
257
+     *
258
+     * @private
259
+     * @returns {ReactElement}
260
+     */
261
+    _renderNewToolbarButton() {
262
+        const { _showDialog, _toolboxVisible, t } = this.props;
263
+        const iconClass = `icon-info ${_showDialog ? 'toggled' : ''}`;
264
+
265
+        return (
266
+            <div className = 'toolbox-button-wth-dialog'>
267
+                <InlineDialog
268
+                    content = { <InfoDialog
269
+                        autoUpdateNumbers = { false }
270
+                        onClose = { this._onDialogClose }
271
+                        onMouseOver = { this._onDialogMouseOver } /> }
272
+                    isOpen = { _toolboxVisible && _showDialog }
273
+                    onClose = { this._onDialogClose }
274
+                    position = { 'top right' }>
275
+                    <ToolbarButtonV2
276
+                        iconName = { iconClass }
277
+                        onClick = { this._onDialogToggle }
278
+                        tooltip = { t('info.tooltip') } />
279
+                </InlineDialog>
280
+            </div>
281
+        );
282
+    }
283
+
214 284
     /**
215 285
      * Set a timeout to automatically hide the {@code InfoDialog}.
216 286
      *
@@ -235,6 +305,7 @@ class InfoDialogButton extends Component {
235 305
  * @param {Object} state - The Redux state.
236 306
  * @private
237 307
  * @returns {{
308
+ *     _dialInNumbers: Array,
238 309
  *     _shouldAutoClose: boolean,
239 310
  *     _showDialog: boolean,
240 311
  *     _toolboxVisible: boolean
@@ -243,14 +314,16 @@ class InfoDialogButton extends Component {
243 314
 function _mapStateToProps(state) {
244 315
     const {
245 316
         infoDialogVisible,
246
-        infoDialogWillAutoClose
317
+        infoDialogWillAutoClose,
318
+        numbers
247 319
     } = state['features/invite'];
248 320
 
249 321
     return {
322
+        _dialInNumbers: numbers,
250 323
         _shouldAutoClose: infoDialogWillAutoClose,
251 324
         _showDialog: infoDialogVisible,
252 325
         _toolboxVisible: state['features/toolbox'].visible
253 326
     };
254 327
 }
255 328
 
256
-export default connect(_mapStateToProps)(InfoDialogButton);
329
+export default translate(connect(_mapStateToProps)(InfoDialogButton));

+ 17
- 1
react/features/invite/components/info-dialog/InfoDialog.web.js Näytä tiedosto

@@ -24,6 +24,15 @@ const logger = require('jitsi-meet-logger').getLogger(__filename);
24 24
  * @extends Component
25 25
  */
26 26
 class InfoDialog extends Component {
27
+    /**
28
+     * Default values for {@code InfoDialog} component's properties.
29
+     *
30
+     * @static
31
+     */
32
+    static defaultProps = {
33
+        autoUpdateNumbers: true
34
+    };
35
+
27 36
     /**
28 37
      * {@code InfoDialog} component's property types.
29 38
      *
@@ -69,6 +78,13 @@ class InfoDialog extends Component {
69 78
          */
70 79
         _password: PropTypes.string,
71 80
 
81
+        /**
82
+         * Whether or not this component should make a request for dial-in
83
+         * numbers. If false, this component will rely on an outside source
84
+         * updating and passing in numbers through the _dialIn prop.
85
+         */
86
+        autoUpdateNumbers: PropTypes.bool,
87
+
72 88
         /**
73 89
          * Invoked to open a dialog for adding participants to the conference.
74 90
          */
@@ -148,7 +164,7 @@ class InfoDialog extends Component {
148 164
      * @returns {void}
149 165
      */
150 166
     componentDidMount() {
151
-        if (!this.state.phoneNumber) {
167
+        if (!this.state.phoneNumber && this.props.autoUpdateNumbers) {
152 168
             this.props.dispatch(updateDialInNumbers());
153 169
         }
154 170
     }

+ 10
- 0
react/features/keyboard-shortcuts/actionTypes.js Näytä tiedosto

@@ -0,0 +1,10 @@
1
+/**
2
+ * The type of the action which signals the keyboard shortcuts dialog should
3
+ * be displayed.
4
+ *
5
+ * {
6
+ *     type: OPEN_KEYBOARD_SHORTCUTS_DIALOG
7
+ * }
8
+ */
9
+export const OPEN_KEYBOARD_SHORTCUTS_DIALOG
10
+    = Symbol('OPEN_KEYBOARD_SHORTCUTS_DIALOG');

+ 14
- 0
react/features/keyboard-shortcuts/actions.js Näytä tiedosto

@@ -0,0 +1,14 @@
1
+import { OPEN_KEYBOARD_SHORTCUTS_DIALOG } from './actionTypes';
2
+
3
+/**
4
+ * Opens the dialog showing available keyboard shortcuts.
5
+ *
6
+ * @returns {{
7
+ *     type: OPEN_KEYBOARD_SHORTCUTS_DIALOG
8
+ * }}
9
+ */
10
+export function openKeyboardShortcutsDialog() {
11
+    return {
12
+        type: OPEN_KEYBOARD_SHORTCUTS_DIALOG
13
+    };
14
+}

+ 3
- 0
react/features/keyboard-shortcuts/index.js Näytä tiedosto

@@ -1 +1,4 @@
1
+export * from './actions';
1 2
 export * from './components';
3
+
4
+import './middleware';

+ 26
- 0
react/features/keyboard-shortcuts/middleware.js Näytä tiedosto

@@ -0,0 +1,26 @@
1
+// @flow
2
+
3
+import { MiddlewareRegistry } from '../base/redux';
4
+
5
+import { OPEN_KEYBOARD_SHORTCUTS_DIALOG } from './actionTypes';
6
+
7
+declare var APP: Object;
8
+
9
+/**
10
+ * Implements the middleware of the feature keyboard-shortcuts.
11
+ *
12
+ * @param {Store} store - The redux store.
13
+ * @returns {Function}
14
+ */
15
+// eslint-disable-next-line no-unused-vars
16
+MiddlewareRegistry.register(store => next => action => {
17
+    switch (action.type) {
18
+    case OPEN_KEYBOARD_SHORTCUTS_DIALOG:
19
+        if (typeof APP === 'object') {
20
+            APP.keyboardshortcut.openDialog();
21
+        }
22
+        break;
23
+    }
24
+
25
+    return next(action);
26
+});

+ 22
- 0
react/features/recording/actionTypes.js Näytä tiedosto

@@ -20,3 +20,25 @@ export const HIDE_RECORDING_LABEL = Symbol('HIDE_RECORDING_LABEL');
20 20
  * @public
21 21
  */
22 22
 export const RECORDING_STATE_UPDATED = Symbol('RECORDING_STATE_UPDATED');
23
+
24
+/**
25
+ * The type of Redux action which updates the current known type of configured
26
+ * recording. For example, type "jibri" is used for live streaming.
27
+ *
28
+ * {
29
+ *     type: RECORDING_STATE_UPDATED,
30
+ *     recordingType: string
31
+ * }
32
+ * @public
33
+ */
34
+export const SET_RECORDING_TYPE = Symbol('SET_RECORDING_TYPE');
35
+
36
+/**
37
+ * The type of Redux action triggers the flow to start or stop recording.
38
+ *
39
+ * {
40
+ *     type: TOGGLE_RECORDING
41
+ * }
42
+ * @public
43
+ */
44
+export const TOGGLE_RECORDING = Symbol('TOGGLE_RECORDING');

+ 36
- 1
react/features/recording/actions.js Näytä tiedosto

@@ -1,4 +1,9 @@
1
-import { HIDE_RECORDING_LABEL, RECORDING_STATE_UPDATED } from './actionTypes';
1
+import {
2
+    HIDE_RECORDING_LABEL,
3
+    RECORDING_STATE_UPDATED,
4
+    SET_RECORDING_TYPE,
5
+    TOGGLE_RECORDING
6
+} from './actionTypes';
2 7
 
3 8
 /**
4 9
  * Hides any displayed recording label, regardless of current recording state.
@@ -13,6 +18,36 @@ export function hideRecordingLabel() {
13 18
     };
14 19
 }
15 20
 
21
+/**
22
+ * Sets what type of recording service will be used.
23
+ *
24
+ * @param {string} recordingType - The type of recording service to be used.
25
+ * Should be one of the enumerated types in {@link RECORDING_TYPES}.
26
+ * @returns {{
27
+ *     type: SET_RECORDING_TYPE,
28
+ *     recordingType: string
29
+ * }}
30
+ */
31
+export function setRecordingType(recordingType) {
32
+    return {
33
+        type: SET_RECORDING_TYPE,
34
+        recordingType
35
+    };
36
+}
37
+
38
+/**
39
+ * Start or stop recording.
40
+ *
41
+ * @returns {{
42
+ *     type: TOGGLE_RECORDING
43
+ * }}
44
+ */
45
+export function toggleRecording() {
46
+    return {
47
+        type: TOGGLE_RECORDING
48
+    };
49
+}
50
+
16 51
 /**
17 52
  * Updates the redux state for the recording feature.
18 53
  *

+ 12
- 0
react/features/recording/constants.js Näytä tiedosto

@@ -0,0 +1,12 @@
1
+// @flow
2
+
3
+/**
4
+ * Expected supported recording types. JIBRI is known to support live streaming
5
+ * whereas JIRECON is for recording.
6
+ *
7
+ * @type {Object}
8
+ */
9
+export const RECORDING_TYPES = {
10
+    JIBRI: 'jibri',
11
+    JIRECON: 'jirecon'
12
+};

+ 2
- 0
react/features/recording/index.js Näytä tiedosto

@@ -1,4 +1,6 @@
1 1
 export * from './actions';
2 2
 export * from './components';
3
+export * from './constants';
3 4
 
5
+import './middleware';
4 6
 import './reducer';

+ 27
- 0
react/features/recording/middleware.js Näytä tiedosto

@@ -0,0 +1,27 @@
1
+// @flow
2
+
3
+import { MiddlewareRegistry } from '../base/redux';
4
+import UIEvents from '../../../service/UI/UIEvents';
5
+
6
+import { TOGGLE_RECORDING } from './actionTypes';
7
+
8
+declare var APP: Object;
9
+
10
+/**
11
+ * Implements the middleware of the feature recording.
12
+ *
13
+ * @param {Store} store - The redux store.
14
+ * @returns {Function}
15
+ */
16
+// eslint-disable-next-line no-unused-vars
17
+MiddlewareRegistry.register(store => next => action => {
18
+    switch (action.type) {
19
+    case TOGGLE_RECORDING:
20
+        if (typeof APP === 'object') {
21
+            APP.UI.emitEvent(UIEvents.TOGGLE_RECORDING);
22
+        }
23
+        break;
24
+    }
25
+
26
+    return next(action);
27
+});

+ 11
- 1
react/features/recording/reducer.js Näytä tiedosto

@@ -1,5 +1,9 @@
1 1
 import { ReducerRegistry } from '../base/redux';
2
-import { HIDE_RECORDING_LABEL, RECORDING_STATE_UPDATED } from './actionTypes';
2
+import {
3
+    HIDE_RECORDING_LABEL,
4
+    RECORDING_STATE_UPDATED,
5
+    SET_RECORDING_TYPE
6
+} from './actionTypes';
3 7
 
4 8
 /**
5 9
  * Reduces the Redux actions of the feature features/recording.
@@ -18,6 +22,12 @@ ReducerRegistry.register('features/recording', (state = {}, action) => {
18 22
             ...action.recordingState
19 23
         };
20 24
 
25
+    case SET_RECORDING_TYPE:
26
+        return {
27
+            ...state,
28
+            recordingType: action.recordingType
29
+        };
30
+
21 31
     default:
22 32
         return state;
23 33
     }

+ 20
- 0
react/features/shared-video/actionTypes.js Näytä tiedosto

@@ -0,0 +1,20 @@
1
+/**
2
+ * The type of the action which signals to update the current known state of the
3
+ * shared YouTube video.
4
+ *
5
+ * {
6
+ *     type: SET_SHARED_VIDEO_STATUS,
7
+ *     status: string
8
+ * }
9
+ */
10
+export const SET_SHARED_VIDEO_STATUS = Symbol('SET_SHARED_VIDEO_STATUS');
11
+
12
+/**
13
+ * The type of the action which signals to start the flow for starting or
14
+ * stopping a shared YouTube video.
15
+ *
16
+ * {
17
+ *     type: TOGGLE_SHARED_VIDEO
18
+ * }
19
+ */
20
+export const TOGGLE_SHARED_VIDEO = Symbol('TOGGLE_SHARED_VIDEO');

+ 31
- 0
react/features/shared-video/actions.js Näytä tiedosto

@@ -0,0 +1,31 @@
1
+import { SET_SHARED_VIDEO_STATUS, TOGGLE_SHARED_VIDEO } from './actionTypes';
2
+
3
+/**
4
+ * Updates the current known status of the shared YouTube video.
5
+ *
6
+ * @param {string} status - The current status of the YouTube video being
7
+ * shared.
8
+ * @returns {{
9
+ *     type: SET_SHARED_VIDEO_STATUS,
10
+ *     status: string
11
+ * }}
12
+ */
13
+export function setSharedVideoStatus(status) {
14
+    return {
15
+        type: SET_SHARED_VIDEO_STATUS,
16
+        status
17
+    };
18
+}
19
+
20
+/**
21
+ * Starts the flow for starting or stopping a shared YouTube video.
22
+ *
23
+ * @returns {{
24
+ *     type: TOGGLE_SHARED_VIDEO
25
+ * }}
26
+ */
27
+export function toggleSharedVideo() {
28
+    return {
29
+        type: TOGGLE_SHARED_VIDEO
30
+    };
31
+}

+ 5
- 0
react/features/shared-video/index.js Näytä tiedosto

@@ -0,0 +1,5 @@
1
+export * from './actions';
2
+export * from './actionTypes';
3
+
4
+import './middleware';
5
+import './reducer';

+ 31
- 0
react/features/shared-video/middleware.js Näytä tiedosto

@@ -0,0 +1,31 @@
1
+// @flow
2
+
3
+import UIEvents from '../../../service/UI/UIEvents';
4
+
5
+import { MiddlewareRegistry } from '../base/redux';
6
+
7
+import { TOGGLE_SHARED_VIDEO } from './actionTypes';
8
+
9
+declare var APP: Object;
10
+
11
+/**
12
+ * Middleware that captures actions related to YouTube video sharing and updates
13
+ * components not hooked into redux.
14
+ *
15
+ * @param {Store} store - The redux store.
16
+ * @returns {Function}
17
+ */
18
+// eslint-disable-next-line no-unused-vars
19
+MiddlewareRegistry.register(store => next => action => {
20
+    if (typeof APP === 'undefined') {
21
+        return next(action);
22
+    }
23
+
24
+    switch (action.type) {
25
+    case TOGGLE_SHARED_VIDEO:
26
+        APP.UI.emitEvent(UIEvents.SHARED_VIDEO_CLICKED);
27
+        break;
28
+    }
29
+
30
+    return next(action);
31
+});

+ 19
- 0
react/features/shared-video/reducer.js Näytä tiedosto

@@ -0,0 +1,19 @@
1
+import { ReducerRegistry } from '../base/redux';
2
+
3
+import { SET_SHARED_VIDEO_STATUS } from './actionTypes';
4
+
5
+/**
6
+ * Reduces the Redux actions of the feature features/shared-video.
7
+ */
8
+ReducerRegistry.register('features/shared-video', (state = {}, action) => {
9
+    switch (action.type) {
10
+    case SET_SHARED_VIDEO_STATUS:
11
+        return {
12
+            ...state,
13
+            status: action.status
14
+        };
15
+
16
+    default:
17
+        return state;
18
+    }
19
+});

+ 49
- 0
react/features/side-panel/actionTypes.js Näytä tiedosto

@@ -0,0 +1,49 @@
1
+/**
2
+ * The type of the action which signals to close the side panel.
3
+ *
4
+ * {
5
+ *     type: CLOSE_PANEL,
6
+ * }
7
+ */
8
+export const CLOSE_PANEL = Symbol('CLOSE_PANEL');
9
+
10
+/**
11
+ * The type of the action which to set the name of the current panel being
12
+ * displayed in the side panel.
13
+ *
14
+ * {
15
+ *     type: SET_VISIBLE_PANEL,
16
+ *     current: string|null
17
+ * }
18
+ */
19
+export const SET_VISIBLE_PANEL = Symbol('SET_VISIBLE_PANEL');
20
+
21
+/**
22
+ * The type of the action which signals to toggle the display of chat in the
23
+ * side panel.
24
+ *
25
+ * {
26
+ *     type: TOGGLE_CHAT
27
+ * }
28
+ */
29
+export const TOGGLE_CHAT = Symbol('TOGGLE_CHAT');
30
+
31
+/**
32
+ * The type of the action which signals to toggle the display of profile editing
33
+ * in the side panel.
34
+ *
35
+ * {
36
+ *     type: TOGGLE_PROFILE
37
+ * }
38
+ */
39
+export const TOGGLE_PROFILE = Symbol('TOGGLE_PROFILE');
40
+
41
+/**
42
+ * The type of the action which signals to toggle the display of settings in the
43
+ * side panel.
44
+ *
45
+ * {
46
+ *     type: TOGGLE_SETTINGS
47
+ * }
48
+ */
49
+export const TOGGLE_SETTINGS = Symbol('TOGGLE_SETTINGS');

+ 77
- 0
react/features/side-panel/actions.js Näytä tiedosto

@@ -0,0 +1,77 @@
1
+import {
2
+    CLOSE_PANEL,
3
+    SET_VISIBLE_PANEL,
4
+    TOGGLE_CHAT,
5
+    TOGGLE_PROFILE,
6
+    TOGGLE_SETTINGS
7
+} from './actionTypes';
8
+
9
+/**
10
+ * Dispatches an action to close the currently displayed side panel.
11
+ *
12
+ * @returns {Function}
13
+ */
14
+export function closePanel() {
15
+    return (dispatch, getState) => {
16
+        dispatch({
17
+            type: CLOSE_PANEL,
18
+            current: getState()['features/side-panel'].current
19
+        });
20
+    };
21
+}
22
+
23
+/**
24
+ * Updates the redux store with the currently displayed side panel.
25
+ *
26
+ * @param {string|null} name - The name of the side panel being displayed. Null
27
+ * (or falsy) should be set if no side panel is being displayed.
28
+ * @returns {{
29
+ *     type: SET_VISIBLE_PANEL,
30
+ *     current: string
31
+ * }}
32
+ */
33
+export function setVisiblePanel(name = null) {
34
+    return {
35
+        type: SET_VISIBLE_PANEL,
36
+        current: name
37
+    };
38
+}
39
+
40
+/**
41
+ * Toggles display of the chat side panel.
42
+ *
43
+ * @returns {{
44
+ *     type: TOGGLE_CHAT
45
+ * }}
46
+ */
47
+export function toggleChat() {
48
+    return {
49
+        type: TOGGLE_CHAT
50
+    };
51
+}
52
+
53
+/**
54
+ * Toggles display of the profile side panel.
55
+ *
56
+ * @returns {{
57
+ *     type: TOGGLE_PROFILE
58
+ * }}
59
+ */
60
+export function toggleProfile() {
61
+    return {
62
+        type: TOGGLE_PROFILE
63
+    };
64
+}
65
+
66
+/**
67
+ * Toggles display of the settings side panel.
68
+ *
69
+ * @returns {{
70
+ *     type: TOGGLE_SETTINGS
71
+ * }}
72
+ */
73
+export function toggleSettings() {
74
+    return {
75
+        type: TOGGLE_SETTINGS
76
+    };
77
+}

+ 0
- 0
react/features/side-panel/components/SidePanel.native.js Näytä tiedosto


+ 62
- 0
react/features/side-panel/components/SidePanel.web.js Näytä tiedosto

@@ -0,0 +1,62 @@
1
+import PropTypes from 'prop-types';
2
+import React, { Component } from 'react';
3
+import { connect } from 'react-redux';
4
+
5
+import { closePanel } from '../actions';
6
+
7
+/**
8
+ * React Component for holding features in a side panel that slides in and out.
9
+ *
10
+ * @extends Component
11
+ */
12
+class SidePanel extends Component {
13
+    /**
14
+     * {@code SidePanel} component's property types.
15
+     *
16
+     * @static
17
+     */
18
+    static propTypes = {
19
+        dispatch: PropTypes.func
20
+    };
21
+
22
+    /**
23
+     * Initializes a new {@code SidePanel} instance.
24
+     *
25
+     * @param {Object} props - The read-only properties with which the new
26
+     * instance is to be initialized.
27
+     */
28
+    constructor(props) {
29
+        super(props);
30
+
31
+        this._onCloseClick = this._onCloseClick.bind(this);
32
+    }
33
+
34
+    /**
35
+     * Implements React's {@link Component#render()}.
36
+     *
37
+     * @inheritdoc
38
+     * @returns {ReactElement}
39
+     */
40
+    render() {
41
+        return (
42
+            <div id = 'sideToolbarContainer'>
43
+                <div
44
+                    className = 'side-toolbar-close'
45
+                    onClick = { this._onCloseClick }>
46
+                    X
47
+                </div>
48
+            </div>
49
+        );
50
+    }
51
+
52
+    /**
53
+     * Callback invoked to hide {@code SidePanel}.
54
+     *
55
+     * @returns {void}
56
+     */
57
+    _onCloseClick() {
58
+        this.props.dispatch(closePanel());
59
+    }
60
+}
61
+
62
+export default connect()(SidePanel);

+ 1
- 0
react/features/side-panel/components/index.js Näytä tiedosto

@@ -0,0 +1 @@
1
+export { default as SidePanel } from './SidePanel';

+ 6
- 0
react/features/side-panel/index.js Näytä tiedosto

@@ -0,0 +1,6 @@
1
+export * from './actions';
2
+export * from './actionTypes';
3
+export * from './components';
4
+
5
+import './middleware';
6
+import './reducer';

+ 45
- 0
react/features/side-panel/middleware.js Näytä tiedosto

@@ -0,0 +1,45 @@
1
+// @flow
2
+
3
+import { MiddlewareRegistry } from '../base/redux';
4
+
5
+import {
6
+    CLOSE_PANEL,
7
+    TOGGLE_CHAT,
8
+    TOGGLE_PROFILE,
9
+    TOGGLE_SETTINGS
10
+} from './actionTypes';
11
+
12
+declare var APP: Object;
13
+
14
+/**
15
+ * Middleware that catches actions related to the non-reactified web side panel.
16
+ *
17
+ * @param {Store} store - Redux store.
18
+ * @returns {Function}
19
+ */
20
+// eslint-disable-next-line no-unused-vars
21
+MiddlewareRegistry.register(store => next => action => {
22
+    if (typeof APP !== 'object') {
23
+        return next(action);
24
+    }
25
+
26
+    switch (action.type) {
27
+    case CLOSE_PANEL:
28
+        APP.UI.toggleSidePanel(action.current);
29
+        break;
30
+
31
+    case TOGGLE_CHAT:
32
+        APP.UI.toggleChat();
33
+        break;
34
+
35
+    case TOGGLE_PROFILE:
36
+        APP.UI.toggleSidePanel('profile_container');
37
+        break;
38
+
39
+    case TOGGLE_SETTINGS:
40
+        APP.UI.toggleSidePanel('settings_container');
41
+        break;
42
+    }
43
+
44
+    return next(action);
45
+});

+ 18
- 0
react/features/side-panel/reducer.js Näytä tiedosto

@@ -0,0 +1,18 @@
1
+import { ReducerRegistry } from '../base/redux';
2
+
3
+import { SET_VISIBLE_PANEL } from './actionTypes';
4
+
5
+/**
6
+ * Reduces the Redux actions of the feature features/side-panel.
7
+ */
8
+ReducerRegistry.register('features/side-panel', (state = {}, action) => {
9
+    switch (action.type) {
10
+    case SET_VISIBLE_PANEL:
11
+        return {
12
+            ...state,
13
+            current: action.current
14
+        };
15
+    }
16
+
17
+    return state;
18
+});

+ 22
- 0
react/features/toolbox/actionTypes.js Näytä tiedosto

@@ -7,6 +7,17 @@
7 7
  */
8 8
 export const CLEAR_TOOLBOX_TIMEOUT = Symbol('CLEAR_TOOLBOX_TIMEOUT');
9 9
 
10
+/**
11
+ * The type of (redux) action which updates whether the conference is or is not
12
+ * currently in full screen view.
13
+ *
14
+ * {
15
+ *     type: FULL_SCREEN_CHANGED,
16
+ *     fullScreen: boolean
17
+ * }
18
+ */
19
+export const FULL_SCREEN_CHANGED = Symbol('FULL_SCREEN_CHANGED');
20
+
10 21
 /**
11 22
  * The type of the action which sets the default toolbar buttons of the Toolbox.
12 23
  *
@@ -19,6 +30,17 @@ export const CLEAR_TOOLBOX_TIMEOUT = Symbol('CLEAR_TOOLBOX_TIMEOUT');
19 30
 export const SET_DEFAULT_TOOLBOX_BUTTONS
20 31
     = Symbol('SET_DEFAULT_TOOLBOX_BUTTONS');
21 32
 
33
+/**
34
+ * The type of (redux) action which requests full screen mode be entered or
35
+ * exited.
36
+ *
37
+ * {
38
+ *     type: SET_FULL_SCREEN,
39
+ *     fullScreen: boolean
40
+ * }
41
+ */
42
+export const SET_FULL_SCREEN = Symbol('SET_FULL_SCREEN');
43
+
22 44
 /**
23 45
  * The type of the action which sets the conference subject.
24 46
  *

+ 4
- 3
react/features/toolbox/actions.native.js Näytä tiedosto

@@ -229,9 +229,10 @@ export function toggleFullScreen(isFullScreen: boolean): Function {
229 229
         const buttonName = 'fullscreen';
230 230
         const button = getButton(buttonName, getState());
231 231
 
232
-        button.toggled = isFullScreen;
233
-
234
-        dispatch(setToolbarButton(buttonName, button));
232
+        if (button) {
233
+            button.toggled = isFullScreen;
234
+            dispatch(setToolbarButton(buttonName, button));
235
+        }
235 236
     };
236 237
 }
237 238
 

+ 38
- 20
react/features/toolbox/actions.web.js Näytä tiedosto

@@ -14,10 +14,13 @@ import {
14 14
     setToolboxTimeout,
15 15
     setToolboxTimeoutMS,
16 16
     setToolboxVisible,
17
-    toggleFullScreen,
18 17
     toggleToolbarButton
19 18
 } from './actions.native';
20
-import { SET_DEFAULT_TOOLBOX_BUTTONS } from './actionTypes';
19
+import {
20
+    FULL_SCREEN_CHANGED,
21
+    SET_DEFAULT_TOOLBOX_BUTTONS,
22
+    SET_FULL_SCREEN
23
+} from './actionTypes';
21 24
 import {
22 25
     getButton,
23 26
     getDefaultToolboxButtons,
@@ -95,6 +98,23 @@ export function dockToolbox(dock: boolean): Function {
95 98
     };
96 99
 }
97 100
 
101
+/**
102
+ * Signals that full screen mode has been entered or exited.
103
+ *
104
+ * @param {boolean} fullScreen - Whether or not full screen mode is currently
105
+ * enabled.
106
+ * @returns {{
107
+ *     type: FULL_SCREEN_CHANGED,
108
+ *     fullScreen: boolean
109
+ * }}
110
+ */
111
+export function fullScreenChanged(fullScreen: boolean) {
112
+    return {
113
+        type: FULL_SCREEN_CHANGED,
114
+        fullScreen
115
+    };
116
+}
117
+
98 118
 /**
99 119
  * Returns button on mount/unmount handlers with dispatch function stored in
100 120
  * closure.
@@ -106,8 +126,6 @@ export function dockToolbox(dock: boolean): Function {
106 126
 function _getButtonHandlers(dispatch) {
107 127
     const localRaiseHandHandler
108 128
         = (...args) => dispatch(changeLocalRaiseHand(...args));
109
-    const toggleFullScreenHandler
110
-        = (...args) => dispatch(toggleFullScreen(...args));
111 129
 
112 130
     return {
113 131
         /**
@@ -119,22 +137,6 @@ function _getButtonHandlers(dispatch) {
119 137
             onMount: () => dispatch(showDesktopSharingButton())
120 138
         },
121 139
 
122
-        /**
123
-         * Mount/Unmount handler for toggling fullscreen button.
124
-         *
125
-         * @type {Object}
126
-         */
127
-        fullscreen: {
128
-            onMount: () =>
129
-                APP.UI.addListener(
130
-                    UIEvents.FULLSCREEN_TOGGLED,
131
-                    toggleFullScreenHandler),
132
-            onUnmount: () =>
133
-                APP.UI.removeListener(
134
-                    UIEvents.FULLSCREEN_TOGGLED,
135
-                    toggleFullScreenHandler)
136
-        },
137
-
138 140
         /**
139 141
          * Mount/Unmount handlers for raisehand button.
140 142
          *
@@ -291,6 +293,22 @@ export function showDialPadButton(show: boolean): Function {
291 293
     };
292 294
 }
293 295
 
296
+/**
297
+ * Signals a request to enter or exit full screen mode.
298
+ *
299
+ * @param {boolean} fullScreen - True to enter full screen mode, false to exit.
300
+ * @returns {{
301
+ *     type: SET_FULL_SCREEN,
302
+ *     fullScreen: boolean
303
+ * }}
304
+ */
305
+export function setFullScreen(fullScreen: boolean) {
306
+    return {
307
+        type: SET_FULL_SCREEN,
308
+        fullScreen
309
+    };
310
+}
311
+
294 312
 /**
295 313
  * Shows recording button.
296 314
  *

+ 106
- 0
react/features/toolbox/components/OverflowMenuButton.web.js Näytä tiedosto

@@ -0,0 +1,106 @@
1
+import InlineDialog from '@atlaskit/inline-dialog';
2
+import PropTypes from 'prop-types';
3
+import React, { Component } from 'react';
4
+
5
+import { translate } from '../../base/i18n';
6
+
7
+import ToolbarButtonV2 from './ToolbarButtonV2';
8
+
9
+/**
10
+ * A React {@code Component} for opening or closing the {@code OverflowMenu}.
11
+ *
12
+ * @extends Component
13
+ */
14
+class OverflowMenuButton extends Component {
15
+    /**
16
+     * {@code OverflowMenuButton} component's property types.
17
+     *
18
+     * @static
19
+     */
20
+    static propTypes = {
21
+        /**
22
+         * A child React Element to display within {@code InlineDialog}.
23
+         */
24
+        children: PropTypes.object,
25
+
26
+        /**
27
+         * Whether or not the OverflowMenu popover should display.
28
+         */
29
+        isOpen: PropTypes.bool,
30
+
31
+        /**
32
+         * Calback to change the visiblility of the overflow menu.
33
+         */
34
+        onVisibilityChange: PropTypes.func,
35
+
36
+        /**
37
+         * Invoked to obtain translated strings.
38
+         */
39
+        t: PropTypes.func
40
+    };
41
+
42
+    /**
43
+     * Initializes a new {@code OverflowMenuButton} instance.
44
+     *
45
+     * @param {Object} props - The read-only properties with which the new
46
+     * instance is to be initialized.
47
+     */
48
+    constructor(props) {
49
+        super(props);
50
+
51
+        // Bind event handlers so they are only bound once per instance.
52
+        this._onCloseDialog = this._onCloseDialog.bind(this);
53
+        this._onToggleDialogVisibility
54
+            = this._onToggleDialogVisibility.bind(this);
55
+    }
56
+
57
+    /**
58
+     * Implements React's {@link Component#render()}.
59
+     *
60
+     * @inheritdoc
61
+     * @returns {ReactElement}
62
+     */
63
+    render() {
64
+        const { children, isOpen, t } = this.props;
65
+        const iconClasses = `icon-thumb-menu ${isOpen ? 'toggled' : ''}`;
66
+
67
+        return (
68
+            <div className = 'toolbox-button-wth-dialog'>
69
+                <InlineDialog
70
+                    content = { children }
71
+                    isOpen = { isOpen }
72
+                    onClose = { this._onCloseDialog }
73
+                    position = { 'top right' }>
74
+                    <ToolbarButtonV2
75
+                        iconName = { iconClasses }
76
+                        onClick = { this._onToggleDialogVisibility }
77
+                        tooltip = { t('toolbar.moreActions') } />
78
+                </InlineDialog>
79
+            </div>
80
+        );
81
+    }
82
+
83
+    /**
84
+     * Callback invoked when {@code InlineDialog} signals that it should be
85
+     * close.
86
+     *
87
+     * @private
88
+     * @returns {void}
89
+     */
90
+    _onCloseDialog() {
91
+        this.props.onVisibilityChange(false);
92
+    }
93
+
94
+    /**
95
+     * Callback invoked to signal that an event has occurred that should change
96
+     * the visibility of the {@code InlineDialog} component.
97
+     *
98
+     * @private
99
+     * @returns {void}
100
+     */
101
+    _onToggleDialogVisibility() {
102
+        this.props.onVisibilityChange(!this.props.isOpen);
103
+    }
104
+}
105
+
106
+export default translate(OverflowMenuButton);

+ 53
- 0
react/features/toolbox/components/OverflowMenuItem.web.js Näytä tiedosto

@@ -0,0 +1,53 @@
1
+import PropTypes from 'prop-types';
2
+import React, { Component } from 'react';
3
+
4
+/**
5
+ * A React {@code Component} for displaying a link to interact with other
6
+ * features of the application.
7
+ *
8
+ * @extends Component
9
+ */
10
+class OverflowMenuItem extends Component {
11
+    /**
12
+     * {@code OverflowMenuItem} component's property types.
13
+     *
14
+     * @static
15
+     */
16
+    static propTypes = {
17
+        /**
18
+         * The icon class to use for displaying an icon before the link text.
19
+         */
20
+        icon: PropTypes.string,
21
+
22
+        /**
23
+         * The callback to invoke when {@code OverflowMenuItem} is clicked.
24
+         */
25
+        onClick: PropTypes.func,
26
+
27
+        /**
28
+         * The text to display in the {@code OverflowMenuItem}.
29
+         */
30
+        text: PropTypes.string
31
+    };
32
+
33
+    /**
34
+     * Implements React's {@link Component#render()}.
35
+     *
36
+     * @inheritdoc
37
+     * @returns {ReactElement}
38
+     */
39
+    render() {
40
+        return (
41
+            <li
42
+                className = 'overflow-menu-item'
43
+                onClick = { this.props.onClick }>
44
+                <span className = 'overflow-menu-item-icon'>
45
+                    <i className = { this.props.icon } />
46
+                </span>
47
+                { this.props.text }
48
+            </li>
49
+        );
50
+    }
51
+}
52
+
53
+export default OverflowMenuItem;

+ 0
- 0
react/features/toolbox/components/OverflowMenuProfileItem.native.js Näytä tiedosto


+ 119
- 0
react/features/toolbox/components/OverflowMenuProfileItem.web.js Näytä tiedosto

@@ -0,0 +1,119 @@
1
+/* globals interfaceConfig */
2
+
3
+import PropTypes from 'prop-types';
4
+import React, { Component } from 'react';
5
+import { connect } from 'react-redux';
6
+
7
+import {
8
+    Avatar,
9
+    getAvatarURL,
10
+    getLocalParticipant
11
+} from '../../base/participants';
12
+
13
+/**
14
+ * A React {@code Component} for displaying a link with a profile avatar as an
15
+ * icon.
16
+ *
17
+ * @extends Component
18
+ */
19
+class OverflowMenuProfileItem extends Component {
20
+    /**
21
+     * {@code OverflowMenuProfileItem}'s property types.
22
+     *
23
+     * @static
24
+     */
25
+    static propTypes = {
26
+        /**
27
+         * The redux representation of the local participant.
28
+         */
29
+        _localParticipant: PropTypes.object,
30
+
31
+        /**
32
+         * Whether the button support clicking or not.
33
+         */
34
+        _unclickable: PropTypes.bool,
35
+
36
+        /**
37
+         * The callback to invoke when {@code OverflowMenuProfileItem} is
38
+         * clicked.
39
+         */
40
+        onClick: PropTypes.func
41
+    };
42
+
43
+    /**
44
+     * Initializes a new {@code OverflowMenuProfileItem} instance.
45
+     *
46
+     * @param {Object} props - The read-only properties with which the new
47
+     * instance is to be initialized.
48
+     */
49
+    constructor(props) {
50
+        super(props);
51
+
52
+        // Bind event handler so it is only bound once for every instance.
53
+        this._onClick = this._onClick.bind(this);
54
+    }
55
+
56
+    /**
57
+     * Implements React's {@link Component#render()}.
58
+     *
59
+     * @inheritdoc
60
+     * @returns {ReactElement}
61
+     */
62
+    render() {
63
+        const { _localParticipant, _unclickable } = this.props;
64
+        const classNames = `overflow-menu-item ${
65
+            _unclickable ? 'unclickable' : ''}`;
66
+        const avatarURL = getAvatarURL(_localParticipant);
67
+        let displayName;
68
+
69
+        if (_localParticipant && _localParticipant.name) {
70
+            displayName = _localParticipant.name.split(' ')[0];
71
+        } else {
72
+            displayName = interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME;
73
+        }
74
+
75
+        return (
76
+            <li
77
+                className = { classNames }
78
+                onClick = { this._onClick }>
79
+                <span className = 'overflow-menu-item-icon'>
80
+                    <Avatar uri = { avatarURL } />
81
+                </span>
82
+                <span className = 'profile-text'>
83
+                    { displayName }
84
+                </span>
85
+            </li>
86
+        );
87
+    }
88
+
89
+    /**
90
+     * Invokes an on click callback if clicking is allowed.
91
+     *
92
+     * @returns {void}
93
+     */
94
+    _onClick() {
95
+        if (!this.props._unclickable) {
96
+            this.props.onClick();
97
+        }
98
+    }
99
+}
100
+
101
+/**
102
+ * Maps (parts of) the Redux state to the associated
103
+ * {@code OverflowMenuProfileItem} component's props.
104
+ *
105
+ * @param {Object} state - The Redux state.
106
+ * @private
107
+ * @returns {{
108
+ *     _localParticipant: Object,
109
+ *     _unclickable: boolean
110
+ * }}
111
+ */
112
+function _mapStateToProps(state) {
113
+    return {
114
+        _localParticipant: getLocalParticipant(state),
115
+        _unclickable: !state['features/base/jwt'].isGuest
116
+    };
117
+}
118
+
119
+export default connect(_mapStateToProps)(OverflowMenuProfileItem);

+ 0
- 0
react/features/toolbox/components/ToolbarButtonV2.native.js Näytä tiedosto


+ 78
- 0
react/features/toolbox/components/ToolbarButtonV2.web.js Näytä tiedosto

@@ -0,0 +1,78 @@
1
+import Tooltip from '@atlaskit/tooltip';
2
+import PropTypes from 'prop-types';
3
+import React from 'react';
4
+
5
+import AbstractToolbarButton from './AbstractToolbarButton';
6
+
7
+/**
8
+ * Represents a button in the toolbar.
9
+ *
10
+ * @extends AbstractToolbarButton
11
+ */
12
+class ToolbarButtonV2 extends AbstractToolbarButton {
13
+    /**
14
+     * Default values for {@code ToolbarButtonV2} component's properties.
15
+     *
16
+     * @static
17
+     */
18
+    static defaultProps = {
19
+        tooltipPosition: 'top'
20
+    };
21
+
22
+    /**
23
+     * {@code ToolbarButtonV2} component's property types.
24
+     *
25
+     * @static
26
+     */
27
+    static propTypes = {
28
+        ...AbstractToolbarButton.propTypes,
29
+
30
+        /**
31
+         * The text to display in the tooltip.
32
+         */
33
+        tooltip: PropTypes.string,
34
+
35
+        /**
36
+         * From which direction the tooltip should appear, relative to the
37
+         * button.
38
+         */
39
+        tooltipPosition: PropTypes.string
40
+    }
41
+
42
+    /**
43
+     * Renders the button of this {@code ToolbarButton}.
44
+     *
45
+     * @param {Object} children - The children, if any, to be rendered inside
46
+     * the button. Presumably, contains the icon of this {@code ToolbarButton}.
47
+     * @protected
48
+     * @returns {ReactElement} The button of this {@code ToolbarButton}.
49
+     */
50
+    _renderButton(children) {
51
+        return (
52
+            <div
53
+                className = 'toolbox-button'
54
+                onClick = { this.props.onClick }>
55
+                <Tooltip
56
+                    description = { this.props.tooltip }
57
+                    position = { this.props.tooltipPosition }>
58
+                    { children }
59
+                </Tooltip>
60
+            </div>
61
+        );
62
+    }
63
+
64
+    /**
65
+     * Renders the icon of this {@code ToolbarButton}.
66
+     *
67
+     * @inheritdoc
68
+     */
69
+    _renderIcon() {
70
+        return (
71
+            <div className = 'toolbox-icon'>
72
+                <i className = { this.props.iconName } />
73
+            </div>
74
+        );
75
+    }
76
+}
77
+
78
+export default ToolbarButtonV2;

+ 6
- 101
react/features/toolbox/components/Toolbox.native.js Näytä tiedosto

@@ -4,26 +4,16 @@ import React, { Component } from 'react';
4 4
 import { View } from 'react-native';
5 5
 import { connect } from 'react-redux';
6 6
 
7
-import {
8
-    AUDIO_MUTE,
9
-    VIDEO_MUTE,
10
-    createToolbarEvent,
11
-    sendAnalytics
12
-} from '../../analytics';
13 7
 import { toggleAudioOnly } from '../../base/conference';
14 8
 import {
15 9
     MEDIA_TYPE,
16
-    setAudioMuted,
17
-    setVideoMuted,
18
-    toggleCameraFacingMode,
19
-    VIDEO_MUTISM_AUTHORITY
10
+    toggleCameraFacingMode
20 11
 } from '../../base/media';
21 12
 import { Container } from '../../base/react';
22 13
 import {
23 14
     isNarrowAspectRatio,
24 15
     makeAspectRatioAware
25 16
 } from '../../base/responsive-ui';
26
-import { ColorPalette } from '../../base/styles';
27 17
 import {
28 18
     EnterPictureInPictureToolbarButton
29 19
 } from '../../mobile/picture-in-picture';
@@ -39,6 +29,8 @@ import AudioRouteButton from './AudioRouteButton';
39 29
 import styles from './styles';
40 30
 import ToolbarButton from './ToolbarButton';
41 31
 
32
+import { AudioMuteButton, HangupButton, VideoMuteButton } from './buttons';
33
+
42 34
 /**
43 35
  * The indicator which determines (at bundle time) whether there should be a
44 36
  * {@code ToolbarButton} in {@code Toolbox} to expose the functionality of the
@@ -118,20 +110,6 @@ type Props = {
118 110
  * Implements the conference toolbox on React Native.
119 111
  */
120 112
 class Toolbox extends Component<Props> {
121
-    /**
122
-     * Initializes a new {@code Toolbox} instance.
123
-     *
124
-     * @param {Props} props - The read-only React {@code Component} props with
125
-     * which the new instance is to be initialized.
126
-     */
127
-    constructor(props: Props) {
128
-        super(props);
129
-
130
-        // Bind event handlers so they are only bound once per instance.
131
-        this._onToggleAudio = this._onToggleAudio.bind(this);
132
-        this._onToggleVideo = this._onToggleVideo.bind(this);
133
-    }
134
-
135 113
     /**
136 114
      * Implements React's {@link Component#render()}.
137 115
      *
@@ -194,64 +172,6 @@ class Toolbox extends Component<Props> {
194 172
         };
195 173
     }
196 174
 
197
-    _onToggleAudio: () => void;
198
-
199
-    /**
200
-     * Dispatches an action to toggle the mute state of the audio/microphone.
201
-     *
202
-     * @private
203
-     * @returns {void}
204
-     */
205
-    _onToggleAudio() {
206
-        const mute = !this.props._audioMuted;
207
-
208
-        sendAnalytics(createToolbarEvent(
209
-            AUDIO_MUTE,
210
-            {
211
-                enable: mute
212
-            }));
213
-
214
-        // The user sees the reality i.e. the state of base/tracks and intends
215
-        // to change reality by tapping on the respective button i.e. the user
216
-        // sets the state of base/media. Whether the user's intention will turn
217
-        // into reality is a whole different story which is of no concern to the
218
-        // tapping.
219
-        this.props.dispatch(
220
-            setAudioMuted(
221
-                mute,
222
-                VIDEO_MUTISM_AUTHORITY.USER,
223
-                /* ensureTrack */ true));
224
-    }
225
-
226
-    _onToggleVideo: () => void;
227
-
228
-    /**
229
-     * Dispatches an action to toggle the mute state of the video/camera.
230
-     *
231
-     * @private
232
-     * @returns {void}
233
-     */
234
-    _onToggleVideo() {
235
-        const mute = !this.props._videoMuted;
236
-
237
-        sendAnalytics(createToolbarEvent(
238
-            VIDEO_MUTE,
239
-            {
240
-                enable: mute
241
-            }));
242
-
243
-        // The user sees the reality i.e. the state of base/tracks and intends
244
-        // to change reality by tapping on the respective button i.e. the user
245
-        // sets the state of base/media. Whether the user's intention will turn
246
-        // into reality is a whole different story which is of no concern to the
247
-        // tapping.
248
-        this.props.dispatch(
249
-            setVideoMuted(
250
-                !this.props._videoMuted,
251
-                VIDEO_MUTISM_AUTHORITY.USER,
252
-                /* ensureTrack */ true));
253
-    }
254
-
255 175
     /**
256 176
      * Renders the toolbar which contains the primary buttons such as hangup,
257 177
      * audio and video mute.
@@ -269,24 +189,9 @@ class Toolbox extends Component<Props> {
269 189
             <View
270 190
                 key = 'primaryToolbar'
271 191
                 style = { styles.primaryToolbar }>
272
-                <ToolbarButton
273
-                    iconName = { audioButtonStyles.iconName }
274
-                    iconStyle = { audioButtonStyles.iconStyle }
275
-                    onClick = { this._onToggleAudio }
276
-                    style = { audioButtonStyles.style } />
277
-                <ToolbarButton
278
-                    accessibilityLabel = 'Hangup'
279
-                    iconName = 'hangup'
280
-                    iconStyle = { styles.whitePrimaryToolbarButtonIcon }
281
-                    onClick = { this.props._onHangup }
282
-                    style = { styles.hangup }
283
-                    underlayColor = { ColorPalette.buttonUnderlay } />
284
-                <ToolbarButton
285
-                    disabled = { this.props._audioOnly }
286
-                    iconName = { videoButtonStyles.iconName }
287
-                    iconStyle = { videoButtonStyles.iconStyle }
288
-                    onClick = { this._onToggleVideo }
289
-                    style = { videoButtonStyles.style } />
192
+                <AudioMuteButton buttonStyles = { audioButtonStyles } />
193
+                <HangupButton />
194
+                <VideoMuteButton buttonStyles = { videoButtonStyles } />
290 195
             </View>
291 196
         );
292 197
 

+ 2
- 23
react/features/toolbox/components/Toolbox.web.js Näytä tiedosto

@@ -5,8 +5,7 @@ import React, { Component } from 'react';
5 5
 import { connect } from 'react-redux';
6 6
 
7 7
 import {
8
-    setDefaultToolboxButtons,
9
-    setToolboxAlwaysVisible
8
+    setDefaultToolboxButtons
10 9
 } from '../actions';
11 10
 import {
12 11
     abstractMapStateToProps
@@ -39,11 +38,6 @@ class Toolbox extends Component<*> {
39 38
          */
40 39
         _setDefaultToolboxButtons: PropTypes.func,
41 40
 
42
-        /**
43
-         * Handler dispatching reset always visible toolbox action.
44
-         */
45
-        _setToolboxAlwaysVisible: PropTypes.func,
46
-
47 41
         /**
48 42
          * Represents conference subject.
49 43
          */
@@ -67,8 +61,6 @@ class Toolbox extends Component<*> {
67 61
      * @returns {void}
68 62
      */
69 63
     componentDidMount(): void {
70
-        this.props._setToolboxAlwaysVisible();
71
-
72 64
         // FIXME The redux action SET_DEFAULT_TOOLBOX_BUTTONS and related source
73 65
         // code such as the redux action creator setDefaultToolboxButtons and
74 66
         // _setDefaultToolboxButtons were introduced to solve the following bug
@@ -168,8 +160,7 @@ class Toolbox extends Component<*> {
168 160
  *
169 161
  * @param {Function} dispatch - Redux action dispatcher.
170 162
  * @returns {{
171
- *     _setDefaultToolboxButtons: Function,
172
- *     _setToolboxAlwaysVisible: Function
163
+ *     _setDefaultToolboxButtons: Function
173 164
  * }}
174 165
  * @private
175 166
  */
@@ -182,18 +173,6 @@ function _mapDispatchToProps(dispatch: Function): Object {
182 173
          */
183 174
         _setDefaultToolboxButtons() {
184 175
             dispatch(setDefaultToolboxButtons());
185
-        },
186
-
187
-        /**
188
-         * Dispatches a (redux) action to reset the permanent visibility of
189
-         * the Toolbox.
190
-         *
191
-         * @returns {Object} Dispatched action.
192
-         */
193
-        _setToolboxAlwaysVisible() {
194
-            dispatch(setToolboxAlwaysVisible(
195
-                config.alwaysVisibleToolbar === true
196
-                    || interfaceConfig.filmStripOnly));
197 176
         }
198 177
     };
199 178
 }

+ 0
- 0
react/features/toolbox/components/ToolboxFilmstrip.native.js Näytä tiedosto


+ 95
- 0
react/features/toolbox/components/ToolboxFilmstrip.web.js Näytä tiedosto

@@ -0,0 +1,95 @@
1
+// @flow
2
+
3
+import React, { Component } from 'react';
4
+import { connect } from 'react-redux';
5
+
6
+import { createToolbarEvent, sendAnalytics } from '../../analytics';
7
+import { translate } from '../../base/i18n';
8
+import { openDeviceSelectionDialog } from '../../device-selection';
9
+
10
+import ToolbarButtonV2 from './ToolbarButtonV2';
11
+import { AudioMuteButton, HangupButton, VideoMuteButton } from './buttons';
12
+
13
+declare var interfaceConfig: Object;
14
+
15
+/**
16
+ * Implements the conference toolbox on React/Web for filmstrip only mode.
17
+ *
18
+ * @extends Component
19
+ */
20
+class ToolboxFilmstrip extends Component<*> {
21
+    _visibleButtons: Object;
22
+
23
+    /**
24
+     * Initializes a new {@code ToolboxFilmstrip} instance.
25
+     *
26
+     * @param {Props} props - The read-only React {@code Component} props with
27
+     * which the new instance is to be initialized.
28
+     */
29
+    constructor(props) {
30
+        super(props);
31
+
32
+        this._visibleButtons = new Set(interfaceConfig.TOOLBAR_BUTTONS);
33
+
34
+        // Bind event handlers so they are only bound once per instance.
35
+        this._onToolbarOpenSettings = this._onToolbarOpenSettings.bind(this);
36
+    }
37
+
38
+    /**
39
+     * Implements React's {@link Component#render()}.
40
+     *
41
+     * @inheritdoc
42
+     * @returns {ReactElement}
43
+     */
44
+    render() {
45
+        const { t } = this.props;
46
+
47
+        return (
48
+            <div className = 'filmstrip-toolbox'>
49
+                { this._shouldShowButton('microphone')
50
+                    && <AudioMuteButton tooltipPosition = 'left' /> }
51
+                { this._shouldShowButton('camera')
52
+                    && <VideoMuteButton tooltipPosition = 'left' /> }
53
+                { this._shouldShowButton('fodeviceselection')
54
+                    && <ToolbarButtonV2
55
+                        iconName = 'icon-settings'
56
+                        onClick = { this._onToolbarOpenSettings }
57
+                        tooltip = { t('toolbar.Settings') }
58
+                        tooltipPosition = 'left' /> }
59
+                { this._shouldShowButton('hangup')
60
+                    && <HangupButton tooltipPosition = 'left' /> }
61
+            </div>
62
+        );
63
+    }
64
+
65
+    _onToolbarOpenSettings: () => void;
66
+
67
+    /**
68
+     * Creates an analytics toolbar event for and dispatches an action to open
69
+     * the device selection popup dialog.
70
+     *
71
+     * @private
72
+     * @returns {void}
73
+     */
74
+    _onToolbarOpenSettings() {
75
+        sendAnalytics(createToolbarEvent('filmstrip.only.device.selection'));
76
+
77
+        this.props.dispatch(openDeviceSelectionDialog());
78
+    }
79
+
80
+    _shouldShowButton: (string) => boolean;
81
+
82
+    /**
83
+     * Returns if a button name has been explicitly configured to be displayed.
84
+     *
85
+     * @param {string} buttonName - The name of the button, as expected in
86
+     * {@link intefaceConfig}.
87
+     * @private
88
+     * @returns {boolean} True if the button should be displayed.
89
+     */
90
+    _shouldShowButton(buttonName) {
91
+        return this._visibleButtons.has(buttonName);
92
+    }
93
+}
94
+
95
+export default translate(connect()(ToolboxFilmstrip));

+ 0
- 0
react/features/toolbox/components/ToolboxV2.native.js Näytä tiedosto


+ 1116
- 0
react/features/toolbox/components/ToolboxV2.web.js
File diff suppressed because it is too large
Näytä tiedosto


+ 87
- 0
react/features/toolbox/components/buttons/AbstractAudioMuteButton.js Näytä tiedosto

@@ -0,0 +1,87 @@
1
+// @flow
2
+
3
+import PropTypes from 'prop-types';
4
+import { Component } from 'react';
5
+
6
+import {
7
+    AUDIO_MUTE,
8
+    createToolbarEvent,
9
+    sendAnalytics
10
+} from '../../../analytics';
11
+import {
12
+    VIDEO_MUTISM_AUTHORITY,
13
+    setAudioMuted
14
+} from '../../../base/media';
15
+
16
+/**
17
+ * An abstract implementation of a button for toggling audio mute.
18
+ */
19
+export default class AbstractAudioMuteButton extends Component<*> {
20
+    /**
21
+     * {@code AbstractAudioMuteButton} component's property types.
22
+     *
23
+     * @static
24
+     */
25
+    static propTypes = {
26
+        /**
27
+         * Whether or not the local microphone is muted.
28
+         */
29
+        _audioMuted: PropTypes.bool,
30
+
31
+        /**
32
+         * Invoked to toggle audio mute.
33
+         */
34
+        dispatch: PropTypes.func
35
+    };
36
+
37
+    /**
38
+     * Initializes a new {@code AbstractAudioMuteButton} instance.
39
+     *
40
+     * @param {Props} props - The read-only React {@code Component} props with
41
+     * which the new instance is to be initialized.
42
+     */
43
+    constructor(props: Object) {
44
+        super(props);
45
+
46
+        // Bind event handler so it is only bound once per instance.
47
+        this._onToolbarToggleAudio = this._onToolbarToggleAudio.bind(this);
48
+    }
49
+
50
+    /**
51
+     * Dispatches an action to toggle audio mute.
52
+     *
53
+     * @private
54
+     * @returns {void}
55
+     */
56
+    _doToggleAudio() {
57
+        // The user sees the reality i.e. the state of base/tracks and intends
58
+        // to change reality by tapping on the respective button i.e. the user
59
+        // sets the state of base/media. Whether the user's intention will turn
60
+        // into reality is a whole different story which is of no concern to the
61
+        // tapping.
62
+        this.props.dispatch(
63
+            setAudioMuted(
64
+                !this.props._audioMuted,
65
+                VIDEO_MUTISM_AUTHORITY.USER,
66
+                /* ensureTrack */ true));
67
+    }
68
+
69
+    _onToolbarToggleAudio: () => void;
70
+
71
+    /**
72
+     * Creates an analytics toolbar event and dispatches an action for toggling
73
+     * audio mute.
74
+     *
75
+     * @private
76
+     * @returns {void}
77
+     */
78
+    _onToolbarToggleAudio() {
79
+        sendAnalytics(createToolbarEvent(
80
+            AUDIO_MUTE,
81
+            {
82
+                enable: !this.props._audioMuted
83
+            }));
84
+
85
+        this._doToggleAudio();
86
+    }
87
+}

+ 51
- 0
react/features/toolbox/components/buttons/AbstractHangupButton.js Näytä tiedosto

@@ -0,0 +1,51 @@
1
+// @flow
2
+
3
+import { Component } from 'react';
4
+
5
+import {
6
+    createToolbarEvent,
7
+    sendAnalytics
8
+} from '../../../analytics';
9
+
10
+/**
11
+ * An abstract implementation of a button for leaving the conference.
12
+ */
13
+export default class AbstractHangupButton extends Component<*> {
14
+    /**
15
+     * Initializes a new {@code AbstractHangupButton} instance.
16
+     *
17
+     * @param {Props} props - The read-only React {@code Component} props with
18
+     * which the new instance is to be initialized.
19
+     */
20
+    constructor(props: Object) {
21
+        super(props);
22
+
23
+        // Bind event handler so it is only bound once per instance.
24
+        this._onToolbarHangup = this._onToolbarHangup.bind(this);
25
+    }
26
+
27
+    /**
28
+     * Dispatches an action for leaving the current conference.
29
+     *
30
+     * @private
31
+     * @returns {void}
32
+     */
33
+    _doHangup() {
34
+        /* to be implemented by descendants */
35
+    }
36
+
37
+    _onToolbarHangup: () => void;
38
+
39
+    /**
40
+     * Creates an analytics toolbar event and dispatches an action for leaving
41
+     * the current conference.
42
+     *
43
+     * @private
44
+     * @returns {void}
45
+     */
46
+    _onToolbarHangup() {
47
+        sendAnalytics(createToolbarEvent('hangup'));
48
+
49
+        this._doHangup();
50
+    }
51
+}

+ 88
- 0
react/features/toolbox/components/buttons/AbstractVideoMuteButton.js Näytä tiedosto

@@ -0,0 +1,88 @@
1
+// @flow
2
+
3
+import PropTypes from 'prop-types';
4
+import { Component } from 'react';
5
+
6
+import {
7
+    VIDEO_MUTE,
8
+    createToolbarEvent,
9
+    sendAnalytics
10
+} from '../../../analytics';
11
+import {
12
+    VIDEO_MUTISM_AUTHORITY,
13
+    setVideoMuted
14
+} from '../../../base/media';
15
+
16
+/**
17
+ * An abstract implementation of a button for toggling video mute.
18
+ */
19
+export default class AbstractVideoMuteButton extends Component<*> {
20
+    /**
21
+     * {@code AbstractVideoMuteButton} component's property types.
22
+     *
23
+     * @static
24
+     */
25
+    static propTypes = {
26
+        /**
27
+         * Whether or not the local camera is muted.
28
+         */
29
+        _videoMuted: PropTypes.bool,
30
+
31
+        /**
32
+         * Invoked to toggle video mute.
33
+         */
34
+        dispatch: PropTypes.func
35
+    };
36
+
37
+    /**
38
+     * Initializes a new {@code AbstractVideoMuteButton} instance.
39
+     *
40
+     * @param {Props} props - The read-only React {@code Component} props with
41
+     * which the new instance is to be initialized.
42
+     */
43
+    constructor(props: Object) {
44
+        super(props);
45
+
46
+        // Bind event handler so it is only bound once per instance.
47
+        this._onToolbarToggleVideo = this._onToolbarToggleVideo.bind(this);
48
+    }
49
+
50
+    /**
51
+     * Dispatches an action to toggle the mute state of the video/camera.
52
+     *
53
+     * @private
54
+     * @returns {void}
55
+     */
56
+    _doToggleVideo() {
57
+        // The user sees the reality i.e. the state of base/tracks and intends
58
+        // to change reality by tapping on the respective button i.e. the user
59
+        // sets the state of base/media. Whether the user's intention will turn
60
+        // into reality is a whole different story which is of no concern to the
61
+        // tapping.
62
+        this.props.dispatch(
63
+            setVideoMuted(
64
+                !this.props._videoMuted,
65
+                VIDEO_MUTISM_AUTHORITY.USER,
66
+                /* ensureTrack */ true));
67
+    }
68
+
69
+
70
+    _onToolbarToggleVideo: () => void;
71
+
72
+    /**
73
+     * Creates an analytics toolbar event and dispatches an action for toggling
74
+     * video mute.
75
+     *
76
+     * @private
77
+     * @returns {void}
78
+     */
79
+    _onToolbarToggleVideo() {
80
+        sendAnalytics(createToolbarEvent(
81
+            VIDEO_MUTE,
82
+            {
83
+                enable: !this.props._videoMuted
84
+            }));
85
+
86
+        this._doToggleVideo();
87
+    }
88
+}

+ 73
- 0
react/features/toolbox/components/buttons/AudioMuteButton.native.js Näytä tiedosto

@@ -0,0 +1,73 @@
1
+// @flow
2
+
3
+import PropTypes from 'prop-types';
4
+import React from 'react';
5
+import { connect } from 'react-redux';
6
+
7
+import { MEDIA_TYPE } from '../../../base/media';
8
+import { isLocalTrackMuted } from '../../../base/tracks';
9
+
10
+import AbstractAudioMuteButton from './AbstractAudioMuteButton';
11
+import ToolbarButton from '../ToolbarButton';
12
+
13
+/**
14
+ * Component that renders a toolbar button for toggling audio mute.
15
+ *
16
+ * @extends AbstractAudioMuteButton
17
+ */
18
+export class AudioMuteButton extends AbstractAudioMuteButton {
19
+    /**
20
+     * {@code AbstractAudioMuteButton} component's property types.
21
+     *
22
+     * @static
23
+     */
24
+    static propTypes = {
25
+        ...AbstractAudioMuteButton.propTypes,
26
+
27
+        /**
28
+         * Styles to be applied to the button and the icon to show.
29
+         */
30
+        buttonStyles: PropTypes.object
31
+    };
32
+
33
+    /**
34
+     * Implements React's {@link Component#render()}.
35
+     *
36
+     * @inheritdoc
37
+     * @returns {ReactElement}
38
+     */
39
+    render() {
40
+        const { buttonStyles } = this.props;
41
+
42
+        return (
43
+            <ToolbarButton
44
+                iconName = { buttonStyles.iconName }
45
+                iconStyle = { buttonStyles.iconStyle }
46
+                onClick = { this._onToolbarToggleAudio }
47
+                style = { buttonStyles.style } />
48
+        );
49
+    }
50
+
51
+    _onToolbarToggleAudio: () => void;
52
+}
53
+
54
+/**
55
+ * Maps (parts of) the Redux state to the associated props for the
56
+ * {@code AudioMuteButton} component.
57
+ *
58
+ * @param {Object} state - The Redux state.
59
+ * @private
60
+ * @returns {{
61
+ *     _audioMuted: boolean,
62
+ * }}
63
+ */
64
+function _mapStateToProps(state) {
65
+    const tracks = state['features/base/tracks'];
66
+
67
+    return {
68
+        _audioMuted: isLocalTrackMuted(tracks, MEDIA_TYPE.AUDIO)
69
+    };
70
+}
71
+
72
+
73
+export default connect(_mapStateToProps)(AudioMuteButton);

+ 166
- 0
react/features/toolbox/components/buttons/AudioMuteButton.web.js Näytä tiedosto

@@ -0,0 +1,166 @@
1
+// @flow
2
+
3
+import PropTypes from 'prop-types';
4
+import React from 'react';
5
+import { connect } from 'react-redux';
6
+
7
+import {
8
+    ACTION_SHORTCUT_TRIGGERED,
9
+    AUDIO_MUTE,
10
+    createShortcutEvent,
11
+    sendAnalytics
12
+} from '../../../analytics';
13
+import { translate } from '../../../base/i18n';
14
+import { MEDIA_TYPE } from '../../../base/media';
15
+import { isLocalTrackMuted } from '../../../base/tracks';
16
+
17
+import AbstractAudioMuteButton from './AbstractAudioMuteButton';
18
+import ToolbarButtonV2 from '../ToolbarButtonV2';
19
+
20
+declare var APP: Object;
21
+
22
+/**
23
+ * Component that renders a toolbar button for toggling audio mute.
24
+ *
25
+ * @extends Component
26
+ */
27
+export class AudioMuteButton extends AbstractAudioMuteButton {
28
+    /**
29
+     * Default values for {@code AudioMuteButton} component's properties.
30
+     *
31
+     * @static
32
+     */
33
+    static defaultProps = {
34
+        tooltipPosition: 'top'
35
+    };
36
+
37
+    /**
38
+     * {@code AudioMuteButton} component's property types.
39
+     *
40
+     * @static
41
+     */
42
+    static propTypes = {
43
+        ...AbstractAudioMuteButton.propTypes,
44
+
45
+        /**
46
+         * The {@code JitsiConference} for the current conference.
47
+         */
48
+        _conference: PropTypes.object,
49
+
50
+        /**
51
+         * Invoked to update the audio mute status.
52
+         */
53
+        dispatch: PropTypes.func,
54
+
55
+        /**
56
+         * Invoked to obtain translated strings.
57
+         */
58
+        t: PropTypes.func,
59
+
60
+        /**
61
+         * Where the tooltip should display, relative to the button.
62
+         */
63
+        tooltipPosition: PropTypes.string
64
+    };
65
+
66
+    /**
67
+     * Initializes a new {@code AudioMuteButton} instance.
68
+     *
69
+     * @param {Props} props - The read-only React {@code Component} props with
70
+     * which the new instance is to be initialized.
71
+     */
72
+    constructor(props: Object) {
73
+        super(props);
74
+
75
+        // Bind event handlers so it is only bound once per instance.
76
+        this._onShortcutToggleAudio = this._onShortcutToggleAudio.bind(this);
77
+    }
78
+
79
+    /**
80
+     * Sets a keyboard shortcuts for toggling audio mute.
81
+     *
82
+     * @inheritdoc
83
+     * @returns {void}
84
+     */
85
+    componentDidMount() {
86
+        APP.keyboardshortcut.registerShortcut(
87
+            'M',
88
+            null,
89
+            this._onShortcutToggleAudio,
90
+            'keyboardShortcuts.mute');
91
+    }
92
+
93
+    /**
94
+     * Removes the registered keyboard shortcut handler.
95
+     *
96
+     * @inheritdoc
97
+     * @returns {void}
98
+     */
99
+    componentWillUnmount() {
100
+        APP.keyboardshortcut.unregisterShortcut('M');
101
+    }
102
+
103
+    /**
104
+     * Implements React's {@link Component#render()}.
105
+     *
106
+     * @inheritdoc
107
+     * @returns {ReactElement}
108
+     */
109
+    render() {
110
+        const { _audioMuted, _conference, t, tooltipPosition } = this.props;
111
+
112
+        return (
113
+            <ToolbarButtonV2
114
+                iconName = { _audioMuted && _conference
115
+                    ? 'icon-mic-disabled toggled'
116
+                    : 'icon-microphone' }
117
+                onClick = { this._onToolbarToggleAudio }
118
+                tooltip = { t('toolbar.mute') }
119
+                tooltipPosition = { tooltipPosition } />
120
+        );
121
+    }
122
+
123
+    _doToggleAudio: () => void;
124
+
125
+    _onShortcutToggleAudio: () => void;
126
+
127
+    /**
128
+     * Creates an analytics keyboard shortcut event and dispatches an action for
129
+     * toggling audio mute.
130
+     *
131
+     * @private
132
+     * @returns {void}
133
+     */
134
+    _onShortcutToggleAudio() {
135
+        sendAnalytics(createShortcutEvent(
136
+            AUDIO_MUTE,
137
+            ACTION_SHORTCUT_TRIGGERED,
138
+            { enable: !this.props._audioMuted }));
139
+
140
+        this._doToggleAudio();
141
+    }
142
+
143
+    _onToolbarToggleAudio: () => void;
144
+}
145
+
146
+/**
147
+ * Maps (parts of) the Redux state to the associated props for the
148
+ * {@code AudioMuteButton} component.
149
+ *
150
+ * @param {Object} state - The Redux state.
151
+ * @private
152
+ * @returns {{
153
+ *     _audioMuted: boolean,
154
+ *     _conference: Object,
155
+ * }}
156
+ */
157
+function _mapStateToProps(state) {
158
+    const tracks = state['features/base/tracks'];
159
+
160
+    return {
161
+        _audioMuted: isLocalTrackMuted(tracks, MEDIA_TYPE.AUDIO),
162
+        _conference: state['features/base/conference'].conference
163
+    };
164
+}
165
+
166
+export default translate(connect(_mapStateToProps)(AudioMuteButton));

+ 63
- 0
react/features/toolbox/components/buttons/HangupButton.native.js Näytä tiedosto

@@ -0,0 +1,63 @@
1
+// @flow
2
+
3
+import PropTypes from 'prop-types';
4
+import React from 'react';
5
+import { connect } from 'react-redux';
6
+
7
+import { appNavigate } from '../../../app';
8
+import { ColorPalette } from '../../../base/styles';
9
+
10
+import AbstractHangupButton from './AbstractHangupButton';
11
+import ToolbarButton from '../ToolbarButton';
12
+import styles from '../styles';
13
+
14
+/**
15
+ * Component that renders a toolbar button for leaving the current conference.
16
+ *
17
+ * @extends Component
18
+ */
19
+class HangupButton extends AbstractHangupButton {
20
+    /**
21
+     * {@code HangupButton} component's property types.
22
+     *
23
+     * @static
24
+     */
25
+    static propTypes = {
26
+        /**
27
+         * Invoked to leave the conference.
28
+         */
29
+        dispatch: PropTypes.func
30
+    };
31
+
32
+    /**
33
+     * Implements React's {@link Component#render()}.
34
+     *
35
+     * @inheritdoc
36
+     * @returns {ReactElement}
37
+     */
38
+    render() {
39
+        return (
40
+            <ToolbarButton
41
+                accessibilityLabel = 'Hangup'
42
+                iconName = 'hangup'
43
+                iconStyle = { styles.whitePrimaryToolbarButtonIcon }
44
+                onClick = { this._onToolbarHangup }
45
+                style = { styles.hangup }
46
+                underlayColor = { ColorPalette.buttonUnderlay } />
47
+        );
48
+    }
49
+
50
+    /**
51
+     * Dispatches an action for leaving the current conference.
52
+     *
53
+     * @private
54
+     * @returns {void}
55
+     */
56
+    _doHangup() {
57
+        this.props.dispatch(appNavigate(undefined));
58
+    }
59
+
60
+    _onToolbarHangup: () => void;
61
+}
62
+
63
+export default connect()(HangupButton);

+ 82
- 0
react/features/toolbox/components/buttons/HangupButton.web.js Näytä tiedosto

@@ -0,0 +1,82 @@
1
+// @flow
2
+
3
+import React from 'react';
4
+import PropTypes from 'prop-types';
5
+
6
+import { connect } from 'react-redux';
7
+
8
+import { disconnect } from '../../../base/connection';
9
+import { translate } from '../../../base/i18n';
10
+
11
+import AbstractHangupButton from './AbstractHangupButton';
12
+import ToolbarButtonV2 from '../ToolbarButtonV2';
13
+
14
+/**
15
+ * Component that renders a toolbar button for leaving the current conference.
16
+ *
17
+ * @extends Component
18
+ */
19
+export class HangupButton extends AbstractHangupButton {
20
+    /**
21
+     * Default values for {@code HangupButton} component's properties.
22
+     *
23
+     * @static
24
+     */
25
+    static defaultProps = {
26
+        tooltipPosition: 'top'
27
+    };
28
+
29
+    /**
30
+     * {@code HangupButton} component's property types.
31
+     *
32
+     * @static
33
+     */
34
+    static propTypes = {
35
+        /**
36
+         * Invoked to trigger conference leave.
37
+         */
38
+        dispatch: PropTypes.func,
39
+
40
+        /**
41
+         * Invoked to obtain translated strings.
42
+         */
43
+        t: PropTypes.func,
44
+
45
+        /**
46
+         * Where the tooltip should display, relative to the button.
47
+         */
48
+        tooltipPosition: PropTypes.string
49
+    }
50
+
51
+    /**
52
+     * Implements React's {@link Component#render()}.
53
+     *
54
+     * @inheritdoc
55
+     * @returns {ReactElement}
56
+     */
57
+    render() {
58
+        const { t, tooltipPosition } = this.props;
59
+
60
+        return (
61
+            <ToolbarButtonV2
62
+                iconName = 'icon-hangup'
63
+                onClick = { this._onToolbarHangup }
64
+                tooltip = { t('toolbar.hangup') }
65
+                tooltipPosition = { tooltipPosition } />
66
+        );
67
+    }
68
+
69
+    _onToolbarHangup: () => void;
70
+
71
+    /**
72
+     * Dispatches an action for leaving the current conference.
73
+     *
74
+     * @private
75
+     * @returns {void}
76
+     */
77
+    _doHangup() {
78
+        this.props.dispatch(disconnect(true));
79
+    }
80
+}
81
+
82
+export default translate(connect()(HangupButton));

+ 82
- 0
react/features/toolbox/components/buttons/VideoMuteButton.native.js Näytä tiedosto

@@ -0,0 +1,82 @@
1
+// @flow
2
+
3
+import PropTypes from 'prop-types';
4
+import React from 'react';
5
+import { connect } from 'react-redux';
6
+
7
+import { MEDIA_TYPE } from '../../../base/media';
8
+import { isLocalTrackMuted } from '../../../base/tracks';
9
+
10
+import AbstractVideoMuteButton from './AbstractVideoMuteButton';
11
+import ToolbarButton from '../ToolbarButton';
12
+
13
+/**
14
+ * Component that renders a toolbar button for toggling video mute.
15
+ *
16
+ * @extends AbstractVideoMuteButton
17
+ */
18
+class VideoMuteButton extends AbstractVideoMuteButton {
19
+    /**
20
+     * {@code VideoMuteButton} component's property types.
21
+     *
22
+     * @static
23
+     */
24
+    static propTypes = {
25
+        ...AbstractVideoMuteButton.propTypes,
26
+
27
+        /**
28
+         * Whether or not the local participant is current in audio only mode.
29
+         * Video mute toggling is disabled in audio only mode.
30
+         */
31
+        _audioOnly: PropTypes.bool,
32
+
33
+        /**
34
+         * Styles to be applied to the button and the icon to show.
35
+         */
36
+        buttonStyles: PropTypes.object
37
+    };
38
+
39
+    /**
40
+     * Implements React's {@link Component#render()}.
41
+     *
42
+     * @inheritdoc
43
+     * @returns {ReactElement}
44
+     */
45
+    render() {
46
+        const { _audioOnly, buttonStyles } = this.props;
47
+
48
+        return (
49
+            <ToolbarButton
50
+                disabled = { _audioOnly }
51
+                iconName = { buttonStyles.iconName }
52
+                iconStyle = { buttonStyles.iconStyle }
53
+                onClick = { this._onToolbarToggleVideo }
54
+                style = { buttonStyles.style } />
55
+        );
56
+    }
57
+
58
+    _onToolbarToggleVideo: () => void;
59
+}
60
+
61
+/**
62
+ * Maps (parts of) the Redux state to the associated props for the
63
+ * {@code VideoMuteButton} component.
64
+ *
65
+ * @param {Object} state - The Redux state.
66
+ * @private
67
+ * @returns {{
68
+ *     _audioOnly: boolean,
69
+ *     _videoMuted: boolean
70
+ * }}
71
+ */
72
+function _mapStateToProps(state) {
73
+    const conference = state['features/base/conference'];
74
+    const tracks = state['features/base/tracks'];
75
+
76
+    return {
77
+        _audioOnly: Boolean(conference.audioOnly),
78
+        _videoMuted: isLocalTrackMuted(tracks, MEDIA_TYPE.VIDEO)
79
+    };
80
+}
81
+
82
+export default connect(_mapStateToProps)(VideoMuteButton);

+ 161
- 0
react/features/toolbox/components/buttons/VideoMuteButton.web.js Näytä tiedosto

@@ -0,0 +1,161 @@
1
+// @flow
2
+
3
+import PropTypes from 'prop-types';
4
+import React from 'react';
5
+import { connect } from 'react-redux';
6
+
7
+import {
8
+    ACTION_SHORTCUT_TRIGGERED,
9
+    VIDEO_MUTE,
10
+    createShortcutEvent,
11
+    sendAnalytics
12
+} from '../../../analytics';
13
+import { translate } from '../../../base/i18n';
14
+import { MEDIA_TYPE } from '../../../base/media';
15
+import { isLocalTrackMuted } from '../../../base/tracks';
16
+
17
+import AbstractVideoMuteButton from './AbstractVideoMuteButton';
18
+import ToolbarButtonV2 from '../ToolbarButtonV2';
19
+
20
+declare var APP: Object;
21
+
22
+/**
23
+ * Component that renders a toolbar button for toggling video mute.
24
+ *
25
+ * @extends AbstractVideoMuteButton
26
+ */
27
+export class VideoMuteButton extends AbstractVideoMuteButton {
28
+    /**
29
+     * Default values for {@code VideoMuteButton} component's properties.
30
+     *
31
+     * @static
32
+     */
33
+    static defaultProps = {
34
+        tooltipPosition: 'top'
35
+    };
36
+
37
+    /**
38
+     * {@code VideoMuteButton} component's property types.
39
+     *
40
+     * @static
41
+     */
42
+    static propTypes = {
43
+        ...AbstractVideoMuteButton.propTypes,
44
+
45
+        /**
46
+         * The {@code JitsiConference} for the current conference.
47
+         */
48
+        _conference: PropTypes.object,
49
+
50
+        /**
51
+         * Invoked to obtain translated strings.
52
+         */
53
+        t: PropTypes.func,
54
+
55
+        /**
56
+         * Where the tooltip should display, relative to the button.
57
+         */
58
+        tooltipPosition: PropTypes.string
59
+    };
60
+
61
+    /**
62
+     * Initializes a new {@code VideoMuteButton} instance.
63
+     *
64
+     * @param {Props} props - The read-only React {@code Component} props with
65
+     * which the new instance is to be initialized.
66
+     */
67
+    constructor(props: Object) {
68
+        super(props);
69
+
70
+        // Bind event handlers so they are only bound once per instance.
71
+        this._onShortcutToggleVideo = this._onShortcutToggleVideo.bind(this);
72
+    }
73
+
74
+    /**
75
+     * Sets a keyboard shortcuts for toggling video mute.
76
+     *
77
+     * @inheritdoc
78
+     * @returns {void}
79
+     */
80
+    componentDidMount() {
81
+        APP.keyboardshortcut.registerShortcut(
82
+            'V',
83
+            null,
84
+            this._onShortcutToggleVideo,
85
+            'keyboardShortcuts.videoMute');
86
+    }
87
+
88
+    /**
89
+     * Removes the registered keyboard shortcut handler.
90
+     *
91
+     * @inheritdoc
92
+     * @returns {void}
93
+     */
94
+    componentWillUnmount() {
95
+        APP.keyboardshortcut.unregisterShortcut('V');
96
+    }
97
+
98
+    /**
99
+     * Implements React's {@link Component#render()}.
100
+     *
101
+     * @inheritdoc
102
+     * @returns {ReactElement}
103
+     */
104
+    render() {
105
+        const { _conference, _videoMuted, t, tooltipPosition } = this.props;
106
+
107
+        return (
108
+            <ToolbarButtonV2
109
+                iconName = { _videoMuted && _conference
110
+                    ? 'icon-camera-disabled toggled'
111
+                    : 'icon-camera' }
112
+                onClick = { this._onToolbarToggleVideo }
113
+                tooltip = { t('toolbar.videomute') }
114
+                tooltipPosition = { tooltipPosition } />
115
+        );
116
+    }
117
+
118
+    _doToggleVideo: () => void;
119
+
120
+    _onShortcutToggleVideo: () => void;
121
+
122
+    /**
123
+     * Creates an analytics keyboard shortcut event for and dispatches an action
124
+     * for toggling video mute.
125
+     *
126
+     * @private
127
+     * @returns {void}
128
+     */
129
+    _onShortcutToggleVideo() {
130
+        sendAnalytics(createShortcutEvent(
131
+            VIDEO_MUTE,
132
+            ACTION_SHORTCUT_TRIGGERED,
133
+            { enable: !this.props._videoMuted }));
134
+
135
+        this._doToggleVideo();
136
+    }
137
+
138
+    _onToolbarToggleVideo: () => void;
139
+}
140
+
141
+/**
142
+ * Maps (parts of) the Redux state to the associated props for the
143
+ * {@code AudioMuteButton} component.
144
+ *
145
+ * @param {Object} state - The Redux state.
146
+ * @private
147
+ * @returns {{
148
+ *     _conference: Object,
149
+ *     _videoMuted: boolean,
150
+ * }}
151
+ */
152
+function _mapStateToProps(state) {
153
+    const tracks = state['features/base/tracks'];
154
+
155
+    return {
156
+        _conference: state['features/base/conference'].conference,
157
+        _videoMuted: isLocalTrackMuted(tracks, MEDIA_TYPE.VIDEO)
158
+    };
159
+}
160
+
161
+export default translate(connect(_mapStateToProps)(VideoMuteButton));

+ 3
- 0
react/features/toolbox/components/buttons/index.js Näytä tiedosto

@@ -0,0 +1,3 @@
1
+export { default as AudioMuteButton } from './AudioMuteButton';
2
+export { default as HangupButton } from './HangupButton';
3
+export { default as VideoMuteButton } from './VideoMuteButton';

+ 3
- 0
react/features/toolbox/components/index.js Näytä tiedosto

@@ -1,4 +1,7 @@
1 1
 export { default as ToolbarButton } from './ToolbarButton';
2
+export { default as ToolbarButtonV2 } from './ToolbarButtonV2';
2 3
 export { default as ToolbarButtonWithDialog }
3 4
     from './ToolbarButtonWithDialog';
4 5
 export { default as Toolbox } from './Toolbox';
6
+export { default as ToolboxFilmstrip } from './ToolboxFilmstrip';
7
+export { default as ToolboxV2 } from './ToolboxV2';

+ 0
- 0
react/features/toolbox/defaultToolbarButtons.web.js Näytä tiedosto


Some files were not shown because too many files changed in this diff

Loading…
Peruuta
Tallenna