Browse Source

React Toolbar

master
Ilya Daynatovich 7 years ago
parent
commit
da4425b5c0
30 changed files with 3226 additions and 1499 deletions
  1. 5
    0
      conference.js
  2. 0
    14
      css/_animations.scss
  3. 4
    1
      css/_notice.scss
  4. 203
    149
      css/_toolbars.scss
  5. 7
    6
      css/_variables.scss
  6. 49
    36
      modules/UI/UI.js
  7. 3
    2
      modules/UI/recording/Recording.js
  8. 5
    4
      modules/UI/shared_video/SharedVideo.js
  9. 6
    6
      modules/UI/side_pannels/chat/Chat.js
  10. 0
    889
      modules/UI/toolbars/Toolbar.js
  11. 0
    149
      modules/UI/toolbars/ToolbarToggler.js
  12. 112
    17
      react/features/conference/components/Conference.native.js
  13. 3
    20
      react/features/conference/components/Conference.web.js
  14. 17
    4
      react/features/film-strip/components/FilmStrip.js
  15. 98
    0
      react/features/toolbar/actionTypes.js
  16. 277
    0
      react/features/toolbar/actions.native.js
  17. 291
    0
      react/features/toolbar/actions.web.js
  18. 0
    165
      react/features/toolbar/components/AbstractToolbar.js
  19. 200
    0
      react/features/toolbar/components/BaseToolbar.web.js
  20. 189
    0
      react/features/toolbar/components/PrimaryToolbar.web.js
  21. 263
    0
      react/features/toolbar/components/SecondaryToolbar.web.js
  22. 161
    36
      react/features/toolbar/components/Toolbar.native.js
  23. 227
    0
      react/features/toolbar/components/Toolbar.web.js
  24. 228
    0
      react/features/toolbar/components/ToolbarButton.web.js
  25. 0
    1
      react/features/toolbar/components/index.js
  26. 389
    0
      react/features/toolbar/defaultToolbarButtons.js
  27. 254
    0
      react/features/toolbar/functions.js
  28. 5
    0
      react/features/toolbar/index.js
  29. 39
    0
      react/features/toolbar/middleware.js
  30. 191
    0
      react/features/toolbar/reducer.js

+ 5
- 0
conference.js View File

@@ -20,6 +20,8 @@ import analytics from './modules/analytics/analytics';
20 20
 
21 21
 import EventEmitter from "events";
22 22
 
23
+import { showDesktopSharingButton } from './react/features/toolbar';
24
+
23 25
 import {
24 26
     AVATAR_ID_COMMAND,
25 27
     AVATAR_URL_COMMAND,
@@ -583,6 +585,9 @@ export default {
583 585
                 APP.connection = connection = con;
584 586
                 this.isDesktopSharingEnabled =
585 587
                     JitsiMeetJS.isDesktopSharingEnabled();
588
+
589
+                APP.store.dispatch(showDesktopSharingButton());
590
+
586 591
                 APP.remoteControl.init();
587 592
                 this._createRoom(tracks);
588 593
 

+ 0
- 14
css/_animations.scss View File

@@ -66,18 +66,4 @@
66 66
 @include keyframes(slideInExtContainer) {
67 67
     from { width: 0; }
68 68
     to { width: $sidebarWidth; }
69
-}
70
-
71
-/**
72
-*   Fade in / out animations
73
-**/
74
-
75
-@include keyframes(fadeIn) {
76
-    from { opacity: 0; }
77
-    to { opacity: 1; }
78
-}
79
-
80
-@include keyframes(fadeOut) {
81
-    from { opacity: 1; }
82
-    to { opacity: 0; }
83 69
 }

+ 4
- 1
css/_notice.scss View File

@@ -1,8 +1,11 @@
1 1
 .notice {
2
-    position: relative;
2
+    position: absolute;
3
+    left: 50%;
3 4
     z-index: $zindex3;
4 5
     margin-top: 6px;
5 6
 
7
+    @include transform(translateX(-50%));
8
+
6 9
     &__message {
7 10
         background-color: #000000;
8 11
         color: white;

+ 203
- 149
css/_toolbars.scss View File

@@ -1,186 +1,236 @@
1
-.toolbar {
2
-    background-color: $toolbarBackground;
1
+/**
2
+ * Round badge.
3
+ */
4
+.badge-round {
5
+    background-color: $toolbarBadgeBackground;
6
+    border-radius: 50%;
7
+    box-sizing: border-box;
8
+    color: $toolbarBadgeColor;
9
+    // Do not inherit the font-family from the toolbar button, because it's an
10
+    // icon style.
11
+    font-family: $baseFontFamily;
12
+    font-size: 9px;
13
+    font-weight: 700;
14
+    line-height: 13px;
15
+    min-width: 13px;
16
+    overflow: hidden;
17
+    text-align: center;
18
+    text-overflow: ellipsis;
19
+    vertical-align: middle;
20
+}
21
+
22
+/**
23
+* Toolbar button styles.
24
+*/
25
+.button {
26
+    color: #FFFFFF;
27
+    cursor: pointer;
28
+    z-index: $zindex1;
29
+    display: inline-block;
30
+    font-size: $toolbarFontSize !important;
31
+    height: 50px;
32
+    line-height: 50px !important;
3 33
     position: relative;
4
-    z-index: $toolbarZ;
5
-    height: 100%;
6
-    pointer-events: auto;
34
+    text-align: center;
35
+    top:0px;
36
+    vertical-align: middle;
37
+    width: 50px;
7 38
 
8
-    /**
9
-     * Splitter button in the toolbar.
10
-     */
11
-    &__splitter {
12
-        display: inline-block;
13
-        vertical-align: middle;
14
-        width: 1px;
15
-        height: 50%;
16
-        margin: 0 $splitterToolbarButtonMargin;
17
-        background: $splitterColor;
39
+    &_hangup {
40
+        color: $hangupColor;
41
+        font-size: $hangupFontSize !important;
42
+    }
43
+
44
+    &[disabled] {
45
+        opacity: 0.5;
46
+    }
47
+
48
+    &:hover, &:active {
49
+        cursor: pointer;
50
+        text-decoration: none;
51
+    }
52
+
53
+    &:not(.toggled) {
54
+        &:hover, &:active {
55
+            // sum opacity with background layer should give us 0.8
56
+            background: $toolbarSelectBackground;
57
+        }
58
+    }
59
+
60
+    &.toggled {
61
+        background: $toolbarToggleBackground;
62
+
63
+        &.icon-camera {
64
+            @extend .icon-camera-disabled;
65
+        }
66
+
67
+        &.icon-full-screen {
68
+            @extend .icon-exit-full-screen;
69
+        }
70
+
71
+        &.icon-microphone {
72
+            @extend .icon-mic-disabled;
73
+        }
74
+    }
75
+
76
+    &.unclickable {
77
+        cursor: default;
78
+
79
+        &:hover, &:active, &.selected {
80
+            background: none;
81
+            cursor: default;
82
+        }
18 83
     }
19 84
 }
20 85
 
21
-#mainToolbarContainer{
86
+.toolbar-container {
22 87
     display: block;
88
+    left:0;
89
+    min-height: 100px;
90
+    opacity: 0;
91
+    pointer-events: none;
23 92
     position: absolute;
93
+    right:0;
24 94
     text-align: center;
25 95
     top:0;
26
-    left:0;
27
-    right:0;
28 96
     z-index: $toolbarZ;
29
-    pointer-events: none;
30
-    min-height: 100px;
31
-    opacity: 0;
32 97
 }
33 98
 
34
-#subject {
99
+/**
100
+* Common toolbar styles.
101
+*/
102
+.toolbar {
103
+    background-color: $toolbarBackground;
104
+    height: 100%;
105
+    pointer-events: auto;
35 106
     position: relative;
36
-    z-index: $zindex3;
37
-    width: auto;
38
-    padding: 5px;
39
-    margin-left: 40%;
40
-    margin-right: 40%;
41
-    text-align: center;
42
-    background: linear-gradient(to bottom, rgba(255,255,255,.85) , rgba(255,255,255,.35));
43
-    box-shadow: 0 0 2px #000000, 0 0 10px #000000;
44
-    border-bottom-left-radius: 12px;
45
-    border-bottom-right-radius: 12px;
46
-}
107
+    z-index: $toolbarZ;
47 108
 
48
-#mainToolbar {
49
-    height: $defaultToolbarSize;
50
-    display: inline-block;
51
-    position: relative;
52
-    top: 30px;
53
-    margin-left: auto;
54
-    margin-right: auto;
55
-    width: auto;
56
-    border-radius: 3px;
57
-    .button:first-child {
58
-        border-bottom-left-radius: 3px;
59
-        border-top-left-radius: 3px;
60
-    }
61
-    .button:last-child {
62
-        border-bottom-right-radius: 3px;
63
-        border-top-right-radius: 3px;
109
+    /**
110
+     * Splitter button in the toolbar.
111
+     */
112
+    &__splitter {
113
+        background: $splitterColor;
114
+        display: inline-block;
115
+        height: 50%;
116
+        margin: 0 $splitterToolbarButtonMargin;
117
+        vertical-align: middle;
118
+        width: 1px;
64 119
     }
65
-}
66 120
 
67
-#extendedToolbar {
68
-    display: -moz-box;
69
-    display: -ms-flexbox;
70
-    display: -webkit-box;
71
-    display: -webkit-flex;
72
-    display: flex;
73
-    width: $defaultToolbarSize;
74
-    height: 100%;
75
-    top: 0;
76
-    left: 0;
77
-    padding-top: 10px;
78
-    box-sizing: border-box;
79
-    flex-direction: column;
80
-    flex-wrap: nowrap;
81
-    justify-content: flex-start;
82
-    align-items: center;
83
-    transform: translateX(-100%);
84
-    -webkit-transform: translateX(-100%);
85
-}
121
+    /**
122
+    * Primary toolbar styles.
123
+    */
124
+    &_primary {
125
+        position: absolute;
126
+        left: 50%;
127
+        top: 30px;
128
+        display: inline-block;
129
+        width: auto;
130
+        height: $defaultToolbarSize;
131
+        border-radius: 3px;
132
+        opacity: 0;
133
+
134
+        @include transform(translateX(-50%));
135
+
136
+        .button:first-child {
137
+            border-bottom-left-radius: 3px;
138
+            border-top-left-radius: 3px;
139
+        }
140
+        .button:last-child {
141
+            border-bottom-right-radius: 3px;
142
+            border-top-right-radius: 3px;
143
+        }
144
+    }
86 145
 
87
-#toolbar_button_hangup {
88
-    color: #BF2117;
89
-    font-size: $hangupFontSize !important;
90
-}
146
+    &_primary a.button:last-child::after {
147
+        content: none;
148
+    }
91 149
 
92
-#toolbar_button_etherpad {
93
-    display: none;
94
-}
150
+    /**
151
+    * Secondary toolbar styles.
152
+    */
153
+    &_secondary {
154
+        position: absolute;
155
+        align-items: center;
156
+        box-sizing: border-box;
157
+        display: -moz-box;
158
+        display: -ms-flexbox;
159
+        display: -webkit-box;
160
+        display: -webkit-flex;
161
+        display: flex;
162
+        flex-direction: column;
163
+        flex-wrap: nowrap;
164
+        height: 100%;
165
+        justify-content: flex-start;
166
+        left: 0;
167
+        padding-top: 10px;
168
+        top: 0;
169
+        transform: translateX(-100%);
170
+        width: $defaultToolbarSize;
171
+        -webkit-transform: translateX(-100%);
172
+
173
+        .button.toggled:not(.icon-raised-hand) {
174
+            background: $toolbarSelectBackground;
175
+            cursor: pointer;
176
+            text-decoration: none;
177
+
178
+            &.unclickable {
179
+                cursor: default;
180
+
181
+                &:hover, &:active, &.selected {
182
+                    background: none;
183
+                    cursor: default;
184
+                }
185
+            }
186
+        }
187
+    }
95 188
 
96
-#mainToolbar a.button:last-child::after {
97
-    content: none;
189
+    /**
190
+     * Toolbar specific round badge.
191
+     */
192
+    .badge-round {
193
+        bottom: 9px;
194
+        position: absolute;
195
+        right: 9px;
196
+    }
98 197
 }
99 198
 
100
-.button {
101
-    display: inline-block;
199
+.subject {
200
+    background: linear-gradient(to bottom, rgba(255,255,255,.85) , rgba(255,255,255,.35));
201
+    border-bottom-left-radius: 12px;
202
+    border-bottom-right-radius: 12px;
203
+    box-shadow: 0 0 2px #000000, 0 0 10px #000000;
204
+    margin-left: 40%;
205
+    margin-right: 40%;
206
+    padding: 5px;
102 207
     position: relative;
103
-    color: #FFFFFF;
104
-    top:0px;
105
-    width: 50px;
106
-    height: 50px;
107
-    cursor: pointer;
108 208
     text-align: center;
109
-    z-index: $zindex1;
110
-    font-size: $toolbarFontSize !important;
111
-    line-height: 50px !important;
112
-    vertical-align: middle;
113
-}
114
-
115
-.button[disabled] {
116
-    opacity: 0.5;
117
-}
118
-
119
-.button.unclickable {
120
-    cursor: default;
121
-}
122
-
123
-.button.toggled {
124
-    background: $toolbarToggleBackground !important;
125
-}
209
+    width: auto;
210
+    z-index: $zindex3;
126 211
 
127
-a.button.unclickable:hover,
128
-a.button.unclickable:active,
129
-a.button.unclickable.selected{
130
-    cursor: default;
131
-    background: none;
132
-}
212
+    &.subject_slide-in {
213
+        top: 80px;
214
+        @include transition(top .3s ease-in);
215
+    }
133 216
 
134
-a.button:hover,
135
-a.button:active,
136
-a.button.selected {
137
-    cursor: pointer;
138
-    text-decoration: none;
139
-    // sum opacity with background layer should give us 0.8
140
-    background: $toolbarSelectBackground;
217
+    &.subject_slide-out {
218
+        top: 0;
219
+        @include transition(top .3s ease-out);
220
+    }
141 221
 }
142 222
 
143 223
 a.button>#avatar {
144
-    width: 30px;
145 224
     border-radius: 50%;
146
-    padding-top: 10px;
147 225
     padding-bottom: 10px;
226
+    padding-top: 10px;
227
+    width: 30px;
148 228
 }
149 229
 
150 230
 #feedbackButton {
151 231
     margin-top: auto;
152 232
 }
153 233
 
154
-/**
155
- * Round badge.
156
- */
157
-.badge-round {
158
-    background-color: $toolbarBadgeBackground;
159
-    color: $toolbarBadgeColor;
160
-    font-size: 9px;
161
-    line-height: 13px;
162
-    font-weight: 700;
163
-    text-align: center;
164
-    border-radius: 50%;
165
-    min-width: 13px;
166
-    overflow: hidden;
167
-    text-overflow: ellipsis;
168
-    box-sizing: border-box;
169
-    vertical-align: middle;
170
-    // Do not inherit the font-family from the toolbar button, because it's an
171
-    // icon style.
172
-    font-family: $baseFontFamily;
173
-}
174
-
175
-/**
176
- * Toolbar specific round badge.
177
- */
178
-.toolbar .badge-round {
179
-    position: absolute;
180
-    right: 9px;
181
-    bottom: 9px;
182
-}
183
-
184 234
 /**
185 235
  * START of slide in animation for extended toolbar.
186 236
  */
@@ -272,9 +322,13 @@ a.button>#avatar {
272 322
  * START of fade in animation for main toolbar
273 323
  */
274 324
 .fadeIn {
275
-    @include animation('fadeIn .3s linear .2s forwards');
325
+    opacity: 1;
326
+
327
+    @include transition(all .3s ease-in);
276 328
 }
277 329
 
278 330
 .fadeOut {
279
-    @include animation('fadeOut .5s linear forwards');
331
+    opacity: 0;
332
+
333
+    @include transition(all .3s ease-out);
280 334
 }

+ 7
- 6
css/_variables.scss View File

@@ -4,13 +4,12 @@
4 4
  * Style variables
5 5
  */
6 6
 $baseFontFamily: 'open_sanslight', 'Helvetica Neue', Helvetica, Arial, sans-serif;
7
-$toolbarFontSize: 1.9em;
7
+$hangupColor: #bf2117;
8 8
 $hangupFontSize: 2em;
9 9
 
10 10
 /**
11 11
  * Size variables.
12 12
  */
13
-$defaultToolbarSize: 50px;
14 13
 
15 14
 // Video layout.
16 15
 $thumbnailToolbarHeight: 22px;
@@ -34,14 +33,16 @@ $tooltipBg: rgba(0,0,0, 0.7);
34 33
 /**
35 34
  * Toolbar
36 35
  */
37
-$toolbarTitleColor: #FFFFFF;
38
-$toolbarTitleFontSize: 19px;
36
+$defaultToolbarSize: 50px;
37
+$splitterToolbarButtonMargin: 18px;
39 38
 $toolbarBackground: rgba(0, 0, 0, 0.5);
40
-$toolbarSelectBackground: rgba(0, 0, 0, .6);
41 39
 $toolbarBadgeBackground: #165ECC;
42 40
 $toolbarBadgeColor: #FFFFFF;
41
+$toolbarFontSize: 1.9em;
42
+$toolbarSelectBackground: rgba(0, 0, 0, .6);
43
+$toolbarTitleColor: #FFFFFF;
44
+$toolbarTitleFontSize: 19px;
43 45
 $toolbarToggleBackground: #12499C;
44
-$splitterToolbarButtonMargin: 18px;
45 46
 
46 47
 /**
47 48
  * Main controls

+ 49
- 36
modules/UI/UI.js View File

@@ -6,8 +6,6 @@ var UI = {};
6 6
 
7 7
 import Chat from "./side_pannels/chat/Chat";
8 8
 import SidePanels from "./side_pannels/SidePanels";
9
-import Toolbar from "./toolbars/Toolbar";
10
-import ToolbarToggler from "./toolbars/ToolbarToggler";
11 9
 import Avatar from "./avatar/Avatar";
12 10
 import SideContainerToggler from "./side_pannels/SideContainerToggler";
13 11
 import UIUtil from "./util/UIUtil";
@@ -25,6 +23,23 @@ import RingOverlay from "./ring_overlay/RingOverlay";
25 23
 import UIErrors from './UIErrors';
26 24
 import { debounce } from "../util/helpers";
27 25
 
26
+import {
27
+    setAudioMuted,
28
+    setVideoMuted
29
+} from '../../react/features/base/media';
30
+import {
31
+    checkAutoEnableDesktopSharing,
32
+    dockToolbar,
33
+    setAudioIconEnabled,
34
+    setToolbarButton,
35
+    setVideoIconEnabled,
36
+    showDialPadButton,
37
+    showEtherpadButton,
38
+    showSharedVideoButton,
39
+    showSIPCallButton,
40
+    showToolbar
41
+} from '../../react/features/toolbar';
42
+
28 43
 var EventEmitter = require("events");
29 44
 UI.messageHandler = require("./util/MessageHandler");
30 45
 var messageHandler = UI.messageHandler;
@@ -83,16 +98,6 @@ JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.CONSTRAINT_FAILED]
83 98
 JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.NO_DATA_FROM_SOURCE]
84 99
     = "dialog.micNotSendingData";
85 100
 
86
-/**
87
- * Initialize toolbars with side panels.
88
- */
89
-function setupToolbars() {
90
-    // Initialize toolbar buttons
91
-    Toolbar.init(eventEmitter);
92
-    // Initialize side panels
93
-    SidePanels.init(eventEmitter);
94
-}
95
-
96 101
 /**
97 102
  * Toggles the application in and out of full screen mode
98 103
  * (a.k.a. presentation mode in Chrome).
@@ -231,7 +236,7 @@ UI.initConference = function () {
231 236
         UI.setUserAvatarID(id, Settings.getAvatarId());
232 237
     }
233 238
 
234
-    Toolbar.checkAutoEnableDesktopSharing();
239
+    APP.store.dispatch(checkAutoEnableDesktopSharing());
235 240
 
236 241
     if(!interfaceConfig.filmStripOnly) {
237 242
         Feedback.init(eventEmitter);
@@ -294,7 +299,6 @@ UI.start = function () {
294 299
     // Set the defaults for tooltips.
295 300
     _setTooltipDefaults();
296 301
 
297
-    ToolbarToggler.init();
298 302
     SideContainerToggler.init(eventEmitter);
299 303
     FilmStrip.init(eventEmitter);
300 304
 
@@ -313,11 +317,9 @@ UI.start = function () {
313 317
                     { leading: true, trailing: false });
314 318
 
315 319
         $("#videoconference_page").mousemove(debouncedShowToolbar);
316
-        setupToolbars();
317 320
 
318
-        // Initialise the recording module.
319
-        if (config.enableRecording)
320
-            Recording.init(eventEmitter, config.recordingType);
321
+        // Initialize side panels
322
+        SidePanels.init(eventEmitter);
321 323
     } else {
322 324
         $("body").addClass("filmstrip-only");
323 325
         UIUtil.setVisible('mainToolbarContainer', false);
@@ -446,7 +448,8 @@ UI.initEtherpad = name => {
446 448
     logger.log('Etherpad is enabled');
447 449
     etherpadManager
448 450
         = new EtherpadManager(config.etherpad_base, name, eventEmitter);
449
-    Toolbar.showEtherpadButton();
451
+
452
+    APP.store.dispatch(showEtherpadButton());
450 453
 };
451 454
 
452 455
 /**
@@ -521,8 +524,9 @@ UI.onPeerVideoTypeChanged
521 524
 UI.updateLocalRole = isModerator => {
522 525
     VideoLayout.showModeratorIndicator();
523 526
 
524
-    Toolbar.showSipCallButton(isModerator);
525
-    Toolbar.showSharedVideoButton(isModerator);
527
+    APP.store.dispatch(showSIPCallButton(isModerator));
528
+    APP.store.dispatch(showSharedVideoButton());
529
+
526 530
     Recording.showRecordingButton(isModerator);
527 531
     SettingsMenu.showStartMutedOptions(isModerator);
528 532
     SettingsMenu.showFollowMeOptions(isModerator);
@@ -676,7 +680,10 @@ UI.askForNickname = function () {
676 680
 UI.setAudioMuted = function (id, muted) {
677 681
     VideoLayout.onAudioMute(id, muted);
678 682
     if (APP.conference.isLocalId(id)) {
679
-        Toolbar.toggleAudioIcon(muted);
683
+        APP.store.dispatch(setAudioMuted(muted));
684
+        APP.store.dispatch(setToolbarButton('microphone', {
685
+            toggled: muted
686
+        }));
680 687
     }
681 688
 };
682 689
 
@@ -686,7 +693,10 @@ UI.setAudioMuted = function (id, muted) {
686 693
 UI.setVideoMuted = function (id, muted) {
687 694
     VideoLayout.onVideoMute(id, muted);
688 695
     if (APP.conference.isLocalId(id)) {
689
-        Toolbar.toggleVideoIcon(muted);
696
+        APP.store.dispatch(setVideoMuted(muted));
697
+        APP.store.dispatch(setToolbarButton('camera', {
698
+            toggled: muted
699
+        }));
690 700
     }
691 701
 };
692 702
 
@@ -716,7 +726,7 @@ UI.removeListener = function (type, listener) {
716 726
  * @param type the type of the event we're emitting
717 727
  * @param options the parameters for the event
718 728
  */
719
-UI.emitEvent = (type, options) => eventEmitter.emit(type, options);
729
+UI.emitEvent = (type, ...options) => eventEmitter.emit(type, ...options);
720 730
 
721 731
 UI.clickOnVideo = function (videoNumber) {
722 732
     let videos = $("#remoteVideos .videocontainer:not(#mixedstream)");
@@ -731,12 +741,12 @@ UI.clickOnVideo = function (videoNumber) {
731 741
 
732 742
 //Used by torture
733 743
 UI.showToolbar = function (timeout) {
734
-    return ToolbarToggler.showToolbar(timeout);
744
+    APP.store.dispatch(showToolbar(timeout));
735 745
 };
736 746
 
737 747
 //Used by torture
738 748
 UI.dockToolbar = function (isDock) {
739
-    ToolbarToggler.dockToolbar(isDock);
749
+    APP.store.dispatch(dockToolbar(isDock));
740 750
 };
741 751
 
742 752
 /**
@@ -916,10 +926,14 @@ UI.setAudioLevel = (id, lvl) => VideoLayout.setAudioLevel(id, lvl);
916 926
 
917 927
 /**
918 928
  * Update state of desktop sharing buttons.
929
+ *
930
+ * @returns {void}
919 931
  */
920
-UI.updateDesktopSharingButtons = function () {
921
-    Toolbar.updateDesktopSharingButtonState();
922
-};
932
+UI.updateDesktopSharingButtons
933
+    = () =>
934
+        APP.store.dispatch(setToolbarButton('desktop', {
935
+            toggled: APP.conference.isSharingScreen
936
+        }));
923 937
 
924 938
 /**
925 939
  * Hide connection quality statistics from UI.
@@ -970,11 +984,8 @@ UI.addMessage = function (from, displayName, message, stamp) {
970 984
     Chat.updateChatConversation(from, displayName, message, stamp);
971 985
 };
972 986
 
973
-// eslint-disable-next-line no-unused-vars
974
-UI.updateDTMFSupport = function (isDTMFSupported) {
975
-    //TODO: enable when the UI is ready
976
-    //Toolbar.showDialPadButton(isDTMFSupported);
977
-};
987
+UI.updateDTMFSupport
988
+    = isDTMFSupported => APP.store.dispatch(showDialPadButton(isDTMFSupported));
978 989
 
979 990
 /**
980 991
  * Show user feedback dialog if its required and enabled after pressing the
@@ -1315,7 +1326,8 @@ UI.onSharedVideoStop = function (id, attributes) {
1315 1326
  * @param {boolean} enabled indicates if the camera button should be enabled
1316 1327
  * or disabled
1317 1328
  */
1318
-UI.setCameraButtonEnabled = enabled => Toolbar.setVideoIconEnabled(enabled);
1329
+UI.setCameraButtonEnabled
1330
+    = enabled => APP.store.dispatch(setVideoIconEnabled(enabled));
1319 1331
 
1320 1332
 /**
1321 1333
  * Enables / disables microphone toolbar button.
@@ -1323,7 +1335,8 @@ UI.setCameraButtonEnabled = enabled => Toolbar.setVideoIconEnabled(enabled);
1323 1335
  * @param {boolean} enabled indicates if the microphone button should be
1324 1336
  * enabled or disabled
1325 1337
  */
1326
-UI.setMicrophoneButtonEnabled = enabled => Toolbar.setAudioIconEnabled(enabled);
1338
+UI.setMicrophoneButtonEnabled
1339
+    = enabled => APP.store.dispatch(setAudioIconEnabled(enabled));
1327 1340
 
1328 1341
 UI.showRingOverlay = function () {
1329 1342
     RingOverlay.show(APP.tokenData.callee, interfaceConfig.DISABLE_RINGING);

+ 3
- 2
modules/UI/recording/Recording.js View File

@@ -20,7 +20,8 @@ import UIEvents from "../../../service/UI/UIEvents";
20 20
 import UIUtil from '../util/UIUtil';
21 21
 import VideoLayout from '../videolayout/VideoLayout';
22 22
 import Feedback from '../feedback/Feedback.js';
23
-import Toolbar from '../toolbars/Toolbar';
23
+
24
+import { hideToolbar } from '../../../react/features/toolbar';
24 25
 
25 26
 /**
26 27
  * The dialog for user input.
@@ -263,7 +264,7 @@ var Recording = {
263 264
                 APP.conference.getMyUserId(), false);
264 265
             VideoLayout.setLocalVideoVisible(false);
265 266
             Feedback.enableFeedback(false);
266
-            Toolbar.enable(false);
267
+            APP.store.dispatch(hideToolbar());
267 268
             APP.UI.messageHandler.enableNotifications(false);
268 269
             APP.UI.messageHandler.enablePopups(false);
269 270
         }

+ 5
- 4
modules/UI/shared_video/SharedVideo.js View File

@@ -9,7 +9,8 @@ import VideoLayout from "../videolayout/VideoLayout";
9 9
 import LargeContainer from '../videolayout/LargeContainer';
10 10
 import SmallVideo from '../videolayout/SmallVideo';
11 11
 import FilmStrip from '../videolayout/FilmStrip';
12
-import ToolbarToggler from "../toolbars/ToolbarToggler";
12
+
13
+import { dockToolbar, showToolbar } from '../../../react/features/toolbar';
13 14
 
14 15
 export const SHARED_VIDEO_CONTAINER_TYPE = "sharedvideo";
15 16
 
@@ -578,7 +579,7 @@ class SharedVideoContainer extends LargeContainer {
578 579
                 self.bodyBackground = document.body.style.background;
579 580
                 document.body.style.background = 'black';
580 581
                 this.$iframe.css({opacity: 1});
581
-                ToolbarToggler.dockToolbar(true);
582
+                APP.store.dispatch(dockToolbar(true));
582 583
                 resolve();
583 584
             });
584 585
         });
@@ -586,7 +587,7 @@ class SharedVideoContainer extends LargeContainer {
586 587
 
587 588
     hide () {
588 589
         let self = this;
589
-        ToolbarToggler.dockToolbar(false);
590
+        APP.store.dispatch(dockToolbar(false));
590 591
         return new Promise(resolve => {
591 592
             this.$iframe.fadeOut(300, () => {
592 593
                 document.body.style.background = self.bodyBackground;
@@ -597,7 +598,7 @@ class SharedVideoContainer extends LargeContainer {
597 598
     }
598 599
 
599 600
     onHoverIn () {
600
-        ToolbarToggler.showToolbar();
601
+        APP.store.dispatch(showToolbar());
601 602
     }
602 603
 
603 604
     get id () {

+ 6
- 6
modules/UI/side_pannels/chat/Chat.js View File

@@ -2,7 +2,6 @@
2 2
 
3 3
 import {processReplacements, linkify} from './Replacement';
4 4
 import CommandsProcessor from './Commands';
5
-import ToolbarToggler from '../../toolbars/ToolbarToggler';
6 5
 import VideoLayout from "../../videolayout/VideoLayout";
7 6
 
8 7
 import UIUtil from '../../util/UIUtil';
@@ -10,6 +9,8 @@ import UIEvents from '../../../../service/UI/UIEvents';
10 9
 
11 10
 import { smileys } from './smileys';
12 11
 
12
+import { dockToolbar, setSubject } from '../../../../react/features/toolbar';
13
+
13 14
 let unreadMessages = 0;
14 15
 const sidePanelsContainerId = 'sideToolbarContainer';
15 16
 const htmlStr = `
@@ -59,7 +60,7 @@ function updateVisualNotification() {
59 60
     if (unreadMessages) {
60 61
         unreadMsgElement.innerHTML = unreadMessages.toString();
61 62
 
62
-        ToolbarToggler.dockToolbar(true);
63
+        APP.store.dispatch(dockToolbar(true));
63 64
 
64 65
         const chatButtonElement
65 66
             = document.getElementById('toolbar_button_chat');
@@ -238,7 +239,7 @@ var Chat = {
238 239
                 // Undock the toolbar when the chat is shown and if we're in a
239 240
                 // video mode.
240 241
                 if (VideoLayout.isLargeVideoVisible()) {
241
-                    ToolbarToggler.dockToolbar(false);
242
+                    APP.store.dispatch(dockToolbar(false));
242 243
                 }
243 244
 
244 245
                 // if we are in conversation mode focus on the text input
@@ -319,10 +320,9 @@ var Chat = {
319 320
             subject = subject.trim();
320 321
         }
321 322
 
322
-        let subjectId = 'subject';
323 323
         const html = linkify(UIUtil.escapeHtml(subject));
324
-        $(`#${subjectId}`).html(html);
325
-        UIUtil.setVisible(subjectId, subject && subject.length > 0);
324
+
325
+        APP.store.dispatch(setSubject(html));
326 326
     },
327 327
 
328 328
     /**

+ 0
- 889
modules/UI/toolbars/Toolbar.js View File

@@ -1,889 +0,0 @@
1
-/* global AJS, APP, $, config, interfaceConfig, JitsiMeetJS */
2
-import UIUtil from '../util/UIUtil';
3
-import UIEvents from '../../../service/UI/UIEvents';
4
-import SideContainerToggler from "../side_pannels/SideContainerToggler";
5
-
6
-let emitter = null;
7
-let Toolbar;
8
-
9
-/**
10
- * Handlers for toolbar buttons.
11
- *
12
- * buttonId {string}: handler {function}
13
- */
14
-const buttonHandlers = {
15
-    "toolbar_button_profile": function () {
16
-        JitsiMeetJS.analytics.sendEvent('toolbar.profile.toggled');
17
-        emitter.emit(UIEvents.TOGGLE_PROFILE);
18
-    },
19
-    "toolbar_button_mute": function () {
20
-        let sharedVideoManager = APP.UI.getSharedVideoManager();
21
-
22
-        if (APP.conference.audioMuted) {
23
-            // If there's a shared video with the volume "on" and we aren't
24
-            // the video owner, we warn the user
25
-            // that currently it's not possible to unmute.
26
-            if (sharedVideoManager
27
-                && sharedVideoManager.isSharedVideoVolumeOn()
28
-                && !sharedVideoManager.isSharedVideoOwner()) {
29
-                APP.UI.showCustomToolbarPopup(
30
-                    '#unableToUnmutePopup', true, 5000);
31
-            }
32
-            else {
33
-                JitsiMeetJS.analytics.sendEvent('toolbar.audio.unmuted');
34
-                emitter.emit(UIEvents.AUDIO_MUTED, false, true);
35
-            }
36
-        } else {
37
-            JitsiMeetJS.analytics.sendEvent('toolbar.audio.muted');
38
-            emitter.emit(UIEvents.AUDIO_MUTED, true, true);
39
-        }
40
-    },
41
-    "toolbar_button_camera": function () {
42
-        if (APP.conference.videoMuted) {
43
-            JitsiMeetJS.analytics.sendEvent('toolbar.video.enabled');
44
-            emitter.emit(UIEvents.VIDEO_MUTED, false);
45
-        } else {
46
-            JitsiMeetJS.analytics.sendEvent('toolbar.video.disabled');
47
-            emitter.emit(UIEvents.VIDEO_MUTED, true);
48
-        }
49
-    },
50
-    "toolbar_button_link": function () {
51
-        JitsiMeetJS.analytics.sendEvent('toolbar.invite.clicked');
52
-        emitter.emit(UIEvents.INVITE_CLICKED);
53
-    },
54
-    "toolbar_button_chat": function () {
55
-        JitsiMeetJS.analytics.sendEvent('toolbar.chat.toggled');
56
-        emitter.emit(UIEvents.TOGGLE_CHAT);
57
-    },
58
-    "toolbar_contact_list": function () {
59
-        JitsiMeetJS.analytics.sendEvent(
60
-            'toolbar.contacts.toggled');
61
-        emitter.emit(UIEvents.TOGGLE_CONTACT_LIST);
62
-    },
63
-    "toolbar_button_etherpad": function () {
64
-        JitsiMeetJS.analytics.sendEvent('toolbar.etherpad.clicked');
65
-        emitter.emit(UIEvents.ETHERPAD_CLICKED);
66
-    },
67
-    "toolbar_button_sharedvideo": function () {
68
-        JitsiMeetJS.analytics.sendEvent('toolbar.sharedvideo.clicked');
69
-        emitter.emit(UIEvents.SHARED_VIDEO_CLICKED);
70
-    },
71
-    "toolbar_button_desktopsharing": function () {
72
-        if (APP.conference.isSharingScreen) {
73
-            JitsiMeetJS.analytics.sendEvent('toolbar.screen.disabled');
74
-        } else {
75
-            JitsiMeetJS.analytics.sendEvent('toolbar.screen.enabled');
76
-        }
77
-        emitter.emit(UIEvents.TOGGLE_SCREENSHARING);
78
-    },
79
-    "toolbar_button_fullScreen": function() {
80
-        JitsiMeetJS.analytics.sendEvent('toolbar.fullscreen.enabled');
81
-
82
-        emitter.emit(UIEvents.TOGGLE_FULLSCREEN);
83
-    },
84
-    "toolbar_button_sip": function () {
85
-        JitsiMeetJS.analytics.sendEvent('toolbar.sip.clicked');
86
-        showSipNumberInput();
87
-    },
88
-    "toolbar_button_dialpad": function () {
89
-        JitsiMeetJS.analytics.sendEvent('toolbar.sip.dialpad.clicked');
90
-        dialpadButtonClicked();
91
-    },
92
-    "toolbar_button_settings": function () {
93
-        JitsiMeetJS.analytics.sendEvent('toolbar.settings.toggled');
94
-        emitter.emit(UIEvents.TOGGLE_SETTINGS);
95
-    },
96
-    "toolbar_button_hangup": function () {
97
-        JitsiMeetJS.analytics.sendEvent('toolbar.hangup');
98
-        emitter.emit(UIEvents.HANGUP);
99
-    },
100
-    "toolbar_button_raisehand": function () {
101
-        JitsiMeetJS.analytics.sendEvent('toolbar.raiseHand.clicked');
102
-        APP.conference.maybeToggleRaisedHand();
103
-    }
104
-};
105
-
106
-/**
107
- * All toolbars buttons description
108
- */
109
-const defaultToolbarButtons = {
110
-    'microphone': {
111
-        id: 'toolbar_button_mute',
112
-        tooltipKey: 'toolbar.mute',
113
-        className: "button icon-microphone",
114
-        shortcut: 'M',
115
-        shortcutAttr: 'mutePopover',
116
-        shortcutFunc: function() {
117
-            JitsiMeetJS.analytics.sendEvent('shortcut.audiomute.toggled');
118
-            APP.conference.toggleAudioMuted();
119
-        },
120
-        shortcutDescription: "keyboardShortcuts.mute",
121
-        popups: [
122
-            {
123
-                id: 'micMutedPopup',
124
-                className: 'loginmenu',
125
-                dataAttr: '[title]toolbar.micMutedPopup'
126
-            },
127
-            {
128
-                id: 'unableToUnmutePopup',
129
-                className: 'loginmenu',
130
-                dataAttr: '[title]toolbar.unableToUnmutePopup'
131
-            },
132
-            {
133
-                id: 'talkWhileMutedPopup',
134
-                className: 'loginmenu',
135
-                dataAttr: '[title]toolbar.talkWhileMutedPopup'
136
-            }
137
-        ],
138
-        content: "Mute / Unmute",
139
-        i18n: "[content]toolbar.mute"
140
-    },
141
-    'camera': {
142
-        id: 'toolbar_button_camera',
143
-        tooltipKey: 'toolbar.videomute',
144
-        className: "button icon-camera",
145
-        shortcut: 'V',
146
-        shortcutAttr: 'toggleVideoPopover',
147
-        shortcutFunc: function() {
148
-            JitsiMeetJS.analytics.sendEvent('shortcut.videomute.toggled');
149
-            APP.conference.toggleVideoMuted();
150
-        },
151
-        shortcutDescription: "keyboardShortcuts.videoMute",
152
-        content: "Start / stop camera",
153
-        i18n: "[content]toolbar.videomute"
154
-    },
155
-    'desktop': {
156
-        id: 'toolbar_button_desktopsharing',
157
-        tooltipKey: 'toolbar.sharescreen',
158
-        className: 'button icon-share-desktop',
159
-        shortcut: 'D',
160
-        shortcutAttr: 'toggleDesktopSharingPopover',
161
-        shortcutFunc: function() {
162
-            JitsiMeetJS.analytics.sendEvent('shortcut.screen.toggled');
163
-            APP.conference.toggleScreenSharing();
164
-        },
165
-        shortcutDescription: 'keyboardShortcuts.toggleScreensharing',
166
-        content: 'Share screen',
167
-        i18n: '[content]toolbar.sharescreen'
168
-    },
169
-    'invite': {
170
-        id: 'toolbar_button_link',
171
-        tooltipKey: 'toolbar.invite',
172
-        className: 'button icon-link',
173
-        content: 'Invite others',
174
-        i18n: '[content]toolbar.invite'
175
-    },
176
-    'chat': {
177
-        id: 'toolbar_button_chat',
178
-        tooltipKey: 'toolbar.chat',
179
-        className: 'button icon-chat',
180
-        shortcut: 'C',
181
-        shortcutAttr: 'toggleChatPopover',
182
-        shortcutFunc: function() {
183
-            JitsiMeetJS.analytics.sendEvent('shortcut.chat.toggled');
184
-            APP.UI.toggleChat();
185
-        },
186
-        shortcutDescription: 'keyboardShortcuts.toggleChat',
187
-        sideContainerId: 'chat_container',
188
-        html: `<span class="badge-round">
189
-                   <span id="unreadMessages"></span>
190
-               </span>`
191
-    },
192
-    'contacts': {
193
-        id: 'toolbar_contact_list',
194
-        tooltipKey: 'bottomtoolbar.contactlist',
195
-        className: 'button icon-contactList',
196
-        sideContainerId: 'contacts_container',
197
-        html: `<span class="badge-round">
198
-                   <span id="numberOfParticipants"></span>
199
-               </span>`
200
-    },
201
-    'profile': {
202
-        id: 'toolbar_button_profile',
203
-        tooltipKey: 'profile.setDisplayNameLabel',
204
-        className: 'button',
205
-        sideContainerId: 'profile_container',
206
-        html: `<img id="avatar" src="images/avatar2.png"/>`
207
-    },
208
-    'etherpad': {
209
-        id: 'toolbar_button_etherpad',
210
-        tooltipKey: 'toolbar.etherpad',
211
-        className: 'button icon-share-doc'
212
-    },
213
-    'fullscreen': {
214
-        id: 'toolbar_button_fullScreen',
215
-        tooltipKey: 'toolbar.fullscreen',
216
-        className: "button icon-full-screen",
217
-        shortcut: 'S',
218
-        shortcutAttr: 'toggleFullscreenPopover',
219
-        shortcutFunc: function() {
220
-            JitsiMeetJS.analytics.sendEvent('shortcut.fullscreen.toggled');
221
-            APP.UI.toggleFullScreen();
222
-        },
223
-        shortcutDescription: "keyboardShortcuts.fullScreen",
224
-        content: "Enter / Exit Full Screen",
225
-        i18n: "[content]toolbar.fullscreen"
226
-    },
227
-    'settings': {
228
-        id: 'toolbar_button_settings',
229
-        tooltipKey: 'toolbar.Settings',
230
-        className: 'button icon-settings',
231
-        sideContainerId: "settings_container"
232
-    },
233
-    'hangup': {
234
-        id: 'toolbar_button_hangup',
235
-        tooltipKey: 'toolbar.hangup',
236
-        className: "button icon-hangup",
237
-        content: "Hang Up",
238
-        i18n: "[content]toolbar.hangup"
239
-    },
240
-    'raisehand': {
241
-        id: "toolbar_button_raisehand",
242
-        tooltipKey: 'toolbar.raiseHand',
243
-        className: "button icon-raised-hand",
244
-        shortcut: "R",
245
-        shortcutAttr: "raiseHandPopover",
246
-        shortcutFunc: function() {
247
-            JitsiMeetJS.analytics.sendEvent("shortcut.raisehand.clicked");
248
-            APP.conference.maybeToggleRaisedHand();
249
-        },
250
-        shortcutDescription: "keyboardShortcuts.raiseHand",
251
-        content: "Raise Hand",
252
-        i18n: "[content]toolbar.raiseHand"
253
-    },
254
-    //init and btn handler: Recording.initRecordingButton (Recording.js)
255
-    'recording': {
256
-        id: 'toolbar_button_record',
257
-        tooltipKey: 'liveStreaming.buttonTooltip',
258
-        className: 'button',
259
-        hidden: true // will be displayed once
260
-                     // the recording functionality is detected
261
-    },
262
-    'sharedvideo': {
263
-        id: 'toolbar_button_sharedvideo',
264
-        tooltipKey: 'toolbar.sharedvideo',
265
-        className: 'button icon-shared-video',
266
-        popups: [
267
-            {
268
-                id: 'sharedVideoMutedPopup',
269
-                className: 'loginmenu extendedToolbarPopup',
270
-                dataAttr: '[title]toolbar.sharedVideoMutedPopup',
271
-                dataAttrPosition: 'w'
272
-            }
273
-        ]
274
-    },
275
-    'sip': {
276
-        id: 'toolbar_button_sip',
277
-        tooltipKey: 'toolbar.sip',
278
-        className: 'button icon-telephone',
279
-        hidden: true // will be displayed once
280
-                     // the SIP calls functionality is detected
281
-    },
282
-    'dialpad': {
283
-        id: 'toolbar_button_dialpad',
284
-        tooltipKey: 'toolbar.dialpad',
285
-        className: 'button icon-dialpad',
286
-        //TODO: remove it after UI.updateDTMFSupport fix
287
-        hidden: true
288
-    }
289
-};
290
-
291
-function dialpadButtonClicked() {
292
-    //TODO show the dialpad box
293
-}
294
-
295
-function showSipNumberInput () {
296
-    let defaultNumber = config.defaultSipNumber
297
-        ? config.defaultSipNumber
298
-        : '';
299
-    let titleKey = "dialog.sipMsg";
300
-    let msgString = (`
301
-            <input class="input-control"
302
-                   name="sipNumber" type="text"
303
-                   value="${defaultNumber}" autofocus>`);
304
-
305
-    APP.UI.messageHandler.openTwoButtonDialog({
306
-        titleKey,
307
-        msgString,
308
-        leftButtonKey: "dialog.Dial",
309
-        submitFunction: function (e, v, m, f) {
310
-            if (v && f.sipNumber) {
311
-                emitter.emit(UIEvents.SIP_DIAL, f.sipNumber);
312
-            }
313
-        },
314
-        focus: ':input:first'
315
-    });
316
-}
317
-
318
-/**
319
- * Get place for toolbar button.
320
- * Now it can be in main toolbar or in extended (left) toolbar
321
- *
322
- * @param btn {string}
323
- * @returns {string}
324
- */
325
-function getToolbarButtonPlace (btn) {
326
-    return interfaceConfig.MAIN_TOOLBAR_BUTTONS.includes(btn) ?
327
-        'main' :
328
-        'extended';
329
-}
330
-
331
-/**
332
- * Event handler for side toolbar container toggled event.
333
- *
334
- * @param {string} containerId - ID of the container.
335
- * @param {boolean} isVisible - Flag showing whether container
336
- * is visible.
337
- * @returns {void}
338
- */
339
-function onSideToolbarContainerToggled(containerId, isVisible) {
340
-    Toolbar._handleSideToolbarContainerToggled(containerId, isVisible);
341
-}
342
-
343
-/**
344
- * Event handler for local raise hand changed event.
345
- *
346
- * @param {boolean} isRaisedHand - Flag showing whether hand is raised.
347
- * @returns {void}
348
- */
349
-function onLocalRaiseHandChanged(isRaisedHand) {
350
-    Toolbar._setToggledState("toolbar_button_raisehand", isRaisedHand);
351
-}
352
-
353
-/**
354
- * Event handler for full screen toggled event.
355
- *
356
- * @param {boolean} isFullScreen - Flag showing whether app in full
357
- * screen mode.
358
- * @returns {void}
359
- */
360
-function onFullScreenToggled(isFullScreen) {
361
-    Toolbar._handleFullScreenToggled(isFullScreen);
362
-}
363
-
364
-Toolbar = {
365
-    init (eventEmitter) {
366
-        emitter = eventEmitter;
367
-        // The toolbar is enabled by default.
368
-        this.enabled = true;
369
-        this.toolbarSelector = $("#mainToolbarContainer");
370
-        this.extendedToolbarSelector = $("#extendedToolbar");
371
-
372
-        // Unregister listeners in case of reinitialization.
373
-        this.unregisterListeners();
374
-
375
-        // Initialise the toolbar buttons.
376
-        // The main toolbar will only take into account
377
-        // it's own configuration from interface_config.
378
-        this._initToolbarButtons();
379
-
380
-        this._setShortcutsAndTooltips();
381
-
382
-        this._setButtonHandlers();
383
-
384
-        this.registerListeners();
385
-
386
-        APP.UI.addListener(UIEvents.SHOW_CUSTOM_TOOLBAR_BUTTON_POPUP,
387
-            (popupID, show, timeout) => {
388
-                Toolbar._showCustomToolbarPopup(popupID, show, timeout);
389
-            });
390
-
391
-        if(!APP.tokenData.isGuest) {
392
-            $("#toolbar_button_profile").addClass("unclickable");
393
-            UIUtil.removeTooltip(
394
-                document.getElementById('toolbar_button_profile'));
395
-        }
396
-    },
397
-    /**
398
-     *  Register listeners for UI events of toolbar component.
399
-     *
400
-     *  @returns {void}
401
-     */
402
-    registerListeners() {
403
-        APP.UI.addListener(UIEvents.SIDE_TOOLBAR_CONTAINER_TOGGLED,
404
-            onSideToolbarContainerToggled);
405
-
406
-        APP.UI.addListener(UIEvents.LOCAL_RAISE_HAND_CHANGED,
407
-            onLocalRaiseHandChanged);
408
-
409
-        APP.UI.addListener(UIEvents.FULLSCREEN_TOGGLED, onFullScreenToggled);
410
-    },
411
-    /**
412
-     *  Unregisters handlers for UI events of Toolbar component.
413
-     *
414
-     *  @returns {void}
415
-     */
416
-    unregisterListeners() {
417
-        APP.UI.removeListener(UIEvents.SIDE_TOOLBAR_CONTAINER_TOGGLED,
418
-            onSideToolbarContainerToggled);
419
-
420
-        APP.UI.removeListener(UIEvents.LOCAL_RAISE_HAND_CHANGED,
421
-            onLocalRaiseHandChanged);
422
-
423
-        APP.UI.removeListener(UIEvents.FULLSCREEN_TOGGLED,
424
-            onFullScreenToggled);
425
-    },
426
-    /**
427
-     * Enables / disables the toolbar.
428
-     * @param {e} set to {true} to enable the toolbar or {false}
429
-     * to disable it
430
-     */
431
-    enable (e) {
432
-        this.enabled = e;
433
-        if (!e && this.isVisible())
434
-            this.hide(false);
435
-    },
436
-    /**
437
-     * Indicates if the bottom toolbar is currently enabled.
438
-     * @return {this.enabled}
439
-     */
440
-    isEnabled() {
441
-        return this.enabled;
442
-    },
443
-
444
-    showEtherpadButton () {
445
-        if (!$('#toolbar_button_etherpad').is(":visible")) {
446
-            $('#toolbar_button_etherpad').css({display: 'inline-block'});
447
-        }
448
-    },
449
-
450
-    // Shows or hides the 'shared video' button.
451
-    showSharedVideoButton () {
452
-        let id = 'toolbar_button_sharedvideo';
453
-        let shouldShow = UIUtil.isButtonEnabled('sharedvideo')
454
-                && !config.disableThirdPartyRequests;
455
-
456
-        if (shouldShow) {
457
-            let el = document.getElementById(id);
458
-            UIUtil.setTooltip(el, 'toolbar.sharedvideo', 'right');
459
-        }
460
-        UIUtil.setVisible(id, shouldShow);
461
-    },
462
-
463
-    // checks whether desktop sharing is enabled and whether
464
-    // we have params to start automatically sharing
465
-    checkAutoEnableDesktopSharing () {
466
-        if (UIUtil.isButtonEnabled('desktop')
467
-            && config.autoEnableDesktopSharing) {
468
-            emitter.emit(UIEvents.TOGGLE_SCREENSHARING);
469
-        }
470
-    },
471
-
472
-    // Shows or hides SIP calls button
473
-    showSipCallButton (show) {
474
-        let shouldShow = APP.conference.sipGatewayEnabled()
475
-            && UIUtil.isButtonEnabled('sip') && show;
476
-        let id = 'toolbar_button_sip';
477
-
478
-        UIUtil.setVisible(id, shouldShow);
479
-    },
480
-
481
-    // Shows or hides the dialpad button
482
-    showDialPadButton (show) {
483
-        let shouldShow = UIUtil.isButtonEnabled('dialpad') && show;
484
-        let id = 'toolbar_button_dialpad';
485
-
486
-        UIUtil.setVisible(id, shouldShow);
487
-    },
488
-
489
-    /**
490
-     * Update the state of the button. The button has blue glow if desktop
491
-     * streaming is active.
492
-     */
493
-    updateDesktopSharingButtonState () {
494
-        this._setToggledState(  "toolbar_button_desktopsharing",
495
-                                APP.conference.isSharingScreen);
496
-    },
497
-
498
-    /**
499
-     * Marks video icon as muted or not.
500
-     *
501
-     * @param {boolean} muted if icon should look like muted or not
502
-     */
503
-    toggleVideoIcon (muted) {
504
-        $('#toolbar_button_camera').toggleClass("icon-camera-disabled", muted);
505
-
506
-        this._setToggledState("toolbar_button_camera", muted);
507
-    },
508
-
509
-    /**
510
-     * Enables / disables audio toolbar button.
511
-     *
512
-     * @param {boolean} enabled indicates if the button should be enabled
513
-     * or disabled
514
-     */
515
-    setVideoIconEnabled (enabled) {
516
-        this._setMediaIconEnabled(
517
-                '#toolbar_button_camera',
518
-                enabled,
519
-                /* data-i18n attribute value */
520
-                `[content]toolbar.${enabled ? 'videomute' : 'cameraDisabled'}`,
521
-                /* shortcut attribute value */
522
-                'toggleVideoPopover');
523
-
524
-        enabled || this.toggleVideoIcon(!enabled);
525
-    },
526
-
527
-    /**
528
-     * Enables/disables the toolbar button associated with a specific media such
529
-     * as audio or video.
530
-     *
531
-     * @param {string} btn - The jQuery selector <tt>string</tt> which
532
-     * identifies the toolbar button to be enabled/disabled.
533
-     * @param {boolean} enabled - <tt>true</tt> to enable the specified
534
-     * <tt>btn</tt>  or <tt>false</tt> to disable it.
535
-     * @param {string} dataI18n - The value to assign to the <tt>data-i18n</tt>
536
-     * attribute of the specified <tt>btn</tt>.
537
-     * @param {string} shortcut - The value, if any, to assign to the
538
-     * <tt>shortcut</tt> attribute of the specified <tt>btn</tt> if the toolbar
539
-     * button is enabled.
540
-     */
541
-    _setMediaIconEnabled(btn, enabled, dataI18n, shortcut) {
542
-        const $btn = $(btn);
543
-
544
-        $btn
545
-            .prop('disabled', !enabled)
546
-            .attr('data-i18n', dataI18n)
547
-            .attr('shortcut', enabled && shortcut ? shortcut : '');
548
-
549
-        enabled
550
-            ? $btn.removeAttr('disabled')
551
-            : $btn.attr('disabled', 'disabled');
552
-
553
-        APP.translation.translateElement($btn);
554
-    },
555
-
556
-    /**
557
-     * Marks audio icon as muted or not.
558
-     *
559
-     * @param {boolean} muted if icon should look like muted or not
560
-     */
561
-    toggleAudioIcon(muted) {
562
-        $('#toolbar_button_mute')
563
-            .toggleClass("icon-microphone", !muted)
564
-            .toggleClass("icon-mic-disabled", muted);
565
-
566
-        this._setToggledState("toolbar_button_mute", muted);
567
-    },
568
-
569
-    /**
570
-     * Enables / disables audio toolbar button.
571
-     *
572
-     * @param {boolean} enabled indicates if the button should be enabled
573
-     * or disabled
574
-     */
575
-    setAudioIconEnabled (enabled) {
576
-        this._setMediaIconEnabled(
577
-                '#toolbar_button_mute',
578
-                enabled,
579
-                /* data-i18n attribute value */
580
-                `[content]toolbar.${enabled ? 'mute' : 'micDisabled'}`,
581
-                /* shortcut attribute value */
582
-                'mutePopover');
583
-
584
-        enabled || this.toggleAudioIcon(!enabled);
585
-    },
586
-
587
-    /**
588
-     * Indicates if the toolbar is currently hovered.
589
-     * @return {boolean} true if the toolbar is currently hovered,
590
-     * false otherwise
591
-     */
592
-    isHovered() {
593
-        var hovered = false;
594
-        this.toolbarSelector.find('*').each(function () {
595
-            let id = $(this).attr('id');
596
-            if ($(`#${id}:hover`).length > 0) {
597
-                hovered = true;
598
-                // break each
599
-                return false;
600
-            }
601
-        });
602
-        if (hovered)
603
-            return true;
604
-        if ($("#bottomToolbar:hover").length > 0
605
-            || $("#extendedToolbar:hover").length > 0
606
-            || SideContainerToggler.isHovered()) {
607
-            return true;
608
-        }
609
-        return false;
610
-    },
611
-
612
-    /**
613
-     * Returns true if this toolbar is currently visible, or false otherwise.
614
-     * @return <tt>true</tt> if currently visible, <tt>false</tt> - otherwise
615
-     */
616
-    isVisible() {
617
-        return this.toolbarSelector.hasClass("fadeIn");
618
-    },
619
-
620
-    /**
621
-     * Hides the toolbar with animation or not depending on the animate
622
-     * parameter.
623
-     */
624
-    hide() {
625
-        this.toolbarSelector
626
-            .removeClass("fadeIn")
627
-            .addClass("fadeOut");
628
-
629
-        let slideInAnimation = (SideContainerToggler.isVisible)
630
-                                    ? "slideInExtX"
631
-                                    : "slideInX";
632
-        let slideOutAnimation = (SideContainerToggler.isVisible)
633
-                                    ? "slideOutExtX"
634
-                                    : "slideOutX";
635
-
636
-        this.extendedToolbarSelector.toggleClass(slideInAnimation)
637
-            .toggleClass(slideOutAnimation);
638
-    },
639
-
640
-    /**
641
-     * Shows the toolbar with animation or not depending on the animate
642
-     * parameter.
643
-     */
644
-    show() {
645
-        if (this.toolbarSelector.hasClass("fadeOut")) {
646
-            this.toolbarSelector.removeClass("fadeOut");
647
-        }
648
-
649
-        let slideInAnimation = (SideContainerToggler.isVisible)
650
-                                ? "slideInExtX"
651
-                                : "slideInX";
652
-        let slideOutAnimation = (SideContainerToggler.isVisible)
653
-                                ? "slideOutExtX"
654
-                                : "slideOutX";
655
-
656
-        if (this.extendedToolbarSelector.hasClass(slideOutAnimation)) {
657
-            this.extendedToolbarSelector.toggleClass(slideOutAnimation);
658
-        }
659
-
660
-        this.toolbarSelector.addClass("fadeIn");
661
-        this.extendedToolbarSelector.toggleClass(slideInAnimation);
662
-    },
663
-
664
-    registerClickListeners(listener) {
665
-        $('#mainToolbarContainer').click(listener);
666
-
667
-        $("#extendedToolbar").click(listener);
668
-    },
669
-
670
-    /**
671
-     * Handles the side toolbar toggle.
672
-     *
673
-     * @param {string} containerId the identifier of the container element
674
-     */
675
-    _handleSideToolbarContainerToggled(containerId) {
676
-        Object.keys(defaultToolbarButtons).forEach(
677
-            id => {
678
-                if (!UIUtil.isButtonEnabled(id))
679
-                    return;
680
-
681
-                var button = defaultToolbarButtons[id];
682
-
683
-                if (button.sideContainerId
684
-                    && button.sideContainerId === containerId) {
685
-                    UIUtil.buttonClick(button.id, "selected");
686
-                    return;
687
-                }
688
-            }
689
-        );
690
-    },
691
-
692
-    /**
693
-     * Handles full screen toggled.
694
-     *
695
-     * @param {boolean} isFullScreen indicates if we're currently in full
696
-     * screen mode
697
-     */
698
-    _handleFullScreenToggled(isFullScreen) {
699
-        let element
700
-            = document.getElementById("toolbar_button_fullScreen");
701
-
702
-        element.className = isFullScreen
703
-            ? element.className
704
-                .replace("icon-full-screen", "icon-exit-full-screen")
705
-            : element.className
706
-                .replace("icon-exit-full-screen", "icon-full-screen");
707
-
708
-        Toolbar._setToggledState("toolbar_button_fullScreen", isFullScreen);
709
-    },
710
-
711
-    /**
712
-     * Initialise toolbar buttons.
713
-     */
714
-    _initToolbarButtons() {
715
-        interfaceConfig.TOOLBAR_BUTTONS.forEach((value, index) => {
716
-            let place = getToolbarButtonPlace(value);
717
-
718
-            if (value && value in defaultToolbarButtons) {
719
-                let button = defaultToolbarButtons[value];
720
-                this._addToolbarButton(
721
-                    button,
722
-                    place,
723
-                    (interfaceConfig.MAIN_TOOLBAR_SPLITTER_INDEX !== undefined
724
-                        && index
725
-                            === interfaceConfig.MAIN_TOOLBAR_SPLITTER_INDEX));
726
-            }
727
-        });
728
-    },
729
-
730
-    /**
731
-     * Adds the given button to the main (top) or extended (left) toolbar.
732
-     *
733
-     * @param {Object} the button to add.
734
-     * @param {boolean} isFirst indicates if this is the first button in the
735
-     * toolbar
736
-     * @param {boolean} isLast indicates if this is the last button in the
737
-     * toolbar
738
-     * @param {boolean} isSplitter if this button is a splitter button for
739
-     * the dialog, which means that a special splitter style will be applied
740
-     */
741
-    _addToolbarButton(button, place, isSplitter) {
742
-        const places = {
743
-            main: 'mainToolbar',
744
-            extended: 'extendedToolbarButtons'
745
-        };
746
-        let id = places[place];
747
-        let buttonElement = document.createElement("a");
748
-        if (button.className) {
749
-            buttonElement.className = button.className;
750
-        }
751
-
752
-        if (isSplitter) {
753
-            let splitter = document.createElement('span');
754
-            splitter.className = 'toolbar__splitter';
755
-            document.getElementById(id).appendChild(splitter);
756
-        }
757
-
758
-        buttonElement.id = button.id;
759
-
760
-        if (button.html)
761
-            buttonElement.innerHTML = button.html;
762
-
763
-        //TODO: remove it after UI.updateDTMFSupport fix
764
-        if (button.hidden)
765
-            buttonElement.style.display = 'none';
766
-
767
-        if (button.shortcutAttr)
768
-            buttonElement.setAttribute("shortcut", button.shortcutAttr);
769
-
770
-        if (button.content)
771
-            buttonElement.setAttribute("content", button.content);
772
-
773
-        if (button.i18n)
774
-            buttonElement.setAttribute("data-i18n", button.i18n);
775
-
776
-        buttonElement.setAttribute("data-container", "body");
777
-        buttonElement.setAttribute("data-placement", "bottom");
778
-        this._addPopups(buttonElement, button.popups);
779
-
780
-        document.getElementById(id)
781
-            .appendChild(buttonElement);
782
-    },
783
-
784
-    _addPopups(buttonElement, popups = []) {
785
-        popups.forEach((popup) => {
786
-            const popupElement = document.createElement('div');
787
-            popupElement.id = popup.id;
788
-            popupElement.className = popup.className;
789
-            popupElement.setAttribute('data-i18n', popup.dataAttr);
790
-
791
-            let gravity = 'n';
792
-            if (popup.dataAttrPosition)
793
-                gravity = popup.dataAttrPosition;
794
-            // use custom attribute to save gravity option
795
-            // we use 'data-tooltip' in UIUtil to activate all tooltips
796
-            // but we want these to be manually triggered
797
-            popupElement.setAttribute('tooltip-gravity', gravity);
798
-
799
-            APP.translation.translateElement($(popupElement));
800
-
801
-            buttonElement.appendChild(popupElement);
802
-        });
803
-    },
804
-
805
-    /**
806
-     * Show custom popup/tooltip for a specified button.
807
-     * @param popupSelectorID the selector id of the popup to show
808
-     * @param show true or false/show or hide the popup
809
-     * @param timeout the time to show the popup
810
-     */
811
-    _showCustomToolbarPopup(popupSelectorID, show, timeout) {
812
-
813
-        const gravity = $(popupSelectorID).attr('tooltip-gravity');
814
-        AJS.$(popupSelectorID)
815
-            .tooltip({
816
-                trigger: 'manual',
817
-                html: true,
818
-                gravity: gravity,
819
-                title: 'title'});
820
-        if (show) {
821
-            AJS.$(popupSelectorID).tooltip('show');
822
-            setTimeout(function () {
823
-                // hide the tooltip
824
-                AJS.$(popupSelectorID).tooltip('hide');
825
-            }, timeout);
826
-        } else {
827
-            AJS.$(popupSelectorID).tooltip('hide');
828
-        }
829
-    },
830
-
831
-/**
832
-     * Sets the toggled state of the given element depending on the isToggled
833
-     * parameter.
834
-     *
835
-     * @param elementId the element identifier
836
-     * @param isToggled indicates if the element should be toggled or untoggled
837
-     */
838
-     _setToggledState(elementId, isToggled) {
839
-        $("#" + elementId).toggleClass("toggled", isToggled);
840
-    },
841
-
842
-    /**
843
-     * Sets Shortcuts and Tooltips for all toolbar buttons
844
-     *
845
-     * @private
846
-     */
847
-    _setShortcutsAndTooltips() {
848
-        Object.keys(defaultToolbarButtons).forEach(
849
-            id => {
850
-                if (UIUtil.isButtonEnabled(id)) {
851
-                    let button = defaultToolbarButtons[id];
852
-                    let buttonElement = document.getElementById(button.id);
853
-                    if (!buttonElement) return false;
854
-                    let tooltipPosition
855
-                        = (interfaceConfig.MAIN_TOOLBAR_BUTTONS
856
-                        .indexOf(id) > -1)
857
-                        ? "bottom" : "right";
858
-
859
-                    UIUtil.setTooltip(  buttonElement,
860
-                        button.tooltipKey,
861
-                        tooltipPosition);
862
-
863
-                    if (button.shortcut)
864
-                        APP.keyboardshortcut.registerShortcut(
865
-                            button.shortcut,
866
-                            button.shortcutAttr,
867
-                            button.shortcutFunc,
868
-                            button.shortcutDescription
869
-                        );
870
-                }
871
-            }
872
-        );
873
-    },
874
-
875
-    /**
876
-     * Sets Handlers for all toolbar buttons
877
-     *
878
-     * @private
879
-     */
880
-    _setButtonHandlers() {
881
-        Object.keys(buttonHandlers).forEach(
882
-            buttonId => $(`#${buttonId}`).click(function(event) {
883
-                !$(this).prop('disabled') && buttonHandlers[buttonId](event);
884
-            })
885
-        );
886
-    }
887
-};
888
-
889
-export default Toolbar;

+ 0
- 149
modules/UI/toolbars/ToolbarToggler.js View File

@@ -1,149 +0,0 @@
1
-/* global APP, config, $, interfaceConfig */
2
-
3
-import UIUtil from '../util/UIUtil';
4
-import Toolbar from './Toolbar';
5
-import SideContainerToggler from "../side_pannels/SideContainerToggler";
6
-
7
-let toolbarTimeoutObject;
8
-let toolbarTimeout = interfaceConfig.INITIAL_TOOLBAR_TIMEOUT;
9
-/**
10
- * If true the toolbar will be always displayed
11
- */
12
-let alwaysVisibleToolbar = false;
13
-
14
-function showDesktopSharingButton() {
15
-    if (APP.conference.isDesktopSharingEnabled &&
16
-        UIUtil.isButtonEnabled('desktop')) {
17
-        $('#toolbar_button_desktopsharing').css({display: "inline-block"});
18
-    } else {
19
-        $('#toolbar_button_desktopsharing').css({display: "none"});
20
-    }
21
-}
22
-
23
-/**
24
- * Hides the toolbar.
25
- *
26
- * @param force {true} to force the hiding of the toolbar without caring about
27
- * the extended toolbar side panels.
28
- */
29
-function hideToolbar(force) { // eslint-disable-line no-unused-vars
30
-    if (alwaysVisibleToolbar) {
31
-        return;
32
-    }
33
-
34
-    clearTimeout(toolbarTimeoutObject);
35
-    toolbarTimeoutObject = null;
36
-
37
-    if (force !== true &&
38
-            (Toolbar.isHovered()
39
-                || APP.UI.isRingOverlayVisible()
40
-                || SideContainerToggler.isVisible())) {
41
-        toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
42
-    } else {
43
-        Toolbar.hide();
44
-        $('#subject').animate({top: "-=40"}, 300);
45
-    }
46
-}
47
-
48
-const ToolbarToggler = {
49
-    /**
50
-     * Initializes the ToolbarToggler
51
-     */
52
-    init() {
53
-        alwaysVisibleToolbar = (config.alwaysVisibleToolbar === true);
54
-
55
-        // disabled
56
-        //this._registerWindowClickListeners();
57
-    },
58
-
59
-    /**
60
-     * Registers click listeners handling the show and hode of toolbars when
61
-     * user clicks outside of toolbar area.
62
-     */
63
-    _registerWindowClickListeners() {
64
-        $(window).click(function() {
65
-            (Toolbar.isEnabled() && Toolbar.isVisible())
66
-                ? hideToolbar(true)
67
-                : this.showToolbar();
68
-        }.bind(this));
69
-
70
-        Toolbar.registerClickListeners(function(event){
71
-            event.stopPropagation();
72
-        });
73
-    },
74
-
75
-    /**
76
-     * Sets the value of alwaysVisibleToolbar variable.
77
-     * @param value {boolean} the new value of alwaysVisibleToolbar variable
78
-     */
79
-    setAlwaysVisibleToolbar(value) {
80
-        alwaysVisibleToolbar = value;
81
-    },
82
-
83
-    /**
84
-     * Resets the value of alwaysVisibleToolbar variable to the default one.
85
-     */
86
-    resetAlwaysVisibleToolbar() {
87
-        alwaysVisibleToolbar = (config.alwaysVisibleToolbar === true);
88
-    },
89
-
90
-    /**
91
-     * Shows the main toolbar.
92
-     * @param timeout (optional) to specify custom timeout value
93
-     */
94
-    showToolbar (timeout) {
95
-        if (interfaceConfig.filmStripOnly) {
96
-            return;
97
-        }
98
-
99
-        var updateTimeout = false;
100
-        if (Toolbar.isEnabled() && !Toolbar.isVisible()) {
101
-            Toolbar.show();
102
-            $('#subject').animate({top: "+=40"}, 300);
103
-            updateTimeout = true;
104
-        }
105
-
106
-        if (updateTimeout) {
107
-            if (toolbarTimeoutObject) {
108
-                clearTimeout(toolbarTimeoutObject);
109
-                toolbarTimeoutObject = null;
110
-            }
111
-            toolbarTimeoutObject
112
-                = setTimeout(hideToolbar, timeout || toolbarTimeout);
113
-            toolbarTimeout = interfaceConfig.TOOLBAR_TIMEOUT;
114
-        }
115
-
116
-        // Show/hide desktop sharing button
117
-        showDesktopSharingButton();
118
-    },
119
-
120
-    /**
121
-     * Docks/undocks the toolbar.
122
-     *
123
-     * @param isDock indicates what operation to perform
124
-     */
125
-    dockToolbar (isDock) {
126
-        if (interfaceConfig.filmStripOnly || !Toolbar.isEnabled()) {
127
-            return;
128
-        }
129
-
130
-        if (isDock) {
131
-            // First make sure the toolbar is shown.
132
-            if (!Toolbar.isVisible()) {
133
-                this.showToolbar();
134
-            }
135
-
136
-            // Then clear the time out, to dock the toolbar.
137
-            clearTimeout(toolbarTimeoutObject);
138
-            toolbarTimeoutObject = null;
139
-        } else {
140
-            if (Toolbar.isVisible()) {
141
-                toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
142
-            } else {
143
-                this.showToolbar();
144
-            }
145
-        }
146
-    }
147
-};
148
-
149
-module.exports = ToolbarToggler;

+ 112
- 17
react/features/conference/components/Conference.native.js View File

@@ -6,7 +6,7 @@ import { DialogContainer } from '../../base/dialog';
6 6
 import { Container } from '../../base/react';
7 7
 import { FilmStrip } from '../../film-strip';
8 8
 import { LargeVideo } from '../../large-video';
9
-import { Toolbar } from '../../toolbar';
9
+import { setToolbarVisible, Toolbar } from '../../toolbar';
10 10
 
11 11
 import { styles } from './styles';
12 12
 
@@ -28,8 +28,39 @@ class Conference extends Component {
28 28
      * @static
29 29
      */
30 30
     static propTypes = {
31
-        dispatch: React.PropTypes.func
32
-    }
31
+        /**
32
+         * The handler which dispatches the (redux) action connect.
33
+         *
34
+         * @private
35
+         * @type {Function}
36
+         */
37
+        _onConnect: React.PropTypes.func,
38
+
39
+        /**
40
+         * The handler which dispatches the (redux) action disconnect.
41
+         *
42
+         * @private
43
+         * @type {Function}
44
+         */
45
+        _onDisconnect: React.PropTypes.func,
46
+
47
+        /**
48
+         * The handler which dispatches the (redux) action setTooblarVisible to
49
+         * show/hide the toolbar.
50
+         *
51
+         * @private
52
+         * @type {boolean}
53
+         */
54
+        _setToolbarVisible: React.PropTypes.func,
55
+
56
+        /**
57
+         * The indicator which determines whether toolbar is visible.
58
+         *
59
+         * @private
60
+         * @type {boolean}
61
+         */
62
+        _toolbarVisible: React.PropTypes.bool
63
+    };
33 64
 
34 65
     /**
35 66
      * Initializes a new Conference instance.
@@ -40,8 +71,6 @@ class Conference extends Component {
40 71
     constructor(props) {
41 72
         super(props);
42 73
 
43
-        this.state = { toolbarVisible: true };
44
-
45 74
         /**
46 75
          * The numerical ID of the timeout in milliseconds after which the
47 76
          * toolbar will be hidden. To be used with
@@ -62,7 +91,7 @@ class Conference extends Component {
62 91
      * returns {void}
63 92
      */
64 93
     componentDidMount() {
65
-        this._setToolbarTimeout(this.state.toolbarVisible);
94
+        this._setToolbarTimeout(this.props._toolbarVisible);
66 95
     }
67 96
 
68 97
     /**
@@ -72,7 +101,7 @@ class Conference extends Component {
72 101
      * @returns {void}
73 102
      */
74 103
     componentWillMount() {
75
-        this.props.dispatch(connect());
104
+        this.props._onConnect();
76 105
     }
77 106
 
78 107
     /**
@@ -85,7 +114,7 @@ class Conference extends Component {
85 114
     componentWillUnmount() {
86 115
         this._clearToolbarTimeout();
87 116
 
88
-        this.props.dispatch(disconnect());
117
+        this.props._onDisconnect();
89 118
     }
90 119
 
91 120
     /**
@@ -95,8 +124,6 @@ class Conference extends Component {
95 124
      * @returns {ReactElement}
96 125
      */
97 126
     render() {
98
-        const toolbarVisible = this.state.toolbarVisible;
99
-
100 127
         return (
101 128
             <Container
102 129
                 onClick = { this._onClick }
@@ -104,11 +131,11 @@ class Conference extends Component {
104 131
                 touchFeedback = { false }>
105 132
 
106 133
                 <LargeVideo />
107
-                <Toolbar visible = { toolbarVisible } />
108
-                <FilmStrip visible = { !toolbarVisible } />
109 134
 
110
-                <DialogContainer />
135
+                <Toolbar />
136
+                <FilmStrip />
111 137
 
138
+                <DialogContainer />
112 139
             </Container>
113 140
         );
114 141
     }
@@ -135,10 +162,9 @@ class Conference extends Component {
135 162
      * @returns {void}
136 163
      */
137 164
     _onClick() {
138
-        const toolbarVisible = !this.state.toolbarVisible;
139
-
140
-        this.setState({ toolbarVisible });
165
+        const toolbarVisible = !this.props._toolbarVisible;
141 166
 
167
+        this.props._setToolbarVisible(toolbarVisible);
142 168
         this._setToolbarTimeout(toolbarVisible);
143 169
     }
144 170
 
@@ -159,4 +185,73 @@ class Conference extends Component {
159 185
     }
160 186
 }
161 187
 
162
-export default reactReduxConnect()(Conference);
188
+/**
189
+ * Maps dispatching of some action to React component props.
190
+ *
191
+ * @param {Function} dispatch - Redux action dispatcher.
192
+ * @private
193
+ * @returns {{
194
+ *     _onConnect: Function,
195
+ *     _onDisconnect: Function,
196
+ *     _setToolbarVisible: Function
197
+ * }}
198
+ */
199
+function _mapDispatchToProps(dispatch) {
200
+    return {
201
+        /**
202
+         * Dispatched an action connecting to the conference.
203
+         *
204
+         * @returns {Object} Dispatched action.
205
+         * @private
206
+         */
207
+        _onConnect() {
208
+            return dispatch(connect());
209
+        },
210
+
211
+        /**
212
+         * Dispatches an action disconnecting from the conference.
213
+         *
214
+         * @returns {Object} Dispatched action.
215
+         * @private
216
+         */
217
+        _onDisconnect() {
218
+            return dispatch(disconnect());
219
+        },
220
+
221
+        /**
222
+         * Dispatched an action changing visiblity of the toolbar.
223
+         *
224
+         * @param {boolean} isVisible - Flag showing whether toolbar is
225
+         * visible.
226
+         * @returns {Object} Dispatched action.
227
+         * @private
228
+         */
229
+        _setToolbarVisible(isVisible: boolean) {
230
+            return dispatch(setToolbarVisible(isVisible));
231
+        }
232
+    };
233
+}
234
+
235
+/**
236
+ * Maps (parts of) the Redux state to the associated Conference's props.
237
+ *
238
+ * @param {Object} state - The Redux state.
239
+ * @private
240
+ * @returns {{
241
+ *     _toolbarVisible: boolean
242
+ * }}
243
+ */
244
+function _mapStateToProps(state) {
245
+    return {
246
+        /**
247
+         * The indicator which determines whether toolbar is visible.
248
+         *
249
+         * @private
250
+         * @type {boolean}
251
+         */
252
+        _toolbarVisible: state['features/toolbar'].visible
253
+    };
254
+}
255
+
256
+export default reactReduxConnect(_mapStateToProps, _mapDispatchToProps)(
257
+        Conference);

+ 3
- 20
react/features/conference/components/Conference.web.js View File

@@ -6,9 +6,8 @@ import { connect as reactReduxConnect } from 'react-redux';
6 6
 import { connect, disconnect } from '../../base/connection';
7 7
 import { DialogContainer } from '../../base/dialog';
8 8
 import { Watermarks } from '../../base/react';
9
-import { FeedbackButton } from '../../feedback';
10 9
 import { OverlayContainer } from '../../overlay';
11
-import { Notice } from '../../toolbar';
10
+import { Toolbar } from '../../toolbar';
12 11
 import { HideNotificationBarStyle } from '../../unsupported-browser';
13 12
 
14 13
 declare var $: Function;
@@ -66,25 +65,8 @@ class Conference extends Component {
66 65
     render() {
67 66
         return (
68 67
             <div id = 'videoconference_page'>
69
-                <div id = 'mainToolbarContainer'>
70
-                    <Notice />
68
+                <Toolbar />
71 69
 
72
-                    <div
73
-                        className = 'toolbar'
74
-                        id = 'mainToolbar' />
75
-                </div>
76
-                <div
77
-                    className = 'hide'
78
-                    id = 'subject' />
79
-                <div
80
-                    className = 'toolbar'
81
-                    id = 'extendedToolbar'>
82
-                    <div id = 'extendedToolbarButtons' />
83
-
84
-                    <FeedbackButton />
85
-
86
-                    <div id = 'sideToolbarContainer' />
87
-                </div>
88 70
                 <div id = 'videospace'>
89 71
                     <div
90 72
                         className = 'videocontainer'
@@ -154,6 +136,7 @@ class Conference extends Component {
154 136
                         </div>
155 137
                     </div>
156 138
                 </div>
139
+
157 140
                 <DialogContainer />
158 141
                 <OverlayContainer />
159 142
                 <HideNotificationBarStyle />

+ 17
- 4
react/features/film-strip/components/FilmStrip.js View File

@@ -1,6 +1,6 @@
1 1
 import React, { Component } from 'react';
2
-import { connect } from 'react-redux';
3 2
 import { ScrollView } from 'react-native';
3
+import { connect } from 'react-redux';
4 4
 
5 5
 import { Container } from '../../base/react';
6 6
 
@@ -33,7 +33,7 @@ class FilmStrip extends Component {
33 33
          * @private
34 34
          * @type {boolean}
35 35
          */
36
-        visible: React.PropTypes.bool.isRequired
36
+        _visible: React.PropTypes.bool.isRequired
37 37
     }
38 38
 
39 39
     /**
@@ -45,7 +45,7 @@ class FilmStrip extends Component {
45 45
         return (
46 46
             <Container
47 47
                 style = { styles.filmStrip }
48
-                visible = { this.props.visible }>
48
+                visible = { this.props._visible }>
49 49
                 <ScrollView
50 50
 
51 51
                     // eslint-disable-next-line react/jsx-curly-spacing
@@ -109,6 +109,7 @@ class FilmStrip extends Component {
109 109
  * @private
110 110
  * @returns {{
111 111
  *     _participants: Participant[],
112
+ *     _visible: boolean
112 113
  *  }}
113 114
  */
114 115
 function _mapStateToProps(state) {
@@ -119,7 +120,19 @@ function _mapStateToProps(state) {
119 120
          * @private
120 121
          * @type {Participant[]}
121 122
          */
122
-        _participants: state['features/base/participants']
123
+        _participants: state['features/base/participants'],
124
+
125
+        /**
126
+         * The indicator which determines whether the film strip is visible.
127
+         *
128
+         * XXX The React Component FilmStrip is used on mobile only at the time
129
+         * of this writing and on mobile the film strip is visible when the
130
+         * toolbar is not.
131
+         *
132
+         * @private
133
+         * @type {boolean}
134
+         */
135
+        _visible: !state['features/toolbar'].visible
123 136
     };
124 137
 }
125 138
 

+ 98
- 0
react/features/toolbar/actionTypes.js View File

@@ -0,0 +1,98 @@
1
+import { Symbol } from '../base/react';
2
+
3
+/**
4
+ * The type of the action which signals that toolbar timeout should be changed.
5
+ *
6
+ * {
7
+ *      type: CLEAR_TOOLBAR_TIMEOUT
8
+ * }
9
+ */
10
+export const CLEAR_TOOLBAR_TIMEOUT = Symbol('CLEAR_TOOLBAR_TIMEOUT');
11
+
12
+/**
13
+ * The type of the action which signals that a value for always visible toolbar
14
+ * should be changed.
15
+ *
16
+ * {
17
+ *     type: SET_ALWAYS_VISIBLE_TOOLBAR,
18
+ *     alwaysVisible: boolean
19
+ * }
20
+ */
21
+export const SET_ALWAYS_VISIBLE_TOOLBAR = Symbol('SET_ALWAYS_VISIBLE_TOOLBAR');
22
+
23
+/**
24
+ * The type of the action which signals that a value for conference subject
25
+ * should be changed.
26
+ *
27
+ * {
28
+ *      type: SET_SUBJECT,
29
+ *      subject: string
30
+ * }
31
+ */
32
+export const SET_SUBJECT = Symbol('SET_SUBJECT');
33
+
34
+/**
35
+ * The type of the action which signals that a value of subject slide in should
36
+ * be changed.
37
+ *
38
+ * {
39
+ *     type: SET_SUBJECT_SLIDE_IN,
40
+ *     subjectSlideIn: boolean
41
+ * }
42
+ */
43
+export const SET_SUBJECT_SLIDE_IN = Symbol('SET_SUBJECT_SLIDE_IN');
44
+
45
+/**
46
+ * The type of the action which signals that a value for toolbar button should
47
+ * be changed.
48
+ *
49
+ * {
50
+ *     type: SET_TOOLBAR_BUTTON,
51
+ *     button: Object,
52
+ *     key: string
53
+ * }
54
+ */
55
+export const SET_TOOLBAR_BUTTON = Symbol('SET_TOOLBAR_BUTTON');
56
+
57
+/**
58
+ * The type of the action which signals that toolbar is/isn't being hovered.
59
+ *
60
+ * {
61
+ *     type: SET_TOOLBAR_HOVERED,
62
+ *     hovered: boolean
63
+ * }
64
+ */
65
+export const SET_TOOLBAR_HOVERED = Symbol('SET_TOOLBAR_HOVERED');
66
+
67
+/**
68
+ * The type of the action which signals that new toolbar timeout should be set
69
+ * and the value of toolbar timeout should be changed.
70
+ *
71
+ * {
72
+ *      type: SET_TOOLBAR_TIMEOUT,
73
+ *      handler: Function,
74
+        toolbarTimeout: number
75
+ * }
76
+ */
77
+export const SET_TOOLBAR_TIMEOUT = Symbol('SET_TOOLBAR_TIMEOUT');
78
+
79
+/**
80
+ * The type of the action which signals that value of toolbar timeout should
81
+ * be changed.
82
+ *
83
+ * {
84
+ *      type: SET_TOOLBAR_TIMEOUT_NUMBER,
85
+ *      toolbarTimeout: number
86
+ * }
87
+ */
88
+export const SET_TOOLBAR_TIMEOUT_NUMBER = Symbol('SET_TOOLBAR_TIMEOUT');
89
+
90
+/**
91
+ * The type of the (redux) action which shows/hides the toolbar.
92
+ *
93
+ * {
94
+ *     type: SET_TOOLBAR_VISIBLE,
95
+ *     visible: boolean
96
+ * }
97
+ */
98
+export const SET_TOOLBAR_VISIBLE = Symbol('SET_TOOLBAR_VISIBLE');

+ 277
- 0
react/features/toolbar/actions.native.js View File

@@ -0,0 +1,277 @@
1
+/* @flow */
2
+
3
+import type { Dispatch } from 'redux-thunk';
4
+
5
+import {
6
+    CLEAR_TOOLBAR_TIMEOUT,
7
+    SET_ALWAYS_VISIBLE_TOOLBAR,
8
+    SET_SUBJECT,
9
+    SET_SUBJECT_SLIDE_IN,
10
+    SET_TOOLBAR_BUTTON,
11
+    SET_TOOLBAR_HOVERED,
12
+    SET_TOOLBAR_TIMEOUT,
13
+    SET_TOOLBAR_TIMEOUT_NUMBER,
14
+    SET_TOOLBAR_VISIBLE
15
+} from './actionTypes';
16
+
17
+/**
18
+ * Event handler for local raise hand changed event.
19
+ *
20
+ * @param {boolean} handRaised - Flag showing whether hand is raised.
21
+ * @returns {Function}
22
+ */
23
+export function changeLocalRaiseHand(handRaised: boolean): Function {
24
+    return (dispatch: Dispatch<*>, getState: Function) => {
25
+        const state = getState();
26
+        const { secondaryToolbarButtons } = state['features/toolbar'];
27
+        const buttonName = 'raisehand';
28
+        const button = secondaryToolbarButtons.get(buttonName);
29
+
30
+        button.toggled = handRaised;
31
+
32
+        dispatch(setToolbarButton(buttonName, button));
33
+    };
34
+}
35
+
36
+/**
37
+ * Signals that toolbar timeout should be cleared.
38
+ *
39
+ * @returns {{
40
+ *      type: CLEAR_TOOLBAR_TIMEOUT
41
+ * }}
42
+ */
43
+export function clearToolbarTimeout(): Object {
44
+    return {
45
+        type: CLEAR_TOOLBAR_TIMEOUT
46
+    };
47
+}
48
+
49
+/**
50
+ * Signals that always visible toolbars value should be changed.
51
+ *
52
+ * @param {boolean} alwaysVisible - Value to be set in redux store.
53
+ * @returns {{
54
+ *     type: SET_ALWAYS_VISIBLE_TOOLBAR,
55
+ *     alwaysVisible: bool
56
+ * }}
57
+ */
58
+export function setAlwaysVisibleToolbar(alwaysVisible: boolean): Object {
59
+    return {
60
+        type: SET_ALWAYS_VISIBLE_TOOLBAR,
61
+        alwaysVisible
62
+    };
63
+}
64
+
65
+/**
66
+ * Enables / disables audio toolbar button.
67
+ *
68
+ * @param {boolean} enabled - Indicates if the button should be enabled
69
+ * or disabled.
70
+ * @returns {Function}
71
+ */
72
+export function setAudioIconEnabled(enabled: boolean = false): Function {
73
+    return (dispatch: Dispatch<*>) => {
74
+        const i18nKey = enabled ? 'mute' : 'micDisabled';
75
+        const i18n = `[content]toolbar.${i18nKey}`;
76
+        const button = {
77
+            enabled,
78
+            i18n,
79
+            toggled: !enabled
80
+        };
81
+
82
+        dispatch(setToolbarButton('microphone', button));
83
+    };
84
+}
85
+
86
+/**
87
+ *  Signals that value of conference subject should be changed.
88
+ *
89
+ *  @param {string} subject - Conference subject string.
90
+ *  @returns {Object}
91
+ */
92
+export function setSubject(subject: string) {
93
+    return {
94
+        type: SET_SUBJECT,
95
+        subject
96
+    };
97
+}
98
+
99
+/**
100
+ * Signals that toolbar subject slide in value should be changed.
101
+ *
102
+ * @param {boolean} subjectSlideIn - Flag showing whether subject is shown.
103
+ * @returns {{
104
+ *     type: SET_SUBJECT_SLIDE_IN,
105
+ *     subjectSlideIn: boolean
106
+ * }}
107
+ */
108
+export function setSubjectSlideIn(subjectSlideIn: boolean): Object {
109
+    return {
110
+        type: SET_SUBJECT_SLIDE_IN,
111
+        subjectSlideIn
112
+    };
113
+}
114
+
115
+/**
116
+ * Signals that value of the button specified by key should be changed.
117
+ *
118
+ * @param {string} buttonName - Button key.
119
+ * @param {Object} button - Button object.
120
+ * @returns {{
121
+ *     type: SET_TOOLBAR_BUTTON,
122
+ *     buttonName: string,
123
+ *     button: Object
124
+ * }}
125
+ */
126
+export function setToolbarButton(buttonName: string, button: Object): Object {
127
+    return {
128
+        type: SET_TOOLBAR_BUTTON,
129
+        buttonName,
130
+        button
131
+    };
132
+}
133
+
134
+/**
135
+ * Signals that toolbar is hovered value should be changed.
136
+ *
137
+ * @param {boolean} hovered - Flag showing whether toolbar is hovered.
138
+ * @returns {{
139
+ *     type: SET_TOOLBAR_HOVERED,
140
+ *     hovered: boolean
141
+ * }}
142
+ */
143
+export function setToolbarHovered(hovered: boolean): Object {
144
+    return {
145
+        type: SET_TOOLBAR_HOVERED,
146
+        hovered
147
+    };
148
+}
149
+
150
+/**
151
+ * Dispatches an action which sets new timeout and clears the previous one.
152
+ *
153
+ * @param {Function} handler - Function to be invoked after the timeout.
154
+ * @param {number} toolbarTimeout - Delay.
155
+ * @returns {{
156
+ *      type: SET_TOOLBAR_TIMEOUT,
157
+ *      handler: Function,
158
+ *      toolbarTimeout: number
159
+ * }}
160
+ */
161
+export function setToolbarTimeout(handler: Function,
162
+        toolbarTimeout: number): Object {
163
+    return {
164
+        type: SET_TOOLBAR_TIMEOUT,
165
+        handler,
166
+        toolbarTimeout
167
+    };
168
+}
169
+
170
+/**
171
+ * Dispatches an action which sets new toolbar timeout value.
172
+ *
173
+ * @param {number} toolbarTimeout - Delay.
174
+ * @returns {{
175
+ *      type: SET_TOOLBAR_TIMEOUT_NUMBER,
176
+ *      toolbarTimeout: number
177
+ * }}
178
+ */
179
+export function setToolbarTimeoutNumber(toolbarTimeout: number): Object {
180
+    return {
181
+        type: SET_TOOLBAR_TIMEOUT_NUMBER,
182
+        toolbarTimeout
183
+    };
184
+}
185
+
186
+/**
187
+ * Shows/hides the toolbar.
188
+ *
189
+ * @param {boolean} visible - True to show the toolbar or false to hide it.
190
+ * @returns {{
191
+ *     type: SET_TOOLBAR_VISIBLE,
192
+ *     visible: boolean
193
+ * }}
194
+ */
195
+export function setToolbarVisible(visible: boolean): Object {
196
+    return {
197
+        type: SET_TOOLBAR_VISIBLE,
198
+        visible
199
+    };
200
+}
201
+
202
+/**
203
+ * Enables / disables audio toolbar button.
204
+ *
205
+ * @param {boolean} enabled - Indicates if the button should be enabled
206
+ * or disabled.
207
+ * @returns {Function}
208
+ */
209
+export function setVideoIconEnabled(enabled: boolean = false): Function {
210
+    return (dispatch: Dispatch<*>) => {
211
+        const i18nKey = enabled ? 'videomute' : 'cameraDisabled';
212
+        const i18n = `[content]toolbar.${i18nKey}`;
213
+        const button = {
214
+            enabled,
215
+            i18n,
216
+            toggled: !enabled
217
+        };
218
+
219
+        dispatch(setToolbarButton('camera', button));
220
+    };
221
+}
222
+
223
+/**
224
+ * Shows etherpad button if it's not shown.
225
+ *
226
+ * @returns {Function}
227
+ */
228
+export function showEtherpadButton(): Function {
229
+    return (dispatch: Dispatch<*>) => {
230
+        dispatch(setToolbarButton('etherpad', {
231
+            hidden: false
232
+        }));
233
+    };
234
+}
235
+
236
+/**
237
+ * Event handler for full screen toggled event.
238
+ *
239
+ * @param {boolean} isFullScreen - Flag showing whether app in full
240
+ * screen mode.
241
+ * @returns {Function}
242
+ */
243
+export function toggleFullScreen(isFullScreen: boolean): Function {
244
+    return (dispatch: Dispatch<*>, getState: Function) => {
245
+        const state = getState();
246
+        const { primaryToolbarButtons } = state['features/toolbar'];
247
+        const buttonName = 'fullscreen';
248
+        const button = primaryToolbarButtons.get(buttonName);
249
+
250
+        button.toggled = isFullScreen;
251
+
252
+        dispatch(setToolbarButton(buttonName, button));
253
+    };
254
+}
255
+
256
+/**
257
+ * Sets negation of button's toggle property.
258
+ *
259
+ * @param {string} buttonName - Button key.
260
+ * @returns {Function}
261
+ */
262
+export function toggleToolbarButton(buttonName: string): Function {
263
+    return (dispatch: Dispatch, getState: Function) => {
264
+        const state = getState();
265
+        const {
266
+            primaryToolbarButtons,
267
+            secondaryToolbarButtons
268
+        } = state['features/toolbar'];
269
+        const button
270
+            = primaryToolbarButtons.get(buttonName)
271
+                || secondaryToolbarButtons.get(buttonName);
272
+
273
+        dispatch(setToolbarButton(buttonName, {
274
+            toggled: !button.toggled
275
+        }));
276
+    };
277
+}

+ 291
- 0
react/features/toolbar/actions.web.js View File

@@ -0,0 +1,291 @@
1
+/* @flow */
2
+
3
+import Recording from '../../../modules/UI/recording/Recording';
4
+import SideContainerToggler
5
+    from '../../../modules/UI/side_pannels/SideContainerToggler';
6
+import UIEvents from '../../../service/UI/UIEvents';
7
+import UIUtil from '../../../modules/UI/util/UIUtil';
8
+
9
+import {
10
+    clearToolbarTimeout,
11
+    setAlwaysVisibleToolbar,
12
+    setSubjectSlideIn,
13
+    setToolbarButton,
14
+    setToolbarTimeout,
15
+    setToolbarTimeoutNumber,
16
+    setToolbarVisible,
17
+    toggleToolbarButton
18
+} from './actions.native';
19
+
20
+export * from './actions.native';
21
+
22
+declare var $: Function;
23
+declare var APP: Object;
24
+declare var config: Object;
25
+declare var interfaceConfig: Object;
26
+
27
+/**
28
+ * Checks whether desktop sharing is enabled and whether
29
+ * we have params to start automatically sharing.
30
+ *
31
+ * @returns {Function}
32
+ */
33
+export function checkAutoEnableDesktopSharing(): Function {
34
+    return () => {
35
+        // XXX Should use dispatcher to toggle screensharing but screensharing
36
+        // hasn't been React-ified yet.
37
+
38
+        if (UIUtil.isButtonEnabled('desktop')
39
+                && config.autoEnableDesktopSharing) {
40
+            APP.UI.eventEmitter.emit(UIEvents.TOGGLE_SCREENSHARING);
41
+        }
42
+    };
43
+}
44
+
45
+/**
46
+ * Docks/undocks toolbar based on its parameter.
47
+ *
48
+ * @param {boolean} dock - True if dock, false otherwise.
49
+ * @returns {Function}
50
+ */
51
+export function dockToolbar(dock: boolean): Function {
52
+    return (dispatch: Dispatch<*>, getState: Function) => {
53
+        if (interfaceConfig.filmStripOnly) {
54
+            return;
55
+        }
56
+
57
+        const state = getState();
58
+        const { toolbarTimeout, visible } = state['features/toolbar'];
59
+
60
+        if (dock) {
61
+            // First make sure the toolbar is shown.
62
+            visible || dispatch(showToolbar());
63
+
64
+            dispatch(clearToolbarTimeout());
65
+        } else if (visible) {
66
+            dispatch(
67
+                setToolbarTimeout(
68
+                    () => dispatch(hideToolbar()),
69
+                    toolbarTimeout));
70
+        } else {
71
+            dispatch(showToolbar());
72
+        }
73
+    };
74
+}
75
+
76
+/**
77
+ * Hides the toolbar.
78
+ *
79
+ * @param {boolean} force - True to force the hiding of the toolbar without
80
+ * caring about the extended toolbar side panels.
81
+ * @returns {Function}
82
+ */
83
+export function hideToolbar(force: boolean = false): Function {
84
+    return (dispatch: Dispatch<*>, getState: Function) => {
85
+        const state = getState();
86
+        const {
87
+            alwaysVisible,
88
+            hovered,
89
+            toolbarTimeout
90
+        } = state['features/toolbar'];
91
+
92
+        if (alwaysVisible) {
93
+            return;
94
+        }
95
+
96
+        dispatch(clearToolbarTimeout());
97
+
98
+        if (!force
99
+                && (hovered
100
+                    || APP.UI.isRingOverlayVisible()
101
+                    || SideContainerToggler.isVisible())) {
102
+            dispatch(
103
+                setToolbarTimeout(
104
+                    () => dispatch(hideToolbar()),
105
+                    toolbarTimeout));
106
+        } else {
107
+            dispatch(setToolbarVisible(false));
108
+            dispatch(setSubjectSlideIn(false));
109
+        }
110
+    };
111
+}
112
+
113
+/**
114
+ * Action that reset always visible toolbar to default state.
115
+ *
116
+ * @returns {Function}
117
+ */
118
+export function resetAlwaysVisibleToolbar(): Function {
119
+    return (dispatch: Dispatch<*>) => {
120
+        const alwaysVisible = config.alwaysVisibleToolbar === true;
121
+
122
+        dispatch(setAlwaysVisibleToolbar(alwaysVisible));
123
+    };
124
+}
125
+
126
+/**
127
+ * Signals that unclickable property of profile button should change its value.
128
+ *
129
+ * @param {boolean} unclickable - Shows whether button is unclickable.
130
+ * @returns {Function}
131
+ */
132
+export function setProfileButtonUnclickable(unclickable: boolean): Function {
133
+    return (dispatch: Dispatch<*>) => {
134
+        const buttonName = 'profile';
135
+
136
+        dispatch(setToolbarButton(buttonName, {
137
+            unclickable
138
+        }));
139
+
140
+        UIUtil.removeTooltip(document.getElementById('toolbar_button_profile'));
141
+    };
142
+}
143
+
144
+/**
145
+ * Shows desktop sharing button.
146
+ *
147
+ * @returns {Function}
148
+ */
149
+export function showDesktopSharingButton(): Function {
150
+    return (dispatch: Dispatch<*>) => {
151
+        const buttonName = 'desktop';
152
+        const visible
153
+            = APP.conference.isDesktopSharingEnabled
154
+                && UIUtil.isButtonEnabled(buttonName);
155
+
156
+        dispatch(setToolbarButton(buttonName, {
157
+            hidden: !visible
158
+        }));
159
+    };
160
+}
161
+
162
+/**
163
+ * Shows or hides the dialpad button.
164
+ *
165
+ * @param {boolean} show - Flag showing whether to show button or not.
166
+ * @returns {Function}
167
+ */
168
+export function showDialPadButton(show: boolean): Function {
169
+    return (dispatch: Dispatch<*>) => {
170
+        const buttonName = 'dialpad';
171
+        const shouldShow = UIUtil.isButtonEnabled(buttonName) && show;
172
+
173
+        if (shouldShow) {
174
+            dispatch(setToolbarButton(buttonName, {
175
+                hidden: false
176
+            }));
177
+        }
178
+    };
179
+}
180
+
181
+/**
182
+ * Shows recording button.
183
+ *
184
+ * @returns {Function}
185
+ */
186
+export function showRecordingButton(): Function {
187
+    return (dispatch: Dispatch<*>) => {
188
+        const eventEmitter = APP.UI.eventEmitter;
189
+        const buttonName = 'recording';
190
+
191
+        dispatch(setToolbarButton(buttonName, {
192
+            hidden: false
193
+        }));
194
+
195
+        Recording.init(eventEmitter, config.recordingType);
196
+    };
197
+}
198
+
199
+/**
200
+ * Shows or hides the 'shared video' button.
201
+ *
202
+ * @returns {Function}
203
+ */
204
+export function showSharedVideoButton(): Function {
205
+    return (dispatch: Dispatch<*>) => {
206
+        const buttonName = 'sharedvideo';
207
+        const shouldShow
208
+            = UIUtil.isButtonEnabled(buttonName)
209
+                && !config.disableThirdPartyRequests;
210
+
211
+        if (shouldShow) {
212
+            dispatch(setToolbarButton(buttonName, {
213
+                hidden: false
214
+            }));
215
+        }
216
+    };
217
+}
218
+
219
+/**
220
+ * Shows SIP call button if it's required and appropriate
221
+ * flag is passed.
222
+ *
223
+ * @param {boolean} show - Flag showing whether to show button or not.
224
+ * @returns {Function}
225
+ */
226
+export function showSIPCallButton(show: boolean): Function {
227
+    return (dispatch: Dispatch<*>) => {
228
+        const buttonName = 'sip';
229
+        const shouldShow
230
+            = APP.conference.sipGatewayEnabled()
231
+                && UIUtil.isButtonEnabled(buttonName)
232
+                && show;
233
+
234
+        if (shouldShow) {
235
+            dispatch(setToolbarButton(buttonName, {
236
+                hidden: !shouldShow
237
+            }));
238
+        }
239
+    };
240
+}
241
+
242
+/**
243
+ * Shows the toolbar for specified timeout.
244
+ *
245
+ * @param {number} timeout - Timeout for showing the toolbar.
246
+ * @returns {Function}
247
+ */
248
+export function showToolbar(timeout: number = 0): Object {
249
+    return (dispatch: Dispatch<*>, getState: Function) => {
250
+        if (interfaceConfig.filmStripOnly) {
251
+            return;
252
+        }
253
+
254
+        const state = getState();
255
+        const { toolbarTimeout, visible } = state['features/toolbar'];
256
+        const finalTimeout = timeout || toolbarTimeout;
257
+
258
+        if (!visible) {
259
+            dispatch(setToolbarVisible(true));
260
+            dispatch(setSubjectSlideIn(true));
261
+            dispatch(
262
+                setToolbarTimeout(() => dispatch(hideToolbar()), finalTimeout));
263
+            dispatch(setToolbarTimeoutNumber(interfaceConfig.TOOLBAR_TIMEOUT));
264
+        }
265
+    };
266
+}
267
+
268
+/**
269
+ * Event handler for side toolbar container toggled event.
270
+ *
271
+ * @param {string} containerId - ID of the container.
272
+ * @returns {void}
273
+ */
274
+export function toggleSideToolbarContainer(containerId: string): Function {
275
+    return (dispatch: Dispatch, getState: Function) => {
276
+        const state = getState();
277
+        const { secondaryToolbarButtons } = state['features/toolbar'];
278
+
279
+        for (const key of secondaryToolbarButtons.keys()) {
280
+            const isButtonEnabled = UIUtil.isButtonEnabled(key);
281
+            const button = secondaryToolbarButtons.get(key);
282
+
283
+            if (isButtonEnabled
284
+                    && button.sideContainerId
285
+                    && button.sideContainerId === containerId) {
286
+                dispatch(toggleToolbarButton(key));
287
+                break;
288
+            }
289
+        }
290
+    };
291
+}

+ 0
- 165
react/features/toolbar/components/AbstractToolbar.js View File

@@ -1,165 +0,0 @@
1
-import React, { Component } from 'react';
2
-
3
-import { appNavigate } from '../../app';
4
-import { toggleAudioMuted, toggleVideoMuted } from '../../base/media';
5
-import { ColorPalette } from '../../base/styles';
6
-import { beginRoomLockRequest } from '../../room-lock';
7
-
8
-import { styles } from './styles';
9
-
10
-/**
11
- * Abstract (base) class for the conference toolbar.
12
- *
13
- * @abstract
14
- */
15
-export class AbstractToolbar extends Component {
16
-    /**
17
-     * AbstractToolbar component's property types.
18
-     *
19
-     * @static
20
-     */
21
-    static propTypes = {
22
-        _audioMuted: React.PropTypes.bool,
23
-
24
-        /**
25
-         * The indicator which determines whether the conference is
26
-         * locked/password-protected.
27
-         *
28
-         * @protected
29
-         * @type {boolean}
30
-         */
31
-        _locked: React.PropTypes.bool,
32
-        _videoMuted: React.PropTypes.bool,
33
-        dispatch: React.PropTypes.func,
34
-        visible: React.PropTypes.bool.isRequired
35
-    }
36
-
37
-    /**
38
-     * Initializes a new AbstractToolbar instance.
39
-     *
40
-     * @param {Object} props - The read-only React Component props with which
41
-     * the new instance is to be initialized.
42
-     */
43
-    constructor(props) {
44
-        super(props);
45
-
46
-        // Bind event handlers so they are only bound once for every instance.
47
-        this._onHangup = this._onHangup.bind(this);
48
-        this._onRoomLock = this._onRoomLock.bind(this);
49
-        this._toggleAudio = this._toggleAudio.bind(this);
50
-        this._toggleVideo = this._toggleVideo.bind(this);
51
-    }
52
-
53
-    /**
54
-     * Gets the styles for a button that toggles the mute state of a specific
55
-     * media type.
56
-     *
57
-     * @param {string} mediaType - The {@link MEDIA_TYPE} associated with the
58
-     * button to get styles for.
59
-     * @protected
60
-     * @returns {{
61
-     *      iconName: string,
62
-     *      iconStyle: Object,
63
-     *      style: Object
64
-     * }}
65
-     */
66
-    _getMuteButtonStyles(mediaType) {
67
-        let iconName;
68
-        let iconStyle;
69
-        let style = styles.primaryToolbarButton;
70
-
71
-        if (this.props[`_${mediaType}Muted`]) {
72
-            iconName = this[`${mediaType}MutedIcon`];
73
-            iconStyle = styles.whiteIcon;
74
-            style = {
75
-                ...style,
76
-                backgroundColor: ColorPalette.buttonUnderlay
77
-            };
78
-        } else {
79
-            iconName = this[`${mediaType}Icon`];
80
-            iconStyle = styles.icon;
81
-        }
82
-
83
-        return {
84
-            iconName,
85
-            iconStyle,
86
-            style
87
-        };
88
-    }
89
-
90
-    /**
91
-     * Dispatches action to leave the current conference.
92
-     *
93
-     * @protected
94
-     * @returns {void}
95
-     */
96
-    _onHangup() {
97
-        // XXX We don't know here which value is effectively/internally used
98
-        // when there's no valid room name to join. It isn't our business to
99
-        // know that anyway. The undefined value is our expression of (1) the
100
-        // lack of knowledge & (2) the desire to no longer have a valid room
101
-        // name to join.
102
-        this.props.dispatch(appNavigate(undefined));
103
-    }
104
-
105
-    /**
106
-     * Dispatches an action to set the lock i.e. password protection of the
107
-     * conference/room.
108
-     *
109
-     * @protected
110
-     * @returns {void}
111
-     */
112
-    _onRoomLock() {
113
-        this.props.dispatch(beginRoomLockRequest());
114
-    }
115
-
116
-    /**
117
-     * Dispatches an action to toggle the mute state of the audio/microphone.
118
-     *
119
-     * @protected
120
-     * @returns {void}
121
-     */
122
-    _toggleAudio() {
123
-        this.props.dispatch(toggleAudioMuted());
124
-    }
125
-
126
-    /**
127
-     * Dispatches an action to toggle the mute state of the video/camera.
128
-     *
129
-     * @protected
130
-     * @returns {void}
131
-     */
132
-    _toggleVideo() {
133
-        this.props.dispatch(toggleVideoMuted());
134
-    }
135
-}
136
-
137
-/**
138
- * Maps parts of media state to component props.
139
- *
140
- * @param {Object} state - Redux state.
141
- * @protected
142
- * @returns {{
143
- *     _audioMuted: boolean,
144
- *     _locked: boolean,
145
- *     _videoMuted: boolean
146
- * }}
147
- */
148
-export function _mapStateToProps(state) {
149
-    const conference = state['features/base/conference'];
150
-    const media = state['features/base/media'];
151
-
152
-    return {
153
-        _audioMuted: media.audio.muted,
154
-
155
-        /**
156
-         * The indicator which determines whether the conference is
157
-         * locked/password-protected.
158
-         *
159
-         * @protected
160
-         * @type {boolean}
161
-         */
162
-        _locked: conference.locked,
163
-        _videoMuted: media.video.muted
164
-    };
165
-}

+ 200
- 0
react/features/toolbar/components/BaseToolbar.web.js View File

@@ -0,0 +1,200 @@
1
+/* @flow */
2
+
3
+import React, { Component } from 'react';
4
+import { connect } from 'react-redux';
5
+
6
+import {
7
+    setToolbarHovered
8
+} from '../actions';
9
+import ToolbarButton from './ToolbarButton';
10
+
11
+declare var APP: Object;
12
+declare var config: Object;
13
+declare var interfaceConfig: Object;
14
+
15
+/**
16
+ * Class implementing Primary Toolbar React component.
17
+ *
18
+ * @class PrimaryToolbar
19
+ * @extends Component
20
+ */
21
+class BaseToolbar extends Component {
22
+
23
+    _renderToolbarButton: Function;
24
+
25
+    /**
26
+     * Base toolbar component's property types.
27
+     *
28
+     * @static
29
+     */
30
+    static propTypes = {
31
+
32
+        /**
33
+         *  Handler for mouse out event.
34
+         */
35
+        _onMouseOut: React.PropTypes.func,
36
+
37
+        /**
38
+         * Handler for mouse over event.
39
+         */
40
+        _onMouseOver: React.PropTypes.func,
41
+
42
+        /**
43
+         * Contains button handlers.
44
+         */
45
+        buttonHandlers: React.PropTypes.object,
46
+
47
+        /**
48
+         * Children of current React component.
49
+         */
50
+        children: React.PropTypes.element,
51
+
52
+        /**
53
+         * Toolbar's class name.
54
+         */
55
+        className: React.PropTypes.string,
56
+
57
+        /**
58
+         * If the toolbar requires splitter this property defines splitter
59
+         * index.
60
+         */
61
+        splitterIndex: React.PropTypes.number,
62
+
63
+        /**
64
+         * Map with toolbar buttons.
65
+         */
66
+        toolbarButtons: React.PropTypes.instanceOf(Map)
67
+    };
68
+
69
+    /**
70
+     * Constructor of Primary toolbar class.
71
+     *
72
+     * @param {Object} props - Object containing React component properties.
73
+     */
74
+    constructor(props) {
75
+        super(props);
76
+
77
+        this._setButtonHandlers();
78
+
79
+        // Bind methods to save the context
80
+        this._renderToolbarButton = this._renderToolbarButton.bind(this);
81
+    }
82
+
83
+    /**
84
+     * Implements React's {@link Component#render()}.
85
+     *
86
+     * @inheritdoc
87
+     * @returns {ReactElement}
88
+     */
89
+    render(): ReactElement<*> {
90
+        const { className } = this.props;
91
+
92
+        return (
93
+            <div
94
+                className = { `toolbar ${className}` }
95
+                onMouseOut = { this.props._onMouseOut }
96
+                onMouseOver = { this.props._onMouseOver }>
97
+                {
98
+                    [ ...this.props.toolbarButtons.entries() ]
99
+                        .reduce(this._renderToolbarButton, [])
100
+                }
101
+                {
102
+                    this.props.children
103
+                }
104
+            </div>
105
+        );
106
+    }
107
+
108
+    /**
109
+     * Renders toolbar button. Method is passed to reduce function.
110
+     *
111
+     * @param {Array} acc - Toolbar buttons array.
112
+     * @param {Array} keyValuePair - Key value pair containing button and its
113
+     * key.
114
+     * @param {number} index - Index of the key value pair in the array.
115
+     * @returns {Array} Array of toolbar buttons and splitter if it's on.
116
+     * @private
117
+     */
118
+    _renderToolbarButton(acc: Array<*>, keyValuePair: Array<*>,
119
+                         index: number): Array<ReactElement<*>> {
120
+        const [ key, button ] = keyValuePair;
121
+        const { splitterIndex } = this.props;
122
+
123
+        if (splitterIndex && index === splitterIndex) {
124
+            const splitter = <span className = 'toolbar__splitter' />;
125
+
126
+            acc.push(splitter);
127
+        }
128
+
129
+        const { onClick, onMount, onUnmount } = button;
130
+
131
+        acc.push(
132
+            <ToolbarButton
133
+                button = { button }
134
+                key = { key }
135
+                onClick = { onClick }
136
+                onMount = { onMount }
137
+                onUnmount = { onUnmount } />
138
+        );
139
+
140
+        return acc;
141
+    }
142
+
143
+    /**
144
+     * Sets handlers for some of the buttons.
145
+     *
146
+     * @private
147
+     * @returns {void}
148
+     */
149
+    _setButtonHandlers(): void {
150
+        const {
151
+            buttonHandlers,
152
+            toolbarButtons
153
+        } = this.props;
154
+
155
+        Object.keys(buttonHandlers).forEach(key => {
156
+            let button = toolbarButtons.get(key);
157
+
158
+            if (button) {
159
+                button = {
160
+                    ...button,
161
+                    ...buttonHandlers[key]
162
+                };
163
+                toolbarButtons.set(key, button);
164
+            }
165
+        });
166
+    }
167
+}
168
+
169
+/**
170
+ * Maps part of Redux actions to component's props.
171
+ *
172
+ * @param {Function} dispatch - Redux action dispatcher.
173
+ * @returns {Object}
174
+ * @private
175
+ */
176
+function _mapDispatchToProps(dispatch: Function): Object {
177
+    return {
178
+        /**
179
+         * Dispatches an action signalling that toolbar is no being hovered.
180
+         *
181
+         * @protected
182
+         * @returns {Object} Dispatched action.
183
+         */
184
+        _onMouseOut() {
185
+            return dispatch(setToolbarHovered(false));
186
+        },
187
+
188
+        /**
189
+         * Dispatches an action signalling that toolbar is now being hovered.
190
+         *
191
+         * @protected
192
+         * @returns {Object} Dispatched action.
193
+         */
194
+        _onMouseOver() {
195
+            return dispatch(setToolbarHovered(true));
196
+        }
197
+    };
198
+}
199
+
200
+export default connect(null, _mapDispatchToProps)(BaseToolbar);

+ 189
- 0
react/features/toolbar/components/PrimaryToolbar.web.js View File

@@ -0,0 +1,189 @@
1
+/* @flow */
2
+
3
+import React, { Component } from 'react';
4
+import { connect } from 'react-redux';
5
+
6
+import UIEvents from '../../../../service/UI/UIEvents';
7
+
8
+import { showDesktopSharingButton, toggleFullScreen } from '../actions';
9
+import BaseToolbar from './BaseToolbar';
10
+import { getToolbarClassNames } from '../functions';
11
+
12
+declare var APP: Object;
13
+declare var interfaceConfig: Object;
14
+
15
+/**
16
+ * Implementation of PrimaryToolbar React Component.
17
+ *
18
+ * @class PrimaryToolbar
19
+ * @extends Component
20
+ */
21
+class PrimaryToolbar extends Component {
22
+
23
+    state: Object;
24
+
25
+    static propTypes = {
26
+        /**
27
+         * Handler for toggling fullscreen mode.
28
+         */
29
+        _onFullScreenToggled: React.PropTypes.func,
30
+
31
+        /**
32
+         * Handler for showing desktop sharing button.
33
+         */
34
+        _onShowDesktopSharingButton: React.PropTypes.func,
35
+
36
+        /**
37
+         * Contains toolbar buttons for primary toolbar.
38
+         */
39
+        _primaryToolbarButtons: React.PropTypes.instanceOf(Map),
40
+
41
+        /**
42
+         * Shows whether toolbar is visible.
43
+         */
44
+        _visible: React.PropTypes.bool
45
+    };
46
+
47
+    /**
48
+     * Constructs instance of primary toolbar React component.
49
+     *
50
+     * @param {Object} props - React component's properties.
51
+     */
52
+    constructor(props) {
53
+        super(props);
54
+
55
+        const buttonHandlers = {
56
+            /**
57
+             * Mount handler for desktop button.
58
+             *
59
+             * @type {Object}
60
+             */
61
+            desktop: {
62
+                onMount: () => this.props._onShowDesktopSharingButton()
63
+            },
64
+
65
+            /**
66
+             * Mount/Unmount handler for toggling fullscreen button.
67
+             *
68
+             * @type {Object}
69
+             */
70
+            fullscreen: {
71
+                onMount: () =>
72
+                    APP.UI.addListener(UIEvents.FULLSCREEN_TOGGLED,
73
+                        this.props._onFullScreenToggled),
74
+                onUnmount: () =>
75
+                    APP.UI.removeListener(UIEvents.FULLSCREEN_TOGGLED,
76
+                        this.props._onFullScreenToggled)
77
+            }
78
+        };
79
+        const splitterIndex = interfaceConfig.MAIN_TOOLBAR_SPLITTER_INDEX;
80
+
81
+        this.state = {
82
+
83
+            /**
84
+             * Object containing on mount/unmount handlers for toolbar buttons.
85
+             *
86
+             * @type {Object}
87
+             */
88
+            buttonHandlers,
89
+
90
+            /**
91
+             * If deployment supports toolbar splitter this value contains its
92
+             * index.
93
+             *
94
+             * @type {number}
95
+             */
96
+            splitterIndex
97
+        };
98
+    }
99
+
100
+    /**
101
+     * Renders primary toolbar component.
102
+     *
103
+     * @returns {ReactElement}
104
+     */
105
+    render() {
106
+        const { buttonHandlers, splitterIndex } = this.state;
107
+        const { _primaryToolbarButtons } = this.props;
108
+        const { primaryToolbarClassName } = getToolbarClassNames(this.props);
109
+
110
+        return (
111
+            <BaseToolbar
112
+                buttonHandlers = { buttonHandlers }
113
+                className = { primaryToolbarClassName }
114
+                splitterIndex = { splitterIndex }
115
+                toolbarButtons = { _primaryToolbarButtons } />
116
+        );
117
+    }
118
+}
119
+
120
+/**
121
+ * Maps some of the Redux actions to the component props.
122
+ *
123
+ * @param {Function} dispatch - Redux action dispatcher.
124
+ * @returns {{
125
+ *      _onShowDesktopSharingButton: Function
126
+ * }}
127
+ * @private
128
+ */
129
+function _mapDispatchToProps(dispatch: Function): Object {
130
+    return {
131
+        /**
132
+         * Dispatches an action signalling that full screen mode is toggled.
133
+         *
134
+         * @param {boolean} isFullScreen - Show whether fullscreen mode is on.
135
+         * @returns {Object} Dispatched action.
136
+         */
137
+        _onFullScreenToggled(isFullScreen: boolean) {
138
+            return dispatch(toggleFullScreen(isFullScreen));
139
+        },
140
+
141
+        /**
142
+         * Dispatches an action signalling that desktop sharing button
143
+         * should be shown.
144
+         *
145
+         * @returns {Object} Dispatched action.
146
+         */
147
+        _onShowDesktopSharingButton() {
148
+            dispatch(showDesktopSharingButton());
149
+        }
150
+    };
151
+}
152
+
153
+/**
154
+ * Maps part of Redux store to React component props.
155
+ *
156
+ * @param {Object} state - Snapshot of Redux store.
157
+ * @returns {{
158
+ *      _primaryToolbarButtons: Map,
159
+ *      _visible: boolean
160
+ * }}
161
+ * @private
162
+ */
163
+function _mapStateToProps(state: Object): Object {
164
+    const {
165
+        primaryToolbarButtons,
166
+        visible
167
+    } = state['features/toolbar'];
168
+
169
+    return {
170
+        /**
171
+         * Default toolbar buttons for primary toolbar.
172
+         *
173
+         * @protected
174
+         * @type {Map}
175
+         */
176
+        _primaryToolbarButtons: primaryToolbarButtons,
177
+
178
+        /**
179
+         * Shows whether toolbar is visible.
180
+         *
181
+         * @protected
182
+         * @type {boolean}
183
+         */
184
+        _visible: visible
185
+    };
186
+}
187
+
188
+export default connect(_mapStateToProps, _mapDispatchToProps)(PrimaryToolbar);
189
+

+ 263
- 0
react/features/toolbar/components/SecondaryToolbar.web.js View File

@@ -0,0 +1,263 @@
1
+/* @flow */
2
+
3
+import React, { Component } from 'react';
4
+import { connect } from 'react-redux';
5
+
6
+import { FeedbackButton } from '../../feedback';
7
+import UIEvents from '../../../../service/UI/UIEvents';
8
+
9
+import {
10
+    changeLocalRaiseHand,
11
+    setProfileButtonUnclickable,
12
+    showRecordingButton,
13
+    toggleSideToolbarContainer
14
+} from '../actions';
15
+import BaseToolbar from './BaseToolbar';
16
+import { getToolbarClassNames } from '../functions';
17
+
18
+declare var APP: Object;
19
+declare var config: Object;
20
+
21
+/**
22
+ * Implementation of secondary toolbar React component.
23
+ *
24
+ * @class SecondaryToolbar
25
+ * @extends Component
26
+ */
27
+class SecondaryToolbar extends Component {
28
+
29
+    state: Object;
30
+
31
+    /**
32
+     * Secondary toolbar property types.
33
+     *
34
+     * @static
35
+     */
36
+    static propTypes = {
37
+        /**
38
+         * Handler dispatching local "Raise hand".
39
+         */
40
+        _onLocalRaiseHandChanged: React.PropTypes.func,
41
+
42
+        /**
43
+         * Handler setting profile button unclickable.
44
+         */
45
+        _onSetProfileButtonUnclickable: React.PropTypes.func,
46
+
47
+        /**
48
+         * Handler for showing recording button.
49
+         */
50
+        _onShowRecordingButton: React.PropTypes.func,
51
+
52
+        /**
53
+         * Handler dispatching toggle toolbar container.
54
+         */
55
+        _onSideToolbarContainerToggled: React.PropTypes.func,
56
+
57
+        /**
58
+         * Contains map of secondary toolbar buttons.
59
+         */
60
+        _secondaryToolbarButtons: React.PropTypes.instanceOf(Map),
61
+
62
+        /**
63
+         * Shows whether toolbar is visible.
64
+         */
65
+        _visible: React.PropTypes.bool
66
+    };
67
+
68
+    /**
69
+     * Constructs instance of SecondaryToolbar component.
70
+     *
71
+     * @param {Object} props - React component properties.
72
+     */
73
+    constructor(props) {
74
+        super(props);
75
+
76
+        const buttonHandlers = {
77
+            /**
78
+             * Mount handler for profile button.
79
+             *
80
+             * @type {Object}
81
+             */
82
+            profile: {
83
+                onMount: () => {
84
+                    APP.tokenData.isGuest
85
+                    || this.props._onSetProfileButtonUnclickable(true);
86
+                }
87
+            },
88
+
89
+            /**
90
+             * Mount/Unmount handlers for raisehand button.
91
+             *
92
+             * @type {button}
93
+             */
94
+            raisehand: {
95
+                onMount: () => {
96
+                    APP.UI.addListener(UIEvents.LOCAL_RAISE_HAND_CHANGED,
97
+                        this.props._onLocalRaiseHandChanged);
98
+                },
99
+                onUnmount: () => {
100
+                    APP.UI.removeListener(UIEvents.LOCAL_RAISE_HAND_CHANGED,
101
+                        this.props._onLocalRaiseHandChanged);
102
+                }
103
+            },
104
+
105
+            /**
106
+             * Mount handler for recording button.
107
+             *
108
+             * @type {Object}
109
+             */
110
+            recording: {
111
+                onMount: () => {
112
+                    if (config.enableRecording) {
113
+                        this.props._onShowRecordingButton();
114
+                    }
115
+                }
116
+            }
117
+        };
118
+
119
+        this.state = {
120
+            /**
121
+             * Object containing on mount/unmount handlers for toolbar buttons.
122
+             *
123
+             * @type {Object}
124
+             */
125
+            buttonHandlers
126
+        };
127
+    }
128
+
129
+    /**
130
+     * Register legacy UI listener.
131
+     *
132
+     * @returns {void}
133
+     */
134
+    componentDidMount(): void {
135
+        APP.UI.addListener(UIEvents.SIDE_TOOLBAR_CONTAINER_TOGGLED,
136
+            this.props._onSideToolbarContainerToggled);
137
+    }
138
+
139
+    /**
140
+     * Unregisters legacy UI listener.
141
+     *
142
+     * @returns {void}
143
+     */
144
+    componentWillUnmount(): void {
145
+        APP.UI.removeListener(UIEvents.SIDE_TOOLBAR_CONTAINER_TOGGLED,
146
+            this.props._onSideToolbarContainerToggled);
147
+    }
148
+
149
+    /**
150
+     * Renders secondary toolbar component.
151
+     *
152
+     * @returns {ReactElement}
153
+     */
154
+    render(): ReactElement<*> {
155
+        const { buttonHandlers } = this.state;
156
+        const { _secondaryToolbarButtons } = this.props;
157
+        const { secondaryToolbarClassName } = getToolbarClassNames(this.props);
158
+
159
+        return (
160
+            <BaseToolbar
161
+                buttonHandlers = { buttonHandlers }
162
+                className = { secondaryToolbarClassName }
163
+                toolbarButtons = { _secondaryToolbarButtons }>
164
+                <FeedbackButton />
165
+            </BaseToolbar>
166
+        );
167
+    }
168
+}
169
+
170
+/**
171
+ * Maps some of Redux actions to component's props.
172
+ *
173
+ * @param {Function} dispatch - Redux action dispatcher.
174
+ * @returns {{
175
+ *      _onLocalRaiseHandChanged: Function,
176
+ *      _onSetProfileButtonUnclickable: Function,
177
+ *      _onShowRecordingButton: Function,
178
+ *      _onSideToolbarContainerToggled
179
+ * }}
180
+ * @private
181
+ */
182
+function _mapDispatchToProps(dispatch: Function): Object {
183
+    return {
184
+        /**
185
+         * Dispatches an action that 'hand' is raised.
186
+         *
187
+         * @param {boolean} isRaisedHand - Show whether hand is raised.
188
+         * @returns {Object} Dispatched action.
189
+         */
190
+        _onLocalRaiseHandChanged(isRaisedHand: boolean) {
191
+            return dispatch(changeLocalRaiseHand(isRaisedHand));
192
+        },
193
+
194
+        /**
195
+         * Dispatches an action signalling to set profile button unclickable.
196
+         *
197
+         * @param {boolean} unclickable - Flag showing whether unclickable
198
+         * property is true.
199
+         * @returns {Object} Dispatched action.
200
+         */
201
+        _onSetProfileButtonUnclickable(unclickable: boolean) {
202
+            return dispatch(setProfileButtonUnclickable(unclickable));
203
+        },
204
+
205
+        /**
206
+         * Dispatches an action signalling that recording button should be
207
+         * shown.
208
+         *
209
+         * @returns {Object} Dispatched action.
210
+         */
211
+        _onShowRecordingButton() {
212
+            return dispatch(showRecordingButton());
213
+        },
214
+
215
+        /**
216
+         * Dispatches an action signalling that side toolbar container is
217
+         * toggled.
218
+         *
219
+         * @param {string} containerId - Id of side toolbar container.
220
+         * @returns {Object} Dispatched action.
221
+         */
222
+        _onSideToolbarContainerToggled(containerId: string) {
223
+            return dispatch(toggleSideToolbarContainer(containerId));
224
+        }
225
+    };
226
+}
227
+
228
+/**
229
+ * Maps part of Redux state to component's props.
230
+ *
231
+ * @param {Object} state - Snapshot of Redux store.
232
+ * @returns {{
233
+ *      _secondaryToolbarButtons: Map,
234
+ *      _visible: boolean
235
+ * }}
236
+ * @private
237
+ */
238
+function _mapStateToProps(state: Object): Object {
239
+    const {
240
+        secondaryToolbarButtons,
241
+        visible
242
+    } = state['features/toolbar'];
243
+
244
+    return {
245
+        /**
246
+         * Default toolbar buttons for secondary toolbar.
247
+         *
248
+         * @protected
249
+         * @type {Map}
250
+         */
251
+        _secondaryToolbarButtons: secondaryToolbarButtons,
252
+
253
+        /**
254
+         * Shows whether toolbar is visible.
255
+         *
256
+         * @protected
257
+         * @type {boolean}
258
+         */
259
+        _visible: visible
260
+    };
261
+}
262
+
263
+export default connect(_mapStateToProps, _mapDispatchToProps)(SecondaryToolbar);

+ 161
- 36
react/features/toolbar/components/Toolbar.native.js View File

@@ -1,41 +1,74 @@
1
-import React from 'react';
1
+import React, { Component } from 'react';
2 2
 import { View } from 'react-native';
3 3
 import { connect } from 'react-redux';
4 4
 
5 5
 import { MEDIA_TYPE, toggleCameraFacingMode } from '../../base/media';
6 6
 import { Container } from '../../base/react';
7 7
 import { ColorPalette } from '../../base/styles';
8
+import { beginRoomLockRequest } from '../../room-lock';
8 9
 
9
-import { AbstractToolbar, _mapStateToProps } from './AbstractToolbar';
10
+import {
11
+    abstractMapDispatchToProps,
12
+    abstractMapStateToProps
13
+} from '../functions';
10 14
 import { styles } from './styles';
11 15
 import ToolbarButton from './ToolbarButton';
12 16
 
13 17
 /**
14 18
  * Implements the conference toolbar on React Native.
15
- *
16
- * @extends AbstractToolbar
17 19
  */
18
-class Toolbar extends AbstractToolbar {
20
+class Toolbar extends Component {
19 21
     /**
20 22
      * Toolbar component's property types.
21 23
      *
22 24
      * @static
23 25
      */
24
-    static propTypes = AbstractToolbar.propTypes
26
+    static propTypes = {
27
+        /**
28
+         * Flag showing that audio is muted.
29
+         */
30
+        _audioMuted: React.PropTypes.bool,
25 31
 
26
-    /**
27
-     * Initializes a new Toolbar instance.
28
-     *
29
-     * @param {Object} props - The read-only React Component props with which
30
-     * the new instance is to be initialized.
31
-     */
32
-    constructor(props) {
33
-        super(props);
32
+        /**
33
+         * Flag showing whether room is locked.
34
+         */
35
+        _locked: React.PropTypes.bool,
34 36
 
35
-        // Bind event handlers so they are only bound once for every instance.
36
-        this._toggleCameraFacingMode
37
-            = this._toggleCameraFacingMode.bind(this);
38
-    }
37
+        /**
38
+         * Handler for hangup.
39
+         */
40
+        _onHangup: React.PropTypes.func,
41
+
42
+        /**
43
+         * Handler for room locking.
44
+         */
45
+        _onRoomLock: React.PropTypes.func,
46
+
47
+        /**
48
+         * Handler for toggle audio.
49
+         */
50
+        _onToggleAudio: React.PropTypes.func,
51
+
52
+        /**
53
+         * Handler for toggling camera facing mode.
54
+         */
55
+        _onToggleCameraFacingMode: React.PropTypes.func,
56
+
57
+        /**
58
+         * Handler for toggling video.
59
+         */
60
+        _onToggleVideo: React.PropTypes.func,
61
+
62
+        /**
63
+         * Flag showing whether video is muted.
64
+         */
65
+        _videoMuted: React.PropTypes.bool,
66
+
67
+        /**
68
+         * Flag showing whether toolbar is visible.
69
+         */
70
+        _visible: React.PropTypes.bool
71
+    };
39 72
 
40 73
     /**
41 74
      * Implements React's {@link Component#render()}.
@@ -47,7 +80,7 @@ class Toolbar extends AbstractToolbar {
47 80
         return (
48 81
             <Container
49 82
                 style = { styles.toolbarContainer }
50
-                visible = { this.props.visible }>
83
+                visible = { this.props._visible }>
51 84
                 {
52 85
                     this._renderPrimaryToolbar()
53 86
                 }
@@ -58,6 +91,43 @@ class Toolbar extends AbstractToolbar {
58 91
         );
59 92
     }
60 93
 
94
+    /**
95
+     * Gets the styles for a button that toggles the mute state of a specific
96
+     * media type.
97
+     *
98
+     * @param {string} mediaType - The {@link MEDIA_TYPE} associated with the
99
+     * button to get styles for.
100
+     * @protected
101
+     * @returns {{
102
+     *     iconName: string,
103
+     *     iconStyle: Object,
104
+     *     style: Object
105
+     * }}
106
+     */
107
+    _getMuteButtonStyles(mediaType) {
108
+        let iconName;
109
+        let iconStyle;
110
+        let style = styles.primaryToolbarButton;
111
+
112
+        if (this.props[`_${mediaType}Muted`]) {
113
+            iconName = this[`${mediaType}MutedIcon`];
114
+            iconStyle = styles.whiteIcon;
115
+            style = {
116
+                ...style,
117
+                backgroundColor: ColorPalette.buttonUnderlay
118
+            };
119
+        } else {
120
+            iconName = this[`${mediaType}Icon`];
121
+            iconStyle = styles.icon;
122
+        }
123
+
124
+        return {
125
+            iconName,
126
+            iconStyle,
127
+            style
128
+        };
129
+    }
130
+
61 131
     /**
62 132
      * Renders the toolbar which contains the primary buttons such as hangup,
63 133
      * audio and video mute.
@@ -76,12 +146,12 @@ class Toolbar extends AbstractToolbar {
76 146
                 <ToolbarButton
77 147
                     iconName = { audioButtonStyles.iconName }
78 148
                     iconStyle = { audioButtonStyles.iconStyle }
79
-                    onClick = { this._toggleAudio }
149
+                    onClick = { this.props._onToggleAudio }
80 150
                     style = { audioButtonStyles.style } />
81 151
                 <ToolbarButton
82 152
                     iconName = 'hangup'
83 153
                     iconStyle = { styles.whiteIcon }
84
-                    onClick = { this._onHangup }
154
+                    onClick = { this.props._onHangup }
85 155
                     style = {{
86 156
                         ...styles.primaryToolbarButton,
87 157
                         backgroundColor: ColorPalette.red
@@ -90,7 +160,7 @@ class Toolbar extends AbstractToolbar {
90 160
                 <ToolbarButton
91 161
                     iconName = { videoButtonStyles.iconName }
92 162
                     iconStyle = { videoButtonStyles.iconStyle }
93
-                    onClick = { this._toggleVideo }
163
+                    onClick = { this.props._onToggleVideo }
94 164
                     style = { videoButtonStyles.style } />
95 165
             </View>
96 166
         );
@@ -125,7 +195,7 @@ class Toolbar extends AbstractToolbar {
125 195
                 <ToolbarButton
126 196
                     iconName = 'switch-camera'
127 197
                     iconStyle = { iconStyle }
128
-                    onClick = { this._toggleCameraFacingMode }
198
+                    onClick = { this.props._onToggleCameraFacingMode }
129 199
                     style = { style }
130 200
                     underlayColor = { underlayColor } />
131 201
                   */}
@@ -134,7 +204,7 @@ class Toolbar extends AbstractToolbar {
134 204
                         this.props._locked ? 'security-locked' : 'security'
135 205
                     }
136 206
                     iconStyle = { iconStyle }
137
-                    onClick = { this._onRoomLock }
207
+                    onClick = { this.props._onRoomLock }
138 208
                     style = { style }
139 209
                     underlayColor = { underlayColor } />
140 210
             </View>
@@ -142,17 +212,6 @@ class Toolbar extends AbstractToolbar {
142 212
 
143 213
         /* eslint-enable react/jsx-curly-spacing,react/jsx-handler-names */
144 214
     }
145
-
146
-    /**
147
-     * Switches between the front/user-facing and rear/environment-facing
148
-     * cameras.
149
-     *
150
-     * @private
151
-     * @returns {void}
152
-     */
153
-    _toggleCameraFacingMode() {
154
-        this.props.dispatch(toggleCameraFacingMode());
155
-    }
156 215
 }
157 216
 
158 217
 /**
@@ -168,4 +227,70 @@ Object.assign(Toolbar.prototype, {
168 227
     videoMutedIcon: 'camera-disabled'
169 228
 });
170 229
 
171
-export default connect(_mapStateToProps)(Toolbar);
230
+/**
231
+ * Maps actions to React component props.
232
+ *
233
+ * @param {Function} dispatch - Redux action dispatcher.
234
+ * @returns {{
235
+ *     _onRoomLock: Function,
236
+ *     _onToggleCameraFacingMode: Function,
237
+ * }}
238
+ * @private
239
+ */
240
+function _mapDispatchToProps(dispatch) {
241
+    return {
242
+        ...abstractMapDispatchToProps(dispatch),
243
+
244
+        /**
245
+         * Dispatches an action to set the lock i.e. password protection of the
246
+         * conference/room.
247
+         *
248
+         * @private
249
+         * @returns {Object} - Dispatched action.
250
+         * @type {Function}
251
+         */
252
+        _onRoomLock() {
253
+            return dispatch(beginRoomLockRequest());
254
+        },
255
+
256
+        /**
257
+         * Switches between the front/user-facing and rear/environment-facing
258
+         * cameras.
259
+         *
260
+         * @private
261
+         * @returns {Object} - Dispatched action.
262
+         * @type {Function}
263
+         */
264
+        _onToggleCameraFacingMode() {
265
+            return dispatch(toggleCameraFacingMode());
266
+        }
267
+    };
268
+}
269
+
270
+/**
271
+ * Maps part of Redux store to React component props.
272
+ *
273
+ * @param {Object} state - Redux store.
274
+ * @returns {{
275
+ *     _locked: boolean
276
+ * }}
277
+ * @private
278
+ */
279
+function _mapStateToProps(state) {
280
+    const conference = state['features/base/conference'];
281
+
282
+    return {
283
+        ...abstractMapStateToProps(state),
284
+
285
+        /**
286
+         * The indicator which determines whether the conference is
287
+         * locked/password-protected.
288
+         *
289
+         * @protected
290
+         * @type {boolean}
291
+         */
292
+        _locked: conference.locked
293
+    };
294
+}
295
+
296
+export default connect(_mapStateToProps, _mapDispatchToProps)(Toolbar);

+ 227
- 0
react/features/toolbar/components/Toolbar.web.js View File

@@ -0,0 +1,227 @@
1
+/* @flow */
2
+
3
+import React, { Component } from 'react';
4
+import { connect } from 'react-redux';
5
+
6
+import UIEvents from '../../../../service/UI/UIEvents';
7
+
8
+import { resetAlwaysVisibleToolbar } from '../actions';
9
+import {
10
+    abstractMapStateToProps,
11
+    showCustomToolbarPopup
12
+} from '../functions';
13
+import Notice from './Notice';
14
+import PrimaryToolbar from './PrimaryToolbar';
15
+import SecondaryToolbar from './SecondaryToolbar';
16
+
17
+declare var APP: Object;
18
+declare var config: Object;
19
+declare var interfaceConfig: Object;
20
+
21
+/**
22
+ * Implements the conference toolbar on React.
23
+ */
24
+class Toolbar extends Component {
25
+
26
+    /**
27
+     * App component's property types.
28
+     *
29
+     * @static
30
+     */
31
+    static propTypes = {
32
+        /**
33
+         * Handler dispatching reset always visible toolbar action.
34
+         */
35
+        _onResetAlwaysVisibleToolbar: React.PropTypes.func,
36
+
37
+        /**
38
+         * Represents conference subject.
39
+         */
40
+        _subject: React.PropTypes.string,
41
+
42
+        /**
43
+         * Flag showing whether to set subject slide in animation.
44
+         */
45
+        _subjectSlideIn: React.PropTypes.bool,
46
+
47
+        /**
48
+         * Property containing toolbar timeout id.
49
+         */
50
+        _timeoutId: React.PropTypes.number
51
+    };
52
+
53
+    /**
54
+     * Invokes reset always visible toolbar after mounting the component and
55
+     * registers legacy UI listeners.
56
+     *
57
+     * @returns {void}
58
+     */
59
+    componentDidMount(): void {
60
+        this.props._onResetAlwaysVisibleToolbar();
61
+
62
+        APP.UI.addListener(UIEvents.SHOW_CUSTOM_TOOLBAR_BUTTON_POPUP,
63
+            showCustomToolbarPopup);
64
+    }
65
+
66
+    /**
67
+     *  Unregisters legacy UI listeners.
68
+     *
69
+     *  @returns {void}
70
+     */
71
+    componentWillUnmount(): void {
72
+        APP.UI.removeListener(UIEvents.SHOW_CUSTOM_TOOLBAR_BUTTON_POPUP,
73
+            showCustomToolbarPopup);
74
+    }
75
+
76
+    /**
77
+     * Implements React's {@link Component#render()}.
78
+     *
79
+     * @inheritdoc
80
+     * @returns {ReactElement}
81
+     */
82
+    render(): ReactElement<*> {
83
+        return (
84
+            <div>
85
+                {
86
+                    this._renderSubject()
87
+                }
88
+                {
89
+                    this._renderToolbars()
90
+                }
91
+                <div id = 'sideToolbarContainer' />
92
+            </div>
93
+        );
94
+    }
95
+
96
+    /**
97
+     * Returns React element representing toolbar subject.
98
+     *
99
+     * @returns {ReactElement}
100
+     * @private
101
+     */
102
+    _renderSubject(): ReactElement<*> | null {
103
+        const { _subjectSlideIn, _subject } = this.props;
104
+        const classNames = [ 'subject' ];
105
+
106
+        if (!_subject) {
107
+            return null;
108
+        }
109
+
110
+        if (_subjectSlideIn) {
111
+            classNames.push('subject_slide-in');
112
+        } else {
113
+            classNames.push('subject_slide-out');
114
+        }
115
+
116
+        // XXX: Since chat is now not reactified we have to dangerously set
117
+        // inner HTML into the component. This has to be refactored while
118
+        // reactification of the Chat.js
119
+        const innerHtml = {
120
+            __html: _subject
121
+        };
122
+
123
+        return (
124
+            <div
125
+                className = { classNames.join(' ') }
126
+
127
+                // eslint-disable-next-line react/no-danger
128
+                dangerouslySetInnerHTML = { innerHtml }
129
+                id = 'subject' />
130
+        );
131
+    }
132
+
133
+    /**
134
+     * Renders primary and secondary toolbars.
135
+     *
136
+     * @returns {ReactElement}
137
+     * @private
138
+     */
139
+    _renderToolbars(): ReactElement<*> | null {
140
+        // We should not show the toolbars till timeout object will be
141
+        // initialized.
142
+        if (this.props._timeoutId === null) {
143
+            return null;
144
+        }
145
+
146
+        return (
147
+            <div>
148
+                <Notice />
149
+                <PrimaryToolbar />
150
+                <SecondaryToolbar />
151
+            </div>
152
+        );
153
+    }
154
+}
155
+
156
+/**
157
+ * Maps parts of Redux actions to component props.
158
+ *
159
+ * @param {Function} dispatch - Redux action dispatcher.
160
+ * @returns {{
161
+ *      _onResetAlwaysVisibleToolbar: Function
162
+ * }}
163
+ * @private
164
+ */
165
+function _mapDispatchToProps(dispatch: Function): Object {
166
+    return {
167
+
168
+        /**
169
+         * Dispatches an action resetting always visible toolbar.
170
+         *
171
+         * @returns {Object} Dispatched action.
172
+         */
173
+        _onResetAlwaysVisibleToolbar() {
174
+            dispatch(resetAlwaysVisibleToolbar());
175
+        }
176
+    };
177
+}
178
+
179
+/**
180
+ * Maps parts of toolbar state to component props.
181
+ *
182
+ * @param {Object} state - Redux state.
183
+ * @private
184
+ * @returns {{
185
+ *     _audioMuted: boolean,
186
+ *     _locked: boolean,
187
+ *     _subjectSlideIn: boolean,
188
+ *     _videoMuted: boolean
189
+ * }}
190
+ */
191
+function _mapStateToProps(state: Object): Object {
192
+    const {
193
+        subject,
194
+        subjectSlideIn,
195
+        timeoutId
196
+    } = state['features/toolbar'];
197
+
198
+    return {
199
+        ...abstractMapStateToProps(state),
200
+
201
+        /**
202
+         * Property containing conference subject.
203
+         *
204
+         * @protected
205
+         * @type {string}
206
+         */
207
+        _subject: subject,
208
+
209
+        /**
210
+         * Flag showing whether to set subject slide in animation.
211
+         *
212
+         * @protected
213
+         * @type {boolean}
214
+         */
215
+        _subjectSlideIn: subjectSlideIn,
216
+
217
+        /**
218
+         * Property containing toolbar timeout id.
219
+         *
220
+         * @protected
221
+         * @type {number}
222
+         */
223
+        _timeoutId: timeoutId
224
+    };
225
+}
226
+
227
+export default connect(_mapStateToProps, _mapDispatchToProps)(Toolbar);

+ 228
- 0
react/features/toolbar/components/ToolbarButton.web.js View File

@@ -0,0 +1,228 @@
1
+/* @flow */
2
+
3
+import React from 'react';
4
+
5
+import { translate } from '../../base/i18n';
6
+
7
+import UIUtil from '../../../../modules/UI/util/UIUtil';
8
+
9
+import AbstractToolbarButton from './AbstractToolbarButton';
10
+import { getButtonAttributesByProps } from '../functions';
11
+
12
+declare var APP: Object;
13
+declare var interfaceConfig: Object;
14
+
15
+/**
16
+ * Represents a button in Toolbar on React.
17
+ *
18
+ * @class ToolbarButton
19
+ * @extends AbstractToolbarButton
20
+ */
21
+class ToolbarButton extends AbstractToolbarButton {
22
+    _createRefToButton: Function;
23
+
24
+    _onClick: Function;
25
+
26
+    /**
27
+     * Toolbar button component's property types.
28
+     *
29
+     * @static
30
+     */
31
+    static propTypes = {
32
+        ...AbstractToolbarButton.propTypes,
33
+
34
+        /**
35
+         * Object describing button.
36
+         */
37
+        button: React.PropTypes.object.isRequired,
38
+
39
+        /**
40
+         * Handler for component mount.
41
+         */
42
+        onMount: React.PropTypes.func,
43
+
44
+        /**
45
+         * Handler for component unmount.
46
+         */
47
+        onUnmount: React.PropTypes.func,
48
+
49
+        /**
50
+         * Translation helper function.
51
+         */
52
+        t: React.PropTypes.func
53
+    };
54
+
55
+    /**
56
+     * Initializes new ToolbarButton instance.
57
+     *
58
+     * @param {Object} props - The read-only properties with which the new
59
+     * instance is to be initialized.
60
+     */
61
+    constructor(props: Object) {
62
+        super(props);
63
+
64
+        // Bind methods to save the context
65
+        this._createRefToButton = this._createRefToButton.bind(this);
66
+        this._onClick = this._onClick.bind(this);
67
+    }
68
+
69
+    /**
70
+     * Sets shortcut/tooltip
71
+     * after mounting of the component.
72
+     *
73
+     * @inheritdoc
74
+     * @returns {void}
75
+     */
76
+    componentDidMount(): void {
77
+        this._setShortcutAndTooltip();
78
+
79
+        if (this.props.onMount) {
80
+            this.props.onMount();
81
+        }
82
+    }
83
+
84
+    /**
85
+     * Invokes on unmount handler if it was passed to the props.
86
+     *
87
+     * @inheritdoc
88
+     * @returns {void}
89
+     */
90
+    componentWillUnmount(): void {
91
+        if (this.props.onUnmount) {
92
+            this.props.onUnmount();
93
+        }
94
+    }
95
+
96
+    /**
97
+     * Implements React's {@link Component#render()}.
98
+     *
99
+     * @inheritdoc
100
+     * @returns {ReactElement}
101
+     */
102
+    render(): ReactElement<*> {
103
+        const { button } = this.props;
104
+        const attributes = getButtonAttributesByProps(button);
105
+        const popups = button.popups || [];
106
+
107
+        return (
108
+            <a
109
+                { ...attributes }
110
+                onClick = { this._onClick }
111
+                ref = { this._createRefToButton }>
112
+                { this._renderInnerElementsIfRequired() }
113
+                { this._renderPopups(popups) }
114
+            </a>
115
+        );
116
+    }
117
+
118
+    /**
119
+     * Creates reference to current toolbar button.
120
+     *
121
+     * @param {HTMLElement} element - HTMLElement representing the toolbar
122
+     * button.
123
+     * @returns {void}
124
+     * @private
125
+     */
126
+    _createRefToButton(element: HTMLElement): void {
127
+        this.button = element;
128
+    }
129
+
130
+    /**
131
+     * Wrapper on on click handler props for current button.
132
+     *
133
+     * @param {Event} event - Click event object.
134
+     * @returns {void}
135
+     * @private
136
+     */
137
+    _onClick(event: Event): void {
138
+        const {
139
+            button,
140
+            onClick
141
+        } = this.props;
142
+        const {
143
+            enabled,
144
+            unclickable
145
+        } = button;
146
+
147
+        if (enabled && !unclickable && onClick) {
148
+            onClick(event);
149
+        }
150
+    }
151
+
152
+    /**
153
+     * If toolbar button should contain children elements
154
+     * renders them.
155
+     *
156
+     * @returns {ReactElement|null}
157
+     * @private
158
+     */
159
+    _renderInnerElementsIfRequired(): ReactElement<*> | null {
160
+        if (this.props.button.html) {
161
+            return this.props.button.html;
162
+        }
163
+
164
+        return null;
165
+    }
166
+
167
+    /**
168
+     * Renders popup element for toolbar button.
169
+     *
170
+     * @param {Array} popups - Array of popup objects.
171
+     * @returns {Array}
172
+     * @private
173
+     */
174
+    _renderPopups(popups: Array<*> = []): Array<*> {
175
+        return popups.map(popup => {
176
+            let gravity = 'n';
177
+
178
+            if (popup.dataAttrPosition) {
179
+                gravity = popup.dataAttrPosition;
180
+            }
181
+
182
+            const title = this.props.t(popup.dataAttr);
183
+
184
+            return (
185
+                <div
186
+                    className = { popup.className }
187
+                    data-popup = { gravity }
188
+                    id = { popup.id }
189
+                    key = { popup.id }
190
+                    title = { title } />
191
+            );
192
+        });
193
+    }
194
+
195
+    /**
196
+     * Sets shortcut and tooltip for current toolbar button.
197
+     *
198
+     * @private
199
+     * @returns {void}
200
+     */
201
+    _setShortcutAndTooltip(): void {
202
+        const { button } = this.props;
203
+        const name = button.buttonName;
204
+
205
+        if (UIUtil.isButtonEnabled(name)) {
206
+            const tooltipPosition
207
+                = interfaceConfig.MAIN_TOOLBAR_BUTTONS.indexOf(name) > -1
208
+                ? 'bottom' : 'right';
209
+
210
+            if (!button.unclickable) {
211
+                UIUtil.setTooltip(this.button,
212
+                    button.tooltipKey,
213
+                    tooltipPosition);
214
+            }
215
+
216
+            if (button.shortcut) {
217
+                APP.keyboardshortcut.registerShortcut(
218
+                    button.shortcut,
219
+                    button.shortcutAttr,
220
+                    button.shortcutFunc,
221
+                    button.shortcutDescription
222
+                );
223
+            }
224
+        }
225
+    }
226
+}
227
+
228
+export default translate(ToolbarButton);

+ 0
- 1
react/features/toolbar/components/index.js View File

@@ -1,2 +1 @@
1
-export { default as Notice } from './Notice';
2 1
 export { default as Toolbar } from './Toolbar';

+ 389
- 0
react/features/toolbar/defaultToolbarButtons.js View File

@@ -0,0 +1,389 @@
1
+/* @flow */
2
+
3
+import React from 'react';
4
+
5
+import UIEvents from '../../../service/UI/UIEvents';
6
+
7
+declare var APP: Object;
8
+declare var config: Object;
9
+declare var JitsiMeetJS: Object;
10
+
11
+/**
12
+ * Shows SIP number dialog.
13
+ *
14
+ * @returns {void}
15
+ */
16
+function _showSIPNumberInput() {
17
+    const defaultNumber = config.defaultSipNumber || '';
18
+    const msgString
19
+        = `<input class="input-control" name="sipNumber" type="text" value="${
20
+            defaultNumber}" autofocus>`;
21
+
22
+    APP.UI.messageHandler.openTwoButtonDialog({
23
+        focus: ':input:first',
24
+        leftButtonKey: 'dialog.Dial',
25
+        msgString,
26
+        titleKey: 'dialog.sipMsg',
27
+
28
+        // eslint-disable-next-line max-params
29
+        submitFunction(event, value, message, formValues) {
30
+            const { sipNumber } = formValues;
31
+
32
+            if (value && sipNumber) {
33
+                APP.UI.emitEvent(UIEvents.SIP_DIAL, sipNumber);
34
+            }
35
+        }
36
+    });
37
+}
38
+
39
+/**
40
+ * All toolbar buttons' descriptions.
41
+ */
42
+export default {
43
+    /**
44
+     * Object representing camera button.
45
+     */
46
+    camera: {
47
+        classNames: [ 'button', 'icon-camera' ],
48
+        enabled: true,
49
+        id: 'toolbar_button_camera',
50
+        onClick() {
51
+            if (APP.conference.videoMuted) {
52
+                JitsiMeetJS.analytics.sendEvent('toolbar.video.enabled');
53
+                APP.UI.emitEvent(UIEvents.VIDEO_MUTED, false);
54
+            } else {
55
+                JitsiMeetJS.analytics.sendEvent('toolbar.video.disabled');
56
+                APP.UI.emitEvent(UIEvents.VIDEO_MUTED, true);
57
+            }
58
+        },
59
+        shortcut: 'V',
60
+        shortcutAttr: 'toggleVideoPopover',
61
+        shortcutFunc() {
62
+            JitsiMeetJS.analytics.sendEvent('shortcut.videomute.toggled');
63
+            APP.conference.toggleVideoMuted();
64
+        },
65
+        shortcutDescription: 'keyboardShortcuts.videoMute',
66
+        tooltipKey: 'toolbar.videomute'
67
+    },
68
+
69
+    /**
70
+     * The Object representing chat button.
71
+     */
72
+    chat: {
73
+        classNames: [ 'button', 'icon-chat' ],
74
+        enabled: true,
75
+        html: <span className = 'badge-round'>
76
+            <span id = 'unreadMessages' />
77
+        </span>,
78
+        id: 'toolbar_button_chat',
79
+        onClick() {
80
+            JitsiMeetJS.analytics.sendEvent('toolbar.chat.toggled');
81
+            APP.UI.emitEvent(UIEvents.TOGGLE_CHAT);
82
+        },
83
+        shortcut: 'C',
84
+        shortcutAttr: 'toggleChatPopover',
85
+        shortcutFunc() {
86
+            JitsiMeetJS.analytics.sendEvent('shortcut.chat.toggled');
87
+            APP.UI.toggleChat();
88
+        },
89
+        shortcutDescription: 'keyboardShortcuts.toggleChat',
90
+        sideContainerId: 'chat_container',
91
+        tooltipKey: 'toolbar.chat'
92
+    },
93
+
94
+    /**
95
+     * The object representing contact list button.
96
+     */
97
+    contacts: {
98
+        classNames: [ 'button', 'icon-contactList' ],
99
+        enabled: true,
100
+
101
+        // XXX: Hotfix to solve race condition between toolbar rendering and
102
+        // contact list view that updates the number of active participants
103
+        // via jQuery. There is case when contact list view updates number of
104
+        // participants but toolbar has not been rendered yet. Since this issue
105
+        // is reproducible only for conferences with the only participant let's
106
+        // use 1 participant as a default value for this badge. Later after
107
+        // reactification of contact list let's use the value of active
108
+        // paricipants from Redux store.
109
+        html: <span className = 'badge-round'>
110
+            <span id = 'numberOfParticipants'>1</span>
111
+        </span>,
112
+        id: 'toolbar_contact_list',
113
+        onClick() {
114
+            JitsiMeetJS.analytics.sendEvent(
115
+                'toolbar.contacts.toggled');
116
+            APP.UI.emitEvent(UIEvents.TOGGLE_CONTACT_LIST);
117
+        },
118
+        sideContainerId: 'contacts_container',
119
+        tooltipKey: 'bottomtoolbar.contactlist'
120
+    },
121
+
122
+    /**
123
+     * The object representing desktop sharing button.
124
+     */
125
+    desktop: {
126
+        classNames: [ 'button', 'icon-share-desktop' ],
127
+        enabled: true,
128
+        id: 'toolbar_button_desktopsharing',
129
+        onClick() {
130
+            if (APP.conference.isSharingScreen) {
131
+                JitsiMeetJS.analytics.sendEvent('toolbar.screen.disabled');
132
+            } else {
133
+                JitsiMeetJS.analytics.sendEvent('toolbar.screen.enabled');
134
+            }
135
+            APP.UI.emitEvent(UIEvents.TOGGLE_SCREENSHARING);
136
+        },
137
+        shortcut: 'D',
138
+        shortcutAttr: 'toggleDesktopSharingPopover',
139
+        shortcutFunc() {
140
+            JitsiMeetJS.analytics.sendEvent('shortcut.screen.toggled');
141
+            APP.conference.toggleScreenSharing();
142
+        },
143
+        shortcutDescription: 'keyboardShortcuts.toggleScreensharing',
144
+        tooltipKey: 'toolbar.sharescreen'
145
+    },
146
+
147
+    /**
148
+     * The object representing dialpad button.
149
+     */
150
+    dialpad: {
151
+        classNames: [ 'button', 'icon-dialpad' ],
152
+        enabled: true,
153
+
154
+        // TODO: remove it after UI.updateDTMFSupport fix
155
+        hidden: true,
156
+        id: 'toolbar_button_dialpad',
157
+        onClick() {
158
+            JitsiMeetJS.analytics.sendEvent('toolbar.sip.dialpad.clicked');
159
+        },
160
+        tooltipKey: 'toolbar.dialpad'
161
+    },
162
+
163
+    /**
164
+     * The object representing etherpad button.
165
+     */
166
+    etherpad: {
167
+        classNames: [ 'button', 'icon-share-doc' ],
168
+        enabled: true,
169
+        hidden: true,
170
+        id: 'toolbar_button_etherpad',
171
+        onClick() {
172
+            JitsiMeetJS.analytics.sendEvent('toolbar.etherpad.clicked');
173
+            APP.UI.emitEvent(UIEvents.ETHERPAD_CLICKED);
174
+        },
175
+        tooltipKey: 'toolbar.etherpad'
176
+    },
177
+
178
+    /**
179
+     * The object representing button toggling full screen mode.
180
+     */
181
+    fullscreen: {
182
+        classNames: [ 'button', 'icon-full-screen' ],
183
+        enabled: true,
184
+        id: 'toolbar_button_fullScreen',
185
+        onClick() {
186
+            JitsiMeetJS.analytics.sendEvent('toolbar.fullscreen.enabled');
187
+
188
+            APP.UI.emitEvent(UIEvents.TOGGLE_FULLSCREEN);
189
+        },
190
+        shortcut: 'S',
191
+        shortcutAttr: 'toggleFullscreenPopover',
192
+        shortcutDescription: 'keyboardShortcuts.fullScreen',
193
+        shortcutFunc() {
194
+            JitsiMeetJS.analytics.sendEvent('shortcut.fullscreen.toggled');
195
+            APP.UI.toggleFullScreen();
196
+        },
197
+        tooltipKey: 'toolbar.fullscreen'
198
+    },
199
+
200
+    /**
201
+     * The object representing hanging the call up button.
202
+     */
203
+    hangup: {
204
+        classNames: [ 'button', 'icon-hangup', 'button_hangup' ],
205
+        enabled: true,
206
+        id: 'toolbar_button_hangup',
207
+        onClick() {
208
+            JitsiMeetJS.analytics.sendEvent('toolbar.hangup');
209
+            APP.UI.emitEvent(UIEvents.HANGUP);
210
+        },
211
+        tooltipKey: 'toolbar.hangup'
212
+    },
213
+
214
+    /**
215
+     * The object representing button showing invite user dialog.
216
+     */
217
+    invite: {
218
+        classNames: [ 'button', 'icon-link' ],
219
+        enabled: true,
220
+        id: 'toolbar_button_link',
221
+        onClick() {
222
+            JitsiMeetJS.analytics.sendEvent('toolbar.invite.clicked');
223
+            APP.UI.emitEvent(UIEvents.INVITE_CLICKED);
224
+        },
225
+        tooltipKey: 'toolbar.invite'
226
+    },
227
+
228
+    /**
229
+     * The object representing microphone button.
230
+     */
231
+    microphone: {
232
+        classNames: [ 'button', 'icon-microphone' ],
233
+        enabled: true,
234
+        id: 'toolbar_button_mute',
235
+        onClick() {
236
+            const sharedVideoManager = APP.UI.getSharedVideoManager();
237
+
238
+            if (APP.conference.audioMuted) {
239
+                // If there's a shared video with the volume "on" and we aren't
240
+                // the video owner, we warn the user
241
+                // that currently it's not possible to unmute.
242
+                if (sharedVideoManager
243
+                    && sharedVideoManager.isSharedVideoVolumeOn()
244
+                    && !sharedVideoManager.isSharedVideoOwner()) {
245
+                    APP.UI.showCustomToolbarPopup(
246
+                        '#unableToUnmutePopup', true, 5000);
247
+                } else {
248
+                    JitsiMeetJS.analytics.sendEvent('toolbar.audio.unmuted');
249
+                    APP.UI.emitEvent(UIEvents.AUDIO_MUTED, false, true);
250
+                }
251
+            } else {
252
+                JitsiMeetJS.analytics.sendEvent('toolbar.audio.muted');
253
+                APP.UI.emitEvent(UIEvents.AUDIO_MUTED, true, true);
254
+            }
255
+        },
256
+        popups: [
257
+            {
258
+                className: 'loginmenu',
259
+                dataAttr: 'toolbar.micMutedPopup',
260
+                id: 'micMutedPopup'
261
+            },
262
+            {
263
+                className: 'loginmenu',
264
+                dataAttr: 'toolbar.unableToUnmutePopup',
265
+                id: 'unableToUnmutePopup'
266
+            },
267
+            {
268
+                className: 'loginmenu',
269
+                dataAttr: 'toolbar.talkWhileMutedPopup',
270
+                id: 'talkWhileMutedPopup'
271
+            }
272
+        ],
273
+        shortcut: 'M',
274
+        shortcutAttr: 'mutePopover',
275
+        shortcutFunc() {
276
+            JitsiMeetJS.analytics.sendEvent('shortcut.audiomute.toggled');
277
+            APP.conference.toggleAudioMuted();
278
+        },
279
+        shortcutDescription: 'keyboardShortcuts.mute',
280
+        tooltipKey: 'toolbar.mute'
281
+    },
282
+
283
+    /**
284
+     * The object representing profile button.
285
+     */
286
+    profile: {
287
+        classNames: [ 'button' ],
288
+        enabled: true,
289
+        html: <img
290
+            id = 'avatar'
291
+            src = 'images/avatar2.png' />,
292
+        id: 'toolbar_button_profile',
293
+        onClick() {
294
+            JitsiMeetJS.analytics.sendEvent('toolbar.profile.toggled');
295
+            APP.UI.emitEvent(UIEvents.TOGGLE_PROFILE);
296
+        },
297
+        sideContainerId: 'profile_container',
298
+        tooltipKey: 'profile.setDisplayNameLabel'
299
+    },
300
+
301
+    /**
302
+     * The object representing "Raise hand" button.
303
+     */
304
+    raisehand: {
305
+        classNames: [ 'button', 'icon-raised-hand' ],
306
+        enabled: true,
307
+        id: 'toolbar_button_raisehand',
308
+        onClick() {
309
+            JitsiMeetJS.analytics.sendEvent('toolbar.raiseHand.clicked');
310
+            APP.conference.maybeToggleRaisedHand();
311
+        },
312
+        shortcut: 'R',
313
+        shortcutAttr: 'raiseHandPopover',
314
+        shortcutDescription: 'keyboardShortcuts.raiseHand',
315
+        shortcutFunc() {
316
+            JitsiMeetJS.analytics.sendEvent('shortcut.raisehand.clicked');
317
+            APP.conference.maybeToggleRaisedHand();
318
+        },
319
+        tooltipKey: 'toolbar.raiseHand'
320
+    },
321
+
322
+    /**
323
+     * The object representing recording button. Requires additional
324
+     * initialization in Recording module.
325
+     */
326
+    recording: {
327
+        classNames: [ 'button' ],
328
+        enabled: true,
329
+
330
+        // will be displayed once the recording functionality is detected
331
+        hidden: true,
332
+        id: 'toolbar_button_record',
333
+        tooltipKey: 'liveStreaming.buttonTooltip'
334
+    },
335
+
336
+    /**
337
+     * The objecr representing settings button.
338
+     */
339
+    settings: {
340
+        classNames: [ 'button', 'icon-settings' ],
341
+        enabled: true,
342
+        id: 'toolbar_button_settings',
343
+        onClick() {
344
+            JitsiMeetJS.analytics.sendEvent('toolbar.settings.toggled');
345
+            APP.UI.emitEvent(UIEvents.TOGGLE_SETTINGS);
346
+        },
347
+        sideContainerId: 'settings_container',
348
+        tooltipKey: 'toolbar.Settings'
349
+    },
350
+
351
+    /**
352
+     * The object representing sharing Youtube video button.
353
+     */
354
+    sharedvideo: {
355
+        classNames: [ 'button', 'icon-shared-video' ],
356
+        enabled: true,
357
+        id: 'toolbar_button_sharedvideo',
358
+        onClick() {
359
+            JitsiMeetJS.analytics.sendEvent('toolbar.sharedvideo.clicked');
360
+            APP.UI.emitEvent(UIEvents.SHARED_VIDEO_CLICKED);
361
+        },
362
+        popups: [
363
+            {
364
+                className: 'loginmenu extendedToolbarPopup',
365
+                dataAttr: 'toolbar.sharedVideoMutedPopup',
366
+                dataAttrPosition: 'w',
367
+                id: 'sharedVideoMutedPopup'
368
+            }
369
+        ],
370
+        tooltipKey: 'toolbar.sharedvideo'
371
+    },
372
+
373
+    /**
374
+     * The object representing SIP call.
375
+     */
376
+    sip: {
377
+        classNames: [ 'button', 'icon-telephone' ],
378
+        enabled: true,
379
+
380
+        // Will be displayed once the SIP calls functionality is detected.
381
+        hidden: true,
382
+        id: 'toolbar_button_sip',
383
+        onClick() {
384
+            JitsiMeetJS.analytics.sendEvent('toolbar.sip.clicked');
385
+            _showSIPNumberInput();
386
+        },
387
+        tooltipKey: 'toolbar.sip'
388
+    }
389
+};

+ 254
- 0
react/features/toolbar/functions.js View File

@@ -0,0 +1,254 @@
1
+/* @flow */
2
+
3
+import SideContainerToggler
4
+    from '../../../modules/UI/side_pannels/SideContainerToggler';
5
+
6
+import { appNavigate } from '../app';
7
+import { toggleAudioMuted, toggleVideoMuted } from '../base/media';
8
+
9
+import defaultToolbarButtons from './defaultToolbarButtons';
10
+
11
+declare var $: Function;
12
+declare var AJS: Object;
13
+declare var interfaceConfig: Object;
14
+
15
+import type { Dispatch } from 'redux-thunk';
16
+
17
+type MapOfAttributes = { [key: string]: * };
18
+
19
+/**
20
+ * Maps actions to React component props.
21
+ *
22
+ * @param {Function} dispatch - Redux action dispatcher.
23
+ * @returns {{
24
+ *     _onHangup: Function,
25
+ *     _onToggleAudio: Function,
26
+ *     _onToggleVideo: Function
27
+ * }}
28
+ * @private
29
+ */
30
+export function abstractMapDispatchToProps(dispatch: Dispatch<*>): Object {
31
+    return {
32
+        /**
33
+         * Dispatches action to leave the current conference.
34
+         *
35
+         * @private
36
+         * @returns {void}
37
+         * @type {Function}
38
+         */
39
+        _onHangup() {
40
+            // XXX We don't know here which value is effectively/internally
41
+            // used when there's no valid room name to join. It isn't our
42
+            // business to know that anyway. The undefined value is our
43
+            // expression of (1) the lack of knowledge & (2) the desire to no
44
+            // longer have a valid room name to join.
45
+            return dispatch(appNavigate(undefined));
46
+        },
47
+
48
+        /**
49
+         * Dispatches an action to toggle the mute state of the
50
+         * audio/microphone.
51
+         *
52
+         * @private
53
+         * @returns {Object} - Dispatched action.
54
+         * @type {Function}
55
+         */
56
+        _onToggleAudio() {
57
+            return dispatch(toggleAudioMuted());
58
+        },
59
+
60
+        /**
61
+         * Dispatches an action to toggle the mute state of the video/camera.
62
+         *
63
+         * @private
64
+         * @returns {Object} - Dispatched action.
65
+         * @type {Function}
66
+         */
67
+        _onToggleVideo() {
68
+            return dispatch(toggleVideoMuted());
69
+        }
70
+    };
71
+}
72
+
73
+/**
74
+ * Maps parts of media state to component props.
75
+ *
76
+ * @param {Object} state - Redux state.
77
+ * @protected
78
+ * @returns {{
79
+ *     _audioMuted: boolean,
80
+ *     _videoMuted: boolean,
81
+ *     _visible: boolean
82
+ * }}
83
+ */
84
+export function abstractMapStateToProps(state: Object): Object {
85
+    const media = state['features/base/media'];
86
+    const { visible } = state['features/toolbar'];
87
+
88
+    return {
89
+        /**
90
+         * Flag showing that audio is muted.
91
+         *
92
+         * @protected
93
+         * @type {boolean}
94
+         */
95
+        _audioMuted: media.audio.muted,
96
+
97
+        /**
98
+         * Flag showing whether video is muted.
99
+         *
100
+         * @protected
101
+         * @type {boolean}
102
+         */
103
+        _videoMuted: media.video.muted,
104
+
105
+        /**
106
+         * Flag showing whether toolbar is visible.
107
+         *
108
+         * @protected
109
+         * @type {boolean}
110
+         */
111
+        _visible: visible
112
+    };
113
+}
114
+
115
+/**
116
+ * Takes toolbar button props and maps them to HTML attributes to set.
117
+ *
118
+ * @param {Object} props - Props set to the React component.
119
+ * @returns {Object}
120
+ */
121
+export function getButtonAttributesByProps(props: Object): MapOfAttributes {
122
+    const classNames = [ ...props.classNames ];
123
+
124
+    props.toggled && classNames.push('toggled');
125
+    props.unclickable && classNames.push('unclickable');
126
+
127
+    const result: MapOfAttributes = {
128
+        className: classNames.join(' '),
129
+        'data-container': 'body',
130
+        'data-placement': 'bottom',
131
+        id: props.id
132
+    };
133
+
134
+    if (!props.enabled) {
135
+        result.disabled = 'disabled';
136
+    }
137
+
138
+    if (props.hidden) {
139
+        result.style = { display: 'none' };
140
+    }
141
+
142
+    return result;
143
+}
144
+
145
+/**
146
+ * Returns object containing default buttons for the primary and secondary
147
+ * toolbars.
148
+ *
149
+ * @returns {Object}
150
+ */
151
+export function getDefaultToolbarButtons(): Object {
152
+    let toolbarButtons = {
153
+        primaryToolbarButtons: new Map(),
154
+        secondaryToolbarButtons: new Map()
155
+    };
156
+
157
+    if (typeof interfaceConfig !== 'undefined'
158
+            && interfaceConfig.TOOLBAR_BUTTONS) {
159
+        toolbarButtons = interfaceConfig.TOOLBAR_BUTTONS.reduce(
160
+            (acc, buttonName) => {
161
+                const button = defaultToolbarButtons[buttonName];
162
+
163
+                if (button) {
164
+                    const place = _getToolbarButtonPlace(buttonName);
165
+
166
+                    button.buttonName = buttonName;
167
+                    acc[place].set(buttonName, button);
168
+                }
169
+
170
+                return acc;
171
+            }, toolbarButtons);
172
+    }
173
+
174
+    return toolbarButtons;
175
+}
176
+
177
+/**
178
+ * Get place for toolbar button.
179
+ * Now it can be in main toolbar or in extended (left) toolbar.
180
+ *
181
+ * @param {string} btn - Button name.
182
+ * @private
183
+ * @returns {string}
184
+ */
185
+function _getToolbarButtonPlace(btn) {
186
+    return (
187
+        interfaceConfig.MAIN_TOOLBAR_BUTTONS.includes(btn)
188
+            ? 'primaryToolbarButtons'
189
+            : 'secondaryToolbarButtons');
190
+}
191
+
192
+/**
193
+ * Returns toolbar class names to add while rendering.
194
+ *
195
+ * @param {Object} props - Props object pass to React component.
196
+ * @returns {Object}
197
+ * @private
198
+ */
199
+export function getToolbarClassNames(props: Object) {
200
+    const primaryToolbarClassNames = [ 'toolbar_primary' ];
201
+    const secondaryToolbarClassNames = [ 'toolbar_secondary' ];
202
+
203
+    if (props._visible) {
204
+        const slideInAnimation
205
+            = SideContainerToggler.isVisible ? 'slideInExtX' : 'slideInX';
206
+
207
+        primaryToolbarClassNames.push('fadeIn');
208
+        secondaryToolbarClassNames.push(slideInAnimation);
209
+    } else {
210
+        const slideOutAnimation
211
+            = SideContainerToggler.isVisible ? 'slideOutExtX' : 'slideOutX';
212
+
213
+        primaryToolbarClassNames.push('fadeOut');
214
+        secondaryToolbarClassNames.push(slideOutAnimation);
215
+    }
216
+
217
+    return {
218
+        primaryToolbarClassName: primaryToolbarClassNames.join(' '),
219
+        secondaryToolbarClassName: secondaryToolbarClassNames.join(' ')
220
+    };
221
+}
222
+
223
+/**
224
+ * Show custom popup/tooltip for a specified button.
225
+ *
226
+ * @param {string} popupSelectorID - The selector id of the popup to show.
227
+ * @param {boolean} show - True or false/show or hide the popup.
228
+ * @param {number} timeout - The time to show the popup.
229
+ * @returns {void}
230
+ */
231
+export function showCustomToolbarPopup(
232
+        popupSelectorID: string,
233
+        show: boolean,
234
+        timeout: number) {
235
+    AJS.$(popupSelectorID).tooltip({
236
+        gravity: $(popupSelectorID).attr('data-popup'),
237
+        html: true,
238
+        title: 'title',
239
+        trigger: 'manual'
240
+    });
241
+
242
+    if (show) {
243
+        AJS.$(popupSelectorID).tooltip('show');
244
+
245
+        setTimeout(
246
+            () => {
247
+                // hide the tooltip
248
+                AJS.$(popupSelectorID).tooltip('hide');
249
+            },
250
+            timeout);
251
+    } else {
252
+        AJS.$(popupSelectorID).tooltip('hide');
253
+    }
254
+}

+ 5
- 0
react/features/toolbar/index.js View File

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

+ 39
- 0
react/features/toolbar/middleware.js View File

@@ -0,0 +1,39 @@
1
+/* @flow */
2
+
3
+import { MiddlewareRegistry } from '../base/redux';
4
+
5
+import {
6
+    CLEAR_TOOLBAR_TIMEOUT,
7
+    SET_TOOLBAR_TIMEOUT
8
+} from './actionTypes';
9
+
10
+/**
11
+ * Middleware that captures toolbar actions and handle changes in toolbar
12
+ * timeout.
13
+ *
14
+ * @param {Store} store - Redux store.
15
+ * @returns {Function}
16
+ */
17
+MiddlewareRegistry.register(store => next => action => {
18
+    switch (action.type) {
19
+    case CLEAR_TOOLBAR_TIMEOUT: {
20
+        const { timeoutId } = store.getState()['features/toolbar'];
21
+
22
+        clearTimeout(timeoutId);
23
+        break;
24
+    }
25
+
26
+    case SET_TOOLBAR_TIMEOUT: {
27
+        const { timeoutId } = store.getState()['features/toolbar'];
28
+        const { handler, toolbarTimeout } = action;
29
+
30
+        clearTimeout(timeoutId);
31
+        const newTimeoutId = setTimeout(handler, toolbarTimeout);
32
+
33
+        action.timeoutId = newTimeoutId;
34
+        break;
35
+    }
36
+    }
37
+
38
+    return next(action);
39
+});

+ 191
- 0
react/features/toolbar/reducer.js View File

@@ -0,0 +1,191 @@
1
+/* @flow */
2
+
3
+import { ReducerRegistry } from '../base/redux';
4
+
5
+import {
6
+    CLEAR_TOOLBAR_TIMEOUT,
7
+    SET_ALWAYS_VISIBLE_TOOLBAR,
8
+    SET_SUBJECT,
9
+    SET_SUBJECT_SLIDE_IN,
10
+    SET_TOOLBAR_BUTTON,
11
+    SET_TOOLBAR_HOVERED,
12
+    SET_TOOLBAR_TIMEOUT,
13
+    SET_TOOLBAR_TIMEOUT_NUMBER,
14
+    SET_TOOLBAR_VISIBLE
15
+} from './actionTypes';
16
+import { getDefaultToolbarButtons } from './functions';
17
+
18
+declare var interfaceConfig: Object;
19
+
20
+/**
21
+ * Returns initial state for toolbar's part of Redux store.
22
+ *
23
+ * @returns {{
24
+ *     primaryToolbarButtons: Map,
25
+ *     secondaryToolbarButtons: Map
26
+ * }}
27
+ * @private
28
+ */
29
+function _getInitialState() {
30
+    // Default toolbar timeout for mobile app.
31
+    let toolbarTimeout = 5000;
32
+
33
+    if (typeof interfaceConfig !== 'undefined'
34
+            && interfaceConfig.INITIAL_TOOLBAR_TIMEOUT) {
35
+        toolbarTimeout = interfaceConfig.INITIAL_TOOLBAR_TIMEOUT;
36
+    }
37
+
38
+    return {
39
+        /**
40
+         * Contains default toolbar buttons for primary and secondary toolbars.
41
+         *
42
+         * @type {Map}
43
+         */
44
+        ...getDefaultToolbarButtons(),
45
+
46
+        /**
47
+         * Shows whether toolbar is always visible.
48
+         *
49
+         * @type {boolean}
50
+         */
51
+        alwaysVisible: false,
52
+
53
+        /**
54
+         * Shows whether toolbar is hovered.
55
+         *
56
+         * @type {boolean}
57
+         */
58
+        hovered: false,
59
+
60
+        /**
61
+         * Contains text of conference subject.
62
+         *
63
+         * @type {string}
64
+         */
65
+        subject: '',
66
+
67
+        /**
68
+         * Shows whether subject is sliding in.
69
+         *
70
+         * @type {boolean}
71
+         */
72
+        subjectSlideIn: false,
73
+
74
+        /**
75
+         * Contains toolbar timeout id.
76
+         *
77
+         * @type {number|null}
78
+         */
79
+        timeoutId: null,
80
+
81
+        /**
82
+         * Contains delay of toolbar timeout.
83
+         *
84
+         * @type {number}
85
+         */
86
+        toolbarTimeout,
87
+
88
+        /**
89
+         * Shows whether toolbar is visible.
90
+         *
91
+         * @type {boolean}
92
+         */
93
+        visible: false
94
+    };
95
+}
96
+
97
+ReducerRegistry.register(
98
+    'features/toolbar',
99
+    (state: Object = _getInitialState(), action: Object) => {
100
+        switch (action.type) {
101
+        case CLEAR_TOOLBAR_TIMEOUT:
102
+            return {
103
+                ...state,
104
+                timeoutId: undefined
105
+            };
106
+
107
+        case SET_ALWAYS_VISIBLE_TOOLBAR:
108
+            return {
109
+                ...state,
110
+                alwaysVisible: action.alwaysVisible
111
+            };
112
+
113
+        case SET_SUBJECT:
114
+            return {
115
+                ...state,
116
+                subject: action.subject
117
+            };
118
+
119
+        case SET_SUBJECT_SLIDE_IN:
120
+            return {
121
+                ...state,
122
+                subjectSlideIn: action.subjectSlideIn
123
+            };
124
+
125
+        case SET_TOOLBAR_BUTTON:
126
+            return _setButton(state, action);
127
+
128
+        case SET_TOOLBAR_HOVERED:
129
+            return {
130
+                ...state,
131
+                hovered: action.hovered
132
+            };
133
+
134
+        case SET_TOOLBAR_TIMEOUT:
135
+            return {
136
+                ...state,
137
+                toolbarTimeout: action.toolbarTimeout,
138
+                timeoutId: action.timeoutId
139
+            };
140
+
141
+        case SET_TOOLBAR_TIMEOUT_NUMBER:
142
+            return {
143
+                ...state,
144
+                toolbarTimeout: action.toolbarTimeout
145
+            };
146
+
147
+        case SET_TOOLBAR_VISIBLE:
148
+            return {
149
+                ...state,
150
+                visible: action.visible
151
+            };
152
+        }
153
+
154
+        return state;
155
+    });
156
+
157
+/**
158
+ * Sets new value of the button.
159
+ *
160
+ * @param {Object} state - Redux state.
161
+ * @param {Object} action - Dispatched action.
162
+ * @param {Object} action.button - Object describing toolbar button.
163
+ * @param {Object} action.buttonName - The name of the button.
164
+ * @returns {Object}
165
+ * @private
166
+ */
167
+function _setButton(state, { buttonName, button }): Object {
168
+    const {
169
+        primaryToolbarButtons,
170
+        secondaryToolbarButtons
171
+    } = state;
172
+    let selectedButton = primaryToolbarButtons.get(buttonName);
173
+    let place = 'primaryToolbarButtons';
174
+
175
+    if (!selectedButton) {
176
+        selectedButton = secondaryToolbarButtons.get(buttonName);
177
+        place = 'secondaryToolbarButtons';
178
+    }
179
+
180
+    selectedButton = {
181
+        ...selectedButton,
182
+        ...button
183
+    };
184
+
185
+    const updatedToolbar = state[place].set(buttonName, selectedButton);
186
+
187
+    return {
188
+        ...state,
189
+        [place]: new Map(updatedToolbar)
190
+    };
191
+}

Loading…
Cancel
Save